@fuzdev/fuz_app 0.23.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.
Files changed (63) hide show
  1. package/dist/actions/action_codegen.d.ts +25 -0
  2. package/dist/actions/action_codegen.d.ts.map +1 -1
  3. package/dist/actions/action_codegen.js +39 -0
  4. package/dist/actions/action_peer.d.ts +7 -0
  5. package/dist/actions/action_peer.d.ts.map +1 -1
  6. package/dist/actions/action_peer.js +1 -1
  7. package/dist/actions/action_types.d.ts +72 -0
  8. package/dist/actions/action_types.d.ts.map +1 -0
  9. package/dist/actions/action_types.js +11 -0
  10. package/dist/actions/cancel.d.ts +78 -0
  11. package/dist/actions/cancel.d.ts.map +1 -0
  12. package/dist/actions/cancel.js +79 -0
  13. package/dist/actions/heartbeat.d.ts +51 -0
  14. package/dist/actions/heartbeat.d.ts.map +1 -0
  15. package/dist/actions/heartbeat.js +50 -0
  16. package/dist/actions/register_action_ws.d.ts +28 -30
  17. package/dist/actions/register_action_ws.d.ts.map +1 -1
  18. package/dist/actions/register_action_ws.js +103 -20
  19. package/dist/actions/rpc_client.d.ts +10 -0
  20. package/dist/actions/rpc_client.d.ts.map +1 -1
  21. package/dist/actions/rpc_client.js +22 -7
  22. package/dist/actions/socket.svelte.d.ts +88 -4
  23. package/dist/actions/socket.svelte.d.ts.map +1 -1
  24. package/dist/actions/socket.svelte.js +322 -6
  25. package/dist/actions/transports.d.ts +18 -3
  26. package/dist/actions/transports.d.ts.map +1 -1
  27. package/dist/actions/transports.js +4 -0
  28. package/dist/actions/transports_http.d.ts +3 -3
  29. package/dist/actions/transports_http.d.ts.map +1 -1
  30. package/dist/actions/transports_http.js +4 -3
  31. package/dist/actions/transports_ws.d.ts +33 -6
  32. package/dist/actions/transports_ws.d.ts.map +1 -1
  33. package/dist/actions/transports_ws.js +43 -46
  34. package/dist/actions/transports_ws_backend.d.ts +12 -3
  35. package/dist/actions/transports_ws_backend.d.ts.map +1 -1
  36. package/dist/actions/transports_ws_backend.js +12 -1
  37. package/dist/auth/bearer_auth.js +0 -1
  38. package/dist/auth/keyring.d.ts.map +1 -1
  39. package/dist/auth/keyring.js +0 -2
  40. package/dist/auth/migrations.js +4 -4
  41. package/dist/db/migrate.d.ts +12 -2
  42. package/dist/db/migrate.d.ts.map +1 -1
  43. package/dist/db/migrate.js +25 -16
  44. package/dist/db/status.d.ts.map +1 -1
  45. package/dist/db/status.js +0 -2
  46. package/dist/dev/setup.js +2 -2
  47. package/dist/http/db_routes.d.ts.map +1 -1
  48. package/dist/http/db_routes.js +0 -1
  49. package/dist/testing/admin_integration.d.ts.map +1 -1
  50. package/dist/testing/admin_integration.js +0 -3
  51. package/dist/testing/app_server.js +1 -1
  52. package/dist/testing/data_exposure.js +6 -8
  53. package/dist/testing/db.js +1 -1
  54. package/dist/testing/integration.js +0 -1
  55. package/dist/testing/rate_limiting.d.ts.map +1 -1
  56. package/dist/testing/rate_limiting.js +0 -6
  57. package/dist/testing/rpc_round_trip.js +4 -4
  58. package/dist/testing/sse_round_trip.d.ts.map +1 -1
  59. package/dist/testing/sse_round_trip.js +1 -2
  60. package/dist/testing/ws_round_trip.d.ts +15 -3
  61. package/dist/testing/ws_round_trip.d.ts.map +1 -1
  62. package/dist/testing/ws_round_trip.js +3 -3
  63. package/package.json +2 -2
