@fuzdev/fuz_app 0.124.0 → 0.126.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 (90) hide show
  1. package/dist/actions/CLAUDE.md +58 -21
  2. package/dist/actions/action_rpc.d.ts +14 -11
  3. package/dist/actions/action_rpc.d.ts.map +1 -1
  4. package/dist/actions/action_rpc.js +5 -4
  5. package/dist/actions/connection_closer.d.ts +13 -3
  6. package/dist/actions/connection_closer.d.ts.map +1 -1
  7. package/dist/actions/connection_closer.js +7 -5
  8. package/dist/actions/register_action_ws.d.ts +18 -13
  9. package/dist/actions/register_action_ws.d.ts.map +1 -1
  10. package/dist/actions/register_action_ws.js +9 -7
  11. package/dist/actions/register_ws_endpoint.d.ts +6 -3
  12. package/dist/actions/register_ws_endpoint.d.ts.map +1 -1
  13. package/dist/actions/register_ws_endpoint.js +6 -3
  14. package/dist/actions/transports.d.ts +9 -0
  15. package/dist/actions/transports.d.ts.map +1 -1
  16. package/dist/actions/transports.js +9 -0
  17. package/dist/actions/transports_ws_backend.d.ts +16 -1
  18. package/dist/actions/transports_ws_backend.d.ts.map +1 -1
  19. package/dist/actions/transports_ws_backend.js +26 -6
  20. package/dist/actions/ws_endpoint_spec.d.ts +24 -4
  21. package/dist/actions/ws_endpoint_spec.d.ts.map +1 -1
  22. package/dist/actions/ws_endpoint_spec.js +15 -1
  23. package/dist/auth/middleware.d.ts +29 -1
  24. package/dist/auth/middleware.d.ts.map +1 -1
  25. package/dist/auth/middleware.js +35 -1
  26. package/dist/http/surface.d.ts +3 -1
  27. package/dist/http/surface.d.ts.map +1 -1
  28. package/dist/http/surface.js +15 -1
  29. package/dist/rate_limiter.d.ts +25 -3
  30. package/dist/rate_limiter.d.ts.map +1 -1
  31. package/dist/rate_limiter.js +4 -3
  32. package/dist/realtime/sse_auth_guard.d.ts +2 -1
  33. package/dist/realtime/sse_auth_guard.d.ts.map +1 -1
  34. package/dist/realtime/sse_auth_guard.js +4 -2
  35. package/dist/realtime/subscriber_registry.d.ts +12 -0
  36. package/dist/realtime/subscriber_registry.d.ts.map +1 -1
  37. package/dist/realtime/subscriber_registry.js +18 -2
  38. package/dist/server/app_server.d.ts +127 -51
  39. package/dist/server/app_server.d.ts.map +1 -1
  40. package/dist/server/app_server.js +107 -60
  41. package/dist/server/app_server_context.d.ts +17 -9
  42. package/dist/server/app_server_context.d.ts.map +1 -1
  43. package/dist/server/static.d.ts +1 -1
  44. package/dist/testing/CLAUDE.md +77 -19
  45. package/dist/testing/app_server.d.ts +6 -3
  46. package/dist/testing/app_server.d.ts.map +1 -1
  47. package/dist/testing/app_server.js +11 -12
  48. package/dist/testing/connection_closer_helpers.d.ts +10 -4
  49. package/dist/testing/connection_closer_helpers.d.ts.map +1 -1
  50. package/dist/testing/connection_closer_helpers.js +4 -0
  51. package/dist/testing/cross_backend/capabilities.d.ts +3 -3
  52. package/dist/testing/cross_backend/default_backend_configs.d.ts +31 -0
  53. package/dist/testing/cross_backend/default_backend_configs.d.ts.map +1 -1
  54. package/dist/testing/cross_backend/default_backend_configs.js +31 -0
  55. package/dist/testing/cross_backend/full_spine_mount.d.ts +35 -11
  56. package/dist/testing/cross_backend/full_spine_mount.d.ts.map +1 -1
  57. package/dist/testing/cross_backend/full_spine_mount.js +26 -9
  58. package/dist/testing/cross_backend/rust_spine_stub_backend_config.d.ts +15 -0
  59. package/dist/testing/cross_backend/rust_spine_stub_backend_config.d.ts.map +1 -1
  60. package/dist/testing/cross_backend/rust_spine_stub_backend_config.js +3 -2
  61. package/dist/testing/cross_backend/testing_backdoor.d.ts.map +1 -1
  62. package/dist/testing/cross_backend/testing_backdoor.js +2 -1
  63. package/dist/testing/cross_backend/testing_reset_actions.d.ts +6 -3
  64. package/dist/testing/cross_backend/testing_reset_actions.d.ts.map +1 -1
  65. package/dist/testing/cross_backend/testing_reset_actions.js +6 -3
  66. package/dist/testing/cross_backend/testing_server_bun.d.ts.map +1 -1
  67. package/dist/testing/cross_backend/testing_server_bun.js +11 -7
  68. package/dist/testing/cross_backend/testing_server_core.d.ts +68 -37
  69. package/dist/testing/cross_backend/testing_server_core.d.ts.map +1 -1
  70. package/dist/testing/cross_backend/testing_server_core.js +20 -12
  71. package/dist/testing/cross_backend/testing_server_deno.d.ts.map +1 -1
  72. package/dist/testing/cross_backend/testing_server_deno.js +5 -4
  73. package/dist/testing/cross_backend/testing_server_node.d.ts.map +1 -1
  74. package/dist/testing/cross_backend/testing_server_node.js +20 -7
  75. package/dist/testing/cross_backend/ts_spine_backend_config.d.ts +17 -1
  76. package/dist/testing/cross_backend/ts_spine_backend_config.d.ts.map +1 -1
  77. package/dist/testing/cross_backend/ts_spine_backend_config.js +11 -6
  78. package/dist/testing/cross_backend/ws_action_rate_limit.d.ts +26 -0
  79. package/dist/testing/cross_backend/ws_action_rate_limit.d.ts.map +1 -0
  80. package/dist/testing/cross_backend/ws_action_rate_limit.js +261 -0
  81. package/dist/testing/stubs.d.ts +14 -4
  82. package/dist/testing/stubs.d.ts.map +1 -1
  83. package/dist/testing/stubs.js +31 -13
  84. package/dist/testing/transports/ws_transport.d.ts +7 -0
  85. package/dist/testing/transports/ws_transport.d.ts.map +1 -1
  86. package/dist/testing/transports/ws_transport.js +1 -1
  87. package/dist/testing/ws_round_trip.d.ts +36 -6
  88. package/dist/testing/ws_round_trip.d.ts.map +1 -1
  89. package/dist/testing/ws_round_trip.js +28 -8
  90. package/package.json +15 -5
