@bytecodealliance/preview3-shim 0.1.1 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/nodejs/cli.d.ts +51 -0
- package/dist/nodejs/cli.js +106 -0
- package/dist/nodejs/clocks.d.ts +55 -0
- package/dist/nodejs/clocks.js +83 -0
- package/dist/nodejs/filesystem/descriptor.d.ts +443 -0
- package/dist/nodejs/filesystem/descriptor.js +1010 -0
- package/dist/nodejs/filesystem/error.d.ts +61 -0
- package/dist/nodejs/filesystem/error.js +86 -0
- package/dist/nodejs/filesystem/utils.d.ts +8 -0
- package/dist/nodejs/filesystem/utils.js +25 -0
- package/dist/nodejs/filesystem.js +2 -0
- package/dist/nodejs/finalization.d.ts +2 -0
- package/{lib → dist}/nodejs/finalization.js +15 -21
- package/dist/nodejs/future.d.ts +98 -0
- package/dist/nodejs/future.js +176 -0
- package/dist/nodejs/http/client.d.ts +22 -0
- package/dist/nodejs/http/client.js +138 -0
- package/dist/nodejs/http/error.d.ts +18 -0
- package/dist/nodejs/http/error.js +62 -0
- package/dist/nodejs/http/fields.d.ts +144 -0
- package/dist/nodejs/http/fields.js +327 -0
- package/dist/nodejs/http/request.d.ts +256 -0
- package/dist/nodejs/http/request.js +606 -0
- package/dist/nodejs/http/response.d.ts +82 -0
- package/dist/nodejs/http/response.js +202 -0
- package/dist/nodejs/http/server.d.ts +13 -0
- package/dist/nodejs/http/server.js +127 -0
- package/dist/nodejs/http.d.ts +19 -0
- package/{lib → dist}/nodejs/http.js +13 -18
- package/dist/nodejs/index.js +8 -0
- package/dist/nodejs/random.d.ts +5 -0
- package/{lib → dist}/nodejs/random.js +2 -8
- package/dist/nodejs/sockets/address.d.ts +78 -0
- package/dist/nodejs/sockets/address.js +120 -0
- package/dist/nodejs/sockets/error.d.ts +83 -0
- package/dist/nodejs/sockets/error.js +120 -0
- package/dist/nodejs/sockets/ip-name-lookup.d.ts +18 -0
- package/dist/nodejs/sockets/ip-name-lookup.js +84 -0
- package/dist/nodejs/sockets/tcp.d.ts +333 -0
- package/dist/nodejs/sockets/tcp.js +723 -0
- package/dist/nodejs/sockets/udp.d.ts +224 -0
- package/dist/nodejs/sockets/udp.js +475 -0
- package/dist/nodejs/sockets.d.ts +11 -0
- package/{lib → dist}/nodejs/sockets.js +2 -5
- package/dist/nodejs/stream.d.ts +161 -0
- package/dist/nodejs/stream.js +388 -0
- package/dist/nodejs/workers/cli-worker.d.ts +1 -0
- package/dist/nodejs/workers/cli-worker.js +41 -0
- package/dist/nodejs/workers/filesystem-worker.d.ts +1 -0
- package/dist/nodejs/workers/filesystem-worker.js +102 -0
- package/dist/nodejs/workers/http-worker.d.ts +1 -0
- package/dist/nodejs/workers/http-worker.js +355 -0
- package/dist/nodejs/workers/resource-worker.d.ts +13 -0
- package/dist/nodejs/workers/resource-worker.js +152 -0
- package/dist/nodejs/workers/tcp-worker.d.ts +2 -0
- package/dist/nodejs/workers/tcp-worker.js +318 -0
- package/dist/nodejs/workers/udp-worker.d.ts +1 -0
- package/dist/nodejs/workers/udp-worker.js +156 -0
- package/package.json +20 -12
- package/types/interfaces/wasi-cli-environment.d.ts +3 -3
- package/types/interfaces/wasi-cli-exit.d.ts +12 -1
- package/types/interfaces/wasi-cli-run.d.ts +1 -1
- package/types/interfaces/wasi-cli-stderr.d.ts +3 -3
- package/types/interfaces/wasi-cli-stdin.d.ts +5 -5
- package/types/interfaces/wasi-cli-stdout.d.ts +3 -3
- package/types/interfaces/wasi-cli-terminal-input.d.ts +1 -1
- package/types/interfaces/wasi-cli-terminal-output.d.ts +1 -1
- package/types/interfaces/wasi-cli-terminal-stderr.d.ts +1 -1
- package/types/interfaces/wasi-cli-terminal-stdin.d.ts +1 -1
- package/types/interfaces/wasi-cli-terminal-stdout.d.ts +1 -1
- package/types/interfaces/wasi-cli-types.d.ts +5 -5
- package/types/interfaces/wasi-clocks-monotonic-clock.d.ts +3 -3
- package/types/interfaces/wasi-clocks-system-clock.d.ts +6 -6
- package/types/interfaces/wasi-clocks-types.d.ts +1 -1
- package/types/interfaces/wasi-filesystem-preopens.d.ts +1 -1
- package/types/interfaces/wasi-filesystem-types.d.ts +74 -74
- package/types/interfaces/wasi-http-client.d.ts +1 -1
- package/types/interfaces/wasi-http-handler.d.ts +1 -1
- package/types/interfaces/wasi-http-types.d.ts +34 -34
- package/types/interfaces/wasi-random-insecure-seed.d.ts +5 -5
- package/types/interfaces/wasi-random-insecure.d.ts +5 -5
- package/types/interfaces/wasi-random-random.d.ts +5 -5
- package/types/interfaces/wasi-sockets-ip-name-lookup.d.ts +11 -11
- package/types/interfaces/wasi-sockets-types.d.ts +121 -121
- package/types/wasi-cli-command.d.ts +23 -23
- package/types/wasi-http-service.d.ts +14 -14
- package/lib/nodejs/cli.js +0 -127
- package/lib/nodejs/clocks.js +0 -100
- package/lib/nodejs/filesystem/descriptor.js +0 -1093
- package/lib/nodejs/filesystem/error.js +0 -92
- package/lib/nodejs/filesystem/utils.js +0 -19
- package/lib/nodejs/future.js +0 -190
- package/lib/nodejs/http/client.js +0 -161
- package/lib/nodejs/http/error.js +0 -71
- package/lib/nodejs/http/fields.js +0 -373
- package/lib/nodejs/http/request.js +0 -691
- package/lib/nodejs/http/response.js +0 -227
- package/lib/nodejs/http/server.js +0 -157
- package/lib/nodejs/sockets/address.js +0 -146
- package/lib/nodejs/sockets/error.js +0 -123
- package/lib/nodejs/sockets/ip-name-lookup.js +0 -93
- package/lib/nodejs/sockets/tcp.js +0 -801
- package/lib/nodejs/sockets/udp.js +0 -516
- package/lib/nodejs/stream.js +0 -437
- package/lib/nodejs/workers/cli-worker.js +0 -49
- package/lib/nodejs/workers/filesystem-worker.js +0 -119
- package/lib/nodejs/workers/http-worker.js +0 -417
- package/lib/nodejs/workers/resource-worker.js +0 -195
- package/lib/nodejs/workers/tcp-worker.js +0 -370
- package/lib/nodejs/workers/udp-worker.js +0 -194
- /package/{lib/nodejs/filesystem.js → dist/nodejs/filesystem.d.ts} +0 -0
- /package/{lib/nodejs/index.js → dist/nodejs/index.d.ts} +0 -0
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Custom error class for WASI http interface that properly formats payload for Jco compatibility
|
|
3
|
+
*
|
|
4
|
+
* https://bytecodealliance.github.io/jco/wit-type-representations.html#result-considerations-idiomatic-js-errors-for-host-implementations
|
|
5
|
+
*/
|
|
6
|
+
export class HttpError extends Error {
|
|
7
|
+
payload;
|
|
8
|
+
/**
|
|
9
|
+
* Create a new WASI http error
|
|
10
|
+
* @param {string} tag - The error tag/type
|
|
11
|
+
* @param {string} [message] - Human-readable error message
|
|
12
|
+
* @param {any} [val] - Optional value/data for the error
|
|
13
|
+
*/
|
|
14
|
+
constructor(tag, message = undefined, val = undefined, opts = {}) {
|
|
15
|
+
super(message || `Error: ${tag}`, { cause: opts.cause });
|
|
16
|
+
this.name = "HttpError";
|
|
17
|
+
this.payload = val !== undefined ? { tag, val } : { tag };
|
|
18
|
+
}
|
|
19
|
+
static from(err) {
|
|
20
|
+
if (err instanceof HttpError) {
|
|
21
|
+
return err;
|
|
22
|
+
}
|
|
23
|
+
if (err?.tag) {
|
|
24
|
+
return new HttpError(err.tag, undefined, err.val);
|
|
25
|
+
}
|
|
26
|
+
const e = getFirstError(err);
|
|
27
|
+
let tag, val;
|
|
28
|
+
switch (e.code) {
|
|
29
|
+
case "ECONNRESET":
|
|
30
|
+
tag = "HTTP-protocol-error";
|
|
31
|
+
break;
|
|
32
|
+
case "ENOTFOUND":
|
|
33
|
+
tag = "DNS-error";
|
|
34
|
+
val = {
|
|
35
|
+
rcode: e.code,
|
|
36
|
+
infoCode: e.errno < 0 ? -e.errno : e.errno,
|
|
37
|
+
};
|
|
38
|
+
break;
|
|
39
|
+
case "ECONNREFUSED":
|
|
40
|
+
tag = "connection-refused";
|
|
41
|
+
break;
|
|
42
|
+
default:
|
|
43
|
+
tag = "internal-error";
|
|
44
|
+
val = e.toString();
|
|
45
|
+
}
|
|
46
|
+
const httpErr = new HttpError(tag, e.message, val, { cause: e });
|
|
47
|
+
Error.captureStackTrace(httpErr, HttpError.from);
|
|
48
|
+
return httpErr;
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
function getFirstError(e) {
|
|
52
|
+
if (typeof e !== "object" || e === null) {
|
|
53
|
+
return e;
|
|
54
|
+
}
|
|
55
|
+
if (e.cause) {
|
|
56
|
+
return getFirstError(e.cause);
|
|
57
|
+
}
|
|
58
|
+
if (e instanceof AggregateError) {
|
|
59
|
+
return getFirstError(e.errors[0]);
|
|
60
|
+
}
|
|
61
|
+
return e;
|
|
62
|
+
}
|
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
export declare const _forbiddenHeaders: {
|
|
2
|
+
readonly value: any;
|
|
3
|
+
};
|
|
4
|
+
/**
|
|
5
|
+
* Implements the WASI Preview3 fields resource for HTTP headers and trailers
|
|
6
|
+
*/
|
|
7
|
+
export declare class Fields {
|
|
8
|
+
#private;
|
|
9
|
+
/**
|
|
10
|
+
* Constructs an empty HTTP Fields
|
|
11
|
+
* WIT:
|
|
12
|
+
* ```
|
|
13
|
+
* constructor();
|
|
14
|
+
* ```
|
|
15
|
+
*/
|
|
16
|
+
constructor();
|
|
17
|
+
/**
|
|
18
|
+
* Constructs a Fields instance from a list of name-value pairs
|
|
19
|
+
* WIT:
|
|
20
|
+
* ```
|
|
21
|
+
* from-list: static func(entries: list<tuple<field-name,field-value>>) -> result<fields, header-error>;
|
|
22
|
+
* ```
|
|
23
|
+
*
|
|
24
|
+
* @param {[string, Uint8Array[]][]} entries - List of field name and value pairs
|
|
25
|
+
* @returns {Fields} A new Fields instance
|
|
26
|
+
* @throws {HttpError} With payload.tag 'invalid-syntax' if any name or value is invalid
|
|
27
|
+
* @throws {HttpError} With payload.tag 'forbidden' if any name is forbidden
|
|
28
|
+
*/
|
|
29
|
+
static fromList(entries: any): Fields;
|
|
30
|
+
/**
|
|
31
|
+
* Get all values for a given field name
|
|
32
|
+
* WIT:
|
|
33
|
+
* ```
|
|
34
|
+
* get: func(name: field-name) -> list<field-value>;
|
|
35
|
+
* ```
|
|
36
|
+
*
|
|
37
|
+
* @param {string} name - The field name to get values for
|
|
38
|
+
* @returns {Uint8Array[]} List of values for the field
|
|
39
|
+
*/
|
|
40
|
+
get(name: any): any;
|
|
41
|
+
/**
|
|
42
|
+
* Check if a field name exists in the collection
|
|
43
|
+
* WIT:
|
|
44
|
+
* ```
|
|
45
|
+
* has: func(name: field-name) -> bool;
|
|
46
|
+
* ```
|
|
47
|
+
*
|
|
48
|
+
* @param {string} name - The field name to check
|
|
49
|
+
* @returns {boolean} True if the field exists
|
|
50
|
+
*/
|
|
51
|
+
has(name: any): boolean;
|
|
52
|
+
/**
|
|
53
|
+
* Set values for a field name, replacing any existing values
|
|
54
|
+
* WIT:
|
|
55
|
+
* ```
|
|
56
|
+
* set: func(name: field-name, value: list<field-value>) -> result<_, header-error>;
|
|
57
|
+
* ```
|
|
58
|
+
*
|
|
59
|
+
* @param {string} name - The field name
|
|
60
|
+
* @param {Uint8Array[]} values - The values to set
|
|
61
|
+
* @throws {HttpError} With payload.tag 'immutable' if the fields are immutable
|
|
62
|
+
* @throws {HttpError} With payload.tag 'invalid-syntax' if name or values are invalid
|
|
63
|
+
* @throws {HttpError} With payload.tag 'forbidden' if name is forbidden
|
|
64
|
+
*/
|
|
65
|
+
set(name: any, values: any): void;
|
|
66
|
+
/**
|
|
67
|
+
* Delete all values for a field name
|
|
68
|
+
* WIT:
|
|
69
|
+
* ```
|
|
70
|
+
* delete: func(name: field-name) -> result<_, header-error>;
|
|
71
|
+
* ```
|
|
72
|
+
*
|
|
73
|
+
* @param {string} name - The field name to delete
|
|
74
|
+
* @throws {HttpError} With payload.tag 'immutable' if the fields are immutable
|
|
75
|
+
*/
|
|
76
|
+
delete(name: any): void;
|
|
77
|
+
/**
|
|
78
|
+
* Get all values for a field name and then delete them
|
|
79
|
+
* WIT:
|
|
80
|
+
* ```
|
|
81
|
+
* get-and-delete: func(name: field-name) -> result<list<field-value>, header-error>;
|
|
82
|
+
* ```
|
|
83
|
+
*
|
|
84
|
+
* @param {string} name - The field name to get and delete
|
|
85
|
+
* @returns {Uint8Array[]} List of values that were deleted
|
|
86
|
+
* @throws {HttpError} With payload.tag 'immutable' if the fields are immutable
|
|
87
|
+
*/
|
|
88
|
+
getAndDelete(name: any): any;
|
|
89
|
+
/**
|
|
90
|
+
* Append a value to a field name
|
|
91
|
+
* WIT:
|
|
92
|
+
* ```
|
|
93
|
+
* append: func(name: field-name, value: field-value) -> result<_, header-error>;
|
|
94
|
+
* ```
|
|
95
|
+
*
|
|
96
|
+
* @param {string} name - The field name
|
|
97
|
+
* @param {Uint8Array} value - The value to append
|
|
98
|
+
* @throws {HttpError} With payload.tag 'immutable' if the fields are immutable
|
|
99
|
+
* @throws {HttpError} With payload.tag 'invalid-syntax' if name or value is invalid
|
|
100
|
+
* @throws {HttpError} With payload.tag 'forbidden' if name is forbidden
|
|
101
|
+
*/
|
|
102
|
+
append(name: any, value: any): void;
|
|
103
|
+
/**
|
|
104
|
+
* Get all entries as a list of name-value pairs
|
|
105
|
+
* WIT:
|
|
106
|
+
* ```
|
|
107
|
+
* copy-all: func() -> list<tuple<field-name,field-value>>;
|
|
108
|
+
* ```
|
|
109
|
+
*
|
|
110
|
+
* @returns {[string, Uint8Array][]} List of all entries
|
|
111
|
+
*/
|
|
112
|
+
copyAll(): any[];
|
|
113
|
+
/**
|
|
114
|
+
* Create a deep copy of this Fields instance
|
|
115
|
+
* WIT:
|
|
116
|
+
* ```
|
|
117
|
+
* clone: func() -> fields;
|
|
118
|
+
* ```
|
|
119
|
+
*
|
|
120
|
+
* @returns {Fields} A new mutable Fields instance with the same entries
|
|
121
|
+
*/
|
|
122
|
+
clone(): Fields;
|
|
123
|
+
/**
|
|
124
|
+
* Mark fields as immutable
|
|
125
|
+
*
|
|
126
|
+
* @param {Fields} fields - The Fields instance to mark as immutable
|
|
127
|
+
* @returns {Fields} The same instance, now immutable
|
|
128
|
+
*/
|
|
129
|
+
static _lock(fields: any): any;
|
|
130
|
+
/**
|
|
131
|
+
* Create a Fields instance from pre-validated entries
|
|
132
|
+
*
|
|
133
|
+
* @param {[string, Uint8Array][]} entries - Validated entries to use
|
|
134
|
+
* @returns {Fields} A new Fields instance with the provided entries
|
|
135
|
+
*/
|
|
136
|
+
static _fromEntriesChecked(entries: any): Fields;
|
|
137
|
+
}
|
|
138
|
+
export declare function _fieldsLock(fields: any): any;
|
|
139
|
+
export declare function _fieldsFromEntriesChecked(entries: any): Fields;
|
|
140
|
+
export declare function _readTrailersForTransport(trailers: any): Promise<any[]>;
|
|
141
|
+
export declare function _trailerResultFromEntries(entries: any): {
|
|
142
|
+
tag: string;
|
|
143
|
+
val: Fields;
|
|
144
|
+
};
|
|
@@ -0,0 +1,327 @@
|
|
|
1
|
+
import { HttpError } from "./error.js";
|
|
2
|
+
import * as http from "node:http";
|
|
3
|
+
const { validateHeaderName = () => { }, validateHeaderValue = () => { } } = http;
|
|
4
|
+
let FORBIDDEN_HEADERS = null;
|
|
5
|
+
export const _forbiddenHeaders = {
|
|
6
|
+
get value() {
|
|
7
|
+
return (FORBIDDEN_HEADERS ??= new Set([
|
|
8
|
+
"connection",
|
|
9
|
+
"keep-alive",
|
|
10
|
+
"host",
|
|
11
|
+
"custom-forbidden-header",
|
|
12
|
+
]));
|
|
13
|
+
},
|
|
14
|
+
};
|
|
15
|
+
/**
|
|
16
|
+
* Implements the WASI Preview3 fields resource for HTTP headers and trailers
|
|
17
|
+
*/
|
|
18
|
+
export class Fields {
|
|
19
|
+
#immutable = false;
|
|
20
|
+
/** @type {[string, Uint8Array][]} */
|
|
21
|
+
#entries = [];
|
|
22
|
+
/** @type {Map<string, [string, Uint8Array[]][]>} */
|
|
23
|
+
#table = new Map();
|
|
24
|
+
/**
|
|
25
|
+
* Constructs an empty HTTP Fields
|
|
26
|
+
* WIT:
|
|
27
|
+
* ```
|
|
28
|
+
* constructor();
|
|
29
|
+
* ```
|
|
30
|
+
*/
|
|
31
|
+
constructor() { }
|
|
32
|
+
/**
|
|
33
|
+
* Constructs a Fields instance from a list of name-value pairs
|
|
34
|
+
* WIT:
|
|
35
|
+
* ```
|
|
36
|
+
* from-list: static func(entries: list<tuple<field-name,field-value>>) -> result<fields, header-error>;
|
|
37
|
+
* ```
|
|
38
|
+
*
|
|
39
|
+
* @param {[string, Uint8Array[]][]} entries - List of field name and value pairs
|
|
40
|
+
* @returns {Fields} A new Fields instance
|
|
41
|
+
* @throws {HttpError} With payload.tag 'invalid-syntax' if any name or value is invalid
|
|
42
|
+
* @throws {HttpError} With payload.tag 'forbidden' if any name is forbidden
|
|
43
|
+
*/
|
|
44
|
+
static fromList(entries) {
|
|
45
|
+
const fields = new Fields();
|
|
46
|
+
for (const [name, values] of entries) {
|
|
47
|
+
for (const value of Array.isArray(values) ? values : [values]) {
|
|
48
|
+
fields.append(name, value);
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
return fields;
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* Get all values for a given field name
|
|
55
|
+
* WIT:
|
|
56
|
+
* ```
|
|
57
|
+
* get: func(name: field-name) -> list<field-value>;
|
|
58
|
+
* ```
|
|
59
|
+
*
|
|
60
|
+
* @param {string} name - The field name to get values for
|
|
61
|
+
* @returns {Uint8Array[]} List of values for the field
|
|
62
|
+
*/
|
|
63
|
+
get(name) {
|
|
64
|
+
const tableEntries = this.#table.get(name.toLowerCase());
|
|
65
|
+
if (!tableEntries) {
|
|
66
|
+
return [];
|
|
67
|
+
}
|
|
68
|
+
return tableEntries.flatMap(([, v]) => v);
|
|
69
|
+
}
|
|
70
|
+
/**
|
|
71
|
+
* Check if a field name exists in the collection
|
|
72
|
+
* WIT:
|
|
73
|
+
* ```
|
|
74
|
+
* has: func(name: field-name) -> bool;
|
|
75
|
+
* ```
|
|
76
|
+
*
|
|
77
|
+
* @param {string} name - The field name to check
|
|
78
|
+
* @returns {boolean} True if the field exists
|
|
79
|
+
*/
|
|
80
|
+
has(name) {
|
|
81
|
+
return this.#table.has(name.toLowerCase());
|
|
82
|
+
}
|
|
83
|
+
/**
|
|
84
|
+
* Set values for a field name, replacing any existing values
|
|
85
|
+
* WIT:
|
|
86
|
+
* ```
|
|
87
|
+
* set: func(name: field-name, value: list<field-value>) -> result<_, header-error>;
|
|
88
|
+
* ```
|
|
89
|
+
*
|
|
90
|
+
* @param {string} name - The field name
|
|
91
|
+
* @param {Uint8Array[]} values - The values to set
|
|
92
|
+
* @throws {HttpError} With payload.tag 'immutable' if the fields are immutable
|
|
93
|
+
* @throws {HttpError} With payload.tag 'invalid-syntax' if name or values are invalid
|
|
94
|
+
* @throws {HttpError} With payload.tag 'forbidden' if name is forbidden
|
|
95
|
+
*/
|
|
96
|
+
set(name, values) {
|
|
97
|
+
this.#ensureMutable();
|
|
98
|
+
this.#validateName(name);
|
|
99
|
+
for (const value of values) {
|
|
100
|
+
this.#validateValue(name, value);
|
|
101
|
+
}
|
|
102
|
+
const lowercased = name.toLowerCase();
|
|
103
|
+
let bucket = this.#table.get(lowercased);
|
|
104
|
+
if (bucket) {
|
|
105
|
+
this.#entries = this.#entries.filter((e) => !bucket.includes(e));
|
|
106
|
+
this.#table.delete(lowercased);
|
|
107
|
+
}
|
|
108
|
+
if (values.length === 0) {
|
|
109
|
+
return;
|
|
110
|
+
}
|
|
111
|
+
bucket = [];
|
|
112
|
+
this.#table.set(lowercased, bucket);
|
|
113
|
+
for (const value of values) {
|
|
114
|
+
const entry = [name, value];
|
|
115
|
+
this.#entries.push(entry);
|
|
116
|
+
bucket.push(entry);
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
/**
|
|
120
|
+
* Delete all values for a field name
|
|
121
|
+
* WIT:
|
|
122
|
+
* ```
|
|
123
|
+
* delete: func(name: field-name) -> result<_, header-error>;
|
|
124
|
+
* ```
|
|
125
|
+
*
|
|
126
|
+
* @param {string} name - The field name to delete
|
|
127
|
+
* @throws {HttpError} With payload.tag 'immutable' if the fields are immutable
|
|
128
|
+
*/
|
|
129
|
+
delete(name) {
|
|
130
|
+
this.#ensureMutable();
|
|
131
|
+
this.#validateName(name);
|
|
132
|
+
const lowercased = name.toLowerCase();
|
|
133
|
+
const tableEntries = this.#table.get(lowercased);
|
|
134
|
+
if (tableEntries) {
|
|
135
|
+
this.#entries = this.#entries.filter((entry) => !tableEntries.includes(entry));
|
|
136
|
+
this.#table.delete(lowercased);
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
/**
|
|
140
|
+
* Get all values for a field name and then delete them
|
|
141
|
+
* WIT:
|
|
142
|
+
* ```
|
|
143
|
+
* get-and-delete: func(name: field-name) -> result<list<field-value>, header-error>;
|
|
144
|
+
* ```
|
|
145
|
+
*
|
|
146
|
+
* @param {string} name - The field name to get and delete
|
|
147
|
+
* @returns {Uint8Array[]} List of values that were deleted
|
|
148
|
+
* @throws {HttpError} With payload.tag 'immutable' if the fields are immutable
|
|
149
|
+
*/
|
|
150
|
+
getAndDelete(name) {
|
|
151
|
+
this.#ensureMutable();
|
|
152
|
+
this.#validateName(name);
|
|
153
|
+
const values = this.get(name);
|
|
154
|
+
this.delete(name);
|
|
155
|
+
return values;
|
|
156
|
+
}
|
|
157
|
+
/**
|
|
158
|
+
* Append a value to a field name
|
|
159
|
+
* WIT:
|
|
160
|
+
* ```
|
|
161
|
+
* append: func(name: field-name, value: field-value) -> result<_, header-error>;
|
|
162
|
+
* ```
|
|
163
|
+
*
|
|
164
|
+
* @param {string} name - The field name
|
|
165
|
+
* @param {Uint8Array} value - The value to append
|
|
166
|
+
* @throws {HttpError} With payload.tag 'immutable' if the fields are immutable
|
|
167
|
+
* @throws {HttpError} With payload.tag 'invalid-syntax' if name or value is invalid
|
|
168
|
+
* @throws {HttpError} With payload.tag 'forbidden' if name is forbidden
|
|
169
|
+
*/
|
|
170
|
+
append(name, value) {
|
|
171
|
+
this.#ensureMutable();
|
|
172
|
+
this.#validateName(name);
|
|
173
|
+
this.#validateValue(name, value);
|
|
174
|
+
const lowercased = name.toLowerCase();
|
|
175
|
+
const tableEntries = this.#table.get(lowercased);
|
|
176
|
+
const entryName = tableEntries?.[0]?.[0] ?? name;
|
|
177
|
+
const entry = [entryName, value];
|
|
178
|
+
this.#entries.push(entry);
|
|
179
|
+
if (tableEntries) {
|
|
180
|
+
tableEntries.push(entry);
|
|
181
|
+
}
|
|
182
|
+
else {
|
|
183
|
+
this.#table.set(lowercased, [entry]);
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
/**
|
|
187
|
+
* Get all entries as a list of name-value pairs
|
|
188
|
+
* WIT:
|
|
189
|
+
* ```
|
|
190
|
+
* copy-all: func() -> list<tuple<field-name,field-value>>;
|
|
191
|
+
* ```
|
|
192
|
+
*
|
|
193
|
+
* @returns {[string, Uint8Array][]} List of all entries
|
|
194
|
+
*/
|
|
195
|
+
copyAll() {
|
|
196
|
+
return this.#entries.slice();
|
|
197
|
+
}
|
|
198
|
+
/**
|
|
199
|
+
* Create a deep copy of this Fields instance
|
|
200
|
+
* WIT:
|
|
201
|
+
* ```
|
|
202
|
+
* clone: func() -> fields;
|
|
203
|
+
* ```
|
|
204
|
+
*
|
|
205
|
+
* @returns {Fields} A new mutable Fields instance with the same entries
|
|
206
|
+
*/
|
|
207
|
+
clone() {
|
|
208
|
+
const entries = this.#entries.slice();
|
|
209
|
+
return _fieldsFromEntriesChecked(entries);
|
|
210
|
+
}
|
|
211
|
+
/**
|
|
212
|
+
* Mark fields as immutable
|
|
213
|
+
*
|
|
214
|
+
* @param {Fields} fields - The Fields instance to mark as immutable
|
|
215
|
+
* @returns {Fields} The same instance, now immutable
|
|
216
|
+
*/
|
|
217
|
+
static _lock(fields) {
|
|
218
|
+
fields.#immutable = true;
|
|
219
|
+
return fields;
|
|
220
|
+
}
|
|
221
|
+
/**
|
|
222
|
+
* Create a Fields instance from pre-validated entries
|
|
223
|
+
*
|
|
224
|
+
* @param {[string, Uint8Array][]} entries - Validated entries to use
|
|
225
|
+
* @returns {Fields} A new Fields instance with the provided entries
|
|
226
|
+
*/
|
|
227
|
+
static _fromEntriesChecked(entries) {
|
|
228
|
+
const fields = new Fields();
|
|
229
|
+
fields.#entries = entries;
|
|
230
|
+
for (const entry of entries) {
|
|
231
|
+
const lowercase = entry[0].toLowerCase();
|
|
232
|
+
const existing = fields.#table.get(lowercase);
|
|
233
|
+
if (existing) {
|
|
234
|
+
existing.push(entry);
|
|
235
|
+
}
|
|
236
|
+
else {
|
|
237
|
+
fields.#table.set(lowercase, [entry]);
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
return fields;
|
|
241
|
+
}
|
|
242
|
+
#validateName(name) {
|
|
243
|
+
try {
|
|
244
|
+
validateHeaderName(name);
|
|
245
|
+
}
|
|
246
|
+
catch {
|
|
247
|
+
throw new HttpError("invalid-syntax", `Invalid header name: ${name}`);
|
|
248
|
+
}
|
|
249
|
+
if (_forbiddenHeaders.value.has(name.toLowerCase())) {
|
|
250
|
+
throw new HttpError("forbidden", `Header ${name} is forbidden`);
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
#validateValue(name, value) {
|
|
254
|
+
try {
|
|
255
|
+
validateHeaderValue(name, bytesToHeaderValueString(value));
|
|
256
|
+
}
|
|
257
|
+
catch {
|
|
258
|
+
throw new HttpError("invalid-syntax", `Invalid header value for ${name}`);
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
#ensureMutable() {
|
|
262
|
+
if (this.#immutable) {
|
|
263
|
+
throw new HttpError("immutable", "Cannot modify immutable fields");
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
function bytesToHeaderValueString(value) {
|
|
268
|
+
let result = "";
|
|
269
|
+
for (let i = 0; i < value.length; i += 0x8000) {
|
|
270
|
+
result += String.fromCharCode(...value.subarray(i, i + 0x8000));
|
|
271
|
+
}
|
|
272
|
+
return result;
|
|
273
|
+
}
|
|
274
|
+
export function _fieldsLock(fields) {
|
|
275
|
+
return Fields._lock(fields);
|
|
276
|
+
}
|
|
277
|
+
export function _fieldsFromEntriesChecked(entries) {
|
|
278
|
+
return Fields._fromEntriesChecked(entries);
|
|
279
|
+
}
|
|
280
|
+
export async function _readTrailersForTransport(trailers) {
|
|
281
|
+
const fields = _unwrapTrailersResult(await trailers.read());
|
|
282
|
+
if (fields == null) {
|
|
283
|
+
return undefined;
|
|
284
|
+
}
|
|
285
|
+
if (fields instanceof Fields) {
|
|
286
|
+
return fields.copyAll();
|
|
287
|
+
}
|
|
288
|
+
if (Array.isArray(fields)) {
|
|
289
|
+
return fields;
|
|
290
|
+
}
|
|
291
|
+
throw new HttpError("invalid-argument", "trailers must be Fields or a list of entries");
|
|
292
|
+
}
|
|
293
|
+
export function _trailerResultFromEntries(entries) {
|
|
294
|
+
return {
|
|
295
|
+
tag: "ok",
|
|
296
|
+
val: entries && entries.length ? _fieldsFromEntriesChecked(entries) : null,
|
|
297
|
+
};
|
|
298
|
+
}
|
|
299
|
+
function _unwrapTrailersResult(result) {
|
|
300
|
+
if (result == null) {
|
|
301
|
+
return undefined;
|
|
302
|
+
}
|
|
303
|
+
if (result instanceof Fields || Array.isArray(result)) {
|
|
304
|
+
return result;
|
|
305
|
+
}
|
|
306
|
+
if (typeof result === "object") {
|
|
307
|
+
if (result.tag === "ok") {
|
|
308
|
+
return _unwrapTrailersResult(result.val);
|
|
309
|
+
}
|
|
310
|
+
if (result.tag === "some") {
|
|
311
|
+
return _unwrapTrailersResult(result.val);
|
|
312
|
+
}
|
|
313
|
+
if (result.tag === "none") {
|
|
314
|
+
return undefined;
|
|
315
|
+
}
|
|
316
|
+
if (result.tag === "err") {
|
|
317
|
+
throw HttpError.from(result.val);
|
|
318
|
+
}
|
|
319
|
+
if (Object.hasOwn(result, "ok")) {
|
|
320
|
+
return _unwrapTrailersResult(result.ok);
|
|
321
|
+
}
|
|
322
|
+
if (Object.hasOwn(result, "err")) {
|
|
323
|
+
throw HttpError.from(result.err);
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
return result;
|
|
327
|
+
}
|