Please, add JsDoc to use type hints in NJS code.
This will help a lot in the development process.
Here is an example of JsDoc I am using (it was written for njs 0.2.7, so could be a bit outdated).
The source code is in the attachments.
/**
* @class httpRequest
* @property {Object.<string, string>} args - Request arguments object, read-only
* @property {Object.<string, string>} headersIn - Incoming headers object, read-only
* @property {Object.<string, string>} headersOut - Outgoing headers object, writable
* @property {string} httpVersion - HTTP version, read-only
* @property {string} method - HTTP method, read-only
* @property {httpRequest} parent - References the parent request object
* @property {string} remoteAddress - Client address, read-only
* @property {string} requestBody - Returns the client request body if it has not been written to a temporary file. To ensure that the client request body is in memory, its size should be limited by client_max_body_size, and a sufficient buffer size should be set using client_body_buffer_size.
* @property {string} responseBody - Holds the {@link httpRequest.subrequest} response body, read-only. The size of {@link httpRequest.responseBody} is limited by the subrequest_output_buffer_size directive.
* @property {number} status - Status, writable
* @property {Object.<string, string>} variables - Nginx variables object, read-only
* @property {string} uri - Current URI, read-only
*/
/**
* Writes a string to the error log on the error level of logging
* @method
* @name httpRequest#error
* @param {string} string
*/
/**
* Finishes sending a response to the client
* @method
* @name httpRequest#finish
*/
/**
* Writes a string to the error log on the info level of logging
* @method
* @name httpRequest#log
* @param {string} string
*/
/**
* Performs an internal redirect to the specified uri. If the uri starts with the “@” prefix, it is considered a named location.
* @method
* @name httpRequest#internalRedirect
* @param {string} uri
*/
/**
* Finishes sending a response to the client
* @method
* @name httpRequest#return
* @param {number} status
* @param {string} [string]
*/
/**
* Sends a part of the response body to the client
* @method
* @name httpRequest#send
* @param {string} string
*/
/**
* Sends the HTTP headers to the client
* @method
* @name httpRequest#sendHeader
*/
/**
* Writes a string to the error log on the warning level of logging
* @method
* @name httpRequest#warn
* @param {string} string
*/
/**
* Creates a subrequest with the given uri and options, and installs an optional completion callback.
* If options is a string, then it holds the subrequest arguments string. Otherwise, options is expected to be an object with the following keys:
* - args
* - body
* - method
*
* The completion callback receives a subrequest response object with methods and properties identical to the parent request object.
*
* @method
* @name httpRequest#subrequest
* @param {string} uri
* @param {string|{[args]: string, [body]: string, [method]: string}} [options]
* @param {httpRequest~subrequestCallback} [callback]
*/
/**
* This callback is displayed as a global member.
* @callback httpRequest~subrequestCallback
* @param {httpRequest} response
*/
/**
* This function is used to handle incoming http requests.
* @callback httpRequest~requestHandler
* @param {httpRequest} request
*/

