@fuzdev/fuz_app 0.63.0 → 0.64.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 (107) hide show
  1. package/dist/actions/CLAUDE.md +124 -11
  2. package/dist/actions/connection_closer.d.ts +68 -0
  3. package/dist/actions/connection_closer.d.ts.map +1 -0
  4. package/dist/actions/connection_closer.js +41 -0
  5. package/dist/actions/register_action_ws.d.ts.map +1 -1
  6. package/dist/actions/register_action_ws.js +23 -2
  7. package/dist/actions/register_ws_endpoint.d.ts +11 -9
  8. package/dist/actions/register_ws_endpoint.d.ts.map +1 -1
  9. package/dist/actions/register_ws_endpoint.js +5 -5
  10. package/dist/actions/transports_ws_auth_guard.d.ts +24 -8
  11. package/dist/actions/transports_ws_auth_guard.d.ts.map +1 -1
  12. package/dist/actions/transports_ws_auth_guard.js +23 -7
  13. package/dist/actions/ws_endpoint_spec.d.ts +119 -0
  14. package/dist/actions/ws_endpoint_spec.d.ts.map +1 -0
  15. package/dist/actions/ws_endpoint_spec.js +13 -0
  16. package/dist/auth/CLAUDE.md +79 -15
  17. package/dist/auth/account_action_specs.d.ts +1 -1
  18. package/dist/auth/account_actions.d.ts +13 -0
  19. package/dist/auth/account_actions.d.ts.map +1 -1
  20. package/dist/auth/account_actions.js +31 -1
  21. package/dist/auth/account_routes.d.ts +12 -2
  22. package/dist/auth/account_routes.d.ts.map +1 -1
  23. package/dist/auth/account_routes.js +55 -8
  24. package/dist/auth/account_schema.d.ts +3 -3
  25. package/dist/auth/admin_action_specs.d.ts +8 -8
  26. package/dist/auth/admin_actions.d.ts +11 -0
  27. package/dist/auth/admin_actions.d.ts.map +1 -1
  28. package/dist/auth/admin_actions.js +25 -0
  29. package/dist/auth/audit_emitter.d.ts +56 -12
  30. package/dist/auth/audit_emitter.d.ts.map +1 -1
  31. package/dist/auth/audit_emitter.js +38 -12
  32. package/dist/auth/audit_log_schema.d.ts +5 -3
  33. package/dist/auth/audit_log_schema.d.ts.map +1 -1
  34. package/dist/auth/audit_log_schema.js +5 -3
  35. package/dist/auth/bootstrap_routes.d.ts +1 -1
  36. package/dist/auth/invite_schema.d.ts +2 -2
  37. package/dist/auth/signup_routes.d.ts +1 -1
  38. package/dist/auth/standard_rpc_actions.d.ts +1 -0
  39. package/dist/auth/standard_rpc_actions.d.ts.map +1 -1
  40. package/dist/auth/standard_rpc_actions.js +1 -0
  41. package/dist/http/CLAUDE.md +26 -10
  42. package/dist/http/ip_canonical.d.ts +99 -0
  43. package/dist/http/ip_canonical.d.ts.map +1 -0
  44. package/dist/http/ip_canonical.js +191 -0
  45. package/dist/http/origin.d.ts +13 -5
  46. package/dist/http/origin.d.ts.map +1 -1
  47. package/dist/http/origin.js +13 -31
  48. package/dist/http/pending_effects.d.ts +1 -1
  49. package/dist/http/pending_effects.js +1 -1
  50. package/dist/http/proxy.d.ts +13 -5
  51. package/dist/http/proxy.d.ts.map +1 -1
  52. package/dist/http/proxy.js +15 -23
  53. package/dist/http/surface.d.ts +50 -0
  54. package/dist/http/surface.d.ts.map +1 -1
  55. package/dist/http/surface.js +27 -1
  56. package/dist/primitive_schemas.d.ts +20 -4
  57. package/dist/primitive_schemas.d.ts.map +1 -1
  58. package/dist/primitive_schemas.js +25 -4
  59. package/dist/realtime/sse_auth_guard.d.ts +16 -4
  60. package/dist/realtime/sse_auth_guard.d.ts.map +1 -1
  61. package/dist/realtime/sse_auth_guard.js +15 -3
  62. package/dist/server/app_backend.d.ts +66 -19
  63. package/dist/server/app_backend.d.ts.map +1 -1
  64. package/dist/server/app_backend.js +57 -34
  65. package/dist/server/app_server.d.ts +60 -0
  66. package/dist/server/app_server.d.ts.map +1 -1
  67. package/dist/server/app_server.js +95 -2
  68. package/dist/server/startup.d.ts.map +1 -1
  69. package/dist/server/startup.js +12 -0
  70. package/dist/testing/CLAUDE.md +64 -28
  71. package/dist/testing/admin_integration.d.ts.map +1 -1
  72. package/dist/testing/admin_integration.js +4 -5
  73. package/dist/testing/adversarial_headers.d.ts +6 -0
  74. package/dist/testing/adversarial_headers.d.ts.map +1 -1
  75. package/dist/testing/adversarial_headers.js +13 -5
  76. package/dist/testing/app_server.d.ts +33 -32
  77. package/dist/testing/app_server.d.ts.map +1 -1
  78. package/dist/testing/app_server.js +4 -13
  79. package/dist/testing/attack_surface.d.ts +8 -7
  80. package/dist/testing/attack_surface.d.ts.map +1 -1
  81. package/dist/testing/attack_surface.js +12 -8
  82. package/dist/testing/audit_completeness.d.ts.map +1 -1
  83. package/dist/testing/audit_completeness.js +3 -5
  84. package/dist/testing/audit_drift_guard.d.ts +116 -0
  85. package/dist/testing/audit_drift_guard.d.ts.map +1 -0
  86. package/dist/testing/audit_drift_guard.js +134 -0
  87. package/dist/testing/connection_closer_helpers.d.ts +44 -0
  88. package/dist/testing/connection_closer_helpers.d.ts.map +1 -0
  89. package/dist/testing/connection_closer_helpers.js +48 -0
  90. package/dist/testing/integration.d.ts.map +1 -1
  91. package/dist/testing/integration.js +7 -9
  92. package/dist/testing/rate_limiting.js +4 -4
  93. package/dist/testing/rpc_helpers.d.ts +2 -1
  94. package/dist/testing/rpc_helpers.d.ts.map +1 -1
  95. package/dist/testing/rpc_round_trip.d.ts.map +1 -1
  96. package/dist/testing/rpc_round_trip.js +6 -8
  97. package/dist/testing/sse_round_trip.d.ts.map +1 -1
  98. package/dist/testing/sse_round_trip.js +12 -6
  99. package/dist/testing/stubs.d.ts +11 -0
  100. package/dist/testing/stubs.d.ts.map +1 -1
  101. package/dist/testing/stubs.js +4 -0
  102. package/dist/testing/surface_invariants.d.ts +66 -1
  103. package/dist/testing/surface_invariants.d.ts.map +1 -1
  104. package/dist/testing/surface_invariants.js +103 -1
  105. package/dist/ui/SurfaceExplorer.svelte +161 -2
  106. package/dist/ui/SurfaceExplorer.svelte.d.ts.map +1 -1
  107. package/package.json +1 -1
