@fuzdev/fuz_app 0.30.0 → 0.32.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 +630 -0
- package/dist/actions/action_rpc.d.ts +29 -0
- package/dist/actions/action_rpc.d.ts.map +1 -1
- package/dist/actions/action_rpc.js +42 -6
- package/dist/actions/action_types.d.ts +2 -2
- package/dist/actions/cancel.d.ts +12 -13
- package/dist/actions/cancel.d.ts.map +1 -1
- package/dist/actions/cancel.js +10 -13
- package/dist/actions/heartbeat.d.ts +8 -13
- package/dist/actions/heartbeat.d.ts.map +1 -1
- package/dist/actions/heartbeat.js +5 -8
- package/dist/actions/register_action_ws.d.ts +3 -3
- package/dist/actions/register_action_ws.js +2 -2
- package/dist/actions/register_ws_endpoint.d.ts +4 -4
- package/dist/actions/register_ws_endpoint.d.ts.map +1 -1
- package/dist/actions/register_ws_endpoint.js +3 -3
- package/dist/actions/rpc_client.d.ts +29 -0
- package/dist/actions/rpc_client.d.ts.map +1 -1
- package/dist/actions/rpc_client.js +31 -0
- package/dist/actions/socket.svelte.d.ts +16 -16
- package/dist/actions/socket.svelte.d.ts.map +1 -1
- package/dist/actions/socket.svelte.js +15 -15
- package/dist/actions/transports_ws_auth_guard.d.ts.map +1 -1
- package/dist/auth/CLAUDE.md +945 -0
- package/dist/auth/account_action_specs.d.ts +216 -0
- package/dist/auth/account_action_specs.d.ts.map +1 -0
- package/dist/auth/account_action_specs.js +159 -0
- package/dist/auth/account_actions.d.ts +51 -0
- package/dist/auth/account_actions.d.ts.map +1 -0
- package/dist/auth/account_actions.js +119 -0
- package/dist/auth/account_queries.d.ts +6 -2
- package/dist/auth/account_queries.d.ts.map +1 -1
- package/dist/auth/account_queries.js +40 -4
- package/dist/auth/account_routes.d.ts +94 -16
- package/dist/auth/account_routes.d.ts.map +1 -1
- package/dist/auth/account_routes.js +108 -180
- package/dist/auth/account_schema.d.ts +85 -30
- package/dist/auth/account_schema.d.ts.map +1 -1
- package/dist/auth/account_schema.js +40 -8
- package/dist/auth/admin_action_specs.d.ts +674 -0
- package/dist/auth/admin_action_specs.d.ts.map +1 -0
- package/dist/auth/admin_action_specs.js +287 -0
- package/dist/auth/admin_actions.d.ts +69 -0
- package/dist/auth/admin_actions.d.ts.map +1 -0
- package/dist/auth/admin_actions.js +256 -0
- package/dist/auth/admin_rpc_actions.d.ts +49 -0
- package/dist/auth/admin_rpc_actions.d.ts.map +1 -0
- package/dist/auth/admin_rpc_actions.js +32 -0
- package/dist/auth/api_token.d.ts +10 -0
- package/dist/auth/api_token.d.ts.map +1 -1
- package/dist/auth/api_token.js +9 -0
- package/dist/auth/api_token_queries.d.ts +3 -3
- package/dist/auth/api_token_queries.js +3 -3
- package/dist/auth/app_settings_schema.d.ts +4 -3
- package/dist/auth/app_settings_schema.d.ts.map +1 -1
- package/dist/auth/app_settings_schema.js +2 -1
- package/dist/auth/audit_log_routes.d.ts +14 -6
- package/dist/auth/audit_log_routes.d.ts.map +1 -1
- package/dist/auth/audit_log_routes.js +22 -79
- package/dist/auth/audit_log_schema.d.ts +100 -29
- package/dist/auth/audit_log_schema.d.ts.map +1 -1
- package/dist/auth/audit_log_schema.js +83 -11
- package/dist/auth/bootstrap_routes.d.ts +14 -0
- package/dist/auth/bootstrap_routes.d.ts.map +1 -1
- package/dist/auth/bootstrap_routes.js +10 -3
- package/dist/auth/cleanup.d.ts +63 -0
- package/dist/auth/cleanup.d.ts.map +1 -0
- package/dist/auth/cleanup.js +80 -0
- package/dist/auth/invite_schema.d.ts +11 -10
- package/dist/auth/invite_schema.d.ts.map +1 -1
- package/dist/auth/invite_schema.js +4 -3
- package/dist/auth/migrations.d.ts +6 -0
- package/dist/auth/migrations.d.ts.map +1 -1
- package/dist/auth/migrations.js +28 -0
- package/dist/auth/permit_offer_action_specs.d.ts +364 -0
- package/dist/auth/permit_offer_action_specs.d.ts.map +1 -0
- package/dist/auth/permit_offer_action_specs.js +216 -0
- package/dist/auth/permit_offer_actions.d.ts +96 -0
- package/dist/auth/permit_offer_actions.d.ts.map +1 -0
- package/dist/auth/permit_offer_actions.js +428 -0
- package/dist/auth/permit_offer_notifications.d.ts +361 -0
- package/dist/auth/permit_offer_notifications.d.ts.map +1 -0
- package/dist/auth/permit_offer_notifications.js +179 -0
- package/dist/auth/permit_offer_queries.d.ts +165 -0
- package/dist/auth/permit_offer_queries.d.ts.map +1 -0
- package/dist/auth/permit_offer_queries.js +390 -0
- package/dist/auth/permit_offer_schema.d.ts +103 -0
- package/dist/auth/permit_offer_schema.d.ts.map +1 -0
- package/dist/auth/permit_offer_schema.js +142 -0
- package/dist/auth/permit_queries.d.ts +77 -14
- package/dist/auth/permit_queries.d.ts.map +1 -1
- package/dist/auth/permit_queries.js +119 -24
- package/dist/auth/session_queries.d.ts +4 -2
- package/dist/auth/session_queries.d.ts.map +1 -1
- package/dist/auth/session_queries.js +4 -2
- package/dist/auth/signup_routes.d.ts +13 -0
- package/dist/auth/signup_routes.d.ts.map +1 -1
- package/dist/auth/signup_routes.js +14 -7
- package/dist/http/CLAUDE.md +584 -0
- package/dist/http/pending_effects.d.ts +29 -0
- package/dist/http/pending_effects.d.ts.map +1 -0
- package/dist/http/pending_effects.js +31 -0
- package/dist/http/route_spec.d.ts.map +1 -1
- package/dist/http/route_spec.js +4 -3
- package/dist/rate_limiter.d.ts +30 -0
- package/dist/rate_limiter.d.ts.map +1 -1
- package/dist/rate_limiter.js +25 -2
- package/dist/realtime/sse_auth_guard.d.ts +2 -0
- package/dist/realtime/sse_auth_guard.d.ts.map +1 -1
- package/dist/realtime/sse_auth_guard.js +5 -3
- package/dist/server/app_server.d.ts +13 -2
- package/dist/server/app_server.d.ts.map +1 -1
- package/dist/server/app_server.js +12 -1
- package/dist/testing/CLAUDE.md +668 -1
- package/dist/testing/admin_integration.d.ts +10 -7
- package/dist/testing/admin_integration.d.ts.map +1 -1
- package/dist/testing/admin_integration.js +382 -482
- package/dist/testing/app_server.d.ts +7 -6
- package/dist/testing/app_server.d.ts.map +1 -1
- package/dist/testing/attack_surface.d.ts +9 -3
- package/dist/testing/attack_surface.d.ts.map +1 -1
- package/dist/testing/attack_surface.js +4 -4
- package/dist/testing/audit_completeness.d.ts +11 -0
- package/dist/testing/audit_completeness.d.ts.map +1 -1
- package/dist/testing/audit_completeness.js +169 -134
- package/dist/testing/auth_apps.d.ts.map +1 -1
- package/dist/testing/auth_apps.js +4 -33
- package/dist/testing/db.d.ts +1 -1
- package/dist/testing/db.d.ts.map +1 -1
- package/dist/testing/db.js +2 -0
- package/dist/testing/entities.d.ts +35 -13
- package/dist/testing/entities.d.ts.map +1 -1
- package/dist/testing/entities.js +17 -0
- package/dist/testing/integration.d.ts +10 -0
- package/dist/testing/integration.d.ts.map +1 -1
- package/dist/testing/integration.js +352 -340
- package/dist/testing/integration_helpers.d.ts +16 -5
- package/dist/testing/integration_helpers.d.ts.map +1 -1
- package/dist/testing/integration_helpers.js +24 -4
- package/dist/testing/rate_limiting.d.ts +7 -0
- package/dist/testing/rate_limiting.d.ts.map +1 -1
- package/dist/testing/rate_limiting.js +41 -10
- package/dist/testing/rpc_helpers.d.ts +153 -1
- package/dist/testing/rpc_helpers.d.ts.map +1 -1
- package/dist/testing/rpc_helpers.js +184 -8
- package/dist/testing/sse_round_trip.d.ts +8 -0
- package/dist/testing/sse_round_trip.d.ts.map +1 -1
- package/dist/testing/sse_round_trip.js +10 -3
- package/dist/testing/standard.d.ts +9 -1
- package/dist/testing/standard.d.ts.map +1 -1
- package/dist/testing/standard.js +6 -2
- package/dist/testing/stubs.d.ts +10 -2
- package/dist/testing/stubs.d.ts.map +1 -1
- package/dist/testing/stubs.js +17 -2
- package/dist/testing/surface_invariants.d.ts +7 -3
- package/dist/testing/surface_invariants.d.ts.map +1 -1
- package/dist/testing/surface_invariants.js +5 -4
- package/dist/testing/ws_round_trip.d.ts.map +1 -1
- package/dist/testing/ws_round_trip.js +9 -38
- package/dist/ui/AccountSessions.svelte +8 -4
- package/dist/ui/AccountSessions.svelte.d.ts.map +1 -1
- package/dist/ui/AdminAccounts.svelte +61 -33
- package/dist/ui/AdminAccounts.svelte.d.ts.map +1 -1
- package/dist/ui/AdminAuditLog.svelte +3 -2
- package/dist/ui/AdminAuditLog.svelte.d.ts.map +1 -1
- package/dist/ui/AdminInvites.svelte +3 -2
- package/dist/ui/AdminInvites.svelte.d.ts.map +1 -1
- package/dist/ui/AdminOverview.svelte +14 -9
- package/dist/ui/AdminOverview.svelte.d.ts.map +1 -1
- package/dist/ui/AdminPermitHistory.svelte +3 -2
- package/dist/ui/AdminPermitHistory.svelte.d.ts.map +1 -1
- package/dist/ui/AdminSessions.svelte +29 -25
- package/dist/ui/AdminSessions.svelte.d.ts.map +1 -1
- package/dist/ui/CLAUDE.md +363 -0
- package/dist/ui/OpenSignupToggle.svelte +6 -3
- package/dist/ui/OpenSignupToggle.svelte.d.ts.map +1 -1
- package/dist/ui/PermitOfferForm.svelte +141 -0
- package/dist/ui/PermitOfferForm.svelte.d.ts +14 -0
- package/dist/ui/PermitOfferForm.svelte.d.ts.map +1 -0
- package/dist/ui/PermitOfferHistory.svelte +109 -0
- package/dist/ui/PermitOfferHistory.svelte.d.ts +11 -0
- package/dist/ui/PermitOfferHistory.svelte.d.ts.map +1 -0
- package/dist/ui/PermitOfferInbox.svelte +121 -0
- package/dist/ui/PermitOfferInbox.svelte.d.ts +12 -0
- package/dist/ui/PermitOfferInbox.svelte.d.ts.map +1 -0
- package/dist/ui/account_sessions_state.svelte.d.ts +53 -3
- package/dist/ui/account_sessions_state.svelte.d.ts.map +1 -1
- package/dist/ui/account_sessions_state.svelte.js +39 -16
- package/dist/ui/admin_accounts_state.svelte.d.ts +118 -2
- package/dist/ui/admin_accounts_state.svelte.d.ts.map +1 -1
- package/dist/ui/admin_accounts_state.svelte.js +99 -23
- package/dist/ui/admin_invites_state.svelte.d.ts +47 -1
- package/dist/ui/admin_invites_state.svelte.d.ts.map +1 -1
- package/dist/ui/admin_invites_state.svelte.js +38 -26
- package/dist/ui/admin_rpc_adapters.d.ts +94 -0
- package/dist/ui/admin_rpc_adapters.d.ts.map +1 -0
- package/dist/ui/admin_rpc_adapters.js +100 -0
- package/dist/ui/admin_sessions_state.svelte.d.ts +26 -0
- package/dist/ui/admin_sessions_state.svelte.d.ts.map +1 -1
- package/dist/ui/admin_sessions_state.svelte.js +35 -21
- package/dist/ui/app_settings_state.svelte.d.ts +39 -0
- package/dist/ui/app_settings_state.svelte.d.ts.map +1 -1
- package/dist/ui/app_settings_state.svelte.js +34 -18
- package/dist/ui/audit_log_state.svelte.d.ts +40 -3
- package/dist/ui/audit_log_state.svelte.d.ts.map +1 -1
- package/dist/ui/audit_log_state.svelte.js +36 -42
- package/dist/ui/auth_state.svelte.d.ts +4 -3
- package/dist/ui/auth_state.svelte.d.ts.map +1 -1
- package/dist/ui/auth_state.svelte.js +4 -1
- package/dist/ui/permit_offers_state.svelte.d.ts +125 -0
- package/dist/ui/permit_offers_state.svelte.d.ts.map +1 -0
- package/dist/ui/permit_offers_state.svelte.js +197 -0
- package/package.json +3 -3
- package/dist/auth/admin_routes.d.ts +0 -29
- package/dist/auth/admin_routes.d.ts.map +0 -1
- package/dist/auth/admin_routes.js +0 -226
- package/dist/auth/app_settings_routes.d.ts +0 -27
- package/dist/auth/app_settings_routes.d.ts.map +0 -1
- package/dist/auth/app_settings_routes.js +0 -66
- package/dist/auth/invite_routes.d.ts +0 -18
- package/dist/auth/invite_routes.d.ts.map +0 -1
- package/dist/auth/invite_routes.js +0 -129
|
@@ -11,6 +11,7 @@
|
|
|
11
11
|
*
|
|
12
12
|
* @module
|
|
13
13
|
*/
|
|
14
|
+
import { z } from 'zod';
|
|
14
15
|
import type { Logger } from '@fuzdev/fuz_util/log.js';
|
|
15
16
|
import type { RequestResponseActionSpec } from './action_spec.js';
|
|
16
17
|
import { type RouteSpec } from '../http/route_spec.js';
|
|
@@ -35,6 +36,15 @@ export interface ActionContext {
|
|
|
35
36
|
background_db: Db;
|
|
36
37
|
/** Fire-and-forget side effects — push here for post-response flushing. */
|
|
37
38
|
pending_effects: Array<Promise<void>>;
|
|
39
|
+
/**
|
|
40
|
+
* Resolved client IP from the trusted-proxy middleware — `'unknown'` if the
|
|
41
|
+
* middleware wasn't in the stack (e.g. WS dispatch) or couldn't resolve.
|
|
42
|
+
* Thread into `audit_log_fire_and_forget` as `ip: ctx.client_ip` for every
|
|
43
|
+
* user-initiated action so RPC audit rows match the REST convention. Pass
|
|
44
|
+
* `null` only for rows written outside a request (e.g. the
|
|
45
|
+
* `permit_offer_expire` cleanup sweep in `auth/cleanup.ts`).
|
|
46
|
+
*/
|
|
47
|
+
client_ip: string;
|
|
38
48
|
/** Logger instance. */
|
|
39
49
|
log: Logger;
|
|
40
50
|
/**
|
|
@@ -71,6 +81,25 @@ export interface RpcAction {
|
|
|
71
81
|
spec: RequestResponseActionSpec;
|
|
72
82
|
handler: ActionHandler;
|
|
73
83
|
}
|
|
84
|
+
/**
|
|
85
|
+
* Pair a spec with a handler while preserving per-method input/output types.
|
|
86
|
+
*
|
|
87
|
+
* Constructing `{spec, handler}` literals widens `handler` to
|
|
88
|
+
* `ActionHandler<any, any>`, so spec/handler drift (renamed Zod schema,
|
|
89
|
+
* output field removal, input shape change) slips past the typechecker.
|
|
90
|
+
* `rpc_action(spec, handler)` binds the handler signature to
|
|
91
|
+
* `(input: z.infer<spec.input>, ctx) => z.infer<spec.output>` via the
|
|
92
|
+
* generic spec parameter — drift surfaces at the call site.
|
|
93
|
+
*
|
|
94
|
+
* Fits fuz_app's factory-closure pattern (handlers close over
|
|
95
|
+
* `grantable_roles`, `app_settings` ref, `notification_sender`, etc.).
|
|
96
|
+
* zzz uses a different shape — a codegen-keyed `Record<Method, Handler>`
|
|
97
|
+
* map typed via generated `ActionInputs`/`ActionOutputs` — which works when
|
|
98
|
+
* handlers are pure (no closure state) and specs are codegen-enumerated.
|
|
99
|
+
* fuz_app's admin + permit-offer actions have neither, so per-pair typing
|
|
100
|
+
* at the registration site is the right fit.
|
|
101
|
+
*/
|
|
102
|
+
export declare const rpc_action: <TSpec extends RequestResponseActionSpec>(spec: TSpec, handler: ActionHandler<z.infer<TSpec["input"]>, z.infer<TSpec["output"]>>) => RpcAction;
|
|
74
103
|
/** Options for `create_rpc_endpoint`. */
|
|
75
104
|
export interface CreateRpcEndpointOptions {
|
|
76
105
|
/** Mount path for the endpoint (e.g., `/api/rpc`). */
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"action_rpc.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/actions/action_rpc.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;
|
|
1
|
+
{"version":3,"file":"action_rpc.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/actions/action_rpc.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAGH,OAAO,EAAC,CAAC,EAAC,MAAM,KAAK,CAAC;AAEtB,OAAO,KAAK,EAAC,MAAM,EAAC,MAAM,yBAAyB,CAAC;AAEpD,OAAO,KAAK,EAAC,yBAAyB,EAAC,MAAM,kBAAkB,CAAC;AAChE,OAAO,EAAoB,KAAK,SAAS,EAAC,MAAM,uBAAuB,CAAC;AAExE,OAAO,EAAgC,KAAK,cAAc,EAAC,MAAM,4BAA4B,CAAC;AAE9F,OAAO,KAAK,EAAC,EAAE,EAAC,MAAM,aAAa,CAAC;AAEpC,OAAO,EAGN,KAAK,gBAAgB,EAGrB,MAAM,oBAAoB,CAAC;AAW5B;;;;;;GAMG;AACH,MAAM,WAAW,aAAa;IAC7B,+DAA+D;IAC/D,IAAI,EAAE,cAAc,GAAG,IAAI,CAAC;IAC5B,iDAAiD;IACjD,UAAU,EAAE,gBAAgB,CAAC;IAC7B,8DAA8D;IAC9D,EAAE,EAAE,EAAE,CAAC;IACP,oFAAoF;IACpF,aAAa,EAAE,EAAE,CAAC;IAClB,2EAA2E;IAC3E,eAAe,EAAE,KAAK,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC;IACtC;;;;;;;OAOG;IACH,SAAS,EAAE,MAAM,CAAC;IAClB,uBAAuB;IACvB,GAAG,EAAE,MAAM,CAAC;IACZ;;;;;;;;OAQG;IACH,MAAM,EAAE,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,OAAO,KAAK,IAAI,CAAC;IAClD;;;;OAIG;IACH,MAAM,EAAE,WAAW,CAAC;CACpB;AAED;;;;;GAKG;AACH,MAAM,MAAM,aAAa,CAAC,MAAM,GAAG,GAAG,EAAE,OAAO,GAAG,GAAG,IAAI,CACxD,KAAK,EAAE,MAAM,EACb,GAAG,EAAE,aAAa,KACd,OAAO,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;AAEhC;;;;;GAKG;AACH,MAAM,WAAW,SAAS;IACzB,IAAI,EAAE,yBAAyB,CAAC;IAChC,OAAO,EAAE,aAAa,CAAC;CACvB;AAED;;;;;;;;;;;;;;;;;GAiBG;AACH,eAAO,MAAM,UAAU,GAAI,KAAK,SAAS,yBAAyB,EACjE,MAAM,KAAK,EACX,SAAS,aAAa,CAAC,CAAC,CAAC,KAAK,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,EAAE,CAAC,CAAC,KAAK,CAAC,KAAK,CAAC,QAAQ,CAAC,CAAC,CAAC,KACvE,SAGD,CAAC;AAEH,yCAAyC;AACzC,MAAM,WAAW,wBAAwB;IACxC,sDAAsD;IACtD,IAAI,EAAE,MAAM,CAAC;IACb,4BAA4B;IAC5B,OAAO,EAAE,KAAK,CAAC,SAAS,CAAC,CAAC;IAC1B,2CAA2C;IAC3C,GAAG,EAAE,MAAM,CAAC;CACZ;AA4DD;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AACH,eAAO,MAAM,mBAAmB,GAAI,SAAS,wBAAwB,KAAG,KAAK,CAAC,SAAS,CAwPtF,CAAC"}
|
|
@@ -14,11 +14,35 @@
|
|
|
14
14
|
import { z } from 'zod';
|
|
15
15
|
import { DEV } from 'esm-env';
|
|
16
16
|
import {} from '../http/route_spec.js';
|
|
17
|
+
import { get_client_ip } from '../http/proxy.js';
|
|
17
18
|
import { get_request_context, has_role } from '../auth/request_context.js';
|
|
18
19
|
import { CREDENTIAL_TYPE_KEY } from '../hono_context.js';
|
|
19
20
|
import { is_null_schema } from '../http/schema_helpers.js';
|
|
20
21
|
import { JSONRPC_VERSION, JsonrpcRequest, } from '../http/jsonrpc.js';
|
|
21
22
|
import { jsonrpc_error_messages, jsonrpc_error_code_to_http_status, JSONRPC_ERROR_CODES, } from '../http/jsonrpc_errors.js';
|
|
23
|
+
import { ERROR_INSUFFICIENT_PERMISSIONS, ERROR_KEEPER_REQUIRES_DAEMON_TOKEN, } from '../http/error_schemas.js';
|
|
24
|
+
/**
|
|
25
|
+
* Pair a spec with a handler while preserving per-method input/output types.
|
|
26
|
+
*
|
|
27
|
+
* Constructing `{spec, handler}` literals widens `handler` to
|
|
28
|
+
* `ActionHandler<any, any>`, so spec/handler drift (renamed Zod schema,
|
|
29
|
+
* output field removal, input shape change) slips past the typechecker.
|
|
30
|
+
* `rpc_action(spec, handler)` binds the handler signature to
|
|
31
|
+
* `(input: z.infer<spec.input>, ctx) => z.infer<spec.output>` via the
|
|
32
|
+
* generic spec parameter — drift surfaces at the call site.
|
|
33
|
+
*
|
|
34
|
+
* Fits fuz_app's factory-closure pattern (handlers close over
|
|
35
|
+
* `grantable_roles`, `app_settings` ref, `notification_sender`, etc.).
|
|
36
|
+
* zzz uses a different shape — a codegen-keyed `Record<Method, Handler>`
|
|
37
|
+
* map typed via generated `ActionInputs`/`ActionOutputs` — which works when
|
|
38
|
+
* handlers are pure (no closure state) and specs are codegen-enumerated.
|
|
39
|
+
* fuz_app's admin + permit-offer actions have neither, so per-pair typing
|
|
40
|
+
* at the registration site is the right fit.
|
|
41
|
+
*/
|
|
42
|
+
export const rpc_action = (spec, handler) => ({
|
|
43
|
+
spec,
|
|
44
|
+
handler: handler,
|
|
45
|
+
});
|
|
22
46
|
/**
|
|
23
47
|
* Format a JSON-RPC error response.
|
|
24
48
|
*
|
|
@@ -49,15 +73,25 @@ const check_action_auth = (auth, request_context, credential_type) => {
|
|
|
49
73
|
if (auth === 'keeper') {
|
|
50
74
|
// keeper requires daemon_token credential type AND the keeper role.
|
|
51
75
|
// API tokens and session cookies cannot access keeper actions even
|
|
52
|
-
// if the account has the keeper permit.
|
|
76
|
+
// if the account has the keeper permit. Attach the credential type
|
|
77
|
+
// under `data` so clients can distinguish "wrong credential shape"
|
|
78
|
+
// from "missing keeper role" — mirrors REST 403 semantics.
|
|
53
79
|
if (credential_type !== 'daemon_token' || !has_role(request_context, 'keeper')) {
|
|
54
|
-
return jsonrpc_error_messages.forbidden(
|
|
80
|
+
return jsonrpc_error_messages.forbidden('forbidden', {
|
|
81
|
+
reason: ERROR_KEEPER_REQUIRES_DAEMON_TOKEN,
|
|
82
|
+
credential_type,
|
|
83
|
+
});
|
|
55
84
|
}
|
|
56
85
|
return null;
|
|
57
86
|
}
|
|
58
|
-
// role check
|
|
87
|
+
// role check — attach `required_role` under `data.required_role` so
|
|
88
|
+
// clients can render targeted copy (matches the former REST `PermissionError`
|
|
89
|
+
// shape that exposed `required_role` as a top-level field).
|
|
59
90
|
if (!has_role(request_context, auth.role)) {
|
|
60
|
-
return jsonrpc_error_messages.forbidden(`requires role: ${auth.role}
|
|
91
|
+
return jsonrpc_error_messages.forbidden(`requires role: ${auth.role}`, {
|
|
92
|
+
reason: ERROR_INSUFFICIENT_PERMISSIONS,
|
|
93
|
+
required_role: auth.role,
|
|
94
|
+
});
|
|
61
95
|
}
|
|
62
96
|
return null;
|
|
63
97
|
};
|
|
@@ -145,6 +179,7 @@ export const create_rpc_endpoint = (options) => {
|
|
|
145
179
|
}
|
|
146
180
|
};
|
|
147
181
|
const signal = c.req.raw.signal;
|
|
182
|
+
const client_ip = get_client_ip(c);
|
|
148
183
|
const execute = async (db) => {
|
|
149
184
|
const action_context = {
|
|
150
185
|
auth: request_context,
|
|
@@ -152,16 +187,17 @@ export const create_rpc_endpoint = (options) => {
|
|
|
152
187
|
db,
|
|
153
188
|
background_db: route.background_db,
|
|
154
189
|
pending_effects: route.pending_effects,
|
|
190
|
+
client_ip,
|
|
155
191
|
log,
|
|
156
192
|
notify,
|
|
157
193
|
signal,
|
|
158
194
|
};
|
|
159
195
|
const output = await action.handler(parse_result.data, action_context);
|
|
160
|
-
// DEV-only output validation
|
|
196
|
+
// DEV-only output validation — logs an error on mismatch, does not throw.
|
|
161
197
|
if (DEV) {
|
|
162
198
|
const output_result = action.spec.output.safeParse(output);
|
|
163
199
|
if (!output_result.success) {
|
|
164
|
-
log.
|
|
200
|
+
log.error(`RPC output schema mismatch: ${method_name}`, output_result.error.issues);
|
|
165
201
|
}
|
|
166
202
|
}
|
|
167
203
|
return c.json({ jsonrpc: JSONRPC_VERSION, id, result: output });
|
|
@@ -54,8 +54,8 @@ export interface BaseHandlerContext {
|
|
|
54
54
|
export type WsActionHandler<TCtx extends BaseHandlerContext = BaseHandlerContext> = (input: unknown, ctx: TCtx) => unknown;
|
|
55
55
|
/**
|
|
56
56
|
* A spec paired with its optional handler — the composable unit passed to
|
|
57
|
-
*
|
|
58
|
-
* both fields; the client reads only
|
|
57
|
+
* `register_action_ws` and `create_rpc_client`. The server uses
|
|
58
|
+
* both fields; the client reads only `spec` (the `handler` is
|
|
59
59
|
* ignored, harmless). Shared fuz_app primitives (e.g. `heartbeat_action`)
|
|
60
60
|
* export a complete tuple so consumers spread them into both sides'
|
|
61
61
|
* `actions` array without inventing per-repo ping plumbing.
|
package/dist/actions/cancel.d.ts
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
*
|
|
5
5
|
* Semantics: the client sends `{jsonrpc, method: 'cancel', params:
|
|
6
6
|
* {request_id}}` to abort an in-flight request on the same socket.
|
|
7
|
-
*
|
|
7
|
+
* `register_action_ws` intercepts this notification and aborts the
|
|
8
8
|
* matching pending request's `ctx.signal`. Unknown ids are no-ops by design —
|
|
9
9
|
* races between response arrival and cancel delivery are safe without extra
|
|
10
10
|
* coordination.
|
|
@@ -12,8 +12,8 @@
|
|
|
12
12
|
* The handler field is an empty stub: cancel semantics are dispatcher-owned
|
|
13
13
|
* (the dispatcher has the `{request_id → AbortController}` map, not the
|
|
14
14
|
* handler). The handler exists for symmetry with other composable primitives
|
|
15
|
-
* like
|
|
16
|
-
* spread
|
|
15
|
+
* like `heartbeat_action`; the dispatcher never calls it. Consumers
|
|
16
|
+
* spread `cancel_action` into their server's `actions` array so
|
|
17
17
|
* `spec_by_method` knows about it (enabling input validation on incoming
|
|
18
18
|
* cancels) and so `create_rpc_client` codegen produces `app.api.cancel()`
|
|
19
19
|
* when desired — though `FrontendWebsocketClient.request({signal})` sends
|
|
@@ -29,11 +29,9 @@
|
|
|
29
29
|
*/
|
|
30
30
|
import { z } from 'zod';
|
|
31
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
32
|
/**
|
|
35
|
-
* Params for
|
|
36
|
-
*
|
|
33
|
+
* Params for the `cancel` notification. `request_id` is the id of the
|
|
34
|
+
* pending request to abort. Must match the id of a request sent on the
|
|
37
35
|
* same socket; cancels from other sockets (or for unknown ids) are ignored.
|
|
38
36
|
*/
|
|
39
37
|
export declare const CancelNotificationParams: z.ZodObject<{
|
|
@@ -50,19 +48,20 @@ export type CancelNotificationParams = z.infer<typeof CancelNotificationParams>;
|
|
|
50
48
|
*/
|
|
51
49
|
export declare const cancel_action_spec: {
|
|
52
50
|
method: string;
|
|
53
|
-
initiator: "both" | "frontend" | "backend";
|
|
54
|
-
input: z.ZodType<unknown, unknown, z.core.$ZodTypeInternals<unknown, unknown>>;
|
|
55
|
-
description: string;
|
|
56
51
|
kind: "remote_notification";
|
|
52
|
+
initiator: "frontend";
|
|
57
53
|
auth: null;
|
|
58
54
|
side_effects: true;
|
|
55
|
+
input: z.ZodObject<{
|
|
56
|
+
request_id: z.ZodUnion<readonly [z.ZodString, z.ZodNumber]>;
|
|
57
|
+
}, z.core.$strict>;
|
|
59
58
|
output: z.ZodVoid;
|
|
60
59
|
async: true;
|
|
61
|
-
|
|
60
|
+
description: string;
|
|
62
61
|
};
|
|
63
62
|
/**
|
|
64
|
-
* Placeholder handler — cancel semantics are owned by
|
|
65
|
-
* not invoked per-handler. Exported for symmetry with the
|
|
63
|
+
* Placeholder handler — cancel semantics are owned by `register_action_ws`,
|
|
64
|
+
* not invoked per-handler. Exported for symmetry with the `Action`
|
|
66
65
|
* tuple shape; the dispatcher short-circuits cancel notifications before any
|
|
67
66
|
* handler lookup happens.
|
|
68
67
|
*/
|
|
@@ -1 +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
|
|
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;;;;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;;;;;;;;;;;;CAWS,CAAC;AAEzC;;;;;GAKG;AACH,eAAO,MAAM,cAAc,QAAO,IAAU,CAAC;AAE7C;;;;;;GAMG;AACH,eAAO,MAAM,aAAa,EAAE,MAG3B,CAAC"}
|
package/dist/actions/cancel.js
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
*
|
|
5
5
|
* Semantics: the client sends `{jsonrpc, method: 'cancel', params:
|
|
6
6
|
* {request_id}}` to abort an in-flight request on the same socket.
|
|
7
|
-
*
|
|
7
|
+
* `register_action_ws` intercepts this notification and aborts the
|
|
8
8
|
* matching pending request's `ctx.signal`. Unknown ids are no-ops by design —
|
|
9
9
|
* races between response arrival and cancel delivery are safe without extra
|
|
10
10
|
* coordination.
|
|
@@ -12,8 +12,8 @@
|
|
|
12
12
|
* The handler field is an empty stub: cancel semantics are dispatcher-owned
|
|
13
13
|
* (the dispatcher has the `{request_id → AbortController}` map, not the
|
|
14
14
|
* handler). The handler exists for symmetry with other composable primitives
|
|
15
|
-
* like
|
|
16
|
-
* spread
|
|
15
|
+
* like `heartbeat_action`; the dispatcher never calls it. Consumers
|
|
16
|
+
* spread `cancel_action` into their server's `actions` array so
|
|
17
17
|
* `spec_by_method` knows about it (enabling input validation on incoming
|
|
18
18
|
* cancels) and so `create_rpc_client` codegen produces `app.api.cancel()`
|
|
19
19
|
* when desired — though `FrontendWebsocketClient.request({signal})` sends
|
|
@@ -29,12 +29,9 @@
|
|
|
29
29
|
*/
|
|
30
30
|
import { z } from 'zod';
|
|
31
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
32
|
/**
|
|
36
|
-
* Params for
|
|
37
|
-
*
|
|
33
|
+
* Params for the `cancel` notification. `request_id` is the id of the
|
|
34
|
+
* pending request to abort. Must match the id of a request sent on the
|
|
38
35
|
* same socket; cancels from other sockets (or for unknown ids) are ignored.
|
|
39
36
|
*/
|
|
40
37
|
export const CancelNotificationParams = z.strictObject({
|
|
@@ -48,8 +45,8 @@ export const CancelNotificationParams = z.strictObject({
|
|
|
48
45
|
* ownership naturally: a different socket's cancel for the same id misses
|
|
49
46
|
* in its own map.
|
|
50
47
|
*/
|
|
51
|
-
export const cancel_action_spec =
|
|
52
|
-
method:
|
|
48
|
+
export const cancel_action_spec = {
|
|
49
|
+
method: 'cancel',
|
|
53
50
|
kind: 'remote_notification',
|
|
54
51
|
initiator: 'frontend',
|
|
55
52
|
auth: null,
|
|
@@ -58,10 +55,10 @@ export const cancel_action_spec = RemoteNotificationActionSpec.parse({
|
|
|
58
55
|
output: z.void(),
|
|
59
56
|
async: true,
|
|
60
57
|
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
|
-
}
|
|
58
|
+
};
|
|
62
59
|
/**
|
|
63
|
-
* Placeholder handler — cancel semantics are owned by
|
|
64
|
-
* not invoked per-handler. Exported for symmetry with the
|
|
60
|
+
* Placeholder handler — cancel semantics are owned by `register_action_ws`,
|
|
61
|
+
* not invoked per-handler. Exported for symmetry with the `Action`
|
|
65
62
|
* tuple shape; the dispatcher short-circuits cancel notifications before any
|
|
66
63
|
* handler lookup happens.
|
|
67
64
|
*/
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Shared heartbeat action — the first composable fuz_app primitive carrying
|
|
3
3
|
* both a spec and a handler in one tuple. Consumers spread
|
|
4
|
-
*
|
|
4
|
+
* `heartbeat_action` into both the server's and the client's `actions`
|
|
5
5
|
* array so disconnect detection works identically across every repo without
|
|
6
6
|
* per-consumer ping plumbing.
|
|
7
7
|
*
|
|
@@ -12,15 +12,13 @@
|
|
|
12
12
|
* alive without any handler-level state.
|
|
13
13
|
*
|
|
14
14
|
* Nullary input/output today. `{client_ts, server_ts}` fields can be added
|
|
15
|
-
* later if clock-skew telemetry ever matters — the
|
|
15
|
+
* later if clock-skew telemetry ever matters — the `Action` container
|
|
16
16
|
* is open for additions without churning consumer call sites.
|
|
17
17
|
*
|
|
18
18
|
* @module
|
|
19
19
|
*/
|
|
20
20
|
import { z } from 'zod';
|
|
21
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
22
|
/**
|
|
25
23
|
* `ActionSpec` for the shared heartbeat. `authenticated` auth — upgrade-time
|
|
26
24
|
* auth has already admitted the socket; heartbeats don't need role gating.
|
|
@@ -28,17 +26,14 @@ export declare const HEARTBEAT_METHOD = "heartbeat";
|
|
|
28
26
|
*/
|
|
29
27
|
export declare const heartbeat_action_spec: {
|
|
30
28
|
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
29
|
kind: "request_response";
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
30
|
+
initiator: "frontend";
|
|
31
|
+
auth: "authenticated";
|
|
32
|
+
side_effects: false;
|
|
33
|
+
input: z.ZodObject<{}, z.core.$strict>;
|
|
34
|
+
output: z.ZodObject<{}, z.core.$strict>;
|
|
40
35
|
async: true;
|
|
41
|
-
|
|
36
|
+
description: string;
|
|
42
37
|
};
|
|
43
38
|
/** Handler — nullary echo. Stateless, suitable for high-frequency pings. */
|
|
44
39
|
export declare const heartbeat_handler: () => Record<string, never>;
|
|
@@ -1 +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
|
|
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;;;;GAIG;AACH,eAAO,MAAM,qBAAqB;;;;;;;;;;CAUG,CAAC;AAEtC,4EAA4E;AAC5E,eAAO,MAAM,iBAAiB,QAAO,MAAM,CAAC,MAAM,EAAE,KAAK,CAAS,CAAC;AAEnE;;;;GAIG;AACH,eAAO,MAAM,gBAAgB,EAAE,MAG9B,CAAC"}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Shared heartbeat action — the first composable fuz_app primitive carrying
|
|
3
3
|
* both a spec and a handler in one tuple. Consumers spread
|
|
4
|
-
*
|
|
4
|
+
* `heartbeat_action` into both the server's and the client's `actions`
|
|
5
5
|
* array so disconnect detection works identically across every repo without
|
|
6
6
|
* per-consumer ping plumbing.
|
|
7
7
|
*
|
|
@@ -12,22 +12,19 @@
|
|
|
12
12
|
* alive without any handler-level state.
|
|
13
13
|
*
|
|
14
14
|
* Nullary input/output today. `{client_ts, server_ts}` fields can be added
|
|
15
|
-
* later if clock-skew telemetry ever matters — the
|
|
15
|
+
* later if clock-skew telemetry ever matters — the `Action` container
|
|
16
16
|
* is open for additions without churning consumer call sites.
|
|
17
17
|
*
|
|
18
18
|
* @module
|
|
19
19
|
*/
|
|
20
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
21
|
/**
|
|
25
22
|
* `ActionSpec` for the shared heartbeat. `authenticated` auth — upgrade-time
|
|
26
23
|
* auth has already admitted the socket; heartbeats don't need role gating.
|
|
27
24
|
* `side_effects: false` keeps it orthogonal to state changes.
|
|
28
25
|
*/
|
|
29
|
-
export const heartbeat_action_spec =
|
|
30
|
-
method:
|
|
26
|
+
export const heartbeat_action_spec = {
|
|
27
|
+
method: 'heartbeat',
|
|
31
28
|
kind: 'request_response',
|
|
32
29
|
initiator: 'frontend',
|
|
33
30
|
auth: 'authenticated',
|
|
@@ -36,7 +33,7 @@ export const heartbeat_action_spec = RequestResponseActionSpec.parse({
|
|
|
36
33
|
output: z.strictObject({}),
|
|
37
34
|
async: true,
|
|
38
35
|
description: 'Shared activity ping — keeps the socket alive and exercises the dispatch path.',
|
|
39
|
-
}
|
|
36
|
+
};
|
|
40
37
|
/** Handler — nullary echo. Stateless, suitable for high-frequency pings. */
|
|
41
38
|
export const heartbeat_handler = () => ({});
|
|
42
39
|
/**
|
|
@@ -79,8 +79,8 @@ export interface SocketCloseContext {
|
|
|
79
79
|
export interface ServerHeartbeatOptions {
|
|
80
80
|
/**
|
|
81
81
|
* Receive-silence (ms) past which the server closes the socket with
|
|
82
|
-
*
|
|
83
|
-
* the counter — chatty clients never trip it. First
|
|
82
|
+
* `WS_CLOSE_SERVER_HEARTBEAT_TIMEOUT`. Any incoming message resets
|
|
83
|
+
* the counter — chatty clients never trip it. First `timeout`
|
|
84
84
|
* window after socket open is exempt (cold-start grace).
|
|
85
85
|
*/
|
|
86
86
|
timeout?: number;
|
|
@@ -97,7 +97,7 @@ export interface RegisterActionWsOptions<TCtx extends BaseHandlerContext> {
|
|
|
97
97
|
* The actions registered on this endpoint — each carries a spec (drives
|
|
98
98
|
* method lookup, per-action auth, input/output validation) and an
|
|
99
99
|
* optional handler (omit for client-only specs like inbound
|
|
100
|
-
* notifications). Include the shared
|
|
100
|
+
* notifications). Include the shared `heartbeat_action` here to
|
|
101
101
|
* complete the disconnect-detection pairing with the frontend client.
|
|
102
102
|
*/
|
|
103
103
|
actions: ReadonlyArray<Action<TCtx>>;
|
|
@@ -39,7 +39,7 @@ import { jsonrpc_error_messages, ThrownJsonrpcError } from '../http/jsonrpc_erro
|
|
|
39
39
|
import { create_jsonrpc_error_response, create_jsonrpc_error_response_from_thrown, create_jsonrpc_notification, to_jsonrpc_message_id, to_jsonrpc_params, is_jsonrpc_request, } from '../http/jsonrpc_helpers.js';
|
|
40
40
|
import { CREDENTIAL_TYPE_KEY, AUTH_API_TOKEN_ID_KEY } from '../hono_context.js';
|
|
41
41
|
import {} from './action_types.js';
|
|
42
|
-
import {
|
|
42
|
+
import { cancel_action_spec, CancelNotificationParams } from './cancel.js';
|
|
43
43
|
import { WS_CLOSE_SERVER_HEARTBEAT_TIMEOUT } from './transports.js';
|
|
44
44
|
import { BackendWebsocketTransport } from './transports_ws_backend.js';
|
|
45
45
|
/** Default inactivity window before the server closes a silent socket. */
|
|
@@ -206,7 +206,7 @@ export const register_action_ws = (options) => {
|
|
|
206
206
|
// are not a feature yet).
|
|
207
207
|
if (!is_jsonrpc_request(json)) {
|
|
208
208
|
if (typeof json === 'object' && json !== null && 'method' in json && !('id' in json)) {
|
|
209
|
-
if (json.method ===
|
|
209
|
+
if (json.method === cancel_action_spec.method) {
|
|
210
210
|
const parsed = CancelNotificationParams.safeParse(json.params);
|
|
211
211
|
if (!parsed.success) {
|
|
212
212
|
log.debug('cancel: invalid params, ignoring', parsed.error.issues);
|
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
* 3. Optional `require_role(required_role)` — for endpoints gated to a
|
|
11
11
|
* specific role.
|
|
12
12
|
*
|
|
13
|
-
* Then delegates to
|
|
13
|
+
* Then delegates to `register_action_ws` for per-message JSON-RPC
|
|
14
14
|
* dispatch.
|
|
15
15
|
*
|
|
16
16
|
* @module
|
|
@@ -18,7 +18,7 @@
|
|
|
18
18
|
import type { RoleName } from '../auth/role_schema.js';
|
|
19
19
|
import { type RegisterActionWsOptions, type RegisterActionWsResult } from './register_action_ws.js';
|
|
20
20
|
import type { BaseHandlerContext } from './action_types.js';
|
|
21
|
-
/** Options for
|
|
21
|
+
/** Options for `register_ws_endpoint`. */
|
|
22
22
|
export interface RegisterWsEndpointOptions<TCtx extends BaseHandlerContext> extends RegisterActionWsOptions<TCtx> {
|
|
23
23
|
/**
|
|
24
24
|
* Origin allowlist regexes — typically parsed from the `ALLOWED_ORIGINS`
|
|
@@ -38,8 +38,8 @@ export interface RegisterWsEndpointOptions<TCtx extends BaseHandlerContext> exte
|
|
|
38
38
|
* Mount a WebSocket endpoint with the standard upgrade stack (origin check
|
|
39
39
|
* + auth + optional role) and JSON-RPC dispatch.
|
|
40
40
|
*
|
|
41
|
-
* Returns the
|
|
42
|
-
* created), same as
|
|
41
|
+
* Returns the `BackendWebsocketTransport` (supplied or freshly
|
|
42
|
+
* created), same as `register_action_ws` — retain it to wire
|
|
43
43
|
* `create_ws_auth_guard` on `on_audit_event` or to broadcast.
|
|
44
44
|
*/
|
|
45
45
|
export declare const register_ws_endpoint: <TCtx extends BaseHandlerContext>(options: RegisterWsEndpointOptions<TCtx>) => RegisterActionWsResult;
|
|
@@ -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;;;;;;;;;;;;;;;;GAgBG;AAMH,OAAO,KAAK,EAAC,QAAQ,EAAC,MAAM,wBAAwB,CAAC;AACrD,OAAO,EAEN,KAAK,uBAAuB,EAC5B,KAAK,sBAAsB,EAC3B,MAAM,yBAAyB,CAAC;AACjC,OAAO,KAAK,EAAC,kBAAkB,EAAC,MAAM,mBAAmB,CAAC;AAE1D,
|
|
1
|
+
{"version":3,"file":"register_ws_endpoint.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/actions/register_ws_endpoint.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AAMH,OAAO,KAAK,EAAC,QAAQ,EAAC,MAAM,wBAAwB,CAAC;AACrD,OAAO,EAEN,KAAK,uBAAuB,EAC5B,KAAK,sBAAsB,EAC3B,MAAM,yBAAyB,CAAC;AACjC,OAAO,KAAK,EAAC,kBAAkB,EAAC,MAAM,mBAAmB,CAAC;AAE1D,0CAA0C;AAC1C,MAAM,WAAW,yBAAyB,CACzC,IAAI,SAAS,kBAAkB,CAC9B,SAAQ,uBAAuB,CAAC,IAAI,CAAC;IACtC;;;;OAIG;IACH,eAAe,EAAE,KAAK,CAAC,MAAM,CAAC,CAAC;IAC/B;;;;;OAKG;IACH,aAAa,CAAC,EAAE,QAAQ,CAAC;CACzB;AAED;;;;;;;GAOG;AACH,eAAO,MAAM,oBAAoB,GAAI,IAAI,SAAS,kBAAkB,EACnE,SAAS,yBAAyB,CAAC,IAAI,CAAC,KACtC,sBAUF,CAAC"}
|
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
* 3. Optional `require_role(required_role)` — for endpoints gated to a
|
|
11
11
|
* specific role.
|
|
12
12
|
*
|
|
13
|
-
* Then delegates to
|
|
13
|
+
* Then delegates to `register_action_ws` for per-message JSON-RPC
|
|
14
14
|
* dispatch.
|
|
15
15
|
*
|
|
16
16
|
* @module
|
|
@@ -23,8 +23,8 @@ import { register_action_ws, } from './register_action_ws.js';
|
|
|
23
23
|
* Mount a WebSocket endpoint with the standard upgrade stack (origin check
|
|
24
24
|
* + auth + optional role) and JSON-RPC dispatch.
|
|
25
25
|
*
|
|
26
|
-
* Returns the
|
|
27
|
-
* created), same as
|
|
26
|
+
* Returns the `BackendWebsocketTransport` (supplied or freshly
|
|
27
|
+
* created), same as `register_action_ws` — retain it to wire
|
|
28
28
|
* `create_ws_auth_guard` on `on_audit_event` or to broadcast.
|
|
29
29
|
*/
|
|
30
30
|
export const register_ws_endpoint = (options) => {
|
|
@@ -65,4 +65,33 @@ export declare const create_rpc_client: (options: CreateRpcClientOptions) => Rec
|
|
|
65
65
|
*/
|
|
66
66
|
export interface RpcClientCallOptions extends ActionPeerSendOptions {
|
|
67
67
|
}
|
|
68
|
+
/**
|
|
69
|
+
* `method, input -> unwrapped output` signature for adapter wiring.
|
|
70
|
+
*
|
|
71
|
+
* The typed `create_rpc_client` Proxy returns `Result<T, JsonrpcErrorObject>`
|
|
72
|
+
* on every call. UI adapters (e.g. `admin_rpc_adapters.ts`) want a
|
|
73
|
+
* throw-on-error shape so form components can match on `error.data.reason`
|
|
74
|
+
* via catch blocks. `create_throwing_rpc_call` bridges the two.
|
|
75
|
+
*/
|
|
76
|
+
export type ThrowingRpcCall = <TOutput = unknown>(method: string, input?: unknown) => Promise<TOutput>;
|
|
77
|
+
/**
|
|
78
|
+
* Wrap a typed RPC client so every call returns its unwrapped value or throws.
|
|
79
|
+
*
|
|
80
|
+
* On `{ok: false}`, throws an `Error` with the JSON-RPC error object's
|
|
81
|
+
* `{code, message, data}` spread onto it — so catch blocks that inspect
|
|
82
|
+
* `err.data?.reason` continue to work. On unknown method, throws a clear
|
|
83
|
+
* "rpc method not found" error instead of the cryptic `undefined is not a
|
|
84
|
+
* function` that would otherwise surface.
|
|
85
|
+
*
|
|
86
|
+
* Invariant upheld by `create_rpc_client`: every `{ok: false}` return
|
|
87
|
+
* carries a well-formed `JsonrpcErrorObject` with `code` + `message`.
|
|
88
|
+
* Callers must still use optional chaining on `err.data` because the
|
|
89
|
+
* JSON-RPC `data` field is spec-level optional — a handler that throws
|
|
90
|
+
* `jsonrpc_errors.forbidden()` without a `data` argument produces
|
|
91
|
+
* `err.data === undefined`.
|
|
92
|
+
*
|
|
93
|
+
* @param api - typed RPC client from `create_rpc_client` (or any Proxy-like
|
|
94
|
+
* object mapping method names to `(input) => Promise<Result<T, error>>`)
|
|
95
|
+
*/
|
|
96
|
+
export declare const create_throwing_rpc_call: (api: Record<string, ((input?: any) => Promise<any>) | undefined>) => ThrowingRpcCall;
|
|
68
97
|
//# sourceMappingURL=rpc_client.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"rpc_client.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/actions/rpc_client.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAQH,OAAO,KAAK,EAAC,sBAAsB,EAAC,MAAM,yBAAyB,CAAC;AAOpE,OAAO,KAAK,EAAC,UAAU,EAAE,qBAAqB,EAAC,MAAM,kBAAkB,CAAC;AACxE,OAAO,KAAK,EAAC,oBAAoB,EAAC,MAAM,wBAAwB,CAAC;AACjE,OAAO,KAAK,EAAC,aAAa,EAAC,MAAM,iBAAiB,CAAC;AAGnD;;;;;;;GAOG;AACH,MAAM,MAAM,kBAAkB,GAAG,CAAC,MAAM,EAAE,MAAM,KAAK,aAAa,GAAG,SAAS,CAAC;AAM/E,8EAA8E;AAC9E,MAAM,WAAW,sBAAsB;IACtC,aAAa,EAAE,CAAC,IAAI,EAAE;QAAC,MAAM,EAAE,MAAM,CAAC;QAAC,iBAAiB,EAAE,oBAAoB,CAAA;KAAC,KAC5E;QACA,sBAAsB,EAAE,CAAC,KAAK,EAAE,GAAG,KAAK,IAAI,CAAC;KAC5C,GACD,SAAS,CAAC;CACb;AAED,uCAAuC;AACvC,MAAM,WAAW,sBAAsB;IACtC,IAAI,EAAE,UAAU,CAAC;IACjB,WAAW,EAAE,sBAAsB,CAAC;IACpC,kEAAkE;IAClE,OAAO,CAAC,EAAE,sBAAsB,CAAC;IACjC;;;;;OAKG;IACH,oBAAoB,CAAC,EAAE,kBAAkB,CAAC;CAC1C;AAED;;;;;;;;;;GAUG;AACH,eAAO,MAAM,iBAAiB,GAC7B,SAAS,sBAAsB,KAC7B,MAAM,CAAC,MAAM,EAAE,CAAC,GAAG,IAAI,EAAE,KAAK,CAAC,GAAG,CAAC,KAAK,GAAG,CAgB7C,CAAC;AA2DF;;;;;GAKG;AACH,MAAM,WAAW,oBAAqB,SAAQ,qBAAqB;CAAG"}
|
|
1
|
+
{"version":3,"file":"rpc_client.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/actions/rpc_client.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAQH,OAAO,KAAK,EAAC,sBAAsB,EAAC,MAAM,yBAAyB,CAAC;AAOpE,OAAO,KAAK,EAAC,UAAU,EAAE,qBAAqB,EAAC,MAAM,kBAAkB,CAAC;AACxE,OAAO,KAAK,EAAC,oBAAoB,EAAC,MAAM,wBAAwB,CAAC;AACjE,OAAO,KAAK,EAAC,aAAa,EAAC,MAAM,iBAAiB,CAAC;AAGnD;;;;;;;GAOG;AACH,MAAM,MAAM,kBAAkB,GAAG,CAAC,MAAM,EAAE,MAAM,KAAK,aAAa,GAAG,SAAS,CAAC;AAM/E,8EAA8E;AAC9E,MAAM,WAAW,sBAAsB;IACtC,aAAa,EAAE,CAAC,IAAI,EAAE;QAAC,MAAM,EAAE,MAAM,CAAC;QAAC,iBAAiB,EAAE,oBAAoB,CAAA;KAAC,KAC5E;QACA,sBAAsB,EAAE,CAAC,KAAK,EAAE,GAAG,KAAK,IAAI,CAAC;KAC5C,GACD,SAAS,CAAC;CACb;AAED,uCAAuC;AACvC,MAAM,WAAW,sBAAsB;IACtC,IAAI,EAAE,UAAU,CAAC;IACjB,WAAW,EAAE,sBAAsB,CAAC;IACpC,kEAAkE;IAClE,OAAO,CAAC,EAAE,sBAAsB,CAAC;IACjC;;;;;OAKG;IACH,oBAAoB,CAAC,EAAE,kBAAkB,CAAC;CAC1C;AAED;;;;;;;;;;GAUG;AACH,eAAO,MAAM,iBAAiB,GAC7B,SAAS,sBAAsB,KAC7B,MAAM,CAAC,MAAM,EAAE,CAAC,GAAG,IAAI,EAAE,KAAK,CAAC,GAAG,CAAC,KAAK,GAAG,CAgB7C,CAAC;AA2DF;;;;;GAKG;AACH,MAAM,WAAW,oBAAqB,SAAQ,qBAAqB;CAAG;AAgItE;;;;;;;GAOG;AACH,MAAM,MAAM,eAAe,GAAG,CAAC,OAAO,GAAG,OAAO,EAC/C,MAAM,EAAE,MAAM,EACd,KAAK,CAAC,EAAE,OAAO,KACX,OAAO,CAAC,OAAO,CAAC,CAAC;AAEtB;;;;;;;;;;;;;;;;;;GAkBG;AACH,eAAO,MAAM,wBAAwB,GACpC,KAAK,MAAM,CAAC,MAAM,EAAE,CAAC,CAAC,KAAK,CAAC,EAAE,GAAG,KAAK,OAAO,CAAC,GAAG,CAAC,CAAC,GAAG,SAAS,CAAC,KAC9D,eAUF,CAAC"}
|
|
@@ -168,3 +168,34 @@ const create_remote_notification_method = (peer, environment, spec, actions, tra
|
|
|
168
168
|
return extract_action_result(event);
|
|
169
169
|
};
|
|
170
170
|
};
|
|
171
|
+
/**
|
|
172
|
+
* Wrap a typed RPC client so every call returns its unwrapped value or throws.
|
|
173
|
+
*
|
|
174
|
+
* On `{ok: false}`, throws an `Error` with the JSON-RPC error object's
|
|
175
|
+
* `{code, message, data}` spread onto it — so catch blocks that inspect
|
|
176
|
+
* `err.data?.reason` continue to work. On unknown method, throws a clear
|
|
177
|
+
* "rpc method not found" error instead of the cryptic `undefined is not a
|
|
178
|
+
* function` that would otherwise surface.
|
|
179
|
+
*
|
|
180
|
+
* Invariant upheld by `create_rpc_client`: every `{ok: false}` return
|
|
181
|
+
* carries a well-formed `JsonrpcErrorObject` with `code` + `message`.
|
|
182
|
+
* Callers must still use optional chaining on `err.data` because the
|
|
183
|
+
* JSON-RPC `data` field is spec-level optional — a handler that throws
|
|
184
|
+
* `jsonrpc_errors.forbidden()` without a `data` argument produces
|
|
185
|
+
* `err.data === undefined`.
|
|
186
|
+
*
|
|
187
|
+
* @param api - typed RPC client from `create_rpc_client` (or any Proxy-like
|
|
188
|
+
* object mapping method names to `(input) => Promise<Result<T, error>>`)
|
|
189
|
+
*/
|
|
190
|
+
export const create_throwing_rpc_call = (api) => {
|
|
191
|
+
return async (method, input) => {
|
|
192
|
+
const fn = api[method];
|
|
193
|
+
if (!fn)
|
|
194
|
+
throw new Error(`rpc method not found: ${method}`);
|
|
195
|
+
const result = await fn(input);
|
|
196
|
+
if (!result.ok) {
|
|
197
|
+
throw Object.assign(new Error(result.error?.message ?? 'rpc error'), result.error);
|
|
198
|
+
}
|
|
199
|
+
return result.value;
|
|
200
|
+
};
|
|
201
|
+
};
|