@@ -14,7 +14,7 @@ import { type SessionOptions } from '../auth/session_cookie.js';
14
14
  import type { BootstrapAccountSuccess } from '../auth/bootstrap_account.js';
15
15
  import type { EventSpec } from '../realtime/sse.js';
16
16
  import { type AuditLogSse } from '../realtime/sse_auth_guard.js';
17
- import { type RateLimiter } from '../rate_limiter.js';
17
+ import { RateLimiter, type RateLimiterMode } from '../rate_limiter.js';
18
18
  import type { DaemonTokenState } from '../auth/daemon_token.js';
19
19
  import type { MigrationResult } from '../db/migrate.js';
20
20
  import type { AppBackend } from './app_backend.js';
@@ -25,7 +25,7 @@ import { type AppSurfaceSpec, type RpcEndpointSpec } from '../http/surface.js';
25
25
  import { type RouteSpec } from '../http/route_spec.js';
26
26
  import type { MiddlewareSpec } from '../http/middleware_spec.js';
27
27
  import { type BootstrapStatus } from '../auth/bootstrap_routes.js';
28
- import type { WsEndpointSpec } from '../actions/ws_endpoint_spec.js';
28
+ import { type WsEndpointSpec } from '../actions/ws_endpoint_spec.js';
29
29
  import type { BackendWebsocketTransport } from '../actions/transports_ws_backend.js';
30
30
  import { type AuthCleanupScheduleOptions } from '../auth/cleanup.js';
31
31
  /**
@@ -95,10 +95,20 @@ export interface AppServerOptions {
95
95
  trusted_proxies: Array<string>;
96
96
  get_connection_ip: (c: Context) => string | undefined;
97
97
  };
98
+ /**
99
+ * What an omitted rate limiter becomes — `'enforced'` (the default) builds
100
+ * a default instance for each `*_rate_limiter` option left out,
101
+ * `'disabled_for_testing'` leaves each of them `null`. An explicit option
102
+ * (an instance or `null`) always wins over the mode. The disabled mode adds
103
+ * one surface warning. Never `'disabled_for_testing'` in production — see
104
+ * `RateLimiterMode`.
105
+ */
106
+ rate_limiters?: RateLimiterMode;
98
107
  /**
99
108
  * Per-IP rate limiter for login + password change — the distributed-spray
100
109
  * backstop. Omit or `undefined` to use a default limiter (5 attempts per
101
- * 15 minutes). Pass `null` to explicitly disable rate limiting.
110
+ * 15 minutes) under `rate_limiters: 'enforced'`, none under
111
+ * `'disabled_for_testing'`. Pass `null` to explicitly disable rate limiting.
102
112
  * Also available on `AppServerContext` for route factory callbacks.
103
113
  *
104
114
  * One instance per auth surface, not one shared across all four. These
@@ -110,32 +120,34 @@ export interface AppServerOptions {
110
120
  */
111
121
  login_ip_rate_limiter?: RateLimiter | null;
112
122
  /**
113
- * Per-IP rate limiter for signup. Omit or `undefined` to use a default
114
- * limiter (5 attempts per 15 minutes). Pass `null` to explicitly disable.
123
+ * Per-IP rate limiter for signup. Omit or `undefined` for the mode's
124
+ * default (5 attempts per 15 minutes under `rate_limiters: 'enforced'`).
125
+ * Pass `null` to explicitly disable.
115
126
  * Separate from `login_ip_rate_limiter` because an open-signup deployment
116
127
  * lets any unauthenticated caller spend it. Also available on
117
128
  * `AppServerContext` for route factory callbacks.
118
129
  */
119
130
  signup_ip_rate_limiter?: RateLimiter | null;
120
131
  /**
121
- * Per-IP rate limiter for bootstrap. Omit or `undefined` to use a default
122
- * limiter (5 attempts per 15 minutes). Pass `null` to explicitly disable.
132
+ * Per-IP rate limiter for bootstrap. Omit or `undefined` for the mode's
133
+ * default (5 attempts per 15 minutes under `rate_limiters: 'enforced'`).
134
+ * Pass `null` to explicitly disable.
123
135
  * Separate from `login_ip_rate_limiter` so a fumbled bootstrap token can't
124
136
  * spend the operator's login budget. Wired directly into the factory-managed
125
137
  * bootstrap route; also on `AppServerContext` for symmetry.
126
138
  */
127
139
  bootstrap_ip_rate_limiter?: RateLimiter | null;
128
140
  /**
129
- * Per-account rate limiter for login attempts.
130
- * Omit or `undefined` to use a default limiter (10 attempts per 30 minutes).
131
- * Pass `null` to explicitly disable rate limiting.
141
+ * Per-account rate limiter for login attempts. Omit or `undefined` for the
142
+ * mode's default (10 attempts per 30 minutes under
143
+ * `rate_limiters: 'enforced'`). Pass `null` to explicitly disable.
132
144
  * Also available on `AppServerContext` for route factory callbacks.
133
145
  */
134
146
  login_account_rate_limiter?: RateLimiter | null;
