@fuzdev/fuz_app 0.127.0 → 0.129.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 +35 -18
- package/dist/actions/connection_closer.d.ts +16 -4
- package/dist/actions/connection_closer.d.ts.map +1 -1
- package/dist/actions/connection_closer.js +20 -4
- package/dist/actions/register_action_ws.d.ts +10 -3
- package/dist/actions/register_action_ws.d.ts.map +1 -1
- package/dist/actions/register_action_ws.js +6 -3
- package/dist/actions/register_ws_endpoint.d.ts +3 -1
- package/dist/actions/register_ws_endpoint.d.ts.map +1 -1
- package/dist/actions/register_ws_endpoint.js +3 -1
- package/dist/auth/CLAUDE.md +14 -10
- package/dist/auth/actor_lookup_action_specs.d.ts +2 -2
- package/dist/auth/actor_lookup_action_specs.js +2 -2
- package/dist/auth/audit_emitter.d.ts +73 -19
- package/dist/auth/audit_emitter.d.ts.map +1 -1
- package/dist/auth/audit_emitter.js +57 -21
- package/dist/auth/bearer_auth.d.ts +3 -2
- package/dist/auth/bearer_auth.d.ts.map +1 -1
- package/dist/auth/bearer_auth.js +3 -2
- package/dist/auth/middleware.d.ts +30 -0
- package/dist/auth/middleware.d.ts.map +1 -1
- package/dist/auth/middleware.js +59 -0
- package/dist/auth/request_context.d.ts +1 -1
- package/dist/auth/request_context.js +1 -1
- package/dist/auth/token_scope.d.ts +4 -2
- package/dist/auth/token_scope.d.ts.map +1 -1
- package/dist/auth/token_scope.js +10 -2
- package/dist/http/CLAUDE.md +3 -4
- package/dist/http/auth_shape.d.ts +3 -2
- package/dist/http/auth_shape.d.ts.map +1 -1
- package/dist/http/auth_shape.js +3 -2
- package/dist/http/common_routes.d.ts +1 -14
- package/dist/http/common_routes.d.ts.map +1 -1
- package/dist/http/common_routes.js +1 -19
- package/dist/http/ip_canonical.d.ts +1 -1
- package/dist/http/ip_canonical.js +1 -1
- package/dist/realtime/sse_auth_guard.d.ts +6 -2
- package/dist/realtime/sse_auth_guard.d.ts.map +1 -1
- package/dist/realtime/sse_auth_guard.js +3 -1
- package/dist/server/app_server.d.ts +157 -19
- package/dist/server/app_server.d.ts.map +1 -1
- package/dist/server/app_server.js +226 -128
- package/dist/server/surface_route.d.ts +32 -0
- package/dist/server/surface_route.d.ts.map +1 -0
- package/dist/server/surface_route.js +46 -0
- package/dist/testing/CLAUDE.md +50 -32
- package/dist/testing/app_server.d.ts +40 -5
- package/dist/testing/app_server.d.ts.map +1 -1
- package/dist/testing/app_server.js +84 -68
- package/dist/testing/attack_surface.d.ts +1 -1
- package/dist/testing/attack_surface.d.ts.map +1 -1
- package/dist/testing/audit_completeness.js +1 -1
- package/dist/testing/audit_drift_guard.d.ts +5 -3
- package/dist/testing/audit_drift_guard.d.ts.map +1 -1
- package/dist/testing/audit_drift_guard.js +14 -4
- package/dist/testing/cross_backend/account_lifecycle.d.ts.map +1 -1
- package/dist/testing/cross_backend/account_lifecycle.js +1 -2
- package/dist/testing/cross_backend/capabilities.d.ts +3 -1
- package/dist/testing/cross_backend/capabilities.d.ts.map +1 -1
- package/dist/testing/cross_backend/default_spine_surface.d.ts +2 -2
- package/dist/testing/cross_backend/default_spine_surface.d.ts.map +1 -1
- package/dist/testing/cross_backend/default_spine_surface.js +5 -2
- package/dist/testing/cross_backend/identity_parity.js +1 -1
- package/dist/testing/cross_backend/in_process_setup.d.ts.map +1 -1
- package/dist/testing/cross_backend/in_process_setup.js +4 -1
- package/dist/testing/cross_backend/method_coverage.d.ts +1 -1
- package/dist/testing/cross_backend/method_coverage.d.ts.map +1 -1
- package/dist/testing/cross_backend/method_coverage.js +2 -2
- package/dist/testing/cross_backend/peer_ping_ws.d.ts +1 -1
- package/dist/testing/cross_backend/peer_ping_ws.d.ts.map +1 -1
- package/dist/testing/cross_backend/role_grant_offer_notification_ws.d.ts +1 -1
- package/dist/testing/cross_backend/role_grant_offer_notification_ws.d.ts.map +1 -1
- package/dist/testing/cross_backend/setup.d.ts +4 -4
- package/dist/testing/cross_backend/setup.js +1 -1
- package/dist/testing/cross_backend/sse_round_trip.d.ts +1 -1
- package/dist/testing/cross_backend/sse_round_trip.d.ts.map +1 -1
- package/dist/testing/cross_backend/testing_reset_actions.d.ts +51 -32
- package/dist/testing/cross_backend/testing_reset_actions.d.ts.map +1 -1
- package/dist/testing/cross_backend/testing_reset_actions.js +70 -51
- package/dist/testing/cross_backend/testing_server_bun.d.ts +1 -1
- package/dist/testing/cross_backend/testing_server_bun.d.ts.map +1 -1
- package/dist/testing/cross_backend/testing_server_bun.js +80 -64
- package/dist/testing/cross_backend/testing_server_core.d.ts +10 -4
- package/dist/testing/cross_backend/testing_server_core.d.ts.map +1 -1
- package/dist/testing/cross_backend/testing_server_deno.d.ts +1 -1
- package/dist/testing/cross_backend/testing_server_deno.d.ts.map +1 -1
- package/dist/testing/cross_backend/testing_server_deno.js +1 -1
- package/dist/testing/cross_backend/testing_server_node.d.ts +1 -1
- package/dist/testing/cross_backend/testing_server_node.d.ts.map +1 -1
- package/dist/testing/cross_backend/testing_server_node.js +2 -2
- package/dist/testing/cross_backend/ws_round_trip.d.ts +3 -2
- package/dist/testing/cross_backend/ws_round_trip.d.ts.map +1 -1
- package/dist/testing/cross_backend/ws_round_trip.js +51 -13
- package/dist/testing/stubs.d.ts +65 -10
- package/dist/testing/stubs.d.ts.map +1 -1
- package/dist/testing/stubs.js +95 -41
- package/dist/testing/ws_round_trip.d.ts +1 -3
- package/dist/testing/ws_round_trip.d.ts.map +1 -1
- package/dist/ui/AdminSurface.svelte +3 -1
- package/dist/ui/AdminSurface.svelte.d.ts.map +1 -1
- package/dist/ui/CLAUDE.md +3 -1
- package/dist/ui/async_slot.svelte.d.ts +4 -4
- package/dist/ui/async_slot.svelte.js +4 -4
- package/dist/ui/keyed_async_slot.svelte.d.ts +4 -4
- package/dist/ui/keyed_async_slot.svelte.js +3 -3
- package/package.json +1 -1
package/dist/actions/CLAUDE.md
CHANGED
|
@@ -565,11 +565,17 @@ fail-open. Failure outcomes (`revoked: false`, a 404) queue no close — they
|
|
|
565
565
|
carry caller-supplied ids.
|
|
566
566
|
|
|
567
567
|
**One closer for every transport.** `create_realtime_closer()` returns a
|
|
568
|
-
`RealtimeCloser` — a `ConnectionCloser` plus `add(closer)` (
|
|
569
|
-
|
|
568
|
+
`RealtimeCloser` — a `ConnectionCloser` plus `add(closer)` and `member_count()`
|
|
569
|
+
— whose every close fans out to each member and returns the sum. Membership is
|
|
570
|
+
by reference: a closer added twice is one member, reached once. `add` returns a
|
|
571
|
+
remover for that addition (idempotent); additions are counted, so the closer
|
|
572
|
+
stays a member until every addition is removed — one owner releasing a shared
|
|
573
|
+
transport leaves it reachable for the others.
|
|
570
574
|
`create_app_backend` puts one on `AppDeps.connection_closer`; `create_app_server`
|
|
571
|
-
adds each mounted WS transport and the audit stream registry
|
|
572
|
-
`
|
|
575
|
+
adds each mounted WS transport and the audit stream registry itself (passing
|
|
576
|
+
`connection_closer: null` to the mount functions so it holds the removers), and
|
|
577
|
+
removes them when its assembly fails or `AppServer.close` runs — after
|
|
578
|
+
`close_all_sockets` across them all. Twin of
|
|
573
579
|
`RealtimeRevoker`. A member that throws does not stop the fan-out — every
|
|
574
580
|
member is called, then the first error is thrown for the caller to log;
|
|
575
581
|
`BackendWebsocketTransport` and `SubscriberRegistry.close_by_identity` treat a
|
|
@@ -586,7 +592,8 @@ the connections of each expired session it deletes — `auth/CLAUDE.md`
|
|
|
586
592
|
§Cleanup), and the
|
|
587
593
|
`connection_closer: RealtimeCloser | null` option of `register_action_ws` /
|
|
588
594
|
`register_ws_endpoint` / `create_audit_log_sse`, which add their transport to it
|
|
589
|
-
(`null` is the explicit opt-out
|
|
595
|
+
(`null` is the explicit opt-out, or for a caller that adds the transport itself
|
|
596
|
+
to hold the remover, as `create_app_server` does).
|
|
590
597
|
|
|
591
598
|
## WS auth guard (`actions/transports_ws_auth_guard.ts`)
|
|
592
599
|
|
|
@@ -676,10 +683,10 @@ enforced.
|
|
|
676
683
|
|
|
677
684
|
Mounted transport reachable at `app_server.ws_endpoints[path]`
|
|
678
685
|
(`Readonly<Record<string, BackendWebsocketTransport>>`). Duplicate paths
|
|
679
|
-
across `WsEndpointSpec`s throw at
|
|
680
|
-
(same `GET <path>` on both `RouteSpec` and
|
|
681
|
-
exact-string match. Pattern overlap (e.g.
|
|
682
|
-
`/api/ws`) is not detected — Hono's specific-before-wildcard routing keeps
|
|
686
|
+
across `WsEndpointSpec`s throw at assembly, before any endpoint mounts.
|
|
687
|
+
Cross-surface collisions (same `GET <path>` on both `RouteSpec` and
|
|
688
|
+
`WsEndpointSpec`) throw with exact-string match. Pattern overlap (e.g.
|
|
689
|
+
`GET /api/:resource` vs `/api/ws`) is not detected — Hono's specific-before-wildcard routing keeps
|
|
683
690
|
those working but avoid the overlap.
|
|
684
691
|
|
|
685
692
|
`auth_guard: true` does NOT close sockets on `role_grant_revoke`
|
|
@@ -746,8 +753,13 @@ revocation closes only after its commit — see
|
|
|
746
753
|
|
|
747
754
|
`connection_closer: RealtimeCloser | null` is a required option: the endpoint's
|
|
748
755
|
transport is added to it, so the revocation handlers' post-commit closes reach
|
|
749
|
-
the sockets opened here.
|
|
750
|
-
(a
|
|
756
|
+
the sockets opened here. The add is the mount's last step, so a mount that
|
|
757
|
+
throws (a registry that fails to compile) adds nothing; the addition is
|
|
758
|
+
permanent, its remover not returned. `null` is for a transport no revocation
|
|
759
|
+
has to reach (a harness driving the dispatcher directly), or one the caller
|
|
760
|
+
adds itself to hold the remover — `create_app_server` passes `null` and adds
|
|
761
|
+
the transport after the mount returns, releasing it when assembly fails or
|
|
762
|
+
the server closes.
|
|
751
763
|
|
|
752
764
|
Because `onOpen` awaits, an adapter can deliver `onMessage` before admission
|
|
753
765
|
(`@hono/node-ws` doesn't await `onOpen` at all). Those frames are **queued, not
|
|
@@ -800,13 +812,18 @@ Per-message side-effect queues: `pending_effects` (eager) drains via
|
|
|
800
812
|
`flush_pending_effects`; `post_commit_effects` (deferred — pushed by
|
|
801
813
|
handlers via `emit_after_commit`) drains via `flush_post_commit_effects`.
|
|
802
814
|
Both flush in the same `try/finally` that releases the request controller,
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
815
|
+
after the reply is sent, and every rejection is logged. Nothing orders that
|
|
816
|
+
flush against other frames: frames on an admitted socket are not serialized
|
|
817
|
+
(`handle_frame` calls aren't chained, and an adapter need not await
|
|
818
|
+
`onMessage` — `@hono/node-ws` doesn't), so a message's effects can still be
|
|
819
|
+
in flight when the caller reads its reply and when the next message
|
|
820
|
+
dispatches. A test that reads the audit log after a WS reply awaits
|
|
821
|
+
`_testing_drain_effects` first, over an emitter built with
|
|
822
|
+
`track_inflight: true` (`src/lib/testing/CLAUDE.md`). The deferred thunks are
|
|
823
|
+
invoked first and the eager writes awaited after, so a revocation's close never
|
|
824
|
+
waits on a slow audit INSERT — held behind one, the socket would stay open on a
|
|
825
|
+
credential already committed gone and dispatch its next frame. The deferred
|
|
826
|
+
queue is **discarded on rollback** before it reaches that flush (a
|
|
810
827
|
rolled-back message fires no post-commit effect). See `http/CLAUDE.md`
|
|
811
828
|
§Pending Effects.
|
|
812
829
|
|
|
@@ -139,20 +139,32 @@ export declare const noop_connection_closer: ConnectionCloser;
|
|
|
139
139
|
*/
|
|
140
140
|
export interface RealtimeCloser extends ConnectionCloser {
|
|
141
141
|
/**
|
|
142
|
-
* Add a transport's closer
|
|
143
|
-
*
|
|
142
|
+
* Add a transport's closer, returning a remover for this addition.
|
|
143
|
+
*
|
|
144
|
+
* Membership is by reference: a closer added twice — two endpoints
|
|
145
|
+
* sharing one transport — is one member, and every close reaches it
|
|
146
|
+
* once. Each `add` is counted, and its remover releases only that count:
|
|
147
|
+
* the closer stays a member until every addition is removed, so one
|
|
148
|
+
* owner releasing a shared transport leaves it reachable for the others.
|
|
149
|
+
* Calling a remover again is a no-op.
|
|
144
150
|
*
|
|
145
151
|
* Members must be the same instances connections are registered on — a
|
|
146
152
|
* closer over a fresh registry closes nothing.
|
|
153
|
+
*
|
|
154
|
+
* @param closer - the transport's closer
|
|
155
|
+
* @returns a remover for this addition, idempotent
|
|
147
156
|
*/
|
|
148
|
-
add: (closer: ConnectionCloser) => void;
|
|
157
|
+
add: (closer: ConnectionCloser) => () => void;
|
|
158
|
+
/** Count of distinct members — introspection for tests and diagnostics. */
|
|
159
|
+
member_count: () => number;
|
|
149
160
|
}
|
|
150
161
|
/**
|
|
151
162
|
* Create an empty `RealtimeCloser`.
|
|
152
163
|
*
|
|
153
164
|
* `create_app_backend` creates the one on `AppDeps.connection_closer`, and
|
|
154
165
|
* `create_app_server` adds each WebSocket transport it mounts and its audit
|
|
155
|
-
* stream registry
|
|
166
|
+
* stream registry, removing them when its assembly fails or the server
|
|
167
|
+
* closes. A consumer that mounts a transport by hand adds it through
|
|
156
168
|
* the `connection_closer` option of `register_ws_endpoint` /
|
|
157
169
|
* `create_audit_log_sse`, or with `add`.
|
|
158
170
|
*/
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"connection_closer.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/actions/connection_closer.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2CG;AAEH,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,wBAAwB,CAAC;AAGnD,OAAO,EAAqB,KAAK,sBAAsB,EAAE,MAAM,4BAA4B,CAAC;AAE5F;;;;;;;;;;;;;;GAcG;AACH,MAAM,WAAW,gBAAgB;IAChC,qGAAqG;IACrG,yBAAyB,EAAE,CAAC,kBAAkB,EAAE,MAAM,KAAK,MAAM,CAAC;IAClE,4EAA4E;IAC5E,uBAAuB,EAAE,CAAC,YAAY,EAAE,MAAM,KAAK,MAAM,CAAC;IAC1D;;;;OAIG;IACH,yBAAyB,EAAE,CAAC,UAAU,EAAE,IAAI,KAAK,MAAM,CAAC;IACxD;;;;;OAKG;IACH,iBAAiB,EAAE,MAAM,MAAM,CAAC;CAChC;AAED;;;;GAIG;AACH,MAAM,MAAM,qBAAqB,GAC9B;IAAE,IAAI,EAAE,SAAS,CAAC;IAAC,kBAAkB,EAAE,MAAM,CAAA;CAAE,GAC/C;IAAE,IAAI,EAAE,OAAO,CAAC;IAAC,YAAY,EAAE,MAAM,CAAA;CAAE,GACvC;IAAE,IAAI,EAAE,SAAS,CAAC;IAAC,UAAU,EAAE,IAAI,CAAA;CAAE,CAAC;AAEzC;;;;;;;GAOG;AACH,eAAO,MAAM,iBAAiB,GAC7B,QAAQ,gBAAgB,EACxB,QAAQ,qBAAqB,KAC3B,MAWF,CAAC;AAEF;;;;;;;;;;;;;;;GAeG;AACH,eAAO,MAAM,sBAAsB,GAClC,KAAK,sBAAsB,EAC3B,QAAQ,gBAAgB,EACxB,QAAQ,qBAAqB,KAC3B,IAIF,CAAC;AAEF;;;GAGG;AACH,eAAO,MAAM,sBAAsB,EAAE,gBAKnC,CAAC;AAEH;;;;;;;;;;;;GAYG;AACH,MAAM,WAAW,cAAe,SAAQ,gBAAgB;IACvD
|
|
1
|
+
{"version":3,"file":"connection_closer.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/actions/connection_closer.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2CG;AAEH,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,wBAAwB,CAAC;AAGnD,OAAO,EAAqB,KAAK,sBAAsB,EAAE,MAAM,4BAA4B,CAAC;AAE5F;;;;;;;;;;;;;;GAcG;AACH,MAAM,WAAW,gBAAgB;IAChC,qGAAqG;IACrG,yBAAyB,EAAE,CAAC,kBAAkB,EAAE,MAAM,KAAK,MAAM,CAAC;IAClE,4EAA4E;IAC5E,uBAAuB,EAAE,CAAC,YAAY,EAAE,MAAM,KAAK,MAAM,CAAC;IAC1D;;;;OAIG;IACH,yBAAyB,EAAE,CAAC,UAAU,EAAE,IAAI,KAAK,MAAM,CAAC;IACxD;;;;;OAKG;IACH,iBAAiB,EAAE,MAAM,MAAM,CAAC;CAChC;AAED;;;;GAIG;AACH,MAAM,MAAM,qBAAqB,GAC9B;IAAE,IAAI,EAAE,SAAS,CAAC;IAAC,kBAAkB,EAAE,MAAM,CAAA;CAAE,GAC/C;IAAE,IAAI,EAAE,OAAO,CAAC;IAAC,YAAY,EAAE,MAAM,CAAA;CAAE,GACvC;IAAE,IAAI,EAAE,SAAS,CAAC;IAAC,UAAU,EAAE,IAAI,CAAA;CAAE,CAAC;AAEzC;;;;;;;GAOG;AACH,eAAO,MAAM,iBAAiB,GAC7B,QAAQ,gBAAgB,EACxB,QAAQ,qBAAqB,KAC3B,MAWF,CAAC;AAEF;;;;;;;;;;;;;;;GAeG;AACH,eAAO,MAAM,sBAAsB,GAClC,KAAK,sBAAsB,EAC3B,QAAQ,gBAAgB,EACxB,QAAQ,qBAAqB,KAC3B,IAIF,CAAC;AAEF;;;GAGG;AACH,eAAO,MAAM,sBAAsB,EAAE,gBAKnC,CAAC;AAEH;;;;;;;;;;;;GAYG;AACH,MAAM,WAAW,cAAe,SAAQ,gBAAgB;IACvD;;;;;;;;;;;;;;;OAeG;IACH,GAAG,EAAE,CAAC,MAAM,EAAE,gBAAgB,KAAK,MAAM,IAAI,CAAC;IAC9C,2EAA2E;IAC3E,YAAY,EAAE,MAAM,MAAM,CAAC;CAC3B;AAED;;;;;;;;;GASG;AACH,eAAO,MAAM,sBAAsB,QAAO,cA4CzC,CAAC"}
|
|
@@ -100,17 +100,19 @@ export const noop_connection_closer = Object.freeze({
|
|
|
100
100
|
*
|
|
101
101
|
* `create_app_backend` creates the one on `AppDeps.connection_closer`, and
|
|
102
102
|
* `create_app_server` adds each WebSocket transport it mounts and its audit
|
|
103
|
-
* stream registry
|
|
103
|
+
* stream registry, removing them when its assembly fails or the server
|
|
104
|
+
* closes. A consumer that mounts a transport by hand adds it through
|
|
104
105
|
* the `connection_closer` option of `register_ws_endpoint` /
|
|
105
106
|
* `create_audit_log_sse`, or with `add`.
|
|
106
107
|
*/
|
|
107
108
|
export const create_realtime_closer = () => {
|
|
108
|
-
|
|
109
|
+
// member → how many additions hold it; iteration order is first addition
|
|
110
|
+
const members = new Map();
|
|
109
111
|
const fan_out = (close) => {
|
|
110
112
|
let count = 0;
|
|
111
113
|
let failed = false;
|
|
112
114
|
let first_error;
|
|
113
|
-
for (const member of members) {
|
|
115
|
+
for (const member of members.keys()) {
|
|
114
116
|
try {
|
|
115
117
|
count += close(member);
|
|
116
118
|
}
|
|
@@ -129,8 +131,22 @@ export const create_realtime_closer = () => {
|
|
|
129
131
|
};
|
|
130
132
|
return Object.freeze({
|
|
131
133
|
add: (closer) => {
|
|
132
|
-
members.
|
|
134
|
+
members.set(closer, (members.get(closer) ?? 0) + 1);
|
|
135
|
+
let removed = false;
|
|
136
|
+
return () => {
|
|
137
|
+
if (removed)
|
|
138
|
+
return;
|
|
139
|
+
removed = true;
|
|
140
|
+
const count = members.get(closer);
|
|
141
|
+
if (count === undefined)
|
|
142
|
+
return;
|
|
143
|
+
if (count <= 1)
|
|
144
|
+
members.delete(closer);
|
|
145
|
+
else
|
|
146
|
+
members.set(closer, count - 1);
|
|
147
|
+
};
|
|
133
148
|
},
|
|
149
|
+
member_count: () => members.size,
|
|
134
150
|
close_sockets_for_session: (session_token_hash) => fan_out((member) => member.close_sockets_for_session(session_token_hash)),
|
|
135
151
|
close_sockets_for_token: (api_token_id) => fan_out((member) => member.close_sockets_for_token(api_token_id)),
|
|
136
152
|
close_sockets_for_account: (account_id) => fan_out((member) => member.close_sockets_for_account(account_id)),
|
|
@@ -95,7 +95,6 @@ import type { Db } from '../db/db.js';
|
|
|
95
95
|
import { type Action } from './action_types.js';
|
|
96
96
|
import type { RealtimeCloser } from './connection_closer.js';
|
|
97
97
|
import { BackendWebsocketTransport, type ConnectionIdentity } from './transports_ws_backend.js';
|
|
98
|
-
export type { Action };
|
|
99
98
|
/** Default inactivity window before the server closes a silent socket. */
|
|
100
99
|
export declare const DEFAULT_SERVER_HEARTBEAT_TIMEOUT = 60000;
|
|
101
100
|
/**
|
|
@@ -244,6 +243,12 @@ export interface RegisterActionWsOptions {
|
|
|
244
243
|
* reaches. Pass `null` only when no revocation has to reach this transport
|
|
245
244
|
* (a harness driving the dispatcher directly), or when the transport was
|
|
246
245
|
* added by hand.
|
|
246
|
+
*
|
|
247
|
+
* The transport is added once the mount succeeds, so a mount that throws
|
|
248
|
+
* adds nothing. The addition is permanent — its remover is not returned.
|
|
249
|
+
* A caller that must release the membership (as `create_app_server` does
|
|
250
|
+
* on close) passes `null` and adds the transport itself, holding the
|
|
251
|
+
* remover `RealtimeCloser.add` returns.
|
|
247
252
|
*/
|
|
248
253
|
connection_closer: RealtimeCloser | null;
|
|
249
254
|
/**
|
|
@@ -355,9 +360,11 @@ export interface RegisterActionWsResult {
|
|
|
355
360
|
* @mutates options.app - registers a `GET path` route via `upgradeWebSocket`
|
|
356
361
|
* @mutates options.transport - per socket, registers, admits, and removes a
|
|
357
362
|
* connection via `register_pending` / `admit` / `remove_connection`
|
|
358
|
-
* @mutates options.connection_closer - adds the endpoint's transport
|
|
363
|
+
* @mutates options.connection_closer - adds the endpoint's transport, once the
|
|
364
|
+
* mount has succeeded
|
|
359
365
|
* @throws Error when `max_connections_per_account` is passed alongside
|
|
360
|
-
* `transport`, or is neither `null` nor a positive integer
|
|
366
|
+
* `transport`, or is neither `null` nor a positive integer, or when
|
|
367
|
+
* `actions` fails to compile (`compile_action_registry`)
|
|
361
368
|
*/
|
|
362
369
|
export declare const register_action_ws: (options: RegisterActionWsOptions) => RegisterActionWsResult;
|
|
363
370
|
//# sourceMappingURL=register_action_ws.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"register_action_ws.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/actions/register_action_ws.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAuFG;AAEH,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,MAAM,CAAC;AACjC,OAAO,KAAK,EAAE,gBAAgB,EAAE,SAAS,EAAE,MAAM,SAAS,CAAC;AAE3D,OAAO,EAAU,KAAK,MAAM,IAAI,UAAU,EAAE,MAAM,yBAAyB,CAAC;AAC5E,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,wBAAwB,CAAC;AAWnD,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,oBAAoB,CAAC;AAmBtD,OAAO,KAAK,EAAE,EAAE,EAAE,MAAM,aAAa,CAAC;AACtC,OAAO,EAAE,KAAK,MAAM,EAAE,MAAM,mBAAmB,CAAC;AAEhD,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,wBAAwB,CAAC;AAa7D,OAAO,EAAE,yBAAyB,EAAE,KAAK,kBAAkB,EAAE,MAAM,4BAA4B,CAAC;AAIhG,
|
|
1
|
+
{"version":3,"file":"register_action_ws.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/actions/register_action_ws.ts"],"names":[],"mappings":"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"}
|
|
@@ -208,9 +208,11 @@ const is_peer_response = (json) => {
|
|
|
208
208
|
* @mutates options.app - registers a `GET path` route via `upgradeWebSocket`
|
|
209
209
|
* @mutates options.transport - per socket, registers, admits, and removes a
|
|
210
210
|
* connection via `register_pending` / `admit` / `remove_connection`
|
|
211
|
-
* @mutates options.connection_closer - adds the endpoint's transport
|
|
211
|
+
* @mutates options.connection_closer - adds the endpoint's transport, once the
|
|
212
|
+
* mount has succeeded
|
|
212
213
|
* @throws Error when `max_connections_per_account` is passed alongside
|
|
213
|
-
* `transport`, or is neither `null` nor a positive integer
|
|
214
|
+
* `transport`, or is neither `null` nor a positive integer, or when
|
|
215
|
+
* `actions` fails to compile (`compile_action_registry`)
|
|
214
216
|
*/
|
|
215
217
|
export const register_action_ws = (options) => {
|
|
216
218
|
if (options.transport && options.max_connections_per_account !== undefined) {
|
|
@@ -224,7 +226,6 @@ export const register_action_ws = (options) => {
|
|
|
224
226
|
max_connections_per_account: options.max_connections_per_account,
|
|
225
227
|
log
|
|
226
228
|
});
|
|
227
|
-
options.connection_closer?.add(transport);
|
|
228
229
|
// Build the dispatcher's per-method lookup. Only request_response
|
|
229
230
|
// specs with a handler reach `action_map` — perform_action is the
|
|
230
231
|
// only site that calls handlers, and it requires an `RpcAction`.
|
|
@@ -800,6 +801,8 @@ export const register_action_ws = (options) => {
|
|
|
800
801
|
}
|
|
801
802
|
};
|
|
802
803
|
}));
|
|
804
|
+
// last, after everything that can throw, so a failed mount leaves no member
|
|
805
|
+
options.connection_closer?.add(transport);
|
|
803
806
|
return { transport };
|
|
804
807
|
};
|
|
805
808
|
/**
|
|
@@ -62,7 +62,9 @@ export interface RegisterWsEndpointOptions extends RegisterActionWsOptions {
|
|
|
62
62
|
* @mutates options.app - applies origin/auth/scope/authorization/role middleware
|
|
63
63
|
* via `app.use`, then registers the `GET path` route via the inner
|
|
64
64
|
* `register_action_ws`
|
|
65
|
-
* @mutates options.connection_closer - adds the endpoint's transport
|
|
65
|
+
* @mutates options.connection_closer - adds the endpoint's transport, once the
|
|
66
|
+
* mount has succeeded, for good — pass `null` and add it yourself to hold the
|
|
67
|
+
* remover
|
|
66
68
|
*/
|
|
67
69
|
export declare const register_ws_endpoint: (options: RegisterWsEndpointOptions) => RegisterActionWsResult;
|
|
68
70
|
//# sourceMappingURL=register_ws_endpoint.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"register_ws_endpoint.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/actions/register_ws_endpoint.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA8BG;AAaH,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,wBAAwB,CAAC;AAGvD,OAAO,EAEN,KAAK,uBAAuB,EAC5B,KAAK,sBAAsB,EAC3B,MAAM,yBAAyB,CAAC;AAEjC,0CAA0C;AAC1C,MAAM,WAAW,yBAA0B,SAAQ,uBAAuB;IACzE;;;;OAIG;IACH,eAAe,EAAE,aAAa,CAAC,MAAM,CAAC,CAAC;IACvC;;;;;;;;OAQG;IACH,cAAc,CAAC,EAAE,aAAa,CAAC,QAAQ,CAAC,CAAC;CACzC;AAgDD
|
|
1
|
+
{"version":3,"file":"register_ws_endpoint.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/actions/register_ws_endpoint.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA8BG;AAaH,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,wBAAwB,CAAC;AAGvD,OAAO,EAEN,KAAK,uBAAuB,EAC5B,KAAK,sBAAsB,EAC3B,MAAM,yBAAyB,CAAC;AAEjC,0CAA0C;AAC1C,MAAM,WAAW,yBAA0B,SAAQ,uBAAuB;IACzE;;;;OAIG;IACH,eAAe,EAAE,aAAa,CAAC,MAAM,CAAC,CAAC;IACvC;;;;;;;;OAQG;IACH,cAAc,CAAC,EAAE,aAAa,CAAC,QAAQ,CAAC,CAAC;CACzC;AAgDD;;;;;;;;;;;;;;;GAeG;AACH,eAAO,MAAM,oBAAoB,GAChC,SAAS,yBAAyB,KAChC,sBAoBF,CAAC"}
|
|
@@ -86,7 +86,9 @@ const create_ws_authorization_middleware = (db) => {
|
|
|
86
86
|
* @mutates options.app - applies origin/auth/scope/authorization/role middleware
|
|
87
87
|
* via `app.use`, then registers the `GET path` route via the inner
|
|
88
88
|
* `register_action_ws`
|
|
89
|
-
* @mutates options.connection_closer - adds the endpoint's transport
|
|
89
|
+
* @mutates options.connection_closer - adds the endpoint's transport, once the
|
|
90
|
+
* mount has succeeded, for good — pass `null` and add it yourself to hold the
|
|
91
|
+
* remover
|
|
90
92
|
*/
|
|
91
93
|
export const register_ws_endpoint = (options) => {
|
|
92
94
|
const { app, path, allowed_origins, db, required_roles, log = new Logger('[ws]'), ...rest } = options;
|
package/dist/auth/CLAUDE.md
CHANGED
|
@@ -121,20 +121,24 @@ upstream in `auth/signup_routes.ts` via `query_invite_find_unclaimed_match_for_u
|
|
|
121
121
|
`auth/audit_emitter.ts` defines the `AuditEmitter` capability that lives on
|
|
122
122
|
`AppDeps.audit`. Built once at backend assembly via the consumer's
|
|
123
123
|
`audit_factory` callback over `create_audit_emitter`; closes over the pool +
|
|
124
|
-
its registered listeners + optional `AuditLogConfig`.
|
|
124
|
+
its registered listeners + optional `AuditLogConfig`. Its methods:
|
|
125
125
|
|
|
126
126
|
- `emit(ctx, input)` — fire-and-forget pool write, pushes to `ctx.pending_effects`; a success row's listener fan-out goes on `ctx.post_commit_effects`
|
|
127
|
-
- `emit_role_grant_target(ctx, auth, input)` — lifts `actor_id` / `account_id` / `ip` boilerplate for role-grant-shape events
|
|
127
|
+
- `emit_role_grant_target(ctx, auth, input)` — lifts `actor_id` / `account_id` / `ip` boilerplate for role-grant-shape events; `input` is an `AuditEmitRoleGrantInput`
|
|
128
128
|
- `emit_pool(input)` — awaitable pool write for code paths without a request context (ad-hoc maintenance scripts; not for success audits paired with a mutation — those write in-tx and `notify` post-commit). Writes, then notifies, whatever the outcome
|
|
129
129
|
- `notify(event)` — fan out an already-written row to listeners (used by in-tx audit batches like `query_accept_offer.audit_events`)
|
|
130
|
-
- `add_listener(listener)` —
|
|
130
|
+
- `add_listener(listener)` — listener registration, returning a remover for that registration (idempotent; the same function registered twice is two registrations, each with its own remover). Twin of Rust `fuz_auth` `AuditEmitter::add_listener` in firing order and snapshot-at-notify
|
|
131
131
|
- `listener_count()` — registered-listener count, for tests / diagnostics
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
`
|
|
136
|
-
|
|
137
|
-
|
|
132
|
+
- `drain_inflight()` — test-binary barrier: waits until no `emit` write is in flight, writes started while waiting included. Only an emitter built with `track_inflight: true` tracks; otherwise it resolves at once (production never tracks). Backs `_testing_drain_effects` (twin of Rust `AuditEmitter::drain_inflight` / `new_with_inflight_tracking`)
|
|
133
|
+
|
|
134
|
+
Listeners are closure-private; they change only through `add_listener` and
|
|
135
|
+
its removers. `create_app_server` registers the audit-log SSE listener,
|
|
136
|
+
per-endpoint WS auth guards, and any `extra_audit_handlers` via `add_listener`
|
|
137
|
+
so SSE + WS fan-out compose on top of the consumer's `on_audit_event` callback
|
|
138
|
+
without shallow-copying `AppDeps`, and removes them when its assembly fails or
|
|
139
|
+
`AppServer.close` runs — the emitter outlives any one server on its backend.
|
|
140
|
+
`notify` iterates a snapshot so a listener registered mid-fan-out fires only on
|
|
141
|
+
the next event, and one removed mid-fan-out still hears the in-flight event
|
|
138
142
|
(converges with the Rust twin's cloned vec).
|
|
139
143
|
|
|
140
144
|
**When listeners hear about a row.** `emit` always writes at once, on the pool
|
|
@@ -211,7 +215,7 @@ Everything else listed under §RPC action surfaces.
|
|
|
211
215
|
|
|
212
216
|
### Middleware
|
|
213
217
|
|
|
214
|
-
- `auth/middleware.ts` — `create_auth_middleware_specs(deps, options)` assembles `[origin, session, request_context, bearer_auth]` + optional `daemon_token`.
|
|
218
|
+
- `auth/middleware.ts` — `create_auth_middleware_specs(deps, options)` assembles `[origin, session, request_context, bearer_auth]` + optional `daemon_token`. `AUTH_MIDDLEWARE_PATH` (`'/api/*'`) is where `create_app_server` mounts the stack; two assembly asserts lean on it: `assert_endpoint_in_auth_scope` (an `rpc_endpoints` / `ws_endpoints` path must sit inside it) and `assert_middleware_stack_mounted(stack_specs, middleware_specs)`, run on a `transform_middleware` result against the pre-transform stack (`[proxy, ...auth]`) — each stack spec, matched by its `handler` closure (not its spoofable `name`), mounted exactly once at its original path (`'*'` for the trusted proxy, `AUTH_MIDDLEWARE_PATH` for auth), in its original order; other specs may sit anywhere, including before the proxy and between the auth layers. The proxy is in the stack because dropping it turns every `get_client_ip` into `'unknown'`, one shared bucket for every IP-keyed limiter. The transform receives shallow copies in a fresh array, so an in-place mutation can't rewrite the originals the check reads. The check pins where those layers sit, not what added layers do — a layer you add runs with full context access. Both run in `create_test_app_surface_spec` too.
|
|
215
219
|
- `auth/request_context.ts` — `RequestContext`, `resolve_acting_actor`, `build_request_context`, predicates (`has_role`, `has_scoped_role`, `has_any_scoped_role` — the dispatcher's role gate and every builtin-role check read the **global** grant via `has_scoped_role(_, role, null)` / `has_any_scoped_role(_, roles, null)`; scope-blind `has_role` is never the gate for a builtin role), guards (`require_auth`, `require_role`, `require_credential_types`, `require_token_scope`), `token_scope_surface_denial` (the direct-call form, for surfaces that aren't route specs), `refresh_role_grants`.
|
|
216
220
|
- `auth/session_middleware.ts` — `process_session_cookie` integration, `create_session_and_set_cookie` (shared by login / signup / bootstrap; returns the token hashes of the sessions the cap evicted, which the caller closes after its commit — only login passes a cap).
|
|
217
221
|
- `auth/resolved_auth.ts` — `ResolvedAuth` (the resolved credential as one value: `account_id`, `credential_type`, `token_hash`, `api_token_id`), `get_resolved_auth(c)` (gathers it from the context keys the auth middleware set), and `revalidate_resolved_auth(deps, resolved)`, the no-touch re-read a long-lived connection runs once it is registered: session row / token row (by id) still live and still on the account, account not soft-deleted; a daemon token re-reads its account only. Reuses the middleware's own queries and predicates. Called by the WS upgrade (`actions/register_action_ws.ts`) and the audit SSE route between registering a connection pending and admitting it. Twin of the Rust `revalidate_resolved_auth`.
|
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
* Account-grain — only that the caller is signed in matters, not which
|
|
13
13
|
* actor is calling, so resolution skips the actor phase. The auth gate
|
|
14
14
|
* + per-account rate limit (default 1200/15min) + the
|
|
15
|
-
*
|
|
15
|
+
* per-call cap (`ACTOR_LOOKUP_IDS_MAX`) bound the batched
|
|
16
16
|
* username-enumeration surface that the `cell_list` ↔ `actor_lookup`
|
|
17
17
|
* pair would otherwise present.
|
|
18
18
|
*
|
|
@@ -38,7 +38,7 @@
|
|
|
38
38
|
* ids), bounded by:
|
|
39
39
|
*
|
|
40
40
|
* 1. rate-limit (per-account, see above),
|
|
41
|
-
* 2.
|
|
41
|
+
* 2. `ACTOR_LOOKUP_IDS_MAX` cap per call,
|
|
42
42
|
* 3. actor-uuid intractability (122-bit random),
|
|
43
43
|
* 4. hard-deleted actors are indistinguishable from never-existed (no
|
|
44
44
|
* tombstone oracle — see `auth/actor_lookup_queries.ts`).
|
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
* Account-grain — only that the caller is signed in matters, not which
|
|
13
13
|
* actor is calling, so resolution skips the actor phase. The auth gate
|
|
14
14
|
* + per-account rate limit (default 1200/15min) + the
|
|
15
|
-
*
|
|
15
|
+
* per-call cap (`ACTOR_LOOKUP_IDS_MAX`) bound the batched
|
|
16
16
|
* username-enumeration surface that the `cell_list` ↔ `actor_lookup`
|
|
17
17
|
* pair would otherwise present.
|
|
18
18
|
*
|
|
@@ -38,7 +38,7 @@
|
|
|
38
38
|
* ids), bounded by:
|
|
39
39
|
*
|
|
40
40
|
* 1. rate-limit (per-account, see above),
|
|
41
|
-
* 2.
|
|
41
|
+
* 2. `ACTOR_LOOKUP_IDS_MAX` cap per call,
|
|
42
42
|
* 3. actor-uuid intractability (122-bit random),
|
|
43
43
|
* 4. hard-deleted actors are indistinguishable from never-existed (no
|
|
44
44
|
* tombstone oracle — see `auth/actor_lookup_queries.ts`).
|
|
@@ -29,6 +29,8 @@
|
|
|
29
29
|
* the query layer, or the offer-expiry rows `auth/cleanup.ts` writes in
|
|
30
30
|
* its sweep transaction). Runs every registered listener; per-listener
|
|
31
31
|
* throws are isolated.
|
|
32
|
+
* - `drain_inflight()` — test-binary barrier: await every in-flight `emit`
|
|
33
|
+
* write, on an emitter built with `track_inflight: true`.
|
|
32
34
|
*
|
|
33
35
|
* ## When listeners hear about a row
|
|
34
36
|
*
|
|
@@ -56,9 +58,10 @@
|
|
|
56
58
|
* registers additional listeners via `add_listener` after the backend is
|
|
57
59
|
* built (the factory-managed audit-log SSE, per-endpoint WS auth guards, any
|
|
58
60
|
* `extra_audit_handlers` on a `WsEndpointSpec`) before the first request
|
|
59
|
-
* runs
|
|
60
|
-
*
|
|
61
|
-
* `
|
|
61
|
+
* runs, and removes each through the remover `add_listener` returns when its
|
|
62
|
+
* assembly fails or the server closes. Consumers can also register
|
|
63
|
+
* listeners directly on the emitter they return from `audit_factory` for
|
|
64
|
+
* setups that don't pass through `create_app_server`.
|
|
62
65
|
*
|
|
63
66
|
* @module
|
|
64
67
|
*/
|
|
@@ -99,6 +102,19 @@ export interface AuditEmitRoleGrantContext extends AuditEmitterContext {
|
|
|
99
102
|
/** Resolved client IP from the trusted-proxy middleware — `'unknown'` if not resolved. */
|
|
100
103
|
client_ip: string;
|
|
101
104
|
}
|
|
105
|
+
/**
|
|
106
|
+
* Input to `AuditEmitter.emit_role_grant_target` — the event fields that
|
|
107
|
+
* vary per call site; `actor_id`, `account_id`, and `ip` are lifted from
|
|
108
|
+
* the auth and request contexts.
|
|
109
|
+
*/
|
|
110
|
+
export interface AuditEmitRoleGrantInput<T extends string> {
|
|
111
|
+
event_type: T;
|
|
112
|
+
target_account_id: Uuid | null;
|
|
113
|
+
target_actor_id: Uuid | null;
|
|
114
|
+
metadata: AuditLogInput<T>['metadata'];
|
|
115
|
+
/** Default `'success'`, as for `emit`. */
|
|
116
|
+
outcome?: 'success' | 'failure';
|
|
117
|
+
}
|
|
102
118
|
/**
|
|
103
119
|
* Bound audit-emit capability. Built once at backend assembly via
|
|
104
120
|
* `create_audit_emitter`; lives on `AppDeps.audit` so factories never see
|
|
@@ -134,13 +150,7 @@ export interface AuditEmitter {
|
|
|
134
150
|
* Reach for the lower-level `emit` only when the event is non-role-grant
|
|
135
151
|
* shape (e.g. `app_settings_update`, bootstrap, signup).
|
|
136
152
|
*/
|
|
137
|
-
emit_role_grant_target<T extends string>(ctx: AuditEmitRoleGrantContext, auth: RequestActorContext, input:
|
|
138
|
-
event_type: T;
|
|
139
|
-
target_account_id: Uuid | null;
|
|
140
|
-
target_actor_id: Uuid | null;
|
|
141
|
-
metadata: AuditLogInput<T>['metadata'];
|
|
142
|
-
outcome?: 'success' | 'failure';
|
|
143
|
-
}): void;
|
|
153
|
+
emit_role_grant_target<T extends string>(ctx: AuditEmitRoleGrantContext, auth: RequestActorContext, input: AuditEmitRoleGrantInput<T>): void;
|
|
144
154
|
/**
|
|
145
155
|
* Awaitable pool write for code paths without a request context.
|
|
146
156
|
*
|
|
@@ -168,22 +178,55 @@ export interface AuditEmitter {
|
|
|
168
178
|
*/
|
|
169
179
|
notify(event: AuditLogEvent): void;
|
|
170
180
|
/**
|
|
171
|
-
* Register an audit-event listener.
|
|
172
|
-
*
|
|
173
|
-
*
|
|
181
|
+
* Register an audit-event listener. Listeners fire in registration order
|
|
182
|
+
* for every row `emit` / `emit_pool` wrote and on every `notify`.
|
|
183
|
+
*
|
|
184
|
+
* Returns a remover that unregisters this registration. Each call is its
|
|
185
|
+
* own registration: the same function registered twice fires twice, and
|
|
186
|
+
* each remover takes out only the registration it was returned for,
|
|
187
|
+
* leaving the others in place and in order. Calling a remover again is a
|
|
188
|
+
* no-op. A `notify` already in progress still runs a listener removed
|
|
189
|
+
* during its fan-out — it iterates a snapshot — and the next one does not.
|
|
174
190
|
*
|
|
175
191
|
* `create_app_server` registers the factory-managed audit-log SSE
|
|
176
192
|
* listener and per-endpoint WS auth guards here so
|
|
177
193
|
* SSE + WS fan-out compose on top of the consumer's `on_audit_event`
|
|
178
|
-
* callback without shallow-copying `AppDeps
|
|
179
|
-
*
|
|
180
|
-
*
|
|
194
|
+
* callback without shallow-copying `AppDeps`, and removes them when its
|
|
195
|
+
* assembly fails or the server closes. Consumers can also register
|
|
196
|
+
* listeners directly for setups that don't run through
|
|
197
|
+
* `create_app_server`. The `on_audit_event` passed at construction has
|
|
198
|
+
* no remover — it lives as long as the emitter.
|
|
181
199
|
*
|
|
182
|
-
* Twin of the Rust `fuz_auth` `AuditEmitter::add_listener
|
|
200
|
+
* Twin of the Rust `fuz_auth` `AuditEmitter::add_listener` in what
|
|
201
|
+
* registration means — firing order, snapshot-at-notify.
|
|
202
|
+
*
|
|
203
|
+
* @param listener - called with each announced audit row
|
|
204
|
+
* @returns a remover for this registration, idempotent
|
|
205
|
+
*/
|
|
206
|
+
add_listener(listener: (event: AuditLogEvent) => void): () => void;
|
|
207
|
+
/**
|
|
208
|
+
* Count of registered listeners, the construction-time `on_audit_event`
|
|
209
|
+
* included — introspection for tests and diagnostics.
|
|
183
210
|
*/
|
|
184
|
-
add_listener(listener: (event: AuditLogEvent) => void): void;
|
|
185
|
-
/** Count of registered listeners — introspection for tests and diagnostics. */
|
|
186
211
|
listener_count(): number;
|
|
212
|
+
/**
|
|
213
|
+
* Await every fire-and-forget `emit` write in flight — the deterministic
|
|
214
|
+
* barrier behind the `_testing_drain_effects` test action.
|
|
215
|
+
*
|
|
216
|
+
* Waits until no tracked write is outstanding, so a write that starts
|
|
217
|
+
* while the drain is waiting (an emit from a post-commit thunk) is awaited
|
|
218
|
+
* too. Once it resolves, a following `audit_log_list` read sees every row
|
|
219
|
+
* whose `emit` ran before the drain was called. A write that starts after
|
|
220
|
+
* the drain resolves is not covered. A success row's listener fan-out runs
|
|
221
|
+
* as a post-commit effect and is not tracked, so an SSE broadcast or an
|
|
222
|
+
* auth-guard close driven by one isn't guaranteed done when the drain
|
|
223
|
+
* resolves — the row itself is.
|
|
224
|
+
*
|
|
225
|
+
* Resolves at once unless the emitter was built with
|
|
226
|
+
* `track_inflight: true` — production never tracks, so there is nothing
|
|
227
|
+
* to wait for. Twin of the Rust `fuz_auth` `AuditEmitter::drain_inflight`.
|
|
228
|
+
*/
|
|
229
|
+
drain_inflight(): Promise<void>;
|
|
187
230
|
}
|
|
188
231
|
/**
|
|
189
232
|
* Signature of `AuditEmitter.emit` — captured by the inner closure so
|
|
@@ -230,6 +273,17 @@ export interface CreateAuditEmitterOptions {
|
|
|
230
273
|
* Leave unset in production — it is a test instrumentation seam.
|
|
231
274
|
*/
|
|
232
275
|
emit_decorator?: EmitDecorator;
|
|
276
|
+
/**
|
|
277
|
+
* Track every in-flight `emit` write so `drain_inflight` can await it.
|
|
278
|
+
* Defaults to `false`, which makes `drain_inflight` resolve at once.
|
|
279
|
+
*
|
|
280
|
+
* Test binaries only. A transport can reply before a request's writes
|
|
281
|
+
* settle — a WebSocket message is answered before its effects flush — so
|
|
282
|
+
* a test reading the audit log after a reply needs this barrier. In
|
|
283
|
+
* production the tracking is pure overhead and nothing drains it. Twin of
|
|
284
|
+
* the Rust `fuz_auth` `AuditEmitter::new_with_inflight_tracking`.
|
|
285
|
+
*/
|
|
286
|
+
track_inflight?: boolean;
|
|
233
287
|
}
|
|
234
288
|
/**
|
|
235
289
|
* Build a bound `AuditEmitter`. Typical caller is the consumer's
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"audit_emitter.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/auth/audit_emitter.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"audit_emitter.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/auth/audit_emitter.ts"],"names":[],"mappings":"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"}
|