@fuzdev/fuz_app 0.55.0 → 0.56.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 +211 -155
- package/dist/actions/action_bridge.d.ts +8 -5
- package/dist/actions/action_bridge.d.ts.map +1 -1
- package/dist/actions/action_bridge.js +1 -11
- package/dist/actions/action_codegen.d.ts +19 -0
- package/dist/actions/action_codegen.d.ts.map +1 -1
- package/dist/actions/action_codegen.js +20 -14
- package/dist/actions/action_registry.d.ts.map +1 -1
- package/dist/actions/action_registry.js +5 -2
- package/dist/actions/action_rpc.d.ts +110 -44
- package/dist/actions/action_rpc.d.ts.map +1 -1
- package/dist/actions/action_rpc.js +92 -287
- package/dist/actions/action_spec.d.ts +55 -16
- package/dist/actions/action_spec.d.ts.map +1 -1
- package/dist/actions/action_spec.js +16 -11
- package/dist/actions/action_types.d.ts +28 -60
- package/dist/actions/action_types.d.ts.map +1 -1
- package/dist/actions/action_types.js +13 -5
- package/dist/actions/broadcast_api.d.ts +2 -2
- package/dist/actions/broadcast_api.js +2 -2
- package/dist/actions/compile_action_registry.d.ts +50 -0
- package/dist/actions/compile_action_registry.d.ts.map +1 -0
- package/dist/actions/compile_action_registry.js +69 -0
- package/dist/actions/heartbeat.d.ts +8 -4
- package/dist/actions/heartbeat.d.ts.map +1 -1
- package/dist/actions/heartbeat.js +5 -4
- package/dist/actions/perform_action.d.ts +145 -0
- package/dist/actions/perform_action.d.ts.map +1 -0
- package/dist/actions/perform_action.js +258 -0
- package/dist/actions/register_action_ws.d.ts +44 -38
- package/dist/actions/register_action_ws.d.ts.map +1 -1
- package/dist/actions/register_action_ws.js +101 -159
- package/dist/actions/register_ws_endpoint.d.ts +2 -10
- package/dist/actions/register_ws_endpoint.d.ts.map +1 -1
- package/dist/actions/register_ws_endpoint.js +32 -10
- package/dist/actions/transports_ws_auth_guard.d.ts +1 -1
- package/dist/actions/transports_ws_auth_guard.js +1 -1
- package/dist/actions/transports_ws_backend.d.ts +1 -1
- package/dist/actions/transports_ws_backend.js +1 -1
- package/dist/auth/CLAUDE.md +673 -442
- package/dist/auth/account_action_specs.d.ts +28 -7
- package/dist/auth/account_action_specs.d.ts.map +1 -1
- package/dist/auth/account_action_specs.js +7 -7
- package/dist/auth/account_actions.d.ts +8 -14
- package/dist/auth/account_actions.d.ts.map +1 -1
- package/dist/auth/account_actions.js +26 -32
- package/dist/auth/account_queries.d.ts +46 -13
- package/dist/auth/account_queries.d.ts.map +1 -1
- package/dist/auth/account_queries.js +73 -33
- package/dist/auth/account_routes.d.ts +4 -3
- package/dist/auth/account_routes.d.ts.map +1 -1
- package/dist/auth/account_routes.js +58 -33
- package/dist/auth/account_schema.d.ts +46 -54
- package/dist/auth/account_schema.d.ts.map +1 -1
- package/dist/auth/account_schema.js +21 -48
- package/dist/auth/admin_action_specs.d.ts +55 -21
- package/dist/auth/admin_action_specs.d.ts.map +1 -1
- package/dist/auth/admin_action_specs.js +42 -26
- package/dist/auth/admin_actions.d.ts +14 -21
- package/dist/auth/admin_actions.d.ts.map +1 -1
- package/dist/auth/admin_actions.js +47 -44
- package/dist/auth/audit_emitter.d.ts +160 -0
- package/dist/auth/audit_emitter.d.ts.map +1 -0
- package/dist/auth/audit_emitter.js +83 -0
- package/dist/auth/audit_log_queries.d.ts +17 -87
- package/dist/auth/audit_log_queries.d.ts.map +1 -1
- package/dist/auth/audit_log_queries.js +17 -96
- package/dist/auth/audit_log_routes.d.ts +1 -1
- package/dist/auth/audit_log_routes.d.ts.map +1 -1
- package/dist/auth/audit_log_routes.js +7 -3
- package/dist/auth/audit_log_schema.d.ts +48 -42
- package/dist/auth/audit_log_schema.d.ts.map +1 -1
- package/dist/auth/audit_log_schema.js +56 -43
- package/dist/auth/auth_guard_resolver.d.ts +44 -0
- package/dist/auth/auth_guard_resolver.d.ts.map +1 -0
- package/dist/auth/auth_guard_resolver.js +56 -0
- package/dist/auth/bootstrap_account.d.ts +7 -7
- package/dist/auth/bootstrap_account.d.ts.map +1 -1
- package/dist/auth/bootstrap_account.js +7 -7
- package/dist/auth/bootstrap_routes.d.ts.map +1 -1
- package/dist/auth/bootstrap_routes.js +11 -10
- package/dist/auth/cleanup.d.ts +20 -26
- package/dist/auth/cleanup.d.ts.map +1 -1
- package/dist/auth/cleanup.js +33 -47
- package/dist/auth/credential_type_schema.d.ts +115 -0
- package/dist/auth/credential_type_schema.d.ts.map +1 -0
- package/dist/auth/credential_type_schema.js +127 -0
- package/dist/auth/daemon_token_middleware.d.ts +1 -1
- package/dist/auth/daemon_token_middleware.js +3 -3
- package/dist/auth/ddl.d.ts +2 -2
- package/dist/auth/ddl.d.ts.map +1 -1
- package/dist/auth/ddl.js +6 -6
- package/dist/auth/deps.d.ts +7 -32
- package/dist/auth/deps.d.ts.map +1 -1
- package/dist/auth/grant_path_schema.d.ts +117 -0
- package/dist/auth/grant_path_schema.d.ts.map +1 -0
- package/dist/auth/grant_path_schema.js +137 -0
- package/dist/auth/invite_queries.d.ts +12 -1
- package/dist/auth/invite_queries.d.ts.map +1 -1
- package/dist/auth/invite_queries.js +12 -1
- package/dist/auth/invite_schema.d.ts +1 -1
- package/dist/auth/invite_schema.d.ts.map +1 -1
- package/dist/auth/invite_schema.js +1 -1
- package/dist/auth/middleware.d.ts.map +1 -1
- package/dist/auth/middleware.js +5 -2
- package/dist/auth/migrations.d.ts +22 -7
- package/dist/auth/migrations.d.ts.map +1 -1
- package/dist/auth/migrations.js +64 -25
- package/dist/auth/request_context.d.ts +157 -170
- package/dist/auth/request_context.d.ts.map +1 -1
- package/dist/auth/request_context.js +224 -268
- package/dist/auth/{permit_offer_action_specs.d.ts → role_grant_offer_action_specs.d.ts} +130 -100
- package/dist/auth/role_grant_offer_action_specs.d.ts.map +1 -0
- package/dist/auth/role_grant_offer_action_specs.js +262 -0
- package/dist/auth/role_grant_offer_actions.d.ts +104 -0
- package/dist/auth/role_grant_offer_actions.d.ts.map +1 -0
- package/dist/auth/{permit_offer_actions.js → role_grant_offer_actions.js} +153 -140
- package/dist/auth/{permit_offer_notifications.d.ts → role_grant_offer_notifications.d.ts} +80 -70
- package/dist/auth/role_grant_offer_notifications.d.ts.map +1 -0
- package/dist/auth/role_grant_offer_notifications.js +182 -0
- package/dist/auth/{permit_offer_queries.d.ts → role_grant_offer_queries.d.ts} +64 -64
- package/dist/auth/role_grant_offer_queries.d.ts.map +1 -0
- package/dist/auth/{permit_offer_queries.js → role_grant_offer_queries.js} +136 -123
- package/dist/auth/role_grant_offer_schema.d.ts +150 -0
- package/dist/auth/role_grant_offer_schema.d.ts.map +1 -0
- package/dist/auth/{permit_offer_schema.js → role_grant_offer_schema.js} +55 -36
- package/dist/auth/role_grant_queries.d.ts +231 -0
- package/dist/auth/role_grant_queries.d.ts.map +1 -0
- package/dist/auth/role_grant_queries.js +320 -0
- package/dist/auth/role_schema.d.ts +150 -40
- package/dist/auth/role_schema.d.ts.map +1 -1
- package/dist/auth/role_schema.js +144 -45
- package/dist/auth/scope_kind_schema.d.ts +96 -0
- package/dist/auth/scope_kind_schema.d.ts.map +1 -0
- package/dist/auth/scope_kind_schema.js +94 -0
- package/dist/auth/self_service_role_action_specs.d.ts +4 -1
- package/dist/auth/self_service_role_action_specs.d.ts.map +1 -1
- package/dist/auth/self_service_role_action_specs.js +2 -2
- package/dist/auth/self_service_role_actions.d.ts +35 -29
- package/dist/auth/self_service_role_actions.d.ts.map +1 -1
- package/dist/auth/self_service_role_actions.js +58 -48
- package/dist/auth/session_cookie.d.ts +43 -6
- package/dist/auth/session_cookie.d.ts.map +1 -1
- package/dist/auth/session_cookie.js +31 -5
- package/dist/auth/session_middleware.d.ts +37 -3
- package/dist/auth/session_middleware.d.ts.map +1 -1
- package/dist/auth/session_middleware.js +33 -7
- package/dist/auth/signup_routes.d.ts.map +1 -1
- package/dist/auth/signup_routes.js +48 -19
- package/dist/auth/standard_action_specs.d.ts +2 -2
- package/dist/auth/standard_action_specs.js +4 -4
- package/dist/auth/standard_rpc_actions.d.ts +23 -19
- package/dist/auth/standard_rpc_actions.d.ts.map +1 -1
- package/dist/auth/standard_rpc_actions.js +12 -12
- package/dist/db/migrate.d.ts +1 -1
- package/dist/db/migrate.js +1 -1
- package/dist/dev/setup.d.ts +2 -2
- package/dist/dev/setup.d.ts.map +1 -1
- package/dist/dev/setup.js +4 -4
- package/dist/env/load.d.ts +1 -1
- package/dist/env/load.js +1 -1
- package/dist/hono_context.d.ts +27 -45
- package/dist/hono_context.d.ts.map +1 -1
- package/dist/hono_context.js +14 -28
- package/dist/http/CLAUDE.md +235 -121
- package/dist/http/auth_shape.d.ts +191 -0
- package/dist/http/auth_shape.d.ts.map +1 -0
- package/dist/http/auth_shape.js +237 -0
- package/dist/http/common_routes.js +3 -3
- package/dist/http/db_routes.d.ts +4 -0
- package/dist/http/db_routes.d.ts.map +1 -1
- package/dist/http/db_routes.js +44 -7
- package/dist/http/error_schemas.d.ts +56 -34
- package/dist/http/error_schemas.d.ts.map +1 -1
- package/dist/http/error_schemas.js +63 -28
- package/dist/http/pending_effects.d.ts +71 -18
- package/dist/http/pending_effects.d.ts.map +1 -1
- package/dist/http/pending_effects.js +87 -18
- package/dist/http/proxy.d.ts +52 -5
- package/dist/http/proxy.d.ts.map +1 -1
- package/dist/http/proxy.js +92 -14
- package/dist/http/route_spec.d.ts +89 -75
- package/dist/http/route_spec.d.ts.map +1 -1
- package/dist/http/route_spec.js +54 -72
- package/dist/http/schema_helpers.d.ts +3 -14
- package/dist/http/schema_helpers.d.ts.map +1 -1
- package/dist/http/schema_helpers.js +2 -14
- package/dist/http/surface.d.ts +2 -10
- package/dist/http/surface.d.ts.map +1 -1
- package/dist/http/surface.js +3 -4
- package/dist/http/surface_query.d.ts +39 -35
- package/dist/http/surface_query.d.ts.map +1 -1
- package/dist/http/surface_query.js +79 -36
- package/dist/primitive_schemas.d.ts +39 -0
- package/dist/primitive_schemas.d.ts.map +1 -0
- package/dist/primitive_schemas.js +40 -0
- package/dist/realtime/sse_auth_guard.d.ts +5 -5
- package/dist/realtime/sse_auth_guard.js +9 -9
- package/dist/runtime/mock.d.ts +1 -1
- package/dist/runtime/mock.js +1 -1
- package/dist/server/app_backend.d.ts +14 -11
- package/dist/server/app_backend.d.ts.map +1 -1
- package/dist/server/app_backend.js +12 -8
- package/dist/server/app_server.d.ts +7 -7
- package/dist/server/app_server.d.ts.map +1 -1
- package/dist/server/app_server.js +35 -40
- package/dist/server/validate_nginx.d.ts +1 -1
- package/dist/server/validate_nginx.js +1 -1
- package/dist/testing/CLAUDE.md +50 -38
- package/dist/testing/admin_integration.d.ts +5 -6
- package/dist/testing/admin_integration.d.ts.map +1 -1
- package/dist/testing/admin_integration.js +87 -85
- package/dist/testing/app_server.d.ts +11 -14
- package/dist/testing/app_server.d.ts.map +1 -1
- package/dist/testing/app_server.js +16 -15
- package/dist/testing/assertions.d.ts.map +1 -1
- package/dist/testing/assertions.js +2 -1
- package/dist/testing/attack_surface.d.ts.map +1 -1
- package/dist/testing/attack_surface.js +15 -9
- package/dist/testing/audit_completeness.d.ts +2 -2
- package/dist/testing/audit_completeness.d.ts.map +1 -1
- package/dist/testing/audit_completeness.js +36 -36
- package/dist/testing/auth_apps.d.ts +5 -4
- package/dist/testing/auth_apps.d.ts.map +1 -1
- package/dist/testing/auth_apps.js +22 -19
- package/dist/testing/data_exposure.d.ts.map +1 -1
- package/dist/testing/data_exposure.js +5 -5
- package/dist/testing/db.d.ts +1 -1
- package/dist/testing/db.d.ts.map +1 -1
- package/dist/testing/db.js +4 -4
- package/dist/testing/db_entities.d.ts +22 -0
- package/dist/testing/db_entities.d.ts.map +1 -0
- package/dist/testing/db_entities.js +28 -0
- package/dist/testing/entities.d.ts +8 -7
- package/dist/testing/entities.d.ts.map +1 -1
- package/dist/testing/entities.js +21 -18
- package/dist/testing/integration.d.ts.map +1 -1
- package/dist/testing/integration.js +13 -14
- package/dist/testing/integration_helpers.d.ts +4 -4
- package/dist/testing/integration_helpers.d.ts.map +1 -1
- package/dist/testing/integration_helpers.js +20 -18
- package/dist/testing/middleware.d.ts +4 -4
- package/dist/testing/middleware.d.ts.map +1 -1
- package/dist/testing/middleware.js +12 -11
- package/dist/testing/rpc_attack_surface.d.ts.map +1 -1
- package/dist/testing/rpc_attack_surface.js +40 -24
- package/dist/testing/rpc_round_trip.d.ts +1 -1
- package/dist/testing/rpc_round_trip.d.ts.map +1 -1
- package/dist/testing/rpc_round_trip.js +14 -13
- package/dist/testing/sse_round_trip.d.ts +3 -4
- package/dist/testing/sse_round_trip.d.ts.map +1 -1
- package/dist/testing/sse_round_trip.js +7 -11
- package/dist/testing/standard.d.ts +1 -1
- package/dist/testing/stubs.d.ts +25 -0
- package/dist/testing/stubs.d.ts.map +1 -1
- package/dist/testing/stubs.js +43 -2
- package/dist/testing/surface_invariants.d.ts +2 -2
- package/dist/testing/ws_round_trip.d.ts +12 -13
- package/dist/testing/ws_round_trip.d.ts.map +1 -1
- package/dist/testing/ws_round_trip.js +19 -11
- package/dist/ui/AdminAccounts.svelte +23 -20
- package/dist/ui/AdminOverview.svelte +15 -13
- package/dist/ui/AdminOverview.svelte.d.ts.map +1 -1
- package/dist/ui/{AdminPermitHistory.svelte → AdminRoleGrantHistory.svelte} +12 -12
- package/dist/ui/AdminRoleGrantHistory.svelte.d.ts +4 -0
- package/dist/ui/AdminRoleGrantHistory.svelte.d.ts.map +1 -0
- package/dist/ui/BootstrapForm.svelte +1 -1
- package/dist/ui/CLAUDE.md +60 -60
- package/dist/ui/{PermitOfferForm.svelte → RoleGrantOfferForm.svelte} +27 -26
- package/dist/ui/{PermitOfferForm.svelte.d.ts → RoleGrantOfferForm.svelte.d.ts} +7 -7
- package/dist/ui/RoleGrantOfferForm.svelte.d.ts.map +1 -0
- package/dist/ui/{PermitOfferHistory.svelte → RoleGrantOfferHistory.svelte} +12 -12
- package/dist/ui/{PermitOfferHistory.svelte.d.ts → RoleGrantOfferHistory.svelte.d.ts} +4 -4
- package/dist/ui/RoleGrantOfferHistory.svelte.d.ts.map +1 -0
- package/dist/ui/{PermitOfferInbox.svelte → RoleGrantOfferInbox.svelte} +14 -14
- package/dist/ui/{PermitOfferInbox.svelte.d.ts → RoleGrantOfferInbox.svelte.d.ts} +4 -4
- package/dist/ui/RoleGrantOfferInbox.svelte.d.ts.map +1 -0
- package/dist/ui/SignupForm.svelte +1 -1
- package/dist/ui/SurfaceExplorer.svelte +35 -15
- package/dist/ui/SurfaceExplorer.svelte.d.ts.map +1 -1
- package/dist/ui/account_sessions_state.svelte.d.ts +2 -3
- package/dist/ui/account_sessions_state.svelte.d.ts.map +1 -1
- package/dist/ui/account_sessions_state.svelte.js +2 -3
- package/dist/ui/admin_accounts_state.svelte.d.ts +18 -18
- package/dist/ui/admin_accounts_state.svelte.d.ts.map +1 -1
- package/dist/ui/admin_accounts_state.svelte.js +16 -16
- package/dist/ui/admin_rpc_adapters.d.ts +20 -20
- package/dist/ui/admin_rpc_adapters.d.ts.map +1 -1
- package/dist/ui/admin_rpc_adapters.js +17 -17
- package/dist/ui/admin_sessions_state.svelte.d.ts +2 -2
- package/dist/ui/admin_sessions_state.svelte.js +2 -2
- package/dist/ui/audit_log_state.svelte.d.ts +7 -7
- package/dist/ui/audit_log_state.svelte.d.ts.map +1 -1
- package/dist/ui/audit_log_state.svelte.js +6 -6
- package/dist/ui/auth_state.svelte.d.ts +3 -3
- package/dist/ui/auth_state.svelte.d.ts.map +1 -1
- package/dist/ui/auth_state.svelte.js +6 -6
- package/dist/ui/format_scope.d.ts +2 -2
- package/dist/ui/format_scope.js +2 -2
- package/dist/ui/{permit_offers_state.svelte.d.ts → role_grant_offers_state.svelte.d.ts} +30 -30
- package/dist/ui/role_grant_offers_state.svelte.d.ts.map +1 -0
- package/dist/ui/{permit_offers_state.svelte.js → role_grant_offers_state.svelte.js} +18 -18
- package/dist/ui/ui_format.js +2 -2
- package/package.json +3 -3
- package/dist/auth/permit_offer_action_specs.d.ts.map +0 -1
- package/dist/auth/permit_offer_action_specs.js +0 -258
- package/dist/auth/permit_offer_actions.d.ts +0 -110
- package/dist/auth/permit_offer_actions.d.ts.map +0 -1
- package/dist/auth/permit_offer_notifications.d.ts.map +0 -1
- package/dist/auth/permit_offer_notifications.js +0 -182
- package/dist/auth/permit_offer_queries.d.ts.map +0 -1
- package/dist/auth/permit_offer_schema.d.ts +0 -125
- package/dist/auth/permit_offer_schema.d.ts.map +0 -1
- package/dist/auth/permit_queries.d.ts +0 -222
- package/dist/auth/permit_queries.d.ts.map +0 -1
- package/dist/auth/permit_queries.js +0 -305
- package/dist/auth/require_keeper.d.ts +0 -20
- package/dist/auth/require_keeper.d.ts.map +0 -1
- package/dist/auth/require_keeper.js +0 -35
- package/dist/auth/route_guards.d.ts +0 -27
- package/dist/auth/route_guards.d.ts.map +0 -1
- package/dist/auth/route_guards.js +0 -38
- package/dist/auth/session_lifecycle.d.ts +0 -37
- package/dist/auth/session_lifecycle.d.ts.map +0 -1
- package/dist/auth/session_lifecycle.js +0 -29
- package/dist/ui/AdminPermitHistory.svelte.d.ts +0 -4
- package/dist/ui/AdminPermitHistory.svelte.d.ts.map +0 -1
- package/dist/ui/PermitOfferForm.svelte.d.ts.map +0 -1
- package/dist/ui/PermitOfferHistory.svelte.d.ts.map +0 -1
- package/dist/ui/PermitOfferInbox.svelte.d.ts.map +0 -1
- package/dist/ui/permit_offers_state.svelte.d.ts.map +0 -1
|
@@ -12,20 +12,24 @@
|
|
|
12
12
|
*/
|
|
13
13
|
import { z } from 'zod';
|
|
14
14
|
import { RateLimitKey } from '../http/error_schemas.js';
|
|
15
|
+
import { RouteAuth } from '../http/auth_shape.js';
|
|
15
16
|
export const ActionKind = z.enum(['request_response', 'remote_notification', 'local_call']);
|
|
16
17
|
export const ActionInitiator = z.enum(['frontend', 'backend', 'both']);
|
|
17
|
-
export const ActionAuth = z.union([
|
|
18
|
-
z.literal('public'),
|
|
19
|
-
z.literal('authenticated'),
|
|
20
|
-
z.literal('keeper'),
|
|
21
|
-
z.strictObject({ role: z.string() }),
|
|
22
|
-
]);
|
|
23
18
|
export const ActionSideEffects = z.boolean();
|
|
24
19
|
export const ActionSpec = z.strictObject({
|
|
25
20
|
method: z.string(),
|
|
26
21
|
kind: ActionKind,
|
|
27
22
|
initiator: ActionInitiator,
|
|
28
|
-
|
|
23
|
+
/**
|
|
24
|
+
* The four-axis auth shape (canonical schema in `http/auth_shape.ts`).
|
|
25
|
+
* `null` for `remote_notification` and `local_call` — those don't
|
|
26
|
+
* dispatch through the request/response auth gate.
|
|
27
|
+
*
|
|
28
|
+
* See `../http/auth_shape.ts` for the design rationale (orthogonal
|
|
29
|
+
* authentication / account-resolution / actor-resolution / role-and-
|
|
30
|
+
* credential authorization axes).
|
|
31
|
+
*/
|
|
32
|
+
auth: RouteAuth.nullable(),
|
|
29
33
|
side_effects: ActionSideEffects,
|
|
30
34
|
input: z.custom((v) => v instanceof z.ZodType),
|
|
31
35
|
output: z.custom((v) => v instanceof z.ZodType),
|
|
@@ -44,7 +48,7 @@ export const ActionSpec = z.strictObject({
|
|
|
44
48
|
* failure. Declarative metadata mirroring the `streams` precedent —
|
|
45
49
|
* codegen, UI form-state matching, and docs read it off the spec instead
|
|
46
50
|
* of scanning handler implementations. Reuses the same `as const` string
|
|
47
|
-
* constants the handler throws (e.g. `
|
|
51
|
+
* constants the handler throws (e.g. `ERROR_ROLE_GRANT_OFFER_*`) so call sites can
|
|
48
52
|
* import either side. Optional — actions that surface only standard
|
|
49
53
|
* transport errors leave it unset.
|
|
50
54
|
*/
|
|
@@ -54,8 +58,9 @@ export const ActionSpec = z.strictObject({
|
|
|
54
58
|
* actions without it skip the rate-limit hook entirely.
|
|
55
59
|
*
|
|
56
60
|
* - `'ip'` — keyed on the resolved client IP (`get_client_ip(c)`).
|
|
57
|
-
* - `'account'` — keyed on the post-auth
|
|
58
|
-
* Registration-time error if paired with `auth
|
|
61
|
+
* - `'account'` — keyed on the post-auth account id (`request_context.account.id`).
|
|
62
|
+
* Registration-time error if paired with `auth.account !== 'required'`
|
|
63
|
+
* (no account to key on).
|
|
59
64
|
* - `'both'` — both checks run; either can block.
|
|
60
65
|
*
|
|
61
66
|
* Throttle-requests semantics — every invocation records, regardless of
|
|
@@ -72,7 +77,7 @@ export const ActionSpec = z.strictObject({
|
|
|
72
77
|
});
|
|
73
78
|
export const RequestResponseActionSpec = ActionSpec.extend({
|
|
74
79
|
kind: z.literal('request_response').default('request_response'),
|
|
75
|
-
auth:
|
|
80
|
+
auth: RouteAuth,
|
|
76
81
|
async: z.literal(true).default(true),
|
|
77
82
|
});
|
|
78
83
|
export const RemoteNotificationActionSpec = ActionSpec.extend({
|
|
@@ -1,72 +1,40 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Shared type surface for the action system —
|
|
2
|
+
* Shared type surface for the action system — `Action` (the composable
|
|
3
|
+
* `{spec, handler?}` tuple) and re-exports of the canonical `ActionContext`
|
|
4
|
+
* + `ActionHandler` shapes.
|
|
3
5
|
*
|
|
4
|
-
*
|
|
5
|
-
* dispatchers (`actions/register_action_ws.ts`, `actions/action_rpc.ts
|
|
6
|
-
*
|
|
7
|
-
* pulling in server-only
|
|
6
|
+
* Sits above `actions/action_spec.ts` (pure Zod schemas) and below the
|
|
7
|
+
* dispatchers (`actions/register_action_ws.ts`, `actions/action_rpc.ts`,
|
|
8
|
+
* `actions/perform_action.ts`). Extracted so the shared protocol actions
|
|
9
|
+
* (e.g. `heartbeat_action`) can name them without pulling in server-only
|
|
10
|
+
* modules.
|
|
11
|
+
*
|
|
12
|
+
* HTTP RPC and WebSocket dispatchers both call into `perform_action`,
|
|
13
|
+
* and both pass the same `ActionContext` to the handler. Consumers
|
|
14
|
+
* inject domain deps via factory closures the same way HTTP RPC
|
|
15
|
+
* factories do (see `auth/standard_rpc_actions.ts`).
|
|
8
16
|
*
|
|
9
17
|
* @module
|
|
10
18
|
*/
|
|
11
|
-
import type { Uuid } from '@fuzdev/fuz_util/id.js';
|
|
12
|
-
import type { JsonrpcRequestId } from '../http/jsonrpc.js';
|
|
13
19
|
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;
|
|
20
|
+
import type { ActionHandler } from './action_rpc.js';
|
|
55
21
|
/**
|
|
56
22
|
* A spec paired with its optional handler — the composable unit passed to
|
|
57
|
-
* `register_action_ws` and `create_rpc_client`. The server uses
|
|
58
|
-
*
|
|
59
|
-
*
|
|
60
|
-
*
|
|
61
|
-
*
|
|
23
|
+
* `register_action_ws` and `create_rpc_client`. The server uses both
|
|
24
|
+
* fields; the client reads only `spec` (the `handler` is ignored,
|
|
25
|
+
* harmless). Shared fuz_app primitives (e.g. `heartbeat_action`) export a
|
|
26
|
+
* complete tuple so consumers spread them into both sides' `actions`
|
|
27
|
+
* arrays without inventing per-repo ping plumbing.
|
|
62
28
|
*
|
|
63
|
-
*
|
|
64
|
-
*
|
|
65
|
-
*
|
|
29
|
+
* Polymorphic on `kind`: `request_response` specs require a handler for
|
|
30
|
+
* dispatch; `remote_notification` specs may declare a stub handler for
|
|
31
|
+
* symmetry but are dispatcher-handled (e.g. `cancel`); `local_call` specs
|
|
32
|
+
* never reach a network dispatcher. The WS dispatcher only invokes
|
|
33
|
+
* handlers on `request_response` actions; everything else is registry-only.
|
|
66
34
|
*/
|
|
67
|
-
export interface Action<
|
|
68
|
-
spec:
|
|
69
|
-
/** Server-side handler
|
|
70
|
-
handler?:
|
|
35
|
+
export interface Action<TSpec extends ActionSpecUnion = ActionSpecUnion> {
|
|
36
|
+
spec: TSpec;
|
|
37
|
+
/** Server-side handler — invoked by dispatchers on `request_response` actions. Ignored for client-only specs and dispatcher-handled notifications. */
|
|
38
|
+
handler?: ActionHandler;
|
|
71
39
|
}
|
|
72
40
|
//# sourceMappingURL=action_types.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"action_types.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/actions/action_types.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"action_types.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/actions/action_types.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AAEH,OAAO,KAAK,EAAC,eAAe,EAAC,MAAM,kBAAkB,CAAC;AACtD,OAAO,KAAK,EAAC,aAAa,EAAC,MAAM,iBAAiB,CAAC;AAEnD;;;;;;;;;;;;;GAaG;AACH,MAAM,WAAW,MAAM,CAAC,KAAK,SAAS,eAAe,GAAG,eAAe;IACtE,IAAI,EAAE,KAAK,CAAC;IACZ,sJAAsJ;IACtJ,OAAO,CAAC,EAAE,aAAa,CAAC;CACxB"}
|
|
@@ -1,10 +1,18 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Shared type surface for the action system —
|
|
2
|
+
* Shared type surface for the action system — `Action` (the composable
|
|
3
|
+
* `{spec, handler?}` tuple) and re-exports of the canonical `ActionContext`
|
|
4
|
+
* + `ActionHandler` shapes.
|
|
3
5
|
*
|
|
4
|
-
*
|
|
5
|
-
* dispatchers (`actions/register_action_ws.ts`, `actions/action_rpc.ts
|
|
6
|
-
*
|
|
7
|
-
* pulling in server-only
|
|
6
|
+
* Sits above `actions/action_spec.ts` (pure Zod schemas) and below the
|
|
7
|
+
* dispatchers (`actions/register_action_ws.ts`, `actions/action_rpc.ts`,
|
|
8
|
+
* `actions/perform_action.ts`). Extracted so the shared protocol actions
|
|
9
|
+
* (e.g. `heartbeat_action`) can name them without pulling in server-only
|
|
10
|
+
* modules.
|
|
11
|
+
*
|
|
12
|
+
* HTTP RPC and WebSocket dispatchers both call into `perform_action`,
|
|
13
|
+
* and both pass the same `ActionContext` to the handler. Consumers
|
|
14
|
+
* inject domain deps via factory closures the same way HTTP RPC
|
|
15
|
+
* factories do (see `auth/standard_rpc_actions.ts`).
|
|
8
16
|
*
|
|
9
17
|
* @module
|
|
10
18
|
*/
|
|
@@ -9,11 +9,11 @@
|
|
|
9
9
|
* Counterpart to `register_action_ws`: that handles request-scoped dispatch
|
|
10
10
|
* (frontend-initiated), this handles broadcast (backend-initiated). Together
|
|
11
11
|
* they cover the two primitives fuz_app consumers share. Request-scoped
|
|
12
|
-
* streaming (`completion_progress`, `
|
|
12
|
+
* streaming (`completion_progress`, `zap_apply` events) stays on
|
|
13
13
|
* `ctx.notify` inside a handler — it's socket-scoped, not broadcast.
|
|
14
14
|
*
|
|
15
15
|
* Extracted from zzz's `backend_actions_api.ts` to stop the pattern from
|
|
16
|
-
* drifting across zzz,
|
|
16
|
+
* drifting across zzz, zap, and undying.
|
|
17
17
|
*
|
|
18
18
|
* @module
|
|
19
19
|
*/
|
|
@@ -9,11 +9,11 @@
|
|
|
9
9
|
* Counterpart to `register_action_ws`: that handles request-scoped dispatch
|
|
10
10
|
* (frontend-initiated), this handles broadcast (backend-initiated). Together
|
|
11
11
|
* they cover the two primitives fuz_app consumers share. Request-scoped
|
|
12
|
-
* streaming (`completion_progress`, `
|
|
12
|
+
* streaming (`completion_progress`, `zap_apply` events) stays on
|
|
13
13
|
* `ctx.notify` inside a handler — it's socket-scoped, not broadcast.
|
|
14
14
|
*
|
|
15
15
|
* Extracted from zzz's `backend_actions_api.ts` to stop the pattern from
|
|
16
|
-
* drifting across zzz,
|
|
16
|
+
* drifting across zzz, zap, and undying.
|
|
17
17
|
*
|
|
18
18
|
* @module
|
|
19
19
|
*/
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared registration loop for action-dispatcher endpoints.
|
|
3
|
+
*
|
|
4
|
+
* `create_rpc_endpoint` (HTTP RPC) and `register_action_ws` (WebSocket)
|
|
5
|
+
* both build a `Map<method, RpcAction>` from a list of action specs and
|
|
6
|
+
* gate the build on the same registry-time invariants:
|
|
7
|
+
*
|
|
8
|
+
* 1. **Auth-shape biconditional** — per `assert_route_auth_acting_biconditional`
|
|
9
|
+
* in `http/auth_shape.ts`: `auth.actor !== 'none' ⟺ input declares
|
|
10
|
+
* acting?: ActingActor`. Fires for every spec with non-null auth.
|
|
11
|
+
* 2. **Rate-limit / account axis** — `rate_limit: 'account' | 'both'`
|
|
12
|
+
* requires `auth.account === 'required'`; without an account on the
|
|
13
|
+
* request there is no key for the per-account bucket.
|
|
14
|
+
* 3. **JSON-RPC §4.2 wire validity** — `request_response` specs whose
|
|
15
|
+
* handler will reach the dispatch map must not use `z.null()` for
|
|
16
|
+
* input (the wire format forbids `"params": null`; use `z.void()`
|
|
17
|
+
* for parameterless methods).
|
|
18
|
+
* 4. **Duplicate method names** — JSON-RPC keys on `method`, so every
|
|
19
|
+
* spec in the array must declare a unique `method` regardless of
|
|
20
|
+
* kind / handler presence.
|
|
21
|
+
*
|
|
22
|
+
* Pre-consolidation each dispatcher inlined these checks; the comment
|
|
23
|
+
* in `register_action_ws.ts` literally said "mirrors the HTTP RPC
|
|
24
|
+
* registration check" but nothing kept them mirrored. Centralizing the
|
|
25
|
+
* loop closes the most likely future drift surface.
|
|
26
|
+
*
|
|
27
|
+
* @module
|
|
28
|
+
*/
|
|
29
|
+
import type { Action } from './action_types.js';
|
|
30
|
+
import type { RpcAction } from './action_rpc.js';
|
|
31
|
+
/** Result returned by `compile_action_registry`. */
|
|
32
|
+
export interface ActionRegistryCompileResult {
|
|
33
|
+
/**
|
|
34
|
+
* Method → `RpcAction` lookup for dispatch. Only `request_response`
|
|
35
|
+
* specs with a handler land here — kind-polymorphic input arrays
|
|
36
|
+
* (the WebSocket dispatcher's `actions: ReadonlyArray<Action>`)
|
|
37
|
+
* pass `remote_notification` / handler-less specs through unchanged.
|
|
38
|
+
*/
|
|
39
|
+
action_map: Map<string, RpcAction>;
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* Validate registry-time invariants and build the dispatcher's
|
|
43
|
+
* method → action lookup.
|
|
44
|
+
*
|
|
45
|
+
* @param actions - polymorphic action array; HTTP RPC passes `RpcAction[]` (narrower), WebSocket passes `Action[]` (kind-polymorphic — handler-less notification specs are accepted)
|
|
46
|
+
* @param ctx_label - per-spec error-message prefix, e.g. `'RPC action'` or `'WS action'`. Combined with the spec method as `${ctx_label} "${method}"`.
|
|
47
|
+
* @throws Error on biconditional violation, rate-limit/account-axis mismatch, JSON-RPC null-input, or duplicate method.
|
|
48
|
+
*/
|
|
49
|
+
export declare const compile_action_registry: (actions: ReadonlyArray<Action>, ctx_label: string) => ActionRegistryCompileResult;
|
|
50
|
+
//# sourceMappingURL=compile_action_registry.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"compile_action_registry.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/actions/compile_action_registry.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2BG;AAEH,OAAO,KAAK,EAAC,MAAM,EAAC,MAAM,mBAAmB,CAAC;AAC9C,OAAO,KAAK,EAAC,SAAS,EAAC,MAAM,iBAAiB,CAAC;AAI/C,oDAAoD;AACpD,MAAM,WAAW,2BAA2B;IAC3C;;;;;OAKG;IACH,UAAU,EAAE,GAAG,CAAC,MAAM,EAAE,SAAS,CAAC,CAAC;CACnC;AAED;;;;;;;GAOG;AACH,eAAO,MAAM,uBAAuB,GACnC,SAAS,aAAa,CAAC,MAAM,CAAC,EAC9B,WAAW,MAAM,KACf,2BAoCF,CAAC"}
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared registration loop for action-dispatcher endpoints.
|
|
3
|
+
*
|
|
4
|
+
* `create_rpc_endpoint` (HTTP RPC) and `register_action_ws` (WebSocket)
|
|
5
|
+
* both build a `Map<method, RpcAction>` from a list of action specs and
|
|
6
|
+
* gate the build on the same registry-time invariants:
|
|
7
|
+
*
|
|
8
|
+
* 1. **Auth-shape biconditional** — per `assert_route_auth_acting_biconditional`
|
|
9
|
+
* in `http/auth_shape.ts`: `auth.actor !== 'none' ⟺ input declares
|
|
10
|
+
* acting?: ActingActor`. Fires for every spec with non-null auth.
|
|
11
|
+
* 2. **Rate-limit / account axis** — `rate_limit: 'account' | 'both'`
|
|
12
|
+
* requires `auth.account === 'required'`; without an account on the
|
|
13
|
+
* request there is no key for the per-account bucket.
|
|
14
|
+
* 3. **JSON-RPC §4.2 wire validity** — `request_response` specs whose
|
|
15
|
+
* handler will reach the dispatch map must not use `z.null()` for
|
|
16
|
+
* input (the wire format forbids `"params": null`; use `z.void()`
|
|
17
|
+
* for parameterless methods).
|
|
18
|
+
* 4. **Duplicate method names** — JSON-RPC keys on `method`, so every
|
|
19
|
+
* spec in the array must declare a unique `method` regardless of
|
|
20
|
+
* kind / handler presence.
|
|
21
|
+
*
|
|
22
|
+
* Pre-consolidation each dispatcher inlined these checks; the comment
|
|
23
|
+
* in `register_action_ws.ts` literally said "mirrors the HTTP RPC
|
|
24
|
+
* registration check" but nothing kept them mirrored. Centralizing the
|
|
25
|
+
* loop closes the most likely future drift surface.
|
|
26
|
+
*
|
|
27
|
+
* @module
|
|
28
|
+
*/
|
|
29
|
+
import { assert_route_auth_acting_biconditional } from '../http/auth_shape.js';
|
|
30
|
+
import { is_null_schema } from '../http/schema_helpers.js';
|
|
31
|
+
/**
|
|
32
|
+
* Validate registry-time invariants and build the dispatcher's
|
|
33
|
+
* method → action lookup.
|
|
34
|
+
*
|
|
35
|
+
* @param actions - polymorphic action array; HTTP RPC passes `RpcAction[]` (narrower), WebSocket passes `Action[]` (kind-polymorphic — handler-less notification specs are accepted)
|
|
36
|
+
* @param ctx_label - per-spec error-message prefix, e.g. `'RPC action'` or `'WS action'`. Combined with the spec method as `${ctx_label} "${method}"`.
|
|
37
|
+
* @throws Error on biconditional violation, rate-limit/account-axis mismatch, JSON-RPC null-input, or duplicate method.
|
|
38
|
+
*/
|
|
39
|
+
export const compile_action_registry = (actions, ctx_label) => {
|
|
40
|
+
const action_map = new Map();
|
|
41
|
+
const seen_methods = new Set();
|
|
42
|
+
for (const action of actions) {
|
|
43
|
+
const { spec } = action;
|
|
44
|
+
const ctx = `${ctx_label} "${spec.method}"`;
|
|
45
|
+
if (seen_methods.has(spec.method)) {
|
|
46
|
+
throw new Error(`Duplicate ${ctx_label} method: ${spec.method}`);
|
|
47
|
+
}
|
|
48
|
+
seen_methods.add(spec.method);
|
|
49
|
+
// Auth-shape invariants apply to any spec with non-null auth (which
|
|
50
|
+
// per the spec union means `kind === 'request_response'`).
|
|
51
|
+
if (spec.auth !== null) {
|
|
52
|
+
assert_route_auth_acting_biconditional(spec.auth, { input: spec.input }, ctx);
|
|
53
|
+
if ((spec.rate_limit === 'account' || spec.rate_limit === 'both') &&
|
|
54
|
+
spec.auth.account !== 'required') {
|
|
55
|
+
throw new Error(`${ctx} declares rate_limit: '${spec.rate_limit}' but auth.account !== 'required' — no account guaranteed for account-keyed limiting. Use 'ip' or set auth.account: 'required'.`);
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
// Only request_response specs with a handler reach the dispatch
|
|
59
|
+
// map. Notifications (e.g. WS `cancel`) and handler-less specs
|
|
60
|
+
// stay registry-only and bypass JSON-RPC wire-validity checks.
|
|
61
|
+
if (spec.kind === 'request_response' && action.handler) {
|
|
62
|
+
if (is_null_schema(spec.input)) {
|
|
63
|
+
throw new Error(`${ctx} uses z.null() for input — JSON-RPC 2.0 §4.2 forbids "params": null on the wire. Use z.void() for parameterless methods.`);
|
|
64
|
+
}
|
|
65
|
+
action_map.set(spec.method, { spec, handler: action.handler });
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
return { action_map };
|
|
69
|
+
};
|
|
@@ -20,15 +20,19 @@
|
|
|
20
20
|
import { z } from 'zod';
|
|
21
21
|
import type { Action } from './action_types.js';
|
|
22
22
|
/**
|
|
23
|
-
* `ActionSpec` for the shared heartbeat.
|
|
24
|
-
* auth has already admitted the socket; heartbeats don't
|
|
25
|
-
* `side_effects: false` keeps it
|
|
23
|
+
* `ActionSpec` for the shared heartbeat. Account-required, actor-none —
|
|
24
|
+
* upgrade-time auth has already admitted the socket; heartbeats don't
|
|
25
|
+
* need role gating or actor resolution. `side_effects: false` keeps it
|
|
26
|
+
* orthogonal to state changes.
|
|
26
27
|
*/
|
|
27
28
|
export declare const heartbeat_action_spec: {
|
|
28
29
|
method: string;
|
|
29
30
|
kind: "request_response";
|
|
30
31
|
initiator: "frontend";
|
|
31
|
-
auth:
|
|
32
|
+
auth: {
|
|
33
|
+
account: "required";
|
|
34
|
+
actor: "none";
|
|
35
|
+
};
|
|
32
36
|
side_effects: false;
|
|
33
37
|
input: z.ZodObject<{}, z.core.$strict>;
|
|
34
38
|
output: z.ZodObject<{}, z.core.$strict>;
|
|
@@ -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;;;;;GAKG;AACH,eAAO,MAAM,qBAAqB;;;;;;;;;;;;;CAUG,CAAC;AAEtC,4EAA4E;AAC5E,eAAO,MAAM,iBAAiB,QAAO,MAAM,CAAC,MAAM,EAAE,KAAK,CAAS,CAAC;AAEnE;;;;;;;GAOG;AACH,eAAO,MAAM,gBAAgB,EAAE,MAG9B,CAAC"}
|
|
@@ -19,15 +19,16 @@
|
|
|
19
19
|
*/
|
|
20
20
|
import { z } from 'zod';
|
|
21
21
|
/**
|
|
22
|
-
* `ActionSpec` for the shared heartbeat.
|
|
23
|
-
* auth has already admitted the socket; heartbeats don't
|
|
24
|
-
* `side_effects: false` keeps it
|
|
22
|
+
* `ActionSpec` for the shared heartbeat. Account-required, actor-none —
|
|
23
|
+
* upgrade-time auth has already admitted the socket; heartbeats don't
|
|
24
|
+
* need role gating or actor resolution. `side_effects: false` keeps it
|
|
25
|
+
* orthogonal to state changes.
|
|
25
26
|
*/
|
|
26
27
|
export const heartbeat_action_spec = {
|
|
27
28
|
method: 'heartbeat',
|
|
28
29
|
kind: 'request_response',
|
|
29
30
|
initiator: 'frontend',
|
|
30
|
-
auth: '
|
|
31
|
+
auth: { account: 'required', actor: 'none' },
|
|
31
32
|
side_effects: false,
|
|
32
33
|
input: z.strictObject({}),
|
|
33
34
|
output: z.strictObject({}),
|
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Transport-agnostic dispatch core shared by HTTP RPC and WebSocket
|
|
3
|
+
* action dispatchers.
|
|
4
|
+
*
|
|
5
|
+
* `perform_action` runs the post-parse pipeline that every action-spec
|
|
6
|
+
* handler must traverse:
|
|
7
|
+
*
|
|
8
|
+
* 1. **Pre-validation auth (401)** — short-circuits unauthenticated callers
|
|
9
|
+
* on `'required'` axes before input validation runs, so callers never
|
|
10
|
+
* see `invalid_params` for methods with required input.
|
|
11
|
+
* 2. **Validate params (400)** — `spec.input.safeParse(raw_params)` with
|
|
12
|
+
* `z.void()` / `?? {}` rules. The validated input lands inside the
|
|
13
|
+
* function so the authorization phase reads `acting` as a typed Zod
|
|
14
|
+
* field.
|
|
15
|
+
* 3. **Authorization phase** — when `auth.actor !== 'none'` (or
|
|
16
|
+
* `auth.account !== 'none' && actor === 'none'`), resolves the actor
|
|
17
|
+
* via `apply_authorization_phase` against the supplied `account_id`
|
|
18
|
+
* plus `validated_input.acting`. Failures fold into a JSON-RPC error
|
|
19
|
+
* envelope. The test-harness escape hatch lives in the caller — pass
|
|
20
|
+
* `preset.request_context` to skip the live phase and use a pre-baked
|
|
21
|
+
* context instead.
|
|
22
|
+
* 4. **Post-authorization auth (403)** — gates `auth.credential_types` and
|
|
23
|
+
* `auth.roles` against the resolved context.
|
|
24
|
+
* 5. **Rate limit (429)** — per-action IP / account throttling, throttle-
|
|
25
|
+
* requests semantics (every invocation records, regardless of outcome).
|
|
26
|
+
* 6. **Dispatch + DEV-only output validation + error normalization** —
|
|
27
|
+
* `spec.side_effects` picks transaction (`deps.db.transaction`) vs
|
|
28
|
+
* pool. Handler throws roll back the transaction; the catch sits
|
|
29
|
+
* outside the transaction boundary. Handler outputs are validated
|
|
30
|
+
* against `spec.output` under DEV (logs an error on mismatch, never
|
|
31
|
+
* throws, never mutates the result).
|
|
32
|
+
*
|
|
33
|
+
* The function is pure data — it never touches a Hono context, so HTTP
|
|
34
|
+
* RPC, REST bridge (when on the action surface), and WS dispatch all
|
|
35
|
+
* call into it the same way and bind the discriminated
|
|
36
|
+
* `PerformActionResult` to their wire shape.
|
|
37
|
+
*
|
|
38
|
+
* @module
|
|
39
|
+
*/
|
|
40
|
+
import type { Logger } from '@fuzdev/fuz_util/log.js';
|
|
41
|
+
import type { Uuid } from '@fuzdev/fuz_util/id.js';
|
|
42
|
+
import { type RequestContext } from '../auth/request_context.js';
|
|
43
|
+
import { type CredentialType } from '../hono_context.js';
|
|
44
|
+
import type { Db } from '../db/db.js';
|
|
45
|
+
import { type JsonrpcRequestId, type JsonrpcErrorObject } from '../http/jsonrpc.js';
|
|
46
|
+
import type { RateLimiter } from '../rate_limiter.js';
|
|
47
|
+
import type { RpcAction } from './action_rpc.js';
|
|
48
|
+
/**
|
|
49
|
+
* Per-call inputs to `perform_action`. Each transport assembles this from
|
|
50
|
+
* its wire envelope + connection identity.
|
|
51
|
+
*/
|
|
52
|
+
export interface PerformActionInput {
|
|
53
|
+
/** The resolved spec + handler (transport does method lookup). */
|
|
54
|
+
action: RpcAction;
|
|
55
|
+
/** Raw params from the wire envelope (post-`JsonrpcRequest.parse`, pre-`spec.input.safeParse`). */
|
|
56
|
+
raw_params: unknown;
|
|
57
|
+
/** JSON-RPC request id — echoed onto the response. */
|
|
58
|
+
request_id: JsonrpcRequestId;
|
|
59
|
+
/** Authenticated account id, or `null` for anonymous. */
|
|
60
|
+
account_id: string | null;
|
|
61
|
+
/** Credential type the request arrived on, or `null` for anonymous. */
|
|
62
|
+
credential_type: CredentialType | null;
|
|
63
|
+
/** Resolved client IP (`'unknown'` if upstream couldn't resolve). */
|
|
64
|
+
client_ip: string;
|
|
65
|
+
/** Per-request abort signal. HTTP: `c.req.raw.signal`. WS: `AbortSignal.any([socket, request])`. */
|
|
66
|
+
signal: AbortSignal;
|
|
67
|
+
/** Send a request-scoped notification. HTTP: DEV-warn-and-drop. WS: socket-scoped. */
|
|
68
|
+
notify: (method: string, params: unknown) => void;
|
|
69
|
+
/** Stable per-socket id on WS; `undefined` on HTTP. */
|
|
70
|
+
connection_id?: Uuid;
|
|
71
|
+
/**
|
|
72
|
+
* Test-harness escape hatch. When set, the live authorization phase is
|
|
73
|
+
* skipped and `request_context` is used directly for post-authorization
|
|
74
|
+
* checks + handler dispatch. Production callers leave this `undefined`.
|
|
75
|
+
*/
|
|
76
|
+
preset?: {
|
|
77
|
+
request_context: RequestContext | null;
|
|
78
|
+
};
|
|
79
|
+
}
|
|
80
|
+
/**
|
|
81
|
+
* Per-deps inputs to `perform_action`. Each transport supplies its own
|
|
82
|
+
* pool-level `Db` and rate limiters; the dispatcher wraps in a transaction
|
|
83
|
+
* iff `spec.side_effects` is true.
|
|
84
|
+
*
|
|
85
|
+
* Pool-resilient fire-and-forget effects (audit writes) run through
|
|
86
|
+
* `AppDeps.audit.emit` from the action factory's closure — the dispatcher
|
|
87
|
+
* never sees the audit emitter. The bound emitter owns the pool.
|
|
88
|
+
*/
|
|
89
|
+
export interface PerformActionDeps {
|
|
90
|
+
/** Pool-level DB. The dispatcher wraps in `db.transaction` for `side_effects: true` actions. */
|
|
91
|
+
db: Db;
|
|
92
|
+
/**
|
|
93
|
+
* Eager fire-and-forget pool-write queue, flushed by the transport's
|
|
94
|
+
* `try/finally` via `flush_pending_effects`.
|
|
95
|
+
*/
|
|
96
|
+
pending_effects: Array<Promise<void>>;
|
|
97
|
+
/**
|
|
98
|
+
* Deferred post-commit thunks pushed via `emit_after_commit`, flushed
|
|
99
|
+
* by the transport's `try/finally` after the handler returns.
|
|
100
|
+
*/
|
|
101
|
+
post_commit_effects: Array<() => void | Promise<void>>;
|
|
102
|
+
/** Logger threaded into `ActionContext.log`. */
|
|
103
|
+
log: Logger;
|
|
104
|
+
/** Per-IP limiter (shared across transports). `null` disables. */
|
|
105
|
+
action_ip_rate_limiter: RateLimiter | null;
|
|
106
|
+
/** Per-account limiter (shared across transports). `null` disables. */
|
|
107
|
+
action_account_rate_limiter: RateLimiter | null;
|
|
108
|
+
}
|
|
109
|
+
/**
|
|
110
|
+
* Discriminated result of `perform_action`. Each transport binds this to
|
|
111
|
+
* its wire shape: HTTP RPC folds the error into a JSON-RPC envelope and
|
|
112
|
+
* returns via `c.json`; WS sends the response over the socket.
|
|
113
|
+
*/
|
|
114
|
+
export type PerformActionResult = {
|
|
115
|
+
kind: 'ok';
|
|
116
|
+
result: unknown;
|
|
117
|
+
} | {
|
|
118
|
+
kind: 'error';
|
|
119
|
+
error: JsonrpcErrorObject;
|
|
120
|
+
status: number;
|
|
121
|
+
};
|
|
122
|
+
/**
|
|
123
|
+
* The shared dispatch core. Pure data — no Hono context, no socket. Each
|
|
124
|
+
* transport calls into this with pre-parsed inputs and binds the result
|
|
125
|
+
* to its wire shape.
|
|
126
|
+
*
|
|
127
|
+
* Phase order: 401 → 400 → 403 → handler. On the test-preset path the
|
|
128
|
+
* dispatcher skips the live authorization phase and uses the supplied
|
|
129
|
+
* pre-baked context for post-authorization checks; pre-validation 401
|
|
130
|
+
* still fires when the harness omits `account_id`.
|
|
131
|
+
*/
|
|
132
|
+
export declare const perform_action: (input: PerformActionInput, deps: PerformActionDeps) => Promise<PerformActionResult>;
|
|
133
|
+
/**
|
|
134
|
+
* Build a JSON-RPC response envelope from a `PerformActionResult` for
|
|
135
|
+
* transports that wire over the JSON-RPC 2.0 message shape (HTTP RPC + WS).
|
|
136
|
+
*/
|
|
137
|
+
export declare const perform_action_result_to_envelope: (id: JsonrpcRequestId, result: PerformActionResult) => {
|
|
138
|
+
jsonrpc: string;
|
|
139
|
+
id: JsonrpcRequestId;
|
|
140
|
+
} & ({
|
|
141
|
+
result: unknown;
|
|
142
|
+
} | {
|
|
143
|
+
error: JsonrpcErrorObject;
|
|
144
|
+
});
|
|
145
|
+
//# sourceMappingURL=perform_action.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"perform_action.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/actions/perform_action.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAsCG;AAGH,OAAO,KAAK,EAAC,MAAM,EAAC,MAAM,yBAAyB,CAAC;AACpD,OAAO,KAAK,EAAC,IAAI,EAAC,MAAM,wBAAwB,CAAC;AAEjD,OAAO,EAGN,KAAK,cAAc,EACnB,MAAM,4BAA4B,CAAC;AACpC,OAAO,EAAC,KAAK,cAAc,EAAC,MAAM,oBAAoB,CAAC;AACvD,OAAO,KAAK,EAAC,EAAE,EAAC,MAAM,aAAa,CAAC;AAEpC,OAAO,EAEN,KAAK,gBAAgB,EAErB,KAAK,kBAAkB,EACvB,MAAM,oBAAoB,CAAC;AAW5B,OAAO,KAAK,EAAC,WAAW,EAAC,MAAM,oBAAoB,CAAC;AAEpD,OAAO,KAAK,EAA+B,SAAS,EAAC,MAAM,iBAAiB,CAAC;AAE7E;;;GAGG;AACH,MAAM,WAAW,kBAAkB;IAClC,kEAAkE;IAClE,MAAM,EAAE,SAAS,CAAC;IAClB,mGAAmG;IACnG,UAAU,EAAE,OAAO,CAAC;IACpB,sDAAsD;IACtD,UAAU,EAAE,gBAAgB,CAAC;IAC7B,yDAAyD;IACzD,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;IAC1B,uEAAuE;IACvE,eAAe,EAAE,cAAc,GAAG,IAAI,CAAC;IACvC,qEAAqE;IACrE,SAAS,EAAE,MAAM,CAAC;IAClB,oGAAoG;IACpG,MAAM,EAAE,WAAW,CAAC;IACpB,sFAAsF;IACtF,MAAM,EAAE,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,OAAO,KAAK,IAAI,CAAC;IAClD,uDAAuD;IACvD,aAAa,CAAC,EAAE,IAAI,CAAC;IACrB;;;;OAIG;IACH,MAAM,CAAC,EAAE;QAAC,eAAe,EAAE,cAAc,GAAG,IAAI,CAAA;KAAC,CAAC;CAClD;AAED;;;;;;;;GAQG;AACH,MAAM,WAAW,iBAAiB;IACjC,gGAAgG;IAChG,EAAE,EAAE,EAAE,CAAC;IACP;;;OAGG;IACH,eAAe,EAAE,KAAK,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC;IACtC;;;OAGG;IACH,mBAAmB,EAAE,KAAK,CAAC,MAAM,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC;IACvD,gDAAgD;IAChD,GAAG,EAAE,MAAM,CAAC;IACZ,kEAAkE;IAClE,sBAAsB,EAAE,WAAW,GAAG,IAAI,CAAC;IAC3C,uEAAuE;IACvE,2BAA2B,EAAE,WAAW,GAAG,IAAI,CAAC;CAChD;AAED;;;;GAIG;AACH,MAAM,MAAM,mBAAmB,GAC5B;IAAC,IAAI,EAAE,IAAI,CAAC;IAAC,MAAM,EAAE,OAAO,CAAA;CAAC,GAC7B;IAAC,IAAI,EAAE,OAAO,CAAC;IAAC,KAAK,EAAE,kBAAkB,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAC,CAAC;AAE9D;;;;;;;;;GASG;AACH,eAAO,MAAM,cAAc,GAC1B,OAAO,kBAAkB,EACzB,MAAM,iBAAiB,KACrB,OAAO,CAAC,mBAAmB,CAuJ7B,CAAC;AA4EF;;;GAGG;AACH,eAAO,MAAM,iCAAiC,GAC7C,IAAI,gBAAgB,EACpB,QAAQ,mBAAmB,KACzB;IAAC,OAAO,EAAE,MAAM,CAAC;IAAC,EAAE,EAAE,gBAAgB,CAAA;CAAC,GAAG,CAAC;IAAC,MAAM,EAAE,OAAO,CAAA;CAAC,GAAG;IAAC,KAAK,EAAE,kBAAkB,CAAA;CAAC,CAK5F,CAAC"}
|