135
147
  /**
136
148
  * Per-account rate limiter for signup attempts, keyed by submitted username.
137
- * Omit or `undefined` to use a default limiter (10 attempts per 30 minutes).
138
- * Pass `null` to explicitly disable rate limiting.
149
+ * Omit or `undefined` for the mode's default (10 attempts per 30 minutes
150
+ * under `rate_limiters: 'enforced'`). Pass `null` to explicitly disable.
139
151
  * Also available on `AppServerContext` for route factory callbacks.
140
152
  */
141
153
  signup_account_rate_limiter?: RateLimiter | null;
@@ -143,20 +155,23 @@ export interface AppServerOptions {
143
155
  * Per-IP rate limiter for the action dispatchers (HTTP RPC + WebSocket).
144
156
  * Consulted for actions whose spec declares `rate_limit: 'ip'` or `'both'`.
145
157
  * Same limiter applies across transports — one budget per action.
146
- * Omit or `undefined` to use a default limiter (600 attempts per
147
- * 15 minutes — permissive). Pass `null` to explicitly disable.
148
- * Also available on `AppServerContext` for consumers wiring
149
- * `register_action_ws`.
158
+ * Omit or `undefined` for the mode's default (600 attempts per 15 minutes
159
+ * — permissive — under `rate_limiters: 'enforced'`). Pass `null` to
160
+ * explicitly disable.
161
+ * Threaded into every endpoint mounted from `rpc_endpoints` and
162
+ * `ws_endpoints`; also available on `AppServerContext`.
150
163
  */
151
164
  action_ip_rate_limiter?: RateLimiter | null;
152
165
  /**
153
- * Per-actor rate limiter for the action dispatchers (HTTP RPC + WebSocket).
154
- * Consulted for actions whose spec declares `rate_limit: 'account'` or
155
- * `'both'`. Keyed on `request_context.actor.id` (post-auth).
156
- * Omit or `undefined` to use a default limiter (1200 attempts per
157
- * 15 minutes — permissive). Pass `null` to explicitly disable.
158
- * Also available on `AppServerContext` for consumers wiring
159
- * `register_action_ws`.
166
+ * Per-account rate limiter for the action dispatchers (HTTP RPC +
167
+ * WebSocket). Consulted for actions whose spec declares
168
+ * `rate_limit: 'account'` or `'both'`. Keyed on the authenticated account's
169
+ * id, so every actor and credential of one account shares a budget.
170
+ * Omit or `undefined` for the mode's default (1200 attempts per 15 minutes
171
+ * — permissive — under `rate_limiters: 'enforced'`). Pass `null` to
172
+ * explicitly disable.
173
+ * Threaded into every endpoint mounted from `rpc_endpoints` and
174
+ * `ws_endpoints`; also available on `AppServerContext`.
160
175
  */
161
176
  action_account_rate_limiter?: RateLimiter | null;
162
177
  /**
@@ -179,7 +194,13 @@ export interface AppServerOptions {
179
194
  * Called after all middleware is ready.
180
195
  */
181
196
  create_route_specs: (context: AppServerContext) => Array<RouteSpec>;
182
- /** Optional: transform middleware specs before applying. */
197
+ /**
198
+ * Optional: transform middleware specs before applying.
199
+ *
200
+ * The endpoint auth-scope check (`assert_endpoint_in_auth_scope`) reads
201
+ * `AUTH_MIDDLEWARE_PATH`, not the transformed specs — a transform that
202
+ * moves or drops the auth middleware is not followed by it.
203
+ */
183
204
  transform_middleware?: (specs: Array<MiddlewareSpec>) => Array<MiddlewareSpec>;
184
205
  /**
185
206
  * Enable factory-managed audit log SSE.
@@ -226,46 +247,79 @@ export interface AppServerOptions {
226
247
  event_specs?: Array<EventSpec>;
227
248
  /**
228
249
  * RPC endpoint specs — single source of truth for both surface generation
229
- * *and* live dispatch. Each entry is mounted via `create_rpc_endpoint`
230
- * against the assembled Hono app, so consumers no longer call
250
+ * *and* live dispatch. Each entry becomes route specs via
251
+ * `create_rpc_endpoint`, applied with the other routes, so consumers don't call
231
252
  * `create_rpc_endpoint` themselves inside `create_route_specs`.
232
253
  *
233
254
  * Accepts either an array (evaluated eagerly) or a factory
234
255
  * `(ctx: AppServerContext) => Array<RpcEndpointSpec>` (evaluated after the
235
256
  * server context is assembled). Use the factory form when action lists
236
257
  * depend on `ctx.deps` — e.g. `create_standard_rpc_actions(ctx.deps)`.
258
+ *
259
+ * Each path must sit inside the auth middleware's scope
260
+ * (`AUTH_MIDDLEWARE_PATH`, `'/api/*'`) — assembly throws otherwise, since
261
+ * an endpoint outside it would see every caller as unauthenticated.
237
262
  */
238
263
  rpc_endpoints?: Array<RpcEndpointSpec> | ((context: AppServerContext) => Array<RpcEndpointSpec>);