@@ -58,7 +58,7 @@ export interface AuditLogSse {
58
58
  subscribe: (stream: SseStream<SseNotification>, options?: SubscribeOptions) => () => void;
59
59
  /** Logger — pass as part of `stream` option to `create_audit_log_route_specs`. */
60
60
  log: Logger;
61
- /** Combined broadcast + guard callback. Pass as `on_audit_event` on `CreateAppBackendOptions`. */
61
+ /** Combined broadcast + guard callback. Wired by `create_app_server`'s `audit_log_sse` option, or compose inside the consumer's `audit_factory` body. */
62
62
  on_audit_event: (event: AuditLogEvent) => void;
63
63
  /** The underlying registry — exposed for subscriber count monitoring. */
64
64
  registry: SubscriberRegistry<SseNotification>;
@@ -86,7 +86,15 @@ export declare const AUDIT_LOG_SSE_MAX_PER_SCOPE = 10;
86
86
  *
87
87
  * Combines `SubscriberRegistry`, `create_sse_auth_guard`, and the broadcast
88
88
  * call into a single object. The result satisfies `AuditLogRouteOptions['stream']`
89
- * and provides the `on_audit_event` callback for `CreateAppBackendOptions`.
89
+ * and provides the `on_audit_event` listener for the audit emitter's chain.
90
+ *
91
+ * Most consumers pass `audit_log_sse: true` to `create_app_server` and never
92
+ * touch this directly — the factory builds an `AuditLogSse`, appends
93
+ * `audit_sse.on_audit_event` to `backend.deps.audit.on_event_chain`, and
94
+ * exposes it via `AppServerContext.audit_sse`. Reach for the manual path
95
+ * (compose inside `audit_factory` body, or
96
+ * `audit.on_event_chain.push(audit_sse.on_audit_event)` post-assembly) only
97
+ * when wiring outside `create_app_server`.
90
98
  *
91
99
  * @param options - factory options
92
100
  * @returns audit log SSE setup (stream options + `on_audit_event` + registry)
@@ -95,8 +103,12 @@ export declare const AUDIT_LOG_SSE_MAX_PER_SCOPE = 10;
95
103
  * ```ts
96
104
  * const audit_sse = create_audit_log_sse({log});
97
105
  *
98
- * // In create_app_backend options:
99
- * on_audit_event: audit_sse.on_audit_event,
106
+ * // Inside the audit_factory body on CreateAppBackendOptions:
107
+ * audit_factory: ({db, log}) => create_audit_emitter({
108
+ * db,
109
+ * log,
110
+ * on_audit_event: audit_sse.on_audit_event,
111
+ * }),
100
112
  *
101
113
  * // In create_route_specs:
102
114
  * create_audit_log_route_specs({stream: audit_sse});
@@ -1 +1 @@
1
- {"version":3,"file":"sse_auth_guard.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/realtime/sse_auth_guard.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAEH,OAAO,KAAK,EAAC,MAAM,EAAC,MAAM,yBAAyB,CAAC;AAEpD,OAAO,EAGN,KAAK,aAAa,EAClB,MAAM,6BAA6B,CAAC;AACrC,OAAO,EAAC,kBAAkB,EAAE,KAAK,gBAAgB,EAAC,MAAM,0BAA0B,CAAC;AACnF,OAAO,KAAK,EAAC,SAAS,EAAE,eAAe,EAAE,SAAS,EAAC,MAAM,UAAU,CAAC;AAEpE,2DAA2D;AAC3D,eAAO,MAAM,iBAAiB,cAAc,CAAC;AAE7C;;;;;;;;;;GAUG;AACH,eAAO,MAAM,sBAAsB,EAAE,WAAW,CAAC,MAAM,CAKrD,CAAC;AAEH;;;;;;;;;;;;;;;;;GAiBG;AACH,eAAO,MAAM,qBAAqB,GAAI,CAAC,EACtC,UAAU,kBAAkB,CAAC,CAAC,CAAC,EAC/B,eAAe,MAAM,GAAG,IAAI,EAC5B,KAAK,MAAM,KACT,CAAC,CAAC,KAAK,EAAE,aAAa,KAAK,IAAI,CA6CjC,CAAC;AAEF;;;;;GAKG;AACH,MAAM,WAAW,WAAW;IAC3B,8FAA8F;IAC9F,SAAS,EAAE,CAAC,MAAM,EAAE,SAAS,CAAC,eAAe,CAAC,EAAE,OAAO,CAAC,EAAE,gBAAgB,KAAK,MAAM,IAAI,CAAC;IAC1F,kFAAkF;IAClF,GAAG,EAAE,MAAM,CAAC;IACZ,kGAAkG;IAClG,cAAc,EAAE,CAAC,KAAK,EAAE,aAAa,KAAK,IAAI,CAAC;IAC/C,yEAAyE;IACzE,QAAQ,EAAE,kBAAkB,CAAC,eAAe,CAAC,CAAC;CAC9C;AAED;;;;;GAKG;AACH,eAAO,MAAM,qBAAqB,EAAE,KAAK,CAAC,SAAS,CAOlD,CAAC;AAEF;;;;;;;;;GASG;AACH,eAAO,MAAM,2BAA2B,KAAK,CAAC;AAE9C;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AACH,eAAO,MAAM,oBAAoB,GAAI,SAAS;IAC7C,mEAAmE;IACnE,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,GAAG,EAAE,MAAM,CAAC;IACZ;;;;OAIG;IACH,aAAa,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;CAC9B,KAAG,WAgBH,CAAC"}
1
+ {"version":3,"file":"sse_auth_guard.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/realtime/sse_auth_guard.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAEH,OAAO,KAAK,EAAC,MAAM,EAAC,MAAM,yBAAyB,CAAC;AAEpD,OAAO,EAGN,KAAK,aAAa,EAClB,MAAM,6BAA6B,CAAC;AACrC,OAAO,EAAC,kBAAkB,EAAE,KAAK,gBAAgB,EAAC,MAAM,0BAA0B,CAAC;AACnF,OAAO,KAAK,EAAC,SAAS,EAAE,eAAe,EAAE,SAAS,EAAC,MAAM,UAAU,CAAC;AAEpE,2DAA2D;AAC3D,eAAO,MAAM,iBAAiB,cAAc,CAAC;AAE7C;;;;;;;;;;GAUG;AACH,eAAO,MAAM,sBAAsB,EAAE,WAAW,CAAC,MAAM,CAKrD,CAAC;AAEH;;;;;;;;;;;;;;;;;GAiBG;AACH,eAAO,MAAM,qBAAqB,GAAI,CAAC,EACtC,UAAU,kBAAkB,CAAC,CAAC,CAAC,EAC/B,eAAe,MAAM,GAAG,IAAI,EAC5B,KAAK,MAAM,KACT,CAAC,CAAC,KAAK,EAAE,aAAa,KAAK,IAAI,CA6CjC,CAAC;AAEF;;;;;GAKG;AACH,MAAM,WAAW,WAAW;IAC3B,8FAA8F;IAC9F,SAAS,EAAE,CAAC,MAAM,EAAE,SAAS,CAAC,eAAe,CAAC,EAAE,OAAO,CAAC,EAAE,gBAAgB,KAAK,MAAM,IAAI,CAAC;IAC1F,kFAAkF;IAClF,GAAG,EAAE,MAAM,CAAC;IACZ,yJAAyJ;IACzJ,cAAc,EAAE,CAAC,KAAK,EAAE,aAAa,KAAK,IAAI,CAAC;IAC/C,yEAAyE;IACzE,QAAQ,EAAE,kBAAkB,CAAC,eAAe,CAAC,CAAC;CAC9C;AAED;;;;;GAKG;AACH,eAAO,MAAM,qBAAqB,EAAE,KAAK,CAAC,SAAS,CAOlD,CAAC;AAEF;;;;;;;;;GASG;AACH,eAAO,MAAM,2BAA2B,KAAK,CAAC;AAE9C;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAmCG;AACH,eAAO,MAAM,oBAAoB,GAAI,SAAS;IAC7C,mEAAmE;IACnE,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,GAAG,EAAE,MAAM,CAAC;IACZ;;;;OAIG;IACH,aAAa,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;CAC9B,KAAG,WAgBH,CAAC"}
@@ -120,7 +120,15 @@ export const AUDIT_LOG_SSE_MAX_PER_SCOPE = 10;
120
120
  *
121
121
  * Combines `SubscriberRegistry`, `create_sse_auth_guard`, and the broadcast
122
122
  * call into a single object. The result satisfies `AuditLogRouteOptions['stream']`
123
- * and provides the `on_audit_event` callback for `CreateAppBackendOptions`.
123
+ * and provides the `on_audit_event` listener for the audit emitter's chain.
124
+ *
125
+ * Most consumers pass `audit_log_sse: true` to `create_app_server` and never
126
+ * touch this directly — the factory builds an `AuditLogSse`, appends
127
+ * `audit_sse.on_audit_event` to `backend.deps.audit.on_event_chain`, and
128
+ * exposes it via `AppServerContext.audit_sse`. Reach for the manual path
129
+ * (compose inside `audit_factory` body, or
130
+ * `audit.on_event_chain.push(audit_sse.on_audit_event)` post-assembly) only
131
+ * when wiring outside `create_app_server`.
124
132
  *
125
133
  * @param options - factory options
126
134
  * @returns audit log SSE setup (stream options + `on_audit_event` + registry)
@@ -129,8 +137,12 @@ export const AUDIT_LOG_SSE_MAX_PER_SCOPE = 10;
129
137
  * ```ts
130
138
  * const audit_sse = create_audit_log_sse({log});
131
139
  *
132
- * // In create_app_backend options:
133
- * on_audit_event: audit_sse.on_audit_event,
140
+ * // Inside the audit_factory body on CreateAppBackendOptions:
141
+ * audit_factory: ({db, log}) => create_audit_emitter({
142
+ * db,
143
+ * log,
144
+ * on_audit_event: audit_sse.on_audit_event,
145
+ * }),
134
146
  *
135
147
  * // In create_route_specs:
136
148
  * create_audit_log_route_specs({stream: audit_sse});
@@ -12,8 +12,8 @@
12
12
  */
13
13
  import { Logger } from '@fuzdev/fuz_util/log.js';
14
14
  import type { AppDeps } from '../auth/deps.js';
15
- import type { AuditLogConfig, AuditLogEvent } from '../auth/audit_log_schema.js';
16
- import type { DbType } from '../db/db.js';
15
+ import { type AuditEmitter } from '../auth/audit_emitter.js';
16
+ import type { DbType, Db } from '../db/db.js';
17
17
  import type { Keyring } from '../auth/keyring.js';
18
18
  import type { PasswordHashDeps } from '../auth/password.js';
19
19
  import type { StatResult } from '../runtime/deps.js';
@@ -33,6 +33,49 @@ export interface AppBackend {
33
33
  /** Close the database connection. Bound to the actual driver. */
34
34
  close: () => Promise<void>;
35
35
  }
36
+ /**
37
+ * Callback that builds the bound `AuditEmitter` after the backend's pool
38
+ * `Db` and `Logger` exist. Required on `CreateAppBackendOptions` so the
39
+ * consumer owns subscriber-chain composition and `AuditLogConfig`
40
+ * selection without the factory holding a default.
41
+ *
42
+ * The factory is invoked exactly once during `create_app_backend`, after
43
+ * `create_db` resolves and migrations run. The emitter it returns lands
44
+ * on `AppDeps.audit` and is captured by every query/handler that reaches
45
+ * `deps.audit.emit(...)`.
46
+ *
47
+ * The canonical body is a one-liner over `create_audit_emitter`:
48
+ *
49
+ * ```ts
50
+ * audit_factory: ({db, log}) => create_audit_emitter({
51
+ * db,
52
+ * log,
53
+ * on_audit_event,
54
+ * audit_log_config,
55
+ * })
56
+ * ```
57
+ *
58
+ * Returning an emitter built against a different `db` than the one passed
59
+ * in would route audit writes to a different pool than handlers query —
60
+ * the callback shape exists specifically to make that mistake structurally
61
+ * impossible.
62
+ */
63
+ export type AuditFactory = (params: {
64
+ db: Db;
65
+ log: Logger;
66
+ }) => AuditEmitter;
67
+ /**
68
+ * Trivial `AuditFactory` for consumers that don't compose `on_audit_event`
69
+ * or `audit_log_config`. Equivalent to
70
+ * `({db, log}) => create_audit_emitter({db, log})` — exported so the
71
+ * default case stays a single-symbol reference rather than five tokens
72
+ * of boilerplate at every consumer.
73
+ *
74
+ * Use the inline form when you need to thread `on_audit_event` /
75
+ * `audit_log_config` / `emit_decorator`; the factory composes those
76
+ * three fields itself so there's nothing this constant can pass through.
77
+ */
78
+ export declare const default_audit_factory: AuditFactory;
36
79
  /**
37
80
  * Input for `create_app_backend()`.
38
81
  *
@@ -55,21 +98,25 @@ export interface CreateAppBackendOptions {
55
98
  /** Structured logger instance. Omit for default (`new Logger('server')`). */
56
99
  log?: Logger;
57
100
  /**
58
- * Initial subscriber appended to `AppDeps.audit.on_event_chain`.
59
- * Use to broadcast audit events via SSE / WS. Additional subscribers
60
- * (e.g. the factory-managed audit-log SSE) are appended at server
61
- * assembly via `audit.on_event_chain.push(listener)` no shallow-copy
62
- * of `AppDeps` required.
63
- */
64
- on_audit_event?: (event: AuditLogEvent) => void;
65
- /**
66
- * Audit-log config for consumer event-type extensions. Built once at
67
- * startup via `create_audit_log_config({extra_events})` and captured
68
- * inside `AppDeps.audit` so consumer handlers cannot silently fall
69
- * back to the builtin config. Omit to use `builtin_audit_log_config`
70
- * (no extra events).
101
+ * Build the bound `AuditEmitter` once the backend's pool `Db` + `Logger`
102
+ * exist. Required the factory owns subscriber-chain composition and
103
+ * `AuditLogConfig` selection without `create_app_backend` holding a
104
+ * default. Typical body:
105
+ *
106
+ * ```ts
107
+ * audit_factory: ({db, log}) => create_audit_emitter({
108
+ * db,
109
+ * log,
110
+ * on_audit_event,
111
+ * audit_log_config,
112
+ * })
113
+ * ```
114
+ *
115
+ * Additional listeners (factory-managed audit SSE, per-endpoint WS
116
+ * auth guards) are appended at `create_app_server` time via
117
+ * `audit.on_event_chain.push(...)`.
71
118
  */
72
- audit_log_config?: AuditLogConfig;
119
+ audit_factory: AuditFactory;
73
120
  /**
74
121
  * Additional migration namespaces to run after the builtin auth namespace.
75
122
  * The shared `schema_version` table records one row per applied migration
@@ -87,10 +134,10 @@ export interface CreateAppBackendOptions {
87
134
  * Initialize the backend: database + auth migrations + deps.
88
135
  *
89
136
  * Calls `create_db` → `run_migrations` (auth namespace, then any
90
- * `migration_namespaces` from options in order) and bundles the result
91
- * with the provided keyring and password deps.
137
+ * `migration_namespaces` from options in order) `audit_factory({db, log})`
138
+ * and bundles the result with the provided keyring and password deps.
92
139
  *
93
- * @param options - keyring, password deps, optional database URL, and optional `migration_namespaces`
140
+ * @param options - keyring, password deps, `audit_factory`, optional database URL, and optional `migration_namespaces`
94
141
  * @returns app backend with deps, database metadata, and combined migration results
95
142
  * @throws Error if `migration_namespaces` contains a namespace in `reserved_migration_namespaces`
96
143
  */
@@ -1 +1 @@
1
- {"version":3,"file":"app_backend.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/server/app_backend.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAEH,OAAO,EAAC,MAAM,EAAC,MAAM,yBAAyB,CAAC;AAE/C,OAAO,KAAK,EAAC,OAAO,EAAC,MAAM,iBAAiB,CAAC;AAC7C,OAAO,KAAK,EAAC,cAAc,EAAE,aAAa,EAAC,MAAM,6BAA6B,CAAC;AAE/E,OAAO,KAAK,EAAC,MAAM,EAAC,MAAM,aAAa,CAAC;AACxC,OAAO,KAAK,EAAC,OAAO,EAAC,MAAM,oBAAoB,CAAC;AAChD,OAAO,KAAK,EAAC,gBAAgB,EAAC,MAAM,qBAAqB,CAAC;AAC1D,OAAO,KAAK,EAAC,UAAU,EAAC,MAAM,oBAAoB,CAAC;AACnD,OAAO,EAAiB,KAAK,kBAAkB,EAAE,KAAK,eAAe,EAAC,MAAM,kBAAkB,CAAC;AAI/F;;;;;GAKG;AACH,MAAM,WAAW,UAAU;IAC1B,IAAI,EAAE,OAAO,CAAC;IACd,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,EAAE,MAAM,CAAC;IAChB,oIAAoI;IACpI,QAAQ,CAAC,iBAAiB,EAAE,aAAa,CAAC,eAAe,CAAC,CAAC;IAC3D,iEAAiE;IACjE,KAAK,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;CAC3B;AAED;;;;;GAKG;AACH,MAAM,WAAW,uBAAuB;IACvC,+DAA+D;IAC/D,IAAI,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,OAAO,CAAC,UAAU,GAAG,IAAI,CAAC,CAAC;IACnD,2BAA2B;IAC3B,cAAc,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,OAAO,CAAC,MAAM,CAAC,CAAC;IAClD,qBAAqB;IACrB,WAAW,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IAC7C,0EAA0E;IAC1E,YAAY,EAAE,MAAM,CAAC;IACrB,wCAAwC;IACxC,OAAO,EAAE,OAAO,CAAC;IACjB,iFAAiF;IACjF,QAAQ,EAAE,gBAAgB,CAAC;IAC3B,6EAA6E;IAC7E,GAAG,CAAC,EAAE,MAAM,CAAC;IACb;;;;;;OAMG;IACH,cAAc,CAAC,EAAE,CAAC,KAAK,EAAE,aAAa,KAAK,IAAI,CAAC;IAChD;;;;;;OAMG;IACH,gBAAgB,CAAC,EAAE,cAAc,CAAC;IAClC;;;;;;;;;;OAUG;IACH,oBAAoB,CAAC,EAAE,aAAa,CAAC,kBAAkB,CAAC,CAAC;CACzD;AAED;;;;;;;;;;GAUG;AACH,eAAO,MAAM,kBAAkB,GAAU,SAAS,uBAAuB,KAAG,OAAO,CAAC,UAAU,CAuC7F,CAAC"}
1
+ {"version":3,"file":"app_backend.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/server/app_backend.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAEH,OAAO,EAAC,MAAM,EAAC,MAAM,yBAAyB,CAAC;AAE/C,OAAO,KAAK,EAAC,OAAO,EAAC,MAAM,iBAAiB,CAAC;AAC7C,OAAO,EAAuB,KAAK,YAAY,EAAC,MAAM,0BAA0B,CAAC;AACjF,OAAO,KAAK,EAAC,MAAM,EAAE,EAAE,EAAC,MAAM,aAAa,CAAC;AAC5C,OAAO,KAAK,EAAC,OAAO,EAAC,MAAM,oBAAoB,CAAC;AAChD,OAAO,KAAK,EAAC,gBAAgB,EAAC,MAAM,qBAAqB,CAAC;AAC1D,OAAO,KAAK,EAAC,UAAU,EAAC,MAAM,oBAAoB,CAAC;AACnD,OAAO,EAAiB,KAAK,kBAAkB,EAAE,KAAK,eAAe,EAAC,MAAM,kBAAkB,CAAC;AAI/F;;;;;GAKG;AACH,MAAM,WAAW,UAAU;IAC1B,IAAI,EAAE,OAAO,CAAC;IACd,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,EAAE,MAAM,CAAC;IAChB,oIAAoI;IACpI,QAAQ,CAAC,iBAAiB,EAAE,aAAa,CAAC,eAAe,CAAC,CAAC;IAC3D,iEAAiE;IACjE,KAAK,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;CAC3B;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AACH,MAAM,MAAM,YAAY,GAAG,CAAC,MAAM,EAAE;IAAC,EAAE,EAAE,EAAE,CAAC;IAAC,GAAG,EAAE,MAAM,CAAA;CAAC,KAAK,YAAY,CAAC;AAE3E;;;;;;;;;;GAUG;AACH,eAAO,MAAM,qBAAqB,EAAE,YAA6D,CAAC;AAElG;;;;;GAKG;AACH,MAAM,WAAW,uBAAuB;IACvC,+DAA+D;IAC/D,IAAI,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,OAAO,CAAC,UAAU,GAAG,IAAI,CAAC,CAAC;IACnD,2BAA2B;IAC3B,cAAc,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,OAAO,CAAC,MAAM,CAAC,CAAC;IAClD,qBAAqB;IACrB,WAAW,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IAC7C,0EAA0E;IAC1E,YAAY,EAAE,MAAM,CAAC;IACrB,wCAAwC;IACxC,OAAO,EAAE,OAAO,CAAC;IACjB,iFAAiF;IACjF,QAAQ,EAAE,gBAAgB,CAAC;IAC3B,6EAA6E;IAC7E,GAAG,CAAC,EAAE,MAAM,CAAC;IACb;;;;;;;;;;;;;;;;;;OAkBG;IACH,aAAa,EAAE,YAAY,CAAC;IAC5B;;;;;;;;;;OAUG;IACH,oBAAoB,CAAC,EAAE,aAAa,CAAC,kBAAkB,CAAC,CAAC;CACzD;AAED;;;;;;;;;;GAUG;AACH,eAAO,MAAM,kBAAkB,GAAU,SAAS,uBAAuB,KAAG,OAAO,CAAC,UAAU,CAiD7F,CAAC"}
@@ -15,52 +15,75 @@ import { create_audit_emitter } from '../auth/audit_emitter.js';
15
15
  import { run_migrations } from '../db/migrate.js';
16
16
  import { auth_migration_ns, reserved_migration_namespaces } from '../auth/migrations.js';
17
17
  import { create_db } from '../db/create_db.js';
18
+ /**
19
+ * Trivial `AuditFactory` for consumers that don't compose `on_audit_event`
20
+ * or `audit_log_config`. Equivalent to
21
+ * `({db, log}) => create_audit_emitter({db, log})` — exported so the
22
+ * default case stays a single-symbol reference rather than five tokens
23
+ * of boilerplate at every consumer.
24
+ *
25
+ * Use the inline form when you need to thread `on_audit_event` /
26
+ * `audit_log_config` / `emit_decorator`; the factory composes those
27
+ * three fields itself so there's nothing this constant can pass through.
28
+ */
29
+ export const default_audit_factory = ({ db, log }) => create_audit_emitter({ db, log });
18
30
  /**
19
31
  * Initialize the backend: database + auth migrations + deps.
20
32
  *
21
33
  * Calls `create_db` → `run_migrations` (auth namespace, then any
22
- * `migration_namespaces` from options in order) and bundles the result
23
- * with the provided keyring and password deps.
34
+ * `migration_namespaces` from options in order) `audit_factory({db, log})`
35
+ * and bundles the result with the provided keyring and password deps.
24
36
  *
25
- * @param options - keyring, password deps, optional database URL, and optional `migration_namespaces`
37
+ * @param options - keyring, password deps, `audit_factory`, optional database URL, and optional `migration_namespaces`
26
38
  * @returns app backend with deps, database metadata, and combined migration results
27
39
  * @throws Error if `migration_namespaces` contains a namespace in `reserved_migration_namespaces`
28
40
  */
29
41
  export const create_app_backend = async (options) => {
30
- const { database_url, keyring, password, stat, read_text_file, delete_file } = options;
42
+ const { database_url, keyring, password, stat, read_text_file, delete_file, audit_factory } = options;
31
43
  const log = options.log ?? new Logger('server');
32
44
  const { db, close, db_type, db_name } = await create_db(database_url);
33
- if (options.migration_namespaces?.length) {
34
- for (const ns of options.migration_namespaces) {
35
- if (reserved_migration_namespaces.includes(ns.namespace)) {
36
- throw new Error(`Migration namespace "${ns.namespace}" is reserved by fuz_app choose a different namespace`);
45
+ // Everything after `create_db` can throw — reserved-namespace check,
46
+ // `run_migrations` (seven MigrationError kinds), `audit_factory`.
47
+ // Without this guard the pool leaks because `close` is only returned
48
+ // on the success path. Cleanup errors are logged and swallowed so the
49
+ // caller sees the original failure, not a teardown-shaped one.
50
+ try {
51
+ if (options.migration_namespaces?.length) {
52
+ for (const ns of options.migration_namespaces) {
53
+ if (reserved_migration_namespaces.includes(ns.namespace)) {
54
+ throw new Error(`Migration namespace "${ns.namespace}" is reserved by fuz_app — choose a different namespace`);
55
+ }
37
56
  }
38
57
  }
58
+ const migration_results = await run_migrations(db, [
59
+ auth_migration_ns,
60
+ ...(options.migration_namespaces ?? []),
61
+ ]);
62
+ const audit = audit_factory({ db, log });
63
+ return {
64
+ db_type,
65
+ db_name,
66
+ migration_results,
67
+ close,
68
+ deps: {
69
+ keyring,
70
+ password,
71
+ db,
72
+ stat,
73
+ read_text_file,
74
+ delete_file,
75
+ log,
76
+ audit,
77
+ },
78
+ };
79
+ }
80
+ catch (err) {
81
+ try {
82
+ await close();
83
+ }
84
+ catch (close_err) {
85
+ log.error('create_app_backend: failed to close db after init error:', close_err);
86
+ }
87
+ throw err;
39
88
  }
40
- const migration_results = await run_migrations(db, [
41
- auth_migration_ns,
42
- ...(options.migration_namespaces ?? []),
43
- ]);
44
- const audit = create_audit_emitter({
45
- db,
46
- log,
47
- on_audit_event: options.on_audit_event,
48
- audit_log_config: options.audit_log_config,
49
- });
50
- return {
51
- db_type,
52
- db_name,
53
- migration_results,
54
- close,
55
- deps: {
56
- keyring,
57
- password,
58
- db,
59
- stat,
60
- read_text_file,
61
- delete_file,
62
- log,
63
- audit,
64
- },
65
- };
66
89
  };
@@ -8,6 +8,7 @@
8
8
  * @module
9
9
  */
10
10
  import { Hono, type Context } from 'hono';
11
+ import type { UpgradeWebSocket } from 'hono/ws';
11
12
  import { z } from 'zod';
12
13
  import { type SessionOptions } from '../auth/session_cookie.js';
13
14
  import type { BootstrapAccountSuccess } from '../auth/bootstrap_account.js';
@@ -25,6 +26,8 @@ import { type AppSurfaceSpec, type RpcEndpointSpec } from '../http/surface.js';
25
26
  import { type RouteSpec } from '../http/route_spec.js';
26
27
  import type { MiddlewareSpec } from '../http/middleware_spec.js';
27
28
  import { type BootstrapStatus } from '../auth/bootstrap_routes.js';
29
+ import type { WsEndpointSpec } from '../actions/ws_endpoint_spec.js';
30
+ import { BackendWebsocketTransport } from '../actions/transports_ws_backend.js';
28
31
  /**
29
32
  * Context passed to `on_effect_error` when a pending effect rejects.
30
33
  */
@@ -163,6 +166,50 @@ export interface AppServerOptions {
163
166
  * `create_standard_rpc_actions(ctx.deps, {app_settings: ctx.app_settings})`.
164
167
  */
165
168
  rpc_endpoints?: Array<RpcEndpointSpec> | ((context: AppServerContext) => Array<RpcEndpointSpec>);
169
+ /**
170
+ * Hono adapter's `upgradeWebSocket` helper. Required whenever
171
+ * `ws_endpoints` resolves to a non-empty array — `create_app_server`
172
+ * throws at assembly otherwise. Omit (along with `ws_endpoints`)
173
+ * when the consumer doesn't mount any WS endpoints. The same
174
+ * adapter helper services every `WsEndpointSpec` mounted from
175
+ * `ws_endpoints` — one adapter per app.
176
+ *
177
+ * For Node, `import {upgradeWebSocket} from '@hono/node-ws'`. For
178
+ * Deno, `import {upgradeWebSocket} from 'hono/deno'`. Test harnesses
179
+ * use `create_stub_upgrade` from `$lib/testing/ws_round_trip.ts`.
180
+ */
181
+ upgradeWebSocket?: UpgradeWebSocket;
182
+ /**
183
+ * WebSocket endpoint specs — single source of truth for both surface
184
+ * generation *and* live dispatch. Each entry is auto-mounted via
185
+ * `register_ws_endpoint` against the assembled Hono app, so
186
+ * consumers no longer call `register_ws_endpoint` themselves.
187
+ *
188
+ * Accepts either an array (evaluated eagerly) or a factory
189
+ * `(ctx: AppServerContext) => ReadonlyArray<WsEndpointSpec>`
190
+ * (evaluated after the server context is assembled). Use the factory
191
+ * form when action lists depend on `ctx.deps` /
192
+ * `ctx.action_*_rate_limiter` — e.g. when spreading
193
+ * `create_standard_rpc_actions(ctx.deps, ...)` over WS.
194
+ *
195
+ * When non-empty, `upgradeWebSocket` must be supplied (throws
196
+ * otherwise). A factory returning `[]` does NOT trip the check —
197
+ * feature-flag gated WS surfaces stay safe.
198
+ *
199
+ * Duplicate `path` values across two `WsEndpointSpec`s throw at
200
+ * mount time (Hono would silently shadow them otherwise).
201
+ *
202
+ * Each spec's `auth_guard?` defaults to `true` — the factory
203
+ * composes `create_ws_auth_guard` + `create_ws_logout_closer`
204
+ * against the mounted transport and appends them to
205
+ * `deps.audit.on_event_chain`. Wiring is deduped by transport
206
+ * **reference identity** so two specs sharing one
207
+ * `BackendWebsocketTransport` instance get a single pair of
208
+ * listeners; wrapped / proxied transports dedupe as separate
209
+ * entries (set `auth_guard: false` on duplicates and compose
210
+ * against the underlying transport once).
211
+ */
212
+ ws_endpoints?: ReadonlyArray<WsEndpointSpec> | ((context: AppServerContext) => ReadonlyArray<WsEndpointSpec>);
166
213
  /**
167
214
  * Env schema for surface generation. Defaults to `BaseServerEnv` —
168
215
  * pass an extended schema (typically `BaseServerEnv.extend({...})`)
@@ -241,6 +288,19 @@ export interface AppServer {
241
288
  * Use `require_audit_sse(server)` to assert the invariant.
242
289
  */
243
290
  audit_sse: AuditLogSse | null;
291
+ /**
292
+ * Path-keyed map of mounted WS endpoints. Each value is the
293
+ * `BackendWebsocketTransport` `create_app_server` registered
294
+ * connections against — supplied via `WsEndpointSpec.transport` or
295
+ * auto-created when omitted. Retain for broadcast / fan-out:
296
+ *
297
+ * ```ts
298
+ * app_server.ws_endpoints['/api/ws'].send_to_account(account_id, msg);
299
+ * ```
300
+ *
301
+ * Empty when no `ws_endpoints` were mounted.
302
+ */
303
+ ws_endpoints: Readonly<Record<string, BackendWebsocketTransport>>;
244
304
  /** Close the database connection. Propagated from `AppBackend`. */
245
305
  close: () => Promise<void>;
246
306
  }
@@ -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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"}
@@ -32,6 +32,9 @@ import { fuz_auth_guard_resolver } from '../auth/auth_guard_resolver.js';
32
32
  import { create_fuz_authorization_handler } from '../auth/request_context.js';
33
33
  import { ERROR_PAYLOAD_TOO_LARGE } from '../http/error_schemas.js';
34
34
  import { create_rpc_endpoint } from '../actions/action_rpc.js';
35
+ import { register_ws_endpoint } from '../actions/register_ws_endpoint.js';
36
+ import { create_ws_auth_guard, create_ws_logout_closer, } from '../actions/transports_ws_auth_guard.js';
37
+ import { BackendWebsocketTransport } from '../actions/transports_ws_backend.js';
35
38
  /**
36
39
  * Assert that `audit_sse` was wired by `create_app_server` and return it
37
40
  * as a non-null `AuditLogSse`. Throws a labelled error when the
@@ -127,7 +130,15 @@ export const create_app_server = async (options) => {
127
130
  const app_settings = await query_app_settings_load({ db: deps.db });
128
131
  // Surface route ref — factory manages the circular ref
129
132
  const surface_ref = {
130
- surface: { middleware: [], routes: [], rpc_endpoints: [], env: [], events: [], diagnostics: [] },
133
+ surface: {
134
+ middleware: [],
135
+ routes: [],
136
+ rpc_endpoints: [],
137
+ ws_endpoints: [],
138
+ env: [],
139
+ events: [],
140
+ diagnostics: [],
141
+ },
131
142
  };
132
143
  // Route specs (consumer routes + factory-managed routes)
133
144
  const context = {
@@ -173,6 +184,15 @@ export const create_app_server = async (options) => {
173
184
  }));
174
185
  }
175
186
  }
187
+ // WS endpoint resolution — done here (alongside RPC) so the captured
188
+ // array threads into surface generation below. Actual mount happens
189
+ // after `apply_route_specs` because `register_ws_endpoint` mutates the
190
+ // live Hono `app` (origin / auth / role / authorization middleware +
191
+ // the `app.get(path, ...)` upgrade route), and `app` does not exist
192
+ // until the assembly phase below.
193
+ const resolved_ws_endpoints = typeof options.ws_endpoints === 'function'
194
+ ? options.ws_endpoints(context)
195
+ : options.ws_endpoints;
176
196
  // Surface route (default: enabled)
177
197
  if (options.surface_route !== false) {
178
198
  factory_routes.push(create_surface_route_spec(surface_ref));
@@ -192,6 +212,7 @@ export const create_app_server = async (options) => {
192
212
  env_schema: options.env_schema ?? BaseServerEnv,
193
213
  event_specs: all_event_specs,
194
214
  rpc_endpoints: resolved_rpc_endpoints,
215
+ ws_endpoints: resolved_ws_endpoints,
195
216
  });
196
217
  // Config-level diagnostics (concatenated after spec-level from generate_app_surface)
197
218
  const config_diagnostics = [];
@@ -215,7 +236,7 @@ export const create_app_server = async (options) => {
215
236
  config_diagnostics.push({
216
237
  level: 'warning',
217
238
  category: 'security',
218
- message: 'Session cookie httpOnly=false — cookie accessible to JavaScript',
239
+ message: 'Session cookie httpOnly=false — cookie accessible to JS',
219
240
  });
220
241
  }
221
242
  }
@@ -291,6 +312,77 @@ export const create_app_server = async (options) => {
291
312
  apply_middleware_specs(app, middleware_specs);
292
313
  const authorize = create_fuz_authorization_handler({ db: deps.db });
293
314
  apply_route_specs(app, route_specs, fuz_auth_guard_resolver, log, deps.db, authorize);
315
+ // WS endpoint auto-mount — must run after `app` exists and
316
+ // `apply_route_specs` has registered the request routes. Each spec
317
+ // becomes a `register_ws_endpoint` call, plus optional `auth_guard`
318
+ // wiring onto the audit chain. `post_route_middleware` and static
319
+ // serving register after this loop, so WS upgrade routes sit
320
+ // adjacent to the consumer routes and ahead of the static fallback —
321
+ // matches the "WS mount is route registration" mental model.
322
+ const mounted_ws_endpoints = {};
323
+ if (resolved_ws_endpoints?.length) {
324
+ if (options.upgradeWebSocket === undefined) {
325
+ throw new Error('create_app_server: ws_endpoints resolved non-empty but upgradeWebSocket is missing. ' +
326
+ "Pass the Hono adapter's upgradeWebSocket helper as a top-level option.");
327
+ }
328
+ // Cross-surface collision: `register_ws_endpoint` mounts a `GET path`
329
+ // upgrade route. If a `RouteSpec` already registered `GET path`,
330
+ // Hono's last-wins semantics would silently shadow the consumer's
331
+ // GET route — fail fast instead.
332
+ const route_spec_get_paths = new Set();
333
+ for (const r of route_specs) {
334
+ if (r.method === 'GET')
335
+ route_spec_get_paths.add(r.path);
336
+ }
337
+ const seen_paths = new Set();
338
+ // Dedupe `auth_guard` wiring by transport reference — two specs
339
+ // sharing one transport instance get a single pair of listeners,
340
+ // otherwise revocation events would fire `close_sockets_for_*`
341
+ // twice per event (idempotent on the transport but log-spammy).
342
+ // Cross-spec OR-semantics: any spec with `auth_guard !== false`
343
+ // wires the guard for that transport; once wired, sibling specs
344
+ // (even with explicit `auth_guard: false`) cannot opt out. To
345
+ // disable, every spec sharing the transport must pass `auth_guard: false`.
346
+ const guarded_transports = new WeakSet();
347
+ for (const endpoint of resolved_ws_endpoints) {
348
+ if (seen_paths.has(endpoint.path)) {
349
+ throw new Error(`create_app_server: duplicate ws_endpoints path: ${endpoint.path}`);
350
+ }
351
+ if (route_spec_get_paths.has(endpoint.path)) {
352
+ throw new Error(`create_app_server: ws_endpoints path collides with a GET RouteSpec: ${endpoint.path}`);
353
+ }
354
+ seen_paths.add(endpoint.path);
355
+ const endpoint_transport = endpoint.transport ?? new BackendWebsocketTransport();
356
+ register_ws_endpoint({
357
+ app,
358
+ path: endpoint.path,
359
+ upgradeWebSocket: options.upgradeWebSocket,
360
+ allowed_origins: endpoint.allowed_origins,
361
+ db: deps.db,
362
+ actions: endpoint.actions,
363
+ transport: endpoint_transport,
364
+ heartbeat: endpoint.heartbeat,
365
+ artificial_delay: endpoint.artificial_delay,
366
+ on_socket_open: endpoint.on_socket_open,
367
+ on_socket_close: endpoint.on_socket_close,
368
+ log,
369
+ required_roles: endpoint.required_roles,
370
+ action_ip_rate_limiter,
371
+ action_account_rate_limiter,
372
+ });
373
+ mounted_ws_endpoints[endpoint.path] = endpoint_transport;
374
+ if (endpoint.auth_guard !== false && !guarded_transports.has(endpoint_transport)) {
375
+ guarded_transports.add(endpoint_transport);
376
+ deps.audit.on_event_chain.push(create_ws_auth_guard(endpoint_transport, log));
377
+ deps.audit.on_event_chain.push(create_ws_logout_closer(endpoint_transport, log));
378
+ }
379
+ if (endpoint.extra_audit_handlers?.length) {
380
+ for (const handler of endpoint.extra_audit_handlers) {
381
+ deps.audit.on_event_chain.push(handler);
382
+ }
383
+ }
384
+ }
385
+ }
294
386
  // Post-route middleware (before static serving)
295
387
  if (options.post_route_middleware) {
296
388
  apply_middleware_specs(app, options.post_route_middleware);
@@ -309,6 +401,7 @@ export const create_app_server = async (options) => {
309
401
  app_settings,
310
402
  migration_results: backend.migration_results,
311
403
  audit_sse,
404
+ ws_endpoints: mounted_ws_endpoints,
312
405
  close: backend.close,
313
406
  };
314
407
  };
@@ -1 +1 @@
1
- {"version":3,"file":"startup.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/server/startup.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,KAAK,EAAC,MAAM,EAAC,MAAM,yBAAyB,CAAC;AAGpD,OAAO,KAAK,EAAC,UAAU,EAAC,MAAM,oBAAoB,CAAC;AAEnD;;;;;;;;GAQG;AACH,eAAO,MAAM,mBAAmB,GAC/B,SAAS,UAAU,EACnB,KAAK,MAAM,EACX,aAAa,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,KAClC,IAqCF,CAAC"}
1
+ {"version":3,"file":"startup.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/server/startup.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,KAAK,EAAC,MAAM,EAAC,MAAM,yBAAyB,CAAC;AAGpD,OAAO,KAAK,EAAC,UAAU,EAAC,MAAM,oBAAoB,CAAC;AAEnD;;;;;;;;GAQG;AACH,eAAO,MAAM,mBAAmB,GAC/B,SAAS,UAAU,EACnB,KAAK,MAAM,EACX,aAAa,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,KAClC,IAkDF,CAAC"}
@@ -17,6 +17,18 @@ import { format_env_display_value } from '../env/mask.js';
17
17
  */
18
18
  export const log_startup_summary = (surface, log, env_values) => {
19
19
  log.info(`Surface: ${surface.routes.length} routes, ${surface.middleware.length} middleware layers`);
20
+ // Endpoint surfaces — logged when non-empty so operators can confirm
21
+ // auto-mount picked up the expected actions (and so a factory that
22
+ // silently returns `[]` is loud at boot instead of a method_not_found
23
+ // at first call).
24
+ if (surface.rpc_endpoints.length) {
25
+ const rpc_method_count = surface.rpc_endpoints.reduce((sum, ep) => sum + ep.methods.length, 0);
26
+ log.info(`RPC: ${surface.rpc_endpoints.length} endpoint(s), ${rpc_method_count} method(s)`);
27
+ }
28
+ if (surface.ws_endpoints.length) {
29
+ const ws_method_count = surface.ws_endpoints.reduce((sum, ep) => sum + ep.methods.length, 0);
30
+ log.info(`WS: ${surface.ws_endpoints.length} endpoint(s), ${ws_method_count} method(s)`);
31
+ }
20
32
  if (surface.env.length) {
21
33
  const required = surface.env.filter((e) => !e.optional);
22
34
  const secret = surface.env.filter((e) => e.sensitivity === 'secret');