@drsm What do you think?
@USSX-Hares Help me understand please, how this annotation can be used? For example, in njs project we do not have JavaScript files, only C-lang ones.
If someone wants to write njs code, can he simply import single JsDoc declaration file?
Can C-lang file be parsed for JsDoc comments?
What is a best way to store JsDoc files for projects with native code (to have a single large file for all the native objects vs many JsDoc files for each C-lang file)?
How JsDoc is compared to Typescript declarations (See discussion here:https://github.com/nginx/njs/issues/102)?
@xeioex
If someone wants to write njs code, can he simply import single JsDoc declaration file?
Yes. But this file should be stored somewhere.
Can C-lang file be parsed for JsDoc comments?
Probably. Example here and here.
What is a best way to store JsDoc files for projects with native code (to have a single large file for all the native objects vs many JsDoc files for each C-lang file)?
IDK
How JsDoc is compared to Typescript declarations (See discussion here:#102)?
JsDoc is written in comments, so no additional preprocessors are required to make the code valid
@USSX-Hares @xeioex
How JsDoc is compared to Typescript declarations
The main difference - in .d.ts files types and interfaces are defined using TypeScript.
There is no requirement to document them, but it can be done using JsDoc comments (with type information stripped).
For me .d.ts is more readable and simpler to write.
example
Would echo the sentiment above. .d.ts has a better IDE code completion and typechecking story.
Would love to see a .d.ts of ngx_js api
any update ? would we plan to supports d.ts ?
@HairyRabbit
yes, we plan to support d.ts in the mid-term future.
Any update? :)
@drsm
Take a look at the following drafts
interface StringConstructor {
bytesFrom(bytes: Array | string, encoding: "hex" | "base64" | "base64url"): NginxByteString;
}
interface NginxByteString implements String {
fromBytes(start: number, end?: number): string;
fromUTF8(start: number, end?: number): string | null;
toString(encoding: "hex" | "base64" | "base64url"): string;
}
interface String {
toBytes(start: number, end?: number): NginxByteString | null;
toUTF8(start: number, end?: number): NginxByteString;
}
/// <reference path="njs.core.d.ts" />
interface NginxSubrequestOptions {
method?: "GET" | "POST" | "OPTIONS" | "HEAD" | "PROPFIND" | "PUT"
| "MKCOL" | "DELETE" | "COPY" | "MOVE" | "PROPPATCH"
| "LOCK" | "PATCH" | "TRACE",
args?: string | NginxByteString,
body?: string | NginxByteString,
detached?: boolean
}
interface NginxHeadersIn {
readonly [prop: string | NginxByteString]: NginxByteString;
}
interface NginxHTTPArgs {
readonly [prop: string | NginxByteString]: NginxByteString;
}
interface NginxHTTPRequest {
readonly args: NginxHTTPArgs;
error(message: string | NginxByteString): void;
finish(): void;
readonly headersIn: NginxHeadersIn;
headersOut: object;
readonly httpVersion: NginxByteString;
log(message: string | NginxByteString): void;
internalRedirect(location: string | NginxByteString): void;
readonly method: NginxByteString;
readonly parent: NginxHTTPRequest;
readonly remoteAddress: NginxByteString;
readonly requestBody: undefined | NginxByteString;
readonly responseBody: undefined | NginxByteString;
return(status: number, body?: string | NginxByteString): void;
send(part: string | NginxByteString): void;
sendHeader(): void;
status: number;
variables: object;
warn(message: string | NginxByteString): void;
readonly uri: NginxByteString;
subrequest(uri: string | NginxByteString, options?: string | NginxByteString | NginxSubrequestOptions,
callback?:(reply:NginxHTTPRequest) => void): void;
subrequest(uri: string | NginxByteString, callback?:(NginxHTTPRequest) => void): void;
}
I want to model the following things:
1) Nginx has internal bytes strings which a bit different from ordinary js string.
But all methods accepts both types. What is the best was to express this in TS?
2) NginxHeadersIn has variable but immutable properties. What is the best was to express this in TS?
2) readonly requestBody: undefined | NginxByteString if property may be unavailable is it the right way to express this?
@xeioex
Thanks, code competition works fine in VSCode after some fixes:
/// <reference path="njs.d.ts" />
/**
* @param {NginxHTTPRequest} r
* */
function handler(r) {
r.headersIn['via'].fromBytes();
r.subrequest('/xxx', {}).then(() => {});
}
- Nginx has internal bytes strings which a bit different from ordinary js string.
But all methods accepts both types. What is the best was to express this in TS?
If byte strings are compatible with ordinary strings:
interface StringConstructor {
bytesFrom(bytes: Array<number> | string, encoding: "hex" | "base64" | "base64url"): NginxByteString;
}
interface NginxByteString extends String {
fromBytes(start?: number, end?: number): string;
fromUTF8(start?: number, end?: number): string | null;
toString(encoding?: "hex" | "base64" | "base64url"): string;
}
interface String {
toBytes(start?: number, end?: number): NginxByteString | null;
toUTF8(start?: number, end?: number): NginxByteString;
}
And string | NginxByteString became string everywhere.
If they are not compatible, then the full interface definition is required:
interface NginxByteString {
fromBytes(start?: number, end?: number): string;
fromUTF8(start?: number, end?: number): string | null;
toString(encoding?: "hex" | "base64" | "base64url"): string;
// all String methods and properties
}
And we can introduce some type instead of string | NginxByteString (node fs.PathLike):
type StringLike = string | NginxByteString;
interface NginxSubrequestOptions {
method?: "GET" | "POST" | "OPTIONS" | "HEAD" | "PROPFIND" | "PUT"
| "MKCOL" | "DELETE" | "COPY" | "MOVE" | "PROPPATCH"
| "LOCK" | "PATCH" | "TRACE",
args?: StringLike,
body?: StringLike,
detached?: boolean
}
- NginxHeadersIn has variable but immutable properties. What is the best was to express this in TS?
interface NginxHeadersIn {
// some common header names to autocomplete
readonly 'accept'?: NginxByteString;
readonly 'accept-patch'?: NginxByteString;
readonly 'via'?: NginxByteString;
// ....
// the header below may be only string or number in ts:
readonly [header: string]: NginxByteString | NginxByteString[] | undefined;
}
readonly requestBody: undefined | NginxByteStringif property may be unavailable is it the right way to express this?
Yes.
subrequest():
// Promise version
subrequest(uri: string | NginxByteString, options?: string | NginxByteString | NginxSubrequestOptions): Promise<NginxHTTPRequest>;
// Long callback version
subrequest(uri: string | NginxByteString, options: string | NginxByteString | NginxSubrequestOptions,
callback:(reply:NginxHTTPRequest) => void): void;
// Short callback version
subrequest(uri: string | NginxByteString, callback:(reply:NginxHTTPRequest) => void): void;
// Detached version
subrequest(uri: string | NginxByteString, options: NginxSubrequestOptions): void;
BTW:
I think, this variant is the less wrong than the others:
interface NginxByteString extends String {
fromBytes(start?: number, end?: number): string;
fromUTF8(start?: number, end?: number): string | null;
toString(): NginxByteString;
toString(encoding?: "hex" | "base64" | "base64url"): string;
}
As the
interface NginxByteString {
fromBytes(start?: number, end?: number): string;
fromUTF8(start?: number, end?: number): string | null;
toString(): NginxByteString;
toString(encoding: "hex" | "base64" | "base64url"): string;
//...
}
will not track types properly anyway.
>> var bs = String.bytesFrom('abcd', 'hex')
undefined
>> (bs + 'abcd').toString('hex')
'abcd61626364'
we can't create operator +(l: NginxByteString, r: string): NginxByteString
@drsm thanks for the feedback, will apply.
@drsm
take a look https://gist.github.com/xeioex/19368ba2a3104512a6bd4f09356d0790
@xeioex
https://gist.github.com/drsm/d7f9e99bb9a8dd0812c14ab390e4bf1f
It compiles:
$ # npm install -g typescript
$ tsc test.ts
$ rm test.js
$ cat test.ts
/// <reference path="src/ts/njs_core.d.ts" />
/// <reference path="nginx/ts/ngx_http_js_module.d.ts" />
function asdf(r: NginxHTTPRequest) {
var bs: NginxByteString;
var s: string;
s = 'asdf';
bs = String.bytesFrom('000000', 'hex');
bs = String.bytesFrom([0,0,0]);
bs = s.toBytes();
bs = s.toUTF8();
s = bs + '';
//bs = 'asdf';
//bs += '';
bs = r.args.x;
bs = r.args[1];
//s = r.args.x;
s = r.args.x.fromUTF8();
s = r.args.x + '';
//r.headersIn['accept'] == 'dddd';
r.headersIn['accept'].fromBytes() == 'dddd';
r.headersOut['content-type'] = 'text/plain';
r.headersOut['connection'] = undefined;
r.headersOut['connection'] = null;
r.variables.a == 'a';
r.variables.b = 'b';
//njs = {};
njs.dump('asdf');
njs.version != '0.1';
bs.fromBytes(null, null);
r.subrequest('asdf', {method: 'POST', args: 'asdf'});
//r.subrequest('asdf', bs);
}
After revising my poor knowledge of typescript, I found that | null | undefined is implied for any type.
The article is available here: http://nginx.org/en/docs/njs/typescript.html
@drsm
cat test.ts:
/// <reference path="build/ts/ngx_http_js_module.d.ts" />
function healthcheck(r: NginxHTTPRequest) {
r.return(200, JSON.parse(r.requestBody));
}
tsc test.ts
test.ts:3:30 - error TS2345: Argument of type 'NjsByteString' is not assignable to parameter of type 'string'.
3 r.return(200, JSON.parse(r.requestBody));
~~~~~~~~~~~~~
Found 1 error.
It seems, NjsByteString cannot be used interchangeably with functions expecting ordinary string.
error TS2367: This condition will always return 'false' since the types 'NjsByteString' and 'string' have no overlap.
if (r.uri === '/') {
@xeioex
It seems, NjsByteString cannot be used interchangeably with functions expecting ordinary string.
The problem is NjsByteString -> String -> Object, there is no relation with a string primitive type.
I was thinking about this mess, but almost forgot, thanks for reminding ).
It looks like opaque types may help us here:
declare const NjsByteStringTypeTag: unique symbol;
type NjsByteString = string & {
[NjsByteStringTypeTag]: true;
/**
* Returns a new Unicode string from a byte string where each byte is replaced
* with a corresponding Unicode code point.
*/
fromBytes(start?: number, end?: number): string;
/**
* Converts a byte string containing a valid UTF8 string into a Unicode string,
* otherwise null is returned.
*/
fromUTF8(start?: number, end?: number): string;
/**
* Encodes a byte string to hex, base64, or base64url.
*/
toString(encoding?: "hex" | "base64" | "base64url"): string;
};
interface StringConstructor {
/**
* Creates a byte string from an encoded string.
*/
bytesFrom(bytes: string, encoding: "hex" | "base64" | "base64url"): NjsByteString;
/**
* Creates a byte string from an array that contains octets.
*/
bytesFrom(bytes: Array<number>): NjsByteString;
}
interface String {
/**
* Serializes a Unicode string with code points up to 255
* into a byte string, otherwise, null is returned.
*/
toBytes(start?: number, end?: number): NjsByteString;
/**
* Serializes a Unicode string to a byte string using UTF8 encoding.
*/
toUTF8(start?: number, end?: number): NjsByteString;
}
type NjsStringLike = string | NjsByteString;
var byteString: NjsByteString = String.bytesFrom([0, 0, 0]);
if (byteString === 'test') {
console.log('fail');
}
console.log(JSON.parse(byteString));
var ordinaryString: string = 'string';
ordinaryString = byteString;
// will fail
// byteString = ordinaryString;
// Type 'string' is not assignable to type '{ [NjsByteStringTypeTag]: true; }'.
byteString = ordinaryString.toBytes();
@xeioex
Take a look:
# HG changeset patch
# User Artem S. Povalyukhin <[email protected]>
# Date 1593275201 -10800
# Sat Jun 27 19:26:41 2020 +0300
# Node ID 2007cfca3d73cf37edd2e73056b7c4d752fbabfd
# Parent fac632b520fa437c29f831f7bf76fa1f81657a9b
Improved typescript API description.
diff -r fac632b520fa -r 2007cfca3d73 nginx/ts/ngx_http_js_module.d.ts
--- a/nginx/ts/ngx_http_js_module.d.ts Thu Jun 25 13:10:04 2020 +0300
+++ b/nginx/ts/ngx_http_js_module.d.ts Sat Jun 27 19:26:41 2020 +0300
@@ -350,7 +350,7 @@ interface NginxHTTPRequest {
subrequest(uri: NjsStringLike, options: NginxSubrequestOptions | string,
callback:(reply:NginxHTTPRequest) => void): void;
subrequest(uri: NjsStringLike, callback:(reply:NginxHTTPRequest) => void): void;
- subrequest(uri: NjsStringLike, options: NginxSubrequestOptions): void;
+ subrequest(uri: NjsStringLike, options: NginxSubrequestOptions & { detached: true }): void;
/**
* Current URI in request, normalized.
*/
diff -r fac632b520fa -r 2007cfca3d73 src/ts/njs_core.d.ts
--- a/src/ts/njs_core.d.ts Thu Jun 25 13:10:04 2020 +0300
+++ b/src/ts/njs_core.d.ts Sat Jun 27 19:26:41 2020 +0300
@@ -14,14 +14,14 @@ interface String {
* Serializes a Unicode string with code points up to 255
* into a byte string, otherwise, null is returned.
*/
- toBytes(start?: number, end?: number): NjsByteString;
+ toBytes(start?: number, end?: number): NjsByteString | null;
/**
* Serializes a Unicode string to a byte string using UTF8 encoding.
*/
toUTF8(start?: number, end?: number): NjsByteString;
}
-interface NjsByteString extends String {
+type NjsByteString = string & {
/**
* Returns a new Unicode string from a byte string where each byte is replaced
* with a corresponding Unicode code point.
@@ -31,12 +31,12 @@ interface NjsByteString extends String {
* Converts a byte string containing a valid UTF8 string into a Unicode string,
* otherwise null is returned.
*/
- fromUTF8(start?: number, end?: number): string;
+ fromUTF8(start?: number, end?: number): string | null;
/**
* Encodes a byte string to hex, base64, or base64url.
*/
- toString(encoding?: "hex" | "base64" | "base64url"): string;
-}
+ toString(encoding: "hex" | "base64" | "base64url"): string;
+};
type NjsStringLike = string | NjsByteString;
I found that | null | undefined is implied for any type.
I was wrong there, it may be disabled by --strictNullChecks compiler flag.
@drsm
error TS2345: Argument of type 'string | NjsByteString | NjsStringLike[]' is not assignable to parameter of type 'NjsStringLike'.
Type 'NjsStringLike[]' is not assignable to type 'NjsStringLike'.
Type 'NjsStringLike[]' is missing the following properties from type 'NjsByteString': fromBytes, fromUTF8, charAt, charCodeAt, and 14 more.
r.return(reply.status, reply.headersOut["Location"]);
The issue is related to the fact the unknown headers are declared as follows:
[prop: string]: NjsStringLike | NjsStringLike[]; (link)
The problem is that we use only lowercase variants of headers ('location' vs 'Location'), is there a way to avoid declaring duplicate variants for each header? Basically we want to signal to TS that keys are case-insensitive.
The second option is to declare only canonical names: "Location", 'Content-Range' etc.
@drsm I am considering the following fix.
# HG changeset patch
# User Dmitry Volyntsev <[email protected]>
# Date 1594047036 0
# Mon Jul 06 14:50:36 2020 +0000
# Node ID e5e6185465757b2e5c001e65e9adc73d7f700cd2
# Parent 2c3c34706ce282d43f511f1a787bf94aeb6c6ced
HTTP: fixed TypeScript description for headers.
The common headers were declared using only lowercase characters.
Unknown outgoing headers are declared as having the following type:
NjsStringLike | NjsStringLike[]. This causes an issue when unknown
header is used in the context requiring only NjsStringLike.
The fix is to declare common headers using canonical casing.
The issue was introduced in 8f3ef384f69e.
diff --git a/nginx/ts/ngx_http_js_module.d.ts b/nginx/ts/ngx_http_js_module.d.ts
--- a/nginx/ts/ngx_http_js_module.d.ts
+++ b/nginx/ts/ngx_http_js_module.d.ts
@@ -6,75 +6,75 @@ interface NginxHTTPArgs {
interface NginxHeadersIn {
// common request headers
- readonly 'accept'?: NjsByteString;
- readonly 'accept-charset'?: NjsByteString;
- readonly 'accept-encoding'?: NjsByteString;
- readonly 'accept-language'?: NjsByteString;
- readonly 'authorization'?: NjsByteString;
- readonly 'cache-control'?: NjsByteString;
- readonly 'connection'?: NjsByteString;
- readonly 'content-length'?: NjsByteString;
- readonly 'content-type'?: NjsByteString;
- readonly 'cookie'?: NjsByteString;
- readonly 'date'?: NjsByteString;
- readonly 'expect'?: NjsByteString;
- readonly 'forwarded'?: NjsByteString;
- readonly 'from'?: NjsByteString;
- readonly 'host'?: NjsByteString;
- readonly 'if-match'?: NjsByteString;
- readonly 'if-modified-since'?: NjsByteString;
- readonly 'if-none-match'?: NjsByteString;
- readonly 'if-range'?: NjsByteString;
- readonly 'if-unmodified-since'?: NjsByteString;
- readonly 'max-forwards'?: NjsByteString;
- readonly 'origin'?: NjsByteString;
- readonly 'pragma'?: NjsByteString;
- readonly 'proxy-authorization'?: NjsByteString;
- readonly 'range'?: NjsByteString;
- readonly 'referer'?: NjsByteString;
- readonly 'te'?: NjsByteString;
- readonly 'user-agent'?: NjsByteString;
- readonly 'upgrade'?: NjsByteString;
- readonly 'via'?: NjsByteString;
- readonly 'warning'?: NjsByteString;
- readonly 'x-forwarded-for'?: NjsByteString;
+ readonly 'Accept'?: NjsByteString;
+ readonly 'Accept-Charset'?: NjsByteString;
+ readonly 'Accept-Encoding'?: NjsByteString;
+ readonly 'Accept-Language'?: NjsByteString;
+ readonly 'Authorization'?: NjsByteString;
+ readonly 'Cache-Control'?: NjsByteString;
+ readonly 'Connection'?: NjsByteString;
+ readonly 'Content-Length'?: NjsByteString;
+ readonly 'Content-Type'?: NjsByteString;
+ readonly 'Cookie'?: NjsByteString;
+ readonly 'Date'?: NjsByteString;
+ readonly 'Expect'?: NjsByteString;
+ readonly 'Forwarded'?: NjsByteString;
+ readonly 'From'?: NjsByteString;
+ readonly 'Host'?: NjsByteString;
+ readonly 'If-Match'?: NjsByteString;
+ readonly 'If-Modified-Since'?: NjsByteString;
+ readonly 'If-None-Match'?: NjsByteString;
+ readonly 'If-Range'?: NjsByteString;
+ readonly 'If-Unmodified-Since'?: NjsByteString;
+ readonly 'Max-Forwards'?: NjsByteString;
+ readonly 'Origin'?: NjsByteString;
+ readonly 'Pragma'?: NjsByteString;
+ readonly 'Proxy-Authorization'?: NjsByteString;
+ readonly 'Range'?: NjsByteString;
+ readonly 'Referer'?: NjsByteString;
+ readonly 'TE'?: NjsByteString;
+ readonly 'User-Agent'?: NjsByteString;
+ readonly 'Upgrade'?: NjsByteString;
+ readonly 'Via'?: NjsByteString;
+ readonly 'Warning'?: NjsByteString;
+ readonly 'X-Forwarded-For'?: NjsByteString;
readonly [prop: string]: NjsByteString;
}
interface NginxHeadersOut {
// common response headers
- 'age'?: NjsStringLike;
- 'allow'?: NjsStringLike;
- 'alt-svc'?: NjsStringLike;
- 'cache-control'?: NjsStringLike;
- 'connection'?: NjsStringLike;
- 'content-disposition'?: NjsStringLike;
- 'content-encoding'?: NjsStringLike;
- 'content-language'?: NjsStringLike;
- 'content-length'?: NjsStringLike;
- 'content-location'?: NjsStringLike;
- 'content-range'?: NjsStringLike;
- 'content-type'?: NjsStringLike;
- 'date'?: NjsStringLike;
- 'etag'?: NjsStringLike;
- 'expires'?: NjsStringLike;
- 'last-modified'?: NjsStringLike;
- 'link'?: NjsStringLike;
- 'location'?: NjsStringLike;
- 'pragma'?: NjsStringLike;
- 'proxy-authenticate'?: NjsStringLike;
- 'retry-after'?: NjsStringLike;
- 'server'?: NjsStringLike;
- 'trailer'?: NjsStringLike;
- 'transfer-encoding'?: NjsStringLike;
- 'upgrade'?: NjsStringLike;
- 'vary'?: NjsStringLike;
- 'via'?: NjsStringLike;
- 'warning'?: NjsStringLike;
- 'www-authenticate'?: NjsStringLike;
+ 'Age'?: NjsStringLike;
+ 'Allow'?: NjsStringLike;
+ 'Alt-Svc'?: NjsStringLike;
+ 'Cache-Control'?: NjsStringLike;
+ 'Connection'?: NjsStringLike;
+ 'Content-Disposition'?: NjsStringLike;
+ 'Content-Encoding'?: NjsStringLike;
+ 'Content-Language'?: NjsStringLike;
+ 'Content-Length'?: NjsStringLike;
+ 'Content-Location'?: NjsStringLike;
+ 'Content-Range'?: NjsStringLike;
+ 'Content-Type'?: NjsStringLike;
+ 'Date'?: NjsStringLike;
+ 'ETag'?: NjsStringLike;
+ 'Expires'?: NjsStringLike;
+ 'Last-Modified'?: NjsStringLike;
+ 'Link'?: NjsStringLike;
+ 'Location'?: NjsStringLike;
+ 'Pragma'?: NjsStringLike;
+ 'Proxy-Authenticate'?: NjsStringLike;
+ 'Retry-After'?: NjsStringLike;
+ 'Server'?: NjsStringLike;
+ 'Trailer'?: NjsStringLike;
+ 'Transfer-Encoding'?: NjsStringLike;
+ 'Upgrade'?: NjsStringLike;
+ 'Vary'?: NjsStringLike;
+ 'Via'?: NjsStringLike;
+ 'Warning'?: NjsStringLike;
+ 'WWW-Authenticate'?: NjsStringLike;
- 'set-cookie'?: NjsStringLike[];
+ 'Set-Cookie'?: NjsStringLike[];
[prop: string]: NjsStringLike | NjsStringLike[];
}
@xeioex
Looks like a hack, but seems to work:
type NginxHeadersOut = {
// common response headers
'www-authenticate'?: NjsStringLike;
// ...
[prop: string]: NjsStringLike;
} & {
[prop in 'set-cookie' | 'Set-Cookie']: NjsStringLike | NjsStringLike[];
};
So, any generic property will be NjsStringLike
updated
function healthcheck(r: NginxHTTPRequest) {
r.headersOut['Set-Cookie'] = ['aaa', 'bbb'];
r.headersOut['Set-Cookie'] = 'test';
//r.headersOut['Fail'] = ['aaa', 'bbb'];
//r.headersOut['Fail'] = {};
r.return(200, r.headersOut['Location']);
}
@drsm
BTW,
r.headersOut['Foo'] = ['a', 'b'] is also supported.
@drsm
My current patchset: https://gist.github.com/8d72abff54aef8add97b43fbf0175682
It also includes make ts_test to validate TS definitions.
What still causes error, but it seems minor:
1)
// declared as NjsByteString only
r.headersOut['Content-Type'] = ['a', 'b'];
2)
r.return(200, reply.headersOut["UNKNOWN"]);
@drsm
I was wrong there, it may be disabled by --strictNullChecks compiler flag.
BTW, tsc --strictNullChecks test.ts generates a lot of warnings (not an issue now, can be improved later).
@xeioex
BTW,
r.headersOut['Foo'] = ['a', 'b']is also supported.
If so, r.return(200, reply.headersOut["UNKNOWN"]); should fail.
We have to cast: r.return(200, reply.headersOut["UNKNOWN"] as string);.
BTW,
tsc --strictNullChecks test.tsgenerates a lot of warnings (not an issue now, can be improved later).
The problem that there is a by-design backdoor in --strictNullChecks:
function test(arg: string[]): string {
var str: string;
str = arg[1]; // may be undefined o_O
return arg[2]; // // may be undefined o_O
}
var s: string = test(['one']);
So, if we change NginxHeadersIn to
interface NginxHeadersIn {
// ...
readonly [prop: string]: NjsByteString | undefined;
}
it will break the
var str: string;
for (var k in r.headersIn) {
str = r.headersIn[k];
}
we'll have to write an ugly
var str: string;
for (var k in r.headersIn) {
if (r.headersIn[k]) {
str = r.headersIn[k];
}
}
looks like divide and conquier is the only way:
diff --git a/nginx/ts/ngx_http_js_module.d.ts b/nginx/ts/ngx_http_js_module.d.ts
index d29b138..efaced1 100644
--- a/nginx/ts/ngx_http_js_module.d.ts
+++ b/nginx/ts/ngx_http_js_module.d.ts
@@ -4,7 +4,7 @@ interface NginxHTTPArgs {
readonly [prop: string]: NjsByteString;
}
-interface NginxHeadersIn {
+type NginxHeadersIn = {
// common request headers
readonly 'Accept'?: NjsByteString;
readonly 'Accept-Charset'?: NjsByteString;
@@ -38,9 +38,9 @@ interface NginxHeadersIn {
readonly 'Via'?: NjsByteString;
readonly 'Warning'?: NjsByteString;
readonly 'X-Forwarded-For'?: NjsByteString;
-
+} & {
readonly [prop: string]: NjsByteString;
-}
+};
interface NginxHeadersOut {
// common response headers