@fuzdev/fuz_app 0.28.0 → 0.29.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/actions/action_peer.d.ts +12 -11
- package/dist/actions/action_peer.d.ts.map +1 -1
- package/dist/actions/action_peer.js +4 -1
- package/dist/actions/rpc_client.d.ts +6 -8
- package/dist/actions/rpc_client.d.ts.map +1 -1
- package/dist/actions/rpc_client.js +2 -0
- package/dist/actions/socket.svelte.d.ts +16 -5
- package/dist/actions/socket.svelte.d.ts.map +1 -1
- package/dist/actions/socket.svelte.js +43 -22
- package/dist/actions/transports.d.ts +22 -5
- package/dist/actions/transports.d.ts.map +1 -1
- package/dist/actions/transports_ws.d.ts.map +1 -1
- package/dist/actions/transports_ws.js +14 -6
- package/dist/http/jsonrpc_errors.d.ts +19 -3
- package/dist/http/jsonrpc_errors.d.ts.map +1 -1
- package/dist/http/jsonrpc_errors.js +30 -2
- package/package.json +1 -1
|
@@ -7,28 +7,29 @@
|
|
|
7
7
|
* @module
|
|
8
8
|
*/
|
|
9
9
|
import { JsonrpcMessageFromServerToClient, JsonrpcNotification, JsonrpcRequest, JsonrpcResponseOrError, JsonrpcErrorResponse } from '../http/jsonrpc.js';
|
|
10
|
-
import { Transports, type TransportName } from './transports.js';
|
|
10
|
+
import { Transports, type TransportName, type TransportSendOptions } from './transports.js';
|
|
11
11
|
import type { ActionEventEnvironment } from './action_event_types.js';
|
|
12
|
-
|
|
12
|
+
/**
|
|
13
|
+
* Per-call options for `ActionPeer.send`. Extends `TransportSendOptions`
|
|
14
|
+
* with `transport_name` for per-call transport selection. The peer-wide
|
|
15
|
+
* default for any field lives on `ActionPeerOptions.default_send_options` —
|
|
16
|
+
* set `queue: true` there once for client-authoritative peers and override
|
|
17
|
+
* per-call for exceptions (e.g. high-frequency position sync where stale
|
|
18
|
+
* replays are wrong).
|
|
19
|
+
*/
|
|
20
|
+
export interface ActionPeerSendOptions extends TransportSendOptions {
|
|
13
21
|
transport_name?: TransportName;
|
|
14
|
-
/**
|
|
15
|
-
* Per-call `AbortSignal`. Forwarded into the chosen transport's `send`,
|
|
16
|
-
* which bottoms out at `FrontendWebsocketClient.request({signal})` for WS
|
|
17
|
-
* (sends the shared `cancel` notification on abort) and at
|
|
18
|
-
* `fetch({signal})` for HTTP. Backend transport ignores it.
|
|
19
|
-
*/
|
|
20
|
-
signal?: AbortSignal;
|
|
21
22
|
}
|
|
22
23
|
export interface ActionPeerOptions {
|
|
23
24
|
environment: ActionEventEnvironment;
|
|
24
25
|
transports?: Transports;
|
|
25
|
-
default_send_options?:
|
|
26
|
+
default_send_options?: Omit<ActionPeerSendOptions, 'signal'>;
|
|
26
27
|
}
|
|
27
28
|
export declare class ActionPeer {
|
|
28
29
|
#private;
|
|
29
30
|
readonly environment: ActionEventEnvironment;
|
|
30
31
|
readonly transports: Transports;
|
|
31
|
-
default_send_options: ActionPeerSendOptions
|
|
32
|
+
default_send_options: Omit<ActionPeerSendOptions, 'signal'>;
|
|
32
33
|
constructor(options: ActionPeerOptions);
|
|
33
34
|
send(message: JsonrpcRequest, options?: ActionPeerSendOptions): Promise<JsonrpcResponseOrError>;
|
|
34
35
|
send(message: JsonrpcNotification, options?: ActionPeerSendOptions): Promise<JsonrpcErrorResponse | null>;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"action_peer.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/actions/action_peer.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,EAEN,gCAAgC,EAChC,mBAAmB,EACnB,cAAc,EACd,sBAAsB,EACtB,oBAAoB,EACpB,MAAM,oBAAoB,CAAC;AAU5B,OAAO,EAAC,UAAU,EAAE,KAAK,aAAa,EAAC,MAAM,iBAAiB,CAAC;
|
|
1
|
+
{"version":3,"file":"action_peer.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/actions/action_peer.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,EAEN,gCAAgC,EAChC,mBAAmB,EACnB,cAAc,EACd,sBAAsB,EACtB,oBAAoB,EACpB,MAAM,oBAAoB,CAAC;AAU5B,OAAO,EAAC,UAAU,EAAE,KAAK,aAAa,EAAE,KAAK,oBAAoB,EAAC,MAAM,iBAAiB,CAAC;AAC1F,OAAO,KAAK,EAAC,sBAAsB,EAAC,MAAM,yBAAyB,CAAC;AAOpE;;;;;;;GAOG;AACH,MAAM,WAAW,qBAAsB,SAAQ,oBAAoB;IAClE,cAAc,CAAC,EAAE,aAAa,CAAC;CAC/B;AAED,MAAM,WAAW,iBAAiB;IACjC,WAAW,EAAE,sBAAsB,CAAC;IAGpC,UAAU,CAAC,EAAE,UAAU,CAAC;IAKxB,oBAAoB,CAAC,EAAE,IAAI,CAAC,qBAAqB,EAAE,QAAQ,CAAC,CAAC;CAC7D;AAED,qBAAa,UAAU;;IACtB,QAAQ,CAAC,WAAW,EAAE,sBAAsB,CAAC;IAC7C,QAAQ,CAAC,UAAU,EAAE,UAAU,CAAC;IAMhC,oBAAoB,EAAE,IAAI,CAAC,qBAAqB,EAAE,QAAQ,CAAC,CAAC;gBAEhD,OAAO,EAAE,iBAAiB;IAOhC,IAAI,CACT,OAAO,EAAE,cAAc,EACvB,OAAO,CAAC,EAAE,qBAAqB,GAC7B,OAAO,CAAC,sBAAsB,CAAC;IAC5B,IAAI,CACT,OAAO,EAAE,mBAAmB,EAC5B,OAAO,CAAC,EAAE,qBAAqB,GAC7B,OAAO,CAAC,oBAAoB,GAAG,IAAI,CAAC;IA8CjC,OAAO,CAAC,OAAO,EAAE,OAAO,GAAG,OAAO,CAAC,gCAAgC,GAAG,IAAI,CAAC;CAyIjF"}
|
|
@@ -33,7 +33,10 @@ export class ActionPeer {
|
|
|
33
33
|
}
|
|
34
34
|
const message_type = is_jsonrpc_request(message) ? 'request' : 'notification';
|
|
35
35
|
this.environment.log?.debug(`[peer] send ${message_type}:`, message.method, `via ${transport.transport_name}`);
|
|
36
|
-
const result = await transport.send(message, {
|
|
36
|
+
const result = await transport.send(message, {
|
|
37
|
+
signal: options?.signal,
|
|
38
|
+
queue: options?.queue ?? this.default_send_options.queue,
|
|
39
|
+
});
|
|
37
40
|
if (result && 'error' in result) {
|
|
38
41
|
this.environment.log?.error(`[peer] send ${message_type} failed:`, message.method, result.error.message);
|
|
39
42
|
}
|
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
* @module
|
|
11
11
|
*/
|
|
12
12
|
import type { ActionEventEnvironment } from './action_event_types.js';
|
|
13
|
-
import type { ActionPeer } from './action_peer.js';
|
|
13
|
+
import type { ActionPeer, ActionPeerSendOptions } from './action_peer.js';
|
|
14
14
|
import type { ActionEventDataUnion } from './action_event_data.js';
|
|
15
15
|
import type { TransportName } from './transports.js';
|
|
16
16
|
/**
|
|
@@ -58,13 +58,11 @@ export interface CreateRpcClientOptions {
|
|
|
58
58
|
*/
|
|
59
59
|
export declare const create_rpc_client: (options: CreateRpcClientOptions) => Record<string, (...args: Array<any>) => any>;
|
|
60
60
|
/**
|
|
61
|
-
* Per-call options accepted by every typed Proxy method.
|
|
62
|
-
*
|
|
63
|
-
*
|
|
64
|
-
*
|
|
61
|
+
* Per-call options accepted by every typed Proxy method. Same shape as
|
|
62
|
+
* `ActionPeerSendOptions` — the client threads these through unchanged
|
|
63
|
+
* to the underlying peer. `transport_name` overrides the per-method
|
|
64
|
+
* `transport_for_method` selector for this call.
|
|
65
65
|
*/
|
|
66
|
-
export interface RpcClientCallOptions {
|
|
67
|
-
signal?: AbortSignal;
|
|
68
|
-
transport_name?: TransportName;
|
|
66
|
+
export interface RpcClientCallOptions extends ActionPeerSendOptions {
|
|
69
67
|
}
|
|
70
68
|
//# sourceMappingURL=rpc_client.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"rpc_client.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/actions/rpc_client.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAQH,OAAO,KAAK,EAAC,sBAAsB,EAAC,MAAM,yBAAyB,CAAC;AAOpE,OAAO,KAAK,EAAC,UAAU,EAAC,MAAM,kBAAkB,CAAC;
|
|
1
|
+
{"version":3,"file":"rpc_client.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/actions/rpc_client.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAQH,OAAO,KAAK,EAAC,sBAAsB,EAAC,MAAM,yBAAyB,CAAC;AAOpE,OAAO,KAAK,EAAC,UAAU,EAAE,qBAAqB,EAAC,MAAM,kBAAkB,CAAC;AACxE,OAAO,KAAK,EAAC,oBAAoB,EAAC,MAAM,wBAAwB,CAAC;AACjE,OAAO,KAAK,EAAC,aAAa,EAAC,MAAM,iBAAiB,CAAC;AAGnD;;;;;;;GAOG;AACH,MAAM,MAAM,kBAAkB,GAAG,CAAC,MAAM,EAAE,MAAM,KAAK,aAAa,GAAG,SAAS,CAAC;AAM/E,8EAA8E;AAC9E,MAAM,WAAW,sBAAsB;IACtC,aAAa,EAAE,CAAC,IAAI,EAAE;QAAC,MAAM,EAAE,MAAM,CAAC;QAAC,iBAAiB,EAAE,oBAAoB,CAAA;KAAC,KAC5E;QACA,sBAAsB,EAAE,CAAC,KAAK,EAAE,GAAG,KAAK,IAAI,CAAC;KAC5C,GACD,SAAS,CAAC;CACb;AAED,uCAAuC;AACvC,MAAM,WAAW,sBAAsB;IACtC,IAAI,EAAE,UAAU,CAAC;IACjB,WAAW,EAAE,sBAAsB,CAAC;IACpC,kEAAkE;IAClE,OAAO,CAAC,EAAE,sBAAsB,CAAC;IACjC;;;;;OAKG;IACH,oBAAoB,CAAC,EAAE,kBAAkB,CAAC;CAC1C;AAED;;;;;;;;;;GAUG;AACH,eAAO,MAAM,iBAAiB,GAC7B,SAAS,sBAAsB,KAC7B,MAAM,CAAC,MAAM,EAAE,CAAC,GAAG,IAAI,EAAE,KAAK,CAAC,GAAG,CAAC,KAAK,GAAG,CAgB7C,CAAC;AA2DF;;;;;GAKG;AACH,MAAM,WAAW,oBAAqB,SAAQ,qBAAqB;CAAG"}
|
|
@@ -127,6 +127,7 @@ const create_request_response_method = (peer, environment, spec, actions, transp
|
|
|
127
127
|
const response = await peer.send(event.data.request, {
|
|
128
128
|
transport_name: options?.transport_name ?? transport_for_method?.(spec.method),
|
|
129
129
|
signal: options?.signal,
|
|
130
|
+
queue: options?.queue,
|
|
130
131
|
});
|
|
131
132
|
event.transition('receive_response');
|
|
132
133
|
// TODO @api shouldn't this happen in the peer like the other method calls?
|
|
@@ -155,6 +156,7 @@ const create_remote_notification_method = (peer, environment, spec, actions, tra
|
|
|
155
156
|
const send_result = await peer.send(event.data.notification, {
|
|
156
157
|
transport_name: options?.transport_name ?? transport_for_method?.(spec.method),
|
|
157
158
|
signal: options?.signal,
|
|
159
|
+
queue: options?.queue,
|
|
158
160
|
});
|
|
159
161
|
// Check if notification failed to send
|
|
160
162
|
if (send_result !== null) {
|
|
@@ -218,8 +218,7 @@ export declare class FrontendWebsocketClient implements WebsocketConnection, Dis
|
|
|
218
218
|
* id (or uses an explicit one supplied via `options.id` — used by
|
|
219
219
|
* `FrontendWebsocketTransport` which delegates to this method and has its
|
|
220
220
|
* own peer-minted UUID), tracks the pending promise, and resolves when the
|
|
221
|
-
* server sends a matching response
|
|
222
|
-
* close, or aborted signal).
|
|
221
|
+
* server sends a matching response.
|
|
223
222
|
*
|
|
224
223
|
* Callers supplying an explicit `options.id` are responsible for
|
|
225
224
|
* uniqueness — the pending map is keyed by id, and a duplicate silently
|
|
@@ -234,10 +233,22 @@ export declare class FrontendWebsocketClient implements WebsocketConnection, Dis
|
|
|
234
233
|
* disconnect-detection slot.
|
|
235
234
|
*
|
|
236
235
|
* On `AbortSignal` fire: rejects the local promise *and* sends the shared
|
|
237
|
-
* `cancel` notification (
|
|
238
|
-
* can abort the matching handler's `ctx.signal`. Suppressed
|
|
239
|
-
* queued-but-never-sent (server doesn't know about it) and
|
|
236
|
+
* `cancel` notification ({@link CANCEL_METHOD}) so the server-side
|
|
237
|
+
* dispatcher can abort the matching handler's `ctx.signal`. Suppressed
|
|
238
|
+
* for queued-but-never-sent (server doesn't know about it) and
|
|
240
239
|
* response-beat-cancel races.
|
|
240
|
+
*
|
|
241
|
+
* Rejections throw `ThrownJsonrpcError` with a specific code so
|
|
242
|
+
* `FrontendWebsocketTransport` can preserve the code verbatim in its
|
|
243
|
+
* error envelope rather than collapsing every rejection to
|
|
244
|
+
* `internal_error`:
|
|
245
|
+
* - `unauthenticated` — session revoked (entry check or close code);
|
|
246
|
+
* - `request_cancelled` — caller's `AbortSignal` fired;
|
|
247
|
+
* - `queue_overflow` — durable queue full;
|
|
248
|
+
* - `service_unavailable` — socket not connected / closed / torn down
|
|
249
|
+
* mid-flight;
|
|
250
|
+
* - `internal_error` — `ws.send` threw (serialization, buffer full);
|
|
251
|
+
* - server's wire code verbatim — JSON-RPC error frame from peer.
|
|
241
252
|
*/
|
|
242
253
|
request<R = unknown>(method: string, params?: unknown, options?: {
|
|
243
254
|
signal?: AbortSignal;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"socket.svelte.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/actions/socket.svelte.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AAGH,OAAO,KAAK,EAAC,MAAM,EAAC,MAAM,yBAAyB,CAAC;AACpD,OAAO,KAAK,EAAC,WAAW,EAAC,MAAM,2BAA2B,CAAC;AAE3D,OAAO,
|
|
1
|
+
{"version":3,"file":"socket.svelte.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/actions/socket.svelte.ts"],"names":[],"mappings":"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"}
|
|
@@ -25,6 +25,7 @@
|
|
|
25
25
|
*/
|
|
26
26
|
import { BROWSER } from 'esm-env';
|
|
27
27
|
import { JSONRPC_VERSION } from '../http/jsonrpc.js';
|
|
28
|
+
import { JSONRPC_ERROR_CODES, ThrownJsonrpcError, jsonrpc_errors } from '../http/jsonrpc_errors.js';
|
|
28
29
|
import { WS_CLOSE_CLIENT_HEARTBEAT_TIMEOUT, WS_CLOSE_SESSION_REVOKED } from './transports.js';
|
|
29
30
|
import { CANCEL_METHOD } from './cancel.js';
|
|
30
31
|
import { HEARTBEAT_METHOD } from './heartbeat.js';
|
|
@@ -273,7 +274,7 @@ export class FrontendWebsocketClient {
|
|
|
273
274
|
this.#teardown(code);
|
|
274
275
|
this.status = 'closed';
|
|
275
276
|
this.#reset_reconnect_counters();
|
|
276
|
-
this.#reject_all('client disconnected');
|
|
277
|
+
this.#reject_all('client disconnected', jsonrpc_errors.service_unavailable);
|
|
277
278
|
}
|
|
278
279
|
/** Explicit-resource-management hook — supports `using client = new FrontendWebsocketClient(url)`. */
|
|
279
280
|
[Symbol.dispose]() {
|
|
@@ -299,8 +300,7 @@ export class FrontendWebsocketClient {
|
|
|
299
300
|
* id (or uses an explicit one supplied via `options.id` — used by
|
|
300
301
|
* `FrontendWebsocketTransport` which delegates to this method and has its
|
|
301
302
|
* own peer-minted UUID), tracks the pending promise, and resolves when the
|
|
302
|
-
* server sends a matching response
|
|
303
|
-
* close, or aborted signal).
|
|
303
|
+
* server sends a matching response.
|
|
304
304
|
*
|
|
305
305
|
* Callers supplying an explicit `options.id` are responsible for
|
|
306
306
|
* uniqueness — the pending map is keyed by id, and a duplicate silently
|
|
@@ -315,17 +315,29 @@ export class FrontendWebsocketClient {
|
|
|
315
315
|
* disconnect-detection slot.
|
|
316
316
|
*
|
|
317
317
|
* On `AbortSignal` fire: rejects the local promise *and* sends the shared
|
|
318
|
-
* `cancel` notification (
|
|
319
|
-
* can abort the matching handler's `ctx.signal`. Suppressed
|
|
320
|
-
* queued-but-never-sent (server doesn't know about it) and
|
|
318
|
+
* `cancel` notification ({@link CANCEL_METHOD}) so the server-side
|
|
319
|
+
* dispatcher can abort the matching handler's `ctx.signal`. Suppressed
|
|
320
|
+
* for queued-but-never-sent (server doesn't know about it) and
|
|
321
321
|
* response-beat-cancel races.
|
|
322
|
+
*
|
|
323
|
+
* Rejections throw `ThrownJsonrpcError` with a specific code so
|
|
324
|
+
* `FrontendWebsocketTransport` can preserve the code verbatim in its
|
|
325
|
+
* error envelope rather than collapsing every rejection to
|
|
326
|
+
* `internal_error`:
|
|
327
|
+
* - `unauthenticated` — session revoked (entry check or close code);
|
|
328
|
+
* - `request_cancelled` — caller's `AbortSignal` fired;
|
|
329
|
+
* - `queue_overflow` — durable queue full;
|
|
330
|
+
* - `service_unavailable` — socket not connected / closed / torn down
|
|
331
|
+
* mid-flight;
|
|
332
|
+
* - `internal_error` — `ws.send` threw (serialization, buffer full);
|
|
333
|
+
* - server's wire code verbatim — JSON-RPC error frame from peer.
|
|
322
334
|
*/
|
|
323
335
|
request(method, params = {}, options = {}) {
|
|
324
336
|
return new Promise((resolve, reject) => {
|
|
325
337
|
const resolve_typed = resolve;
|
|
326
338
|
const reject_typed = reject;
|
|
327
339
|
if (this.#revoked) {
|
|
328
|
-
reject_typed(
|
|
340
|
+
reject_typed(jsonrpc_errors.unauthenticated('[socket] session revoked'));
|
|
329
341
|
return;
|
|
330
342
|
}
|
|
331
343
|
const { signal = null } = options;
|
|
@@ -378,7 +390,7 @@ export class FrontendWebsocketClient {
|
|
|
378
390
|
return;
|
|
379
391
|
}
|
|
380
392
|
this.#detach_signal(pending);
|
|
381
|
-
reject_typed(
|
|
393
|
+
reject_typed(jsonrpc_errors.internal_error(`[socket] send failed for ${method}`));
|
|
382
394
|
return;
|
|
383
395
|
}
|
|
384
396
|
if (should_queue) {
|
|
@@ -386,7 +398,7 @@ export class FrontendWebsocketClient {
|
|
|
386
398
|
return;
|
|
387
399
|
}
|
|
388
400
|
this.#detach_signal(pending);
|
|
389
|
-
reject_typed(
|
|
401
|
+
reject_typed(jsonrpc_errors.service_unavailable(`[socket] not connected (method=${method})`));
|
|
390
402
|
});
|
|
391
403
|
}
|
|
392
404
|
add_message_handler(handler) {
|
|
@@ -398,7 +410,7 @@ export class FrontendWebsocketClient {
|
|
|
398
410
|
return () => this.#error_handlers.delete(handler);
|
|
399
411
|
}
|
|
400
412
|
#build_abort_error(method) {
|
|
401
|
-
return
|
|
413
|
+
return jsonrpc_errors.request_cancelled(`[socket] request aborted (method=${method})`);
|
|
402
414
|
}
|
|
403
415
|
/**
|
|
404
416
|
* Fire-and-forget cancel notification to the server. The dispatcher
|
|
@@ -421,7 +433,7 @@ export class FrontendWebsocketClient {
|
|
|
421
433
|
#enqueue(queued) {
|
|
422
434
|
if (this.#queue.length >= this.#queue_max_size) {
|
|
423
435
|
this.#detach_signal(queued);
|
|
424
|
-
queued.reject(
|
|
436
|
+
queued.reject(jsonrpc_errors.queue_overflow(`[socket] request queue overflow (method=${queued.method}, max=${this.#queue_max_size})`));
|
|
425
437
|
return;
|
|
426
438
|
}
|
|
427
439
|
this.#queue.push(queued);
|
|
@@ -454,25 +466,25 @@ export class FrontendWebsocketClient {
|
|
|
454
466
|
}
|
|
455
467
|
else {
|
|
456
468
|
this.#detach_signal(q);
|
|
457
|
-
q.reject(
|
|
469
|
+
q.reject(jsonrpc_errors.internal_error(`[socket] queued request send failed (method=${q.method})`));
|
|
458
470
|
}
|
|
459
471
|
}
|
|
460
472
|
}
|
|
461
|
-
#reject_all(reason) {
|
|
473
|
+
#reject_all(reason, make_error) {
|
|
462
474
|
const pending = this.#pending;
|
|
463
475
|
this.#pending = new Map();
|
|
464
476
|
for (const [id, p] of pending) {
|
|
465
477
|
this.#detach_signal(p);
|
|
466
|
-
p.reject(
|
|
478
|
+
p.reject(make_error(`[socket] ${reason} (method=${p.method}, id=${id})`));
|
|
467
479
|
}
|
|
468
480
|
const queued = this.#queue;
|
|
469
481
|
this.#queue = [];
|
|
470
482
|
for (const q of queued) {
|
|
471
483
|
this.#detach_signal(q);
|
|
472
|
-
q.reject(
|
|
484
|
+
q.reject(make_error(`[socket] ${reason} (method=${q.method})`));
|
|
473
485
|
}
|
|
474
486
|
}
|
|
475
|
-
#reject_pending_only(reason) {
|
|
487
|
+
#reject_pending_only(reason, make_error) {
|
|
476
488
|
// Socket closed but auto-reconnect will try again — pending requests were
|
|
477
489
|
// in flight on the old socket so we can't correlate them after reopen;
|
|
478
490
|
// queued requests haven't been sent yet and stay buffered for the flush.
|
|
@@ -480,7 +492,7 @@ export class FrontendWebsocketClient {
|
|
|
480
492
|
this.#pending = new Map();
|
|
481
493
|
for (const [id, p] of pending) {
|
|
482
494
|
this.#detach_signal(p);
|
|
483
|
-
p.reject(
|
|
495
|
+
p.reject(make_error(`[socket] ${reason} (method=${p.method}, id=${id})`));
|
|
484
496
|
}
|
|
485
497
|
}
|
|
486
498
|
#start_heartbeat() {
|
|
@@ -547,7 +559,7 @@ export class FrontendWebsocketClient {
|
|
|
547
559
|
// record it here so the client-initiated close is still observable,
|
|
548
560
|
// and reject any pending requests that can never resolve now.
|
|
549
561
|
this.#record_close(close_code, '');
|
|
550
|
-
this.#reject_pending_only(`socket torn down (code ${close_code})
|
|
562
|
+
this.#reject_pending_only(`socket torn down (code ${close_code})`, jsonrpc_errors.service_unavailable);
|
|
551
563
|
}
|
|
552
564
|
this.ws = null;
|
|
553
565
|
}
|
|
@@ -603,12 +615,12 @@ export class FrontendWebsocketClient {
|
|
|
603
615
|
this.status = 'closed';
|
|
604
616
|
this.#cancel_reconnect();
|
|
605
617
|
this.#reset_reconnect_counters();
|
|
606
|
-
this.#reject_all('session revoked');
|
|
618
|
+
this.#reject_all('session revoked', jsonrpc_errors.unauthenticated);
|
|
607
619
|
return;
|
|
608
620
|
}
|
|
609
621
|
// Pending in-flight requests can't be correlated post-reconnect; reject
|
|
610
622
|
// them. Queue stays so the flush on reopen replays unsent work.
|
|
611
|
-
this.#reject_pending_only(`connection closed (code ${event.code})
|
|
623
|
+
this.#reject_pending_only(`connection closed (code ${event.code})`, jsonrpc_errors.service_unavailable);
|
|
612
624
|
// Let `#schedule_reconnect` set `status: 'reconnecting'` directly to avoid
|
|
613
625
|
// a transient `'closed'` flicker; only set `'closed'` when reconnect is off.
|
|
614
626
|
if (this.#auto_reconnect) {
|
|
@@ -616,7 +628,7 @@ export class FrontendWebsocketClient {
|
|
|
616
628
|
}
|
|
617
629
|
else {
|
|
618
630
|
this.status = 'closed';
|
|
619
|
-
this.#reject_all('connection closed, auto-reconnect disabled');
|
|
631
|
+
this.#reject_all('connection closed, auto-reconnect disabled', jsonrpc_errors.service_unavailable);
|
|
620
632
|
}
|
|
621
633
|
};
|
|
622
634
|
#handle_error = (event) => {
|
|
@@ -656,7 +668,16 @@ export class FrontendWebsocketClient {
|
|
|
656
668
|
this.#detach_signal(pending);
|
|
657
669
|
if ('error' in json && json.error) {
|
|
658
670
|
const err = json.error;
|
|
659
|
-
|
|
671
|
+
// Preserve the server's wire code verbatim so the transport's
|
|
672
|
+
// catch block re-emits the same code in its envelope. Fall
|
|
673
|
+
// back to `internal_error` only when the frame is malformed.
|
|
674
|
+
const wire_code = typeof err.code === 'number'
|
|
675
|
+
? err.code
|
|
676
|
+
: JSONRPC_ERROR_CODES.internal_error;
|
|
677
|
+
const wire_message = typeof err.message === 'string' && err.message.length > 0
|
|
678
|
+
? err.message
|
|
679
|
+
: 'unknown error';
|
|
680
|
+
pending.reject(new ThrownJsonrpcError(wire_code, wire_message, err.data));
|
|
660
681
|
}
|
|
661
682
|
else {
|
|
662
683
|
pending.resolve(json.result);
|
|
@@ -18,14 +18,31 @@ export declare const TransportName: z.ZodString;
|
|
|
18
18
|
export type TransportName = z.infer<typeof TransportName>;
|
|
19
19
|
/**
|
|
20
20
|
* Per-call options accepted by every transport's `send`. Optional and
|
|
21
|
-
* extensible — adding a field is non-breaking.
|
|
22
|
-
*
|
|
23
|
-
*
|
|
24
|
-
* `fetch({signal})` for HTTP. Backend transport receives the option but
|
|
25
|
-
* has no per-call abort surface to honor.
|
|
21
|
+
* extensible — adding a field is non-breaking. Source of truth for the
|
|
22
|
+
* shared option shape; `ActionPeerSendOptions` and `RpcClientCallOptions`
|
|
23
|
+
* extend it.
|
|
26
24
|
*/
|
|
27
25
|
export interface TransportSendOptions {
|
|
26
|
+
/**
|
|
27
|
+
* Per-call cancellation. Bottoms out at
|
|
28
|
+
* `FrontendWebsocketClient.request({signal})` on the WS path (sends the
|
|
29
|
+
* shared `cancel` notification on abort) and at `fetch({signal})` on
|
|
30
|
+
* HTTP. Backend transport has no per-call abort surface to honor.
|
|
31
|
+
*/
|
|
28
32
|
signal?: AbortSignal;
|
|
33
|
+
/**
|
|
34
|
+
* Per-call durable-queue opt-in. Names the **client-authoritative vs
|
|
35
|
+
* server-authoritative** distinction — server-authoritative consumers
|
|
36
|
+
* (e.g. zzz completion calls) fail fast with `service_unavailable` when
|
|
37
|
+
* the transport is down; client-authoritative consumers (games,
|
|
38
|
+
* real-time apps) buffer and replay on reconnect because the user
|
|
39
|
+
* already committed to the action at click time. Honored only by
|
|
40
|
+
* `FrontendWebsocketTransport` on the `request_response` path (default
|
|
41
|
+
* `false`). HTTP and backend transports ignore it; WS notifications
|
|
42
|
+
* also ignore it and always fail-fast when disconnected (fire-and-forget
|
|
43
|
+
* `connection.send` has no queue semantic).
|
|
44
|
+
*/
|
|
45
|
+
queue?: boolean;
|
|
29
46
|
}
|
|
30
47
|
export interface Transport {
|
|
31
48
|
transport_name: TransportName;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"transports.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/actions/transports.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,EAAC,CAAC,EAAC,MAAM,KAAK,CAAC;AAEtB,OAAO,KAAK,EACX,gCAAgC,EAChC,gCAAgC,EAChC,mBAAmB,EACnB,cAAc,EACd,sBAAsB,EACtB,oBAAoB,EACpB,MAAM,oBAAoB,CAAC;AAE5B,mDAAmD;AACnD,eAAO,MAAM,wBAAwB,OAAO,CAAC;AAC7C,sEAAsE;AACtE,eAAO,MAAM,iCAAiC,OAAO,CAAC;AACtD,yEAAyE;AACzE,eAAO,MAAM,iCAAiC,OAAO,CAAC;AAKtD,eAAO,MAAM,aAAa,aAAa,CAAC;AACxC,MAAM,MAAM,aAAa,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,aAAa,CAAC,CAAC;AAE1D
|
|
1
|
+
{"version":3,"file":"transports.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/actions/transports.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,EAAC,CAAC,EAAC,MAAM,KAAK,CAAC;AAEtB,OAAO,KAAK,EACX,gCAAgC,EAChC,gCAAgC,EAChC,mBAAmB,EACnB,cAAc,EACd,sBAAsB,EACtB,oBAAoB,EACpB,MAAM,oBAAoB,CAAC;AAE5B,mDAAmD;AACnD,eAAO,MAAM,wBAAwB,OAAO,CAAC;AAC7C,sEAAsE;AACtE,eAAO,MAAM,iCAAiC,OAAO,CAAC;AACtD,yEAAyE;AACzE,eAAO,MAAM,iCAAiC,OAAO,CAAC;AAKtD,eAAO,MAAM,aAAa,aAAa,CAAC;AACxC,MAAM,MAAM,aAAa,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,aAAa,CAAC,CAAC;AAE1D;;;;;GAKG;AACH,MAAM,WAAW,oBAAoB;IACpC;;;;;OAKG;IACH,MAAM,CAAC,EAAE,WAAW,CAAC;IACrB;;;;;;;;;;;OAWG;IACH,KAAK,CAAC,EAAE,OAAO,CAAC;CAChB;AAED,MAAM,WAAW,SAAS;IACzB,cAAc,EAAE,aAAa,CAAC;IAE9B,IAAI,CAAC,OAAO,EAAE,cAAc,EAAE,OAAO,CAAC,EAAE,oBAAoB,GAAG,OAAO,CAAC,sBAAsB,CAAC,CAAC;IAC/F,IAAI,CACH,OAAO,EAAE,mBAAmB,EAC5B,OAAO,CAAC,EAAE,oBAAoB,GAC5B,OAAO,CAAC,oBAAoB,GAAG,IAAI,CAAC,CAAC;IACxC,IAAI,CACH,OAAO,EAAE,gCAAgC,EACzC,OAAO,CAAC,EAAE,oBAAoB,GAC5B,OAAO,CAAC,gCAAgC,GAAG,IAAI,CAAC,CAAC;IACpD,QAAQ,EAAE,MAAM,OAAO,CAAC;IACxB,OAAO,CAAC,EAAE,MAAM,IAAI,CAAC;CACrB;AAED,qBAAa,UAAU;;IAItB;;;OAGG;IACH,cAAc,EAAE,OAAO,CAAQ;IAE/B;;OAEG;IACH,kBAAkB,CAAC,SAAS,EAAE,SAAS,GAAG,IAAI;IAS9C,qBAAqB,CAAC,cAAc,EAAE,aAAa,GAAG,IAAI;IAM1D;;;;;OAKG;IACH,aAAa,CAAC,cAAc,CAAC,EAAE,aAAa,GAAG,SAAS,GAAG,IAAI;IAO/D,QAAQ,IAAI,OAAO,GAAG,IAAI;IAM1B,qBAAqB,IAAI,SAAS,GAAG,IAAI;IAIzC,0BAA0B,IAAI,aAAa,GAAG,IAAI;IAIlD,qBAAqB,CAAC,cAAc,EAAE,aAAa,GAAG,SAAS,GAAG,IAAI;CAqDtE"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"transports_ws.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/actions/transports_ws.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAWH,OAAO,KAAK,EAGX,mBAAmB,EACnB,cAAc,EACd,gBAAgB,EAChB,sBAAsB,EACtB,oBAAoB,EACpB,MAAM,oBAAoB,CAAC;AAC5B,OAAO,KAAK,EAAC,SAAS,EAAE,oBAAoB,EAAC,MAAM,iBAAiB,CAAC;AAIrE;;GAEG;AACH,MAAM,WAAW,mBAAmB;IACnC,IAAI,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,OAAO,CAAC;IAChC,QAAQ,CAAC,SAAS,EAAE,OAAO,CAAC;IAC5B,mBAAmB,EAAE,CAAC,OAAO,EAAE,CAAC,KAAK,EAAE,YAAY,KAAK,IAAI,KAAK,MAAM,IAAI,CAAC;IAC5E,iBAAiB,EAAE,CAAC,OAAO,EAAE,CAAC,KAAK,EAAE,KAAK,KAAK,IAAI,KAAK,MAAM,IAAI,CAAC;CACnE;AAED;;;;;;;;;;;GAWG;AACH,MAAM,WAAW,sBAAuB,SAAQ,mBAAmB;IAClE,OAAO,EAAE,CACR,MAAM,EAAE,MAAM,EACd,MAAM,EAAE,OAAO,EACf,OAAO,CAAC,EAAE;QAAC,MAAM,CAAC,EAAE,WAAW,CAAC;QAAC,KAAK,CAAC,EAAE,OAAO,CAAC;QAAC,EAAE,CAAC,EAAE,gBAAgB,CAAA;KAAC,KACpE,OAAO,CAAC,OAAO,CAAC,CAAC;CACtB;AAED,qBAAa,0BAA2B,YAAW,SAAS;;IAC3D,QAAQ,CAAC,cAAc,EAAG,wBAAwB,CAAU;gBAOhD,UAAU,EAAE,sBAAsB,EAAE,OAAO,EAAE,CAAC,IAAI,EAAE,OAAO,KAAK,OAAO,CAAC,OAAO,CAAC;IAyBtF,IAAI,CACT,OAAO,EAAE,cAAc,EACvB,OAAO,CAAC,EAAE,oBAAoB,GAC5B,OAAO,CAAC,sBAAsB,CAAC;IAC5B,IAAI,CACT,OAAO,EAAE,mBAAmB,EAC5B,OAAO,CAAC,EAAE,oBAAoB,GAC5B,OAAO,CAAC,oBAAoB,GAAG,IAAI,CAAC;
|
|
1
|
+
{"version":3,"file":"transports_ws.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/actions/transports_ws.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAWH,OAAO,KAAK,EAGX,mBAAmB,EACnB,cAAc,EACd,gBAAgB,EAChB,sBAAsB,EACtB,oBAAoB,EACpB,MAAM,oBAAoB,CAAC;AAC5B,OAAO,KAAK,EAAC,SAAS,EAAE,oBAAoB,EAAC,MAAM,iBAAiB,CAAC;AAIrE;;GAEG;AACH,MAAM,WAAW,mBAAmB;IACnC,IAAI,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,OAAO,CAAC;IAChC,QAAQ,CAAC,SAAS,EAAE,OAAO,CAAC;IAC5B,mBAAmB,EAAE,CAAC,OAAO,EAAE,CAAC,KAAK,EAAE,YAAY,KAAK,IAAI,KAAK,MAAM,IAAI,CAAC;IAC5E,iBAAiB,EAAE,CAAC,OAAO,EAAE,CAAC,KAAK,EAAE,KAAK,KAAK,IAAI,KAAK,MAAM,IAAI,CAAC;CACnE;AAED;;;;;;;;;;;GAWG;AACH,MAAM,WAAW,sBAAuB,SAAQ,mBAAmB;IAClE,OAAO,EAAE,CACR,MAAM,EAAE,MAAM,EACd,MAAM,EAAE,OAAO,EACf,OAAO,CAAC,EAAE;QAAC,MAAM,CAAC,EAAE,WAAW,CAAC;QAAC,KAAK,CAAC,EAAE,OAAO,CAAC;QAAC,EAAE,CAAC,EAAE,gBAAgB,CAAA;KAAC,KACpE,OAAO,CAAC,OAAO,CAAC,CAAC;CACtB;AAED,qBAAa,0BAA2B,YAAW,SAAS;;IAC3D,QAAQ,CAAC,cAAc,EAAG,wBAAwB,CAAU;gBAOhD,UAAU,EAAE,sBAAsB,EAAE,OAAO,EAAE,CAAC,IAAI,EAAE,OAAO,KAAK,OAAO,CAAC,OAAO,CAAC;IAyBtF,IAAI,CACT,OAAO,EAAE,cAAc,EACvB,OAAO,CAAC,EAAE,oBAAoB,GAC5B,OAAO,CAAC,sBAAsB,CAAC;IAC5B,IAAI,CACT,OAAO,EAAE,mBAAmB,EAC5B,OAAO,CAAC,EAAE,oBAAoB,GAC5B,OAAO,CAAC,oBAAoB,GAAG,IAAI,CAAC;IA4DvC,QAAQ,IAAI,OAAO;IAInB,OAAO,IAAI,IAAI;CAUf"}
|
|
@@ -42,11 +42,19 @@ export class FrontendWebsocketTransport {
|
|
|
42
42
|
});
|
|
43
43
|
}
|
|
44
44
|
async send(message, options) {
|
|
45
|
-
//
|
|
46
|
-
//
|
|
47
|
-
//
|
|
48
|
-
//
|
|
49
|
-
|
|
45
|
+
// Notifications fail-fast when disconnected regardless of `queue` —
|
|
46
|
+
// `connection.send()` is fire-and-forget with no queue semantic, so
|
|
47
|
+
// silently dropping would masquerade as success at the rpc_client
|
|
48
|
+
// layer (caller would see `{ok: true}` for a lost message).
|
|
49
|
+
//
|
|
50
|
+
// Requests have no such gate here: `connection.request()` throws
|
|
51
|
+
// `ThrownJsonrpcError` with the right code (`service_unavailable`
|
|
52
|
+
// when not connected, `queue_overflow` when the durable queue is
|
|
53
|
+
// full, `request_cancelled` on abort, server's wire code for peer
|
|
54
|
+
// error frames), and the catch block below preserves that code
|
|
55
|
+
// verbatim in the error envelope. Queuing is routed via `queue`.
|
|
56
|
+
const queue = options?.queue ?? false;
|
|
57
|
+
if (is_jsonrpc_notification(message) && !this.is_ready()) {
|
|
50
58
|
return create_jsonrpc_error_response(to_jsonrpc_message_id(message), jsonrpc_error_messages.service_unavailable('WebSocket not connected'));
|
|
51
59
|
}
|
|
52
60
|
if (is_jsonrpc_request(message)) {
|
|
@@ -54,7 +62,7 @@ export class FrontendWebsocketTransport {
|
|
|
54
62
|
const result = await this.#connection.request(message.method, message.params, {
|
|
55
63
|
id: message.id,
|
|
56
64
|
signal: options?.signal,
|
|
57
|
-
queue
|
|
65
|
+
queue,
|
|
58
66
|
});
|
|
59
67
|
return create_jsonrpc_response(message.id, to_jsonrpc_result(result));
|
|
60
68
|
}
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
*
|
|
4
4
|
* Provides error types, named constructors, and HTTP status mapping
|
|
5
5
|
* for the throw/catch error pattern used by `apply_route_specs`.
|
|
6
|
-
* Core error codes (5 standard +
|
|
6
|
+
* Core error codes (5 standard + 10 general application). Domain-specific
|
|
7
7
|
* codes stay in consumers — add by casting `as JsonrpcErrorCode`.
|
|
8
8
|
*
|
|
9
9
|
* `JsonrpcErrorCode` and `JsonrpcErrorObject` types are Zod-inferred
|
|
@@ -20,9 +20,9 @@ import { type JsonrpcErrorCode, type JsonrpcErrorObject } from './jsonrpc.js';
|
|
|
20
20
|
/** Default message for unknown errors. */
|
|
21
21
|
export declare const UNKNOWN_ERROR_MESSAGE = "unknown error";
|
|
22
22
|
/** Names of standard and general application JSON-RPC error codes. */
|
|
23
|
-
export type JsonrpcErrorName = 'parse_error' | 'invalid_request' | 'method_not_found' | 'invalid_params' | 'internal_error' | 'unauthenticated' | 'forbidden' | 'not_found' | 'conflict' | 'validation_error' | 'rate_limited' | 'service_unavailable' | 'timeout';
|
|
23
|
+
export type JsonrpcErrorName = 'parse_error' | 'invalid_request' | 'method_not_found' | 'invalid_params' | 'internal_error' | 'unauthenticated' | 'forbidden' | 'not_found' | 'conflict' | 'validation_error' | 'rate_limited' | 'service_unavailable' | 'timeout' | 'queue_overflow' | 'request_cancelled';
|
|
24
24
|
/**
|
|
25
|
-
* Standard JSON-RPC error codes (5) plus general application codes (
|
|
25
|
+
* Standard JSON-RPC error codes (5) plus general application codes (10).
|
|
26
26
|
*
|
|
27
27
|
* Extensible — consumers add domain-specific codes to their own objects
|
|
28
28
|
* by casting `as JsonrpcErrorCode`. Application codes use the -32000 to
|
|
@@ -55,6 +55,18 @@ export declare const JSONRPC_ERROR_CODES: {
|
|
|
55
55
|
readonly rate_limited: JsonrpcErrorCode;
|
|
56
56
|
readonly service_unavailable: JsonrpcErrorCode;
|
|
57
57
|
readonly timeout: JsonrpcErrorCode;
|
|
58
|
+
/**
|
|
59
|
+
* Client-side backpressure — an outbound buffer (e.g. `FrontendWebsocketClient`'s
|
|
60
|
+
* disconnected request queue) refused a new request because it was full.
|
|
61
|
+
* Distinct from `rate_limited`, which signals a server-side policy.
|
|
62
|
+
*/
|
|
63
|
+
readonly queue_overflow: JsonrpcErrorCode;
|
|
64
|
+
/**
|
|
65
|
+
* Caller-initiated cancellation (e.g. `AbortSignal` fired). Cooperative,
|
|
66
|
+
* not a failure — the request did not complete because the caller asked
|
|
67
|
+
* for it to stop.
|
|
68
|
+
*/
|
|
69
|
+
readonly request_cancelled: JsonrpcErrorCode;
|
|
58
70
|
};
|
|
59
71
|
/**
|
|
60
72
|
* Named constructors for `JsonrpcErrorObject` values.
|
|
@@ -77,6 +89,8 @@ export declare const jsonrpc_error_messages: {
|
|
|
77
89
|
readonly rate_limited: (message?: string, data?: unknown) => JsonrpcErrorObject;
|
|
78
90
|
readonly service_unavailable: (message?: string, data?: unknown) => JsonrpcErrorObject;
|
|
79
91
|
readonly timeout: (message?: string, data?: unknown) => JsonrpcErrorObject;
|
|
92
|
+
readonly queue_overflow: (message?: string, data?: unknown) => JsonrpcErrorObject;
|
|
93
|
+
readonly request_cancelled: (message?: string, data?: unknown) => JsonrpcErrorObject;
|
|
80
94
|
};
|
|
81
95
|
/**
|
|
82
96
|
* Error class carrying a JSON-RPC error code — thrown by handlers,
|
|
@@ -108,6 +122,8 @@ export declare const jsonrpc_errors: {
|
|
|
108
122
|
readonly rate_limited: (message?: string | undefined, data?: unknown) => ThrownJsonrpcError;
|
|
109
123
|
readonly service_unavailable: (message?: string | undefined, data?: unknown) => ThrownJsonrpcError;
|
|
110
124
|
readonly timeout: (message?: string | undefined, data?: unknown) => ThrownJsonrpcError;
|
|
125
|
+
readonly queue_overflow: (message?: string | undefined, data?: unknown) => ThrownJsonrpcError;
|
|
126
|
+
readonly request_cancelled: (message?: string | undefined, data?: unknown) => ThrownJsonrpcError;
|
|
111
127
|
};
|
|
112
128
|
/**
|
|
113
129
|
* Maps JSON-RPC error codes to HTTP status codes.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"jsonrpc_errors.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/http/jsonrpc_errors.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AAEH,OAAO,EAMN,KAAK,gBAAgB,EACrB,KAAK,kBAAkB,EACvB,MAAM,cAAc,CAAC;AAEtB,0CAA0C;AAC1C,eAAO,MAAM,qBAAqB,kBAAkB,CAAC;AAErD,sEAAsE;AACtE,MAAM,MAAM,gBAAgB,GACzB,aAAa,GACb,iBAAiB,GACjB,kBAAkB,GAClB,gBAAgB,GAChB,gBAAgB,GAChB,iBAAiB,GACjB,WAAW,GACX,WAAW,GACX,UAAU,GACV,kBAAkB,GAClB,cAAc,GACd,qBAAqB,GACrB,SAAS,CAAC;
|
|
1
|
+
{"version":3,"file":"jsonrpc_errors.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/http/jsonrpc_errors.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AAEH,OAAO,EAMN,KAAK,gBAAgB,EACrB,KAAK,kBAAkB,EACvB,MAAM,cAAc,CAAC;AAEtB,0CAA0C;AAC1C,eAAO,MAAM,qBAAqB,kBAAkB,CAAC;AAErD,sEAAsE;AACtE,MAAM,MAAM,gBAAgB,GACzB,aAAa,GACb,iBAAiB,GACjB,kBAAkB,GAClB,gBAAgB,GAChB,gBAAgB,GAChB,iBAAiB,GACjB,WAAW,GACX,WAAW,GACX,UAAU,GACV,kBAAkB,GAClB,cAAc,GACd,qBAAqB,GACrB,SAAS,GACT,gBAAgB,GAChB,mBAAmB,CAAC;AAEvB;;;;;;GAMG;AACH,eAAO,MAAM,mBAAmB;0BAEK,gBAAgB;8BACR,gBAAgB;+BACd,gBAAgB;6BACpB,gBAAgB;6BAChB,gBAAgB;IAG1D;;;;OAIG;8BACwB,gBAAgB;IAC3C;;;OAGG;wBACkB,gBAAgB;wBAChB,gBAAgB;uBACjB,gBAAgB;IACpC;;;OAGG;+BACyB,gBAAgB;2BACpB,gBAAgB;kCACT,gBAAgB;sBAC5B,gBAAgB;IACnC;;;;OAIG;6BACuB,gBAAgB;IAC1C;;;;OAIG;gCAC0B,gBAAgB;CACiB,CAAC;AAEhE;;;;;;GAMG;AACH,eAAO,MAAM,sBAAsB;kCACb,OAAO,KAAG,kBAAkB;sCAMxB,OAAO,KAAG,kBAAkB;yCAMzB,MAAM,SAAS,OAAO,KAAG,kBAAkB;wCAM5C,MAAM,SAAS,OAAO,KAAG,kBAAkB;wCAO5D,MAAM,SACR,OAAO,KACZ,kBAAkB;yCAMM,MAAM,SAA6B,OAAO,KAAG,kBAAkB;mCAMrE,MAAM,SAAuB,OAAO,KAAG,kBAAkB;oCAMvD,MAAM,SAAS,OAAO,KAAG,kBAAkB;kCAM9C,MAAM,SAAsB,OAAO,KAAG,kBAAkB;0CAMhD,MAAM,SAA8B,OAAO,KAAG,kBAAkB;sCAMpE,MAAM,SAA0B,OAAO,KAAG,kBAAkB;6CAO1E,MAAM,SACR,OAAO,KACZ,kBAAkB;iCAMF,MAAM,SAAqB,OAAO,KAAG,kBAAkB;wCAMhD,MAAM,SAA4B,OAAO,KAAG,kBAAkB;2CAO9E,MAAM,SACR,OAAO,KACZ,kBAAkB;CAKoE,CAAC;AAE3F;;;;;GAKG;AACH,qBAAa,kBAAmB,SAAQ,KAAK;IAC5C,IAAI,EAAE,gBAAgB,CAAC;IACvB,IAAI,CAAC,EAAE,OAAO,CAAC;gBAEH,IAAI,EAAE,gBAAgB,EAAE,OAAO,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,OAAO,EAAE,OAAO,CAAC,EAAE,YAAY;CAK3F;AAWD;;;;GAIG;AACH,eAAO,MAAM,cAAc;8CAXQ,kBAAkB;kDAAlB,kBAAkB;gFAAlB,kBAAkB;+EAAlB,kBAAkB;+EAAlB,kBAAkB;gFAAlB,kBAAkB;0EAAlB,kBAAkB;2EAAlB,kBAAkB;yEAAlB,kBAAkB;iFAAlB,kBAAkB;6EAAlB,kBAAkB;oFAAlB,kBAAkB;wEAAlB,kBAAkB;+EAAlB,kBAAkB;kFAAlB,kBAAkB;CA2BqC,CAAC;AAI3F;;;;;GAKG;AACH,eAAO,MAAM,iCAAiC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAkBpE,CAAC;AAEF;;;;;;GAMG;AACH,eAAO,MAAM,iCAAiC,EAAE,MAAM,CAAC,MAAM,EAAE,gBAAgB,CAMzC,CAAC;AAEvC;;;;;;;;GAQG;AACH,eAAO,MAAM,iCAAiC,GAAI,MAAM,gBAAgB,KAAG,MAClB,CAAC;AAE1D;;;;;;;GAOG;AACH,eAAO,MAAM,iCAAiC,GAAI,QAAQ,MAAM,KAAG,gBACa,CAAC"}
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
*
|
|
4
4
|
* Provides error types, named constructors, and HTTP status mapping
|
|
5
5
|
* for the throw/catch error pattern used by `apply_route_specs`.
|
|
6
|
-
* Core error codes (5 standard +
|
|
6
|
+
* Core error codes (5 standard + 10 general application). Domain-specific
|
|
7
7
|
* codes stay in consumers — add by casting `as JsonrpcErrorCode`.
|
|
8
8
|
*
|
|
9
9
|
* `JsonrpcErrorCode` and `JsonrpcErrorObject` types are Zod-inferred
|
|
@@ -20,7 +20,7 @@ import { JSONRPC_PARSE_ERROR, JSONRPC_INVALID_REQUEST, JSONRPC_METHOD_NOT_FOUND,
|
|
|
20
20
|
/** Default message for unknown errors. */
|
|
21
21
|
export const UNKNOWN_ERROR_MESSAGE = 'unknown error';
|
|
22
22
|
/**
|
|
23
|
-
* Standard JSON-RPC error codes (5) plus general application codes (
|
|
23
|
+
* Standard JSON-RPC error codes (5) plus general application codes (10).
|
|
24
24
|
*
|
|
25
25
|
* Extensible — consumers add domain-specific codes to their own objects
|
|
26
26
|
* by casting `as JsonrpcErrorCode`. Application codes use the -32000 to
|
|
@@ -55,6 +55,18 @@ export const JSONRPC_ERROR_CODES = {
|
|
|
55
55
|
rate_limited: -32006,
|
|
56
56
|
service_unavailable: -32007,
|
|
57
57
|
timeout: -32008,
|
|
58
|
+
/**
|
|
59
|
+
* Client-side backpressure — an outbound buffer (e.g. `FrontendWebsocketClient`'s
|
|
60
|
+
* disconnected request queue) refused a new request because it was full.
|
|
61
|
+
* Distinct from `rate_limited`, which signals a server-side policy.
|
|
62
|
+
*/
|
|
63
|
+
queue_overflow: -32009,
|
|
64
|
+
/**
|
|
65
|
+
* Caller-initiated cancellation (e.g. `AbortSignal` fired). Cooperative,
|
|
66
|
+
* not a failure — the request did not complete because the caller asked
|
|
67
|
+
* for it to stop.
|
|
68
|
+
*/
|
|
69
|
+
request_cancelled: -32010,
|
|
58
70
|
};
|
|
59
71
|
/**
|
|
60
72
|
* Named constructors for `JsonrpcErrorObject` values.
|
|
@@ -129,6 +141,16 @@ export const jsonrpc_error_messages = {
|
|
|
129
141
|
message,
|
|
130
142
|
data,
|
|
131
143
|
}),
|
|
144
|
+
queue_overflow: (message = 'queue overflow', data) => ({
|
|
145
|
+
code: JSONRPC_ERROR_CODES.queue_overflow,
|
|
146
|
+
message,
|
|
147
|
+
data,
|
|
148
|
+
}),
|
|
149
|
+
request_cancelled: (message = 'request cancelled', data) => ({
|
|
150
|
+
code: JSONRPC_ERROR_CODES.request_cancelled,
|
|
151
|
+
message,
|
|
152
|
+
data,
|
|
153
|
+
}),
|
|
132
154
|
};
|
|
133
155
|
/**
|
|
134
156
|
* Error class carrying a JSON-RPC error code — thrown by handlers,
|
|
@@ -168,6 +190,8 @@ export const jsonrpc_errors = {
|
|
|
168
190
|
rate_limited: create_error_thrower(jsonrpc_error_messages.rate_limited),
|
|
169
191
|
service_unavailable: create_error_thrower(jsonrpc_error_messages.service_unavailable),
|
|
170
192
|
timeout: create_error_thrower(jsonrpc_error_messages.timeout),
|
|
193
|
+
queue_overflow: create_error_thrower(jsonrpc_error_messages.queue_overflow),
|
|
194
|
+
request_cancelled: create_error_thrower(jsonrpc_error_messages.request_cancelled),
|
|
171
195
|
};
|
|
172
196
|
// --- HTTP status mapping ---
|
|
173
197
|
/**
|
|
@@ -187,9 +211,13 @@ export const JSONRPC_ERROR_CODE_TO_HTTP_STATUS = {
|
|
|
187
211
|
[-32003]: 404, // not_found
|
|
188
212
|
[-32004]: 409, // conflict
|
|
189
213
|
[-32005]: 422, // validation_error
|
|
214
|
+
// queue_overflow shares 429 with rate_limited — listed first so reverse
|
|
215
|
+
// map wins with rate_limited (server-side) rather than client-side overflow.
|
|
216
|
+
[-32009]: 429, // queue_overflow (client-side backpressure)
|
|
190
217
|
[-32006]: 429, // rate_limited
|
|
191
218
|
[-32007]: 503, // service_unavailable
|
|
192
219
|
[-32008]: 504, // timeout
|
|
220
|
+
[-32010]: 499, // request_cancelled (nginx "client closed request")
|
|
193
221
|
};
|
|
194
222
|
/**
|
|
195
223
|
* Maps HTTP status codes to JSON-RPC error codes (reverse mapping).
|