@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.
- package/dist/actions/CLAUDE.md +124 -11
- package/dist/actions/connection_closer.d.ts +68 -0
- package/dist/actions/connection_closer.d.ts.map +1 -0
- package/dist/actions/connection_closer.js +41 -0
- package/dist/actions/register_action_ws.d.ts.map +1 -1
- package/dist/actions/register_action_ws.js +23 -2
- package/dist/actions/register_ws_endpoint.d.ts +11 -9
- package/dist/actions/register_ws_endpoint.d.ts.map +1 -1
- package/dist/actions/register_ws_endpoint.js +5 -5
- package/dist/actions/transports_ws_auth_guard.d.ts +24 -8
- package/dist/actions/transports_ws_auth_guard.d.ts.map +1 -1
- package/dist/actions/transports_ws_auth_guard.js +23 -7
- package/dist/actions/ws_endpoint_spec.d.ts +119 -0
- package/dist/actions/ws_endpoint_spec.d.ts.map +1 -0
- package/dist/actions/ws_endpoint_spec.js +13 -0
- package/dist/auth/CLAUDE.md +79 -15
- package/dist/auth/account_action_specs.d.ts +1 -1
- package/dist/auth/account_actions.d.ts +13 -0
- package/dist/auth/account_actions.d.ts.map +1 -1
- package/dist/auth/account_actions.js +31 -1
- package/dist/auth/account_routes.d.ts +12 -2
- package/dist/auth/account_routes.d.ts.map +1 -1
- package/dist/auth/account_routes.js +55 -8
- package/dist/auth/account_schema.d.ts +3 -3
- package/dist/auth/admin_action_specs.d.ts +8 -8
- package/dist/auth/admin_actions.d.ts +11 -0
- package/dist/auth/admin_actions.d.ts.map +1 -1
- package/dist/auth/admin_actions.js +25 -0
- package/dist/auth/audit_emitter.d.ts +56 -12
- package/dist/auth/audit_emitter.d.ts.map +1 -1
- package/dist/auth/audit_emitter.js +38 -12
- package/dist/auth/audit_log_schema.d.ts +5 -3
- package/dist/auth/audit_log_schema.d.ts.map +1 -1
- package/dist/auth/audit_log_schema.js +5 -3
- package/dist/auth/bootstrap_routes.d.ts +1 -1
- package/dist/auth/invite_schema.d.ts +2 -2
- package/dist/auth/signup_routes.d.ts +1 -1
- package/dist/auth/standard_rpc_actions.d.ts +1 -0
- package/dist/auth/standard_rpc_actions.d.ts.map +1 -1
- package/dist/auth/standard_rpc_actions.js +1 -0
- package/dist/http/CLAUDE.md +26 -10
- package/dist/http/ip_canonical.d.ts +99 -0
- package/dist/http/ip_canonical.d.ts.map +1 -0
- package/dist/http/ip_canonical.js +191 -0
- package/dist/http/origin.d.ts +13 -5
- package/dist/http/origin.d.ts.map +1 -1
- package/dist/http/origin.js +13 -31
- package/dist/http/pending_effects.d.ts +1 -1
- package/dist/http/pending_effects.js +1 -1
- package/dist/http/proxy.d.ts +13 -5
- package/dist/http/proxy.d.ts.map +1 -1
- package/dist/http/proxy.js +15 -23
- package/dist/http/surface.d.ts +50 -0
- package/dist/http/surface.d.ts.map +1 -1
- package/dist/http/surface.js +27 -1
- package/dist/primitive_schemas.d.ts +20 -4
- package/dist/primitive_schemas.d.ts.map +1 -1
- package/dist/primitive_schemas.js +25 -4
- package/dist/realtime/sse_auth_guard.d.ts +16 -4
- package/dist/realtime/sse_auth_guard.d.ts.map +1 -1
- package/dist/realtime/sse_auth_guard.js +15 -3
- package/dist/server/app_backend.d.ts +66 -19
- package/dist/server/app_backend.d.ts.map +1 -1
- package/dist/server/app_backend.js +57 -34
- package/dist/server/app_server.d.ts +60 -0
- package/dist/server/app_server.d.ts.map +1 -1
- package/dist/server/app_server.js +95 -2
- package/dist/server/startup.d.ts.map +1 -1
- package/dist/server/startup.js +12 -0
- package/dist/testing/CLAUDE.md +64 -28
- package/dist/testing/admin_integration.d.ts.map +1 -1
- package/dist/testing/admin_integration.js +4 -5
- package/dist/testing/adversarial_headers.d.ts +6 -0
- package/dist/testing/adversarial_headers.d.ts.map +1 -1
- package/dist/testing/adversarial_headers.js +13 -5
- package/dist/testing/app_server.d.ts +33 -32
- package/dist/testing/app_server.d.ts.map +1 -1
- package/dist/testing/app_server.js +4 -13
- package/dist/testing/attack_surface.d.ts +8 -7
- package/dist/testing/attack_surface.d.ts.map +1 -1
- package/dist/testing/attack_surface.js +12 -8
- package/dist/testing/audit_completeness.d.ts.map +1 -1
- package/dist/testing/audit_completeness.js +3 -5
- package/dist/testing/audit_drift_guard.d.ts +116 -0
- package/dist/testing/audit_drift_guard.d.ts.map +1 -0
- package/dist/testing/audit_drift_guard.js +134 -0
- package/dist/testing/connection_closer_helpers.d.ts +44 -0
- package/dist/testing/connection_closer_helpers.d.ts.map +1 -0
- package/dist/testing/connection_closer_helpers.js +48 -0
- package/dist/testing/integration.d.ts.map +1 -1
- package/dist/testing/integration.js +7 -9
- package/dist/testing/rate_limiting.js +4 -4
- package/dist/testing/rpc_helpers.d.ts +2 -1
- package/dist/testing/rpc_helpers.d.ts.map +1 -1
- package/dist/testing/rpc_round_trip.d.ts.map +1 -1
- package/dist/testing/rpc_round_trip.js +6 -8
- package/dist/testing/sse_round_trip.d.ts.map +1 -1
- package/dist/testing/sse_round_trip.js +12 -6
- package/dist/testing/stubs.d.ts +11 -0
- package/dist/testing/stubs.d.ts.map +1 -1
- package/dist/testing/stubs.js +4 -0
- package/dist/testing/surface_invariants.d.ts +66 -1
- package/dist/testing/surface_invariants.d.ts.map +1 -1
- package/dist/testing/surface_invariants.js +103 -1
- package/dist/ui/SurfaceExplorer.svelte +161 -2
- package/dist/ui/SurfaceExplorer.svelte.d.ts.map +1 -1
- 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.
|
|
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`
|
|
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
|
-
* //
|
|
99
|
-
*
|
|
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,
|
|
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`
|
|
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
|
-
* //
|
|
133
|
-
*
|
|
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
|
|
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
|
-
*
|
|
59
|
-
*
|
|
60
|
-
*
|
|
61
|
-
*
|
|
62
|
-
*
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
*
|
|
67
|
-
*
|
|
68
|
-
*
|
|
69
|
-
*
|
|
70
|
-
*
|
|
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
|
-
|
|
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)
|
|
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,
|
|
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)
|
|
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
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
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: {
|
|
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
|
|
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,
|
|
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"}
|
package/dist/server/startup.js
CHANGED
|
@@ -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');
|