239
264
  /**
240
- * Hono adapter's `upgradeWebSocket` helper. Required whenever
241
- * `ws_endpoints` resolves to a non-empty array — `create_app_server`
242
- * throws at assembly otherwise. Omit (along with `ws_endpoints`)
243
- * when the consumer doesn't mount any WS endpoints. The same
244
- * adapter helper services every `WsEndpointSpec` mounted from
245
- * `ws_endpoints` — one adapter per app.
265
+ * Returns the Hono runtime adapter's `upgradeWebSocket` helper for the
266
+ * assembled app. `create_app_server` calls it once, with the app it
267
+ * builds (the same instance as `AppServer.app`), after creating the app
268
+ * and before mounting `ws_endpoints` — and only when `ws_endpoints`
269
+ * resolves non-empty, so it is never called for a server with no
270
+ * WebSocket endpoints. Required in that case: assembly throws without it.
271
+ * One helper services every mounted endpoint.
272
+ *
273
+ * A factory rather than the helper itself because Node's adapter needs
274
+ * the app: `@hono/node-ws` has no module-level helper, only
275
+ * `createNodeWebSocket({app})`, and `create_app_server` is what builds
276
+ * the app. Deno and Bun ignore the argument:
277
+ *
278
+ * ```ts
279
+ * import {upgradeWebSocket} from '@hono/deno'; // or '@hono/bun'
280
+ * create_upgrade_websocket: () => upgradeWebSocket,
281
+ * ```
246
282
  *
247
- * For Node, `import {upgradeWebSocket} from '@hono/node-ws'`. For
248
- * Deno, `import {upgradeWebSocket} from 'hono/deno'`. Test harnesses
249
- * use `create_stub_upgrade` from `$lib/testing/ws_round_trip.ts`.
283
+ * Node keeps the adapter so it can attach to the server after `serve()`:
284
+ *
285
+ * ```ts
286
+ * let node_ws: NodeWebSocket | undefined;
287
+ * const server = await create_app_server({
288
+ * ...,
289
+ * create_upgrade_websocket: (app) => (node_ws = createNodeWebSocket({app})).upgradeWebSocket,
290
+ * });
291
+ * node_ws?.injectWebSocket(serve({fetch: server.app.fetch, port}));
292
+ * ```
293
+ *
294
+ * Test harnesses pass `() => stub.upgradeWebSocket` with
295
+ * `create_stub_upgrade` from `testing/ws_round_trip.ts`.
250
296
  */
251
- upgradeWebSocket?: UpgradeWebSocket;
297
+ create_upgrade_websocket?: (app: Hono) => UpgradeWebSocket;
252
298
  /**
253
299
  * WebSocket endpoint specs — single source of truth for both surface
254
300
  * generation *and* live dispatch. Each entry is auto-mounted via
255
- * `register_ws_endpoint` against the assembled Hono app, so
256
- * consumers no longer call `register_ws_endpoint` themselves.
301
+ * `register_ws_endpoint` against the assembled Hono app on every
302
+ * runtime, so consumers don't call `register_ws_endpoint` themselves.
257
303
  *
258
304
  * Accepts either an array (evaluated eagerly) or a factory
259
305
  * `(ctx: AppServerContext) => ReadonlyArray<WsEndpointSpec>`
260
306
  * (evaluated after the server context is assembled). Use the factory
261
- * form when action lists depend on `ctx.deps` /
262
- * `ctx.action_*_rate_limiter` — e.g. when spreading
263
- * `create_standard_rpc_actions(ctx.deps, ...)` over WS.
307
+ * form when action lists depend on `ctx.deps` — e.g. when spreading
308
+ * `create_standard_rpc_actions({...ctx.deps, notification_sender})` over WS.
264
309
  *
265
- * When non-empty, `upgradeWebSocket` must be supplied (throws
310
+ * When non-empty, `create_upgrade_websocket` must be supplied (throws
266
311
  * otherwise). A factory returning `[]` does NOT trip the check —
267
312
  * feature-flag gated WS surfaces stay safe.
268
313
  *
314
+ * Every mount gets what the HTTP surface gets: the auth middleware (a
315
+ * path outside `AUTH_MIDDLEWARE_PATH` throws at assembly, like
316
+ * `rpc_endpoints`), the server's `allowed_origins` unless the spec
317
+ * narrows them, and the same `action_ip_rate_limiter` /
318
+ * `action_account_rate_limiter` instances the RPC endpoints charge — one
319
+ * budget per action across both transports. The upgrade routes register
320
+ * after the route specs and before `post_route_middleware` and
321
+ * `static_serving`, so neither runs ahead of an upgrade.
322
+ *
269
323
  * Duplicate `path` values across two `WsEndpointSpec`s throw at
270
324
  * mount time (Hono would silently shadow them otherwise).
271
325
  *
@@ -349,10 +403,27 @@ export interface AppServer {
349
403
  */
350
404
  ws_endpoints: Readonly<Record<string, BackendWebsocketTransport>>;
351
405
  /**
352
- * Shut the server's resources down: stop the auth cleanup schedule when
353
- * the `auth_cleanup` option started one — waiting for a pass in progress —
354
- * then close the database connection (`AppBackend.close`). That wait has
355
- * no bound of its own; a shutdown with a deadline races it.
406
+ * Shut the server's resources down, in order:
407
+ *
408
+ * 1. stop the auth cleanup schedule when the `auth_cleanup` option started
409
+ * one, waiting for a pass in progress — that wait has no bound of its
410
+ * own, so a shutdown with a deadline races it
411
+ * 2. close every live connection on `deps.connection_closer`
412
+ * (`close_all_sockets`) — each WebSocket with `WS_CLOSE_GOING_AWAY`, each
413
+ * audit stream — including transports supplied through
414
+ * `WsEndpointSpec.transport` or added by hand, since they are registered
415
+ * on the backend's closer and the backend is going away; a close that
416
+ * throws is logged and the shutdown goes on
417
+ * 3. `dispose` the rate limiters `create_app_server` built for omitted
418
+ * options — never one passed in, which its caller owns
419
+ * 4. close the database connection (`AppBackend.close`)
420
+ *
421
+ * Idempotent: a second call settles with the first one's outcome (a failed
422
+ * close is not retried), and concurrent calls share one shutdown.
423
+ *
424
+ * Stop accepting new HTTP connections first — without awaiting the
425
+ * listener's full drain, since live WebSockets hold it open until `close`
426
+ * ends them — so no new request reaches a closed database.
356
427
  */
357
428
  close: () => Promise<void>;
358
429
  }
