@fuzdev/fuz_app 0.24.0 → 0.25.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_codegen.d.ts +25 -0
- package/dist/actions/action_codegen.d.ts.map +1 -1
- package/dist/actions/action_codegen.js +39 -0
- package/dist/actions/action_peer.d.ts +7 -0
- package/dist/actions/action_peer.d.ts.map +1 -1
- package/dist/actions/action_peer.js +1 -1
- package/dist/actions/action_types.d.ts +16 -1
- package/dist/actions/action_types.d.ts.map +1 -1
- package/dist/actions/cancel.d.ts +78 -0
- package/dist/actions/cancel.d.ts.map +1 -0
- package/dist/actions/cancel.js +79 -0
- package/dist/actions/register_action_ws.d.ts.map +1 -1
- package/dist/actions/register_action_ws.js +50 -17
- package/dist/actions/rpc_client.d.ts +10 -0
- package/dist/actions/rpc_client.d.ts.map +1 -1
- package/dist/actions/rpc_client.js +22 -7
- package/dist/actions/socket.svelte.d.ts +22 -10
- package/dist/actions/socket.svelte.d.ts.map +1 -1
- package/dist/actions/socket.svelte.js +46 -12
- package/dist/actions/transports.d.ts +14 -3
- package/dist/actions/transports.d.ts.map +1 -1
- package/dist/actions/transports_http.d.ts +3 -3
- package/dist/actions/transports_http.d.ts.map +1 -1
- package/dist/actions/transports_http.js +4 -3
- package/dist/actions/transports_ws.d.ts +33 -6
- package/dist/actions/transports_ws.d.ts.map +1 -1
- package/dist/actions/transports_ws.js +43 -46
- package/dist/actions/transports_ws_backend.d.ts +12 -3
- package/dist/actions/transports_ws_backend.d.ts.map +1 -1
- package/dist/actions/transports_ws_backend.js +12 -1
- package/dist/auth/bearer_auth.js +0 -1
- package/dist/auth/keyring.d.ts.map +1 -1
- package/dist/auth/keyring.js +0 -2
- package/dist/auth/migrations.js +4 -4
- package/dist/db/migrate.d.ts +12 -2
- package/dist/db/migrate.d.ts.map +1 -1
- package/dist/db/migrate.js +25 -16
- package/dist/db/status.d.ts.map +1 -1
- package/dist/db/status.js +0 -2
- package/dist/dev/setup.js +2 -2
- package/dist/http/db_routes.d.ts.map +1 -1
- package/dist/http/db_routes.js +0 -1
- package/dist/testing/admin_integration.d.ts.map +1 -1
- package/dist/testing/admin_integration.js +0 -3
- package/dist/testing/app_server.js +1 -1
- package/dist/testing/data_exposure.js +6 -8
- package/dist/testing/db.js +1 -1
- package/dist/testing/integration.js +0 -1
- package/dist/testing/rate_limiting.d.ts.map +1 -1
- package/dist/testing/rate_limiting.js +0 -6
- package/dist/testing/rpc_round_trip.js +4 -4
- package/dist/testing/sse_round_trip.d.ts.map +1 -1
- package/dist/testing/sse_round_trip.js +1 -2
- package/package.json +2 -2
|
@@ -26,6 +26,7 @@
|
|
|
26
26
|
import { BROWSER } from 'esm-env';
|
|
27
27
|
import { JSONRPC_VERSION } from '../http/jsonrpc.js';
|
|
28
28
|
import { WS_CLOSE_CLIENT_HEARTBEAT_TIMEOUT, WS_CLOSE_SESSION_REVOKED } from './transports.js';
|
|
29
|
+
import { CANCEL_METHOD } from './cancel.js';
|
|
29
30
|
import { HEARTBEAT_METHOD } from './heartbeat.js';
|
|
30
31
|
/** Default WebSocket close code (normal closure). */
|
|
31
32
|
export const DEFAULT_CLOSE_CODE = 1000;
|
|
@@ -253,19 +254,29 @@ export class FrontendWebsocketClient {
|
|
|
253
254
|
}
|
|
254
255
|
/**
|
|
255
256
|
* Promise-based JSON-RPC over the socket. Auto-assigns a monotonic request
|
|
256
|
-
* id
|
|
257
|
-
*
|
|
258
|
-
*
|
|
257
|
+
* id (or uses an explicit one supplied via `options.id` — used by
|
|
258
|
+
* `FrontendWebsocketTransport` which delegates to this method and has its
|
|
259
|
+
* own peer-minted UUID), tracks the pending promise, and resolves when the
|
|
260
|
+
* server sends a matching response (or rejects on error frame, socket
|
|
261
|
+
* close, or aborted signal).
|
|
262
|
+
*
|
|
263
|
+
* Callers supplying an explicit `options.id` are responsible for
|
|
264
|
+
* uniqueness — the pending map is keyed by id, and a duplicate silently
|
|
265
|
+
* overwrites the prior entry. Auto-minted ids are monotonic and never
|
|
266
|
+
* collide with themselves or with peer-minted UUIDs (the types differ:
|
|
267
|
+
* integer vs string).
|
|
259
268
|
*
|
|
260
269
|
* While the socket is disconnected, the request is buffered in a bounded
|
|
261
|
-
* queue (default-on,
|
|
262
|
-
*
|
|
263
|
-
*
|
|
264
|
-
*
|
|
270
|
+
* queue (default-on, `DEFAULT_QUEUE_MAX_SIZE`) and flushed on reopen. Pass
|
|
271
|
+
* `{queue: false}` to reject immediately when disconnected — used
|
|
272
|
+
* internally by the heartbeat, which must not fight the queue for the
|
|
273
|
+
* disconnect-detection slot.
|
|
265
274
|
*
|
|
266
|
-
* `AbortSignal`
|
|
267
|
-
*
|
|
268
|
-
* the
|
|
275
|
+
* On `AbortSignal` fire: rejects the local promise *and* sends the shared
|
|
276
|
+
* `cancel` notification (`CANCEL_METHOD`) so the server-side dispatcher
|
|
277
|
+
* can abort the matching handler's `ctx.signal`. Suppressed for
|
|
278
|
+
* queued-but-never-sent (server doesn't know about it) and
|
|
279
|
+
* response-beat-cancel races.
|
|
269
280
|
*/
|
|
270
281
|
request(method, params = {}, options = {}) {
|
|
271
282
|
return new Promise((resolve, reject) => {
|
|
@@ -280,7 +291,7 @@ export class FrontendWebsocketClient {
|
|
|
280
291
|
reject_typed(this.#build_abort_error(method));
|
|
281
292
|
return;
|
|
282
293
|
}
|
|
283
|
-
const id = ++this.#next_request_id;
|
|
294
|
+
const id = options.id ?? ++this.#next_request_id;
|
|
284
295
|
const frame = { jsonrpc: JSONRPC_VERSION, id, method, params };
|
|
285
296
|
// Bind the signal listener up-front so `#detach_signal` can find it by
|
|
286
297
|
// reference regardless of which settlement path runs (inline send,
|
|
@@ -290,11 +301,21 @@ export class FrontendWebsocketClient {
|
|
|
290
301
|
? () => {
|
|
291
302
|
if (!pending)
|
|
292
303
|
return;
|
|
293
|
-
|
|
304
|
+
// `Map.delete` returns true iff the entry existed — which
|
|
305
|
+
// is our signal that the frame was actually written to
|
|
306
|
+
// the socket (pending-only tracks in-flight). If it was
|
|
307
|
+
// only queued (never sent), the server doesn't know
|
|
308
|
+
// about it and doesn't need a cancel. If the response
|
|
309
|
+
// beat the abort, `#handle_message` already deleted the
|
|
310
|
+
// entry and detached this listener, so this closure
|
|
311
|
+
// never runs in that race.
|
|
312
|
+
const was_in_flight = this.#pending.delete(id);
|
|
294
313
|
this.#drop_queued(id);
|
|
295
314
|
this.#detach_signal(pending);
|
|
296
315
|
pending = null;
|
|
297
316
|
reject_typed(this.#build_abort_error(method));
|
|
317
|
+
if (was_in_flight)
|
|
318
|
+
this.#send_cancel(id);
|
|
298
319
|
}
|
|
299
320
|
: null;
|
|
300
321
|
if (signal && signal_handler)
|
|
@@ -337,6 +358,19 @@ export class FrontendWebsocketClient {
|
|
|
337
358
|
#build_abort_error(method) {
|
|
338
359
|
return new Error(`[socket] request aborted (method=${method})`);
|
|
339
360
|
}
|
|
361
|
+
/**
|
|
362
|
+
* Fire-and-forget cancel notification to the server. The dispatcher
|
|
363
|
+
* looks up the matching pending handler's per-request `AbortController`
|
|
364
|
+
* and aborts it; unknown ids no-op. Drops silently when disconnected —
|
|
365
|
+
* the server's own socket-close path will abort any in-flight handlers.
|
|
366
|
+
*/
|
|
367
|
+
#send_cancel(request_id) {
|
|
368
|
+
this.send({
|
|
369
|
+
jsonrpc: JSONRPC_VERSION,
|
|
370
|
+
method: CANCEL_METHOD,
|
|
371
|
+
params: { request_id },
|
|
372
|
+
});
|
|
373
|
+
}
|
|
340
374
|
#detach_signal(pending) {
|
|
341
375
|
if (pending.signal && pending.signal_handler) {
|
|
342
376
|
pending.signal.removeEventListener('abort', pending.signal_handler);
|
|
@@ -16,11 +16,22 @@ export declare const WS_CLOSE_CLIENT_HEARTBEAT_TIMEOUT = 4002;
|
|
|
16
16
|
export declare const WS_CLOSE_SERVER_HEARTBEAT_TIMEOUT = 4003;
|
|
17
17
|
export declare const TransportName: z.ZodString;
|
|
18
18
|
export type TransportName = z.infer<typeof TransportName>;
|
|
19
|
+
/**
|
|
20
|
+
* Per-call options accepted by every transport's `send`. Optional and
|
|
21
|
+
* extensible — adding a field is non-breaking. Today: an `AbortSignal`
|
|
22
|
+
* for cancellation that bottoms out at `FrontendWebsocketClient.request`
|
|
23
|
+
* (which sends the shared `cancel` notification on abort) and at
|
|
24
|
+
* `fetch({signal})` for HTTP. Backend transport receives the option but
|
|
25
|
+
* has no per-call abort surface to honor.
|
|
26
|
+
*/
|
|
27
|
+
export interface TransportSendOptions {
|
|
28
|
+
signal?: AbortSignal;
|
|
29
|
+
}
|
|
19
30
|
export interface Transport {
|
|
20
31
|
transport_name: TransportName;
|
|
21
|
-
send(message: JsonrpcRequest): Promise<JsonrpcResponseOrError>;
|
|
22
|
-
send(message: JsonrpcNotification): Promise<JsonrpcErrorResponse | null>;
|
|
23
|
-
send(message: JsonrpcMessageFromClientToServer): Promise<JsonrpcMessageFromServerToClient | null>;
|
|
32
|
+
send(message: JsonrpcRequest, options?: TransportSendOptions): Promise<JsonrpcResponseOrError>;
|
|
33
|
+
send(message: JsonrpcNotification, options?: TransportSendOptions): Promise<JsonrpcErrorResponse | null>;
|
|
34
|
+
send(message: JsonrpcMessageFromClientToServer, options?: TransportSendOptions): Promise<JsonrpcMessageFromServerToClient | null>;
|
|
24
35
|
is_ready: () => boolean;
|
|
25
36
|
dispose?: () => void;
|
|
26
37
|
}
|
|
@@ -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,MAAM,WAAW,SAAS;IACzB,cAAc,EAAE,aAAa,CAAC;IAE9B,IAAI,CAAC,OAAO,EAAE,cAAc,GAAG,OAAO,CAAC,sBAAsB,CAAC,CAAC;IAC/
|
|
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;;;;;;;GAOG;AACH,MAAM,WAAW,oBAAoB;IACpC,MAAM,CAAC,EAAE,WAAW,CAAC;CACrB;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"}
|
|
@@ -4,13 +4,13 @@
|
|
|
4
4
|
* @module
|
|
5
5
|
*/
|
|
6
6
|
import type { JsonrpcNotification, JsonrpcRequest, JsonrpcResponseOrError, JsonrpcErrorResponse } from '../http/jsonrpc.js';
|
|
7
|
-
import type { Transport } from './transports.js';
|
|
7
|
+
import type { Transport, TransportSendOptions } from './transports.js';
|
|
8
8
|
export declare class FrontendHttpTransport implements Transport {
|
|
9
9
|
#private;
|
|
10
10
|
readonly transport_name: "frontend_http_rpc";
|
|
11
11
|
constructor(url: string, headers?: Record<string, string>, has_side_effects?: (method: string) => boolean);
|
|
12
|
-
send(message: JsonrpcRequest): Promise<JsonrpcResponseOrError>;
|
|
13
|
-
send(message: JsonrpcNotification): Promise<JsonrpcErrorResponse | null>;
|
|
12
|
+
send(message: JsonrpcRequest, options?: TransportSendOptions): Promise<JsonrpcResponseOrError>;
|
|
13
|
+
send(message: JsonrpcNotification, options?: TransportSendOptions): Promise<JsonrpcErrorResponse | null>;
|
|
14
14
|
is_ready(): boolean;
|
|
15
15
|
}
|
|
16
16
|
//# sourceMappingURL=transports_http.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"transports_http.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/actions/transports_http.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAeH,OAAO,KAAK,EAGX,mBAAmB,EACnB,cAAc,EACd,sBAAsB,EACtB,oBAAoB,EACpB,MAAM,oBAAoB,CAAC;AAC5B,OAAO,KAAK,EAAC,SAAS,EAAC,MAAM,iBAAiB,CAAC;
|
|
1
|
+
{"version":3,"file":"transports_http.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/actions/transports_http.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAeH,OAAO,KAAK,EAGX,mBAAmB,EACnB,cAAc,EACd,sBAAsB,EACtB,oBAAoB,EACpB,MAAM,oBAAoB,CAAC;AAC5B,OAAO,KAAK,EAAC,SAAS,EAAE,oBAAoB,EAAC,MAAM,iBAAiB,CAAC;AAErE,qBAAa,qBAAsB,YAAW,SAAS;;IACtD,QAAQ,CAAC,cAAc,EAAG,mBAAmB,CAAU;gBAOtD,GAAG,EAAE,MAAM,EACX,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,EAChC,gBAAgB,CAAC,EAAE,CAAC,MAAM,EAAE,MAAM,KAAK,OAAO;IAOzC,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;IAyEvC,QAAQ,IAAI,OAAO;CAGnB"}
|
|
@@ -16,7 +16,8 @@ export class FrontendHttpTransport {
|
|
|
16
16
|
this.#headers = headers ?? { 'content-type': 'application/json', accept: 'application/json' };
|
|
17
17
|
this.#has_side_effects = has_side_effects;
|
|
18
18
|
}
|
|
19
|
-
async send(message) {
|
|
19
|
+
async send(message, options) {
|
|
20
|
+
const signal = options?.signal;
|
|
20
21
|
try {
|
|
21
22
|
let response;
|
|
22
23
|
if (this.#has_side_effects && !this.#has_side_effects(message.method) && 'id' in message) {
|
|
@@ -31,6 +32,7 @@ export class FrontendHttpTransport {
|
|
|
31
32
|
response = await fetch(`${this.#url}${separator}${search_params.toString()}`, {
|
|
32
33
|
method: 'GET',
|
|
33
34
|
headers: this.#headers,
|
|
35
|
+
signal,
|
|
34
36
|
});
|
|
35
37
|
}
|
|
36
38
|
else {
|
|
@@ -38,8 +40,7 @@ export class FrontendHttpTransport {
|
|
|
38
40
|
method: 'POST',
|
|
39
41
|
headers: this.#headers,
|
|
40
42
|
body: JSON.stringify(message),
|
|
41
|
-
|
|
42
|
-
// signal: AbortSignal.timeout(REQUEST_TIMEOUT),
|
|
43
|
+
signal,
|
|
43
44
|
});
|
|
44
45
|
}
|
|
45
46
|
const result = await response.json();
|
|
@@ -1,10 +1,18 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* WebSocket transport —
|
|
2
|
+
* Frontend WebSocket transport — thin adapter over `WebsocketRpcConnection`.
|
|
3
|
+
*
|
|
4
|
+
* Delegates request/response correlation, the durable queue, the heartbeat,
|
|
5
|
+
* and `AbortSignal`-driven cancel to the underlying connection (the
|
|
6
|
+
* canonical implementation is `FrontendWebsocketClient`). The transport's
|
|
7
|
+
* own job is the `Transport` contract: route inbound server-pushed
|
|
8
|
+
* messages into `peer.receive` and translate the connection's
|
|
9
|
+
* `Promise<R>`/`ThrownJsonrpcError` shape into `JsonrpcResponseOrError`
|
|
10
|
+
* envelopes. No parallel pending-request map.
|
|
3
11
|
*
|
|
4
12
|
* @module
|
|
5
13
|
*/
|
|
6
|
-
import type { JsonrpcNotification, JsonrpcRequest, JsonrpcResponseOrError, JsonrpcErrorResponse } from '../http/jsonrpc.js';
|
|
7
|
-
import type { Transport } from './transports.js';
|
|
14
|
+
import type { JsonrpcNotification, JsonrpcRequest, JsonrpcRequestId, JsonrpcResponseOrError, JsonrpcErrorResponse } from '../http/jsonrpc.js';
|
|
15
|
+
import type { Transport, TransportSendOptions } from './transports.js';
|
|
8
16
|
/**
|
|
9
17
|
* Minimal interface for a WebSocket connection, decoupled from the concrete Socket Cell.
|
|
10
18
|
*/
|
|
@@ -14,12 +22,31 @@ export interface WebsocketConnection {
|
|
|
14
22
|
add_message_handler: (handler: (event: MessageEvent) => void) => () => void;
|
|
15
23
|
add_error_handler: (handler: (event: Event) => void) => () => void;
|
|
16
24
|
}
|
|
25
|
+
/**
|
|
26
|
+
* RPC-capable WebSocket connection — a `WebsocketConnection` that also
|
|
27
|
+
* handles request/response correlation with timeout, queue,
|
|
28
|
+
* `AbortSignal` cancel, and explicit-id support. Required by
|
|
29
|
+
* `FrontendWebsocketTransport` so it can delegate the pending-map
|
|
30
|
+
* bookkeeping to one canonical implementation
|
|
31
|
+
* (`FrontendWebsocketClient`) instead of running a parallel one.
|
|
32
|
+
*
|
|
33
|
+
* Consumer wrappers around `FrontendWebsocketClient` (e.g. zzz's
|
|
34
|
+
* `Socket`) implement this by adding a one-line delegate to the
|
|
35
|
+
* underlying client's `request`.
|
|
36
|
+
*/
|
|
37
|
+
export interface WebsocketRpcConnection extends WebsocketConnection {
|
|
38
|
+
request: (method: string, params: unknown, options?: {
|
|
39
|
+
signal?: AbortSignal;
|
|
40
|
+
queue?: boolean;
|
|
41
|
+
id?: JsonrpcRequestId;
|
|
42
|
+
}) => Promise<unknown>;
|
|
43
|
+
}
|
|
17
44
|
export declare class FrontendWebsocketTransport implements Transport {
|
|
18
45
|
#private;
|
|
19
46
|
readonly transport_name: "frontend_websocket_rpc";
|
|
20
|
-
constructor(connection:
|
|
21
|
-
send(message: JsonrpcRequest): Promise<JsonrpcResponseOrError>;
|
|
22
|
-
send(message: JsonrpcNotification): Promise<JsonrpcErrorResponse | null>;
|
|
47
|
+
constructor(connection: WebsocketRpcConnection, receive: (data: unknown) => Promise<unknown>);
|
|
48
|
+
send(message: JsonrpcRequest, options?: TransportSendOptions): Promise<JsonrpcResponseOrError>;
|
|
49
|
+
send(message: JsonrpcNotification, options?: TransportSendOptions): Promise<JsonrpcErrorResponse | null>;
|
|
23
50
|
is_ready(): boolean;
|
|
24
51
|
dispose(): void;
|
|
25
52
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"transports_ws.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/actions/transports_ws.ts"],"names":[],"mappings":"AAAA
|
|
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;IAoDvC,QAAQ,IAAI,OAAO;IAInB,OAAO,IAAI,IAAI;CAUf"}
|
|
@@ -1,82 +1,79 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* WebSocket transport —
|
|
2
|
+
* Frontend WebSocket transport — thin adapter over `WebsocketRpcConnection`.
|
|
3
|
+
*
|
|
4
|
+
* Delegates request/response correlation, the durable queue, the heartbeat,
|
|
5
|
+
* and `AbortSignal`-driven cancel to the underlying connection (the
|
|
6
|
+
* canonical implementation is `FrontendWebsocketClient`). The transport's
|
|
7
|
+
* own job is the `Transport` contract: route inbound server-pushed
|
|
8
|
+
* messages into `peer.receive` and translate the connection's
|
|
9
|
+
* `Promise<R>`/`ThrownJsonrpcError` shape into `JsonrpcResponseOrError`
|
|
10
|
+
* envelopes. No parallel pending-request map.
|
|
3
11
|
*
|
|
4
12
|
* @module
|
|
5
13
|
*/
|
|
6
|
-
import { ThrownJsonrpcError, jsonrpc_error_messages
|
|
7
|
-
import { is_jsonrpc_notification, is_jsonrpc_request,
|
|
8
|
-
import { RequestTracker } from './request_tracker.svelte.js';
|
|
14
|
+
import { ThrownJsonrpcError, jsonrpc_error_messages } from '../http/jsonrpc_errors.js';
|
|
15
|
+
import { is_jsonrpc_notification, is_jsonrpc_request, to_jsonrpc_message_id, to_jsonrpc_result, create_jsonrpc_response, create_jsonrpc_error_response, } from '../http/jsonrpc_helpers.js';
|
|
9
16
|
export class FrontendWebsocketTransport {
|
|
10
17
|
transport_name = 'frontend_websocket_rpc';
|
|
11
18
|
#connection;
|
|
12
19
|
#receive;
|
|
13
|
-
#request_tracker;
|
|
14
20
|
#remove_message_handler;
|
|
15
21
|
#remove_error_handler;
|
|
16
|
-
constructor(connection, receive
|
|
22
|
+
constructor(connection, receive) {
|
|
17
23
|
this.#connection = connection;
|
|
18
24
|
this.#receive = receive;
|
|
19
|
-
|
|
20
|
-
//
|
|
25
|
+
// Inbound dispatch — only server-pushed requests/notifications need
|
|
26
|
+
// routing here. Responses to requests we sent are correlated by the
|
|
27
|
+
// connection's own `request()` pending map.
|
|
21
28
|
this.#remove_message_handler = connection.add_message_handler(async (event) => {
|
|
22
29
|
try {
|
|
23
30
|
const data = JSON.parse(event.data);
|
|
24
|
-
|
|
25
|
-
// Check if this is a response to one of our requests
|
|
26
|
-
if (is_jsonrpc_response(data) || (is_jsonrpc_error_response(data) && data.id !== null)) {
|
|
27
|
-
// This is a response to a request we sent
|
|
28
|
-
this.#request_tracker.handle_message(data);
|
|
29
|
-
}
|
|
30
|
-
else if (is_jsonrpc_request(data) || is_jsonrpc_notification(data)) {
|
|
31
|
-
// This is a new request/notification from the server
|
|
31
|
+
if (is_jsonrpc_request(data) || is_jsonrpc_notification(data)) {
|
|
32
32
|
await this.#receive(data);
|
|
33
33
|
}
|
|
34
|
-
|
|
35
|
-
console.warn('[ws_transport] received unknown message type:', data);
|
|
36
|
-
}
|
|
34
|
+
// Responses are owned by `connection.request()` — ignore here.
|
|
37
35
|
}
|
|
38
36
|
catch (error) {
|
|
39
37
|
console.error('[ws_transport] error parsing WebSocket message:', error);
|
|
40
|
-
// TODO maybe send the whole thing back wrapped in an error?
|
|
41
|
-
// can't reference anything else for a response
|
|
42
38
|
}
|
|
43
39
|
});
|
|
44
40
|
this.#remove_error_handler = connection.add_error_handler((event) => {
|
|
45
41
|
console.error('[ws_transport] WebSocket error:', event);
|
|
46
42
|
});
|
|
47
43
|
}
|
|
48
|
-
async send(message) {
|
|
44
|
+
async send(message, options) {
|
|
45
|
+
// Fail-fast at the transport boundary. The connection's own queue
|
|
46
|
+
// would buffer the request and flush on reconnect; that's the right
|
|
47
|
+
// default for direct `client.request()` callers but the typed Proxy
|
|
48
|
+
// path expects "service unavailable" semantics when the WS is down.
|
|
49
49
|
if (!this.is_ready()) {
|
|
50
50
|
return create_jsonrpc_error_response(to_jsonrpc_message_id(message), jsonrpc_error_messages.service_unavailable('WebSocket not connected'));
|
|
51
51
|
}
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
return result;
|
|
52
|
+
if (is_jsonrpc_request(message)) {
|
|
53
|
+
try {
|
|
54
|
+
const result = await this.#connection.request(message.method, message.params, {
|
|
55
|
+
id: message.id,
|
|
56
|
+
signal: options?.signal,
|
|
57
|
+
queue: false,
|
|
58
|
+
});
|
|
59
|
+
return create_jsonrpc_response(message.id, to_jsonrpc_result(result));
|
|
61
60
|
}
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
61
|
+
catch (error) {
|
|
62
|
+
if (error instanceof ThrownJsonrpcError) {
|
|
63
|
+
return create_jsonrpc_error_response(message.id, {
|
|
64
|
+
code: error.code,
|
|
65
|
+
message: error.message,
|
|
66
|
+
data: error.data,
|
|
67
|
+
});
|
|
68
|
+
}
|
|
69
|
+
return create_jsonrpc_error_response(message.id, jsonrpc_error_messages.internal_error(error instanceof Error ? error.message : String(error)));
|
|
66
70
|
}
|
|
67
|
-
// Invalid message type - return error with id if available
|
|
68
|
-
return create_jsonrpc_error_response(to_jsonrpc_message_id(message), jsonrpc_error_messages.invalid_request());
|
|
69
71
|
}
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
code: error.code,
|
|
74
|
-
message: error.message,
|
|
75
|
-
data: error.data,
|
|
76
|
-
});
|
|
77
|
-
}
|
|
78
|
-
return create_jsonrpc_error_response(to_jsonrpc_message_id(message), jsonrpc_error_messages.internal_error(error.message || UNKNOWN_ERROR_MESSAGE));
|
|
72
|
+
if (is_jsonrpc_notification(message)) {
|
|
73
|
+
this.#connection.send(message);
|
|
74
|
+
return null;
|
|
79
75
|
}
|
|
76
|
+
return create_jsonrpc_error_response(to_jsonrpc_message_id(message), jsonrpc_error_messages.invalid_request());
|
|
80
77
|
}
|
|
81
78
|
is_ready() {
|
|
82
79
|
return this.#connection.connected;
|
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
import type { WSContext } from 'hono/ws';
|
|
8
8
|
import type { JsonrpcMessageFromServerToClient, JsonrpcNotification, JsonrpcRequest, JsonrpcResponseOrError, JsonrpcErrorResponse } from '../http/jsonrpc.js';
|
|
9
9
|
import { type Uuid } from '../uuid.js';
|
|
10
|
-
import { type Transport } from './transports.js';
|
|
10
|
+
import { type Transport, type TransportSendOptions } from './transports.js';
|
|
11
11
|
/**
|
|
12
12
|
* Auth identity attached to a single WebSocket connection.
|
|
13
13
|
*
|
|
@@ -78,8 +78,8 @@ export declare class BackendWebsocketTransport implements FilterableBroadcastTra
|
|
|
78
78
|
* @returns the number of sockets closed
|
|
79
79
|
*/
|
|
80
80
|
close_sockets_for_token(api_token_id: string): number;
|
|
81
|
-
send(message: JsonrpcRequest): Promise<JsonrpcResponseOrError>;
|
|
82
|
-
send(message: JsonrpcNotification): Promise<JsonrpcErrorResponse | null>;
|
|
81
|
+
send(message: JsonrpcRequest, options?: TransportSendOptions): Promise<JsonrpcResponseOrError>;
|
|
82
|
+
send(message: JsonrpcNotification, options?: TransportSendOptions): Promise<JsonrpcErrorResponse | null>;
|
|
83
83
|
/**
|
|
84
84
|
* Broadcast to connections whose identity satisfies a predicate.
|
|
85
85
|
*
|
|
@@ -92,5 +92,14 @@ export declare class BackendWebsocketTransport implements FilterableBroadcastTra
|
|
|
92
92
|
*/
|
|
93
93
|
broadcast_filtered(message: JsonrpcMessageFromServerToClient, predicate: (identity: ConnectionIdentity) => boolean): number;
|
|
94
94
|
is_ready(): boolean;
|
|
95
|
+
/**
|
|
96
|
+
* Number of currently tracked WebSocket connections.
|
|
97
|
+
*
|
|
98
|
+
* Read-only counter intended for telemetry, logging, and tests.
|
|
99
|
+
* Counts every entry in the connection map — including connections
|
|
100
|
+
* that have been closed by the peer but not yet removed by the WS
|
|
101
|
+
* adapter's `onClose` callback.
|
|
102
|
+
*/
|
|
103
|
+
get_connection_count(): number;
|
|
95
104
|
}
|
|
96
105
|
//# sourceMappingURL=transports_ws_backend.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"transports_ws_backend.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/actions/transports_ws_backend.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,KAAK,EAAC,SAAS,EAAC,MAAM,SAAS,CAAC;AAEvC,OAAO,KAAK,EAEX,gCAAgC,EAChC,mBAAmB,EACnB,cAAc,EACd,sBAAsB,EACtB,oBAAoB,EACpB,MAAM,oBAAoB,CAAC;AAO5B,OAAO,EAAc,KAAK,IAAI,EAAC,MAAM,YAAY,CAAC;AAClD,OAAO,EAA2B,KAAK,SAAS,EAAC,MAAM,iBAAiB,CAAC;
|
|
1
|
+
{"version":3,"file":"transports_ws_backend.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/actions/transports_ws_backend.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,KAAK,EAAC,SAAS,EAAC,MAAM,SAAS,CAAC;AAEvC,OAAO,KAAK,EAEX,gCAAgC,EAChC,mBAAmB,EACnB,cAAc,EACd,sBAAsB,EACtB,oBAAoB,EACpB,MAAM,oBAAoB,CAAC;AAO5B,OAAO,EAAc,KAAK,IAAI,EAAC,MAAM,YAAY,CAAC;AAClD,OAAO,EAA2B,KAAK,SAAS,EAAE,KAAK,oBAAoB,EAAC,MAAM,iBAAiB,CAAC;AAIpG;;;;;;;GAOG;AACH,MAAM,WAAW,kBAAkB;IAClC,sEAAsE;IACtE,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;IAC1B,4CAA4C;IAC5C,UAAU,EAAE,IAAI,CAAC;IACjB,sEAAsE;IACtE,YAAY,EAAE,MAAM,GAAG,IAAI,CAAC;CAC5B;AAED;;;;;;;;GAQG;AACH,MAAM,WAAW,4BAA6B,SAAQ,SAAS;IAC9D,kBAAkB,EAAE,CACnB,OAAO,EAAE,gCAAgC,EACzC,SAAS,EAAE,CAAC,QAAQ,EAAE,kBAAkB,KAAK,OAAO,KAChD,MAAM,CAAC;CACZ;AAED,qDAAqD;AACrD,eAAO,MAAM,iCAAiC,GAC7C,WAAW,SAAS,KAClB,SAAS,IAAI,4BAEqE,CAAC;AAEtF,qBAAa,yBAA0B,YAAW,4BAA4B;;IAC7E,QAAQ,CAAC,cAAc,EAAG,uBAAuB,CAAU;IAY3D;;;;;;;;OAQG;IACH,cAAc,CACb,EAAE,EAAE,SAAS,EACb,UAAU,EAAE,MAAM,GAAG,IAAI,EACzB,UAAU,EAAE,IAAI,EAChB,YAAY,GAAE,MAAM,GAAG,IAAW,GAChC,IAAI;IAQP;;;OAGG;IACH,iBAAiB,CAAC,EAAE,EAAE,SAAS,GAAG,IAAI;IA0BtC;;;;OAIG;IACH,yBAAyB,CAAC,UAAU,EAAE,MAAM,GAAG,MAAM;IAIrD;;;;OAIG;IACH,yBAAyB,CAAC,UAAU,EAAE,IAAI,GAAG,MAAM;IAInD;;;;;;;;OAQG;IACH,uBAAuB,CAAC,YAAY,EAAE,MAAM,GAAG,MAAM;IAsB/C,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;IA6CvC;;;;;;;;;OASG;IACH,kBAAkB,CACjB,OAAO,EAAE,gCAAgC,EACzC,SAAS,EAAE,CAAC,QAAQ,EAAE,kBAAkB,KAAK,OAAO,GAClD,MAAM;IAoBT,QAAQ,IAAI,OAAO;IAInB;;;;;;;OAOG;IACH,oBAAoB,IAAI,MAAM;CAG9B"}
|
|
@@ -107,7 +107,7 @@ export class BackendWebsocketTransport {
|
|
|
107
107
|
this.#cleanup_connection(connection_id, ws);
|
|
108
108
|
ws.close(WS_CLOSE_SESSION_REVOKED, 'Session revoked');
|
|
109
109
|
}
|
|
110
|
-
async send(message) {
|
|
110
|
+
async send(message, _options) {
|
|
111
111
|
// TODO currently just broadcasts all messages to all clients, the transport abstraction is still a WIP
|
|
112
112
|
if (is_jsonrpc_request(message)) {
|
|
113
113
|
return create_jsonrpc_error_response(message.id,
|
|
@@ -170,4 +170,15 @@ export class BackendWebsocketTransport {
|
|
|
170
170
|
is_ready() {
|
|
171
171
|
return this.#connections.size > 0;
|
|
172
172
|
}
|
|
173
|
+
/**
|
|
174
|
+
* Number of currently tracked WebSocket connections.
|
|
175
|
+
*
|
|
176
|
+
* Read-only counter intended for telemetry, logging, and tests.
|
|
177
|
+
* Counts every entry in the connection map — including connections
|
|
178
|
+
* that have been closed by the peer but not yet removed by the WS
|
|
179
|
+
* adapter's `onClose` callback.
|
|
180
|
+
*/
|
|
181
|
+
get_connection_count() {
|
|
182
|
+
return this.#connections.size;
|
|
183
|
+
}
|
|
173
184
|
}
|
package/dist/auth/bearer_auth.js
CHANGED
|
@@ -50,7 +50,6 @@ export const create_bearer_auth_middleware = (deps, ip_rate_limiter, log) => {
|
|
|
50
50
|
// Case-insensitive scheme matching per RFC 7235 §2.1 — defense-in-depth:
|
|
51
51
|
// without this, a `bearer` (lowercase) header silently bypasses token
|
|
52
52
|
// validation and browser-context rejection instead of being processed.
|
|
53
|
-
// eslint-disable-next-line @typescript-eslint/prefer-optional-chain
|
|
54
53
|
if (!auth_header || auth_header.slice(0, 7).toLowerCase() !== 'bearer ') {
|
|
55
54
|
await next();
|
|
56
55
|
return;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"keyring.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/auth/keyring.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AAOH;;;GAGG;AACH,MAAM,WAAW,OAAO;IACvB;;;OAGG;IACH,IAAI,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,OAAO,CAAC,MAAM,CAAC,CAAC;IAEzC;;;;OAIG;IACH,MAAM,EAAE,CAAC,YAAY,EAAE,MAAM,KAAK,OAAO,CAAC;QAAC,KAAK,EAAE,MAAM,CAAC;QAAC,SAAS,EAAE,MAAM,CAAA;KAAC,GAAG,IAAI,CAAC,CAAC;CACrF;AAED;;;;;;;;;;;;;;;;;GAiBG;AACH,eAAO,MAAM,cAAc,GAAI,WAAW,MAAM,GAAG,SAAS,KAAG,OAAO,GAAG,
|
|
1
|
+
{"version":3,"file":"keyring.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/auth/keyring.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AAOH;;;GAGG;AACH,MAAM,WAAW,OAAO;IACvB;;;OAGG;IACH,IAAI,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,OAAO,CAAC,MAAM,CAAC,CAAC;IAEzC;;;;OAIG;IACH,MAAM,EAAE,CAAC,YAAY,EAAE,MAAM,KAAK,OAAO,CAAC;QAAC,KAAK,EAAE,MAAM,CAAC;QAAC,SAAS,EAAE,MAAM,CAAA;KAAC,GAAG,IAAI,CAAC,CAAC;CACrF;AAED;;;;;;;;;;;;;;;;;GAiBG;AACH,eAAO,MAAM,cAAc,GAAI,WAAW,MAAM,GAAG,SAAS,KAAG,OAAO,GAAG,IAiCxE,CAAC;AAEF;;;;;;;;;GASG;AACH,eAAO,MAAM,gBAAgB,GAAI,WAAW,MAAM,GAAG,SAAS,KAAG,KAAK,CAAC,MAAM,CAc5E,CAAC;AA6CF;;;GAGG;AACH,MAAM,MAAM,sBAAsB,GAC/B;IAAC,EAAE,EAAE,IAAI,CAAC;IAAC,OAAO,EAAE,OAAO,CAAA;CAAC,GAC5B;IAAC,EAAE,EAAE,KAAK,CAAC;IAAC,MAAM,EAAE,KAAK,CAAC,MAAM,CAAC,CAAA;CAAC,CAAC;AAEtC;;;;;;;;GAQG;AACH,eAAO,MAAM,wBAAwB,GAAI,WAAW,MAAM,GAAG,SAAS,KAAG,sBAUxE,CAAC"}
|
package/dist/auth/keyring.js
CHANGED
|
@@ -58,9 +58,7 @@ export const create_keyring = (env_value) => {
|
|
|
58
58
|
},
|
|
59
59
|
async verify(signed_value) {
|
|
60
60
|
for (let i = 0; i < secrets.length; i++) {
|
|
61
|
-
// eslint-disable-next-line no-await-in-loop
|
|
62
61
|
const key = await get_key(i);
|
|
63
|
-
// eslint-disable-next-line no-await-in-loop
|
|
64
62
|
const result = await verify_with_crypto_key(signed_value, key);
|
|
65
63
|
if (result !== false) {
|
|
66
64
|
return { value: result, key_index: i };
|
package/dist/auth/migrations.js
CHANGED
|
@@ -49,23 +49,23 @@ export const AUTH_MIGRATIONS = [
|
|
|
49
49
|
await db.query(ACTOR_INDEX);
|
|
50
50
|
await db.query(PERMIT_SCHEMA);
|
|
51
51
|
for (const sql of PERMIT_INDEXES) {
|
|
52
|
-
await db.query(sql);
|
|
52
|
+
await db.query(sql);
|
|
53
53
|
}
|
|
54
54
|
await db.query(AUTH_SESSION_SCHEMA);
|
|
55
55
|
for (const sql of AUTH_SESSION_INDEXES) {
|
|
56
|
-
await db.query(sql);
|
|
56
|
+
await db.query(sql);
|
|
57
57
|
}
|
|
58
58
|
await db.query(API_TOKEN_SCHEMA);
|
|
59
59
|
await db.query(API_TOKEN_INDEX);
|
|
60
60
|
await db.query(AUDIT_LOG_SCHEMA);
|
|
61
61
|
for (const sql of AUDIT_LOG_INDEXES) {
|
|
62
|
-
await db.query(sql);
|
|
62
|
+
await db.query(sql);
|
|
63
63
|
}
|
|
64
64
|
await db.query(BOOTSTRAP_LOCK_SCHEMA);
|
|
65
65
|
await db.query(BOOTSTRAP_LOCK_SEED);
|
|
66
66
|
await db.query(INVITE_SCHEMA);
|
|
67
67
|
for (const sql of INVITE_INDEXES) {
|
|
68
|
-
await db.query(sql);
|
|
68
|
+
await db.query(sql);
|
|
69
69
|
}
|
|
70
70
|
await db.query(APP_SETTINGS_SCHEMA);
|
|
71
71
|
await db.query(APP_SETTINGS_SEED);
|
package/dist/db/migrate.d.ts
CHANGED
|
@@ -3,7 +3,11 @@
|
|
|
3
3
|
*
|
|
4
4
|
* Migrations are functions in ordered arrays, grouped by namespace.
|
|
5
5
|
* A `schema_version` table tracks progress per namespace.
|
|
6
|
-
*
|
|
6
|
+
*
|
|
7
|
+
* **Chain-level transactions**: All pending migrations in a namespace run in a
|
|
8
|
+
* single transaction. Any failure rolls back every migration in that run —
|
|
9
|
+
* no partial-state recovery. This rules out non-transactional DDL (e.g.,
|
|
10
|
+
* `CREATE INDEX CONCURRENTLY`); run those out of band.
|
|
7
11
|
*
|
|
8
12
|
* **Forward-only**: No down-migrations. Schema changes are additive.
|
|
9
13
|
* For pre-release development, collapse migrations into a single v0.
|
|
@@ -53,9 +57,15 @@ export interface MigrationResult {
|
|
|
53
57
|
*
|
|
54
58
|
* Creates the `schema_version` tracking table if it does not exist,
|
|
55
59
|
* then for each namespace: acquires an advisory lock, reads the current
|
|
56
|
-
* version, runs pending migrations in order
|
|
60
|
+
* version, runs all pending migrations in order inside a single transaction,
|
|
57
61
|
* updates the stored version, and releases the lock.
|
|
58
62
|
*
|
|
63
|
+
* **Atomicity**: The pending chain for each namespace runs in one transaction —
|
|
64
|
+
* any failure rolls back every migration that ran in that invocation. The
|
|
65
|
+
* next run starts from the previously-stored version, re-running the whole
|
|
66
|
+
* (fixed) chain. Namespaces are independent: a later namespace's failure
|
|
67
|
+
* does not roll back an earlier namespace that already committed.
|
|
68
|
+
*
|
|
59
69
|
* **Concurrency**: Uses PostgreSQL advisory locks to serialize concurrent
|
|
60
70
|
* callers on the same namespace. Safe for multi-instance deployments.
|
|
61
71
|
*
|
package/dist/db/migrate.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"migrate.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/db/migrate.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"migrate.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/db/migrate.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;GAqBG;AAEH,OAAO,KAAK,EAAC,EAAE,EAAC,MAAM,SAAS,CAAC;AAEhC;;;;GAIG;AACH,MAAM,MAAM,WAAW,GAAG,CAAC,EAAE,EAAE,EAAE,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;AAEpD;;;;GAIG;AACH,MAAM,MAAM,SAAS,GAAG,WAAW,GAAG;IAAC,IAAI,EAAE,MAAM,CAAC;IAAC,EAAE,EAAE,WAAW,CAAA;CAAC,CAAC;AAEtE;;;;GAIG;AACH,MAAM,WAAW,kBAAkB;IAClC,SAAS,EAAE,MAAM,CAAC;IAClB,UAAU,EAAE,KAAK,CAAC,SAAS,CAAC,CAAC;CAC7B;AAED,2DAA2D;AAC3D,MAAM,WAAW,eAAe;IAC/B,SAAS,EAAE,MAAM,CAAC;IAClB,YAAY,EAAE,MAAM,CAAC;IACrB,UAAU,EAAE,MAAM,CAAC;IACnB,kBAAkB,EAAE,MAAM,CAAC;CAC3B;AA8BD;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,eAAO,MAAM,cAAc,GAC1B,IAAI,EAAE,EACN,YAAY,KAAK,CAAC,kBAAkB,CAAC,KACnC,OAAO,CAAC,KAAK,CAAC,eAAe,CAAC,CA0EhC,CAAC"}
|