@@ -1,82 +1,79 @@
1
1
  /**
2
- * WebSocket transport — sends JSON-RPC messages via WebSocket with request tracking.
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, UNKNOWN_ERROR_MESSAGE, } from '../http/jsonrpc_errors.js';
7
- import { is_jsonrpc_notification, is_jsonrpc_request, is_jsonrpc_response, is_jsonrpc_error_response, to_jsonrpc_message_id, create_jsonrpc_error_response, } from '../http/jsonrpc_helpers.js';
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, request_timeout_ms) {
22
+ constructor(connection, receive) {
17
23
  this.#connection = connection;
18
24
  this.#receive = receive;
19
- this.#request_tracker = new RequestTracker(request_timeout_ms);
20
- // TODO maybe we want to do this setup elsewhere, not hardcoded like this
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
- // TODO the `data.id !== null` check should be refactored, maybe we want the "Error Message Response" concept for non-null ids
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
- else {
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
- try {
53
- // If this is a JSON-RPC request with an id (not a notification), set up request tracking.
54
- if (is_jsonrpc_request(message)) {
55
- // TODO track the whole request?
56
- const deferred = this.#request_tracker.track_request(message.id);
57
- this.#connection.send(message);
58
- // Return the promise that will resolve when the response is received
59
- const result = await deferred.promise;
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
- else if (is_jsonrpc_notification(message)) {
63
- // For notifications, just send without tracking
64
- this.#connection.send(message);
65
- return null;
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
- catch (error) {
71
- if (error instanceof ThrownJsonrpcError) {
72
- return create_jsonrpc_error_response(to_jsonrpc_message_id(message), {
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;AAIzE;;;;;;;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,CAAC,OAAO,EAAE,cAAc,GAAG,OAAO,CAAC,sBAAsB,CAAC;IAC9D,IAAI,CAAC,OAAO,EAAE,mBAAmB,GAAG,OAAO,CAAC,oBAAoB,GAAG,IAAI,CAAC;IA4C9E;;;;;;;;;OASG;IACH,kBAAkB,CACjB,OAAO,EAAE,gCAAgC,EACzC,SAAS,EAAE,CAAC,QAAQ,EAAE,kBAAkB,KAAK,OAAO,GAClD,MAAM;IAoBT,QAAQ,IAAI,OAAO;CAGnB"}
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
  }
@@ -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,IAkCxE,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"}
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"}
@@ -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 };
@@ -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); // eslint-disable-line no-await-in-loop
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); // eslint-disable-line no-await-in-loop
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); // eslint-disable-line no-await-in-loop
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); // eslint-disable-line no-await-in-loop
68
+ await db.query(sql);
69
69
  }
70
70
  await db.query(APP_SETTINGS_SCHEMA);
71
71
  await db.query(APP_SETTINGS_SEED);
@@ -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
- * Each migration runs in its own transaction.
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 (each in its own transaction),
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
  *
@@ -1 +1 @@
1
- {"version":3,"file":"migrate.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/db/migrate.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;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;;;;;;;;;;;;;;GAcG;AACH,eAAO,MAAM,cAAc,GAC1B,IAAI,EAAE,EACN,YAAY,KAAK,CAAC,kBAAkB,CAAC,KACnC,OAAO,CAAC,KAAK,CAAC,eAAe,CAAC,CA2EhC,CAAC"}
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"}
@@ -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
- * Each migration runs in its own transaction.
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.
@@ -46,9 +50,15 @@ const namespace_lock_key = (namespace) => {
46
50
  *
47
51
  * Creates the `schema_version` tracking table if it does not exist,
48
52
  * then for each namespace: acquires an advisory lock, reads the current
49
- * version, runs pending migrations in order (each in its own transaction),
53
+ * version, runs all pending migrations in order inside a single transaction,
50
54
  * updates the stored version, and releases the lock.
51
55
  *
56
+ * **Atomicity**: The pending chain for each namespace runs in one transaction —
57
+ * any failure rolls back every migration that ran in that invocation. The
58
+ * next run starts from the previously-stored version, re-running the whole
59
+ * (fixed) chain. Namespaces are independent: a later namespace's failure
60
+ * does not roll back an earlier namespace that already committed.
61
+ *
52
62
  * **Concurrency**: Uses PostgreSQL advisory locks to serialize concurrent
53
63
  * callers on the same namespace. Safe for multi-instance deployments.
54
64
  *
@@ -59,7 +69,6 @@ const namespace_lock_key = (namespace) => {
59
69
  export const run_migrations = async (db, namespaces) => {
60
70
  await db.query(SCHEMA_VERSION_DDL);
61
71
  const results = [];
62
- /* eslint-disable no-await-in-loop */
63
72
  for (const { namespace, migrations } of namespaces) {
64
73
  const lock_key = namespace_lock_key(namespace);
65
74
  // Acquire advisory lock — serializes concurrent migration runs
@@ -78,24 +87,25 @@ export const run_migrations = async (db, namespaces) => {
78
87
  if (current_version === migrations.length) {
79
88
  continue; // up to date
80
89
  }
81
- // run pending migrations, each in its own transaction with version upsert
82
- for (let i = current_version; i < migrations.length; i++) {
83
- const { fn, name } = resolve_migration(migrations[i]);
84
- const label = name != null ? `"${name}"` : '';
85
- try {
86
- await db.transaction(async (tx) => {
90
+ // run all pending migrations in a single transaction any failure
91
+ // rolls back the whole pending chain
92
+ await db.transaction(async (tx) => {
93
+ for (let i = current_version; i < migrations.length; i++) {
94
+ const { fn, name } = resolve_migration(migrations[i]);
95
+ const label = name != null ? `"${name}"` : '';
96
+ try {
87
97
  await fn(tx);
88
98
  await tx.query(`INSERT INTO schema_version (namespace, version, applied_at)
89
99
  VALUES ($1, $2, NOW())
90
100
  ON CONFLICT (namespace)
91
101
  DO UPDATE SET version = $2, applied_at = NOW()`, [namespace, i + 1]);
92
- });
93
- }
94
- catch (err) {
95
- const name_part = label ? ` ${label}` : '';
96
- throw new Error(`Migration ${namespace}[${i}]${name_part} failed: ${err instanceof Error ? err.message : String(err)}`, { cause: err });
102
+ }
103
+ catch (err) {
104
+ const name_part = label ? ` ${label}` : '';
105
+ throw new Error(`Migration ${namespace}[${i}]${name_part} failed: ${err instanceof Error ? err.message : String(err)}`, { cause: err });
106
+ }
97
107
  }
98
- }
108
+ });
99
109
  results.push({
100
110
  namespace,
101
111
  from_version: current_version,
@@ -113,6 +123,5 @@ export const run_migrations = async (db, namespaces) => {
113
123
  }
114
124
  }
115
125
  }
116
- /* eslint-enable no-await-in-loop */
117
126
  return results;
118
127
  };
@@ -1 +1 @@
1
- {"version":3,"file":"status.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/db/status.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,KAAK,EAAC,EAAE,EAAC,MAAM,SAAS,CAAC;AAChC,OAAO,KAAK,EAAC,kBAAkB,EAAC,MAAM,cAAc,CAAC;AAErD;;GAEG;AACH,MAAM,WAAW,eAAe;IAC/B,SAAS,EAAE,MAAM,CAAC;IAClB,qDAAqD;IACrD,eAAe,EAAE,MAAM,CAAC;IACxB,mDAAmD;IACnD,iBAAiB,EAAE,MAAM,CAAC;IAC1B,wCAAwC;IACxC,UAAU,EAAE,OAAO,CAAC;CACpB;AAED;;GAEG;AACH,MAAM,WAAW,WAAW;IAC3B,IAAI,EAAE,MAAM,CAAC;IACb,SAAS,EAAE,MAAM,CAAC;CAClB;AAED;;GAEG;AACH,MAAM,WAAW,QAAQ;IACxB,yCAAyC;IACzC,SAAS,EAAE,OAAO,CAAC;IACnB,0CAA0C;IAC1C,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,+BAA+B;IAC/B,WAAW,EAAE,MAAM,CAAC;IACpB,4BAA4B;IAC5B,MAAM,EAAE,KAAK,CAAC,WAAW,CAAC,CAAC;IAC3B,sCAAsC;IACtC,UAAU,EAAE,KAAK,CAAC,eAAe,CAAC,CAAC;CACnC;AAED;;;;;;;;GAQG;AACH,eAAO,MAAM,eAAe,GAC3B,IAAI,EAAE,EACN,aAAa,KAAK,CAAC,kBAAkB,CAAC,KACpC,OAAO,CAAC,QAAQ,CA+ElB,CAAC;AAEF;;;;;GAKG;AACH,eAAO,MAAM,gBAAgB,GAAI,QAAQ,QAAQ,KAAG,MA6BnD,CAAC"}
1
+ {"version":3,"file":"status.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/db/status.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,KAAK,EAAC,EAAE,EAAC,MAAM,SAAS,CAAC;AAChC,OAAO,KAAK,EAAC,kBAAkB,EAAC,MAAM,cAAc,CAAC;AAErD;;GAEG;AACH,MAAM,WAAW,eAAe;IAC/B,SAAS,EAAE,MAAM,CAAC;IAClB,qDAAqD;IACrD,eAAe,EAAE,MAAM,CAAC;IACxB,mDAAmD;IACnD,iBAAiB,EAAE,MAAM,CAAC;IAC1B,wCAAwC;IACxC,UAAU,EAAE,OAAO,CAAC;CACpB;AAED;;GAEG;AACH,MAAM,WAAW,WAAW;IAC3B,IAAI,EAAE,MAAM,CAAC;IACb,SAAS,EAAE,MAAM,CAAC;CAClB;AAED;;GAEG;AACH,MAAM,WAAW,QAAQ;IACxB,yCAAyC;IACzC,SAAS,EAAE,OAAO,CAAC;IACnB,0CAA0C;IAC1C,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,+BAA+B;IAC/B,WAAW,EAAE,MAAM,CAAC;IACpB,4BAA4B;IAC5B,MAAM,EAAE,KAAK,CAAC,WAAW,CAAC,CAAC;IAC3B,sCAAsC;IACtC,UAAU,EAAE,KAAK,CAAC,eAAe,CAAC,CAAC;CACnC;AAED;;;;;;;;GAQG;AACH,eAAO,MAAM,eAAe,GAC3B,IAAI,EAAE,EACN,aAAa,KAAK,CAAC,kBAAkB,CAAC,KACpC,OAAO,CAAC,QAAQ,CA8ElB,CAAC;AAEF;;;;;GAKG;AACH,eAAO,MAAM,gBAAgB,GAAI,QAAQ,QAAQ,KAAG,MA6BnD,CAAC"}
package/dist/db/status.js CHANGED
@@ -36,7 +36,6 @@ export const query_db_status = async (db, namespaces) => {
36
36
  const tables = [];
37
37
  for (const { table_name } of table_rows) {
38
38
  // table_name from information_schema is trusted
39
- // eslint-disable-next-line no-await-in-loop
40
39
  const result = await db.query_one(`SELECT COUNT(*) as count FROM "${table_name}"`);
41
40
  tables.push({
42
41
  name: table_name,
@@ -53,7 +52,6 @@ export const query_db_status = async (db, namespaces) => {
53
52
  ) as exists`);
54
53
  if (sv_exists?.exists) {
55
54
  for (const { namespace, migrations: ns_migrations } of namespaces) {
56
- // eslint-disable-next-line no-await-in-loop
57
55
  const row = await db.query_one('SELECT version FROM schema_version WHERE namespace = $1', [namespace]);
58
56
  const current_version = row?.version ?? 0;
59
57
  migrations.push({
package/dist/dev/setup.js CHANGED
@@ -93,7 +93,7 @@ export const setup_env_file = async (deps, env_path, example_path, options) => {
93
93
  for (const [key, generate] of Object.entries(replacements)) {
94
94
  const pattern = new RegExp(`^${key}=$`, 'm');
95
95
  if (pattern.test(content)) {
96
- const value = await generate(); // eslint-disable-line no-await-in-loop
96
+ const value = await generate();
97
97
  content = content.replace(pattern, `${key}=${value}`);
98
98
  changed = true;
99
99
  log.ok(`Generated ${key} in existing ${env_path}`);
@@ -114,7 +114,7 @@ export const setup_env_file = async (deps, env_path, example_path, options) => {
114
114
  for (const [key, generate] of Object.entries(replacements)) {
115
115
  const pattern = new RegExp(`^${key}=$`, 'm');
116
116
  if (pattern.test(content)) {
117
- const value = await generate(); // eslint-disable-line no-await-in-loop
117
+ const value = await generate();
118
118
  content = content.replace(pattern, `${key}=${value}`);
119
119
  }
120
120
  }
@@ -1 +1 @@
1
- {"version":3,"file":"db_routes.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/http/db_routes.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAGH,OAAO,KAAK,EAAC,MAAM,EAAC,MAAM,yBAAyB,CAAC;AAEpD,OAAO,KAAK,EAAC,EAAE,EAAE,MAAM,EAAC,MAAM,aAAa,CAAC;AAC5C,OAAO,EAAmB,KAAK,SAAS,EAAC,MAAM,iBAAiB,CAAC;AAWjE;;GAEG;AACH,MAAM,WAAW,SAAS;IACzB,UAAU,EAAE,MAAM,CAAC;CACnB;AAED;;GAEG;AACH,MAAM,WAAW,cAAc;IAC9B,IAAI,EAAE,MAAM,CAAC;IACb,SAAS,EAAE,MAAM,CAAC;CAClB;AAED;;GAEG;AACH,MAAM,WAAW,cAAc;IAC9B,WAAW,EAAE,MAAM,CAAC;CACpB;AAED;;GAEG;AACH,MAAM,WAAW,UAAU;IAC1B,WAAW,EAAE,MAAM,CAAC;IACpB,SAAS,EAAE,MAAM,CAAC;IAClB,WAAW,EAAE,MAAM,CAAC;CACpB;AAED;;GAEG;AACH,MAAM,WAAW,cAAc;IAC9B,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,EAAE,MAAM,CAAC;IAChB,8EAA8E;IAC9E,WAAW,CAAC,EAAE,CAAC,EAAE,EAAE,EAAE,KAAK,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAAC;IAC3D,+EAA+E;IAC/E,GAAG,CAAC,EAAE,MAAM,CAAC;CACb;AAED;;;;;GAKG;AACH,eAAO,MAAM,qBAAqB,GAAI,SAAS,cAAc,KAAG,KAAK,CAAC,SAAS,CAuN9E,CAAC"}
1
+ {"version":3,"file":"db_routes.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/http/db_routes.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAGH,OAAO,KAAK,EAAC,MAAM,EAAC,MAAM,yBAAyB,CAAC;AAEpD,OAAO,KAAK,EAAC,EAAE,EAAE,MAAM,EAAC,MAAM,aAAa,CAAC;AAC5C,OAAO,EAAmB,KAAK,SAAS,EAAC,MAAM,iBAAiB,CAAC;AAWjE;;GAEG;AACH,MAAM,WAAW,SAAS;IACzB,UAAU,EAAE,MAAM,CAAC;CACnB;AAED;;GAEG;AACH,MAAM,WAAW,cAAc;IAC9B,IAAI,EAAE,MAAM,CAAC;IACb,SAAS,EAAE,MAAM,CAAC;CAClB;AAED;;GAEG;AACH,MAAM,WAAW,cAAc;IAC9B,WAAW,EAAE,MAAM,CAAC;CACpB;AAED;;GAEG;AACH,MAAM,WAAW,UAAU;IAC1B,WAAW,EAAE,MAAM,CAAC;IACpB,SAAS,EAAE,MAAM,CAAC;IAClB,WAAW,EAAE,MAAM,CAAC;CACpB;AAED;;GAEG;AACH,MAAM,WAAW,cAAc;IAC9B,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,EAAE,MAAM,CAAC;IAChB,8EAA8E;IAC9E,WAAW,CAAC,EAAE,CAAC,EAAE,EAAE,EAAE,KAAK,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAAC;IAC3D,+EAA+E;IAC/E,GAAG,CAAC,EAAE,MAAM,CAAC;CACb;AAED;;;;;GAKG;AACH,eAAO,MAAM,qBAAqB,GAAI,SAAS,cAAc,KAAG,KAAK,CAAC,SAAS,CAsN9E,CAAC"}
@@ -66,7 +66,6 @@ export const create_db_route_specs = (options) => {
66
66
  ORDER BY table_name`);
67
67
  const tables = [];
68
68
  for (const { table_name } of table_names) {
69
- // eslint-disable-next-line no-await-in-loop
70
69
  const result = await route.db.query_one(`SELECT COUNT(*) as count FROM "${assert_valid_sql_identifier(table_name)}"`);
71
70
  tables.push({
72
71
  name: table_name,
@@ -1 +1 @@
1
- {"version":3,"file":"admin_integration.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/testing/admin_integration.ts"],"names":[],"mappings":"AAAA,OAAO,qBAAqB,CAAC;AAiB7B,OAAO,KAAK,EAAC,cAAc,EAAC,MAAM,2BAA2B,CAAC;AAC9D,OAAO,KAAK,EAAC,gBAAgB,EAAE,gBAAgB,EAAC,MAAM,yBAAyB,CAAC;AAChF,OAAO,KAAK,EAAC,SAAS,EAAC,MAAM,uBAAuB,CAAC;AACrD,OAAO,EAA0B,KAAK,gBAAgB,EAAC,MAAM,wBAAwB,CAAC;AAGtF,OAAO,EAIN,KAAK,SAAS,EACd,MAAM,SAAS,CAAC;AAUjB;;GAEG;AACH,MAAM,WAAW,mCAAmC;IACnD,4CAA4C;IAC5C,eAAe,EAAE,cAAc,CAAC,MAAM,CAAC,CAAC;IACxC,wDAAwD;IACxD,kBAAkB,EAAE,CAAC,GAAG,EAAE,gBAAgB,KAAK,KAAK,CAAC,SAAS,CAAC,CAAC;IAChE,4GAA4G;IAC5G,KAAK,EAAE,gBAAgB,CAAC;IACxB;;;;;OAKG;IACH,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,iDAAiD;IACjD,WAAW,CAAC,EAAE,OAAO,CACpB,IAAI,CAAC,gBAAgB,EAAE,SAAS,GAAG,iBAAiB,GAAG,oBAAoB,CAAC,CAC5E,CAAC;IACF;;;OAGG;IACH,YAAY,CAAC,EAAE,KAAK,CAAC,SAAS,CAAC,CAAC;CAChC;AAgDD;;;;;;;;;;;GAWG;AACH,eAAO,MAAM,yCAAyC,GACrD,SAAS,mCAAmC,KAC1C,IAwnCF,CAAC"}
1
+ {"version":3,"file":"admin_integration.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/testing/admin_integration.ts"],"names":[],"mappings":"AAAA,OAAO,qBAAqB,CAAC;AAiB7B,OAAO,KAAK,EAAC,cAAc,EAAC,MAAM,2BAA2B,CAAC;AAC9D,OAAO,KAAK,EAAC,gBAAgB,EAAE,gBAAgB,EAAC,MAAM,yBAAyB,CAAC;AAChF,OAAO,KAAK,EAAC,SAAS,EAAC,MAAM,uBAAuB,CAAC;AACrD,OAAO,EAA0B,KAAK,gBAAgB,EAAC,MAAM,wBAAwB,CAAC;AAGtF,OAAO,EAIN,KAAK,SAAS,EACd,MAAM,SAAS,CAAC;AAUjB;;GAEG;AACH,MAAM,WAAW,mCAAmC;IACnD,4CAA4C;IAC5C,eAAe,EAAE,cAAc,CAAC,MAAM,CAAC,CAAC;IACxC,wDAAwD;IACxD,kBAAkB,EAAE,CAAC,GAAG,EAAE,gBAAgB,KAAK,KAAK,CAAC,SAAS,CAAC,CAAC;IAChE,4GAA4G;IAC5G,KAAK,EAAE,gBAAgB,CAAC;IACxB;;;;;OAKG;IACH,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,iDAAiD;IACjD,WAAW,CAAC,EAAE,OAAO,CACpB,IAAI,CAAC,gBAAgB,EAAE,SAAS,GAAG,iBAAiB,GAAG,oBAAoB,CAAC,CAC5E,CAAC;IACF;;;OAGG;IACH,YAAY,CAAC,EAAE,KAAK,CAAC,SAAS,CAAC,CAAC;CAChC;AAgDD;;;;;;;;;;;GAWG;AACH,eAAO,MAAM,yCAAyC,GACrD,SAAS,mCAAmC,KAC1C,IAsnCF,CAAC"}
@@ -804,7 +804,6 @@ export const describe_standard_admin_integration_tests = (options) => {
804
804
  const admin_routes = test_app.route_specs.filter((s) => s.path.startsWith(prefix) && s.auth.type === 'role' && s.auth.role === 'admin');
805
805
  // Hit admin routes without auth to exercise 401 error schemas
806
806
  for (const route of admin_routes.slice(0, 5)) {
807
- // eslint-disable-next-line no-await-in-loop
808
807
  const res = await test_app.app.request(route.path, {
809
808
  method: route.method,
810
809
  headers: { host: 'localhost' },
@@ -826,12 +825,10 @@ export const describe_standard_admin_integration_tests = (options) => {
826
825
  s.auth.role === 'admin');
827
826
  assert.ok(admin_get_routes.length > 0, 'Expected at least one admin GET route — ensure create_route_specs includes admin routes');
828
827
  for (const route of admin_get_routes) {
829
- // eslint-disable-next-line no-await-in-loop
830
828
  const res = await test_app.app.request(route.path, {
831
829
  headers: test_app.create_session_headers(),
832
830
  });
833
831
  assert.strictEqual(res.status, 200, `${route.method} ${route.path} should return 200`);
834
- // eslint-disable-next-line no-await-in-loop
835
832
  await assert_response_matches_spec(test_app.route_specs, route.method, route.path, res);
836
833
  }
837
834
  });
@@ -51,7 +51,7 @@ export const bootstrap_test_account = async (options) => {
51
51
  });
52
52
  // Grant roles
53
53
  for (const role of roles) {
54
- await query_grant_permit(deps, { actor_id: actor.id, role, granted_by: null }); // eslint-disable-line no-await-in-loop
54
+ await query_grant_permit(deps, { actor_id: actor.id, role, granted_by: null });
55
55
  }
56
56
  // Create API token
57
57
  const { token: api_token, id: token_id, token_hash } = generate_api_token();
@@ -171,18 +171,17 @@ const describe_data_exposure_runtime_tests = (options) => {
171
171
  if (skip_set.has(route_key))
172
172
  continue;
173
173
  const url = resolve_valid_path(spec.path, spec.params);
174
- // eslint-disable-next-line no-await-in-loop
175
174
  const res = await test_app.app.request(url, {
176
175
  method: spec.method,
177
176
  headers: { host: 'localhost', origin: 'http://localhost:5173' },
178
177
  });
179
178
  if (res.headers.get('Content-Type')?.includes('text/event-stream')) {
180
- await res.body?.cancel(); // eslint-disable-line no-await-in-loop
179
+ await res.body?.cancel();
181
180
  continue;
182
181
  }
183
182
  let error_body;
184
183
  try {
185
- error_body = await res.clone().json(); // eslint-disable-line no-await-in-loop
184
+ error_body = await res.clone().json();
186
185
  }
187
186
  catch {
188
187
  continue;
@@ -200,7 +199,6 @@ const describe_data_exposure_runtime_tests = (options) => {
200
199
  continue;
201
200
  const url = resolve_valid_path(spec.path, spec.params);
202
201
  const headers = authed_account.create_session_headers();
203
- // eslint-disable-next-line no-await-in-loop
204
202
  const res = await test_app.app.request(url, {
205
203
  method: spec.method,
206
204
  headers,
@@ -208,7 +206,7 @@ const describe_data_exposure_runtime_tests = (options) => {
208
206
  assert.strictEqual(res.status, 403, `${route_key} should return 403 for non-admin user`);
209
207
  let error_body;
210
208
  try {
211
- error_body = await res.clone().json(); // eslint-disable-line no-await-in-loop
209
+ error_body = await res.clone().json();
212
210
  }
213
211
  catch {
214
212
  continue;
@@ -247,16 +245,16 @@ const describe_data_exposure_runtime_tests = (options) => {
247
245
  },
248
246
  ...(body ? { body: JSON.stringify(body) } : {}),
249
247
  };
250
- const res = await test_app.app.request(url, request_init); // eslint-disable-line no-await-in-loop
248
+ const res = await test_app.app.request(url, request_init);
251
249
  if (res.headers.get('Content-Type')?.includes('text/event-stream')) {
252
- await res.body?.cancel(); // eslint-disable-line no-await-in-loop
250
+ await res.body?.cancel();
253
251
  continue;
254
252
  }
255
253
  if (!res.ok)
256
254
  continue;
257
255
  let response_body;
258
256
  try {
259
- response_body = await res.clone().json(); // eslint-disable-line no-await-in-loop
257
+ response_body = await res.clone().json();
260
258
  }
261
259
  catch {
262
260
  continue;
@@ -206,7 +206,7 @@ export const AUTH_DROP_TABLES = [
206
206
  */
207
207
  export const drop_auth_schema = async (db) => {
208
208
  for (const table of AUTH_DROP_TABLES) {
209
- await db.query(`DROP TABLE IF EXISTS ${assert_valid_sql_identifier(table)} CASCADE`); // eslint-disable-line no-await-in-loop
209
+ await db.query(`DROP TABLE IF EXISTS ${assert_valid_sql_identifier(table)} CASCADE`);
210
210
  }
211
211
  await db.query('DROP TABLE IF EXISTS schema_version CASCADE');
212
212
  };
@@ -840,7 +840,6 @@ export const describe_standard_integration_tests = (options) => {
840
840
  const route = find_auth_route(test_app.route_specs, suffix, suffix === '/tokens/create' || suffix === '/sessions/revoke-all' ? 'POST' : 'GET');
841
841
  if (!route)
842
842
  continue;
843
- // eslint-disable-next-line no-await-in-loop
844
843
  const res = await test_app.app.request(route.path, {
845
844
  method: route.method,
846
845
  headers: { host: 'localhost' },
@@ -1 +1 @@
1
- {"version":3,"file":"rate_limiting.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/testing/rate_limiting.ts"],"names":[],"mappings":"AAAA,OAAO,qBAAqB,CAAC;AAiB7B,OAAO,KAAK,EAAC,cAAc,EAAC,MAAM,2BAA2B,CAAC;AAC9D,OAAO,KAAK,EAAC,gBAAgB,EAAE,gBAAgB,EAAC,MAAM,yBAAyB,CAAC;AAChF,OAAO,KAAK,EAAC,SAAS,EAAC,MAAM,uBAAuB,CAAC;AAKrD,OAAO,EAIN,KAAK,SAAS,EACd,MAAM,SAAS,CAAC;AAKjB;;GAEG;AACH,MAAM,WAAW,uBAAuB;IACvC,4CAA4C;IAC5C,eAAe,EAAE,cAAc,CAAC,MAAM,CAAC,CAAC;IACxC,wDAAwD;IACxD,kBAAkB,EAAE,CAAC,GAAG,EAAE,gBAAgB,KAAK,KAAK,CAAC,SAAS,CAAC,CAAC;IAChE,iDAAiD;IACjD,WAAW,CAAC,EAAE,OAAO,CACpB,IAAI,CAAC,gBAAgB,EAAE,SAAS,GAAG,iBAAiB,GAAG,oBAAoB,CAAC,CAC5E,CAAC;IACF;;OAEG;IACH,YAAY,CAAC,EAAE,KAAK,CAAC,SAAS,CAAC,CAAC;IAChC;;;OAGG;IACH,YAAY,CAAC,EAAE,MAAM,CAAC;CACtB;AAED;;;;;;;;;;;;;;;GAeG;AACH,eAAO,MAAM,4BAA4B,GAAI,SAAS,uBAAuB,KAAG,IA+N/E,CAAC"}
1
+ {"version":3,"file":"rate_limiting.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/testing/rate_limiting.ts"],"names":[],"mappings":"AAAA,OAAO,qBAAqB,CAAC;AAiB7B,OAAO,KAAK,EAAC,cAAc,EAAC,MAAM,2BAA2B,CAAC;AAC9D,OAAO,KAAK,EAAC,gBAAgB,EAAE,gBAAgB,EAAC,MAAM,yBAAyB,CAAC;AAChF,OAAO,KAAK,EAAC,SAAS,EAAC,MAAM,uBAAuB,CAAC;AAKrD,OAAO,EAIN,KAAK,SAAS,EACd,MAAM,SAAS,CAAC;AAKjB;;GAEG;AACH,MAAM,WAAW,uBAAuB;IACvC,4CAA4C;IAC5C,eAAe,EAAE,cAAc,CAAC,MAAM,CAAC,CAAC;IACxC,wDAAwD;IACxD,kBAAkB,EAAE,CAAC,GAAG,EAAE,gBAAgB,KAAK,KAAK,CAAC,SAAS,CAAC,CAAC;IAChE,iDAAiD;IACjD,WAAW,CAAC,EAAE,OAAO,CACpB,IAAI,CAAC,gBAAgB,EAAE,SAAS,GAAG,iBAAiB,GAAG,oBAAoB,CAAC,CAC5E,CAAC;IACF;;OAEG;IACH,YAAY,CAAC,EAAE,KAAK,CAAC,SAAS,CAAC,CAAC;IAChC;;;OAGG;IACH,YAAY,CAAC,EAAE,MAAM,CAAC;CACtB;AAED;;;;;;;;;;;;;;;GAeG;AACH,eAAO,MAAM,4BAA4B,GAAI,SAAS,uBAAuB,KAAG,IA4N/E,CAAC"}
@@ -64,7 +64,6 @@ export const describe_rate_limiting_tests = (options) => {
64
64
  const login_route = find_auth_route(test_app.route_specs, '/login', 'POST');
65
65
  assert.ok(login_route, 'Expected POST /login route — ensure create_route_specs includes account routes');
66
66
  // Fire max_attempts failed login requests (sequential — must exhaust the window)
67
- /* eslint-disable no-await-in-loop */
68
67
  for (let i = 0; i < max_attempts; i++) {
69
68
  const res = await test_app.app.request(login_route.path, {
70
69
  method: 'POST',
@@ -77,7 +76,6 @@ export const describe_rate_limiting_tests = (options) => {
77
76
  });
78
77
  assert.notStrictEqual(res.status, 429, `Request ${i + 1}/${max_attempts} should not be rate limited`);
79
78
  }
80
- /* eslint-enable no-await-in-loop */
81
79
  // The next request should be rate limited
82
80
  const blocked_res = await test_app.app.request(login_route.path, {
83
81
  method: 'POST',
@@ -119,7 +117,6 @@ export const describe_rate_limiting_tests = (options) => {
119
117
  assert.ok(login_route, 'Expected POST /login route — ensure create_route_specs includes account routes');
120
118
  const target_username = 'rate_limit_target';
121
119
  // Fire max_attempts failed login requests for the same username
122
- /* eslint-disable no-await-in-loop */
123
120
  for (let i = 0; i < max_attempts; i++) {
124
121
  const res = await test_app.app.request(login_route.path, {
125
122
  method: 'POST',
@@ -132,7 +129,6 @@ export const describe_rate_limiting_tests = (options) => {
132
129
  });
133
130
  assert.notStrictEqual(res.status, 429, `Request ${i + 1}/${max_attempts} should not be rate limited`);
134
131
  }
135
- /* eslint-enable no-await-in-loop */
136
132
  // The next request for the same username should be rate limited
137
133
  const blocked_res = await test_app.app.request(login_route.path, {
138
134
  method: 'POST',
@@ -184,7 +180,6 @@ export const describe_rate_limiting_tests = (options) => {
184
180
  const verify_route = find_auth_route(test_app.route_specs, '/verify', 'GET');
185
181
  assert.ok(verify_route, 'Expected GET /verify route — ensure create_route_specs includes account routes');
186
182
  // Fire max_attempts invalid bearer requests (sequential — must exhaust the window)
187
- /* eslint-disable no-await-in-loop */
188
183
  for (let i = 0; i < max_attempts; i++) {
189
184
  const res = await test_app.app.request(verify_route.path, {
190
185
  headers: {
@@ -194,7 +189,6 @@ export const describe_rate_limiting_tests = (options) => {
194
189
  });
195
190
  assert.notStrictEqual(res.status, 429, `Request ${i + 1}/${max_attempts} should not be rate limited`);
196
191
  }
197
- /* eslint-enable no-await-in-loop */
198
192
  // The next request should be rate limited
199
193
  const blocked_res = await test_app.app.request(verify_route.path, {
200
194
  headers: {