@@ -381,9 +452,11 @@ export declare const DEFAULT_MAX_BODY_SIZE: number;
381
452
  /**
382
453
  * Create a fully assembled Hono app with auth, middleware, and routes.
383
454
  *
384
- * Handles the assembly lifecycle: proxy middleware → auth middleware →
385
- * bootstrap status → route specs → surface generation → Hono app assembly →
386
- * static serving. Database migrations belong to the backend lifecycle —
455
+ * Handles the assembly lifecycle: rate limiters → audit SSE → proxy
456
+ * middleware → auth middleware → bootstrap status → route specs (consumer,
457
+ * bootstrap, RPC endpoints, surface) → surface generation → Hono app assembly
458
+ * (routes, then WS endpoints) → post-route middleware → static serving → the
459
+ * auth cleanup schedule. Database migrations belong to the backend lifecycle —
387
460
  * pass `migration_namespaces` to `create_app_backend`.
388
461
  *
389
462
  * When `audit_log_sse` is set, the SSE registry's listener is registered via
@@ -397,8 +470,11 @@ export declare const DEFAULT_MAX_BODY_SIZE: number;
397
470
  * to assert the invariant.
398
471
  *
399
472
  * When `auth_cleanup` is set, the auth cleanup schedule starts as the last
400
- * step of assembly — so an assembly that throws leaves no schedule running —
401
- * and the returned `close` stops it before closing the database.
473
+ * step of assembly — so an assembly that throws leaves no schedule running.
474
+ *
475
+ * The returned `close` owns shutdown: it stops that schedule, closes every
476
+ * live connection on the backend's closer, disposes the rate limiters built
477
+ * here, and closes the database (`AppServer.close`).
402
478
  *
403
479
  * @returns assembled Hono app, backend, surface build, and bootstrap status
404
480
  */
