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