actions/action_bridge.ts

Bridge functions to derive RouteSpec and EventSpec from ActionSpec.

Action specs define the contract (method, input/output, auth, side effects). Bridge functions produce transport-specific specs from them. HTTP-specific concerns (path, handler) come from options.

view source

Declarations
#

5 declarations

ActionEventOptions
#

ActionRouteOptions
#

actions/action_bridge.ts view source

ActionRouteOptions import type {ActionRouteOptions} from '@fuzdev/fuz_app/actions/action_bridge.js';

Options for deriving a RouteSpec from an ActionSpec.

path

type string

handler

type (c: Context<any, any, {}>, route: RouteContext): Response | Promise<Response>

c

type Context<any, any, {}>

route

returns Response | Promise<Response>

params?

URL path parameter schema. Use z.strictObject() with string fields matching :param segments.

type ZodObject<$ZodLooseShape, $strip>

query?

URL query parameter schema. Use z.strictObject() with string fields.

type ZodObject<$ZodLooseShape, $strip>

http_method?

Override the default HTTP method (default: side_effects → POST, else GET).

type RouteMethod

auth?

Override the route's auth shape — defaults to the action spec's auth (the canonical shape from http/auth_shape.ts is shared verbatim between action specs and route specs, so no mapping is needed).

The bridge fills in required_scope on whichever shape it ends up with, so an override still gets the token-scope gate; declare required_scope here to name a different capability (e.g. surface:<name> when the bridged route is a stream rather than a request/response call — see the token-scope note on create_action_route_spec).

Overriding to *widen* (admitting a credential the action's own gate refused) makes this route the consumer's to audit, not the spine's.

type RouteAuth

errors?

Handler-specific error schemas (HTTP status code → Zod schema). Transport-specific — not on ActionSpec.

type Partial<Record<number, ZodType<unknown, unknown, $ZodTypeInternals<unknown, unknown>>>>

create_action_event_spec
#

actions/action_bridge.ts view source

(spec: { method: string; kind: "request_response" | "remote_notification" | "local_call"; initiator: "frontend" | "backend" | "both"; auth: { account: "optional" | "none" | "required"; actor: "optional" | ... 1 more ... | "required"; roles?: readonly string[] | undefined; credential_types?: readonly string[] | undefined; required_scope?: string | undefined; } | null; ... 7 more ...; rate_limit?: "both" | ... 2 more ... | undefined; }, options?: ActionEventOptions | undefined): EventSpec import {create_action_event_spec} from '@fuzdev/fuz_app/actions/action_bridge.js';

Derive an EventSpec from an ActionSpec.

Only remote_notification actions can become push events.

spec

the action spec (must have kind: 'remote_notification')

options?

optional event-specific options (channel)

optional

returns

EventSpec

throws

  • Error - if `spec.kind` is not `'remote_notification'`

create_action_route_spec
#

actions/action_bridge.ts view source

(spec: { method: string; kind: "request_response" | "remote_notification" | "local_call"; initiator: "frontend" | "backend" | "both"; auth: { account: "optional" | "none" | "required"; actor: "optional" | ... 1 more ... | "required"; roles?: readonly string[] | undefined; credential_types?: readonly string[] | undefined; required_scope?: string | undefined; } | null; ... 7 more ...; rate_limit?: "both" | ... 2 more ... | undefined; }, options: ActionRouteOptions): RouteSpec import {create_action_route_spec} from '@fuzdev/fuz_app/actions/action_bridge.js';

Derive a RouteSpec from an ActionSpec and options.

Only request_response actions (which require non-null auth) can become routes. remote_notification actions (auth null) should use create_action_event_spec. local_call actions are not for HTTP transport.

Error schemas are transport-specific (keyed by HTTP status codes) and belong on the options, not the action spec. Action specs define the contract; transport concerns like HTTP error codes are added at the bridge layer.

The bridge carries the token-scope gate across for you. A bridged route runs options.handler through the REST pipeline and never reaches perform_action, so the dispatcher's per-method scope check cannot fire on it — which would leave a bearer-reachable route that a narrowed api token walks straight through. So the derived spec declares auth.required_scope: 'rpc:<method>', and fuz_auth_guard_resolver mounts the same refusal ahead of the role gate. A token that lists the method reaches the bridged route; one that doesn't gets the 403 it would have gotten over RPC.

Per-method rather than rule 3's blanket refusal because a bridged route *has* a method identity — which is exactly what the non-RPC surfaces rule 3 covers (the db browser, a bare-hash read, a stream) do not, and why that rule is all-or-nothing there. Bridge something with no request/response shape — an SSE stream, a file download — and rule 3's reasoning applies instead: pass options.auth with required_scope: 'surface:<name>', naming your own surface. See docs/security.md §Token scoping.

Skipped for a public action (account: 'none', actor: 'none'), where the same holder reaches the route by dropping the credential, so the guard would enforce nothing — the shape fuz_auth_guard_resolver refuses outright.

spec

the action spec (must have non-null auth)

options

HTTP-specific options (path, handler, optional overrides)

returns

RouteSpec

throws

  • Error - if `spec.auth` is null (only `request_response` actions can become routes; notifications and local calls cannot)

derive_http_method
#

actions/action_bridge.ts view source

(side_effects: boolean): RouteMethod import {derive_http_method} from '@fuzdev/fuz_app/actions/action_bridge.js';

Derive the default HTTP method from side effects.

side_effects

type boolean

returns

RouteMethod

Depends on
#

Imported by
#