@@ -1 +1 @@
1
- {"version":3,"file":"app_server.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/server/app_server.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"app_server.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/server/app_server.ts"],"names":[],"mappings":"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"}
@@ -14,7 +14,7 @@ import { z } from 'zod';
14
14
  import { session_cookie_options } from '../auth/session_cookie.js';
15
15
  import { create_audit_log_sse, audit_log_event_specs } from '../realtime/sse_auth_guard.js';
16
16
  import { BaseServerEnv } from './env.js';
17
- import { create_rate_limiter, default_login_account_rate_limit, default_action_account_rate_limit, default_action_ip_rate_limit } from '../rate_limiter.js';
17
+ import { RateLimiter, default_login_ip_rate_limit, default_login_account_rate_limit, default_action_account_rate_limit, default_action_ip_rate_limit } from '../rate_limiter.js';
18
18
  // Side-effect import: augments Hono's ContextVariableMap so consumers
19
19
  // that import app_server get type-safe c.get('auth_session_id') etc.
20
20
  import '../hono_context.js';
@@ -26,12 +26,13 @@ import { apply_middleware_specs, apply_route_specs, prefix_route_specs } from '.
26
26
  import { check_bootstrap_status, create_bootstrap_route_specs } from '../auth/bootstrap_routes.js';
27
27
  import { create_surface_route_spec } from '../http/common_routes.js';
28
28
  import { flush_pending_effects, flush_post_commit_effects } from '../http/pending_effects.js';
29
- import { create_auth_middleware_specs } from '../auth/middleware.js';
29
+ import { assert_endpoint_in_auth_scope, create_auth_middleware_specs } from '../auth/middleware.js';
30
30
  import { fuz_auth_guard_resolver } from '../auth/auth_guard_resolver.js';
31
31
  import { create_fuz_authorization_handler } from '../auth/request_context.js';
32
32
  import { ERROR_PAYLOAD_TOO_LARGE } from '../http/error_schemas.js';
33
33
  import { create_rpc_endpoint } from '../actions/action_rpc.js';
34
34
  import { register_ws_endpoint } from '../actions/register_ws_endpoint.js';
35
+ import { resolve_ws_endpoints } from '../actions/ws_endpoint_spec.js';
35
36
  import { create_ws_auth_guard } from '../actions/transports_ws_auth_guard.js';
36
37
  import { start_auth_cleanup } from '../auth/cleanup.js';
37
38
  /**
@@ -62,9 +63,11 @@ export const DEFAULT_MAX_BODY_SIZE = 1024 * 1024;
62
63
  /**
63
64
  * Create a fully assembled Hono app with auth, middleware, and routes.
64
65
  *
65
- * Handles the assembly lifecycle: proxy middleware → auth middleware →
66
- * bootstrap status → route specs → surface generation → Hono app assembly →
67
- * static serving. Database migrations belong to the backend lifecycle —
66
+ * Handles the assembly lifecycle: rate limiters → audit SSE → proxy
67
+ * middleware → auth middleware → bootstrap status → route specs (consumer,
68
+ * bootstrap, RPC endpoints, surface) → surface generation → Hono app assembly
69
+ * (routes, then WS endpoints) → post-route middleware → static serving → the
70
+ * auth cleanup schedule. Database migrations belong to the backend lifecycle —
68
71
  * pass `migration_namespaces` to `create_app_backend`.
69
72
  *
70
73
  * When `audit_log_sse` is set, the SSE registry's listener is registered via
@@ -78,8 +81,11 @@ export const DEFAULT_MAX_BODY_SIZE = 1024 * 1024;
78
81
  * to assert the invariant.
79
82
  *
80
83
  * When `auth_cleanup` is set, the auth cleanup schedule starts as the last
81
- * step of assembly — so an assembly that throws leaves no schedule running —
82
- * and the returned `close` stops it before closing the database.
84
+ * step of assembly — so an assembly that throws leaves no schedule running.
85
+ *
86
+ * The returned `close` owns shutdown: it stops that schedule, closes every
87
+ * live connection on the backend's closer, disposes the rate limiters built
88
+ * here, and closes the database (`AppServer.close`).
83
89
  *
84
90
  * @returns assembled Hono app, backend, surface build, and bootstrap status
85
91
  */
@@ -87,30 +93,29 @@ export const create_app_server = async (options) => {
87
93
  const { backend } = options;
88
94
  const { deps } = backend;
89
95
  const { log } = deps;
90
- // Rate limiter defaults (undefined = default, null = disable)
91
- // One instance per surface — see `AppServerOptions.login_ip_rate_limiter`
92
- // for why these are not a single shared bucket.
93
- const login_ip_rate_limiter = options.login_ip_rate_limiter === undefined
94
- ? create_rate_limiter()
95
- : options.login_ip_rate_limiter;
96
- const signup_ip_rate_limiter = options.signup_ip_rate_limiter === undefined
97
- ? create_rate_limiter()
98
- : options.signup_ip_rate_limiter;
99
- const bootstrap_ip_rate_limiter = options.bootstrap_ip_rate_limiter === undefined
100
- ? create_rate_limiter()
101
- : options.bootstrap_ip_rate_limiter;
102
- const login_account_rate_limiter = options.login_account_rate_limiter === undefined
103
- ? create_rate_limiter(default_login_account_rate_limit)
104
- : options.login_account_rate_limiter;
105
- const signup_account_rate_limiter = options.signup_account_rate_limiter === undefined
106
- ? create_rate_limiter(default_login_account_rate_limit)
107
- : options.signup_account_rate_limiter;
108
- const action_ip_rate_limiter = options.action_ip_rate_limiter === undefined
109
- ? create_rate_limiter(default_action_ip_rate_limit)
110
- : options.action_ip_rate_limiter;
111
- const action_account_rate_limiter = options.action_account_rate_limiter === undefined
112
- ? create_rate_limiter(default_action_account_rate_limit)
113
- : options.action_account_rate_limiter;
96
+ // Rate limiters: an explicit option (instance or `null`) wins; an omitted
97
+ // one is a default instance under 'enforced', `null` under
98
+ // 'disabled_for_testing'. One instance per surface — see
99
+ // `AppServerOptions.login_ip_rate_limiter` for why these are not a single
100
+ // shared bucket. The ones built here are the server's to dispose on close.
101
+ const rate_limiter_mode = options.rate_limiters ?? 'enforced';
102
+ const built_rate_limiters = [];
103
+ const resolve_rate_limiter = (option, defaults) => {
104
+ if (option !== undefined)
105
+ return option;
106
+ if (rate_limiter_mode === 'disabled_for_testing')
107
+ return null;
108
+ const limiter = new RateLimiter({ ...defaults });
109
+ built_rate_limiters.push(limiter);
110
+ return limiter;
111
+ };
112
+ const login_ip_rate_limiter = resolve_rate_limiter(options.login_ip_rate_limiter, default_login_ip_rate_limit);
113
+ const signup_ip_rate_limiter = resolve_rate_limiter(options.signup_ip_rate_limiter, default_login_ip_rate_limit);
114
+ const bootstrap_ip_rate_limiter = resolve_rate_limiter(options.bootstrap_ip_rate_limiter, default_login_ip_rate_limit);
115
+ const login_account_rate_limiter = resolve_rate_limiter(options.login_account_rate_limiter, default_login_account_rate_limit);
116
+ const signup_account_rate_limiter = resolve_rate_limiter(options.signup_account_rate_limiter, default_login_account_rate_limit);
117
+ const action_ip_rate_limiter = resolve_rate_limiter(options.action_ip_rate_limiter, default_action_ip_rate_limit);
118
+ const action_account_rate_limiter = resolve_rate_limiter(options.action_account_rate_limiter, default_action_account_rate_limit);
114
119
  // Factory-managed audit SSE — registers a listener on the bound emitter
115
120
  // so SSE fan-out runs alongside the consumer's `on_audit_event`
116
121
  // without rebuilding `AppDeps`, and adds the registry to the backend's
@@ -195,6 +200,7 @@ export const create_app_server = async (options) => {
195
200
  : options.rpc_endpoints;
196
201
  if (resolved_rpc_endpoints) {
197
202
  for (const endpoint of resolved_rpc_endpoints) {
203
+ assert_endpoint_in_auth_scope('rpc_endpoints', endpoint.path);
198
204
  factory_routes.push(...create_rpc_endpoint({
199
205
  path: endpoint.path,
200
206
  actions: endpoint.actions,
@@ -210,9 +216,21 @@ export const create_app_server = async (options) => {
210
216
  // live Hono `app` (origin / auth / role / authorization middleware +
211
217
  // the `app.get(path, ...)` upgrade route), and `app` does not exist
212
218
  // until the assembly phase below.
213
- const resolved_ws_endpoints = typeof options.ws_endpoints === 'function'
219
+ // A spec without `allowed_origins` takes the server's list — resolved here,
220
+ // once, so the mount and the surface read the same patterns.
221
+ const resolved_ws_endpoints = resolve_ws_endpoints(typeof options.ws_endpoints === 'function'
214
222
  ? options.ws_endpoints(context)
215
- : options.ws_endpoints;
223
+ : options.ws_endpoints, options.allowed_origins);
224
+ const create_upgrade_websocket = options.create_upgrade_websocket;
225
+ if (resolved_ws_endpoints?.length) {
226
+ if (create_upgrade_websocket === undefined) {
227
+ throw new Error('create_app_server: ws_endpoints resolved non-empty but create_upgrade_websocket is missing. ' +
228
+ "Pass a factory returning the Hono adapter's upgradeWebSocket helper.");
229
+ }
230
+ for (const endpoint of resolved_ws_endpoints) {
231
+ assert_endpoint_in_auth_scope('ws_endpoints', endpoint.path);
232
+ }
233
+ }
216
234
  // Surface route (default: enabled)
217
235
  if (options.surface_route !== false) {
218
236
  factory_routes.push(create_surface_route_spec(surface_ref));
@@ -260,20 +278,35 @@ export const create_app_server = async (options) => {
260
278
  });
261
279
  }
262
280
  }
263
- // One diagnostic per surface — a deployment that disables login's limiter
264
- // but keeps signup's is a different posture than one that disables both,
265
- // and a single collapsed warning hid which surface was open.
266
- for (const [name, limiter] of [
267
- ['login', login_ip_rate_limiter],
268
- ['signup', signup_ip_rate_limiter],
269
- ['bootstrap', bootstrap_ip_rate_limiter]
270
- ]) {
271
- if (limiter === null) {
272
- config_diagnostics.push({
273
- level: 'warning',
274
- category: 'config',
275
- message: `${name} IP rate limiter explicitly disabled (null)`
276
- });
281
+ if (rate_limiter_mode === 'disabled_for_testing') {
282
+ // one warning for the mode rather than one per limiter it nulled
283
+ config_diagnostics.push({
284
+ level: 'warning',
285
+ category: 'security',
286
+ message: "rate limiters disabled for testing (rate_limiters: 'disabled_for_testing') — " +
287
+ 'every limiter not passed explicitly is off; never use in production'
288
+ });
289
+ }
290
+ else {
291
+ // One diagnostic per explicitly-null limiter — a deployment that disables
292
+ // login's limiter but keeps signup's is a different posture than one that
293
+ // disables both, and a single collapsed warning hid which surface was open.
294
+ for (const [name, limiter] of [
295
+ ['login IP', login_ip_rate_limiter],
296
+ ['signup IP', signup_ip_rate_limiter],
297
+ ['bootstrap IP', bootstrap_ip_rate_limiter],
298
+ ['login account', login_account_rate_limiter],
299
+ ['signup account', signup_account_rate_limiter],
300
+ ['action IP', action_ip_rate_limiter],
301
+ ['action account', action_account_rate_limiter]
302
+ ]) {
303
+ if (limiter === null) {
304
+ config_diagnostics.push({
305
+ level: 'warning',
306
+ category: 'security',
307
+ message: `${name} rate limiter explicitly disabled (null)`
308
+ });
309
+ }
277
310
  }
278
311
  }
279
312
  if (config_diagnostics.length) {
@@ -284,6 +317,12 @@ export const create_app_server = async (options) => {
284
317
  log_startup_summary(surface_spec.surface, log, options.env_values);
285
318
  // Hono app assembly
286
319
  const app = new Hono();
320
+ // The adapter helper for the WS mount below — built from this app, which
321
+ // Node's `createNodeWebSocket({app})` needs, and only when there is
322
+ // something to mount.
323
+ const upgrade_websocket = resolved_ws_endpoints?.length && create_upgrade_websocket
324
+ ? create_upgrade_websocket(app)
325
+ : null;
287
326
  // Two-queue side-effect flush. `pending_effects` collects eager
288
327
  // fire-and-forget promises (audit emits, api-token usage). `post_commit_effects` collects deferred thunks pushed via
289
328
  // `emit_after_commit` (WS notifications, anything that must observe a
@@ -347,11 +386,7 @@ export const create_app_server = async (options) => {
347
386
  // adjacent to the consumer routes and ahead of the static fallback —
348
387
  // matches the "WS mount is route registration" mental model.
349
388
  const mounted_ws_endpoints = {};
350
- if (resolved_ws_endpoints?.length) {
351
- if (options.upgradeWebSocket === undefined) {
352
- throw new Error('create_app_server: ws_endpoints resolved non-empty but upgradeWebSocket is missing. ' +
353
- "Pass the Hono adapter's upgradeWebSocket helper as a top-level option.");
354
- }
389
+ if (resolved_ws_endpoints?.length && upgrade_websocket) {
355
390
  // Cross-surface collision: `register_ws_endpoint` mounts a `GET path`
356
391
  // upgrade route. If a `RouteSpec` already registered `GET path`,
357
392
  // Hono's last-wins semantics would silently shadow the consumer's
@@ -382,7 +417,7 @@ export const create_app_server = async (options) => {
382
417
  const { transport: endpoint_transport } = register_ws_endpoint({
383
418
  app,
384
419
  path: endpoint.path,
385
- upgradeWebSocket: options.upgradeWebSocket,
420
+ upgradeWebSocket: upgrade_websocket,
386
421
  allowed_origins: endpoint.allowed_origins,
387
422
  db: deps.db,
388
423
  actions: endpoint.actions,
@@ -429,6 +464,24 @@ export const create_app_server = async (options) => {
429
464
  const auth_cleanup = options.auth_cleanup
430
465
  ? start_auth_cleanup(deps, options.auth_cleanup === true ? undefined : options.auth_cleanup)
431
466
  : null;
467
+ // One shutdown, shared by every call — see `AppServer.close` for the order.
468
+ let closing = null;
469
+ const shutdown = async () => {
470
+ // the schedule first, so a pass in progress finishes on a live pool
471
+ if (auth_cleanup)
472
+ await auth_cleanup.stop();
473
+ // then the connections, so none outlives the server — a failure here
474
+ // must not keep the database open
475
+ try {
476
+ deps.connection_closer.close_all_sockets();
477
+ }
478
+ catch (error) {
479
+ log.error('create_app_server: closing live connections on shutdown failed:', error);
480
+ }
481
+ for (const limiter of built_rate_limiters)
482
+ limiter.dispose();
483
+ await backend.close();
484
+ };
432
485
  return {
433
486
  app,
434
487
  surface_spec,
@@ -436,12 +489,6 @@ export const create_app_server = async (options) => {
436
489
  migration_results: backend.migration_results,
437
490
  audit_sse,
438
491
  ws_endpoints: mounted_ws_endpoints,
439
- close: auth_cleanup
440
- ? async () => {
441
- // the schedule first, so a pass in progress finishes on a live pool
442
- await auth_cleanup.stop();
443
- await backend.close();
444
- }
445
- : backend.close
492
+ close: () => (closing ??= shutdown())
446
493
  };
447
494
  };
@@ -26,22 +26,30 @@ export interface AppServerContext {
26
26
  bootstrap_status: BootstrapStatus;
27
27
  session_options: SessionOptions<string>;
28
28
  /**
29
- * Per-IP login + password-change rate limiter (from options). `null` when
30
- * not configured. One instance per auth surface — see
31
- * `AppServerOptions.login_ip_rate_limiter` for why these aren't shared.
29
+ * Per-IP login + password-change rate limiter. One instance per auth
30
+ * surface — see `AppServerOptions.login_ip_rate_limiter` for why these
31
+ * aren't shared.
32
+ *
33
+ * Each limiter here is what `create_app_server` resolved: the instance
34
+ * its option passed, else a default instance under
35
+ * `rate_limiters: 'enforced'` (the default). `null` when disabled — the
36
+ * option was an explicit `null`, or the mode is `'disabled_for_testing'`.
32
37
  */
33
38
  login_ip_rate_limiter: RateLimiter | null;
34
- /** Per-IP signup rate limiter (from options). `null` when not configured. */
39
+ /** Per-IP signup rate limiter. `null` when disabled. */
35
40
  signup_ip_rate_limiter: RateLimiter | null;
36
- /** Per-IP bootstrap rate limiter (from options). `null` when not configured. */
41
+ /** Per-IP bootstrap rate limiter. `null` when disabled. */
37
42
  bootstrap_ip_rate_limiter: RateLimiter | null;
38
- /** Per-account login rate limiter (from options). `null` when not configured. */
43
+ /** Per-account login rate limiter. `null` when disabled. */
39
44
  login_account_rate_limiter: RateLimiter | null;
40
- /** Per-account signup rate limiter (from options). `null` when not configured. */
45
+ /** Per-account signup rate limiter. `null` when disabled. */
41
46
  signup_account_rate_limiter: RateLimiter | null;
42
- /** Per-IP action-dispatcher rate limiter — shared across HTTP RPC + WS. `null` when not configured. */
47
+ /** Per-IP action-dispatcher rate limiter — shared across HTTP RPC + WS. `null` when disabled. */
43
48
  action_ip_rate_limiter: RateLimiter | null;
44
- /** Per-actor action-dispatcher rate limiter — shared across HTTP RPC + WS. `null` when not configured. */
49
+ /**
50
+ * Per-account action-dispatcher rate limiter, keyed on the authenticated
51
+ * account's id — shared across HTTP RPC + WS. `null` when disabled.
52
+ */
45
53
  action_account_rate_limiter: RateLimiter | null;
46
54
  /**
47
55
  * Factory-managed audit log SSE. Non-null when the `audit_log_sse`
@@ -1 +1 @@
1
- {"version":3,"file":"app_server_context.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/server/app_server_context.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAEH,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,iBAAiB,CAAC;AAC/C,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAC;AACnD,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,6BAA6B,CAAC;AACnE,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,2BAA2B,CAAC;AAChE,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,oBAAoB,CAAC;AACtD,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,+BAA+B,CAAC;AAEjE,8CAA8C;AAC9C,MAAM,WAAW,gBAAgB;IAChC,IAAI,EAAE,OAAO,CAAC;IACd,OAAO,EAAE,UAAU,CAAC;IACpB,gBAAgB,EAAE,eAAe,CAAC;IAClC,eAAe,EAAE,cAAc,CAAC,MAAM,CAAC,CAAC;IACxC;;;;OAIG;IACH,qBAAqB,EAAE,WAAW,GAAG,IAAI,CAAC;IAC1C,6EAA6E;IAC7E,sBAAsB,EAAE,WAAW,GAAG,IAAI,CAAC;IAC3C,gFAAgF;IAChF,yBAAyB,EAAE,WAAW,GAAG,IAAI,CAAC;IAC9C,iFAAiF;IACjF,0BAA0B,EAAE,WAAW,GAAG,IAAI,CAAC;IAC/C,kFAAkF;IAClF,2BAA2B,EAAE,WAAW,GAAG,IAAI,CAAC;IAChD,uGAAuG;IACvG,sBAAsB,EAAE,WAAW,GAAG,IAAI,CAAC;IAC3C,0GAA0G;IAC1G,2BAA2B,EAAE,WAAW,GAAG,IAAI,CAAC;IAChD;;;;OAIG;IACH,SAAS,EAAE,WAAW,GAAG,IAAI,CAAC;CAC9B"}
1
+ {"version":3,"file":"app_server_context.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/server/app_server_context.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAEH,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,iBAAiB,CAAC;AAC/C,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAC;AACnD,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,6BAA6B,CAAC;AACnE,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,2BAA2B,CAAC;AAChE,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,oBAAoB,CAAC;AACtD,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,+BAA+B,CAAC;AAEjE,8CAA8C;AAC9C,MAAM,WAAW,gBAAgB;IAChC,IAAI,EAAE,OAAO,CAAC;IACd,OAAO,EAAE,UAAU,CAAC;IACpB,gBAAgB,EAAE,eAAe,CAAC;IAClC,eAAe,EAAE,cAAc,CAAC,MAAM,CAAC,CAAC;IACxC;;;;;;;;;OASG;IACH,qBAAqB,EAAE,WAAW,GAAG,IAAI,CAAC;IAC1C,wDAAwD;IACxD,sBAAsB,EAAE,WAAW,GAAG,IAAI,CAAC;IAC3C,2DAA2D;IAC3D,yBAAyB,EAAE,WAAW,GAAG,IAAI,CAAC;IAC9C,4DAA4D;IAC5D,0BAA0B,EAAE,WAAW,GAAG,IAAI,CAAC;IAC/C,6DAA6D;IAC7D,2BAA2B,EAAE,WAAW,GAAG,IAAI,CAAC;IAChD,iGAAiG;IACjG,sBAAsB,EAAE,WAAW,GAAG,IAAI,CAAC;IAC3C;;;OAGG;IACH,2BAA2B,EAAE,WAAW,GAAG,IAAI,CAAC;IAChD;;;;OAIG;IACH,SAAS,EAAE,WAAW,GAAG,IAAI,CAAC;CAC9B"}