@defjs/core 0.4.0 → 0.6.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/README.md +25 -71
- package/index.d.ts +387 -525
- package/index.js +742 -995
- package/index.min.js +20 -20
- package/package.json +1 -1
package/index.js
CHANGED
|
@@ -1,23 +1,3 @@
|
|
|
1
|
-
// src/client/command.ts
|
|
2
|
-
const COMMAND_TYPE = Symbol.for("defjs.command.type");
|
|
3
|
-
const HTTP_COMMAND = Symbol.for("defjs.command.http");
|
|
4
|
-
const EVENT_STREAM_COMMAND = Symbol.for("defjs.command.event-stream");
|
|
5
|
-
const WEB_SOCKET_COMMAND = Symbol.for("defjs.command.web-socket");
|
|
6
|
-
/** The command's own runner, so `client.execute` never imports a transport the app does not use. */
|
|
7
|
-
const COMMAND_EXECUTE = Symbol.for("defjs.command.execute");
|
|
8
|
-
function commandTypeOf(value) {
|
|
9
|
-
if (typeof value !== "object" || value === null || !(COMMAND_TYPE in value)) return;
|
|
10
|
-
return value[COMMAND_TYPE];
|
|
11
|
-
}
|
|
12
|
-
function isHttpCommand(value) {
|
|
13
|
-
return commandTypeOf(value) === HTTP_COMMAND;
|
|
14
|
-
}
|
|
15
|
-
function isEventStreamCommand(value) {
|
|
16
|
-
return commandTypeOf(value) === EVENT_STREAM_COMMAND;
|
|
17
|
-
}
|
|
18
|
-
function isWebSocketCommand(value) {
|
|
19
|
-
return commandTypeOf(value) === WEB_SOCKET_COMMAND;
|
|
20
|
-
}
|
|
21
1
|
// src/client/config.ts
|
|
22
2
|
const PLATFORM_FETCH = globalThis.fetch;
|
|
23
3
|
const PLATFORM_FETCH_HANDLE = PLATFORM_FETCH?.bind(globalThis);
|
|
@@ -61,149 +41,498 @@ function createClientConfig() {
|
|
|
61
41
|
};
|
|
62
42
|
}
|
|
63
43
|
// src/client/client.ts
|
|
44
|
+
/**
|
|
45
|
+
* Internal symbol key for a client's base configuration and request options.
|
|
46
|
+
*
|
|
47
|
+
* Application code passes the client to an operation. It does not call the client or read this property.
|
|
48
|
+
*
|
|
49
|
+
* @group client
|
|
50
|
+
*/
|
|
64
51
|
const CLIENT = Symbol("Client");
|
|
52
|
+
const KNOWN_FIELDS = [
|
|
53
|
+
"signal",
|
|
54
|
+
"onUploadProgress",
|
|
55
|
+
"onDownloadProgress",
|
|
56
|
+
"beforeConnect",
|
|
57
|
+
"protocols",
|
|
58
|
+
"heartbeat",
|
|
59
|
+
"reconnect"
|
|
60
|
+
];
|
|
61
|
+
function snapshot(patch) {
|
|
62
|
+
const out = {};
|
|
63
|
+
if (patch === void 0) return out;
|
|
64
|
+
for (const key of KNOWN_FIELDS) {
|
|
65
|
+
const value = patch[key];
|
|
66
|
+
if (value !== void 0) out[key] = value;
|
|
67
|
+
}
|
|
68
|
+
return out;
|
|
69
|
+
}
|
|
70
|
+
function createClientValue(config, options) {
|
|
71
|
+
return { [CLIENT]: {
|
|
72
|
+
config,
|
|
73
|
+
options
|
|
74
|
+
} };
|
|
75
|
+
}
|
|
65
76
|
/**
|
|
66
|
-
* Creates a Defjs
|
|
77
|
+
* Creates a Defjs {@link DerivableClient} from option helpers.
|
|
67
78
|
*
|
|
68
|
-
* Options apply in order: a later option overwrites an earlier one for the same setting, and interceptors accumulate. Prefer creating the client inside the request boundary when interceptors close over auth or tenants. Default HTTP/SSE fetch availability is checked when
|
|
79
|
+
* Options apply in order: a later option overwrites an earlier one for the same setting, and interceptors accumulate. Prefer creating the client inside the request boundary when interceptors close over auth or tenants. Default HTTP/SSE fetch availability is checked when an operation invokes transport.
|
|
80
|
+
*
|
|
81
|
+
* The returned factory is a function. Calling it with {@link RequestOptions} returns a {@link Client} value for one call and performs no I/O; see {@link DerivableClient} for the snapshot rules. The factory itself is also a `Client` and carries no request options, so it can be passed to an operation directly. The base configuration is shared by every client value and is not frozen at runtime. It is fixed by convention: nothing in the library changes it, and applications should not either.
|
|
69
82
|
*
|
|
70
83
|
* @param options - Client option helpers such as `withEndpoint`.
|
|
71
|
-
* @returns A
|
|
84
|
+
* @returns A callable factory that carries the resolved base configuration.
|
|
72
85
|
*
|
|
73
86
|
* @group client
|
|
87
|
+
* @see {@link DerivableClient}
|
|
74
88
|
* @see {@link Client}
|
|
75
89
|
* @see {@link ClientOption}
|
|
90
|
+
* @see {@link RequestOptions}
|
|
76
91
|
* @exampleFile examples/api/create-client.ts#example
|
|
77
92
|
*/
|
|
78
93
|
function createClient(...options) {
|
|
79
|
-
const
|
|
80
|
-
for (const option of options) option(
|
|
81
|
-
function
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
execute
|
|
94
|
+
const config = createClientConfig();
|
|
95
|
+
for (const option of options) option(config);
|
|
96
|
+
function factory(patch) {
|
|
97
|
+
return createClientValue(config, snapshot(patch));
|
|
98
|
+
}
|
|
99
|
+
factory[CLIENT] = {
|
|
100
|
+
config,
|
|
101
|
+
options: {}
|
|
88
102
|
};
|
|
103
|
+
return factory;
|
|
89
104
|
}
|
|
90
|
-
// src/
|
|
91
|
-
const DEFINITION = Symbol.for("defjs.struct.definition");
|
|
92
|
-
const OMIT = Symbol("defjs.struct.omit");
|
|
105
|
+
// src/client/option.ts
|
|
93
106
|
/**
|
|
94
|
-
*
|
|
95
|
-
*
|
|
107
|
+
* Sets the valid absolute base URL prepended to relative request paths.
|
|
108
|
+
*
|
|
109
|
+
* The default is an empty string, so execution requires an explicit endpoint. Its pathname is kept as a directory; its query and hash are discarded. For WebSocket operations, an `http:` base becomes `ws:` and `https:` becomes `wss:`.
|
|
110
|
+
*
|
|
111
|
+
* A missing, relative, or invalid endpoint, or an illegal request path, fails with `REQ_BUILD_FAILED` during building, before any interceptor or transport runs, even when an interceptor would short-circuit. HTTP, SSE, and WebSocket share this rule. The resolved request must keep the base origin and stay under its directory; C0 control characters (including tab, CR, and LF), backslashes, `?`, `#`, and directory escapes in a path are rejected. Scheme-like and protocol-relative forms are rejected even with leading spaces; a plain inner space is allowed and sent as `%20`. An interceptor's final `req.endpoint` is checked against its final `req.baseEndpoint` with the same rule, but it may deliberately replace the base with another valid absolute URL. Deterministic URL errors are never retried.
|
|
112
|
+
*
|
|
113
|
+
* @param endpoint - Absolute base endpoint. Missing or invalid endpoints fail request building.
|
|
114
|
+
* @returns An option that writes `config.endpoint`.
|
|
115
|
+
* @group client
|
|
96
116
|
*/
|
|
97
|
-
|
|
117
|
+
function withEndpoint(endpoint) {
|
|
118
|
+
return (config) => {
|
|
119
|
+
config.endpoint = endpoint;
|
|
120
|
+
};
|
|
121
|
+
}
|
|
98
122
|
/**
|
|
99
|
-
*
|
|
123
|
+
* Sets static default headers for HTTP and SSE requests built by this client.
|
|
100
124
|
*
|
|
101
|
-
*
|
|
125
|
+
* WebSocket connections do not use them. The `HeadersInit` is copied into a `Headers` object when the option is applied. A later call replaces the earlier headers instead of merging.
|
|
102
126
|
*
|
|
103
|
-
*
|
|
127
|
+
* Headers returned by a request builder replace a default of the same name. The first value for that name replaces the default; later values in the same array append only to that replacement. A scalar or a `Headers` instance also replaces. Other default names stay.
|
|
104
128
|
*
|
|
105
|
-
*
|
|
106
|
-
*
|
|
129
|
+
* Per-request or computed credentials, such as a refreshed Bearer token or an HMAC signature, belong in an interceptor, because they need the serialized body or a refreshed credential.
|
|
130
|
+
*
|
|
131
|
+
* @param headers - `HeadersInit` bag cloned onto the client config.
|
|
132
|
+
* @returns An option that writes `config.headers`.
|
|
133
|
+
* @group client
|
|
107
134
|
*/
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
"array",
|
|
113
|
-
"arrayBuffer",
|
|
114
|
-
"bigint",
|
|
115
|
-
"blob",
|
|
116
|
-
"boolean",
|
|
117
|
-
"date",
|
|
118
|
-
"discriminatedUnion",
|
|
119
|
-
"enum",
|
|
120
|
-
"file",
|
|
121
|
-
"intersection",
|
|
122
|
-
"literal",
|
|
123
|
-
"null",
|
|
124
|
-
"number",
|
|
125
|
-
"object",
|
|
126
|
-
"or",
|
|
127
|
-
"record",
|
|
128
|
-
"string",
|
|
129
|
-
"tuple",
|
|
130
|
-
"unknown"
|
|
131
|
-
]);
|
|
132
|
-
function isStruct(value) {
|
|
133
|
-
if (typeof value !== "object" || value === null || !Object.hasOwn(value, DEFINITION)) return false;
|
|
134
|
-
const definition = value[DEFINITION];
|
|
135
|
-
if (typeof definition !== "object" || definition === null) return false;
|
|
136
|
-
if (!KNOWN_KINDS.has(definition.kind)) return false;
|
|
137
|
-
const flags = definition.flags;
|
|
138
|
-
return typeof flags === "object" && flags !== null && typeof flags.nullable === "boolean" && typeof flags.optional === "boolean";
|
|
135
|
+
function withHeaders(headers) {
|
|
136
|
+
return (config) => {
|
|
137
|
+
config.headers = new Headers(headers);
|
|
138
|
+
};
|
|
139
139
|
}
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
140
|
+
/**
|
|
141
|
+
* Appends interceptors to the client interceptor chain.
|
|
142
|
+
*
|
|
143
|
+
* Calling it again appends more; it never replaces earlier ones. The first registered interceptor is the outermost layer. HTTP, SSE, and WebSocket interceptors can be mixed in one call. When an operation runs, only the interceptors of that transport run, in their registration order.
|
|
144
|
+
*
|
|
145
|
+
* @param interceptors - Interceptors applied in registration order.
|
|
146
|
+
* @returns An option that pushes onto `config.interceptors`.
|
|
147
|
+
* @group client
|
|
148
|
+
*/
|
|
149
|
+
function withInterceptors(...interceptors) {
|
|
150
|
+
return (config) => {
|
|
151
|
+
config.interceptors.push(...interceptors);
|
|
152
|
+
};
|
|
144
153
|
}
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
154
|
+
/**
|
|
155
|
+
* Replaces query encoding for HTTP, SSE, and WebSocket requests.
|
|
156
|
+
*
|
|
157
|
+
* `serializer(query)` receives the encoded wire object: aliases applied, `Date` as an ISO string, `bigint` as a string. Nested objects and arrays are kept as they are. It must return the query string without a leading `?`. The string is used verbatim.
|
|
158
|
+
*
|
|
159
|
+
* It is not called when the request has no query. A builder that returns `URLSearchParams` skips it. Without this option, queries use `URLSearchParams`: arrays repeat keys and a nested object fails.
|
|
160
|
+
*
|
|
161
|
+
* @param serializer - Function that turns an encoded query object into a string.
|
|
162
|
+
* @returns An option that sets `config.querySerializer`.
|
|
163
|
+
* @group client
|
|
164
|
+
*/
|
|
165
|
+
function withQuerySerializer(serializer) {
|
|
166
|
+
return (config) => {
|
|
167
|
+
config.querySerializer = serializer;
|
|
153
168
|
};
|
|
154
169
|
}
|
|
155
|
-
|
|
156
|
-
|
|
170
|
+
/**
|
|
171
|
+
* Replaces the Fetch-compatible function used for HTTP requests.
|
|
172
|
+
*
|
|
173
|
+
* The default is the platform `fetch`. `fetchImpl` is a {@link FetchHandle}, so a test double does not need host extras such as `fetch.preconnect`. It only affects HTTP. SSE uses {@link withSSEHandle}.
|
|
174
|
+
*
|
|
175
|
+
* @param fetchImpl - `(input, init?) => Promise<Response>`. Not `typeof fetch`, so
|
|
176
|
+
* a mock does not need host extras such as `preconnect`.
|
|
177
|
+
* @returns An option that sets `config.http.handle`.
|
|
178
|
+
* @group client
|
|
179
|
+
*/
|
|
180
|
+
function withHTTPHandle(fetchImpl) {
|
|
181
|
+
return (config) => {
|
|
182
|
+
config.http.handle = fetchImpl;
|
|
183
|
+
};
|
|
157
184
|
}
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
185
|
+
/**
|
|
186
|
+
* Replaces the Fetch-compatible function used to open SSE event streams.
|
|
187
|
+
*
|
|
188
|
+
* The default is the platform `fetch`. `fetchImpl` is a {@link FetchHandle}. It only affects SSE. HTTP uses {@link withHTTPHandle}.
|
|
189
|
+
*
|
|
190
|
+
* @param fetchImpl - `(input, init?) => Promise<Response>`. Not `typeof fetch`, so
|
|
191
|
+
* a mock does not need host extras such as `preconnect`.
|
|
192
|
+
* @returns An option that sets `config.sse.handle`.
|
|
193
|
+
* @group client
|
|
194
|
+
*/
|
|
195
|
+
function withSSEHandle(fetchImpl) {
|
|
196
|
+
return (config) => {
|
|
197
|
+
config.sse.handle = fetchImpl;
|
|
198
|
+
};
|
|
162
199
|
}
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
200
|
+
/**
|
|
201
|
+
* Replaces the WebSocket constructor used for socket connections.
|
|
202
|
+
*
|
|
203
|
+
* The default is `globalThis.WebSocket`. `WebSocketImpl` is a {@link WebSocketHandleConstructor} that returns a {@link WebSocketHandle}. If no constructor is available, a WebSocket operation fails with an `ENV_UNSUPPORTED` fault.
|
|
204
|
+
*
|
|
205
|
+
* @param WebSocketImpl - Constructor that returns a `WebSocketHandle`.
|
|
206
|
+
* @returns An option that sets `config.webSocket.handle`.
|
|
207
|
+
* @group client
|
|
208
|
+
*/
|
|
209
|
+
function withWebSocketHandle(WebSocketImpl) {
|
|
210
|
+
return (config) => {
|
|
211
|
+
config.webSocket.handle = WebSocketImpl;
|
|
212
|
+
};
|
|
169
213
|
}
|
|
170
|
-
/**
|
|
171
|
-
|
|
172
|
-
|
|
214
|
+
/**
|
|
215
|
+
* Runs a hook before the initial WebSocket connect and before every reconnect.
|
|
216
|
+
*
|
|
217
|
+
* The hook type is:
|
|
218
|
+
*
|
|
219
|
+
* ```ts
|
|
220
|
+
* (context: { attempt: number; signal: AbortSignal }) => void | Promise<void>
|
|
221
|
+
* ```
|
|
222
|
+
*
|
|
223
|
+
* - `attempt` is `0` for the first connect and grows by one for each reconnect.
|
|
224
|
+
* - Pass `signal` into token refreshes and other async work so cancelling the session stops them.
|
|
225
|
+
* - If the hook throws, the attempt fails with an `EXT_HOOK_FAILED` fault.
|
|
226
|
+
* - A `beforeConnect` in the request options replaces this one for that call.
|
|
227
|
+
*
|
|
228
|
+
* @param beforeConnect - Hook receiving attempt index and abort signal.
|
|
229
|
+
* @returns An option that sets `config.webSocket.beforeConnect`.
|
|
230
|
+
* @group client
|
|
231
|
+
*/
|
|
232
|
+
function withWebSocketBeforeConnect(beforeConnect) {
|
|
233
|
+
return (config) => {
|
|
234
|
+
config.webSocket.beforeConnect = beforeConnect;
|
|
235
|
+
};
|
|
173
236
|
}
|
|
174
237
|
/**
|
|
175
|
-
*
|
|
238
|
+
* Sets the WebSocket subprotocols offered during the handshake.
|
|
176
239
|
*
|
|
177
|
-
* `
|
|
178
|
-
* though the instances differ. Plain objects are copied onto a null prototype so `__proto__` stays
|
|
179
|
-
* an own data key. Anything else, including arrays of different lengths, is a conflict.
|
|
240
|
+
* They are passed as the second argument to the `WebSocket` constructor only when the list is not empty, so the platform can run the RFC 6455 subprotocol negotiation. They matter only when the server requires a specific subprotocol, for example `['json']` or `['v1']`.
|
|
180
241
|
*
|
|
181
|
-
*
|
|
182
|
-
*
|
|
183
|
-
*
|
|
184
|
-
*
|
|
242
|
+
* Precedence, highest first: the `protocols` request option, this client option, then `protocols` on the endpoint definition.
|
|
243
|
+
*
|
|
244
|
+
* @param protocols - Subprotocol list for the WebSocket constructor.
|
|
245
|
+
* @returns An option that sets `config.webSocket.protocols`.
|
|
246
|
+
* @group client
|
|
185
247
|
*/
|
|
186
|
-
function
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
value: left
|
|
248
|
+
function withWebSocketProtocols(protocols) {
|
|
249
|
+
return (config) => {
|
|
250
|
+
config.webSocket.protocols = protocols;
|
|
190
251
|
};
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
252
|
+
}
|
|
253
|
+
/**
|
|
254
|
+
* Enables WebSocket heartbeat messages with optional acknowledgement detection.
|
|
255
|
+
*
|
|
256
|
+
* Passing `undefined` clears an earlier setting. The option type is:
|
|
257
|
+
*
|
|
258
|
+
* ```ts
|
|
259
|
+
* {
|
|
260
|
+
* intervalMs: number
|
|
261
|
+
* isAck?: (message: unknown) => boolean
|
|
262
|
+
* message?: <T = unknown>() => T | unknown
|
|
263
|
+
* timeoutMs?: number
|
|
264
|
+
* }
|
|
265
|
+
* ```
|
|
266
|
+
*
|
|
267
|
+
* - Every `intervalMs`, `message()` is called and its result is sent through the endpoint's `outgoing` struct. No message is sent when `message` is omitted or returns `undefined`.
|
|
268
|
+
* - With `timeoutMs`, an acknowledgement is expected after each heartbeat. `isAck` receives the decoded incoming message; when it returns `true`, the timer is cleared and that message is **not** delivered to `receive`.
|
|
269
|
+
* - If no ack arrives within `timeoutMs`, or a heartbeat cannot be sent, the session fails. This does not trigger a reconnect.
|
|
270
|
+
* - Heartbeat uses the same JSON guard as `session.send`: a value JSON cannot represent after struct encoding causes `TypeError` and ends the logical session.
|
|
271
|
+
* - `intervalMs` and `timeoutMs` must be positive, finite, and at most `2_147_483_647`; otherwise the operation fails with `REQ_OPTIONS_INVALID`.
|
|
272
|
+
* - A `heartbeat` in the request options replaces this one for that call.
|
|
273
|
+
*
|
|
274
|
+
* @param options - Heartbeat interval, payload, timeout, and ACK predicate.
|
|
275
|
+
* @returns An option that sets `config.webSocket.heartbeat`.
|
|
276
|
+
* @group client
|
|
277
|
+
*/
|
|
278
|
+
function withWebSocketHeartbeat(options) {
|
|
279
|
+
return (config) => {
|
|
280
|
+
config.webSocket.heartbeat = options;
|
|
194
281
|
};
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
282
|
+
}
|
|
283
|
+
/**
|
|
284
|
+
* Configures automatic reconnection when the socket closes for any reason other than a manual `session.close()`.
|
|
285
|
+
*
|
|
286
|
+
* Reconnection is opt-in: without this option a close ends the session. Passing `undefined` clears an earlier setting. The option type is:
|
|
287
|
+
*
|
|
288
|
+
* ```ts
|
|
289
|
+
* {
|
|
290
|
+
* attempts?: number
|
|
291
|
+
* delayMs?: number
|
|
292
|
+
* factor?: number
|
|
293
|
+
* jitter?: number
|
|
294
|
+
* maxDelayMs?: number
|
|
295
|
+
* shouldReconnect?: (context: { attempt: number; cause?: unknown; code?: number; reason?: string; wasClean?: boolean }) => boolean
|
|
296
|
+
* }
|
|
297
|
+
* ```
|
|
298
|
+
*
|
|
299
|
+
* - `attempts` is the retry budget for each outage. It defaults to `3`, and `0` disables reconnection. Successful `open` resets the budget and backoff exponent; the next outage starts at retry 1. Omitting it does not give unlimited retries within one outage. A socket that successfully opens and is immediately closed again by the server can keep reconnecting at the base delay (subject to jitter and caps), because every open resets the budget; use `shouldReconnect` to stop it.
|
|
300
|
+
* - `delayMs` defaults to `1000`, `factor` to `2`, `maxDelayMs` to `30000`, and `jitter` (0 to 1) to `0`. Retry _n_ starts with `delayMs * factor^(n-1)`, capped at `maxDelayMs`; jitter and rounding follow, then the final delay is capped again at `maxDelayMs` and `2_147_483_647` ms. A zero base stays zero; valid-number overflow saturates at those caps.
|
|
301
|
+
* - `shouldReconnect` can return `false` to end the session instead. Without it every close is retried while the budget lasts. It is not called for a manual `session.close()`.
|
|
302
|
+
* - Reconnect options are validated before connecting: `attempts` must be a non-negative safe integer; `delayMs` must be finite and non-negative; `maxDelayMs` must be a non-negative finite number or positive `Infinity`, which removes the configured cap but not the platform limit of `2_147_483_647` ms; `factor` must be finite and positive; `jitter` must be finite and within 0–1. Invalid values make the operation fail with `REQ_OPTIONS_INVALID` before connecting. `attempts: 0` disables subsequent retries, but all explicitly supplied reconnect fields are still validated before the initial connection.
|
|
303
|
+
* - A `reconnect` in the request options replaces this one for that call.
|
|
304
|
+
*
|
|
305
|
+
* @param options - Retry budget, backoff, and `shouldReconnect` gate.
|
|
306
|
+
* @returns An option that sets `config.webSocket.reconnect`.
|
|
307
|
+
* @group client
|
|
308
|
+
*/
|
|
309
|
+
function withWebSocketReconnect(options) {
|
|
310
|
+
return (config) => {
|
|
311
|
+
config.webSocket.reconnect = options;
|
|
312
|
+
};
|
|
313
|
+
}
|
|
314
|
+
/**
|
|
315
|
+
* Observes SSE events that have no matching event struct or fail validation.
|
|
316
|
+
*
|
|
317
|
+
* The handler may be async and is awaited. The callback receives:
|
|
318
|
+
*
|
|
319
|
+
* ```ts
|
|
320
|
+
* {
|
|
321
|
+
* reason: 'missing-struct' | 'validation-failed'
|
|
322
|
+
* message: { id: string; event: string; data: string }
|
|
323
|
+
* cause?: unknown
|
|
324
|
+
* signal: AbortSignal
|
|
325
|
+
* }
|
|
326
|
+
* ```
|
|
327
|
+
*
|
|
328
|
+
* - The invalid event is **always dropped**; it is never delivered to the stream. The callback only observes it.
|
|
329
|
+
* - The return value is ignored. An error thrown by the callback is swallowed and does not close the stream, unless `signal` has already aborted.
|
|
330
|
+
* - `cause` is set for `validation-failed`. Without a handler the invalid event is dropped silently.
|
|
331
|
+
*
|
|
332
|
+
* @param handler - Observer callback for invalid events.
|
|
333
|
+
* @returns An option that sets `config.sse.onInvalidEvent`.
|
|
334
|
+
* @group client
|
|
335
|
+
*/
|
|
336
|
+
function withSSEOnInvalidEvent(handler) {
|
|
337
|
+
return (config) => {
|
|
338
|
+
config.sse.onInvalidEvent = handler;
|
|
339
|
+
};
|
|
340
|
+
}
|
|
341
|
+
/**
|
|
342
|
+
* Configures automatic reconnection after an SSE stream fails.
|
|
343
|
+
*
|
|
344
|
+
* Reconnection is opt-in: without this option a failed stream is not retried. Passing `undefined` clears an earlier setting.
|
|
345
|
+
*
|
|
346
|
+
* ```ts
|
|
347
|
+
* type SSEReconnect = {
|
|
348
|
+
* attempts?: number;
|
|
349
|
+
* delayMs?: number;
|
|
350
|
+
* factor?: number;
|
|
351
|
+
* jitter?: number;
|
|
352
|
+
* maxDelayMs?: number;
|
|
353
|
+
* shouldReconnect?: (context: {
|
|
354
|
+
* attempt: number;
|
|
355
|
+
* cause?: unknown;
|
|
356
|
+
* lastEventId: string;
|
|
357
|
+
* method: string;
|
|
358
|
+
* open?: {
|
|
359
|
+
* response: { status: number; statusText: string; url: string };
|
|
360
|
+
* url: string;
|
|
361
|
+
* };
|
|
362
|
+
* }) => boolean | Promise<boolean>;
|
|
363
|
+
* };
|
|
364
|
+
* ```
|
|
365
|
+
*
|
|
366
|
+
* - `attempts` is the retry budget for each outage. It defaults to `3` when `reconnect` is set, and `0` disables retries. A successful handshake resets the budget, callback `attempt`, and backoff exponent. Omitting it does not give unlimited retries within one outage. Repeated successful opens followed immediately by retryable stream failures can keep reconnecting at the current base delay (subject to jitter and caps), because each open resets the budget; use `shouldReconnect` to stop them. Normal EOF remains terminal.
|
|
367
|
+
* - Without `shouldReconnect`, only `GET`, `HEAD`, `OPTIONS`, `PUT`, and `DELETE` reconnect. With it, its result decides for every method, `POST` included; `method` in the context tells endpoints apart. Return `false` to stop for this failure. It runs before the attempts limit is checked.
|
|
368
|
+
* - A request with a `ReadableStream` body never reconnects, and `shouldReconnect` is not called for it.
|
|
369
|
+
* - `delayMs`, `factor`, `jitter` (0 to 1), and `maxDelayMs` shape the backoff. `factor` defaults to `1` and `jitter` to `0`. `maxDelayMs` caps the final delay after jitter and rounding. Without a configured cap, the platform timer limit of `2_147_483_647` ms still applies. A zero base stays zero; valid-number overflow saturates at the caps.
|
|
370
|
+
* - `delayMs` is only the starting base delay (`1000` by default). A server `retry:` field (digits only, in milliseconds) replaces it for later attempts.
|
|
371
|
+
* - The last event id is sent again as UTF-8 bytes in `Last-Event-ID` on the next attempt. An empty `id:` clears it; an ID containing NUL is ignored.
|
|
372
|
+
* - Reconnect options are validated before interceptors or Fetch: `attempts` must be a non-negative safe integer; `delayMs` must be finite and non-negative; `maxDelayMs` must be a non-negative finite number or positive `Infinity`, which removes the configured cap but not the platform limit of `2_147_483_647` ms; `factor` must be finite and positive; `jitter` must be finite and within 0–1. Invalid values return `REQ_OPTIONS_INVALID` when the operation runs.
|
|
373
|
+
*
|
|
374
|
+
* @param options - Retry budget, backoff, and `shouldReconnect` gate.
|
|
375
|
+
* @returns An option that sets `config.sse.reconnect`.
|
|
376
|
+
* @group client
|
|
377
|
+
*/
|
|
378
|
+
function withSSEReconnect(options) {
|
|
379
|
+
return (config) => {
|
|
380
|
+
config.sse.reconnect = options;
|
|
381
|
+
};
|
|
382
|
+
}
|
|
383
|
+
/**
|
|
384
|
+
* Enables XSRF cookie-to-header token forwarding for HTTP requests.
|
|
385
|
+
*
|
|
386
|
+
* SSE and WebSocket requests do not get the header. `cookieName` defaults to `XSRF-TOKEN` and `headerName` to `X-XSRF-TOKEN` when names are omitted.
|
|
387
|
+
*
|
|
388
|
+
* - The header is set only when all of these hold: the method is not `GET`, `HEAD`, `OPTIONS`, or `TRACE`; the request does not already have that header; the client has an endpoint; and, whenever `location` exists (Window or Worker), the resolved request URL has the same origin as `location.origin`. A cross-origin request does not call the provider.
|
|
389
|
+
* - Without `tokenProvider`, the token is read from `document.cookie` and URL-decoded only when `document` exists. Workers and servers need a provider to supply a token.
|
|
390
|
+
* - With `tokenProvider`, the cookie is never read. The provider receives the request, and its result is the token. An empty string, `null`, or `undefined` means no header.
|
|
391
|
+
*
|
|
392
|
+
* @param options - Cookie/header names and optional token provider.
|
|
393
|
+
* @returns An option that sets `config.xsrf`.
|
|
394
|
+
* @group client
|
|
395
|
+
*/
|
|
396
|
+
function withXSRF(options = {}) {
|
|
397
|
+
return (config) => {
|
|
398
|
+
config.xsrf = {
|
|
399
|
+
cookieName: options.cookieName ?? "XSRF-TOKEN",
|
|
400
|
+
headerName: options.headerName ?? "X-XSRF-TOKEN",
|
|
401
|
+
tokenProvider: options.tokenProvider
|
|
402
|
+
};
|
|
403
|
+
};
|
|
404
|
+
}
|
|
405
|
+
/**
|
|
406
|
+
* Sets whether HTTP and SSE requests include credentials.
|
|
407
|
+
*
|
|
408
|
+
* When `value` is `true`, Fetch uses `credentials: 'include'`, so cookies and other credentials are sent with cross-origin requests. When it is `false` or never set, `credentials` is left unset and Fetch uses its default. The WebSocket constructor does not receive this setting.
|
|
409
|
+
*
|
|
410
|
+
* @param value - When `true`, credentials are sent with cross-origin requests.
|
|
411
|
+
* @returns An option that sets `config.withCredentials`.
|
|
412
|
+
* @group client
|
|
413
|
+
*/
|
|
414
|
+
function withCredentials(value) {
|
|
415
|
+
return (config) => {
|
|
416
|
+
config.withCredentials = value;
|
|
417
|
+
};
|
|
418
|
+
}
|
|
419
|
+
// src/struct/symbols.ts
|
|
420
|
+
const DEFINITION = Symbol.for("defjs.struct.definition");
|
|
421
|
+
const OMIT = Symbol("defjs.struct.omit");
|
|
422
|
+
/**
|
|
423
|
+
* Stands in for a parsed value when parsing fails; the issue waits in the slot that `failure()` fills.
|
|
424
|
+
* Not in the symbol registry, so no input value can ever equal it.
|
|
425
|
+
*/
|
|
426
|
+
const FAILED = Symbol("defjs.struct.failed");
|
|
427
|
+
/**
|
|
428
|
+
* Type-only export of the internal enumerable tag key on objects produced while parsing endpoint input, used by the outbound encoder.
|
|
429
|
+
*
|
|
430
|
+
* `STRUCT` is exported as a **type** only (`export type { STRUCT }`). `@defjs/core` has no runtime `STRUCT` export, so `import { STRUCT } from '@defjs/core'` fails to compile and gives no value.
|
|
431
|
+
*
|
|
432
|
+
* It marks those objects so the outbound encoder knows which struct produced them. Response decoding and `struct.parse` leave it unset. The symbol is internal to the outbound encoder; application code should not depend on it.
|
|
433
|
+
*
|
|
434
|
+
* @internal
|
|
435
|
+
* @group struct
|
|
436
|
+
*/
|
|
437
|
+
const STRUCT = Symbol.for("defjs.struct.STRUCT");
|
|
438
|
+
// src/struct/guards.ts
|
|
439
|
+
const KNOWN_KINDS = /* @__PURE__ */ new Set([
|
|
440
|
+
"any",
|
|
441
|
+
"array",
|
|
442
|
+
"arrayBuffer",
|
|
443
|
+
"bigint",
|
|
444
|
+
"blob",
|
|
445
|
+
"boolean",
|
|
446
|
+
"date",
|
|
447
|
+
"discriminatedUnion",
|
|
448
|
+
"enum",
|
|
449
|
+
"file",
|
|
450
|
+
"intersection",
|
|
451
|
+
"literal",
|
|
452
|
+
"null",
|
|
453
|
+
"number",
|
|
454
|
+
"object",
|
|
455
|
+
"or",
|
|
456
|
+
"record",
|
|
457
|
+
"string",
|
|
458
|
+
"tuple",
|
|
459
|
+
"unknown"
|
|
460
|
+
]);
|
|
461
|
+
function isStruct(value) {
|
|
462
|
+
if (typeof value !== "object" || value === null || !Object.hasOwn(value, DEFINITION)) return false;
|
|
463
|
+
const definition = value[DEFINITION];
|
|
464
|
+
if (typeof definition !== "object" || definition === null) return false;
|
|
465
|
+
if (!KNOWN_KINDS.has(definition.kind)) return false;
|
|
466
|
+
const flags = definition.flags;
|
|
467
|
+
return typeof flags === "object" && flags !== null && typeof flags.nullable === "boolean" && typeof flags.optional === "boolean";
|
|
468
|
+
}
|
|
469
|
+
// src/struct/guide.ts
|
|
470
|
+
const GUIDE_SET = Symbol.for("defjs.struct.guideSet");
|
|
471
|
+
function isGuideSet(value) {
|
|
472
|
+
return typeof value === "object" && value !== null && value[GUIDE_SET] === true;
|
|
473
|
+
}
|
|
474
|
+
function guideTag(members) {
|
|
475
|
+
const unique = [];
|
|
476
|
+
for (const member of members) if (!unique.includes(member)) unique.push(member);
|
|
477
|
+
if (unique.length === 0) return;
|
|
478
|
+
if (unique.length === 1) return unique[0];
|
|
479
|
+
return {
|
|
480
|
+
[GUIDE_SET]: true,
|
|
481
|
+
members: unique
|
|
482
|
+
};
|
|
483
|
+
}
|
|
484
|
+
function guideMembers(guide) {
|
|
485
|
+
return isGuideSet(guide) ? guide.members : [guide];
|
|
486
|
+
}
|
|
487
|
+
function combineGuides(left, right) {
|
|
488
|
+
if (!left) return right;
|
|
489
|
+
if (!right) return left;
|
|
490
|
+
return guideTag([...guideMembers(left), ...guideMembers(right)]);
|
|
491
|
+
}
|
|
492
|
+
function readOwnGuide(value, guides) {
|
|
493
|
+
const tag = guides?.get(value) ?? value[STRUCT];
|
|
494
|
+
if (isGuideSet(tag)) return tag;
|
|
495
|
+
if (!isStruct(tag)) return;
|
|
496
|
+
const kind = tag[DEFINITION].kind;
|
|
497
|
+
if (kind === "object" || kind === "record") return tag;
|
|
498
|
+
}
|
|
499
|
+
/** Tag a null-prototype node this parse just created. Enumerable so object spread keeps its guide. */
|
|
500
|
+
function tagOwnNode(value, tag) {
|
|
501
|
+
value[STRUCT] = tag;
|
|
502
|
+
}
|
|
503
|
+
/**
|
|
504
|
+
* Deep-merge two intersection outputs.
|
|
505
|
+
*
|
|
506
|
+
* `Object.is` keeps NaN and distinguishes signed zeroes. Equal-time Dates are one instant even
|
|
507
|
+
* though the instances differ. Plain objects are copied onto a null prototype so `__proto__` stays
|
|
508
|
+
* an own data key. Anything else, including arrays of different lengths, is a conflict.
|
|
509
|
+
*
|
|
510
|
+
* `tagNodes` folds each side's parse tag into the new object at this level. One-sided children are
|
|
511
|
+
* copied with the tag their own parser already wrote, so a later walk is not required. Null and
|
|
512
|
+
* undefined never enter this branch. `depth` is the caller's logical path length, not a fresh merge budget.
|
|
513
|
+
* Distinct containers consume a level even when they came from opaque leaves; identical references do not recurse.
|
|
514
|
+
*/
|
|
515
|
+
function mergeIntersectionValues(left, right, tagNodes, depth, context) {
|
|
516
|
+
if (Object.is(left, right)) return {
|
|
517
|
+
ok: true,
|
|
518
|
+
value: left
|
|
519
|
+
};
|
|
520
|
+
if (left instanceof Date && right instanceof Date && left.getTime() === right.getTime()) return {
|
|
521
|
+
ok: true,
|
|
522
|
+
value: left
|
|
523
|
+
};
|
|
524
|
+
if (isPlainObject(left) && isPlainObject(right)) {
|
|
525
|
+
if (depth >= 256) return {
|
|
526
|
+
ok: false,
|
|
527
|
+
path: [],
|
|
528
|
+
received: right
|
|
529
|
+
};
|
|
530
|
+
const output = nullObject();
|
|
531
|
+
for (const key of Object.keys(left)) output[key] = left[key];
|
|
532
|
+
for (const key of Object.keys(right)) {
|
|
533
|
+
if (!hasOwnKey(output, key)) {
|
|
534
|
+
output[key] = right[key];
|
|
535
|
+
continue;
|
|
207
536
|
}
|
|
208
537
|
const merged = mergeIntersectionValues(output[key], right[key], tagNodes, depth + 1, context);
|
|
209
538
|
if (!merged.ok) return {
|
|
@@ -452,7 +781,7 @@ function appendRecordToHeaders(headers, value) {
|
|
|
452
781
|
/**
|
|
453
782
|
* Resolves a relative definition path under the base endpoint's origin and directory into a URL.
|
|
454
783
|
*
|
|
455
|
-
* During
|
|
784
|
+
* During an operation, a missing, relative, or invalid base or an illegal
|
|
456
785
|
* path becomes `REQ_BUILD_FAILED` before any interceptor or transport runs,
|
|
457
786
|
* including a short-circuiting interceptor, for HTTP, SSE, and WebSocket alike.
|
|
458
787
|
* An interceptor's final `req.endpoint` is also validated against its final
|
|
@@ -774,527 +1103,32 @@ function createPreflightFault(code, cause, response) {
|
|
|
774
1103
|
/**
|
|
775
1104
|
* Builds a `NET_*` `PreflightFault`, choosing the code from the shape of `cause`.
|
|
776
1105
|
*
|
|
777
|
-
* Optional `response` metadata is attached when the transport already had some
|
|
778
|
-
* to report. The cause is kept as `cause`.
|
|
779
|
-
*
|
|
780
|
-
* - `NET_ABORTED` when {@link isAbortCause} recognizes an abort-branded `Error`
|
|
781
|
-
* (including `ERR_ABORTED` from another Core copy) or a `DOMException` named
|
|
782
|
-
* `AbortError`. The message is `Request was aborted`.
|
|
783
|
-
* - `NET_TIMEOUT` when {@link isTimeoutCause} recognizes a timeout-branded
|
|
784
|
-
* `Error` (including `ERR_TIMEOUT` from another Core copy) or a `DOMException`
|
|
785
|
-
* or `Error` named `TimeoutError`. The message is the cause's message, or
|
|
786
|
-
* `Request timed out` when that is empty.
|
|
787
|
-
* - `NET_UNREACHABLE` for anything else, with the cause's message.
|
|
788
|
-
*
|
|
789
|
-
* @param cause - Underlying cancellation, timeout, or transport failure.
|
|
790
|
-
* @param response - Metadata, only where the transport already had some to report.
|
|
791
|
-
* @returns A `NET_ABORTED`, `NET_TIMEOUT`, or `NET_UNREACHABLE` fault.
|
|
792
|
-
* @group errors
|
|
793
|
-
*/
|
|
794
|
-
function createNetworkFault(cause, response) {
|
|
795
|
-
if (isAbortCause(cause)) return withResponse(createFault(ERR_ABORTED.message, cause), "NET_ABORTED", response);
|
|
796
|
-
if (isTimeoutCause(cause)) return withResponse(createFault(cause instanceof Error && cause.message ? cause.message : ERR_TIMEOUT.message, cause), "NET_TIMEOUT", response);
|
|
797
|
-
return withResponse(createFault(describeCause(cause), cause), "NET_UNREACHABLE", response);
|
|
798
|
-
}
|
|
799
|
-
function withResponse(fault, code, response) {
|
|
800
|
-
fault.code = code;
|
|
801
|
-
if (response !== void 0) fault.response = response;
|
|
802
|
-
return fault;
|
|
803
|
-
}
|
|
804
|
-
// src/internal/abort.ts
|
|
805
|
-
const ABORT_TIMEOUT_CONFLICT_MESSAGE = "abort and timeout cannot be used together";
|
|
806
|
-
const MAX_TIMER_DELAY_MS$2 = 2147483647;
|
|
807
|
-
function snapshotCancellationConfig(config) {
|
|
808
|
-
return {
|
|
809
|
-
abort: config.abort,
|
|
810
|
-
signal: config.signal,
|
|
811
|
-
timeout: config.timeout
|
|
812
|
-
};
|
|
813
|
-
}
|
|
814
|
-
function hasAbortTimeoutConflict(config) {
|
|
815
|
-
return config !== void 0 && config.abort !== void 0 && config.timeout !== void 0;
|
|
816
|
-
}
|
|
817
|
-
/** The cancellation cause for an already-aborted signal, or `undefined` while it is still live. */
|
|
818
|
-
function resolveAbortCause(signal) {
|
|
819
|
-
if (!signal.aborted) return;
|
|
820
|
-
return resolveAbortedCause(signal);
|
|
821
|
-
}
|
|
822
|
-
/** `ERR_TIMEOUT` or `ERR_ABORTED`, chosen from the signal's abort reason, including another copy's timeout sentinel. */
|
|
823
|
-
function resolveAbortedCause(signal) {
|
|
824
|
-
return isTimeoutCause(signal.reason) ? ERR_TIMEOUT : ERR_ABORTED;
|
|
825
|
-
}
|
|
826
|
-
/** `REQ_OPTIONS_INVALID` for execute options that pass both `abort` and `timeout`. */
|
|
827
|
-
function createAbortTimeoutConflictFault() {
|
|
828
|
-
return createPreflightFault("REQ_OPTIONS_INVALID", /* @__PURE__ */ new Error(ABORT_TIMEOUT_CONFLICT_MESSAGE));
|
|
829
|
-
}
|
|
830
|
-
/** The cancellation fault for an already-aborted signal, or `undefined` while it is still live. */
|
|
831
|
-
function resolveAbortFault(signal) {
|
|
832
|
-
if (!signal.aborted) return;
|
|
833
|
-
return resolveAbortedFault(signal);
|
|
834
|
-
}
|
|
835
|
-
/** `NET_ABORTED` or `NET_TIMEOUT`, chosen from the signal's abort reason. */
|
|
836
|
-
function resolveAbortedFault(signal) {
|
|
837
|
-
return createNetworkFault(resolveAbortedCause(signal));
|
|
838
|
-
}
|
|
839
|
-
function validateTransportTimeout(timeout) {
|
|
840
|
-
if (typeof timeout !== "undefined" && (!Number.isSafeInteger(timeout) || timeout <= 0 || timeout > MAX_TIMER_DELAY_MS$2)) throw new RangeError(`Request timeout must be a positive safe integer no greater than ${MAX_TIMER_DELAY_MS$2}`);
|
|
841
|
-
}
|
|
842
|
-
async function awaitWithSignal(run, signal) {
|
|
843
|
-
signal.throwIfAborted();
|
|
844
|
-
let rejectAbort;
|
|
845
|
-
const aborted = new Promise((_resolve, reject) => {
|
|
846
|
-
rejectAbort = reject;
|
|
847
|
-
});
|
|
848
|
-
const onAbort = () => rejectAbort(signal.reason);
|
|
849
|
-
signal.addEventListener("abort", onAbort, { once: true });
|
|
850
|
-
const task = Promise.resolve().then(() => {
|
|
851
|
-
signal.throwIfAborted();
|
|
852
|
-
return run();
|
|
853
|
-
});
|
|
854
|
-
task.catch(() => void 0);
|
|
855
|
-
try {
|
|
856
|
-
const value = await Promise.race([task, aborted]);
|
|
857
|
-
signal.throwIfAborted();
|
|
858
|
-
return value;
|
|
859
|
-
} finally {
|
|
860
|
-
signal.removeEventListener("abort", onAbort);
|
|
861
|
-
}
|
|
862
|
-
}
|
|
863
|
-
/**
|
|
864
|
-
* Arm `timeout` before synchronous parse and builder work.
|
|
865
|
-
*
|
|
866
|
-
* A timer task cannot run until that work yields. `expiredFault` also watches
|
|
867
|
-
* the wall clock so a microtask transport cannot succeed after the deadline.
|
|
868
|
-
* `release` clears the timer and makes a later wall-clock check a no-op, so a
|
|
869
|
-
* settled startup can no longer abort the request signal.
|
|
870
|
-
*/
|
|
871
|
-
function armTransportDeadline(timeout) {
|
|
872
|
-
if (typeof timeout !== "number") return {
|
|
873
|
-
expiredFault: () => void 0,
|
|
874
|
-
release: () => void 0,
|
|
875
|
-
releaseOnAbort: () => void 0,
|
|
876
|
-
signal: void 0
|
|
877
|
-
};
|
|
878
|
-
const startedAt = Date.now();
|
|
879
|
-
const controller = new AbortController();
|
|
880
|
-
let released = false;
|
|
881
|
-
let watched;
|
|
882
|
-
const timer = setTimeout(() => {
|
|
883
|
-
controller.abort(ERR_TIMEOUT);
|
|
884
|
-
}, timeout);
|
|
885
|
-
const release = () => {
|
|
886
|
-
if (released) return;
|
|
887
|
-
released = true;
|
|
888
|
-
clearTimeout(timer);
|
|
889
|
-
watched?.removeEventListener("abort", release);
|
|
890
|
-
};
|
|
891
|
-
return {
|
|
892
|
-
release,
|
|
893
|
-
releaseOnAbort(signal) {
|
|
894
|
-
watched = signal;
|
|
895
|
-
signal.addEventListener("abort", release, { once: true });
|
|
896
|
-
},
|
|
897
|
-
signal: controller.signal,
|
|
898
|
-
expiredFault() {
|
|
899
|
-
if (released || Date.now() - startedAt < timeout) return;
|
|
900
|
-
controller.abort(ERR_TIMEOUT);
|
|
901
|
-
return resolveAbortedFault(controller.signal);
|
|
902
|
-
}
|
|
903
|
-
};
|
|
904
|
-
}
|
|
905
|
-
function resolveTransportCancellation(deadline, signal, fallback) {
|
|
906
|
-
return resolveAbortFault(signal) ?? deadline.expiredFault() ?? fallback;
|
|
907
|
-
}
|
|
908
|
-
const forwardingSignals = /* @__PURE__ */ new WeakMap();
|
|
909
|
-
/** Release listeners owned by a signal returned from `mergeAbortSignals`. */
|
|
910
|
-
function releaseAbortSignal(signal) {
|
|
911
|
-
forwardingSignals.get(signal)?.release();
|
|
912
|
-
}
|
|
913
|
-
function mergeAbortSignals(controller, signals, timeout) {
|
|
914
|
-
validateTransportTimeout(timeout);
|
|
915
|
-
const hasTimeout = typeof timeout === "number";
|
|
916
|
-
if (!hasTimeout && signals.length > 0 && signals.every((signal) => signal === void 0 || signal === controller)) return controller;
|
|
917
|
-
const merged = [];
|
|
918
|
-
const seen = /* @__PURE__ */ new Set();
|
|
919
|
-
const append = (signal) => {
|
|
920
|
-
const state = forwardingSignals.get(signal);
|
|
921
|
-
if (state?.active) {
|
|
922
|
-
for (const source of state.sources) append(source);
|
|
923
|
-
return;
|
|
924
|
-
}
|
|
925
|
-
if (!seen.has(signal)) {
|
|
926
|
-
seen.add(signal);
|
|
927
|
-
merged.push(signal);
|
|
928
|
-
}
|
|
929
|
-
};
|
|
930
|
-
append(controller);
|
|
931
|
-
for (const signal of signals) if (signal) append(signal);
|
|
932
|
-
if (!hasTimeout && merged.length === 1) return merged[0];
|
|
933
|
-
let timeoutTimer;
|
|
934
|
-
const combinedController = new AbortController();
|
|
935
|
-
const listeners = [];
|
|
936
|
-
let abortWithReason;
|
|
937
|
-
const state = {
|
|
938
|
-
active: true,
|
|
939
|
-
release() {
|
|
940
|
-
if (!state.active) return;
|
|
941
|
-
state.active = false;
|
|
942
|
-
for (const [source, listener] of listeners) source.removeEventListener("abort", listener);
|
|
943
|
-
if (timeoutTimer !== void 0) clearTimeout(timeoutTimer);
|
|
944
|
-
},
|
|
945
|
-
sources: merged
|
|
946
|
-
};
|
|
947
|
-
forwardingSignals.set(combinedController.signal, state);
|
|
948
|
-
abortWithReason = (reason) => {
|
|
949
|
-
if (combinedController.signal.aborted) return;
|
|
950
|
-
combinedController.abort(reason);
|
|
951
|
-
state.release();
|
|
952
|
-
};
|
|
953
|
-
if (hasTimeout) timeoutTimer = setTimeout(() => {
|
|
954
|
-
abortWithReason(ERR_TIMEOUT);
|
|
955
|
-
}, timeout);
|
|
956
|
-
const alreadyAborted = merged.find((signal) => signal.aborted);
|
|
957
|
-
if (alreadyAborted) {
|
|
958
|
-
abortWithReason(alreadyAborted.reason);
|
|
959
|
-
return combinedController.signal;
|
|
960
|
-
}
|
|
961
|
-
for (const source of merged) {
|
|
962
|
-
const listener = () => abortWithReason(source.reason);
|
|
963
|
-
listeners.push([source, listener]);
|
|
964
|
-
source.addEventListener("abort", listener, { once: true });
|
|
965
|
-
}
|
|
966
|
-
return combinedController.signal;
|
|
967
|
-
}
|
|
968
|
-
// src/client/option.ts
|
|
969
|
-
/**
|
|
970
|
-
* Sets the valid absolute base URL prepended to relative request paths.
|
|
971
|
-
*
|
|
972
|
-
* The default is an empty string, so execution requires an explicit endpoint. Its pathname is kept as a directory; its query and hash are discarded. For WebSocket commands, an `http:` base becomes `ws:` and `https:` becomes `wss:`.
|
|
973
|
-
*
|
|
974
|
-
* A missing, relative, or invalid endpoint, or an illegal request path, fails with `REQ_BUILD_FAILED` during building, before any interceptor or transport runs, even when an interceptor would short-circuit. HTTP, SSE, and WebSocket share this rule. The resolved request must keep the base origin and stay under its directory; C0 control characters (including tab, CR, and LF), backslashes, `?`, `#`, and directory escapes in a path are rejected. Scheme-like and protocol-relative forms are rejected even with leading spaces; a plain inner space is allowed and sent as `%20`. An interceptor's final `req.endpoint` is checked against its final `req.baseEndpoint` with the same rule, but it may deliberately replace the base with another valid absolute URL. Deterministic URL errors are never retried.
|
|
975
|
-
*
|
|
976
|
-
* @param endpoint - Absolute base endpoint. Missing or invalid endpoints fail request building.
|
|
977
|
-
* @returns An option that writes `config.endpoint`.
|
|
978
|
-
* @group client
|
|
979
|
-
*/
|
|
980
|
-
function withEndpoint(endpoint) {
|
|
981
|
-
return (config) => {
|
|
982
|
-
config.endpoint = endpoint;
|
|
983
|
-
};
|
|
984
|
-
}
|
|
985
|
-
/**
|
|
986
|
-
* Sets static default headers for HTTP and SSE requests built by this client.
|
|
987
|
-
*
|
|
988
|
-
* WebSocket connections do not use them. The `HeadersInit` is copied into a `Headers` object when the option is applied. A later call replaces the earlier headers instead of merging.
|
|
989
|
-
*
|
|
990
|
-
* Headers returned by a request builder replace a default of the same name. The first value for that name replaces the default; later values in the same array append only to that replacement. A scalar or a `Headers` instance also replaces. Other default names stay.
|
|
991
|
-
*
|
|
992
|
-
* Per-request or computed credentials, such as a refreshed Bearer token or an HMAC signature, belong in an interceptor, because they need the serialized body or a refreshed credential.
|
|
993
|
-
*
|
|
994
|
-
* @param headers - `HeadersInit` bag cloned onto the client config.
|
|
995
|
-
* @returns An option that writes `config.headers`.
|
|
996
|
-
* @group client
|
|
997
|
-
*/
|
|
998
|
-
function withHeaders(headers) {
|
|
999
|
-
return (config) => {
|
|
1000
|
-
config.headers = new Headers(headers);
|
|
1001
|
-
};
|
|
1002
|
-
}
|
|
1003
|
-
/**
|
|
1004
|
-
* Sets the default timeout, in milliseconds, for HTTP `execute` only.
|
|
1005
|
-
*
|
|
1006
|
-
* SSE and WebSocket ignore it. `ms` must be a positive safe integer no greater than `2_147_483_647`. Anything else throws `RangeError` immediately, when `withTimeout` is called, not when the client executes.
|
|
1007
|
-
*
|
|
1008
|
-
* The timer starts before the interceptor chain runs. When it fires, the call returns a `NET_TIMEOUT` fault. Execute `{ timeout }` replaces this default for that call. Execute `{ abort }` combines with the default: an abort gives `NET_ABORTED`, the timer gives `NET_TIMEOUT`. Passing both `abort` and `timeout` to the same `execute` call is still rejected.
|
|
1009
|
-
*
|
|
1010
|
-
* @param ms - Positive safe integer timeout in milliseconds.
|
|
1011
|
-
* @returns An option that writes `config.timeout`.
|
|
1012
|
-
* @group client
|
|
1013
|
-
*/
|
|
1014
|
-
function withTimeout(ms) {
|
|
1015
|
-
validateTransportTimeout(ms);
|
|
1016
|
-
return (config) => {
|
|
1017
|
-
config.timeout = ms;
|
|
1018
|
-
};
|
|
1019
|
-
}
|
|
1020
|
-
/**
|
|
1021
|
-
* Appends interceptors to the client interceptor chain.
|
|
1022
|
-
*
|
|
1023
|
-
* Calling it again appends more; it never replaces earlier ones. The first registered interceptor is the outermost layer. HTTP, SSE, and WebSocket interceptors can be mixed in one call. At execute time only the interceptors of the command's transport run, in their registration order.
|
|
1024
|
-
*
|
|
1025
|
-
* @param interceptors - Interceptors applied in registration order.
|
|
1026
|
-
* @returns An option that pushes onto `config.interceptors`.
|
|
1027
|
-
* @group client
|
|
1028
|
-
*/
|
|
1029
|
-
function withInterceptors(...interceptors) {
|
|
1030
|
-
return (config) => {
|
|
1031
|
-
config.interceptors.push(...interceptors);
|
|
1032
|
-
};
|
|
1033
|
-
}
|
|
1034
|
-
/**
|
|
1035
|
-
* Replaces query encoding for HTTP, SSE, and WebSocket requests.
|
|
1036
|
-
*
|
|
1037
|
-
* `serializer(query)` receives the encoded wire object: aliases applied, `Date` as an ISO string, `bigint` as a string. Nested objects and arrays are kept as they are. It must return the query string without a leading `?`. The string is used verbatim.
|
|
1038
|
-
*
|
|
1039
|
-
* It is not called when the request has no query. A builder that returns `URLSearchParams` skips it. Without this option, queries use `URLSearchParams`: arrays repeat keys and a nested object fails.
|
|
1040
|
-
*
|
|
1041
|
-
* @param serializer - Function that turns an encoded query object into a string.
|
|
1042
|
-
* @returns An option that sets `config.querySerializer`.
|
|
1043
|
-
* @group client
|
|
1044
|
-
*/
|
|
1045
|
-
function withQuerySerializer(serializer) {
|
|
1046
|
-
return (config) => {
|
|
1047
|
-
config.querySerializer = serializer;
|
|
1048
|
-
};
|
|
1049
|
-
}
|
|
1050
|
-
/**
|
|
1051
|
-
* Replaces the Fetch-compatible function used for HTTP requests.
|
|
1052
|
-
*
|
|
1053
|
-
* The default is the platform `fetch`. `fetchImpl` is a {@link FetchHandle}, so a test double does not need host extras such as `fetch.preconnect`. It only affects HTTP. SSE uses {@link withSSEHandle}.
|
|
1054
|
-
*
|
|
1055
|
-
* @param fetchImpl - `(input, init?) => Promise<Response>`. Not `typeof fetch`, so
|
|
1056
|
-
* a mock does not need host extras such as `preconnect`.
|
|
1057
|
-
* @returns An option that sets `config.http.handle`.
|
|
1058
|
-
* @group client
|
|
1059
|
-
*/
|
|
1060
|
-
function withHTTPHandle(fetchImpl) {
|
|
1061
|
-
return (config) => {
|
|
1062
|
-
config.http.handle = fetchImpl;
|
|
1063
|
-
};
|
|
1064
|
-
}
|
|
1065
|
-
/**
|
|
1066
|
-
* Replaces the Fetch-compatible function used to open SSE event streams.
|
|
1067
|
-
*
|
|
1068
|
-
* The default is the platform `fetch`. `fetchImpl` is a {@link FetchHandle}. It only affects SSE. HTTP uses {@link withHTTPHandle}.
|
|
1069
|
-
*
|
|
1070
|
-
* @param fetchImpl - `(input, init?) => Promise<Response>`. Not `typeof fetch`, so
|
|
1071
|
-
* a mock does not need host extras such as `preconnect`.
|
|
1072
|
-
* @returns An option that sets `config.sse.handle`.
|
|
1073
|
-
* @group client
|
|
1074
|
-
*/
|
|
1075
|
-
function withSSEHandle(fetchImpl) {
|
|
1076
|
-
return (config) => {
|
|
1077
|
-
config.sse.handle = fetchImpl;
|
|
1078
|
-
};
|
|
1079
|
-
}
|
|
1080
|
-
/**
|
|
1081
|
-
* Replaces the WebSocket constructor used for socket connections.
|
|
1082
|
-
*
|
|
1083
|
-
* The default is `globalThis.WebSocket`. `WebSocketImpl` is a {@link WebSocketHandleConstructor} that returns a {@link WebSocketHandle}. If no constructor is available, executing a WebSocket command fails with an `ENV_UNSUPPORTED` fault.
|
|
1084
|
-
*
|
|
1085
|
-
* @param WebSocketImpl - Constructor that returns a `WebSocketHandle`.
|
|
1086
|
-
* @returns An option that sets `config.webSocket.handle`.
|
|
1087
|
-
* @group client
|
|
1088
|
-
*/
|
|
1089
|
-
function withWebSocketHandle(WebSocketImpl) {
|
|
1090
|
-
return (config) => {
|
|
1091
|
-
config.webSocket.handle = WebSocketImpl;
|
|
1092
|
-
};
|
|
1093
|
-
}
|
|
1094
|
-
/**
|
|
1095
|
-
* Runs a hook before the initial WebSocket connect and before every reconnect.
|
|
1096
|
-
*
|
|
1097
|
-
* The hook type is:
|
|
1098
|
-
*
|
|
1099
|
-
* ```ts
|
|
1100
|
-
* (context: { attempt: number; signal: AbortSignal }) => void | Promise<void>
|
|
1101
|
-
* ```
|
|
1102
|
-
*
|
|
1103
|
-
* - `attempt` is `0` for the first connect and grows by one for each reconnect.
|
|
1104
|
-
* - Pass `signal` into token refreshes and other async work so cancelling the session stops them.
|
|
1105
|
-
* - If the hook throws, the attempt fails with an `EXT_HOOK_FAILED` fault.
|
|
1106
|
-
* - A `beforeConnect` in the execute options replaces this one for that call.
|
|
1107
|
-
*
|
|
1108
|
-
* @param beforeConnect - Hook receiving attempt index and abort signal.
|
|
1109
|
-
* @returns An option that sets `config.webSocket.beforeConnect`.
|
|
1110
|
-
* @group client
|
|
1111
|
-
*/
|
|
1112
|
-
function withWebSocketBeforeConnect(beforeConnect) {
|
|
1113
|
-
return (config) => {
|
|
1114
|
-
config.webSocket.beforeConnect = beforeConnect;
|
|
1115
|
-
};
|
|
1116
|
-
}
|
|
1117
|
-
/**
|
|
1118
|
-
* Sets the WebSocket subprotocols offered during the handshake.
|
|
1119
|
-
*
|
|
1120
|
-
* They are passed as the second argument to the `WebSocket` constructor only when the list is not empty, so the platform can run the RFC 6455 subprotocol negotiation. They matter only when the server requires a specific subprotocol, for example `['json']` or `['v1']`.
|
|
1121
|
-
*
|
|
1122
|
-
* Precedence, highest first: the `protocols` execute option, this client option, then `protocols` on the endpoint definition.
|
|
1123
|
-
*
|
|
1124
|
-
* @param protocols - Subprotocol list for the WebSocket constructor.
|
|
1125
|
-
* @returns An option that sets `config.webSocket.protocols`.
|
|
1126
|
-
* @group client
|
|
1127
|
-
*/
|
|
1128
|
-
function withWebSocketProtocols(protocols) {
|
|
1129
|
-
return (config) => {
|
|
1130
|
-
config.webSocket.protocols = protocols;
|
|
1131
|
-
};
|
|
1132
|
-
}
|
|
1133
|
-
/**
|
|
1134
|
-
* Enables WebSocket heartbeat messages with optional acknowledgement detection.
|
|
1135
|
-
*
|
|
1136
|
-
* Passing `undefined` clears an earlier setting. The option type is:
|
|
1137
|
-
*
|
|
1138
|
-
* ```ts
|
|
1139
|
-
* {
|
|
1140
|
-
* intervalMs: number
|
|
1141
|
-
* isAck?: (message: unknown) => boolean
|
|
1142
|
-
* message?: <T = unknown>() => T | unknown
|
|
1143
|
-
* timeoutMs?: number
|
|
1144
|
-
* }
|
|
1145
|
-
* ```
|
|
1146
|
-
*
|
|
1147
|
-
* - Every `intervalMs`, `message()` is called and its result is sent through the endpoint's `outgoing` struct. No message is sent when `message` is omitted or returns `undefined`.
|
|
1148
|
-
* - With `timeoutMs`, an acknowledgement is expected after each heartbeat. `isAck` receives the decoded incoming message; when it returns `true`, the timer is cleared and that message is **not** delivered to `receive`.
|
|
1149
|
-
* - If no ack arrives within `timeoutMs`, or a heartbeat cannot be sent, the session fails. This does not trigger a reconnect.
|
|
1150
|
-
* - Heartbeat uses the same JSON guard as `session.send`: a value JSON cannot represent after struct encoding causes `TypeError` and ends the logical session.
|
|
1151
|
-
* - `intervalMs` and `timeoutMs` must be positive, finite, and at most `2_147_483_647`; otherwise execute fails with `REQ_OPTIONS_INVALID`.
|
|
1152
|
-
* - A `heartbeat` in the execute options replaces this one for that call.
|
|
1153
|
-
*
|
|
1154
|
-
* @param options - Heartbeat interval, payload, timeout, and ACK predicate.
|
|
1155
|
-
* @returns An option that sets `config.webSocket.heartbeat`.
|
|
1156
|
-
* @group client
|
|
1157
|
-
*/
|
|
1158
|
-
function withWebSocketHeartbeat(options) {
|
|
1159
|
-
return (config) => {
|
|
1160
|
-
config.webSocket.heartbeat = options;
|
|
1161
|
-
};
|
|
1162
|
-
}
|
|
1163
|
-
/**
|
|
1164
|
-
* Configures automatic reconnection when the socket closes for any reason other than a manual `session.close()`.
|
|
1165
|
-
*
|
|
1166
|
-
* Reconnection is opt-in: without this option a close ends the session. Passing `undefined` clears an earlier setting. The option type is:
|
|
1167
|
-
*
|
|
1168
|
-
* ```ts
|
|
1169
|
-
* {
|
|
1170
|
-
* attempts?: number
|
|
1171
|
-
* delayMs?: number
|
|
1172
|
-
* factor?: number
|
|
1173
|
-
* jitter?: number
|
|
1174
|
-
* maxDelayMs?: number
|
|
1175
|
-
* shouldReconnect?: (context: { attempt: number; cause?: unknown; code?: number; reason?: string; wasClean?: boolean }) => boolean
|
|
1176
|
-
* }
|
|
1177
|
-
* ```
|
|
1178
|
-
*
|
|
1179
|
-
* - `attempts` is the retry budget for each outage. It defaults to `3`, and `0` disables reconnection. Successful `open` resets the budget and backoff exponent; the next outage starts at retry 1. Omitting it does not give unlimited retries within one outage. A socket that successfully opens and is immediately closed again by the server can keep reconnecting at the base delay (subject to jitter and caps), because every open resets the budget; use `shouldReconnect` to stop it.
|
|
1180
|
-
* - `delayMs` defaults to `1000`, `factor` to `2`, `maxDelayMs` to `30000`, and `jitter` (0 to 1) to `0`. Retry _n_ starts with `delayMs * factor^(n-1)`, capped at `maxDelayMs`; jitter and rounding follow, then the final delay is capped again at `maxDelayMs` and `2_147_483_647` ms. A zero base stays zero; valid-number overflow saturates at those caps.
|
|
1181
|
-
* - `shouldReconnect` can return `false` to end the session instead. Without it every close is retried while the budget lasts. It is not called for a manual `session.close()`.
|
|
1182
|
-
* - Reconnect options are validated before connecting: `attempts` must be a non-negative safe integer; `delayMs` must be finite and non-negative; `maxDelayMs` must be a non-negative finite number or positive `Infinity`, which removes the configured cap but not the platform limit of `2_147_483_647` ms; `factor` must be finite and positive; `jitter` must be finite and within 0–1. Invalid values make execute fail with `REQ_OPTIONS_INVALID` before connecting. `attempts: 0` disables subsequent retries, but all explicitly supplied reconnect fields are still validated before the initial connection.
|
|
1183
|
-
* - A `reconnect` in the execute options replaces this one for that call.
|
|
1184
|
-
*
|
|
1185
|
-
* @param options - Retry budget, backoff, and `shouldReconnect` gate.
|
|
1186
|
-
* @returns An option that sets `config.webSocket.reconnect`.
|
|
1187
|
-
* @group client
|
|
1188
|
-
*/
|
|
1189
|
-
function withWebSocketReconnect(options) {
|
|
1190
|
-
return (config) => {
|
|
1191
|
-
config.webSocket.reconnect = options;
|
|
1192
|
-
};
|
|
1193
|
-
}
|
|
1194
|
-
/**
|
|
1195
|
-
* Observes SSE events that have no matching event struct or fail validation.
|
|
1196
|
-
*
|
|
1197
|
-
* The handler may be async and is awaited. The callback receives:
|
|
1198
|
-
*
|
|
1199
|
-
* ```ts
|
|
1200
|
-
* {
|
|
1201
|
-
* reason: 'missing-struct' | 'validation-failed'
|
|
1202
|
-
* message: { id: string; event: string; data: string }
|
|
1203
|
-
* cause?: unknown
|
|
1204
|
-
* signal: AbortSignal
|
|
1205
|
-
* }
|
|
1206
|
-
* ```
|
|
1207
|
-
*
|
|
1208
|
-
* - The invalid event is **always dropped**; it is never delivered to the stream. The callback only observes it.
|
|
1209
|
-
* - The return value is ignored. An error thrown by the callback is swallowed and does not close the stream, unless `signal` has already aborted.
|
|
1210
|
-
* - `cause` is set for `validation-failed`. Without a handler the invalid event is dropped silently.
|
|
1211
|
-
*
|
|
1212
|
-
* @param handler - Observer callback for invalid events.
|
|
1213
|
-
* @returns An option that sets `config.sse.onInvalidEvent`.
|
|
1214
|
-
* @group client
|
|
1215
|
-
*/
|
|
1216
|
-
function withSSEOnInvalidEvent(handler) {
|
|
1217
|
-
return (config) => {
|
|
1218
|
-
config.sse.onInvalidEvent = handler;
|
|
1219
|
-
};
|
|
1220
|
-
}
|
|
1221
|
-
/**
|
|
1222
|
-
* Configures automatic reconnection after an SSE stream fails.
|
|
1223
|
-
*
|
|
1224
|
-
* Reconnection is opt-in: without this option a failed stream is not retried. Passing `undefined` clears an earlier setting.
|
|
1225
|
-
*
|
|
1226
|
-
* ```ts
|
|
1227
|
-
* type SSEReconnect = {
|
|
1228
|
-
* attempts?: number;
|
|
1229
|
-
* delayMs?: number;
|
|
1230
|
-
* factor?: number;
|
|
1231
|
-
* jitter?: number;
|
|
1232
|
-
* maxDelayMs?: number;
|
|
1233
|
-
* shouldReconnect?: (context: {
|
|
1234
|
-
* attempt: number;
|
|
1235
|
-
* cause?: unknown;
|
|
1236
|
-
* lastEventId: string;
|
|
1237
|
-
* method: string;
|
|
1238
|
-
* open?: {
|
|
1239
|
-
* response: { status: number; statusText: string; url: string };
|
|
1240
|
-
* url: string;
|
|
1241
|
-
* };
|
|
1242
|
-
* }) => boolean | Promise<boolean>;
|
|
1243
|
-
* };
|
|
1244
|
-
* ```
|
|
1245
|
-
*
|
|
1246
|
-
* - `attempts` is the retry budget for each outage. It defaults to `3` when `reconnect` is set, and `0` disables retries. A successful handshake resets the budget, callback `attempt`, and backoff exponent. Omitting it does not give unlimited retries within one outage. Repeated successful opens followed immediately by retryable stream failures can keep reconnecting at the current base delay (subject to jitter and caps), because each open resets the budget; use `shouldReconnect` to stop them. Normal EOF remains terminal.
|
|
1247
|
-
* - Without `shouldReconnect`, only `GET`, `HEAD`, `OPTIONS`, `PUT`, and `DELETE` reconnect. With it, its result decides for every method, `POST` included; `method` in the context tells endpoints apart. Return `false` to stop for this failure. It runs before the attempts limit is checked.
|
|
1248
|
-
* - A request with a `ReadableStream` body never reconnects, and `shouldReconnect` is not called for it.
|
|
1249
|
-
* - `delayMs`, `factor`, `jitter` (0 to 1), and `maxDelayMs` shape the backoff. `factor` defaults to `1` and `jitter` to `0`. `maxDelayMs` caps the final delay after jitter and rounding. Without a configured cap, the platform timer limit of `2_147_483_647` ms still applies. A zero base stays zero; valid-number overflow saturates at the caps.
|
|
1250
|
-
* - `delayMs` is only the starting base delay (`1000` by default). A server `retry:` field (digits only, in milliseconds) replaces it for later attempts.
|
|
1251
|
-
* - The last event id is sent again as UTF-8 bytes in `Last-Event-ID` on the next attempt. An empty `id:` clears it; an ID containing NUL is ignored.
|
|
1252
|
-
* - Reconnect options are validated before interceptors or Fetch: `attempts` must be a non-negative safe integer; `delayMs` must be finite and non-negative; `maxDelayMs` must be a non-negative finite number or positive `Infinity`, which removes the configured cap but not the platform limit of `2_147_483_647` ms; `factor` must be finite and positive; `jitter` must be finite and within 0–1. Invalid values return `REQ_OPTIONS_INVALID` at execute time.
|
|
1253
|
-
*
|
|
1254
|
-
* @param options - Retry budget, backoff, and `shouldReconnect` gate.
|
|
1255
|
-
* @returns An option that sets `config.sse.reconnect`.
|
|
1256
|
-
* @group client
|
|
1257
|
-
*/
|
|
1258
|
-
function withSSEReconnect(options) {
|
|
1259
|
-
return (config) => {
|
|
1260
|
-
config.sse.reconnect = options;
|
|
1261
|
-
};
|
|
1262
|
-
}
|
|
1263
|
-
/**
|
|
1264
|
-
* Enables XSRF cookie-to-header token forwarding for HTTP requests.
|
|
1265
|
-
*
|
|
1266
|
-
* SSE and WebSocket requests do not get the header. `cookieName` defaults to `XSRF-TOKEN` and `headerName` to `X-XSRF-TOKEN` when names are omitted.
|
|
1267
|
-
*
|
|
1268
|
-
* - The header is set only when all of these hold: the method is not `GET`, `HEAD`, `OPTIONS`, or `TRACE`; the request does not already have that header; the client has an endpoint; and, whenever `location` exists (Window or Worker), the resolved request URL has the same origin as `location.origin`. A cross-origin request does not call the provider.
|
|
1269
|
-
* - Without `tokenProvider`, the token is read from `document.cookie` and URL-decoded only when `document` exists. Workers and servers need a provider to supply a token.
|
|
1270
|
-
* - With `tokenProvider`, the cookie is never read. The provider receives the request, and its result is the token. An empty string, `null`, or `undefined` means no header.
|
|
1271
|
-
*
|
|
1272
|
-
* @param options - Cookie/header names and optional token provider.
|
|
1273
|
-
* @returns An option that sets `config.xsrf`.
|
|
1274
|
-
* @group client
|
|
1275
|
-
*/
|
|
1276
|
-
function withXSRF(options = {}) {
|
|
1277
|
-
return (config) => {
|
|
1278
|
-
config.xsrf = {
|
|
1279
|
-
cookieName: options.cookieName ?? "XSRF-TOKEN",
|
|
1280
|
-
headerName: options.headerName ?? "X-XSRF-TOKEN",
|
|
1281
|
-
tokenProvider: options.tokenProvider
|
|
1282
|
-
};
|
|
1283
|
-
};
|
|
1284
|
-
}
|
|
1285
|
-
/**
|
|
1286
|
-
* Sets whether HTTP and SSE requests include credentials.
|
|
1287
|
-
*
|
|
1288
|
-
* When `value` is `true`, Fetch uses `credentials: 'include'`, so cookies and other credentials are sent with cross-origin requests. When it is `false` or never set, `credentials` is left unset and Fetch uses its default. The WebSocket constructor does not receive this setting.
|
|
1106
|
+
* Optional `response` metadata is attached when the transport already had some
|
|
1107
|
+
* to report. The cause is kept as `cause`.
|
|
1289
1108
|
*
|
|
1290
|
-
*
|
|
1291
|
-
*
|
|
1292
|
-
*
|
|
1109
|
+
* - `NET_ABORTED` when {@link isAbortCause} recognizes an abort-branded `Error`
|
|
1110
|
+
* (including `ERR_ABORTED` from another Core copy) or a `DOMException` named
|
|
1111
|
+
* `AbortError`. The message is `Request was aborted`.
|
|
1112
|
+
* - `NET_TIMEOUT` when {@link isTimeoutCause} recognizes a timeout-branded
|
|
1113
|
+
* `Error` (including `ERR_TIMEOUT` from another Core copy) or a `DOMException`
|
|
1114
|
+
* or `Error` named `TimeoutError`. The message is the cause's message, or
|
|
1115
|
+
* `Request timed out` when that is empty.
|
|
1116
|
+
* - `NET_UNREACHABLE` for anything else, with the cause's message.
|
|
1117
|
+
*
|
|
1118
|
+
* @param cause - Underlying cancellation, timeout, or transport failure.
|
|
1119
|
+
* @param response - Metadata, only where the transport already had some to report.
|
|
1120
|
+
* @returns A `NET_ABORTED`, `NET_TIMEOUT`, or `NET_UNREACHABLE` fault.
|
|
1121
|
+
* @group errors
|
|
1293
1122
|
*/
|
|
1294
|
-
function
|
|
1295
|
-
return (
|
|
1296
|
-
|
|
1297
|
-
|
|
1123
|
+
function createNetworkFault(cause, response) {
|
|
1124
|
+
if (isAbortCause(cause)) return withResponse(createFault(ERR_ABORTED.message, cause), "NET_ABORTED", response);
|
|
1125
|
+
if (isTimeoutCause(cause)) return withResponse(createFault(cause instanceof Error && cause.message ? cause.message : ERR_TIMEOUT.message, cause), "NET_TIMEOUT", response);
|
|
1126
|
+
return withResponse(createFault(describeCause(cause), cause), "NET_UNREACHABLE", response);
|
|
1127
|
+
}
|
|
1128
|
+
function withResponse(fault, code, response) {
|
|
1129
|
+
fault.code = code;
|
|
1130
|
+
if (response !== void 0) fault.response = response;
|
|
1131
|
+
return fault;
|
|
1298
1132
|
}
|
|
1299
1133
|
// src/interceptor/interceptor.ts
|
|
1300
1134
|
/**
|
|
@@ -1303,10 +1137,10 @@ function withCredentials(value) {
|
|
|
1303
1137
|
* Returns `{ kind: 'http', fn }`. Call `next(req)` (optionally with a modified
|
|
1304
1138
|
* request) or short-circuit with `makeResponse`. `next` runs only the
|
|
1305
1139
|
* interceptors registered after this one and then the fetch handler. HTTP
|
|
1306
|
-
* interceptors do not run for SSE or WebSocket
|
|
1140
|
+
* interceptors do not run for SSE or WebSocket operations.
|
|
1307
1141
|
*
|
|
1308
1142
|
* A deterministic URL error from `next(req)` rejects the returned promise, so
|
|
1309
|
-
* `.then` or `.catch` recovery runs; if it is not recovered,
|
|
1143
|
+
* `.then` or `.catch` recovery runs; if it is not recovered, the operation
|
|
1310
1144
|
* still reports `REQ_BUILD_FAILED`.
|
|
1311
1145
|
*
|
|
1312
1146
|
* @param fn - HTTP interceptor function.
|
|
@@ -1322,12 +1156,12 @@ function createHttpInterceptor(fn) {
|
|
|
1322
1156
|
/**
|
|
1323
1157
|
* Wraps an `SSEInterceptorFn` as a tagged `SSEInterceptor`.
|
|
1324
1158
|
*
|
|
1325
|
-
* Returns `{ kind: 'sse', fn }`. SSE interceptors run only for SSE
|
|
1159
|
+
* Returns `{ kind: 'sse', fn }`. SSE interceptors run only for SSE operations, in
|
|
1326
1160
|
* registration order; `next(req)` opens the stream through the remaining
|
|
1327
1161
|
* interceptors.
|
|
1328
1162
|
*
|
|
1329
1163
|
* A deterministic URL error from `next(req)` rejects the returned promise, so
|
|
1330
|
-
* `.then` or `.catch` recovery runs; if it is not recovered,
|
|
1164
|
+
* `.then` or `.catch` recovery runs; if it is not recovered, the operation
|
|
1331
1165
|
* still reports `REQ_BUILD_FAILED`.
|
|
1332
1166
|
*
|
|
1333
1167
|
* `EventStreamHandle` extends `AsyncDisposable`. A structural short-circuit
|
|
@@ -1348,7 +1182,7 @@ function createSSEInterceptor(fn) {
|
|
|
1348
1182
|
* Wraps a `WebSocketInterceptorFn` as a tagged `WebSocketInterceptor`.
|
|
1349
1183
|
*
|
|
1350
1184
|
* Returns `{ kind: 'web-socket', fn }`. WebSocket interceptors run only for
|
|
1351
|
-
* WebSocket
|
|
1185
|
+
* WebSocket operations. `next` and the interceptor return a
|
|
1352
1186
|
* {@link WebSocketSessionLike}, the interceptor-facing session. A wrapper or
|
|
1353
1187
|
* short-circuit session must forward its `[Symbol.asyncDispose]()` teardown.
|
|
1354
1188
|
*
|
|
@@ -1377,6 +1211,99 @@ function resolveSSEInterceptors(interceptors) {
|
|
|
1377
1211
|
function resolveWebSocketInterceptors(interceptors) {
|
|
1378
1212
|
return interceptors.filter((i) => i.kind === "web-socket").map((i) => i.fn);
|
|
1379
1213
|
}
|
|
1214
|
+
// src/internal/abort.ts
|
|
1215
|
+
/** The cancellation cause for an already-aborted signal, or `undefined` while it is still live. */
|
|
1216
|
+
function resolveAbortCause(signal) {
|
|
1217
|
+
if (!signal.aborted) return;
|
|
1218
|
+
return resolveAbortedCause(signal);
|
|
1219
|
+
}
|
|
1220
|
+
/** `ERR_TIMEOUT` or `ERR_ABORTED`, chosen from the signal's abort reason, including another copy's timeout sentinel. */
|
|
1221
|
+
function resolveAbortedCause(signal) {
|
|
1222
|
+
return isTimeoutCause(signal.reason) ? ERR_TIMEOUT : ERR_ABORTED;
|
|
1223
|
+
}
|
|
1224
|
+
/** The cancellation fault for an already-aborted signal, or `undefined` while it is still live. */
|
|
1225
|
+
function resolveAbortFault(signal) {
|
|
1226
|
+
if (!signal.aborted) return;
|
|
1227
|
+
return resolveAbortedFault(signal);
|
|
1228
|
+
}
|
|
1229
|
+
/** `NET_ABORTED` or `NET_TIMEOUT`, chosen from the signal's abort reason. */
|
|
1230
|
+
function resolveAbortedFault(signal) {
|
|
1231
|
+
return createNetworkFault(resolveAbortedCause(signal));
|
|
1232
|
+
}
|
|
1233
|
+
async function awaitWithSignal(run, signal) {
|
|
1234
|
+
signal.throwIfAborted();
|
|
1235
|
+
let rejectAbort;
|
|
1236
|
+
const aborted = new Promise((_resolve, reject) => {
|
|
1237
|
+
rejectAbort = reject;
|
|
1238
|
+
});
|
|
1239
|
+
const onAbort = () => rejectAbort(signal.reason);
|
|
1240
|
+
signal.addEventListener("abort", onAbort, { once: true });
|
|
1241
|
+
const task = Promise.resolve().then(() => {
|
|
1242
|
+
signal.throwIfAborted();
|
|
1243
|
+
return run();
|
|
1244
|
+
});
|
|
1245
|
+
task.catch(() => void 0);
|
|
1246
|
+
try {
|
|
1247
|
+
const value = await Promise.race([task, aborted]);
|
|
1248
|
+
signal.throwIfAborted();
|
|
1249
|
+
return value;
|
|
1250
|
+
} finally {
|
|
1251
|
+
signal.removeEventListener("abort", onAbort);
|
|
1252
|
+
}
|
|
1253
|
+
}
|
|
1254
|
+
const forwardingSignals = /* @__PURE__ */ new WeakMap();
|
|
1255
|
+
/** Release listeners owned by a signal returned from `mergeAbortSignals`. */
|
|
1256
|
+
function releaseAbortSignal(signal) {
|
|
1257
|
+
forwardingSignals.get(signal)?.release();
|
|
1258
|
+
}
|
|
1259
|
+
function mergeAbortSignals(controller, signals) {
|
|
1260
|
+
if (signals.length > 0 && signals.every((signal) => signal === void 0 || signal === controller)) return controller;
|
|
1261
|
+
const merged = [];
|
|
1262
|
+
const seen = /* @__PURE__ */ new Set();
|
|
1263
|
+
const append = (signal) => {
|
|
1264
|
+
const state = forwardingSignals.get(signal);
|
|
1265
|
+
if (state?.active) {
|
|
1266
|
+
for (const source of state.sources) append(source);
|
|
1267
|
+
return;
|
|
1268
|
+
}
|
|
1269
|
+
if (!seen.has(signal)) {
|
|
1270
|
+
seen.add(signal);
|
|
1271
|
+
merged.push(signal);
|
|
1272
|
+
}
|
|
1273
|
+
};
|
|
1274
|
+
append(controller);
|
|
1275
|
+
for (const signal of signals) if (signal) append(signal);
|
|
1276
|
+
if (merged.length === 1) return merged[0];
|
|
1277
|
+
const combinedController = new AbortController();
|
|
1278
|
+
const listeners = [];
|
|
1279
|
+
let abortWithReason;
|
|
1280
|
+
const state = {
|
|
1281
|
+
active: true,
|
|
1282
|
+
release() {
|
|
1283
|
+
if (!state.active) return;
|
|
1284
|
+
state.active = false;
|
|
1285
|
+
for (const [source, listener] of listeners) source.removeEventListener("abort", listener);
|
|
1286
|
+
},
|
|
1287
|
+
sources: merged
|
|
1288
|
+
};
|
|
1289
|
+
forwardingSignals.set(combinedController.signal, state);
|
|
1290
|
+
abortWithReason = (reason) => {
|
|
1291
|
+
if (combinedController.signal.aborted) return;
|
|
1292
|
+
combinedController.abort(reason);
|
|
1293
|
+
state.release();
|
|
1294
|
+
};
|
|
1295
|
+
const alreadyAborted = merged.find((signal) => signal.aborted);
|
|
1296
|
+
if (alreadyAborted) {
|
|
1297
|
+
abortWithReason(alreadyAborted.reason);
|
|
1298
|
+
return combinedController.signal;
|
|
1299
|
+
}
|
|
1300
|
+
for (const source of merged) {
|
|
1301
|
+
const listener = () => abortWithReason(source.reason);
|
|
1302
|
+
listeners.push([source, listener]);
|
|
1303
|
+
source.addEventListener("abort", listener, { once: true });
|
|
1304
|
+
}
|
|
1305
|
+
return combinedController.signal;
|
|
1306
|
+
}
|
|
1380
1307
|
// src/internal/bytes.ts
|
|
1381
1308
|
const textDecoder = new TextDecoder();
|
|
1382
1309
|
function decodeBytes(value) {
|
|
@@ -1774,7 +1701,9 @@ var StructError = class extends Error {
|
|
|
1774
1701
|
}
|
|
1775
1702
|
};
|
|
1776
1703
|
function createFormattedError() {
|
|
1777
|
-
|
|
1704
|
+
const node = Object.create(null);
|
|
1705
|
+
node._errors = [];
|
|
1706
|
+
return node;
|
|
1778
1707
|
}
|
|
1779
1708
|
function formatErrorTreeKey(segment) {
|
|
1780
1709
|
const key = String(segment);
|
|
@@ -2696,7 +2625,7 @@ function createBaseTransportRequest(method, path, input, builder, options) {
|
|
|
2696
2625
|
const declaredContentType = headers.has("content-type");
|
|
2697
2626
|
if (options.transport === "sse" && built.defaultContentType && !declaredContentType) headers.set("content-type", built.defaultContentType);
|
|
2698
2627
|
const request = {
|
|
2699
|
-
|
|
2628
|
+
signal: options.signal,
|
|
2700
2629
|
baseEndpoint: options.baseEndpoint,
|
|
2701
2630
|
body: built.body,
|
|
2702
2631
|
bodyContentTypeSource: built.body,
|
|
@@ -2706,7 +2635,6 @@ function createBaseTransportRequest(method, path, input, builder, options) {
|
|
|
2706
2635
|
operation: options.operation,
|
|
2707
2636
|
queryParams,
|
|
2708
2637
|
queryString,
|
|
2709
|
-
timeout: options.timeout,
|
|
2710
2638
|
withCredentials: options.withCredentials ?? false
|
|
2711
2639
|
};
|
|
2712
2640
|
resolveRequestUrl(request);
|
|
@@ -2718,14 +2646,13 @@ function createBaseTransportRequest(method, path, input, builder, options) {
|
|
|
2718
2646
|
function createHttpRequest(method, path, input, builder, options) {
|
|
2719
2647
|
return {
|
|
2720
2648
|
...createBaseTransportRequest(method, path, input, builder, {
|
|
2721
|
-
|
|
2649
|
+
signal: options.signal,
|
|
2722
2650
|
baseEndpoint: options.baseEndpoint,
|
|
2723
2651
|
defaultHeaders: options.defaultHeaders,
|
|
2724
2652
|
inputDeclared: options.inputDeclared,
|
|
2725
2653
|
inputGuides: options.inputGuides,
|
|
2726
2654
|
operation: options.operation,
|
|
2727
2655
|
querySerializer: options.querySerializer,
|
|
2728
|
-
timeout: options.timeout,
|
|
2729
2656
|
transport: "http",
|
|
2730
2657
|
withCredentials: options.withCredentials
|
|
2731
2658
|
}),
|
|
@@ -2743,7 +2670,7 @@ function createHttpRequest(method, path, input, builder, options) {
|
|
|
2743
2670
|
*
|
|
2744
2671
|
* An upload observer can fail before any response exists, so `meta` is absent then. Download
|
|
2745
2672
|
* observers, truncated reads, and representation checks already have a response. Those steps
|
|
2746
|
-
* are indistinguishable once they collapse into a bare cause, so
|
|
2673
|
+
* are indistinguishable once they collapse into a bare cause, so operation execution maps
|
|
2747
2674
|
* `code` straight through.
|
|
2748
2675
|
*
|
|
2749
2676
|
* Caller cancellation is deliberately never tagged: it stays the raw abort reason so the
|
|
@@ -3014,8 +2941,8 @@ function createFetchRequestInit(request) {
|
|
|
3014
2941
|
return createFetchInitBase(request, {
|
|
3015
2942
|
defaultAccept: "application/json, text/plain, */*",
|
|
3016
2943
|
headers,
|
|
3017
|
-
signal: request.
|
|
3018
|
-
wrapReadableStreamBody: uploadProgress ? (body) => wrapUploadProgressStream(body, uploadProgress, getContentLength(headers), request.
|
|
2944
|
+
signal: request.signal,
|
|
2945
|
+
wrapReadableStreamBody: uploadProgress ? (body) => wrapUploadProgressStream(body, uploadProgress, getContentLength(headers), request.signal) : void 0
|
|
3019
2946
|
});
|
|
3020
2947
|
}
|
|
3021
2948
|
function createFetchRequest(request) {
|
|
@@ -3075,7 +3002,7 @@ async function parseFetchResponse(httpRequest, response, fallbackUrl, platform)
|
|
|
3075
3002
|
throw new BodyFailure("RES_MEDIA_TYPE_INVALID", /* @__PURE__ */ new TypeError(`Expected a JSON media type, received ${contentType || "<none>"}`), meta);
|
|
3076
3003
|
}
|
|
3077
3004
|
if (response.body && platform && !downloadProgress) {
|
|
3078
|
-
const bytes = await readWholeBody(response, httpRequest.
|
|
3005
|
+
const bytes = await readWholeBody(response, httpRequest.signal, meta);
|
|
3079
3006
|
try {
|
|
3080
3007
|
body = parseBytesBody(responseType, bytes, contentType);
|
|
3081
3008
|
} catch (error) {
|
|
@@ -3084,7 +3011,7 @@ async function parseFetchResponse(httpRequest, response, fallbackUrl, platform)
|
|
|
3084
3011
|
} else if (response.body) {
|
|
3085
3012
|
const chunks = [];
|
|
3086
3013
|
const reader = response.body.getReader();
|
|
3087
|
-
const signal = httpRequest.
|
|
3014
|
+
const signal = httpRequest.signal;
|
|
3088
3015
|
const cancelRead = () => void reader.cancel(signal?.reason).catch(() => void 0);
|
|
3089
3016
|
signal?.addEventListener("abort", cancelRead, { once: true });
|
|
3090
3017
|
let receivedLength = 0;
|
|
@@ -3140,7 +3067,7 @@ async function parseFetchResponse(httpRequest, response, fallbackUrl, platform)
|
|
|
3140
3067
|
* global `fetch` resolved at send time. It rejects, rather than resolving with
|
|
3141
3068
|
* a synthetic response, when no response reached the client.
|
|
3142
3069
|
*
|
|
3143
|
-
* @param httpRequest - Normalized request (URL, headers, body,
|
|
3070
|
+
* @param httpRequest - Normalized request (URL, headers, body, signal, progress hooks).
|
|
3144
3071
|
* @param fetchImpl - Fetch implementation; defaults to global `fetch`. The library default handle is called as
|
|
3145
3072
|
* `(url, init)` while the global is untouched, and its body is read in one piece unless download progress is observed. Any other handle receives a
|
|
3146
3073
|
* `Request` and its body is read chunk by chunk.
|
|
@@ -3168,13 +3095,13 @@ async function fetchHandler(httpRequest, fetchImpl = resolveGlobalFetch()) {
|
|
|
3168
3095
|
try {
|
|
3169
3096
|
response = await sendFetchRequest(observed, fetchImpl);
|
|
3170
3097
|
} catch (error) {
|
|
3171
|
-
throw observerFailure !== void 0 && !httpRequest.
|
|
3098
|
+
throw observerFailure !== void 0 && !httpRequest.signal?.aborted ? observerFailure : error;
|
|
3172
3099
|
}
|
|
3173
3100
|
if (observerFailure !== void 0) throw observerFailure;
|
|
3174
3101
|
return response;
|
|
3175
3102
|
}
|
|
3176
3103
|
async function sendFetchRequest(httpRequest, fetchImpl) {
|
|
3177
|
-
const abortSignal = httpRequest.
|
|
3104
|
+
const abortSignal = httpRequest.signal;
|
|
3178
3105
|
const platform = fetchImpl === PLATFORM_FETCH_HANDLE;
|
|
3179
3106
|
let prepared = httpRequest;
|
|
3180
3107
|
let request;
|
|
@@ -3219,7 +3146,7 @@ function isBrowserWindowOrWorker() {
|
|
|
3219
3146
|
async function prepareUploadProgressRequest(request) {
|
|
3220
3147
|
const { uploadProgress, body } = request;
|
|
3221
3148
|
if (!uploadProgress || body == null || isReadableStreamBody(body) || !supportsStreamingRequestBody() || isBrowserWindowOrWorker()) return request;
|
|
3222
|
-
request.
|
|
3149
|
+
request.signal?.throwIfAborted();
|
|
3223
3150
|
const converted = convertBodyToUploadStream(body);
|
|
3224
3151
|
if (!converted) return request;
|
|
3225
3152
|
const headers = new Headers(request.headers);
|
|
@@ -3233,7 +3160,7 @@ async function prepareUploadProgressRequest(request) {
|
|
|
3233
3160
|
total
|
|
3234
3161
|
};
|
|
3235
3162
|
try {
|
|
3236
|
-
if (request.
|
|
3163
|
+
if (request.signal) await awaitWithSignal(() => uploadProgress(startEvent), request.signal);
|
|
3237
3164
|
else await uploadProgress(startEvent);
|
|
3238
3165
|
} catch (error) {
|
|
3239
3166
|
converted.stream.cancel(error).catch(() => void 0);
|
|
@@ -3272,8 +3199,7 @@ function convertBodyToUploadStream(body) {
|
|
|
3272
3199
|
* Declares a typed HTTP request endpoint.
|
|
3273
3200
|
*
|
|
3274
3201
|
* Pass method, path, an optional input struct, and the single `output` and
|
|
3275
|
-
* `error` structs. Call the returned
|
|
3276
|
-
* for `client.execute`.
|
|
3202
|
+
* `error` structs. Call the returned operation with a client and, when declared, the input.
|
|
3277
3203
|
*
|
|
3278
3204
|
* The contract is `method`, `path`, an optional `input` struct, optional
|
|
3279
3205
|
* `output` and `error` structs, and an optional `operation`. A declared `input`
|
|
@@ -3289,7 +3215,7 @@ function convertBodyToUploadStream(body) {
|
|
|
3289
3215
|
* omitting one means that body is never read.
|
|
3290
3216
|
*
|
|
3291
3217
|
* @param definition - Request contract (method, path, input, builder, output).
|
|
3292
|
-
* @returns An
|
|
3218
|
+
* @returns An operation `(client, ...input) => Promise<error-first tuple>`.
|
|
3293
3219
|
*
|
|
3294
3220
|
* The checked example file passes the whole input object as `path`. This snippet maps individual fields into `path` and `query` instead:
|
|
3295
3221
|
*
|
|
@@ -3308,43 +3234,10 @@ function convertBodyToUploadStream(body) {
|
|
|
3308
3234
|
*/
|
|
3309
3235
|
function defineRequest(definition) {
|
|
3310
3236
|
const endpoint = { ...definition };
|
|
3311
|
-
|
|
3312
|
-
return {
|
|
3313
|
-
[COMMAND_EXECUTE]: executeHttpCommand,
|
|
3314
|
-
[COMMAND_TYPE]: HTTP_COMMAND,
|
|
3315
|
-
definition: endpoint,
|
|
3316
|
-
input
|
|
3317
|
-
};
|
|
3318
|
-
}
|
|
3319
|
-
return ((input) => create(input));
|
|
3237
|
+
return (async (client, input) => executeHttpCommand(client[CLIENT].config, endpoint, input, client[CLIENT].options));
|
|
3320
3238
|
}
|
|
3321
|
-
|
|
3322
|
-
|
|
3323
|
-
*
|
|
3324
|
-
* Low-level entry used by `client.execute`. Prefer `client.execute(command, options)`
|
|
3325
|
-
* in application code. `HttpClientConfig` is the HTTP part of `ClientConfig`, so
|
|
3326
|
-
* a `ClientConfig` is accepted. `HttpExecuteAwaitResult` is the internal result
|
|
3327
|
-
* tuple.
|
|
3328
|
-
*
|
|
3329
|
-
* The result is `[null, data, response]` or `[fault, undefined, undefined]`.
|
|
3330
|
-
* `data` is the decoded `output` body, or `undefined` when none is declared.
|
|
3331
|
-
* `response` is `DecodedResponse` when `output` is declared and `HttpMeta`
|
|
3332
|
-
* otherwise. The fault is `Fault<TError>`. A non-2xx status is `HTTP_STATUS`
|
|
3333
|
-
* when no `error` struct is declared (its `data` is `undefined`) or when the
|
|
3334
|
-
* declared body decodes (its `data` is that body). When a declared `error` body
|
|
3335
|
-
* cannot be read, the fault is a `RES_*` decode fault instead. On failure the
|
|
3336
|
-
* tuple's third slot is `undefined`; the fault carries the response metadata.
|
|
3337
|
-
* `options.timeout` falls back to the client's `withTimeout` default when omitted.
|
|
3338
|
-
*
|
|
3339
|
-
* @param clientConfig - Resolved HTTP client configuration (endpoint, interceptors, handler).
|
|
3340
|
-
* @param command - Command from a `defineRequest` endpoint.
|
|
3341
|
-
* @param options - Per-request progress and cancellation options.
|
|
3342
|
-
* @returns An await-result tuple of success body or typed request error.
|
|
3343
|
-
* @group http
|
|
3344
|
-
*/
|
|
3345
|
-
async function executeHttpCommand(clientConfig, command, options) {
|
|
3346
|
-
const { definition, input } = command;
|
|
3347
|
-
const config = options ?? {};
|
|
3239
|
+
async function executeHttpCommand(clientConfig, definition, input, options) {
|
|
3240
|
+
const config = options;
|
|
3348
3241
|
const fail = (fault) => {
|
|
3349
3242
|
return [
|
|
3350
3243
|
fault,
|
|
@@ -3352,34 +3245,10 @@ async function executeHttpCommand(clientConfig, command, options) {
|
|
|
3352
3245
|
void 0
|
|
3353
3246
|
];
|
|
3354
3247
|
};
|
|
3355
|
-
|
|
3356
|
-
|
|
3357
|
-
|
|
3358
|
-
abort: config.abort,
|
|
3359
|
-
signal: config.signal,
|
|
3360
|
-
timeout: config.timeout !== void 0 ? config.timeout : clientConfig.timeout
|
|
3361
|
-
});
|
|
3362
|
-
} catch (error) {
|
|
3363
|
-
return fail(createPreflightFault("REQ_OPTIONS_INVALID", error));
|
|
3364
|
-
}
|
|
3365
|
-
if (hasAbortTimeoutConflict(config)) return fail(createAbortTimeoutConflictFault());
|
|
3366
|
-
try {
|
|
3367
|
-
validateTransportTimeout(cancellation.timeout);
|
|
3368
|
-
} catch (error) {
|
|
3369
|
-
return fail(createPreflightFault("REQ_OPTIONS_INVALID", error));
|
|
3370
|
-
}
|
|
3371
|
-
const controller = new AbortController();
|
|
3372
|
-
const preAbortedSignal = [cancellation.abort, cancellation.signal].find((signal) => signal?.aborted);
|
|
3373
|
-
if (preAbortedSignal) return fail(resolveAbortedFault(preAbortedSignal));
|
|
3374
|
-
const deadline = armTransportDeadline(cancellation.timeout);
|
|
3375
|
-
const requestSignal = cancellation.abort === void 0 && cancellation.signal === void 0 && deadline.signal ? deadline.signal : mergeAbortSignals(controller.signal, [
|
|
3376
|
-
cancellation.abort,
|
|
3377
|
-
cancellation.signal,
|
|
3378
|
-
deadline.signal
|
|
3379
|
-
]);
|
|
3380
|
-
deadline.releaseOnAbort(requestSignal);
|
|
3248
|
+
const callerAbort = config.signal;
|
|
3249
|
+
if (callerAbort?.aborted) return fail(resolveAbortedFault(callerAbort));
|
|
3250
|
+
const requestSignal = mergeAbortSignals(new AbortController().signal, [callerAbort]);
|
|
3381
3251
|
const stopBeforeTransport = (fault) => {
|
|
3382
|
-
deadline.release();
|
|
3383
3252
|
releaseAbortSignal(requestSignal);
|
|
3384
3253
|
return fail(fault);
|
|
3385
3254
|
};
|
|
@@ -3390,14 +3259,14 @@ async function executeHttpCommand(clientConfig, command, options) {
|
|
|
3390
3259
|
try {
|
|
3391
3260
|
parsedInput = parseEndpointInput(definition.input, input, inputGuides);
|
|
3392
3261
|
} catch (error) {
|
|
3393
|
-
return stopBeforeTransport(
|
|
3262
|
+
return stopBeforeTransport(resolveAbortFault(requestSignal) ?? createPreflightFault("REQ_INPUT_INVALID", error));
|
|
3394
3263
|
}
|
|
3395
|
-
const parsedCancellation =
|
|
3264
|
+
const parsedCancellation = resolveAbortFault(requestSignal);
|
|
3396
3265
|
if (parsedCancellation) return stopBeforeTransport(parsedCancellation);
|
|
3397
3266
|
let request;
|
|
3398
3267
|
try {
|
|
3399
3268
|
request = createHttpRequest(definition.method, definition.path, parsedInput, definition.builder, {
|
|
3400
|
-
|
|
3269
|
+
signal: requestSignal,
|
|
3401
3270
|
baseEndpoint: clientConfig.endpoint,
|
|
3402
3271
|
defaultHeaders: clientConfig.headers,
|
|
3403
3272
|
downloadProgress: config.onDownloadProgress,
|
|
@@ -3407,17 +3276,15 @@ async function executeHttpCommand(clientConfig, command, options) {
|
|
|
3407
3276
|
operation: definition.operation,
|
|
3408
3277
|
querySerializer: clientConfig.querySerializer,
|
|
3409
3278
|
responseType,
|
|
3410
|
-
timeout: cancellation.timeout,
|
|
3411
3279
|
uploadProgress: config.onUploadProgress,
|
|
3412
3280
|
withCredentials: clientConfig.withCredentials,
|
|
3413
3281
|
xsrf: clientConfig.xsrf
|
|
3414
3282
|
});
|
|
3415
3283
|
} catch (error) {
|
|
3416
|
-
return stopBeforeTransport(
|
|
3284
|
+
return stopBeforeTransport(resolveAbortFault(requestSignal) ?? createPreflightFault("REQ_BUILD_FAILED", error));
|
|
3417
3285
|
}
|
|
3418
|
-
const builtCancellation =
|
|
3286
|
+
const builtCancellation = resolveAbortFault(requestSignal);
|
|
3419
3287
|
if (builtCancellation) return stopBeforeTransport(builtCancellation);
|
|
3420
|
-
const releaseRequestTimeout = deadline.release;
|
|
3421
3288
|
const transportOrigin = createTransportOrigin();
|
|
3422
3289
|
const transportController = new AbortController();
|
|
3423
3290
|
let chainSettled = false;
|
|
@@ -3436,8 +3303,8 @@ async function executeHttpCommand(clientConfig, command, options) {
|
|
|
3436
3303
|
rejected.catch(() => void 0);
|
|
3437
3304
|
return rejected;
|
|
3438
3305
|
}
|
|
3439
|
-
const transportAbort = nextRequest.
|
|
3440
|
-
if (
|
|
3306
|
+
const transportAbort = nextRequest.signal === void 0 || nextRequest.signal === requestSignal ? transportController.signal : mergeAbortSignals(transportController.signal, [nextRequest.signal]);
|
|
3307
|
+
if (resolveAbortFault(transportAbort)) {
|
|
3441
3308
|
const rejected = Promise.reject(transportOrigin.mark(resolveAbortCause(transportAbort)));
|
|
3442
3309
|
releaseAbortSignal(transportAbort);
|
|
3443
3310
|
rejected.catch(() => void 0);
|
|
@@ -3445,7 +3312,7 @@ async function executeHttpCommand(clientConfig, command, options) {
|
|
|
3445
3312
|
}
|
|
3446
3313
|
const pending = fetchHandler({
|
|
3447
3314
|
...nextRequest,
|
|
3448
|
-
|
|
3315
|
+
signal: transportAbort
|
|
3449
3316
|
}, clientConfig.http.handle).catch((error) => {
|
|
3450
3317
|
throw transportOrigin.mark(resolveAbortCause(transportAbort) ?? error);
|
|
3451
3318
|
}).finally(() => {
|
|
@@ -3469,7 +3336,7 @@ async function executeHttpCommand(clientConfig, command, options) {
|
|
|
3469
3336
|
response = settled;
|
|
3470
3337
|
} catch (error) {
|
|
3471
3338
|
const fromTransport = transportOrigin.has(error);
|
|
3472
|
-
const cancelled =
|
|
3339
|
+
const cancelled = resolveAbortFault(requestSignal);
|
|
3473
3340
|
if (cancelled) {
|
|
3474
3341
|
abortTransport();
|
|
3475
3342
|
if (inflightTransport) await Promise.allSettled([inflightTransport]);
|
|
@@ -3486,19 +3353,13 @@ async function executeHttpCommand(clientConfig, command, options) {
|
|
|
3486
3353
|
requestSignal.removeEventListener("abort", abortTransport);
|
|
3487
3354
|
chainSettled = true;
|
|
3488
3355
|
if (!transportController.signal.aborted) transportController.abort(/* @__PURE__ */ new Error("HTTP interceptor chain settled"));
|
|
3489
|
-
if (!response)
|
|
3490
|
-
releaseRequestTimeout();
|
|
3491
|
-
releaseAbortSignal(requestSignal);
|
|
3492
|
-
}
|
|
3356
|
+
if (!response) releaseAbortSignal(requestSignal);
|
|
3493
3357
|
}
|
|
3494
3358
|
const settledResponse = response;
|
|
3495
3359
|
const meta = toHttpMeta(settledResponse);
|
|
3496
3360
|
const struct = settledResponse.ok ? definition.output : definition.error;
|
|
3497
3361
|
if (!struct) {
|
|
3498
|
-
const cancelled = resolveTransportCancellation(deadline, requestSignal);
|
|
3499
|
-
releaseRequestTimeout();
|
|
3500
3362
|
releaseAbortSignal(requestSignal);
|
|
3501
|
-
if (cancelled) return fail(cancelled);
|
|
3502
3363
|
if (settledResponse.ok) return [
|
|
3503
3364
|
null,
|
|
3504
3365
|
void 0,
|
|
@@ -3509,16 +3370,13 @@ async function executeHttpCommand(clientConfig, command, options) {
|
|
|
3509
3370
|
let decoded;
|
|
3510
3371
|
try {
|
|
3511
3372
|
decoded = await decodeDeclaredBody(struct, settledResponse, requestSignal);
|
|
3512
|
-
const cancelled = resolveTransportCancellation(deadline, requestSignal);
|
|
3513
|
-
if (cancelled) return fail(cancelled);
|
|
3514
3373
|
} catch (error) {
|
|
3515
|
-
const cancelled =
|
|
3374
|
+
const cancelled = resolveAbortFault(requestSignal);
|
|
3516
3375
|
if (cancelled) return fail(cancelled);
|
|
3517
3376
|
if (error instanceof JsonMediaTypeError) return fail(createDecodeFault("RES_MEDIA_TYPE_INVALID", error, meta));
|
|
3518
3377
|
if (error instanceof StructError) return fail(createDecodeFault("RES_STRUCT_MISMATCH", error, meta));
|
|
3519
3378
|
return fail(createDecodeFault("RES_DECODE_FAILED", error, meta));
|
|
3520
3379
|
} finally {
|
|
3521
|
-
releaseRequestTimeout();
|
|
3522
3380
|
releaseAbortSignal(requestSignal);
|
|
3523
3381
|
}
|
|
3524
3382
|
const decodedResponse = {
|
|
@@ -3608,7 +3466,7 @@ function createBasicAuthModifier(fn, options) {
|
|
|
3608
3466
|
* `encode`, calling `basicAuthHttpInterceptor` throws
|
|
3609
3467
|
* `Error: BasicAuthInterceptor is not supported in this environment` when `btoa`
|
|
3610
3468
|
* is not available in the runtime. Use {@link basicAuthSSEInterceptor} for SSE
|
|
3611
|
-
*
|
|
3469
|
+
* operations.
|
|
3612
3470
|
*
|
|
3613
3471
|
* @param fn - Factory returning username/password for each request.
|
|
3614
3472
|
* @param options - Optional custom encoder (defaults to `btoa`).
|
|
@@ -4010,7 +3868,7 @@ async function fetchEventStream(request, options) {
|
|
|
4010
3868
|
}
|
|
4011
3869
|
async function start() {
|
|
4012
3870
|
while (!closeController.signal.aborted) {
|
|
4013
|
-
const attemptAbort = mergeAbortSignals(closeController.signal, [request.
|
|
3871
|
+
const attemptAbort = mergeAbortSignals(closeController.signal, [request.signal]);
|
|
4014
3872
|
let response;
|
|
4015
3873
|
let readerStarted = false;
|
|
4016
3874
|
try {
|
|
@@ -4047,8 +3905,8 @@ async function fetchEventStream(request, options) {
|
|
|
4047
3905
|
return;
|
|
4048
3906
|
} catch (error) {
|
|
4049
3907
|
if (response?.body && !readerStarted) response.body.cancel(error).catch(() => void 0);
|
|
4050
|
-
const normalizedError = normalizeAbortError(error, request.
|
|
4051
|
-
if (closeController.signal.aborted || request.
|
|
3908
|
+
const normalizedError = normalizeAbortError(error, request.signal, closeController.signal);
|
|
3909
|
+
if (closeController.signal.aborted || request.signal?.aborted) {
|
|
4052
3910
|
finishAborted(attemptAbort);
|
|
4053
3911
|
return;
|
|
4054
3912
|
}
|
|
@@ -4056,7 +3914,7 @@ async function fetchEventStream(request, options) {
|
|
|
4056
3914
|
const fatalError = toEventStreamFatalError(retryError);
|
|
4057
3915
|
if (fatalError) {
|
|
4058
3916
|
await observeFatalError(fatalError, attemptAbort);
|
|
4059
|
-
if (closeController.signal.aborted || request.
|
|
3917
|
+
if (closeController.signal.aborted || request.signal?.aborted) finishAborted(attemptAbort);
|
|
4060
3918
|
else finishError(fatalError);
|
|
4061
3919
|
return;
|
|
4062
3920
|
}
|
|
@@ -4068,7 +3926,7 @@ async function fetchEventStream(request, options) {
|
|
|
4068
3926
|
try {
|
|
4069
3927
|
retryDelay = await resolveRetryDelay(retryError, attemptAbort);
|
|
4070
3928
|
} catch (policyError) {
|
|
4071
|
-
if (closeController.signal.aborted || request.
|
|
3929
|
+
if (closeController.signal.aborted || request.signal?.aborted) finishAborted(attemptAbort);
|
|
4072
3930
|
else finishError(policyError);
|
|
4073
3931
|
return;
|
|
4074
3932
|
}
|
|
@@ -4080,7 +3938,7 @@ async function fetchEventStream(request, options) {
|
|
|
4080
3938
|
await wait(retryDelay, attemptAbort);
|
|
4081
3939
|
} catch (waitError) {
|
|
4082
3940
|
/* istanbul ignore else -- @preserve */
|
|
4083
|
-
if (closeController.signal.aborted || request.
|
|
3941
|
+
if (closeController.signal.aborted || request.signal?.aborted) finishAborted(attemptAbort);
|
|
4084
3942
|
else finishError(waitError);
|
|
4085
3943
|
return;
|
|
4086
3944
|
}
|
|
@@ -4298,15 +4156,15 @@ function toCloseReason(reason) {
|
|
|
4298
4156
|
/**
|
|
4299
4157
|
* Declare a typed Server-Sent Events endpoint.
|
|
4300
4158
|
*
|
|
4301
|
-
* Pass path, event structs, and buffer/queue limits. Call the returned
|
|
4302
|
-
* with
|
|
4159
|
+
* Pass path, event structs, and buffer/queue limits. Call the returned operation
|
|
4160
|
+
* with a client and, when declared, the input.
|
|
4303
4161
|
*
|
|
4304
4162
|
* - **definition** — `path`, an `events` map, and the required `maxBufferSize` and `maxQueueSize` limits (positive safe integers). Optional `method` (default `'GET'`), `operation`, `input`, and `error`. A declared `input` always requires an explicit `builder`. `input` only defines the parameters; the builder alone decides where each value goes. Only definitions without `input` may omit `builder`.
|
|
4305
4163
|
*
|
|
4306
4164
|
* `EventStreamDefinitionBase`, `EventStreamErrorField`, and `BuilderField` are internal helper types: `error` is required only when `TError` is a struct, and `builder` is `(input: BuilderInput<TInput>) => EventStreamRequestShape` when `input` is declared.
|
|
4307
4165
|
*
|
|
4308
4166
|
* @param definition - SSE contract (path, events, buffer limits, optional input).
|
|
4309
|
-
* @returns An
|
|
4167
|
+
* @returns An operation `(client, ...input) => Promise<error-first tuple>`.
|
|
4310
4168
|
*
|
|
4311
4169
|
* @group sse
|
|
4312
4170
|
* @exampleFile examples/api/define-event-stream.ts#example
|
|
@@ -4316,61 +4174,12 @@ function defineEventStream(definition) {
|
|
|
4316
4174
|
...definition,
|
|
4317
4175
|
method: definition.method ?? "GET"
|
|
4318
4176
|
};
|
|
4319
|
-
|
|
4320
|
-
return {
|
|
4321
|
-
[COMMAND_EXECUTE]: executeEventStreamCommand,
|
|
4322
|
-
[COMMAND_TYPE]: EVENT_STREAM_COMMAND,
|
|
4323
|
-
endpoint,
|
|
4324
|
-
input
|
|
4325
|
-
};
|
|
4326
|
-
}
|
|
4327
|
-
return ((input) => create(input));
|
|
4328
|
-
}
|
|
4329
|
-
/**
|
|
4330
|
-
* Open an SSE command against the given client config.
|
|
4331
|
-
*
|
|
4332
|
-
* Prefer `client.execute(command)` in application code; this is the low-level entry used by the client.
|
|
4333
|
-
*
|
|
4334
|
-
* The result is `[null, stream, open]` or `[fault, undefined, open | undefined]`. The fault is `Fault<TError>`: the decoded handshake body is on `data` when `error` is declared, and `Fault<undefined>` otherwise.
|
|
4335
|
-
*
|
|
4336
|
-
* @param clientConfig - Resolved client configuration.
|
|
4337
|
-
* @param command - SSE command from an `EventStreamEndpoint`.
|
|
4338
|
-
* @param options - Optional execute options (abort, timeout).
|
|
4339
|
-
* @returns Await-result tuple of `[error, stream, open]`.
|
|
4340
|
-
* @group sse
|
|
4341
|
-
*/
|
|
4342
|
-
async function executeEventStreamCommand(clientConfig, command, options) {
|
|
4343
|
-
const { endpoint, input } = command;
|
|
4344
|
-
return runEventStreamCommand(clientConfig, endpoint, input, options ?? {}, new AbortController());
|
|
4177
|
+
return (async (client, input) => runEventStreamCommand(client[CLIENT].config, endpoint, input, client[CLIENT].options, new AbortController()));
|
|
4345
4178
|
}
|
|
4346
4179
|
async function runEventStreamCommand(clientConfig, endpoint, input, config, controller) {
|
|
4347
|
-
|
|
4348
|
-
|
|
4349
|
-
|
|
4350
|
-
} catch (error) {
|
|
4351
|
-
return [
|
|
4352
|
-
createPreflightFault("REQ_OPTIONS_INVALID", error),
|
|
4353
|
-
void 0,
|
|
4354
|
-
void 0
|
|
4355
|
-
];
|
|
4356
|
-
}
|
|
4357
|
-
if (hasAbortTimeoutConflict(cancellation)) return [
|
|
4358
|
-
createAbortTimeoutConflictFault(),
|
|
4359
|
-
void 0,
|
|
4360
|
-
void 0
|
|
4361
|
-
];
|
|
4362
|
-
try {
|
|
4363
|
-
validateTransportTimeout(cancellation.timeout);
|
|
4364
|
-
} catch (error) {
|
|
4365
|
-
return [
|
|
4366
|
-
createPreflightFault("REQ_OPTIONS_INVALID", error),
|
|
4367
|
-
void 0,
|
|
4368
|
-
void 0
|
|
4369
|
-
];
|
|
4370
|
-
}
|
|
4371
|
-
const preAbortedSignal = [cancellation.abort, cancellation.signal].find((signal) => signal?.aborted);
|
|
4372
|
-
if (preAbortedSignal) return [
|
|
4373
|
-
resolveAbortedFault(preAbortedSignal),
|
|
4180
|
+
const callerAbort = config.signal;
|
|
4181
|
+
if (callerAbort?.aborted) return [
|
|
4182
|
+
resolveAbortedFault(callerAbort),
|
|
4374
4183
|
void 0,
|
|
4375
4184
|
void 0
|
|
4376
4185
|
];
|
|
@@ -4390,15 +4199,8 @@ async function runEventStreamCommand(clientConfig, endpoint, input, config, cont
|
|
|
4390
4199
|
void 0
|
|
4391
4200
|
];
|
|
4392
4201
|
}
|
|
4393
|
-
const
|
|
4394
|
-
const requestSignal = mergeAbortSignals(controller.signal, [
|
|
4395
|
-
cancellation.abort,
|
|
4396
|
-
cancellation.signal,
|
|
4397
|
-
deadline.signal
|
|
4398
|
-
]);
|
|
4399
|
-
deadline.releaseOnAbort(requestSignal);
|
|
4202
|
+
const requestSignal = mergeAbortSignals(controller.signal, [callerAbort]);
|
|
4400
4203
|
const stopBeforeTransport = (fault) => {
|
|
4401
|
-
deadline.release();
|
|
4402
4204
|
releaseAbortSignal(requestSignal);
|
|
4403
4205
|
return [
|
|
4404
4206
|
fault,
|
|
@@ -4411,28 +4213,27 @@ async function runEventStreamCommand(clientConfig, endpoint, input, config, cont
|
|
|
4411
4213
|
try {
|
|
4412
4214
|
parsedInput = parseEndpointInput(endpoint.input, input, inputGuides);
|
|
4413
4215
|
} catch (error) {
|
|
4414
|
-
return stopBeforeTransport(
|
|
4216
|
+
return stopBeforeTransport(resolveAbortFault(requestSignal) ?? createPreflightFault("REQ_INPUT_INVALID", error));
|
|
4415
4217
|
}
|
|
4416
|
-
const parsedCancellation =
|
|
4218
|
+
const parsedCancellation = resolveAbortFault(requestSignal);
|
|
4417
4219
|
if (parsedCancellation) return stopBeforeTransport(parsedCancellation);
|
|
4418
4220
|
let request;
|
|
4419
4221
|
try {
|
|
4420
4222
|
request = createBaseTransportRequest(endpoint.method, endpoint.path, parsedInput, endpoint.builder, {
|
|
4421
|
-
|
|
4223
|
+
signal: requestSignal,
|
|
4422
4224
|
baseEndpoint: clientConfig.endpoint,
|
|
4423
4225
|
defaultHeaders: clientConfig.headers,
|
|
4424
4226
|
inputDeclared: endpoint.input !== void 0,
|
|
4425
4227
|
inputGuides,
|
|
4426
4228
|
operation: endpoint.operation,
|
|
4427
4229
|
querySerializer: clientConfig.querySerializer,
|
|
4428
|
-
timeout: cancellation.timeout,
|
|
4429
4230
|
transport: "sse",
|
|
4430
4231
|
withCredentials: clientConfig.withCredentials
|
|
4431
4232
|
});
|
|
4432
4233
|
} catch (error) {
|
|
4433
|
-
return stopBeforeTransport(
|
|
4234
|
+
return stopBeforeTransport(resolveAbortFault(requestSignal) ?? createPreflightFault("REQ_BUILD_FAILED", error));
|
|
4434
4235
|
}
|
|
4435
|
-
const builtCancellation =
|
|
4236
|
+
const builtCancellation = resolveAbortFault(requestSignal);
|
|
4436
4237
|
if (builtCancellation) return stopBeforeTransport(builtCancellation);
|
|
4437
4238
|
let chainSettled = false;
|
|
4438
4239
|
let streamDelivered = false;
|
|
@@ -4454,8 +4255,8 @@ async function runEventStreamCommand(clientConfig, endpoint, input, config, cont
|
|
|
4454
4255
|
rejected.catch(() => void 0);
|
|
4455
4256
|
return rejected;
|
|
4456
4257
|
}
|
|
4457
|
-
const transportAbort = mergeAbortSignals(requestSignal, [req.
|
|
4458
|
-
if (
|
|
4258
|
+
const transportAbort = mergeAbortSignals(requestSignal, [req.signal]);
|
|
4259
|
+
if (resolveAbortFault(transportAbort)) {
|
|
4459
4260
|
const rejected = Promise.reject(transportOrigin.mark(resolveAbortCause(transportAbort)));
|
|
4460
4261
|
releaseAbortSignal(transportAbort);
|
|
4461
4262
|
rejected.catch(() => void 0);
|
|
@@ -4478,7 +4279,7 @@ async function runEventStreamCommand(clientConfig, endpoint, input, config, cont
|
|
|
4478
4279
|
const failure = {};
|
|
4479
4280
|
const promise = fetchEventStream({
|
|
4480
4281
|
...req,
|
|
4481
|
-
|
|
4282
|
+
signal: streamAbort
|
|
4482
4283
|
}, {
|
|
4483
4284
|
fetch: clientConfig.sse.handle,
|
|
4484
4285
|
failure,
|
|
@@ -4536,7 +4337,6 @@ async function runEventStreamCommand(clientConfig, endpoint, input, config, cont
|
|
|
4536
4337
|
openInfo
|
|
4537
4338
|
];
|
|
4538
4339
|
} finally {
|
|
4539
|
-
deadline.release();
|
|
4540
4340
|
if (!streamDelivered) releaseAbortSignal(requestSignal);
|
|
4541
4341
|
}
|
|
4542
4342
|
function discardOwnedStreams(reason, delivered) {
|
|
@@ -5330,7 +5130,7 @@ const struct = {
|
|
|
5330
5130
|
function createWebSocketRequest(params) {
|
|
5331
5131
|
const { queryParams, queryString } = resolveTransportQuery(params.query, params.querySerializer);
|
|
5332
5132
|
const request = {
|
|
5333
|
-
|
|
5133
|
+
signal: params.signal,
|
|
5334
5134
|
baseEndpoint: params.baseEndpoint,
|
|
5335
5135
|
endpoint: fillUrl(params.path, params.params),
|
|
5336
5136
|
headers: new Headers(),
|
|
@@ -5467,70 +5267,97 @@ function shouldReconnect(config, outcome, attempt) {
|
|
|
5467
5267
|
* Declare a typed WebSocket endpoint.
|
|
5468
5268
|
*
|
|
5469
5269
|
* Pass path, incoming (and optional outgoing) message structs, and queue limits.
|
|
5470
|
-
* Call the returned
|
|
5270
|
+
* Call the returned operation with a client and, when declared, the input.
|
|
5471
5271
|
*
|
|
5472
5272
|
* - **definition** — `path`, one `incoming` struct, and the required `maxIncomingQueueSize` (a positive safe integer). Optional `outgoing` and `input` structs, static `protocols`, `maxOutgoingQueueSize` (a non-negative safe integer), and `operation`. A declared `input` always requires an explicit `builder`. `input` only defines the parameters; the builder alone decides where each value goes. Only definitions without `input` may omit `builder`.
|
|
5473
5273
|
*
|
|
5474
5274
|
* A WebSocket `builder` may return `path` and `query` only; `headers` and `body` are rejected. `WebSocketDefinitionBase` and `BuilderField` are internal helper types.
|
|
5475
5275
|
*
|
|
5476
5276
|
* @param definition - WebSocket contract (path, structs, queue limits, optional input).
|
|
5477
|
-
* @returns An
|
|
5277
|
+
* @returns An operation `(client, ...input) => Promise<error-first tuple>`.
|
|
5478
5278
|
*
|
|
5479
5279
|
* @group web-socket
|
|
5480
5280
|
* @exampleFile examples/api/define-web-socket.ts#example
|
|
5481
5281
|
*/
|
|
5482
5282
|
function defineWebSocket(definition) {
|
|
5483
5283
|
const endpoint = { ...definition };
|
|
5484
|
-
|
|
5485
|
-
|
|
5486
|
-
|
|
5487
|
-
|
|
5488
|
-
|
|
5489
|
-
|
|
5284
|
+
return (async (client, input) => {
|
|
5285
|
+
const requestOptions = client[CLIENT].options;
|
|
5286
|
+
const controller = new AbortController();
|
|
5287
|
+
const socketState = {
|
|
5288
|
+
listeners: {
|
|
5289
|
+
runtimeError: /* @__PURE__ */ new Set(),
|
|
5290
|
+
stateChange: /* @__PURE__ */ new Set()
|
|
5291
|
+
},
|
|
5292
|
+
status: "idle",
|
|
5293
|
+
transition: 0
|
|
5490
5294
|
};
|
|
5295
|
+
return runWebSocketCommand(client[CLIENT].config, endpoint, input, requestOptions, controller, socketState);
|
|
5296
|
+
});
|
|
5297
|
+
}
|
|
5298
|
+
function readQueueLimits(maxIncomingQueueSize, maxOutgoingQueueSize) {
|
|
5299
|
+
try {
|
|
5300
|
+
validateWebSocketQueueLimits(maxIncomingQueueSize, maxOutgoingQueueSize);
|
|
5301
|
+
return [void 0, true];
|
|
5302
|
+
} catch (error) {
|
|
5303
|
+
return [createPreflightFault("REQ_OPTIONS_INVALID", error), void 0];
|
|
5491
5304
|
}
|
|
5492
|
-
return ((input) => create(input));
|
|
5493
5305
|
}
|
|
5494
|
-
|
|
5495
|
-
setSocketState(state, "connecting");
|
|
5496
|
-
let cancellationConfig;
|
|
5306
|
+
function readCallOptions(config, webSocket) {
|
|
5497
5307
|
try {
|
|
5498
|
-
|
|
5308
|
+
const beforeConnect = config.beforeConnect;
|
|
5309
|
+
const configuredProtocols = config.protocols;
|
|
5310
|
+
const protocols = configuredProtocols ? [...configuredProtocols] : void 0;
|
|
5311
|
+
const reconnectOption = config.reconnect ?? webSocket.reconnect;
|
|
5312
|
+
const heartbeatOption = config.heartbeat ?? webSocket.heartbeat;
|
|
5313
|
+
const heartbeat = heartbeatOption ? {
|
|
5314
|
+
intervalMs: heartbeatOption.intervalMs,
|
|
5315
|
+
isAck: heartbeatOption.isAck,
|
|
5316
|
+
message: heartbeatOption.message,
|
|
5317
|
+
timeoutMs: heartbeatOption.timeoutMs
|
|
5318
|
+
} : void 0;
|
|
5319
|
+
validateHeartbeatConfig(heartbeat);
|
|
5320
|
+
return [void 0, {
|
|
5321
|
+
beforeConnect,
|
|
5322
|
+
heartbeat,
|
|
5323
|
+
protocols,
|
|
5324
|
+
reconnect: normalizeReconnectConfig(reconnectOption)
|
|
5325
|
+
}];
|
|
5499
5326
|
} catch (error) {
|
|
5500
|
-
|
|
5501
|
-
state.error = definitionError;
|
|
5502
|
-
setSocketState(state, "error");
|
|
5503
|
-
return [
|
|
5504
|
-
definitionError,
|
|
5505
|
-
void 0,
|
|
5506
|
-
void 0
|
|
5507
|
-
];
|
|
5327
|
+
return [createPreflightFault("REQ_OPTIONS_INVALID", error), void 0];
|
|
5508
5328
|
}
|
|
5509
|
-
|
|
5510
|
-
|
|
5511
|
-
|
|
5512
|
-
|
|
5513
|
-
|
|
5514
|
-
|
|
5515
|
-
void 0,
|
|
5516
|
-
void 0
|
|
5517
|
-
];
|
|
5329
|
+
}
|
|
5330
|
+
function parseInput(inputStruct, input, guides, signal) {
|
|
5331
|
+
try {
|
|
5332
|
+
return [void 0, parseEndpointInput(inputStruct, input, guides)];
|
|
5333
|
+
} catch (error) {
|
|
5334
|
+
return [resolveAbortFault(signal) ?? createPreflightFault("REQ_INPUT_INVALID", error), void 0];
|
|
5518
5335
|
}
|
|
5336
|
+
}
|
|
5337
|
+
function buildOutbound(parsedInput, builder, inputDeclared, guides, signal) {
|
|
5519
5338
|
try {
|
|
5520
|
-
|
|
5339
|
+
return [void 0, materializeOutboundRequest(runOutboundBuilder(parsedInput, builder, inputDeclared), "webSocket", guides)];
|
|
5521
5340
|
} catch (error) {
|
|
5522
|
-
|
|
5523
|
-
|
|
5524
|
-
|
|
5525
|
-
|
|
5526
|
-
|
|
5527
|
-
|
|
5528
|
-
|
|
5529
|
-
];
|
|
5341
|
+
return [resolveAbortFault(signal) ?? createPreflightFault("REQ_BUILD_FAILED", error), void 0];
|
|
5342
|
+
}
|
|
5343
|
+
}
|
|
5344
|
+
function createSocketRequest(args) {
|
|
5345
|
+
try {
|
|
5346
|
+
return [void 0, createWebSocketRequest(args)];
|
|
5347
|
+
} catch (error) {
|
|
5348
|
+
return [resolveAbortFault(args.signal) ?? createPreflightFault("REQ_BUILD_FAILED", error), void 0];
|
|
5530
5349
|
}
|
|
5531
|
-
|
|
5532
|
-
|
|
5533
|
-
|
|
5350
|
+
}
|
|
5351
|
+
function resolveSocketConstructor(handle) {
|
|
5352
|
+
const candidate = handle ?? globalThis.WebSocket;
|
|
5353
|
+
if (typeof candidate !== "function") return [createPreflightFault("ENV_UNSUPPORTED", /* @__PURE__ */ new Error("WebSocket is not supported in current runtime")), void 0];
|
|
5354
|
+
return [void 0, candidate];
|
|
5355
|
+
}
|
|
5356
|
+
async function runWebSocketCommand(clientConfig, endpoint, input, config, controller, state) {
|
|
5357
|
+
setSocketState(state, "connecting");
|
|
5358
|
+
const callerAbort = config.signal;
|
|
5359
|
+
if (callerAbort?.aborted) {
|
|
5360
|
+
const aborted = resolveAbortedFault(callerAbort);
|
|
5534
5361
|
state.error = aborted;
|
|
5535
5362
|
setSocketState(state, aborted.code === "NET_ABORTED" ? "aborted" : "error");
|
|
5536
5363
|
return [
|
|
@@ -5539,56 +5366,26 @@ async function runWebSocketCommand(clientConfig, endpoint, input, config, contro
|
|
|
5539
5366
|
void 0
|
|
5540
5367
|
];
|
|
5541
5368
|
}
|
|
5542
|
-
|
|
5543
|
-
|
|
5544
|
-
} catch (error) {
|
|
5545
|
-
const definitionError = createPreflightFault("REQ_OPTIONS_INVALID", error);
|
|
5546
|
-
state.error = definitionError;
|
|
5547
|
-
setSocketState(state, "error");
|
|
5548
|
-
return [
|
|
5549
|
-
definitionError,
|
|
5550
|
-
void 0,
|
|
5551
|
-
void 0
|
|
5552
|
-
];
|
|
5553
|
-
}
|
|
5554
|
-
let beforeConnectOption;
|
|
5555
|
-
let protocolsOption;
|
|
5556
|
-
let reconnect;
|
|
5557
|
-
let heartbeatConfig;
|
|
5558
|
-
try {
|
|
5559
|
-
beforeConnectOption = config.beforeConnect;
|
|
5560
|
-
const configuredProtocols = config.protocols;
|
|
5561
|
-
protocolsOption = configuredProtocols ? [...configuredProtocols] : void 0;
|
|
5562
|
-
const reconnectOption = config.reconnect ?? clientConfig.webSocket.reconnect;
|
|
5563
|
-
const heartbeatOption = config.heartbeat ?? clientConfig.webSocket.heartbeat;
|
|
5564
|
-
heartbeatConfig = heartbeatOption ? {
|
|
5565
|
-
intervalMs: heartbeatOption.intervalMs,
|
|
5566
|
-
isAck: heartbeatOption.isAck,
|
|
5567
|
-
message: heartbeatOption.message,
|
|
5568
|
-
timeoutMs: heartbeatOption.timeoutMs
|
|
5569
|
-
} : void 0;
|
|
5570
|
-
validateHeartbeatConfig(heartbeatConfig);
|
|
5571
|
-
reconnect = normalizeReconnectConfig(reconnectOption);
|
|
5572
|
-
} catch (error) {
|
|
5573
|
-
const definitionError = createPreflightFault("REQ_OPTIONS_INVALID", error);
|
|
5574
|
-
state.error = definitionError;
|
|
5369
|
+
const stopInvalidOptions = (fault) => {
|
|
5370
|
+
state.error = fault;
|
|
5575
5371
|
setSocketState(state, "error");
|
|
5576
5372
|
return [
|
|
5577
|
-
|
|
5373
|
+
fault,
|
|
5578
5374
|
void 0,
|
|
5579
5375
|
void 0
|
|
5580
5376
|
];
|
|
5581
|
-
}
|
|
5582
|
-
const
|
|
5583
|
-
|
|
5584
|
-
|
|
5585
|
-
|
|
5586
|
-
|
|
5587
|
-
|
|
5377
|
+
};
|
|
5378
|
+
const [queueFault] = readQueueLimits(endpoint.maxIncomingQueueSize, endpoint.maxOutgoingQueueSize);
|
|
5379
|
+
if (queueFault) return stopInvalidOptions(queueFault);
|
|
5380
|
+
const [optionsFault, callOptions] = readCallOptions(config, clientConfig.webSocket);
|
|
5381
|
+
if (optionsFault) return stopInvalidOptions(optionsFault);
|
|
5382
|
+
const beforeConnectOption = callOptions.beforeConnect;
|
|
5383
|
+
const protocolsOption = callOptions.protocols;
|
|
5384
|
+
const reconnect = callOptions.reconnect;
|
|
5385
|
+
const heartbeatConfig = callOptions.heartbeat;
|
|
5386
|
+
const chainSignal = mergeAbortSignals(new AbortController().signal, [callerAbort]);
|
|
5588
5387
|
const signal = mergeAbortSignals(controller.signal, [chainSignal]);
|
|
5589
|
-
deadline.releaseOnAbort(chainSignal);
|
|
5590
5388
|
const stopBeforeTransport = (fault) => {
|
|
5591
|
-
deadline.release();
|
|
5592
5389
|
releaseAbortSignal(chainSignal);
|
|
5593
5390
|
releaseAbortSignal(signal);
|
|
5594
5391
|
state.error = fault;
|
|
@@ -5600,39 +5397,28 @@ async function runWebSocketCommand(clientConfig, endpoint, input, config, contro
|
|
|
5600
5397
|
];
|
|
5601
5398
|
};
|
|
5602
5399
|
const inputGuides = /* @__PURE__ */ new WeakMap();
|
|
5603
|
-
|
|
5604
|
-
|
|
5605
|
-
|
|
5606
|
-
} catch (error) {
|
|
5607
|
-
return stopBeforeTransport(resolveTransportCancellation(deadline, signal, createPreflightFault("REQ_INPUT_INVALID", error)));
|
|
5608
|
-
}
|
|
5609
|
-
const parsedCancellation = resolveTransportCancellation(deadline, signal);
|
|
5400
|
+
const [inputFault, parsedInput] = parseInput(endpoint.input, input, inputGuides, signal);
|
|
5401
|
+
if (inputFault) return stopBeforeTransport(inputFault);
|
|
5402
|
+
const parsedCancellation = resolveAbortFault(signal);
|
|
5610
5403
|
if (parsedCancellation) return stopBeforeTransport(parsedCancellation);
|
|
5611
|
-
|
|
5612
|
-
|
|
5613
|
-
|
|
5614
|
-
} catch (error) {
|
|
5615
|
-
return stopBeforeTransport(resolveTransportCancellation(deadline, signal, createPreflightFault("REQ_BUILD_FAILED", error)));
|
|
5616
|
-
}
|
|
5617
|
-
const builtCancellation = resolveTransportCancellation(deadline, signal);
|
|
5404
|
+
const [buildFault, built] = buildOutbound(parsedInput, endpoint.builder, endpoint.input !== void 0, inputGuides, signal);
|
|
5405
|
+
if (buildFault) return stopBeforeTransport(buildFault);
|
|
5406
|
+
const builtCancellation = resolveAbortFault(signal);
|
|
5618
5407
|
if (builtCancellation) return stopBeforeTransport(builtCancellation);
|
|
5619
|
-
|
|
5620
|
-
|
|
5621
|
-
|
|
5622
|
-
|
|
5623
|
-
|
|
5624
|
-
|
|
5625
|
-
|
|
5626
|
-
|
|
5627
|
-
|
|
5628
|
-
|
|
5629
|
-
|
|
5630
|
-
|
|
5631
|
-
|
|
5632
|
-
|
|
5633
|
-
}
|
|
5634
|
-
const WebSocketCtor = clientConfig.webSocket.handle ?? globalThis.WebSocket;
|
|
5635
|
-
if (typeof WebSocketCtor !== "function") return stopBeforeTransport(createPreflightFault("ENV_UNSUPPORTED", /* @__PURE__ */ new Error("WebSocket is not supported in current runtime")));
|
|
5408
|
+
const [requestFault, wsRequest] = createSocketRequest({
|
|
5409
|
+
signal,
|
|
5410
|
+
baseEndpoint: clientConfig.endpoint,
|
|
5411
|
+
operation: endpoint.operation,
|
|
5412
|
+
params: built.params,
|
|
5413
|
+
path: endpoint.path,
|
|
5414
|
+
query: built.query,
|
|
5415
|
+
querySerializer: clientConfig.querySerializer,
|
|
5416
|
+
withCredentials: clientConfig.withCredentials
|
|
5417
|
+
});
|
|
5418
|
+
if (requestFault) return stopBeforeTransport(requestFault);
|
|
5419
|
+
const constructorStage = resolveSocketConstructor(clientConfig.webSocket.handle);
|
|
5420
|
+
if (constructorStage[0]) return stopBeforeTransport(constructorStage[0]);
|
|
5421
|
+
const WebSocketCtor = constructorStage[1];
|
|
5636
5422
|
const WebSocketImpl = WebSocketCtor;
|
|
5637
5423
|
let startupConnection;
|
|
5638
5424
|
const undeliveredSessions = /* @__PURE__ */ new Set();
|
|
@@ -5648,7 +5434,7 @@ async function runWebSocketCommand(clientConfig, endpoint, input, config, contro
|
|
|
5648
5434
|
wsHandlerInvoked = true;
|
|
5649
5435
|
const executionSignal = signal;
|
|
5650
5436
|
const pendingSession = new Promise((resolveSession, rejectSession) => {
|
|
5651
|
-
const signal = mergeAbortSignals(executionSignal, [req.
|
|
5437
|
+
const signal = mergeAbortSignals(executionSignal, [req.signal]);
|
|
5652
5438
|
const incomingQueue = new AsyncQueue({ maxSize: endpoint.maxIncomingQueueSize });
|
|
5653
5439
|
const closedDeferred = Promise.withResolvers();
|
|
5654
5440
|
const sessionController = {
|
|
@@ -5800,15 +5586,6 @@ async function runWebSocketCommand(clientConfig, endpoint, input, config, contro
|
|
|
5800
5586
|
};
|
|
5801
5587
|
}
|
|
5802
5588
|
async function connectOnce(url, protocols) {
|
|
5803
|
-
const due = resolveTransportCancellation(deadline, signal);
|
|
5804
|
-
if (due) return {
|
|
5805
|
-
cause: due,
|
|
5806
|
-
closeInfo: {
|
|
5807
|
-
cause: due,
|
|
5808
|
-
reason: due.message
|
|
5809
|
-
},
|
|
5810
|
-
opened: false
|
|
5811
|
-
};
|
|
5812
5589
|
let socket;
|
|
5813
5590
|
try {
|
|
5814
5591
|
socket = protocols.length > 0 ? new WebSocketImpl(url, [...protocols]) : new WebSocketImpl(url);
|
|
@@ -5900,7 +5677,6 @@ async function runWebSocketCommand(clientConfig, endpoint, input, config, contro
|
|
|
5900
5677
|
if (!startupSettled) {
|
|
5901
5678
|
startupConnection = latestConnection;
|
|
5902
5679
|
startupSettled = true;
|
|
5903
|
-
deadline.release();
|
|
5904
5680
|
resolveSession(session);
|
|
5905
5681
|
}
|
|
5906
5682
|
if (state.status === "open") startHeartbeat(socket, sessionController, heartbeatConfig, endpoint.outgoing, (cause) => {
|
|
@@ -6047,7 +5823,6 @@ async function runWebSocketCommand(clientConfig, endpoint, input, config, contro
|
|
|
6047
5823
|
/* istanbul ignore if -- @preserve defensive once-only gate: terminal callers and reentrant state transitions check finished before invoking finish */
|
|
6048
5824
|
if (finished) return;
|
|
6049
5825
|
finished = true;
|
|
6050
|
-
deadline.release();
|
|
6051
5826
|
stopHeartbeat(sessionController);
|
|
6052
5827
|
const activeSocket = sessionController.currentSocket;
|
|
6053
5828
|
if (final.kind !== "closed" && activeSocket && !options.skipNativeClose) requestNativeCloseBestEffort(activeSocket);
|
|
@@ -6162,7 +5937,6 @@ async function runWebSocketCommand(clientConfig, endpoint, input, config, contro
|
|
|
6162
5937
|
const publicConnection = session?.connection;
|
|
6163
5938
|
if (typeof session?.closed?.then !== "function" || typeof session.close !== "function" || typeof session.send !== "function" || typeof session.receive?.[Symbol.asyncIterator] !== "function" || typeof session[Symbol.asyncDispose] !== "function" || typeof session.onRuntimeError !== "function" || typeof session.onStateChange !== "function" || typeof publicConnection?.generation !== "number" || typeof session.bufferedAmount !== "number" || typeof session.state !== "string") throw new TypeError("WebSocket interceptor must resolve a WebSocket session");
|
|
6164
5939
|
chainSettled = true;
|
|
6165
|
-
deadline.release();
|
|
6166
5940
|
releaseAbortSignal(chainSignal);
|
|
6167
5941
|
let deliveredSession = [...undeliveredSessions].some((createdSession) => createdSession === session || createdSession.closed === session.closed);
|
|
6168
5942
|
const pendingSessionSnapshot = [...pendingSessions];
|
|
@@ -6188,7 +5962,6 @@ async function runWebSocketCommand(clientConfig, endpoint, input, config, contro
|
|
|
6188
5962
|
];
|
|
6189
5963
|
} catch (error) {
|
|
6190
5964
|
chainSettled = true;
|
|
6191
|
-
deadline.release();
|
|
6192
5965
|
releaseAbortSignal(chainSignal);
|
|
6193
5966
|
const signalError = resolveAbortFault(chainSignal);
|
|
6194
5967
|
const requestError = signalError ?? (isFault(error) ? error : createPreflightFault("EXT_INTERCEPTOR_FAILED", error));
|
|
@@ -6212,32 +5985,6 @@ async function runWebSocketCommand(clientConfig, endpoint, input, config, contro
|
|
|
6212
5985
|
];
|
|
6213
5986
|
}
|
|
6214
5987
|
}
|
|
6215
|
-
/**
|
|
6216
|
-
* Open a WebSocket command against the given client config.
|
|
6217
|
-
*
|
|
6218
|
-
* Prefer `client.execute(command)` in application code; this is the low-level entry used by the client.
|
|
6219
|
-
*
|
|
6220
|
-
* The result is `[null, session, connection]` or `[fault, undefined, connection | undefined]`, where the fault is `Fault<undefined>`.
|
|
6221
|
-
*
|
|
6222
|
-
* WebSocket execute options can override the client's `beforeConnect`, `heartbeat`, `protocols`, and `reconnect`.
|
|
6223
|
-
*
|
|
6224
|
-
* @param clientConfig - Resolved client configuration.
|
|
6225
|
-
* @param command - WebSocket command from a `WebSocketEndpoint`.
|
|
6226
|
-
* @param options - Optional execute options (heartbeat, reconnect, abort).
|
|
6227
|
-
* @returns Await-result tuple of `[error, socket, connection]`.
|
|
6228
|
-
* @group web-socket
|
|
6229
|
-
*/
|
|
6230
|
-
async function executeWebSocketCommand(clientConfig, command, options) {
|
|
6231
|
-
const { endpoint, input } = command;
|
|
6232
|
-
return runWebSocketCommand(clientConfig, endpoint, input, options ?? {}, new AbortController(), {
|
|
6233
|
-
listeners: {
|
|
6234
|
-
runtimeError: /* @__PURE__ */ new Set(),
|
|
6235
|
-
stateChange: /* @__PURE__ */ new Set()
|
|
6236
|
-
},
|
|
6237
|
-
status: "idle",
|
|
6238
|
-
transition: 0
|
|
6239
|
-
});
|
|
6240
|
-
}
|
|
6241
5988
|
const SOCKET_CLOSE_GRACE_MS = 1e3;
|
|
6242
5989
|
const noop = () => void 0;
|
|
6243
5990
|
function createWebSocketSession(outgoing, queue, closed, state, sessionController, sendQueue, maxOutgoingQueueSize, openState, isUnavailable, requestClose, disposeSession) {
|
|
@@ -6413,4 +6160,4 @@ function faultFromCloseInfo(closeInfo) {
|
|
|
6413
6160
|
function isFault(value) {
|
|
6414
6161
|
return typeof value === "object" && value !== null && "code" in value && typeof value.code === "string" && value instanceof Error;
|
|
6415
6162
|
}
|
|
6416
|
-
export { ERR_ABORTED, ERR_TIMEOUT, StructError, basicAuthHttpInterceptor, basicAuthSSEInterceptor, createClient, createDecodeFault, createHttpInterceptor, createHttpStatusFault, createNetworkFault, createPreflightFault, createResolvedRequestUrl, createSSEInterceptor, createUndecodedHttpStatusFault, createWebSocketInterceptor, defineEventStream, defineRequest, defineWebSocket,
|
|
6163
|
+
export { ERR_ABORTED, ERR_TIMEOUT, StructError, basicAuthHttpInterceptor, basicAuthSSEInterceptor, createClient, createDecodeFault, createHttpInterceptor, createHttpStatusFault, createNetworkFault, createPreflightFault, createResolvedRequestUrl, createSSEInterceptor, createUndecodedHttpStatusFault, createWebSocketInterceptor, defineEventStream, defineRequest, defineWebSocket, fetchHandler, isAbortCause, isTimeoutCause, makeResponse, struct, withCredentials, withEndpoint, withHTTPHandle, withHeaders, withInterceptors, withQuerySerializer, withSSEHandle, withSSEOnInvalidEvent, withSSEReconnect, withWebSocketBeforeConnect, withWebSocketHandle, withWebSocketHeartbeat, withWebSocketProtocols, withWebSocketReconnect, withXSRF };
|