@fuzdev/fuz_app 0.23.0 → 0.25.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/action_codegen.d.ts +25 -0
- package/dist/actions/action_codegen.d.ts.map +1 -1
- package/dist/actions/action_codegen.js +39 -0
- package/dist/actions/action_peer.d.ts +7 -0
- package/dist/actions/action_peer.d.ts.map +1 -1
- package/dist/actions/action_peer.js +1 -1
- package/dist/actions/action_types.d.ts +72 -0
- package/dist/actions/action_types.d.ts.map +1 -0
- package/dist/actions/action_types.js +11 -0
- package/dist/actions/cancel.d.ts +78 -0
- package/dist/actions/cancel.d.ts.map +1 -0
- package/dist/actions/cancel.js +79 -0
- package/dist/actions/heartbeat.d.ts +51 -0
- package/dist/actions/heartbeat.d.ts.map +1 -0
- package/dist/actions/heartbeat.js +50 -0
- package/dist/actions/register_action_ws.d.ts +28 -30
- package/dist/actions/register_action_ws.d.ts.map +1 -1
- package/dist/actions/register_action_ws.js +103 -20
- package/dist/actions/rpc_client.d.ts +10 -0
- package/dist/actions/rpc_client.d.ts.map +1 -1
- package/dist/actions/rpc_client.js +22 -7
- package/dist/actions/socket.svelte.d.ts +88 -4
- package/dist/actions/socket.svelte.d.ts.map +1 -1
- package/dist/actions/socket.svelte.js +322 -6
- package/dist/actions/transports.d.ts +18 -3
- package/dist/actions/transports.d.ts.map +1 -1
- package/dist/actions/transports.js +4 -0
- package/dist/actions/transports_http.d.ts +3 -3
- package/dist/actions/transports_http.d.ts.map +1 -1
- package/dist/actions/transports_http.js +4 -3
- package/dist/actions/transports_ws.d.ts +33 -6
- package/dist/actions/transports_ws.d.ts.map +1 -1
- package/dist/actions/transports_ws.js +43 -46
- package/dist/actions/transports_ws_backend.d.ts +12 -3
- package/dist/actions/transports_ws_backend.d.ts.map +1 -1
- package/dist/actions/transports_ws_backend.js +12 -1
- package/dist/auth/bearer_auth.js +0 -1
- package/dist/auth/keyring.d.ts.map +1 -1
- package/dist/auth/keyring.js +0 -2
- package/dist/auth/migrations.js +4 -4
- package/dist/db/migrate.d.ts +12 -2
- package/dist/db/migrate.d.ts.map +1 -1
- package/dist/db/migrate.js +25 -16
- package/dist/db/status.d.ts.map +1 -1
- package/dist/db/status.js +0 -2
- package/dist/dev/setup.js +2 -2
- package/dist/http/db_routes.d.ts.map +1 -1
- package/dist/http/db_routes.js +0 -1
- package/dist/testing/admin_integration.d.ts.map +1 -1
- package/dist/testing/admin_integration.js +0 -3
- package/dist/testing/app_server.js +1 -1
- package/dist/testing/data_exposure.js +6 -8
- package/dist/testing/db.js +1 -1
- package/dist/testing/integration.js +0 -1
- package/dist/testing/rate_limiting.d.ts.map +1 -1
- package/dist/testing/rate_limiting.js +0 -6
- package/dist/testing/rpc_round_trip.js +4 -4
- package/dist/testing/sse_round_trip.d.ts.map +1 -1
- package/dist/testing/sse_round_trip.js +1 -2
- package/dist/testing/ws_round_trip.d.ts +15 -3
- package/dist/testing/ws_round_trip.d.ts.map +1 -1
- package/dist/testing/ws_round_trip.js +3 -3
- package/package.json +2 -2
|
@@ -110,5 +110,30 @@ export declare const to_action_spec_output_identifier: (method: string) => strin
|
|
|
110
110
|
*/
|
|
111
111
|
export declare const get_innermost_type: (schema: z.ZodType) => z.ZodType;
|
|
112
112
|
export declare const get_innermost_type_name: (schema: z.ZodType) => string;
|
|
113
|
+
/**
|
|
114
|
+
* Generates one method line of the typed `ActionsApi` interface for a single
|
|
115
|
+
* spec. Encapsulates the input/options/return-type signature shape so the
|
|
116
|
+
* surface evolves in one place when fields like `signal` or `transport_name`
|
|
117
|
+
* are added to per-call options.
|
|
118
|
+
*
|
|
119
|
+
* Async methods (request_response, async local_call) get an optional second
|
|
120
|
+
* `options?: RpcClientCallOptions` arg (`{signal?, transport_name?}`). Sync
|
|
121
|
+
* local_call methods omit the options arg — `signal` can't cooperatively
|
|
122
|
+
* interrupt a synchronous handler and there's no transport to select.
|
|
123
|
+
*
|
|
124
|
+
* Consumers must import `ActionInputs`, `ActionOutputs`, `Result`,
|
|
125
|
+
* `JsonrpcErrorObject`, and (for async) `RpcClientCallOptions` into the
|
|
126
|
+
* generated module — the helper only emits the type references.
|
|
127
|
+
*
|
|
128
|
+
* @param spec - the action spec to emit
|
|
129
|
+
* @param options.sync_returns_value - when true (default), sync local_call
|
|
130
|
+
* methods return the output value directly; when false they're wrapped in
|
|
131
|
+
* `Result<{value, error}>` like async methods. Set to `false` if your
|
|
132
|
+
* ActionsApi treats every method uniformly.
|
|
133
|
+
* @returns one line like `foo: (input: ActionInputs['foo'], options?: RpcClientCallOptions) => Promise<Result<...>>;`
|
|
134
|
+
*/
|
|
135
|
+
export declare const generate_actions_api_method_signature: (spec: ActionSpecUnion, options?: {
|
|
136
|
+
sync_returns_value?: boolean;
|
|
137
|
+
}) => string;
|
|
113
138
|
export {};
|
|
114
139
|
//# sourceMappingURL=action_codegen.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"action_codegen.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/actions/action_codegen.ts"],"names":[],"mappings":"AACA,OAAO,EAAC,CAAC,EAAC,MAAM,KAAK,CAAC;AAGtB,OAAO,KAAK,EAAC,eAAe,EAAE,gBAAgB,EAAC,MAAM,kBAAkB,CAAC;AAOxE;;GAEG;AACH,UAAU,UAAU;IACnB,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,GAAG,OAAO,GAAG,WAAW,CAAC;CACrC;AAED;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH,qBAAa,aAAa;;IACzB,OAAO,EAAE,GAAG,CAAC,MAAM,EAAE,GAAG,CAAC,MAAM,EAAE,UAAU,CAAC,CAAC,CAAa;IAE1D;;;;OAIG;IACH,GAAG,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,IAAI;IAQrC;;;;OAIG;IACH,QAAQ,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,IAAI;IAI1C;;OAEG;IACH,QAAQ,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,KAAK,EAAE,KAAK,CAAC,MAAM,CAAC,GAAG,IAAI;IAOrD;;OAEG;IACH,SAAS,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,KAAK,EAAE,KAAK,CAAC,MAAM,CAAC,GAAG,IAAI;IAgCtD;;;OAGG;IACH,KAAK,IAAI,MAAM;IAIf;;OAEG;IACH,WAAW,IAAI,OAAO;IAItB;;OAEG;IACH,IAAI,YAAY,IAAI,MAAM,CAEzB;IAED;;;OAGG;IACH,OAAO,IAAI,KAAK,CAAC,MAAM,CAAC;IAIxB;;OAEG;IACH,KAAK,IAAI,IAAI;CAqDb;AAED;;GAEG;AACH,eAAO,MAAM,mBAAmB,GAC/B,MAAM,eAAe,EACrB,UAAU,UAAU,GAAG,SAAS,KAC9B,KAAK,CAAC,gBAAgB,CA4DxB,CAAC;AAEF;;;GAGG;AACH,eAAO,MAAM,uBAAuB,GACnC,MAAM,eAAe,EACrB,OAAO,gBAAgB,EACvB,SAAS,aAAa,EACtB,aAAa,MAAM,KACjB,MAkBF,CAAC;AAEF;;;;;;GAMG;AACH,eAAO,MAAM,uBAAuB,GACnC,MAAM,eAAe,EACrB,UAAU,UAAU,GAAG,SAAS,EAChC,SAAS,aAAa,EACtB,UAAU;IAAC,iBAAiB,CAAC,EAAE,MAAM,CAAA;CAAC,KACpC,MA4BF,CAAC;AAEF;;GAEG;AACH,eAAO,MAAM,aAAa,GAAI,aAAa,MAAM,KAAG,MACU,CAAC;AAG/D,eAAO,MAAM,yBAAyB,GAAI,QAAQ,MAAM,KAAG,MAAiC,CAAC;AAC7F,eAAO,MAAM,+BAA+B,GAAI,QAAQ,MAAM,KAAG,MACpB,CAAC;AAC9C,eAAO,MAAM,gCAAgC,GAAI,QAAQ,MAAM,KAAG,MACpB,CAAC;AAE/C;;;;;GAKG;AACH,eAAO,MAAM,kBAAkB,GAAI,QAAQ,CAAC,CAAC,OAAO,KAAG,CAAC,CAAC,OAwBxD,CAAC;AAEF,eAAO,MAAM,uBAAuB,GAAI,QAAQ,CAAC,CAAC,OAAO,KAAG,MAI3D,CAAC"}
|
|
1
|
+
{"version":3,"file":"action_codegen.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/actions/action_codegen.ts"],"names":[],"mappings":"AACA,OAAO,EAAC,CAAC,EAAC,MAAM,KAAK,CAAC;AAGtB,OAAO,KAAK,EAAC,eAAe,EAAE,gBAAgB,EAAC,MAAM,kBAAkB,CAAC;AAOxE;;GAEG;AACH,UAAU,UAAU;IACnB,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,GAAG,OAAO,GAAG,WAAW,CAAC;CACrC;AAED;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH,qBAAa,aAAa;;IACzB,OAAO,EAAE,GAAG,CAAC,MAAM,EAAE,GAAG,CAAC,MAAM,EAAE,UAAU,CAAC,CAAC,CAAa;IAE1D;;;;OAIG;IACH,GAAG,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,IAAI;IAQrC;;;;OAIG;IACH,QAAQ,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,IAAI;IAI1C;;OAEG;IACH,QAAQ,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,KAAK,EAAE,KAAK,CAAC,MAAM,CAAC,GAAG,IAAI;IAOrD;;OAEG;IACH,SAAS,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,KAAK,EAAE,KAAK,CAAC,MAAM,CAAC,GAAG,IAAI;IAgCtD;;;OAGG;IACH,KAAK,IAAI,MAAM;IAIf;;OAEG;IACH,WAAW,IAAI,OAAO;IAItB;;OAEG;IACH,IAAI,YAAY,IAAI,MAAM,CAEzB;IAED;;;OAGG;IACH,OAAO,IAAI,KAAK,CAAC,MAAM,CAAC;IAIxB;;OAEG;IACH,KAAK,IAAI,IAAI;CAqDb;AAED;;GAEG;AACH,eAAO,MAAM,mBAAmB,GAC/B,MAAM,eAAe,EACrB,UAAU,UAAU,GAAG,SAAS,KAC9B,KAAK,CAAC,gBAAgB,CA4DxB,CAAC;AAEF;;;GAGG;AACH,eAAO,MAAM,uBAAuB,GACnC,MAAM,eAAe,EACrB,OAAO,gBAAgB,EACvB,SAAS,aAAa,EACtB,aAAa,MAAM,KACjB,MAkBF,CAAC;AAEF;;;;;;GAMG;AACH,eAAO,MAAM,uBAAuB,GACnC,MAAM,eAAe,EACrB,UAAU,UAAU,GAAG,SAAS,EAChC,SAAS,aAAa,EACtB,UAAU;IAAC,iBAAiB,CAAC,EAAE,MAAM,CAAA;CAAC,KACpC,MA4BF,CAAC;AAEF;;GAEG;AACH,eAAO,MAAM,aAAa,GAAI,aAAa,MAAM,KAAG,MACU,CAAC;AAG/D,eAAO,MAAM,yBAAyB,GAAI,QAAQ,MAAM,KAAG,MAAiC,CAAC;AAC7F,eAAO,MAAM,+BAA+B,GAAI,QAAQ,MAAM,KAAG,MACpB,CAAC;AAC9C,eAAO,MAAM,gCAAgC,GAAI,QAAQ,MAAM,KAAG,MACpB,CAAC;AAE/C;;;;;GAKG;AACH,eAAO,MAAM,kBAAkB,GAAI,QAAQ,CAAC,CAAC,OAAO,KAAG,CAAC,CAAC,OAwBxD,CAAC;AAEF,eAAO,MAAM,uBAAuB,GAAI,QAAQ,CAAC,CAAC,OAAO,KAAG,MAI3D,CAAC;AAEF;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,eAAO,MAAM,qCAAqC,GACjD,MAAM,eAAe,EACrB,UAAU;IAAC,kBAAkB,CAAC,EAAE,OAAO,CAAA;CAAC,KACtC,MAmBF,CAAC"}
|
|
@@ -316,3 +316,42 @@ export const get_innermost_type_name = (schema) => {
|
|
|
316
316
|
const def = innermost.def;
|
|
317
317
|
return def.type;
|
|
318
318
|
};
|
|
319
|
+
/**
|
|
320
|
+
* Generates one method line of the typed `ActionsApi` interface for a single
|
|
321
|
+
* spec. Encapsulates the input/options/return-type signature shape so the
|
|
322
|
+
* surface evolves in one place when fields like `signal` or `transport_name`
|
|
323
|
+
* are added to per-call options.
|
|
324
|
+
*
|
|
325
|
+
* Async methods (request_response, async local_call) get an optional second
|
|
326
|
+
* `options?: RpcClientCallOptions` arg (`{signal?, transport_name?}`). Sync
|
|
327
|
+
* local_call methods omit the options arg — `signal` can't cooperatively
|
|
328
|
+
* interrupt a synchronous handler and there's no transport to select.
|
|
329
|
+
*
|
|
330
|
+
* Consumers must import `ActionInputs`, `ActionOutputs`, `Result`,
|
|
331
|
+
* `JsonrpcErrorObject`, and (for async) `RpcClientCallOptions` into the
|
|
332
|
+
* generated module — the helper only emits the type references.
|
|
333
|
+
*
|
|
334
|
+
* @param spec - the action spec to emit
|
|
335
|
+
* @param options.sync_returns_value - when true (default), sync local_call
|
|
336
|
+
* methods return the output value directly; when false they're wrapped in
|
|
337
|
+
* `Result<{value, error}>` like async methods. Set to `false` if your
|
|
338
|
+
* ActionsApi treats every method uniformly.
|
|
339
|
+
* @returns one line like `foo: (input: ActionInputs['foo'], options?: RpcClientCallOptions) => Promise<Result<...>>;`
|
|
340
|
+
*/
|
|
341
|
+
export const generate_actions_api_method_signature = (spec, options) => {
|
|
342
|
+
const sync_returns_value = options?.sync_returns_value ?? true;
|
|
343
|
+
const innermost_type_name = get_innermost_type_name(spec.input);
|
|
344
|
+
const has_input = innermost_type_name !== 'null' && innermost_type_name !== 'void';
|
|
345
|
+
const input_param = has_input
|
|
346
|
+
? `input${spec.input.safeParse(undefined).success ? '?' : ''}: ActionInputs['${spec.method}']`
|
|
347
|
+
: 'input?: void';
|
|
348
|
+
const is_async = spec.kind === 'request_response' || (spec.kind === 'local_call' && spec.async);
|
|
349
|
+
const options_param = is_async ? ', options?: RpcClientCallOptions' : '';
|
|
350
|
+
const result_return = `Result<{value: ActionOutputs['${spec.method}']}, {error: JsonrpcErrorObject}>`;
|
|
351
|
+
const return_type = is_async
|
|
352
|
+
? `Promise<${result_return}>`
|
|
353
|
+
: sync_returns_value
|
|
354
|
+
? `ActionOutputs['${spec.method}']`
|
|
355
|
+
: result_return;
|
|
356
|
+
return `${spec.method}: (${input_param}${options_param}) => ${return_type};`;
|
|
357
|
+
};
|
|
@@ -11,6 +11,13 @@ import { Transports, type TransportName } from './transports.js';
|
|
|
11
11
|
import type { ActionEventEnvironment } from './action_event_types.js';
|
|
12
12
|
export interface ActionPeerSendOptions {
|
|
13
13
|
transport_name?: TransportName;
|
|
14
|
+
/**
|
|
15
|
+
* Per-call `AbortSignal`. Forwarded into the chosen transport's `send`,
|
|
16
|
+
* which bottoms out at `FrontendWebsocketClient.request({signal})` for WS
|
|
17
|
+
* (sends the shared `cancel` notification on abort) and at
|
|
18
|
+
* `fetch({signal})` for HTTP. Backend transport ignores it.
|
|
19
|
+
*/
|
|
20
|
+
signal?: AbortSignal;
|
|
14
21
|
}
|
|
15
22
|
export interface ActionPeerOptions {
|
|
16
23
|
environment: ActionEventEnvironment;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"action_peer.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/actions/action_peer.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,EAEN,gCAAgC,EAChC,mBAAmB,EACnB,cAAc,EACd,sBAAsB,EACtB,oBAAoB,EACpB,MAAM,oBAAoB,CAAC;AAU5B,OAAO,EAAC,UAAU,EAAE,KAAK,aAAa,EAAC,MAAM,iBAAiB,CAAC;AAC/D,OAAO,KAAK,EAAC,sBAAsB,EAAC,MAAM,yBAAyB,CAAC;AAOpE,MAAM,WAAW,qBAAqB;IACrC,cAAc,CAAC,EAAE,aAAa,CAAC;
|
|
1
|
+
{"version":3,"file":"action_peer.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/actions/action_peer.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,EAEN,gCAAgC,EAChC,mBAAmB,EACnB,cAAc,EACd,sBAAsB,EACtB,oBAAoB,EACpB,MAAM,oBAAoB,CAAC;AAU5B,OAAO,EAAC,UAAU,EAAE,KAAK,aAAa,EAAC,MAAM,iBAAiB,CAAC;AAC/D,OAAO,KAAK,EAAC,sBAAsB,EAAC,MAAM,yBAAyB,CAAC;AAOpE,MAAM,WAAW,qBAAqB;IACrC,cAAc,CAAC,EAAE,aAAa,CAAC;IAC/B;;;;;OAKG;IACH,MAAM,CAAC,EAAE,WAAW,CAAC;CACrB;AAED,MAAM,WAAW,iBAAiB;IACjC,WAAW,EAAE,sBAAsB,CAAC;IAGpC,UAAU,CAAC,EAAE,UAAU,CAAC;IAGxB,oBAAoB,CAAC,EAAE,OAAO,CAAC,qBAAqB,CAAC,CAAC;CACtD;AAED,qBAAa,UAAU;;IACtB,QAAQ,CAAC,WAAW,EAAE,sBAAsB,CAAC;IAC7C,QAAQ,CAAC,UAAU,EAAE,UAAU,CAAC;IAMhC,oBAAoB,EAAE,qBAAqB,CAAC;gBAEhC,OAAO,EAAE,iBAAiB;IAOhC,IAAI,CACT,OAAO,EAAE,cAAc,EACvB,OAAO,CAAC,EAAE,qBAAqB,GAC7B,OAAO,CAAC,sBAAsB,CAAC;IAC5B,IAAI,CACT,OAAO,EAAE,mBAAmB,EAC5B,OAAO,CAAC,EAAE,qBAAqB,GAC7B,OAAO,CAAC,oBAAoB,GAAG,IAAI,CAAC;IA2CjC,OAAO,CAAC,OAAO,EAAE,OAAO,GAAG,OAAO,CAAC,gCAAgC,GAAG,IAAI,CAAC;CAyIjF"}
|
|
@@ -33,7 +33,7 @@ export class ActionPeer {
|
|
|
33
33
|
}
|
|
34
34
|
const message_type = is_jsonrpc_request(message) ? 'request' : 'notification';
|
|
35
35
|
this.environment.log?.debug(`[peer] send ${message_type}:`, message.method, `via ${transport.transport_name}`);
|
|
36
|
-
const result = await transport.send(message);
|
|
36
|
+
const result = await transport.send(message, { signal: options?.signal });
|
|
37
37
|
if (result && 'error' in result) {
|
|
38
38
|
this.environment.log?.error(`[peer] send ${message_type} failed:`, message.method, result.error.message);
|
|
39
39
|
}
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared type surface for the action system — context, handler, composable Action tuple.
|
|
3
|
+
*
|
|
4
|
+
* These types sit above `action_spec.ts` (pure Zod schemas) and below the
|
|
5
|
+
* dispatchers (`register_action_ws.ts`, `action_rpc.ts`). Extracted so the
|
|
6
|
+
* shared composable fuz_app actions (e.g. `heartbeat_action`) can name them
|
|
7
|
+
* without pulling in server-only modules.
|
|
8
|
+
*
|
|
9
|
+
* @module
|
|
10
|
+
*/
|
|
11
|
+
import type { JsonrpcRequestId } from '../http/jsonrpc.js';
|
|
12
|
+
import type { Uuid } from '../uuid.js';
|
|
13
|
+
import type { ActionSpecUnion } from './action_spec.js';
|
|
14
|
+
/**
|
|
15
|
+
* Minimum per-request context every server-side WS handler receives.
|
|
16
|
+
*
|
|
17
|
+
* Consumers extend this with domain-specific fields via the dispatcher's
|
|
18
|
+
* `extend_context` option. Mirrors the HTTP-side `ActionContext` and Rust's
|
|
19
|
+
* `Ctx<'a>` shape (`request_id` + `NotifyFn` + `CancellationToken`).
|
|
20
|
+
*/
|
|
21
|
+
export interface BaseHandlerContext {
|
|
22
|
+
/** JSON-RPC envelope request id — echoed back on the response. */
|
|
23
|
+
request_id: JsonrpcRequestId;
|
|
24
|
+
/**
|
|
25
|
+
* Stable per-socket connection id assigned by
|
|
26
|
+
* `BackendWebsocketTransport.add_connection` — same reference across every
|
|
27
|
+
* message on this socket, also passed to `on_socket_open` /
|
|
28
|
+
* `on_socket_close`. Consumers key per-connection domain state on this
|
|
29
|
+
* directly instead of trying to derive it from signals (which are
|
|
30
|
+
* per-message composites of `AbortSignal.any([socket, request])`).
|
|
31
|
+
*/
|
|
32
|
+
connection_id: Uuid;
|
|
33
|
+
/**
|
|
34
|
+
* Send a request-scoped JSON-RPC notification to the originating socket.
|
|
35
|
+
* Not a broadcast — the message only reaches the client whose request
|
|
36
|
+
* triggered this handler.
|
|
37
|
+
*/
|
|
38
|
+
notify: (method: string, params: unknown) => void;
|
|
39
|
+
/**
|
|
40
|
+
* Fires on socket close OR on a client-initiated `cancel` notification
|
|
41
|
+
* matching this request's id. Streaming handlers poll for early
|
|
42
|
+
* termination; per-message composite (`AbortSignal.any([socket, request])`)
|
|
43
|
+
* — not stable across messages.
|
|
44
|
+
*/
|
|
45
|
+
signal: AbortSignal;
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* Handler signature — receives validated input and per-request context.
|
|
49
|
+
*
|
|
50
|
+
* Named to disambiguate from `actions/action_rpc.ts`'s `ActionHandler`
|
|
51
|
+
* (HTTP-side, `ActionContext` + two generic slots). The WS variant is
|
|
52
|
+
* single-slotted on the context and returns `unknown`.
|
|
53
|
+
*/
|
|
54
|
+
export type WsActionHandler<TCtx extends BaseHandlerContext = BaseHandlerContext> = (input: unknown, ctx: TCtx) => unknown;
|
|
55
|
+
/**
|
|
56
|
+
* A spec paired with its optional handler — the composable unit passed to
|
|
57
|
+
* {@link register_action_ws} and {@link create_rpc_client}. The server uses
|
|
58
|
+
* both fields; the client reads only {@link spec} (the {@link handler} is
|
|
59
|
+
* ignored, harmless). Shared fuz_app primitives (e.g. `heartbeat_action`)
|
|
60
|
+
* export a complete tuple so consumers spread them into both sides'
|
|
61
|
+
* `actions` array without inventing per-repo ping plumbing.
|
|
62
|
+
*
|
|
63
|
+
* Left open for future fields (`rate_limit`, ACL, middleware hooks) so
|
|
64
|
+
* additions attach to the action itself instead of scattering across
|
|
65
|
+
* parallel arrays.
|
|
66
|
+
*/
|
|
67
|
+
export interface Action<TCtx extends BaseHandlerContext = BaseHandlerContext> {
|
|
68
|
+
spec: ActionSpecUnion;
|
|
69
|
+
/** Server-side handler. Ignored by the client. Omit for client-only specs. */
|
|
70
|
+
handler?: WsActionHandler<TCtx>;
|
|
71
|
+
}
|
|
72
|
+
//# sourceMappingURL=action_types.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"action_types.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/actions/action_types.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH,OAAO,KAAK,EAAC,gBAAgB,EAAC,MAAM,oBAAoB,CAAC;AACzD,OAAO,KAAK,EAAC,IAAI,EAAC,MAAM,YAAY,CAAC;AACrC,OAAO,KAAK,EAAC,eAAe,EAAC,MAAM,kBAAkB,CAAC;AAEtD;;;;;;GAMG;AACH,MAAM,WAAW,kBAAkB;IAClC,kEAAkE;IAClE,UAAU,EAAE,gBAAgB,CAAC;IAC7B;;;;;;;OAOG;IACH,aAAa,EAAE,IAAI,CAAC;IACpB;;;;OAIG;IACH,MAAM,EAAE,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,OAAO,KAAK,IAAI,CAAC;IAClD;;;;;OAKG;IACH,MAAM,EAAE,WAAW,CAAC;CACpB;AAED;;;;;;GAMG;AACH,MAAM,MAAM,eAAe,CAAC,IAAI,SAAS,kBAAkB,GAAG,kBAAkB,IAAI,CACnF,KAAK,EAAE,OAAO,EACd,GAAG,EAAE,IAAI,KACL,OAAO,CAAC;AAEb;;;;;;;;;;;GAWG;AACH,MAAM,WAAW,MAAM,CAAC,IAAI,SAAS,kBAAkB,GAAG,kBAAkB;IAC3E,IAAI,EAAE,eAAe,CAAC;IACtB,8EAA8E;IAC9E,OAAO,CAAC,EAAE,eAAe,CAAC,IAAI,CAAC,CAAC;CAChC"}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared type surface for the action system — context, handler, composable Action tuple.
|
|
3
|
+
*
|
|
4
|
+
* These types sit above `action_spec.ts` (pure Zod schemas) and below the
|
|
5
|
+
* dispatchers (`register_action_ws.ts`, `action_rpc.ts`). Extracted so the
|
|
6
|
+
* shared composable fuz_app actions (e.g. `heartbeat_action`) can name them
|
|
7
|
+
* without pulling in server-only modules.
|
|
8
|
+
*
|
|
9
|
+
* @module
|
|
10
|
+
*/
|
|
11
|
+
export {};
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared cancel action — the second composable fuz_app primitive, validating
|
|
3
|
+
* the spec+handler tuple pattern on a notification-kind action.
|
|
4
|
+
*
|
|
5
|
+
* Semantics: the client sends `{jsonrpc, method: 'cancel', params:
|
|
6
|
+
* {request_id}}` to abort an in-flight request on the same socket.
|
|
7
|
+
* {@link register_action_ws} intercepts this notification and aborts the
|
|
8
|
+
* matching pending request's `ctx.signal`. Unknown ids are no-ops by design —
|
|
9
|
+
* races between response arrival and cancel delivery are safe without extra
|
|
10
|
+
* coordination.
|
|
11
|
+
*
|
|
12
|
+
* The handler field is an empty stub: cancel semantics are dispatcher-owned
|
|
13
|
+
* (the dispatcher has the `{request_id → AbortController}` map, not the
|
|
14
|
+
* handler). The handler exists for symmetry with other composable primitives
|
|
15
|
+
* like {@link heartbeat_action}; the dispatcher never calls it. Consumers
|
|
16
|
+
* spread {@link cancel_action} into their server's `actions` array so
|
|
17
|
+
* `spec_by_method` knows about it (enabling input validation on incoming
|
|
18
|
+
* cancels) and so `create_rpc_client` codegen produces `app.api.cancel()`
|
|
19
|
+
* when desired — though `FrontendWebsocketClient.request({signal})` sends
|
|
20
|
+
* the cancel on abort without needing the typed API.
|
|
21
|
+
*
|
|
22
|
+
* Wire format is snake_case `cancel` with `{request_id}`, not MCP's
|
|
23
|
+
* `$/cancelRequest` with `{requestId}` — fuz_app's WS transport isn't MCP,
|
|
24
|
+
* and adopting MCP's convention would leak protocol-specific framing into
|
|
25
|
+
* the base transport. When MCP elicitation (Phase 5) lands, a translation
|
|
26
|
+
* layer at the MCP adapter is the right seam.
|
|
27
|
+
*
|
|
28
|
+
* @module
|
|
29
|
+
*/
|
|
30
|
+
import { z } from 'zod';
|
|
31
|
+
import type { Action } from './action_types.js';
|
|
32
|
+
/** Method name on the wire — shared across every fuz_app consumer. */
|
|
33
|
+
export declare const CANCEL_METHOD = "cancel";
|
|
34
|
+
/**
|
|
35
|
+
* Params for a {@link CANCEL_METHOD} notification. `request_id` is the id of
|
|
36
|
+
* the pending request to abort. Must match the id of a request sent on the
|
|
37
|
+
* same socket; cancels from other sockets (or for unknown ids) are ignored.
|
|
38
|
+
*/
|
|
39
|
+
export declare const CancelNotificationParams: z.ZodObject<{
|
|
40
|
+
request_id: z.ZodUnion<readonly [z.ZodString, z.ZodNumber]>;
|
|
41
|
+
}, z.core.$strict>;
|
|
42
|
+
export type CancelNotificationParams = z.infer<typeof CancelNotificationParams>;
|
|
43
|
+
/**
|
|
44
|
+
* `ActionSpec` for the shared cancel. `auth: null` matches every other
|
|
45
|
+
* remote-notification spec — upgrade-time auth has already admitted the
|
|
46
|
+
* socket, so per-action auth on a fire-and-forget notification is moot. The
|
|
47
|
+
* per-connection `{request_id → AbortController}` map enforces socket-scoped
|
|
48
|
+
* ownership naturally: a different socket's cancel for the same id misses
|
|
49
|
+
* in its own map.
|
|
50
|
+
*/
|
|
51
|
+
export declare const cancel_action_spec: {
|
|
52
|
+
method: string;
|
|
53
|
+
initiator: "both" | "frontend" | "backend";
|
|
54
|
+
input: z.ZodType<unknown, unknown, z.core.$ZodTypeInternals<unknown, unknown>>;
|
|
55
|
+
description: string;
|
|
56
|
+
kind: "remote_notification";
|
|
57
|
+
auth: null;
|
|
58
|
+
side_effects: true;
|
|
59
|
+
output: z.ZodVoid;
|
|
60
|
+
async: true;
|
|
61
|
+
streams?: string | undefined;
|
|
62
|
+
};
|
|
63
|
+
/**
|
|
64
|
+
* Placeholder handler — cancel semantics are owned by {@link register_action_ws},
|
|
65
|
+
* not invoked per-handler. Exported for symmetry with the {@link Action}
|
|
66
|
+
* tuple shape; the dispatcher short-circuits cancel notifications before any
|
|
67
|
+
* handler lookup happens.
|
|
68
|
+
*/
|
|
69
|
+
export declare const cancel_handler: () => void;
|
|
70
|
+
/**
|
|
71
|
+
* Composable tuple — spread into the server's `actions` array so the
|
|
72
|
+
* dispatcher registers the spec for input validation and so `create_rpc_client`
|
|
73
|
+
* codegen sees the method. The client doesn't need to call it directly;
|
|
74
|
+
* `FrontendWebsocketClient.request({signal})` sends the cancel notification
|
|
75
|
+
* automatically when the signal fires.
|
|
76
|
+
*/
|
|
77
|
+
export declare const cancel_action: Action;
|
|
78
|
+
//# sourceMappingURL=cancel.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cancel.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/actions/cancel.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4BG;AAEH,OAAO,EAAC,CAAC,EAAC,MAAM,KAAK,CAAC;AAItB,OAAO,KAAK,EAAC,MAAM,EAAC,MAAM,mBAAmB,CAAC;AAE9C,sEAAsE;AACtE,eAAO,MAAM,aAAa,WAAW,CAAC;AAEtC;;;;GAIG;AACH,eAAO,MAAM,wBAAwB;;kBAEnC,CAAC;AACH,MAAM,MAAM,wBAAwB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,wBAAwB,CAAC,CAAC;AAEhF;;;;;;;GAOG;AACH,eAAO,MAAM,kBAAkB;;;;;;;;;;;CAW7B,CAAC;AAEH;;;;;GAKG;AACH,eAAO,MAAM,cAAc,QAAO,IAAU,CAAC;AAE7C;;;;;;GAMG;AACH,eAAO,MAAM,aAAa,EAAE,MAG3B,CAAC"}
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared cancel action — the second composable fuz_app primitive, validating
|
|
3
|
+
* the spec+handler tuple pattern on a notification-kind action.
|
|
4
|
+
*
|
|
5
|
+
* Semantics: the client sends `{jsonrpc, method: 'cancel', params:
|
|
6
|
+
* {request_id}}` to abort an in-flight request on the same socket.
|
|
7
|
+
* {@link register_action_ws} intercepts this notification and aborts the
|
|
8
|
+
* matching pending request's `ctx.signal`. Unknown ids are no-ops by design —
|
|
9
|
+
* races between response arrival and cancel delivery are safe without extra
|
|
10
|
+
* coordination.
|
|
11
|
+
*
|
|
12
|
+
* The handler field is an empty stub: cancel semantics are dispatcher-owned
|
|
13
|
+
* (the dispatcher has the `{request_id → AbortController}` map, not the
|
|
14
|
+
* handler). The handler exists for symmetry with other composable primitives
|
|
15
|
+
* like {@link heartbeat_action}; the dispatcher never calls it. Consumers
|
|
16
|
+
* spread {@link cancel_action} into their server's `actions` array so
|
|
17
|
+
* `spec_by_method` knows about it (enabling input validation on incoming
|
|
18
|
+
* cancels) and so `create_rpc_client` codegen produces `app.api.cancel()`
|
|
19
|
+
* when desired — though `FrontendWebsocketClient.request({signal})` sends
|
|
20
|
+
* the cancel on abort without needing the typed API.
|
|
21
|
+
*
|
|
22
|
+
* Wire format is snake_case `cancel` with `{request_id}`, not MCP's
|
|
23
|
+
* `$/cancelRequest` with `{requestId}` — fuz_app's WS transport isn't MCP,
|
|
24
|
+
* and adopting MCP's convention would leak protocol-specific framing into
|
|
25
|
+
* the base transport. When MCP elicitation (Phase 5) lands, a translation
|
|
26
|
+
* layer at the MCP adapter is the right seam.
|
|
27
|
+
*
|
|
28
|
+
* @module
|
|
29
|
+
*/
|
|
30
|
+
import { z } from 'zod';
|
|
31
|
+
import { JsonrpcRequestId } from '../http/jsonrpc.js';
|
|
32
|
+
import { RemoteNotificationActionSpec } from './action_spec.js';
|
|
33
|
+
/** Method name on the wire — shared across every fuz_app consumer. */
|
|
34
|
+
export const CANCEL_METHOD = 'cancel';
|
|
35
|
+
/**
|
|
36
|
+
* Params for a {@link CANCEL_METHOD} notification. `request_id` is the id of
|
|
37
|
+
* the pending request to abort. Must match the id of a request sent on the
|
|
38
|
+
* same socket; cancels from other sockets (or for unknown ids) are ignored.
|
|
39
|
+
*/
|
|
40
|
+
export const CancelNotificationParams = z.strictObject({
|
|
41
|
+
request_id: JsonrpcRequestId,
|
|
42
|
+
});
|
|
43
|
+
/**
|
|
44
|
+
* `ActionSpec` for the shared cancel. `auth: null` matches every other
|
|
45
|
+
* remote-notification spec — upgrade-time auth has already admitted the
|
|
46
|
+
* socket, so per-action auth on a fire-and-forget notification is moot. The
|
|
47
|
+
* per-connection `{request_id → AbortController}` map enforces socket-scoped
|
|
48
|
+
* ownership naturally: a different socket's cancel for the same id misses
|
|
49
|
+
* in its own map.
|
|
50
|
+
*/
|
|
51
|
+
export const cancel_action_spec = RemoteNotificationActionSpec.parse({
|
|
52
|
+
method: CANCEL_METHOD,
|
|
53
|
+
kind: 'remote_notification',
|
|
54
|
+
initiator: 'frontend',
|
|
55
|
+
auth: null,
|
|
56
|
+
side_effects: true,
|
|
57
|
+
input: CancelNotificationParams,
|
|
58
|
+
output: z.void(),
|
|
59
|
+
async: true,
|
|
60
|
+
description: 'Client-initiated cancellation of an in-flight request by id. Dispatcher-handled: aborts the ctx.signal of the matching pending request on the same socket. Unknown or completed ids no-op.',
|
|
61
|
+
});
|
|
62
|
+
/**
|
|
63
|
+
* Placeholder handler — cancel semantics are owned by {@link register_action_ws},
|
|
64
|
+
* not invoked per-handler. Exported for symmetry with the {@link Action}
|
|
65
|
+
* tuple shape; the dispatcher short-circuits cancel notifications before any
|
|
66
|
+
* handler lookup happens.
|
|
67
|
+
*/
|
|
68
|
+
export const cancel_handler = () => { }; // eslint-disable-line @typescript-eslint/no-empty-function
|
|
69
|
+
/**
|
|
70
|
+
* Composable tuple — spread into the server's `actions` array so the
|
|
71
|
+
* dispatcher registers the spec for input validation and so `create_rpc_client`
|
|
72
|
+
* codegen sees the method. The client doesn't need to call it directly;
|
|
73
|
+
* `FrontendWebsocketClient.request({signal})` sends the cancel notification
|
|
74
|
+
* automatically when the signal fires.
|
|
75
|
+
*/
|
|
76
|
+
export const cancel_action = {
|
|
77
|
+
spec: cancel_action_spec,
|
|
78
|
+
handler: cancel_handler,
|
|
79
|
+
};
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared heartbeat action — the first composable fuz_app primitive carrying
|
|
3
|
+
* both a spec and a handler in one tuple. Consumers spread
|
|
4
|
+
* {@link heartbeat_action} into both the server's and the client's `actions`
|
|
5
|
+
* array so disconnect detection works identically across every repo without
|
|
6
|
+
* per-consumer ping plumbing.
|
|
7
|
+
*
|
|
8
|
+
* The client's activity-aware heartbeat timer (in
|
|
9
|
+
* `FrontendWebsocketClient`) issues a `heartbeat` request whenever the
|
|
10
|
+
* connection has been idle for its configured interval; server-side the
|
|
11
|
+
* dispatcher tracks receive time, so incoming heartbeats keep the socket
|
|
12
|
+
* alive without any handler-level state.
|
|
13
|
+
*
|
|
14
|
+
* Nullary input/output today. `{client_ts, server_ts}` fields can be added
|
|
15
|
+
* later if clock-skew telemetry ever matters — the {@link Action} container
|
|
16
|
+
* is open for additions without churning consumer call sites.
|
|
17
|
+
*
|
|
18
|
+
* @module
|
|
19
|
+
*/
|
|
20
|
+
import { z } from 'zod';
|
|
21
|
+
import type { Action } from './action_types.js';
|
|
22
|
+
/** Method name on the wire — shared across every fuz_app consumer. */
|
|
23
|
+
export declare const HEARTBEAT_METHOD = "heartbeat";
|
|
24
|
+
/**
|
|
25
|
+
* `ActionSpec` for the shared heartbeat. `authenticated` auth — upgrade-time
|
|
26
|
+
* auth has already admitted the socket; heartbeats don't need role gating.
|
|
27
|
+
* `side_effects: false` keeps it orthogonal to state changes.
|
|
28
|
+
*/
|
|
29
|
+
export declare const heartbeat_action_spec: {
|
|
30
|
+
method: string;
|
|
31
|
+
initiator: "both" | "frontend" | "backend";
|
|
32
|
+
side_effects: boolean;
|
|
33
|
+
input: z.ZodType<unknown, unknown, z.core.$ZodTypeInternals<unknown, unknown>>;
|
|
34
|
+
output: z.ZodType<unknown, unknown, z.core.$ZodTypeInternals<unknown, unknown>>;
|
|
35
|
+
description: string;
|
|
36
|
+
kind: "request_response";
|
|
37
|
+
auth: "authenticated" | "keeper" | "public" | {
|
|
38
|
+
role: string;
|
|
39
|
+
};
|
|
40
|
+
async: true;
|
|
41
|
+
streams?: string | undefined;
|
|
42
|
+
};
|
|
43
|
+
/** Handler — nullary echo. Stateless, suitable for high-frequency pings. */
|
|
44
|
+
export declare const heartbeat_handler: () => Record<string, never>;
|
|
45
|
+
/**
|
|
46
|
+
* Composable tuple — spread into the server's `actions` array for dispatch
|
|
47
|
+
* and into the client's `actions` array so `create_rpc_client` types
|
|
48
|
+
* `app.api.heartbeat()` against the shared spec.
|
|
49
|
+
*/
|
|
50
|
+
export declare const heartbeat_action: Action;
|
|
51
|
+
//# sourceMappingURL=heartbeat.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"heartbeat.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/actions/heartbeat.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AAEH,OAAO,EAAC,CAAC,EAAC,MAAM,KAAK,CAAC;AAGtB,OAAO,KAAK,EAAC,MAAM,EAAC,MAAM,mBAAmB,CAAC;AAE9C,sEAAsE;AACtE,eAAO,MAAM,gBAAgB,cAAc,CAAC;AAE5C;;;;GAIG;AACH,eAAO,MAAM,qBAAqB;;;;;;;;;;;;;CAUhC,CAAC;AAEH,4EAA4E;AAC5E,eAAO,MAAM,iBAAiB,QAAO,MAAM,CAAC,MAAM,EAAE,KAAK,CAAS,CAAC;AAEnE;;;;GAIG;AACH,eAAO,MAAM,gBAAgB,EAAE,MAG9B,CAAC"}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared heartbeat action — the first composable fuz_app primitive carrying
|
|
3
|
+
* both a spec and a handler in one tuple. Consumers spread
|
|
4
|
+
* {@link heartbeat_action} into both the server's and the client's `actions`
|
|
5
|
+
* array so disconnect detection works identically across every repo without
|
|
6
|
+
* per-consumer ping plumbing.
|
|
7
|
+
*
|
|
8
|
+
* The client's activity-aware heartbeat timer (in
|
|
9
|
+
* `FrontendWebsocketClient`) issues a `heartbeat` request whenever the
|
|
10
|
+
* connection has been idle for its configured interval; server-side the
|
|
11
|
+
* dispatcher tracks receive time, so incoming heartbeats keep the socket
|
|
12
|
+
* alive without any handler-level state.
|
|
13
|
+
*
|
|
14
|
+
* Nullary input/output today. `{client_ts, server_ts}` fields can be added
|
|
15
|
+
* later if clock-skew telemetry ever matters — the {@link Action} container
|
|
16
|
+
* is open for additions without churning consumer call sites.
|
|
17
|
+
*
|
|
18
|
+
* @module
|
|
19
|
+
*/
|
|
20
|
+
import { z } from 'zod';
|
|
21
|
+
import { RequestResponseActionSpec } from './action_spec.js';
|
|
22
|
+
/** Method name on the wire — shared across every fuz_app consumer. */
|
|
23
|
+
export const HEARTBEAT_METHOD = 'heartbeat';
|
|
24
|
+
/**
|
|
25
|
+
* `ActionSpec` for the shared heartbeat. `authenticated` auth — upgrade-time
|
|
26
|
+
* auth has already admitted the socket; heartbeats don't need role gating.
|
|
27
|
+
* `side_effects: false` keeps it orthogonal to state changes.
|
|
28
|
+
*/
|
|
29
|
+
export const heartbeat_action_spec = RequestResponseActionSpec.parse({
|
|
30
|
+
method: HEARTBEAT_METHOD,
|
|
31
|
+
kind: 'request_response',
|
|
32
|
+
initiator: 'frontend',
|
|
33
|
+
auth: 'authenticated',
|
|
34
|
+
side_effects: false,
|
|
35
|
+
input: z.strictObject({}),
|
|
36
|
+
output: z.strictObject({}),
|
|
37
|
+
async: true,
|
|
38
|
+
description: 'Shared activity ping — keeps the socket alive and exercises the dispatch path.',
|
|
39
|
+
});
|
|
40
|
+
/** Handler — nullary echo. Stateless, suitable for high-frequency pings. */
|
|
41
|
+
export const heartbeat_handler = () => ({});
|
|
42
|
+
/**
|
|
43
|
+
* Composable tuple — spread into the server's `actions` array for dispatch
|
|
44
|
+
* and into the client's `actions` array so `create_rpc_client` types
|
|
45
|
+
* `app.api.heartbeat()` against the shared spec.
|
|
46
|
+
*/
|
|
47
|
+
export const heartbeat_action = {
|
|
48
|
+
spec: heartbeat_action_spec,
|
|
49
|
+
handler: heartbeat_handler,
|
|
50
|
+
};
|
|
@@ -24,34 +24,12 @@
|
|
|
24
24
|
import type { Context, Hono } from 'hono';
|
|
25
25
|
import type { UpgradeWebSocket, WSContext } from 'hono/ws';
|
|
26
26
|
import { type Logger as LoggerType } from '@fuzdev/fuz_util/log.js';
|
|
27
|
-
import { type JsonrpcRequestId } from '../http/jsonrpc.js';
|
|
28
27
|
import type { Uuid } from '../uuid.js';
|
|
29
|
-
import type
|
|
28
|
+
import { type Action, type BaseHandlerContext, type WsActionHandler } from './action_types.js';
|
|
30
29
|
import { BackendWebsocketTransport, type ConnectionIdentity } from './transports_ws_backend.js';
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
* Consumers extend this with domain-specific fields via
|
|
35
|
-
* `RegisterActionWsOptions.extend_context` (e.g., a `backend` singleton
|
|
36
|
-
* or the authenticated `RequestContext`). Keeping the base minimal matches
|
|
37
|
-
* the HTTP-side `ActionContext` (from `actions/action_rpc.ts`) and mirrors
|
|
38
|
-
* Rust's `Ctx<'a>` shape (`request_id` + `NotifyFn` + `CancellationToken`).
|
|
39
|
-
*/
|
|
40
|
-
export interface BaseHandlerContext {
|
|
41
|
-
/** JSON-RPC envelope request id — echoed back on the response. */
|
|
42
|
-
request_id: JsonrpcRequestId;
|
|
43
|
-
/**
|
|
44
|
-
* Send a request-scoped JSON-RPC notification to the originating socket.
|
|
45
|
-
* Not a broadcast — the message only reaches the client whose request
|
|
46
|
-
* triggered this handler. Streaming handlers (e.g. `completion_progress`)
|
|
47
|
-
* route chunks through this.
|
|
48
|
-
*/
|
|
49
|
-
notify: (method: string, params: unknown) => void;
|
|
50
|
-
/** Fires on socket close; streaming handlers poll for early termination. */
|
|
51
|
-
signal: AbortSignal;
|
|
52
|
-
}
|
|
53
|
-
/** Handler signature — receives validated input and per-request context. */
|
|
54
|
-
export type WsActionHandler<TCtx extends BaseHandlerContext> = (input: unknown, ctx: TCtx) => unknown;
|
|
30
|
+
export type { Action, BaseHandlerContext, WsActionHandler };
|
|
31
|
+
/** Default inactivity window before the server closes a silent socket. */
|
|
32
|
+
export declare const DEFAULT_SERVER_HEARTBEAT_TIMEOUT = 60000;
|
|
55
33
|
/**
|
|
56
34
|
* Context passed to the `on_socket_open` hook.
|
|
57
35
|
*
|
|
@@ -91,6 +69,15 @@ export interface SocketCloseContext {
|
|
|
91
69
|
/** Auth identity captured at open time — still valid even if the transport already cleaned up. */
|
|
92
70
|
identity: ConnectionIdentity;
|
|
93
71
|
}
|
|
72
|
+
export interface ServerHeartbeatOptions {
|
|
73
|
+
/**
|
|
74
|
+
* Receive-silence (ms) past which the server closes the socket with
|
|
75
|
+
* {@link WS_CLOSE_SERVER_HEARTBEAT_TIMEOUT}. Any incoming message resets
|
|
76
|
+
* the counter — chatty clients never trip it. First {@link timeout}
|
|
77
|
+
* window after socket open is exempt (cold-start grace).
|
|
78
|
+
*/
|
|
79
|
+
timeout?: number;
|
|
80
|
+
}
|
|
94
81
|
/** Options for `register_action_ws`. */
|
|
95
82
|
export interface RegisterActionWsOptions<TCtx extends BaseHandlerContext> {
|
|
96
83
|
/** Mount path (e.g., `/api/ws`). */
|
|
@@ -99,10 +86,14 @@ export interface RegisterActionWsOptions<TCtx extends BaseHandlerContext> {
|
|
|
99
86
|
app: Hono;
|
|
100
87
|
/** Hono's `upgradeWebSocket` helper from the runtime adapter. */
|
|
101
88
|
upgradeWebSocket: UpgradeWebSocket;
|
|
102
|
-
/**
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
89
|
+
/**
|
|
90
|
+
* The actions registered on this endpoint — each carries a spec (drives
|
|
91
|
+
* method lookup, per-action auth, input/output validation) and an
|
|
92
|
+
* optional handler (omit for client-only specs like inbound
|
|
93
|
+
* notifications). Include the shared {@link heartbeat_action} here to
|
|
94
|
+
* complete the disconnect-detection pairing with the frontend client.
|
|
95
|
+
*/
|
|
96
|
+
actions: ReadonlyArray<Action<TCtx>>;
|
|
106
97
|
/**
|
|
107
98
|
* Build the per-request context from the base and the upgrade-time Hono
|
|
108
99
|
* context. Called once per incoming message. Consumers use this to attach
|
|
@@ -116,6 +107,13 @@ export interface RegisterActionWsOptions<TCtx extends BaseHandlerContext> {
|
|
|
116
107
|
* handle for `create_ws_auth_guard` and `send_to`/`broadcast`.
|
|
117
108
|
*/
|
|
118
109
|
transport?: BackendWebsocketTransport;
|
|
110
|
+
/**
|
|
111
|
+
* Server-side heartbeat policy. Default-on (receive-silence detection,
|
|
112
|
+
* 60s timeout). `false` disables the timer entirely — only do this if
|
|
113
|
+
* the upstream stack (TCP keepalive, Cloudflare idle timeout, etc.)
|
|
114
|
+
* already owns disconnect detection. Pass an object to tune the timeout.
|
|
115
|
+
*/
|
|
116
|
+
heartbeat?: boolean | ServerHeartbeatOptions;
|
|
119
117
|
/** Optional per-message delay for testing loading states. Ignored when `0`. */
|
|
120
118
|
artificial_delay?: number;
|
|
121
119
|
/** Optional logger; defaults to `[ws]` namespace. */
|
|
@@ -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;;;;;;;;;;;;;;;;;;;;;;GAsBG;AAGH,OAAO,KAAK,EAAC,OAAO,EAAE,IAAI,EAAC,MAAM,MAAM,CAAC;AACxC,OAAO,KAAK,EAAC,gBAAgB,EAAE,SAAS,EAAC,MAAM,SAAS,CAAC;AAEzD,OAAO,EAAS,KAAK,MAAM,IAAI,UAAU,EAAC,MAAM,yBAAyB,CAAC;
|
|
1
|
+
{"version":3,"file":"register_action_ws.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/actions/register_action_ws.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;GAsBG;AAGH,OAAO,KAAK,EAAC,OAAO,EAAE,IAAI,EAAC,MAAM,MAAM,CAAC;AACxC,OAAO,KAAK,EAAC,gBAAgB,EAAE,SAAS,EAAC,MAAM,SAAS,CAAC;AAEzD,OAAO,EAAS,KAAK,MAAM,IAAI,UAAU,EAAC,MAAM,yBAAyB,CAAC;AAgB1E,OAAO,KAAK,EAAC,IAAI,EAAC,MAAM,YAAY,CAAC;AAErC,OAAO,EAAC,KAAK,MAAM,EAAE,KAAK,kBAAkB,EAAE,KAAK,eAAe,EAAC,MAAM,mBAAmB,CAAC;AAG7F,OAAO,EAAC,yBAAyB,EAAE,KAAK,kBAAkB,EAAC,MAAM,4BAA4B,CAAC;AAE9F,YAAY,EAAC,MAAM,EAAE,kBAAkB,EAAE,eAAe,EAAC,CAAC;AAE1D,0EAA0E;AAC1E,eAAO,MAAM,gCAAgC,QAAS,CAAC;AAEvD;;;;;;;GAOG;AACH,MAAM,WAAW,iBAAiB;IACjC,qFAAqF;IACrF,EAAE,EAAE,SAAS,CAAC;IACd,4EAA4E;IAC5E,aAAa,EAAE,IAAI,CAAC;IACpB,oDAAoD;IACpD,QAAQ,EAAE,kBAAkB,CAAC;IAC7B;;;OAGG;IACH,MAAM,EAAE,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,OAAO,KAAK,IAAI,CAAC;IAClD,wFAAwF;IACxF,MAAM,EAAE,WAAW,CAAC;CACpB;AAED;;;;;;;GAOG;AACH,MAAM,WAAW,kBAAkB;IAClC,+CAA+C;IAC/C,EAAE,EAAE,SAAS,CAAC;IACd,2CAA2C;IAC3C,aAAa,EAAE,IAAI,CAAC;IACpB,kGAAkG;IAClG,QAAQ,EAAE,kBAAkB,CAAC;CAC7B;AAED,MAAM,WAAW,sBAAsB;IACtC;;;;;OAKG;IACH,OAAO,CAAC,EAAE,MAAM,CAAC;CACjB;AAED,wCAAwC;AACxC,MAAM,WAAW,uBAAuB,CAAC,IAAI,SAAS,kBAAkB;IACvE,oCAAoC;IACpC,IAAI,EAAE,MAAM,CAAC;IACb,gCAAgC;IAChC,GAAG,EAAE,IAAI,CAAC;IACV,iEAAiE;IACjE,gBAAgB,EAAE,gBAAgB,CAAC;IACnC;;;;;;OAMG;IACH,OAAO,EAAE,aAAa,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC;IACrC;;;;;OAKG;IACH,cAAc,EAAE,CAAC,IAAI,EAAE,kBAAkB,EAAE,CAAC,EAAE,OAAO,KAAK,IAAI,CAAC;IAC/D;;;;OAIG;IACH,SAAS,CAAC,EAAE,yBAAyB,CAAC;IACtC;;;;;OAKG;IACH,SAAS,CAAC,EAAE,OAAO,GAAG,sBAAsB,CAAC;IAC7C,+EAA+E;IAC/E,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,qDAAqD;IACrD,GAAG,CAAC,EAAE,UAAU,CAAC;IACjB;;;;;OAKG;IACH,cAAc,CAAC,EAAE,CAAC,GAAG,EAAE,iBAAiB,KAAK,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAClE;;;;;OAKG;IACH,eAAe,CAAC,EAAE,CAAC,GAAG,EAAE,kBAAkB,KAAK,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;CACpE;AAED,sCAAsC;AACtC,MAAM,WAAW,sBAAsB;IACtC,yEAAyE;IACzE,SAAS,EAAE,yBAAyB,CAAC;CACrC;AAED;;;;;;;;;;;;;;;;;GAiBG;AACH,eAAO,MAAM,kBAAkB,GAAI,IAAI,SAAS,kBAAkB,EACjE,SAAS,uBAAuB,CAAC,IAAI,CAAC,KACpC,sBAwWF,CAAC"}
|