api #

fullstack app library

373 modules · 2288 declarations

Modules
#

AcceptOfferInput
#

auth/role_grant_offer_queries.ts view source

AcceptOfferInput import type {AcceptOfferInput} from '@fuzdev/fuz_app/auth/role_grant_offer_queries.js';

offer_id

type Uuid

to_account_id

Account of the accepting recipient — IDOR guard against another account accepting the offer.

type Uuid

actor_id

Accepting actor — the actor that will hold the resulting role_grant. Must belong to to_account_id; the query verifies and throws if not (defense-in-depth — the action handler passes auth.actor.id which is session-bound, but the query enforces the invariant for all callers including tests and future direct consumers).

Required because under multi-actor an account may host many actors; the resulting role_grant must bind to the actor that actually accepted, not "an" actor on the account picked by query order.

type Uuid

ip?

Optional IP to stamp on the audit events.

type string | null

AcceptOfferResult
#

auth/role_grant_offer_queries.ts view source

AcceptOfferResult import type {AcceptOfferResult} from '@fuzdev/fuz_app/auth/role_grant_offer_queries.js';

Result of query_accept_offer — the role_grant produced (new or pre-existing on race), plus the (now-accepted) offer.

role_grant

type RoleGrant

offer

type RoleGrantOffer

created

true if this call is the one that accepted the offer (new role_grant inserted); false on a replay (or a race loser) returning the already-created, still-held role_grant — the caller then audits and notifies nothing.

type boolean

superseded_offers

Sibling offers superseded by this accept — empty on the race-loser path. Each entry carries its grantor's from_account_id so the caller can fan out role_grant_offer_supersede notifications without a second round-trip.

type SupersededOffer[]

audit_events

Audit events emitted in-transaction — fed back through audit.notify by the caller, which fans out to the registered listeners. Includes one role_grant_offer_supersede per superseded sibling.

type AuditLogEvent[]

Account
#

auth/account_schema.ts view source

Account import type {Account} from '@fuzdev/fuz_app/auth/account_schema.js';

Account — authentication identity. You log in as an account.

id

type Uuid

username

type string

email

type string | null

email_verified

type boolean

password_hash

type string

created_at

type string

created_by

type Uuid | null

updated_at

type string

updated_by

type Uuid | null

deleted_at

Soft-delete tombstone. Non-null means the account is deleted (delete = soft); auth resolution treats it as absent. A hard purge removes the row entirely. See auth/account_queries.ts.

type string | null

deleted_by

Actor that performed the soft-delete (initiator: self / admin / keeper). Paired with deleted_at, mirroring role_grant's revoked_at / revoked_by. Plain UUID (no FK, like created_by / updated_by on this table).

type Uuid | null

ACCOUNT_COLUMNS
#

auth/account_queries.ts view source

readonly ["id", "username", "email", "email_verified", "password_hash", "created_at", "created_by", "updated_at", "updated_by", "deleted_at", "deleted_by"] import {ACCOUNT_COLUMNS} from '@fuzdev/fuz_app/auth/account_queries.js';

The full account column set, named explicitly so a row read fails loud on schema drift.

SELECT * silently omits a dropped column, which the login lookups then misread: query_account_by_username_or_email filters its result with account.deleted_at === null, so a missing deleted_at column reads back as undefined, undefined === null is false, and *every* login resolves to "not found" (401) — a silent, total auth outage instead of an error. Selecting named columns turns that drift into a hard Postgres column "..." does not exist. Mirrors the Rust side (fuz_auth/src/account_queries.rs), which selects named columns and decodes them positionally. Keep in sync with Account and the account DDL in auth/auth_ddl.ts.

account_delete_action_spec
#

auth/admin_action_specs.ts view source

{ method: string; kind: "request_response"; initiator: "frontend"; auth: { account: "required"; actor: "required"; }; side_effects: true; input: ZodDefault<ZodObject<{ account_id: ZodOptional<ZodNullable<$ZodBranded<ZodUUID, "Uuid", "out">>>; acting: ZodOptional<...>; }, $strict>>; output: ZodObject<...>; async: tru... import {account_delete_action_spec} from '@fuzdev/fuz_app/auth/admin_action_specs.js';

Soft-delete an account (reversible tombstone). Self-or-admin: the caller may delete their own account; deleting another requires the admin role (handler-enforced elevation). No admin_ prefix — the privilege lives in the auth check, not the name, so self-service deletion stays open (delete = soft, purge = hard).

ACCOUNT_EMAIL_INDEX
#

auth/auth_ddl.ts view source

"\nCREATE UNIQUE INDEX IF NOT EXISTS idx_account_email ON account (LOWER(email)) WHERE email IS NOT NULL" import {ACCOUNT_EMAIL_INDEX} from '@fuzdev/fuz_app/auth/auth_ddl.js';

ACCOUNT_ID_KEY
#

hono_context.ts view source

"auth_account_id" import {ACCOUNT_ID_KEY} from '@fuzdev/fuz_app/hono_context.js';

Hono context variable name for the authenticated account id.

Set by the auth middleware (session, bearer, or daemon token) on a valid credential. null for unauthenticated requests. The route-spec wrapper / RPC dispatcher's authorization phase reads this when resolving the acting actor; account-grain auth guards (require_auth) and account-grain handlers read it directly.

account_purge_action_spec
#

auth/admin_action_specs.ts view source

{ method: string; kind: "request_response"; initiator: "frontend"; auth: { account: "required"; actor: "required"; roles: string[]; credential_types: string[]; }; side_effects: true; input: ZodObject<{ account_id: $ZodBranded<...>; confirm: ZodOptional<...>; acting: ZodOptional<...>; }, $strict>; output: ZodObject<.... import {account_purge_action_spec} from '@fuzdev/fuz_app/auth/admin_action_specs.js';

Hard-purge an account (keeper-gated, irreversible). Keeper credential (daemon_token) + keeper role + explicit confirm: true. Not admin-reachable and not self-service — the most dangerous operation is the most restricted. purge = hard; the word + gating + WARN flag the danger (fail-loud).

ACCOUNT_SCHEMA
#

auth/auth_ddl.ts view source

"\nCREATE TABLE IF NOT EXISTS account (\n id UUID PRIMARY KEY DEFAULT gen_random_uuid(),\n username TEXT UNIQUE NOT NULL,\n email TEXT,\n email_verified BOOLEAN NOT NULL DEFAULT false,\n password_hash TEXT NOT NULL,\n created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),\n created_by UUID,\n updated_at TIMESTAMPTZ ... import {ACCOUNT_SCHEMA} from '@fuzdev/fuz_app/auth/auth_ddl.js';

account_session_list_action_spec
#

auth/account_action_specs.ts view source

{ method: string; kind: "request_response"; initiator: "frontend"; auth: { account: "required"; actor: "none"; }; side_effects: false; input: ZodVoid; output: ZodObject<{ sessions: ZodArray<ZodObject<{ id: $ZodBranded<ZodString, "SessionId", "out">; account_id: $ZodBranded<...>; created_at: ZodString; expires_at: Zo... import {account_session_list_action_spec} from '@fuzdev/fuz_app/auth/account_action_specs.js';

account_session_revoke_action_spec
#

auth/account_action_specs.ts view source

{ method: string; kind: "request_response"; initiator: "frontend"; auth: { account: "required"; actor: "none"; credential_types: string[]; }; side_effects: true; input: ZodObject<{ session_id: $ZodBranded<ZodString, "SessionId", "out">; }, $strict>; output: ZodObject<...>; async: true; description: string; } import {account_session_revoke_action_spec} from '@fuzdev/fuz_app/auth/account_action_specs.js';

account_session_revoke_all_action_spec
#

auth/account_action_specs.ts view source

{ method: string; kind: "request_response"; initiator: "frontend"; auth: { account: "required"; actor: "none"; credential_types: string[]; }; side_effects: true; input: ZodVoid; output: ZodObject<...>; async: true; description: string; } import {account_session_revoke_all_action_spec} from '@fuzdev/fuz_app/auth/account_action_specs.js';

account_sessions_rpc_context
#

ui/account_sessions_state.svelte.ts view source

{ get: (error_message?: string | undefined) => () => AccountSessionsRpc; get_maybe: () => (() => AccountSessionsRpc) | undefined; set: (value: () => AccountSessionsRpc) => () => AccountSessionsRpc; } import {account_sessions_rpc_context} from '@fuzdev/fuz_app/ui/account_sessions_state.svelte.js';

Svelte context carrying the reactive AccountSessionsRpc accessor. Mirrors the admin-side RPC contexts. get() throws when no provisioner ran above the component — the adapter is required.

account_status_route_shape
#

auth/account_route_schema.ts view source

{ method: "GET"; path: string; auth: { account: "none"; actor: "none"; }; reads_credential: true; description: string; input: ZodNull; output: ZodObject<{ account: ZodObject<{ id: $ZodBranded<ZodUUID, "Uuid", "out">; username: ZodPipe<...>; email: ZodNullable<...>; email_verified: ZodBoolean; created_at: ZodString; ... import {account_status_route_shape} from '@fuzdev/fuz_app/auth/account_route_schema.js';

The GET /status route shape minus its handler — pure hono-free data. create_account_status_route_spec spreads this and attaches the live handler (which reads the account id off the request context); surface generation spreads it with a stub handler.

The path is relative like the sibling account shapes (/login, /verify), so it composes under prefix_route_specs('/api/account', …) into /api/account/status. create_account_route_specs bundles it (so every account surface serves /status, matching the Rust account_router); mirror Rust by mounting it as part of the account family, not separately.

account_token_create_action_spec
#

auth/account_action_specs.ts view source

{ method: string; kind: "request_response"; initiator: "frontend"; auth: { account: "required"; actor: "none"; credential_types: string[]; }; side_effects: true; input: ZodObject<{ name: ZodDefault<ZodString>; scope: ZodDiscriminatedUnion<...>; lifetime: ZodDiscriminatedUnion<...>; }, $strict>; output: ZodObject<...... import {account_token_create_action_spec} from '@fuzdev/fuz_app/auth/account_action_specs.js';

credential_types: ['session'] — see docs/security.md §Credential-channel gating.

rate_limit: 'account' bounds the burn rate of API-token creates. The outstanding-token count is already capped by max_tokens (via query_api_token_enforce_limit), but the per-account *rate* of churn is not — without this cap, a caller could rotate tokens in a tight loop to amplify token_create audit churn or attempt to provoke downstream rate-limit hot spots.

account_token_list_action_spec
#

auth/account_action_specs.ts view source

{ method: string; kind: "request_response"; initiator: "frontend"; auth: { account: "required"; actor: "none"; }; side_effects: false; input: ZodVoid; output: ZodObject<{ tokens: ZodArray<ZodObject<{ id: ZodString; ... 6 more ...; scope: ZodString; }, $strict>>; }, $strict>; async: true; description: string; } import {account_token_list_action_spec} from '@fuzdev/fuz_app/auth/account_action_specs.js';

account_token_revoke_action_spec
#

auth/account_action_specs.ts view source

{ method: string; kind: "request_response"; initiator: "frontend"; auth: { account: "required"; actor: "none"; credential_types: string[]; }; side_effects: true; input: ZodObject<{ token_id: ZodString; }, $strict>; output: ZodObject<...>; async: true; description: string; } import {account_token_revoke_action_spec} from '@fuzdev/fuz_app/auth/account_action_specs.js';

account_undelete_action_spec
#

auth/admin_action_specs.ts view source

{ method: string; kind: "request_response"; initiator: "frontend"; auth: { account: "required"; actor: "required"; roles: string[]; }; side_effects: true; input: ZodObject<{ account_id: $ZodBranded<ZodUUID, "Uuid", "out">; acting: ZodOptional<...>; }, $strict>; output: ZodObject<...>; async: true; description: strin... import {account_undelete_action_spec} from '@fuzdev/fuz_app/auth/admin_action_specs.js';

Reactivate a soft-deleted account (clears the tombstone). Admin-only — there is no self path because a soft-deleted account can't authenticate (auth resolution excludes it and its sessions are revoked), so reactivation is always an admin acting on another account. The inverse of account_delete; does not restore revoked sessions/tokens (delete = soft, purge = hard).

ACCOUNT_USERNAME_CI_INDEX
#

auth/auth_ddl.ts view source

"\nCREATE UNIQUE INDEX IF NOT EXISTS idx_account_username_ci ON account (LOWER(username))" import {ACCOUNT_USERNAME_CI_INDEX} from '@fuzdev/fuz_app/auth/auth_ddl.js';

account_verify_action_spec
#

auth/account_action_specs.ts view source

{ method: string; kind: "request_response"; initiator: "frontend"; auth: { account: "required"; actor: "none"; }; side_effects: false; input: ZodVoid; output: ZodObject<{ id: $ZodBranded<ZodUUID, "Uuid", "out">; username: ZodPipe<...>; email: ZodNullable<...>; email_verified: ZodBoolean; created_at: ZodString; }, $s... import {account_verify_action_spec} from '@fuzdev/fuz_app/auth/account_action_specs.js';

AccountActionOptions
#

AccountDeleteInput
#

auth/admin_action_specs.ts view source

value + type

{ account_id?: (string & $brand<"Uuid">) | null | undefined; acting?: (string & $brand<"Uuid">) | undefined; } import {AccountDeleteInput} from '@fuzdev/fuz_app/auth/admin_action_specs.js';

Input for account_delete (soft delete). account_id is optional — omitted (or equal to the caller's own account) is a self-delete; a different account requires the admin role (handler-enforced elevation, like role_grant_offer_list).

account_id?

type (string & $brand<"Uuid">) | null

acting?

type string & $brand<"Uuid">

AccountDeleteOutput
#

auth/admin_action_specs.ts view source

value + type

{ ok: true; deleted: boolean; } import {AccountDeleteOutput} from '@fuzdev/fuz_app/auth/admin_action_specs.js';

Output for account_delete.

ok

type true

deleted

type boolean

AccountIdentitySnapshot
#

auth/account_queries.ts view source

AccountIdentitySnapshot import type {AccountIdentitySnapshot} from '@fuzdev/fuz_app/auth/account_queries.js';

Identifying values snapshotted into a deletion/purge audit event so the identity behind a now-orphaned audit_log id isn't lost. Mirrors the Rust AccountIdentitySnapshot.

username

type string

email

type string | null

AccountLifecycleCrossTestOptions
#

testing/cross_backend/account_lifecycle.ts view source

RpcPathCapabilityGatedCrossSuiteOptions import type {AccountLifecycleCrossTestOptions} from '@fuzdev/fuz_app/testing/cross_backend/account_lifecycle.js';

Options for the account-lifecycle parity suite. The standard capability-gating RPC-dispatched cross-suite shape (setup_test / capabilities / rpc_path); aliases RpcPathCapabilityGatedCrossSuiteOptions rather than duplicating.

rpc_path?

RPC endpoint path the methods are mounted on. Default /api/rpc.

type string

readonly

setup_test

Per-test fixture-producing function (fresh keeper + db per call).

type (): Promise<TestFixtureBase>

readonly
returns Promise<TestFixtureBase>

capabilities

Backend capability declarations — each suite gates on its own flag.

type BackendCapabilities

readonly

AccountPurgeInput
#

auth/admin_action_specs.ts view source

value + type

{ account_id: string & $brand<"Uuid">; confirm?: boolean | undefined; acting?: (string & $brand<"Uuid">) | undefined; } import {AccountPurgeInput} from '@fuzdev/fuz_app/auth/admin_action_specs.js';

Input for account_purge (hard, irreversible delete). Keeper-only.

account_id

type string & $brand<"Uuid">

confirm?

type boolean

acting?

type string & $brand<"Uuid">

AccountPurgeOutput
#

auth/admin_action_specs.ts view source

value + type

{ ok: true; purged: boolean; } import {AccountPurgeOutput} from '@fuzdev/fuz_app/auth/admin_action_specs.js';

Output for account_purge.

ok

type true

purged

type boolean

AccountRouteOptions
#

auth/account_routes.ts view source

AccountRouteOptions import type {AccountRouteOptions} from '@fuzdev/fuz_app/auth/account_routes.js';

Per-factory configuration for account route specs.

inheritance

login_ip_rate_limiter

Rate limiter for login + password-change attempts, keyed by client IP. Pass null to disable. The distributed-spray backstop: login is the one surface that guesses *other* accounts' credentials, so this bucket is never refunded on success (see RateLimiter.reset). Password change shares it — it is password-bearing on the same account grain, and the Rust spine shares the same instance across both.

type RateLimiter | null

login_account_rate_limiter

Rate limiter for login attempts, keyed by submitted username. Pass null to disable.

type RateLimiter | null

max_sessions?

Max active sessions per account. Evicts oldest on login. Default 5, null disables.

type number | null

login_fail_floor_ms?

Minimum wall-clock time (ms) for login 401 responses. Set to 0 or a negative number to disable (e.g., in tests). Default DEFAULT_LOGIN_FAIL_FLOOR_MS.

type number

login_fail_jitter_ms?

Uniform jitter window (±ms) layered on the floor. Set to 0 to disable jitter while keeping the floor. Default DEFAULT_LOGIN_FAIL_JITTER_MS.

type number

bootstrap_status?

Runtime bootstrap status for the bundled GET /status route — when available, its unauthenticated 401 carries bootstrap_available: true so a fresh frontend can route to the bootstrap flow. Pass ctx.bootstrap_status (the live BootstrapStatus ref) so the flag tracks the one-shot bootstrap completing. Omit when no bootstrap flow is wired — /status is still served, just without the flag.

type { available: boolean; }

AccountRouteShapeOptions
#

auth/account_route_schema.ts view source

AccountRouteShapeOptions import type {AccountRouteShapeOptions} from '@fuzdev/fuz_app/auth/account_route_schema.js';

Option inputs that shape the account route metadata (not its handlers).

login_account_rate_limited

Whether a per-account login rate limiter is wired — toggles /password's rate_limit.

type boolean

AccountSessions
#

AccountSessionsRpc
#

ui/account_sessions_state.svelte.ts view source

AccountSessionsRpc import type {AccountSessionsRpc} from '@fuzdev/fuz_app/ui/account_sessions_state.svelte.js';

Narrow RPC surface consumed by AccountSessionsState. Consumers adapt their typed RPC client to this shape. Mirrors the other per-domain *Rpc interfaces (AdminAccountsRpc, AuditLogRpc, AdminInvitesRpc).

The three methods wrap the corresponding action specs on auth/account_actions.ts:

  • list → account_session_list
  • revoke → account_session_revoke (IDOR-guarded by account_id server-side)
  • revoke_all → account_session_revoke_all

list

type (): Promise<{ sessions: { id: string & $brand<"SessionId">; account_id: string & $brand<"Uuid">; created_at: string; expires_at: string; }[]; }>

returns Promise<{ sessions: { id: string & $brand<"SessionId">; account_id: string & $brand<"Uuid">; created_at: string; expires_at: string; }[]; }>

revoke

type (params: { session_id: string & $brand<"SessionId">; }): Promise<{ ok: true; revoked: boolean; }>

params

type { session_id: string & $brand<"SessionId">; }
returns Promise<{ ok: true; revoked: boolean; }>

revoke_all

type (): Promise<{ ok: true; count: number; }>

returns Promise<{ ok: true; count: number; }>

AccountSessionsState
#

ui/account_sessions_state.svelte.ts view source

import {AccountSessionsState} from '@fuzdev/fuz_app/ui/account_sessions_state.svelte.js';

list

type AsyncSlot<void, string>

readonly

revoke

type KeyedAsyncSlot<string, void, string>

readonly

revoke_all

type AsyncSlot<void, string>

readonly

sessions

type AuthSessionJson[]

$state.raw

active_count

type number

readonly $derived

constructor

type new (options: AccountSessionsStateOptions): AccountSessionsState

options

fetch

type (): Promise<void>

returns Promise<void>

submit_revoke

type (id: string & $brand<"SessionId">): Promise<void>

id

returns Promise<void>

submit_revoke_all

type (): Promise<void>

returns Promise<void>

AccountSessionsStateOptions
#

ui/account_sessions_state.svelte.ts view source

AccountSessionsStateOptions import type {AccountSessionsStateOptions} from '@fuzdev/fuz_app/ui/account_sessions_state.svelte.js';

get_rpc

Reactive accessor for the RPC adapter. Matches the get_rpc pattern on the admin state classes.

type (): AccountSessionsRpc

AccountStatusInput
#

auth/account_route_schema.ts view source

value + type

null import {AccountStatusInput} from '@fuzdev/fuz_app/auth/account_route_schema.js';

Input for GET /api/account/status. No parameters — caller is the subject.

AccountStatusOptions
#

auth/account_routes.ts view source

AccountStatusOptions import type {AccountStatusOptions} from '@fuzdev/fuz_app/auth/account_routes.js';

Options for the account status route spec.

path?

Override the default path (/api/account/status).

type string

bootstrap_status?

Runtime bootstrap status — when available, 401 responses include bootstrap_available.

type { available: boolean; }

AccountStatusOutput
#

auth/account_route_schema.ts view source

value + type

{ account: { id: string & $brand<"Uuid">; username: string; email: string | null; email_verified: boolean; created_at: string; }; actor: { id: string & $brand<"Uuid">; name: string; } | null; role_grants: { ...; }[]; } import {AccountStatusOutput} from '@fuzdev/fuz_app/auth/account_route_schema.js';

Output for GET /api/account/status.

account

type SessionAccountJson

actor

type ActorSummaryJson | null

role_grants

type RoleGrantSummaryJson[]

AccountStatusUnauthenticatedError
#

auth/account_route_schema.ts view source

value + type

{ [x: string]: unknown; error: "authentication_required"; bootstrap_available?: boolean | undefined; } import {AccountStatusUnauthenticatedError} from '@fuzdev/fuz_app/auth/account_route_schema.js';

Error body for GET /api/account/status on the unauthenticated path.

error

type "authentication_required"

bootstrap_available?

type boolean

[key: string]

type unknown

AccountUndeleteInput
#

auth/admin_action_specs.ts view source

value + type

{ account_id: string & $brand<"Uuid">; acting?: (string & $brand<"Uuid">) | undefined; } import {AccountUndeleteInput} from '@fuzdev/fuz_app/auth/admin_action_specs.js';

Input for account_undelete (reactivation). account_id is required — unlike account_delete there is no self path: a soft-deleted account can't authenticate (auth resolution excludes it, sessions are revoked), so reactivation is always an admin acting on another account.

account_id

type string & $brand<"Uuid">

acting?

type string & $brand<"Uuid">

AccountUndeleteOutput
#

auth/admin_action_specs.ts view source

value + type

{ ok: true; undeleted: boolean; } import {AccountUndeleteOutput} from '@fuzdev/fuz_app/auth/admin_action_specs.js';

Output for account_undelete.

ok

type true

undeleted

type boolean

AccountVanishedError
#

http/error_schemas.ts view source

value + type

{ [x: string]: unknown; error: "account_vanished"; } import {AccountVanishedError} from '@fuzdev/fuz_app/http/error_schemas.js';

error

type "account_vanished"

[key: string]

type unknown

ActingActor
#

http/auth_shape.ts view source

value + type

(string & $brand<"Uuid">) | undefined

type (string & $brand<"Uuid">) | undefined

import {ActingActor} from '@fuzdev/fuz_app/http/auth_shape.js';

acting field shared by every input that needs the caller's acting actor. Declaring acting: ActingActor on a route or action input signals to the dispatcher's authorization phase to resolve an actor against the authenticated account: it runs resolve_acting_actor, builds the actor-bound RequestContext, and loads role_grants before auth guards fire.

Resolution rules: omitted + 1 actor → use it; omitted + multiple actors → actor_required with the available list; supplied + on the account → use it; supplied + foreign actor → actor_not_on_account.

Account-grain routes — input doesn't declare acting and auth doesn't require role_grants — skip resolution entirely; their RequestContext.actor is null and the audit envelope's actor_id stays null.

Lives next to RouteAuth because the two are paired by registry-time invariant 2: auth.actor !== 'none' ⟺ input (or query, on REST GETs) declares acting?: ActingActor. Keeping the contract in one module removes the http/ → auth/ import that an earlier split forced.

ActingSlots
#

http/auth_shape.ts view source

ActingSlots import type {ActingSlots} from '@fuzdev/fuz_app/http/auth_shape.js';

Slots where a spec may declare the acting?: ActingActor field — input for both REST + actions; query for REST GETs that bi-locate acting on the query schema (actions have no query shape, so the field is omitted on action call sites).

input

type ZodType<unknown, unknown, $ZodTypeInternals<unknown, unknown>>

query?

type ZodType<unknown, unknown, $ZodTypeInternals<unknown, unknown>>

Action
#

actions/action_types.ts view source

Action<TSpec> import type {Action} from '@fuzdev/fuz_app/actions/action_types.js';

A spec paired with its optional handler — the composable unit passed to register_action_ws and create_rpc_client. The server uses both fields; the client reads only spec (the handler is ignored, harmless). Shared fuz_app primitives (e.g. heartbeat_action) export a complete tuple so consumers spread them into both sides' actions arrays without inventing per-repo ping plumbing.

Polymorphic on kind: request_response specs require a handler for dispatch; remote_notification specs may declare a stub handler for symmetry but are dispatcher-handled (e.g. cancel); local_call specs never reach a network dispatcher. The WS dispatcher only invokes handlers on request_response actions; everything else is registry-only.

generics

Action<TSpec extends ActionSpecUnion = ActionSpecUnion>
TSpec
constraint ActionSpecUnion

spec

type TSpec

handler?

Server-side handler — invoked by dispatchers on request_response actions. Ignored for client-only specs and dispatcher-handled notifications.

type (input: any, ctx: ActionContext): any

input

type any

ctx

returns any

action_event_phase_by_kind
#

actions/action_event_types.ts view source

Record<"request_response" | "remote_notification" | "local_call", readonly ("send_request" | "receive_request" | "send_response" | "receive_response" | "send_error" | "receive_error" | "send" | "receive" | "execute")[]>

type Record<ActionKind, readonly ActionEventPhase[]>

import {action_event_phase_by_kind} from '@fuzdev/fuz_app/actions/action_event_types.js';

action_event_phase_transitions
#

actions/action_event_types.ts view source

Record<"send_request" | "receive_request" | "send_response" | "receive_response" | "send_error" | "receive_error" | "send" | "receive" | "execute", "send_request" | "receive_request" | ... 7 more ... | null>

type Record<ActionEventPhase, "send_request" | "receive_request" | "send_response" | "receive_response" | "send_error" | "receive_error" | "send" | "receive" | "execute" | null>

import {action_event_phase_transitions} from '@fuzdev/fuz_app/actions/action_event_types.js';

action_event_step_transitions
#

action_manifest_entry
#

testing/cross_backend/action_manifest.ts view source

(spec: { readonly method: string; readonly auth: { account: "optional" | "none" | "required"; actor: "optional" | "none" | "required"; roles?: readonly string[] | undefined; credential_types?: readonly string[] | undefined; required_scope?: string | undefined; }; readonly side_effects: boolean; readonly rate_limit?: "both" | ... 2 more ... | undefined; }): { ...; } import {action_manifest_entry} from '@fuzdev/fuz_app/testing/cross_backend/action_manifest.js';

Normalize one spec's auth + side-effects into a manifest entry. Pulls the four auth axes off RouteAuth (the same shape the Rust AuthSpec mirrors) and flattens optional roles / credential_types to sorted arrays.

spec

type { readonly method: string; readonly auth: { account: "optional" | "none" | "required"; actor: "optional" | "none" | "required"; roles?: readonly string[] | undefined; credential_types?: readonly string[] | undefined; required_scope?: string | undefined; }; readonly side_effects: boolean; readonly rate_limit?: "both"...

returns

ActionManifestEntry

action_method_enum_kinds_all
#

ACTION_RATE_LIMIT_ENABLED_ENV
#

testing/cross_backend/spine_surface_constants.ts view source

"FUZ_ACTION_RATE_LIMIT_ENABLED" import {ACTION_RATE_LIMIT_ENABLED_ENV} from '@fuzdev/fuz_app/testing/cross_backend/spine_surface_constants.js';

Env var both spine binaries read to enable their action rate limiters ('true' on / unset off) — one per-IP and one per-account limiter, shared by the RPC and WS endpoints (and, on the Rust stub, the auth-family handlers that charge in-handler). The cross-language contract for the action rate-limit cross suite (ws_action_rate_limit.ts); same delivery as LOGIN_RATE_LIMIT_ENABLED_ENV. Off, neither spine builds an action limiter.

action_rate_limit_env
#

testing/cross_backend/default_backend_configs.ts view source

(options: ActionRateLimitEnvOptions): Record<string, string> import {action_rate_limit_env} from '@fuzdev/fuz_app/testing/cross_backend/default_backend_configs.js';

The extra_env entries carrying the action rate-limit toggles, shared by the TS and Rust backend configs so one option pair drives both impls. action_rate_limit_max_attempts is passed through even without the enable flag, so the binary refuses the combination instead of the harness hiding it.

options

returns

Record<string, string>

ACTION_RATE_LIMIT_MAX_ATTEMPTS_ENV
#

testing/cross_backend/spine_surface_constants.ts view source

"FUZ_ACTION_RATE_LIMIT_MAX_ATTEMPTS" import {ACTION_RATE_LIMIT_MAX_ATTEMPTS_ENV} from '@fuzdev/fuz_app/testing/cross_backend/spine_surface_constants.js';

Env var replacing both action limiters' max_attempts (a positive integer; the windows stay default_action_ip_rate_limit's / default_action_account_rate_limit's). Both spine binaries refuse to boot when it is set without ACTION_RATE_LIMIT_ENABLED_ENV or is not a positive integer, so a suite never asserts a throttle the backend didn't build.

ActionActorContext
#

actions/action_rpc.ts view source

ActionActorContext import type {ActionActorContext} from '@fuzdev/fuz_app/actions/action_rpc.js';

ActionContext narrowed to a resolved acting actor.

Used by handlers whose spec declares auth.actor === 'required' — the dispatcher's authorization phase resolves an actor (per registry-time invariant 2 the input declares acting?: ActingActor), so ctx.auth.actor is non-null. Selected automatically by rpc_action's conditional return type for the actor-implying tier.

inheritance

extends: Omit<ActionContext, 'auth'>

auth

type RequestActorContext

ActionAuthContext
#

actions/action_rpc.ts view source

ActionAuthContext import type {ActionAuthContext} from '@fuzdev/fuz_app/actions/action_rpc.js';

ActionContext narrowed to a non-null RequestContext.

Used by handlers whose spec declares auth.account === 'required' (with auth.actor === 'none') — the dispatcher's pre-authorization 401 gate guarantees request_context is populated before the handler runs, but the actor slot stays null because no acting resolution happened. Selected automatically by rpc_action's conditional return type for the account-grain tier.

inheritance

extends: Omit<ActionContext, 'auth'>

auth

type RequestContext

ActionContext
#

actions/action_rpc.ts view source

ActionContext import type {ActionContext} from '@fuzdev/fuz_app/actions/action_rpc.js';

Per-request context provided to action handlers across every transport (HTTP RPC, WebSocket, REST bridge). Built once per dispatched action by perform_action and threaded into the handler.

auth is RequestContext | null — handlers for authenticated actions can narrow via the dispatcher's authorization-phase guarantee.

Single handler context shape across every transport. Consumers inject domain deps via factory closures the same way HTTP RPC factories do.

auth

The authenticated identity, or null for public routes.

type RequestContext | null

request_id

The JSON-RPC request ID from the envelope.

type JsonrpcRequestId

connection_id?

Stable per-socket connection id on WebSocket transport; undefined on HTTP RPC. Consumers key per-connection domain state on this directly; HTTP handlers ignore it.

type Uuid

request_client?

Initiate a JSON-RPC request to the originating client and await its typed reply — the server→client direction of ActionPeer. Present only on the WebSocket transport (it targets the originating socket); undefined on HTTP RPC, where there is no return socket. Handlers that depend on it must handle its absence — e.g. peer/ping surfaces peer_no_transport.

type (method: string, params: { [x: string]: unknown; } | undefined, options?: PeerRequestOptions | undefined): Promise<PeerRequestOutcome>

method

type string

params

type JsonrpcRequestParams | undefined

options?

optional
returns Promise<PeerRequestOutcome>

db

Transaction-scoped when spec.side_effects is true (the dispatcher wraps in db.transaction); pool-level otherwise. Handlers that need rollback-resilient writes call deps.audit.emit(ctx, input), which captures the pool inside its closure.

type Db

pending_effects

Eager fire-and-forget queue — push the in-flight Promise<void> for pool writes already running (audit emits, api-token usage tracking). Drained via flush_pending_effects after the handler returns.

type Promise<void>[]

post_commit_effects

Deferred post-commit thunks — do not push directly; reach for emit_after_commit(ctx, fn) from http/pending_effects.ts. The flush site invokes each thunk after the handler (and any wrapping db.transaction) returns.

type (() => void | Promise<void>)[]

client_ip

Resolved client IP from the trusted-proxy middleware — 'unknown' if the middleware wasn't in the stack (e.g. WS dispatch) or couldn't resolve. Thread into deps.audit.emit as ip: ctx.client_ip for every user-initiated action so RPC audit rows match the REST convention. Pass null only for rows written outside a request (e.g. the role_grant_offer_expire cleanup sweep in auth/cleanup.ts).

type string

credential_type

Credential channel the request arrived on ('session' | 'api_token' | 'daemon_token'), or null for anonymous requests. Same value the dispatcher's credential_types gate consumed at step 3 — exposed here so handlers can record it in audit metadata (defense in depth: the gate may be loosened or bypassed in a future refactor, but the audit row preserves what actually authenticated the request).

type "session" | "api_token" | "daemon_token" | null

log

Logger instance.

type Logger

notify

Send a request-scoped JSON-RPC notification to the originator.

On streaming transports (WebSocket) this routes to the originating connection only. On the HTTP RPC transport this is a no-op with a DEV-mode warn — non-streaming transports have no channel for mid- request notifications. The streams field on an ActionSpec names the notification method this handler is expected to emit.

type (method: string, params: unknown): void

method

type string

params

type unknown
returns void

signal

AbortSignal that fires when the originating request is cancelled (client disconnect on HTTP, socket close or per-request cancel notification on WebSocket). Streaming handlers should check this for early termination.

type AbortSignal

ActionDispatcher
#

actions/action_dispatcher.ts view source

import {ActionDispatcher} from '@fuzdev/fuz_app/actions/action_dispatcher.js';

environment

type ActionEventEnvironment

readonly

transports

type Transports

readonly

default_send_options

type Omit<ActionDispatcherSendOptions, "signal">

constructor

type new (options: ActionDispatcherOptions): ActionDispatcher

options

send

Resolve a transport (per-call name → default name → registry default) and forward the message. Catches unexpected throws and converts them to JSON-RPC error responses — this method never throws.

type (message: { [x: string]: unknown; jsonrpc: "2.0"; id: string | number; method: string; params?: { [x: string]: unknown; } | undefined; }, options?: ActionDispatcherSendOptions | undefined): Promise<...>

message

options?

optional
returns Promise<JsonrpcResponseOrError>

the response envelope for requests, or null for successful notifications (JsonrpcErrorResponse if the notification's transport send failed)

receive

Dispatch an inbound JSON-RPC message — request, notification, or malformed envelope. Never throws; unexpected failures become JSON-RPC error responses.

type (message: unknown): Promise<{ [x: string]: unknown; jsonrpc: "2.0"; id: string | number; result: JSONType; } | { [x: string]: unknown; jsonrpc: "2.0"; id: string | number | null; error: { [x: string]: unknown; code: -32700 | ... 4 more ... | (number & $brand<...>); message: string; data?: unknown; }; } | { ...; } | null>

message

type unknown

response message for requests, null for notifications, or an invalid_request error for malformed input

ActionDispatcherOptions
#

ActionDispatcherSendOptions
#

actions/action_dispatcher.ts view source

ActionDispatcherSendOptions import type {ActionDispatcherSendOptions} from '@fuzdev/fuz_app/actions/action_dispatcher.js';

Per-call options for ActionDispatcher.send. Extends TransportSendOptions with transport_name for per-call transport selection. The peer-wide default for any field lives on ActionDispatcherOptions.default_send_options — set queue: true there once for client-authoritative peers and override per-call for exceptions (e.g. high-frequency position sync where stale replays are wrong).

inheritance

transport_name?

type string

ActionEvent
#

actions/action_event.ts view source

import {ActionEvent} from '@fuzdev/fuz_app/actions/action_event.js';

Action event that manages the lifecycle of an action through its state machine.

generics

ActionEvent<TMethod extends string = string, TPhase extends ActionEventPhase = ActionEventPhase, TStep extends ActionEventStep = ActionEventStep>
TMethod
constraint string
default string
TPhase
constraint ActionEventPhase
TStep
constraint ActionEventStep

environment

type ActionEventEnvironment

readonly

spec

method narrows to TMethod so consumers passing a typed TApi to create_rpc_client get event.spec.method typed as the union of their API's method names rather than plain string. The runtime value comes from lookup_action_spec(method) keyed off the Proxy get trap, so the narrowing matches the dispatched method.

type (RequestResponseActionSpec & { method: TMethod; }) | (RemoteNotificationActionSpec & { method: TMethod; }) | (LocalCallActionSpec & { method: TMethod; })

readonly

constructor

type new <TMethod extends string = string, TPhase extends ActionEventPhase = "send_request" | "receive_request" | "send_response" | "receive_response" | "send_error" | "receive_error" | "send" | "receive" | "execute", TStep extends ActionEventStep = "initial" | ... 3 more ... | "failed">(environment: ActionEventEnvironment, spec: { ...; } | ... 1 more ... | { ...; }, data: ActionEventDataUnion<...>): ActionEvent<...>

environment

spec

data

toJSON

type (): ActionEventDataUnion<TMethod>

observe

Subscribe a listener fired on every data transition.

type (listener: ActionEventChangeObserver<TMethod>): () => void

listener

called with (new_data, old_data, event) after each mutation

type ActionEventChangeObserver<TMethod>
returns () => void

unsubscribe function

mutates

  • this — adds `listener` to the internal observer set

set_data

Replace the event's data and notify observers.

type (new_data: ActionEventDataUnion<TMethod>): void

new_data

returns void

mutates

  • this — sets `data` and fires every registered observer in insertion order

parse

Parse the event's payload against the action's schemas — spec.input for outgoing and incoming calls, spec.output for a received response.

A received response's result is validated like any other inbound data, except that keys spec.output doesn't declare are dropped rather than refused (safe_parse_dropping_unknown_keys). On success output becomes the parsed value (brands, defaults, and transforms applied); on any other mismatch the event moves to receive_error with an internal_error whose data.reason is ERROR_RESPONSE_OUTPUT_INVALID (the remote broke its contract — not the caller's input), the same path an error response takes.

type (): this

returns this

this for chaining with handle_async / handle_sync

throws

  • Error - if called from a step other than `initial`

mutates

  • this — transitions step from `initial` to `parsed` (or to `failed` / `receive_error` on validation or response error)

handle_async

Execute the handler for the current phase.

type (): Promise<void>

returns Promise<void>

throws

  • Error - if called from a step other than `parsed` (or `failed`, which no-ops). Handler-thrown `ThrownJsonrpcError` is caught and routed through error phases; other throws are wrapped as `internal_error` carrying the thrown message (see `#to_handler_error`).

mutates

  • this — transitions step `parsed → handling → handled`, or to `failed` / `send_error` / `receive_error` on handler throw. No-op when already `failed`.

handle_sync

Execute handler synchronously (only for sync local_call actions).

type (): void

returns void

throws

  • Error - if the spec is not a sync `local_call`, or if called from a step other than `parsed` (or `failed`, which no-ops).

mutates

  • this — transitions step `parsed → handling → handled`, or to `failed` on handler throw. No-op when already `failed`.

transition

Transition to a new phase.

type (phase: "send_request" | "receive_request" | "send_response" | "receive_response" | "send_error" | "receive_error" | "send" | "receive" | "execute"): void

phase

the next phase to transition into

returns void

throws

  • Error - if called from a step other than `handled` (or `failed`, which no-ops), or if the phase transition is illegal for the current phase

mutates

  • this — replaces `data` with a fresh phase-initial record, carrying forward `request` / `response` / `error` / `output` as appropriate for the kind/phase pair

is_complete

type (): boolean

returns boolean

update_progress

type (progress: unknown): void

progress

type unknown
returns void

set_request

type (request: { [x: string]: unknown; jsonrpc: "2.0"; id: string | number; method: string; params?: { [x: string]: unknown; } | undefined; }): void

request

returns void

set_response

type (response: { [x: string]: unknown; jsonrpc: "2.0"; id: string | number; result: JSONType; } | { [x: string]: unknown; jsonrpc: "2.0"; id: string | number | null; error: { [x: string]: unknown; code: -32700 | -32600 | -32601 | -32602 | -32603 | (number & $brand<...>); message: string; data?: unknown; }; }): void

response

returns void

set_notification

type (notification: { [x: string]: unknown; jsonrpc: "2.0"; method: string; params?: { [x: string]: unknown; } | undefined; }): void

notification

returns void

data

type ({ kind: "request_response"; phase: "send_request"; step: "initial"; method: TMethod; executor: "frontend" | "backend"; input: unknown; output: null; error: null; progress: null; request: null; response: null; notification: null; } & { phase: TPhase; step: TStep; }) | ({ kind: "request_response"; phase: "send_request"; step: "parsed"; method: TMethod; executor: "frontend" | "backend"; input: unknown; output: null; error: null; progress: null; request: null; response: null; notification: null; } & { phase: TPhase; step: TStep; }) | ({ kind: "request_response"; phase: "send_request"; step: "handling"; method: TMethod; executor: "frontend" | "backend"; input: unknown; output: null; error: null; progress: unknown; request: { [x: string]: unknown; jsonrpc: "2.0"; id: string | number; method: string; params?: { [x: string]: unknown; } | undefined; }; response: null; notification: null; } & { phase: TPhase; step: TStep; }) | ({ kind: "request_response"; phase: "send_request"; step: "handled"; method: TMethod; executor: "frontend" | "backend"; input: unknown; output: null; error: null; progress: unknown; request: { [x: string]: unknown; jsonrpc: "2.0"; id: string | number; method: string; params?: { [x: string]: unknown; } | undefined; }; response: null; notification: null; } & { phase: TPhase; step: TStep; }) | ({ kind: "request_response"; phase: "send_request"; step: "failed"; method: TMethod; executor: "frontend" | "backend"; input: unknown; output: null; error: { [x: string]: unknown; code: -32700 | -32600 | -32601 | -32602 | -32603 | (number & $brand<"JsonrpcServerErrorCode">); message: string; data?: unknown; }; progress: unknown; request: { [x: string]: unknown; jsonrpc: "2.0"; id: string | number; method: string; params?: { [x: string]: unknown; } | undefined; } | null; response: null; notification: null; } & { phase: TPhase; step: TStep; }) | ({ kind: "request_response"; phase: "receive_request"; step: "initial"; method: TMethod; executor: "frontend" | "backend"; input: unknown; output: null; error: null; progress: null; request: { [x: string]: unknown; jsonrpc: "2.0"; id: string | number; method: string; params?: { [x: string]: unknown; } | undefined; }; response: null; notification: null; } & { phase: TPhase; step: TStep; }) | ({ kind: "request_response"; phase: "receive_request"; step: "parsed"; method: TMethod; executor: "frontend" | "backend"; input: unknown; output: null; error: null; progress: null; request: { [x: string]: unknown; jsonrpc: "2.0"; id: string | number; method: string; params?: { [x: string]: unknown; } | undefined; }; response: null; notification: null; } & { phase: TPhase; step: TStep; }) | ({ kind: "request_response"; phase: "receive_request"; step: "handling"; method: TMethod; executor: "frontend" | "backend"; input: unknown; output: null; error: null; progress: unknown; request: { [x: string]: unknown; jsonrpc: "2.0"; id: string | number; method: string; params?: { [x: string]: unknown; } | undefined; }; response: null; notification: null; } & { phase: TPhase; step: TStep; }) | ({ kind: "request_response"; phase: "receive_request"; step: "handled"; method: TMethod; executor: "frontend" | "backend"; input: unknown; output: unknown; error: null; progress: unknown; request: { [x: string]: unknown; jsonrpc: "2.0"; id: string | number; method: string; params?: { [x: string]: unknown; } | undefined; }; response: null; notification: null; } & { phase: TPhase; step: TStep; }) | ({ kind: "request_response"; phase: "receive_request"; step: "failed"; method: TMethod; executor: "frontend" | "backend"; input: unknown; output: null; error: { [x: string]: unknown; code: -32700 | -32600 | -32601 | -32602 | -32603 | (number & $brand<"JsonrpcServerErrorCode">); message: string; data?: unknown; }; progress: unknown; request: { [x: string]: unknown; jsonrpc: "2.0"; id: string | number; method: string; params?: { [x: string]: unknown; } | undefined; }; response: null; notification: null; } & { phase: TPhase; step: TStep; }) | ({ kind: "request_response"; phase: "send_response"; step: "initial"; method: TMethod; executor: "frontend" | "backend"; input: unknown; output: unknown; error: null; progress: null; request: { [x: string]: unknown; jsonrpc: "2.0"; id: string | number; method: string; params?: { [x: string]: unknown; } | undefined; }; response: { [x: string]: unknown; jsonrpc: "2.0"; id: string | number; result: JSONType; } | { [x: string]: unknown; jsonrpc: "2.0"; id: string | number | null; error: { [x: string]: unknown; code: -32700 | -32600 | -32601 | -32602 | -32603 | (number & $brand<"JsonrpcServerErrorCode">); message: string; data?: unknown; }; }; notification: null; } & { phase: TPhase; step: TStep; }) | ({ kind: "request_response"; phase: "send_response"; step: "parsed"; method: TMethod; executor: "frontend" | "backend"; input: unknown; output: unknown; error: null; progress: null; request: { [x: string]: unknown; jsonrpc: "2.0"; id: string | number; method: string; params?: { [x: string]: unknown; } | undefined; }; response: { [x: string]: unknown; jsonrpc: "2.0"; id: string | number; result: JSONType; } | { [x: string]: unknown; jsonrpc: "2.0"; id: string | number | null; error: { [x: string]: unknown; code: -32700 | -32600 | -32601 | -32602 | -32603 | (number & $brand<"JsonrpcServerErrorCode">); message: string; data?: unknown; }; }; notification: null; } & { phase: TPhase; step: TStep; }) | ({ kind: "request_response"; phase: "send_response"; step: "handling"; method: TMethod; executor: "frontend" | "backend"; input: unknown; output: unknown; error: null; progress: unknown; request: { [x: string]: unknown; jsonrpc: "2.0"; id: string | number; method: string; params?: { [x: string]: unknown; } | undefined; }; response: { [x: string]: unknown; jsonrpc: "2.0"; id: string | number; result: JSONType; } | { [x: string]: unknown; jsonrpc: "2.0"; id: string | number | null; error: { [x: string]: unknown; code: -32700 | -32600 | -32601 | -32602 | -32603 | (number & $brand<"JsonrpcServerErrorCode">); message: string; data?: unknown; }; }; notification: null; } & { phase: TPhase; step: TStep; }) | ({ kind: "request_response"; phase: "send_response"; step: "handled"; method: TMethod; executor: "frontend" | "backend"; input: unknown; output: unknown; error: null; progress: unknown; request: { [x: string]: unknown; jsonrpc: "2.0"; id: string | number; method: string; params?: { [x: string]: unknown; } | undefined; }; response: { [x: string]: unknown; jsonrpc: "2.0"; id: string | number; result: JSONType; } | { [x: string]: unknown; jsonrpc: "2.0"; id: string | number | null; error: { [x: string]: unknown; code: -32700 | -32600 | -32601 | -32602 | -32603 | (number & $brand<"JsonrpcServerErrorCode">); message: string; data?: unknown; }; }; notification: null; } & { phase: TPhase; step: TStep; }) | ({ kind: "request_response"; phase: "send_response"; step: "failed"; method: TMethod; executor: "frontend" | "backend"; input: unknown; output: unknown; error: { [x: string]: unknown; code: -32700 | -32600 | -32601 | -32602 | -32603 | (number & $brand<"JsonrpcServerErrorCode">); message: string; data?: unknown; }; progress: unknown; request: { [x: string]: unknown; jsonrpc: "2.0"; id: string | number; method: string; params?: { [x: string]: unknown; } | undefined; }; response: { [x: string]: unknown; jsonrpc: "2.0"; id: string | number; result: JSONType; } | { [x: string]: unknown; jsonrpc: "2.0"; id: string | number | null; error: { [x: string]: unknown; code: -32700 | -32600 | -32601 | -32602 | -32603 | (number & $brand<"JsonrpcServerErrorCode">); message: string; data?: unknown; }; }; notification: null; } & { phase: TPhase; step: TStep; }) | ({ kind: "request_response"; phase: "receive_response"; step: "initial"; method: TMethod; executor: "frontend" | "backend"; input: unknown; output: unknown; error: null; progress: null; request: { [x: string]: unknown; jsonrpc: "2.0"; id: string | number; method: string; params?: { [x: string]: unknown; } | undefined; }; response: { [x: string]: unknown; jsonrpc: "2.0"; id: string | number; result: JSONType; } | { [x: string]: unknown; jsonrpc: "2.0"; id: string | number | null; error: { [x: string]: unknown; code: -32700 | -32600 | -32601 | -32602 | -32603 | (number & $brand<"JsonrpcServerErrorCode">); message: string; data?: unknown; }; }; notification: null; } & { phase: TPhase; step: TStep; }) | ({ kind: "request_response"; phase: "receive_response"; step: "parsed"; method: TMethod; executor: "frontend" | "backend"; input: unknown; output: unknown; error: null; progress: null; request: { [x: string]: unknown; jsonrpc: "2.0"; id: string | number; method: string; params?: { [x: string]: unknown; } | undefined; }; response: { [x: string]: unknown; jsonrpc: "2.0"; id: string | number; result: JSONType; } | { [x: string]: unknown; jsonrpc: "2.0"; id: string | number | null; error: { [x: string]: unknown; code: -32700 | -32600 | -32601 | -32602 | -32603 | (number & $brand<"JsonrpcServerErrorCode">); message: string; data?: unknown; }; }; notification: null; } & { phase: TPhase; step: TStep; }) | ({ kind: "request_response"; phase: "receive_response"; step: "handling"; method: TMethod; executor: "frontend" | "backend"; input: unknown; output: unknown; error: null; progress: unknown; request: { [x: string]: unknown; jsonrpc: "2.0"; id: string | number; method: string; params?: { [x: string]: unknown; } | undefined; }; response: { [x: string]: unknown; jsonrpc: "2.0"; id: string | number; result: JSONType; } | { [x: string]: unknown; jsonrpc: "2.0"; id: string | number | null; error: { [x: string]: unknown; code: -32700 | -32600 | -32601 | -32602 | -32603 | (number & $brand<"JsonrpcServerErrorCode">); message: string; data?: unknown; }; }; notification: null; } & { phase: TPhase; step: TStep; }) | ({ kind: "request_response"; phase: "receive_response"; step: "handled"; method: TMethod; executor: "frontend" | "backend"; input: unknown; output: unknown; error: null; progress: unknown; request: { [x: string]: unknown; jsonrpc: "2.0"; id: string | number; method: string; params?: { [x: string]: unknown; } | undefined; }; response: { [x: string]: unknown; jsonrpc: "2.0"; id: string | number; result: JSONType; } | { [x: string]: unknown; jsonrpc: "2.0"; id: string | number | null; error: { [x: string]: unknown; code: -32700 | -32600 | -32601 | -32602 | -32603 | (number & $brand<"JsonrpcServerErrorCode">); message: string; data?: unknown; }; }; notification: null; } & { phase: TPhase; step: TStep; }) | ({ kind: "request_response"; phase: "receive_response"; step: "failed"; method: TMethod; executor: "frontend" | "backend"; input: unknown; output: unknown; error: { [x: string]: unknown; code: -32700 | -32600 | -32601 | -32602 | -32603 | (number & $brand<"JsonrpcServerErrorCode">); message: string; data?: unknown; }; progress: unknown; request: { [x: string]: unknown; jsonrpc: "2.0"; id: string | number; method: string; params?: { [x: string]: unknown; } | undefined; }; response: { [x: string]: unknown; jsonrpc: "2.0"; id: string | number; result: JSONType; } | { [x: string]: unknown; jsonrpc: "2.0"; id: string | number | null; error: { [x: string]: unknown; code: -32700 | -32600 | -32601 | -32602 | -32603 | (number & $brand<"JsonrpcServerErrorCode">); message: string; data?: unknown; }; }; notification: null; } & { phase: TPhase; step: TStep; }) | ({ kind: "request_response"; phase: "send_error"; step: "initial"; method: TMethod; executor: "frontend" | "backend"; input: unknown; output: null; error: { [x: string]: unknown; code: -32700 | -32600 | -32601 | -32602 | -32603 | (number & $brand<"JsonrpcServerErrorCode">); message: string; data?: unknown; }; progress: null; request: { [x: string]: unknown; jsonrpc: "2.0"; id: string | number; method: string; params?: { [x: string]: unknown; } | undefined; } | null; response: null; notification: null; } & { phase: TPhase; step: TStep; }) | ({ kind: "request_response"; phase: "send_error"; step: "parsed"; method: TMethod; executor: "frontend" | "backend"; input: unknown; output: null; error: { [x: string]: unknown; code: -32700 | -32600 | -32601 | -32602 | -32603 | (number & $brand<"JsonrpcServerErrorCode">); message: string; data?: unknown; }; progress: null; request: { [x: string]: unknown; jsonrpc: "2.0"; id: string | number; method: string; params?: { [x: string]: unknown; } | undefined; } | null; response: null; notification: null; } & { phase: TPhase; step: TStep; }) | ({ kind: "request_response"; phase: "send_error"; step: "handling"; method: TMethod; executor: "frontend" | "backend"; input: unknown; output: null; error: { [x: string]: unknown; code: -32700 | -32600 | -32601 | -32602 | -32603 | (number & $brand<"JsonrpcServerErrorCode">); message: string; data?: unknown; }; progress: unknown; request: { [x: string]: unknown; jsonrpc: "2.0"; id: string | number; method: string; params?: { [x: string]: unknown; } | undefined; } | null; response: null; notification: null; } & { phase: TPhase; step: TStep; }) | ({ kind: "request_response"; phase: "send_error"; step: "handled"; method: TMethod; executor: "frontend" | "backend"; input: unknown; output: null; error: { [x: string]: unknown; code: -32700 | -32600 | -32601 | -32602 | -32603 | (number & $brand<"JsonrpcServerErrorCode">); message: string; data?: unknown; }; progress: unknown; request: { [x: string]: unknown; jsonrpc: "2.0"; id: string | number; method: string; params?: { [x: string]: unknown; } | undefined; } | null; response: null; notification: null; } & { phase: TPhase; step: TStep; }) | ({ kind: "request_response"; phase: "receive_error"; step: "initial"; method: TMethod; executor: "frontend" | "backend"; input: unknown; output: null; error: { [x: string]: unknown; code: -32700 | -32600 | -32601 | -32602 | -32603 | (number & $brand<"JsonrpcServerErrorCode">); message: string; data?: unknown; }; progress: null; request: { [x: string]: unknown; jsonrpc: "2.0"; id: string | number; method: string; params?: { [x: string]: unknown; } | undefined; }; response: { [x: string]: unknown; jsonrpc: "2.0"; id: string | number; result: JSONType; } | { [x: string]: unknown; jsonrpc: "2.0"; id: string | number | null; error: { [x: string]: unknown; code: -32700 | -32600 | -32601 | -32602 | -32603 | (number & $brand<"JsonrpcServerErrorCode">); message: string; data?: unknown; }; }; notification: null; } & { phase: TPhase; step: TStep; }) | ({ kind: "request_response"; phase: "receive_error"; step: "parsed"; method: TMethod; executor: "frontend" | "backend"; input: unknown; output: null; error: { [x: string]: unknown; code: -32700 | -32600 | -32601 | -32602 | -32603 | (number & $brand<"JsonrpcServerErrorCode">); message: string; data?: unknown; }; progress: null; request: { [x: string]: unknown; jsonrpc: "2.0"; id: string | number; method: string; params?: { [x: string]: unknown; } | undefined; }; response: { [x: string]: unknown; jsonrpc: "2.0"; id: string | number; result: JSONType; } | { [x: string]: unknown; jsonrpc: "2.0"; id: string | number | null; error: { [x: string]: unknown; code: -32700 | -32600 | -32601 | -32602 | -32603 | (number & $brand<"JsonrpcServerErrorCode">); message: string; data?: unknown; }; }; notification: null; } & { phase: TPhase; step: TStep; }) | ({ kind: "request_response"; phase: "receive_error"; step: "handling"; method: TMethod; executor: "frontend" | "backend"; input: unknown; output: null; error: { [x: string]: unknown; code: -32700 | -32600 | -32601 | -32602 | -32603 | (number & $brand<"JsonrpcServerErrorCode">); message: string; data?: unknown; }; progress: unknown; request: { [x: string]: unknown; jsonrpc: "2.0"; id: string | number; method: string; params?: { [x: string]: unknown; } | undefined; }; response: { [x: string]: unknown; jsonrpc: "2.0"; id: string | number; result: JSONType; } | { [x: string]: unknown; jsonrpc: "2.0"; id: string | number | null; error: { [x: string]: unknown; code: -32700 | -32600 | -32601 | -32602 | -32603 | (number & $brand<"JsonrpcServerErrorCode">); message: string; data?: unknown; }; }; notification: null; } & { phase: TPhase; step: TStep; }) | ({ kind: "request_response"; phase: "receive_error"; step: "handled"; method: TMethod; executor: "frontend" | "backend"; input: unknown; output: null; error: { [x: string]: unknown; code: -32700 | -32600 | -32601 | -32602 | -32603 | (number & $brand<"JsonrpcServerErrorCode">); message: string; data?: unknown; }; progress: unknown; request: { [x: string]: unknown; jsonrpc: "2.0"; id: string | number; method: string; params?: { [x: string]: unknown; } | undefined; }; response: { [x: string]: unknown; jsonrpc: "2.0"; id: string | number; result: JSONType; } | { [x: string]: unknown; jsonrpc: "2.0"; id: string | number | null; error: { [x: string]: unknown; code: -32700 | -32600 | -32601 | -32602 | -32603 | (number & $brand<"JsonrpcServerErrorCode">); message: string; data?: unknown; }; }; notification: null; } & { phase: TPhase; step: TStep; }) | ({ kind: "remote_notification"; phase: "send"; step: "initial"; method: TMethod; executor: "frontend" | "backend"; input: unknown; output: null; error: null; progress: null; request: null; response: null; notification: null; } & { phase: TPhase; step: TStep; }) | ({ kind: "remote_notification"; phase: "send"; step: "parsed"; method: TMethod; executor: "frontend" | "backend"; input: unknown; output: null; error: null; progress: null; request: null; response: null; notification: null; } & { phase: TPhase; step: TStep; }) | ({ kind: "remote_notification"; phase: "send"; step: "handling"; method: TMethod; executor: "frontend" | "backend"; input: unknown; output: null; error: null; progress: unknown; request: null; response: null; notification: { [x: string]: unknown; jsonrpc: "2.0"; method: string; params?: { [x: string]: unknown; } | undefined; }; } & { phase: TPhase; step: TStep; }) | ({ kind: "remote_notification"; phase: "send"; step: "handled"; method: TMethod; executor: "frontend" | "backend"; input: unknown; output: null; error: null; progress: unknown; request: null; response: null; notification: { [x: string]: unknown; jsonrpc: "2.0"; method: string; params?: { [x: string]: unknown; } | undefined; }; } & { phase: TPhase; step: TStep; }) | ({ kind: "remote_notification"; phase: "send"; step: "failed"; method: TMethod; executor: "frontend" | "backend"; input: unknown; output: null; error: { [x: string]: unknown; code: -32700 | -32600 | -32601 | -32602 | -32603 | (number & $brand<"JsonrpcServerErrorCode">); message: string; data?: unknown; }; progress: unknown; request: null; response: null; notification: { [x: string]: unknown; jsonrpc: "2.0"; method: string; params?: { [x: string]: unknown; } | undefined; } | null; } & { phase: TPhase; step: TStep; }) | ({ kind: "remote_notification"; phase: "receive"; step: "initial"; method: TMethod; executor: "frontend" | "backend"; input: unknown; output: null; error: null; progress: null; request: null; response: null; notification: { [x: string]: unknown; jsonrpc: "2.0"; method: string; params?: { [x: string]: unknown; } | undefined; }; } & { phase: TPhase; step: TStep; }) | ({ kind: "remote_notification"; phase: "receive"; step: "parsed"; method: TMethod; executor: "frontend" | "backend"; input: unknown; output: null; error: null; progress: null; request: null; response: null; notification: { [x: string]: unknown; jsonrpc: "2.0"; method: string; params?: { [x: string]: unknown; } | undefined; }; } & { phase: TPhase; step: TStep; }) | ({ kind: "remote_notification"; phase: "receive"; step: "handling"; method: TMethod; executor: "frontend" | "backend"; input: unknown; output: null; error: null; progress: unknown; request: null; response: null; notification: { [x: string]: unknown; jsonrpc: "2.0"; method: string; params?: { [x: string]: unknown; } | undefined; }; } & { phase: TPhase; step: TStep; }) | ({ kind: "remote_notification"; phase: "receive"; step: "handled"; method: TMethod; executor: "frontend" | "backend"; input: unknown; output: null; error: null; progress: unknown; request: null; response: null; notification: { [x: string]: unknown; jsonrpc: "2.0"; method: string; params?: { [x: string]: unknown; } | undefined; }; } & { phase: TPhase; step: TStep; }) | ({ kind: "remote_notification"; phase: "receive"; step: "failed"; method: TMethod; executor: "frontend" | "backend"; input: unknown; output: null; error: { [x: string]: unknown; code: -32700 | -32600 | -32601 | -32602 | -32603 | (number & $brand<"JsonrpcServerErrorCode">); message: string; data?: unknown; }; progress: unknown; request: null; response: null; notification: { [x: string]: unknown; jsonrpc: "2.0"; method: string; params?: { [x: string]: unknown; } | undefined; }; } & { phase: TPhase; step: TStep; }) | ({ kind: "local_call"; phase: "execute"; step: "initial"; method: TMethod; executor: "frontend" | "backend"; input: unknown; output: null; error: null; progress: null; request: null; response: null; notification: null; } & { phase: TPhase; step: TStep; }) | ({ kind: "local_call"; phase: "execute"; step: "parsed"; method: TMethod; executor: "frontend" | "backend"; input: unknown; output: null; error: null; progress: null; request: null; response: null; notification: null; } & { phase: TPhase; step: TStep; }) | ({ kind: "local_call"; phase: "execute"; step: "handling"; method: TMethod; executor: "frontend" | "backend"; input: unknown; output: null; error: null; progress: unknown; request: null; response: null; notification: null; } & { phase: TPhase; step: TStep; }) | ({ kind: "local_call"; phase: "execute"; step: "handled"; method: TMethod; executor: "frontend" | "backend"; input: unknown; output: unknown; error: null; progress: unknown; request: null; response: null; notification: null; } & { phase: TPhase; step: TStep; }) | ({ kind: "local_call"; phase: "execute"; step: "failed"; method: TMethod; executor: "frontend" | "backend"; input: unknown; output: null; error: { [x: string]: unknown; code: -32700 | -32600 | -32601 | -32602 | -32603 | (number & $brand<"JsonrpcServerErrorCode">); message: string; data?: unknown; }; progress: unknown; request: null; response: null; notification: null; } & { phase: TPhase; step: TStep; })

getter

ActionEventChangeObserver
#

actions/action_event.ts view source

ActionEventChangeObserver<TMethod> import type {ActionEventChangeObserver} from '@fuzdev/fuz_app/actions/action_event.js';

generics

ActionEventChangeObserver<TMethod extends string = string>
TMethod
constraint string
default string

(call)

type (new_data: ActionEventDataUnion<TMethod>, old_data: ActionEventDataUnion<TMethod>, event: ActionEvent<TMethod, "send_request" | ... 7 more ... | "execute", "initial" | ... 3 more ... | "failed">): void

new_data

old_data

event

type ActionEvent<TMethod, "send_request" | "receive_request" | "send_response" | "receive_response" | "send_error" | "receive_error" | "send" | "receive" | "execute", "initial" | "parsed" | "handling" | "handled" | "failed">
returns void

ActionEventData
#

actions/action_event_data.ts view source

value + type

{ kind: "request_response" | "remote_notification" | "local_call"; phase: "send_request" | "receive_request" | "send_response" | "receive_response" | "send_error" | "receive_error" | "send" | "receive" | "execute"; ... 9 more ...; notification: { ...; } | null; } import {ActionEventData} from '@fuzdev/fuz_app/actions/action_event_data.js';

kind

type "request_response" | "remote_notification" | "local_call"

phase

type "send_request" | "receive_request" | "send_response" | "receive_response" | "send_error" | "receive_error" | "send" | "receive" | "execute"

step

type "initial" | "parsed" | "handling" | "handled" | "failed"

method

type string

executor

type "frontend" | "backend"

input

type unknown

output

type unknown

error

type JsonrpcErrorObject | null

progress

type unknown

request

type JsonrpcRequest | null

response

type JsonrpcResponse | JsonrpcErrorResponse | null

notification

type JsonrpcNotification | null

ActionEventDataUnion
#

ActionEventEnvironment
#

actions/action_event_types.ts view source

ActionEventEnvironment import type {ActionEventEnvironment} from '@fuzdev/fuz_app/actions/action_event_types.js';

executor

type ActionExecutor

readonly

lookup_action_handler

type (method: string, phase: "send_request" | "receive_request" | "send_response" | "receive_response" | "send_error" | "receive_error" | "send" | "receive" | "execute"): ((event: any) => any) | undefined

method

type string

phase

returns ((event: any) => any) | undefined

lookup_action_spec

type (method: string): { method: string; initiator: "frontend" | "backend" | "both"; side_effects: boolean; input: ZodType<unknown, unknown, $ZodTypeInternals<unknown, unknown>>; ... 7 more ...; rate_limit?: "both" | ... 2 more ... | undefined; } | { ...; } | { ...; } | undefined

method

type string

log?

Where dispatch diagnostics go — failed sends, rejected or failing inbound notifications and requests. Unset falls back to console_action_log; null silences them.

type Logger | null

readonly

ActionEventLocalCallData
#

actions/action_event_data.ts view source

ActionEventLocalCallData<TMethod, TInput, TOutput>

type { kind: "local_call"; phase: "execute"; step: "initial"; method: TMethod; executor: "frontend" | "backend"; input: unknown; output: null; error: null; progress: null; request: null; response: null; notification: null; } | { kind: "local_call"; phase: "execute"; step: "parsed"; method: TMethod; executor: "frontend" | "backend"; input: TInput; output: null; error: null; progress: null; request: null; response: null; notification: null; } | { kind: "local_call"; phase: "execute"; step: "handling"; method: TMethod; executor: "frontend" | "backend"; input: TInput; output: null; error: null; progress: unknown; request: null; response: null; notification: null; } | { kind: "local_call"; phase: "execute"; step: "handled"; method: TMethod; executor: "frontend" | "backend"; input: TInput; output: TOutput; error: null; progress: unknown; request: null; response: null; notification: null; } | { kind: "local_call"; phase: "execute"; step: "failed"; method: TMethod; executor: "frontend" | "backend"; input: unknown; output: null; error: { [x: string]: unknown; code: -32700 | -32600 | -32601 | -32602 | -32603 | (number & $brand<"JsonrpcServerErrorCode">); message: string; data?: unknown; }; progress: unknown; request: null; response: null; notification: null; }

import type {ActionEventLocalCallData} from '@fuzdev/fuz_app/actions/action_event_data.js';

generics

ActionEventLocalCallData<TMethod extends string = string, TInput = unknown, TOutput = unknown>
TMethod
constraint string
default string
TInput
default unknown
TOutput
default unknown

ActionEventOptions
#

ActionEventPhase
#

actions/action_spec.ts view source

value + type

"send_request" | "receive_request" | "send_response" | "receive_response" | "send_error" | "receive_error" | "send" | "receive" | "execute"

type "send_request" | "receive_request" | "send_response" | "receive_response" | "send_error" | "receive_error" | "send" | "receive" | "execute"

import {ActionEventPhase} from '@fuzdev/fuz_app/actions/action_spec.js';

ActionEventRemoteNotificationData
#

actions/action_event_data.ts view source

ActionEventRemoteNotificationData<TMethod, TInput>

type { kind: "remote_notification"; phase: "send"; step: "initial"; method: TMethod; executor: "frontend" | "backend"; input: unknown; output: null; error: null; progress: null; request: null; response: null; notification: null; } | { kind: "remote_notification"; phase: "send"; step: "parsed"; method: TMethod; executor: "frontend" | "backend"; input: TInput; output: null; error: null; progress: null; request: null; response: null; notification: null; } | { kind: "remote_notification"; phase: "send"; step: "handling"; method: TMethod; executor: "frontend" | "backend"; input: TInput; output: null; error: null; progress: unknown; request: null; response: null; notification: { [x: string]: unknown; jsonrpc: "2.0"; method: string; params?: { [x: string]: unknown; } | undefined; }; } | { kind: "remote_notification"; phase: "send"; step: "handled"; method: TMethod; executor: "frontend" | "backend"; input: TInput; output: null; error: null; progress: unknown; request: null; response: null; notification: { [x: string]: unknown; jsonrpc: "2.0"; method: string; params?: { [x: string]: unknown; } | undefined; }; } | { kind: "remote_notification"; phase: "send"; step: "failed"; method: TMethod; executor: "frontend" | "backend"; input: unknown; output: null; error: { [x: string]: unknown; code: -32700 | -32600 | -32601 | -32602 | -32603 | (number & $brand<"JsonrpcServerErrorCode">); message: string; data?: unknown; }; progress: unknown; request: null; response: null; notification: { [x: string]: unknown; jsonrpc: "2.0"; method: string; params?: { [x: string]: unknown; } | undefined; } | null; } | { kind: "remote_notification"; phase: "receive"; step: "initial"; method: TMethod; executor: "frontend" | "backend"; input: unknown; output: null; error: null; progress: null; request: null; response: null; notification: { [x: string]: unknown; jsonrpc: "2.0"; method: string; params?: { [x: string]: unknown; } | undefined; }; } | { kind: "remote_notification"; phase: "receive"; step: "parsed"; method: TMethod; executor: "frontend" | "backend"; input: TInput; output: null; error: null; progress: null; request: null; response: null; notification: { [x: string]: unknown; jsonrpc: "2.0"; method: string; params?: { [x: string]: unknown; } | undefined; }; } | { kind: "remote_notification"; phase: "receive"; step: "handling"; method: TMethod; executor: "frontend" | "backend"; input: TInput; output: null; error: null; progress: unknown; request: null; response: null; notification: { [x: string]: unknown; jsonrpc: "2.0"; method: string; params?: { [x: string]: unknown; } | undefined; }; } | { kind: "remote_notification"; phase: "receive"; step: "handled"; method: TMethod; executor: "frontend" | "backend"; input: TInput; output: null; error: null; progress: unknown; request: null; response: null; notification: { [x: string]: unknown; jsonrpc: "2.0"; method: string; params?: { [x: string]: unknown; } | undefined; }; } | { kind: "remote_notification"; phase: "receive"; step: "failed"; method: TMethod; executor: "frontend" | "backend"; input: unknown; output: null; error: { [x: string]: unknown; code: -32700 | -32600 | -32601 | -32602 | -32603 | (number & $brand<"JsonrpcServerErrorCode">); message: string; data?: unknown; }; progress: unknown; request: null; response: null; notification: { [x: string]: unknown; jsonrpc: "2.0"; method: string; params?: { [x: string]: unknown; } | undefined; }; }

import type {ActionEventRemoteNotificationData} from '@fuzdev/fuz_app/actions/action_event_data.js';

generics

ActionEventRemoteNotificationData<TMethod extends string = string, TInput = unknown>
TMethod
constraint string
default string
TInput
default unknown

ActionEventRequestResponseData
#

actions/action_event_data.ts view source

ActionEventRequestResponseData<TMethod, TInput, TOutput>

type { kind: "request_response"; phase: "send_request"; step: "initial"; method: TMethod; executor: "frontend" | "backend"; input: unknown; output: null; error: null; progress: null; request: null; response: null; notification: null; } | { kind: "request_response"; phase: "send_request"; step: "parsed"; method: TMethod; executor: "frontend" | "backend"; input: TInput; output: null; error: null; progress: null; request: null; response: null; notification: null; } | { kind: "request_response"; phase: "send_request"; step: "handling"; method: TMethod; executor: "frontend" | "backend"; input: TInput; output: null; error: null; progress: unknown; request: { [x: string]: unknown; jsonrpc: "2.0"; id: string | number; method: string; params?: { [x: string]: unknown; } | undefined; }; response: null; notification: null; } | { kind: "request_response"; phase: "send_request"; step: "handled"; method: TMethod; executor: "frontend" | "backend"; input: TInput; output: null; error: null; progress: unknown; request: { [x: string]: unknown; jsonrpc: "2.0"; id: string | number; method: string; params?: { [x: string]: unknown; } | undefined; }; response: null; notification: null; } | { kind: "request_response"; phase: "send_request"; step: "failed"; method: TMethod; executor: "frontend" | "backend"; input: unknown; output: null; error: { [x: string]: unknown; code: -32700 | -32600 | -32601 | -32602 | -32603 | (number & $brand<"JsonrpcServerErrorCode">); message: string; data?: unknown; }; progress: unknown; request: { [x: string]: unknown; jsonrpc: "2.0"; id: string | number; method: string; params?: { [x: string]: unknown; } | undefined; } | null; response: null; notification: null; } | { kind: "request_response"; phase: "receive_request"; step: "initial"; method: TMethod; executor: "frontend" | "backend"; input: unknown; output: null; error: null; progress: null; request: { [x: string]: unknown; jsonrpc: "2.0"; id: string | number; method: string; params?: { [x: string]: unknown; } | undefined; }; response: null; notification: null; } | { kind: "request_response"; phase: "receive_request"; step: "parsed"; method: TMethod; executor: "frontend" | "backend"; input: TInput; output: null; error: null; progress: null; request: { [x: string]: unknown; jsonrpc: "2.0"; id: string | number; method: string; params?: { [x: string]: unknown; } | undefined; }; response: null; notification: null; } | { kind: "request_response"; phase: "receive_request"; step: "handling"; method: TMethod; executor: "frontend" | "backend"; input: TInput; output: null; error: null; progress: unknown; request: { [x: string]: unknown; jsonrpc: "2.0"; id: string | number; method: string; params?: { [x: string]: unknown; } | undefined; }; response: null; notification: null; } | { kind: "request_response"; phase: "receive_request"; step: "handled"; method: TMethod; executor: "frontend" | "backend"; input: TInput; output: TOutput; error: null; progress: unknown; request: { [x: string]: unknown; jsonrpc: "2.0"; id: string | number; method: string; params?: { [x: string]: unknown; } | undefined; }; response: null; notification: null; } | { kind: "request_response"; phase: "receive_request"; step: "failed"; method: TMethod; executor: "frontend" | "backend"; input: unknown; output: null; error: { [x: string]: unknown; code: -32700 | -32600 | -32601 | -32602 | -32603 | (number & $brand<"JsonrpcServerErrorCode">); message: string; data?: unknown; }; progress: unknown; request: { [x: string]: unknown; jsonrpc: "2.0"; id: string | number; method: string; params?: { [x: string]: unknown; } | undefined; }; response: null; notification: null; } | { kind: "request_response"; phase: "send_response"; step: "initial"; method: TMethod; executor: "frontend" | "backend"; input: TInput; output: TOutput; error: null; progress: null; request: { [x: string]: unknown; jsonrpc: "2.0"; id: string | number; method: string; params?: { [x: string]: unknown; } | undefined; }; response: { [x: string]: unknown; jsonrpc: "2.0"; id: string | number; result: JSONType; } | { [x: string]: unknown; jsonrpc: "2.0"; id: string | number | null; error: { [x: string]: unknown; code: -32700 | -32600 | -32601 | -32602 | -32603 | (number & $brand<"JsonrpcServerErrorCode">); message: string; data?: unknown; }; }; notification: null; } | { kind: "request_response"; phase: "send_response"; step: "parsed"; method: TMethod; executor: "frontend" | "backend"; input: TInput; output: TOutput; error: null; progress: null; request: { [x: string]: unknown; jsonrpc: "2.0"; id: string | number; method: string; params?: { [x: string]: unknown; } | undefined; }; response: { [x: string]: unknown; jsonrpc: "2.0"; id: string | number; result: JSONType; } | { [x: string]: unknown; jsonrpc: "2.0"; id: string | number | null; error: { [x: string]: unknown; code: -32700 | -32600 | -32601 | -32602 | -32603 | (number & $brand<"JsonrpcServerErrorCode">); message: string; data?: unknown; }; }; notification: null; } | { kind: "request_response"; phase: "send_response"; step: "handling"; method: TMethod; executor: "frontend" | "backend"; input: TInput; output: TOutput; error: null; progress: unknown; request: { [x: string]: unknown; jsonrpc: "2.0"; id: string | number; method: string; params?: { [x: string]: unknown; } | undefined; }; response: { [x: string]: unknown; jsonrpc: "2.0"; id: string | number; result: JSONType; } | { [x: string]: unknown; jsonrpc: "2.0"; id: string | number | null; error: { [x: string]: unknown; code: -32700 | -32600 | -32601 | -32602 | -32603 | (number & $brand<"JsonrpcServerErrorCode">); message: string; data?: unknown; }; }; notification: null; } | { kind: "request_response"; phase: "send_response"; step: "handled"; method: TMethod; executor: "frontend" | "backend"; input: TInput; output: TOutput; error: null; progress: unknown; request: { [x: string]: unknown; jsonrpc: "2.0"; id: string | number; method: string; params?: { [x: string]: unknown; } | undefined; }; response: { [x: string]: unknown; jsonrpc: "2.0"; id: string | number; result: JSONType; } | { [x: string]: unknown; jsonrpc: "2.0"; id: string | number | null; error: { [x: string]: unknown; code: -32700 | -32600 | -32601 | -32602 | -32603 | (number & $brand<"JsonrpcServerErrorCode">); message: string; data?: unknown; }; }; notification: null; } | { kind: "request_response"; phase: "send_response"; step: "failed"; method: TMethod; executor: "frontend" | "backend"; input: TInput; output: TOutput | null; error: { [x: string]: unknown; code: -32700 | -32600 | -32601 | -32602 | -32603 | (number & $brand<"JsonrpcServerErrorCode">); message: string; data?: unknown; }; progress: unknown; request: { [x: string]: unknown; jsonrpc: "2.0"; id: string | number; method: string; params?: { [x: string]: unknown; } | undefined; }; response: { [x: string]: unknown; jsonrpc: "2.0"; id: string | number; result: JSONType; } | { [x: string]: unknown; jsonrpc: "2.0"; id: string | number | null; error: { [x: string]: unknown; code: -32700 | -32600 | -32601 | -32602 | -32603 | (number & $brand<"JsonrpcServerErrorCode">); message: string; data?: unknown; }; }; notification: null; } | { kind: "request_response"; phase: "receive_response"; step: "initial"; method: TMethod; executor: "frontend" | "backend"; input: TInput; output: unknown; error: null; progress: null; request: { [x: string]: unknown; jsonrpc: "2.0"; id: string | number; method: string; params?: { [x: string]: unknown; } | undefined; }; response: { [x: string]: unknown; jsonrpc: "2.0"; id: string | number; result: JSONType; } | { [x: string]: unknown; jsonrpc: "2.0"; id: string | number | null; error: { [x: string]: unknown; code: -32700 | -32600 | -32601 | -32602 | -32603 | (number & $brand<"JsonrpcServerErrorCode">); message: string; data?: unknown; }; }; notification: null; } | { kind: "request_response"; phase: "receive_response"; step: "parsed"; method: TMethod; executor: "frontend" | "backend"; input: TInput; output: TOutput; error: null; progress: null; request: { [x: string]: unknown; jsonrpc: "2.0"; id: string | number; method: string; params?: { [x: string]: unknown; } | undefined; }; response: { [x: string]: unknown; jsonrpc: "2.0"; id: string | number; result: JSONType; } | { [x: string]: unknown; jsonrpc: "2.0"; id: string | number | null; error: { [x: string]: unknown; code: -32700 | -32600 | -32601 | -32602 | -32603 | (number & $brand<"JsonrpcServerErrorCode">); message: string; data?: unknown; }; }; notification: null; } | { kind: "request_response"; phase: "receive_response"; step: "handling"; method: TMethod; executor: "frontend" | "backend"; input: TInput; output: TOutput; error: null; progress: unknown; request: { [x: string]: unknown; jsonrpc: "2.0"; id: string | number; method: string; params?: { [x: string]: unknown; } | undefined; }; response: { [x: string]: unknown; jsonrpc: "2.0"; id: string | number; result: JSONType; } | { [x: string]: unknown; jsonrpc: "2.0"; id: string | number | null; error: { [x: string]: unknown; code: -32700 | -32600 | -32601 | -32602 | -32603 | (number & $brand<"JsonrpcServerErrorCode">); message: string; data?: unknown; }; }; notification: null; } | { kind: "request_response"; phase: "receive_response"; step: "handled"; method: TMethod; executor: "frontend" | "backend"; input: TInput; output: TOutput; error: null; progress: unknown; request: { [x: string]: unknown; jsonrpc: "2.0"; id: string | number; method: string; params?: { [x: string]: unknown; } | undefined; }; response: { [x: string]: unknown; jsonrpc: "2.0"; id: string | number; result: JSONType; } | { [x: string]: unknown; jsonrpc: "2.0"; id: string | number | null; error: { [x: string]: unknown; code: -32700 | -32600 | -32601 | -32602 | -32603 | (number & $brand<"JsonrpcServerErrorCode">); message: string; data?: unknown; }; }; notification: null; } | { kind: "request_response"; phase: "receive_response"; step: "failed"; method: TMethod; executor: "frontend" | "backend"; input: TInput; output: TOutput | null; error: { [x: string]: unknown; code: -32700 | -32600 | -32601 | -32602 | -32603 | (number & $brand<"JsonrpcServerErrorCode">); message: string; data?: unknown; }; progress: unknown; request: { [x: string]: unknown; jsonrpc: "2.0"; id: string | number; method: string; params?: { [x: string]: unknown; } | undefined; }; response: { [x: string]: unknown; jsonrpc: "2.0"; id: string | number; result: JSONType; } | { [x: string]: unknown; jsonrpc: "2.0"; id: string | number | null; error: { [x: string]: unknown; code: -32700 | -32600 | -32601 | -32602 | -32603 | (number & $brand<"JsonrpcServerErrorCode">); message: string; data?: unknown; }; }; notification: null; } | { kind: "request_response"; phase: "send_error"; step: "initial"; method: TMethod; executor: "frontend" | "backend"; input: unknown; output: null; error: { [x: string]: unknown; code: -32700 | -32600 | -32601 | -32602 | -32603 | (number & $brand<"JsonrpcServerErrorCode">); message: string; data?: unknown; }; progress: null; request: { [x: string]: unknown; jsonrpc: "2.0"; id: string | number; method: string; params?: { [x: string]: unknown; } | undefined; } | null; response: null; notification: null; } | { kind: "request_response"; phase: "send_error"; step: "parsed"; method: TMethod; executor: "frontend" | "backend"; input: TInput; output: null; error: { [x: string]: unknown; code: -32700 | -32600 | -32601 | -32602 | -32603 | (number & $brand<"JsonrpcServerErrorCode">); message: string; data?: unknown; }; progress: null; request: { [x: string]: unknown; jsonrpc: "2.0"; id: string | number; method: string; params?: { [x: string]: unknown; } | undefined; } | null; response: null; notification: null; } | { kind: "request_response"; phase: "send_error"; step: "handling"; method: TMethod; executor: "frontend" | "backend"; input: TInput; output: null; error: { [x: string]: unknown; code: -32700 | -32600 | -32601 | -32602 | -32603 | (number & $brand<"JsonrpcServerErrorCode">); message: string; data?: unknown; }; progress: unknown; request: { [x: string]: unknown; jsonrpc: "2.0"; id: string | number; method: string; params?: { [x: string]: unknown; } | undefined; } | null; response: null; notification: null; } | { kind: "request_response"; phase: "send_error"; step: "handled"; method: TMethod; executor: "frontend" | "backend"; input: TInput; output: null; error: { [x: string]: unknown; code: -32700 | -32600 | -32601 | -32602 | -32603 | (number & $brand<"JsonrpcServerErrorCode">); message: string; data?: unknown; }; progress: unknown; request: { [x: string]: unknown; jsonrpc: "2.0"; id: string | number; method: string; params?: { [x: string]: unknown; } | undefined; } | null; response: null; notification: null; } | { kind: "request_response"; phase: "receive_error"; step: "initial"; method: TMethod; executor: "frontend" | "backend"; input: TInput; output: null; error: { [x: string]: unknown; code: -32700 | -32600 | -32601 | -32602 | -32603 | (number & $brand<"JsonrpcServerErrorCode">); message: string; data?: unknown; }; progress: null; request: { [x: string]: unknown; jsonrpc: "2.0"; id: string | number; method: string; params?: { [x: string]: unknown; } | undefined; }; response: { [x: string]: unknown; jsonrpc: "2.0"; id: string | number; result: JSONType; } | { [x: string]: unknown; jsonrpc: "2.0"; id: string | number | null; error: { [x: string]: unknown; code: -32700 | -32600 | -32601 | -32602 | -32603 | (number & $brand<"JsonrpcServerErrorCode">); message: string; data?: unknown; }; }; notification: null; } | { kind: "request_response"; phase: "receive_error"; step: "parsed"; method: TMethod; executor: "frontend" | "backend"; input: TInput; output: null; error: { [x: string]: unknown; code: -32700 | -32600 | -32601 | -32602 | -32603 | (number & $brand<"JsonrpcServerErrorCode">); message: string; data?: unknown; }; progress: null; request: { [x: string]: unknown; jsonrpc: "2.0"; id: string | number; method: string; params?: { [x: string]: unknown; } | undefined; }; response: { [x: string]: unknown; jsonrpc: "2.0"; id: string | number; result: JSONType; } | { [x: string]: unknown; jsonrpc: "2.0"; id: string | number | null; error: { [x: string]: unknown; code: -32700 | -32600 | -32601 | -32602 | -32603 | (number & $brand<"JsonrpcServerErrorCode">); message: string; data?: unknown; }; }; notification: null; } | { kind: "request_response"; phase: "receive_error"; step: "handling"; method: TMethod; executor: "frontend" | "backend"; input: TInput; output: null; error: { [x: string]: unknown; code: -32700 | -32600 | -32601 | -32602 | -32603 | (number & $brand<"JsonrpcServerErrorCode">); message: string; data?: unknown; }; progress: unknown; request: { [x: string]: unknown; jsonrpc: "2.0"; id: string | number; method: string; params?: { [x: string]: unknown; } | undefined; }; response: { [x: string]: unknown; jsonrpc: "2.0"; id: string | number; result: JSONType; } | { [x: string]: unknown; jsonrpc: "2.0"; id: string | number | null; error: { [x: string]: unknown; code: -32700 | -32600 | -32601 | -32602 | -32603 | (number & $brand<"JsonrpcServerErrorCode">); message: string; data?: unknown; }; }; notification: null; } | { kind: "request_response"; phase: "receive_error"; step: "handled"; method: TMethod; executor: "frontend" | "backend"; input: TInput; output: null; error: { [x: string]: unknown; code: -32700 | -32600 | -32601 | -32602 | -32603 | (number & $brand<"JsonrpcServerErrorCode">); message: string; data?: unknown; }; progress: unknown; request: { [x: string]: unknown; jsonrpc: "2.0"; id: string | number; method: string; params?: { [x: string]: unknown; } | undefined; }; response: { [x: string]: unknown; jsonrpc: "2.0"; id: string | number; result: JSONType; } | { [x: string]: unknown; jsonrpc: "2.0"; id: string | number | null; error: { [x: string]: unknown; code: -32700 | -32600 | -32601 | -32602 | -32603 | (number & $brand<"JsonrpcServerErrorCode">); message: string; data?: unknown; }; }; notification: null; }

import type {ActionEventRequestResponseData} from '@fuzdev/fuz_app/actions/action_event_data.js';

generics

ActionEventRequestResponseData<TMethod extends string = string, TInput = unknown, TOutput = unknown>
TMethod
constraint string
default string
TInput
default unknown
TOutput
default unknown

ActionEventStep
#

actions/action_event_types.ts view source

value + type

"initial" | "parsed" | "handling" | "handled" | "failed"

type "initial" | "parsed" | "handling" | "handled" | "failed"

import {ActionEventStep} from '@fuzdev/fuz_app/actions/action_event_types.js';

ActionExecutor
#

ActionFactoryDeps
#

auth/deps.ts view source

ActionFactoryDeps import type {ActionFactoryDeps} from '@fuzdev/fuz_app/auth/deps.js';

Capabilities for action-spec factories — the "Action caps" shape.

The minimal slice every create_*_actions factory needs: a log for RPC-internal error logging and the bound audit emitter for fire-and-forget audit writes. RouteFactoryDeps (and AppDeps) satisfy it structurally, so consumers pass their fuller deps bundle straight through.

log

Structured logger instance.

type Logger

audit

Bound audit emitter for fire-and-forget audit writes.

type AuditEmitter

ActionHandler
#

actions/action_rpc.ts view source

ActionHandler<TInput, TOutput> import type {ActionHandler} from '@fuzdev/fuz_app/actions/action_rpc.js';

Handler function for an RPC action.

Receives validated input and an ActionContext with per-request deps. Returns the output value (serialized to JSON by the wrapper).

generics

ActionHandler<TInput = any, TOutput = any>
TInput
default any
TOutput
default any

(call)

type (input: TInput, ctx: ActionContext): TOutput | Promise<TOutput>

input

type TInput

ctx

returns TOutput | Promise<TOutput>

ActionInitiator
#

actions/action_spec.ts view source

value + type

"frontend" | "backend" | "both"

type "frontend" | "backend" | "both"

import {ActionInitiator} from '@fuzdev/fuz_app/actions/action_spec.js';

ActionKind
#

actions/action_spec.ts view source

value + type

"request_response" | "remote_notification" | "local_call"

type "request_response" | "remote_notification" | "local_call"

import {ActionKind} from '@fuzdev/fuz_app/actions/action_spec.js';

ActionLog
#

actions/action_event_types.ts view source

ActionLog import type {ActionLog} from '@fuzdev/fuz_app/actions/action_event_types.js';

The logging surface the action dispatch path uses.

external types

Pick<Logger, 'error' | 'warn' | 'debug'>

ActionManifest
#

testing/cross_backend/action_manifest.ts view source

value + type

{ methods: { method: string; side_effects: boolean; account: "optional" | "none" | "required"; actor: "optional" | "none" | "required"; roles: string[]; credential_types: string[]; rate_limit: "both" | ... 2 more ... | null; }[]; } import {ActionManifest} from '@fuzdev/fuz_app/testing/cross_backend/action_manifest.js';

The full action manifest — every entry, sorted by method.

methods

type ActionManifestEntry[]

ActionManifestDiff
#

testing/cross_backend/action_manifest_parity.ts view source

ActionManifestDiff

type { readonly kind: "method_only_in"; readonly where: "a" | "b"; readonly method: string; } | { readonly kind: "side_effects_differ"; readonly method: string; readonly a: boolean; readonly b: boolean; } | { readonly kind: "rate_limit_differs"; readonly method: string; readonly a: "both" | "account" | "ip" | null; readonly b: "both" | "account" | "ip" | null; } | { readonly kind: "auth_field_differs"; readonly method: string; readonly field: "account" | "actor" | "roles" | "credential_types"; readonly a: unknown; readonly b: unknown; }

import type {ActionManifestDiff} from '@fuzdev/fuz_app/testing/cross_backend/action_manifest_parity.js';

Structured drift entry. where is the named source impl ('a' or 'b').

ActionManifestDiffLabels
#

testing/cross_backend/action_manifest_parity.ts view source

ActionManifestDiffLabels import type {ActionManifestDiffLabels} from '@fuzdev/fuz_app/testing/cross_backend/action_manifest_parity.js';

Labels used in formatted output — defaults to 'a' and 'b'.

a?

type string

readonly

b?

type string

readonly

ActionManifestEntry
#

testing/cross_backend/action_manifest.ts view source

value + type

{ method: string; side_effects: boolean; account: "optional" | "none" | "required"; actor: "optional" | "none" | "required"; roles: string[]; credential_types: string[]; rate_limit: "both" | "account" | "ip" | null; } import {ActionManifestEntry} from '@fuzdev/fuz_app/testing/cross_backend/action_manifest.js';

One method's normalized RPC metadata — the cross-impl-comparable unit. roles / credential_types are always present + sorted (an absent gate and an empty list both serialize to []) so the diff never trips on a undefined-vs-[] or declaration-order difference between impls; the auth axes reuse the canonical AuthAxisState enum. rate_limit is nullable rather than optional for the same reason — an unthrottled action serializes an explicit null, so a missing key is drift, not a default.

method

RPC method name (spec.method).

type string

side_effects

true when the action declares side_effects (mutation; POST-only on the RPC transport).

type boolean

account

The account auth axis.

type "optional" | "none" | "required"

actor

The actor auth axis.

type "optional" | "none" | "required"

roles

Required roles (any-of), sorted; [] when the action declares no role gate.

type string[]

credential_types

Permitted credential channels, sorted; [] when ungated (any credential).

type string[]

rate_limit

Per-action rate-limit class, or null when the action declares none.

type "both" | "account" | "ip" | null

ActionMethodEnumKind
#

actions/action_codegen.ts view source

ActionMethodEnumKind

type "all" | "request_response" | "remote_notification" | "local_call" | "frontend" | "backend" | "frontend_handled" | "backend_handled" | "broadcast"

import type {ActionMethodEnumKind} from '@fuzdev/fuz_app/actions/action_codegen.js';

Discriminator for generate_action_method_enums — which method-set enums to emit.

ActionRateLimitEnvOptions
#

ActionRegistry
#

actions/action_registry.ts view source

import {ActionRegistry} from '@fuzdev/fuz_app/actions/action_registry.js';

specs

type ActionSpecUnion[]

readonly

constructor

type new (specs: ({ method: string; initiator: "frontend" | "backend" | "both"; side_effects: boolean; input: ZodType<unknown, unknown, $ZodTypeInternals<unknown, unknown>>; output: ZodType<unknown, unknown, $ZodTypeInternals<unknown, unknown>>; ... 6 more ...; rate_limit?: "both" | ... 2 more ... | undefined; } | { ...; } | { ...; })[]): ActionRegistry

specs

spec_by_method

type Map<string, ActionSpecUnion>

getter

methods

type string[]

getter

request_response_specs

type RequestResponseActionSpec[]

getter

remote_notification_specs

type RemoteNotificationActionSpec[]

getter

local_call_specs

type LocalCallActionSpec[]

getter

request_response_methods

type string[]

getter

remote_notification_methods

type string[]

getter

local_call_methods

type string[]

getter

specs_relevant_to_frontend

type ActionSpecUnion[]

getter

specs_relevant_to_backend

type ActionSpecUnion[]

getter

methods_relevant_to_frontend

type string[]

getter

methods_relevant_to_backend

type string[]

getter

frontend_handled_specs

type RequestResponseActionSpec[]

getter

backend_handled_specs

type RequestResponseActionSpec[]

getter

frontend_handled_methods

type string[]

getter

backend_handled_methods

type string[]

getter

broadcast_specs

type RemoteNotificationActionSpec[]

getter

broadcast_methods

type string[]

getter

backend_initiated_specs

type ActionSpecUnion[]

getter

backend_initiated_methods

type string[]

getter

backend_to_frontend_specs

type ActionSpecUnion[]

getter

frontend_to_backend_specs

type ActionSpecUnion[]

getter

frontend_to_backend_methods

type string[]

getter

backend_to_frontend_methods

type string[]

getter

public_specs

type ActionSpecUnion[]

getter

authenticated_specs

type ActionSpecUnion[]

getter

public_methods

type string[]

getter

authenticated_methods

type string[]

getter

ActionRegistryCompileResult
#

actions/compile_action_registry.ts view source

ActionRegistryCompileResult import type {ActionRegistryCompileResult} from '@fuzdev/fuz_app/actions/compile_action_registry.js';

Result returned by compile_action_registry.

action_map

Method → RpcAction lookup for dispatch. Only request_response specs with a handler land here — kind-polymorphic input arrays (the WebSocket dispatcher's actions: ReadonlyArray<Action>) pass remote_notification / handler-less specs through unchanged.

type Map<string, RpcAction>

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>>>>

ActionSideEffects
#

ActionSpec
#

actions/action_spec.ts view source

value + type

{ 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; r... import {ActionSpec} from '@fuzdev/fuz_app/actions/action_spec.js';

method

type string

kind

type "request_response" | "remote_notification" | "local_call"

initiator

type "frontend" | "backend" | "both"

auth

The flat auth shape (canonical schema in http/auth_shape.ts). null for remote_notification and local_call — those don't dispatch through the request/response auth gate.

See http/auth_shape.ts for the design rationale (orthogonal authentication / account-resolution / actor-resolution / role-and- credential authorization axes). required_scope is the one field an action may not set — compile_action_registry rejects it, since nothing on the RPC path mounts a guard from it; the dispatcher derives the same rpc:<method> capability from spec.method instead.

type RouteAuth | null

side_effects

type boolean

input

type ZodType<unknown, unknown, $ZodTypeInternals<unknown, unknown>>

output

type ZodType<unknown, unknown, $ZodTypeInternals<unknown, unknown>>

async

type boolean

description

type string

streams?

Names the notification method this action emits as request-scoped progress. Forward-compatible handshake — transport-agnostic, does not imply a specific delivery mechanism. Registry-time validation (e.g., ensuring the named method is a remote_notification spec) is a consumer-side concern.

type string

error_reasons?

Error reason codes this action may surface via error.data.reason on failure. Declarative metadata mirroring the streams precedent — codegen, UI form-state matching, and docs read it off the spec instead of scanning handler implementations. Reuses the same as const string constants the handler throws (e.g. ERROR_ROLE_GRANT_OFFER_*) so call sites can import either side. Optional — actions that surface only standard transport errors leave it unset.

type readonly string[]

rate_limit?

Rate limit key the RPC dispatcher consults for this action. Optional — actions without it skip the rate-limit hook entirely.

  • 'ip' — keyed on the resolved client IP (get_client_ip(c)).
  • 'account' — keyed on the post-auth account id (request_context.account.id). Registration-time error if paired with auth.account !== 'required' (no account to key on).
  • 'both' — both checks run; either can block.

Throttle-requests semantics — every invocation records, regardless of outcome (different from REST login's throttle-failures, which resets on success). Suits admin mutation oracles (invite_create account- existence probe) where the *successful* invocation is the threat.

Today only RequestResponseActionSpec is consulted by the RPC dispatcher. The field is on the base for shape symmetry with streams and error_reasons; remote_notification / local_call dispatchers don't read it.

type "both" | "account" | "ip"

ActionSpecUnion
#

actions/action_spec.ts view source

value + type

{ method: string; initiator: "frontend" | "backend" | "both"; side_effects: boolean; input: ZodType<unknown, unknown, $ZodTypeInternals<unknown, unknown>>; output: ZodType<unknown, unknown, $ZodTypeInternals<unknown, unknown>>; ... 6 more ...; rate_limit?: "both" | ... 2 more ... | undefined; } | { ...; } | { ...; }

type RequestResponseActionSpec | RemoteNotificationActionSpec | LocalCallActionSpec

import {ActionSpecUnion} from '@fuzdev/fuz_app/actions/action_spec.js';

Actor
#

auth/account_schema.ts view source

Actor import type {Actor} from '@fuzdev/fuz_app/auth/account_schema.js';

Actor — the entity that acts. Owns cells, holds role_grants, appears in audit trails.

id

type Uuid

account_id

type Uuid

name

type string

created_at

type string

updated_at

type string | null

updated_by

type Uuid | null

deleted_at

Soft-delete tombstone — set alongside the owning account's soft-delete.

type string | null

deleted_by

Actor that performed the soft-delete. Paired with deleted_at.

type Uuid | null

ACTOR_COLUMNS
#

auth/account_queries.ts view source

readonly ["id", "account_id", "name", "created_at", "updated_at", "updated_by", "deleted_at", "deleted_by"] import {ACTOR_COLUMNS} from '@fuzdev/fuz_app/auth/account_queries.js';

The full actor column set — the same fail-loud discipline as ACCOUNT_COLUMNS (this module owns both tables). Keep in sync with Actor and the actor DDL in auth/auth_ddl.ts.

ACTOR_INDEX
#

auth/auth_ddl.ts view source

"\nCREATE INDEX IF NOT EXISTS idx_actor_account ON actor(account_id)" import {ACTOR_INDEX} from '@fuzdev/fuz_app/auth/auth_ddl.js';

actor_lookup_action_spec
#

auth/actor_lookup_action_specs.ts view source

{ method: string; kind: "request_response"; initiator: "frontend"; auth: { account: "required"; actor: "none"; }; side_effects: false; input: ZodObject<{ ids: ZodArray<$ZodBranded<ZodUUID, "Uuid", "out">>; }, $strict>; output: ZodObject<...>; async: true; rate_limit: "account"; description: string; } import {actor_lookup_action_spec} from '@fuzdev/fuz_app/auth/actor_lookup_action_specs.js';

ACTOR_LOOKUP_IDS_MAX
#

auth/actor_lookup_action_specs.ts view source

50 import {ACTOR_LOOKUP_IDS_MAX} from '@fuzdev/fuz_app/auth/actor_lookup_action_specs.js';

Hard cap on the number of ids resolvable in one call. Bounds the batched username-enumeration surface.

ACTOR_NAME_LOWER_INDEX
#

auth/auth_ddl.ts view source

"\nCREATE INDEX IF NOT EXISTS idx_actor_name_lower ON actor (LOWER(name) text_pattern_ops)" import {ACTOR_NAME_LOWER_INDEX} from '@fuzdev/fuz_app/auth/auth_ddl.js';

Functional index on LOWER(actor.name) supporting case-insensitive prefix search by actor_search (LOWER(name) LIKE LOWER(query) || '%'). text_pattern_ops keeps the LIKE-prefix pattern index-eligible — without it the planner falls back to a sequential scan once the table grows.

ACTOR_SCHEMA
#

auth/auth_ddl.ts view source

"\nCREATE TABLE IF NOT EXISTS actor (\n id UUID PRIMARY KEY DEFAULT gen_random_uuid(),\n account_id UUID NOT NULL REFERENCES account(id) ON DELETE CASCADE,\n name TEXT NOT NULL,\n created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),\n updated_at TIMESTAMPTZ,\n updated_by UUID REFERENCES actor(id) ON DELETE SET NULL,... import {ACTOR_SCHEMA} from '@fuzdev/fuz_app/auth/auth_ddl.js';

actor_search_action_spec
#

auth/actor_search_action_specs.ts view source

{ method: string; kind: "request_response"; initiator: "frontend"; auth: { account: "required"; actor: "none"; }; side_effects: false; input: ZodObject<{ query: ZodString; scope_ids: ZodOptional<ZodArray<$ZodBranded<ZodUUID, "Uuid", "out">>>; limit: ZodOptional<...>; }, $strict>; ... 4 more ...; description: string; } import {actor_search_action_spec} from '@fuzdev/fuz_app/auth/actor_search_action_specs.js';

ACTOR_SEARCH_LIMIT_DEFAULT
#

ACTOR_SEARCH_LIMIT_MAX
#

ACTOR_SEARCH_QUERY_LENGTH_MAX
#

auth/actor_search_action_specs.ts view source

50 import {ACTOR_SEARCH_QUERY_LENGTH_MAX} from '@fuzdev/fuz_app/auth/actor_search_action_specs.js';

Hard cap on the query string length, in UTF-16 code units. Long inputs offer no extra search value once they exceed actor.name realistic lengths, and a low cap keeps the per-request work bounded for pathological inputs.

ActorActionHandler
#

actions/action_rpc.ts view source

ActorActionHandler<TInput, TOutput> import type {ActorActionHandler} from '@fuzdev/fuz_app/actions/action_rpc.js';

Handler signature for an actor-implying RPC action — auth.actor === 'required'. Mirrors ActionHandler but tightens the ctx.auth slot to the non-null RequestActorContext (with non-null actor).

generics

ActorActionHandler<TInput = any, TOutput = any>
TInput
default any
TOutput
default any

(call)

type (input: TInput, ctx: ActionActorContext): TOutput | Promise<TOutput>

input

type TInput

ctx

returns TOutput | Promise<TOutput>

ActorLookupActionDeps
#

ActorLookupCrossTestOptions
#

testing/cross_backend/actor_lookup.ts view source

RpcPathCrossSuiteOptions import type {ActorLookupCrossTestOptions} from '@fuzdev/fuz_app/testing/cross_backend/actor_lookup.js';

Options for the actor-lookup parity suite. The standard RPC-dispatched cross-suite shape (setup_test / rpc_path); aliases the shared RpcPathCrossSuiteOptions rather than minting a duplicate. No case here is capability-gated, so the flag bundle is not on the shape.

rpc_path?

RPC endpoint path the methods are mounted on. Default /api/rpc.

type string

readonly

setup_test

Per-test fixture-producing function (fresh keeper + db per call).

type (): Promise<TestFixtureBase>

readonly
returns Promise<TestFixtureBase>

ActorLookupEntryJson
#

auth/actor_lookup_action_specs.ts view source

value + type

{ id: string & $brand<"Uuid">; username: string; display_name?: string | undefined; } import {ActorLookupEntryJson} from '@fuzdev/fuz_app/auth/actor_lookup_action_specs.js';

One resolved actor row. display_name omitted when blank.

id

type string & $brand<"Uuid">

username

type string

display_name?

type string

ActorLookupInput
#

ActorLookupOutput
#

ActorLookupRow
#

auth/actor_lookup_queries.ts view source

ActorLookupRow import type {ActorLookupRow} from '@fuzdev/fuz_app/auth/actor_lookup_queries.js';

Row shape returned to handlers — wire mapping happens at the action layer.

id

type Uuid

username

type string

display_name

type string | null

ActorNotOnAccountError
#

http/error_schemas.ts view source

value + type

{ [x: string]: unknown; error: "actor_not_on_account"; } import {ActorNotOnAccountError} from '@fuzdev/fuz_app/http/error_schemas.js';

error

type "actor_not_on_account"

[key: string]

type unknown

ActorRequiredError
#

http/error_schemas.ts view source

value + type

{ [x: string]: unknown; error: "actor_required"; available: { [x: string]: unknown; id: string; name: string; }[]; } import {ActorRequiredError} from '@fuzdev/fuz_app/http/error_schemas.js';

Authorization-phase failure shapes. Surfaced when the dispatcher's apply_authorization_phase rejects a request before the handler runs — the route is acting-aware (input declares acting?: ActingActor or auth requires role_grants), but actor resolution failed.

400: actor_required (with available[]) for unspecified-actor on a multi-actor account; actor_not_on_account for a supplied actor id that doesn't belong to the authenticated account.

500: no_actors_on_account for a signup-invariant violation (the actor list enumerated empty); account_vanished for a torn-read race (account/actor row deleted between credential validation and the dispatcher's follow-up read).

Used by derive_error_schemas when auth.actor !== 'none' so the merged error surface matches what the dispatcher actually emits.

error

type "actor_required"

available

type { [x: string]: unknown; id: string; name: string; }[]

[key: string]

type unknown

ActorSearchActionDeps
#

ActorSearchCrossTestOptions
#

testing/cross_backend/actor_search.ts view source

RpcPathCrossSuiteOptions import type {ActorSearchCrossTestOptions} from '@fuzdev/fuz_app/testing/cross_backend/actor_search.js';

Options for the actor-search parity suite (the standard RPC-dispatched shape).

rpc_path?

RPC endpoint path the methods are mounted on. Default /api/rpc.

type string

readonly

setup_test

Per-test fixture-producing function (fresh keeper + db per call).

type (): Promise<TestFixtureBase>

readonly
returns Promise<TestFixtureBase>

ActorSearchInput
#

auth/actor_search_action_specs.ts view source

value + type

{ query: string; scope_ids?: (string & $brand<"Uuid">)[] | undefined; limit?: number | undefined; } import {ActorSearchInput} from '@fuzdev/fuz_app/auth/actor_search_action_specs.js';

query

type string

scope_ids?

type (string & $brand<"Uuid">)[]

limit?

type number

ActorSearchOutput
#

ActorSearchQueryInput
#

auth/actor_search_queries.ts view source

ActorSearchQueryInput import type {ActorSearchQueryInput} from '@fuzdev/fuz_app/auth/actor_search_queries.js';

Inputs for query_actor_search.

query

Case-insensitive prefix string. Must be non-empty (action layer enforces min(1)).

type string

scope_ids?

When non-empty, restrict to actors holding an active role_grant on one of these scope ids. When empty / omitted, no scope filter is applied — the handler is responsible for the admin gate.

type readonly Uuid[]

limit

Maximum rows to return. The handler clamps to ACTOR_SEARCH_LIMIT_MAX.

type number

ActorSummaryJson
#

auth/account_schema.ts view source

value + type

{ id: string & $brand<"Uuid">; name: string; } import {ActorSummaryJson} from '@fuzdev/fuz_app/auth/account_schema.js';

Zod schema for the actor summary returned in admin account listings.

id

type string & $brand<"Uuid">

name

type string

admin_account_list_action_spec
#

auth/admin_action_specs.ts view source

{ method: string; kind: "request_response"; initiator: "frontend"; auth: { account: "required"; actor: "required"; roles: string[]; }; side_effects: false; input: ZodDefault<ZodObject<{ acting: ZodOptional<$ZodBranded<ZodUUID, "Uuid", "out">>; limit: ZodOptional<...>; offset: ZodOptional<...>; include_deleted: ZodOp... import {admin_account_list_action_spec} from '@fuzdev/fuz_app/auth/admin_action_specs.js';

rate_limit: 'account' bounds admin-side scraping of the account table via (limit, offset) walking — admin trust is not a substitute for a read-rate cap when the listing is paginated and cross-account (yields every account + actor + active role_grant in the system).

ADMIN_ACCOUNT_LIST_DEFAULT_LIMIT
#

ADMIN_ACCOUNT_LIST_LIMIT_MAX
#

admin_accounts_rpc_context
#

ui/admin_accounts_state.svelte.ts view source

{ get: (error_message?: string | undefined) => () => AdminAccountsRpc; get_maybe: () => (() => AdminAccountsRpc) | undefined; set: (value: () => AdminAccountsRpc) => () => AdminAccountsRpc; } import {admin_accounts_rpc_context} from '@fuzdev/fuz_app/ui/admin_accounts_state.svelte.js';

Svelte context carrying the reactive AdminAccountsRpc accessor. The provisioner (typically the admin route shell) calls set(() => rpc); consumers read with const get_rpc = admin_accounts_rpc_context.get(); and either pass the accessor straight to AdminAccountsState/ AdminSessionsState or wrap it with const rpc = $derived(get_rpc()); for direct RPC calls. get() throws when no provisioner ran above the component — the adapter is required, not optional.

admin_invites_rpc_context
#

ui/admin_invites_state.svelte.ts view source

{ get: (error_message?: string | undefined) => () => AdminInvitesRpc; get_maybe: () => (() => AdminInvitesRpc) | undefined; set: (value: () => AdminInvitesRpc) => () => AdminInvitesRpc; } import {admin_invites_rpc_context} from '@fuzdev/fuz_app/ui/admin_invites_state.svelte.js';

Svelte context carrying the reactive AdminInvitesRpc accessor. Mirrors admin_accounts_rpc_context. get() throws when no provisioner ran above the component — the adapter is required.

admin_only_field_blocklist
#

testing/integration_helpers.ts view source

readonly string[] import {admin_only_field_blocklist} from '@fuzdev/fuz_app/testing/integration_helpers.js';

Field names that must not appear in non-admin HTTP response bodies.

admin_session_list_action_spec
#

auth/admin_action_specs.ts view source

{ method: string; kind: "request_response"; initiator: "frontend"; auth: { account: "required"; actor: "required"; roles: string[]; }; side_effects: false; input: ZodDefault<ZodObject<{ acting: ZodOptional<$ZodBranded<ZodUUID, "Uuid", "out">>; }, $strict>>; output: ZodObject<...>; async: true; description: string; r... import {admin_session_list_action_spec} from '@fuzdev/fuz_app/auth/admin_action_specs.js';

rate_limit: 'account' bounds cross-account scraping of every active auth_session row — no pagination, but the read is unbounded across accounts and reveals one row per live cookie globally.

admin_session_revoke_all_action_spec
#

auth/admin_action_specs.ts view source

{ method: string; kind: "request_response"; initiator: "frontend"; auth: { account: "required"; actor: "required"; roles: string[]; }; side_effects: true; input: ZodObject<{ account_id: $ZodBranded<ZodUUID, "Uuid", "out">; acting: ZodOptional<...>; }, $strict>; output: ZodObject<...>; async: true; description: strin... import {admin_session_revoke_all_action_spec} from '@fuzdev/fuz_app/auth/admin_action_specs.js';

admin_token_revoke_all_action_spec
#

auth/admin_action_specs.ts view source

{ method: string; kind: "request_response"; initiator: "frontend"; auth: { account: "required"; actor: "required"; roles: string[]; }; side_effects: true; input: ZodObject<{ account_id: $ZodBranded<ZodUUID, "Uuid", "out">; acting: ZodOptional<...>; }, $strict>; output: ZodObject<...>; async: true; description: strin... import {admin_token_revoke_all_action_spec} from '@fuzdev/fuz_app/auth/admin_action_specs.js';

AdminAccountEntryJson
#

auth/account_schema.ts view source

value + type

{ account: { id: string & $brand<"Uuid">; username: string; email: string | null; email_verified: boolean; created_at: string; updated_at: string; updated_by: (string & $brand<"Uuid">) | null; deleted_at: string | null; }; actor: { ...; } | null; role_grants: { ...; }[]; pending_offers: { ...; }[]; } import {AdminAccountEntryJson} from '@fuzdev/fuz_app/auth/account_schema.js';

Zod schema for an admin account listing entry (account + actor + role_grants + pending offers).

account

type AdminAccountJson

actor

type ActorSummaryJson | null

role_grants

type RoleGrantSummaryJson[]

pending_offers

type PendingOfferSummaryJson[]

AdminAccountJson
#

auth/account_schema.ts view source

value + type

{ id: string & $brand<"Uuid">; username: string; email: string | null; email_verified: boolean; created_at: string; updated_at: string; updated_by: (string & $brand<"Uuid">) | null; deleted_at: string | null; } import {AdminAccountJson} from '@fuzdev/fuz_app/auth/account_schema.js';

Zod schema for admin-facing account data — extends SessionAccountJson with audit fields.

id

type string & $brand<"Uuid">

username

type string

email

type string | null

email_verified

type boolean

created_at

type string

updated_at

type string

updated_by

type (string & $brand<"Uuid">) | null

deleted_at

Soft-delete tombstone, non-null when the account is deleted. Surfaced so the admin UI can mark tombstoned rows (shown only when the listing is requested with include_deleted) and offer reactivation via account_undelete. Active listings always carry null here.

type string | null

AdminAccountListInput
#

auth/admin_action_specs.ts view source

value + type

{ acting?: (string & $brand<"Uuid">) | undefined; limit?: number | null | undefined; offset?: number | null | undefined; include_deleted?: boolean | null | undefined; } import {AdminAccountListInput} from '@fuzdev/fuz_app/auth/admin_action_specs.js';

Input for admin_account_list.

acting?

type string & $brand<"Uuid">

limit?

type number | null

offset?

type number | null

include_deleted?

type boolean | null

AdminAccountListOptions
#

auth/account_queries.ts view source

AdminAccountListOptions import type {AdminAccountListOptions} from '@fuzdev/fuz_app/auth/account_queries.js';

limit?

Max accounts to return. Defaults to ADMIN_ACCOUNT_LIST_DEFAULT_LIMIT when omitted; pass null explicitly to disable the limit (unbounded fetch — for trusted internal callers / scripts; the RPC schema bounds wire callers to [1, ADMIN_ACCOUNT_LIST_LIMIT_MAX]).

type number | null

offset?

Pagination offset. Defaults to 0.

type number | null

include_deleted?

Include soft-deleted (tombstoned) accounts. Defaults to false — the listing shows active accounts only, matching auth resolution. Set true for the admin UI's "show deleted" view, which offers reactivation via account_undelete.

type boolean | null

AdminAccountListOutput
#

auth/admin_action_specs.ts view source

value + type

{ accounts: { account: { id: string & $brand<"Uuid">; username: string; email: string | null; email_verified: boolean; created_at: string; updated_at: string; updated_by: (string & $brand<...>) | null; deleted_at: string | null; }; actor: { ...; } | null; role_grants: { ...; }[]; pending_offers: { ...; }[]; }[]; gra... import {AdminAccountListOutput} from '@fuzdev/fuz_app/auth/admin_action_specs.js';

Output for admin_account_list.

accounts

type AdminAccountEntryJson[]

grantable_roles

type string[]

AdminAccounts
#

AdminAccountsRpc
#

ui/admin_accounts_state.svelte.ts view source

AdminAccountsRpc import type {AdminAccountsRpc} from '@fuzdev/fuz_app/ui/admin_accounts_state.svelte.js';

Narrow RPC surface consumed by AdminAccountsState. Consumers adapt their typed RPC client (e.g. a create_rpc_client Proxy) to this shape — the state class stays decoupled from the client's Result return type so tests can inject plain-function stubs. Mirrors the RoleGrantOffersRpc pattern.

Every operation flows through RPC: the listing reuses admin_account_list, grant reuses role_grant_offer_create, revoke and retract have dedicated actions, and the session / token revoke-all mutations reuse admin_session_revoke_all and admin_token_revoke_all.

Method signatures track the underlying action specs — Uuid-branded ids propagate from the wire through the state class to the components. The adapter built by create_admin_rpc_adapters therefore needs zero casts to bridge to the typed throwing Proxy.

list_accounts

type (include_deleted?: boolean | undefined): Promise<{ accounts: { account: { id: string & $brand<"Uuid">; username: string; email: string | null; email_verified: boolean; created_at: string; updated_at: string; updated_by: (string & $brand<...>) | null; deleted_at: string | null; }; actor: { ...; } | null; role_grants: { ...; }[]; pending_offers: { ...; }[]; }[]; grantable_roles: string[]; }>

include_deleted?

type boolean
optional
returns Promise<AdminAccountListOutput>

delete_account

type (account_id: string & $brand<"Uuid">): Promise<{ ok: true; deleted: boolean; }>

account_id

type Uuid
returns Promise<AccountDeleteOutput>

undelete_account

type (account_id: string & $brand<"Uuid">): Promise<{ ok: true; undeleted: boolean; }>

account_id

type Uuid
returns Promise<AccountUndeleteOutput>

list_sessions

type (): Promise<{ sessions: { id: string & $brand<"SessionId">; account_id: string & $brand<"Uuid">; created_at: string; expires_at: string; username: string; }[]; }>

returns Promise<AdminSessionListOutput>

create_role_grant

type (params: { to_account_id: string & $brand<"Uuid">; role: string; to_actor_id?: (string & $brand<"Uuid">) | null | undefined; scope_kind?: string | null | undefined; scope_id?: (string & $brand<...>) | ... 1 more ... | undefined; message?: string | ... 1 more ... | undefined; acting?: (string & $brand<...>) | undefined; }): Promise<...>

params

revoke_role_grant

type (params: { actor_id: string & $brand<"Uuid">; role_grant_id: string & $brand<"Uuid">; reason?: string | null | undefined; acting?: (string & $brand<"Uuid">) | undefined; }): Promise<...>

params

returns Promise<RoleGrantRevokeOutput>

retract_offer

type (offer_id: string & $brand<"Uuid">): Promise<{ ok: true; }>

offer_id

type Uuid
returns Promise<RoleGrantOfferOkOutput>

session_revoke_all

type (params: { account_id: string & $brand<"Uuid">; acting?: (string & $brand<"Uuid">) | undefined; }): Promise<{ ok: true; count: number; }>

params

token_revoke_all

type (params: { account_id: string & $brand<"Uuid">; acting?: (string & $brand<"Uuid">) | undefined; }): Promise<{ ok: true; count: number; }>

params

returns Promise<AdminTokenRevokeAllOutput>

AdminAccountsState
#

ui/admin_accounts_state.svelte.ts view source

import {AdminAccountsState} from '@fuzdev/fuz_app/ui/admin_accounts_state.svelte.js';

list

type AsyncSlot<void, string>

readonly

grant

type KeyedAsyncSlot<string, RoleGrantOfferJson, string>

readonly

revoke

type KeyedAsyncSlot<string & $brand<"Uuid">, void, string>

readonly

retract

type KeyedAsyncSlot<string & $brand<"Uuid">, void, string>

readonly

soft_delete

type KeyedAsyncSlot<string & $brand<"Uuid">, void, string>

readonly

undelete

type KeyedAsyncSlot<string & $brand<"Uuid">, void, string>

readonly

accounts

type AdminAccountEntryJson[]

$state.raw

grantable_roles

type string[]

$state.raw

show_deleted

When true, fetch() includes soft-deleted (tombstoned) accounts so the admin can reactivate them. Toggled via set_show_deleted.

type boolean

$state

account_count

type number

readonly $derived

constructor

type new (options: AdminAccountsStateOptions): AdminAccountsState

options

fetch

type (): Promise<void>

returns Promise<void>

set_show_deleted

Toggle whether soft-deleted accounts appear in the listing, then re-fetch. Tombstoned rows are surfaced so an admin can reactivate them via submit_undelete.

type (value: boolean): Promise<void>

value

type boolean
returns Promise<void>

submit_delete

Soft-delete an account (reversible tombstone) via account_delete. Keyed by account_id so per-row spinners/errors stay independent. Refreshes the listing on success so the row drops out (active view) or flips to its tombstoned state (show_deleted view).

type (account_id: string & $brand<"Uuid">): Promise<void>

account_id

type Uuid
returns Promise<void>

submit_undelete

Reactivate a soft-deleted account via account_undelete (admin-only). Keyed by account_id; refreshes the listing on success so the row returns to active state.

type (account_id: string & $brand<"Uuid">): Promise<void>

account_id

type Uuid
returns Promise<void>

submit_grant

Offer the role to the recipient via the role_grant_offer_create RPC. Server returns the pending offer; the recipient must accept before the role_grant materializes. Returns the offer payload on success so callers can drive follow-up UX (e.g. seed RoleGrantOffersState.outgoing).

A re-offer from the same admin to the same (account, role) refreshes the existing pending row — the returned offer id is stable across those calls.

to_actor_id (optional) narrows the offer to a specific actor on account_id; the keyed-slot key stays at account_id:role for the account-grain default (so existing consumers keep working) and becomes account_id:role:to_actor_id when actor-targeted, so the two variants can be in flight without colliding on the per-row spinner.

type (account_id: string & $brand<"Uuid">, role: string, to_actor_id?: (string & $brand<"Uuid">) | null | undefined): Promise<{ id: string & $brand<"Uuid">; ... 14 more ...; resulting_role_grant_id: (string & $brand<...>) | null; } | undefined>

account_id

type Uuid

role

type string

to_actor_id?

type Uuid | null
optional
returns Promise<RoleGrantOfferJson | undefined>

submit_revoke

Revoke an active role_grant via the role_grant_revoke RPC.

actor_id is the natural key — role_grants are actor-scoped, and the admin UI reads row.actor.id straight from the listing, so the state class takes it directly rather than deriving it from account_id. The optional reason is stamped on role_grant.revoked_reason and surfaced on the revokee's WS notification.

type (actor_id: string & $brand<"Uuid">, role_grant_id: string & $brand<"Uuid">, reason?: string | null | undefined): Promise<void>

actor_id

type Uuid

role_grant_id

type Uuid

reason?

type string | null
optional
returns Promise<void>

submit_retract

Retract a pending offer the admin issued via the role_grant_offer_retract RPC. The action handles auth, audit, and the role_grant_offer_retracted WS notification.

After success, refetches the listing so pending_offers drops the row and the "+ {role}" button un-hides.

type (offer_id: string & $brand<"Uuid">): Promise<void>

offer_id

type Uuid
returns Promise<void>

AdminAccountsStateOptions
#

ui/admin_accounts_state.svelte.ts view source

AdminAccountsStateOptions import type {AdminAccountsStateOptions} from '@fuzdev/fuz_app/ui/admin_accounts_state.svelte.js';

get_rpc

Reactive accessor for the RPC adapter. Matches RoleGrantOffersStateOptions.account_id / actor_id pattern — lets the component pass a $props()-sourced rpc without tripping Svelte's state_referenced_locally warning.

type (): AdminAccountsRpc

AdminActionOptions
#

auth/admin_actions.ts view source

AdminActionOptions import type {AdminActionOptions} from '@fuzdev/fuz_app/auth/admin_actions.js';

roles?

Role schema result from create_role_schema(). Defaults to builtin roles only. Used to derive grantable_roles (the subset whose RoleSpec.grant_paths includes 'admin') returned by admin_account_list.

type RoleSchemaResult

AdminAuditLog
#

AdminInvites
#

AdminInvitesRpc
#

ui/admin_invites_state.svelte.ts view source

AdminInvitesRpc import type {AdminInvitesRpc} from '@fuzdev/fuz_app/ui/admin_invites_state.svelte.js';

Narrow RPC surface consumed by AdminInvitesState. Consumers adapt their typed RPC client to this shape. error.data.reason on thrown errors carries the ERROR_INVITE_* constant — handled by the caller when user-friendly messages are needed. Method signatures track the wire spec types directly so the adapter needs no casts.

list

type (): Promise<{ invites: { id: string & $brand<"Uuid">; email: string | null; username: string | null; claimed_by: (string & $brand<"Uuid">) | null; claimed_at: string | null; created_at: string; created_by: (string & $brand<...>) | null; created_by_username: string | null; claimed_by_username: string | null; }[]; }>

returns Promise<InviteListOutput>

create

type (params: { email?: string | null | undefined; username?: string | null | undefined; acting?: (string & $brand<"Uuid">) | undefined; }): Promise<{ ok: true; invite: { id: string & $brand<"Uuid">; ... 5 more ...; created_by: (string & $brand<...>) | null; }; }>

params

returns Promise<InviteCreateOutput>

delete

type (params: { invite_id: string & $brand<"Uuid">; acting?: (string & $brand<"Uuid">) | undefined; }): Promise<{ ok: true; }>

params

returns Promise<InviteDeleteOutput>

AdminInvitesState
#

ui/admin_invites_state.svelte.ts view source

import {AdminInvitesState} from '@fuzdev/fuz_app/ui/admin_invites_state.svelte.js';

list

type AsyncSlot<void, string>

readonly

create

type AsyncSlot<void, string>

readonly

remove

type KeyedAsyncSlot<string & $brand<"Uuid">, void, string>

readonly

invites

type InviteWithUsernamesJson[]

$state.raw

invite_count

type number

readonly $derived

unclaimed_count

type number

readonly $derived

constructor

type new (options: AdminInvitesStateOptions): AdminInvitesState

options

fetch

type (): Promise<void>

returns Promise<void>

submit_create

type (email?: string | undefined, username?: string | undefined): Promise<boolean>

email?

type string
optional

username?

type string
optional
returns Promise<boolean>

submit_delete

type (id: string & $brand<"Uuid">): Promise<void>

id

type Uuid
returns Promise<void>

AdminInvitesStateOptions
#

AdminOverview
#

AdminRoleGrantHistory
#

AdminRpcAdapters
#

AdminRpcApi
#

ui/admin_rpc_adapters.ts view source

AdminRpcApi import type {AdminRpcApi} from '@fuzdev/fuz_app/ui/admin_rpc_adapters.js';

The wire-method surface this module needs from the typed throwing RPC client. Every method returns the unwrapped value or throws an Error carrying the JSON-RPC {code, message, data?} shape — i.e. the ThrowingApi<...> view of the corresponding action specs.

Consumers pass the typed throwing Proxy returned by create_frontend_rpc_client directly. Structural typing means any superset (e.g. the consumer's full ThrowingApi<ActionsApi>) is assignable as long as these methods are present at these signatures.

admin_account_list

type (input?: { acting?: (string & $brand<"Uuid">) | undefined; limit?: number | null | undefined; offset?: number | null | undefined; include_deleted?: boolean | null | undefined; } | undefined): Promise<...>

input?

optional
returns Promise<AdminAccountListOutput>

account_delete

type (input: { account_id?: (string & $brand<"Uuid">) | null | undefined; acting?: (string & $brand<"Uuid">) | undefined; }): Promise<{ ok: true; deleted: boolean; }>

input

returns Promise<AccountDeleteOutput>

account_undelete

type (input: { account_id: string & $brand<"Uuid">; acting?: (string & $brand<"Uuid">) | undefined; }): Promise<{ ok: true; undeleted: boolean; }>

input

returns Promise<AccountUndeleteOutput>

admin_session_list

type (): Promise<{ sessions: { id: string & $brand<"SessionId">; account_id: string & $brand<"Uuid">; created_at: string; expires_at: string; username: string; }[]; }>

returns Promise<AdminSessionListOutput>

admin_session_revoke_all

type (input: { account_id: string & $brand<"Uuid">; acting?: (string & $brand<"Uuid">) | undefined; }): Promise<{ ok: true; count: number; }>

input

admin_token_revoke_all

type (input: { account_id: string & $brand<"Uuid">; acting?: (string & $brand<"Uuid">) | undefined; }): Promise<{ ok: true; count: number; }>

input

returns Promise<AdminTokenRevokeAllOutput>

audit_log_list

type (input: { event_type?: string | null | undefined; outcome?: "success" | "failure" | null | undefined; account_id?: (string & $brand<"Uuid">) | null | undefined; limit?: number | null | undefined; offset?: number | ... 1 more ... | undefined; since_seq?: number | ... 1 more ... | undefined; acting?: (string & $brand<...>) | undefined; }): Promise<...>

input

returns Promise<AuditLogListOutput>

audit_log_role_grant_history

type (input: { limit?: number | null | undefined; offset?: number | null | undefined; acting?: (string & $brand<"Uuid">) | undefined; }): Promise<{ events: { id: string & $brand<"Uuid">; ... 11 more ...; target_username: string | null; }[]; }>

input

invite_list

type (): Promise<{ invites: { id: string & $brand<"Uuid">; email: string | null; username: string | null; claimed_by: (string & $brand<"Uuid">) | null; claimed_at: string | null; created_at: string; created_by: (string & $brand<...>) | null; created_by_username: string | null; claimed_by_username: string | null; }[]; }>

returns Promise<InviteListOutput>

invite_create

type (input: { email?: string | null | undefined; username?: string | null | undefined; acting?: (string & $brand<"Uuid">) | undefined; }): Promise<{ ok: true; invite: { id: string & $brand<"Uuid">; ... 5 more ...; created_by: (string & $brand<...>) | null; }; }>

input

returns Promise<InviteCreateOutput>

invite_delete

type (input: { invite_id: string & $brand<"Uuid">; acting?: (string & $brand<"Uuid">) | undefined; }): Promise<{ ok: true; }>

input

returns Promise<InviteDeleteOutput>

app_settings_get

type (): Promise<{ settings: { open_signup: boolean; updated_at: string | null; updated_by: (string & $brand<"Uuid">) | null; updated_by_username: string | null; }; }>

returns Promise<AppSettingsGetOutput>

app_settings_update

type (input: { open_signup: boolean; acting?: (string & $brand<"Uuid">) | undefined; }): Promise<{ ok: true; settings: { open_signup: boolean; updated_at: string | null; updated_by: (string & $brand<...>) | null; updated_by_username: string | null; }; }>

input

returns Promise<AppSettingsUpdateOutput>

role_grant_offer_create

type (input: { to_account_id: string & $brand<"Uuid">; role: string; to_actor_id?: (string & $brand<"Uuid">) | null | undefined; scope_kind?: string | null | undefined; scope_id?: (string & $brand<...>) | ... 1 more ... | undefined; message?: string | ... 1 more ... | undefined; acting?: (string & $brand<...>) | undefined; }): Promise<...>

input

role_grant_offer_retract

type (input: { offer_id: string & $brand<"Uuid">; acting?: (string & $brand<"Uuid">) | undefined; }): Promise<{ ok: true; }>

input

returns Promise<RoleGrantOfferOkOutput>

role_grant_revoke

type (input: { actor_id: string & $brand<"Uuid">; role_grant_id: string & $brand<"Uuid">; reason?: string | null | undefined; acting?: (string & $brand<"Uuid">) | undefined; }): Promise<...>

input

returns Promise<RoleGrantRevokeOutput>

AdminSessionJson
#

auth/audit_log_schema.ts view source

value + type

{ id: string & $brand<"SessionId">; account_id: string & $brand<"Uuid">; created_at: string; expires_at: string; username: string; } import {AdminSessionJson} from '@fuzdev/fuz_app/auth/audit_log_schema.js';

Zod schema for admin session listing (session + username).

id

type string & $brand<"SessionId">

account_id

type string & $brand<"Uuid">

created_at

type string

expires_at

type string

username

type string

AdminSessionListInput
#

auth/admin_action_specs.ts view source

value + type

{ acting?: (string & $brand<"Uuid">) | undefined; } import {AdminSessionListInput} from '@fuzdev/fuz_app/auth/admin_action_specs.js';

Input for admin_session_list.

acting?

type string & $brand<"Uuid">

AdminSessionListOutput
#

auth/admin_action_specs.ts view source

value + type

{ sessions: { id: string & $brand<"SessionId">; account_id: string & $brand<"Uuid">; created_at: string; expires_at: string; username: string; }[]; } import {AdminSessionListOutput} from '@fuzdev/fuz_app/auth/admin_action_specs.js';

Output for admin_session_list. Cross-account listing; fan-out already scoped by role auth.

sessions

type AdminSessionJson[]

AdminSessionRevokeAllInput
#

auth/admin_action_specs.ts view source

value + type

{ account_id: string & $brand<"Uuid">; acting?: (string & $brand<"Uuid">) | undefined; } import {AdminSessionRevokeAllInput} from '@fuzdev/fuz_app/auth/admin_action_specs.js';

Input for admin_session_revoke_all.

account_id

type string & $brand<"Uuid">

acting?

type string & $brand<"Uuid">

AdminSessionRevokeAllOutput
#

auth/admin_action_specs.ts view source

value + type

{ ok: true; count: number; } import {AdminSessionRevokeAllOutput} from '@fuzdev/fuz_app/auth/admin_action_specs.js';

Output for admin_session_revoke_all.

ok

type true

count

type number

AdminSessions
#

AdminSessionsState
#

ui/admin_sessions_state.svelte.ts view source

import {AdminSessionsState} from '@fuzdev/fuz_app/ui/admin_sessions_state.svelte.js';

list

type AsyncSlot<void, string>

readonly

revoke_sessions

type KeyedAsyncSlot<string & $brand<"Uuid">, void, string>

readonly

revoke_tokens

type KeyedAsyncSlot<string & $brand<"Uuid">, void, string>

readonly

sessions

type AdminSessionJson[]

$state.raw

active_count

type number

readonly $derived

constructor

type new (options: AdminSessionsStateOptions): AdminSessionsState

options

fetch

type (): Promise<void>

returns Promise<void>

submit_revoke_sessions

type (account_id: string & $brand<"Uuid">): Promise<void>

account_id

type Uuid
returns Promise<void>

submit_revoke_tokens

type (account_id: string & $brand<"Uuid">): Promise<void>

account_id

type Uuid
returns Promise<void>

AdminSessionsStateOptions
#

ui/admin_sessions_state.svelte.ts view source

AdminSessionsStateOptions import type {AdminSessionsStateOptions} from '@fuzdev/fuz_app/ui/admin_sessions_state.svelte.js';

Options for AdminSessionsState.

The RPC adapter drives every operation (listing + the two revoke-all mutations).

get_rpc

Reactive accessor for the RPC adapter. Mirrors AdminAccountsStateOptions.get_rpc so a single adapter instance backs both states without tripping Svelte's state_referenced_locally warning.

type (): AdminAccountsRpc

AdminSettings
#

AdminSurface
#

AdminTokenRevokeAllInput
#

auth/admin_action_specs.ts view source

value + type

{ account_id: string & $brand<"Uuid">; acting?: (string & $brand<"Uuid">) | undefined; } import {AdminTokenRevokeAllInput} from '@fuzdev/fuz_app/auth/admin_action_specs.js';

Input for admin_token_revoke_all.

account_id

type string & $brand<"Uuid">

acting?

type string & $brand<"Uuid">

AdminTokenRevokeAllOutput
#

auth/admin_action_specs.ts view source

value + type

{ ok: true; count: number; } import {AdminTokenRevokeAllOutput} from '@fuzdev/fuz_app/auth/admin_action_specs.js';

Output for admin_token_revoke_all.

ok

type true

count

type number

AdversarialHeaderCase
#

testing/adversarial_headers.ts view source

AdversarialHeaderCase import type {AdversarialHeaderCase} from '@fuzdev/fuz_app/testing/adversarial_headers.js';

A header-level attack case for middleware stack testing.

name

type string

headers

type Record<string, string>

expected_status

type number

expected_error?

type string

expected_error_schema?

Zod schema to validate error response body against. Defaults to ApiError when expected_error is set.

type ZodType<unknown, unknown, $ZodTypeInternals<unknown, unknown>>

validate_expectation

Whether the request should reach token validation or be short-circuited by earlier middleware.

type "called" | "not_called"

AdversarialTestOptions
#

testing/attack_surface.ts view source

AdversarialTestOptions import type {AdversarialTestOptions} from '@fuzdev/fuz_app/testing/attack_surface.js';

Options for adversarial test runners (auth enforcement and input validation).

build

Build the app surface bundle (surface + route specs + middleware specs).

type (): AppSurfaceSpec

roles

All roles in the app (e.g. ['admin', 'keeper']).

type string[]

skip_routes?

Routes to skip, in 'METHOD /path' form (the surface key) — the escape hatch every sibling suite carries. Reach for it only when a route cannot be driven generically at all (a handler needing real seeded state, a path segment no schema can describe); a route whose params merely have a format is handled by the synthesizer.

type string[]

Alignment
#

ui/position_helpers.ts view source

Alignment

type "start" | "center" | "end"

import type {Alignment} from '@fuzdev/fuz_app/ui/position_helpers.js';

Alignment options for positioned elements.

all_account_action_specs
#

auth/account_action_specs.ts view source

{ method: string; initiator: "frontend" | "backend" | "both"; side_effects: boolean; input: ZodType<unknown, unknown, $ZodTypeInternals<unknown, unknown>>; output: ZodType<unknown, unknown, $ZodTypeInternals<unknown, unknown>>; ... 6 more ...; rate_limit?: "both" | ... 2 more ... | undefined; }[]

type RequestResponseActionSpec[]

import {all_account_action_specs} from '@fuzdev/fuz_app/auth/account_action_specs.js';

All self-service account action specs — a codegen-ready registry. Consumers spread this into their own action-spec array to include account methods in a typed client surface.

all_actor_lookup_action_specs
#

auth/actor_lookup_action_specs.ts view source

readonly [{ method: string; kind: "request_response"; initiator: "frontend"; auth: { account: "required"; actor: "none"; }; side_effects: false; input: ZodObject<{ ids: ZodArray<$ZodBranded<ZodUUID, "Uuid", "out">>; }, $strict>; output: ZodObject<...>; async: true; rate_limit: "account"; description: string; }] import {all_actor_lookup_action_specs} from '@fuzdev/fuz_app/auth/actor_lookup_action_specs.js';

All actor_lookup action specs — independent opt-in registry. Consumers spread alongside all_standard_action_specs if they want the labels arc; not folded into the standard bundle because consumers without a byline surface can skip it.

all_actor_search_action_specs
#

auth/actor_search_action_specs.ts view source

readonly [{ method: string; kind: "request_response"; initiator: "frontend"; auth: { account: "required"; actor: "none"; }; side_effects: false; input: ZodObject<{ query: ZodString; scope_ids: ZodOptional<ZodArray<$ZodBranded<...>>>; limit: ZodOptional<...>; }, $strict>; ... 4 more ...; description: string; }] import {all_actor_search_action_specs} from '@fuzdev/fuz_app/auth/actor_search_action_specs.js';

All actor_search action specs — independent opt-in registry. Like all_actor_lookup_action_specs, not folded into all_standard_action_specs because consumers without a person-target picker can skip it.

all_admin_action_specs
#

auth/admin_action_specs.ts view source

{ method: string; initiator: "frontend" | "backend" | "both"; side_effects: boolean; input: ZodType<unknown, unknown, $ZodTypeInternals<unknown, unknown>>; output: ZodType<unknown, unknown, $ZodTypeInternals<unknown, unknown>>; ... 6 more ...; rate_limit?: "both" | ... 2 more ... | undefined; }[]

type RequestResponseActionSpec[]

import {all_admin_action_specs} from '@fuzdev/fuz_app/auth/admin_action_specs.js';

All admin action specs — a codegen-ready registry. Consumers spread this into their own action-spec array to include admin methods in a typed client surface. Includes the two app-settings specs, whose handlers the runtime factory always wires.

all_cell_action_specs
#

auth/cell_action_specs.ts view source

readonly [{ method: string; kind: "request_response"; initiator: "frontend"; auth: { account: "required"; actor: "required"; }; side_effects: true; input: ZodObject<{ data: ZodObject<{ label: ZodOptional<ZodString>; summary: ZodOptional<...>; }, $loose>; ... 4 more ...; acting: ZodOptional<...>; }, $strict>; output:... import {all_cell_action_specs} from '@fuzdev/fuz_app/auth/cell_action_specs.js';

All cell-layer action specs — composed by app registries. Bundles the seven generic verbs (this module), the three cell_grant_* specs, the three cell_field_* specs, the four cell_item_* specs, and the cell_audit_list spec so codegen + UI clients see a single cell namespace.

all_cell_audit_action_specs
#

auth/cell_audit_action_specs.ts view source

readonly [{ method: string; kind: "request_response"; initiator: "frontend"; auth: { account: "required"; actor: "required"; }; side_effects: false; input: ZodObject<{ cell_id: $ZodBranded<ZodUUID, "Uuid", "out">; acting: ZodOptional<...>; }, $strict>; output: ZodObject<...>; async: true; description: string; }] import {all_cell_audit_action_specs} from '@fuzdev/fuz_app/auth/cell_audit_action_specs.js';

Registry export to compose into all_cell_action_specs.

all_cell_field_action_specs
#

auth/cell_field_action_specs.ts view source

readonly [{ method: string; kind: "request_response"; initiator: "frontend"; auth: { account: "required"; actor: "required"; }; side_effects: true; input: ZodObject<{ source_id: $ZodBranded<ZodUUID, "Uuid", "out">; name: $ZodBranded<...>; target_id: $ZodBranded<...>; acting: ZodOptional<...>; }, $strict>; output: Zo... import {all_cell_field_action_specs} from '@fuzdev/fuz_app/auth/cell_field_action_specs.js';

All cell_field action specs — composed into all_cell_action_specs.

all_cell_grant_action_specs
#

auth/cell_grant_action_specs.ts view source

readonly [{ method: string; kind: "request_response"; initiator: "frontend"; auth: { account: "required"; actor: "required"; }; side_effects: true; input: ZodObject<{ cell_id: $ZodBranded<ZodUUID, "Uuid", "out">; level: ZodEnum<...>; principal: ZodDiscriminatedUnion<...>; acting: ZodOptional<...>; }, $strict>; outpu... import {all_cell_grant_action_specs} from '@fuzdev/fuz_app/auth/cell_grant_action_specs.js';

All cell_grant action specs — composed into all_cell_action_specs.

all_cell_item_action_specs
#

auth/cell_item_action_specs.ts view source

readonly [{ method: string; kind: "request_response"; initiator: "frontend"; auth: { account: "required"; actor: "required"; }; side_effects: true; input: ZodObject<{ parent_id: $ZodBranded<ZodUUID, "Uuid", "out">; child_id: $ZodBranded<...>; position: $ZodBranded<...>; acting: ZodOptional<...>; }, $strict>; output:... import {all_cell_item_action_specs} from '@fuzdev/fuz_app/auth/cell_item_action_specs.js';

All cell_item action specs — composed into all_cell_action_specs.

all_fuz_auth_action_spec_registries
#

auth/all_action_spec_registries.ts view source

readonly FuzAuthActionSpecRegistry[]

type readonly FuzAuthActionSpecRegistry[]

import {all_fuz_auth_action_spec_registries} from '@fuzdev/fuz_app/auth/all_action_spec_registries.js';

Every fuz_auth action-spec registry, in dependency-stable order.

Update this list when a new fuz_auth registry lands. The walker tests (action_spec_input_invariants.test.ts, all_action_spec_registries.acting_biconditional.test.ts) iterate over it — a missing entry silently skips coverage, which is the failure mode the registry-of-registries shape exists to prevent.

all_role_grant_offer_action_specs
#

auth/role_grant_offer_action_specs.ts view source

{ method: string; initiator: "frontend" | "backend" | "both"; side_effects: boolean; input: ZodType<unknown, unknown, $ZodTypeInternals<unknown, unknown>>; output: ZodType<unknown, unknown, $ZodTypeInternals<unknown, unknown>>; ... 6 more ...; rate_limit?: "both" | ... 2 more ... | undefined; }[]

type RequestResponseActionSpec[]

import {all_role_grant_offer_action_specs} from '@fuzdev/fuz_app/auth/role_grant_offer_action_specs.js';

All role-grant-offer action specs — a codegen-ready registry. Consumers spread this into their own action-spec array to include offer lifecycle + revoke + assign methods in a typed client surface.

all_self_service_role_action_specs
#

auth/self_service_role_action_specs.ts view source

readonly { method: string; initiator: "frontend" | "backend" | "both"; side_effects: boolean; input: ZodType<unknown, unknown, $ZodTypeInternals<unknown, unknown>>; output: ZodType<unknown, unknown, $ZodTypeInternals<unknown, unknown>>; ... 6 more ...; rate_limit?: "both" | ... 2 more ... | undefined; }[]

type readonly RequestResponseActionSpec[]

import {all_self_service_role_action_specs} from '@fuzdev/fuz_app/auth/self_service_role_action_specs.js';

All self-service role action specs — a codegen-ready registry. Single-element post-unification, kept for symmetry with the other all_*_action_specs exports so codegen and frontend bundles import the same shape.

all_standard_action_specs
#

auth/standard_action_specs.ts view source

readonly { method: string; initiator: "frontend" | "backend" | "both"; side_effects: boolean; input: ZodType<unknown, unknown, $ZodTypeInternals<unknown, unknown>>; output: ZodType<unknown, unknown, $ZodTypeInternals<unknown, unknown>>; ... 6 more ...; rate_limit?: "both" | ... 2 more ... | undefined; }[]

type readonly RequestResponseActionSpec[]

import {all_standard_action_specs} from '@fuzdev/fuz_app/auth/standard_action_specs.js';

Combined spec registry for the standard RPC surface (admin + role_grant_offer + account). Symmetric with create_standard_rpc_actions.

Spec count is the sum of the three sub-registries. Adding a method to any sub-registry surfaces here automatically.

AllCellActionsOptions
#

ALLOWED_ORIGINS_ERROR_MESSAGES
#

http/origin.ts view source

Readonly<Record<"empty_entry" | "not_printable_ascii" | "missing_scheme" | "unsupported_scheme" | "has_path" | "has_userinfo" | "missing_host" | "invalid_host_char" | "unbracketed_ipv6" | ... 7 more ... | "no_origins", string>>

type Readonly<Record<AllowedOriginsErrorKind, string>>

import {ALLOWED_ORIGINS_ERROR_MESSAGES} from '@fuzdev/fuz_app/http/origin.js';

The operator-facing reason for each refusal kind.

AllowedOriginsError
#

http/origin.ts view source

AllowedOriginsError import type {AllowedOriginsError} from '@fuzdev/fuz_app/http/origin.js';

One reason parse_allowed_origins refused a value.

kind

type AllowedOriginsErrorKind

readonly

index?

The refused entry's 1-based position; absent for no_origins.

type number

readonly

entry?

The refused entry as given, trimmed; absent for no_origins.

type string

readonly

AllowedOriginsErrorKind
#

http/origin.ts view source

value + type

"empty_entry" | "not_printable_ascii" | "missing_scheme" | "unsupported_scheme" | "has_path" | "has_userinfo" | "missing_host" | "invalid_host_char" | "unbracketed_ipv6" | ... 7 more ... | "no_origins"

type "empty_entry" | "not_printable_ascii" | "missing_scheme" | "unsupported_scheme" | "has_path" | "has_userinfo" | "missing_host" | "invalid_host_char" | "unbracketed_ipv6" | "partial_wildcard" | "non_canonical_ipv4" | "unclosed_ipv6" | "invalid_ipv6" | "invalid_after_ipv6" | "invalid_port" | "default_port" | "no_origins"

import {AllowedOriginsErrorKind} from '@fuzdev/fuz_app/http/origin.js';

Why an allowlist value was refused: no_origins (a blank value) or an entry's kind.

announce_rate_limiters_disabled
#

rate_limiter.ts view source

(): void import {announce_rate_limiters_disabled} from '@fuzdev/fuz_app/rate_limiter.js';

Print RATE_LIMITERS_DISABLED_BANNER with console.error, once per module instance.

Deliberately on stderr as well as the surface warning, which goes through the server's logger: a deployment that reached this mode is unprotected on every password-bearing route, and that must show in its first lines of output even with the logger silenced. Once per module instance, so a module instance that assembles many in-process servers prints it once, while every spawned test binary prints it at startup — the twin of the Rust spine's RateLimiterMode::limiter.

returns

void

API_TOKEN_BODY_LENGTH
#

API_TOKEN_COLUMNS
#

auth/api_token_queries.ts view source

readonly ["id", "account_id", "name", "token_hash", "expires_at", "last_used_at", "last_used_ip", "created_at", "scope"] import {API_TOKEN_COLUMNS} from '@fuzdev/fuz_app/auth/api_token_queries.js';

The full api_token column set, named explicitly so a row read fails loud on schema drift (see ACCOUNT_COLUMNS in auth/account_queries.ts for the outage class; the Rust twin names columns at every token site). Keep in sync with ApiToken and the migration chain's end state.

API_TOKEN_ENTROPY_BYTES
#

auth/api_token.ts view source

32 import {API_TOKEN_ENTROPY_BYTES} from '@fuzdev/fuz_app/auth/api_token.js';

Random bytes encoded into the raw token body (256 bits).

API_TOKEN_ID_REGEX
#

auth/api_token.ts view source

RegExp import {API_TOKEN_ID_REGEX} from '@fuzdev/fuz_app/auth/api_token.js';

Regex for the public API token id (e.g. tok_abC0_d-3xyzA). Twelve base64url characters after the tok_ prefix. Matches the format produced by generate_api_token.

API_TOKEN_INDEX
#

auth/auth_ddl.ts view source

"\nCREATE INDEX IF NOT EXISTS idx_api_token_account ON api_token(account_id)" import {API_TOKEN_INDEX} from '@fuzdev/fuz_app/auth/auth_ddl.js';

API_TOKEN_NAME_LENGTH_MAX
#

auth/account_action_specs.ts view source

255 import {API_TOKEN_NAME_LENGTH_MAX} from '@fuzdev/fuz_app/auth/account_action_specs.js';

Maximum length of an API token's name, in UTF-16 code units (what String.length measures; enforced by bounded_string). The name is copied into the token_create audit row, so it is bounded where it enters. Same value as the Rust spine's API_TOKEN_NAME_LENGTH_MAX.

API_TOKEN_PREFIX
#

auth/api_token.ts view source

"secret_fuz_token_" import {API_TOKEN_PREFIX} from '@fuzdev/fuz_app/auth/api_token.js';

Prefix for all fuz API tokens (enables secret scanning).

API_TOKEN_REGEX
#

API_TOKEN_SCHEMA
#

auth/auth_ddl.ts view source

"\nCREATE TABLE IF NOT EXISTS api_token (\n id TEXT PRIMARY KEY,\n account_id UUID NOT NULL REFERENCES account(id) ON DELETE CASCADE,\n name TEXT NOT NULL,\n token_hash TEXT NOT NULL,\n expires_at TIMESTAMPTZ,\n last_used_at TIMESTAMPTZ,\n last_used_ip TEXT,\n created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()\n)" import {API_TOKEN_SCHEMA} from '@fuzdev/fuz_app/auth/auth_ddl.js';

ApiError
#

http/error_schemas.ts view source

value + type

{ [x: string]: unknown; error: string; } import {ApiError} from '@fuzdev/fuz_app/http/error_schemas.js';

Base API error — all JSON error responses have at least {error: string}.

error

type string

[key: string]

type unknown

ApiToken
#

auth/account_schema.ts view source

ApiToken import type {ApiToken} from '@fuzdev/fuz_app/auth/account_schema.js';

API token for CLI/programmatic access.

id

type string

account_id

type Uuid

name

type string

token_hash

type string

expires_at

type string | null

last_used_at

type string | null

last_used_ip

type string | null

created_at

type string

scope

The token's authority narrowing (api_token.scope, NOT NULL). Stored as JSONB; read back through parse_token_scope, which is fail-closed — an unreadable document refuses the credential rather than widening it.

type unknown

ApiTokenId
#

auth/api_token.ts view source

value + type

string import {ApiTokenId} from '@fuzdev/fuz_app/auth/api_token.js';

Zod schema for the public API token id.

ApiTokenQueryDeps
#

APP_SERVER_RATE_LIMITER_KEYS
#

server/app_server.ts view source

internal

readonly ("login_ip_rate_limiter" | "signup_ip_rate_limiter" | "bootstrap_ip_rate_limiter" | "login_account_rate_limiter" | "signup_account_rate_limiter" | "action_ip_rate_limiter" | "action_account_rate_limiter")[]

type readonly ("login_ip_rate_limiter" | "signup_ip_rate_limiter" | "bootstrap_ip_rate_limiter" | "login_account_rate_limiter" | "signup_account_rate_limiter" | "action_ip_rate_limiter" | "action_account_rate_limiter")[]

import {APP_SERVER_RATE_LIMITER_KEYS} from '@fuzdev/fuz_app/server/app_server.js';

Every AppServerOptions rate-limiter field, in build order.

APP_SETTINGS_COLUMNS
#

auth/app_settings_queries.ts view source

readonly ["id", "open_signup", "updated_at", "updated_by"] import {APP_SETTINGS_COLUMNS} from '@fuzdev/fuz_app/auth/app_settings_queries.js';

The full app_settings column set — the singleton id (always 1) plus the settings the row type carries — drift-guarded like every other *_COLUMNS. Reads project APP_SETTINGS_ROW_COLUMNS, which omits the constant id since AppSettings doesn't carry it. Keep in sync with AppSettings and the app_settings DDL in auth/auth_ddl.ts.

app_settings_get_action_spec
#

auth/admin_action_specs.ts view source

{ method: string; kind: "request_response"; initiator: "frontend"; auth: { account: "required"; actor: "required"; roles: string[]; }; side_effects: false; input: ZodDefault<ZodObject<{ acting: ZodOptional<$ZodBranded<ZodUUID, "Uuid", "out">>; }, $strict>>; output: ZodObject<...>; async: true; description: string; } import {app_settings_get_action_spec} from '@fuzdev/fuz_app/auth/admin_action_specs.js';

app_settings_rpc_context
#

ui/app_settings_state.svelte.ts view source

{ get: (error_message?: string | undefined) => () => AppSettingsRpc; get_maybe: () => (() => AppSettingsRpc) | undefined; set: (value: () => AppSettingsRpc) => () => AppSettingsRpc; } import {app_settings_rpc_context} from '@fuzdev/fuz_app/ui/app_settings_state.svelte.js';

Svelte context carrying the reactive AppSettingsRpc accessor. Mirrors admin_accounts_rpc_context. get() throws when no provisioner ran above the component — the adapter is required.

APP_SETTINGS_SCHEMA
#

auth/auth_ddl.ts view source

"\nCREATE TABLE IF NOT EXISTS app_settings (\n id INTEGER PRIMARY KEY DEFAULT 1 CHECK (id = 1),\n open_signup BOOLEAN NOT NULL DEFAULT false,\n updated_at TIMESTAMPTZ,\n updated_by UUID\n)" import {APP_SETTINGS_SCHEMA} from '@fuzdev/fuz_app/auth/auth_ddl.js';

APP_SETTINGS_SEED
#

auth/auth_ddl.ts view source

"\nINSERT INTO app_settings (id) VALUES (1) ON CONFLICT DO NOTHING" import {APP_SETTINGS_SEED} from '@fuzdev/fuz_app/auth/auth_ddl.js';

app_settings_update_action_spec
#

auth/admin_action_specs.ts view source

{ method: string; kind: "request_response"; initiator: "frontend"; auth: { account: "required"; actor: "required"; roles: string[]; }; side_effects: true; input: ZodObject<{ open_signup: ZodBoolean; acting: ZodOptional<...>; }, $strict>; output: ZodObject<...>; async: true; description: string; rate_limit: "account"... import {app_settings_update_action_spec} from '@fuzdev/fuz_app/auth/admin_action_specs.js';

AppBackend
#

server/app_backend.ts view source

AppBackend import type {AppBackend} from '@fuzdev/fuz_app/server/app_backend.js';

Result of create_app_backend() — database metadata + deps bundle.

This is the initialized backend, not the HTTP server. Pass it to create_app_server() to assemble the Hono app.

deps

type AppDeps

db_type

type DbType

db_name

type string

migration_results

Migration results from create_app_backend — auth migrations plus any consumer namespaces passed via migration_namespaces.

type readonly MigrationResult[]

readonly

close

Close the database connection. Bound to the actual driver.

type (): Promise<void>

returns Promise<void>

AppDeps
#

auth/deps.ts view source

AppDeps import type {AppDeps} from '@fuzdev/fuz_app/auth/deps.js';

Stateless capabilities bundle for fuz_app backends.

Injectable and swappable per environment (production vs test). Does not contain config (static values) or runtime state (mutable refs).

read_secure_file

Hardened secret-file read — used for the bootstrap token (the file that mints the keeper account). Production wiring passes the runtime's read_secure_file (FsSecureReadDeps), which rejects symlinks, non-regular files, group/other-accessible modes, and oversized files; both the boot-time availability probe and the request-time read go through this one capability so the probe can never be laxer than the read it gates.

type (path: string): Promise<Uint8Array<ArrayBufferLike>>

path

type string
returns Promise<Uint8Array<ArrayBufferLike>>

delete_file

Delete a file.

type (path: string): Promise<void>

path

type string
returns Promise<void>

keyring

HMAC-SHA256 cookie signing keyring.

type Keyring

password

Password hashing operations. Use argon2_password_deps in production.

type PasswordHashDeps

db

Database instance.

type Db

log

Structured logger instance.

type Logger

audit

Bound audit emitter. Closes over the pool, its registered listeners, and the optional AuditLogConfig. Built once at backend assembly via create_audit_emitter so handlers can never accidentally write audits against the request transaction — there is no pool slot on the handler context.

type AuditEmitter

connection_closer

Closes live connections — WebSocket and SSE — when a credential is revoked. Every revocation handler queues its close on this (queue_connection_close), so it must reach every transport of the backend: create_app_backend creates it empty, and create_app_server adds each WebSocket transport it mounts and its audit stream registry. A transport mounted by hand is added through the connection_closer option of register_ws_endpoint / create_audit_log_sse.

type RealtimeCloser

fact_store?

Optional content-addressed byte store. Present only on backends that serve binary content (facts) — minimal consumers leave it unset. The consumer constructs a PgFactStore (db/fact_store.ts) over its facts directory (disk_root + fs) at its own backend assembly and assigns it here; create_app_backend stays facts-agnostic.

type FactStore

apply_authorization_phase
#

auth/request_context.ts view source

(deps: QueryDeps, account_id: string | null, auth: { account: "optional" | "none" | "required"; actor: "optional" | "none" | "required"; roles?: readonly string[] | undefined; credential_types?: readonly string[] | undefined; required_scope?: string | undefined; }, acting_value: string | undefined): Promise<...> import {apply_authorization_phase} from '@fuzdev/fuz_app/auth/request_context.js';

Apply the dispatcher's authorization phase against the flat-record RouteAuth shape. Shared by the route-spec wrapper, the HTTP RPC dispatcher, and the per-message WS dispatcher. Phase order: pre-authorization 401 → authorization phase → post-authorization 403 → input validation 400.

Pure data — the function does not touch a Hono context. Each transport passes account_id (extracted from its own credential surface) and binds the returned AuthorizationResult to its wire shape. The REST pipeline additionally writes REQUEST_CONTEXT_KEY on c for downstream require_role / require_credential_types middleware that still reads the resolved context off the Hono context.

Branching by auth.account × auth.actor:

  • Both 'none' → {ok: true, request_context: null}. Public actions never see a RequestContext.
  • account_id == null on any non-public route → same null request_context. The 'required' callers were already rejected at the pre-authorization gate in the dispatcher; only genuine anonymous access on an 'optional' axis lands here.
  • actor === 'none' → builds account-only context via build_account_context. Null lookup → account_vanished 500 failure.
  • actor === 'required' → resolves the actor from acting_value (or single-actor account); failures map to 400 / 500.
  • actor === 'optional' → same as 'required' except multi-actor accounts without an acting value fall back to account-only context (no actor_required 400). Bad acting ids still 400.

500 branches stay distinct: ERROR_NO_ACTORS_ON_ACCOUNT (signup invariant violation), ERROR_ACCOUNT_VANISHED (torn read after resolve).

deps

account_id

type string | null

auth

acting_value

type string | undefined

returns

Promise<AuthorizationResult>

apply_middleware_specs
#

http/route_spec.ts view source

(app: Hono<BlankEnv, BlankSchema, "/">, specs: MiddlewareSpec[]): void import {apply_middleware_specs} from '@fuzdev/fuz_app/http/route_spec.js';

Apply named middleware specs to a Hono app.

app

type Hono<BlankEnv, BlankSchema, "/">

specs

returns

void

mutates

  • app

apply_route_specs
#

http/route_spec.ts view source

(app: Hono<BlankEnv, BlankSchema, "/">, specs: RouteSpec[], resolve_auth_guards: AuthGuardResolver, log: Logger, db: Db, authorize?: AuthorizationHandler | undefined): void import {apply_route_specs} from '@fuzdev/fuz_app/http/route_spec.js';

Apply route specs to a Hono app.

For each spec: resolves auth to guards via the provided resolver, adds input validation middleware (for routes with non-null input schemas), runs the optional authorization phase to resolve the acting actor + build the request context, wraps handler with DEV-only output and error validation, wraps with error catch layer (catches ThrownJsonrpcError and generic errors), and registers the route.

Per-route middleware order: content type (415, POST only) → params → query → pre-authorization auth guards (401 + rule-3 scope) → authorization phase → post-authorization auth guards (403) → input validation (400) → handler. The content type goes first because it says almost nothing about the route, and a forged request should be refused before any credential is read. Body validation runs behind every authority gate, so a caller any of them refuse never sees route-shape information from a parse failure — a 400 there would confirm the route exists and describe how to call it. Params and query still validate first (they address the route rather than carry its payload, and the authorization phase reads acting off the query on GETs).

Each handler receives a RouteContext with:

  • db: transaction-scoped when RouteSpec.transaction is true; pool-level otherwise
  • pending_effects: eager fire-and-forget pool-write queue
  • post_commit_effects: deferred-thunk queue (push via emit_after_commit)

Also enforces registry-time invariant 2 from the auth-shape design: auth.actor !== 'none' ⟺ input or query declares acting?: ActingActor. REST is bi-located (GETs declare acting on query, mutations on input), so the check passes both slots; the action-dispatcher registries (compile_action_registry) share the same helper with input only — ActionSpec has no query shape.

app

type Hono<BlankEnv, BlankSchema, "/">

specs

type RouteSpec[]

resolve_auth_guards

log

type Logger

db

used for transaction wrapping and RouteContext

type Db

authorize?

optional authorization phase; runs after the pre-authorization guards and before the post-authorization guards

optional

returns

void

throws

  • Error - if two specs share the same `method` + `path` (each combination must be unique), or if any spec violates the actor-acting biconditional

mutates

  • app

AppServer
#

server/app_server.ts view source

AppServer import type {AppServer} from '@fuzdev/fuz_app/server/app_server.js';

Result of create_app_server().

app

type Hono<BlankEnv, BlankSchema, "/">

surface_spec

Surface spec — serializable surface + raw specs that produced it.

type AppSurfaceSpec

bootstrap_status

type BootstrapStatus

migration_results

Migration results from create_app_backend (auth + any migration_namespaces passed there).

type readonly MigrationResult[]

audit_sse

Factory-managed audit log SSE. Non-null when the audit_log_sse option was passed to create_app_server, null when omitted. Use require_audit_sse(server) to assert the invariant.

type AuditLogSse | null

ws_endpoints

Path-keyed map of mounted WS endpoints. Each value is the BackendWebsocketTransport create_app_server registered connections against — supplied via WsEndpointSpec.transport or auto-created when omitted. Retain for broadcast / fan-out:

app_server.ws_endpoints['/api/ws'].send_to_account(account_id, msg);

Empty when no ws_endpoints were mounted.

type Readonly<Record<string, BackendWebsocketTransport>>

close

Shut the server's resources down, in order:

  1. stop the auth cleanup schedule when the auth_cleanup option started one, waiting for a pass in progress — that wait has no bound of its own, so a shutdown with a deadline races it
  2. close every live connection on deps.connection_closer (close_all_sockets) — each WebSocket with WS_CLOSE_GOING_AWAY, each audit stream — including transports supplied through WsEndpointSpec.transport or added by hand, since they are registered on the backend's closer and the backend is going away; a close that throws is logged and the shutdown goes on. Each transport and registry is left closing, so an upgrade or stream that registers afterward is born closed — refused at once rather than outliving the server — and a closed transport hosts no other server
  3. release what assembly acquired, last acquired first: remove the audit listeners create_app_server registered on deps.audit (the audit-log SSE listener, the WS auth guards, each extra_audit_handlers entry) and the closer members it added to deps.connection_closer (each mounted transport, the audit stream registry), and dispose the rate limiters it built for omitted options — never one passed in, which its caller owns. A release that throws is logged and the rest still run. The backend's emitter and closer are left as they were before assembly, so a failed assembly can be retried on the same backend, and a backend whose close you own (a test harness) can host another server
  4. close the database connection (AppBackend.close)

Idempotent: a second call settles with the first one's outcome (a failed close is not retried), and concurrent calls share one shutdown.

Stop accepting new HTTP connections first — without awaiting the listener's full drain, since live WebSockets hold it open until close ends them — so no new request reaches a closed database.

type (): Promise<void>

returns Promise<void>

AppServerContext
#

server/app_server_context.ts view source

AppServerContext import type {AppServerContext} from '@fuzdev/fuz_app/server/app_server_context.js';

Context passed to create_route_specs.

deps

type AppDeps

backend

type AppBackend

bootstrap_status

type BootstrapStatus

session_options

type SessionOptions<string>

login_ip_rate_limiter

Per-IP login + password-change rate limiter. One instance per auth surface — see AppServerOptions.login_ip_rate_limiter for why these aren't shared.

Each limiter here is what create_app_server resolved: the instance its option passed, else a default instance under rate_limiters: 'enforced' (the default). null when disabled — the option was an explicit null, or the mode is 'disabled_for_testing'.

type RateLimiter | null

signup_ip_rate_limiter

Per-IP signup rate limiter. null when disabled.

type RateLimiter | null

bootstrap_ip_rate_limiter

Per-IP bootstrap rate limiter. null when disabled.

type RateLimiter | null

login_account_rate_limiter

Per-account login rate limiter. null when disabled.

type RateLimiter | null

signup_account_rate_limiter

Per-account signup rate limiter. null when disabled.

type RateLimiter | null

action_ip_rate_limiter

Per-IP action-dispatcher rate limiter — shared across HTTP RPC + WS. null when disabled.

type RateLimiter | null

action_account_rate_limiter

Per-account action-dispatcher rate limiter, keyed on the authenticated account's id — shared across HTTP RPC + WS. null when disabled.

type RateLimiter | null

audit_sse

Factory-managed audit log SSE. Non-null when the audit_log_sse option was passed to create_app_server, null when omitted. Use require_audit_sse(ctx) to assert the invariant.

type AuditLogSse | null

AppServerOptions
#

server/app_server.ts view source

AppServerOptions import type {AppServerOptions} from '@fuzdev/fuz_app/server/app_server.js';

Configuration for create_app_server().

Requires a pre-initialized AppBackend from create_app_backend(). Two explicit steps: init backend then assemble server.

backend

Pre-initialized backend from create_app_backend().

type AppBackend

session_options

Session options for cookie-based auth.

type SessionOptions<string>

allowed_origins

Parsed allowed origin patterns.

type RegExp[]

origin_carve_outs?

Paths the origin check skips, each with its reason. The check covers every path by default (ORIGIN_MIDDLEWARE_PATH) — a request with no Origin passes it anyway, so probes and CLI clients need no carve-out; one is for a path that refuses browser callers by other means. Copied at assembly, so a later change to this array changes nothing; assembly throws on a carve-out with no reason, or one that overlaps the auth middleware's scope (AUTH_MIDDLEWARE_PATH) — every route that reads or mints a credential, and every RPC, WS, and bootstrap endpoint, sits there (assert_origin_carve_outs_valid).

type MiddlewareCarveOut[]

proxy

Trusted proxy options.

type { trusted_proxies: string[]; get_connection_ip: (c: Context<any, any, {}>) => string | undefined; }

rate_limiters?

What an omitted rate limiter becomes — 'enforced' (the default) builds a default instance for each *_rate_limiter option left out, 'disabled_for_testing' leaves each of them null. An explicit option (an instance or null) always wins over the mode. The disabled mode adds one surface warning, and prints a stderr banner once per module instance (announce_rate_limiters_disabled). Never 'disabled_for_testing' in production — see RateLimiterMode.

type RateLimiterMode

login_ip_rate_limiter?

Per-IP rate limiter for login + password change — the distributed-spray backstop. Omit or undefined to use a default limiter (5 attempts per 15 minutes) under rate_limiters: 'enforced', none under 'disabled_for_testing'. Pass null to explicitly disable rate limiting. Also available on AppServerContext for route factory callbacks.

One instance per auth surface, not one shared across all four. These buckets are monotone within their window — a success never refunds them (see RateLimiter.reset) — so a shared instance let a failure on any surface spend the budget that bounds guessing on every other one, and let one caller's exhaustion deny four routes at once. Pass the same limiter to two of these fields to opt back into a shared budget.

type RateLimiter | null

signup_ip_rate_limiter?

Per-IP rate limiter for signup. Omit or undefined for the mode's default (5 attempts per 15 minutes under rate_limiters: 'enforced'). Pass null to explicitly disable. Separate from login_ip_rate_limiter because an open-signup deployment lets any unauthenticated caller spend it. Also available on AppServerContext for route factory callbacks.

type RateLimiter | null

bootstrap_ip_rate_limiter?

Per-IP rate limiter for bootstrap. Omit or undefined for the mode's default (5 attempts per 15 minutes under rate_limiters: 'enforced'). Pass null to explicitly disable. Separate from login_ip_rate_limiter so a fumbled bootstrap token can't spend the operator's login budget. Wired directly into the factory-managed bootstrap route; also on AppServerContext for symmetry.

type RateLimiter | null

login_account_rate_limiter?

Per-account rate limiter for login attempts. Omit or undefined for the mode's default (10 attempts per 30 minutes under rate_limiters: 'enforced'). Pass null to explicitly disable. Also available on AppServerContext for route factory callbacks.

type RateLimiter | null

signup_account_rate_limiter?

Per-account rate limiter for signup attempts, keyed by submitted username. Omit or undefined for the mode's default (10 attempts per 30 minutes under rate_limiters: 'enforced'). Pass null to explicitly disable. Also available on AppServerContext for route factory callbacks.

type RateLimiter | null

action_ip_rate_limiter?

Per-IP rate limiter for the action dispatchers (HTTP RPC + WebSocket). Consulted for actions whose spec declares rate_limit: 'ip' or 'both'. Same limiter applies across transports — one budget per action. Omit or undefined for the mode's default (600 attempts per 15 minutes — permissive — under rate_limiters: 'enforced'). Pass null to explicitly disable. Threaded into every endpoint mounted from rpc_endpoints and ws_endpoints; also available on AppServerContext.

type RateLimiter | null

action_account_rate_limiter?

Per-account rate limiter for the action dispatchers (HTTP RPC + WebSocket). Consulted for actions whose spec declares rate_limit: 'account' or 'both'. Keyed on the authenticated account's id, so every actor and credential of one account shares a budget. Omit or undefined for the mode's default (1200 attempts per 15 minutes — permissive — under rate_limiters: 'enforced'). Pass null to explicitly disable. Threaded into every endpoint mounted from rpc_endpoints and ws_endpoints; also available on AppServerContext.

type RateLimiter | null

max_body_size?

Maximum allowed request body size in bytes. Omit or undefined to use the default (1 MiB). Pass null to explicitly disable body size limiting.

type number | null

daemon_token_state?

Daemon token state for keeper auth. Omit to disable.

type DaemonTokenState

bootstrap?

Bootstrap options. Omit to skip bootstrap status check and routes.

type BootstrapServerOptions

surface_route?

Mount GET /api/surface, which serves this server's generated AppSurface (every route, its auth, and its schemas) to an admin UI such as AdminSurface.svelte. Off by default — the Rust spine serves no such route, and a map of the API is nothing to publish unasked.

When mounted the route is admin-only (roles: ['admin'], so it takes the ?acting= selector) and a rule-3 token surface (required_scope: 'surface:app_surface'): a narrowed API token is refused it whatever its method list says. See server/surface_route.ts.

type boolean

default false

create_route_specs

Build route specs from the initialized backend. Called after all middleware is ready.

type (context: AppServerContext): RouteSpec[]

context

returns RouteSpec[]

transform_middleware?

Optional: transform middleware specs before applying.

Receives shallow copies of the trusted-proxy spec followed by the auth stack, in a fresh array — mutating them leaves the originals untouched. Add layers anywhere (before the proxy spec, between it and the auth stack, between the auth layers, or after), but keep each of those specs — its handler, not just its name — mounted once at its original path ('*' for the proxy and the origin check, AUTH_MIDDLEWARE_PATH for the rest of the auth stack) in its original order: the endpoint auth-scope check (assert_endpoint_in_auth_scope) and client-IP rate limiting (get_client_ip) assume it, so assembly throws (assert_middleware_stack_mounted) if the transform moves, drops, duplicates, reorders, wraps, or replaces one. The check pins where those layers sit, not what added layers do — a layer you add runs with full context access.

type (specs: MiddlewareSpec[]): MiddlewareSpec[]

specs

returns MiddlewareSpec[]

audit_log_sse?

Enable factory-managed audit log SSE.

When truthy, creates an AuditLogSse instance internally, registers the SSE listener via backend.deps.audit.add_listener (composing with the consumer's on_audit_event callback rather than rebuilding AppDeps), adds its registry to backend.deps.connection_closer so revocations close its streams, and auto-includes audit_log_event_specs in the surface. The result is exposed on AppServerContext (for route factories) and AppServer (for the caller), always typed as AuditLogSse | null — when this option is set, the field is non-null. Use require_audit_sse(ctx) to assert the invariant in route factories that depend on it.

Pass true for defaults (admin role), or {role: 'custom'} for a custom role. Omit to wire audit SSE manually.

type true | { role?: string | undefined; }

auth_cleanup?

Schedule the auth cleanup beside the server: start_auth_cleanup over backend.deps — a pass as assembly finishes, then one per interval — stopped by AppServer.close. Each pass deletes expired sessions and closes the connections they opened through deps.connection_closer (every WebSocket transport and audit stream this server mounts), and audits each newly expired role_grant offer once.

Off by default; a production server should turn it on. Without it nothing sweeps: expired session rows accumulate and an offer's expiry is never audited. Connections do not depend on it — each closes at its credential's deadline (auth/credential_deadline.ts), and the sweep's close repeats that one. The Rust spine's servers all schedule the twin (spawn_auth_cleanup).

Pass true for the default interval (DEFAULT_AUTH_CLEANUP_INTERVAL_MS), or {interval_ms} for another. Leave it off in an in-process test harness — a background pass deletes rows and writes audit rows mid-test — and in a process that runs run_auth_cleanup on a schedule of its own.

type boolean | AuthCleanupScheduleOptions

event_specs?

SSE event specs for surface generation. Defaults to [] (no SSE events).

type EventSpec[]

rpc_endpoints?

RPC endpoint specs — single source of truth for both surface generation *and* live dispatch. Each entry becomes route specs via create_rpc_endpoint, applied with the other routes, so consumers don't call create_rpc_endpoint themselves inside create_route_specs.

Accepts either an array (evaluated eagerly) or a factory (ctx: AppServerContext) => Array<RpcEndpointSpec> (evaluated after the server context is assembled). Use the factory form when action lists depend on ctx.deps — e.g. create_standard_rpc_actions(ctx.deps).

Each path must sit inside the auth middleware's scope (AUTH_MIDDLEWARE_PATH, '/api/*') — assembly throws otherwise, since an endpoint outside it would see every caller as unauthenticated.

type RpcEndpointSpec[] | ((context: AppServerContext) => RpcEndpointSpec[])

create_upgrade_websocket?

Returns the Hono runtime adapter's upgradeWebSocket helper for the assembled app. create_app_server calls it once, with the app it builds (the same instance as AppServer.app), after creating the app and before mounting ws_endpoints — and only when ws_endpoints resolves non-empty, so it is never called for a server with no WebSocket endpoints. Required in that case: assembly throws without it. One helper services every mounted endpoint.

A factory rather than the helper itself because Node's adapter needs the app: @hono/node-ws has no module-level helper, only createNodeWebSocket({app}), and create_app_server is what builds the app. Deno and Bun ignore the argument:

import {upgradeWebSocket} from '@hono/deno'; // or '@hono/bun' create_upgrade_websocket: () => upgradeWebSocket,

Node keeps the adapter so it can attach to the server after serve():

let node_ws: NodeWebSocket | undefined; const server = await create_app_server({ ..., create_upgrade_websocket: (app) => (node_ws = createNodeWebSocket({app})).upgradeWebSocket, }); node_ws?.injectWebSocket(serve({fetch: server.app.fetch, port}));

Test harnesses pass () => stub.upgradeWebSocket with create_stub_upgrade from testing/ws_round_trip.ts.

type (app: Hono<BlankEnv, BlankSchema, "/">): UpgradeWebSocket<unknown, any, WSEvents<unknown>>

app

type Hono<BlankEnv, BlankSchema, "/">
returns UpgradeWebSocket<unknown, any, WSEvents<unknown>>

ws_endpoints?

WebSocket endpoint specs — single source of truth for both surface generation *and* live dispatch. Each entry is auto-mounted via register_ws_endpoint against the assembled Hono app on every runtime, so consumers don't call register_ws_endpoint themselves.

Accepts either an array (evaluated eagerly) or a factory (ctx: AppServerContext) => ReadonlyArray<WsEndpointSpec> (evaluated after the server context is assembled). Use the factory form when action lists depend on ctx.deps — e.g. when spreading create_standard_rpc_actions({...ctx.deps, notification_sender}) over WS.

When non-empty, create_upgrade_websocket must be supplied (throws otherwise). A factory returning [] does NOT trip the check — feature-flag gated WS surfaces stay safe.

Every mount gets what the HTTP surface gets: the auth middleware (a path outside AUTH_MIDDLEWARE_PATH throws at assembly, like rpc_endpoints), the server's allowed_origins unless the spec narrows them, and the same action_ip_rate_limiter / action_account_rate_limiter instances the RPC endpoints charge — one budget per action across both transports. The upgrade routes register after the route specs and before post_route_middleware and static_serving, so neither runs ahead of an upgrade.

Duplicate path values across two WsEndpointSpecs, and a path a GET RouteSpec already claims, throw at assembly before any endpoint mounts (Hono would silently shadow them otherwise).

Every mounted transport is added to deps.connection_closer, so the revocation handlers' post-commit closes reach its sockets — unconditionally, since those closes are what ends a revoked credential's connections. AppServer.close removes it again.

Each spec's auth_guard? defaults to true — the factory registers create_ws_auth_guard against the mounted transport via deps.audit.add_listener, repeating the close on the audit row. Wiring is deduped by transport reference identity so two specs sharing one BackendWebsocketTransport instance get a single listener; wrapped / proxied transports dedupe as separate entries (set auth_guard: false on duplicates and compose against the underlying transport once).

type readonly WsEndpointSpec[] | ((context: AppServerContext) => readonly WsEndpointSpec[])

env_schema?

Env schema for surface generation. Defaults to BaseServerEnv — pass an extended schema (typically BaseServerEnv.extend({...})) when the consumer adds app-specific env vars.

type ZodObject<$ZodLooseShape, $strip>

post_route_middleware?

Middleware applied after routes, before static serving. Included in surface.

type MiddlewareSpec[]

static_serving?

Static file serving. Omit if not serving static files.

type { serve_static: ServeStaticFactory; root?: string | undefined; spa_fallback?: string | undefined; is_spa_route?: ((path: string) => boolean) | undefined; }

await_pending_effects?

Await all pending fire-and-forget effects before returning the response. Use in tests so audit log assertions don't need polling. Default false (production: true fire-and-forget).

type boolean

on_effect_error?

Called when a pending effect rejects. Use for monitoring, metrics, or alerting in production. Only called when await_pending_effects is false (production mode).

type (error: unknown, context: EffectErrorContext): void

error

type unknown

context

returns void

env_values?

Env values for startup summary logging.

type Record<string, unknown>

AppServerRateLimiterKey
#

server/app_server.ts view source

internal

"login_ip_rate_limiter" | "signup_ip_rate_limiter" | "bootstrap_ip_rate_limiter" | "login_account_rate_limiter" | "signup_account_rate_limiter" | "action_ip_rate_limiter" | "action_account_rate_limiter"

type "login_ip_rate_limiter" | "signup_ip_rate_limiter" | "bootstrap_ip_rate_limiter" | "login_account_rate_limiter" | "signup_account_rate_limiter" | "action_ip_rate_limiter" | "action_account_rate_limiter"

import type {AppServerRateLimiterKey} from '@fuzdev/fuz_app/server/app_server.js';

Name of an AppServerOptions rate-limiter field — also the AppServerContext field its resolved limiter lands on.

AppSettings
#

auth/app_settings_schema.ts view source

AppSettings import type {AppSettings} from '@fuzdev/fuz_app/auth/app_settings_schema.js';

App settings row from the database.

open_signup

type boolean

updated_at

type string | null

updated_by

type Uuid | null

AppSettingsCrossTestOptions
#

testing/cross_backend/app_settings.ts view source

RpcPathCrossSuiteOptions import type {AppSettingsCrossTestOptions} from '@fuzdev/fuz_app/testing/cross_backend/app_settings.js';

Options for the app-settings effect suite (the standard RPC-dispatched shape).

rpc_path?

RPC endpoint path the methods are mounted on. Default /api/rpc.

type string

readonly

setup_test

Per-test fixture-producing function (fresh keeper + db per call).

type (): Promise<TestFixtureBase>

readonly
returns Promise<TestFixtureBase>

AppSettingsGetInput
#

auth/admin_action_specs.ts view source

value + type

{ acting?: (string & $brand<"Uuid">) | undefined; } import {AppSettingsGetInput} from '@fuzdev/fuz_app/auth/admin_action_specs.js';

Input for app_settings_get.

acting?

type string & $brand<"Uuid">

AppSettingsGetOutput
#

auth/admin_action_specs.ts view source

value + type

{ settings: { open_signup: boolean; updated_at: string | null; updated_by: (string & $brand<"Uuid">) | null; updated_by_username: string | null; }; } import {AppSettingsGetOutput} from '@fuzdev/fuz_app/auth/admin_action_specs.js';

Output for app_settings_get.

settings

type AppSettingsWithUsernameJson

AppSettingsJson
#

auth/app_settings_schema.ts view source

value + type

{ open_signup: boolean; updated_at: string | null; updated_by: (string & $brand<"Uuid">) | null; } import {AppSettingsJson} from '@fuzdev/fuz_app/auth/app_settings_schema.js';

Zod schema for client-safe app settings data.

open_signup

type boolean

updated_at

type string | null

updated_by

type (string & $brand<"Uuid">) | null

AppSettingsRpc
#

ui/app_settings_state.svelte.ts view source

AppSettingsRpc import type {AppSettingsRpc} from '@fuzdev/fuz_app/ui/app_settings_state.svelte.js';

Narrow RPC surface consumed by AppSettingsState. Consumers adapt their typed RPC client to this shape. Method signatures track the wire spec inputs/outputs directly so the adapter needs no casts.

get

type (): Promise<{ settings: { open_signup: boolean; updated_at: string | null; updated_by: (string & $brand<"Uuid">) | null; updated_by_username: string | null; }; }>

returns Promise<AppSettingsGetOutput>

update

type (params: { open_signup: boolean; acting?: (string & $brand<"Uuid">) | undefined; }): Promise<{ ok: true; settings: { open_signup: boolean; updated_at: string | null; updated_by: (string & $brand<...>) | null; updated_by_username: string | null; }; }>

params

returns Promise<AppSettingsUpdateOutput>

AppSettingsState
#

ui/app_settings_state.svelte.ts view source

import {AppSettingsState} from '@fuzdev/fuz_app/ui/app_settings_state.svelte.js';

list

type AsyncSlot<void, string>

readonly

update

type AsyncSlot<void, string>

readonly

settings

type AppSettingsWithUsernameJson | null

$state.raw

constructor

type new (options: AppSettingsStateOptions): AppSettingsState

options

fetch

type (): Promise<void>

returns Promise<void>

update_open_signup

type (value: boolean): Promise<void>

value

type boolean
returns Promise<void>

AppSettingsStateOptions
#

AppSettingsUpdateInput
#

auth/admin_action_specs.ts view source

value + type

{ open_signup: boolean; acting?: (string & $brand<"Uuid">) | undefined; } import {AppSettingsUpdateInput} from '@fuzdev/fuz_app/auth/admin_action_specs.js';

Input for app_settings_update.

open_signup

type boolean

acting?

type string & $brand<"Uuid">

AppSettingsUpdateOutput
#

auth/admin_action_specs.ts view source

value + type

{ ok: true; settings: { open_signup: boolean; updated_at: string | null; updated_by: (string & $brand<"Uuid">) | null; updated_by_username: string | null; }; } import {AppSettingsUpdateOutput} from '@fuzdev/fuz_app/auth/admin_action_specs.js';

Output for app_settings_update.

ok

type true

settings

type AppSettingsWithUsernameJson

AppSettingsWithUsernameJson
#

auth/app_settings_schema.ts view source

value + type

{ open_signup: boolean; updated_at: string | null; updated_by: (string & $brand<"Uuid">) | null; updated_by_username: string | null; } import {AppSettingsWithUsernameJson} from '@fuzdev/fuz_app/auth/app_settings_schema.js';

Zod schema for admin app settings with resolved updater username.

open_signup

type boolean

updated_at

type string | null

updated_by

type (string & $brand<"Uuid">) | null

updated_by_username

type string | null

AppShell
#

ui/AppShell.svelte view source

accepts children

import AppShell from '@fuzdev/fuz_app/ui/AppShell.svelte';

children

type Snippet<[]>

sidebar

type Snippet<[]>

sidebar_width?

Sidebar width in pixels when shown; in narrow mode capped at 85vw.

type number
optional default 180

sidebar_label?

Accessible name for the sidebar while it's the narrow-mode overlay, announced as a modal dialog.

type string
optional default 'sidebar'

sidebar_state?

Optional pre-built SidebarState for sharing visibility across shells.

optional

keyboard_shortcut?

Single-key shortcut that toggles the sidebar (e.g. 'b') while it's enabled and no modal is open — the persistent sidebar in wide mode, the overlay in narrow mode. false disables.

type string | false
optional default false

show_toggle?

Whether to render the built-in (or custom) toggle button. It renders only while the sidebar is enabled either way.

type boolean
optional default true

toggle_button?

Custom toggle-button renderer; receives the title, visibility, toggle callback, and the sidebar element's id. Give the button class="app-shell-toggle" so it stacks above the sidebar and scrim (it renders as a direct child of the root) — the z-index applies only to a positioned button, so position it (e.g. position: fixed) — and aria-controls={sidebar_id} so closing can return focus to it. show_sidebar is viewport-dependent: the server renders it as in wide mode, so rendering it as visible content changes on hydration at narrow widths — attributes are repaired, but key visible content on CSS or the viewport-free fields instead.

type Snippet<[{ title: string; show_sidebar: boolean; toggle: () => void; sidebar_id: string; }]>
optional
snippet parameters
arg0 { title: string; show_sidebar: boolean; toggle: () => void; sidebar_id: string; }

class?

type ClassValue | null
optional

external types

SvelteHTMLElements['div']

AppSurface
#

AppSurfaceDiagnostic
#

http/surface.ts view source

AppSurfaceDiagnostic import type {AppSurfaceDiagnostic} from '@fuzdev/fuz_app/http/surface.js';

Assembly-time diagnostic collected during surface generation or server assembly.

level

type "warning" | "info"

category

type string

message

type string

source?

type string

AppSurfaceEnv
#

http/surface.ts view source

AppSurfaceEnv import type {AppSurfaceEnv} from '@fuzdev/fuz_app/http/surface.js';

An env var in the generated attack surface (JSON-serializable).

name

type string

description

type string

sensitivity

Sensitivity level from .meta({sensitivity}). null when not sensitive.

type "secret" | null

has_default

type boolean

optional

type boolean

AppSurfaceEvent
#

http/surface.ts view source

AppSurfaceEvent import type {AppSurfaceEvent} from '@fuzdev/fuz_app/http/surface.js';

An SSE event in the generated attack surface (JSON-serializable).

method

type string

description

type string

channel

type string | null

params_schema

type unknown

AppSurfaceMiddleware
#

http/surface.ts view source

AppSurfaceMiddleware import type {AppSurfaceMiddleware} from '@fuzdev/fuz_app/http/surface.js';

A middleware in the generated attack surface (JSON-serializable).

name

type string

path

type string

error_schemas

JSON Schema representations of error responses, keyed by HTTP status code. null when none.

type Record<string, unknown> | null

carve_outs?

Paths under path the layer does not run on, each with its reason. Absent when none.

type { path: string; reason: string; }[]

AppSurfaceRoute
#

http/surface.ts view source

AppSurfaceRoute import type {AppSurfaceRoute} from '@fuzdev/fuz_app/http/surface.js';

A route in the generated attack surface (JSON-serializable).

method

type string

path

type string

auth

type RouteAuth

applicable_middleware

type string[]

description

type string

is_mutation

Whether this route mutates state (POST, PUT, DELETE, PATCH).

type boolean

transaction

Whether this route's handler runs inside a database transaction.

type boolean

raw_body

Whether this route carries raw bytes / a streaming protocol rather than JSON (see RouteSpec.raw_body). When true, input_schema / output_schema being null means "raw bytes", not "no body".

type boolean

rate_limit_key

Rate limit key type declared on the route spec. null when not rate-limited.

type "both" | "account" | "ip" | null

params_schema

JSON Schema representation of the URL path params schema. null when no params.

type unknown

query_schema

JSON Schema representation of the URL query params schema. null when no query schema.

type unknown

input_schema

JSON Schema representation of the request body schema. null for no-body routes.

type unknown

output_schema

JSON Schema representation of the success response schema.

type unknown

error_schemas

JSON Schema representations of error responses, keyed by HTTP status code. null when none.

type Record<string, unknown> | null

AppSurfaceRpcEndpoint
#

http/surface.ts view source

AppSurfaceRpcEndpoint import type {AppSurfaceRpcEndpoint} from '@fuzdev/fuz_app/http/surface.js';

An RPC endpoint in the generated attack surface (JSON-serializable).

path

type string

methods

type AppSurfaceRpcMethod[]

AppSurfaceRpcMethod
#

http/surface.ts view source

AppSurfaceRpcMethod import type {AppSurfaceRpcMethod} from '@fuzdev/fuz_app/http/surface.js';

A method within an RPC endpoint in the generated attack surface (JSON-serializable).

name

type string

auth

type RouteAuth

input_schema

JSON Schema representation of the input schema. null for null-input methods.

type unknown

output_schema

JSON Schema representation of the output schema.

type unknown

side_effects

type boolean

description

type string

rate_limit_key

Rate limit key declared on the action spec. null when not rate-limited.

type "both" | "account" | "ip" | null

AppSurfaceSpec
#

http/surface.ts view source

AppSurfaceSpec import type {AppSurfaceSpec} from '@fuzdev/fuz_app/http/surface.js';

The surface bundled with the source specs that produced it.

AppSurface is JSON-serializable (snapshots, UI, startup logging) — it's the observability layer, written to disk by gro gen for human inspection + drift detection.

AppSurfaceSpec is runtime-only — tests, introspection, attack surface assertions. Both in-process and cross-process tests construct an AppSurfaceSpec in TS via create_test_app_surface_spec (or a consumer equivalent); the cross-process-ness lives in the transport + per-test fixture, not the schema source.

surface

type AppSurface

route_specs

type RouteSpec[]

middleware_specs

type MiddlewareSpec[]

rpc_endpoints

type RpcEndpointSpec[]

ws_endpoints

type ResolvedWsEndpointSpec[]

AppSurfaceWsEndpoint
#

http/surface.ts view source

AppSurfaceWsEndpoint import type {AppSurfaceWsEndpoint} from '@fuzdev/fuz_app/http/surface.js';

A WebSocket endpoint in the generated attack surface (JSON-serializable).

path

type string

allowed_origins

Upgrade-time origin allowlist — the spec's own allowed_origins, or the server's when it declares none — one entry per regex, stringified via RegExp.prototype.toString() ('/<source>/<flags>'). An empty array refuses every request that carries an Origin. Reviewers read this as the exact pattern matched at the upgrade gate, not a wildcard approximation. Reconstruct via new RegExp(source, flags) if needed.

type readonly string[]

required_roles

Upgrade-time role gate — empty array when no required_roles was declared (any-authenticated). Documents the coarse gate; per-action auth on each method covers per-message authorization.

type readonly string[]

methods

type AppSurfaceWsMethod[]

AppSurfaceWsMethod
#

http/surface.ts view source

AppSurfaceWsMethod import type {AppSurfaceWsMethod} from '@fuzdev/fuz_app/http/surface.js';

A method within a WebSocket endpoint in the generated attack surface (JSON-serializable).

name

type string

kind

request_response (inbound dispatch) or remote_notification (server → client).

type ActionKind

auth

Per-action auth shape. null for remote_notification (server → client) — notifications have no inbound dispatch and therefore no auth axis. request_response always carries a RouteAuth.

type RouteAuth | null

input_schema

JSON Schema of the input schema. null for nullary inputs.

type unknown

output_schema

JSON Schema of the output schema.

type unknown

description

type string

side_effects

type boolean

rate_limit_key

Rate limit key declared on the action spec. null when not rate-limited.

type "both" | "account" | "ip" | null

argon2_password_deps
#

assert_404_schemas_use_specific_errors
#

testing/surface_invariants.ts view source

(surface: AppSurface): void import {assert_404_schemas_use_specific_errors} from '@fuzdev/fuz_app/testing/surface_invariants.js';

Routes declaring 404 error schemas should use specific z.literal() or z.enum() error codes, not generic z.string().

A generic 404 schema (ApiError with z.string()) means the error code is unconstrained — the handler could return any string, making client error handling fragile. Routes with params (:id) are the primary 404 producers; their error schemas should use specific constants like ERROR_ACCOUNT_NOT_FOUND.

Only flags routes that have params_schema (param-driven resource lookup) — routes declaring 404 for other reasons (e.g., bootstrap not configured) may legitimately use generic schemas.

surface

returns

void

assert_action_manifests_equal
#

testing/cross_backend/action_manifest_parity.ts view source

(a: { methods: { method: string; side_effects: boolean; account: "optional" | "none" | "required"; actor: "optional" | "none" | "required"; roles: string[]; credential_types: string[]; rate_limit: "both" | ... 2 more ... | null; }[]; }, b: { ...; }, labels?: ActionManifestDiffLabels): void import {assert_action_manifests_equal} from '@fuzdev/fuz_app/testing/cross_backend/action_manifest_parity.js';

Throw if the two manifests disagree. The error message names the impls (via labels) and lists every diff, so the failure is self-diagnosing.

a

b

labels

default {}

returns

void

assert_close_call
#

testing/connection_closer_helpers.ts view source

(call: RecordedClose | undefined, method: "all" | "account" | "token" | "role" | "session", id: string | null): void import {assert_close_call} from '@fuzdev/fuz_app/testing/connection_closer_helpers.js';

Pin {method, id} on a single recorded close call.

Throws via assert.ok if call is undefined — index a recorded calls array directly (calls[0]) and let this helper handle the missing-element case.

call

type RecordedClose | undefined

method

type "all" | "account" | "token" | "role" | "session"

id

type string | null

returns

void

assert_columns_match_live
#

testing/db.ts view source

(db: Db, table: string, columns: readonly string[]): Promise<void> import {assert_columns_match_live} from '@fuzdev/fuz_app/testing/db.js';

Assert a *_COLUMNS projection const names exactly the live columns of table — the drift guard for an exported const (ACCOUNT_COLUMNS, CELL_COLUMNS, a consumer's own).

This is the reverse of the fail-loud read: a named projection makes a *dropped* column fail loud at query time, but a column *added* to the table and not to the projection would silently vanish from every row read. Both directions are covered by the equality here. It guards one const; it can't see a table that has *no* const — for that, keep a table → const registry and assert its key set against query_public_columns (fuz_app's own is src/test/db/column_projections.db.test.ts).

db

a bootstrapped test database

type Db

table

the public-schema table the const projects

type string

columns

the projection const's column names

type readonly string[]

returns

Promise<void>

assert_credential_routes_in_auth_scope
#

auth/middleware.ts view source

(route_specs: readonly RouteSpec[]): void import {assert_credential_routes_in_auth_scope} from '@fuzdev/fuz_app/auth/middleware.js';

Throw when a route that reads or mints a credential sits outside the auth middleware's scope (AUTH_MIDDLEWARE_PATH). A route reads one when its auth.account is not 'none', or when it is marked reads_credential (a handler that reads the resolved account behind no gate: account status) — outside the scope no session or bearer parsing runs, so it would answer every caller as unauthenticated. A route mints one when it is marked mints_credential (login, signup, bootstrap) — outside the scope an origin carve-out could cover it (assert_origin_carve_outs_valid refuses one only inside), and a forged cross-site request to it is login CSRF.

Account and signup route specs are relative and the consumer prefixes them, so this is what holds them inside the scope whatever prefix they get. create_app_server runs it over every route it registers, consumer and factory alike, and create_test_app_surface_spec does the same.

route_specs

every route the server registers

type readonly RouteSpec[]

returns

void

throws

  • Error - naming the first route that reads or mints a credential outside `AUTH_MIDDLEWARE_PATH`

assert_descriptions_present
#

assert_endpoint_in_auth_scope
#

auth/middleware.ts view source

(option: "rpc_endpoints" | "ws_endpoints" | "bootstrap.route_prefix", path: string): void import {assert_endpoint_in_auth_scope} from '@fuzdev/fuz_app/auth/middleware.js';

Throw when an endpoint path sits outside the auth middleware's scope (AUTH_MIDDLEWARE_PATH) — there it would get no session or bearer parsing, so every call or upgrade would be unauthenticated. middleware_applies matches the way Hono matches the middleware's pattern.

create_app_server calls it for every rpc_endpoints and ws_endpoints path and the bootstrap route at assembly, and create_test_app_surface_spec does the same, so a surface snapshot never records an endpoint the real server refuses.

option

the option the path came from, named in the error

type "rpc_endpoints" | "ws_endpoints" | "bootstrap.route_prefix"

path

the endpoint path to check

type string

returns

void

throws

  • Error - when `path` is outside `AUTH_MIDDLEWARE_PATH`

assert_error_code_status_consistency
#

testing/surface_invariants.ts view source

(surface: AppSurface): void import {assert_error_code_status_consistency} from '@fuzdev/fuz_app/testing/surface_invariants.js';

The same z.literal() error code should not appear at different HTTP status codes across routes.

Extracts const values from error schema error properties (which correspond to z.literal() in the Zod source). Walks union branches (anyOf from z.union, oneOf from z.discriminatedUnion) so literal codes nested inside merged unions (e.g. validation 400 + actor-resolution 400) are still tracked. Flags when the same literal appears at different status codes — e.g., ERROR_INVALID_CREDENTIALS at both 401 and 403 would be a bug.

Only checks const values (literal schemas). Generic z.string() schemas (which produce {type: 'string'}) and z.enum() schemas are ignored — the literal-only narrow keeps the check unambiguous.

surface

returns

void

assert_error_coverage
#

testing/error_coverage.ts view source

(collector: ErrorCoverageCollector, route_specs: RouteSpec[], options?: ErrorCoverageOptions | undefined): void import {assert_error_coverage} from '@fuzdev/fuz_app/testing/error_coverage.js';

Assert error coverage meets a minimum threshold.

Computes the ratio of exercised error paths to total declared error paths. For routes whose status error schema names specific codes (z.literal or z.enum), each declared code counts as one coverage path; for schemas without declared codes (ApiError/z.string()), the status counts as one path. A status-only observation covers all declared codes for that status (the "any-code" rule).

When min_coverage is 0 (default), logs coverage info without failing. When > 0, fails if coverage is below the threshold.

collector

route_specs

type RouteSpec[]

options?

optional

returns

void

throws

  • AssertionError - if `min_coverage > 0` and the covered/total ratio falls below the threshold — the failure message lists every uncovered route + status (+ code).

assert_error_schema_tightness
#

testing/surface_invariants.ts view source

(surface: AppSurface, options?: ErrorSchemaTightnessOptions | undefined): void import {assert_error_schema_tightness} from '@fuzdev/fuz_app/testing/surface_invariants.js';

Assert that all error schemas meet a minimum specificity threshold.

Calls audit_error_schema_tightness and fails on any entry below the configured threshold. Use allowlist and ignore_statuses to exclude known exceptions during progressive tightening.

surface

options?

optional

returns

void

throws

  • AssertionError - listing every route × status combination whose error schema specificity is below `min_specificity` (default `'enum'`) and is not in `allowlist` or `ignore_statuses`.

assert_error_schema_valid
#

testing/assertions.ts view source

(lookup: Map<string, Partial<Record<number, ZodType<unknown, unknown, $ZodTypeInternals<unknown, unknown>>>>>, route: AppSurfaceRoute, status: number, body: unknown): void import {assert_error_schema_valid} from '@fuzdev/fuz_app/testing/assertions.js';

Assert that an error schema exists for a route+status and validate the body against it.

Protected routes should always have auto-derived error schemas (401 for authenticated, 403 for role-restricted). A missing schema indicates a gap in error schema derivation.

lookup

map from "METHOD /path" to merged error schemas

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

route

the surface route to validate against

status

expected HTTP status code

type number

body

the parsed response body to validate

type unknown

returns

void

throws

  • AssertionError - if no schema is declared for the route+status pair.
  • ZodError - if the body does not satisfy the declared schema.

assert_error_schemas_structurally_valid
#

testing/surface_invariants.ts view source

(surface: AppSurface): void import {assert_error_schemas_structurally_valid} from '@fuzdev/fuz_app/testing/surface_invariants.js';

Every route's declared error schemas must have an error field at the top level (conforming to the ApiError base shape {error: string}).

Walks union branches (anyOf from z.union, oneOf from z.discriminatedUnion) so every emit shape inside a merged 400 / 404 is checked, not just the top-level wrapper.

Catches typos in error schema definitions and ensures consumers can always read .error from error responses.

surface

returns

void

assert_expected_headers
#

testing/cross_backend/conformance_table.ts view source

(headers: Record<string, string>, expected: Record<string, string | null>, label: string): void import {assert_expected_headers} from '@fuzdev/fuz_app/testing/cross_backend/conformance_table.js';

Assert each declared header expectation: a string value must be present and equal (header name matched case-insensitively), null must be absent. The negative-space twin for headers — expect.headers pins a header beyond the always-on no-fingerprint floor.

headers

type Record<string, string>

expected

type Record<string, string | null>

label

type string

returns

void

assert_full_middleware_stack
#

testing/assertions.ts view source

(surface: AppSurface, path_prefix: string, expected_middleware: string[]): void import {assert_full_middleware_stack} from '@fuzdev/fuz_app/testing/assertions.js';

Verify every route under a path prefix has the exact expected middleware stack.

surface

the app surface to check

path_prefix

prefix to filter routes (e.g. '/api/')

type string

expected_middleware

the exact middleware names in order

type string[]

returns

void

throws

  • AssertionError - if no routes match `path_prefix`, or if any matching route's `applicable_middleware` does not deep-equal `expected_middleware`.

assert_input_routes_declare_400
#

testing/surface_invariants.ts view source

(surface: AppSurface): void import {assert_input_routes_declare_400} from '@fuzdev/fuz_app/testing/surface_invariants.js';

Every route with non-null input_schema has 400 in error_schemas.

surface

returns

void

assert_iso8601_seconds
#

testing/cross_backend/wire_shapes.ts view source

(value: unknown, label: string): void import {assert_iso8601_seconds} from '@fuzdev/fuz_app/testing/cross_backend/wire_shapes.js';

Assert value is a timestamp in the spine's canonical wire shape: second-precision UTC, exactly 20 characters. The TS spine emits it from the iso8601_timestamp_column SQL projection (db/sql_columns.ts) and to_iso8601_seconds (timestamp.ts); the Rust spine from fuz_db::iso8601_timestamp_column and fuz_sys::rfc3339_now.

value

the field read off a wire response

type unknown

label

the field's name, for the failure message

type string

returns

void

assert_iso8601_seconds_nullable
#

testing/cross_backend/wire_shapes.ts view source

(value: unknown, label: string): void import {assert_iso8601_seconds_nullable} from '@fuzdev/fuz_app/testing/cross_backend/wire_shapes.js';

Assert value is either null or a canonical second-precision UTC timestamp — the shape of every nullable timestamp on the wire (expires_at, deleted_at, updated_at, …).

value

the field read off a wire response

type unknown

label

the field's name, for the failure message

type string

returns

void

assert_jsonrpc_error_response
#

testing/rpc_helpers.ts view source

(body: unknown, expected_code?: -32700 | -32600 | -32601 | -32602 | -32603 | (number & $brand<"JsonrpcServerErrorCode">) | undefined): void import {assert_jsonrpc_error_response} from '@fuzdev/fuz_app/testing/rpc_helpers.js';

Assert that a response body is a valid JSON-RPC error response.

Validates the structure matches JsonrpcErrorResponse and optionally checks the error code.

body

type unknown

expected_code?

optional error code to assert

optional

returns

void

assert_jsonrpc_success_response
#

testing/rpc_helpers.ts view source

(body: unknown, output_schema?: ZodType<unknown, unknown, $ZodTypeInternals<unknown, unknown>> | undefined): void import {assert_jsonrpc_success_response} from '@fuzdev/fuz_app/testing/rpc_helpers.js';

Assert that a response body is a valid JSON-RPC success response.

Validates the structure matches JsonrpcResponse. When output_schema is provided, also validates the result field against the declared output schema — matching the REST round-trip's assert_response_matches_spec.

body

type unknown

output_schema?

optional Zod schema to validate the result field against

type ZodType<unknown, unknown, $ZodTypeInternals<unknown, unknown>>
optional

returns

void

assert_keeper_routes_under_prefix
#

testing/surface_invariants.ts view source

(surface: AppSurface, prefixes?: string[]): void import {assert_keeper_routes_under_prefix} from '@fuzdev/fuz_app/testing/surface_invariants.js';

Keeper-protected routes must be under expected path prefixes.

Catches keeper routes accidentally placed outside the API namespace (e.g., a keeper route at /health or /admin/ instead of /api/...).

surface

prefixes

type string[]
default DEFAULT_KEEPER_ROUTE_PREFIXES

returns

void

assert_middleware_errors_propagated
#

testing/surface_invariants.ts view source

(surface: AppSurface): void import {assert_middleware_errors_propagated} from '@fuzdev/fuz_app/testing/surface_invariants.js';

Every applicable middleware that declares errors must have those status codes present in the route's error_schemas.

surface

returns

void

assert_middleware_stack_mounted
#

auth/middleware.ts view source

(stack_specs: readonly MiddlewareSpec[], middleware_specs: readonly MiddlewareSpec[]): void import {assert_middleware_stack_mounted} from '@fuzdev/fuz_app/auth/middleware.js';

Throw unless a middleware list still mounts every spec of the stack it was built from — the trusted-proxy spec and the auth middleware — exactly once, at its original path, in its original order.

create_app_server and create_test_app_surface_spec run it on the list a transform_middleware option returns, against the stack they handed it ([proxy, ...auth]). The endpoint auth-scope check (assert_endpoint_in_auth_scope) assumes the auth stack is mounted at AUTH_MIDDLEWARE_PATH, and every IP-keyed rate limiter assumes the proxy spec ran first — without it get_client_ip answers 'unknown' for every caller, collapsing each limiter into one shared bucket. So a transform that moves, drops, duplicates, reorders, wraps, or replaces a stack layer is refused. Other specs may sit anywhere, including before the proxy spec and between the auth layers. The check pins where the stack's layers sit, not what added layers do — a layer you add runs with full context access.

Each stack spec is matched by its handler — the closure its factory built — not by name, which any spec can carry, so a same-named replacement or a wrapped handler counts as missing. A spread copy ({...spec}) keeps the handler and still matches. Its path is compared with the original's, so pass specs the transform could not reach: the assemblies hand the transform shallow copies in a fresh array and keep the originals here.

stack_specs

the stack the transform was built from, in mount order

type readonly MiddlewareSpec[]

middleware_specs

the middleware list the transform returned

type readonly MiddlewareSpec[]

returns

void

throws

  • Error - when a stack spec is missing, mounted more than once, mounted at another path, or out of order

assert_migration_trackers_equal
#

testing/schema_parity.ts view source

(a: { entries: { namespace: string; name: string; sequence: number; }[]; }, b: { entries: { namespace: string; name: string; sequence: number; }[]; }, labels?: SchemaDiffLabels): void import {assert_migration_trackers_equal} from '@fuzdev/fuz_app/testing/schema_parity.js';

Throw if the two spines' schema_version trackers disagree — the gate for the swap-freely invariant (any consumer can swap TS↔Rust over one DB without re-bootstrapping). This catches what assert_schema_snapshots_equal is blind to by design: the snapshot excludes the tracker, so a migration-name or partitioning divergence that yields an identical *schema* (e.g. cell_v0 vs full_cell_schema, or cell_history bundled vs isolated) passes schema parity but breaks the runner's positional name-prefix check at boot (name-divergence-at-N). The error names the impls and lists every diff.

a

b

labels

default {}

returns

void

assert_mutation_routes_use_post
#

testing/surface_invariants.ts view source

(surface: AppSurface): void import {assert_mutation_routes_use_post} from '@fuzdev/fuz_app/testing/surface_invariants.js';

Routes with non-null input schemas should use POST (or other mutation methods), not GET.

GET routes with request bodies are technically allowed by HTTP but semantically suspicious — they bypass browser security assumptions about GET being idempotent. Query-string-driven filtering (audit log, list endpoints) should use params schemas or query string parsing, not input schemas.

Note: RPC endpoints (create_rpc_endpoint) use input: z.null() on their route specs — the dispatcher handles body/query parsing internally. Real input schemas live in rpc_endpoints surface, not on routes; see assert_rpc_ws_surface_invariants for the parallel checks over RPC/WS method shapes.

surface

returns

void

assert_no_audit_drift
#

testing/audit_drift_guard.ts view source

(): void import {assert_no_audit_drift} from '@fuzdev/fuz_app/testing/audit_drift_guard.js';

Assert the process-wide audit counters are zero — the check install_audit_drift_guard runs after each test. Exported for a test that manages the counters itself.

returns

void

throws

  • AssertionError - naming the first counter that is not zero

assert_no_duplicate_routes
#

assert_no_error_info_leakage
#

testing/integration_helpers.ts view source

(body: unknown, context: string): void import {assert_no_error_info_leakage} from '@fuzdev/fuz_app/testing/integration_helpers.js';

Assert that an error response contains no leaky field values.

Checks both field names and string values for patterns indicating stack traces, SQL, or internal paths. Accepts unknown so callers pass response bodies / nested envelope fields directly without intermediate as casts; non-object bodies skip the field-name check.

body

type unknown

context

description for error messages

type string

returns

void

assert_no_fingerprint_headers
#

testing/cross_backend/conformance_table.ts view source

(headers: Record<string, string>, label: string): void import {assert_no_fingerprint_headers} from '@fuzdev/fuz_app/testing/cross_backend/conformance_table.js';

Assert a response carries none of the FINGERPRINT_HEADERS. Run on every case unconditionally — the always-on no-fingerprint floor.

headers

type Record<string, string>

label

type string

returns

void

assert_no_sensitive_fields_in_json
#

testing/integration_helpers.ts view source

(body: unknown, blocklist: readonly string[], context: string): void import {assert_no_sensitive_fields_in_json} from '@fuzdev/fuz_app/testing/integration_helpers.js';

Assert that a parsed JSON body contains no fields from the given blocklist.

body

type unknown

blocklist

type readonly string[]

context

description for error messages

type string

returns

void

assert_no_testing_methods
#

testing/surface_invariants.ts view source

(surface: AppSurface): void import {assert_no_testing_methods} from '@fuzdev/fuz_app/testing/surface_invariants.js';

No _testing_* backdoor action ever appears as a method on the declared surface (RPC or WS).

The test-backdoor actions (_testing_reset et al.) are daemon-token-gated privileged actions a consumer's test binary appends to its live RPC endpoint at assembly time — but they are deliberately excluded from surface generation (spine_rpc_endpoints and every consumer's create_*_app_surface_spec omit them) so the published attack surface, the committed *_attack_surface.json snapshot, and codegen never carry a backdoor. This invariant is the structural guard against a future wiring change that folds create_testing_actions(...) into the *declared* registry instead of the live-only append — the surface stays the authoritative "what the server exposes" map only if a backdoor can never hide in it.

Pairs with the wire-level negative-credential check (describe_testing_backdoor_cross_tests, cross-process) and the spec-level gate check: this one pins absence-from-surface, those pin the daemon-token gate and the 401/403 behavior.

surface

returns

void

throws

  • AssertionError - naming the offending endpoint + method.

assert_no_unexpected_public_mutations
#

testing/surface_invariants.ts view source

(surface: AppSurface, allowlist?: string[]): void import {assert_no_unexpected_public_mutations} from '@fuzdev/fuz_app/testing/surface_invariants.js';

Public mutation routes (auth: none + is_mutation) must be in the allowlist.

Catches accidentally unprotected POST/PUT/DELETE routes. Routes like login and bootstrap are public mutations by design — they go in the allowlist.

surface

allowlist

type string[]
default []

returns

void

assert_non_admin_schemas_no_admin_fields
#

testing/data_exposure.ts view source

(surface: AppSurface, admin_only_fields?: readonly string[]): void import {assert_non_admin_schemas_no_admin_fields} from '@fuzdev/fuz_app/testing/data_exposure.js';

Assert that non-admin route output schemas don't contain admin-only fields.

surface

admin_only_fields

type readonly string[]
default admin_only_field_blocklist

returns

void

assert_only_expected_public_routes
#

testing/assertions.ts view source

(surface: AppSurface, expected_public: string[]): void import {assert_only_expected_public_routes} from '@fuzdev/fuz_app/testing/assertions.js';

Bidirectional check: no unexpected public routes, no missing expected ones.

surface

the app surface to check

expected_public

format: ['GET /health', 'POST /api/account/login']

type string[]

returns

void

throws

  • AssertionError - if the live surface has public routes not in `expected_public`, or if any entry in `expected_public` is missing from the live surface.

assert_origin_carve_outs_valid
#

auth/middleware.ts view source

(carve_outs: readonly MiddlewareCarveOut[], auth_path?: string): void import {assert_origin_carve_outs_valid} from '@fuzdev/fuz_app/auth/middleware.js';

Throw unless every origin carve-out names a path and a reason, and stays off the auth middleware's scope: a path that starts with / (a Hono pattern like /hooks/* or /health), a non-blank reason — a carve-out is an argument someone wrote down, never an empty placeholder — and a pattern that matches nothing under auth_path (middleware_paths_overlap).

The scope rule is what keeps a carve-out off every credentialed route. Assembly holds every route that reads a credential (auth.account not 'none', or reads_credential) or mints one (mints_credential: login, signup, bootstrap) inside AUTH_MIDDLEWARE_PATH (assert_credential_routes_in_auth_scope), and every RPC, WS, and bootstrap endpoint besides (assert_endpoint_in_auth_scope). The minting routes declare no auth but are what a forged cross-site request targets, so refusing by route auth would miss them; refusing by scope catches them. A route outside the scope reads no credential the auth middleware parses, and mints none, so a carve-out there removes no credentialed check.

carve_outs

the carve-outs to check

type readonly MiddlewareCarveOut[]

auth_path

the auth middleware's path pattern (default AUTH_MIDDLEWARE_PATH)

type string
default AUTH_MIDDLEWARE_PATH

returns

void

throws

  • Error - naming the first carve-out with no path, no reason, or a pattern overlapping `auth_path`

assert_origin_conformance
#

testing/cross_backend/origin_conformance.ts view source

(checks: readonly OriginConformanceCheck[], answer: readonly unknown[], backend: string): void import {assert_origin_conformance} from '@fuzdev/fuz_app/testing/cross_backend/origin_conformance.js';

Compare each check's answer with its expectation and fail once, listing every row that disagrees.

checks

the table's checks

type readonly OriginConformanceCheck[]

answer

the answer each check got, in order

type readonly unknown[]

backend

the name the failure message gives the answerer

type string

returns

void

assert_output_schemas_no_sensitive_fields
#

testing/data_exposure.ts view source

(surface: AppSurface, sensitive_fields?: readonly string[]): void import {assert_output_schemas_no_sensitive_fields} from '@fuzdev/fuz_app/testing/data_exposure.js';

Assert that no output schema in the surface contains sensitive field names.

surface

sensitive_fields

type readonly string[]
default sensitive_field_blocklist

returns

void

assert_params_routes_declare_400
#

testing/surface_invariants.ts view source

(surface: AppSurface): void import {assert_params_routes_declare_400} from '@fuzdev/fuz_app/testing/surface_invariants.js';

Every route with non-null params_schema has 400 in error_schemas.

surface

returns

void

assert_protected_routes_declare_401
#

testing/surface_invariants.ts view source

(surface: AppSurface): void import {assert_protected_routes_declare_401} from '@fuzdev/fuz_app/testing/surface_invariants.js';

Every protected route has 401 in error_schemas.

surface

returns

void

assert_query_routes_declare_400
#

testing/surface_invariants.ts view source

(surface: AppSurface): void import {assert_query_routes_declare_400} from '@fuzdev/fuz_app/testing/surface_invariants.js';

Every route with non-null query_schema has 400 in error_schemas.

surface

returns

void

assert_rate_limit_retry_after_header
#

testing/integration_helpers.ts view source

(response: Response, body: { retry_after: number; }): void import {assert_rate_limit_retry_after_header} from '@fuzdev/fuz_app/testing/integration_helpers.js';

Assert that a 429 response includes a valid Retry-After header matching the JSON body's retry_after field.

response

type Response

body

type { retry_after: number; }

returns

void

assert_response_matches_spec
#

testing/integration_helpers.ts view source

(route_specs: RouteSpec[], method: string, path: string, response: Response): Promise<void> import {assert_response_matches_spec} from '@fuzdev/fuz_app/testing/integration_helpers.js';

Validate a response body against the route spec's declared schemas.

For 2xx responses, validates against spec.output. For error responses, validates against the merged error schema for that status code.

route_specs

type RouteSpec[]

method

type string

path

type string

response

type Response

returns

Promise<void>

throws

  • Error - if no route spec matches `method` + `path`, if the response body fails to parse against the declared output / error schema, or if the response is non-JSON despite a declared schema for that status.

assert_role_routes_declare_403
#

testing/surface_invariants.ts view source

(surface: AppSurface): void import {assert_role_routes_declare_403} from '@fuzdev/fuz_app/testing/surface_invariants.js';

Every route with an authority gate that can answer 403 declares one — a role gate, or a credential-channel gate of any kind (keeper included).

The credential half is not just the keeper routes: the session-only gates point the same axis the other way and refuse with the same status, so a route carrying one and no 403 schema would have its denial body go unvalidated by the DEV-only error-schema check.

surface

returns

void

assert_route_auth_acting_biconditional
#

http/auth_shape.ts view source

(auth: { account: "optional" | "none" | "required"; actor: "optional" | "none" | "required"; roles?: readonly string[] | undefined; credential_types?: readonly string[] | undefined; required_scope?: string | undefined; }, slots: ActingSlots, context: string): void import {assert_route_auth_acting_biconditional} from '@fuzdev/fuz_app/http/auth_shape.js';

Registry-time biconditional check: `auth.actor !== 'none' ⟺ some supplied slot declares acting?: ActingActor`. Throws on violation.

The slot set differs by surface: REST passes {input, query} (both locatable, query only set for GETs); action dispatchers pass {input} (no query shape on ActionSpec). The throw message lists the slots that were actually in play, so an actor-required action without acting doesn't point the operator at a query slot that doesn't exist on their spec.

Called by every dispatcher registration loop (apply_route_specs, compile_action_registry) on every spec it accepts.

auth

the route/action's auth shape

slots

the spec's acting-bearing schemas; query omitted on action call sites

context

identifier for the throwing message (route key, RPC method, etc.)

type string

returns

void

throws

  • Error - when the biconditional is violated

assert_row
#

db/assert_row.ts view source

<T>(row: T | undefined, context?: string | undefined): T import {assert_row} from '@fuzdev/fuz_app/db/assert_row.js';

Assert that a row is present, throwing a descriptive error if missing.

Use after INSERT ... RETURNING queries where the database guarantees a row is returned on success. Replaces bare row! non-null assertions with an explicit runtime check.

row

the row from query_one (T | undefined) or rows[0] (T | undefined)

type T | undefined

context?

optional context for the error message (e.g. table or operation name)

type string
optional

returns

T

the row, guaranteed non-undefined

generics

assert_row<T>
T

throws

  • Error - if `row` is `undefined`

assert_rpc_method_coverage
#

testing/cross_backend/method_coverage.ts view source

(input: RpcMethodCoverageInput): void import {assert_rpc_method_coverage} from '@fuzdev/fuz_app/testing/cross_backend/method_coverage.js';

Assert the live RPC method set reconciles exactly with the coverage manifest, and that every manifest entry's tier is internally consistent.

Fails loud on: a live method missing from the manifest (mounted-but-unclaimed), a manifest row naming a method the live mount no longer exposes (stale row), a declared-surface method absent from the live mount (the full mount must be a superset), a duplicate manifest entry, or any tier/declared/backdoor inconsistency (e.g. an off_surface row that is actually on the declared surface, or one missing its suite).

input

returns

void

throws

  • AssertionError - naming the specific divergence.

assert_rpc_method_descriptions_present
#

testing/surface_invariants.ts view source

(surface: AppSurface): void import {assert_rpc_method_descriptions_present} from '@fuzdev/fuz_app/testing/surface_invariants.js';

Every RPC method on every endpoint has a non-empty description.

Parallel of assert_descriptions_present over surface.rpc_endpoints. Empty descriptions on RPC methods leak through library.json codegen and consumer-facing docs without the route-level check catching them.

surface

returns

void

assert_rpc_ws_surface_invariants
#

testing/surface_invariants.ts view source

(surface: AppSurface): void import {assert_rpc_ws_surface_invariants} from '@fuzdev/fuz_app/testing/surface_invariants.js';

Run all RPC / WS structural invariants. Options-free — applies universally to the surface.rpc_endpoints and surface.ws_endpoints slots produced by generate_app_surface.

Parallel of assert_surface_invariants for the non-REST surfaces. Within-endpoint duplicate method names and the auth-shape biconditional are already enforced at startup by compile_action_registry (see actions/CLAUDE.md §Registry compile) — these assertions cover only the contract-surface concerns that a runtime registration check cannot: empty descriptions, missing protocol-action spread on WS endpoints, kind ⇔ auth drift on WS methods, and a _testing_* backdoor action leaking onto the declared surface.

surface

returns

void

throws

  • AssertionError - on the first invariant violation; the message names the offending endpoint, method, and field.

assert_schema_snapshots_equal
#

testing/schema_parity.ts view source

(a: { tables: Record<string, { columns: Record<string, { data_type: string; udt_name: string; is_nullable: boolean; column_default: string | null; is_identity: boolean; }>; indexes: { name: string; definition: string; }[]; constraints: { ...; }[]; }>; sequences: Record<...>; enums: Record<...>; }, b: { ...; }, labels?: SchemaDiffLabels): void import {assert_schema_snapshots_equal} from '@fuzdev/fuz_app/testing/schema_parity.js';

Throw if the two snapshots disagree. The error message names the impls (via labels) and lists every diff, so the failure is self-diagnosing.

Consumers wire this after bootstrapping each impl against an isolated DB:

await drop_recreate_db('zzz_test'); await spawn_backend(deno_config); const snapshot_deno = await query_schema_snapshot(db, {}); await drop_recreate_db('zzz_test'); await spawn_backend(rust_config); const snapshot_rust = await query_schema_snapshot(db, {}); assert_schema_snapshots_equal(snapshot_deno, snapshot_rust, {a: 'deno', b: 'rust'});

a

b

labels

default {}

returns

void

assert_secure_mode
#

runtime/secure_file.ts view source

(path: string, mode: number): void import {assert_secure_mode} from '@fuzdev/fuz_app/runtime/secure_file.js';

Refuse any group/other-accessible mode (only 0600/0400 pass).

Callers own the platform gating: the real runtimes skip the check where modes aren't meaningful (Node on Windows, a null Deno mode); the mock checks its simulated modes unconditionally.

path

type string

mode

type number

returns

void

throws

  • Error - naming the path, the offending mode, and the `chmod` fix

assert_secure_regular_file
#

runtime/secure_file.ts view source

(path: string, is_file: boolean): void import {assert_secure_regular_file} from '@fuzdev/fuz_app/runtime/secure_file.js';

Refuse anything but a regular file — a FIFO, device node, socket, or directory at the secret's path.

path

type string

is_file

type boolean

returns

void

throws

  • Error - naming the path

assert_secure_size
#

runtime/secure_file.ts view source

(path: string, size: number): void import {assert_secure_size} from '@fuzdev/fuz_app/runtime/secure_file.js';

Refuse a byte count over MAX_SECURE_FILE_SIZE.

path

type string

size

type number

returns

void

throws

  • Error - naming the path, the size, and the cap

assert_sensitive_routes_rate_limited
#

testing/surface_invariants.ts view source

(surface: AppSurface, sensitive_patterns?: (string | RegExp)[]): void import {assert_sensitive_routes_rate_limited} from '@fuzdev/fuz_app/testing/surface_invariants.js';

Sensitive routes must declare rate limiting (rate_limit_key is non-null) or have 429 in their error schemas.

Matches routes against sensitive patterns and flags any that lack rate limit declarations. Catches forgotten rate limiting on credential-handling routes.

surface

sensitive_patterns

type (string | RegExp)[]
default DEFAULT_SENSITIVE_PATTERNS

returns

void

assert_surface_deterministic
#

testing/assertions.ts view source

(build_surface: () => AppSurface): void import {assert_surface_deterministic} from '@fuzdev/fuz_app/testing/assertions.js';

Verify surface generation is deterministic (build twice, compare).

build_surface

type () => AppSurface

returns

void

assert_surface_invariants
#

testing/surface_invariants.ts view source

(surface: AppSurface): void import {assert_surface_invariants} from '@fuzdev/fuz_app/testing/surface_invariants.js';

Run all structural invariants. Options-free — applies universally.

Catches schema/surface generation bugs: missing 401/403/400 declarations, empty descriptions, duplicate routes, middleware-injected error codes unpropagated to routes, structurally invalid error schemas, error codes appearing at multiple statuses, and generic 404 schemas on param routes.

surface

returns

void

throws

  • AssertionError - on the first invariant violation; the message names the offending route and the missing/inconsistent field.

assert_surface_matches_snapshot
#

testing/assertions.ts view source

(surface: AppSurface, snapshot_path: string): void import {assert_surface_matches_snapshot} from '@fuzdev/fuz_app/testing/assertions.js';

Compare live surface against a committed snapshot JSON file.

Failure message instructs the developer to run gro gen to update the snapshot — every fuz_app consumer wires the snapshot through a *.gen.ts file so regeneration goes through the same pipeline as the rest of the generated artifacts.

surface

the live surface to check

snapshot_path

absolute path to the committed JSON snapshot

type string

returns

void

throws

  • AssertionError - if the live surface does not deep-equal the snapshot, or `Error` if the snapshot file is unreadable / malformed JSON.

assert_surface_security_policy
#

testing/surface_invariants.ts view source

(surface: AppSurface, options?: SurfaceSecurityPolicyOptions): void import {assert_surface_security_policy} from '@fuzdev/fuz_app/testing/surface_invariants.js';

Run security policy invariants. Configurable with sensible defaults.

Checks:

  • Sensitive routes are rate-limited
  • No unexpected public mutation routes
  • Input schemas use mutation methods (not GET)
  • Keeper routes under expected prefixes

surface

options

default {}

returns

void

throws

  • AssertionError - on the first policy violation; the message names the offending route.

assert_valid_sql_identifier
#

db/sql_identifier.ts view source

(name: string): string import {assert_valid_sql_identifier} from '@fuzdev/fuz_app/db/sql_identifier.js';

Assert that a string is a valid SQL identifier.

Use this before interpolating table or column names into DDL queries where parameterized placeholders ($1) are not supported.

name

the identifier to validate

type string

returns

string

the validated identifier

throws

  • Error - if the identifier contains characters outside `[a-zA-Z0-9_]` or starts with a digit

assert_ws_endpoints_include_protocol_actions
#

testing/surface_invariants.ts view source

(surface: AppSurface): void import {assert_ws_endpoints_include_protocol_actions} from '@fuzdev/fuz_app/testing/surface_invariants.js';

Every WS endpoint's methods includes every protocol action method (heartbeat, cancel).

Consumers register WS endpoints by spreading protocol_actions from actions/protocol.ts before their own actions:

ws_endpoints: [{path: '/api/ws', actions: [...protocol_actions, ...consumer_actions], ...}]

Forgetting the spread compiles cleanly but breaks at runtime: client-side heartbeats and cancel notifications get method_not_found from the dispatcher, so disconnect detection silently regresses and per-request cancel never aborts the matching handler. Catch the mistake at the surface layer rather than at runtime.

surface

returns

void

assert_ws_method_descriptions_present
#

assert_ws_notifications_have_null_auth
#

testing/surface_invariants.ts view source

(surface: AppSurface): void import {assert_ws_notifications_have_null_auth} from '@fuzdev/fuz_app/testing/surface_invariants.js';

WS methods follow the kind ⇔ auth biconditional emitted by surface generation: kind === 'remote_notification' ⟺ auth === null.

generate_app_surface produces this shape directly from the action spec union (notifications carry auth: null per ActionSpecUnion; request_response carries a RouteAuth). The assertion guards against drift if a future surface emitter, transform, or test fixture violates it — and gives consumers a clear failure message when a hand-built surface mocks the shape incorrectly.

surface

returns

void

AsyncSlot
#

ui/async_slot.svelte.ts view source

import {AsyncSlot} from '@fuzdev/fuz_app/ui/async_slot.svelte.js';

Reactive container for a single async operation.

generics

AsyncSlot<T = void, E = string>
T
default void
E
default string

status

type AsyncStatus

$state.raw

data

type T | undefined

$state.raw

error

type E | null

$state.raw

error_data

The raw caught value from the last failed run(), for programmatic inspection.

type unknown

$state.raw

initial

Convenience derived: status === 'initial'.

type boolean

readonly $derived

loading

Convenience derived: status === 'pending'.

type boolean

readonly $derived

succeeded

Convenience derived: status === 'success'.

type boolean

readonly $derived

failed

Convenience derived: status === 'failure'.

type boolean

readonly $derived

constructor

type new <T = void, E = string>(options?: AsyncSlotOptions<T, E>): AsyncSlot<T, E>

options

type AsyncSlotOptions<T, E>
default {}

run

Run an async operation. The callback receives an AbortSignal it can forward to fetch / RPC clients that support cancellation; the slot also discards superseded results internally even if the callback ignores the signal.

Supersession rule: a second run() aborts the first's signal AND silently drops its commit if it resolves anyway. So back-to-back-to-back run() calls leave only the last call's result in data.

Abort rule: a run() that throws because of its own signal (manual abort(), external options.signal, OR supersession by another run()) does NOT promote to 'failure'. Manual / external aborts revert status to the previous resolved state ('initial' if no run() has ever succeeded, 'success' otherwise). Supersession is handled by the bail-on-mismatch check, leaving the second run's 'pending' standing.

type (fn: (signal: AbortSignal) => Promise<T>, options?: RunOptions): Promise<T | undefined>

fn

type (signal: AbortSignal) => Promise<T>

options

default {}
returns Promise<T | undefined>

the resolved value on success; undefined on failure, abort, or supersession

abort

Manually abort the in-flight run, if any. Reverts status synchronously to the prior resolved state — 'initial' if no run() (or set()) has ever succeeded on this slot, 'success' otherwise. The aborted run's eventual resolution / rejection is dropped without writing to state (the run's Promise resolves to undefined).

type (reason?: unknown): void

reason?

type unknown
optional
returns void

set

Replace data directly and mark the slot 'success'. For post-mutation hydration where the calling RPC already returned the canonical row (parallels CellState.set_cell).

Aborts any in-flight run() first — without this, the in-flight callback could resolve after set() and overwrite the explicit value (the bail-on-mismatch check only fires when #controller was rotated).

type (data: T): void

data

type T
returns void

mutates

  • this

reset

Reset to 'initial', clear data / error / error_data, and abort any in-flight run. After reset() the slot looks like a fresh instance with no initial option.

type (): void

returns void

mutates

  • this

AsyncSlotOptions
#

ui/async_slot.svelte.ts view source

AsyncSlotOptions<T, E> import type {AsyncSlotOptions} from '@fuzdev/fuz_app/ui/async_slot.svelte.js';

generics

AsyncSlotOptions<T, E = string>
T
E
default string

initial?

Seed data and put the slot in 'success' before any run(). Useful when the page already has the resource in hand (SSR hydration, a mutation response, hand-off from a parent slot).

type T

map_error?

Convert a caught throw into the error value stored in AsyncSlot.error. Default extracts Error.message (falling back to 'Request failed' for non-Error throws). Pass to_rpc_error_message to unwrap JSON-RPC data.reason codes.

type (e: unknown): E

e

type unknown
returns E

preserve_error_on_retry?

When true, the previous error / error_data survive the start of a new run() until the next success (or another failure overwrites them). Useful for retry UX that wants to keep the failure message visible alongside an inline spinner. Default false — run() clears the error at the start so the pending state reads "no current error."

type boolean

audit_error_schema_tightness
#

testing/surface_invariants.ts view source

(surface: AppSurface): ErrorSchemaAuditEntry[] import {audit_error_schema_tightness} from '@fuzdev/fuz_app/testing/surface_invariants.js';

Audit error schema tightness across all routes in a surface.

Reports which route x status code combinations use generic ApiError (z.string()) vs specific z.literal() or z.enum() error codes. Use the output to prioritize progressive tightening of error schemas.

surface

returns

ErrorSchemaAuditEntry[]

audit entries for every route x status combination

audit_event_revocation_scopes
#

auth/audit_log_schema.ts view source

Readonly<Record<"login" | "logout" | "bootstrap" | "signup" | "password_change" | "session_revoke" | "session_revoke_all" | "token_create" | "token_revoke" | "token_revoke_all" | "role_grant_create" | ... 16 more ... | "db_admin_row_delete", RevocationScope>>

type Readonly<Record<AuditEventType, RevocationScope>>

import {audit_event_revocation_scopes} from '@fuzdev/fuz_app/auth/audit_log_schema.js';

The RevocationScope of every builtin audit event — declared once, so the WebSocket and SSE revocation listeners read one answer instead of keeping a list each. Typed over AuditEventType, so a new event type does not compile until it declares its scope here.

A scope other than none marks an event whose success invalidates a live connection's credential or access: logout, password_change, session_revoke_all, token_revoke_all, account_delete, and account_purge are account-wide, session_revoke and token_revoke name the one credential they ended, and role_grant_revoke names the lost role. Granting access revokes nothing, and neither does an event about a subject other than a connection's identity.

Twin of the revocation_scope column of the Rust spine's AUDIT_EVENT_SPECS.

AUDIT_EVENT_TYPE_NAME_REGEX
#

auth/audit_log_schema.ts view source

RegExp import {AUDIT_EVENT_TYPE_NAME_REGEX} from '@fuzdev/fuz_app/auth/audit_log_schema.js';

Letter start, then letters, digits, _, ., /, -. Accepts snake_case, dotted, and namespaced consumer conventions; rejects empty strings, leading separators, whitespace, and control characters.

AUDIT_EVENT_TYPES
#

auth/audit_log_schema.ts view source

readonly ["login", "logout", "bootstrap", "signup", "password_change", "session_revoke", "session_revoke_all", "token_create", "token_revoke", "token_revoke_all", "role_grant_create", ... 16 more ..., "db_admin_row_delete"] import {AUDIT_EVENT_TYPES} from '@fuzdev/fuz_app/auth/audit_log_schema.js';

All tracked auth event types. Frozen to convert accidental in-process mutation (test cross-contamination, cast escapes) into loud TypeErrors. Not a security boundary — in-process code has many other paths to subvert audit logging.

AUDIT_LOG_CHANNEL
#

AUDIT_LOG_COLUMNS
#

auth/audit_log_queries.ts view source

readonly ["id", "seq", "event_type", "outcome", "actor_id", "account_id", "target_account_id", "target_actor_id", "ip", "created_at", "metadata"] import {AUDIT_LOG_COLUMNS} from '@fuzdev/fuz_app/auth/audit_log_queries.js';

The full audit_log column set, named explicitly so a row read fails loud on schema drift — audit rows ride the audit_log_list / role-grant-history RPC responses and the SSE broadcast raw, so a SELECT * would silently carry a dropped or leftover column into the strict-validated wire shapes (see ACCOUNT_COLUMNS in auth/account_queries.ts for the outage class; the Rust twin names columns at every audit site). Keep in sync with AuditLogEvent and the migration chain's end state. Exported for the per-cell timeline read in db/cell_audit_queries.ts.

AUDIT_LOG_DEFAULT_LIMIT
#

audit_log_event_specs
#

audit_log_expr
#

auth/audit_log_queries.ts view source

(alias: string) => ColumnExpr import {audit_log_expr} from '@fuzdev/fuz_app/auth/audit_log_queries.js';

The audit_log timestamp override, by row qualifier ('' bare, al for the username JOINs). Exported for the per-cell timeline read in db/cell_audit_queries.ts, which projects the same table.

AUDIT_LOG_INDEXES
#

audit_log_list_action_spec
#

auth/admin_action_specs.ts view source

{ method: string; kind: "request_response"; initiator: "frontend"; auth: { account: "required"; actor: "required"; roles: string[]; }; side_effects: false; input: ZodDefault<ZodObject<{ event_type: ZodOptional<ZodNullable<ZodString>>; ... 5 more ...; acting: ZodOptional<...>; }, $strict>>; output: ZodObject<...>; as... import {audit_log_list_action_spec} from '@fuzdev/fuz_app/auth/admin_action_specs.js';

rate_limit: 'account' bounds admin-side enumeration of the entire audit log via (limit, offset) walking — same shape as admin_account_list_action_spec. The listing carries cross-account forensic detail (target ids, IPs, metadata), so the read-rate cap is the only check that distinguishes a human reviewer from a scraping script.

AUDIT_LOG_LIST_LIMIT_MAX
#

audit_log_role_grant_history_action_spec
#

auth/admin_action_specs.ts view source

{ method: string; kind: "request_response"; initiator: "frontend"; auth: { account: "required"; actor: "required"; roles: string[]; }; side_effects: false; input: ZodDefault<ZodObject<{ limit: ZodOptional<ZodNullable<ZodNumber>>; offset: ZodOptional<...>; acting: ZodOptional<...>; }, $strict>>; output: ZodObject<...... import {audit_log_role_grant_history_action_spec} from '@fuzdev/fuz_app/auth/admin_action_specs.js';

rate_limit: 'account' bounds admin-side enumeration of the role_grant history via (limit, offset) walking — same shape as audit_log_list, narrower projection but identical scraping vector.

audit_log_rpc_context
#

ui/audit_log_state.svelte.ts view source

{ get: (error_message?: string | undefined) => () => AuditLogRpc; get_maybe: () => (() => AuditLogRpc) | undefined; set: (value: () => AuditLogRpc) => () => AuditLogRpc; } import {audit_log_rpc_context} from '@fuzdev/fuz_app/ui/audit_log_state.svelte.js';

Svelte context carrying the reactive AuditLogRpc accessor. Mirrors admin_accounts_rpc_context. get() throws when no provisioner ran above the component — the adapter is required.

AUDIT_LOG_SCHEMA
#

auth/audit_log_ddl.ts view source

"\nCREATE TABLE IF NOT EXISTS audit_log (\n id UUID PRIMARY KEY DEFAULT gen_random_uuid(),\n seq BIGSERIAL NOT NULL,\n event_type TEXT NOT NULL,\n outcome TEXT NOT NULL DEFAULT 'success',\n actor_id UUID,\n account_id UUID,\n target_account_id UUID,\n target_actor_id UUID,\n ip TEXT,\n created_at TIMESTAMP... import {AUDIT_LOG_SCHEMA} from '@fuzdev/fuz_app/auth/audit_log_ddl.js';

AUDIT_LOG_SSE_MAX_PER_SCOPE
#

realtime/sse_auth_guard.ts view source

10 import {AUDIT_LOG_SSE_MAX_PER_SCOPE} from '@fuzdev/fuz_app/realtime/sse_auth_guard.js';

Default max concurrent SSE subscribers per session scope for the audit log.

The audit log SSE subscribes with scope = session_hash and groups = [account_id]. Only scope is capped — so this limits tabs per session. An account's total streams across all sessions is bounded transitively by max_sessions × AUDIT_LOG_SSE_MAX_PER_SCOPE. 10 tabs per session is a comfortable ceiling for normal use; consumers raising it above ~50 should consider server-side connection limits.

audit_metadata_schemas
#

auth/audit_log_schema.ts view source

Readonly<{ login: ZodNullable<ZodObject<{ username: ZodString; }, $loose>>; logout: ZodNull; bootstrap: ZodNullable<ZodObject<{ error: ZodString; }, $loose>>; ... 24 more ...; db_admin_row_delete: ZodObject<...>; }>

type Readonly<{ login: ZodNullable<ZodObject<{ username: ZodString; }, $loose>>; logout: ZodNull; bootstrap: ZodNullable<ZodObject<{ error: ZodString; }, $loose>>; ... 24 more ...; db_admin_row_delete: ZodObject<...>; }>

import {audit_metadata_schemas} from '@fuzdev/fuz_app/auth/audit_log_schema.js';

Per-event-type metadata Zod schemas. z.looseObject so consumers can add fields while known ones are validated. The record is frozen to catch mutation bugs at the key level (e.g. tests that try to swap in a stub schema); the Zod schemas themselves are reachable and mutable — freeze isn't a security boundary.

audit_unmatched_peer_response
#

actions/peer_request.ts view source

(log: Logger, connection_id: string & $brand<"Uuid">, id: string | number | null): void import {audit_unmatched_peer_response} from '@fuzdev/fuz_app/actions/peer_request.js';

Sampled, bounded audit for an inbound response that matched no pending request on its connection — an unsolicited {id, result}, a cross-connection id echo, or a late/duplicate reply for an already-settled id. Auditing every rejected frame would let a junk flood turn the log into the DoS target, so this warns on the first few then samples 1-in-256. Twin of the Rust fuz_realtime::peer::audit_unmatched_response.

log

the WS dispatcher's logger

type Logger

connection_id

the socket the unmatched reply arrived on

type Uuid

id

the reply's echoed id (null when absent)

type string | number | null

returns

void

AuditCompletenessTestOptions
#

testing/audit_completeness.ts view source

AuditCompletenessTestOptions import type {AuditCompletenessTestOptions} from '@fuzdev/fuz_app/testing/audit_completeness.js';

setup_test

Per-test fixture-producing function. The audit suite calls this in every test() body — auth_integration_truncate_tables clears the audit log between tests, so each test re-bootstraps the keeper and the observer admin against a fresh table.

type (): Promise<TestFixtureBase>

returns Promise<TestFixtureBase>

surface_source

App surface (with route specs). Constructed in TS by the consumer; same shape for in-process and cross-process tests. The audit suite is Tier 2 today (stays in-process per the cross-backend-integration design), but takes the same surface shape as Tier 1 suites for options uniformity.

type AppSurfaceSpec

session_options

Session config — needed for factory-form rpc_endpoints resolution.

type SessionOptions<string>

rpc_endpoints

RPC endpoint specs — required. The admin role_grant flow is RPC-only and the suite hard-fails without it.

type RpcEndpointsSuiteOption

AuditEmitFn
#

auth/audit_emitter.ts view source

AuditEmitFn import type {AuditEmitFn} from '@fuzdev/fuz_app/auth/audit_emitter.js';

Signature of AuditEmitter.emit — captured by the inner closure so emit_role_grant_target reaches the decorated function rather than a this.emit lookup. Exposed as a type so EmitDecorator can name the inner / outer slot.

(call)

type <T extends string>(ctx: AuditEmitterContext, input: AuditLogInput<T>): void

ctx

input

returns void

AuditEmitRoleGrantContext
#

auth/audit_emitter.ts view source

AuditEmitRoleGrantContext import type {AuditEmitRoleGrantContext} from '@fuzdev/fuz_app/auth/audit_emitter.js';

Context required by AuditEmitter.emit_role_grant_target — adds client_ip so the helper can lift the ip: ctx.client_ip boilerplate every role-grant-shape emit site repeated.

inheritance

client_ip

Resolved client IP from the trusted-proxy middleware — 'unknown' if not resolved.

type string

AuditEmitRoleGrantInput
#

auth/audit_emitter.ts view source

AuditEmitRoleGrantInput<T> import type {AuditEmitRoleGrantInput} from '@fuzdev/fuz_app/auth/audit_emitter.js';

Input to AuditEmitter.emit_role_grant_target — the event fields that vary per call site; actor_id, account_id, and ip are lifted from the auth and request contexts.

generics

AuditEmitRoleGrantInput<T extends string>
T
constraint string

event_type

type T

target_account_id

type Uuid | null

target_actor_id

type Uuid | null

metadata

type (T extends "login" | "logout" | "bootstrap" | "signup" | "password_change" | "session_revoke" | "session_revoke_all" | "token_create" | "token_revoke" | "token_revoke_all" | "role_grant_create" | "role_grant_revoke" | "role_grant_offer_create" | "role_grant_offer_accept" | "role_grant_offer_decline" | "role_grant_offer_retract" | "role_grant_offer_expire" | "role_grant_offer_supersede" | "invite_create" | "invite_delete" | "account_delete" | "account_purge" | "account_undelete" | "actor_delete" | "actor_purge" | "actor_undelete" | "app_settings_update" | "db_admin_row_delete" ? (AuditMetadataMap[T] & Record<string, unknown>) | null : Record<string, unknown> | null) | undefined

outcome?

Default 'success', as for emit.

type "success" | "failure"

AuditEmitter
#

auth/audit_emitter.ts view source

AuditEmitter import type {AuditEmitter} from '@fuzdev/fuz_app/auth/audit_emitter.js';

Bound audit-emit capability. Built once at backend assembly via create_audit_emitter; lives on AppDeps.audit so factories never see the pool.

emit

Fire-and-forget audit write via the captured pool.

The in-flight promise is pushed onto ctx.pending_effects so tests with await_pending_effects: true can assert side effects inline. Errors are logged, never thrown. A written row fans out to every listener on the chain (notify): a failure row as soon as it is written, a success row from ctx.post_commit_effects — after the request's transaction commits, and never when the handler throws.

Returns void deliberately — the in-flight promise is already on ctx.pending_effects, and exposing it would tempt callers to await (sequencing audit writes onto the response hot path) or sprinkle void to placate no-floating-promises. For awaitable writes from code paths without pending_effects, use emit_pool.

type <T extends string>(ctx: AuditEmitterContext, input: AuditLogInput<T>): void

ctx

input

returns void

mutates

  • audit_log table — inserts the row via the captured pool
  • ctx.pending_effects — appends the in-flight settled promise
  • ctx.post_commit_effects — appends a success row's listener fan-out

emit_role_grant_target

Emit a role-grant-shape audit event with actor_id / account_id / ip lifted from auth + ctx. Delegates to emit.

Use for any event populating one of the target_*_id columns. Reach for the lower-level emit only when the event is non-role-grant shape (e.g. app_settings_update, bootstrap, signup).

type <T extends string>(ctx: AuditEmitRoleGrantContext, auth: RequestActorContext, input: AuditEmitRoleGrantInput<T>): void

ctx

auth

input

returns void

emit_pool

Awaitable pool write for code paths without a request context.

Writes, then notifies the listeners, whatever the outcome — there is no request transaction to order the fan-out against. Errors are logged and swallowed (resolved void), so callers can sequence writes with await audit.emit_pool(...) without try/catch boilerplate.

Not for a success audit paired with a state mutation: the pool write can't roll back with the state, and a swallowed error leaves the mutation unaudited. Write those in the mutation's transaction (query_audit_log against the tx) and notify after the commit — the shape auth/cleanup.ts's offer sweep and query_accept_offer use.

type <T extends string>(input: AuditLogInput<T>): Promise<void>

input

returns Promise<void>

mutates

  • audit_log table — inserts the row via the captured pool

notify

Fan out an already-written audit row to the registered listeners.

Use only when the row was inserted in-transaction by a query helper that returned the AuditLogEvent (e.g. query_accept_offer.audit_events). Per-listener exceptions are caught and logged; one failing listener does not starve siblings.

type (event: AuditLogEvent): void

event

returns void

add_listener

Register an audit-event listener. Listeners fire in registration order for every row emit / emit_pool wrote and on every notify.

Returns a remover that unregisters this registration. Each call is its own registration: the same function registered twice fires twice, and each remover takes out only the registration it was returned for, leaving the others in place and in order. Calling a remover again is a no-op. A notify already in progress still runs a listener removed during its fan-out — it iterates a snapshot — and the next one does not.

create_app_server registers the factory-managed audit-log SSE listener and per-endpoint WS auth guards here so SSE + WS fan-out compose on top of the consumer's on_audit_event callback without shallow-copying AppDeps, and removes them when its assembly fails or the server closes. Consumers can also register listeners directly for setups that don't run through create_app_server. The on_audit_event passed at construction has no remover — it lives as long as the emitter.

Twin of the Rust fuz_auth AuditEmitter::add_listener in what registration means — firing order, snapshot-at-notify.

type (listener: (event: AuditLogEvent) => void): () => void

listener

called with each announced audit row

type (event: AuditLogEvent) => void
returns () => void

a remover for this registration, idempotent

listener_count

Count of registered listeners, the construction-time on_audit_event included — introspection for tests and diagnostics.

type (): number

returns number

drain_inflight

Await every fire-and-forget emit write in flight — the deterministic barrier behind the _testing_drain_effects test action.

Waits until no tracked write is outstanding, so a write that starts while the drain is waiting (an emit from a post-commit thunk) is awaited too. Once it resolves, a following audit_log_list read sees every row whose emit ran before the drain was called. A write that starts after the drain resolves is not covered. A success row's listener fan-out runs as a post-commit effect and is not tracked, so an SSE broadcast or an auth-guard close driven by one isn't guaranteed done when the drain resolves — the row itself is.

Resolves at once unless the emitter was built with track_inflight: true — production never tracks, so there is nothing to wait for. Twin of the Rust fuz_auth AuditEmitter::drain_inflight.

type (): Promise<void>

returns Promise<void>

AuditEmitterContext
#

auth/audit_emitter.ts view source

AuditEmitterContext import type {AuditEmitterContext} from '@fuzdev/fuz_app/auth/audit_emitter.js';

Per-request context required by AuditEmitter.emit — the request's two side-effect queues. The bound emitter carries its own log reference inside the closure, so per-call contexts don't need one.

The write is eager: the bound emitter fires the pool write immediately and pushes the in-flight Promise<void> onto pending_effects. Deferring it would only delay forensic visibility, since a pool-routed write is already rollback-resilient. A success row's listener fan-out goes on post_commit_effects instead (module doc, "When listeners hear about a row").

One success-only event also defers its *write*: the db_admin_row_delete emit in http/db_routes.ts wraps the whole emit in emit_after_commit, so the trail can't claim a mutation whose transaction failed at COMMIT.

Both RouteContext and ActionContext structurally satisfy this shape, so handlers pass route / ctx directly.

inheritance

pending_effects

type Promise<void>[]

AuditEventHandler
#

actions/transports_ws_auth_guard.ts view source

AuditEventHandler import type {AuditEventHandler} from '@fuzdev/fuz_app/actions/transports_ws_auth_guard.js';

Audit-event callback shape — the function CreateAuditEmitterOptions.on_audit_event accepts and that create_ws_auth_guard returns.

Exported so consumers composing multiple handlers (typically create_ws_auth_guard + their own pre-existing on_audit_event) can annotate their composed callback without reaching for Parameters<typeof create_ws_auth_guard>[0].

(call)

type (event: AuditLogEvent): void

event

returns void

AuditEventType
#

auth/audit_log_schema.ts view source

value + type

"login" | "logout" | "bootstrap" | "signup" | "password_change" | "session_revoke" | "session_revoke_all" | "token_create" | "token_revoke" | "token_revoke_all" | "role_grant_create" | ... 16 more ... | "db_admin_row_delete"

type "login" | "logout" | "bootstrap" | "signup" | "password_change" | "session_revoke" | "session_revoke_all" | "token_create" | "token_revoke" | "token_revoke_all" | "role_grant_create" | "role_grant_revoke" | "role_grant_offer_create" | "role_grant_offer_accept" | "role_grant_offer_decline" | "role_grant_offer_retract" | "role_grant_offer_expire" | "role_grant_offer_supersede" | "invite_create" | "invite_delete" | "account_delete" | "account_purge" | "account_undelete" | "actor_delete" | "actor_purge" | "actor_undelete" | "app_settings_update" | "db_admin_row_delete"

import {AuditEventType} from '@fuzdev/fuz_app/auth/audit_log_schema.js';

Zod schema for audit event types.

AuditEventTypeName
#

auth/audit_log_schema.ts view source

value + type

string import {AuditEventTypeName} from '@fuzdev/fuz_app/auth/audit_log_schema.js';

Zod schema for valid audit event-type name strings.

AuditFactory
#

server/app_backend.ts view source

AuditFactory import type {AuditFactory} from '@fuzdev/fuz_app/server/app_backend.js';

Callback that builds the bound AuditEmitter after the backend's pool Db and Logger exist. Required on CreateAppBackendOptions so the consumer owns subscriber-chain composition and AuditLogConfig selection without the factory holding a default.

The factory is invoked exactly once during create_app_backend, after create_db resolves and migrations run. The emitter it returns lands on AppDeps.audit and is captured by every query/handler that reaches deps.audit.emit(...).

The canonical body is a one-liner over create_audit_emitter:

audit_factory: ({db, log}) => create_audit_emitter({ db, log, on_audit_event, audit_log_config, })

Returning an emitter built against a different db than the one passed in would route audit writes to a different pool than handlers query — the callback shape exists specifically to make that mistake structurally impossible.

(call)

type (params: { db: Db; log: Logger; }): AuditEmitter

params

type { db: Db; log: Logger; }
returns AuditEmitter

AuditLogConfig
#

auth/audit_log_schema.ts view source

AuditLogConfig import type {AuditLogConfig} from '@fuzdev/fuz_app/auth/audit_log_schema.js';

Configuration bundle for audit-log event types and metadata schemas.

Lets consumers extend the closed AUDIT_EVENT_TYPES enum with their own event strings (and metadata Zod schemas) without forking. Pass to create_audit_emitter (or query_audit_log for in-tx call sites) as the optional config argument; both default to builtin_audit_log_config.

The DB column is TEXT NOT NULL and never enforced an enum, so consumer event types round-trip through query_audit_log_list and SSE identically to builtins.

Constructed configs are deep-frozen (wrapper, event_types, metadata_schemas) to catch accidental mutation bugs early. Not a security boundary against in-process code, which can subvert audit logging through other paths.

event_types

All recognized event-type strings — fuz_app builtins plus consumer extras.

type readonly string[]

readonly

metadata_schemas

Per-event-type metadata schemas. Missing entries skip metadata validation for that type (row still written; metadata stored as raw JSONB).

type Readonly<Record<string, ZodType<unknown, unknown, $ZodTypeInternals<unknown, unknown>>>>

readonly

AuditLogEvent
#

auth/audit_log_schema.ts view source

AuditLogEvent import type {AuditLogEvent} from '@fuzdev/fuz_app/auth/audit_log_schema.js';

Audit log row from the database. See AuditLogEventJson for event_type widening rationale.

id

type Uuid

seq

type number

event_type

type string

outcome

type AuditOutcome

actor_id

Operator (the actor that initiated the event) — populated when the request resolved an acting actor.

Resolution is driven per-request by the route-spec wrapper / RPC dispatcher; a route gets an acting actor when its input schema declares acting?: ActingActor or its auth requires role_grants (role / keeper). Account-grain operations declare neither, so no actor is resolved and actor_id is null: login (also pre-credential), logout, signup, bootstrap, password_change, session/token revoke, app_settings_update, invite events. Role grant events, admin actions, and actor-targeted offers populate this with the initiator's actor.

type Uuid | null

account_id

type Uuid | null

target_account_id

type Uuid | null

target_actor_id

Actor-grain target — populated when the event subject is bound to a specific actor.

Concretely:

  • Always populated: role_grant_revoke and role_grant_create (admin direct-grant, self-service toggle, and in-tx role_grant_offer_accept all populate both target columns — the role_grant's grantee is the actor-grain subject regardless of who initiated the grant), role_grant_offer_accept on accept (the accept binds the actor deterministically), role_grant_offer_decline (the grantor actor — decline is *to* the offering actor).
  • Conditionally populated: offer-shape events (role_grant_offer_create, _expire, _retract, _supersede) carry the actor when the offer was actor-targeted at create time (role_grant_offer.to_actor_id set), null when the offer was account-grain (any actor on to_account_id may accept).
  • Not populated: admin actions, account-shape events (login, logout, signup, bootstrap, password_change, session/token revoke, app_settings_update, invite events) — subject is the account or no specific resource, not an actor-bound role_grant.
  • Not populated: events whose principal isn't an actor-bound resource (e.g. consumer events that name a non-actor scope in metadata).

Multi-actor invariants this column relies on: when both target_actor_id and target_account_id are populated they refer to the same account (actor.account_id-derivable). The invariant holds uniformly across every populated event including decline (the grantor's account is joined into the decline RETURNING) and the supersede cascade (the recipient account is known on role_grant_offer.to_account_id). target_account_id stays the SSE/WS socket-close key because sessions remain account-grain after multi-actor lands.

type Uuid | null

ip

type string | null

created_at

type string

metadata

type Record<string, unknown> | null

AuditLogEventJson
#

auth/audit_log_schema.ts view source

value + type

{ id: string & $brand<"Uuid">; seq: number; event_type: string; outcome: "success" | "failure"; actor_id: (string & $brand<"Uuid">) | null; account_id: (string & $brand<...>) | null; ... 4 more ...; metadata: Record<...> | null; } import {AuditLogEventJson} from '@fuzdev/fuz_app/auth/audit_log_schema.js';

Zod schema for client-safe audit log event.

event_type is AuditEventTypeName (regex-validated string) — matches the AuditLogEvent row and the DB's TEXT NOT NULL column. Consumer types registered via create_audit_log_config({extra_events}) round-trip through queries, on_audit_event callbacks, and JSON-RPC responses identically to builtins. AuditLogInput<T> stays parameterized on the write side so AuditMetadataMap narrowing via get_audit_metadata works.

id

type string & $brand<"Uuid">

seq

type number

event_type

type string

outcome

type "success" | "failure"

actor_id

type (string & $brand<"Uuid">) | null

account_id

type (string & $brand<"Uuid">) | null

target_account_id

type (string & $brand<"Uuid">) | null

target_actor_id

type (string & $brand<"Uuid">) | null

ip

type string | null

created_at

type string

metadata

type Record<string, unknown> | null

AuditLogEventWithUsernamesJson
#

auth/audit_log_schema.ts view source

value + type

{ id: string & $brand<"Uuid">; seq: number; event_type: string; outcome: "success" | "failure"; actor_id: (string & $brand<"Uuid">) | null; account_id: (string & $brand<...>) | null; ... 6 more ...; target_username: string | null; } import {AuditLogEventWithUsernamesJson} from '@fuzdev/fuz_app/auth/audit_log_schema.js';

Zod schema for audit log events with resolved usernames.

id

type string & $brand<"Uuid">

seq

type number

event_type

type string

outcome

type "success" | "failure"

actor_id

type (string & $brand<"Uuid">) | null

account_id

type (string & $brand<"Uuid">) | null

target_account_id

type (string & $brand<"Uuid">) | null

target_actor_id

type (string & $brand<"Uuid">) | null

ip

type string | null

created_at

type string

metadata

type Record<string, unknown> | null

username

type string | null

target_username

type string | null

AuditLogInput
#

auth/audit_log_schema.ts view source

AuditLogInput<T> import type {AuditLogInput} from '@fuzdev/fuz_app/auth/audit_log_schema.js';

Input for creating an audit log entry.

generics

AuditLogInput<T extends string = AuditEventType>
T
constraint string

event_type

type T

outcome?

type AuditOutcome

actor_id?

type Uuid | null

account_id?

type Uuid | null

target_account_id?

type Uuid | null

target_actor_id?

type Uuid | null

ip?

type string | null

metadata?

Per-event-type metadata. Builtin T narrows to AuditMetadataMap[T]; consumer strings widen to a generic record (validation runs against AuditLogConfig.metadata_schemas at insert time).

type T extends "login" | "logout" | "bootstrap" | "signup" | "password_change" | "session_revoke" | "session_revoke_all" | "token_create" | "token_revoke" | "token_revoke_all" | ... 17 more ... | "db_admin_row_delete" ? (AuditMetadataMap[T] & Record<...>) | null : Record<...> | null

AuditLogListInput
#

auth/admin_action_specs.ts view source

value + type

{ event_type?: string | null | undefined; outcome?: "success" | "failure" | null | undefined; account_id?: (string & $brand<"Uuid">) | null | undefined; limit?: number | null | undefined; offset?: number | ... 1 more ... | undefined; since_seq?: number | ... 1 more ... | undefined; acting?: (string & $brand<...>) | ... import {AuditLogListInput} from '@fuzdev/fuz_app/auth/admin_action_specs.js';

Input for audit_log_list. All filter fields are optional — omit for the default newest-first page. since_seq exists for SSE reconnection gap fill (caller supplies the highest seq seen; server returns everything after).

event_type?

type string | null

outcome?

type "success" | "failure" | null

account_id?

type (string & $brand<"Uuid">) | null

limit?

type number | null

offset?

type number | null

since_seq?

type number | null

acting?

type string & $brand<"Uuid">

AuditLogListOptions
#

auth/audit_log_schema.ts view source

AuditLogListOptions import type {AuditLogListOptions} from '@fuzdev/fuz_app/auth/audit_log_schema.js';

Options for listing audit log entries.

limit?

type number

offset?

type number

event_type?

Event-type filter. Accepts any string — builtins or consumer-registered via create_audit_log_config({extra_events}). The DB column is TEXT NOT NULL with no CHECK, so unknown strings simply match nothing.

type string

event_type_in?

type string[]

account_id?

type Uuid

outcome?

type AuditOutcome

since_seq?

When set, only return events with seq greater than this value. Enables SSE reconnection gap fill.

type number

AuditLogListOutput
#

auth/admin_action_specs.ts view source

value + type

{ events: { id: string & $brand<"Uuid">; seq: number; event_type: string; outcome: "success" | "failure"; actor_id: (string & $brand<"Uuid">) | null; account_id: (string & $brand<...>) | null; ... 6 more ...; target_username: string | null; }[]; } import {AuditLogListOutput} from '@fuzdev/fuz_app/auth/admin_action_specs.js';

Output for audit_log_list.

events

type AuditLogEventWithUsernamesJson[]

AuditLogRoleGrantHistoryInput
#

auth/admin_action_specs.ts view source

value + type

{ limit?: number | null | undefined; offset?: number | null | undefined; acting?: (string & $brand<"Uuid">) | undefined; } import {AuditLogRoleGrantHistoryInput} from '@fuzdev/fuz_app/auth/admin_action_specs.js';

Input for audit_log_role_grant_history.

limit?

type number | null

offset?

type number | null

acting?

type string & $brand<"Uuid">

AuditLogRoleGrantHistoryOutput
#

auth/admin_action_specs.ts view source

value + type

{ events: { id: string & $brand<"Uuid">; seq: number; event_type: string; outcome: "success" | "failure"; actor_id: (string & $brand<"Uuid">) | null; account_id: (string & $brand<...>) | null; ... 6 more ...; target_username: string | null; }[]; } import {AuditLogRoleGrantHistoryOutput} from '@fuzdev/fuz_app/auth/admin_action_specs.js';

Output for audit_log_role_grant_history.

events

type RoleGrantHistoryEventJson[]

AuditLogRouteOptions
#

auth/audit_log_routes.ts view source

AuditLogRouteOptions import type {AuditLogRouteOptions} from '@fuzdev/fuz_app/auth/audit_log_routes.js';

Options for audit log route specs.

required_role?

Role required to access audit routes. Default 'admin'.

type string

stream?

When provided, includes an SSE route at /audit/stream for realtime audit events. The route registers each stream on registry under the subscriber's session hash (scope) and account id (groups, with the API token id beside it for a bearer) — the keys close_by_identity() closes on for auth revocation. An AuditLogSse (create_audit_log_sse, or AppServerContext.audit_sse) satisfies it.

type { registry: SubscriberRegistry<SseNotification>; log: Logger; }

AuditLogRpc
#

ui/audit_log_state.svelte.ts view source

AuditLogRpc import type {AuditLogRpc} from '@fuzdev/fuz_app/ui/audit_log_state.svelte.js';

Narrow RPC surface consumed by AuditLogState. Consumers adapt their typed RPC client to this shape. Mirrors AdminAccountsRpc / AdminInvitesRpc. Method signatures track the wire spec inputs/outputs directly so the adapter needs no casts.

list

type (input?: { event_type?: string | null | undefined; outcome?: "success" | "failure" | null | undefined; account_id?: (string & $brand<"Uuid">) | null | undefined; limit?: number | null | undefined; offset?: number | ... 1 more ... | undefined; since_seq?: number | ... 1 more ... | undefined; acting?: (string & $brand<...>) | undefined; } | undefined): Promise<...>

input?

optional
returns Promise<AuditLogListOutput>

role_grant_history

type (input?: { limit?: number | null | undefined; offset?: number | null | undefined; acting?: (string & $brand<"Uuid">) | undefined; } | undefined): Promise<{ events: { id: string & $brand<...>; ... 11 more ...; target_username: string | null; }[]; }>

input?

optional

AuditLogSse
#

realtime/sse_auth_guard.ts view source

AuditLogSse import type {AuditLogSse} from '@fuzdev/fuz_app/realtime/sse_auth_guard.js';

Convenience factory result for audit log SSE.

Satisfies AuditLogRouteOptions['stream'] and provides the combined on_audit_event callback (broadcast + guard).

registry

The subscriber registry the audit stream route registers each stream on (two-phase: subscribe_pending then admit) — pass as part of the stream option to create_audit_log_route_specs. Also exposed for subscriber count monitoring.

type SubscriberRegistry<SseNotification>

log

Logger — pass as part of stream option to create_audit_log_route_specs.

type Logger

on_audit_event

Combined broadcast + guard callback. Wired by create_app_server's audit_log_sse option, or compose inside the consumer's audit_factory body.

type (event: AuditLogEvent): void

event

returns void

required_role

The role the stream requires — what a caller adapting registry passes create_sse_connection_closer, so its role close matches the guard's.

type string

AuditLogState
#

ui/audit_log_state.svelte.ts view source

import {AuditLogState} from '@fuzdev/fuz_app/ui/audit_log_state.svelte.js';

list

type AsyncSlot<void, string>

readonly

role_grant_history

type AsyncSlot<void, string>

readonly

events

type AuditLogEventWithUsernamesJson[]

$state.raw

role_grant_history_events

type RoleGrantHistoryEventJson[]

$state.raw

count

type number

readonly $derived

connected

Whether the SSE stream is currently connected.

type boolean

$state.raw

constructor

type new (options: AuditLogStateOptions): AuditLogState

options

fetch

type (options?: { event_type?: string | null | undefined; outcome?: "success" | "failure" | null | undefined; account_id?: (string & $brand<"Uuid">) | null | undefined; limit?: number | null | undefined; offset?: number | ... 1 more ... | undefined; since_seq?: number | ... 1 more ... | undefined; acting?: (string & $brand<...>) | undefined; } | undefined): Promise<...>

options?

optional
returns Promise<void>

fetch_role_grant_history

type (limit?: number | undefined, offset?: number | undefined): Promise<void>

limit?

type number
optional

offset?

type number
optional
returns Promise<void>

subscribe

Connect to the SSE stream for realtime audit events.

New events are prepended to events. EventSource auto-reconnects on transient errors; since_seq fills gaps on reconnection.

type (): () => void

returns () => void

cleanup function that closes the connection

mutates

  • this

disconnect

Close the SSE connection.

type (): void

returns void

mutates

  • this

AuditLogStateOptions
#

ui/audit_log_state.svelte.ts view source

AuditLogStateOptions import type {AuditLogStateOptions} from '@fuzdev/fuz_app/ui/audit_log_state.svelte.js';

get_rpc

Reactive accessor for the RPC adapter. Matches the get_rpc pattern on AdminAccountsState. The SSE stream uses EventSource directly and is independent of this adapter.

type (): AuditLogRpc

returns AuditLogRpc

stream_url?

SSE stream URL. Defaults to the shipped admin audit-log stream route.

type string

AuditMetadataMap
#

auth/audit_log_schema.ts view source

AuditMetadataMap import type {AuditMetadataMap} from '@fuzdev/fuz_app/auth/audit_log_schema.js';

Mapped type of metadata shapes per event type, derived from Zod schemas.

login

type { [x: string]: unknown; username: string; } | null

logout

type null

bootstrap

type { [x: string]: unknown; error: string; } | null

signup

type { [x: string]: unknown; username: string; invite_id?: (string & $brand<"Uuid">) | undefined; open_signup?: boolean | undefined; reason?: string | undefined; email?: string | undefined; }

password_change

type { [x: string]: unknown; sessions_revoked?: number | undefined; tokens_revoked?: number | undefined; reason?: "concurrent_change" | undefined; credential_type?: "session" | "api_token" | "daemon_token" | undefined; } | null

session_revoke

type { [x: string]: unknown; session_id: string & $brand<"SessionId">; credential_type?: "session" | "api_token" | "daemon_token" | undefined; }

session_revoke_all

type { [x: string]: unknown; count?: number | undefined; reason?: string | undefined; attempted_account_id?: (string & $brand<"Uuid">) | undefined; credential_type?: "session" | "api_token" | "daemon_token" | undefined; }

token_create

type { [x: string]: unknown; token_id: string; name: string; credential_type?: "session" | "api_token" | "daemon_token" | undefined; }

token_revoke

type { [x: string]: unknown; token_id: string; credential_type?: "session" | "api_token" | "daemon_token" | undefined; }

token_revoke_all

type { [x: string]: unknown; count?: number | undefined; reason?: string | undefined; attempted_account_id?: (string & $brand<"Uuid">) | undefined; }

role_grant_create

type { [x: string]: unknown; role: string; role_grant_id?: (string & $brand<"Uuid">) | undefined; scope_kind?: string | null | undefined; scope_id?: (string & $brand<"Uuid">) | null | undefined; source_offer_id?: (string & $brand<...>) | undefined; self_service?: boolean | undefined; }

role_grant_revoke

type { [x: string]: unknown; role: string; role_grant_id: string & $brand<"Uuid">; scope_id?: (string & $brand<"Uuid">) | null | undefined; reason?: string | undefined; self_service?: boolean | undefined; }

role_grant_offer_create

type { [x: string]: unknown; role: string; to_account_id: string & $brand<"Uuid">; offer_id?: (string & $brand<"Uuid">) | undefined; scope_id?: (string & $brand<"Uuid">) | null | undefined; }

role_grant_offer_accept

type { [x: string]: unknown; offer_id: string & $brand<"Uuid">; role: string; role_grant_id?: (string & $brand<"Uuid">) | undefined; scope_id?: (string & $brand<"Uuid">) | null | undefined; reason?: string | undefined; }

role_grant_offer_decline

type { [x: string]: unknown; offer_id: string & $brand<"Uuid">; role: string; scope_id?: (string & $brand<"Uuid">) | null | undefined; reason?: string | undefined; }

role_grant_offer_retract

type { [x: string]: unknown; offer_id: string & $brand<"Uuid">; role: string; scope_id?: (string & $brand<"Uuid">) | null | undefined; }

role_grant_offer_expire

type { [x: string]: unknown; offer_id: string & $brand<"Uuid">; role: string; scope_id?: (string & $brand<"Uuid">) | null | undefined; }

role_grant_offer_supersede

type { [x: string]: unknown; offer_id: string & $brand<"Uuid">; role: string; reason: "sibling_accepted" | "role_grant_revoked" | "scope_destroyed"; cause_id: string & $brand<"Uuid">; scope_id?: (string & $brand<...>) | ... 1 more ... | undefined; }

invite_create

type { [x: string]: unknown; invite_id: string & $brand<"Uuid">; email: string | null; username: string | null; }

invite_delete

type { [x: string]: unknown; invite_id: string & $brand<"Uuid">; }

account_delete

type { [x: string]: unknown; username?: string | undefined; email?: string | null | undefined; reason?: string | undefined; attempted_account_id?: (string & $brand<"Uuid">) | undefined; }

account_purge

type { [x: string]: unknown; username?: string | undefined; email?: string | null | undefined; reason?: string | undefined; attempted_account_id?: (string & $brand<"Uuid">) | undefined; }

account_undelete

type { [x: string]: unknown; username?: string | undefined; email?: string | null | undefined; reason?: string | undefined; attempted_account_id?: (string & $brand<"Uuid">) | undefined; }

actor_delete

type { [x: string]: unknown; name?: string | undefined; }

actor_purge

type { [x: string]: unknown; name?: string | undefined; }

actor_undelete

type { [x: string]: unknown; name?: string | undefined; }

app_settings_update

type { [x: string]: unknown; setting: string; old_value: unknown; new_value: unknown; }

db_admin_row_delete

type { [x: string]: unknown; table: string; pk_column: string; id: string; }

AuditOutcome
#

auth/audit_log_schema.ts view source

value + type

"success" | "failure"

type "success" | "failure"

import {AuditOutcome} from '@fuzdev/fuz_app/auth/audit_log_schema.js';

Zod schema for audit event outcomes.

AuditStreamQuery
#

auth/audit_log_route_schema.ts view source

value + type

{ acting?: (string & $brand<"Uuid">) | undefined; } import {AuditStreamQuery} from '@fuzdev/fuz_app/auth/audit_log_route_schema.js';

Query schema for the audit-log SSE route — multi-actor admins pass ?acting=<uuid>.

acting?

type string & $brand<"Uuid">

AUTH_API_TOKEN_ID_KEY
#

hono_context.ts view source

"auth_api_token_id" import {AUTH_API_TOKEN_ID_KEY} from '@fuzdev/fuz_app/hono_context.js';

Hono context variable name for the authenticated API token id.

auth_integration_truncate_tables
#

testing/db.ts view source

string[] import {auth_integration_truncate_tables} from '@fuzdev/fuz_app/testing/db.js';

Auth tables including audit_log — for integration tests that exercise the full middleware stack (login, admin, rate limiting).

Separate from auth_truncate_tables because unit-level DB tests that don't touch audit logging don't need to truncate it.

AUTH_MIDDLEWARE_PATH
#

auth/middleware.ts view source

"/api/*" import {AUTH_MIDDLEWARE_PATH} from '@fuzdev/fuz_app/auth/middleware.js';

The Hono path pattern the auth middleware stack mounts on by default — session parsing, request context, bearer and daemon token auth all run for the paths it matches, and no others (the origin check runs on every path, ORIGIN_MIDDLEWARE_PATH).

create_app_server mounts the stack here, and refuses at assembly (via assert_endpoint_in_auth_scope, which create_test_app_surface_spec also runs) an rpc_endpoints, ws_endpoints, or bootstrap path the pattern does not match (tested with middleware_applies, which follows Hono: '/api/*' matches /api and everything under /api/): an endpoint outside it would get no session or bearer parsing, so every call or upgrade would be unauthenticated.

AUTH_MIGRATION_NAMESPACE
#

auth/migrations.ts view source

"fuz_auth" import {AUTH_MIGRATION_NAMESPACE} from '@fuzdev/fuz_app/auth/migrations.js';

Namespace identifier for fuz_app auth migrations.

auth_migration_ns
#

auth/migrations.ts view source

MigrationNamespace import {auth_migration_ns} from '@fuzdev/fuz_app/auth/migrations.js';

Pre-composed migration namespace for auth tables.

auth_migrations
#

auth/migrations.ts view source

Migration[]

type Migration[]

import {auth_migrations} from '@fuzdev/fuz_app/auth/migrations.js';

Auth schema migrations in order.

  • v0: Full auth schema — account (with email_verified), actor, role_grant, auth_session, api_token, audit_log (with seq), bootstrap_lock, invite, app_settings, plus all indexes and seeds.
  • v1: role_grant_offer table for consentful grants; adds scope_id / scope_kind / source_offer_id / revoked_reason to role_grant and swaps the (actor_id, role) partial unique index for a scope-aware variant using the index-side 'GLOBAL' token + all-zeros sentinel UUID. The (scope_kind, scope_id) pair is enforced paired-null by role_grant_scope_kind_paired / role_grant_offer_scope_kind_paired CHECK constraints — both null for global, both non-null for scoped. The role_grant_offer table carries a superseded_at terminal state; its partial unique index is scoped by (to_account, role, scope_kind, scope, from_actor) so multiple grantors may coexist. scope_kind is informative-only in v1 (registry-membership validation against create_scope_kind_schema); v2 may add INSERT-time (role, scope_kind) enforcement.

AUTH_SESSION_COLUMNS
#

auth/session_queries.ts view source

readonly ["id", "account_id", "created_at", "expires_at"] import {AUTH_SESSION_COLUMNS} from '@fuzdev/fuz_app/auth/session_queries.js';

The full auth_session column set, named explicitly so a row read fails loud on schema drift — SELECT * would silently carry a dropped or leftover column into the strict-validated wire shapes (see ACCOUNT_COLUMNS in auth/account_queries.ts for the outage class this discipline exists to prevent; the Rust twin already names columns at every session site). Keep in sync with AuthSession and the migration chain's end state — not the frozen v0 DDL in auth/auth_ddl.ts, which still creates last_seen_at for the appended drop migration to remove.

AUTH_SESSION_INDEXES
#

AUTH_SESSION_LIFETIME_MS
#

auth/session_queries.ts view source

number import {AUTH_SESSION_LIFETIME_MS} from '@fuzdev/fuz_app/auth/session_queries.js';

Session lifetime in milliseconds (30 days).

An absolute cap: expires_at is set once at mint and never extended — there is deliberately no touch/renewal query on either spine (a sliding window renews a leaked cookie forever; see docs/security.md §Session Security). The cookie's SESSION_AGE_MAX mirrors this value.

AUTH_SESSION_SCHEMA
#

auth/auth_ddl.ts view source

"\nCREATE TABLE IF NOT EXISTS auth_session (\n id TEXT PRIMARY KEY,\n account_id UUID NOT NULL REFERENCES account(id) ON DELETE CASCADE,\n created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),\n expires_at TIMESTAMPTZ NOT NULL,\n last_seen_at TIMESTAMPTZ NOT NULL DEFAULT NOW()\n)" import {AUTH_SESSION_SCHEMA} from '@fuzdev/fuz_app/auth/auth_ddl.js';

AUTH_SESSION_TOKEN_HASH_KEY
#

auth/request_context.ts view source

"auth_session_token_hash" import {AUTH_SESSION_TOKEN_HASH_KEY} from '@fuzdev/fuz_app/auth/request_context.js';

Hono context variable name for the authenticated session token hash.

Set by create_request_context_middleware after a successful session lookup. null when the request is unauthenticated or authenticated via a non-session credential (bearer token, daemon token). Exposed so handlers can scope per-session resources (e.g., SSE stream identity for targeted disconnection on session_revoke) without re-hashing the token.

auth_state_context
#

ui/auth_state.svelte.ts view source

{ get: (error_message?: string | undefined) => AuthState; get_maybe: () => AuthState | undefined; set: (value: AuthState) => AuthState; } import {auth_state_context} from '@fuzdev/fuz_app/ui/auth_state.svelte.js';

Svelte context for AuthState. Use auth_state_context.set(state) in the provider and auth_state_context.get() to access.

auth_truncate_tables
#

testing/db.ts view source

string[] import {auth_truncate_tables} from '@fuzdev/fuz_app/testing/db.js';

Auth table names in truncation order (children first for FK safety).

Consumer projects can spread this into their own list and append app-specific tables.

AuthActionHandler
#

actions/action_rpc.ts view source

AuthActionHandler<TInput, TOutput> import type {AuthActionHandler} from '@fuzdev/fuz_app/actions/action_rpc.js';

Handler signature for an account-grain RPC action — auth.account === 'required' and auth.actor === 'none'. Mirrors ActionHandler but tightens the ctx.auth slot to the non-null RequestContext (with actor: null).

generics

AuthActionHandler<TInput = any, TOutput = any>
TInput
default any
TOutput
default any

(call)

type (input: TInput, ctx: ActionAuthContext): TOutput | Promise<TOutput>

input

type TInput

ctx

returns TOutput | Promise<TOutput>

AuthAxisState
#

http/auth_shape.ts view source

value + type

"optional" | "none" | "required"

type "optional" | "none" | "required"

import {AuthAxisState} from '@fuzdev/fuz_app/http/auth_shape.js';

Per-axis auth state — names the dispatcher's behavior on account and actor independently:

  • 'none' — explicitly skipped, even when the credential provides it. Public actions (no auth surface) and notifications declare this.
  • 'optional' — surfaced if the credential provides it, null otherwise. Identity-aware reads with anonymous fallback (cell_get-style) declare this on account / actor.
  • 'required' — must be resolved; the dispatcher rejects requests that fail to provide it (401 for account === 'required' without a credential; the authorization phase 4xx for actor === 'required' without an actor binding).

AuthCleanupDeps
#

auth/cleanup.ts view source

AuthCleanupDeps import type {AuthCleanupDeps} from '@fuzdev/fuz_app/auth/cleanup.js';

Dependencies for the cleanup helpers. AppDeps satisfies it structurally, so a server passes backend.deps.

inheritance

extends: QueryDeps

log

type Logger

audit

Bound audit emitter. cleanup_expired_role_grant_offers writes its audit rows in its own transaction and fans each one out through audit.notify after the commit, so listeners never see an event that rolled back. Required — production wiring always has a bound emitter on AppDeps.audit, and tests that need a no-op pass create_test_audit_emitter().

type AuditEmitter

connection_closer

Closes the connections of each session the sweep deletes. Pass the closer the app's revocation handlers hold — AppDeps.connection_closer, the RealtimeCloser over every transport it serves. Required: a sweep with nothing to close through would strand the swept sessions' connections. noop_connection_closer is for a database no other process holds connections on — a sweep run beside a live server from another process deletes the rows that server's own pass would have closed for.

type ConnectionCloser

AuthCleanupResult
#

auth/cleanup.ts view source

AuthCleanupResult import type {AuthCleanupResult} from '@fuzdev/fuz_app/auth/cleanup.js';

Counts from one run_auth_cleanup pass. The Rust AuthCleanupResult is the same shape.

expired_sessions

Number of expired session rows deleted — each one's connections closed.

type number

expired_offers

Number of expired role_grant offer rows audit-stamped.

type number

AuthCleanupScheduleOptions
#

AuthGuardResolver
#

http/route_spec.ts view source

AuthGuardResolver import type {AuthGuardResolver} from '@fuzdev/fuz_app/http/route_spec.js';

Resolves a RouteAuth to middleware guard handlers.

Injected into apply_route_specs to decouple route registration from auth-specific middleware. See fuz_auth_guard_resolver in auth/auth_guard_resolver.ts for the standard implementation.

(call)

type (auth: { account: "optional" | "none" | "required"; actor: "optional" | "none" | "required"; roles?: readonly string[] | undefined; credential_types?: readonly string[] | undefined; required_scope?: string | undefined; }): AuthGuards

auth

returns AuthGuards

AuthGuards
#

http/route_spec.ts view source

AuthGuards import type {AuthGuards} from '@fuzdev/fuz_app/http/route_spec.js';

Two-phase auth guard set returned by AuthGuardResolver.

pre_authorization runs before the authorization phase — the 401 check and the rule-3 token-scope refusal live here, so a caller either one turns away never reaches the actor resolution or the route's body. post_authorization runs after the authorization phase has populated RequestContext — credential / role checks live here because they read c.var.request_context.role_grants.

Both phases run ahead of body validation, which is why neither is named for it: route-shape information stays behind every authority gate. The names say which side of the authorization phase a guard sits on, the only axis that still distinguishes them.

pre_authorization

type MiddlewareHandler[]

post_authorization

type MiddlewareHandler[]

AuthMiddlewareOptions
#

auth/middleware.ts view source

AuthMiddlewareOptions import type {AuthMiddlewareOptions} from '@fuzdev/fuz_app/auth/middleware.js';

Per-factory configuration for the standard auth middleware stack.

allowed_origins

type RegExp[]

session_options

type SessionOptions<string>

path?

Path pattern for the session, request-context, bearer, and daemon-token layers (default: AUTH_MIDDLEWARE_PATH, '/api/*'). The origin check always mounts on every path (ORIGIN_MIDDLEWARE_PATH).

type string

origin_carve_outs?

Paths the origin check skips, each with its reason (default none). Copied at assembly (freeze_origin_carve_outs), then checked by assert_origin_carve_outs_valid — none may overlap path; recorded on the origin spec, so the generated surface leaves the check's 403 off the carved-out routes.

type readonly MiddlewareCarveOut[]

daemon_token_state?

Daemon token state for keeper auth. Omit to disable daemon token middleware.

type DaemonTokenState

AuthorizationFailureBody
#

auth/request_context.ts view source

AuthorizationFailureBody

type { error: "actor_required"; available: { id: string; name: string; }[]; } | { error: "actor_not_on_account"; } | { error: "no_actors_on_account"; } | { error: "account_vanished"; }

import type {AuthorizationFailureBody} from '@fuzdev/fuz_app/auth/request_context.js';

Resolution-failure shape returned by apply_authorization_phase. Each transport binds this to the appropriate wire shape — REST emits the body directly via c.json(body, status); the RPC dispatcher folds it into a JSON-RPC error envelope {jsonrpc, id, error: {code, message, data}}.

The auth phase deliberately stops short of constructing a Response so the same failure flows through every transport without the auth-domain code knowing about JSON-RPC. See ../../../CLAUDE.md §Cleanest architecture takes priority for the rationale.

AuthorizationHandler
#

http/route_spec.ts view source

AuthorizationHandler import type {AuthorizationHandler} from '@fuzdev/fuz_app/http/route_spec.js';

Per-route authorization phase. Runs after the pre-authorization auth guards and before input validation; resolves the acting actor (when `auth.actor !== 'none') from the acting selector — c.var.validated_query.acting` on GETs, read off the raw body on mutations — and sets the request context on the Hono context. Per-route order in apply_route_specs: content type (415) → params → query → pre-authorization auth (401 + rule-3 scope) → authorization phase → post-authorization auth (403) → input validation (400) → handler.

Returns a Response to short-circuit (resolution failure → 400 / 500), or void to continue. The http framework stays auth-agnostic — fuz_app provides the implementation via create_fuz_authorization_handler in auth/request_context.ts.

(call)

type (c: Context<any, any, {}>, spec: RouteSpec): Promise<void | Response>

c

type Context<any, any, {}>

spec

returns Promise<Response | void>

AuthorizationResult
#

auth/request_context.ts view source

AuthorizationResult

type { ok: true; request_context: RequestContext | null; } | { ok: false; status: 500 | 400; body: AuthorizationFailureBody; }

import type {AuthorizationResult} from '@fuzdev/fuz_app/auth/request_context.js';

Result of the authorization phase. Pure data — the auth domain stops short of touching the Hono context or producing a Response so HTTP RPC, WS, and REST each bind the same shape to their wire surface.

  • {ok: true, request_context} — request_context is non-null on resolved (actor-bound or account-only) outcomes; null for public actions ({account: 'none', actor: 'none'}) and for genuine anonymous access on an 'optional' axis. Public and unauthenticated collapse to the same null request_context; every transport already treated them identically.
  • {ok: false, status, body} — 400/500 failure. status is narrowed to the two values the auth phase emits, so Hono's c.json status overload accepts the literals directly. The 500 reasons stay distinct in body: no_actors_on_account (signup invariant violation); account_vanished (torn read after resolve).

AuthSession
#

auth/account_schema.ts view source

{ id: string & $brand<"SessionId">; account_id: string & $brand<"Uuid">; created_at: string; expires_at: string; }

type AuthSessionJson

import type {AuthSession} from '@fuzdev/fuz_app/auth/account_schema.js';

Server-side auth session row, keyed by blake3 hash of the session token. Structurally identical to the wire shape AuthSessionJson — kept as the DB-row name so query signatures keep saying which side of the boundary they read.

id

type string & $brand<"SessionId">

account_id

type string & $brand<"Uuid">

created_at

type string

expires_at

type string

AuthSessionJson
#

auth/account_schema.ts view source

value + type

{ id: string & $brand<"SessionId">; account_id: string & $brand<"Uuid">; created_at: string; expires_at: string; } import {AuthSessionJson} from '@fuzdev/fuz_app/auth/account_schema.js';

Zod schema for AuthSession — id is the blake3 hash, safe for client.

id

type string & $brand<"SessionId">

account_id

type string & $brand<"Uuid">

created_at

type string

expires_at

type string

AuthSessionRouteOptions
#

auth/account_routes.ts view source

AuthSessionRouteOptions import type {AuthSessionRouteOptions} from '@fuzdev/fuz_app/auth/account_routes.js';

Shared options for route factories that create sessions.

Extended by AccountRouteOptions and SignupRouteOptions. Consumers can destructure these from AppServerContext once and spread into multiple factories.

The per-IP limiter is deliberately *not* here. Each auth surface names its own (login_ip_rate_limiter, signup_ip_rate_limiter, bootstrap_ip_rate_limiter) because the buckets are monotone within their window — see RateLimiter.reset. A shared base field made one instance the path of least resistance, which let a failure on any surface spend the budget that bounds guessing on every other one.

session_options

type SessionOptions<string>

AuthState
#

ui/auth_state.svelte.ts view source

import {AuthState} from '@fuzdev/fuz_app/ui/auth_state.svelte.js';

verifying

type boolean

$state.raw

verified

type boolean

$state.raw

verify_error

type string | null

$state.raw

account

type SessionAccount | null

$state.raw

actor

type ActorSummaryJson | null

$state.raw

role_grants

type RoleGrantSummaryJson[]

$state.raw

active_role_grants

The live subset of role_grants, at every scope. Liveness is judged when role_grants is assigned, so a grant that expires while the page stays open reads as active until the next check_session.

type RoleGrantSummaryJson[]

readonly $derived

global_roles

The roles held globally — the role of each active grant whose scope_id is null. A grant scoped to one resource is not in here: it confers its role on that resource only, and a scoped grant of a builtin role confers nothing, so a role gate reads the global grant. Scoped grants are read from active_role_grants, by scope_id.

Shapes the UI only — the server re-checks every request.

type ReadonlySet<string>

readonly $derived

is_admin

True for an active global admin grant, the rule the server's admin gates apply.

type boolean

readonly $derived

needs_bootstrap

True when bootstrap is available (no accounts exist yet).

type boolean

$state.raw

has_global_role

Whether the account holds an active global grant of role — reactive when read in a $derived or the template. A scoped grant doesn't count; see global_roles.

type (role: string): boolean

role

the role to check

type string
returns boolean

check_session

Check auth state and bootstrap availability.

Fetches GET /api/account/status — returns account info (200) or 401 with optional bootstrap_available flag. Called on init, and after login/bootstrap to refresh state.

type (): Promise<void>

returns Promise<void>

mutates

  • this

login

Log in with username and password. Translates 401 / 429 to friendly messages on verify_error; refreshes the session via check_session on success.

type (username: string, password: string): Promise<boolean>

username

type string

password

type string
returns Promise<boolean>

true if login succeeded, false otherwise

mutates

  • this

bootstrap

Bootstrap the first keeper account using a single-use token.

type (token: string, username: string, password: string): Promise<boolean>

token

type string

username

type string

password

type string
returns Promise<boolean>

true if bootstrap succeeded, false otherwise

mutates

  • this

signup

Sign up via invite (or open signup, when app_settings.open_signup is true server-side). Translates 403 / 409 / 429 to friendly messages on verify_error.

type (username: string, password: string, email?: string | undefined): Promise<boolean>

username

type string

password

type string

email?

type string
optional
returns Promise<boolean>

true if signup succeeded, false otherwise

mutates

  • this

logout

Log out — best-effort POST /api/account/logout to clear the session cookie, then clears local state regardless of the network outcome.

type (): Promise<void>

returns Promise<void>

mutates

  • this

AuthTestApps
#

testing/auth_apps.ts view source

AuthTestApps import type {AuthTestApps} from '@fuzdev/fuz_app/testing/auth_apps.js';

Pre-built Hono apps for each auth level, shared across adversarial test suites.

public

type Hono<BlankEnv, BlankSchema, "/">

authed

type Hono<BlankEnv, BlankSchema, "/">

keeper

type Hono<BlankEnv, BlankSchema, "/">

by_credential_type

One authenticated app per builtin credential type, each holding the keeper role — the channel axis, where by_role is the authority axis.

A credential gate is pre-authorization, so probing it needs an app on a channel the route refuses, and proving a gated route still works needs one on a channel it admits. keeper is the 'daemon_token' entry of this map, not a second app.

type Map<CredentialType, Hono<BlankEnv, BlankSchema, "/">>

by_role

type Map<string, Hono<BlankEnv, BlankSchema, "/">>

BackendBootstrapConfig
#

testing/cross_backend/backend_config.ts view source

BackendBootstrapConfig import type {BackendBootstrapConfig} from '@fuzdev/fuz_app/testing/cross_backend/backend_config.js';

Auth-bootstrap configuration for a spawnable test binary. The runner writes token to token_path before launching the child, then POSTs bootstrap_path (default /api/account/bootstrap) with the token plus the username / password to mint the keeper account and capture the session cookie. After health-probe, the runner reads daemon_token_path to load the binary's deterministic daemon token, which default_cross_process_setup threads onto the per-test TestFixture for _testing_reset calls and other keeper-credential operations.

token_path

Path the binary reads for the bootstrap token (env: *_BOOTSTRAP_TOKEN_PATH).

type string

readonly

token

Token text written to token_path before spawn.

type string

readonly

username

Username for the bootstrapped keeper.

type string

readonly

password

Password for the bootstrapped keeper.

type string

readonly

daemon_token_path

Path the test binary writes its daemon-token JSON to on boot (env: *_DAEMON_TOKEN_PATH). spawn_backend reads this file once after the health probe succeeds and threads the token onto BackendHandle.daemon_token for _testing_reset calls plus any other admin/keeper-gated cross-process tests.

type string

readonly

BackendCapabilities
#

testing/cross_backend/capabilities.ts view source

BackendCapabilities import type {BackendCapabilities} from '@fuzdev/fuz_app/testing/cross_backend/capabilities.js';

Optional behaviors a backend may support. Each flag's TSDoc names the tests that gate on it; add a new flag here before referencing it from a suite body, and document the gating tests inline. Wiring facts that gate nothing belong in BackendShapeNotes, not here.

ws

WebSocket transport is reachable end-to-end. Gates the cross-process WS round-trip suite and the per-account connection-cap suite (describe_ws_connection_cap_cross_tests); the in-process describe_ws_round_trip_tests runs against register_action_ws directly and ignores this flag.

type boolean

readonly

sse

SSE transport is reachable end-to-end. Gates the cross-process SSE suite (describe_cross_process_sse_tests — connect, audit data frame, close-on-revoke, the per-session stream cap); in-process SSE uses the on_audit_event hook and ignores this flag.

type boolean

readonly

cell_crud

Cell CRUD verbs (cell_create / cell_get / cell_update / cell_delete / cell_list) are live-mounted on the backend's RPC path and its DB carries the fuz_cell migration namespace. Gates the dedicated describe_cell_crud_cross_tests suite. Like ws / sse, cells stay off the standard declared surface — only this flag opts a backend into the cell parity coverage.

type boolean

readonly

cell_relations

The relation / ACL / audit cell verbs beyond plain CRUD (cell_grant_* / cell_field_* / cell_item_* / cell_clone / cell_audit_list) are live-mounted on the backend's RPC path. Gates the dedicated describe_cell_relations_cross_tests suite — grant lifecycle, field / item bidirectional relations, clone shallow + deep, manage-tier audit gating, and the now-reachable cell_visibility_manage_only 403 (editor-grant principal). Like cell_crud, these stay off the standard declared surface; a backend mounting only plain CRUD declares cell_crud: true, cell_relations: false.

type boolean

readonly

account_lifecycle

The account-lifecycle admin verbs (account_delete soft-delete, account_undelete reactivation, account_purge keeper hard-delete) are live-mounted on the backend's RPC path. Gates the dedicated describe_account_lifecycle_cross_tests suite.

Unlike cells / fact-serving / ws / sse, these verbs are on the standard declared surface — they live in create_admin_actions, so create_spine_surface_spec carries them and the spec-derived round-trip + attack-surface suites already auto-enumerate their wire shape + auth. This flag gates the *behavioral* parity the generic round-trip can't provide: it can't drive verbs that delete their own subject (soft-delete → undelete round-trip, keeper-confirmed purge, the keeper-guard refusal), so the dedicated cross suite adds them.

type boolean

readonly

fact_serving

The cell-gated fact-serving routes (GET /api/cells/:cell_id/facts/:hash + the admin-only GET /api/facts/:hash) are live-mounted on the backend, its DB carries the fuz_facts migration namespace, and it registers the _testing_put_fact seeder. Gates describe_fact_serving_cross_tests — the per-reference read model (cell-scoped admit via a viewable cell, cross-owner-dedup-no-leak, 404-mask, bare-hash admin-only). Like cells, the serve routes stay off the standard declared surface.

type boolean

readonly

ready

The /ready readiness deploy gate (GET /ready) is live-mounted on the backend — the public column-presence schema-drift probe over the committed expected_schema.json. Gates describe_ready_cross_tests (anonymous GET /ready → 200 {ready: true} on a clean spine bootstrap). Like ws/sse/cells, the route stays off the standard declared surface (create_spine_surface_spec); this flag opts a backend into the dedicated readiness parity coverage. The drift → 503 path stays per-impl unit tests.

type boolean

readonly

account_status

The account surface serves GET /api/account/status (account info + bootstrap_available flag). Bundled into create_account_route_specs, so any backend mounting the account routes serves it — true for every spine. Gates the account status response body case in describe_standard_integration_tests: when true the case asserts the route is present (fail-loud on 404, no silent skip); when false it skips explicitly (a backend that deliberately omits the route).

type boolean

readonly

peer_request

The backend can initiate a JSON-RPC request to a connected client and await its typed reply (the server→client request/response direction ActionPeer adds). Gates describe_peer_ping_ws_tests — the on-demand peer/ping round-trip plus its security negatives (unsolicited-response rejection, per-connection id isolation, never-replying Timeout, wrong-shape reply rejection).

true for both the Rust spine (server-initiated requests landed Rust-first canonical) and the TS spine (BackendWebsocketTransport.request_connection drives the round-trip, correlated by register_action_ws). The conservative ts_default_capabilities keeps it false like sse/ready — a TS backend opts in once it mounts peer/ping on a WS endpoint and the HTTP RPC endpoint (the no-transport refusal). Like ws/sse/cells, peer/ping stays off the standard declared surface (it's a protocol action, manifest-excluded), so this flag is the only opt-in into the peer parity coverage.

type boolean

readonly

ws_handshake_pipelining

The backend's HTTP server accepts WebSocket frames written in the same TCP write as the upgrade request, handing them to the socket once the handshake completes. Gates the frames sent with the handshake case in describe_cross_process_ws_tests — the deterministic form of "frames that arrive before the connection is admitted wait for admission", since those frames are in the backend's hands before it can have admitted anything.

true for Node (ws), Deno, and the Rust spine (hyper hands the leftover bytes to the upgraded stream); false for a Bun backend that upgrades after an asynchronous check, as one behind auth does — Bun.serve answers such a request 400 (observed on Bun 1.4.3). A backend without it still runs the suite's client-library case (requests sent the moment the socket opens), which pins the same contract without forcing the ordering.

type boolean

readonly

ws_account_actions

The backend's WebSocket endpoint mounts the self-service account actions (account_session_revoke_all among them), so a socket can revoke the session it is running on. Gates the self-revocation case in describe_cross_process_ws_tests: the socket reads the reply to its request, then is closed with WS_CLOSE_SESSION_REVOKED. Also gates that suite's WS-mutation audit case (with rpc_path; it calls _testing_drain_effects).

true on fuz_app's own spine presets — the TS spine binary mounts its full action set on /api/ws, and the Rust testing_spine_stub serves one action registry on RPC and WS. false in the family defaults: a consumer's WS endpoint carries whatever its domain mounts, so it opts in once the account actions are there.

type boolean

readonly

ws_self_revocation_reply

A socket that revokes the credential it is running on reads the reply to that request before the revocation's close. Read by the self-revocation case in describe_cross_process_ws_tests (which ws_account_actions gates): with it true the case requires the reply frame ahead of the WS_CLOSE_SESSION_REVOKED close; with it false the case requires the close alone.

true for the TS family — register_action_ws sends the response and only then flushes the request's post-commit queue, where the close is. false for the Rust family: its dispatch runs the post-commit queue before handing the response to the socket loop, and the loop, finding the connection already closed, writes the close frame and drops the response. The caller's revocation still took effect; it is not told so. The flag records that divergence so the suite stays green on both spines without hiding it — flip it once the Rust spine answers first.

type boolean

readonly

cell_gated_create

A test CellCreateAuthorize policy is live-mounted on the backend's cell layer — creating a kind: 'gated' cell requires the participant role or admin; every other kind (and a typeless cell) is open. Gates describe_cell_gated_create_cross_tests, the TS↔Rust cell-creation- authorizer parity proof. The authorizer adds no method / column / wire shape, so schema-snapshot + action-manifest parity are blind to an authorizer divergence — this behavioral cross case is the only gate that catches one. true only on the reference spine binaries that mount the test policy (the TS spine full_spine_mount + the Rust testing_spine_stub); consumers and the in-process default app don't mount it.

type boolean

readonly

ws_role_gated

The backend mounts a second, role-gated WebSocket endpoint at SPINE_ROLE_GATED_WS_PATH, gated on SPINE_ROLE_GATED_WS_ROLES, beside its ungated ws_path. Gates describe_ws_role_gated_cross_tests: a revoked global grant of a listed role closes the account's gated socket with WS_CLOSE_PERMISSION_REVOKED, its ungated socket stays open and still receives the account's pushes, a re-upgrade is refused at the gate, and a scoped revoke closes nothing. true only on the reference spine binaries that mount that endpoint (the TS spine binary + the Rust testing_spine_stub).

type boolean

readonly

ws_refusal_body

A refused WebSocket upgrade carries its refusal body — the flat {error, ...} the same refusal answers over plain HTTP. Not a gate: it forks how describe_transport_composition_cross_tests reads its WS rows. true, an empty refusal body fails the case; false, an empty body is allowed (the case pins the status alone) and a non-empty one is still asserted.

false on Node: @hono/node-ws writes a refused upgrade to the raw socket with Content-Length: 0, dropping the response body. true on Deno, Bun, and the Rust spine, which send the response as built.

type boolean

readonly

BackendConfig
#

testing/cross_backend/backend_config.ts view source

BackendConfig import type {BackendConfig} from '@fuzdev/fuz_app/testing/cross_backend/backend_config.js';

Configuration for one spawnable test backend. Consumer factories (deno_backend_config(), rust_backend_config()) produce these and the runner consumes them through spawn_backend.

Path defaults match the standard fuz_app surface — Deno + Rust spine (zzz_server, fuz_forge_server, testing_spine_stub) all converge on /api/account/{bootstrap,login,logout,password}, /api/rpc, /api/ws, /health. Override only when a backend deliberately diverges (which it shouldn't, per the contract).

name

Diagnostic label ("deno", "rust", "spine_stub"). Surfaces in test output.

type string

readonly

start_command

argv passed to the spawn. The first entry is the binary path.

type readonly string[]

readonly

base_url

Base URL for HTTP requests, including port (e.g. http://localhost:8788).

type string

readonly

rpc_path

JSON-RPC endpoint mount point. Default /api/rpc.

type string

readonly

ws_path

WebSocket endpoint mount point. Default /api/ws.

type string

readonly

sse_path?

SSE stream mount point — drives the cross-process SSE suite's stream path. Optional: only backends advertising capabilities.sse serve a stream, and the suite defaults to /api/admin/audit/stream (the standard fuz_app audit-log stream) when omitted. Set it only when a backend mounts its stream elsewhere.

type string

readonly

health_path

Readiness probe path. Default /health.

type string

readonly

bootstrap_path

Bootstrap POST path. Default /api/account/bootstrap.

type string

readonly

cookie_name

Session cookie name the backend issues. Default fuz_session per the ecosystem convergence; consumers using a custom session name (legacy zzz_session, etc.) override. default_cross_process_setup extracts the per-account session value from the transport jar by this name so the cross-process TestAccount.session_cookie matches the in-process shape.

type string

readonly

startup_timeout_ms

How long to wait for the health probe (ms) before giving up.

type number

readonly

env

Env vars merged into the child process. Must include the binary's *_BOOTSTRAP_TOKEN_PATH + *_DAEMON_TOKEN_PATH env var names so the binary reads/writes the right files. Also must include the binary's *_ALLOWED_ORIGINS (typically 'http://localhost:*' for cross-process tests).

type Readonly<Record<string, string>>

readonly

bootstrap

Auth bootstrap details — see BackendBootstrapConfig.

type BackendBootstrapConfig

readonly

capabilities

Capabilities this backend supports — drives test_if(capabilities.X, ...) gating in suite bodies. See testing/cross_backend/capabilities.ts for the vocabulary and existing flags.

type BackendCapabilities

readonly

BackendHandle
#

testing/cross_backend/spawn_backend.ts view source

BackendHandle import type {BackendHandle} from '@fuzdev/fuz_app/testing/cross_backend/spawn_backend.js';

Handle returned by spawn_backend — passed to per-test setup helpers.

config

The config used to spawn this backend. Carried for diagnostic + downstream access.

type BackendConfig

readonly

child

Child process reference — exposed for diagnostic logging only.

type ChildProcess

readonly

daemon_token

Deterministic daemon token captured from config.bootstrap.daemon_token_path after the binary booted. default_cross_process_setup builds keeper-daemon-token headers from this for _testing_reset calls.

type string

readonly

teardown

SIGTERM the child's process group, drain stderr, await exit. Idempotent — calls after the first are no-ops.

type (): Promise<void>

readonly
returns Promise<void>

BackendShapeNotes
#

testing/cross_backend/capabilities.ts view source

BackendShapeNotes import type {BackendShapeNotes} from '@fuzdev/fuz_app/testing/cross_backend/capabilities.js';

Backend wiring facts recorded for documentation — not gating flags.

The companion to BackendCapabilities: where each capability flag is read by a suite (a test_if(capabilities.X, ...) gate, or a fork of one case's assertion), nothing reads these. They record middleware / limiter wiring that differs between the TS and Rust families (a backend-shape record) but gates no cross test today. They live in their own type precisely so BackendCapabilities stops claiming gating power it doesn't have — fold a flag in here the moment it has no test_if reader, and promote it back the day a suite genuinely gates on it.

bearer_auth

Bearer-token auth (Authorization: Bearer <token>) is wired through the backend's middleware stack. true on every spine — the bearer-token cases in describe_standard_integration_tests / describe_rate_limiting_tests run unconditionally.

type boolean

readonly

trusted_proxy

Trusted-proxy XFF parsing (X-Forwarded-For etc.) is wired. Records the proxy-default difference between the TS family (false — the test binary leaves proxy parsing off) and the Rust family (true — the client-IP middleware is always wired; the env-gate only chooses XFF vs the TCP peer).

type boolean

readonly

login_rate_limit

Per-account login rate limiting is wired. false for the TS family (the canonical path leaves the limiter null in test mode), true for the Rust family (env-gated bucket on /login + /password).

type boolean

readonly

BackendWebsocketTransport
#

actions/transports_ws_backend.ts view source

import {BackendWebsocketTransport} from '@fuzdev/fuz_app/actions/transports_ws_backend.js';

inheritance

transport_name

type "backend_websocket_rpc"

readonly

constructor

type new (options?: BackendWebsocketTransportOptions | undefined): BackendWebsocketTransport

options?

optional

throws

  • Error - when `max_connections_per_account` is neither `null` nor a positive integer, or `outbound_queue_bytes` is not a positive integer

register_pending

Register a connection un-admitted — phase one of the two-phase registration an upgrade runs (module doc).

The entry is closeable at once: a close_sockets_for_* matching token_hash / account_id / api_token_id removes it, aborts abort_controller, and closes ws with WS_CLOSE_SESSION_REVOKED (close_sockets_for_role, matching required_roles, with WS_CLOSE_PERMISSION_REVOKED); the later admit then refuses. Until admitted it evicts nothing, is not counted toward the per-account cap, and receives no message.

The caller owns the entry until it is admitted: a refusal closes it with close_connection, and remove_connection drops it once its socket has closed.

Once the transport is closing (close_all_sockets) the registration is born closed: nothing is inserted, abort_controller is aborted, and ws is closed with WS_CLOSE_GOING_AWAY before this returns. The id names nothing, so admit refuses it (module doc, "Shutdown").

type (ws: WSContext<unknown>, token_hash: string | null, account_id: string & $brand<"Uuid">, api_token_id?: string | null, abort_controller?: AbortController | null, required_roles?: readonly string[]): string & $brand<...>

ws

the socket to close on revocation

type WSContext<unknown>

token_hash

blake3 session token hash, or null for non-session credentials

type string | null

account_id

the authenticated account

type Uuid

api_token_id

api_token.id for bearer connections, else null

type string | null
default null

abort_controller

the socket-scoped controller to abort when the transport closes this connection (revocation or cap eviction)

type AbortController | null
default null

required_roles

the roles the endpoint gated the upgrade on, for close_sockets_for_role; empty for an ungated endpoint

type readonly string[]
default []
returns Uuid

the freshly assigned connection_id (branded Uuid)

mutates

  • this — inserts an un-admitted entry into `#connections`, unless the transport is closing

take_pending_close_code

The close code the transport closed a still-pending registration with — WS_CLOSE_SESSION_REVOKED for a credential revocation, WS_CLOSE_PERMISSION_REVOKED for a role close, WS_CLOSE_GOING_AWAY for the shutdown — read once: the record is dropped by this call (and by remove_connection). null when the transport did not close it while pending. For a caller whose admit was refused, to say why; the twin of the reason the Rust ConnectionRegistry::admit refuses with.

type (connection_id: string & $brand<"Uuid">): number | null

connection_id

the id register_pending returned

type Uuid
returns number | null

the close code, or null

mutates

  • this — drops the recorded code

admit

Admit a pending connection — phase two (module doc).

If the entry is gone — a revocation closed it while it was pending, or it was removed — nothing is evicted and the connection is refused. Otherwise the per-account cap is enforced exactly as in add_connection, counting only admitted connections, and the entry becomes admitted: it now receives messages, is counted, and can itself be superseded. Admitting an already-admitted connection is a no-op that answers true.

type (connection_id: string & $brand<"Uuid">): boolean

connection_id

the id register_pending returned

type Uuid
returns boolean

false when the connection is no longer registered — the caller closes the socket as revoked

mutates

  • this — marks the entry admitted; past the cap, first removes the account's oldest admitted connections and closes their `WSContext`

add_connection

Register a connection, admitted at once — the one-step form of register_pending + admit.

Not for an upgrade that authenticated a revocable credential: a revocation landing between that upgrade's credential check and this call closes nothing, and the connection would be admitted on a dead credential (module doc, "Two-phase registration"). This is for a connection with nothing to revoke behind it.

Session connections pass a token hash for targeted revocation. Bearer token connections (api_token) pass the api_token.id so the socket can be closed when that specific token is revoked without tearing down the account's other sockets. Daemon-token connections pass null for both — they're only reachable via close_sockets_for_account.

Enforces the per-account cap (max_connections_per_account): when account_id already holds the maximum, its oldest admitted connections are removed and sent a WS_CLOSE_CONNECTION_LIMIT close before the new one is inserted. A removed connection gets no further broadcast or peer request, stops counting toward the cap at once, and has its abort_controller aborted; its socket is closed when the close handshake completes. The new connection is always admitted — unless the transport is closing: then it is born closed, as in register_pending, and evicts nothing.

type (ws: WSContext<unknown>, token_hash: string | null, account_id: string & $brand<"Uuid">, api_token_id?: string | null, abort_controller?: AbortController | null, required_roles?: readonly string[]): string & $brand<...>

ws

type WSContext<unknown>

token_hash

type string | null

account_id

type Uuid

api_token_id

type string | null
default null

abort_controller

type AbortController | null
default null

required_roles

the roles the endpoint gated the connection on, for close_sockets_for_role; empty for an ungated one

type readonly string[]
default []
returns Uuid

the freshly assigned connection_id (branded Uuid)

mutates

  • this — inserts an admitted entry into `#connections`; past the cap, first removes the account's oldest admitted connections and closes their `WSContext`

remove_connection

Remove a connection and its auth tracking data, pending or admitted, by the id register_pending / add_connection returned. Idempotent — safe to call after revocation or a cap eviction has already cleaned up, and a no-op for an unknown id.

Keyed by id rather than by WSContext because a runtime adapter may hand each socket event its own context object (@hono/bun does), so the one a close event carries can't identify the connection.

Removal only: the socket is not closed and the registered abort_controller is not aborted — the caller that ends a socket does both. What was waiting to be written to it is dropped.

For a socket that has closed (an adapter's close event): on a connection the transport closed it also drops the close deadline, so calling it while that socket is still closing leaves a client that is not reading uncut.

type (connection_id: string & $brand<"Uuid">): void

connection_id

type Uuid
returns void

mutates

  • this — deletes the connection's entry from `#connections`, drops its waiting frames and write deadlines, and settles its pending peer requests as `connection_gone`

close_connection

Close one connection, pending or admitted, with code and reason — the close register_action_ws makes for a heartbeat timeout, an oversized message, a failed on_socket_open, and a refused admission.

The connection is removed and its controller aborted at once; frames still waiting for the socket are handed to it ahead of the close frame while its buffer has room and discarded otherwise, and the close frame is held to WS_CLOSE_WRITE_TIMEOUT (module doc, "Outbound bound"). A socket whose close throws is logged, not thrown.

type (connection_id: string & $brand<"Uuid">, code: number, reason: string): boolean

connection_id

the id register_pending / add_connection returned

type Uuid

code

the close code

type number

reason

the close reason

type string
returns boolean

false when the connection is not registered — nothing was closed

mutates

  • this — removes the connection, aborts its registered controller, and closes its `WSContext`

close_sockets_for_session

Close all sockets associated with a specific session token hash, including a registration still pending admission.

type (token_hash: string): number

token_hash

type string
returns number

the number of sockets closed

mutates

  • this — removes matching connections, aborts their registered controllers, and closes their underlying `WSContext` with `WS_CLOSE_SESSION_REVOKED`

close_sockets_for_account

Close all sockets associated with a specific account, including a registration still pending admission.

type (account_id: string & $brand<"Uuid">): number

account_id

type Uuid
returns number

the number of sockets closed

mutates

  • this — removes matching connections, aborts their registered controllers, and closes their underlying `WSContext` with `WS_CLOSE_SESSION_REVOKED`

close_sockets_for_token

Close all sockets associated with a specific API token, including a registration still pending admission.

Closes just this token's sockets, so revoking one token — or evicting one past the token cap — doesn't tear down the account's session-authenticated sockets or other tokens' sockets.

type (api_token_id: string): number

api_token_id

type string
returns number

the number of sockets closed

mutates

  • this — removes matching connections, aborts their registered controllers, and closes their underlying `WSContext` with `WS_CLOSE_SESSION_REVOKED`

close_sockets_for_role

Close the sockets of account_id whose endpoint required role, including a registration still pending admission — the close for a revoked global grant of a role the upgrade gate admitted on. Each closes with WS_CLOSE_PERMISSION_REVOKED, not a revocation's code: the credential is still good.

Over-closes an any-of gate. An endpoint gated on several roles admits an account holding any one of them, but this closes on the loss of any listed role, without checking whether the account still holds another. The client can reconnect when it still qualifies. The account's sockets on endpoints that did not list role, and on ungated ones, stay open.

type (account_id: string & $brand<"Uuid">, role: string): number

account_id

type Uuid

role

type string
returns number

the number of sockets closed

mutates

  • this — removes matching connections, aborts their registered controllers, and closes their underlying `WSContext` with `WS_CLOSE_PERMISSION_REVOKED`

close_all_sockets

Close every connection, pending or admitted, with WS_CLOSE_GOING_AWAY — the shutdown close AppServer.close runs. Not a revocation: the client reconnects under its ordinary backoff.

Each closed connection is removed and its controller aborted, as a revocation does, so its heartbeat timer stops and nothing more dispatches on it. A socket whose close throws does not stop the loop.

Leaves the transport closing, for good: every later registration is born closed (module doc, "Shutdown"). A second call closes nothing.

type (): number

returns number

the number of sockets closed

throws

  • the - first error a socket's `close` threw, after every connection was attempted

mutates

  • this — sets the transport closing, removes every connection, aborts its registered controller, and closes its underlying `WSContext` with `WS_CLOSE_GOING_AWAY`

is_closing

Whether close_all_sockets has run — the transport is shutting down and every registration from now on is born closed (module doc, "Shutdown"). For telemetry and logs: register_action_ws reads it to say why an upgrade was refused.

type (): boolean

returns boolean

send

type (message: { [x: string]: unknown; jsonrpc: "2.0"; id: string | number; method: string; params?: { [x: string]: unknown; } | undefined; }, options?: TransportSendOptions | undefined): Promise<...>

message

options?

optional
returns Promise<JsonrpcResponseOrError>

broadcast_filtered

Broadcast to admitted connections whose identity satisfies a predicate. A registration pending admission is skipped before the predicate runs.

Used by the broadcast API when a consumer supplies a subscription ACL hook (e.g. an event that only reaches the account that owns the resource it reports). When no ACL is needed, callers should prefer send(message) / #broadcast to skip the per-connection predicate overhead.

A connection the message overflows is closed and not counted (module doc, "Outbound bound").

type (message: { [x: string]: unknown; jsonrpc: "2.0"; id: string | number; result: JSONType; } | { [x: string]: unknown; jsonrpc: "2.0"; id: string | number | null; error: { [x: string]: unknown; code: -32700 | -32600 | -32601 | -32602 | -32603 | (number & $brand<...>); message: string; data?: unknown; }; } | { ...; }, predicate: (identity: ConnectionIdentity) => boolean): number

message

predicate

type (identity: ConnectionIdentity) => boolean
returns number

the number of connections that accepted the message

send_to_account

Send a message to every admitted socket bound to a specific account.

Targeted per-account fan-out for any flow where the delivery target is a single known account. Prefer this over broadcast_filtered when the filter is exactly "this account_id"; reach for broadcast_filtered when the ACL is an arbitrary predicate over ConnectionIdentity.

Mirrors close_sockets_for_account on the send side: every connection for the account (session, bearer, and daemon-token) receives the message.

type (account_id: string & $brand<"Uuid">, message: { [x: string]: unknown; jsonrpc: "2.0"; id: string | number; result: JSONType; } | { [x: string]: unknown; jsonrpc: "2.0"; id: string | number | null; error: { ...; }; } | { ...; }): number

account_id

type Uuid

message

returns number

the number of connections that accepted the message

send_to

Send one serialized frame to a single admitted connection — the path register_action_ws sends every response, error frame, and socket-scoped notification through.

Never waits and never throws. A frame the connection's outbound budget does not fit closes the connection with WS_CLOSE_OUTBOUND_OVERFLOW before this returns (module doc, "Outbound bound").

type (connection_id: string & $brand<"Uuid">, frame: string): boolean

connection_id

the id register_pending / add_connection returned

type Uuid

frame

the frame's text, already serialized

type string
returns boolean

false when the frame was not accepted: the connection is not registered, is still pending admission, or was just closed for the overflow this frame caused

mutates

  • this — queues the frame on the connection's writer; on an overflow removes the connection, aborts its controller, and closes its `WSContext`

request_connection

Initiate a JSON-RPC request to a single connected client and await its reply — the server→client request/response direction (ActionPeer).

Sends {jsonrpc, method, params, id} to exactly the connection_id socket (never a broadcast) and registers a pending entry scoped to that connection. Resolves when the client's matching reply arrives (routed in via resolve_peer_response), the deadline elapses (timeout), the per-connection cap is hit (too_many_in_flight), or the socket closes (connection_gone). A connection still pending admission answers connection_gone like an unknown one. Never throws — every failure is a PeerRequestError.

Delegates correlation to #pending (id allocation, deadline, cap, drain); this method owns only the socket lookup + the send. Server-issued ids are s-prefixed so a malicious client echoing a non-s id (or an id it chose for its own request) matches nothing.

type (connection_id: string & $brand<"Uuid">, method: string, params: { [x: string]: unknown; } | undefined, options?: PeerRequestOptions | undefined): Promise<...>

connection_id

type Uuid

method

type string

params

type JsonrpcRequestParams | undefined

options?

optional
returns Promise<PeerRequestOutcome>

the client's success result, or a PeerRequestError

mutates

  • this — registers then clears an entry in `#pending`

resolve_peer_response

Route an inbound client reply to the matching pending server→client request on connection_id (delegates to #pending.resolve).

Returns false when no entry matches — an unsolicited, cross-connection, or already-settled reply — so the caller drops it. Per-connection scoping means a reply arriving on the wrong socket resolves nothing.

type (connection_id: string & $brand<"Uuid">, response: { [x: string]: unknown; jsonrpc: "2.0"; id: string | number; result: JSONType; } | { [x: string]: unknown; jsonrpc: "2.0"; id: string | number | null; error: { ...; }; }): boolean

connection_id

type Uuid

response

returns boolean

whether a pending request was resolved

mutates

  • this — clears the matched entry from `#pending`

is_registered

Whether a connection is still registered — and admitted: a registration pending admission answers false, like every other reader of the transport.

register_action_ws checks it before dispatching each inbound message, so a socket the server has closed — revoked, superseded by the cap, or refused at admission — dispatches nothing more while its close handshake is still in flight. The twin of the Rust ConnectionRegistry::is_registered.

type (connection_id: string & $brand<"Uuid">): boolean

connection_id

type Uuid
returns boolean

is_ready

type (): boolean

returns boolean

get_connection_count

Number of currently admitted WebSocket connections.

Read-only counter intended for telemetry, logging, and tests. A registration still pending admission is not a connection yet and is counted by get_pending_connection_count instead. Includes connections that have been closed by the peer but not yet removed by the WS adapter's onClose callback.

type (): number

returns number

get_pending_connection_count

Number of registrations awaiting admission — between register_pending and admit (or their refusal). Each lives for one credential re-check, so a count that stays high means upgrades are stalling there. For telemetry and tests.

type (): number

returns number

BackendWebsocketTransportOptions
#

actions/transports_ws_backend.ts view source

BackendWebsocketTransportOptions import type {BackendWebsocketTransportOptions} from '@fuzdev/fuz_app/actions/transports_ws_backend.js';

max_connections_per_account?

Max concurrent connections per account_id, across every credential type (session, API token, daemon token — none exempt). The policy is evict-oldest: a connection past the cap is always admitted, and the account's oldest connections are closed with WS_CLOSE_CONNECTION_LIMIT to make room. Refusing the new one instead would let half-open sockets lock a user out with their own dead connections, and would let a stolen credential fill the slots ahead of the real user — revocation is the fix for a stolen credential, and evict-oldest never stands in its way.

The cap is per transport: endpoints sharing one transport share one count per account. null disables it.

type number | null

default DEFAULT_MAX_CONNECTIONS_PER_ACCOUNT

outbound_queue_bytes?

Byte budget of the frames waiting to be written to one connection. Each waiting frame is charged its UTF-8 length plus OUTBOUND_FRAME_OVERHEAD_BYTES; a frame that does not fit closes the connection with WS_CLOSE_OUTBOUND_OVERFLOW, and a frame is always accepted when nothing is waiting, so one message larger than the whole budget is still deliverable (module doc, "Outbound bound"). Not derived from the inbound message cap.

type number

default DEFAULT_WS_OUTBOUND_QUEUE_BYTES

log?

Logger for cap evictions, outbound overflows and stalls, and failed sends or closes. An overflow or a stall logs one warn line — the connection, its account, and what was waiting. A cap eviction logs at info — the account, how many connections were closed, and the cap — so an operator can see a client that keeps meeting it. Unset falls back to a [ws] logger; null silences it. register_action_ws passes its own logger to the transport it creates.

type Logger | null

baseline
#

db/migrate.ts view source

(db: Db, ns: MigrationNamespace, names: readonly string[]): Promise<void> import {baseline} from '@fuzdev/fuz_app/db/migrate.js';

Insert tracker rows for the named migrations of a namespace without executing them.

Used to promote an existing schema (e.g. produced by an out-of-band bootstrap, or a hand-applied DDL set) into the identity tracker. baseline() trusts the operator-supplied list — it does not verify that the schema actually matches what the named migrations would have produced. Pair with a schema-assertion script post-baseline before re-enabling traffic.

Contract:

  • Creates the schema_version table if missing — a promotion script can call baseline() against a fresh tracker with no separate DDL step.
  • Acquires the same per-namespace advisory lock as run_migrations (with the same try/catch fallback for environments lacking pg_advisory_lock).
  • Refuses if any tracker rows already exist *for this namespace* — lets multi-call baseline scripts resume after partial failure (completed namespaces guard themselves while remaining ones still run).
  • Verifies the supplied names are a strict prefix of the namespace's current migrations array — a name not in the array, or out of order, errors before any INSERT.
  • Writes sequences 0..N-1 in one transaction.

db

the database instance

type Db

ns

the namespace whose migrations are being baselined

names

prefix of ns.migrations[].name to record as already-applied

type readonly string[]

returns

Promise<void>

throws

  • MigrationError - with `kind` of `baseline-name-not-in-code`, `baseline-name-out-of-order`, or `baseline-namespace-already-populated`

mutates

  • schema_version — inserts tracker rows for `names` without running the corresponding migration bodies

BaseServerEnv
#

server/env.ts view source

value + type

{ NODE_ENV: "development" | "production"; PORT: number; HOST: string; DATABASE_URL: string; SECRET_FUZ_COOKIE_KEYS: string; FUZ_ALLOWED_ORIGINS: string; PUBLIC_FUZ_API_URL: string; FUZ_FACTS_DIR: string; ... 6 more ...; FUZ_FACTS_X_ACCEL_REDIRECT_PREFIX?: string | undefined; } import {BaseServerEnv} from '@fuzdev/fuz_app/server/env.js';

Base Zod schema for server environment variables.

Provides the common fields used by fuz apps: server config, database, auth, security, public URLs, and SMTP.

Apps can use directly or extend with app-specific fields via .extend().

NODE_ENV

type "development" | "production"

PORT

type number

HOST

type string

DATABASE_URL

type string

SECRET_FUZ_COOKIE_KEYS

type string

FUZ_ALLOWED_ORIGINS

type string

PUBLIC_FUZ_API_URL

type string

FUZ_FACTS_DIR

type string

PUBLIC_FUZ_WEBSOCKET_URL?

type string

PUBLIC_FUZ_CONTACT_EMAIL?

type string

FUZ_BOOTSTRAP_TOKEN_PATH?

type string

SMTP_HOST?

type string

SMTP_USER?

type string

SMTP_PASSWORD?

type string

FUZ_FACTS_X_ACCEL_REDIRECT_PREFIX?

type string

BearerAuthMocks
#

testing/middleware.ts view source

BearerAuthMocks import type {BearerAuthMocks} from '@fuzdev/fuz_app/testing/middleware.js';

Mocks bundle returned by create_bearer_auth_mocks.

mock_validate

type Mock

mock_find_by_id

type Mock

mock_find_actor_by_id

type Mock

mock_find_actors_by_account

type Mock

mock_find_active_for_actor

type Mock

BearerAuthTestCase
#

testing/middleware.ts view source

BearerAuthTestCase import type {BearerAuthTestCase} from '@fuzdev/fuz_app/testing/middleware.js';

A full test case for the table-driven bearer auth runner.

inheritance

validate_expectation

Whether the request should reach token validation or be short-circuited.

type "called" | "not_called"

assert_account_set?

If true, assert ACCOUNT_ID_KEY was set and CREDENTIAL_TYPE_KEY is 'api_token'.

type boolean

expected_account_id?

Expected ACCOUNT_ID_KEY value when assert_account_set is true.

type string

expected_api_token_id?

If set, assert AUTH_API_TOKEN_ID_KEY was set to this value after a successful bearer auth.

type string

expected_token_scope?

If set, assert TOKEN_SCOPE_KEY was set to this scope after a successful bearer auth.

type TokenScope

expected_credential_deadline_ms?

If set, assert CREDENTIAL_DEADLINE_KEY after a successful bearer auth: null for none (an eternal token), or the milliseconds it should lie ahead, within a second.

type number | null

assert_context_preserved?

If true, assert the pre-existing session ACCOUNT_ID_KEY and credential type are preserved.

type boolean

assert_mocks?

Optional callback for custom spy assertions on the mocks bundle.

type (mocks: BearerAuthMocks): void

mocks

returns void

BearerAuthTestOptions
#

testing/middleware.ts view source

BearerAuthTestOptions import type {BearerAuthTestOptions} from '@fuzdev/fuz_app/testing/middleware.js';

Mock configuration for bearer auth middleware test setup.

name

Test description.

type string

headers?

Request headers.

type Record<string, string>

pre_context?

Pre-set request context (simulates session already resolved).

type RequestContext

mock_validate_result?

What query_validate_api_token() returns.

type unknown

mock_find_by_id_result?

What query_account_by_id() returns.

type unknown

mock_find_actor_by_id_result?

What query_actor_by_id() returns.

type unknown

mock_role_grants_result?

What query_role_grant_find_active_for_actor() returns.

type unknown

expected_status

Expected HTTP status, or 'next' if the middleware should call next().

type number | "next"

expected_error?

Expected error field in JSON response body.

type string

expected_error_schema?

Zod schema to validate error response body against. Defaults to ApiError when expected_error is set.

type ZodType<unknown, unknown, $ZodTypeInternals<unknown, unknown>>

BenchScenario
#

testing/cross_backend/bench/scenario.ts view source

BenchScenario import type {BenchScenario} from '@fuzdev/fuz_app/testing/cross_backend/bench/scenario.js';

One benchmarkable wire scenario. The run body is the Benchmark task fn: it must throw on a non-success response so the benchmark records a failed iteration rather than timing an error path as if it succeeded.

Scenarios should be idempotent — they run thousands of times against a single bootstrapped backend with no reset between iterations. Prefer reads; a mutating scenario must not accumulate unbounded state.

name

Scenario name (groups the per-backend results in the report).

type string

readonly

requires?

Optional capability gate — return false to skip this scenario on a backend that can't serve it (e.g. a WS scenario needs capabilities.ws).

type (capabilities: BackendCapabilities): boolean

readonly

capabilities

returns boolean

run

The timed body. Throws on a non-success response.

type (ctx: BenchScenarioContext): Promise<void>

readonly

ctx

returns Promise<void>

BenchScenarioContext
#

testing/cross_backend/bench/scenario.ts view source

BenchScenarioContext import type {BenchScenarioContext} from '@fuzdev/fuz_app/testing/cross_backend/bench/scenario.js';

transport

Pre-authed transport — the bootstrapped keeper's session cookie jar.

type (url: string, init: RequestInit): Promise<Response>

readonly

url

type string

init

type RequestInit
returns Promise<Response>

rpc_path

RPC endpoint path, e.g. '/api/rpc'.

type string

readonly

capabilities

Declared capabilities of the backend this context targets.

type BackendCapabilities

readonly

Blake3Hash
#

primitive_schemas.ts view source

value + type

string import {Blake3Hash} from '@fuzdev/fuz_app/primitive_schemas.js';

Zod schema for a BLAKE3 hex hash — 64 lowercase hex characters (256-bit output).

BodySizeCrossTestOptions
#

testing/cross_backend/body_size.ts view source

BodySizeCrossTestOptions import type {BodySizeCrossTestOptions} from '@fuzdev/fuz_app/testing/cross_backend/body_size.js';

Options for the body-size parity suite — the standard RPC-dispatched cross-suite shape (setup_test / rpc_path) plus where the backend's socket is. The limit is on every spine, so no case is capability-gated and the flag bundle is not on the shape.

inheritance

base_url

Base URL the spawned backend is reachable at (e.g. http://localhost:1178), or null for an in-process run, which has no socket. Given a URL, the over-cap cases write to the socket directly and send only the bytes the refusal needs; with null they send whole bodies through the fixture's transport.

Pass the URL whenever the fixture's transport reaches a real socket. null there uploads each whole over-cap body through fetch, where the write races the close that follows the 413 and the case fails some of the time.

type string | null

readonly

BodySizeSmugglingCrossTestOptions
#

testing/cross_backend/body_size_smuggling.ts view source

BodySizeSmugglingCrossTestOptions import type {BodySizeSmugglingCrossTestOptions} from '@fuzdev/fuz_app/testing/cross_backend/body_size_smuggling.js';

Options for the smuggling probe — needs the raw URL, not a transport.

base_url

Base URL the spawned backend is reachable at (e.g. http://localhost:1178).

type string

readonly

rpc_path?

RPC endpoint path to target. Default /api/rpc.

type string

readonly

bootstrap
#

testing/transports/bootstrap.ts view source

(options: BootstrapOptions): Promise<BootstrapResult> import {bootstrap} from '@fuzdev/fuz_app/testing/transports/bootstrap.js';

Fire POST {config.bootstrap_path} and capture the keeper session.

options

returns

Promise<BootstrapResult>

throws

  • Error - when the binary refuses bootstrap (non-2xx response) or the body fails to parse as the expected `{account, actor}` envelope. The error carries the status + raw body so a mistyped token / username collision / boot-time DB drift surfaces with enough context to debug.

bootstrap_account
#

auth/bootstrap_account.ts view source

(deps: BootstrapAccountDeps, provided_token: string, input: BootstrapAccountInput): Promise<BootstrapAccountResult> import {bootstrap_account} from '@fuzdev/fuz_app/auth/bootstrap_account.js';

Bootstrap the first account with keeper and admin privileges.

Uses an atomic bootstrap_lock UPDATE to prevent concurrent bootstrap attempts (TOCTOU). The full flow runs in a single transaction:

  1. Read and verify the bootstrap token (before transaction)
  2. Hash the password (CPU-intensive, before transaction)
  3. Acquire the bootstrap lock atomically (inside transaction)
  4. Create account + actor
  5. Grant keeper and admin role_grants (no expiry, granted_by = null)
  6. Delete the token file (after commit, reported via token_file_deleted)

deps

database, token path, filesystem callbacks, and password hashing

provided_token

the bootstrap token from the user

type string

input

username and password

returns

Promise<BootstrapAccountResult>

the created account, actor, and role_grants — or a bootstrap failure

mutates

  • bootstrap_lock row — flips `bootstrapped` to `true` atomically
  • account / actor / role_grant tables — inserts the bootstrap account, actor, and the keeper + admin role_grants
  • filesystem — deletes the bootstrap token file after commit (reported via `token_file_deleted`)

bootstrap_backend
#

testing/cross_backend/bootstrap_backend.ts view source

(config: BackendConfig): Promise<BootstrappedBackendHandle> import {bootstrap_backend} from '@fuzdev/fuz_app/testing/cross_backend/bootstrap_backend.js';

Spawn the test binary described by config, bootstrap a keeper, and return the enriched handle.

The keeper transport is constructed against config.base_url with no initial cookies; bootstrap() populates its jar with the session cookie returned by POST {config.bootstrap_path}. Subsequent calls against bootstrapped.keeper_transport are authenticated as keeper.

Mirrors the composition default_cross_process_setup's caller would otherwise hand-roll in every consumer's globalSetup.

config

returns

Promise<BootstrappedBackendHandle>

BOOTSTRAP_LOCK_SCHEMA
#

auth/auth_ddl.ts view source

"\nCREATE TABLE IF NOT EXISTS bootstrap_lock (\n id INTEGER PRIMARY KEY DEFAULT 1 CHECK (id = 1),\n bootstrapped BOOLEAN NOT NULL DEFAULT false\n)" import {BOOTSTRAP_LOCK_SCHEMA} from '@fuzdev/fuz_app/auth/auth_ddl.js';

BOOTSTRAP_LOCK_SEED
#

auth/auth_ddl.ts view source

"\nINSERT INTO bootstrap_lock (id, bootstrapped)\n SELECT 1, EXISTS(SELECT 1 FROM account)\n ON CONFLICT DO NOTHING" import {BOOTSTRAP_LOCK_SEED} from '@fuzdev/fuz_app/auth/auth_ddl.js';

Seed the bootstrap_lock table, setting bootstrapped based on whether accounts exist.

bootstrap_route_shape
#

auth/bootstrap_route_schema.ts view source

{ method: "POST"; path: string; auth: { account: "none"; actor: "none"; }; mints_credential: true; description: string; transaction: false; input: ZodObject<{ token: ZodString; username: ZodPipe<ZodString, ZodTransform<...>>; password: ZodString; }, $strict>; output: ZodObject<...>; rate_limit: "ip"; errors: { ...; ... import {bootstrap_route_shape} from '@fuzdev/fuz_app/auth/bootstrap_route_schema.js';

The POST /bootstrap route shape minus its handler — pure hono-free data. create_bootstrap_route_specs spreads this and attaches the live handler; surface generation spreads it with a stub handler (handlers are never run during surface assembly, only the shape is read).

bootstrap_test_keeper
#

testing/app_server.ts view source

(options: CreateTestAccountWithCredentialsOptions): Promise<{ account: { id: string & $brand<"Uuid">; username: string; }; actor: { ...; }; api_token: string; session_cookie: string; }> import {bootstrap_test_keeper} from '@fuzdev/fuz_app/testing/app_server.js';

Bootstrap the test-DB keeper. Direct-query shortcut for the default create_test_app path — bootstrap is not what most tests exercise, so we skip the real bootstrap_account flow (no audit row, no on_bootstrap callback). Tests that need the full success-path flow use create_test_app_for_bootstrap instead.

Flips bootstrap_lock.bootstrapped = true so the post-insert DB state matches a real bootstrap completion — production code can trust the lock as the single signal without a belt-and-suspenders query_account_has_any defense.

options

returns

Promise<{ account: { id: string & $brand<"Uuid">; username: string; }; actor: { id: string & $brand<"Uuid">; }; api_token: string; session_cookie: string; }>

mutates

  • the underlying options.db — inserts the account/actor/roles/ — API token/session_cookie rows AND flips `bootstrap_lock.bootstrapped`.

BootstrapAccountDeps
#

auth/bootstrap_account.ts view source

BootstrapAccountDeps import type {BootstrapAccountDeps} from '@fuzdev/fuz_app/auth/bootstrap_account.js';

Dependencies for bootstrap_account.

db

type Db

token_path

Path to the bootstrap token file on disk.

type string

read_secure_file

Hardened secret-file read (see FsSecureReadDeps.read_secure_file) — the token mints the keeper account, so a symlinked, non-regular, group/other-readable, or oversized file must refuse rather than be honored. Throws on refusal; every throw reads as TOKEN_FILE_MISSING upstream.

type (path: string): Promise<Uint8Array<ArrayBufferLike>>

path

type string
returns Promise<Uint8Array<ArrayBufferLike>>

delete_file

Delete a file.

type (path: string): Promise<void>

path

type string
returns Promise<void>

password

Only hashing is needed — verification happens separately during login.

type Pick<PasswordHashDeps, "hash_password">

log

Structured logger instance.

type Logger

BootstrapAccountFailure
#

auth/bootstrap_account.ts view source

BootstrapAccountFailure

type { ok: false; error: "already_bootstrapped"; status: 403; } | { ok: false; error: "token_file_missing"; status: 404; } | { ok: false; error: "invalid_token"; status: 401; }

import type {BootstrapAccountFailure} from '@fuzdev/fuz_app/auth/bootstrap_account.js';

Bootstrap failure result.

BootstrapAccountInput
#

auth/bootstrap_account.ts view source

BootstrapAccountInput import type {BootstrapAccountInput} from '@fuzdev/fuz_app/auth/bootstrap_account.js';

Input for the bootstrap account creation.

username

type string

password

type string

BootstrapAccountResult
#

BootstrapAccountSuccess
#

auth/bootstrap_account.ts view source

BootstrapAccountSuccess import type {BootstrapAccountSuccess} from '@fuzdev/fuz_app/auth/bootstrap_account.js';

Successful bootstrap result with the created entities.

ok

type true

account

type Account

actor

type Actor

role_grants

type { keeper: RoleGrant; admin: RoleGrant; }

token_file_deleted

Whether the bootstrap token file was successfully deleted after account creation.

type boolean

BootstrapDisabledOptions
#

server/app_server.ts view source

BootstrapDisabledOptions import type {BootstrapDisabledOptions} from '@fuzdev/fuz_app/server/app_server.js';

mode

type "disabled"

BootstrapForm
#

ui/BootstrapForm.svelte view source

import BootstrapForm from '@fuzdev/fuz_app/ui/BootstrapForm.svelte';

redirect_on_bootstrap?

Path to navigate to after the first-keeper account is created.

type string
optional default resolve('/')

BootstrapInput
#

auth/bootstrap_route_schema.ts view source

value + type

{ token: string; username: string; password: string; } import {BootstrapInput} from '@fuzdev/fuz_app/auth/bootstrap_route_schema.js';

Input for POST /bootstrap. token is the one-shot token file contents.

token

type string

username

type string

password

type string

BootstrapLiveOptions
#

server/app_server.ts view source

BootstrapLiveOptions import type {BootstrapLiveOptions} from '@fuzdev/fuz_app/server/app_server.js';

mode

type "live"

token_path

type string

route_prefix?

Route prefix for bootstrap routes. Default '/api/account'; must sit under /api/.

type string

on_bootstrap?

Called after successful bootstrap (account + session created). Use for app-specific post-bootstrap work like generating API tokens.

type (result: BootstrapAccountSuccess, c: Context<any, any, {}>): Promise<void>

result

c

type Context<any, any, {}>
returns Promise<void>

BootstrapOptions
#

testing/transports/bootstrap.ts view source

BootstrapOptions import type {BootstrapOptions} from '@fuzdev/fuz_app/testing/transports/bootstrap.js';

Input for bootstrap().

transport

The cookie-threading HTTP transport pointed at the binary. After bootstrap() resolves, the transport carries the keeper session cookie — every later call against it is authenticated as keeper.

type (url: string, init: RequestInit): Promise<Response>

readonly

url

type string

init

type RequestInit
returns Promise<Response>

config

Backend config — used for bootstrap_path plus the bootstrap.username / bootstrap.password / bootstrap.token credentials. The runner already wrote bootstrap.token to bootstrap.token_path before spawning, so the binary picks the token up at startup.

type BackendConfig

readonly

BootstrapOutput
#

auth/bootstrap_route_schema.ts view source

value + type

{ ok: true; account: { id: string & $brand<"Uuid">; username: string; }; actor: { id: string & $brand<"Uuid">; }; } import {BootstrapOutput} from '@fuzdev/fuz_app/auth/bootstrap_route_schema.js';

Output for POST /bootstrap. Session cookie is the operative side effect.

ok

type true

account

type { id: string & $brand<"Uuid">; username: string; }

actor

type { id: string & $brand<"Uuid">; }

BootstrappedBackendHandle
#

testing/cross_backend/setup.ts view source

BootstrappedBackendHandle import type {BootstrappedBackendHandle} from '@fuzdev/fuz_app/testing/cross_backend/setup.js';

Cross-process backend handle enriched with the bootstrapped keeper's captured credentials. Consumers compose this in vitest's globalSetup:

const handle = await spawn_backend(config); const keeper_transport = create_fetch_transport({base_url: config.base_url}); const keeper = await bootstrap({transport: keeper_transport, config}); const bootstrapped: BootstrappedBackendHandle = { ...handle, keeper_transport, keeper_daemon_transport: create_fetch_transport({ base_url: config.base_url, initial_cookies: keeper.cookies, origin: null, }), keeper_account: keeper.account, keeper_actor: keeper.actor, keeper_cookies: keeper.cookies, };

default_cross_process_setup(bootstrapped, options) reads from this shape — the per-test fixture closes over the keeper credentials so cross-process tests can drive admin-RPC / audit-observer flows against the long-lived bootstrapped admin alongside the per-test signup+login account.

inheritance

extends: BackendHandle

keeper_transport

Transport carrying the keeper session cookie + cookie jar.

type (url: string, init: RequestInit): Promise<Response>

readonly

url

type string

init

type RequestInit
returns Promise<Response>

keeper_daemon_transport

Daemon-token transport for the keeper — same cookie jar as keeper_transport but with origin: null so it sends no Origin header. The daemon-token middleware discards the credential in a browser context (any Origin / Referer), so the _testing_* daemon-token calls (_testing_reset / _testing_schema_snapshot / _testing_action_manifest / _testing_mint_session) must route through this Origin-free transport. CSRF/cookie-session calls (invite_create, role_grant_offer_create) stay on keeper_transport, which keeps its default Origin.

type (url: string, init: RequestInit): Promise<Response>

readonly

url

type string

init

type RequestInit
returns Promise<Response>

keeper_account

Keeper account JSON captured from POST /bootstrap.

type { readonly id: string & $brand<"Uuid">; readonly username: string; }

readonly

keeper_actor

Keeper actor JSON captured from POST /bootstrap.

type { readonly id: string & $brand<"Uuid">; }

readonly

keeper_cookies

Raw keeper Set-Cookie values — thread into ws_transport for keeper-authenticated WS upgrades.

type readonly string[]

readonly

BootstrapResult
#

testing/transports/bootstrap.ts view source

BootstrapResult import type {BootstrapResult} from '@fuzdev/fuz_app/testing/transports/bootstrap.js';

The keeper credentials captured from POST /api/account/bootstrap.

transport

Same transport that came in, now carrying the keeper session cookie in its jar. Returned for call-site clarity (callers don't have to remember the mutation happens in place).

type (url: string, init: RequestInit): Promise<Response>

readonly

url

type string

init

type RequestInit
returns Promise<Response>

account

Account JSON returned by POST /bootstrap.

type { readonly id: string & $brand<"Uuid">; readonly username: string; }

readonly

actor

Actor JSON returned by POST /bootstrap.

type { readonly id: string & $brand<"Uuid">; }

readonly

cookies

Raw Set-Cookie values for threading into a WS transport.

type readonly string[]

readonly

BootstrapRouteOptions
#

auth/bootstrap_routes.ts view source

BootstrapRouteOptions import type {BootstrapRouteOptions} from '@fuzdev/fuz_app/auth/bootstrap_routes.js';

Per-factory configuration for bootstrap route specs.

bootstrap_status is runtime state (a mutable ref), not a dep or options value — it is passed through so the route handler can flip it on success.

session_options

type SessionOptions<string>

bootstrap_status

Shared mutable reference — flipped to false after successful bootstrap.

type BootstrapStatus

on_bootstrap?

Called after successful bootstrap (account + session created). Use for app-specific post-bootstrap work like generating API tokens.

type (result: BootstrapAccountSuccess, c: Context<any, any, {}>): Promise<void>

result

c

type Context<any, any, {}>
returns Promise<void>

bootstrap_ip_rate_limiter

Rate limiter for bootstrap attempts, keyed by client IP. Pass null to disable. Its own instance, not login's: bootstrap is one-shot and its bucket is never refunded on success (see RateLimiter.reset), so a fumbled token would otherwise leave the operator's *login* budget nearly spent on a deployment where their new account is the only one that exists. The Rust spine rate-limits bootstrap not at all (the token is 32 bytes of CSPRNG compared in constant time); this is the tighter side of that divergence, kept because the token also sits in a file whose read path an operator can misconfigure.

type RateLimiter | null

BootstrapServerOptions
#

server/app_server.ts view source

BootstrapServerOptions

type BootstrapDisabledOptions | BootstrapSurfaceOnlyOptions | BootstrapLiveOptions

import type {BootstrapServerOptions} from '@fuzdev/fuz_app/server/app_server.js';

Bootstrap configuration for AppServerOptions.bootstrap.

Discriminated union over three deployment intents. Distinct from BootstrapRouteOptions in auth/bootstrap_routes.ts — that one is per-factory runtime state (mutable bootstrap_status ref, rate limiter); this one is the consumer-facing server option that create_app_server reads at startup to decide whether to mount the routes and where.

Three modes:

  • disabled — no route mounted, nothing in /api/surface. Equivalent to omitting bootstrap entirely; the explicit mode is for documentation and reviewable intent at the wiring layer.
  • surface_only — route present, permanent 403 via check_bootstrap_status. For tests asserting on the disabled-but-present wire shape.
  • live — route mounted, real token verification. Success path reachable. token_path is required (non-nullable).

BootstrapStatus
#

auth/bootstrap_routes.ts view source

BootstrapStatus import type {BootstrapStatus} from '@fuzdev/fuz_app/auth/bootstrap_routes.js';

Bootstrap status — runtime state computed once at startup.

available

type boolean

token_path

type string | null

BootstrapSuccessTestOptions
#

testing/bootstrap_success.ts view source

BootstrapSuccessTestOptions import type {BootstrapSuccessTestOptions} from '@fuzdev/fuz_app/testing/bootstrap_success.js';

session_options

type SessionOptions<string>

create_route_specs

Same factory the consumer's production server uses.

type (ctx: AppServerContext): RouteSpec[]

ctx

returns RouteSpec[]

rpc_endpoints?

RPC endpoints — passed through to create_app_server for shape parity.

type RpcEndpointsSuiteOption

bootstrap

Live bootstrap config — the suite drives POST /bootstrap against bootstrap.token_path. The suite does NOT assert on on_bootstrap callback invocation (Hono-coupled signature is in-process only); assertions land on observable DB state.

type BootstrapLiveOptions

bootstrap_token?

Override the synthetic token text. Default deterministic.

type string

BootstrapSurfaceOnlyOptions
#

server/app_server.ts view source

BootstrapSurfaceOnlyOptions import type {BootstrapSurfaceOnlyOptions} from '@fuzdev/fuz_app/server/app_server.js';

mode

type "surface_only"

route_prefix?

Route prefix for surface generation. Default '/api/account'; must sit under /api/.

type string

BootstrapTestKeeperOptions
#

testing/app_server.ts view source

CreateTestAccountWithCredentialsOptions import type {BootstrapTestKeeperOptions} from '@fuzdev/fuz_app/testing/app_server.js';

Alias for the keeper-flavored call site. Same shape.

db

type Db

keyring

type Keyring

session_options

type SessionOptions<string>

password

type PasswordHashDeps

username?

type string

password_value?

type string

roles?

type string[]

email?

Optional email stored on the account row — exercises the username-or-email login lookup.

type string

bounded_string
#

primitive_schemas.ts view source

({ min, max, max_message }: BoundedStringOptions): ZodString import {bounded_string} from '@fuzdev/fuz_app/primitive_schemas.js';

A string schema whose length is bounded in UTF-16 code units — what String.length measures, and what the Rust spine counts (encode_utf16().count()), so an astral-plane character costs two on both. Every .min() / .max() length bound on a wire string in the package is built here, bar a bare .min(1), which reads the same in either count.

Zod's .min() / .max() alone don't pin the unit: depending on the installed Zod they count code units or Unicode code points, and the peer range admits both. The schema still carries them — they are what puts minLength / maxLength in the generated JSON Schema — and a check beside them settles the verdict on String.length: it adds the over-max or under-min issue when Zod raised none, and withdraws one Zod raised for a string that is within the bound in units. The issue it adds has the fields of Zod's own and, like Zod's, lets the checks chained after it run, so a refusal reads the same whichever count Zod used.

Returns a plain ZodString, so format checks, .brand(), .nullish() and .meta() chain as usual. Don't chain a further .min() / .max() — pass the bound here.

__0

returns

ZodString

the bounded string schema

BoundedStringOptions
#

primitive_schemas.ts view source

BoundedStringOptions import type {BoundedStringOptions} from '@fuzdev/fuz_app/primitive_schemas.js';

Length bounds for bounded_string, in UTF-16 code units.

min?

Shortest string admitted. Omit for no lower bound.

type number

max

Longest string admitted.

type number

max_message?

Message for the over-max issue, in place of Zod's default.

type string

BroadcastApi
#

actions/broadcast_api.ts view source

BroadcastApi import type {BroadcastApi} from '@fuzdev/fuz_app/actions/broadcast_api.js';

Loose base shape for a broadcast API. Consumers typically declare a stricter per-method interface (e.g. BackendActionsApi) and pin it via the type parameter on create_broadcast_api.

[key: string]

type (input: never) => Promise<void>

build_account_context
#

auth/request_context.ts view source

(deps: QueryDeps, account_id: string): Promise<RequestContext | null> import {build_account_context} from '@fuzdev/fuz_app/auth/request_context.js';

Build an account-only RequestContext (no actor, no role_grants) from an account id.

Used by the dispatcher's authorization phase for authenticated routes that don't need an acting actor — account-grain operations (logout, password change, account self-service). Lets handlers read auth.account.id / auth.account.username uniformly with role_grant-bound routes; the cost is one extra query_account_by_id per request.

Returns null when the account row is missing (e.g. deleted between the auth middleware's session lookup and the dispatcher) — caller surfaces that as a 500 since it represents a torn read.

deps

query dependencies

account_id

the account to build context for

type string

returns

Promise<RequestContext | null>

an account-only request context, or null if the account is missing

build_action_manifest
#

testing/cross_backend/action_manifest.ts view source

(specs: readonly Pick<{ method: string; initiator: "frontend" | "backend" | "both"; side_effects: boolean; input: ZodType<unknown, unknown, $ZodTypeInternals<unknown, unknown>>; output: ZodType<unknown, unknown, $ZodTypeInternals<unknown, unknown>>; ... 6 more ...; rate_limit?: "both" | ... 2 more ... | undefined; }, "method" | ... 2 more ... | "rate_limit">[]): { ...; } import {build_action_manifest} from '@fuzdev/fuz_app/testing/cross_backend/action_manifest.js';

Build the normalized ActionManifest from a list of request-response specs. Entries are sorted by method so two impls producing the same set serialize identically regardless of mount order. The caller owns the scope it passes (the TS spine passes its full mount; the Rust stub filters PROTOCOL_ACTION_SPECS first) — see the module doc.

specs

type readonly Pick<RequestResponseActionSpec, "method" | "auth" | "side_effects" | "rate_limit">[]

returns

ActionManifest

build_extra_account_fixture
#

testing/cross_backend/setup.ts view source

(seeded: { account: { id: string & $brand<"Uuid">; username: string; }; actor: { id: string & $brand<"Uuid">; }; api_token: string; session_cookie: string; }, cookie_name: string): ExtraAccountFixture import {build_extra_account_fixture} from '@fuzdev/fuz_app/testing/cross_backend/setup.js';

Build an ExtraAccountFixture from a seeded `{account, actor, api_token, session_cookie}` bundle and the session cookie name.

Same shape produced by either path that seeds bootstrap-time secondaries: in-process via create_test_account_with_credentials against the live backend's DB, or cross-process via the _testing_reset RPC's extra_accounts output. Both call this helper so the fixture-side header builders + field plumbing stays in one place.

seeded

type { account: { id: string & $brand<"Uuid">; username: string; }; actor: { id: string & $brand<"Uuid">; }; api_token: string; session_cookie: string; }

cookie_name

type string

returns

ExtraAccountFixture

build_full_spine_mount
#

testing/cross_backend/full_spine_mount.ts view source

(deps: AppDeps, options: FullSpineMountOptions): FullSpineMount import {build_full_spine_mount} from '@fuzdev/fuz_app/testing/cross_backend/full_spine_mount.js';

Build the full live mount for both of the spine binary's endpoints: the RPC list, and the WS list that serves the same actions behind the protocol actions.

The Rust testing_spine_stub serves one action registry on RPC and WS, so mounting the full list on WS keeps the two spines' WS method sets equal — the cell verbs among them, whose dispatcher-charged rate limit describe_ws_action_rate_limit_cross_tests drives over a socket. The one difference is cancel: a registered protocol action on TS, read-loop-owned (never dispatched) on Rust.

deps

the backend AppDeps

type AppDeps

options

daemon-token state + optional WS notification sender

returns

FullSpineMount

the RPC and WS action lists, built once and sharing handler instances

build_full_spine_rpc_actions
#

testing/cross_backend/full_spine_mount.ts view source

(deps: AppDeps, options: FullSpineMountOptions): RpcAction[] import {build_full_spine_rpc_actions} from '@fuzdev/fuz_app/testing/cross_backend/full_spine_mount.js';

Build the complete live RPC action list the spine test binary mounts on its single endpoint: the declared create_standard_rpc_actions bundle plus the off-surface families (_testing_* backdoors, cells, actor resolvers).

Mirrors the previous inline assembly in testing_spine_server.ts exactly — session_options is pinned to spine_session_options (the binary's cookie config) and roles to spine_roles (carrying cell_editor), so the only runtime-varying inputs are the daemon-token state + notification sender.

deps

the backend AppDeps (stub deps suffice for method enumeration)

type AppDeps

options

daemon-token state + optional WS notification sender

returns

RpcAction[]

every RpcAction the binary exposes, in mount order

build_request_context
#

auth/request_context.ts view source

(deps: QueryDeps, account_id: string, actor_id: string): Promise<RequestActorContext | null> import {build_request_context} from '@fuzdev/fuz_app/auth/request_context.js';

Build a full RequestContext from an account id and an explicit actor id (already resolved via resolve_acting_actor).

Loads account + the named actor + the actor's active role_grants. Verifies the actor.account_id === account.id binding so downstream handlers can trust ctx.actor.account_id === ctx.account.id. Returns null when the account is missing, the actor is missing, or the actor doesn't belong to the supplied account.

Called by the route-spec / RPC dispatcher's authorization phase for routes that need an acting actor; account-grain routes use build_account_context instead.

deps

query dependencies

account_id

the account to build context for

type string

actor_id

the actor this request acts as

type string

returns

Promise<RequestActorContext | null>

a request context, or null if account/actor not found or mismatched

build_role_grant_offer_accepted_notification
#

auth/role_grant_offer_notifications.ts view source

(params: { offer: { id: string & $brand<"Uuid">; from_actor_id: string & $brand<"Uuid">; to_account_id: string & $brand<"Uuid">; to_actor_id: (string & $brand<"Uuid">) | null; ... 11 more ...; resulting_role_grant_id: (string & $brand<...>) | null; }; }): { ...; } import {build_role_grant_offer_accepted_notification} from '@fuzdev/fuz_app/auth/role_grant_offer_notifications.js';

params

returns

JsonrpcNotification

build_role_grant_offer_declined_notification
#

auth/role_grant_offer_notifications.ts view source

(params: { offer: { id: string & $brand<"Uuid">; from_actor_id: string & $brand<"Uuid">; to_account_id: string & $brand<"Uuid">; to_actor_id: (string & $brand<"Uuid">) | null; ... 11 more ...; resulting_role_grant_id: (string & $brand<...>) | null; }; }): { ...; } import {build_role_grant_offer_declined_notification} from '@fuzdev/fuz_app/auth/role_grant_offer_notifications.js';

params

returns

JsonrpcNotification

build_role_grant_offer_received_notification
#

auth/role_grant_offer_notifications.ts view source

(params: { offer: { id: string & $brand<"Uuid">; from_actor_id: string & $brand<"Uuid">; to_account_id: string & $brand<"Uuid">; to_actor_id: (string & $brand<"Uuid">) | null; ... 11 more ...; resulting_role_grant_id: (string & $brand<...>) | null; }; }): { ...; } import {build_role_grant_offer_received_notification} from '@fuzdev/fuz_app/auth/role_grant_offer_notifications.js';

params

returns

JsonrpcNotification

build_role_grant_offer_retracted_notification
#

auth/role_grant_offer_notifications.ts view source

(params: { offer: { id: string & $brand<"Uuid">; from_actor_id: string & $brand<"Uuid">; to_account_id: string & $brand<"Uuid">; to_actor_id: (string & $brand<"Uuid">) | null; ... 11 more ...; resulting_role_grant_id: (string & $brand<...>) | null; }; }): { ...; } import {build_role_grant_offer_retracted_notification} from '@fuzdev/fuz_app/auth/role_grant_offer_notifications.js';

params

returns

JsonrpcNotification

build_role_grant_offer_supersede_notification
#

auth/role_grant_offer_notifications.ts view source

(params: { offer: { id: string & $brand<"Uuid">; from_actor_id: string & $brand<"Uuid">; to_account_id: string & $brand<"Uuid">; to_actor_id: (string & $brand<"Uuid">) | null; ... 11 more ...; resulting_role_grant_id: (string & $brand<...>) | null; }; reason: "sibling_accepted" | ... 1 more ... | "scope_destroyed"; cause_id: string & $brand<...>; }): { ...; } import {build_role_grant_offer_supersede_notification} from '@fuzdev/fuz_app/auth/role_grant_offer_notifications.js';

params

returns

JsonrpcNotification

build_role_grant_revoke_notification
#

auth/role_grant_offer_notifications.ts view source

(params: { role_grant_id: string & $brand<"Uuid">; role: string; scope_id: (string & $brand<"Uuid">) | null; reason: string | null; }): { [x: string]: unknown; jsonrpc: "2.0"; method: string; params?: { ...; } | undefined; } import {build_role_grant_revoke_notification} from '@fuzdev/fuz_app/auth/role_grant_offer_notifications.js';

params

returns

JsonrpcNotification

build_test_backend_paths
#

testing/cross_backend/build_test_backend_paths.ts view source

(prefix: string): TestBackendPaths import {build_test_backend_paths} from '@fuzdev/fuz_app/testing/cross_backend/build_test_backend_paths.js';

Build the generic path layout for a cross-process test backend. prefix is typically the BackendConfig.name (e.g. 'deno', 'rust', 'spine_stub').

prefix

type string

returns

TestBackendPaths

BuildTestingAppContext
#

testing/cross_backend/testing_server_core.ts view source

BuildTestingAppContext import type {BuildTestingAppContext} from '@fuzdev/fuz_app/testing/cross_backend/testing_server_core.js';

What the core hands a StartTestingServerOptions.build_app seam.

prepare_websocket

Prepare the runtime adapter's WS upgrade helper and return the factory to pass to create_app_server as create_upgrade_websocket. The core runs the adapter's attach step once the server is bound, so a build that mounts ws_endpoints needs nothing else. An HTTP-only build doesn't call it. Call it at most once per build: the Node adapter attaches one upgrade listener per preparation, and two would race on the shared socket.

type (options?: PrepareWebsocketOptions | undefined): (app: Hono<BlankEnv, BlankSchema, "/">) => UpgradeWebSocket<unknown, any, WSEvents<unknown>>

options?

optional
returns (app: Hono<BlankEnv, BlankSchema, "/">) => UpgradeWebSocket<unknown, any, WSEvents<unknown>>

builtin_audit_log_config
#

auth/audit_log_schema.ts view source

AuditLogConfig import {builtin_audit_log_config} from '@fuzdev/fuz_app/auth/audit_log_schema.js';

Builtin fuz_app audit-log config — every existing event type and its metadata schema.

builtin_credential_type_meta
#

auth/credential_type_schema.ts view source

ReadonlyMap<string, CredentialTypeMeta>

type ReadonlyMap<string, CredentialTypeMeta>

import {builtin_credential_type_meta} from '@fuzdev/fuz_app/auth/credential_type_schema.js';

Builtin credential-type metadata. Not overridable by consumers.

Typed ReadonlyMap for the contract — but JS Maps don't honor Object.freeze for .set / .delete / .clear (they mutate internal slots, not own properties), so freeze adds no runtime guard here. Read once at startup by create_credential_type_schema; runtime mutation has no effect on already-built schemas.

BUILTIN_CREDENTIAL_TYPES
#

auth/credential_type_schema.ts view source

readonly ["session", "api_token", "daemon_token"] import {BUILTIN_CREDENTIAL_TYPES} from '@fuzdev/fuz_app/auth/credential_type_schema.js';

The builtin credential-type names as a const tuple.

builtin_grant_path_meta
#

auth/grant_path_schema.ts view source

ReadonlyMap<string, GrantPathMeta>

type ReadonlyMap<string, GrantPathMeta>

import {builtin_grant_path_meta} from '@fuzdev/fuz_app/auth/grant_path_schema.js';

Builtin grant-path metadata. Not overridable by consumers.

Typed ReadonlyMap for the contract — but JS Maps don't honor Object.freeze for .set / .delete / .clear (they mutate internal slots, not own properties), so freeze adds no runtime guard here. Read once at startup by create_grant_path_schema; runtime mutation has no effect on already-built schemas.

BUILTIN_GRANT_PATHS
#

auth/grant_path_schema.ts view source

readonly ["admin", "self_service", "system", "bootstrap"] import {BUILTIN_GRANT_PATHS} from '@fuzdev/fuz_app/auth/grant_path_schema.js';

The builtin grant-path names as a const tuple.

builtin_role_specs_by_name
#

auth/role_schema.ts view source

ReadonlyMap<string, RoleSpec>

type ReadonlyMap<string, RoleSpec>

import {builtin_role_specs_by_name} from '@fuzdev/fuz_app/auth/role_schema.js';

Builtin role specs, keyed by role name. Not overridable by consumers — read once at startup by create_role_schema and the action factories that fall back to builtins when no consumer roles is supplied. ReadonlyMap encodes the contract; runtime mutation has no effect on already-built role schemas (the factory copies entries into a fresh Map).

BUILTIN_ROLES
#

auth/role_schema.ts view source

readonly ["keeper", "admin"] import {BUILTIN_ROLES} from '@fuzdev/fuz_app/auth/role_schema.js';

The builtin role names as a const tuple.

BuiltinCredentialType
#

auth/credential_type_schema.ts view source

value + type

"session" | "api_token" | "daemon_token"

type "session" | "api_token" | "daemon_token"

import {BuiltinCredentialType} from '@fuzdev/fuz_app/auth/credential_type_schema.js';

Zod enum for builtin credential types only.

BuiltinGrantPath
#

auth/grant_path_schema.ts view source

value + type

"bootstrap" | "admin" | "self_service" | "system"

type "bootstrap" | "admin" | "self_service" | "system"

import {BuiltinGrantPath} from '@fuzdev/fuz_app/auth/grant_path_schema.js';

Zod enum for builtin grant paths only.

BuiltinOfferRefusal
#

auth/role_grant_offer_queries.ts view source

BuiltinOfferRefusal

type "bootstrap_only" | "scoped" | "grantor_not_authorized"

import type {BuiltinOfferRefusal} from '@fuzdev/fuz_app/auth/role_grant_offer_queries.js';

Why query_accept_offer refused to mint a builtin-role grant.

Each is a row no current grant path creates but a database can still hold (a direct query_role_grant_offer_create call, or an offer made under a since-closed path), so accept — the chokepoint where an offer becomes a role_grant — checks them itself.

  • bootstrap_only — the offer is of keeper, which is bootstrap-only: never conferred through an offer, whoever made it.
  • scoped — the offer binds a builtin role to a scope; builtin roles are global-only.
  • grantor_not_authorized — the grantor no longer holds an active global admin on an active actor of an active account. Conferring a builtin role is global-admin authority, so it is re-checked when the grant is minted, not only when the offer was made.

BuiltinRole
#

auth/role_schema.ts view source

value + type

"admin" | "keeper"

type "admin" | "keeper"

import {BuiltinRole} from '@fuzdev/fuz_app/auth/role_schema.js';

Zod schema for builtin roles only.

BuiltTestingApp
#

testing/cross_backend/testing_server_core.ts view source

BuiltTestingApp import type {BuiltTestingApp} from '@fuzdev/fuz_app/testing/cross_backend/testing_server_core.js';

The assembled app a StartTestingServerOptions.build_app seam returns.

app

The assembled Hono app (HTTP routes, RPC, and WS endpoints already mounted).

type Hono<BlankEnv, BlankSchema, "/">

close

Tear down backend(s) + DB + any rotation on graceful shutdown. Called after the listener stops accepting connections and before its drain is awaited, so it must end live WebSockets (AppServer.close does).

type (): Promise<void>

returns Promise<void>

can_edit_cell
#

auth/cell_authorize.ts view source

(auth: RequestContext | null, cell: CellRow, grants: readonly CellGrantRow[] | null): boolean import {can_edit_cell} from '@fuzdev/fuz_app/auth/cell_authorize.js';

Edit authorization for a cell.

Unauthenticated callers can never edit. Admin always allowed.

IMPORTANT: the cell.created_by === null branch is explicit defense-in-depth. NULL created_by means system origin (well-known cells seeded by migration, future daemon/agent cells). Non-admin edits MUST be denied — editor-level grants do NOT bypass this guard, because system cells are policy-controlled at admin level. Do NOT collapse into a single equality check that would silently return false for NULL via JS equality semantics — the explicit branch survives refactors and reads as a load-bearing security property.

auth

request context, or null for unauthenticated callers

type RequestContext | null

cell

the cell row

type CellRow

grants

the cell's grant list, or null to skip the grant branch

type readonly CellGrantRow[] | null

returns

boolean

whether the caller may edit the cell

can_manage_cell
#

auth/cell_authorize.ts view source

(auth: RequestContext | null, cell: CellRow): boolean import {can_manage_cell} from '@fuzdev/fuz_app/auth/cell_authorize.js';

Manage authorization for a cell — admin || owner.

The implicit tier above editor: gates visibility writes and all grant management (cell_grant_create / _list / _revoke). NOT delegable and NOT a grant level — an editor-grant holder is never a manager. Grants are not consulted.

NULL created_by (system origin) has no owner, so manage falls to admin only — the explicit NULL guard lives in is_owner.

auth

request context, or null for unauthenticated callers

type RequestContext | null

cell

the cell row

type CellRow

returns

boolean

whether the caller is in the manage tier for the cell

can_offer_global_role
#

ui/admin_accounts_state.svelte.ts view source

(entry: Pick<{ account: { id: string & $brand<"Uuid">; username: string; email: string | null; email_verified: boolean; created_at: string; updated_at: string; updated_by: (string & $brand<...>) | null; deleted_at: string | null; }; actor: { ...; } | null; role_grants: { ...; }[]; pending_offers: { ...; }[]; }, "role_grants" | "pending_offers">, role: string, now?: Date): boolean import {can_offer_global_role} from '@fuzdev/fuz_app/ui/admin_accounts_state.svelte.js';

Whether a listing row can be offered a global grant of role — the test behind AdminAccounts' + role button, which submit_grant backs with an unscoped offer. Only what the offer would duplicate rules it out: a global grant of the role, or a pending global offer of it. A grant or offer scoped to one resource is a different grant, so it doesn't.

entry

the account's listing row

type Pick<AdminAccountEntryJson, "role_grants" | "pending_offers">

role

the role the button would offer

type string

now

current time for the grant-expiry check (defaults to new Date(), pass for testability); pending offers are taken as listed — the listing returns only unexpired ones

type Date
default new Date()

returns

boolean

can_view_cell
#

auth/cell_authorize.ts view source

(auth: RequestContext | null, cell: CellRow, grants: readonly CellGrantRow[] | null): boolean import {can_view_cell} from '@fuzdev/fuz_app/auth/cell_authorize.js';

View authorization for a cell.

  • Admin: always allowed.
  • cell.visibility === 'public': allowed for everyone, including unauthenticated callers (e.g. a public landing cell).
  • Owner (cell.created_by === auth.actor.id): allowed.
  • Any active grant on the cell admits the caller (actor-shaped: match on actor_id; role-shaped: match on (role, scope_id?) against an active role_grant).
  • Otherwise: false.

auth

request context, or null for unauthenticated callers

type RequestContext | null

cell

the cell row

type CellRow

grants

the cell's grant list, or null to skip the grant branch

type readonly CellGrantRow[] | null

returns

boolean

whether the caller may view the cell

cancel_action
#

actions/cancel.ts view source

Action<{ method: string; initiator: "frontend" | "backend" | "both"; side_effects: boolean; input: ZodType<unknown, unknown, $ZodTypeInternals<unknown, unknown>>; output: ZodType<unknown, unknown, $ZodTypeInternals<unknown, unknown>>; ... 6 more ...; rate_limit?: "both" | ... 2 more ... | undefined; } | { ...; } | {...

type Action<ActionSpecUnion>

import {cancel_action} from '@fuzdev/fuz_app/actions/cancel.js';

Protocol-action tuple — spread into the server's actions array (or via protocol_actions from actions/protocol.ts) so the dispatcher registers the spec for input validation and so create_rpc_client codegen sees the method. The client doesn't need to call it directly; FrontendWebsocketClient.request({signal}) sends the cancel notification automatically when the signal fires.

cancel_action_spec
#

actions/cancel.ts view source

{ method: string; kind: "remote_notification"; initiator: "frontend"; auth: null; side_effects: true; input: ZodObject<{ request_id: ZodUnion<readonly [ZodString, ZodNumber]>; }, $strict>; output: ZodVoid; async: true; description: string; } import {cancel_action_spec} from '@fuzdev/fuz_app/actions/cancel.js';

ActionSpec for the shared cancel. auth: null matches every other remote-notification spec — upgrade-time auth has already admitted the socket, so per-action auth on a fire-and-forget notification is moot. The per-connection {request_id → AbortController} map enforces socket-scoped ownership naturally: a different socket's cancel for the same id misses in its own map.

cancel_handler
#

actions/cancel.ts view source

(): void import {cancel_handler} from '@fuzdev/fuz_app/actions/cancel.js';

Placeholder handler — cancel semantics are owned by register_action_ws, not invoked per-handler. Exported for symmetry with the Action tuple shape; the dispatcher short-circuits cancel notifications before any handler lookup happens.

returns

void

CancelNotificationParams
#

actions/cancel.ts view source

value + type

{ request_id: string | number; } import {CancelNotificationParams} from '@fuzdev/fuz_app/actions/cancel.js';

Params for the cancel notification. request_id is the id of the pending request to abort. Must match the id of a request sent on the same socket; cancels from other sockets (or for unknown ids) are ignored.

request_id

type string | number

canonicalize_ip
#

http/ip_canonical.ts view source

(ip: string): string import {canonicalize_ip} from '@fuzdev/fuz_app/http/ip_canonical.js';

Canonicalize an IP address string.

Returns the RFC 5952 canonical form for parseable IPv4 or IPv6 input. Returns the input unchanged (only lowercased) when the input is non-IP ('unknown'), malformed ('attacker:controlled', '::1\n'), or any string the strict char-set filter rejects.

Idempotent. canonicalize_ip(canonicalize_ip(x)) === canonicalize_ip(x) for every input.

Order-safe for IPv4-mapped IPv6. The ::ffff: prefix strip runs AFTER the canonical emit because the canonical form of an IPv4-mapped IPv6 address is the dotted form (::ffff:127.0.0.1, not ::ffff:7f00:1). Stripping before canonicalize would miss the full-hex form. Closes the normalize_ipv4_mapped_collapse_is_order_safe test from the Rust port.

ip

type string

returns

string

examples

canonicalize_ip('::0001') // → '::1' canonicalize_ip('0:0:0:0:0:0:0:1') // → '::1' canonicalize_ip('2001:0DB8::0001') // → '2001:db8::1' canonicalize_ip('::ffff:127.0.0.1') // → '127.0.0.1' canonicalize_ip('0:0:0:0:0:ffff:7f00:1') // → '127.0.0.1' canonicalize_ip('::ffff:1') // → '::ffff:1' (NOT IPv4-mapped — group[5] is 0, not ffff) canonicalize_ip('127.0.0.1') // → '127.0.0.1' canonicalize_ip('not-an-ip') // → 'not-an-ip' (passes through) canonicalize_ip('::1\n') // → '::1\n' (fails char-set; passes through) canonicalize_ip('203.0.113.1:8080') // → '203.0.113.1:8080' (passes through; validate_ip_strict rejects)

CapabilityGatedCrossSuiteOptions
#

testing/cross_backend/setup.ts view source

CapabilityGatedCrossSuiteOptions import type {CapabilityGatedCrossSuiteOptions} from '@fuzdev/fuz_app/testing/cross_backend/setup.js';

CrossSuiteOptions for a suite that reads capability flags — the ones whose cases are test_if-gated on a per-backend declaration. The field is on this variant rather than the base so a suite's option type states whether it consults capabilities: a caller cannot pass a flag bundle to a suite that ignores it, and a suite cannot quietly stop reading one while still demanding it.

inheritance

capabilities

Backend capability declarations — each suite gates on its own flag.

type BackendCapabilities

readonly

capture_action_manifest
#

testing/cross_backend/setup.ts view source

(handle: ReconstructedBootstrappedBackendHandle): Promise<{ methods: { method: string; side_effects: boolean; account: "optional" | "none" | "required"; actor: "optional" | ... 1 more ... | "required"; roles: string[]; credential_types: string[]; rate_limit: "both" | ... 2 more ... | null; }[]; }> import {capture_action_manifest} from '@fuzdev/fuz_app/testing/cross_backend/setup.js';

Capture a backend's live RPC action manifest over the _testing_action_manifest RPC action (keeper daemon-token channel). The action-surface twin of capture_schema_snapshot — pair two calls with assert_action_manifests_equal (testing/cross_backend/action_manifest_parity.ts) to gate that the TS spine and the Rust testing_spine_stub mount the same method set with the same per-method auth shape. Each impl answers from its own live registry (TS via build_action_manifest, Rust via the fuz_testing mirror); the normalized shapes match by design.

handle

returns

Promise<ActionManifest>

capture_migration_tracker
#

testing/cross_backend/setup.ts view source

(handle: ReconstructedBootstrappedBackendHandle): Promise<{ entries: { namespace: string; name: string; sequence: number; }[]; }> import {capture_migration_tracker} from '@fuzdev/fuz_app/testing/cross_backend/setup.js';

Capture a backend's schema_version migration tracker over the _testing_migration_tracker RPC action (keeper daemon-token channel). The provenance twin of capture_schema_snapshot — where that reads the resulting schema (and excludes the tracker), this reads the tracker rows themselves. Pair two calls with assert_migration_trackers_equal (testing/schema_parity.ts) to gate that the TS spine and the Rust testing_spine_stub record byte-identical migration identity (namespace + name + sequence) — the invariant the schema-snapshot gate is blind to.

handle

returns

Promise<MigrationTracker>

capture_schema_snapshot
#

testing/cross_backend/setup.ts view source

(handle: ReconstructedBootstrappedBackendHandle, options?: { exclude_tables?: readonly string[] | undefined; }): Promise<{ tables: Record<string, { columns: Record<...>; indexes: { ...; }[]; constraints: { ...; }[]; }>; sequences: Record<...>; enums: Record<...>; }> import {capture_schema_snapshot} from '@fuzdev/fuz_app/testing/cross_backend/setup.js';

Capture a backend's schema snapshot over the _testing_schema_snapshot RPC action (keeper daemon-token channel). The canonical way for a cross-impl parity gate to read each backend's live schema — pair two calls with assert_schema_snapshots_equal (testing/schema_parity.ts).

exclude_tables drops documented divergences from both sides before comparison (e.g. a cell-primary Rust backend lacks tables the TS schema has). Each impl answers from its own introspection — TS via query_schema_snapshot, Rust via fuz_db::query_schema_snapshot — and the snapshot shapes match by design.

handle

options

type { exclude_tables?: readonly string[] | undefined; }
default {}

returns

Promise<SchemaSnapshot>

CardinalPosition
#

ui/position_helpers.ts view source

CardinalPosition

type "left" | "right" | "top" | "bottom"

import type {CardinalPosition} from '@fuzdev/fuz_app/ui/position_helpers.js';

Basic position options for UI elements (cardinal directions).

cell_audit_events
#

auth/cell_audit_events.ts view source

Readonly<Record<string, ZodType<unknown, unknown, $ZodTypeInternals<unknown, unknown>>>>

type Readonly<Record<string, ZodType<unknown, unknown, $ZodTypeInternals<unknown, unknown>>>>

import {cell_audit_events} from '@fuzdev/fuz_app/auth/cell_audit_events.js';

Cell-layer event_type → metadata schema map for extra_events.

Covers the seven generic cell verbs' mutation events plus the grant / field / item relation events. Read-only verbs (cell_get, cell_list, cell_*_list, cell_audit_list) emit nothing and are absent here.

cell_audit_list_action_spec
#

auth/cell_audit_action_specs.ts view source

also exported from auth/cell_action_specs.ts

{ method: string; kind: "request_response"; initiator: "frontend"; auth: { account: "required"; actor: "required"; }; side_effects: false; input: ZodObject<{ cell_id: $ZodBranded<ZodUUID, "Uuid", "out">; acting: ZodOptional<...>; }, $strict>; output: ZodObject<...>; async: true; description: string; } import {cell_audit_list_action_spec} from '@fuzdev/fuz_app/auth/cell_audit_action_specs.js';

CELL_AUDIT_LIST_DEFAULT_LIMIT
#

cell_clone_action_spec
#

auth/cell_action_specs.ts view source

{ method: string; kind: "request_response"; initiator: "frontend"; auth: { account: "required"; actor: "required"; }; side_effects: true; input: ZodObject<{ source_id: $ZodBranded<ZodUUID, "Uuid", "out">; deep: ZodOptional<...>; with_data_patch: ZodOptional<...>; acting: ZodOptional<...>; }, $strict>; output: ZodObj... import {cell_clone_action_spec} from '@fuzdev/fuz_app/auth/cell_action_specs.js';

CELL_COLUMNS
#

db/cell_queries.ts view source

readonly ["id", "data", "kind", "visibility", "path", "refs", "parent_id", "root_id", "moderation", "created_at", "updated_at", "deleted_at", "created_by", "updated_by"] import {CELL_COLUMNS} from '@fuzdev/fuz_app/db/cell_queries.js';

The full cell column set, named explicitly so a row read fails loud on schema drift — a SELECT * silently omits a dropped column and the deleted_at IS NULL-shaped predicates on the hydrated row then misread undefined (see ACCOUNT_COLUMNS in auth/account_queries.ts for the outage class). Column order mirrors the Rust twin's cell_columns(alias) (fuz_cell), which decodes the same projection positionally. The derived grant_count is not a column — cell_row_projection appends it. Keep in sync with CellRow and the cell DDL in db/cell_ddl.ts.

cell_create_action_spec
#

auth/cell_action_specs.ts view source

{ method: string; kind: "request_response"; initiator: "frontend"; auth: { account: "required"; actor: "required"; }; side_effects: true; input: ZodObject<{ data: ZodObject<{ label: ZodOptional<ZodString>; summary: ZodOptional<...>; }, $loose>; ... 4 more ...; acting: ZodOptional<...>; }, $strict>; output: ZodObject... import {cell_create_action_spec} from '@fuzdev/fuz_app/auth/cell_action_specs.js';

cell_delete_action_spec
#

auth/cell_action_specs.ts view source

{ method: string; kind: "request_response"; initiator: "frontend"; auth: { account: "required"; actor: "required"; }; side_effects: true; input: ZodObject<{ cell_id: $ZodBranded<ZodUUID, "Uuid", "out">; acting: ZodOptional<...>; }, $strict>; output: ZodObject<...>; async: true; description: string; } import {cell_delete_action_spec} from '@fuzdev/fuz_app/auth/cell_action_specs.js';

CELL_DROP_TABLES
#

db/cell_ddl.ts view source

readonly ["cell_field", "cell_item", "cell_grant", "cell"] import {CELL_DROP_TABLES} from '@fuzdev/fuz_app/db/cell_ddl.js';

Tables created by CELL_MIGRATION_NS, in drop order (children first).

CELL_EDITOR_ROLE
#

CELL_FIELD_COLUMNS
#

cell_field_delete_action_spec
#

auth/cell_field_action_specs.ts view source

also exported from auth/cell_action_specs.ts

{ method: string; kind: "request_response"; initiator: "frontend"; auth: { account: "required"; actor: "required"; }; side_effects: true; input: ZodObject<{ source_id: $ZodBranded<ZodUUID, "Uuid", "out">; name: $ZodBranded<...>; acting: ZodOptional<...>; }, $strict>; output: ZodObject<...>; async: true; description:... import {cell_field_delete_action_spec} from '@fuzdev/fuz_app/auth/cell_field_action_specs.js';

CELL_FIELD_INDEXES
#

db/cell_ddl.ts view source

string[] import {CELL_FIELD_INDEXES} from '@fuzdev/fuz_app/db/cell_ddl.js';

cell_field indexes.

  • PK on (source_id, name) covers forward lookup ("what does this cell point to via field X?") and the per-source fields list.
  • idx_cell_field_target covers reverse lookup ("which cells link to this target?").

Soft-delete is filtered by JOIN at the read boundary; no partial indexes here on deleted_at (would force index churn on cell soft-delete toggles, and the join filter is sufficient).

cell_field_list_action_spec
#

auth/cell_field_action_specs.ts view source

also exported from auth/cell_action_specs.ts

{ method: string; kind: "request_response"; initiator: "frontend"; auth: { account: "optional"; actor: "optional"; }; side_effects: false; input: ZodObject<{ source_id: ZodOptional<$ZodBranded<ZodUUID, "Uuid", "out">>; target_id: ZodOptional<...>; name_after: ZodOptional<...>; limit: ZodOptional<...>; acting: ZodOpt... import {cell_field_list_action_spec} from '@fuzdev/fuz_app/auth/cell_field_action_specs.js';

CELL_FIELD_NAME_REGEX
#

auth/cell_field_action_specs.ts view source

RegExp import {CELL_FIELD_NAME_REGEX} from '@fuzdev/fuz_app/auth/cell_field_action_specs.js';

Field name grammar — fuz snake_case identifier convention. Anchored ^[a-z][a-z0-9_]{0,63}$: leading letter, alphanumeric + underscore trailing, 64-char cap. No reserved names yet.

CELL_FIELD_SCHEMA
#

db/cell_ddl.ts view source

"\nCREATE TABLE IF NOT EXISTS cell_field (\n\tsource_id UUID NOT NULL REFERENCES cell(id) ON DELETE CASCADE,\n\tname TEXT NOT NULL,\n\ttarget_id UUID NOT NULL REFERENCES cell(id) ON DELETE CASCADE,\n\tcreated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),\n\tPRIMARY KEY (source_id, name)\n)" import {CELL_FIELD_SCHEMA} from '@fuzdev/fuz_app/db/cell_ddl.js';

cell_field table — named relation ((source_id, name) → target_id). One target per name per source — JSON-object keys are unique. Multiplicity is expressed by composition (foo.tags = collection_cell whose items[] are the tags), not by allowing duplicate (source_id, name) rows.

cell_field_set_action_spec
#

auth/cell_field_action_specs.ts view source

also exported from auth/cell_action_specs.ts

{ method: string; kind: "request_response"; initiator: "frontend"; auth: { account: "required"; actor: "required"; }; side_effects: true; input: ZodObject<{ source_id: $ZodBranded<ZodUUID, "Uuid", "out">; name: $ZodBranded<...>; target_id: $ZodBranded<...>; acting: ZodOptional<...>; }, $strict>; output: ZodObject<..... import {cell_field_set_action_spec} from '@fuzdev/fuz_app/auth/cell_field_action_specs.js';

cell_get_action_spec
#

auth/cell_action_specs.ts view source

{ method: string; kind: "request_response"; initiator: "frontend"; auth: { account: "optional"; actor: "optional"; }; side_effects: false; input: ZodObject<{ id: ZodOptional<$ZodBranded<ZodUUID, "Uuid", "out">>; path: ZodOptional<...>; acting: ZodOptional<...>; }, $strict>; output: ZodObject<...>; async: true; rate_... import {cell_get_action_spec} from '@fuzdev/fuz_app/auth/cell_action_specs.js';

CELL_GRANT_COLUMNS
#

db/cell_grant_queries.ts view source

readonly ["id", "cell_id", "level", "actor_id", "role", "scope_id", "granted_by", "created_at"] import {CELL_GRANT_COLUMNS} from '@fuzdev/fuz_app/db/cell_grant_queries.js';

The full cell_grant column set, named explicitly so a row read fails loud on schema drift (see CELL_COLUMNS in db/cell_queries.ts). Column order mirrors the Rust twin's grant_columns(alias) (fuz_cell). Keep in sync with CellGrantRow and the cell_grant DDL in db/cell_ddl.ts.

cell_grant_create_action_spec
#

auth/cell_grant_action_specs.ts view source

also exported from auth/cell_action_specs.ts

{ method: string; kind: "request_response"; initiator: "frontend"; auth: { account: "required"; actor: "required"; }; side_effects: true; input: ZodObject<{ cell_id: $ZodBranded<ZodUUID, "Uuid", "out">; level: ZodEnum<...>; principal: ZodDiscriminatedUnion<...>; acting: ZodOptional<...>; }, $strict>; output: ZodObje... import {cell_grant_create_action_spec} from '@fuzdev/fuz_app/auth/cell_grant_action_specs.js';

CELL_GRANT_INDEXES
#

db/cell_ddl.ts view source

string[] import {CELL_GRANT_INDEXES} from '@fuzdev/fuz_app/db/cell_ddl.js';

cell_grant indexes.

  • idx_cell_grant_cell: forward lookup ("who has access to this cell?").
  • idx_cell_grant_actor: reverse lookup ("which cells does this actor have access to?").
  • idx_cell_grant_role_scope: reverse lookup for role-shaped principals.
  • idx_cell_grant_unique_actor: prevents duplicate actor-shaped grants for the same cell. Re-granting updates level via UPSERT on this index.
  • idx_cell_grant_unique_role_scope: same, for role-shaped grants. NULLS NOT DISTINCT so two rows with the same (cell_id, role) and scope_id IS NULL collide — without it, default NULL-distinct semantics would let duplicate null-scope role grants slip past the re-share UPSERT path. Requires PostgreSQL 15+ (pglite tracks PG 16).

cell_grant_list_action_spec
#

auth/cell_grant_action_specs.ts view source

also exported from auth/cell_action_specs.ts

{ method: string; kind: "request_response"; initiator: "frontend"; auth: { account: "required"; actor: "required"; }; side_effects: false; input: ZodObject<{ cell_id: $ZodBranded<ZodUUID, "Uuid", "out">; acting: ZodOptional<...>; }, $strict>; output: ZodObject<...>; async: true; description: string; } import {cell_grant_list_action_spec} from '@fuzdev/fuz_app/auth/cell_grant_action_specs.js';

cell_grant_revoke_action_spec
#

auth/cell_grant_action_specs.ts view source

also exported from auth/cell_action_specs.ts

{ method: string; kind: "request_response"; initiator: "frontend"; auth: { account: "required"; actor: "required"; }; side_effects: true; input: ZodObject<{ grant_id: $ZodBranded<ZodUUID, "Uuid", "out">; acting: ZodOptional<...>; }, $strict>; output: ZodObject<...>; async: true; description: string; } import {cell_grant_revoke_action_spec} from '@fuzdev/fuz_app/auth/cell_grant_action_specs.js';

CELL_GRANT_SCHEMA
#

db/cell_ddl.ts view source

"\nCREATE TABLE IF NOT EXISTS cell_grant (\n\tid UUID PRIMARY KEY DEFAULT gen_random_uuid(),\n\tcell_id UUID NOT NULL REFERENCES cell(id) ON DELETE CASCADE,\n\tlevel TEXT NOT NULL CHECK (level IN ('viewer', 'editor')),\n\tactor_id UUID REFERENCES actor(id) ON DELETE CASCADE,\n\trole TEXT,\n\tscope_id UUID,\n\tgrante... import {CELL_GRANT_SCHEMA} from '@fuzdev/fuz_app/db/cell_ddl.js';

cell_grant table — resource-side ACL for cells. Each row admits a principal (actor or (role, scope_id)) at a level (viewer or editor). Owner is implicit (cell.created_by); the table never carries owner rows.

The single-principal arm is actor-grain (actor_id FK); the other arm is role-shaped ((role, scope_id)). The CHECK enforces exactly one arm.

CELL_HISTORY_DROP_TABLES
#

CELL_HISTORY_INDEXES
#

db/cell_history_ddl.ts view source

string[] import {CELL_HISTORY_INDEXES} from '@fuzdev/fuz_app/db/cell_history_ddl.js';

Cell-history indexes.

  • idx_cell_history_cell: per-cell timeline reads (newest first).
  • idx_cell_history_fact: fact → cells reverse lookup for GC liveness and provenance queries.

CELL_HISTORY_MIGRATION_NAMESPACE
#

db/cell_history_ddl.ts view source

"fuz_cell_history" import {CELL_HISTORY_MIGRATION_NAMESPACE} from '@fuzdev/fuz_app/db/cell_history_ddl.js';

Namespace identifier for cell-history migrations.

CELL_HISTORY_MIGRATION_NS
#

CELL_HISTORY_MIGRATIONS
#

CELL_HISTORY_SCHEMA
#

db/cell_history_ddl.ts view source

"\nCREATE TABLE IF NOT EXISTS cell_history (\n\tid BIGSERIAL PRIMARY KEY,\n\tcell_id UUID NOT NULL REFERENCES cell(id) ON DELETE CASCADE,\n\tfact_hash TEXT NOT NULL,\n\taction_id UUID,\n\tcreated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()\n)" import {CELL_HISTORY_SCHEMA} from '@fuzdev/fuz_app/db/cell_history_ddl.js';

cell_history table — append-only log of cell snapshot references.

CELL_INDEXES
#

db/cell_ddl.ts view source

string[] import {CELL_INDEXES} from '@fuzdev/fuz_app/db/cell_ddl.js';

Cell indexes — all active-only, partial on deleted_at IS NULL.

  • idx_cell_active: active-cell list/scan ordered by creation.
  • idx_cell_path_unique: global path uniqueness + read-side path lookup. Partial on path + active so reused paths after soft delete are allowed.
  • idx_cell_kind: the cell_list kind filter (cell.kind = ?) and kind-scoped scans. Active-only.
  • idx_cell_data: shape-driven queries (`data

CELL_ITEM_COLUMNS
#

cell_item_delete_action_spec
#

auth/cell_item_action_specs.ts view source

also exported from auth/cell_action_specs.ts

{ method: string; kind: "request_response"; initiator: "frontend"; auth: { account: "required"; actor: "required"; }; side_effects: true; input: ZodObject<{ parent_id: $ZodBranded<ZodUUID, "Uuid", "out">; position: $ZodBranded<...>; acting: ZodOptional<...>; }, $strict>; output: ZodObject<...>; async: true; descript... import {cell_item_delete_action_spec} from '@fuzdev/fuz_app/auth/cell_item_action_specs.js';

CELL_ITEM_INDEXES
#

db/cell_ddl.ts view source

string[] import {CELL_ITEM_INDEXES} from '@fuzdev/fuz_app/db/cell_ddl.js';

cell_item indexes.

  • PK on (parent_id, position) covers ordered scans for the per-parent items list (SELECT ... ORDER BY position).
  • idx_cell_item_child covers reverse lookup ("which parents contain this child?").

cell_item_insert_action_spec
#

auth/cell_item_action_specs.ts view source

also exported from auth/cell_action_specs.ts

{ method: string; kind: "request_response"; initiator: "frontend"; auth: { account: "required"; actor: "required"; }; side_effects: true; input: ZodObject<{ parent_id: $ZodBranded<ZodUUID, "Uuid", "out">; child_id: $ZodBranded<...>; position: $ZodBranded<...>; acting: ZodOptional<...>; }, $strict>; output: ZodObject... import {cell_item_insert_action_spec} from '@fuzdev/fuz_app/auth/cell_item_action_specs.js';

cell_item_list_action_spec
#

auth/cell_item_action_specs.ts view source

also exported from auth/cell_action_specs.ts

{ method: string; kind: "request_response"; initiator: "frontend"; auth: { account: "optional"; actor: "optional"; }; side_effects: false; input: ZodObject<{ parent_id: ZodOptional<$ZodBranded<ZodUUID, "Uuid", "out">>; child_id: ZodOptional<...>; position_after: ZodOptional<...>; limit: ZodOptional<...>; acting: Zod... import {cell_item_list_action_spec} from '@fuzdev/fuz_app/auth/cell_item_action_specs.js';

cell_item_move_action_spec
#

auth/cell_item_action_specs.ts view source

also exported from auth/cell_action_specs.ts

{ method: string; kind: "request_response"; initiator: "frontend"; auth: { account: "required"; actor: "required"; }; side_effects: true; input: ZodObject<{ parent_id: $ZodBranded<ZodUUID, "Uuid", "out">; position: $ZodBranded<...>; new_position: $ZodBranded<...>; acting: ZodOptional<...>; }, $strict>; output: ZodOb... import {cell_item_move_action_spec} from '@fuzdev/fuz_app/auth/cell_item_action_specs.js';

CELL_ITEM_SCHEMA
#

db/cell_ddl.ts view source

"\nCREATE TABLE IF NOT EXISTS cell_item (\n\tparent_id UUID NOT NULL REFERENCES cell(id) ON DELETE CASCADE,\n\tposition TEXT NOT NULL,\n\tchild_id UUID NOT NULL REFERENCES cell(id) ON DELETE CASCADE,\n\tcreated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),\n\tPRIMARY KEY (parent_id, position)\n)" import {CELL_ITEM_SCHEMA} from '@fuzdev/fuz_app/db/cell_ddl.js';

cell_item table — ordered child membership keyed by an opaque fractional-indexing string. (parent_id, position) PK enforces one cell per slot; the same child_id may appear at multiple positions (the primitive is JSON-array-shaped — ordered multiset, not set). Domain dedup rules ride on top in helpers.

CELL_KIND_LENGTH_MAX
#

auth/cell_action_specs.ts view source

64 import {CELL_KIND_LENGTH_MAX} from '@fuzdev/fuz_app/auth/cell_action_specs.js';

Maximum length of a cell kind on the wire, in UTF-16 code units (what String.length measures; enforced by bounded_string) — on cell_create, and on the cell_list filter. The cell layer imposes no grammar on kind; the bound is there because the tag is copied into every audit row the cell's mutations write. The same bound a cell field name carries (CELL_FIELD_NAME_REGEX), the cell layer's other machine-readable tag, and the same value as the Rust spine's CELL_KIND_LENGTH_MAX.

cell_list_action_spec
#

auth/cell_action_specs.ts view source

{ method: string; kind: "request_response"; initiator: "frontend"; auth: { account: "optional"; actor: "optional"; }; side_effects: false; input: ZodDefault<ZodObject<{ ids: ZodOptional<ZodArray<$ZodBranded<ZodUUID, "Uuid", "out">>>; ... 12 more ...; acting: ZodOptional<...>; }, $strict>>; output: ZodObject<...>; as... import {cell_list_action_spec} from '@fuzdev/fuz_app/auth/cell_action_specs.js';

CELL_LIST_LIMIT_DEFAULT
#

CELL_LIST_LIMIT_MAX
#

auth/cell_action_specs.ts view source

200 import {CELL_LIST_LIMIT_MAX} from '@fuzdev/fuz_app/auth/cell_action_specs.js';

Soft cap on the size of a cell.list request page. Larger pages chew memory both server- and client-side; combined with the visibility predicate's filter cost, 200 is a safe ceiling.

CELL_MIGRATION_NAMESPACE
#

db/cell_ddl.ts view source

"fuz_cell" import {CELL_MIGRATION_NAMESPACE} from '@fuzdev/fuz_app/db/cell_ddl.js';

Namespace identifier for cell migrations.

CELL_MIGRATION_NS
#

CELL_MIGRATIONS
#

cell_moderate_action_spec
#

auth/cell_action_specs.ts view source

{ method: string; kind: "request_response"; initiator: "frontend"; auth: { account: "required"; actor: "required"; }; side_effects: true; input: ZodObject<{ cell_id: $ZodBranded<ZodUUID, "Uuid", "out">; moderation: ZodEnum<...>; acting: ZodOptional<...>; }, $strict>; output: ZodObject<...>; async: true; rate_limit: ... import {cell_moderate_action_spec} from '@fuzdev/fuz_app/auth/cell_action_specs.js';

CELL_PATH_LENGTH_MAX
#

auth/cell_action_specs.ts view source

256 import {CELL_PATH_LENGTH_MAX} from '@fuzdev/fuz_app/auth/cell_action_specs.js';

Wire form for cell.path.

At the spec level we only enforce that the value is a non-empty string with a sane upper bound — the cell layer is generic and doesn't impose a path grammar. App-side curation (well-known names like /map/main, /site/events) is admin-driven. The bound is in UTF-16 code units.

CELL_RELATIONS_BUNDLE_LIMIT
#

auth/cell_action_specs.ts view source

500 import {CELL_RELATIONS_BUNDLE_LIMIT} from '@fuzdev/fuz_app/auth/cell_action_specs.js';

Hard cap on bundled relation arrays in cell_get (per-relation LIMIT). Beyond this the response sets *_truncated: true and the client paginates via cell_item_list({parent_id, position_after}) / cell_field_list({source_id, name_after}).

CELL_ROLE_HOLDER_USERNAME
#

cell_row_projection
#

db/cell_queries.ts view source

(alias: string): string import {cell_row_projection} from '@fuzdev/fuz_app/db/cell_queries.js';

The full CellRow projection — CELL_COLUMNS qualified by alias plus the derived grant_count (a correlated subquery against cell_grant, served by idx_cell_grant_cell) — used by every cell-row SELECT / RETURNING. alias is the row qualifier: the table name ('cell') for single-table reads + RETURNING, the query alias ('c') for query_cell_list. Twin of fuz_cell's cell_columns(alias).

::int narrows the count from bigint so the JS row hydrates a number rather than a bigint primitive — counts on a single cell are trivially within int32.

Exported for consumers writing their own cell-row reads: CELL_COLUMNS alone hydrates a row without grant_count, which typechecks as a CellRow but fails at to_cell_json — this is the projection that actually produces one.

alias

type string

returns

string

CELL_SCHEMA
#

db/cell_ddl.ts view source

"\nCREATE TABLE IF NOT EXISTS cell (\n\tid UUID PRIMARY KEY DEFAULT gen_random_uuid(),\n\tdata JSONB NOT NULL,\n\tkind TEXT,\n\tvisibility cell_visibility NOT NULL DEFAULT 'private',\n\tpath TEXT,\n\trefs TEXT[],\n\tparent_id UUID REFERENCES cell(id) ON DELETE SET NULL,\n\troot_id UUID REFERENCES cell(id) ON DELETE ... import {CELL_SCHEMA} from '@fuzdev/fuz_app/db/cell_ddl.js';

cell table — universal content primitive: identity + content only. Parent→child membership lives in cell_item; named relations live in cell_field. Includes the created_by / updated_by ownership columns.

visibility is the access-control axis — private (default) is restricted to admin / owner / cell_grant-admitted callers; public admits everyone, including unauthenticated visitors. Lives as a top-level column so the auth predicate reads off the row directly rather than reaching into data.

path is the global namespace axis (no tenant/hub scoping) — globally unique on active rows via idx_cell_path_unique.

kind is the capability / identity axis — a nullable top-level column (peer to visibility / path), not a field inside data. It is the discriminator a creation authorizer gates on (see auth/cell_actions.ts CellCreateAuthorize) and is write-once: set at INSERT and carried on no update path, so a cell's kind is fixed at birth. Content stays duck-typed in data; kind is a capability tag, not a content-type.

parent_id / root_id are the directory tree (containment): parent_id is the immediate container (nullable self-FK; NULL = a root), root_id is the governing root denormalized for flat-subtree queries (`root_id = parent.root_id ?? parent.id, so a root has NULL`). Both are set once at create and immutable in v1 (carried on no update path). moderation (nullable text — pending / approved / rejected; NULL = unmoderated) is the approval-lifecycle marker, peer to visibility (a control field with a non-author writer — see auth/cell_actions.ts), never inside data.

cell_update_action_spec
#

auth/cell_action_specs.ts view source

{ method: string; kind: "request_response"; initiator: "frontend"; auth: { account: "required"; actor: "required"; }; side_effects: true; input: ZodObject<{ cell_id: $ZodBranded<ZodUUID, "Uuid", "out">; data: ZodOptional<...>; visibility: ZodOptional<...>; path: ZodOptional<...>; acting: ZodOptional<...>; }, $strict... import {cell_update_action_spec} from '@fuzdev/fuz_app/auth/cell_action_specs.js';

CELL_VISIBILITY_TYPE
#

db/cell_ddl.ts view source

"\nDO $$ BEGIN\n\tCREATE TYPE cell_visibility AS ENUM ('private', 'public');\nEXCEPTION WHEN duplicate_object THEN NULL;\nEND $$" import {CELL_VISIBILITY_TYPE} from '@fuzdev/fuz_app/db/cell_ddl.js';

cell_visibility enum — access-control axis for a cell. Lives as a top-level column (not inside data) because visibility is access control, not content metadata. cell_grant is the other ACL surface; keeping visibility as a peer column (not a JSON field) co-locates access-control state and lets the planner reason about it directly.

Ships with two states ('private', 'public'); a third (unlisted / public-link) folds in via ALTER TYPE when public-link sharing lands.

Wrapped in a DO block so the migration can replay idempotently — CREATE TYPE has no IF NOT EXISTS variant in PostgreSQL.

CellActionDeps
#

auth/cell_actions.ts view source

CellActionDeps

type ActionFactoryDeps & { validate_data?: ((data: { [x: string]: unknown; label?: string | undefined; summary?: string | undefined; }) => { [x: string]: unknown; label?: string | undefined; summary?: string | undefined; }) | undefined; authorize_create?: CellCreateAuthorize | undefined; }

import type {CellActionDeps} from '@fuzdev/fuz_app/auth/cell_actions.js';

Dependencies for create_cell_actions.

validate_data is the optional sub-API hook for per-kind shape validation (e.g., a collection/entry registry). It runs on every incoming data payload (create, update, clone-merged) and may throw a ZodError — the handler converts that into the standard invalid_params JSON-RPC error so per-kind validation failures surface to clients with code -32602 (not -32603 / internal). When omitted, payloads pass through as-is.

authorize_create is the optional creation-gate hook (see CellCreateAuthorize). When omitted, create is open (today's behavior).

log

Structured logger instance.

type Logger

audit

Bound audit emitter for fire-and-forget audit writes.

type AuditEmitter

validate_data?

type (data: { [x: string]: unknown; label?: string | undefined; summary?: string | undefined; }): { [x: string]: unknown; label?: string | undefined; summary?: string | undefined; }

data

returns CellData

authorize_create?

type (auth: RequestActorContext, input: CellCreateAuthorizeInput): CellCreateVerdict | Promise<CellCreateVerdict>

auth

input

CellAuditEventJson
#

auth/cell_audit_action_specs.ts view source

value + type

{ id: string & $brand<"Uuid">; seq: number; event_type: string; outcome: "success" | "failure"; actor_id: (string & $brand<"Uuid">) | null; created_at: string; } import {CellAuditEventJson} from '@fuzdev/fuz_app/auth/cell_audit_action_specs.js';

Wire shape for a single cell-audit row. Narrower than AuditLogEventJson — account_id and target_account_id are deliberately omitted so this verb does NOT surface the actor↔account join. target_actor_id and metadata are dropped too: target_actor_id is NULL for every cell-domain event (the grant recipient lives inside metadata.principal on grant rows, not on the audit-log top-level field); metadata is unread by the timeline UI.

ip is also omitted: it is PII about the actors who touched the cell, and even at the manage tier this per-cell timeline has no need for it (admins reach the full audit_log surface, which carries ip, through the admin audit verbs). Keeping it off this wire avoids leaking collaborators' IPs to a cell's owner.

All omitted fields can be re-added under a richer admin-only event-detail view later — keep the wire surface honest about what consumers use.

id

type string & $brand<"Uuid">

seq

type number

event_type

type string

outcome

type "success" | "failure"

actor_id

type (string & $brand<"Uuid">) | null

created_at

type string

CellAuditListInput
#

auth/cell_audit_action_specs.ts view source

value + type

{ cell_id: string & $brand<"Uuid">; acting?: (string & $brand<"Uuid">) | undefined; } import {CellAuditListInput} from '@fuzdev/fuz_app/auth/cell_audit_action_specs.js';

cell_id

type string & $brand<"Uuid">

acting?

type string & $brand<"Uuid">

CellAuditListOptions
#

db/cell_audit_queries.ts view source

CellAuditListOptions import type {CellAuditListOptions} from '@fuzdev/fuz_app/db/cell_audit_queries.js';

limit

type number

before?

Cursor — return rows with seq < before.

type number

CellAuditListOutput
#

auth/cell_audit_action_specs.ts view source

value + type

{ events: { id: string & $brand<"Uuid">; seq: number; event_type: string; outcome: "success" | "failure"; actor_id: (string & $brand<"Uuid">) | null; created_at: string; }[]; } import {CellAuditListOutput} from '@fuzdev/fuz_app/auth/cell_audit_action_specs.js';

events

type CellAuditEventJson[]

CellAuditMetadata
#

auth/cell_audit_metadata.ts view source

value + type

{ [x: string]: unknown; cell_id: string & $brand<"Uuid">; kind?: string | undefined; path?: string | null | undefined; } import {CellAuditMetadata} from '@fuzdev/fuz_app/auth/cell_audit_metadata.js';

Shared metadata envelope for cell mutations. kind and path are captured at emit-time so the audit-log viewer can show useful context for soft-deleted rows even after the cell snapshot is gone. Relation membership is tracked independently via the cell_item_* / cell_field_* per-row audit events.

Loose object: per-kind handlers may extend the metadata without spec churn.

cell_id

type string & $brand<"Uuid">

kind?

type string

path?

type string | null

[key: string]

type unknown

CellCloneAuditMetadata
#

auth/cell_audit_metadata.ts view source

value + type

{ [x: string]: unknown; source_id: string & $brand<"Uuid">; new_id: string & $brand<"Uuid">; deep: boolean; item_count: number; kind?: string | undefined; } import {CellCloneAuditMetadata} from '@fuzdev/fuz_app/auth/cell_audit_metadata.js';

Metadata envelope for cell_clone.

source_id and new_id capture the parent → clone edge. deep flags whether children were walked. item_count reports the number of children actually cloned (post-skip). kind is captured at emit-time so an operator can filter the audit log by source shape (e.g., "every collection clone").

No skipped-child count is recorded: surfacing how many children the caller couldn't view would leak the source's hidden-child count to the cloner (who owns — and can audit — the clone). Non-viewable children are dropped silently (D8).

source_id

type string & $brand<"Uuid">

new_id

type string & $brand<"Uuid">

deep

type boolean

item_count

type number

kind?

type string

[key: string]

type unknown

CellCloneInput
#

auth/cell_action_specs.ts view source

value + type

{ source_id: string & $brand<"Uuid">; deep?: boolean | undefined; with_data_patch?: { [x: string]: unknown; label?: string | undefined; summary?: string | undefined; } | undefined; acting?: (string & $brand<...>) | undefined; } import {CellCloneInput} from '@fuzdev/fuz_app/auth/cell_action_specs.js';

Input for cell_clone. Source must be view-admitted by can_view_cell (404 otherwise — IDOR mask). The clone is owned by the caller; path is always nulled (admin-only paths can't auto-clone). Provenance lives only in the cell_clone audit row's source_id — no provenance fields are stamped into data.

source_id

type string & $brand<"Uuid">

deep?

type boolean

with_data_patch?

type CellData

acting?

type string & $brand<"Uuid">

CellCloneOutput
#

auth/cell_action_specs.ts view source

value + type

{ cell: { id: string & $brand<"Uuid">; path: (string & $brand<"CellPath">) | null; data: { [x: string]: unknown; label?: string | undefined; summary?: string | undefined; }; kind: string | null; ... 10 more ...; grant_count: number; }; } import {CellCloneOutput} from '@fuzdev/fuz_app/auth/cell_action_specs.js';

cell

type CellJson

CellCreateAuthorize
#

auth/cell_actions.ts view source

CellCreateAuthorize import type {CellCreateAuthorize} from '@fuzdev/fuz_app/auth/cell_actions.js';

Opt-in, parent-aware creation authorizer — the TS twin of the Rust CellCreateAuthorize trait. Gates both roots (parent_id = null) and contributions; answers "may *this actor* create *this kind* here?" and returns a CellCreateVerdict. Runs in cell_create after validate_data and after the handler resolves root_id from the parent (an unviewable parent already 404-masks before this runs). Omitted = today's open create (all consumers untouched). Async-capable (DB / policy calls) — a DB-backed impl closes over its own db (the create handler stays unaware).

(call)

type (auth: RequestActorContext, input: CellCreateAuthorizeInput): CellCreateVerdict | Promise<CellCreateVerdict>

auth

input

CellCreateAuthorizeInput
#

auth/cell_actions.ts view source

CellCreateAuthorizeInput import type {CellCreateAuthorizeInput} from '@fuzdev/fuz_app/auth/cell_actions.js';

Input to a CellCreateAuthorize callback — the TS twin of the Rust CellCreateAuthorizeInput. Parent-aware: it carries the directory context (parent_id / the handler-resolved root_id) so the authorizer can resolve the governing root's policy.

kind

The cell's kind (the top-level cell.kind value); null for a typeless cell.

type string | null

data

The cell data (kind-free — a kind key is rejected upstream). For richer M3-era policies (e.g. content pre-screen).

type CellData

parent_id

The immediate container the create targets. null = a root creation; otherwise a contribution under that parent.

type Uuid | null

root_id

The governing root of the directory subtree, resolved by the handler from the parent (parent.root_id ?? parent.id). null for a root creation.

type Uuid | null

root_data

The governing root's data — the handler reads it in-tx (when an authorizer is mounted) and hands it over, so a directory-aware authorizer resolves root.data.policy[kind] without a DB read of its own (pure predicate; reading in-tx avoids the single-connection PGlite deadlock a separate handle would hit). null for a root creation, or when no authorizer is mounted.

type CellData | null

scope_id

Target scope — designed-in for M2 space-scoping; always null in v1 (cells carry no scope column).

type Uuid | null

CellCreateInput
#

auth/cell_action_specs.ts view source

value + type

{ data: { [x: string]: unknown; label?: string | undefined; summary?: string | undefined; }; kind?: string | null | undefined; visibility?: "private" | "public" | undefined; path?: (string & $brand<"CellPath">) | null | undefined; parent_id?: (string & $brand<...>) | ... 1 more ... | undefined; acting?: (string & $b... import {CellCreateInput} from '@fuzdev/fuz_app/auth/cell_action_specs.js';

Input for cell_create. created_by is NOT on the wire — the handler stamps it from auth.actor.id. path is admin-only; non-admin callers supplying path get ERROR_CELL_PATH_ADMIN_ONLY (forbidden).

data

type CellData

kind?

type string | null

visibility?

type "private" | "public"

path?

type (string & $brand<"CellPath">) | null

parent_id?

type (string & $brand<"Uuid">) | null

acting?

type string & $brand<"Uuid">

CellCreateOutput
#

auth/cell_action_specs.ts view source

value + type

{ cell: { id: string & $brand<"Uuid">; path: (string & $brand<"CellPath">) | null; data: { [x: string]: unknown; label?: string | undefined; summary?: string | undefined; }; kind: string | null; ... 10 more ...; grant_count: number; }; } import {CellCreateOutput} from '@fuzdev/fuz_app/auth/cell_action_specs.js';

cell

type CellJson

CellCreateQueryInput
#

db/cell_queries.ts view source

CellCreateQueryInput import type {CellCreateQueryInput} from '@fuzdev/fuz_app/db/cell_queries.js';

Input for query_cell_create. refs is derived from data. kind is the write-once capability axis (cell.kind column) — set here at INSERT and absent from CellUpdatePatch, so it can never change post-create. parent_id / root_id / moderation are the directory-tree + lifecycle columns the handler derives (parent containment, governing root, the create-authorizer verdict) — likewise write-once / control-gated, never on CellUpdatePatch.

data

type Json

kind?

type string | null

visibility?

type CellVisibility

path?

type string | null

parent_id?

type Uuid | null

root_id?

type Uuid | null

moderation?

type string | null

created_by?

type Uuid | null

CellCreateVerdict
#

auth/cell_actions.ts view source

CellCreateVerdict

type { allow: false; } | { allow: true; moderation_required: boolean; }

import type {CellCreateVerdict} from '@fuzdev/fuz_app/auth/cell_actions.js';

An authorizer's decision for a cell_create — the TS twin of the Rust Verdict. Folds the moderation outcome into the authority decision (one policy resolution, not two): {allow: false} denies (the handler surfaces a 403 forbidden for a viewable parent / a root creation), `{allow: true, moderation_required} admits — true → born pending + private, false` → born approved at the author's visibility.

CellData
#

auth/cell_data_schema.ts view source

value + type

{ [x: string]: unknown; label?: string | undefined; summary?: string | undefined; } import {CellData} from '@fuzdev/fuz_app/auth/cell_data_schema.js';

Base cell-data shape. All fields optional; loose mode admits arbitrary additional keys so apps can attach metadata or stage new kinds without touching the wire schema. kind lives on the top-level cell.kind column, not here (see the module doc).

label?

type string

summary?

type string

[key: string]

type unknown

CellDeleteInput
#

auth/cell_action_specs.ts view source

value + type

{ cell_id: string & $brand<"Uuid">; acting?: (string & $brand<"Uuid">) | undefined; } import {CellDeleteInput} from '@fuzdev/fuz_app/auth/cell_action_specs.js';

cell_id

type string & $brand<"Uuid">

acting?

type string & $brand<"Uuid">

CellDeleteOutput
#

auth/cell_action_specs.ts view source

value + type

{ ok: true; deleted: boolean; } import {CellDeleteOutput} from '@fuzdev/fuz_app/auth/cell_action_specs.js';

ok

type true

deleted

type boolean

CellFieldActionDeps
#

auth/cell_field_actions.ts view source

ActionFactoryDeps import type {CellFieldActionDeps} from '@fuzdev/fuz_app/auth/cell_field_actions.js';

log

Structured logger instance.

type Logger

audit

Bound audit emitter for fire-and-forget audit writes.

type AuditEmitter

CellFieldDeleteAuditMetadata
#

auth/cell_field_audit_metadata.ts view source

value + type

{ [x: string]: unknown; source_id: string & $brand<"Uuid">; name: string; target_id: string & $brand<"Uuid">; } import {CellFieldDeleteAuditMetadata} from '@fuzdev/fuz_app/auth/cell_field_audit_metadata.js';

Metadata envelope for cell_field_delete.

source_id

type string & $brand<"Uuid">

name

type string

target_id

type string & $brand<"Uuid">

[key: string]

type unknown

CellFieldDeleteInput
#

auth/cell_field_action_specs.ts view source

value + type

{ source_id: string & $brand<"Uuid">; name: string & $brand<"CellFieldName">; acting?: (string & $brand<"Uuid">) | undefined; } import {CellFieldDeleteInput} from '@fuzdev/fuz_app/auth/cell_field_action_specs.js';

Input for cell_field_delete. Idempotent: a successful response is returned even when no row matched.

source_id

type string & $brand<"Uuid">

name

type string & $brand<"CellFieldName">

acting?

type string & $brand<"Uuid">

CellFieldDeleteOutput
#

auth/cell_field_action_specs.ts view source

value + type

{ ok: true; deleted: boolean; } import {CellFieldDeleteOutput} from '@fuzdev/fuz_app/auth/cell_field_action_specs.js';

ok

type true

deleted

type boolean

CellFieldListInput
#

auth/cell_field_action_specs.ts view source

value + type

{ source_id?: (string & $brand<"Uuid">) | undefined; target_id?: (string & $brand<"Uuid">) | undefined; name_after?: (string & $brand<"CellFieldName">) | undefined; limit?: number | undefined; acting?: (string & $brand<...>) | undefined; } import {CellFieldListInput} from '@fuzdev/fuz_app/auth/cell_field_action_specs.js';

Input for cell_field_list. Pass source_id for forward fields or target_id for reverse upfields — exactly one (the schema rejects both / neither). Reverse listing has 2-layer authz (target view-check gates the call; per-source view-check filters the rows).

Forward listing supports cursor pagination via name_after (return rows whose name > name_after lex). The reverse listing doesn't paginate (the result set is small in practice — number of sources pointing at a given target).

source_id?

type string & $brand<"Uuid">

target_id?

type string & $brand<"Uuid">

name_after?

type string & $brand<"CellFieldName">

limit?

type number

acting?

type string & $brand<"Uuid">

CellFieldListOutput
#

auth/cell_field_action_specs.ts view source

value + type

{ fields: { source_id: string & $brand<"Uuid">; name: string; target_id: string & $brand<"Uuid">; created_at: string; }[]; } import {CellFieldListOutput} from '@fuzdev/fuz_app/auth/cell_field_action_specs.js';

fields

type FieldJson[]

CellFieldName
#

auth/cell_field_action_specs.ts view source

value + type

string & $brand<"CellFieldName">

type string & $brand<"CellFieldName">

import {CellFieldName} from '@fuzdev/fuz_app/auth/cell_field_action_specs.js';

external types

z.infer<typeof CellFieldName>

CellFieldRow
#

db/cell_field_queries.ts view source

CellFieldRow import type {CellFieldRow} from '@fuzdev/fuz_app/db/cell_field_queries.js';

Row shape returned by cell_field SELECTs.

source_id

type Uuid

name

type string

target_id

type Uuid

created_at

type string

CellFieldSetAuditMetadata
#

auth/cell_field_audit_metadata.ts view source

value + type

{ [x: string]: unknown; source_id: string & $brand<"Uuid">; name: string; target_id: string & $brand<"Uuid">; } import {CellFieldSetAuditMetadata} from '@fuzdev/fuz_app/auth/cell_field_audit_metadata.js';

Metadata envelope for cell_field_set. Emitted on every successful create OR update path (UPSERT on (source_id, name)); the audit reader correlates create-vs-update via repeated (source_id, name) if needed.

source_id

type string & $brand<"Uuid">

name

type string

target_id

type string & $brand<"Uuid">

[key: string]

type unknown

CellFieldSetInput
#

auth/cell_field_action_specs.ts view source

value + type

{ source_id: string & $brand<"Uuid">; name: string & $brand<"CellFieldName">; target_id: string & $brand<"Uuid">; acting?: (string & $brand<"Uuid">) | undefined; } import {CellFieldSetInput} from '@fuzdev/fuz_app/auth/cell_field_action_specs.js';

Input for cell_field_set. UPSERT on (source_id, name) — re-issuing the same input updates target_id and bumps created_at.

source_id

type string & $brand<"Uuid">

name

type string & $brand<"CellFieldName">

target_id

type string & $brand<"Uuid">

acting?

type string & $brand<"Uuid">

CellFieldSetOutput
#

auth/cell_field_action_specs.ts view source

value + type

{ field: { source_id: string & $brand<"Uuid">; name: string; target_id: string & $brand<"Uuid">; created_at: string; }; } import {CellFieldSetOutput} from '@fuzdev/fuz_app/auth/cell_field_action_specs.js';

field

type FieldJson

CellFieldSetQueryInput
#

CellGetInput
#

auth/cell_action_specs.ts view source

value + type

{ id?: (string & $brand<"Uuid">) | undefined; path?: (string & $brand<"CellPath">) | undefined; acting?: (string & $brand<"Uuid">) | undefined; } import {CellGetInput} from '@fuzdev/fuz_app/auth/cell_action_specs.js';

Input for cell_get. Pass id OR path (exactly one expected; both accepted, id takes precedence). The handler responds with 404 when no row matches OR when can_view_cell rejects the caller — same code so private-cell existence doesn't leak.

id?

type string & $brand<"Uuid">

path?

type string & $brand<"CellPath">

acting?

type string & $brand<"Uuid">

CellGetOutput
#

auth/cell_action_specs.ts view source

value + type

{ cell: { id: string & $brand<"Uuid">; path: (string & $brand<"CellPath">) | null; data: { [x: string]: unknown; label?: string | undefined; summary?: string | undefined; }; kind: string | null; ... 10 more ...; grant_count: number; }; ... 5 more ...; can_grant: boolean; } import {CellGetOutput} from '@fuzdev/fuz_app/auth/cell_action_specs.js';

Output for cell_get. Bundles relation arrays (fields + items) server-side via JOINs so the common "show this cell with its children" flow needs one round-trip. Targets are filtered to those the caller may view (strict target-visibility). Per-relation `LIMIT CELL_RELATIONS_BUNDLE_LIMIT`; clients paginate via cell_item_list({parent_id, position_after}) / cell_field_list({source_id}) when truncated.

cell

type CellJson

fields

type FieldJson[]

fields_truncated

type boolean

items

type ItemJson[]

items_truncated

type boolean

can_edit

type boolean

can_grant

type boolean

CellGrantActionDeps
#

auth/cell_grant_actions.ts view source

CellGrantActionDeps

type ActionFactoryDeps & { roles: RoleSchemaResult; }

import type {CellGrantActionDeps} from '@fuzdev/fuz_app/auth/cell_grant_actions.js';

Dependencies for create_cell_grant_actions.

roles is the role schema — read for the role-validity gate on cell_grant_create. The other slots match CellActionDeps so audit-log emit goes through the same fire-and-forget plumbing.

log

Structured logger instance.

type Logger

audit

Bound audit emitter for fire-and-forget audit writes.

type AuditEmitter

roles

type RoleSchemaResult

CellGrantCreateAuditMetadata
#

auth/cell_grant_audit_metadata.ts view source

value + type

{ [x: string]: unknown; cell_id: string & $brand<"Uuid">; grant_id: string & $brand<"Uuid">; level: "viewer" | "editor"; principal: { [x: string]: unknown; actor_id: string & $brand<"Uuid">; } | { ...; }; } import {CellGrantCreateAuditMetadata} from '@fuzdev/fuz_app/auth/cell_grant_audit_metadata.js';

Metadata envelope for cell_grant_create.

Emitted on every successful create OR re-share update path (UPSERT-on-unique-index). The audit reader correlates create-vs-update via grant_id if needed; the design doesn't require distinguishing the two at the metadata level.

cell_id

type string & $brand<"Uuid">

grant_id

type string & $brand<"Uuid">

level

type "viewer" | "editor"

principal

type CellGrantPrincipalAuditMetadata

[key: string]

type unknown

CellGrantCreateInput
#

auth/cell_grant_action_specs.ts view source

value + type

{ cell_id: string & $brand<"Uuid">; level: "viewer" | "editor"; principal: { kind: "actor"; actor_id: string & $brand<"Uuid">; } | { kind: "role"; role: string; scope_id?: (string & $brand<...>) | ... 1 more ... | undefined; }; acting?: (string & $brand<...>) | undefined; } import {CellGrantCreateInput} from '@fuzdev/fuz_app/auth/cell_grant_action_specs.js';

Input for cell_grant_create. Idempotent on the unique index — re-granting the same (cell_id, principal) pair updates level + granted_by rather than producing a duplicate row.

cell_id

type string & $brand<"Uuid">

level

type "viewer" | "editor"

principal

type CellGrantPrincipalInput

acting?

type string & $brand<"Uuid">

CellGrantCreateOutput
#

auth/cell_grant_action_specs.ts view source

value + type

{ grant: { id: string & $brand<"Uuid">; cell_id: string & $brand<"Uuid">; level: "viewer" | "editor"; actor_id: (string & $brand<"Uuid">) | null; role: string | null; scope_id: (string & $brand<...>) | null; granted_by: (string & $brand<...>) | null; created_at: string; }; } import {CellGrantCreateOutput} from '@fuzdev/fuz_app/auth/cell_grant_action_specs.js';

grant

type GrantJson

CellGrantCreateQueryInput
#

CellGrantLevel
#

auth/cell_grant_action_specs.ts view source

value + type

"viewer" | "editor"

type "viewer" | "editor"

import {CellGrantLevel} from '@fuzdev/fuz_app/auth/cell_grant_action_specs.js';

Grant level — view-only or view-plus-edit.

CellGrantListInput
#

auth/cell_grant_action_specs.ts view source

value + type

{ cell_id: string & $brand<"Uuid">; acting?: (string & $brand<"Uuid">) | undefined; } import {CellGrantListInput} from '@fuzdev/fuz_app/auth/cell_grant_action_specs.js';

cell_id

type string & $brand<"Uuid">

acting?

type string & $brand<"Uuid">

CellGrantListOutput
#

auth/cell_grant_action_specs.ts view source

value + type

{ grants: { id: string & $brand<"Uuid">; cell_id: string & $brand<"Uuid">; level: "viewer" | "editor"; actor_id: (string & $brand<"Uuid">) | null; role: string | null; scope_id: (string & $brand<...>) | null; granted_by: (string & $brand<...>) | null; created_at: string; }[]; } import {CellGrantListOutput} from '@fuzdev/fuz_app/auth/cell_grant_action_specs.js';

grants

type GrantJson[]

CellGrantPrincipalAuditMetadata
#

auth/cell_grant_audit_metadata.ts view source

value + type

{ [x: string]: unknown; actor_id: string & $brand<"Uuid">; } | { [x: string]: unknown; role: string; scope_id: (string & $brand<"Uuid">) | null; }

type { [x: string]: unknown; actor_id: string & $brand<"Uuid">; } | { [x: string]: unknown; role: string; scope_id: (string & $brand<"Uuid">) | null; }

import {CellGrantPrincipalAuditMetadata} from '@fuzdev/fuz_app/auth/cell_grant_audit_metadata.js';

Principal columns as stored on cell_grant. Discriminated by which keys are present: {actor_id} for an actor-shaped grant, {role, scope_id} for a role-shaped grant. Actor-shaped grants carry only the id; names are never persisted in the audit envelope.

CellGrantPrincipalInput
#

auth/cell_grant_action_specs.ts view source

value + type

{ kind: "actor"; actor_id: string & $brand<"Uuid">; } | { kind: "role"; role: string; scope_id?: (string & $brand<"Uuid">) | null | undefined; }

type { kind: "actor"; actor_id: string & $brand<"Uuid">; } | { kind: "role"; role: string; scope_id?: (string & $brand<"Uuid">) | null | undefined; }

import {CellGrantPrincipalInput} from '@fuzdev/fuz_app/auth/cell_grant_action_specs.js';

Wire-input principal. Discriminated by kind. Actor-shaped principals carry a resolved actor_id — the picker UI runs actor_search to convert a typed name to an id before this verb is called.

CellGrantPrincipalQueryInput
#

db/cell_grant_queries.ts view source

CellGrantPrincipalQueryInput

type { kind: "actor"; actor_id: string & $brand<"Uuid">; } | { kind: "role"; role: string; scope_id: (string & $brand<"Uuid">) | null; }

import type {CellGrantPrincipalQueryInput} from '@fuzdev/fuz_app/db/cell_grant_queries.js';

Discriminated principal input for query_cell_grant_create. Wire and query shapes are aligned — actor-shaped principals carry a pre-resolved actor_id; pickers run actor_search to convert a typed name to an id upstream of the handler.

CellGrantRevokeAuditMetadata
#

auth/cell_grant_audit_metadata.ts view source

value + type

{ [x: string]: unknown; cell_id: string & $brand<"Uuid">; grant_id: string & $brand<"Uuid">; level: "viewer" | "editor"; principal: { [x: string]: unknown; actor_id: string & $brand<"Uuid">; } | { ...; }; self?: true | undefined; } import {CellGrantRevokeAuditMetadata} from '@fuzdev/fuz_app/auth/cell_grant_audit_metadata.js';

Metadata envelope for cell_grant_revoke.

self: true distinguishes the recipient-side "leave shared cell" path (actor-shaped grant where the principal actor === caller actor) from a delegator-side revoke. Single event type for both — the boolean is enough for forensic review and avoids surface- doubling with a parallel cell_grant_leave event.

cell_id

type string & $brand<"Uuid">

grant_id

type string & $brand<"Uuid">

level

type "viewer" | "editor"

principal

type CellGrantPrincipalAuditMetadata

self?

type true

[key: string]

type unknown

CellGrantRevokeInput
#

auth/cell_grant_action_specs.ts view source

value + type

{ grant_id: string & $brand<"Uuid">; acting?: (string & $brand<"Uuid">) | undefined; } import {CellGrantRevokeInput} from '@fuzdev/fuz_app/auth/cell_grant_action_specs.js';

grant_id

type string & $brand<"Uuid">

acting?

type string & $brand<"Uuid">

CellGrantRevokeOutput
#

auth/cell_grant_action_specs.ts view source

value + type

{ ok: true; still_admitted: boolean; } import {CellGrantRevokeOutput} from '@fuzdev/fuz_app/auth/cell_grant_action_specs.js';

Output for cell_grant_revoke. still_admitted is true when the caller retains some admit path on the cell after the revoke (other grant, ownership, admin). Always true for non-self revokes (the caller didn't admit via this row to begin with).

ok

type true

still_admitted

type boolean

CellGrantRow
#

db/cell_grant_queries.ts view source

CellGrantRow import type {CellGrantRow} from '@fuzdev/fuz_app/db/cell_grant_queries.js';

Row shape returned by cell_grant SELECTs.

id

type Uuid

cell_id

type Uuid

level

type CellGrantLevel

actor_id

type Uuid | null

role

type string | null

scope_id

type Uuid | null

granted_by

type Uuid | null

created_at

type string

CellItemActionDeps
#

auth/cell_item_actions.ts view source

ActionFactoryDeps import type {CellItemActionDeps} from '@fuzdev/fuz_app/auth/cell_item_actions.js';

log

Structured logger instance.

type Logger

audit

Bound audit emitter for fire-and-forget audit writes.

type AuditEmitter

CellItemDeleteAuditMetadata
#

auth/cell_item_audit_metadata.ts view source

value + type

{ [x: string]: unknown; parent_id: string & $brand<"Uuid">; position: string; child_id: string & $brand<"Uuid">; } import {CellItemDeleteAuditMetadata} from '@fuzdev/fuz_app/auth/cell_item_audit_metadata.js';

Metadata envelope for cell_item_delete.

parent_id

type string & $brand<"Uuid">

position

type string

child_id

type string & $brand<"Uuid">

[key: string]

type unknown

CellItemDeleteInput
#

auth/cell_item_action_specs.ts view source

value + type

{ parent_id: string & $brand<"Uuid">; position: string & $brand<"CellItemPosition">; acting?: (string & $brand<"Uuid">) | undefined; } import {CellItemDeleteInput} from '@fuzdev/fuz_app/auth/cell_item_action_specs.js';

Input for cell_item_delete. Idempotent on the slot key.

parent_id

type string & $brand<"Uuid">

position

type string & $brand<"CellItemPosition">

acting?

type string & $brand<"Uuid">

CellItemDeleteOutput
#

auth/cell_item_action_specs.ts view source

value + type

{ ok: true; deleted: boolean; } import {CellItemDeleteOutput} from '@fuzdev/fuz_app/auth/cell_item_action_specs.js';

ok

type true

deleted

type boolean

CellItemInsertAuditMetadata
#

auth/cell_item_audit_metadata.ts view source

value + type

{ [x: string]: unknown; parent_id: string & $brand<"Uuid">; position: string; child_id: string & $brand<"Uuid">; } import {CellItemInsertAuditMetadata} from '@fuzdev/fuz_app/auth/cell_item_audit_metadata.js';

Metadata envelope for cell_item_insert.

parent_id

type string & $brand<"Uuid">

position

type string

child_id

type string & $brand<"Uuid">

[key: string]

type unknown

CellItemInsertInput
#

auth/cell_item_action_specs.ts view source

value + type

{ parent_id: string & $brand<"Uuid">; child_id: string & $brand<"Uuid">; position: string & $brand<"CellItemPosition">; acting?: (string & $brand<"Uuid">) | undefined; } import {CellItemInsertInput} from '@fuzdev/fuz_app/auth/cell_item_action_specs.js';

Input for cell_item_insert. Caller computes position via fractional_index_between(prev, next) (@fuzdev/fuz_util/fractional_index.ts) client-side. Returns cell_item_position_taken on `(parent_id, position)` unique violation; client refreshes bracket and retries.

parent_id

type string & $brand<"Uuid">

child_id

type string & $brand<"Uuid">

position

type string & $brand<"CellItemPosition">

acting?

type string & $brand<"Uuid">

CellItemInsertOutput
#

auth/cell_item_action_specs.ts view source

value + type

{ item: { parent_id: string & $brand<"Uuid">; position: string & $brand<"CellItemPosition">; child_id: string & $brand<"Uuid">; created_at: string; }; } import {CellItemInsertOutput} from '@fuzdev/fuz_app/auth/cell_item_action_specs.js';

item

type ItemJson

CellItemInsertQueryInput
#

CellItemListInput
#

auth/cell_item_action_specs.ts view source

value + type

{ parent_id?: (string & $brand<"Uuid">) | undefined; child_id?: (string & $brand<"Uuid">) | undefined; position_after?: (string & $brand<"CellItemPosition">) | undefined; limit?: number | undefined; acting?: (string & $brand<...>) | undefined; } import {CellItemListInput} from '@fuzdev/fuz_app/auth/cell_item_action_specs.js';

Input for cell_item_list. Pass parent_id for forward items or child_id for reverse lists — exactly one. Reverse listing has 2-layer authz (child view-check gates the call; per-parent view-check filters the rows).

Forward listing supports cursor pagination via position_after (return rows with position > position_after). The reverse listing doesn't paginate (the result set is small in practice — number of parents containing a given child).

parent_id?

type string & $brand<"Uuid">

child_id?

type string & $brand<"Uuid">

position_after?

type string & $brand<"CellItemPosition">

limit?

type number

acting?

type string & $brand<"Uuid">

CellItemListOutput
#

auth/cell_item_action_specs.ts view source

value + type

{ items: { parent_id: string & $brand<"Uuid">; position: string & $brand<"CellItemPosition">; child_id: string & $brand<"Uuid">; created_at: string; }[]; } import {CellItemListOutput} from '@fuzdev/fuz_app/auth/cell_item_action_specs.js';

items

type ItemJson[]

CellItemMoveAuditMetadata
#

auth/cell_item_audit_metadata.ts view source

value + type

{ [x: string]: unknown; parent_id: string & $brand<"Uuid">; position_old: string; position_new: string; } import {CellItemMoveAuditMetadata} from '@fuzdev/fuz_app/auth/cell_item_audit_metadata.js';

Metadata envelope for cell_item_move. Carries both old and new position so the audit trail shows the reorder without a join back to the live row.

parent_id

type string & $brand<"Uuid">

position_old

type string

position_new

type string

[key: string]

type unknown

CellItemMoveInput
#

auth/cell_item_action_specs.ts view source

value + type

{ parent_id: string & $brand<"Uuid">; position: string & $brand<"CellItemPosition">; new_position: string & $brand<"CellItemPosition">; acting?: (string & $brand<...>) | undefined; } import {CellItemMoveInput} from '@fuzdev/fuz_app/auth/cell_item_action_specs.js';

Input for cell_item_move. Move within the same parent (cross-parent moves are a future extension).

parent_id

type string & $brand<"Uuid">

position

type string & $brand<"CellItemPosition">

new_position

type string & $brand<"CellItemPosition">

acting?

type string & $brand<"Uuid">

CellItemMoveOutput
#

auth/cell_item_action_specs.ts view source

value + type

{ item: { parent_id: string & $brand<"Uuid">; position: string & $brand<"CellItemPosition">; child_id: string & $brand<"Uuid">; created_at: string; }; } import {CellItemMoveOutput} from '@fuzdev/fuz_app/auth/cell_item_action_specs.js';

item

type ItemJson

CellItemPosition
#

auth/cell_item_action_specs.ts view source

value + type

string & $brand<"CellItemPosition">

type string & $brand<"CellItemPosition">

import {CellItemPosition} from '@fuzdev/fuz_app/auth/cell_item_action_specs.js';

Position grammar — base62 fractional-indexing key. Wire enforces non-empty, alphabet only, and the helper's FRACTIONAL_INDEX_LENGTH_MAX cap (well above realistic lengths even for hundreds of consecutive front-inserts; set high to avoid arbitrary cliffs). Lex ordering is the contract; the no-trailing-'0' invariant lives in the helper, not the wire.

external types

z.infer<typeof CellItemPosition>

CellItemRow
#

db/cell_item_queries.ts view source

CellItemRow import type {CellItemRow} from '@fuzdev/fuz_app/db/cell_item_queries.js';

Row shape returned by cell_item SELECTs.

parent_id

type Uuid

position

type string

child_id

type Uuid

created_at

type string

CellJson
#

auth/cell_action_specs.ts view source

value + type

{ id: string & $brand<"Uuid">; path: (string & $brand<"CellPath">) | null; data: { [x: string]: unknown; label?: string | undefined; summary?: string | undefined; }; kind: string | null; ... 10 more ...; grant_count: number; } import {CellJson} from '@fuzdev/fuz_app/auth/cell_action_specs.js';

Wire form for a cell row. data is the typed-but-permissive CellData shape (kind / label / summary typed-and-optional, additional fields pass through). Per-kind shape validation is sub-API and handled by the app's validate_data deps callback (see auth/cell_actions.ts).

visibility is the access-control axis — a top-level column on the row, not a field inside data. cell_grant and visibility are the two ACL surfaces; both live as peers, not embedded in content.

path is the global namespace axis (no tenant/hub scoping).

Relations (items, fields) are NOT carried on the cell row — they live in the cell_item / cell_field sibling tables. Bundled arrays appear on CellGetOutput; other read verbs (cell_list) do not bundle.

id

type string & $brand<"Uuid">

path

type (string & $brand<"CellPath">) | null

data

type CellData

kind

type string | null

visibility

type "private" | "public"

refs

type (string & Brand<"FactHash">)[] | null

parent_id

type (string & $brand<"Uuid">) | null

root_id

type (string & $brand<"Uuid">) | null

moderation

type string | null

created_by

type (string & $brand<"Uuid">) | null

updated_by

type (string & $brand<"Uuid">) | null

created_at

type string

updated_at

type string | null

deleted_at

type string | null

grant_count

type number

CellListInput
#

auth/cell_action_specs.ts view source

value + type

{ ids?: (string & $brand<"Uuid">)[] | undefined; kind?: string | undefined; visibility?: "private" | "public" | undefined; ref?: (string & Brand<"FactHash">) | undefined; ... 9 more ...; acting?: (string & $brand<...>) | undefined; } import {CellListInput} from '@fuzdev/fuz_app/auth/cell_action_specs.js';

Input for cell_list. Filters are optional and combine with AND. The handler applies the SQL-side visibility predicate from query_cell_list so the page-window stays correct under pagination — post-filtering in JS would silently truncate pages.

ids is the batch-read filter — pass a list of cell ids to fetch them in one round-trip (avoids N+1 when rendering a collection's items[]). The visibility predicate still runs, so callers passing ids they can't view simply get fewer rows back. Capped at CELL_LIST_LIMIT_MAX.

shared_with: 'me' narrows to cells that admit the caller via a cell_grant row (actor-shaped or role-shaped principal) AND that the caller does not own. Authenticated only; combine with kind / path_prefix etc. to scope further. Combining with created_by: <my-actor-id> produces an empty result by definition (owner is implicit, never appears as a grant principal); we don't reject the combination at the schema layer because SQL emptiness is correct.

ids?

type (string & $brand<"Uuid">)[]

kind?

type string

visibility?

type "private" | "public"

ref?

type string & Brand<"FactHash">

created_by?

type string & $brand<"Uuid">

path_prefix?

type string & $brand<"CellPath">

root_id?

type string & $brand<"Uuid">

moderation?

type string

shared_with?

type "me"

order_by?

type "created_at" | "updated_at"

order_direction?

type "asc" | "desc"

limit?

type number

offset?

type number

acting?

type string & $brand<"Uuid">

CellListOptions
#

db/cell_queries.ts view source

CellListOptions import type {CellListOptions} from '@fuzdev/fuz_app/db/cell_queries.js';

Common pagination + tombstone-visibility options for list queries.

limit?

type number

offset?

type number

include_deleted?

type boolean

CellListOutput
#

auth/cell_action_specs.ts view source

value + type

{ cells: { id: string & $brand<"Uuid">; path: (string & $brand<"CellPath">) | null; data: { [x: string]: unknown; label?: string | undefined; summary?: string | undefined; }; kind: string | null; ... 10 more ...; grant_count: number; }[]; cell_grants?: Record<...> | undefined; } import {CellListOutput} from '@fuzdev/fuz_app/auth/cell_action_specs.js';

cells

type CellJson[]

cell_grants?

type Record<string, GrantJson[]>

CellListParams
#

db/cell_queries.ts view source

CellListParams import type {CellListParams} from '@fuzdev/fuz_app/db/cell_queries.js';

Parameters for query_cell_list. All filter dimensions are optional.

kind?

Match cell.kind = ? (uses idx_cell_kind).

type string

visibility?

Match cell.visibility = ? directly on the top-level column. Additional narrowing on top of the SQL-side auth visibility predicate — useful for the public discovery feed where authed callers must NOT see their own private entries mixed in.

type CellVisibility

ref?

Match cells whose refs[] contains this hash (uses idx_cell_refs).

type FactHash

created_by?

Filter to cells created by this actor (uses idx_cell_created_by).

type Uuid

path_prefix?

Filter to cells whose path starts with this prefix. Wildcard metachars in the prefix are NOT special — starts_with() does literal matching.

type string

root_id?

Scope to a directory subtree by governing root (cell.root_id = ?, uses idx_cell_root).

type Uuid

moderation?

Filter by moderation lifecycle marker ('pending' drives the mod queue via idx_cell_moderation_pending).

Note: the visibility predicate still applies, so a 'pending' (private) queue surfaces only to viewers it admits — admin / owner / grant. A non-admin container manager won't see pending children here until a manager-scoped visibility branch is added, so for now the queue is an admin surface (cell_moderate of a known id still works regardless).

type string

ids?

Batch-fetch by id. The visibility predicate still runs, so callers passing ids they can't view simply get fewer rows back. Order of the returned rows follows order_by / order_direction, not the input list — callers that need positional output (e.g. preserving a collection's items[] order) should re-index client-side.

type Uuid[]

viewer_actor_id

Viewer actor for the visibility predicate. Pass null for unauthenticated callers — only cell.visibility === 'public' rows are admitted then.

type Uuid | null

viewer_is_admin

When true, the visibility predicate is dropped (admin sees all). When false, rows pass when public, owned by the viewer, or admitted by a cell_grant row.

type boolean

caller_actor_id?

Caller's actor_id for the actor-shaped grant branch. NULL = anonymous (actor-grants can never admit). Kept distinct from viewer_actor_id for the predicate's clarity (the visibility branch and the grant branch are independent concerns even when they currently agree).

type Uuid | null

caller_role_grant_roles?

Caller's role_grant roles, parallel-array projection of auth.role_grants (active-only — middleware filters). Pair-wise aligned with caller_role_grant_scope_ids. Empty array (or omitted) admits no role-shaped grants. The two arrays MUST have equal length — unnest(text[], uuid[]) null-pads on length mismatch and would silently widen role-grant admits.

type readonly string[]

caller_role_grant_scope_ids?

Caller's role_grant scope ids, parallel-array projection. NULLs in the array mark global (any-scope) role_grants — IS NOT DISTINCT FROM handles them per design.

type readonly (Uuid | null)[]

shared_with_caller_only?

When true, narrow to cells admitting the caller via a cell_grant row AND that the caller does not own. Authenticated only (viewer_actor_id must be set). Combine with kind / path_prefix etc. to scope further.

type boolean

order_by?

Sort column. Default created_at.

type "created_at" | "updated_at"

order_direction?

Sort direction. Default desc.

type "asc" | "desc"

limit?

Page size.

type number

offset?

Page offset.

type number

include_deleted?

Include soft-deleted rows. Default false.

type boolean

CellModerateAuditMetadata
#

auth/cell_audit_metadata.ts view source

value + type

{ [x: string]: unknown; cell_id: string & $brand<"Uuid">; root_id: string & $brand<"Uuid">; moderation: "approved" | "rejected"; } import {CellModerateAuditMetadata} from '@fuzdev/fuz_app/auth/cell_audit_metadata.js';

Metadata envelope for cell_moderate. cell_id is the moderated contribution; root_id is its governing root (the container whose moderation authority gated the call); moderation is the terminal decision applied ('approved' | 'rejected').

cell_id

type string & $brand<"Uuid">

root_id

type string & $brand<"Uuid">

moderation

type "approved" | "rejected"

[key: string]

type unknown

CellModerateInput
#

auth/cell_action_specs.ts view source

value + type

{ cell_id: string & $brand<"Uuid">; moderation: "approved" | "rejected"; acting?: (string & $brand<"Uuid">) | undefined; } import {CellModerateInput} from '@fuzdev/fuz_app/auth/cell_action_specs.js';

Input for cell_moderate — the pending → approved | rejected transition, gated on moderation authority over the governing root (admin / root owner in v1), not the contribution (which the author manages, and could otherwise self-approve). 404 when the target isn't viewable; 403 when it is but the caller isn't a root manager (the author lands here).

cell_id

type string & $brand<"Uuid">

moderation

type "approved" | "rejected"

acting?

type string & $brand<"Uuid">

CellModerateOutput
#

auth/cell_action_specs.ts view source

value + type

{ cell: { id: string & $brand<"Uuid">; path: (string & $brand<"CellPath">) | null; data: { [x: string]: unknown; label?: string | undefined; summary?: string | undefined; }; kind: string | null; ... 10 more ...; grant_count: number; }; } import {CellModerateOutput} from '@fuzdev/fuz_app/auth/cell_action_specs.js';

cell

type CellJson

CellModerationDecision
#

auth/cell_action_specs.ts view source

value + type

"approved" | "rejected"

type "approved" | "rejected"

import {CellModerationDecision} from '@fuzdev/fuz_app/auth/cell_action_specs.js';

The terminal transition a cell_moderate call applies to a gated contribution's moderation lifecycle. The born state ('pending') is set by the create authorizer, never on the wire; the verb only moves it to a terminal state. 'approved' publishes (also flips visibility → 'public'); 'rejected' leaves it private.

CellPath
#

auth/cell_action_specs.ts view source

value + type

string & $brand<"CellPath">

type string & $brand<"CellPath">

import {CellPath} from '@fuzdev/fuz_app/auth/cell_action_specs.js';

Branded so the type system distinguishes a validated path from any other string. Construct via CellPath.parse(s) at external boundaries; the RPC dispatcher does this automatically when the wire schema (CellCreateInput, CellGetInput, CellUpdateInput, CellListInput) is parsed at the entry point. Frontend callers handing a raw string to api.cell_* cast at the callsite (as CellPath) — the runtime check still runs server-side.

external types

z.infer<typeof CellPath>

CellRow
#

db/cell_queries.ts view source

CellRow import type {CellRow} from '@fuzdev/fuz_app/db/cell_queries.js';

Row shape returned by cell SELECTs. data is typed as CellData — the storage layer trusts the wire validation; the row is what was written, and the wire validates CellData on every write.

Parent↔child membership and named relations live in the cell_item / cell_field sibling tables (see db/cell_item_queries.ts / db/cell_field_queries.ts). The cell row carries identity + content only.

grant_count is a derived projection (correlated subquery against cell_grant keyed by cell_id, served by idx_cell_grant_cell) — not a table column. New cells naturally land at 0.

id

type Uuid

data

type CellData

kind

type string | null

visibility

type CellVisibility

path

type string | null

refs

type FactHash[] | null

parent_id

type Uuid | null

root_id

type Uuid | null

moderation

type string | null

created_at

type string

updated_at

type string | null

deleted_at

type string | null

created_by

type Uuid | null

updated_by

type Uuid | null

grant_count

type number

CellUpdateInput
#

auth/cell_action_specs.ts view source

value + type

{ cell_id: string & $brand<"Uuid">; data?: { [x: string]: unknown; label?: string | undefined; summary?: string | undefined; } | undefined; visibility?: "private" | "public" | undefined; path?: (string & $brand<...>) | ... 1 more ... | undefined; acting?: (string & $brand<...>) | undefined; } import {CellUpdateInput} from '@fuzdev/fuz_app/auth/cell_action_specs.js';

Input for cell_update. Fields left undefined keep their existing value. path writes are admin-only (handler-enforced); non-admin callers supplying path get ERROR_CELL_PATH_ADMIN_ONLY even if no other field is changing. visibility writes require the manage tier (can_manage_cell = admin / owner) — editor-grant holders editing data cannot flip visibility (ERROR_CELL_VISIBILITY_MANAGE_ONLY).

cell_id

type string & $brand<"Uuid">

data?

type CellData

visibility?

type "private" | "public"

path?

type (string & $brand<"CellPath">) | null

acting?

type string & $brand<"Uuid">

CellUpdateOutput
#

auth/cell_action_specs.ts view source

value + type

{ cell: { id: string & $brand<"Uuid">; path: (string & $brand<"CellPath">) | null; data: { [x: string]: unknown; label?: string | undefined; summary?: string | undefined; }; kind: string | null; ... 10 more ...; grant_count: number; }; } import {CellUpdateOutput} from '@fuzdev/fuz_app/auth/cell_action_specs.js';

cell

type CellJson

CellUpdatePatch
#

db/cell_queries.ts view source

CellUpdatePatch import type {CellUpdatePatch} from '@fuzdev/fuz_app/db/cell_queries.js';

Patch for query_cell_update. Fields left undefined are unchanged; path may be explicitly set to null to clear. refs is re-derived from data whenever data is updated.

data?

type Json

visibility?

type CellVisibility

path?

type string | null

updated_by?

type Uuid | null

CellVisibility
#

auth/cell_action_specs.ts view source

value + type

"private" | "public"

type "private" | "public"

import {CellVisibility} from '@fuzdev/fuz_app/auth/cell_action_specs.js';

Cell visibility — the coarse-grained access-control axis for a cell. Sibling to cell_grant (the fine-grained allowlist of actor- / role-shaped principals at viewer / editor levels). Together they form the cell-layer access-control surface:

  • cell.visibility = 'public' admits everyone, including unauthenticated visitors. cell_grant rows still apply for edit- level admit; read is universal.
  • cell.visibility = 'private' (default) restricts read to admin / owner (created_by) / cell_grant-admitted callers.

Stored as a top-level PG enum column (cell.visibility) — NOT inside cell.data, which is content metadata only. can_view_cell reads the column directly.

check_bootstrap_status
#

auth/bootstrap_routes.ts view source

(deps: CheckBootstrapStatusDeps, options: { token_path: string | null; }): Promise<BootstrapStatus> import {check_bootstrap_status} from '@fuzdev/fuz_app/auth/bootstrap_routes.js';

Check bootstrap availability at startup.

Bootstrap is available when:

  1. A token path is configured
  2. The token file passes the secure read (exists, not a symlink, mode 0600/0400, within the size cap)
  3. The bootstrap_lock table shows bootstrapped = false

The probe reads through the same read_secure_file the request-time read uses, so "bootstrap is available" and "the token file can actually be read" can't drift apart — an availability check laxer than the read it gates is the misconfiguration that reports green at boot and fails at request time. Twin of the Rust spine's is_bootstrap_available.

deps

filesystem and database access for the check

options

static configuration including token_path

type { token_path: string | null; }

returns

Promise<BootstrapStatus>

an object with available (boolean) and token_path (string | null)

check_daemon_health
#

cli/daemon.ts view source

(deps: FetchDeps, port: number, host?: string, timeout_ms?: number): Promise<boolean> import {check_daemon_health} from '@fuzdev/fuz_app/cli/daemon.js';

Check if a daemon is healthy by probing its /health endpoint.

Complements is_daemon_running (PID check) with an HTTP liveness probe. Requires the daemon to register a /health route (e.g. via create_health_route_spec).

deps

runtime with fetch capability

port

port the daemon should be listening on

type number

host

hostname (default localhost)

type string
default 'localhost'

timeout_ms

request timeout in milliseconds (default 2000)

type number
default 2000

returns

Promise<boolean>

true if the health endpoint responds with 2xx

check_error_response_fields
#

testing/integration_helpers.ts view source

(body: Record<string, unknown>): string[] import {check_error_response_fields} from '@fuzdev/fuz_app/testing/integration_helpers.js';

List the fields in an error response body that are not in the known-safe set.

Error schemas use z.looseObject (intentional — multiple producers), but test responses should be checked for fields that could leak information.

body

type Record<string, unknown>

returns

string[]

array of unexpected field names (empty = clean)

check_schema_drift
#

db/schema_ready.ts view source

(db: Db, expected: ExpectedSchema): Promise<SchemaDriftResult> import {check_schema_drift} from '@fuzdev/fuz_app/db/schema_ready.js';

Compare the live DB's columns against expected. Reports tables and columns the running code expects that the live DB lacks — the drift that breaks queries. Extra live tables / columns are ignored: forward-compatible, and a newer-than-fixture DB shouldn't fail readiness.

db

live database to introspect

type Db

expected

the committed column map a fresh bootstrap produces

returns

Promise<SchemaDriftResult>

CheckBootstrapStatusDeps
#

auth/bootstrap_routes.ts view source

CheckBootstrapStatusDeps import type {CheckBootstrapStatusDeps} from '@fuzdev/fuz_app/auth/bootstrap_routes.js';

Dependencies for checking bootstrap status at startup.

read_secure_file

Hardened secret-file read — the same capability the request-time read uses.

type (path: string): Promise<Uint8Array<ArrayBufferLike>>

path

type string
returns Promise<Uint8Array<ArrayBufferLike>>

db

Only the single-row bootstrap_lock read — narrower than the full Db.

type Pick<Db, "query_one">

log

type Logger

cleanup_expired_role_grant_offers
#

auth/cleanup.ts view source

(deps: AuthCleanupDeps): Promise<number> import {cleanup_expired_role_grant_offers} from '@fuzdev/fuz_app/auth/cleanup.js';

Sweep expired role_grant offers and emit one role_grant_offer_expire audit event per newly expired row.

One transaction: query_role_grant_offer_sweep_expired claims the expired, not-yet-audited offers (stamping expire_audited_at) and each claimed row's audit row is inserted beside it. After the commit, each event fans out through deps.audit.notify — per-listener throws are isolated there, so one bad listener never starves the rest. Twin of the Rust cleanup_expired_role_grant_offers.

Returns the count of offers audited. The offer rows themselves are preserved — offers carry audit value for the history view even after expiry, and accepted rows are the provenance for the resulting role_grant (deleting expired rows would not threaten that, but keeping them uniform with the retention policy for terminal rows is simpler).

deps

returns

Promise<number>

throws

  • Error - if the claim or an audit insert fails — the transaction rolls back, so no offer is stamped and the next run retries

mutates

  • role_grant_offer rows — stamps `expire_audited_at` on each swept offer
  • audit_log table — inserts one `role_grant_offer_expire` row per swept offer

cleanup_expired_sessions
#

auth/cleanup.ts view source

(deps: AuthCleanupDeps): Promise<number> import {cleanup_expired_sessions} from '@fuzdev/fuz_app/auth/cleanup.js';

Delete every expired session and close the connections each one opened, returning the count of sessions deleted.

The delete runs in its own transaction and the closes follow its commit, so on a successful pass every close is for a row already gone (see the module doc's § Expired sessions). Each swept session is closed by its id — the session-token hash the transports key on — so another session of the same account keeps its connections. A pass that closed something logs the counts at info. No audit row is written.

A closer that throws for one session does not spare the rest: every swept session is attempted and each failure is logged, as the post-commit queue does for a revocation's close.

Twin of the Rust cleanup_expired_sessions.

deps

returns

Promise<number>

how many expired sessions were deleted

throws

  • Error - from opening the transaction, the delete, or the commit. The first two close nothing and delete nothing. A failed commit is thrown **after** the closes: the sessions the delete returned are expired whether or not the commit landed, so their connections are closed either way, and the next pass deletes any row the commit left.

mutates

  • auth_session table — deletes every row past `expires_at`

clear_session_cookie
#

ClientApiTokenJson
#

auth/account_schema.ts view source

value + type

{ id: string; account_id: string & $brand<"Uuid">; name: string; expires_at: string | null; last_used_at: string | null; last_used_ip: string | null; created_at: string; scope: string; } import {ClientApiTokenJson} from '@fuzdev/fuz_app/auth/account_schema.js';

Zod schema for client-safe API token listing (excludes token_hash).

id

type string

account_id

type string & $brand<"Uuid">

name

type string

expires_at

type string | null

last_used_at

type string | null

last_used_ip

type string | null

created_at

type string

scope

Human-readable scope label (full / full (grandfathered) / N methods). A label rather than the document so the list stays a display surface — nothing authorizes off this, and a grandfathered token reads differently from a deliberately-full one.

type string

CliLogger
#

cli/logger.ts view source

CliLogger import type {CliLogger} from '@fuzdev/fuz_app/cli/logger.js';

error

Logs an error via Logger (gets Logger's error prefix).

type (...args: unknown[]): void

args

type unknown[]
returns void

warn

Logs a warning via Logger (gets Logger's warn prefix).

type (...args: unknown[]): void

args

type unknown[]
returns void

info

Logs info via Logger (gets Logger's label prefix).

type (...args: unknown[]): void

args

type unknown[]
returns void

debug

Logs debug via Logger (gets Logger's debug prefix).

type (...args: unknown[]): void

args

type unknown[]
returns void

raw

Logs raw output via Logger (no prefix, no level filtering).

type (...args: unknown[]): void

args

type unknown[]
returns void

success

Logs a success message with [done] prefix at info level.

type (msg: string): void

msg

type string
returns void

skip

Logs a skip message with [skip] prefix at info level.

type (msg: string): void

msg

type string
returns void

step

Logs a step message with ==> prefix at info level.

type (msg: string): void

msg

type string
returns void

header

Logs a header with === title === decoration at info level.

type (title: string): void

title

type string
returns void

dim

Logs a dimmed message at info level.

type (msg: string): void

msg

type string
returns void

logger

The underlying Logger instance.

type Logger

close_connections
#

close_connections_after_commit
#

actions/connection_closer.ts view source

(closer: ConnectionCloser, target: ConnectionCloseTarget, log: Logger): void import {close_connections_after_commit} from '@fuzdev/fuz_app/actions/connection_closer.js';

Close the connections target names, now, for a revocation that has already committed — logging a close that throws instead of throwing it.

For a handler with no route transaction, closing straight after its own commit: a throw there would fail a request whose revocation stands, and skip the work after the close (the response, the audit row). A closer over several transports closes the rest before it rethrows, so catching loses no close. The twin of the post-commit flush, which catches and logs the same way.

closer

the closer to run, deps.connection_closer in a handler

target

the connections to close

log

where a failing close is reported

type Logger

returns

void

collect_config_diagnostics
#

server/app_server.ts view source

internal

(input: ConfigDiagnosticsInput): AppSurfaceDiagnostic[] import {collect_config_diagnostics} from '@fuzdev/fuz_app/server/app_server.js';

The config-level diagnostics create_app_server appends to the surface's spec-level ones: a weakened session cookie, and an open rate-limited surface — one warning for 'disabled_for_testing', otherwise one per explicitly-null limiter, so a deployment that disables login's limiter but keeps signup's reads differently from one that disables both.

input

returns

AppSurfaceDiagnostic[]

collect_json_keys_recursive
#

testing/integration_helpers.ts view source

(value: unknown): Set<string> import {collect_json_keys_recursive} from '@fuzdev/fuz_app/testing/integration_helpers.js';

Recursively collect all key names from a parsed JSON value.

Walks objects and arrays to find every property name at any nesting depth.

value

type unknown

returns

Set<string>

collect_json_schema_property_names
#

testing/data_exposure.ts view source

(schema: unknown): Set<string> import {collect_json_schema_property_names} from '@fuzdev/fuz_app/testing/data_exposure.js';

Recursively collect all property names from a JSON Schema.

Walks properties, items, allOf/anyOf/oneOf, and additionalProperties to find every declared field name at any depth.

schema

type unknown

returns

Set<string>

collect_middleware_errors
#

http/surface.ts view source

(middleware: MiddlewareSpec[], route_path: string): Partial<Record<number, ZodType<unknown, unknown, $ZodTypeInternals<unknown, unknown>>>> | null import {collect_middleware_errors} from '@fuzdev/fuz_app/http/surface.js';

Collect error schemas from all middleware that applies to a route path.

middleware

route_path

type string

returns

Partial<Record<number, ZodType<unknown, unknown, $ZodTypeInternals<unknown, unknown>>>> | null

merged middleware error schemas, or null if none

colors
#

cli/util.ts view source

{ readonly green: "" | "\u001B[32m"; readonly yellow: "" | "\u001B[33m"; readonly blue: "" | "\u001B[34m"; readonly red: "" | "\u001B[31m"; readonly cyan: "" | "\u001B[36m"; readonly dim: "" | "\u001B[2m"; readonly bold: "" | "\u001B[1m"; readonly reset: "" | "\u001B[0m"; } import {colors} from '@fuzdev/fuz_app/cli/util.js';

ColumnExpr
#

db/sql_columns.ts view source

ColumnExpr import type {ColumnExpr} from '@fuzdev/fuz_app/db/sql_columns.js';

Per-column expression override for a projection — return the SQL to project for column, or undefined to keep the bare column reference.

Twin of the expr closure fuz_db::qualify_columns takes. The Rust side decodes rows positionally, so its overrides need no output name; TS reads rows by name, so columns_sql / qualify_columns alias every override back to its column (… AS created_at) — otherwise a to_char(…) projection would arrive under Postgres' derived name and read as undefined.

undefined is the *only* decline sentinel. An empty string is a returned expression, not a decline: it renders AS created_at and the query fails to parse. This is documented rather than guarded — every override in the repo is a literal lookup returning a fragment or nothing, and a malformed projection failing loud at the first query beats silently swallowing an expression the caller meant to supply.

(call)

type (column: string): string | undefined

column

type string
returns string | undefined

ColumnInfo
#

http/db_routes.ts view source

ColumnInfo import type {ColumnInfo} from '@fuzdev/fuz_app/http/db_routes.js';

Column metadata from information_schema.

column_name

type string

data_type

type string

is_nullable

type string

ColumnLayout
#

ui/ColumnLayout.svelte view source

accepts children

import ColumnLayout from '@fuzdev/fuz_app/ui/ColumnLayout.svelte';

aside

type Snippet<[]>

children

type Snippet<[]>

column_width?

CSS width of the fixed aside column.

type string
optional default '280px'

toggle_label?

The stacked toggle's text.

type string
optional default 'menu'

class?

type ClassValue | null
optional default ''

external types

SvelteHTMLElements['div']

columns_sql
#

db/sql_columns.ts view source

(columns: readonly string[], expr?: ColumnExpr | undefined): string import {columns_sql} from '@fuzdev/fuz_app/db/sql_columns.js';

Render a *_COLUMNS const as a SQL select list, in projection order.

columns

the column names

type readonly string[]

expr?

optional per-column expression override; an overridden column is aliased back to its own name

optional

returns

string

a, b, c

ColumnSnapshot
#

testing/schema_introspect.ts view source

value + type

{ data_type: string; udt_name: string; is_nullable: boolean; column_default: string | null; is_identity: boolean; } import {ColumnSnapshot} from '@fuzdev/fuz_app/testing/schema_introspect.js';

Per-column structural metadata. The Zod schema is the canonical source for the column shape — SchemaSnapshot reuses it as the cross-impl _testing_schema_snapshot RPC action's wire validator, so the introspection type and the wire contract can't drift apart.

data_type

SQL standard type name from information_schema.columns.data_type.

type string

udt_name

Postgres-native type name from information_schema.columns.udt_name.

type string

is_nullable

true when the column accepts NULL.

type boolean

column_default

Default-value expression as Postgres reports it, or null if none.

type string | null

is_identity

true when the column was declared GENERATED ... AS IDENTITY.

type boolean

CommandDeps
#

runtime/deps.ts view source

CommandDeps import type {CommandDeps} from '@fuzdev/fuz_app/runtime/deps.js';

Command execution.

run_command

Run a command and return the result. Never throws — failures surface as success: false.

options.cwd sets the child's working directory. options.signal aborts the child when the signal fires. options.timeout_ms kills the child after the given duration and returns timed_out: true on the result.

type (cmd: string, args: string[], options?: RunCommandOptions | undefined): Promise<CommandResult>

cmd

type string

args

type string[]

options?

optional
returns Promise<CommandResult>

CommandMeta
#

cli/help.ts view source

CommandMeta<TCategory> import type {CommandMeta} from '@fuzdev/fuz_app/cli/help.js';

Command metadata for help generation.

generics

CommandMeta<TCategory extends string = string>
TCategory
constraint string
default string

schema?

fuz_schema object schema for the command's args.

type AnySchema

summary

type string

usage

type string

category

type TCategory

CommandResult
#

runtime/deps.ts view source

CommandResult import type {CommandResult} from '@fuzdev/fuz_app/runtime/deps.js';

Result of executing a command.

timed_out is present only when timeout_ms was passed in RunCommandOptions and the process was killed after exceeding the timeout. Callers that pass timeout_ms should check this flag to distinguish timeout from exit-code failure.

success

type boolean

code

type number

stdout

type string

stderr

type string

timed_out?

type boolean

compare_cross_impl
#

testing/cross_backend/bench/bench_report.ts view source

(result: CrossImplBenchResult, options?: CompareCrossImplOptions | undefined): CrossImplComparisonEntry[] import {compare_cross_impl} from '@fuzdev/fuz_app/testing/cross_backend/bench/bench_report.js';

Welch-test verdict for every non-reference backend vs the reference, per scenario. With deno + node + rust and reference: 'deno' you get node vs deno and rust vs deno for each scenario.

result

options?

optional

returns

CrossImplComparisonEntry[]

CompareCrossImplOptions
#

testing/cross_backend/bench/bench_report.ts view source

CompareCrossImplOptions import type {CompareCrossImplOptions} from '@fuzdev/fuz_app/testing/cross_backend/bench/bench_report.js';

reference?

Backend to compare every other backend against. Defaults to result.backends[0].

type string

readonly

compile_action_registry
#

actions/compile_action_registry.ts view source

(actions: readonly Action<{ method: string; initiator: "frontend" | "backend" | "both"; side_effects: boolean; input: ZodType<unknown, unknown, $ZodTypeInternals<unknown, unknown>>; output: ZodType<unknown, unknown, $ZodTypeInternals<...>>; ... 6 more ...; rate_limit?: "both" | ... 2 more ... | undefined; } | { ...; } | { ...; }>[], ctx_label: string): ActionRegistryCompileResult import {compile_action_registry} from '@fuzdev/fuz_app/actions/compile_action_registry.js';

Validate registry-time invariants and build the dispatcher's method → action lookup.

actions

polymorphic action array; HTTP RPC passes RpcAction[] (narrower), WebSocket passes Action[] (kind-polymorphic — handler-less notification specs are accepted)

type readonly Action<ActionSpecUnion>[]

ctx_label

per-spec error-message prefix, e.g. 'RPC action' or 'WS action'. Combined with the spec method as ${ctx_label} "${method}".

type string

returns

ActionRegistryCompileResult

throws

  • Error - on biconditional violation, rate-limit/account-axis mismatch, JSON-RPC null-input or void-output, or duplicate method.

compose_gen_file
#

actions/action_codegen.ts view source

(input: { origin_path: string; imports: ImportBuilder; blocks: readonly string[]; }): string import {compose_gen_file} from '@fuzdev/fuz_app/actions/action_codegen.js';

Wrap the per-*.gen.ts boilerplate (banner + imports.build() + blocks join + template literal) into one call. Returns the full file body as a string ready to return from a Gen function.

Each consumer producer collapses to one compose_gen_file call wrapping the helper invocations.

input

type { origin_path: string; imports: ImportBuilder; blocks: readonly string[]; }

returns

string

examples

export const gen: Gen = ({origin_path}) => { const imports = new ImportBuilder(); return compose_gen_file({ origin_path, imports, blocks: [ generate_action_specs_record(all_action_specs, imports), generate_action_inputs_outputs(all_action_specs, imports), generate_action_event_datas(all_action_specs, imports), ], }); };

Empty blocks ('') are filtered out so helpers that short-circuit on empty spec sets don't introduce stray double blank lines.

ConfigDiagnosticsInput
#

server/app_server.ts view source

internal

ConfigDiagnosticsInput import type {ConfigDiagnosticsInput} from '@fuzdev/fuz_app/server/app_server.js';

cookie_options

The session cookie overrides, from AppServerOptions.session_options.

type Partial<SessionCookieOptions> | undefined

rate_limiter_mode

The rate_limiters mode, defaulted.

type RateLimiterMode

rate_limiters

The resolved limiters, from resolve_rate_limiters.

type Record<AppServerRateLimiterKey, RateLimiter | null>

confirm
#

cli/util.ts view source

(runtime: TerminalDeps, message: string): Promise<boolean> import {confirm} from '@fuzdev/fuz_app/cli/util.js';

Prompt for yes/no confirmation.

runtime

runtime with stdout_write and stdin_read capabilities

message

message to display

type string

returns

Promise<boolean>

true if user confirms, false otherwise

ConfirmButton
#

ui/ConfirmButton.svelte view source

accepts children

import ConfirmButton from '@fuzdev/fuz_app/ui/ConfirmButton.svelte';

onconfirm

type (popover: Popover) => void
snippet parameters
popover Popover

popover_button_attrs?

type HTMLButtonAttributes
optional

hide_on_confirm?

type boolean
optional default true

popover_content?

Unlike on PopoverButton this is optional and has a confirm arg

type Snippet<[popover: Popover, confirm: () => void]>
optional
snippet parameters
popover Popover
confirm () => void

popover_button_content?

Content for the popover button

type Snippet<[popover: Popover, confirm: () => void]>
optional
snippet parameters
popover Popover
confirm () => void

children?

Unlike on PopoverButton this has a confirm arg

type Snippet<[popover: Popover, confirm: () => void]>
optional
snippet parameters
popover Popover
confirm () => void

label?

Simple string content for the trigger. Mutually exclusive with children.

type string
optional

pending?

When true, the trigger is disabled and a spinner overlays the content (mirrors PendingButton). The label / children stay rendered underneath so the button keeps its size.

type boolean
optional default false

position?

optional default 'left'

align?

optional

disable_outside_click?

type boolean
optional

popover_class?

type string
optional

popover_attrs?

type HTMLAttributes<HTMLDivElement>
optional

popover_container_attrs?

type HTMLAttributes<HTMLDivElement>
optional

button?

type Snippet<[popover: Popover]>
optional
snippet parameters
popover Popover

disabled?

type boolean | null
optional

external types

OmitStrict<SvelteHTMLElements['button'], 'children'>

ConformanceCase
#

testing/cross_backend/conformance_case.ts view source

value + type

{ name: string; request: { method: string; as: "token" | "keeper" | "daemon" | "invalid_daemon" | "daemon_browser" | "bearer_browser" | "malformed_bearer" | "scoped_token" | "anonymous" | "fresh_non_admin" | "role_holder" | "wrong_role" | "expired_session"; params?: unknown; verb?: "GET" | ... 1 more ... | undefined... import {ConformanceCase} from '@fuzdev/fuz_app/testing/cross_backend/conformance_case.js';

A single conformance case. name is the assertion; the optional free-text note is printed in the test label / failure output. A security case's note should reference a public fuz_app doc property (security.md / architecture.md / module TSDoc), since the table ships in a public package — not an internal planning doc. The note is documentation, not a gate: it stays free-text by design because a non-empty-string check never catches a *wrong* citation — the citation is verified in review.

name

type string

request

type ConformanceCaseRequest

expect

type ConformanceCaseExpectation

note?

type string

xfail?

type ConformanceCaseXfail

ConformanceCaseExpectation
#

testing/cross_backend/conformance_case.ts view source

value + type

{ status: number; error_reason?: string | undefined; fields?: Record<string, unknown> | undefined; absent_fields?: string[] | undefined; headers?: Record<string, string | null> | undefined; equivalence_group?: string | undefined; } import {ConformanceCaseExpectation} from '@fuzdev/fuz_app/testing/cross_backend/conformance_case.js';

The expected response shape a conformance case asserts.

status

type number

error_reason?

type string

fields?

type Record<string, unknown>

absent_fields?

type string[]

headers?

type Record<string, string | null>

equivalence_group?

type string

ConformanceCaseRequest
#

testing/cross_backend/conformance_case.ts view source

value + type

{ method: string; as: "token" | "keeper" | "daemon" | "invalid_daemon" | "daemon_browser" | "bearer_browser" | "malformed_bearer" | "scoped_token" | "anonymous" | "fresh_non_admin" | "role_holder" | "wrong_role" | "expired_session"; params?: unknown; verb?: "GET" | ... 1 more ... | undefined; } import {ConformanceCaseRequest} from '@fuzdev/fuz_app/testing/cross_backend/conformance_case.js';

The request a conformance case issues.

method

type string

as

type "token" | "keeper" | "daemon" | "invalid_daemon" | "daemon_browser" | "bearer_browser" | "malformed_bearer" | "scoped_token" | "anonymous" | "fresh_non_admin" | "role_holder" | "wrong_role" | "expired_session"

params?

type unknown

verb?

type "GET" | "POST"

ConformanceCaseXfail
#

testing/cross_backend/conformance_case.ts view source

value + type

{ tracking_id: string; reason: string; } import {ConformanceCaseXfail} from '@fuzdev/fuz_app/testing/cross_backend/conformance_case.js';

Marks a case as a deferred-by-design gap. The runner routes it through xfail_until instead of a normal test — visible (distinct from pass) and self-cleaning (flips red when the impl starts passing, forcing the marker's removal). Use for declared gaps (e.g. facts), never for in-scope gaps (those fail loud as a red test).

tracking_id

type string

reason

type string

ConformancePrincipal
#

testing/cross_backend/conformance_case.ts view source

value + type

"token" | "keeper" | "daemon" | "invalid_daemon" | "daemon_browser" | "bearer_browser" | "malformed_bearer" | "scoped_token" | "anonymous" | "fresh_non_admin" | "role_holder" | "wrong_role" | "expired_session"

type "token" | "keeper" | "daemon" | "invalid_daemon" | "daemon_browser" | "bearer_browser" | "malformed_bearer" | "scoped_token" | "anonymous" | "fresh_non_admin" | "role_holder" | "wrong_role" | "expired_session"

import {ConformancePrincipal} from '@fuzdev/fuz_app/testing/cross_backend/conformance_case.js';

Closed enum of fixture-provisioned principals a case runs as. Each value maps to a TestFixture accessor (or a seeded extra_accounts entry) in the runner's resolve_principal — there is no inline credential minting in a case (that would be the setup-DSL trap).

  • keeper — the per-test bootstrapped keeper (holds ROLE_KEEPER + ROLE_ADMIN), session credential.
  • daemon — the keeper authenticated via the daemon-token header.
  • invalid_daemon — a *malformed/invalid* X-Daemon-Token carried alongside the keeper's session cookie, over a non-browser (no-Origin) transport. The middleware soft-fail-discards the invalid daemon token (matching the Rust spine's None), so auth falls through to the session leg: the request authenticates as the keeper-via-session and a daemon-gated action then refuses the session credential with credential_type_required — not a hard invalid_daemon_token 401. The no-Origin transport keeps the daemon token on the invalid-token path rather than the browser-context discard; the session base credential is what makes the credential-type gate (not the auth gate) the refusing layer (without it the discard would 401 anonymous).
  • daemon_browser — a *valid* X-Daemon-Token carried in a browser context (default Origin present) alongside the keeper's session cookie. Browsers attach Origin automatically; the daemon-token middleware discards a header-bearing daemon token as browser context (mirroring the bearer guard and the Rust spine's is_browser_context), so the *valid* token is dropped and auth falls through to the session leg → a daemon-gated action then refuses the session credential with credential_type_required. Distinct from invalid_daemon: here the token is well-formed and current, so a 403 (not a 400 confirm-guard hit like daemon) proves the browser-context discard fired — a valid daemon token does NOT authenticate when an Origin is present. Origin is deliberately NOT suppressed; its presence is the signal under test.
  • token — the keeper authenticated via a bearer api-token (non-browser context; the runner suppresses Origin so the token isn't discarded).
  • bearer_browser — a *valid* bearer api-token carried in a browser context (default Origin present), fresh jar so NO session rides alongside. The bearer middleware discards the token as browser context (mirroring the daemon guard + the Rust spine's is_browser_context), so the request arrives anonymous and an authed action 401s. Proves a stolen bearer cannot be replayed from a browser — wire-indistinguishable from sending no credential (the token principal is the honored counterpart: it suppresses Origin, so the same token authenticates).
  • malformed_bearer — the keeper's bearer api-token with its last character dropped, non-browser context, fresh jar. A value that cannot be a token fails each spine's shape precheck and soft-fails before any database work, so the request arrives anonymous — wire-indistinguishable from sending no credential.
  • scoped_token — the keeper authenticated via a bearer api-token minted with a narrowed TokenScope (`{kind: 'methods', methods: [SCOPED_TOKEN_ADMITTED_METHOD]}`), non-browser context. Minted through the production account_token_create path over the keeper's session, not seeded — _testing_reset deliberately seeds a full token, and a harness that narrowed the seed would silently re-scope every other bearer case. The scope gate sits between the credential gate and the role gate, so this principal is what distinguishes token_scope_required from the credential-type and role denials on either side of it.
  • anonymous — no credential, fresh cookie jar.
  • fresh_non_admin — a freshly minted account with no roles, session credential (via the production invite → signup → login flow).
  • role_holder — a seeded extra_accounts principal holding a specific role; the runner reads it by the username named in ConformanceTableOptions.principals.role_holder.
  • wrong_role — a seeded extra_accounts principal holding a role other than the one a route requires; named via ConformanceTableOptions.principals.wrong_role.
  • expired_session — the keeper account presented via an *expired server-side session* cookie (minted by fixture.mint_expired_session(): a backdated auth_session row behind a still-valid signed cookie payload, so the authoritative DB-row expiry gate is what refuses it).

ConformancePrincipalConfig
#

testing/cross_backend/conformance_table.ts view source

ConformancePrincipalConfig import type {ConformancePrincipalConfig} from '@fuzdev/fuz_app/testing/cross_backend/conformance_table.js';

Names a seeded extra_accounts username for the role_holder / wrong_role principals — the only two that aren't backed by an always-available fixture accessor. Suites exercising those principals declare the matching extra_accounts at setup and name them here.

role_holder?

extra_accounts username for the role_holder principal.

type string

readonly

wrong_role?

extra_accounts username for the wrong_role principal.

type string

readonly

ConformanceTableOptions
#

testing/cross_backend/conformance_table.ts view source

ConformanceTableOptions import type {ConformanceTableOptions} from '@fuzdev/fuz_app/testing/cross_backend/conformance_table.js';

cases

The conformance cases to run, in order.

type readonly ConformanceCase[]

readonly

setup_test

Per-test fixture producer (in-process or cross-process).

type (): Promise<TestFixtureBase>

readonly
returns Promise<TestFixtureBase>

surface_source

Surface spec — supplies the RouteSpecs for the REST branch.

type AppSurfaceSpec

readonly

capabilities

Declared backend capabilities (reserved for capability-gated rows).

type BackendCapabilities

readonly

rpc_endpoints

RPC endpoints — resolved to find each method's action spec.

type RpcEndpointsSuiteOption

readonly

session_options

Session options — needed to resolve the rpc_endpoints factory form.

type SessionOptions<string>

readonly

principals?

Maps the role_holder / wrong_role principals to seeded usernames.

type ConformancePrincipalConfig

readonly

suite_name?

describe block label. Defaults to 'conformance table'.

type string

readonly

connect_raw_ws
#

testing/transports/ws_raw_client.ts view source

(options: RawWsConnectOptions): Promise<RawWsClient> import {connect_raw_ws} from '@fuzdev/fuz_app/testing/transports/ws_raw_client.js';

Upgrade a raw TCP socket to a WebSocket.

Resolves once the server answers 101; rejects on any other status or a connection error.

options

returns

Promise<RawWsClient>

throws

  • Error - when the upgrade is refused or the connection fails

ConnectionCloser
#

actions/connection_closer.ts view source

ConnectionCloser import type {ConnectionCloser} from '@fuzdev/fuz_app/actions/connection_closer.js';

Close live connections by identity, or all of them.

Each method returns how many connections it closed. Closing is idempotent: a connection already gone isn't counted, so a handler's close and an audit listener's close on the same revocation compose. Implementations are synchronous.

One connection that fails to close must not spare the rest: an implementation attempts every matching connection, then throws the first error it met.

BackendWebsocketTransport satisfies this structurally, and create_sse_connection_closer adapts a SubscriberRegistry.

close_sockets_for_session

Close every connection authenticated by the session whose blake3 hash is session_token_hash.

type (session_token_hash: string): number

session_token_hash

type string
returns number

close_sockets_for_token

Close every connection authenticated by the API token api_token_id.

type (api_token_id: string): number

api_token_id

type string
returns number

close_sockets_for_account

Close every connection of account_id, whatever credential opened it — the close for a revocation that invalidates all of an account's credentials.

type (account_id: string & $brand<"Uuid">): number

account_id

type Uuid
returns number

close_sockets_for_role

Close the connections of account_id whose endpoint gated them on role — the close for a revoked global grant of that role (a scoped grant never admits at a role gate, so revoking one closes nothing; the caller filters, to_global_role_revocation). A WebSocket gets WS_CLOSE_PERMISSION_REVOKED, not a revocation's code: the credential is still good.

Over-closes an any-of gate: a connection whose endpoint listed several roles closes on the loss of any of them, whether or not the account still holds another. The client reconnects into the gate if it still qualifies.

type (account_id: string & $brand<"Uuid">, role: string): number

account_id

type Uuid

role

type string
returns number

close_all_sockets

Close every connection, whoever opened it — the shutdown close AppServer.close runs. Not a revocation: a WebSocket gets WS_CLOSE_GOING_AWAY, so its client reconnects rather than treating its credential as revoked. The transport is left closing: a registration that arrives afterward is born closed, refused at once (BackendWebsocketTransport and SubscriberRegistry both do).

type (): number

returns number

ConnectionCloseTarget
#

actions/connection_closer.ts view source

ConnectionCloseTarget

type { kind: "session"; session_token_hash: string; } | { kind: "token"; api_token_id: string; } | { kind: "account"; account_id: string & $brand<"Uuid">; } | { kind: "role"; account_id: string & $brand<"Uuid">; role: string; }

import type {ConnectionCloseTarget} from '@fuzdev/fuz_app/actions/connection_closer.js';

Which connections a revocation closes — the argument of queue_connection_close, one variant per revocation close of ConnectionCloser (close_all_sockets is shutdown, never a revocation). No handler queues role today: the audit listeners are a role's only closer.

ConnectionIdentity
#

actions/transports_ws_backend.ts view source

ConnectionIdentity import type {ConnectionIdentity} from '@fuzdev/fuz_app/actions/transports_ws_backend.js';

Auth identity attached to a single WebSocket connection.

One record per connection. token_hash is set for cookie-session connections, api_token_id for bearer (api_token) connections, and both are null for daemon-token connections (reachable only via BackendWebsocketTransport.close_sockets_for_account). required_roles is what the connection's endpoint gated the upgrade on, kept per registration because endpoints with different gates can share one transport.

token_hash

Blake3 session token hash, or null for non-session credentials.

type string | null

account_id

Authenticated account id. Always set.

type Uuid

api_token_id

api_token.id for bearer-authenticated connections, else null.

type string | null

required_roles

The roles the endpoint required at upgrade (any-of), or empty for an ungated endpoint — what close_sockets_for_role matches on.

type readonly string[]

console_action_log
#

actions/action_event_types.ts view source

ActionLog import {console_action_log} from '@fuzdev/fuz_app/actions/action_event_types.js';

Fallback log for environments that leave log unset: warnings and errors go to console, so a notification that fails to parse or a handler that throws is visible rather than silently dropped; per-message debug chatter is discarded. A plain console adapter rather than a Logger so it's safe in any runtime, the browser included.

ContributionRule
#

CookieAttributesCrossTestOptions
#

testing/cross_backend/cookie_attributes.ts view source

CookieAttributesCrossTestOptions import type {CookieAttributesCrossTestOptions} from '@fuzdev/fuz_app/testing/cross_backend/cookie_attributes.js';

Options for the session-cookie-attribute parity suite.

setup_test

Per-test fixture producer (cross-process only — see the module doc).

type (): Promise<TestFixtureBase>

readonly
returns Promise<TestFixtureBase>

cookie_name

The spine's session cookie name (handle.config.cookie_name, e.g. fuz_session). The Set-Cookie under test is matched by this name so a future cookie-name change surfaces here as "no session cookie set" rather than a silent miss.

type string

readonly

login_path?

REST login route path. Default /api/account/login.

type string

readonly

logout_path?

REST logout route path. Default /api/account/logout.

type string

readonly

CoverageFilterOptions
#

testing/error_coverage.ts view source

CoverageFilterOptions import type {CoverageFilterOptions} from '@fuzdev/fuz_app/testing/error_coverage.js';

Options controlling which routes/statuses are considered for coverage.

ignore_routes?

Routes to skip, in 'METHOD /path' format.

type string[]

ignore_statuses?

HTTP status codes to skip.

type number[]

create_account_actions
#

auth/account_actions.ts view source

(deps: RevokingActionFactoryDeps, options?: AccountActionOptions): RpcAction[] import {create_account_actions} from '@fuzdev/fuz_app/auth/account_actions.js';

Create the self-service account RPC actions.

deps

RevokingActionFactoryDeps (log, audit, connection_closer). audit.emit writes audit rows via the captured pool; the bound emitter encapsulates on_audit_event fan-out and the optional AuditLogConfig. connection_closer closes a revoked credential's live connections after the revocation commits.

options

per-factory configuration

default {}

returns

RpcAction[]

the RpcAction array to spread into a create_rpc_endpoint call

create_account_route_shapes
#

auth/account_route_schema.ts view source

(options: AccountRouteShapeOptions): [Omit<RouteSpec, "handler">, Omit<RouteSpec, "handler">, Omit<RouteSpec, "handler">, Omit<...>] import {create_account_route_shapes} from '@fuzdev/fuz_app/auth/account_route_schema.js';

The four account route shapes (/verify, /login, /logout, /password) minus their handlers — pure hono-free data. create_account_route_specs spreads each and attaches the live handler; cross-process surface builders spread them with stub handlers. Single source of truth — the shapes can't drift between the live routes and the surface.

Returns a fixed 4-tuple [verify, login, logout, password] so destructuring yields non-optional shapes under noUncheckedIndexedAccess.

options

returns

[Omit<RouteSpec, "handler">, Omit<RouteSpec, "handler">, Omit<RouteSpec, "handler">, Omit<RouteSpec, "handler">]

create_account_route_specs
#

auth/account_routes.ts view source

(deps: RouteFactoryDeps, options: AccountRouteOptions): RouteSpec[] import {create_account_route_specs} from '@fuzdev/fuz_app/auth/account_routes.js';

Create account route specs for session-based auth.

The returned specs cover the REST flows that stay after the RPC migration: /status (account info + bootstrap availability), /verify (nginx auth_request shim), /login, /logout, /password. /status is bundled here (relative path, prefixed to /api/account/status by the caller) so every account surface serves it, matching the Rust account_router. Self-service session/token management is on auth/account_actions.ts.

/login closes the connections of the sessions its cap evicts, and /logout and /password close the account's, each once its revocation has committed: /logout through the route transaction's post-commit queue (queue_connection_close), /login and /password — which open their own transaction around just their writes — straight after it commits (close_connections_after_commit, which logs a failing close rather than failing the committed request), so a later throw can't drop a close a committed revocation owes. All on deps.connection_closer.

deps

stateless capabilities (keyring, password, log, audit, connection_closer)

options

per-factory configuration (session_options, login_ip_rate_limiter, login_account_rate_limiter, bootstrap_status)

returns

RouteSpec[]

route specs (not yet applied to Hono)

create_account_status_route_spec
#

auth/account_routes.ts view source

(options?: AccountStatusOptions | undefined): RouteSpec import {create_account_status_route_spec} from '@fuzdev/fuz_app/auth/account_routes.js';

Create the account status route spec.

Handles both authenticated and unauthenticated requests:

  • Authenticated: returns {account} with 200
  • Unauthenticated: returns 401 with optional bootstrap_available flag

This eliminates the need for a separate /health fetch on page load — the frontend gets both session state and bootstrap availability in one request.

options?

optional configuration (bootstrap_status for bootstrap detection)

optional

returns

RouteSpec

a single account status route spec

create_action_event
#

actions/action_event.ts view source

<TMethod extends string = string>(environment: ActionEventEnvironment, spec: { method: string; initiator: "frontend" | "backend" | "both"; side_effects: boolean; input: ZodType<unknown, unknown, $ZodTypeInternals<...>>; ... 7 more ...; rate_limit?: "both" | ... 2 more ... | undefined; } | { ...; } | { ...; }, input: unknown, initial_phase?: "send_request" | ... 8 more ... | undefined): ActionEvent<...> import {create_action_event} from '@fuzdev/fuz_app/actions/action_event.js';

Create an action event from a spec and initial input.

environment

spec

input

type unknown

initial_phase?

optional

returns

ActionEvent<TMethod, "send_request" | "receive_request" | "send_response" | "receive_response" | "send_error" | "receive_error" | "send" | "receive" | "execute", "initial" | "parsed" | "handling" | "handled" | "failed">

generics

create_action_event<TMethod extends string = string>
TMethod
constraint string
default string

throws

  • Error - if `initial_phase` is omitted and the executor cannot initiate the action (per `spec.initiator`)

create_action_event_from_json
#

actions/action_event.ts view source

<TMethod extends string = string>(json: ActionEventDataUnion<TMethod>, environment: ActionEventEnvironment): ActionEvent<TMethod, "send_request" | ... 7 more ... | "execute", "initial" | ... 3 more ... | "failed"> import {create_action_event_from_json} from '@fuzdev/fuz_app/actions/action_event.js';

Reconstruct an action event from serialized JSON data.

json

environment

returns

ActionEvent<TMethod, "send_request" | "receive_request" | "send_response" | "receive_response" | "send_error" | "receive_error" | "send" | "receive" | "execute", "initial" | "parsed" | "handling" | "handled" | "failed">

generics

create_action_event_from_json<TMethod extends string = string>
TMethod
constraint string
default string

throws

  • Error - if the JSON's `method` field has no spec registered in `environment`

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)

create_active_role_grant
#

testing/cross_backend/role_grant_cross_helpers.ts view source

(fixture: TestFixtureBase, recipient: TestAccount, options?: RoleGrantFixtureOptions): Promise<string> import {create_active_role_grant} from '@fuzdev/fuz_app/testing/cross_backend/role_grant_cross_helpers.js';

Materialize an active global grant of role on recipient through a fresh keeper offer and the recipient's accept, returning its role_grant_id — the handle role_grant_revoke keys on. Idempotent on a grant the account already holds: the accept returns that grant's id.

fixture

the per-test fixture

recipient

the account that accepts the offer

options

the role and the RPC path

default {}

returns

Promise<string>

the active grant's id

create_actor_lookup_actions
#

create_actor_search_actions
#

create_admin_actions
#

auth/admin_actions.ts view source

(deps: RevokingActionFactoryDeps, options?: AdminActionOptions): RpcAction[] import {create_admin_actions} from '@fuzdev/fuz_app/auth/admin_actions.js';

Create the admin-only RPC actions.

deps

RevokingActionFactoryDeps (log, audit, connection_closer). log drives RPC-internal error logging; audit.emit writes audit rows via the captured pool. The bound emitter encapsulates listener fan-out and the optional AuditLogConfig. connection_closer closes the target account's live connections after a revoke-all, delete, or purge commits.

options

role schema for grantable_roles derivation

default {}

returns

RpcAction[]

the RpcAction array to spread into a create_rpc_endpoint call

create_admin_rpc_adapters
#

ui/admin_rpc_adapters.ts view source

(api: AdminRpcApi): AdminRpcAdapters import {create_admin_rpc_adapters} from '@fuzdev/fuz_app/ui/admin_rpc_adapters.js';

Build the four admin RPC adapters from a typed throwing RPC client.

Method-name mapping:

Narrow RPC methodAction spec method
admin_accounts.list_accountsadmin_account_list
admin_accounts.delete_accountaccount_delete (soft)
admin_accounts.undelete_accountaccount_undelete
admin_accounts.list_sessionsadmin_session_list
admin_accounts.create_role_grantrole_grant_offer_create
admin_accounts.revoke_role_grantrole_grant_revoke
admin_accounts.retract_offerrole_grant_offer_retract
admin_accounts.session_revoke_alladmin_session_revoke_all
admin_accounts.token_revoke_alladmin_token_revoke_all
admin_invites.listinvite_list
admin_invites.createinvite_create
admin_invites.deleteinvite_delete
audit_log.listaudit_log_list
audit_log.role_grant_historyaudit_log_role_grant_history
app_settings.getapp_settings_get
app_settings.updateapp_settings_update

All four adapter factories call through the same api — consumers pass the typed throwing Proxy from create_frontend_rpc_client once, regardless of how many admin surfaces they mount.

api

returns

AdminRpcAdapters

create_admitted_ws_transport
#

testing/transports/ws_transport.ts view source

(options: WsTransportOptions): Promise<WsClient> import {create_admitted_ws_transport} from '@fuzdev/fuz_app/testing/transports/ws_transport.js';

Open a real-upgrade WS client and round-trip heartbeat on it, so the backend has admitted the connection by the time this resolves.

Both spines answer the upgrade handshake before the connection is registered and admitted (the credential is re-read in between), and deliver no broadcast or targeted notification to a connection still pending admission. A request is answered only once the connection is admitted, so the round trip is the one signal a client has. Suites that open a socket and then wait for a server-initiated message, or that need connections registered in a known order, open through this.

heartbeat is a protocol action, present on every WS endpoint. Its response frame (id WS_ADMISSION_PROBE_ID) stays in the client's messages.

options

returns

Promise<WsClient>

throws

  • Error - if the upgrade fails, or the heartbeat is refused or times out — the socket is closed first

create_all_cell_actions
#

auth/all_cell_actions.ts view source

(deps: CellActionDeps, options: AllCellActionsOptions): RpcAction[] import {create_all_cell_actions} from '@fuzdev/fuz_app/auth/all_cell_actions.js';

Build the full cell RPC action set — CRUD (create_cell_actions, which also carries cell_clone) + grant ACL + field + item relations + per-cell audit — as a single handler-bound bundle.

The handler-side twin of the all_cell_action_specs spec bundle and the sibling of create_standard_rpc_actions. Assembling the five cell factories here means an HTTP-RPC mount and a WS mount (or two different backends) can't silently diverge on which cell verbs they expose — the spine_method_coverage reconciliation gate enforces that the spine's live mount matches its coverage manifest, and this aggregator is the single list every mount draws from.

Distinct from create_cell_actions (the CRUD-only factory this bundles) — reach for this whenever a backend mounts the complete cell layer.

deps

CellActionDeps (log, audit, optional validate_data)

options

the role schema for grant validation

returns

RpcAction[]

every cell RpcAction, in mount order

create_app_backend
#

server/app_backend.ts view source

(options: CreateAppBackendOptions): Promise<AppBackend> import {create_app_backend} from '@fuzdev/fuz_app/server/app_backend.js';

Initialize the backend: database + auth migrations + deps.

Calls create_db → run_migrations (auth namespace, then any migration_namespaces from options in order) → audit_factory({db, log}) and bundles the result with the provided keyring and password deps and a connection_closer (empty until create_app_server adds its transports).

options

keyring, password deps, audit_factory, optional database URL, and optional migration_namespaces

returns

Promise<AppBackend>

app backend with deps, database metadata, and combined migration results

throws

  • Error - if `migration_namespaces` contains a namespace in `reserved_migration_namespaces`

create_app_server
#

server/app_server.ts view source

(options: AppServerOptions): Promise<AppServer> import {create_app_server} from '@fuzdev/fuz_app/server/app_server.js';

Create a fully assembled Hono app with auth, middleware, and routes.

Handles the assembly lifecycle: rate limiters → audit SSE → proxy middleware → auth middleware → bootstrap status → route specs (consumer, bootstrap, RPC endpoints, surface) → surface generation → Hono app assembly (routes, then WS endpoints) → post-route middleware → static serving → the auth cleanup schedule. Database migrations belong to the backend lifecycle — pass migration_namespaces to create_app_backend.

When audit_log_sse is set, the SSE registry's listener is registered via backend.deps.audit.add_listener — no shallow-copy of AppDeps. That registry and every WebSocket transport mounted from ws_endpoints are added to backend.deps.connection_closer, so the revocation handlers built from ctx.deps close their connections with no further wiring. The audit_sse field on the returned AppServer (and the AppServerContext passed to create_route_specs) is non-null in that case; consumers can call require_audit_sse(ctx) / require_audit_sse(server) to assert the invariant.

Assembly acquires a few things that outlive it — the rate limiters it builds, the listeners it registers on deps.audit, the members it adds to deps.connection_closer — and records a release for each as it goes. When assembly throws, every release recorded so far runs, last acquired first (one that throws is logged and the rest still run), and the original error is rethrown: a failed assembly leaves the backend's emitter and closer as it found them and no limiter timer running. When auth_cleanup is set, the auth cleanup schedule starts as the last step of assembly — nothing after it can throw, so an assembly that throws leaves no schedule running.

The returned close owns shutdown: it stops that schedule, closes every live connection on the backend's closer, runs the same releases, and closes the database (AppServer.close).

options

returns

Promise<AppServer>

the assembled Hono app with its surface, bootstrap status, migration results, audit SSE, mounted WS transports, and close

throws

  • Error - from any assembly step, after releasing what assembly acquired

create_app_surface_spec
#

create_audit_emitter
#

auth/audit_emitter.ts view source

(options: CreateAuditEmitterOptions): AuditEmitter import {create_audit_emitter} from '@fuzdev/fuz_app/auth/audit_emitter.js';

Build a bound AuditEmitter. Typical caller is the consumer's audit_factory callback on CreateAppBackendOptions — create_app_backend invokes that callback with its constructed {db, log} and lands the result on AppDeps.audit.

options

pool, logger, optional initial subscriber, optional config

returns

AuditEmitter

the bound emitter; closes over the pool + config + listener chain

create_audit_log_config
#

auth/audit_log_schema.ts view source

(options?: CreateAuditLogConfigOptions | undefined): AuditLogConfig import {create_audit_log_config} from '@fuzdev/fuz_app/auth/audit_log_schema.js';

Build an AuditLogConfig by merging fuz_app builtins with consumer extras.

Throws when an extra_events key collides with a builtin event type, or fails AuditEventTypeName format validation.

Call once at startup; pass the result into the consumer's audit_factory body — typically `({db, log}) => create_audit_emitter({db, log, audit_log_config, ...})` — so it gets captured inside the bound AppDeps.audit emitter. Builtin handlers omit the audit_log_config slot and pick up builtin_audit_log_config.

options?

optional

returns

AuditLogConfig

throws

  • Error - when an `extra_events` key collides with a builtin event type or fails `AuditEventTypeName` format validation

create_audit_log_route_shape
#

auth/audit_log_route_schema.ts view source

(required_role?: string): Omit<RouteSpec, "handler"> import {create_audit_log_route_shape} from '@fuzdev/fuz_app/auth/audit_log_route_schema.js';

The GET /audit/stream SSE route shape minus its handler — pure hono-free data. create_audit_log_route_specs spreads this and attaches the live SSE handler; cross-process surface builders spread it with a stub handler. The output is z.null() because SSE streams have no JSON response body.

required_role

role gating the stream (default DEFAULT_AUDIT_STREAM_ROLE)

type string
default DEFAULT_AUDIT_STREAM_ROLE

returns

Omit<RouteSpec, "handler">

the SSE route shape minus its handler

create_audit_log_route_specs
#

auth/audit_log_routes.ts view source

(options?: AuditLogRouteOptions | undefined): RouteSpec[] import {create_audit_log_route_specs} from '@fuzdev/fuz_app/auth/audit_log_routes.js';

Create the optional audit-log SSE route spec.

Returns an empty array when options.stream is not set — no REST routes live here apart from the stream.

Admission

A stream is authorized once, so the handler must not open one on a credential or role revoked while the request was in flight. The route's gates (401, credential channel, token scope, acting actor, role) run in the route-spec pipeline, before the handler and so before any stream is registered — a revocation landing after them would close nothing. So the handler registers the stream pending (SubscriberRegistry.subscribe_pending), then re-reads the credential (revalidate_resolved_auth) and the acting actor's role grants, and only then admits (SubscriberRegistry.admit). A revocation that committed before those reads is seen by them — 401 authentication_required for a dead credential, 403 insufficient_permissions for a lost role — and one whose close ran after the registration found the pending entry: admission then fails, and the response is a 200 whose body is the connect comment and nothing else — to an EventSource, a stream closed an instant after it opened (the client's reconnect is answered by the gates, with the precise status). A pending registration receives no audit row, counts toward no cap, and is removed when a re-read refuses the request. Once the server is shutting down (SubscriberRegistry class doc, "Shutdown") the registration is born closed, the re-reads are skipped, and the answer is that same connect comment alone — as it is when the shutdown closes a registration whose re-read is in flight, even if the re-read then fails. The twin of the Rust spine's audit_stream_router, which registers before its role read rather than repeating it.

Credential deadline

Expiry writes nothing a revocation listener could hear, so an admitted stream watches its own credential's expiry (ResolvedAuth.credential_deadline — for the session-only stock route, the earlier of the cookie's and the row's) and is closed at it, as a revoked stream is: the body ends with no final frame, and the client's reconnect meets the gates. The wait is schedule_credential_deadline's chain of bounded timers, cancelled when the stream closes.

options?

optional stream wiring + role override

optional

returns

RouteSpec[]

the SSE route spec (when options.stream is provided) or an empty array

create_audit_log_sse
#

realtime/sse_auth_guard.ts view source

(options: { role?: string | undefined; log: Logger; connection_closer: RealtimeCloser | null; max_per_scope?: number | null | undefined; }): AuditLogSse import {create_audit_log_sse} from '@fuzdev/fuz_app/realtime/sse_auth_guard.js';

Create a complete audit log SSE setup with broadcasting, auth guard, and connection closer.

Combines SubscriberRegistry, create_sse_auth_guard, and the broadcast call into a single object, and adds the registry to connection_closer for good — the remover add returns is dropped, so a caller that must take the registry out again passes null and adds it itself. The result satisfies AuditLogRouteOptions['stream'] and provides the on_audit_event listener for the audit emitter.

Most consumers pass audit_log_sse: true to create_app_server and never touch this directly — the factory builds an AuditLogSse, registers audit_sse.on_audit_event via backend.deps.audit.add_listener, and exposes it via AppServerContext.audit_sse. Reach for the manual path (compose inside audit_factory body, or audit.add_listener(audit_sse.on_audit_event) post-assembly) only when wiring outside create_app_server.

options

factory options

type { role?: string | undefined; log: Logger; connection_closer: RealtimeCloser | null; max_per_scope?: number | null | undefined; }

returns

AuditLogSse

audit log SSE setup (stream options + on_audit_event + registry)

examples

const connection_closer = create_realtime_closer(); const audit_sse = create_audit_log_sse({log, connection_closer}); // On CreateAppBackendOptions — the same closer, and the listener inside // the audit_factory body: connection_closer, audit_factory: ({db, log}) => create_audit_emitter({ db, log, on_audit_event: audit_sse.on_audit_event, }), // In create_route_specs: create_audit_log_route_specs({stream: audit_sse}); // In create_app_server options: event_specs: audit_log_event_specs,

create_auth_middleware_specs
#

auth/middleware.ts view source

(deps: AppDeps, options: AuthMiddlewareOptions): Promise<MiddlewareSpec[]> import {create_auth_middleware_specs} from '@fuzdev/fuz_app/auth/middleware.js';

Create the auth middleware stack.

Returns [origin, session, request_context, bearer_auth] middleware specs: the origin check on every path less its carve-outs, the rest for the given path pattern. When daemon_token_state is provided, appends a daemon_token layer. Apps can append extra entries for non-standard paths (e.g., tx's /tx binary endpoint).

deps

stateless capabilities (keyring, db)

type AppDeps

options

middleware configuration (allowed_origins, session_options, path, daemon_token_state, origin_carve_outs)

returns

Promise<MiddlewareSpec[]>

the middleware spec array

throws

  • Error - when an origin carve-out has no path or no reason, or overlaps `path`

create_auth_test_apps
#

testing/auth_apps.ts view source

(route_specs: RouteSpec[], roles: string[]): AuthTestApps import {create_auth_test_apps} from '@fuzdev/fuz_app/testing/auth_apps.js';

Create one Hono test app per auth level.

route_specs

the route specs to register

type RouteSpec[]

roles

all roles in the app

type string[]

returns

AuthTestApps

create_banner
#

actions/action_codegen.ts view source

(origin_path: string): string import {create_banner} from '@fuzdev/fuz_app/actions/action_codegen.js';

"DO NOT EDIT" banner naming the gen producer.

origin_path

type string

returns

string

create_bearer_auth_middleware
#

auth/bearer_auth.ts view source

(deps: QueryDeps, log: Logger): MiddlewareHandler import {create_bearer_auth_middleware} from '@fuzdev/fuz_app/auth/bearer_auth.js';

Create middleware that authenticates via bearer token.

Soft-fails for malformed, invalid, expired, or empty tokens — calls next() without setting account identity, letting downstream auth enforcement (the RPC dispatcher's pre-authorization / post-authorization auth gates or require_auth) return a consistent JSON-RPC or route-level error. This avoids leaking token-specific diagnostics (invalid_token, account_not_found) that could aid enumeration attacks, and ensures public actions are not blocked by bad credentials.

Discards bearer tokens when an Origin or Referer header is present — next() with no identity, as for a bad token — so browsers must use cookie auth, reducing attack surface. A token that fails the is_api_token_shaped precheck soft-fails before the database is touched. Auth scheme matching is case-insensitive per RFC 7235. On success, sets c.var.auth_account_id, CREDENTIAL_TYPE_KEY = 'api_token', and AUTH_API_TOKEN_ID_KEY. Skips when an account is already authenticated (e.g. by session middleware). Acting-actor resolution + RequestContext construction are deferred to the dispatcher's authorization phase.

There is deliberately no rate limit on this path, and no 429 — every failure soft-fails to "no credential". An API token is 32 bytes of CSPRNG output resolved by a blake3 hash lookup, so guessing is bounded by entropy, not by throttling; a limiter here would buy nothing measurable while costing availability (the check/record has to precede the async lookup to close its own TOCTOU window, so concurrent requests bearing a *valid* token race each other into a 429). The Rust spine never had one here; this is the converged shape. See docs/security.md §Why bearer auth is not rate limited.

deps

query dependencies (pool-level db for middleware)

log

the logger instance

type Logger

returns

MiddlewareHandler

mutates

  • Hono context — sets `ACCOUNT_ID_KEY`, `CREDENTIAL_TYPE_KEY`, `AUTH_API_TOKEN_ID_KEY`, and `CREDENTIAL_DEADLINE_KEY` on success

create_bearer_auth_mocks
#

testing/middleware.ts view source

(tc: BearerAuthTestOptions): BearerAuthMocks import {create_bearer_auth_mocks} from '@fuzdev/fuz_app/testing/middleware.js';

Create mock dependencies for create_bearer_auth_middleware, configured per test case.

Configures the module-level mocks for query_validate_api_token, query_account_by_id, query_actor_by_id, and query_role_grant_find_active_for_actor so each test case controls return values independently.

tc

returns

BearerAuthMocks

mocks bundle with spy references

mutates

  • module-level vi.mock registrations for api_token_queries, — `account_queries`, and `role_grant_queries` — each call resets and re-binds the four spies, so cases run in sequence without bleeding state.

create_bearer_auth_test_app
#

testing/middleware.ts view source

(tc: BearerAuthTestOptions): { app: Hono<BlankEnv, BlankSchema, "/">; mocks: BearerAuthMocks; } import {create_bearer_auth_test_app} from '@fuzdev/fuz_app/testing/middleware.js';

Create a Hono app wired with create_bearer_auth_middleware using mocked deps.

The route handler at /api/test returns the resolved context in the response body, enabling assertions on REQUEST_CONTEXT_KEY and CREDENTIAL_TYPE_KEY.

tc

returns

{ app: Hono<BlankEnv, BlankSchema, "/">; mocks: BearerAuthMocks; }

create_body_limit_middleware
#

http/body_limit.ts view source

(max_size: number): MiddlewareHandler import {create_body_limit_middleware} from '@fuzdev/fuz_app/http/body_limit.js';

Create the body size limit middleware.

max_size

the largest accepted body, in bytes

type number

returns

MiddlewareHandler

middleware answering 413 {error: 'payload_too_large'} past the cap and 400 {error: 'invalid_content_length'} to a Content-Length that is not a run of digits

create_bootstrap_route_specs
#

auth/bootstrap_routes.ts view source

(deps: RouteFactoryDeps, options: BootstrapRouteOptions): RouteSpec[] import {create_bootstrap_route_specs} from '@fuzdev/fuz_app/auth/bootstrap_routes.js';

Create bootstrap route specs for first-time account creation.

deps

stateless capabilities including filesystem access

options

per-factory configuration (session, token path, bootstrap status)

returns

RouteSpec[]

route specs (not yet applied to Hono)

create_broadcast_api
#

actions/broadcast_api.ts view source

<TApi extends object>(options: CreateBroadcastApiOptions): TApi import {create_broadcast_api} from '@fuzdev/fuz_app/actions/broadcast_api.js';

Builds a typed broadcast API from a set of action specs.

For each spec, adds a method keyed by spec.method that:

  • Validates input against the spec's Zod schema (logs and returns on failure)
  • Creates a JSON-RPC notification from the validated input
  • Broadcasts via the peer (filtered by should_deliver when supplied)

Silently returns when no transport is ready (e.g. before any clients connect). Errors during send are logged but never thrown — broadcasts are fire-and-forget from the handler's perspective.

Typed consumer surface

Consumers declare an explicit interface and pin it via the type parameter:

export interface BackendActionsApi { filer_change: (input: ActionInputs['filer_change']) => Promise<void>; workspace_changed: (input: ActionInputs['workspace_changed']) => Promise<void>; } const api = create_broadcast_api<BackendActionsApi>({ peer: backend.peer, specs: [filer_change_action_spec, workspace_changed_action_spec], });

The cast is unchecked — callers must keep the interface and the specs array in sync. Codegen (action_collections.gen.ts) is a natural fit if the consumer already generates per-method type maps.

options

returns

TApi

generics

create_broadcast_api<TApi extends object>
TApi
constraint object

create_bun_testing_adapter
#

create_cell_actions
#

create_cell_audit_actions
#

create_cell_field_actions
#

create_cell_grant_actions
#

create_cell_item_actions
#

create_cli_logger
#

cli/logger.ts view source

(logger: Logger): CliLogger import {create_cli_logger} from '@fuzdev/fuz_app/cli/logger.js';

Creates a CLI logger wrapping a Logger with semantic output methods.

logger

the Logger instance to wrap

type Logger

returns

CliLogger

a CliLogger with CLI semantic methods mapped to Logger levels

create_credential_type_schema
#

auth/credential_type_schema.ts view source

(consumer_types?: Record<string, CredentialTypeMeta>): CredentialTypeSchemaResult import {create_credential_type_schema} from '@fuzdev/fuz_app/auth/credential_type_schema.js';

Create a credential-type schema from the builtin set plus optional consumer-declared additions.

Builtins (session, api_token, daemon_token) are always present; consumer entries that collide with a builtin name throw at construction. Pass the result into create_role_schema's optional credential_types parameter so each role's required_credential_types entries are validated against this set at construction time.

consumer_types

optional consumer-declared credential-type set with optional metadata

type Record<string, CredentialTypeMeta>
default {}

returns

CredentialTypeSchemaResult

{CredentialType, credential_types} — Zod schema and metadata map

throws

  • Error - if any `consumer_types` key fails the `CredentialTypeName` regex, collides with a builtin name, or appears more than once

examples

// simple — builtins only const {CredentialType, credential_types} = create_credential_type_schema(); // with consumer extensions const {CredentialType} = create_credential_type_schema({ sso_assertion: {description: 'OIDC SSO assertion bound to an IdP-asserted account.'}, });

create_cross_backend_global_setup
#

testing/cross_backend/create_cross_backend_global_setup.ts view source

({ configs, derive_name, provide_key }: CrossBackendGlobalSetupOptions): (project: TestProject) => Promise<() => Promise<void>> import {create_cross_backend_global_setup} from '@fuzdev/fuz_app/testing/cross_backend/create_cross_backend_global_setup.js';

Build a vitest globalSetup default export. Returns the (project) => teardown function vitest 4 expects.

__0

returns

(project: TestProject) => Promise<() => Promise<void>>

create_daemon_token_middleware
#

auth/daemon_token_middleware.ts view source

(state: DaemonTokenState, deps: QueryDeps, log: Logger): MiddlewareHandler import {create_daemon_token_middleware} from '@fuzdev/fuz_app/auth/daemon_token_middleware.js';

Create middleware that authenticates via daemon token.

Checks the X-Daemon-Token header. Behavior:

  • No header: pass through (don't touch existing context).
  • Header present + Origin / Referer present: discard the credential (browser context) and pass through — daemon tokens are loopback-only and never carry an Origin in production, so a header-bearing request is not a legitimate daemon caller. Mirrors the bearer guard: next() rather than 401, so downstream auth enforcement returns credential_type_required (not a hard fail). Silent on the wire (anti-enumeration); in DEV only, sets X-Fuz-Auth-Debug: daemon_token_discarded_browser_context.
  • Header present + Zod-invalid (malformed): soft-fail discard (pass through, not 401) — mirrors the bearer guard and the Rust spine's resolve.rs (None). Downstream a daemon-gated action returns credential_type_required; a public action proceeds anonymous.
  • Header present + invalid value (not the current/previous token): soft-fail discard (pass through, not 401) — same downstream behavior.
  • Header present + valid + keeper_account_id null (still pre-bootstrap after the lazy refresh): soft-fail discard (pass through, not 503) — mirrors the Rust spine's resolve.rs (None), so the request falls through to anonymous and a daemon-gated action returns credential_type_required downstream.
  • Header present + valid + ok: set `c.var.auth_account_id = state.keeper_account_id, CREDENTIAL_TYPE_KEY = 'daemon_token'` (overrides any existing session / bearer identity).

Acting-actor resolution + RequestContext construction are deferred to the dispatcher's authorization phase. Multi-actor keeper accounts surface actor_required from there if a daemon caller doesn't pass an explicit acting value.

state

the daemon token runtime state

deps

query dependencies (pool-level db for keeper-account resolution)

log

the logger instance

type Logger

returns

MiddlewareHandler

mutates

  • Hono context — sets `ACCOUNT_ID_KEY`, `CREDENTIAL_TYPE_KEY`, and `AUTH_API_TOKEN_ID_KEY` on a valid token

create_database
#

dev/setup.ts view source

(deps: CommandDeps, db_name: string, options?: CreateDatabaseOptions | undefined): Promise<CommandResult> import {create_database} from '@fuzdev/fuz_app/dev/setup.js';

Create a PostgreSQL database if createdb is available.

Does not throw — returns the underlying command result so callers can decide how to react to a missing createdb or an "already exists" failure.

deps

command execution capability

db_name

database name to create

type string

options?

logger

optional

returns

Promise<CommandResult>

the command result

mutates

  • external database — invokes `createdb` to create `db_name` when available

create_db
#

db/create_db.ts view source

(database_url: string): Promise<CreateDbResult> import {create_db} from '@fuzdev/fuz_app/db/create_db.js';

Create a database connection based on a URL.

The close callback is bound to the actual driver — callers never need to know which driver is in use.

For direct driver construction without URL routing, import create_pg_db from db/db_pg.ts, create_pglite_db from db/db_pglite.ts or create_pglet_db_driver from db/db_pglet.ts.

database_url

connection URL (postgres://, postgresql://, file://, or memory://)

type string

returns

Promise<CreateDbResult>

database instance, close callback, type, and display name

throws

  • Error - if `database_url` uses an unsupported scheme. Driver construction (`pg.Pool`, `PGlite` or pglet) may also throw on bad connection parameters or missing peer-dependency packages.

create_db_route_specs
#

create_default_db_factory
#

testing/db.ts view source

(init_schema: (db: Db) => Promise<void>): DbFactory import {create_default_db_factory} from '@fuzdev/fuz_app/testing/db.js';

Create the default in-memory database factory for tests: pglet-wasm, or the builder set_default_db_factory installed.

The factory fuz_app's own test helpers fall back to when a suite passes none (create_test_app_server, describe_rate_limiting_tests, describe_sse_route_tests). Call create_pglite_factory or create_pglet_wasm_factory directly to pin a driver.

init_schema

callback to initialize the database schema

type (db: Db) => Promise<void>

returns

DbFactory

create_deno_runtime
#

runtime/deno.ts view source

(args: readonly string[]): RuntimeDeps import {create_deno_runtime} from '@fuzdev/fuz_app/runtime/deno.js';

Create a runtime backed by Deno APIs.

Returns an object satisfying all *Deps interfaces from runtime/deps.ts. Pass to shared functions that accept EnvDeps, FsReadDeps, etc.

args

CLI arguments (typically Deno.args)

type readonly string[]

returns

RuntimeDeps

runtime implementation using Deno APIs

create_deno_testing_adapter
#

create_describe_db
#

testing/db.ts view source

(factories: DbFactory | DbFactory[], truncate_tables: string[]): (name: string, fn: (get_db: () => Db) => void) => void import {create_describe_db} from '@fuzdev/fuz_app/testing/db.js';

Create a describe_db function bound to specific factories and truncate tables.

Returns a 2-arg (name, fn) function that runs the test suite against each factory. Each factory gets its own describe block with a shared database instance, automatic beforeEach truncation, and afterAll cleanup. Skipped factories use describe.skip.

factories

one or more database factories to run suites against

truncate_tables

tables to truncate between tests (children first for FK safety)

type string[]

returns

(name: string, fn: (get_db: () => Db) => void) => void

mutates

  • the underlying database between tests — beforeEach issues — `TRUNCATE <truncate_tables> CASCADE` against the shared instance.

create_dual_spawn_global_setup
#

testing/cross_backend/create_dual_spawn_global_setup.ts view source

({ configs, provide_keys }: DualSpawnGlobalSetupOptions): (project: TestProject) => Promise<() => Promise<void>> import {create_dual_spawn_global_setup} from '@fuzdev/fuz_app/testing/cross_backend/create_dual_spawn_global_setup.js';

Build a vitest globalSetup default export that spawns both backends.

__0

returns

(project: TestProject) => Promise<() => Promise<void>>

create_expired_test_cookie
#

create_extract_global_flags
#

cli/args.ts view source

<T extends Record<string, unknown>>(schema: Schema<T, any, string, unknown>, fallback: T): (unparsed: ParsedArgs) => { flags: T; remaining: ParsedArgs; } import {create_extract_global_flags} from '@fuzdev/fuz_app/cli/args.js';

Create a project-specific global flag extractor.

Returns a function that separates global flags from command-specific args. The schema defines which flags are global (with aliases via meta(entry, {aliases}), read on any wrapper layer of the entry), and the fallback provides defaults when parsing fails. Every entry is global, both of a no- pair like color and no-color included, so --no-color, which argv_parse reads as color: false, never lands in remaining. A no- pair and aliases resolve as args_parse resolves them.

schema

fuz_schema object schema for global flags

type Schema<T, any, string, unknown>

fallback

default values when parsing fails

type T

returns

(unparsed: ParsedArgs) => { flags: T; remaining: ParsedArgs; }

extractor function (unparsed) => {flags, remaining}

generics

create_extract_global_flags<T extends Record<string, unknown>>
T
constraint Record<string, unknown>

create_fake_hono_context
#

testing/ws_round_trip.ts view source

(opts: FakeHonoContextOptions): Context<any, any, {}> import {create_fake_hono_context} from '@fuzdev/fuz_app/testing/ws_round_trip.js';

Build a fake Hono Context exposing the auth keys the dispatcher reads via c.get(...) — the ones the production auth middleware sets, including the session token hash a session connection is registered under. Only .get() is populated — no other Hono context surface is simulated.

opts

returns

Context<any, any, {}>

create_fake_ws
#

testing/ws_round_trip.ts view source

(options?: CreateFakeWsOptions | undefined): FakeWs import {create_fake_ws} from '@fuzdev/fuz_app/testing/ws_round_trip.js';

Build a real WSContext backed by in-memory send/close capture. Parsing of outgoing frames is left to the caller — sends holds the raw strings as the dispatcher wrote them.

readyState reads 1 (open) until the socket is closed (2) or terminated (3).

options?

optional

returns

FakeWs

create_fetch_transport
#

testing/transports/fetch_transport.ts view source

(options: FetchTransportOptions): FetchTransport import {create_fetch_transport} from '@fuzdev/fuz_app/testing/transports/fetch_transport.js';

Build a cookie-threading transport pinned to options.base_url. The returned function carries a private Map<name, cookie-head> jar that updates on every response's Set-Cookie and re-sends on every subsequent request.

Request rewriting:

  • Absolute URLs (http://other.example/...) pass through verbatim — handy for cross-origin negative tests that target a deliberately different host.
  • Relative URLs are resolved against base_url.
  • Origin is set to options.origin ?? base_url unless the caller already provided one.
  • Cookie is set from the jar unless the caller already provided one.

options

returns

FetchTransport

create_frontend_rpc_client
#

actions/frontend_rpc_client.ts view source

<TApi extends object>(options: CreateFrontendRpcClientOptions<TApi>): FrontendRpcClient<TApi> import {create_frontend_rpc_client} from '@fuzdev/fuz_app/actions/frontend_rpc_client.js';

Build a frontend-only typed RPC client. See module doc for the bundle's design.

options

returns

FrontendRpcClient<TApi>

generics

create_frontend_rpc_client<TApi extends object>
TApi
constraint object

create_fuz_authorization_handler
#

auth/request_context.ts view source

(deps: QueryDeps): (c: Context<any, any, {}>, spec: RouteSpec) => Promise<void | Response> import {create_fuz_authorization_handler} from '@fuzdev/fuz_app/auth/request_context.js';

Create the route-spec authorization handler used by apply_route_specs.

Reads the acting selector via read_route_acting — c.var.validated_query on GETs, the raw body on mutations, since input validation now runs after the authority gates. Public routes (`auth.account === 'none' && auth.actor === 'none'`) skip the phase entirely.

Per registry-time invariant 2, auth.actor !== 'none' ⟺ the input (or query) schema declares acting?: ActingActor — so the selector is present on exactly the specs that read it, and input validation is what rejects a malformed one.

Resolved contexts land on REQUEST_CONTEXT_KEY so the post-authorization REST middleware (require_role, require_credential_types) reads the actor-bound context off c.var. The HTTP RPC and WS dispatchers consume the apply_authorization_phase outcome directly without round-tripping through c.var.

deps

returns

(c: Context<any, any, {}>, spec: RouteSpec) => Promise<void | Response>

create_grant_path_schema
#

auth/grant_path_schema.ts view source

(consumer_paths?: Record<string, GrantPathMeta>): GrantPathSchemaResult import {create_grant_path_schema} from '@fuzdev/fuz_app/auth/grant_path_schema.js';

Create a grant-path schema from the builtin set plus optional consumer-declared additions.

Builtins (admin, self_service, system, bootstrap) are always present; consumer entries that collide with a builtin name throw at construction. Pass the result into create_role_schema's optional grant_paths parameter so each role's grant_paths entries are validated against this set at construction time.

consumer_paths

optional consumer-declared grant-path set with optional metadata

type Record<string, GrantPathMeta>
default {}

returns

GrantPathSchemaResult

{GrantPath, grant_paths} — Zod schema and metadata map

throws

  • Error - if any `consumer_paths` key fails the `GrantPathName` regex, collides with a builtin name, or appears more than once

examples

// simple — builtins only const {GrantPath, grant_paths} = create_grant_path_schema(); // with consumer extensions const {GrantPath} = create_grant_path_schema({ invite_only: {description: 'Granted by claiming a consumer-issued invite.'}, });

create_health_route_spec
#

http/common_routes.ts view source

(): RouteSpec import {create_health_route_spec} from '@fuzdev/fuz_app/http/common_routes.js';

Create a public health check route spec.

Infrastructure endpoint for uptime monitors and load balancers. Bootstrap availability is exposed via /api/account/status instead.

returns

RouteSpec

create_help
#

cli/help.ts view source

<TCategory extends string>(options: HelpOptions<TCategory>): HelpGenerator import {create_help} from '@fuzdev/fuz_app/cli/help.js';

Create a help generator configured for an application.

options

help configuration

type HelpOptions<TCategory>

returns

HelpGenerator

help generator with generate_main_help, generate_command_help, and get_help_text

generics

create_help<TCategory extends string>
TCategory
constraint string

create_initial_data
#

actions/action_event_helpers.ts view source

(kind: "request_response" | "remote_notification" | "local_call", phase: "send_request" | "receive_request" | "send_response" | "receive_response" | "send_error" | "receive_error" | "send" | "receive" | "execute", method: string, executor: "frontend" | "backend", input: unknown): { ...; } import {create_initial_data} from '@fuzdev/fuz_app/actions/action_event_helpers.js';

kind

phase

method

type string

executor

input

type unknown

returns

ActionEventData

create_jsonrpc_error_response
#

http/jsonrpc_helpers.ts view source

(id: string | number | null, error: { [x: string]: unknown; code: -32700 | -32600 | -32601 | -32602 | -32603 | (number & $brand<"JsonrpcServerErrorCode">); message: string; data?: unknown; }): { ...; } import {create_jsonrpc_error_response} from '@fuzdev/fuz_app/http/jsonrpc_helpers.js';

Creates a JSON-RPC error response message.

id

type string | number | null

error

returns

JsonrpcErrorResponse

create_jsonrpc_error_response_from_thrown
#

http/jsonrpc_helpers.ts view source

(id: string | number | null, error: unknown): { [x: string]: unknown; jsonrpc: "2.0"; id: string | number | null; error: { [x: string]: unknown; code: -32700 | -32600 | -32601 | -32602 | -32603 | (number & $brand<...>); message: string; data?: unknown; }; } import {create_jsonrpc_error_response_from_thrown} from '@fuzdev/fuz_app/http/jsonrpc_helpers.js';

Creates a JSON-RPC error response from any error. Handles ThrownJsonrpcError (preserves code/message/data) and regular Error objects (maps to internal_error, includes stack in DEV).

id

type string | number | null

error

type unknown

returns

JsonrpcErrorResponse

create_jsonrpc_notification
#

http/jsonrpc_helpers.ts view source

(method: string, params: { [x: string]: unknown; } | undefined): { [x: string]: unknown; jsonrpc: "2.0"; method: string; params?: { [x: string]: unknown; } | undefined; } import {create_jsonrpc_notification} from '@fuzdev/fuz_app/http/jsonrpc_helpers.js';

Creates a JSON-RPC notification message (no id, no response expected).

method

type string

params

type JsonrpcNotificationParams | undefined

returns

JsonrpcNotification

create_jsonrpc_request
#

http/jsonrpc_helpers.ts view source

(method: string, params: { [x: string]: unknown; } | undefined, id: string | number): { [x: string]: unknown; jsonrpc: "2.0"; id: string | number; method: string; params?: { [x: string]: unknown; } | undefined; } import {create_jsonrpc_request} from '@fuzdev/fuz_app/http/jsonrpc_helpers.js';

Creates a JSON-RPC request message.

method

type string

params

type JsonrpcRequestParams | undefined

id

returns

JsonrpcRequest

create_jsonrpc_response
#

http/jsonrpc_helpers.ts view source

(id: string | number, result: JSONType): { [x: string]: unknown; jsonrpc: "2.0"; id: string | number; result: JSONType; } import {create_jsonrpc_response} from '@fuzdev/fuz_app/http/jsonrpc_helpers.js';

Creates a JSON-RPC success response message.

id

result

type JSONType

returns

JsonrpcResponse

create_keyring
#

auth/keyring.ts view source

(env_value: string | undefined): Keyring | null import {create_keyring} from '@fuzdev/fuz_app/auth/keyring.js';

Create a keyring from environment variable.

Keys are separated by __ for rotation support. First key is used for signing, all keys are tried for verification.

CryptoKeys are cached on first use for performance.

Security: key rotation is an operational concern. Old keys remain valid for verification indefinitely — a leaked old key can forge session cookies until it is removed from SECRET_FUZ_COOKIE_KEYS. After rotating to a new signing key, remove the old key within a grace period (e.g. 24–48 hours, long enough for active sessions to re-sign with the new key via cookie refresh). Treat SECRET_FUZ_COOKIE_KEYS changes as security-critical deploys.

env_value

the SECRET_FUZ_COOKIE_KEYS environment variable

type string | undefined

returns

Keyring | null

keyring or null if no keys configured

create_loopback_app_server_options
#

testing/app_server.ts view source

(): Pick<AppServerOptions, "allowed_origins" | "proxy" | "env_schema"> import {create_loopback_app_server_options} from '@fuzdev/fuz_app/testing/app_server.js';

The loopback create_app_server options an in-process test server takes: localhost origins allowed, the loopback proxy trusted (so X-Forwarded-For sets the client IP), and an empty env schema. A fresh object per call — spread it ahead of the options a test varies.

returns

Pick<AppServerOptions, "allowed_origins" | "proxy" | "env_schema">

create_message_too_large_error
#

actions/transports_ws.ts view source

(size: number, max_message_bytes: number): { [x: string]: unknown; code: -32700 | -32600 | -32601 | -32602 | -32603 | (number & $brand<"JsonrpcServerErrorCode">); message: string; data?: unknown; } import {create_message_too_large_error} from '@fuzdev/fuz_app/actions/transports_ws.js';

The error FrontendWebsocketTransport answers an oversized message with — invalid_request (the whole message is refused) carrying data.reason: 'payload_too_large', the REST 413's reason.

size

type number

max_message_bytes

type number

returns

JsonrpcErrorObject

create_mock_fs
#

testing/mock_fs.ts view source

(initial_files?: Record<string, string>): MockFs import {create_mock_fs} from '@fuzdev/fuz_app/testing/mock_fs.js';

Creates an in-memory file system for tests.

read_file throws an ENOENT-tagged error for missing paths so callers can exercise the same "file doesn't exist" code path as node:fs.

initial_files

type Record<string, string>
default {}

returns

MockFs

create_mock_runtime
#

runtime/mock.ts view source

(args?: string[]): MockRuntime import {create_mock_runtime} from '@fuzdev/fuz_app/runtime/mock.js';

Create a mock RuntimeDeps for testing.

The mock exit records the code on exit_calls and throws MockExitError (so the never-returning contract holds in tests). fetch throws TypeError when no mock_fetch_responses pattern matches the request URL.

args

type string[]
default []

returns

MockRuntime

MockRuntime with controllable state

examples

const runtime = create_mock_runtime(['apply', 'zap.ts']); runtime.mock_env.set('HOME', '/home/test'); runtime.mock_fs.set('/home/test/.app/config.json', '{}'); await some_function(runtime); assert.strictEqual(runtime.command_calls.length, 1); assert.deepStrictEqual(runtime.exit_calls, [0]);

create_namespace_qualifier
#

actions/action_codegen.ts view source

(sources: readonly SpecSource[], imports: ImportBuilder): { qualify_spec: (spec: { method: string; initiator: "frontend" | "backend" | "both"; side_effects: boolean; ... 8 more ...; rate_limit?: "both" | ... 2 more ... | undefined; } | { ...; } | { ...; }) => string; all_specs: readonly ({ ...; } | ... 1 more ... | { ...; })[]; } import {create_namespace_qualifier} from '@fuzdev/fuz_app/actions/action_codegen.js';

Multi-source consumer helper. Takes a list of {ns, module, specs} rows, registers import * as ns from module for each on imports, builds the method_to_ns lookup with duplicate-method detection, and returns {qualify_spec, all_specs} ready to thread through the high-level helpers.

Closes the per-file boilerplate gap that kept tx + visiones on hand-rolled template strings even after qualify_spec? landed in API review II — the per-call callback wasn't enough; the import dance + dup-check was the real boilerplate.

sources

type readonly SpecSource[]

imports

returns

{ qualify_spec: (spec: { method: string; initiator: "frontend" | "backend" | "both"; side_effects: boolean; input: ZodType<unknown, unknown, $ZodTypeInternals<unknown, unknown>>; output: ZodType<...>; ... 6 more ...; rate_limit?: "both" | ... 2 more ... | undefined; } | { ...; } | { ...; }) => string; all_specs: rea...

throws

  • Error - if two sources contain the same method name (same-method detection is the consumer's primary debugging signal). Also throws if the returned `qualify_spec` is later called with a method not registered in any source.

examples

const sources = [ {ns: 'tx_specs', module: './action_specs.ts', specs: all_zap_action_specs}, {ns: 'admin_specs', module: '@fuzdev/fuz_app/auth/admin_action_specs.ts', specs: all_admin_action_specs}, ]; export const gen: Gen = ({origin_path}) => { const imports = new ImportBuilder(); const {qualify_spec, all_specs} = create_namespace_qualifier(sources, imports); return compose_gen_file({ origin_path, imports, blocks: [ generate_action_specs_record(all_specs, imports, {qualify_spec}), generate_action_inputs_outputs(all_specs, imports, {qualify_spec}), ], }); };

create_node_runtime
#

runtime/node.ts view source

(args?: readonly string[]): RuntimeDeps import {create_node_runtime} from '@fuzdev/fuz_app/runtime/node.js';

Create a RuntimeDeps backed by Node.js APIs.

args

CLI arguments (typically process.argv.slice(2))

type readonly string[]
default process.argv.slice(2)

returns

RuntimeDeps

RuntimeDeps implementation using Node.js runtime

create_node_testing_adapter
#

create_noop_stub
#

testing/stubs.ts view source

<T = any>(_label: string, overrides?: Record<string, unknown> | undefined): T import {create_noop_stub} from '@fuzdev/fuz_app/testing/stubs.js';

Create a Proxy where every method access returns a no-op async function.

Use for deps that may be reached during "correct auth passes guard" tests but whose return values don't matter. Unlike the explicit method listing, this auto-updates when interfaces change.

_label

type string

overrides?

explicit properties to set (e.g. {db: stub_db})

type Record<string, unknown>
optional

returns

T

generics

create_noop_stub<T = any>
T
default any

create_pending_role_grant_offer
#

testing/cross_backend/role_grant_cross_helpers.ts view source

(fixture: TestFixtureBase, to_account_id: string, grantor_headers?: Record<string, string>, options?: RoleGrantFixtureOptions): Promise<...> import {create_pending_role_grant_offer} from '@fuzdev/fuz_app/testing/cross_backend/role_grant_cross_helpers.js';

Create a fresh pending offer of role from grantor_headers (defaults to the keeper) to to_account_id, returning the created offer.

fixture

the per-test fixture

to_account_id

the recipient account

type string

grantor_headers

the grantor's session headers; the keeper's when omitted

type Record<string, string>
default fixture.create_session_headers()

options

the role and the RPC path

default {}

returns

Promise<{ id: string; }>

the created offer

create_pg_db
#

db/db_pg.ts view source

(pool: Pool): DbDriverResult import {create_pg_db} from '@fuzdev/fuz_app/db/db_pg.js';

Create a Db backed by a pg.Pool.

Owns the transaction implementation, acquiring a dedicated pool client per transaction.

pool

an already-constructed pg.Pool

type Pool

returns

DbDriverResult

the Db instance and a close callback bound to pool.end()

create_pg_factory
#

testing/db.ts view source

(init_schema: (db: Db) => Promise<void>, test_url?: string | undefined): DbFactory import {create_pg_factory} from '@fuzdev/fuz_app/testing/db.js';

Create a pg (PostgreSQL) database factory for tests.

Skipped when test_url is not provided. Each create() opens a pool, resets the public schema (reset_public_schema) and runs init_schema — the lifecycle every factory here shares, so a persistent test database never carries stale DDL or tracker rows from an earlier run or another suite's schema. Point test_url at a database used only for tests.

init_schema

callback to initialize the database schema

type (db: Db) => Promise<void>

test_url?

PostgreSQL connection URL (e.g. from TEST_DATABASE_URL)

type string
optional

returns

DbFactory

a factory that creates pg databases. The returned create() throws when test_url is unset (despite the skip: true flag — defense against direct invocation), and rewrites Postgres "database does not exist" errors into a createdb hint message.

create_pglet_db_driver
#

db/db_pglet.ts view source

(pglet: PgletDb): DbDriverResult import {create_pglet_db_driver} from '@fuzdev/fuz_app/db/db_pglet.js';

Create a Db backed by a pglet instance.

Named _driver to stay clear of @fuzdev/pglet-wasm's own create_pglet_db, which creates the instance this wraps. Create it with create_pglet_instance (or with PGLET_COERCION) so it reads values as the other drivers do.

pglet

a @fuzdev/pglet-wasm instance

type PgletDb

returns

DbDriverResult

the Db instance and a close callback bound to pglet.close()

create_pglet_instance
#

db/db_pglet.ts view source

(): Promise<PgletDb> import {create_pglet_instance} from '@fuzdev/fuz_app/db/db_pglet.js';

Create an in-memory pglet instance with PGLET_COERCION.

Loads @fuzdev/pglet-wasm on demand and awaits its init() — a no-op on the package's Node entry, where the wasm loads at import; the browser entry loads it here.

returns

Promise<PgletDb>

a fresh, empty instance; wrap it with create_pglet_db_driver

create_pglet_wasm_factory
#

testing/db.ts view source

(init_schema: (db: Db) => Promise<void>): DbFactory import {create_pglet_wasm_factory} from '@fuzdev/fuz_app/testing/db.js';

Create a pglet-wasm (in-memory) database factory for tests.

Always enabled — needs the optional @fuzdev/pglet-wasm peer installed. Shares one pglet instance across the pglet-wasm factories this module builds, created with fuz_app's value coercion (PGLET_COERCION); each create() resets the schema via DROP SCHEMA public CASCADE.

init_schema

callback to initialize the database schema

type (db: Db) => Promise<void>

returns

DbFactory

create_pglite_db
#

db/db_pglite.ts view source

(pglite: PGlite): DbDriverResult import {create_pglite_db} from '@fuzdev/fuz_app/db/db_pglite.js';

Create a Db backed by a PGlite instance.

Delegates transactions to PGlite's native transaction() method.

pglite

an already-constructed PGlite instance

type PGlite

returns

DbDriverResult

the Db instance and a close callback bound to pglite.close()

create_pglite_factory
#

testing/db.ts view source

(init_schema: (db: Db) => Promise<void>): DbFactory import {create_pglite_factory} from '@fuzdev/fuz_app/testing/db.js';

Create a PGlite (in-memory) database factory for tests.

Always enabled — needs the optional @electric-sql/pglite peer installed. Shares one PGlite WASM instance across the PGlite factories this module builds; each create() resets the schema via DROP SCHEMA public CASCADE instead of paying the cold start again.

init_schema

callback to initialize the database schema

type (db: Db) => Promise<void>

returns

DbFactory

create_proxy_middleware
#

http/proxy.ts view source

(options: ProxyOptions): MiddlewareHandler import {create_proxy_middleware} from '@fuzdev/fuz_app/http/proxy.js';

Create a Hono middleware that resolves the client IP from trusted proxies.

Sets client_ip on the Hono context for downstream use by get_client_ip. All client IPs are normalized (lowercase, IPv4-mapped IPv6 stripped).

Resolution logic:

  1. No X-Forwarded-For → use connection IP directly.
  2. X-Forwarded-For present but connection is untrusted → ignore header (spoofed by a direct attacker), use connection IP.
  3. X-Forwarded-For present and connection is trusted → walk header right-to-left, strip trusted entries, use first untrusted entry.

options

trusted proxy configuration

returns

MiddlewareHandler

throws

  • Error - if any entry in `options.trusted_proxies` is invalid (parsed eagerly via `parse_proxy_entry`)

mutates

  • c.var.client_ip — set to the resolved (or `'unknown'`) client IP per request

create_proxy_middleware_spec
#

http/proxy.ts view source

(options: ProxyOptions): MiddlewareSpec import {create_proxy_middleware_spec} from '@fuzdev/fuz_app/http/proxy.js';

Create a middleware spec for trusted proxy resolution.

Apply before auth middleware so client_ip is available for rate limiting.

options

trusted proxy configuration

returns

MiddlewareSpec

create_rate_limiter
#

create_ready_route_spec
#

http/common_routes.ts view source

(options: ReadyRouteOptions): RouteSpec import {create_ready_route_spec} from '@fuzdev/fuz_app/http/common_routes.js';

Create the /ready readiness route spec — the deploy gate.

Returns 200 {ready: true} when the live DB's columns cover expected, else 503 {error} (schema_drift when columns are missing, db_unreachable when the introspection query throws). The detailed drift goes to the server log only — the public body stays a minimal code so the endpoint doesn't leak schema structure (mirrors why /api/surface is admin-only). A deploy poll treats 503 as a failed release and rolls back, turning a silent schema-drift auth outage into a loud blocked deploy. See db/schema_ready.ts for the column-presence rationale and auth/migrations.ts for the frozen-append discipline that prevents the drift in the first place.

options

returns

RouteSpec

create_realtime_closer
#

actions/connection_closer.ts view source

(): RealtimeCloser import {create_realtime_closer} from '@fuzdev/fuz_app/actions/connection_closer.js';

Create an empty RealtimeCloser.

create_app_backend creates the one on AppDeps.connection_closer, and create_app_server adds each WebSocket transport it mounts and its audit stream registry, removing them when its assembly fails or the server closes. A consumer that mounts a transport by hand adds it through the connection_closer option of register_ws_endpoint / create_audit_log_sse, or with add.

returns

RealtimeCloser

create_recording_audit_emitter
#

testing/audit_drift_guard.ts view source

(calls_ref?: AuditLogInput<"login" | "logout" | "bootstrap" | "signup" | "password_change" | "session_revoke" | "session_revoke_all" | "token_create" | "token_revoke" | "token_revoke_all" | "role_grant_create" | ... 16 more ... | "db_admin_row_delete">[] | undefined): RecordingAuditEmitter import {create_recording_audit_emitter} from '@fuzdev/fuz_app/testing/audit_drift_guard.js';

Build a no-op AuditEmitter that records every emit, emit_pool, and emit_role_grant_target call into calls as an AuditLogInput. Use to capture audit metadata shapes in unit tests (e.g. password change failure outcome, role-grant create denial) without standing up a database and the query_audit_log pipeline.

Capture scope — all four production fan-out shapes. emit_role_grant_target mirrors create_audit_emitter's lift logic in place — actor_id / account_id / ip are populated from auth + ctx and the event_type / outcome / target_*_id / metadata fields forward from the input envelope. Tests asserting on role-grant-shape emissions read out of the same homogeneous calls array. notify is a no-op; add_listener records into a local array that listener_count reports, and returns a remover with the production semantics (per registration, idempotent) — registered listeners never fire, since this emitter captures emit shapes, not fan-out. drain_inflight resolves at once: a recorded call writes nothing, so nothing is ever in flight.

emit AND emit_pool both append to calls, so a test reads either entry point's writes off the same array.

Pass calls_ref to write into a caller-owned array (callers that declared const events: Array<AuditLogInput> = [] and want to keep the reference). Omit to let the helper allocate a fresh array and return it on the calls field of the result.

The returned emitter is deliberately NOT frozen — slots stay mutable so a test can override one when it needs bespoke shape (e.g. an emit_pool that throws on the first call). The production create_audit_emitter freeze invariant exists to catch the patch_audit_emit_capture hot-patch footgun against the closure-captured emit; the recording emitter has no inner closure, so the freeze isn't load-bearing here.

calls_ref?

type AuditLogInput<"login" | "logout" | "bootstrap" | "signup" | "password_change" | "session_revoke" | "session_revoke_all" | "token_create" | "token_revoke" | "token_revoke_all" | "role_grant_create" | "role_grant_revoke" | "role_grant_offer_create" | "role_grant_offer_accept" | "role_grant_offer_decline" | "role_grant_offer_retract" | "role_grant_offer_expire" | "role_grant_offer_supersede" | "invite_create" | "invite_delete" | "account_delete" | "account_purge" | "account_undelete" | "actor_delete" | "actor_purge" | "actor_undelete" | "app_settings_update" | "db_admin_row_delete">[]
optional

returns

RecordingAuditEmitter

create_recording_closer
#

testing/connection_closer_helpers.ts view source

(): RecordingCloser import {create_recording_closer} from '@fuzdev/fuz_app/testing/connection_closer_helpers.js';

Build a ConnectionCloser that records every call into calls rather than touching real transports. Each method returns 1 ("one connection closed") regardless of whether a real connection exists — handlers ignore the return value.

To see a handler's closes, add the closer to the backend's deps.connection_closer (RealtimeCloser.add), or build the action factory's deps with it.

returns

RecordingCloser

create_request_context_middleware
#

auth/request_context.ts view source

(deps: QueryDeps, session_context_key?: string): MiddlewareHandler import {create_request_context_middleware} from '@fuzdev/fuz_app/auth/request_context.js';

Create middleware that authenticates the account from a session cookie.

Reads the session identity (set by session middleware), looks up the auth_session, and on a valid session sets c.var.auth_account_id, CREDENTIAL_TYPE_KEY = 'session', AUTH_SESSION_TOKEN_HASH_KEY, and CREDENTIAL_DEADLINE_KEY — the earlier of the cookie's signed expiry (SESSION_COOKIE_EXPIRES_AT_KEY) and the row's. Touches the session (fire-and-forget). Does not load actor or role_grants; REQUEST_CONTEXT_KEY is left null — the route-spec / RPC dispatcher authorization phase resolves the acting actor and builds the full RequestContext when the route needs one.

Invalid / missing session leaves all keys null and calls next() — require_auth / require_role enforce.

deps

query dependencies (pool-level db for middleware)

session_context_key

the Hono context key where session middleware stored the session token

type string
default 'auth_session_id'

returns

MiddlewareHandler

mutates

  • Hono context — sets `ACCOUNT_ID_KEY`, `CREDENTIAL_TYPE_KEY`, `AUTH_SESSION_TOKEN_HASH_KEY`, `AUTH_API_TOKEN_ID_KEY`, and `CREDENTIAL_DEADLINE_KEY`

create_role_grant_offer_actions
#

auth/role_grant_offer_actions.ts view source

(deps: ActionFactoryDeps & { notification_sender?: NotificationSender | null | undefined; }, options?: RoleGrantOfferActionOptions): RpcAction[] import {create_role_grant_offer_actions} from '@fuzdev/fuz_app/auth/role_grant_offer_actions.js';

Create the eight role-grant-offer RPC actions (six offer-lifecycle methods plus role_grant_revoke and the immediate role_grant_assign).

deps

ActionFactoryDeps (log, audit) plus optional notification_sender for WS fan-out — when absent, WS fan-out is silently skipped (DB-only side effects still happen). Consumers wiring BackendWebsocketTransport assign its instance directly (the transport's send_to_account signature accepts the broader JsonrpcMessageFromServerToClient, which is contravariantly compatible)

type ActionFactoryDeps & { notification_sender?: NotificationSender | null | undefined; }

options

role schema, default TTL, authorization override

default {}

returns

RpcAction[]

the RpcAction array to spread into a create_rpc_endpoint call

create_role_schema
#

auth/role_schema.ts view source

(consumer_roles: readonly RoleSpec[], options?: CreateRoleSchemaOptions): RoleSchemaResult import {create_role_schema} from '@fuzdev/fuz_app/auth/role_schema.js';

Create a role schema and spec map that extends the builtins with app-defined roles.

Call once at server init. The returned Role schema validates role strings at I/O boundaries (grant endpoint, role_grant queries). The role_specs map is read by middleware for required_credential_types checks and by admin / self-service factories to derive their default eligibility filters from RoleSpec.grant_paths.

Construction-time guards (all fire on misconfiguration):

  1. Every consumer_roles[i].name matches RoleName — its regex and its ROLE_NAME_LENGTH_MAX length cap.
  2. No two consumer roles share a name.
  3. No consumer role collides with a builtin (keeper / admin).
  4. When options.credential_types is supplied, every entry in required_credential_types is registered in that map.
  5. When options.scope_kinds is supplied, every entry in applicable_scope_kinds is registered in that map. (Builtins declare empty applicable_scope_kinds, so they pass any registry.)
  6. When options.grant_paths is supplied, every entry in grant_paths is registered in that map. (Builtins use only 'admin' and 'bootstrap', both of which are builtin grant paths, so they pass the default registry from create_grant_path_schema().)

consumer_roles

app-defined role specs

type readonly RoleSpec[]

options

optional registries for cross-axis validation

default {}

returns

RoleSchemaResult

{Role, role_specs} — Zod schema and full spec map

throws

  • Error - if any `consumer_roles` entry fails any of the construction-time guards above

examples

// visiones — opt into all four registries for full construction-time validation const credential_types = create_credential_type_schema(); const scope_kinds = create_scope_kind_schema({ classroom: {description: 'A classroom — teacher and student role_grants scope here.'}, }); const grant_paths = create_grant_path_schema(); const {Role, role_specs} = create_role_schema( [ { name: 'teacher', description: 'Educator role. Web-grantable; applies at classroom scope.', grant_paths: ['admin'], applicable_scope_kinds: ['classroom'], }, ], {credential_types, scope_kinds, grant_paths}, );

create_route_skip_filter
#

testing/auth_apps.ts view source

(skip_routes: string[] | undefined): (route: { method: string; path: string; }) => boolean import {create_route_skip_filter} from '@fuzdev/fuz_app/testing/auth_apps.js';

Build the skip predicate an adversarial suite applies from its skip_routes option — routes named in 'METHOD /path' form, the surface key. undefined skips nothing.

skip_routes

type string[] | undefined

returns

(route: { method: string; path: string; }) => boolean

create_rpc_client
#

actions/rpc_client.ts view source

<TApi extends object>(options: CreateRpcClientOptions<TApi>): TApi import {create_rpc_client} from '@fuzdev/fuz_app/actions/rpc_client.js';

Creates a Proxy-based API from action specs.

Method calls are dynamically dispatched based on the action spec's kind:

  • request_response → send request, await response, return Result
  • remote_notification → send notification, return Result
  • local_call → execute locally (sync or async), return Result or throw

Generic TApi is the consumer's typed Proxy interface (typically a codegen-derived ActionsApi). Required — no default, so forgetting it is a type error rather than a silent slide into any. The `as unknown as TApi` coercion lives inside this function so call sites get a typed return without a cast at the seam. TApi is a type-layer promise about what the Proxy responds to; the runtime walks specs (kept in sync by the consumer, codegen recommended).

const api_result = create_rpc_client<MyActionsApi>({peer, environment});

options

returns

TApi

a Proxy typed as TApi that responds to any method name found in the environment's specs

generics

create_rpc_client<TApi extends object>
TApi
constraint object

create_rpc_endpoint
#

actions/action_rpc.ts view source

(options: CreateRpcEndpointOptions): RouteSpec[] import {create_rpc_endpoint} from '@fuzdev/fuz_app/actions/action_rpc.js';

Single JSON-RPC 2.0 endpoint — the canonical RPC transport binding.

Returns two RouteSpec entries (GET + POST on the same path) for apply_route_specs. The internal dispatcher handles:

  1. Parse envelope — POST: JSON body as JsonrpcRequest. GET: method and params from query string.
  2. Lookup method — find the RpcAction by method name.
  3. Pre-authorization auth — short-circuit unauthenticated when no account is on the request, before any later phase runs.
  4. Authorization phase — resolve the acting actor (when the action's auth requires role_grants or its input declares acting?: ActingActor) and build the request context. Runs before input validation so role-grant-grain auth checks return 403 before 400 invalid_params; acting is read from raw params by read_acting, which parses it as a UUID and treats anything else as omitted.
  5. Post-authorization auth — enforce role / keeper requirements against the request context.
  6. Rate limit — per-action IP / account throttling, ahead of params validation so a malformed call still charges.
  7. Validate params — parse input against the action's input schema.
  8. Dispatch — acquire DB handle (transaction for mutations, pool for reads), construct ActionContext, call handler, return JSON-RPC response.

GET is restricted to side_effects: false actions (cacheable reads). All errors use JSON-RPC format: {jsonrpc, id, error: {code, message, data?}}.

The RouteSpecs use auth: {type: 'none'} because auth is checked per-action inside the dispatcher, and transaction: false because transaction scope is per-action (mutations get a transaction, reads get pool). They are marked reads_credential, since the dispatcher reads the resolved credential, so assembly refuses the pair mounted outside the auth middleware's scope (assert_credential_routes_in_auth_scope) whether it arrives as rpc_endpoints or through create_route_specs.

options

endpoint path, actions, logger, and the two action rate limiters

returns

RouteSpec[]

route specs (GET + POST) ready for apply_route_specs

throws

  • Error - if two actions share the same `spec.method` (registration-time duplicate detection); also throws if any action's `spec.input` is `z.null()` (JSON-RPC 2.0 §4.2 forbids `params: null` on the wire — use `z.void()` for parameterless methods).

create_rpc_get_url
#

testing/rpc_helpers.ts view source

(endpoint_path: string, method: string, params?: unknown, id?: string | number): string import {create_rpc_get_url} from '@fuzdev/fuz_app/testing/rpc_helpers.js';

Build a GET URL with JSON-RPC query parameters.

endpoint_path

the RPC endpoint path (e.g., /api/rpc)

type string

method

type string

params?

params (omit for parameterless methods)

type unknown
optional

id

request id (default 'test')

type string | number
default 'test'

returns

string

create_rpc_post_init
#

testing/rpc_helpers.ts view source

(method: string, params?: unknown, id?: string | number): RequestInit import {create_rpc_post_init} from '@fuzdev/fuz_app/testing/rpc_helpers.js';

Create a RequestInit for a JSON-RPC POST request.

method

type string

params?

params (omit for parameterless methods; null is also stripped for ergonomic call sites — JSON-RPC 2.0 §4.2 forbids "params": null on the wire, and create_rpc_endpoint rejects z.null() action input schemas at registration). Tests that need to construct a literal "params": null envelope (e.g. asserting envelope-level rejection) should build the body inline rather than route through this helper.

type unknown
optional

id

request id (default 'test')

type string | number
default 'test'

returns

RequestInit

create_scope_kind_schema
#

auth/scope_kind_schema.ts view source

(consumer_kinds: Record<string, ScopeKindMeta>): ScopeKindSchemaResult import {create_scope_kind_schema} from '@fuzdev/fuz_app/auth/scope_kind_schema.js';

Create a scope-kind schema from a consumer-declared registry.

Open registry — no builtins. The 'GLOBAL' token used inside the partial unique indexes on role_grant and role_grant_offer is not a registry entry (it's an index expression only) and cannot collide with consumer-declared kinds because the regex rejects uppercase.

Call once at server init. Pass the result into create_role_schema's optional scope_kinds parameter so each role's applicable_scope_kinds entries are validated against this set at construction time. v1 keeps applicable_scope_kinds informative-only (registry-membership validation only); v2 may add INSERT-time (role, scope_kind) enforcement once the shape is clear from real consumer usage.

consumer_kinds

the consumer-declared scope-kind set with optional metadata

type Record<string, ScopeKindMeta>

returns

ScopeKindSchemaResult

{ScopeKind, scope_kinds} — Zod schema and metadata map

throws

  • Error - if any `consumer_kinds` key fails the `ScopeKindName` regex or appears more than once

examples

// visiones const {ScopeKind, scope_kinds} = create_scope_kind_schema({ classroom: {description: 'A classroom — teacher and student role_grants scope here.'}, });

create_self_service_role_actions
#

auth/self_service_role_actions.ts view source

(deps: ActionFactoryDeps, options?: SelfServiceRoleActionsOptions): RpcAction[] import {create_self_service_role_actions} from '@fuzdev/fuz_app/auth/self_service_role_actions.js';

Build the unified self-service role toggle RPC action.

deps

ActionFactoryDeps (log, audit); audit.emit writes audit rows via the captured pool. The bound emitter encapsulates on_audit_event fan-out and the optional AuditLogConfig.

options

optional eligible-role override plus optional role schema for default-eligibility derivation

default {}

returns

RpcAction[]

the RpcAction array to spread into a create_rpc_endpoint call

throws

  • Error - at factory time if any `eligible_roles` entry is missing from `options.roles.role_specs`
  • Error - at factory time if the eligible set names a builtin role or an invalid role name

create_serve_cell_fact_route_spec
#

server/serve_fact_route.ts view source

(options: CreateServeFactRouteSpecOptions): RouteSpec import {create_serve_cell_fact_route_spec} from '@fuzdev/fuz_app/server/serve_fact_route.js';

Build the cell-scoped GET /api/cells/:cell_id/facts/:hash RouteSpec — the per-reference read.

Resolves the named cell (404 if missing / soft-deleted), requires can_view_cell(caller, cell) AND cell.refs to include the hash (else 404, masked), then serves the bytes. Authz is scoped to this one (cell, hash) edge — never unioned across the fact's other referrers.

Pure-public auth — the handler builds the per-request RequestContext from c.var.account_id and enforces visibility per-reference.

options

returns

RouteSpec

create_serve_fact_route_spec
#

server/serve_fact_route.ts view source

(options: CreateServeFactRouteSpecOptions): RouteSpec import {create_serve_fact_route_spec} from '@fuzdev/fuz_app/server/serve_fact_route.js';

Build the admin-only bare-hash GET /api/facts/:hash RouteSpec.

An admin's reach already spans every cell, so serving by bare hash grants no escalation — the union concern that made this route a cross-owner leak for non-admins is vacuous for an admin. Non-admin callers are rejected at the auth phase (403) and never reach the handler. Confidential non-admin reads always go through the cell-scoped route above.

Auth is {account: 'required', actor: 'required', roles: ['admin']} — the dispatcher's authorization phase resolves the acting actor and the post-authorization guard enforces the admin role before the handler runs. The handler re-checks the global admin grant (has_scoped_role(_, admin, null), the auth phase's own reading) as defense-in-depth so a future mounting/auth-shape regression fails closed rather than serving by bare hash to a non-admin.

options

returns

RouteSpec

create_server_status_route_spec
#

http/common_routes.ts view source

(options: ServerStatusOptions): RouteSpec import {create_server_status_route_spec} from '@fuzdev/fuz_app/http/common_routes.js';

Create an authenticated server status route spec.

Returns version and uptime. Unlike the public health check, this requires authentication.

options

returns

RouteSpec

create_session_and_set_cookie
#

create_session_config
#

auth/session_cookie.ts view source

(cookie_name: string): SessionOptions<string> import {create_session_config} from '@fuzdev/fuz_app/auth/session_cookie.js';

Create a session config for raw session token identity.

The standard pattern: cookie stores the raw session token, server hashes it (blake3) to look up the auth_session row. Only the cookie_name varies per app.

cookie_name

cookie name (e.g. 'zap_session', 'visiones_session')

type string

returns

SessionOptions<string>

a SessionOptions<string> ready for use with session middleware

create_session_cookie_value
#

create_session_middleware
#

auth/session_middleware.ts view source

<TIdentity>(keyring: Keyring, options: SessionOptions<TIdentity>): MiddlewareHandler import {create_session_middleware} from '@fuzdev/fuz_app/auth/session_middleware.js';

Create session middleware that parses cookies and sets identity on context.

Always sets the identity on context (null when invalid/missing) for type-safe reads. Uses options.context_key as the Hono context variable name.

keyring

key ring for cookie verification

type Keyring

options

session configuration

type SessionOptions<TIdentity>

returns

MiddlewareHandler

generics

create_session_middleware<TIdentity>
TIdentity

mutates

  • Hono context — sets `options.context_key` and `SESSION_COOKIE_EXPIRES_AT_KEY`, and may clear the session cookie

create_signup_route_shape
#

auth/signup_route_schema.ts view source

(options: SignupRouteShapeOptions): Omit<RouteSpec, "handler"> import {create_signup_route_shape} from '@fuzdev/fuz_app/auth/signup_route_schema.js';

The POST /signup route shape minus its handler — pure hono-free data. create_signup_route_specs spreads this and attaches the live handler; cross-process surface builders spread it with a stub handler. Single source of truth — the shape can't drift between the live route and the surface.

options

returns

Omit<RouteSpec, "handler">

create_signup_route_specs
#

auth/signup_routes.ts view source

(deps: RouteFactoryDeps, options: SignupRouteOptions): RouteSpec[] import {create_signup_route_specs} from '@fuzdev/fuz_app/auth/signup_routes.js';

Create signup route specs for account creation.

deps

stateless capabilities

options

per-factory configuration

returns

RouteSpec[]

route specs (not yet applied to Hono)

create_spine_ready_route_spec
#

testing/cross_backend/default_spine_surface.ts view source

(log?: Logger | undefined): RouteSpec import {create_spine_ready_route_spec} from '@fuzdev/fuz_app/testing/cross_backend/default_spine_surface.js';

The spine's /ready route spec — the column-presence schema-drift deploy gate, reading SPINE_EXPECTED_SCHEMA_URL. Mounted live by the TS spine binary (in build_spine_app) and the in-process readiness parity leg, but kept off the declared surface (create_spine_surface_spec) like the fact-serving / ws / sse behaviors — describe_ready_cross_tests (gated on capabilities.ready) is its explicit coverage, not the generic round-trip.

log?

optional logger for server-side drift diagnostics

type Logger
optional

returns

RouteSpec

create_spine_route_specs
#

testing/cross_backend/default_spine_surface.ts view source

(ctx: AppServerContext): RouteSpec[] import {create_spine_route_specs} from '@fuzdev/fuz_app/testing/cross_backend/default_spine_surface.js';

Account REST + signup route specs under /api/account (bootstrap auto-mounted by the surface builder / create_app_server), plus the audit-log SSE stream under /api/admin only when ctx.audit_sse is set (the TS spine binary passes audit_log_sse: true).

The shared create_spine_surface_spec() builds its ctx with audit_sse: null, so the declared surface snapshot stays SSE-free and the Rust spine_stub cross test is unaffected — only the live TS binary mounts the stream at SPINE_SSE_PATH.

ctx

returns

RouteSpec[]

create_spine_surface_spec
#

testing/cross_backend/default_spine_surface.ts view source

(): AppSurfaceSpec import {create_spine_surface_spec} from '@fuzdev/fuz_app/testing/cross_backend/default_spine_surface.js';

The AppSurfaceSpec for the standard spine surface — the wire-shape source the cross-process round-trip + RPC-round-trip suites validate against. bootstrap: {mode: 'surface_only'} mounts POST /api/account/bootstrap's shape to match the binary (which wires bootstrap for real); the harness's globalSetup already consumed the live bootstrap, so the cross-process round-trip validates the binary's 409 against the route's declared error schema.

returns

AppSurfaceSpec

create_sse_auth_guard
#

realtime/sse_auth_guard.ts view source

<T>(registry: SubscriberRegistry<T>, required_role: string | null, log: Logger): (event: AuditLogEvent) => void import {create_sse_auth_guard} from '@fuzdev/fuz_app/realtime/sse_auth_guard.js';

Create an audit event handler that closes SSE streams on a successful revocation row, dispatching on the event's RevocationScope (audit_event_revocation_scopes, the table create_ws_auth_guard reads too):

  • session → the stream registered under the revoked session's hash (metadata.session_id). Closing all of an account's streams for a single-session revoke would be over-aggressive.
  • token → the stream registered under the revoked API token's id (metadata.token_id). The audit stream route admits sessions only, so no stream of its own is keyed that way; a consumer route that admits bearers registers the token id and is closed here.
  • account → every stream of target_account_id ?? account_id.
  • role → every stream of the target account, when the revoked role (metadata.role) is required_role and the revoked grant was global (to_global_role_revocation): the stream's role gate admits on global grants only, so revoking a scoped grant of the same role takes away nothing it admitted on. A stream is authorized once at open and has no later check; the WebSocket guard closes its role-gated sockets on the same rows.
  • none → nothing.

Over-closing a one-way feed is cheap: the client reconnects if it is still authorized.

The registry's subscribers must carry account_id as an identity key (in SubscribeOptions.groups), and the session hash as scope for the session-scoped close.

registry

the subscriber registry to guard

required_role

the role that grants access to the SSE endpoint, or null to skip role_grant_revoke handling entirely (for streams not gated by a specific role_grant)

type string | null

log

logger for disconnect events

type Logger

returns

(event: AuditLogEvent) => void

an on_audit_event callback

generics

create_sse_auth_guard<T>
T

create_sse_connection_closer
#

realtime/sse_auth_guard.ts view source

<T>(registry: SubscriberRegistry<T>, required_role: string | null): ConnectionCloser import {create_sse_connection_closer} from '@fuzdev/fuz_app/realtime/sse_auth_guard.js';

Adapt a SubscriberRegistry to ConnectionCloser, so a revocation handler's close reaches its streams.

Each method is SubscriberRegistry.close_by_identity on the id it is given, so it closes the streams registered under that id as their scope or in their groups — pending registrations included. The audit stream route registers scope = session hash and groups = [account id]; a route that admits bearer tokens registers the API token id too, and close_sockets_for_token then reaches it. close_sockets_for_role closes the account's streams when role is the one the registry's streams are gated on — the registry keeps one gating role for all its streams, not one per stream. close_all_sockets is SubscriberRegistry.close_all — every stream, for shutdown.

Twin of the Rust spine's SocketRevoker impl on SseRegistry.

registry

the registry to close streams on

required_role

the role every stream of registry was gated on, or null when they are not role-gated (close_sockets_for_role then closes nothing)

type string | null

returns

ConnectionCloser

generics

create_sse_connection_closer<T>
T

create_sse_frame_reader
#

testing/transports/sse_frame_reader.ts view source

(reader: ReadableStreamDefaultReader<Uint8Array<ArrayBufferLike>>, default_timeout_ms?: number): SseFrameReader import {create_sse_frame_reader} from '@fuzdev/fuz_app/testing/transports/sse_frame_reader.js';

Wrap a byte-stream reader in \n\n-delimited SSE frame parsing.

Preserves bytes past a frame terminator in an internal buffer for the next read_frame. read_frame and wait_for_close both race each underlying read against timeout_ms so a misbehaving stream surfaces as a failure rather than a vitest hang.

reader

type ReadableStreamDefaultReader<Uint8Array<ArrayBufferLike>>

default_timeout_ms

type number
default SSE_FRAME_READ_TIMEOUT_MS

returns

SseFrameReader

create_sse_response
#

realtime/sse.ts view source

<T = unknown>(c: Context<any, any, {}>, log: Logger, options?: SseResponseOptions | undefined): { response: Response; stream: SseStream<T>; } import {create_sse_response} from '@fuzdev/fuz_app/realtime/sse.js';

Create an SSE response for a Hono context.

Wraps Hono's streamSSE to provide a {response, stream} API compatible with SubscriberRegistry push-based broadcasting. The callback suspends via a promise that resolves on client disconnect or explicit close(), keeping the stream alive for external sends.

The stream closes when its client disconnects, whenever that happens — also before the handler returns the response, which no runtime reports by cancelling the body. A client already gone when this is called yields a stream that is already closed, so register cleanup with on_close (which runs a late listener at once) before handing the stream anywhere that would count it — SubscriberRegistry.admit after on_close(pending.unsubscribe) refuses it.

Uses hono_stream.write() directly (not writeSSE) to avoid Hono's HTML callback resolution — keeps the same data: JSON\n\n format.

What the stream holds unread is bounded by SSE_OUTBOUND_QUEUE_BYTES; a frame past it ends the stream (module doc, "Outbound bound").

c

type Context<any, any, {}>

log

logger for serialization and on_close listener errors, and for the warning when the stream ends on its outbound budget

type Logger

options?

what the overflow warning names

optional

returns

{ response: Response; stream: SseStream<T>; }

object with the streaming Response and an SseStream controller

generics

create_sse_response<T = unknown>
T
default unknown

create_sse_transport
#

testing/transports/sse_transport.ts view source

(options: SseTransportOptions): Promise<SseTransport> import {create_sse_transport} from '@fuzdev/fuz_app/testing/transports/sse_transport.js';

Open a real-HTTP SSE stream pinned to options.base_url + sse_path.

Resolves once the response headers arrive and the body is a text/event-stream; rejects if the connect is refused (non-2xx status, wrong content type, missing body) so the test surfaces the real cause rather than hanging.

options

returns

Promise<SseTransport>

throws

  • Error - if the connect fails (status, content type, or no body).

create_standard_adversarial_cases
#

testing/adversarial_headers.ts view source

(allowed_origin: string): AdversarialHeaderCase[] import {create_standard_adversarial_cases} from '@fuzdev/fuz_app/testing/adversarial_headers.js';

7 standard adversarial header cases applicable to any middleware stack.

Origin verification is Origin-only — fuz_app's verify_request_source no longer falls back to Referer (matches zzz_server::auth::is_request_origin_allowed). Bearer auth still treats a Referer header as a browser-context indicator and silently discards the bearer token — so Referer-bearing requests reach the route as unauthenticated rather than 403.

allowed_origin

an origin that passes the origin check

type string

returns

AdversarialHeaderCase[]

create_standard_rpc_actions
#

auth/standard_rpc_actions.ts view source

(deps: StandardRpcActionsDeps, options?: StandardRpcActionsOptions): RpcAction[] import {create_standard_rpc_actions} from '@fuzdev/fuz_app/auth/standard_rpc_actions.js';

Build the combined admin + role-grant-offer + account RPC action set.

Spreads create_admin_actions(deps, {roles}), create_role_grant_offer_actions(deps, {roles, default_ttl_ms, authorize}), and create_account_actions(deps, {max_tokens}). The shared roles option flows to admin + role-grant-offer.

deps

StandardRpcActionsDeps (log, audit, connection_closer from RevokingActionFactoryDeps; optional notification_sender for WS fan-out)

options

role schema, role-grant-offer config, account config

default {}

returns

RpcAction[]

RPC actions to pass as rpc_endpoints or spread into create_rpc_endpoint

create_static_middleware
#

server/static.ts view source

(serve_static: ServeStaticFactory, options?: { root?: string | undefined; spa_fallback?: string | undefined; is_spa_route?: ((path: string) => boolean) | undefined; } | undefined): MiddlewareHandler[] import {create_static_middleware} from '@fuzdev/fuz_app/server/static.js';

Create static file serving middleware for SvelteKit static builds.

Returns an array of middleware handlers to register on '/*'.

serve_static

runtime-specific serveStatic factory

options?

optional root directory and SPA fallback path

type { root?: string | undefined; spa_fallback?: string | undefined; is_spa_route?: ((path: string) => boolean) | undefined; }
optional

returns

MiddlewareHandler[]

create_stub_api_middleware
#

testing/stubs.ts view source

(options?: { include_daemon_token?: boolean | undefined; origin_carve_outs?: readonly MiddlewareCarveOut[] | undefined; } | undefined): MiddlewareSpec[] import {create_stub_api_middleware} from '@fuzdev/fuz_app/testing/stubs.js';

Create the API middleware stub array matching create_auth_middleware_specs output — the same names, paths, order, and declared errors (the surface merges middleware errors into every route under the path, so a stub that declared more than the real stack would widen the test surface). Like the real factory's, each spec gets its own pass-through handler, so assert_middleware_stack_mounted can tell the layers apart by identity.

options?

type { include_daemon_token?: boolean | undefined; origin_carve_outs?: readonly MiddlewareCarveOut[] | undefined; }
optional

returns

MiddlewareSpec[]

create_stub_app_backend
#

testing/stubs.ts view source

(options?: CreateStubAppBackendOptions | undefined): AppBackend import {create_stub_app_backend} from '@fuzdev/fuz_app/testing/stubs.js';

Create a stub AppBackend with no database behind it — no migration results, and a close that does nothing unless one is passed. For assembling a server (create_app_server) in tests that never reach the database; one whose close does nothing can host server after server.

options?

the deps and close to use in place of the defaults

optional

returns

AppBackend

create_stub_app_deps
#

testing/stubs.ts view source

(): AppDeps import {create_stub_app_deps} from '@fuzdev/fuz_app/testing/stubs.js';

Create no-op AppDeps for auth surface testing.

returns

AppDeps

create_stub_app_server_context
#

testing/stubs.ts view source

(session_options: SessionOptions<string>): AppServerContext import {create_stub_app_server_context} from '@fuzdev/fuz_app/testing/stubs.js';

Create a stub AppServerContext for attack surface testing.

Provides sensible defaults for all fields. Pass session_options since it varies per consumer; other fields use stubs/nulls.

session_options

consumer's session config (required — varies per app)

type SessionOptions<string>

returns

AppServerContext

create_stub_audit_sse
#

testing/stubs.ts view source

(): AuditLogSse import {create_stub_audit_sse} from '@fuzdev/fuz_app/testing/stubs.js';

Build a no-op AuditLogSse for tests that wire audit_sse into the surface helper but don't assert on SSE fan-out or subscriber state.

on_audit_event is a no-op, so nothing is broadcast; the registry is a fresh SubscriberRegistry instance (call sites that inspect .size or call .close_* see a real registry, so writes are isolated per test). Tests that need real SSE plumbing build it via create_audit_log_sse against create_test_app.

returns

AuditLogSse

create_stub_db
#

testing/stubs.ts view source

(): Db import {create_stub_db} from '@fuzdev/fuz_app/testing/stubs.js';

Create a stub Db for handler tests that use apply_route_specs with declarative transactions.

Returns a real Db instance with:

  • query returns empty rows (safety net for unmocked query functions)
  • query_one returns undefined
  • transaction(fn) calls fn(db) synchronously (no real transaction)

returns

Db

create_stub_upgrade
#

testing/ws_round_trip.ts view source

(): StubUpgrade import {create_stub_upgrade} from '@fuzdev/fuz_app/testing/ws_round_trip.js';

Build a fake upgradeWebSocket that stands in for a runtime adapter's.

Like the real adapters, the middleware it returns passes a request without an Upgrade: websocket header to next(). An upgrade request is recorded in upgrades — its real Context and the endpoint's events factory — and answered 200 with no body (a Response cannot carry the 101 an adapter answers). No socket is opened: tests drive the recorded factory, so an upgrade sent through app.request exercises the app's whole middleware chain — origin, session or bearer, client IP — before the dispatcher sees it.

returns

StubUpgrade

create_surface_route_spec
#

server/surface_route.ts view source

(options: SurfaceRouteOptions): RouteSpec import {create_surface_route_spec} from '@fuzdev/fuz_app/server/surface_route.js';

Create the admin-only route spec that serves the AppSurface as JSON.

Surface data reveals API structure (routes, auth, schemas), so the route requires the global admin role. It is also a non-RPC surface: it declares required_scope: 'surface:app_surface', so a narrowed API token is refused it whatever its method list says (403 token_scope_required, ahead of the role gate), while a full-scope admin bearer passes. The admin role implies actor: 'required', so the route takes the ?acting= selector a multi-actor admin disambiguates with.

options

the surface to serve

returns

RouteSpec

the GET /api/surface route spec

create_test_account
#

create_test_account_with_actor
#

testing/db_entities.ts view source

(db: Db, options: { username: string; password_hash?: string | undefined; }): Promise<TestAccountWithActor> import {create_test_account_with_actor} from '@fuzdev/fuz_app/testing/db_entities.js';

Create an account + actor row pair in the database for tests.

Wraps query_create_account_with_actor with a default password_hash so suites that don't exercise password verification can stay terse. Replaces the per-file create_user / create_test_actor / create_test_account helpers that had accumulated across the auth test suite.

db

type Db

options

type { username: string; password_hash?: string | undefined; }

returns

Promise<TestAccountWithActor>

create_test_account_with_credentials
#

testing/app_server.ts view source

(options: CreateTestAccountWithCredentialsOptions): Promise<{ account: { id: string & $brand<"Uuid">; username: string; }; actor: { ...; }; api_token: string; session_cookie: string; }> import {create_test_account_with_credentials} from '@fuzdev/fuz_app/testing/app_server.js';

Create a test account with credentials. Use for additional accounts minted alongside the keeper (e.g. TestApp.create_account for cross-account / multi-user tests). Does NOT flip bootstrap_lock — non-keeper accounts should not appear to the system as bootstrap having happened.

Creates an account with actor, grants roles, creates an API token, creates a session, and signs a session cookie.

options

returns

Promise<{ account: { id: string & $brand<"Uuid">; username: string; }; actor: { id: string & $brand<"Uuid">; }; api_token: string; session_cookie: string; }>

mutates

  • the underlying options.db — inserts rows into account, actor, — `role_grant` (one per role), `api_token`, and `auth_session`.

create_test_actor
#

create_test_app
#

testing/app_server.ts view source

(options: CreateTestAppOptions): Promise<TestApp> import {create_test_app} from '@fuzdev/fuz_app/testing/app_server.js';

Create a fully assembled test app with a Hono server, middleware, and routes.

Combines create_test_app_server + create_app_server into a single call. Disables every rate limiter (rate_limiters: 'disabled_for_testing') and logging by default (test-friendly). A limiter passed through app_options wins over the mode, so a rate-limit test enables just the one it pins.

A fresh Hono app is created each call — middleware closures bind to the server's deps (db, keyring), so reuse across servers is unsafe. The expensive resource (the WASM database) is cached separately in testing/db.ts.

options

test app configuration

returns

Promise<TestApp>

a TestApp ready for HTTP testing

create_test_app_for_bootstrap
#

testing/app_server.ts view source

(options: CreateTestAppForBootstrapOptions): Promise<TestAppForBootstrap> import {create_test_app_for_bootstrap} from '@fuzdev/fuz_app/testing/app_server.js';

Create a test app in the pre-bootstrap state for exercising the bootstrap success path end-to-end.

Skips the keeper pre-creation create_test_app does by default — bootstrap_lock.bootstrapped stays at false and the DB has no accounts. The fs stubs return options.bootstrap_token when the bootstrap handler reads bootstrap.token_path, so a POST /bootstrap with {token: bootstrap_token, username, password} reaches the success branch. Rate limiters are disabled the way create_test_app disables them — a limiter in app_options wins.

Pair with describe_bootstrap_success_tests for the consumer-runnable suite that drives the full happy path + adjacent assertions on observable state (account exists, audit row emitted, on_bootstrap callback fired).

options

bootstrap config + factory inputs

returns

Promise<TestAppForBootstrap>

a TestAppForBootstrap ready for the test to drive bootstrap

create_test_app_from_specs
#

testing/auth_apps.ts view source

(route_specs: RouteSpec[], auth_ctx?: RequestContext | undefined, credential_type?: "session" | "api_token" | "daemon_token" | undefined): Hono<BlankEnv, BlankSchema, "/"> import {create_test_app_from_specs} from '@fuzdev/fuz_app/testing/auth_apps.js';

Create a Hono test app from route specs with optional auth context.

route_specs

the route specs to register

type RouteSpec[]

auth_ctx?

optional request context to inject via middleware

optional

credential_type?

optional credential type (default: 'session' when auth_ctx provided)

optional

returns

Hono<BlankEnv, BlankSchema, "/">

create_test_app_server
#

testing/app_server.ts view source

(options: TestAppServerOptions): Promise<TestAppServer> import {create_test_app_server} from '@fuzdev/fuz_app/testing/app_server.js';

Create an app server with a bootstrapped account for testing.

Sets up:

  • Auth tables (via the cached default factory, or reuses existing db)
  • A keeper account with hashed password
  • Role role_grants for each role in options.roles
  • An API token for Bearer auth
  • A session with a signed cookie value

Uses stub_password_deps by default — deterministic hashing that works correctly for login/logout tests without Argon2 overhead.

options

session options and optional overrides

returns

Promise<TestAppServer>

a TestAppServer ready for HTTP testing

mutates

  • the underlying database — when db is supplied, first resets the — `app_settings` row (`open_signup`, `updated_at`, `updated_by`); in either branch inserts an account, actor, role role_grants, API token, and session row, and flips `bootstrap_lock.bootstrapped` (`bootstrap_test_keeper`).

create_test_app_surface_spec
#

testing/stubs.ts view source

(options: CreateTestAppSurfaceSpecOptions): AppSurfaceSpec import {create_test_app_surface_spec} from '@fuzdev/fuz_app/testing/stubs.js';

Create an AppSurfaceSpec for the standard testing suites.

Used by both in-process and cross-process tests as the schema source — the cross-process-ness lives in the transport + per-test fixture, not here. The on-disk *_attack_surface.json snapshot is observability (gen-time drift detection via assert_surface_matches_snapshot); the suites consume the spec object this function returns, not the JSON file.

Mirrors create_app_server's route assembly, in its order: consumer routes, then the factory-managed bootstrap routes, RPC endpoint routes, and GET /api/surface (with surface_route: true), then surface generation — including its refusal of an rpc_endpoints or ws_endpoints path outside the auth middleware's scope (assert_endpoint_in_auth_scope), of a route that reads or mints a credential outside it (assert_credential_routes_in_auth_scope), and of a transform_middleware that doesn't keep the proxy and auth stack mounted (assert_middleware_stack_mounted). The middleware list mirrors the real one: a stub trusted_proxy spec at '*', then the stub auth stack (with daemon_token when options.daemon_token is set). If create_app_server changes how it wires routes, update this helper to stay in sync (single source of truth for all consumers).

options

surface spec options

returns

AppSurfaceSpec

the surface spec for the standard suites

throws

  • Error - when an endpoint path, or a route that reads or mints a credential, is outside `AUTH_MIDDLEWARE_PATH`, a `ws_endpoints` spec declares no `allowed_origins` and no `allowed_origins` option is given, or `transform_middleware` moves, drops, duplicates, reorders, wraps, or replaces the stub proxy spec or a stub auth spec

create_test_audit_emitter
#

testing/stubs.ts view source

(): AuditEmitter import {create_test_audit_emitter} from '@fuzdev/fuz_app/testing/stubs.js';

Build a no-op AuditEmitter for tests that don't assert on audit fan-out.

emit / emit_role_grant_target are no-ops; emit_pool resolves immediately; notify is a no-op; add_listener throws, so a test that wires a listener fails loudly instead of silently never firing (create_recording_audit_emitter accepts listeners); listener_count returns 0; drain_inflight resolves immediately, since nothing is ever written. Tests asserting on real audit-row persistence (or on listener fan-out) build a real emitter via create_audit_emitter against a stub or real DB — create_test_app already does this on the test backend.

returns

AuditEmitter

create_test_audit_event
#

create_test_context
#

create_test_extra_actor
#

testing/db_entities.ts view source

(db: Db, account_id: string, name: string): Promise<Actor> import {create_test_extra_actor} from '@fuzdev/fuz_app/testing/db_entities.js';

Add a second actor to an existing test account — the multi-actor edge.

Wraps the production query_create_actor; the TS twin of the Rust tests/common seed_extra_actor helper. Lets a .db.test.ts suite build a multi-actor account without reimplementing the raw insert.

db

type Db

account_id

type string

name

type string

returns

Promise<Actor>

create_test_middleware_stack_app
#

testing/middleware.ts view source

(options?: TestMiddlewareStackOptions | undefined): TestMiddlewareStackApp import {create_test_middleware_stack_app} from '@fuzdev/fuz_app/testing/middleware.js';

Create a Hono app with real proxy + origin + bearer middleware for integration testing.

All DB queries return undefined (no real database needed). The echo route at TEST_MIDDLEWARE_PATH returns {ok, client_ip, has_context}.

options?

optional

returns

TestMiddlewareStackApp

the app and mock spies (reconfigure via mockImplementation for valid-token paths)

throws

  • Error - when `allowed_origins` is not a valid allowlist

mutates

  • module-level vi.mock registrations for the four bearer-auth query — modules — each call resets the spies before wiring the stack.

create_test_raw_api_token
#

testing/entities.ts view source

(label?: string): string import {create_test_raw_api_token} from '@fuzdev/fuz_app/testing/entities.js';

Create a well-formed raw API token for tests that need a bearer value to pass the is_api_token_shaped precheck and reach token validation. The body is label padded with A to the generated length, so distinct labels give distinct tokens and a token in a failure message reads back to its test. Not a real credential — it authenticates only where a test mocks the lookup.

label

base64url characters (A-Z a-z 0-9 - _) that start the token body

type string
default 'test'

returns

string

throws

  • Error - when `label` is longer than the body or not base64url

create_test_request_context
#

testing/auth_apps.ts view source

(role?: string | undefined): RequestContext import {create_test_request_context} from '@fuzdev/fuz_app/testing/auth_apps.js';

Create a mock RequestContext with optional role role_grant.

role?

type string
optional

returns

RequestContext

create_test_role_grant
#

create_test_role_grant_direct
#

testing/db_entities.ts view source

(db: Db, input: CreateRoleGrantInput): Promise<RoleGrant> import {create_test_role_grant_direct} from '@fuzdev/fuz_app/testing/db_entities.js';

Materialize a role_grant directly via query_create_role_grant, bypassing the production offer/accept consent flow.

In-process only. This helper takes a raw Db handle and seeds rows without firing audit fan-out, WebSocket broadcasts, or the _supersede notification chain a real grant emits. Cross-process suites must instead drive role_grant_offer_create_action_spec + role_grant_offer_accept_action_spec via testing/role_grant_helpers.ts's role_grant_offer_and_accept so the fixture observes the full post-commit fan-out the way production does — otherwise tests would mask real divergence between the TS and Rust spines.

Use this helper for query-level (*.db.test.ts) tests that exercise revoke or isolation semantics — not the consent path itself. The schema's source_offer_id = null shape is an intentional admin-direct escape; this helper exposes it so suites don't reimplement the same direct-seed wrapper.

db

type Db

input

returns

Promise<RoleGrant>

create_test_validated_api_token
#

create_testing_action_manifest_action
#

testing/cross_backend/testing_reset_actions.ts view source

(mounted: readonly RpcAction[]): RpcAction import {create_testing_action_manifest_action} from '@fuzdev/fuz_app/testing/cross_backend/testing_reset_actions.js';

Build the _testing_action_manifest action over a backend's full live RPC mount. The handler closes over a manifest computed once at assembly time from every mounted spec except the protocol methods (PROTOCOL_METHODS) plus this action's own spec — the manifest action is itself a live method, so it must appear in the dump it serves. mounted excludes it (it's appended to the mount *after* the rest is assembled, in build_full_spine_rpc_actions), so its static spec is folded in here; the Rust mirror folds in its own descriptor the same way.

mounted

every other action on the backend's live RPC mount

type readonly RpcAction[]

returns

RpcAction

create_testing_actions
#

testing/cross_backend/testing_reset_actions.ts view source

(deps: AppDeps, options: CreateTestingActionsOptions): RpcAction[] import {create_testing_actions} from '@fuzdev/fuz_app/testing/cross_backend/testing_reset_actions.js';

Build the testing RPC actions for a test binary's registry.

Returns the bundled _testing_* actions (module doc). The test binary calls this at server-assembly time and registers the result on its dispatcher. _testing_drain_effects drains deps.audit, so the binary's audit_factory builds the emitter with track_inflight: true.

The reset action's table-wipe list mirrors auth_integration_truncate_tables from testing/db.ts — the canonical "auth tables a between-test reset must clear" set. testing_reset_actions.coverage.test.ts enforces the set-equality invariant so a future auth migration that adds a table to that list without updating this handler fails CI.

deps

type AppDeps

options

returns

RpcAction[]

create_testing_drain_effects_action
#

testing/cross_backend/testing_reset_actions.ts view source

(audit: AuditEmitter): RpcAction import {create_testing_drain_effects_action} from '@fuzdev/fuz_app/testing/cross_backend/testing_reset_actions.js';

Build the standalone _testing_drain_effects action over audit — the emitter the backend's handlers emit through (deps.audit), built with track_inflight: true (see the spec doc). Mount it on any test endpoint whose suite asserts on audit rows (the spine binary bundles it via create_testing_actions; in-process suites mount it directly).

audit

the backend's emitter, whose in-flight writes the action awaits

returns

RpcAction

create_testing_flood_notifications_action
#

testing/cross_backend/testing_reset_actions.ts view source

(): RpcAction import {create_testing_flood_notifications_action} from '@fuzdev/fuz_app/testing/cross_backend/testing_reset_actions.js';

Build the standalone _testing_flood_notifications action (see the spec doc). The spine binary bundles it via create_testing_actions; mount it directly on an in-process WS endpoint whose suite drives an outbound overflow.

returns

RpcAction

create_testing_migration_tracker_action
#

create_testing_origin_check_action
#

create_testing_schema_snapshot_action
#

create_testing_shorten_credential_expiry_action
#

testing/cross_backend/testing_reset_actions.ts view source

(): RpcAction import {create_testing_shorten_credential_expiry_action} from '@fuzdev/fuz_app/testing/cross_backend/testing_reset_actions.js';

Build the standalone _testing_shorten_credential_expiry action (see the spec doc).

returns

RpcAction

create_throwing_api
#

actions/rpc_client.ts view source

<TApi extends object>(api_result: TApi): ThrowingApi<TApi> import {create_throwing_api} from '@fuzdev/fuz_app/actions/rpc_client.js';

Wrap a typed RPC client so every call resolves to its unwrapped value or throws an Error carrying the JSON-RPC {code, message, data?} shape.

Implementation is a Proxy because the underlying create_rpc_client return is itself a Proxy with no concrete keys — a key-by-key wrap would need to enumerate the typed surface, which only the consumer's generated ActionsApi interface knows.

Pass-through on non-Result returns is deliberate: sync local_call Proxy methods return values directly (see create_sync_local_call_method above). The Proxy can't distinguish those at get-time, so the wrapper inspects result shape at call-time and only unwraps when it sees a Result. Non-object returns pass through unchanged.

Only {code, data} cross onto the thrown Error — name / stack are left as the Error's own properties so attacker-shaped result.error payloads cannot overwrite them.

Recommended consumer convention: create_frontend_rpc_client ships both shapes by default — api (throwing) for hot-path call sites and api_result (Result) for sites that inspect error.data.reason without try/catch. Result is the protocol primitive; this wrapper is the ergonomic layer over it. Picking is per call site — both Proxies share the same underlying transport.

Catch blocks read err.data?.reason — optional chaining required because JSON-RPC data is spec-level optional.

On unknown string-keyed methods, the get trap returns a function that throws "rpc method not found: <prop>" on invocation — clearer than the JS default "api.foo is not a function". Symbol props and then stay undefined so the Proxy isn't accidentally treated as a thenable (await api would otherwise probe then and trip the thrower).

api_result

typed Result-returning RPC client from create_rpc_client<ActionsApi>(...). The "_result" suffix names what the underlying calls return (Result<{value}, {error}>).

type TApi

returns

ThrowingApi<TApi>

generics

create_throwing_api<TApi extends object>
TApi
constraint object

create_throwing_stub
#

testing/stubs.ts view source

<T = any>(label: string): T import {create_throwing_stub} from '@fuzdev/fuz_app/testing/stubs.js';

Create a Proxy that throws descriptive errors on any property access or method call.

Use for deps that should never be reached during a test. If a test accidentally calls through to a throwing stub, the error message identifies exactly which stub was hit, catching test bugs that would silently pass with {} as any.

JS-internal probes (Symbol, then, constructor, $$typeof) return undefined so the proxy doesn't crash framework-level identity checks; toJSON returns "[throwing_stub:label]" so accidental serialization surfaces the stub's identity in console output rather than silent "{}".

label

descriptive name for error messages (e.g. 'keyring', 'db')

type string

returns

T

generics

create_throwing_stub<T = any>
T
default any

throws

  • Error - on any non-internal property access, labeled with the stub name and the offending property.

create_uuid
#

create_validated_broadcaster
#

realtime/sse.ts view source

<T extends SseNotification>(broadcaster: { broadcast: (channel: string, data: T) => void; }, event_specs: EventSpec[], log: Logger): { broadcast: (channel: string, data: T) => void; } import {create_validated_broadcaster} from '@fuzdev/fuz_app/realtime/sse.js';

Create a broadcaster that validates events in DEV mode.

In DEV: warns on unknown methods and invalid params. In production: passes through with zero overhead.

broadcaster

duck-typed broadcaster (e.g. SubscriberRegistry)

type { broadcast: (channel: string, data: T) => void; }

event_specs

event specs to validate against

type EventSpec[]

log

logger used to emit DEV warnings on unknown methods or param mismatches

type Logger

returns

{ broadcast: (channel: string, data: T) => void; }

validated broadcaster wrapper (passthrough in production)

generics

create_validated_broadcaster<T extends SseNotification>
T
constraint SseNotification

create_validated_keyring
#

auth/keyring.ts view source

(env_value: string | undefined): ValidatedKeyringResult import {create_validated_keyring} from '@fuzdev/fuz_app/auth/keyring.js';

Validate and create a keyring in one step.

Returns a discriminated union so callers handle exit/logging their own way (e.g. Deno.exit(1) vs runtime.exit(1)).

env_value

the SECRET_FUZ_COOKIE_KEYS environment variable

type string | undefined

returns

ValidatedKeyringResult

{ok: true, keyring} or {ok: false, errors}

create_ws_auth_guard
#

actions/transports_ws_auth_guard.ts view source

(closer: ConnectionCloser, log: Logger): AuditEventHandler import {create_ws_auth_guard} from '@fuzdev/fuz_app/actions/transports_ws_auth_guard.js';

Create an audit event handler that closes connections on a successful revocation row, dispatching on the event's RevocationScope:

  • session → close_sockets_for_session(metadata.session_id)
  • token → close_sockets_for_token(metadata.token_id)
  • account → close_sockets_for_account(target_account_id ?? account_id) — logout, password_change, session_revoke_all, token_revoke_all, account_delete, account_purge
  • role → `close_sockets_for_role(target_account_id ?? account_id, metadata.role)` when the revoked grant was global; a scoped revoke closes nothing (module doc)
  • none → nothing — every other event, and any event type fuz_app does not define

Ignores outcome === 'failure' events — they carry attacker-controlled identifiers (e.g. a session_revoke that the DB rejected still records the submitted session_id), so reacting to them would let any authenticated user close another user's socket by guessing a session hash or token id.

closer

what to close on — a BackendWebsocketTransport, or a RealtimeCloser to reach every transport from one listener

log

logger for disconnect events (info level on non-zero closures)

type Logger

returns

AuditEventHandler

an on_audit_event callback suitable for create_audit_emitter's on_audit_event slot, or for registering via audit.add_listener post-assembly. The returned callback closes matching connections on closer on every relevant event.

create_ws_test_harness
#

testing/ws_round_trip.ts view source

(options: CreateWsTestHarnessOptions): WsTestHarness import {create_ws_test_harness} from '@fuzdev/fuz_app/testing/ws_round_trip.js';

Create a WebSocket test harness for the given specs + handlers.

Registers against a throwaway Hono app with a fake upgradeWebSocket; the captured events factory is invoked per connect() with a synthesized Hono context carrying the requested auth identity. Returned clients drive the real onOpen/onMessage/onClose path against a real WSContext.

options

returns

WsTestHarness

create_ws_transport
#

testing/transports/ws_transport.ts view source

(options: WsTransportOptions): Promise<WsClient> import {create_ws_transport} from '@fuzdev/fuz_app/testing/transports/ws_transport.js';

Build a real-upgrade WS client pinned to options.base_url + ws_path.

Resolves once the upgrade succeeds and the socket is in OPEN state; rejects if the upgrade is refused (401, allowlist rejection, network failure). Incoming messages are JSON-parsed and pushed onto the messages array; wait_for checks already-received messages first before waiting for new arrivals.

An open socket is not yet an admitted connection: both spines answer the handshake first and admit the connection a credential re-check later, and deliver no server-initiated message to it until then. Requests sent meanwhile are queued and answered, so a suite that only round-trips can use this directly; one that waits for a push (a broadcast, a targeted notification) opens with create_admitted_ws_transport instead.

options

returns

Promise<WsClient>

throws

  • Error - if the upgrade fails (status, network) — the rejection message carries the underlying error so the test surfaces the real cause rather than hanging.

create_x_accel_config
#

server/x_accel.ts view source

(redirect_prefix: string, nginx_config: string): { redirect_prefix: string; } & $brand<"XAccelConfig"> import {create_x_accel_config} from '@fuzdev/fuz_app/server/x_accel.js';

Build a validated XAccelConfig, asserting the nginx location serving redirect_prefix is internal;.

The make-impossible-states gate: serving can only emit X-Accel-Redirect into a location proven internal; at boot.

redirect_prefix

the X-Accel redirect prefix (e.g. /_facts/)

type string

nginx_config

the nginx config template string to validate against

type string

returns

XAccelConfig

the validated XAccelConfig carrying redirect_prefix

throws

  • `XAccelConfigError` when `nginx_config` has no matching `location` for `redirect_prefix`, or the matching block is not marked `internal;` — either is a public facts location that bypasses every cell-visibility check.

CreateAccountInput
#

auth/account_schema.ts view source

CreateAccountInput import type {CreateAccountInput} from '@fuzdev/fuz_app/auth/account_schema.js';

username

type string

password_hash

type string

email?

type string | null

CreateAppBackendOptions
#

server/app_backend.ts view source

CreateAppBackendOptions import type {CreateAppBackendOptions} from '@fuzdev/fuz_app/server/app_backend.js';

Input for create_app_backend().

keyring is passed pre-validated — callers handle their own error reporting (e.g., tx uses runtime.exit(1) on invalid keys).

read_secure_file

Hardened secret-file read for the bootstrap token — pass the runtime's read_secure_file (see FsSecureReadDeps). Rejects symlinks, non-regular files, group/other-accessible modes, and oversized files.

type (path: string): Promise<Uint8Array<ArrayBufferLike>>

path

type string
returns Promise<Uint8Array<ArrayBufferLike>>

delete_file

Delete a file.

type (path: string): Promise<void>

path

type string
returns Promise<void>

database_url

Database connection URL (postgres://, file://, or memory://).

type string

keyring

Validated cookie signing keyring.

type Keyring

password

Password hashing implementation. Use argon2_password_deps in production.

type PasswordHashDeps

log?

Structured logger instance. Omit for default (new Logger('server')).

type Logger

audit_factory

Build the bound AuditEmitter once the backend's pool Db + Logger exist. Required — the factory owns listener composition and AuditLogConfig selection without create_app_backend holding a default. Typical body:

audit_factory: ({db, log}) => create_audit_emitter({ db, log, on_audit_event, audit_log_config, })

Additional listeners (factory-managed audit SSE, per-endpoint WS auth guards) are registered at create_app_server time via audit.add_listener(...).

type (params: { db: Db; log: Logger; }): AuditEmitter

params

type { db: Db; log: Logger; }
returns AuditEmitter

connection_closer?

The closer to put on AppDeps.connection_closer. Omit for a fresh one — the usual case, since create_app_server adds the transports it mounts. Pass one built up front (create_realtime_closer()) when a transport is constructed before the backend and has to be added to the same closer the handlers will hold — e.g. an audit stream from create_audit_log_sse whose listener audit_factory registers.

type RealtimeCloser

migration_namespaces?

Additional migration namespaces to run after the builtin auth namespace. The shared schema_version table records one row per applied migration (namespace, name, sequence); order is append-only so forward-only guarantees hold per-namespace.

Names in reserved_migration_namespaces (currently ['fuz_auth']) are rejected at startup. Omit for no extra namespaces. This is the only place to splice consumer migrations — DB init belongs to the backend lifecycle, not server assembly.

type readonly MigrationNamespace[]

CreateAuditEmitterOptions
#

auth/audit_emitter.ts view source

CreateAuditEmitterOptions import type {CreateAuditEmitterOptions} from '@fuzdev/fuz_app/auth/audit_emitter.js';

db

Pool-level Db. Captured by every emit call.

type Db

log

Logger for write + listener-callback failures.

type Logger

on_audit_event?

Initial listener — registered as the first listener when set. Omit for backends that compose listeners post-assembly (e.g. via audit_log_sse).

type ((event: AuditLogEvent) => void) | null

audit_log_config?

Audit-log config. Defaults to builtin_audit_log_config. Consumer- extended configs from create_audit_log_config({extra_events}) get registered here once at backend assembly.

type AuditLogConfig

emit_decorator?

Test-only hook to wrap emit at construction time. The decorated function is captured by emit_role_grant_target's closure and is the function exposed on the returned AuditEmitter, so both call shapes route through it — see EmitDecorator for the rationale.

Leave unset in production — it is a test instrumentation seam.

type (inner: AuditEmitFn): AuditEmitFn

inner

returns AuditEmitFn

track_inflight?

Track every in-flight emit write so drain_inflight can await it. Defaults to false, which makes drain_inflight resolve at once.

Test binaries only. A transport can reply before a request's writes settle — a WebSocket message is answered before its effects flush — so a test reading the audit log after a reply needs this barrier. In production the tracking is pure overhead and nothing drains it. Twin of the Rust fuz_auth AuditEmitter::new_with_inflight_tracking.

type boolean

CreateAuditLogConfigOptions
#

auth/audit_log_schema.ts view source

CreateAuditLogConfigOptions import type {CreateAuditLogConfigOptions} from '@fuzdev/fuz_app/auth/audit_log_schema.js';

extra_events?

Extra event types keyed by event-type string. Value is a Zod metadata schema, or null to register the type without validation (row still written, metadata stored as raw JSONB).

Collisions with builtin event-type strings throw at construction. Schemas are run via safeParse at insert time; mismatches log + count but never throw (fail-open — see the drift counters in auth/audit_log_queries.ts).

type Readonly<Record<string, ZodType<unknown, unknown, $ZodTypeInternals<unknown, unknown>> | null>>

CreateBroadcastApiOptions
#

actions/broadcast_api.ts view source

CreateBroadcastApiOptions import type {CreateBroadcastApiOptions} from '@fuzdev/fuz_app/actions/broadcast_api.js';

peer

The peer holding the transport registry used for sends.

type ActionDispatcher

specs

Notification specs to expose as broadcast methods. Typically the remote_notification specs whose initiator is backend (or both). Other kinds are accepted — the helper only uses spec.method and spec.input — but the typical use is notifications.

type readonly ActionSpecUnion[]

log?

Logger for validation/send errors. Defaults to a [broadcast] namespace.

type Logger | null

should_deliver?

Optional per-connection ACL predicate. When set, the broadcast fans out via the transport's broadcast_filtered (feature-detected) — each connection's identity is checked before the message is sent. When unset, the transport broadcasts unfiltered via transport.send.

Requires a transport that implements FilterableBroadcastTransport (today: only BackendWebsocketTransport). If set and the active transport is not filterable, the send is skipped and an error logged.

type (connection: ConnectionIdentity, method: string, input: unknown): boolean

connection

method

type string

input

type unknown
returns boolean

CreateDatabaseOptions
#

CreateDbResult
#

db/create_db.ts view source

CreateDbResult import type {CreateDbResult} from '@fuzdev/fuz_app/db/create_db.js';

Result of database initialization.

db

type Db

close

Close the database connection. Bound to the actual driver at construction.

type (): Promise<void>

returns Promise<void>

db_type

type DbType

db_name

type string

CreateFakeWsOptions
#

testing/ws_round_trip.ts view source

CreateFakeWsOptions import type {CreateFakeWsOptions} from '@fuzdev/fuz_app/testing/ws_round_trip.js';

Options for create_fake_ws.

stalled?

Start with the client not reading (FakeWsSocket.stalled).

type boolean

CreateFrontendRpcClientOptions
#

actions/frontend_rpc_client.ts view source

CreateFrontendRpcClientOptions<TApi> import type {CreateFrontendRpcClientOptions} from '@fuzdev/fuz_app/actions/frontend_rpc_client.js';

generics

CreateFrontendRpcClientOptions<TApi extends object = object>
TApi
constraint object
default object

specs

Action specs the typed Proxy can dispatch. Methods absent from this list silently return undefined from the Proxy — the generic TApi cannot constrain runtime membership, so consumers must keep this list in sync with the typed surface (codegen recommended).

Protocol actions (heartbeat, cancel) are not auto-spread — they're filtered out of generated action_specs by codegen's include_protocol_actions: false default and consumers spread them in explicitly so the contract stays visible at every registration site. For WS-using consumers, spread protocol_action_specs from actions/protocol.ts here: specs: [...protocol_action_specs, ...action_specs]. HTTP-only consumers can omit them.

type readonly ActionSpecUnion[]

path?

HTTP RPC endpoint path for the default FrontendHttpTransport. Defaults to /api/rpc. Ignored when transports is provided.

type string

transports?

Optional explicit transport list. When provided, the default FrontendHttpTransport(path) is not registered — the caller is responsible for at least one ready transport. Use for WS-first or WS+HTTP mixed setups.

type readonly Transport[]

transport_for_method?

Optional per-method transport selector — pure pass-through to create_rpc_client. Return the transport name to use for a given method, or undefined to fall back to the peer's default selection.

Useful when methods are registered on different backend dispatchers (e.g. streaming actions on WS, REST RPC on HTTP) — a tx-style mixed setup. Per-call RpcClientCallOptions.transport_name overrides this for individual dispatches.

type (method: string): string | undefined

method

type string
returns string | undefined

on_action_event?

Optional callback fired once per dispatched action — pure pass-through to create_rpc_client. Used by zzz-style consumers that thread the ActionEvent into a reactive cell (add_from_json + listen_to_action_event) for pending / failed / value derivations.

event.spec.method and event.data.method narrow to keyof TApi & string — drop the as ActionMethod cast at the call site when TApi is a generated ActionsApi interface.

type (event: ActionEvent<keyof TApi & string, "send_request" | "receive_request" | "send_response" | "receive_response" | "send_error" | "receive_error" | "send" | "receive" | "execute", "initial" | ... 3 more ... | "failed">): void

event

type ActionEvent<(keyof TApi) & string, "send_request" | "receive_request" | "send_response" | "receive_response" | "send_error" | "receive_error" | "send" | "receive" | "execute", "initial" | "parsed" | "handling" | "handled" | "failed">
returns void

lookup_action_handler?

Optional handler resolver. Wired onto environment.lookup_action_handler — the registry the dispatcher uses to find handlers for inbound messages and lifecycle phases. Defaults to () => undefined, which is fine for HTTP-only frontends that never receive a server-pushed notification or register a receive_error recovery hook.

Common reasons to provide this:

  • Server-pushed notifications over WS — return a handler for (method, 'receive') so a remote_notification arriving on the socket dispatches to your subscriber bus (tx-style).
  • Per-method retry / telemetry on errors — return a handler for (method, 'receive_error'). Note that as of the extract_action_result fix, a missing handler already produces {ok: false, error} — the stub is no longer required just to surface server errors.

type (method: string, phase: "send_request" | "receive_request" | "send_response" | "receive_response" | "send_error" | "receive_error" | "send" | "receive" | "execute"): ((event: any) => any) | undefined

method

type string

phase

returns ((event: any) => any) | undefined

log?

Where dispatch diagnostics go — pure pass-through to environment.log. Unset falls back to console_action_log; null silences them (see resolve_action_log).

type Logger | null

CreateInviteInput
#

auth/invite_schema.ts view source

CreateInviteInput import type {CreateInviteInput} from '@fuzdev/fuz_app/auth/invite_schema.js';

Input for creating an invite.

email?

type string | null

username?

type string | null

created_by

type Uuid | null

CreateRoleGrantInput
#

auth/account_schema.ts view source

CreateRoleGrantInput import type {CreateRoleGrantInput} from '@fuzdev/fuz_app/auth/account_schema.js';

actor_id

type Uuid

role

type string

scope_kind?

Machine-readable kind for the scope_id. Required iff scope_id is set; must be null/omitted when scope_id is null. The DB-level role_grant_scope_kind_paired CHECK rejects mismatched pairs.

type string | null

scope_id?

Scope the grant applies to. null / omitted grants a global role_grant.

type Uuid | null

expires_at?

type Date | null

granted_by

type Uuid | null

source_offer_id?

Offer id that produced this role_grant. Set by query_accept_offer; leave unset for direct grants.

type Uuid | null

CreateRoleGrantOfferInput
#

auth/role_grant_offer_schema.ts view source

CreateRoleGrantOfferInput import type {CreateRoleGrantOfferInput} from '@fuzdev/fuz_app/auth/role_grant_offer_schema.js';

Input for query_role_grant_offer_create.

expires_at must be supplied — the query layer does not apply a default, so callers can thread their own TTL (typically ROLE_GRANT_OFFER_DEFAULT_TTL_MS).

from_actor_id

type Uuid

to_account_id

type Uuid

to_actor_id?

Optional actor-grain target on the recipient account. When set, query_role_grant_offer_create validates that the actor belongs to to_account_id and stamps the column; accept then matches against this specific actor. Omit (or pass null) for the account-grain default — any actor on to_account_id may accept.

type Uuid | null

role

type string

scope_kind?

Machine-readable kind for the scope_id. Required iff scope_id is set; must be null when scope_id is null (DB-level CHECK rejects the mismatch). Consumer-declared via create_scope_kind_schema(...).

type string | null

scope_id?

type Uuid | null

message?

type string | null

expires_at

type Date

CreateRoleSchemaOptions
#

auth/role_schema.ts view source

CreateRoleSchemaOptions import type {CreateRoleSchemaOptions} from '@fuzdev/fuz_app/auth/role_schema.js';

Optional registries to validate RoleSpec cross-axis fields against at construction time.

credential_types?

Pass create_credential_type_schema() to validate RoleSpec.required_credential_types entries.

type CredentialTypeSchemaResult

scope_kinds?

Pass create_scope_kind_schema() to validate RoleSpec.applicable_scope_kinds entries.

type ScopeKindSchemaResult

grant_paths?

Pass create_grant_path_schema() to validate RoleSpec.grant_paths entries.

type GrantPathSchemaResult

CreateRpcClientOptions
#

actions/rpc_client.ts view source

CreateRpcClientOptions<TApi> import type {CreateRpcClientOptions} from '@fuzdev/fuz_app/actions/rpc_client.js';

Options for create_rpc_client.

generics

CreateRpcClientOptions<TApi extends object = object>
TApi
constraint object
default object

peer

type ActionDispatcher

environment

type ActionEventEnvironment

on_action_event?

Optional callback fired once per dispatched action with the live ActionEvent. Consumers wire reactive state here — e.g. zzz's Actions cell calls add_from_json + listen_to_action_event inside the callback so its history stays decoupled from the rpc_client surface.

event.spec.method and event.data.method narrow to keyof TApi & string — drop the as ActionMethod cast at the call site when TApi is a generated ActionsApi interface.

type (event: ActionEvent<keyof TApi & string, "send_request" | "receive_request" | "send_response" | "receive_response" | "send_error" | "receive_error" | "send" | "receive" | "execute", "initial" | ... 3 more ... | "failed">): void

event

type ActionEvent<(keyof TApi) & string, "send_request" | "receive_request" | "send_response" | "receive_response" | "send_error" | "receive_error" | "send" | "receive" | "execute", "initial" | "parsed" | "handling" | "handled" | "failed">
returns void

transport_for_method?

Optional per-method transport selector. When provided, the client calls peer.send(msg, {transport_name}) with the returned transport for each request_response / remote_notification dispatch. Returning undefined falls back to the peer's default selection.

type (method: string): string | undefined

method

type string
returns string | undefined

CreateRpcEndpointOptions
#

actions/action_rpc.ts view source

CreateRpcEndpointOptions import type {CreateRpcEndpointOptions} from '@fuzdev/fuz_app/actions/action_rpc.js';

Options for create_rpc_endpoint.

path

Mount path for the endpoint (e.g., /api/rpc).

type string

actions

RPC actions to serve.

type RpcAction[]

log

Logger instance for handler context.

type Logger

action_ip_rate_limiter

Per-IP rate limiter consulted for actions whose spec declares rate_limit: 'ip' or 'both'. Required, with no default: pass null to turn the IP check off, so an unthrottled endpoint is a choice the call site states rather than an option it forgot. create_app_server passes its own (a default limiter unless configured). Share one instance with the WebSocket action dispatcher — one budget per action, not per transport.

type RateLimiter | null

action_account_rate_limiter

Per-account rate limiter consulted for actions whose spec declares rate_limit: 'account' or 'both'. Keyed on request_context.account.id (account-grain — billed to the authenticated account regardless of which actor was resolved). Required, with no default: pass null to turn the account check off. Share one instance with the WebSocket action dispatcher.

type RateLimiter | null

CreateServeFactRouteSpecOptions
#

server/serve_fact_route.ts view source

CreateServeFactRouteSpecOptions import type {CreateServeFactRouteSpecOptions} from '@fuzdev/fuz_app/server/serve_fact_route.js';

deps

App deps reference. Currently unused at handler time (cell + fact tables are read via RouteContext.db); kept on the factory signature for symmetry with sibling route factories and to give future role_grant-scoped viewer extensions somewhere to read other deps without changing the public shape.

type AppDeps

facts_dir

Absolute path of the facts directory. Used for the dev/test streaming path.

type string

x_accel?

When set, external facts return an X-Accel-Redirect pointing at ${x_accel.redirect_prefix}<shard>/<rest> — nginx's internal facts location serves the bytes. When unset, external facts stream from <facts_dir>/<shard>/<rest> directly. Production sets a validated XAccelConfig (built via create_x_accel_config, which proves the facts location is internal;); tests + dev leave it unset.

type XAccelConfig

log

type Logger

CreateSessionAndSetCookieOptions
#

auth/session_middleware.ts view source

CreateSessionAndSetCookieOptions import type {CreateSessionAndSetCookieOptions} from '@fuzdev/fuz_app/auth/session_middleware.js';

keyring

Keyring for cookie signing.

type Keyring

deps

Query deps (needs db for session creation).

type QueryDeps

c

Hono context for setting the cookie.

type Context<any, any, {}>

account_id

The account to create a session for.

type string

session_options

Session cookie configuration.

type SessionOptions<string>

max_sessions?

Per-account session cap (null to skip enforcement).

type number | null

CreateStubAppBackendOptions
#

testing/stubs.ts view source

CreateStubAppBackendOptions import type {CreateStubAppBackendOptions} from '@fuzdev/fuz_app/testing/stubs.js';

deps?

The backend's deps. Default a fresh create_stub_app_deps().

type AppDeps

close?

The backend's close. Default one that does nothing.

type (): Promise<void>

returns Promise<void>

CreateTestAccountOptions
#

testing/cross_backend/setup.ts view source

CreateTestAccountOptions import type {CreateTestAccountOptions} from '@fuzdev/fuz_app/testing/cross_backend/setup.js';

Options for TestFixture.create_account — mints an additional bootstrapped account alongside the keeper. Matches the existing TestApp.create_account signature so the migration to fixture-style reads is a one-site call rewrite per use.

username?

type string

readonly

password_value?

type string

readonly

roles?

type string[]

readonly

email?

Optional email to store on the account, exercising the username-or-email login lookup. Cross-process this rides the production signup body (claimed against the username-scoped invite, which matches regardless of the email); in-process it lands directly on the inserted row. Must be a valid Email (the cross-process signup validates it) and unique per test (the LOWER(email) partial-unique index rejects collisions).

type string

readonly

CreateTestAccountWithCredentialsOptions
#

testing/app_server.ts view source

CreateTestAccountWithCredentialsOptions import type {CreateTestAccountWithCredentialsOptions} from '@fuzdev/fuz_app/testing/app_server.js';

Options for bootstrap_test_keeper and create_test_account_with_credentials.

Same shape for both — the data inserted is identical; the only behavioral difference is the lock flip on the keeper path.

db

type Db

keyring

type Keyring

session_options

type SessionOptions<string>

password

type PasswordHashDeps

username?

type string

password_value?

type string

roles?

type string[]

email?

Optional email stored on the account row — exercises the username-or-email login lookup.

type string

CreateTestAppAccountArgs
#

testing/app_server.ts view source

CreateTestAppAccountArgs import type {CreateTestAppAccountArgs} from '@fuzdev/fuz_app/testing/app_server.js';

Args for TestApp.create_account — mint an additional account alongside the keeper. Mirrors the cross-process CreateTestAccountOptions (the two create_account paths share a shape so fixture call sites read identically), kept local to avoid a cross-module cycle with cross_backend/setup.ts.

username?

type string

password_value?

type string

roles?

type string[]

email?

Optional email stored on the account row — exercises the username-or-email login lookup.

type string

CreateTestAppForBootstrapOptions
#

testing/app_server.ts view source

CreateTestAppForBootstrapOptions import type {CreateTestAppForBootstrapOptions} from '@fuzdev/fuz_app/testing/app_server.js';

Configuration for create_test_app_for_bootstrap. Like CreateTestAppOptions but the keeper-related fields drop (no pre-bootstrap keeper) and bootstrap is required + narrowed to live mode (the helper exists specifically to drive the success path).

session_options

type SessionOptions<string>

create_route_specs

type (context: AppServerContext): RouteSpec[]

context

returns RouteSpec[]

rpc_endpoints?

type RpcEndpointsSuiteOption

app_options?

type Partial<Omit<AppServerOptions, "backend" | "bootstrap" | "session_options" | "create_route_specs" | "rpc_endpoints">>

bootstrap

Live bootstrap config — the test drives POST /bootstrap against this.

type BootstrapLiveOptions

bootstrap_token

Token contents the stub fs returns when reading bootstrap.token_path. The test posts a body containing this same value as token to satisfy the timing-safe equality check inside bootstrap_account.

type string

db?

type Db

db_type?

type DbType

password?

type PasswordHashDeps

audit_factory?

type (params: { db: Db; log: Logger; }): AuditEmitter

params

type { db: Db; log: Logger; }
returns AuditEmitter

CreateTestAppOptions
#

testing/app_server.ts view source

CreateTestAppOptions import type {CreateTestAppOptions} from '@fuzdev/fuz_app/testing/app_server.js';

Configuration for create_test_app.

inheritance

create_route_specs

Route spec factory — called with the assembled AppServerContext.

type (context: AppServerContext): RouteSpec[]

context

returns RouteSpec[]

rpc_endpoints?

RPC endpoints mounted by create_app_server — eager array or (ctx: AppServerContext) => Array<RpcEndpointSpec> factory. Single source of truth; the equivalent slot under app_options is Omit'd so setup-time path lookup and runtime dispatch read from one place. Symmetric with the suite-level rpc_endpoints option on describe_standard_admin_integration_tests etc.

type RpcEndpointsSuiteOption

bootstrap?

Bootstrap config — symmetric with AppServerOptions.bootstrap. Same single-source-of-truth precedent as rpc_endpoints: setup-time surface generation and runtime dispatch both read this slot, so the equivalent field under app_options is Omit'd. Discriminated union over {mode: 'disabled' | 'surface_only' | 'live'}. Omit (or pass {mode: 'disabled'}) for the default — no bootstrap route mounted.

For tests that exercise the bootstrap success path against a real token + empty DB, use create_test_app_for_bootstrap instead — it skips the keeper pre-creation that blocks the success branch.

type BootstrapServerOptions

app_options?

Optional overrides for AppServerOptions. Excludes fields create_test_app manages directly: backend, session_options, create_route_specs, rpc_endpoints, bootstrap (top-level slots above).

type Partial<Omit<AppServerOptions, "backend" | "bootstrap" | "session_options" | "create_route_specs" | "rpc_endpoints">>

CreateTestAppSurfaceSpecOptions
#

testing/stubs.ts view source

CreateTestAppSurfaceSpecOptions import type {CreateTestAppSurfaceSpecOptions} from '@fuzdev/fuz_app/testing/stubs.js';

session_options

Consumer's session config (required — varies per app).

type SessionOptions<string>

create_route_specs

Consumer's route factory — receives the same AppServerContext as production.

type (ctx: AppServerContext): RouteSpec[]

ctx

returns RouteSpec[]

env_schema?

Env schema for surface generation (default: BaseServerEnv).

type ZodObject<$ZodLooseShape, $strip>

event_specs?

SSE event specs for surface generation.

type EventSpec[]

rpc_endpoints?

RPC endpoint specs for surface generation.

Accepts either an array (eager) or a factory (ctx: AppServerContext) => Array<RpcEndpointSpec> — symmetric with create_app_server's rpc_endpoints option, so consumers can pass the same factory to both entry points. The factory runs once against the stub AppServerContext this helper already builds.

type RpcEndpointSpec[] | ((ctx: AppServerContext) => RpcEndpointSpec[])

ws_endpoints?

WebSocket endpoint specs for surface generation. Symmetric with create_app_server's ws_endpoints option — pass the same value to both entry points so the attack surface tests see the same WS endpoints production auto-mounts. The factory runs once against the stub AppServerContext this helper already builds. No create_upgrade_websocket needed — this helper produces an AppSurfaceSpec only, never mounts.

type readonly WsEndpointSpec[] | ((ctx: AppServerContext) => readonly WsEndpointSpec[])

allowed_origins?

The server's origin allowlist — AppServerOptions.allowed_origins. Read only as the default for a ws_endpoints spec that declares no allowed_origins, as create_app_server does; required when any spec omits its own — without it, such a spec throws, since the surface records the exact patterns the upgrade gate matches.

type readonly RegExp[]

transform_middleware?

Transform middleware array — AppServerOptions.transform_middleware. Receives shallow copies of a stub trusted_proxy spec (at '*') followed by the stub auth stack, in a fresh array, as create_app_server passes the real ones. Throws, like create_app_server, if the result doesn't keep each of those specs mounted once at its original path in its original order (assert_middleware_stack_mounted).

type (specs: MiddlewareSpec[]): MiddlewareSpec[]

specs

returns MiddlewareSpec[]

origin_carve_outs?

The origin check's carve-outs — AppServerOptions.origin_carve_outs. Recorded on the stub origin spec, so the surface leaves the check's 403 off the carved-out routes as the live server's does; refused, like create_app_server, when one has no reason or overlaps the auth middleware's scope.

type MiddlewareCarveOut[]

bootstrap?

Bootstrap config — symmetric with AppServerOptions.bootstrap. Discriminated by mode: 'disabled' skips the route (same as omission), 'surface_only' mounts the route shape, 'live' accepts a token_path for production symmetry (surface assembly only uses it for shape symmetry; the value is a live-execution concern handled by create_test_app → create_app_server).

Surface assembly only reads route_prefix (default '/api/account').

type BootstrapServerOptions

surface_route?

Symmetric with AppServerOptions.surface_route — true lists the admin-only GET /api/surface, which create_app_server mounts only when opted in. Pass the same value to both entry points so the surface tests probe the route exactly when the live server serves it.

type boolean

default false

daemon_token?

List the daemon_token middleware layer, as create_app_server mounts it when given a daemon_token_state. Pass true exactly when the live server gets one — create_test_app always passes one.

type boolean

default false

CreateTestingActionsOptions
#

testing/cross_backend/testing_reset_actions.ts view source

CreateTestingActionsOptions import type {CreateTestingActionsOptions} from '@fuzdev/fuz_app/testing/cross_backend/testing_reset_actions.js';

session_options

Session cookie options — the reset action uses these when signing the fresh keeper's (and any extra accounts') session cookies. Pass the same SessionOptions the live create_app_server call was wired with.

type SessionOptions<string>

readonly

daemon_token_state

Daemon-token runtime state — the reset action points state.keeper_account_id at the freshly seeded keeper once its transaction commits (a post-commit effect, so a reset that rolls back leaves the previous keeper cached). Pass the same DaemonTokenState instance the daemon-token middleware reads.

type DaemonTokenState

readonly

reset_state?

Consumer-supplied callback invoked after the auth-table reset, passed the same transactional Db the auth wipes ran on. DB-domain consumers (e.g. fuz_forge truncating its cell / fact / file tables) MUST use this db rather than a separately-pooled connection — under the in-memory drivers' single connection (pglet, PGlite) a second connection deadlocks against this still-open transaction. testing_zzz_server clears in-memory workspace registry + terminals + scoped-FS scratch (ignores db); testing_spine_stub has no domain layer and omits the option. Runs inside the same RPC dispatch as the auth-table writes, so a throw surfaces to the caller as a JSON-RPC error and the per-test fixture short-circuits.

type (db: Db): void | Promise<void>

readonly

db

type Db
returns Promise<void> | void

CreateWsTestHarnessOptions
#

testing/ws_round_trip.ts view source

CreateWsTestHarnessOptions import type {CreateWsTestHarnessOptions} from '@fuzdev/fuz_app/testing/ws_round_trip.js';

actions

The actions registered on this endpoint — matches the shape register_action_ws accepts. Each entry is a {spec, handler?} tuple; shared fuz_app primitives (like heartbeat_action) can be spread in alongside consumer-specific actions.

type readonly Action<ActionSpecUnion>[]

transport?

Pass a pre-created transport to share with a broadcast API.

type BackendWebsocketTransport

heartbeat?

Threaded through to register_action_ws. Defaults to false in tests — fake timers + receive-silence detection need explicit opt-in and per- test tuning to avoid spurious closes.

type boolean | ServerHeartbeatOptions

log?

Optional logger. Defaults to a silent [ws-test] logger.

type Logger

on_socket_open?

Threaded straight through to register_action_ws.

type (ctx: SocketOpenContext): void | Promise<void>

ctx

returns void | Promise<void>

on_socket_close?

Threaded straight through to register_action_ws.

type (ctx: SocketCloseContext): void | Promise<void>

ctx

returns void | Promise<void>

on_request?

Optional responder for server-initiated requests, applied to every connect(). Purely additive (and unexercised until the TS server transport gains server-initiated requests — the deferred BackendWebsocketTransport.send() convergence): with it set, an inbound server→client request is answered rather than surfaced as a message; without it, behavior is unchanged. Mirrors the cross-process create_ws_transport seam so suite bodies share one shape.

type (request: { [x: string]: unknown; jsonrpc: "2.0"; id: string | number; method: string; params?: { [x: string]: unknown; } | undefined; }): WsResponderOutcome | Promise<WsResponderOutcome>

request

credential_deadline_after
#

auth/credential_deadline.ts view source

(remaining_ms: readonly (number | null)[], now?: number): number | null import {credential_deadline_after} from '@fuzdev/fuz_app/auth/credential_deadline.js';

The deadline now plus the shortest of remaining_ms, as a monotonic performance.now() timestamp, or null when no entry carries one.

A negative or NaN entry — a row its own expires_at > NOW() check already refused, read at the boundary — counts as zero.

remaining_ms

each source's remaining lifetime in ms, null for none

type readonly (number | null)[]

now

the monotonic clock reading to anchor to

type number
default performance.now()

returns

number | null

the deadline, or null for a credential with no expiry

CREDENTIAL_DEADLINE_KEY
#

hono_context.ts view source

"credential_deadline" import {CREDENTIAL_DEADLINE_KEY} from '@fuzdev/fuz_app/hono_context.js';

Hono context variable name for the credential's deadline — when it stops authenticating, as a monotonic performance.now() timestamp (auth/credential_deadline.ts), or null for one with no expiry (a daemon token, an eternal API token) and for an unauthenticated request.

Set by every auth middleware that resolves a credential: session (the earlier of the signed cookie's expiry and the row's), bearer (the row's), daemon token (null). A long-lived connection opened on the credential closes at it (ResolvedAuth.credential_deadline).

CREDENTIAL_DEADLINE_TIMER_CAP_MS
#

auth/credential_deadline.ts view source

3600000 import {CREDENTIAL_DEADLINE_TIMER_CAP_MS} from '@fuzdev/fuz_app/auth/credential_deadline.js';

The longest single timer the deadline wait sets — one hour, far inside setTimeout's ceiling (2^31 - 1 ms, about 24.8 days, past which a timer fires at once) and the Rust twin's tokio ceiling, and short enough that a resume from suspend is noticed within it. A longer deadline is reached through several.

CREDENTIAL_TYPE_API_TOKEN
#

auth/credential_type_schema.ts view source

"api_token" import {CREDENTIAL_TYPE_API_TOKEN} from '@fuzdev/fuz_app/auth/credential_type_schema.js';

HTTP Authorization: Bearer API token credential. The wire literal 'api_token' aligns with the api_token storage table name; the constant is named _API_TOKEN (not _BEARER) to keep wire and storage nomenclature in lockstep.

CREDENTIAL_TYPE_DAEMON_TOKEN
#

auth/credential_type_schema.ts view source

"daemon_token" import {CREDENTIAL_TYPE_DAEMON_TOKEN} from '@fuzdev/fuz_app/auth/credential_type_schema.js';

Daemon-token credential — filesystem proof for the keeper account.

CREDENTIAL_TYPE_KEY
#

hono_context.ts view source

"credential_type" import {CREDENTIAL_TYPE_KEY} from '@fuzdev/fuz_app/hono_context.js';

Hono context variable name for the credential type.

CREDENTIAL_TYPE_NAME_REGEX
#

CREDENTIAL_TYPE_SESSION
#

CREDENTIAL_TYPES
#

hono_context.ts view source

readonly ["session", "api_token", "daemon_token"] import {CREDENTIAL_TYPES} from '@fuzdev/fuz_app/hono_context.js';

The credential types that can authenticate a request — the closed set of fuz_app builtins. The open registry on top (create_credential_type_schema(consumer_types)) is consulted at registry time by create_role_schema for RoleSpec.required_credential_types validation; the wire-validated CredentialType enum here stays narrow because middleware only ever sets one of the three builtins.

CredentialDeadlineCrossTestOptions
#

testing/cross_backend/credential_deadline.ts view source

CredentialDeadlineCrossTestOptions import type {CredentialDeadlineCrossTestOptions} from '@fuzdev/fuz_app/testing/cross_backend/credential_deadline.js';

setup_test

Per-test fixture producer (default_cross_process_setup(handle)).

type (): Promise<TestFixtureBase>

readonly
returns Promise<TestFixtureBase>

capabilities

Backend capability flags — the WebSocket cases gate on ws, the stream case on sse.

type BackendCapabilities

readonly

base_url

Base URL the backend is reachable at.

type string

readonly

ws_path

The WebSocket endpoint path (e.g. /api/ws).

type string

readonly

sse_path?

The audit-log stream path. Default /api/admin/audit/stream.

type string

readonly

rpc_path?

JSON-RPC endpoint path. Default SPINE_RPC_PATH.

type string

readonly

CredentialHeaderRobustnessCrossTestOptions
#

testing/cross_backend/credential_header_robustness.ts view source

CredentialHeaderRobustnessCrossTestOptions import type {CredentialHeaderRobustnessCrossTestOptions} from '@fuzdev/fuz_app/testing/cross_backend/credential_header_robustness.js';

Options for the credential-header robustness probe — needs the raw URL + a valid daemon token.

base_url

Base URL the spawned backend is reachable at (e.g. http://localhost:1178).

type string

readonly

rpc_path?

RPC endpoint path to target. Default /api/rpc.

type string

readonly

daemon_token

A valid daemon token (keeper) — handle.daemon_token.

type string

readonly

CredentialType
#

hono_context.ts view source

value + type

"session" | "api_token" | "daemon_token"

type "session" | "api_token" | "daemon_token"

import {CredentialType} from '@fuzdev/fuz_app/hono_context.js';

Credential type — how a request was authenticated.

CredentialTypeMeta
#

auth/credential_type_schema.ts view source

CredentialTypeMeta import type {CredentialTypeMeta} from '@fuzdev/fuz_app/auth/credential_type_schema.js';

Per-credential-type metadata. description is admin-UI-facing copy (mirrors RoleSpec.description and ScopeKindMeta.description). Open shape so v2 can extend without a breaking change.

description?

type string

CredentialTypeName
#

CredentialTypeRequiredError
#

http/error_schemas.ts view source

value + type

{ [x: string]: unknown; error: "credential_type_required"; required_credential_types: readonly string[]; } import {CredentialTypeRequiredError} from '@fuzdev/fuz_app/http/error_schemas.js';

Credential-type error — returned by the dispatcher's post-authorization credential gate (and the require_credential_types REST middleware) when the request's credential type isn't in the route's auth.credential_types allowlist.

required_credential_types carries what the route declared (['daemon_token'] for keeper; future gates carry their own labels). Symmetric with PermissionError's required_roles: clients see what the route demanded, not what their credential is.

error

type "credential_type_required"

required_credential_types

type readonly string[]

[key: string]

type unknown

CredentialTypeSchemaResult
#

auth/credential_type_schema.ts view source

CredentialTypeSchemaResult import type {CredentialTypeSchemaResult} from '@fuzdev/fuz_app/auth/credential_type_schema.js';

The result of create_credential_type_schema — a Zod schema and metadata map.

CredentialType

Zod schema that validates credential-type name strings against the registered set (builtins + consumer-declared). Use at I/O boundaries (admin UIs, codegen) and as the construction-time check inside create_role_schema for every RoleSpec.required_credential_types entry.

type ZodType<string, unknown, $ZodTypeInternals<string, unknown>>

credential_types

Map of every registered credential-type to its metadata. Keyed by name. Read at startup by admin / codegen surfaces.

type ReadonlyMap<string, CredentialTypeMeta>

cross_rpc_call
#

testing/cross_backend/cell_cross_helpers.ts view source

(transport: FetchTransport, path: string, method: string, params: unknown, headers: Record<string, string>): Promise<RpcResult> import {cross_rpc_call} from '@fuzdev/fuz_app/testing/cross_backend/cell_cross_helpers.js';

POST a JSON-RPC call over a cross-process FetchTransport with the given auth headers. Distinct from testing/rpc_helpers.ts's app-based rpc_call: this variant drives the cookie-jar FetchTransport the cross-backend harness spawns against, and returns the slim RpcResult the cell suites read.

transport

path

type string

method

type string

params

type unknown

headers

type Record<string, string>

returns

Promise<RpcResult>

CrossBackendGlobalSetupOptions
#

testing/cross_backend/create_cross_backend_global_setup.ts view source

CrossBackendGlobalSetupOptions import type {CrossBackendGlobalSetupOptions} from '@fuzdev/fuz_app/testing/cross_backend/create_cross_backend_global_setup.js';

configs

Map of derived backend name → BackendConfig factory. The derived name (see derive_name) selects the factory; unknown names throw with the full supported list so a misnamed project surfaces clearly.

type Readonly<Record<string, () => BackendConfig>>

readonly

derive_name?

Derive the backend name from the vitest project name. Default strips cross_backend_(ts_)?.

type (project_name: string): string

readonly

project_name

type string
returns string

provide_key?

Key passed to project.provide (and read by inject in test files). Default 'backend_handle'. Augment vitest's ProvidedContext for it.

type string

readonly

CrossBackendProjectOptions
#

testing/cross_backend/make_cross_backend_project.ts view source

CrossBackendProjectOptions import type {CrossBackendProjectOptions} from '@fuzdev/fuz_app/testing/cross_backend/make_cross_backend_project.js';

name

vitest project name. create_cross_backend_global_setup derives the backend name from it (by default stripping a cross_backend_(ts_)? prefix), so name projects cross_backend_<backend> (e.g. cross_backend_rust, cross_backend_ts_deno).

type string

readonly

global_setup

Path to the consumer's vitest globalSetup module, relative to the consumer repo root (e.g. './src/test/cross_backend/global_setup.ts'). That module is expected to export a create_cross_backend_global_setup result as its default.

type string

readonly

include?

Test-file globs. Default: ['src/test/cross_backend/*.cross.test.ts'].

type readonly string[]

readonly

exclude?

Globs to exclude from include (e.g. a backend-specific variant file). Default: [].

type readonly string[]

readonly

group_order?

vitest sequence.groupOrder. Default: 3 (runs after unit + db).

type number

readonly

test_timeout?

Per-test timeout in ms. Default: 30_000 — see DEFAULT_TEST_TIMEOUT for why the 5 s vitest default does not fit a spawned-backend suite. Always emitted, so a consumer's root testTimeout never leaks in.

type number

readonly

hook_timeout?

Per-hook timeout in ms for beforeAll / afterAll etc. Default: 30_000. Always emitted, so a consumer's root hookTimeout never leaks in.

type number

readonly

CrossImplBenchEntry
#

testing/cross_backend/bench/run_cross_impl_bench.ts view source

CrossImplBenchEntry import type {CrossImplBenchEntry} from '@fuzdev/fuz_app/testing/cross_backend/bench/run_cross_impl_bench.js';

One backend × one scenario, with its timing result.

backend

type string

readonly

scenario

type string

readonly

result

type BenchmarkResult

readonly

CrossImplBenchResult
#

testing/cross_backend/bench/run_cross_impl_bench.ts view source

CrossImplBenchResult import type {CrossImplBenchResult} from '@fuzdev/fuz_app/testing/cross_backend/bench/run_cross_impl_bench.js';

Full sweep across the supplied backends and scenarios.

backends

Backend names, in the order they were run.

type readonly string[]

readonly

scenarios

Scenario names that ran on at least one backend.

type readonly string[]

readonly

entries

type readonly CrossImplBenchEntry[]

readonly

CrossImplComparisonEntry
#

testing/cross_backend/bench/bench_report.ts view source

CrossImplComparisonEntry import type {CrossImplComparisonEntry} from '@fuzdev/fuz_app/testing/cross_backend/bench/bench_report.js';

One backend's result compared against the reference backend, per scenario.

scenario

type string

readonly

reference

The reference backend (a side of the comparison).

type string

readonly

backend

The compared backend (b side).

type string

readonly

comparison

type BenchmarkComparison

readonly

CrossProcessSetupOptions
#

testing/cross_backend/setup.ts view source

CrossProcessSetupOptions import type {CrossProcessSetupOptions} from '@fuzdev/fuz_app/testing/cross_backend/setup.js';

extra_keeper_roles?

Additional roles to grant the fresh keeper on every per-test reset, *in addition to* the [ROLE_KEEPER, ROLE_ADMIN] defaults the _testing_reset action seeds. Cross-process mirror of in-process extra_keeper_roles on default_in_process_suite_options.

Costs nothing extra per test — the _testing_reset action seeds the keeper in a single transaction regardless of how many roles are in the list.

ROLE_ADMIN is already in the default set, so admin-suite consumers usually pass an empty / omitted array. Consumer-defined roles (e.g. teacher) are passed here when the keeper-acting test needs them.

Keeper ≠ admin. Tests that need a *non-admin* secondary account with ROLE_KEEPER declare it via extra_accounts — ROLE_KEEPER's RoleSpec.grant_paths is bootstrap-only, so it can only be granted at the test-binary bootstrap-equivalent step.

type readonly string[]

readonly

extra_accounts?

Bootstrap-time secondary accounts seeded alongside the keeper on every per-test reset. See ExtraAccountSpec for why this is a cradle-only bypass. The reset action seeds them in the same transaction as the keeper.

type readonly ExtraAccountSpec[]

readonly

extra_actors?

Additional actor names to seed on the keeper account, beyond its single bootstrap actor — exposed on fixture.extra_actors. Declare to put the keeper into a multi-actor state so the actor_required / explicit-acting branches are reachable cross-process. See TestFixtureBase.extra_actors.

type readonly string[]

readonly

CrossProcessSseTestOptions
#

testing/cross_backend/sse_round_trip.ts view source

CrossProcessSseTestOptions import type {CrossProcessSseTestOptions} from '@fuzdev/fuz_app/testing/cross_backend/sse_round_trip.js';

setup_test

Per-test fixture producer (default_cross_process_setup(handle)). Each case reads the fresh-per-test keeper's session cookies from fixture.transport.cookies() to thread onto the stream. The keeper holds ROLE_ADMIN by default, so it can subscribe to the admin-gated audit stream and drive admin_session_revoke_all.

type (): Promise<TestFixtureBase>

readonly
returns Promise<TestFixtureBase>

capabilities

Backend capability flags; every case gates on capabilities.sse.

type BackendCapabilities

readonly

base_url

Base URL the backend is reachable at (e.g. http://localhost:1178).

type string

readonly

sse_path?

SSE stream path on the backend. Defaults to /api/admin/audit/stream.

type string

readonly

rpc_path?

RPC endpoint path (e.g. /api/rpc) used by the data-frame and close-on-revoke cases to fire admin_session_revoke_all / account_session_revoke_all over the keeper's session channel. When omitted, those cases are skipped — they depend on the standard account + admin actions being mounted on the RPC endpoint.

type string

readonly

origin?

Origin for the stream request. Defaults to base_url.

type string

readonly

max_per_scope?

The backend's per-session audit-stream cap, which the cap case opens one stream past. Defaults to AUDIT_LOG_SSE_MAX_PER_SCOPE, the cap both spines apply unless overridden; null (a backend with the cap disabled) skips the case.

type number | null

readonly

CrossProcessWsTestOptions
#

testing/cross_backend/ws_round_trip.ts view source

CrossProcessWsTestOptions import type {CrossProcessWsTestOptions} from '@fuzdev/fuz_app/testing/cross_backend/ws_round_trip.js';

setup_test

Per-test fixture producer (default_cross_process_setup(handle)). The authenticated case reads the fresh-per-test keeper's session cookies from fixture.transport.cookies() to thread onto the upgrade.

type (): Promise<TestFixtureBase>

readonly
returns Promise<TestFixtureBase>

capabilities

Backend capability flags; every case gates on capabilities.ws.

type BackendCapabilities

readonly

base_url

Base URL the backend is reachable at (e.g. http://localhost:1178).

type string

readonly

ws_path

WebSocket endpoint path on the backend (e.g. /api/ws).

type string

readonly

origin?

Origin for the authenticated upgrade. Defaults to base_url.

type string

readonly

rpc_path?

RPC endpoint path (e.g. /api/rpc) used by the close-on-revoke case to fire account_session_revoke_all over the keeper's session channel. When omitted, that case is skipped — it depends on the standard account actions being mounted on the RPC endpoint.

type string

readonly

max_message_bytes?

The backend's inbound WebSocket message cap, in bytes — the oversized-message case sends a message just past it. Defaults to DEFAULT_WS_MAX_MESSAGE_BYTES; pass the consumer's value when it raises the server cap.

type number

readonly

CrossSuiteOptions
#

testing/cross_backend/setup.ts view source

CrossSuiteOptions import type {CrossSuiteOptions} from '@fuzdev/fuz_app/testing/cross_backend/setup.js';

Base options shared by every imperative cross-backend parity suite (origin / ready / body_size / cell_* / actor_* / account_lifecycle / app_settings / testing_backdoor / …). The setup_test core is identical across them; each suite extends this with its own path field (rpc_path for RPC-dispatched suites via RpcPathCrossSuiteOptions, ready_path for the readiness probe, etc.), and a suite that actually gates cases on a capability flag extends CapabilityGatedCrossSuiteOptions instead. Lives here in the neutral fixture-types home rather than in any one domain helper, so a non-cell suite never reaches into the cell helpers for its option shape.

setup_test

Per-test fixture-producing function (fresh keeper + db per call).

type (): Promise<TestFixtureBase>

readonly
returns Promise<TestFixtureBase>

DAEMON_TOKEN_HEADER
#

auth/daemon_token.ts view source

"X-Daemon-Token" import {DAEMON_TOKEN_HEADER} from '@fuzdev/fuz_app/auth/daemon_token.js';

The X-Daemon-Token header name.

DaemonInfo
#

cli/daemon.ts view source

value + type

{ version: number; pid: number; port: number; started: string; app_version: string; } import {DaemonInfo} from '@fuzdev/fuz_app/cli/daemon.js';

Daemon info schema for ~/.{name}/run/daemon.json, a strict @fuzdev/fuz_schema object schema.

version

type number

pid

type number

port

type number

started

type string

app_version

type string

DaemonToken
#

auth/daemon_token.ts view source

value + type

string import {DaemonToken} from '@fuzdev/fuz_app/auth/daemon_token.js';

Daemon token format: 43 base64url characters (256 bits).

DaemonTokenRotation
#

DaemonTokenRotationOptions
#

testing/daemon_token_rotation.ts view source

DaemonTokenRotationOptions import type {DaemonTokenRotationOptions} from '@fuzdev/fuz_app/testing/daemon_token_rotation.js';

Options for daemon token rotation.

token_path

Absolute path the token file is written to. Caller computes from its own conventions — e.g. get_daemon_token_path(runtime, app_name) for the standard ~/.{name}/run/daemon_token layout, or a path derived from PUBLIC_<APP>_DIR for cross-process test setups that isolate the app dir to a tmpdir.

type string

rotation_interval_ms?

Rotation interval in ms. Default: 30000 (30s).

type number

DaemonTokenState
#

auth/daemon_token.ts view source

DaemonTokenState import type {DaemonTokenState} from '@fuzdev/fuz_app/auth/daemon_token.js';

Mutable runtime state for daemon token rotation.

This is runtime state (not AppDeps or *Options) — it changes during operation. Created at server startup, passed to the middleware factory.

current_token

Current valid token.

type string

previous_token

Previous token, still valid during the race window. null before first rotation.

type string | null

rotated_at

When the last rotation occurred.

type Date

keeper_account_id

The account ID of the keeper (resolved at startup, set by on_bootstrap).

type string | null

DaemonTokenWriteDeps
#

testing/daemon_token_rotation.ts view source

DaemonTokenWriteDeps

type Pick<EnvDeps, "env_get"> & Pick<FsWriteDeps, "mkdir" | "write_text_file" | "rename">

import type {DaemonTokenWriteDeps} from '@fuzdev/fuz_app/testing/daemon_token_rotation.js';

Deps for writing the daemon token to disk.

env_get

Get an environment variable value.

type (name: string): string | undefined

name

type string
returns string | undefined

mkdir

Create a directory. mode applies at creation (no-op where modes don't apply).

type (path: string, options?: { recursive?: boolean | undefined; mode?: number | undefined; } | undefined): Promise<void>

path

type string

options?

type { recursive?: boolean | undefined; mode?: number | undefined; }
optional
returns Promise<void>

rename

Rename (move) a file.

type (old_path: string, new_path: string): Promise<void>

old_path

type string

new_path

type string
returns Promise<void>

write_text_file

Write text to a file. See WriteFileOptions for mode / exclusive.

type (path: string, content: string, options?: WriteFileOptions | undefined): Promise<void>

path

type string

content

type string

options?

optional
returns Promise<void>

DataExposureTestOptions
#

testing/data_exposure.ts view source

DataExposureTestOptions import type {DataExposureTestOptions} from '@fuzdev/fuz_app/testing/data_exposure.js';

setup_test

Per-test fixture-producing function (per-describe cadence).

type (): Promise<TestFixtureBase>

returns Promise<TestFixtureBase>

surface_source

App surface for schema-level + route-iteration checks. Constructed in TS by the consumer; same shape for in-process and cross-process tests.

type AppSurfaceSpec

sensitive_fields?

Fields that must never appear in any response. Default: sensitive_field_blocklist.

type readonly string[]

admin_only_fields?

Fields that must not appear in non-admin responses. Default: admin_only_field_blocklist.

type readonly string[]

skip_routes?

Routes to skip, in 'METHOD /path' format.

type string[]

Datatable
#

ui/Datatable.svelte view source

accepts children

import Datatable from '@fuzdev/fuz_app/ui/Datatable.svelte';

columns

type DatatableColumn<T>[]

rows

type T[]

row_key?

Row property used as the keyed-each key.

type string & (keyof T)
optional default 'id'

height?

CSS height for the scrollable region (e.g. '400px'). Omit to size to content.

type string
optional

header?

Override default header-cell rendering. Receives the column.

type Snippet<[column: DatatableColumn<T>]>
optional
snippet parameters
column DatatableColumn<T>

cell?

Override default cell rendering. Receives column, row, and the cell value.

type Snippet<[column: DatatableColumn<T>, row: T, value: T[keyof T]]>
optional
snippet parameters
column DatatableColumn<T>
row T
value T[keyof T]

empty?

Rendered when rows is empty. Defaults to a no data text.

type Snippet<[]>
optional

external types

SvelteHTMLElements['div']

generics

Datatable<T extends Record<string, any> = Record<string, any>>
T
constraint Record<string, any>
default Record<string, any>

DATATABLE_COLUMN_WIDTH_DEFAULT
#

ui/datatable.ts view source

120 import {DATATABLE_COLUMN_WIDTH_DEFAULT} from '@fuzdev/fuz_app/ui/datatable.js';

Default initial column width in pixels.

DATATABLE_MIN_COLUMN_WIDTH
#

ui/datatable.ts view source

50 import {DATATABLE_MIN_COLUMN_WIDTH} from '@fuzdev/fuz_app/ui/datatable.js';

Default minimum column width in pixels.

DatatableColumn
#

ui/datatable.ts view source

DatatableColumn<T> import type {DatatableColumn} from '@fuzdev/fuz_app/ui/datatable.js';

Column definition for a Datatable.

generics

DatatableColumn<T = unknown>
T
default unknown

key

Row data accessor key.

type string & (keyof T)

label

Header label text.

type string

width?

Initial column width in pixels.

type number

min_width?

Minimum column width in pixels.

type number

format?

Format a cell value for display. Falls back to format_value when absent.

type (value: T[keyof T], row: T): string

value

type T[keyof T]

row

type T
returns string

Datetime
#

primitive_schemas.ts view source

value + type

string & $brand<"Datetime">

type string & $brand<"Datetime">

import {Datetime} from '@fuzdev/fuz_app/primitive_schemas.js';

Branded ISO 8601 datetime schema.

external types

z.infer<typeof Datetime>

DatetimeNow
#

primitive_schemas.ts view source

value + type

string & $brand<"Datetime">

type string & $brand<"Datetime">

import {DatetimeNow} from '@fuzdev/fuz_app/primitive_schemas.js';

Datetime that defaults to get_datetime_now() when the input is undefined.

external types

z.infer<typeof DatetimeNow>

Db
#

db/db.ts view source

import {Db} from '@fuzdev/fuz_app/db/db.js';

Database wrapper providing a consistent query and transaction interface.

Construct via create_pg_db() from db/db_pg.ts, create_pglite_db() from db/db_pglite.ts or create_pglet_db_driver() from db/db_pglet.ts for proper transaction support, or via create_db() for URL-based auto-detection.

examples

const {db, close} = await create_db('postgres://...'); const users = await db.query<User>('SELECT * FROM users WHERE active = $1', [true]); await db.transaction(async (tx) => { await tx.query('INSERT INTO users ...'); await tx.query('INSERT INTO audit_log ...'); }); await close();

client

type DbClient

readonly

constructor

type new (options: DbDeps): Db

options

type DbDeps

query

Execute a query and return all rows.

type <T>(text: string, values?: unknown[] | undefined): Promise<T[]>

text

SQL text with $1, $2, ... parameter placeholders

type string

values?

parameter values bound to the placeholders in text

type unknown[]
optional
returns Promise<T[]>

the result rows, typed as T

query_one

Execute a query and return the first row, or undefined if no rows.

type <T>(text: string, values?: unknown[] | undefined): Promise<T | undefined>

text

SQL text with $1, $2, ... parameter placeholders

type string

values?

parameter values bound to the placeholders in text

type unknown[]
optional
returns Promise<T | undefined>

the first row, or undefined when the result set is empty

transaction

Run a function inside a database transaction.

The callback receives a transaction-scoped Db. Queries inside the callback go through the transaction connection; queries outside use the pool normally. Commits on success, rolls back on error.

type <T>(fn: (tx_db: Db) => Promise<T>): Promise<T>

fn

async function receiving a transaction-scoped Db

type (tx_db: Db) => Promise<T>
returns Promise<T>

the value returned by fn

throws

  • Error - propagated from `fn` after `ROLLBACK`, or from the driver if `BEGIN` / `COMMIT` / `ROLLBACK` itself fails

DB_ADMIN_STATEMENT_TIMEOUT_MS
#

http/db_routes.ts view source

5000 import {DB_ADMIN_STATEMENT_TIMEOUT_MS} from '@fuzdev/fuz_app/http/db_routes.js';

Per-statement timeout applied (SET LOCAL statement_timeout) inside the table-list, table-detail, and row-DELETE transactions — no single statement outlives it. (Per statement, not per request: the table listing's one-COUNT(*)-per-table loop can still take several in sequence.) Milliseconds.

DB_TABLE_ROWS_DEFAULT_LIMIT
#

http/db_routes.ts view source

100 import {DB_TABLE_ROWS_DEFAULT_LIMIT} from '@fuzdev/fuz_app/http/db_routes.js';

Default page size for GET /tables/:name rows.

DB_TABLE_ROWS_LIMIT_MAX
#

http/db_routes.ts view source

1000 import {DB_TABLE_ROWS_LIMIT_MAX} from '@fuzdev/fuz_app/http/db_routes.js';

Maximum page size for GET /tables/:name rows.

DbClient
#

db/db.ts view source

DbClient import type {DbClient} from '@fuzdev/fuz_app/db/db.js';

Minimal interface that pg, pglite and pglet satisfy.

query

type <T = unknown>(text: string, values?: unknown[] | undefined): Promise<{ rows: T[]; }>

text

type string

values?

type unknown[]
optional
returns Promise<{ rows: T[]; }>

DbDeps
#

db/db.ts view source

DbDeps import type {DbDeps} from '@fuzdev/fuz_app/db/db.js';

Configuration for constructing a Db with transaction support.

transaction is injected by create_db which knows the driver. For pg: acquires a dedicated pool client per transaction. For PGlite: delegates to pglite.transaction(). For pglet: delegates to the adapter's transaction().

client

type DbClient

transaction

type <T>(fn: (tx_db: Db) => Promise<T>): Promise<T>

fn

type (tx_db: Db) => Promise<T>
returns Promise<T>

DbDriverResult
#

db/db.ts view source

DbDriverResult import type {DbDriverResult} from '@fuzdev/fuz_app/db/db.js';

Result of constructing a driver-specific Db.

Returned by create_pg_db(), create_pglite_db() and create_pglet_db_driver(). The close callback is bound to the actual driver — callers never need to know which driver is in use.

db

type Db

close

Close the database connection. Bound to the actual driver at construction.

type (): Promise<void>

returns Promise<void>

DbFactory
#

testing/db.ts view source

DbFactory import type {DbFactory} from '@fuzdev/fuz_app/testing/db.js';

Factory interface for creating test database instances.

name

type string

create

type (): Promise<Db>

returns Promise<Db>

close

type (db: Db): Promise<void>

db

type Db
returns Promise<void>

skip

type boolean

skip_reason?

type string

DbFactoryBuilder
#

testing/db.ts view source

DbFactoryBuilder import type {DbFactoryBuilder} from '@fuzdev/fuz_app/testing/db.js';

Builds a DbFactory from a schema initializer — the shape of every factory builder here.

(call)

type (init_schema: (db: Db) => Promise<void>): DbFactory

init_schema

type (db: Db) => Promise<void>
returns DbFactory

DbRouteDeps
#

http/db_routes.ts view source

DbRouteDeps import type {DbRouteDeps} from '@fuzdev/fuz_app/http/db_routes.js';

Capabilities the db routes need — a narrow structural slice of AppDeps (auth/deps.ts), declared here so http/ stays auth-free. The bound auth/audit_emitter.ts AuditEmitter (and therefore AppDeps.audit / RouteFactoryDeps.audit) satisfies audit structurally.

audit

Pool-routed fire-and-forget audit emit. The row-DELETE handler defers the call via emit_after_commit, so the success-only trail row can never claim a delete whose transaction failed at COMMIT — the pool routing means the write itself never rides the request transaction. The context carries both of the request's side-effect queues: the write goes on pending_effects, the row's listener fan-out on post_commit_effects.

type { emit: (ctx: { pending_effects: Promise<void>[]; post_commit_effects: (() => void | Promise<void>)[]; }, input: { event_type: "db_admin_row_delete"; account_id: (string & $brand<...>) | null; ip: string; metadata: { ...; }; }) => void; }

DbRouteOptions
#

http/db_routes.ts view source

DbRouteOptions import type {DbRouteOptions} from '@fuzdev/fuz_app/http/db_routes.js';

Per-factory configuration for db routes.

db_type

type DbType

db_name

type string

browsable_tables

The tables the browser exposes — an explicit allowlist gating the table list, table detail, and row-DELETE alike. Required with no "all" escape hatch, so a future secret-bearing table stays unlisted until a consumer names it, and NON_BROWSABLE_TABLES is subtracted even when named. An unlisted table 404s as table_not_found — the same answer as a table that doesn't exist.

type readonly string[]

extra_stats?

Optional callback to provide app-specific stats in the health response.

type (db: Db): Promise<Record<string, unknown>>

db

type Db
returns Promise<Record<string, unknown>>

log?

Optional logger for server-side diagnostics (e.g. FK violation details).

type Logger

non_deletable_tables?

Consumer tables to exclude from row deletion, unioned with the builtin NON_DELETABLE_TABLES (which this never replaces).

type readonly string[]

DbStatus
#

db/status.ts view source

DbStatus import type {DbStatus} from '@fuzdev/fuz_app/db/status.js';

Full database status snapshot.

connected

Whether the database is reachable.

type boolean

error?

Error message if connection failed.

type string

table_count

Number of public tables.

type number

tables

Per-table row counts.

type TableStatus[]

migrations

Per-namespace migration status.

type MigrationStatus[]

DbType
#

db/db.ts view source

DbType

type "postgres" | "pglite-file" | "pglet-memory"

import type {DbType} from '@fuzdev/fuz_app/db/db.js';

Database driver type.

DeclinedOffer
#

auth/role_grant_offer_queries.ts view source

DeclinedOffer import type {DeclinedOffer} from '@fuzdev/fuz_app/auth/role_grant_offer_queries.js';

Result of query_role_grant_offer_decline — the declined offer plus the grantor's account_id.

inheritance

from_account_id

Grantor's account_id, resolved via a join on actor so the audit envelope's target_account_id (decline is *to* the grantor) and the post-commit notification target are both addressable without a second round-trip.

type Uuid

default_action_account_rate_limit
#

rate_limiter.ts view source

RateLimiterOptions import {default_action_account_rate_limit} from '@fuzdev/fuz_app/rate_limiter.js';

Default options for per-account action-dispatcher rate limiting: 1200 attempts per 15 minutes. Shared by the HTTP RPC and WebSocket action dispatchers. Permissive — sustained ~80/min is well above any human admin workflow; an oracle probing 10k addresses still finishes in ~2 hours, slow enough to surface in audit. Tighten downstream.

default_action_ip_rate_limit
#

rate_limiter.ts view source

RateLimiterOptions import {default_action_ip_rate_limit} from '@fuzdev/fuz_app/rate_limiter.js';

Default options for per-IP action-dispatcher rate limiting: 600 attempts per 15 minutes. Shared by the HTTP RPC and WebSocket action dispatchers (one budget per action, not per transport). Permissive — catches runaway scripts and egregious oracle probes, but well above human or normal automation pace. Tighten downstream for stricter deployments.

default_audit_factory
#

server/app_backend.ts view source

(params: { db: Db; log: Logger; }): AuditEmitter import {default_audit_factory} from '@fuzdev/fuz_app/server/app_backend.js';

Trivial AuditFactory for consumers that don't compose on_audit_event or audit_log_config. Equivalent to ({db, log}) => create_audit_emitter({db, log}) — exported so the default case stays a single-symbol reference rather than five tokens of boilerplate at every consumer.

Use the inline form when you need to thread on_audit_event / audit_log_config / emit_decorator; the factory composes those three fields itself so there's nothing this constant can pass through.

params

type { db: Db; log: Logger; }

returns

AuditEmitter

DEFAULT_AUDIT_STREAM_ROLE
#

DEFAULT_AUTH_CLEANUP_INTERVAL_MS
#

auth/cleanup.ts view source

number import {DEFAULT_AUTH_CLEANUP_INTERVAL_MS} from '@fuzdev/fuz_app/auth/cleanup.js';

How often start_auth_cleanup runs a pass after its startup pass (10 minutes), unless the consumer passes its own. Equal to the Rust spine's DEFAULT_AUTH_CLEANUP_INTERVAL.

An expired session or offer is already refused by every check that reads it, and a connection closes at its credential's deadline on its own, so the interval bounds two things: how late an offer expiry's audit row lands, and how long dead rows linger.

DEFAULT_BACKOFF_FACTOR
#

actions/socket.svelte.ts view source

1.5 import {DEFAULT_BACKOFF_FACTOR} from '@fuzdev/fuz_app/actions/socket.svelte.js';

Exponential backoff factor: delay = base * factor^(attempt-1).

default_bench_scenarios
#

testing/cross_backend/bench/scenario.ts view source

readonly BenchScenario[]

type readonly BenchScenario[]

import {default_bench_scenarios} from '@fuzdev/fuz_app/testing/cross_backend/bench/scenario.js';

Starter cross-impl scenarios — all on the standard spine surface, so they run on every backend (TS Hono, Rust spine), and all reads, so they're safe to repeat against one bootstrapped keeper without state accumulation.

  • account_verify — the dispatch + auth-resolve floor (no real query work).
  • account_session_list — an authed DB read.
  • audit_log_list — an admin paginated read (the keeper holds ROLE_ADMIN).

login is deliberately omitted: the cross-process test binaries wire a fast TestingArgon2idHasher, so a login scenario would measure dispatch rather than real Argon2 cost — misleading without its own clearly-labeled tier.

DEFAULT_CLOSE_CODE
#

DEFAULT_COLLECTIONS_PATH
#

actions/action_codegen.ts view source

"./action_collections.ts" import {DEFAULT_COLLECTIONS_PATH} from '@fuzdev/fuz_app/actions/action_codegen.js';

Default collections_path — every consumer's gen producers point at the sibling action_collections.ts.

default_cross_process_setup
#

testing/cross_backend/setup.ts view source

(handle: ReconstructedBootstrappedBackendHandle, options?: CrossProcessSetupOptions | undefined): SetupTest import {default_cross_process_setup} from '@fuzdev/fuz_app/testing/cross_backend/setup.js';

Build a SetupTest against a spawned + bootstrapped backend.

Per-test body (unconditional reset — fresh keeper every test):

  1. Fire _testing_reset via the keeper's daemon-token channel. The action wipes auth tables, seeds a fresh keeper (with extra_keeper_roles applied), seeds any extra_accounts, and returns the new credentials.
  2. Build the TestFixture closing over the new keeper as the fixture's primary account / actor (matching in-process semantics). fixture.extra_accounts[username] exposes any bootstrap-time secondaries.
  3. fixture.create_account() mints additional *post-bootstrap* accounts via the production signup + login flow (invite → signup → login → token). Roles go through offer/accept (production consent path).

No reset: boolean opt-in — every test runs against a freshly bootstrapped keeper. This converges in-process and cross-process keeper lifetimes; mutation-cascade tests (password change, revoke-all) and hardcoded-username signup tests work uniformly.

handle

options?

optional

returns

SetupTest

default_error_schema_tightness
#

testing/surface_invariants.ts view source

ErrorSchemaTightnessOptions import {default_error_schema_tightness} from '@fuzdev/fuz_app/testing/surface_invariants.js';

Baseline error schema tightness applied by describe_standard_attack_surface_tests when no config is passed.

Uses min_specificity: 'enum' (the assertion default) with ignore_statuses for middleware-derived status codes that are commonly generic (auth middleware produces multiple error codes at 401/403, and 429 comes from rate limiters), and allowlist seeded with fuz_app_stock_route_tightness_allowlist so fuz_app-shipped routes with heterogeneous generic schemas don't force every consumer to hand-maintain an identical allowlist. Consumers can pass a narrower config with project-specific allowlist entries, or pass null to skip the assertion entirely.

default_format_scope
#

ui/format_scope.ts view source

(args: { scope_id: string | null; role: string; }): string | null import {default_format_scope} from '@fuzdev/fuz_app/ui/format_scope.js';

Default FormatScope — always returns null so callers fall back to the raw uuid.

args

type { scope_id: string | null; role: string; }

returns

string | null

DEFAULT_HEARTBEAT_INTERVAL
#

actions/socket.svelte.ts view source

30000 import {DEFAULT_HEARTBEAT_INTERVAL} from '@fuzdev/fuz_app/actions/socket.svelte.js';

Idle interval before sending a heartbeat (ms).

DEFAULT_HEARTBEAT_RECEIVE_TIMEOUT
#

default_in_process_setup
#

testing/cross_backend/in_process_setup.ts view source

(options: InProcessSetupOptions): SetupTest import {default_in_process_setup} from '@fuzdev/fuz_app/testing/cross_backend/in_process_setup.js';

Build a SetupTest that creates a fresh TestApp per call via create_test_app and projects it into the TestFixture shape.

Same factory inputs create_test_app already takes — this helper is a projection layer, not a new lifecycle. fuz_app's own src/test/ and consumer suites pass default_in_process_setup({...factory_inputs}) in place of the old per-suite factory-input bundle. The extra_accounts slot (see InProcessSetupOptions) seeds bootstrap-time secondaries directly via create_test_account_with_credentials against the same DB the keeper just landed on — mirrors the cross-process _testing_reset cradle so suite bodies read fixture.extra_accounts[username] uniformly regardless of transport.

The describe-level auth_integration_truncate_tables / WASM database cache lifecycle stays in create_default_db_factory / create_describe_db (testing/db.ts) — default_in_process_setup doesn't manage db state beyond what create_test_app already does.

options

returns

SetupTest

default_in_process_suite_options
#

testing/cross_backend/in_process_setup.ts view source

<const O extends DefaultInProcessSuiteOptions>(options: O): { setup_test: SetupTest; surface_source: AppSurfaceSpec; capabilities: BackendCapabilities; session_options: O["session_options"]; create_route_specs: O["create_route_specs"]; rpc_endpoints: O["rpc_endpoints"]; } import {default_in_process_suite_options} from '@fuzdev/fuz_app/testing/cross_backend/in_process_setup.js';

Build the full in-process suite bundle in a single helper invocation. Output covers {setup_test, surface_source, capabilities} plus every factory input the Tier 1 suites read at their top level (session_options, create_route_specs, rpc_endpoints) — so the call site spreads once and adds only suite-specific extras (roles, skip_routes, input_overrides, db_factories, ...).

// Suite-extras-free call: helper output is the entire options bag. describe_round_trip_validation(default_in_process_suite_options({ session_options, create_route_specs, rpc_endpoints: [rpc_endpoint_spec], })); // With suite-specific extras: spread and add. describe_standard_admin_integration_tests({ ...default_in_process_suite_options({ session_options, create_route_specs, rpc_endpoints, extra_keeper_roles: [ROLE_ADMIN], }), roles, });

Suites that don't read session_options / rpc_endpoints at their top level (round_trip, data_exposure) accept the spread anyway — excess properties on spread sources aren't checked by TS, and the uniform shape keeps consumer call sites mechanical.

options

type O

returns

{ setup_test: SetupTest; surface_source: AppSurfaceSpec; capabilities: BackendCapabilities; session_options: O["session_options"]; create_route_specs: O["create_route_specs"]; rpc_endpoints: O["rpc_endpoints"]; }

generics

default_in_process_suite_options<O extends DefaultInProcessSuiteOptions>
O

DEFAULT_INTEGRATION_ERROR_COVERAGE
#

testing/error_coverage.ts view source

0.2 import {DEFAULT_INTEGRATION_ERROR_COVERAGE} from '@fuzdev/fuz_app/testing/error_coverage.js';

Default minimum error coverage threshold for the standard integration and admin test suites. Conservative — not all error paths are exercisable in the composable suites. Consumers should increase as their test suites mature.

default_login_account_rate_limit
#

rate_limiter.ts view source

RateLimiterOptions import {default_login_account_rate_limit} from '@fuzdev/fuz_app/rate_limiter.js';

Default options for per-account login rate limiting: 10 attempts per 30 minutes.

DEFAULT_LOGIN_FAIL_FLOOR_MS
#

auth/account_routes.ts view source

250 import {DEFAULT_LOGIN_FAIL_FLOOR_MS} from '@fuzdev/fuz_app/auth/account_routes.js';

Default minimum wall-clock time (ms) for a login failure (401) response.

Picked to exceed the p99 of every 401 code path (Argon2id dominates at ~100ms, plus DB + overhead). The handler races failure work against sleep(floor + jitter) via await, so observed response time = max(work, delay). Found-vs-not-found and rate-limit-skipped-vs-not paths converge. Only 401 is padded — 429 stays fast by design to keep rate-limit DoS handling cheap.

DEFAULT_LOGIN_FAIL_JITTER_MS
#

auth/account_routes.ts view source

25 import {DEFAULT_LOGIN_FAIL_JITTER_MS} from '@fuzdev/fuz_app/auth/account_routes.js';

Default uniform jitter window (±ms) layered on the floor.

Random jitter prevents a stable clamp point from leaking whenever a path occasionally exceeds the floor. Math.random is sufficient — we only need unpredictability of the exact delay, not cryptographic guarantees.

default_login_ip_rate_limit
#

rate_limiter.ts view source

RateLimiterOptions import {default_login_ip_rate_limit} from '@fuzdev/fuz_app/rate_limiter.js';

Default options for per-IP auth rate limiting: 5 attempts per 15 minutes. The default for each of the three per-surface IP limiters (login + password change, signup, bootstrap) and the shared cap the Rust spine's DEFAULT_LOGIN_IP_RATE_LIMIT pins.

Deliberately not widened when the buckets became monotone (a success no longer refunds them — see RateLimiter.reset). Widening would have bought NAT'd egress headroom by loosening the one bound that caps credential guessing from a single address; splitting one shared bucket into three per-surface ones buys the same headroom without touching that bound.

DEFAULT_MAX_BODY_SIZE
#

server/app_server.ts view source

number import {DEFAULT_MAX_BODY_SIZE} from '@fuzdev/fuz_app/server/app_server.js';

Default maximum request body size: 1 MiB.

DEFAULT_MAX_CONNECTIONS_PER_ACCOUNT
#

actions/transports_ws_backend.ts view source

50 import {DEFAULT_MAX_CONNECTIONS_PER_ACCOUNT} from '@fuzdev/fuz_app/actions/transports_ws_backend.js';

Default per-account WebSocket connection cap, applied by BackendWebsocketTransport unless overridden via BackendWebsocketTransportOptions.max_connections_per_account.

Matches the account-wide ceiling the audit SSE cap implies (the default session cap, DEFAULT_MAX_SESSIONS, times AUDIT_LOG_SSE_MAX_PER_SCOPE streams per session) — generous enough that a user's tabs and tools never meet it; only an abusive or leaking client does. The twin of the Rust spine's DEFAULT_MAX_CONNECTIONS_PER_ACCOUNT.

DEFAULT_MAX_SESSIONS
#

auth/account_route_schema.ts view source

5 import {DEFAULT_MAX_SESSIONS} from '@fuzdev/fuz_app/auth/account_route_schema.js';

Default maximum sessions per account.

The Rust twin pins the same 5 as a const (fuz_auth's DEFAULT_MAX_SESSIONS) rather than a route-state field, because its AccountRouteState has no Default and a new field would break every exhaustive literal in every consumer workspace. So this stays the configurable side of the pair (null disables), and the two ship the same number — retuning means editing both.

DEFAULT_MAX_TOKENS
#

DEFAULT_METATYPES_PATH
#

actions/action_codegen.ts view source

"./action_metatypes.ts" import {DEFAULT_METATYPES_PATH} from '@fuzdev/fuz_app/actions/action_codegen.js';

Default metatypes_path — sibling action_metatypes.ts carrying the generated ActionMethod.

DEFAULT_PEER_REQUEST_TIMEOUT
#

actions/peer_request.ts view source

10000 import {DEFAULT_PEER_REQUEST_TIMEOUT} from '@fuzdev/fuz_app/actions/peer_request.js';

Default deadline (ms) for a server→client peer request before it resolves timeout. Twin of the Rust spine's DEFAULT_PEER_TIMEOUT.

DEFAULT_QUEUE_MAX_SIZE
#

actions/socket.svelte.ts view source

100 import {DEFAULT_QUEUE_MAX_SIZE} from '@fuzdev/fuz_app/actions/socket.svelte.js';

Default bound on buffered requests while disconnected. Overflow rejects.

DEFAULT_RATE_LIMITER_MAX_KEYS
#

rate_limiter.ts view source

100000 import {DEFAULT_RATE_LIMITER_MAX_KEYS} from '@fuzdev/fuz_app/rate_limiter.js';

Default tracked-key cap: bounds worst-case memory under key-enumeration attacks (an attacker rotating source IPs cannot grow the backing map indefinitely between cleanup ticks). Tuned to comfortably fit real traffic for a single-instance deployment while capping memory at a few MB in the worst case.

DEFAULT_RECONNECT_DELAY
#

DEFAULT_RECONNECT_DELAY_MAX
#

actions/socket.svelte.ts view source

10000 import {DEFAULT_RECONNECT_DELAY_MAX} from '@fuzdev/fuz_app/actions/socket.svelte.js';

Max reconnect delay in ms (cap on exponential backoff).

DEFAULT_ROTATION_INTERVAL_MS
#

DEFAULT_SERVER_HEARTBEAT_TIMEOUT
#

actions/register_action_ws.ts view source

60000 import {DEFAULT_SERVER_HEARTBEAT_TIMEOUT} from '@fuzdev/fuz_app/actions/register_action_ws.js';

Default inactivity window before the server closes a silent socket.

default_setup_logger
#

dev/setup.ts view source

SetupLogger import {default_setup_logger} from '@fuzdev/fuz_app/dev/setup.js';

Default logger using bracket format.

DEFAULT_SIGNUP_FAIL_FLOOR_MS
#

auth/signup_routes.ts view source

250 import {DEFAULT_SIGNUP_FAIL_FLOOR_MS} from '@fuzdev/fuz_app/auth/signup_routes.js';

Default minimum wall-clock time (ms) for a signup denial (403 / 409) response.

Parallel to login's DEFAULT_LOGIN_FAIL_FLOOR_MS. Without a floor, an attacker can distinguish ERROR_NO_MATCHING_INVITE (cheap — bails before Argon2 + tx) from ERROR_SIGNUP_CONFLICT (Argon2 + tx + rollback) via response time and use the gap as a username-enumeration oracle. Picked to exceed the p99 of every denial code path (Argon2id dominates at ~100ms, plus DB + overhead). 429 stays fast by design (same precedent as login) so rate-limit DoS handling stays cheap.

DEFAULT_SIGNUP_FAIL_JITTER_MS
#

auth/signup_routes.ts view source

25 import {DEFAULT_SIGNUP_FAIL_JITTER_MS} from '@fuzdev/fuz_app/auth/signup_routes.js';

Default uniform jitter window (±ms) layered on the floor.

Random jitter prevents a stable clamp point from leaking whenever a path occasionally exceeds the floor. Math.random is sufficient — we only need unpredictability of the exact delay, not cryptographic guarantees.

DEFAULT_SPECS_MODULE
#

actions/action_codegen.ts view source

"./action_specs.ts" import {DEFAULT_SPECS_MODULE} from '@fuzdev/fuz_app/actions/action_codegen.js';

Default specs_module — sibling action_specs.ts namespace bundled by the consumer.

default_test_bootstrap_token
#

testing/cross_backend/default_secrets.ts view source

"test_bootstrap_token_for_cross_be" import {default_test_bootstrap_token} from '@fuzdev/fuz_app/testing/cross_backend/default_secrets.js';

Fixed bootstrap token written to each backend's token_path before spawn. The test binary reads + consumes this via its *_BOOTSTRAP_TOKEN_PATH env var; the harness POSTs the same token to /api/account/bootstrap to mint the keeper account. Any 32+ char string works — the binary just compares bytes, no entropy required for tests.

default_test_cookie_keys
#

default_test_keeper_password
#

default_test_keeper_username
#

DEFAULT_TEST_PASSWORD
#

testing/test_credentials.ts view source

"test-password-123" import {DEFAULT_TEST_PASSWORD} from '@fuzdev/fuz_app/testing/test_credentials.js';

Default password for test-bootstrapped accounts.

DEFAULT_WS_MAX_MESSAGE_BYTES
#

actions/transports.ts view source

number import {DEFAULT_WS_MAX_MESSAGE_BYTES} from '@fuzdev/fuz_app/actions/transports.js';

Default cap on one WebSocket message, in UTF-8 bytes — equal to the default HTTP body cap (DEFAULT_MAX_BODY_SIZE), so one JSON-RPC request is bounded the same on both transports. The server closes the socket on a larger inbound message with WS_CLOSE_MESSAGE_TOO_BIG (there's no per-message error reply), and FrontendWebsocketTransport refuses to send one. The twin of the Rust spine's DEFAULT_WS_MAX_MESSAGE_BYTES.

DEFAULT_WS_OUTBOUND_QUEUE_BYTES
#

actions/transports_ws_backend.ts view source

number import {DEFAULT_WS_OUTBOUND_QUEUE_BYTES} from '@fuzdev/fuz_app/actions/transports_ws_backend.js';

Default byte budget of the frames waiting to be written to one connection, applied by BackendWebsocketTransport unless overridden via BackendWebsocketTransportOptions.outbound_queue_bytes.

4 MiB — four messages at DEFAULT_WS_MAX_MESSAGE_BYTES as it stands, and a value of its own: it does not move with the inbound message cap. A consumer whose server pushes more than this at a client in one burst — large frames, or long runs of them — sets its own. Same value as the Rust spine's DEFAULT_WS_OUTBOUND_QUEUE_BYTES.

DefaultInProcessSuiteOptions
#

testing/cross_backend/in_process_setup.ts view source

DefaultInProcessSuiteOptions import type {DefaultInProcessSuiteOptions} from '@fuzdev/fuz_app/testing/cross_backend/in_process_setup.js';

Consumer-facing options for default_in_process_suite_options — the minimal factory inputs both default_in_process_setup and create_test_app_surface_spec consume to produce the {setup_test, surface_source, capabilities} bundle.

session_options

type SessionOptions<string>

create_route_specs

type (ctx: AppServerContext): RouteSpec[]

ctx

returns RouteSpec[]

rpc_endpoints?

type RpcEndpointsSuiteOption

bootstrap?

Bootstrap config — top-level slot, single source of truth for both surface generation and live dispatch. Same precedent as rpc_endpoints. Discriminated by mode; omit for the default (no bootstrap route mounted).

type BootstrapServerOptions

app_options?

type Partial<Omit<AppServerOptions, "backend" | "bootstrap" | "session_options" | "create_route_specs" | "rpc_endpoints">>

extra_keeper_roles?

Additional roles to grant the bootstrapped keeper alongside ROLE_KEEPER — additive, never replaces. The keeper account always holds ROLE_KEEPER (otherwise daemon-token auth breaks); pass extras here for suites that need additional role coverage.

Admin-suite consumers pass [ROLE_ADMIN] so the default keeper can hit admin-gated RPC methods. describe_standard_admin_integration_tests and describe_audit_completeness_tests need this.

type string[]

extra_accounts?

Bootstrap-time secondary accounts seeded alongside the keeper. See ExtraAccountSpec for the cradle-only-bypass rationale. Same shape as the cross-process extra_accounts option — suites read seeded accounts from fixture.extra_accounts[username] regardless of transport.

type readonly ExtraAccountSpec[]

extra_actors?

Additional actor names to seed on the bootstrapped keeper — exposed on fixture.extra_actors. See TestFixtureBase.extra_actors.

type readonly string[]

surface_source?

Pre-built AppSurfaceSpec — overrides the default which calls create_test_app_surface_spec against the same factory inputs. Pass when surface assembly needs fields outside the shared subset (e.g. env_schema, event_specs, ws_endpoints, transform_middleware).

type AppSurfaceSpec

deliver_inbound
#

testing/transports/ws_client.ts view source

(parsed: unknown, received: unknown[], waiters: WsWaiter[], on_request: WsRequestResponder | undefined, reply: (message: unknown) => void): void import {deliver_inbound} from '@fuzdev/fuz_app/testing/transports/ws_client.js';

Deliver one parsed inbound frame to a client's receive sink — the shared parse→(respond|push)→resolve core.

A server-initiated request ({method, id}) is answered by on_request when supplied (replying via reply) and is not surfaced as a normal message; everything else — notifications, replies to the client's own requests, and non-JSON frames — is pushed onto received and resolves any matching waiters, exactly as before. With no on_request, every frame (including a server-initiated request) flows to the sink unchanged, so the seam is purely additive.

parsed

type unknown

received

type unknown[]

waiters

type WsWaiter[]

on_request

type (WsRequestResponder) | undefined

reply

type (message: unknown) => void

returns

void

derive_error_schemas
#

http/error_schemas.ts view source

({ auth, has_input, has_params, has_query, rate_limit, checks_content_type }: DeriveErrorSchemasOptions): Partial<Record<number, ZodType<unknown, unknown, $ZodTypeInternals<unknown, unknown>>>> import {derive_error_schemas} from '@fuzdev/fuz_app/http/error_schemas.js';

__0

returns

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

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

DeriveErrorSchemasOptions
#

http/error_schemas.ts view source

DeriveErrorSchemasOptions import type {DeriveErrorSchemasOptions} from '@fuzdev/fuz_app/http/error_schemas.js';

Derive error schemas from a route's auth requirement, input schema, and rate limit config.

Returns the error schemas that middleware will auto-produce for this route. Route handlers can declare additional error schemas via RouteSpec.errors; explicit entries override auto-derived ones for the same status code.

Derivation rules under the new flat-record auth shape:

auth

type RouteAuth

has_input?

type boolean

has_params?

type boolean

has_query?

type boolean

rate_limit?

type RateLimitKey

checks_content_type?

type boolean

describe_account_lifecycle_cross_tests
#

describe_actor_lookup_cross_tests
#

describe_actor_search_cross_tests
#

describe_adversarial_404
#

testing/adversarial_404.ts view source

(options: AdversarialTestOptions): void import {describe_adversarial_404} from '@fuzdev/fuz_app/testing/adversarial_404.js';

Generate adversarial 404 response validation tests.

For each route with params + 404 in error_schemas:

  1. Creates a stub handler returning 404 with the declared error code
  2. Fires a request with valid-format params (nil UUIDs for UUID params)
  3. Validates response status is 404
  4. Validates response body matches the declared 404 Zod schema

options

returns

void

describe_adversarial_auth
#

testing/attack_surface.ts view source

(options: AdversarialTestOptions): void import {describe_adversarial_auth} from '@fuzdev/fuz_app/testing/attack_surface.js';

Generate adversarial HTTP auth enforcement test suites.

Describe blocks:

  • unauthenticated → 401 — every protected route
  • wrong role → 403 — every role route the role apps' session credential reaches, tested with all non-matching roles
  • authenticated without role → 403 — the same routes, no-role context
  • credential-gated routes reject a disallowed credential → 403 — every route declaring auth.credential_types, probed on a channel it refuses
  • correct auth passes guard — every protected route, assert not 401/403

options

returns

void

describe_adversarial_input
#

testing/adversarial_input.ts view source

(options: AdversarialTestOptions): void import {describe_adversarial_input} from '@fuzdev/fuz_app/testing/adversarial_input.js';

Generate adversarial input validation test suites.

Tests input body validation and params validation for all routes. Uses correct auth credentials so auth guards pass and validation middleware is actually exercised.

options

returns

void

describe_app_settings_cross_tests
#

describe_audit_completeness_tests
#

testing/audit_completeness.ts view source

(options: AuditCompletenessTestOptions): void import {describe_audit_completeness_tests} from '@fuzdev/fuz_app/testing/audit_completeness.js';

Composable audit log completeness test suite.

Verifies that every auth mutation route produces the correct audit log event type. Exercises routes via HTTP requests against a real database, then reads events back through the audit_log_list RPC (the production observation path the admin UI consumes).

options

returns

void

throws

  • Error - at setup time when `options.rpc_endpoints` is empty — the mutation-audit tests drive role_grant flow, session/token revoke-all, and invite create/delete through their RPC action specs. Hard-fails via `require_rpc_endpoint_path`.

describe_bearer_auth_cases
#

testing/middleware.ts view source

(suite_name: string, cases: BearerAuthTestCase[]): void import {describe_bearer_auth_cases} from '@fuzdev/fuz_app/testing/middleware.js';

Run a table of bearer auth middleware test cases.

Generates one test() per case inside a describe() block.

suite_name

type string

cases

returns

void

describe_body_size_cross_tests
#

describe_body_size_smuggling_cross_tests
#

describe_bootstrap_success_tests
#

describe_cell_crud_cross_tests
#

describe_cell_gated_create_cross_tests
#

describe_cell_grant_role_cross_tests
#

describe_cell_moderate_cross_tests
#

testing/cross_backend/cell_gated_create.ts view source

(options: RpcPathCapabilityGatedCrossSuiteOptions): void import {describe_cell_moderate_cross_tests} from '@fuzdev/fuz_app/testing/cross_backend/cell_gated_create.js';

Cross-backend parity for the cell_moderate verb (the `pending → approved | rejected` transition) — root-authority-gated.

Builds a moderated public space (admin) + a participant's post (born pending + private under the moderation_required: true policy), then proves both spines agree:

  • a root manager (admin) approves → moderation: 'approved' + visibility: 'public' (the contribution goes live).
  • the author cannot self-approve → 403 cell_moderate_forbidden (the author can *view* their own pending cell, so they reach the gate, and are denied — moderation authority is over the governing root, not the contribution).
  • a non-viewer is 404-masked → a stranger who can't see the pending contribution gets cell_not_found, never learns it exists.
  • reject → moderation: 'rejected', visibility stays private.

Gated on capabilities.cell_gated_create — a pending contribution only exists when the directory authorizer is mounted (reference spine binaries).

options

returns

void

describe_cell_relations_cross_tests
#

describe_conformance_table_tests
#

describe_cookie_attributes_cross_tests
#

describe_credential_deadline_cross_tests
#

testing/cross_backend/credential_deadline.ts view source

(options: CredentialDeadlineCrossTestOptions): void import {describe_credential_deadline_cross_tests} from '@fuzdev/fuz_app/testing/cross_backend/credential_deadline.js';

Register the credential-deadline suite: a WebSocket on a session or an API token closes with 4001 "Credential expired" at its credential's expiry, an audit stream on a session ends there, and the expiry seam never extends.

options

returns

void

describe_credential_header_robustness_cross_tests
#

describe_cross_process_sse_tests
#

testing/cross_backend/sse_round_trip.ts view source

(options: CrossProcessSseTestOptions): void import {describe_cross_process_sse_tests} from '@fuzdev/fuz_app/testing/cross_backend/sse_round_trip.js';

Register the cross-process SSE round-trip suite. Its cases run over a real streaming fetch: connected-comment, audit data frame, account-wide close-on-revoke, session-scoped close-on-revoke, close on account delete, and the per-session cap.

options

returns

void

describe_cross_process_ws_tests
#

testing/cross_backend/ws_round_trip.ts view source

(options: CrossProcessWsTestOptions): void import {describe_cross_process_ws_tests} from '@fuzdev/fuz_app/testing/cross_backend/ws_round_trip.js';

Register the cross-process WS round-trip suite over a real upgrade: authed heartbeat round-trip, heartbeat's parameterless-shape contract, an oversized message closing with WS_CLOSE_MESSAGE_TOO_BIG, anonymous-upgrade refusal, disallowed-origin refusal, and — when rpc_path is supplied — session revocation and account deletion closing the live socket, plus a self-revoking socket reading its reply before its close, and a WS mutation's audit row read back after _testing_drain_effects.

options

returns

void

describe_data_exposure_tests
#

testing/data_exposure.ts view source

(options: DataExposureTestOptions): void import {describe_data_exposure_tests} from '@fuzdev/fuz_app/testing/data_exposure.js';

Composable data exposure test suite.

Three test groups:

  1. Schema-level — walk JSON Schema output/error schemas for sensitive field names
  2. Runtime — fire real requests and check response bodies against blocklists
  3. Cross-privilege — admin routes return 403 for non-admin, error responses contain no sensitive fields

options

returns

void

describe_fact_serving_cross_tests
#

describe_identity_parity_cross_tests
#

describe_login_security_cross_tests
#

describe_origin_conformance_cross_tests
#

testing/cross_backend/origin_conformance.ts view source

(options: RpcPathCrossSuiteOptions): void import {describe_origin_conformance_cross_tests} from '@fuzdev/fuz_app/testing/cross_backend/origin_conformance.js';

Register the origin conformance suite: every row of the committed table, sent to the backend's _testing_origin_check action over the keeper's daemon-token channel, must get the answer the table names. The action is on every spine test binary, so the suite is ungated.

options

returns

void

describe_origin_cross_tests
#

describe_origin_scope_cross_tests
#

describe_peer_ping_ws_tests
#

testing/cross_backend/peer_ping_ws.ts view source

(options: PeerPingWsTestOptions): void import {describe_peer_ping_ws_tests} from '@fuzdev/fuz_app/testing/cross_backend/peer_ping_ws.js';

Register the server-initiated peer/ping suite — a positive round-trip plus the security negatives the design doc's §Security surface mandates (unsolicited-response rejection, per-connection id isolation, never-reply Timeout, wrong-shape reply rejection, plus client-error forwarding and the HTTP no-transport path). Gated on capabilities.peer_request.

options

returns

void

describe_query_shape_cross_tests
#

describe_rate_limiting_tests
#

testing/rate_limiting.ts view source

(options: RateLimitingTestOptions): void import {describe_rate_limiting_tests} from '@fuzdev/fuz_app/testing/rate_limiting.js';

Standard rate limiting integration test suite.

Creates 2 test groups:

  1. IP rate limiting on login — fires max_attempts + 1 login requests, verifies the last returns 429 with a valid RateLimitError body.
  2. Per-account rate limiting on login — fires max_attempts + 1 login requests with the same username, verifies the last returns 429.

There is deliberately no bearer-auth group: the bearer path carries no rate limiter on either spine, because an API token's entropy — not throttling — is what bounds guessing it. See auth/bearer_auth.ts.

Each test group asserts that required routes exist, failing with a descriptive message if the consumer's route specs are misconfigured.

options

returns

void

describe_ready_cross_tests
#

describe_role_grant_offer_enumeration_cross_tests
#

describe_role_grant_offer_notification_ws_tests
#

testing/cross_backend/role_grant_offer_notification_ws.ts view source

(options: RoleGrantOfferNotificationWsTestOptions): void import {describe_role_grant_offer_notification_ws_tests} from '@fuzdev/fuz_app/testing/cross_backend/role_grant_offer_notification_ws.js';

Register the role-grant-offer WS notification suite — seven cases over a real upgrade, one per server-initiated notification (received / accepted / declined / retracted / revoke + supersede on both the accept and revoke cascades). Each opens the affected counterparty's socket, drives the lifecycle RPC, and strict-parses the delivered frame against its canonical params schema. Gated on capabilities.ws.

options

returns

void

describe_role_grant_participation_cross_tests
#

describe_role_grant_role_grammar_cross_tests
#

describe_round_trip_validation
#

testing/round_trip.ts view source

(options: RoundTripTestOptions): void import {describe_round_trip_validation} from '@fuzdev/fuz_app/testing/round_trip.js';

Run schema-driven round-trip validation tests.

For each route:

  1. Resolve URL with valid params
  2. Generate a valid request body (or use override)
  3. Pick auth headers matching the route's auth requirement
  4. Fire the request through fixture.transport and validate the response

SSE routes are skipped by Content-Type sniff. Routes returning non-2xx with valid input are still validated against their declared error schemas.

options

returns

void

describe_rpc_attack_surface_tests
#

testing/rpc_attack_surface.ts view source

(options: RpcAttackSurfaceOptions): void import {describe_rpc_attack_surface_tests} from '@fuzdev/fuz_app/testing/rpc_attack_surface.js';

Run the standard RPC attack surface test suite.

Generates 3 test groups:

  1. Auth enforcement — per-method auth checks via JSON-RPC envelopes
  2. Adversarial envelopes — malformed JSON-RPC requests
  3. Adversarial params — schema-invalid params per method

Skips silently when surface.rpc_endpoints is empty.

options

returns

void

describe_rpc_round_trip_tests
#

testing/rpc_round_trip.ts view source

(options: RpcRoundTripTestOptions): void import {describe_rpc_round_trip_tests} from '@fuzdev/fuz_app/testing/rpc_round_trip.js';

Run schema-driven round-trip validation for RPC endpoints.

For each method:

  1. Generate valid params from the action's input schema
  2. Fire a POST request with JSON-RPC envelope
  3. For side_effects: false methods, also fire a GET request
  4. Validate response is well-formed JSON-RPC; successful responses are also validated against the method's declared output schema

Error responses (from missing DB state, etc.) are expected and validated as well-formed JSON-RPC errors. Successful responses are validated against action.spec.output. A method not found (-32601) error is the one exception — it means the backend is missing a method the local surface advertises, so the round-trip fails loud (assert_method_implemented) rather than accepting it as a valid error envelope.

options

returns

void

describe_session_cap_cross_tests
#

describe_simple_request_cross_tests
#

describe_sse_route_tests
#

testing/sse_round_trip.ts view source

(options: SseRouteTestOptions): void import {describe_sse_route_tests} from '@fuzdev/fuz_app/testing/sse_round_trip.js';

Run SSE route validation tests.

For each route: opens an authenticated SSE connection, asserts the connected comment, fires the trigger, validates the resulting payload, then asserts close-on-revoke (unless opted out).

options

returns

void

throws

  • Error - at setup time when `options.rpc_endpoints` is empty — the close-on-revoke assertion dispatches `account_session_revoke_all` via RPC. Hard-fails via `require_rpc_endpoint_path`.

describe_standard_admin_integration_tests
#

testing/admin_integration.ts view source

(options: StandardAdminIntegrationTestOptions): void import {describe_standard_admin_integration_tests} from '@fuzdev/fuz_app/testing/admin_integration.js';

Standard admin integration test suite for fuz_app admin routes.

Exercises account listing, role_grant grant/revoke (via RPC), session management, token management, audit log reads, admin-to-admin isolation, and 401/403 error-coverage on the admin REST surface. Output-schema conformance is not in scope — see the module docstring for the suites that cover it.

options

returns

void

throws

  • Error - at setup time when `options.rpc_endpoints` is empty — admin role_grant grant/revoke, session/token revoke-all, and audit-log reads are RPC-only. Hard-fails via `require_rpc_endpoint_path` so consumers see a clear setup error rather than `method not found` mid-suite.

describe_standard_adversarial_headers
#

testing/adversarial_headers.ts view source

(suite_name: string, options: TestMiddlewareStackOptions, allowed_origin: string, extra_cases?: AdversarialHeaderCase[] | undefined): void import {describe_standard_adversarial_headers} from '@fuzdev/fuz_app/testing/adversarial_headers.js';

Create a middleware stack app with standard adversarial header tests.

Convenience wrapper combining create_test_middleware_stack_app and create_standard_adversarial_cases. Generates one test() per case inside a describe() block — asserts body content for both error and success cases, and verifies that mock_validate was (or was not) reached per the case's validate_expectation flag, ensuring earlier middleware actually short-circuits before token validation in the rejection cases.

suite_name

the describe block name

type string

options

middleware stack configuration

allowed_origin

an origin that passes the origin check (used for standard cases)

type string

extra_cases?

additional cases appended after the 7 standard ones

optional

returns

void

describe_standard_attack_surface_tests
#

testing/attack_surface.ts view source

(options: StandardAttackSurfaceOptions): void import {describe_standard_attack_surface_tests} from '@fuzdev/fuz_app/testing/attack_surface.js';

Run the standard attack surface test suite.

Test groups:

  1. Snapshot — live surface matches committed JSON
  2. Determinism — building twice yields identical results
  3. Public routes — bidirectional check (no unexpected, no missing)
  4. Middleware stack — every API route has the full middleware chain
  5. Surface invariants — structural assertions over surface.routes (error schemas, descriptions, duplicates, consistency)
  6. RPC/WS surface invariants — structural assertions over surface.rpc_endpoints + surface.ws_endpoints (descriptions, protocol-action spread, kind ⇔ auth)
  7. Security policy — rate limiting on sensitive routes, no unexpected public mutations, method conventions
  8. Error schema tightness — informational log of generic vs specific error schemas, plus assertion against default_error_schema_tightness by default (opt out with error_schema_tightness: null)
  9. Adversarial auth — unauthenticated/wrong-role/correct-auth enforcement
  10. Adversarial input — input body and params validation
  11. Adversarial 404 — stub 404 handlers, validate response bodies against declared schemas

Consumer test files call this with project-specific options, then add any project-specific assertions in additional describe blocks.

options

returns

void

describe_standard_cross_process_tests
#

testing/cross_backend/standard.ts view source

(options: StandardCrossProcessTestOptions): void import {describe_standard_cross_process_tests} from '@fuzdev/fuz_app/testing/cross_backend/standard.js';

Run the cross-process standard test bundle — integration, admin (when roles provided), round trip, RPC round trip, data exposure. See the module doc for the suites omitted from this bundle and why.

options

returns

void

describe_standard_integration_tests
#

testing/integration.ts view source

(options: StandardIntegrationTestOptions): void import {describe_standard_integration_tests} from '@fuzdev/fuz_app/testing/integration.js';

Standard integration test suite for fuz_app auth routes.

Exercises login/logout, cookie attributes, session security, session revocation, password change (incl. API token revocation), origin verification, bearer auth (incl. browser context discard on mutations), token revocation, cross-account isolation, expired credential rejection, signup invite edge cases, and response body validation.

Each test group asserts that required routes exist, failing with a descriptive message if the consumer's route specs are misconfigured.

The two signup-invite-edge-case tests call invite_create_action_spec (admin-gated) over the fixture's session, so consumers wiring signup + admin actions must thread extra_keeper_roles: [ROLE_ADMIN] through either default_in_process_suite_options or `default_cross_process_setup(handle, {extra_keeper_roles: [ROLE_ADMIN]}). In both modes the fixture's fixture.account` is the fresh keeper, and the extra-keeper-roles list grants the bootstrapped keeper additional roles inline (cross-process via _testing_reset's bootstrap-time seeding; in-process via bootstrap_test_keeper) — no per-role RPC cost, no offer/accept round-trip. The tests run against the production open_signup: false default — the cross-process per-test secondary mint via fixture.create_account() is invite-gated (keeper drives invite_create before signup) so the harness doesn't need to flip the setting. Consumers that don't wire signup or invite_create silently skip these two tests.

options

returns

void

throws

  • Error - at setup time when `options.rpc_endpoints` is empty — the suite hard-fails via `require_rpc_endpoint_path` rather than running tests that would crash mid-suite trying to dispatch `account_verify` / `account_session_*` / `account_token_*`.

describe_standard_tests
#

testing/standard.ts view source

(options: StandardTestOptions): void import {describe_standard_tests} from '@fuzdev/fuz_app/testing/standard.js';

Run the full standard test bundle — integration, admin (when roles provided), audit completeness (when roles provided), bootstrap success (when bootstrap.mode === 'live'), round trip, RPC round trip, data exposure, rate limiting.

options

returns

void

describe_testing_backdoor_cross_tests
#

describe_token_lifetime_cross_tests
#

describe_token_scope_surface_cross_tests
#

describe_transport_composition_cross_tests
#

testing/cross_backend/transport_composition.ts view source

(options: TransportCompositionCrossTestOptions): void import {describe_transport_composition_cross_tests} from '@fuzdev/fuz_app/testing/cross_backend/transport_composition.js';

Register the front-door table's cases — one test per cell, each on a fresh fixture.

This is the front-door contract: a backend that wires it answers each transport's probes as the table says — when one request trips several refusals, the one the table names answers.

options

returns

void

describe_ws_action_rate_limit_cross_tests
#

testing/cross_backend/ws_action_rate_limit.ts view source

(options: WsActionRateLimitCrossTestOptions): void import {describe_ws_action_rate_limit_cross_tests} from '@fuzdev/fuz_app/testing/cross_backend/ws_action_rate_limit.js';

Register the action rate-limit suite: the account axis over WS, one account bucket across HTTP RPC and WS, the per-IP axis keyed by X-Forwarded-For across both transports, and the account axis at the RPC and WS dispatchers.

options

returns

void

describe_ws_connection_cap_cross_tests
#

describe_ws_inbound_dispatch_cross_tests
#

describe_ws_outbound_overflow_cross_tests
#

testing/cross_backend/ws_outbound_overflow.ts view source

(options: WsOutboundOverflowCrossTestOptions): void import {describe_ws_outbound_overflow_cross_tests} from '@fuzdev/fuz_app/testing/cross_backend/ws_outbound_overflow.js';

Register the outbound-overflow parity suite over a real upgrade: a burst within the budget arrives whole, one past it closes the socket with WS_CLOSE_OUTBOUND_OVERFLOW and no response, and the account's other connection is unaffected.

options

returns

void

describe_ws_role_gated_cross_tests
#

testing/cross_backend/ws_role_gated.ts view source

(options: WsRoleGatedCrossTestOptions): void import {describe_ws_role_gated_cross_tests} from '@fuzdev/fuz_app/testing/cross_backend/ws_role_gated.js';

Register the role-gated WebSocket suite: a revoked global grant of a gating role closes the account's gated socket with WS_CLOSE_PERMISSION_REVOKED, its ungated socket stays open and still receives the account's pushes, and a re-upgrade is refused at the gate; a scoped revoke of a gating role closes nothing.

That the closed registration is sent nothing more is beyond what a client can observe once it has read the close frame, so the suite does not claim it: each spine's own transport tests pin it.

options

returns

void

detect_format
#

testing/schema_generators.ts view source

(field_schema: ZodType<unknown, unknown, $ZodTypeInternals<unknown, unknown>>): string | null import {detect_format} from '@fuzdev/fuz_app/testing/schema_generators.js';

Detect format constraints on a field by converting to JSON Schema. Returns format string (e.g. 'uuid', 'email') or null.

field_schema

type ZodType<unknown, unknown, $ZodTypeInternals<unknown, unknown>>

returns

string | null

diff_action_manifests
#

testing/cross_backend/action_manifest_parity.ts view source

(a: { methods: { method: string; side_effects: boolean; account: "optional" | "none" | "required"; actor: "optional" | "none" | "required"; roles: string[]; credential_types: string[]; rate_limit: "both" | ... 2 more ... | null; }[]; }, b: { ...; }): ActionManifestDiff[] import {diff_action_manifests} from '@fuzdev/fuz_app/testing/cross_backend/action_manifest_parity.js';

Structural diff between two manifests — empty array means parity holds.

Order is deterministic: methods in sorted order, with each method's side-effect + auth-field sub-diffs grouped together.

a

b

returns

ActionManifestDiff[]

diff_migration_trackers
#

testing/schema_parity.ts view source

(a: { entries: { namespace: string; name: string; sequence: number; }[]; }, b: { entries: { namespace: string; name: string; sequence: number; }[]; }): MigrationTrackerDiff[] import {diff_migration_trackers} from '@fuzdev/fuz_app/testing/schema_parity.js';

Structural diff between two migration trackers — empty array means the two spines recorded byte-identical migration identity. Keyed on (namespace, name); sequence mismatches on a shared key are reported too. Deterministic order: shared/missing rows in sorted (namespace, name) order.

a

b

returns

MigrationTrackerDiff[]

diff_schema_snapshots
#

testing/schema_parity.ts view source

(a: { tables: Record<string, { columns: Record<string, { data_type: string; udt_name: string; is_nullable: boolean; column_default: string | null; is_identity: boolean; }>; indexes: { name: string; definition: string; }[]; constraints: { ...; }[]; }>; sequences: Record<...>; enums: Record<...>; }, b: { ...; }): SchemaDiff[] import {diff_schema_snapshots} from '@fuzdev/fuz_app/testing/schema_parity.js';

Structural diff between two snapshots — empty array means parity holds.

Order of diffs is deterministic: tables in sorted order (with column/index/constraint sub-diffs grouped per table), then sequences. Consumers can rely on this for stable diff output.

a

b

returns

SchemaDiff[]

dispatch_with_post_commit_rollback
#

http/pending_effects.ts view source

<T>(post_commit_effects: (() => void | Promise<void>)[] | undefined, dispatch: () => T | Promise<T>): Promise<Awaited<T>> import {dispatch_with_post_commit_rollback} from '@fuzdev/fuz_app/http/pending_effects.js';

Run a handler dispatch (the handler plus its wrapping db.transaction), discarding any post_commit_effects it queued via emit_after_commit if it throws. A thrown handler rolls back its transaction, so firing those deferred effects would announce state that never committed. On any throw the queue is truncated back to its pre-dispatch depth — not cleared, so entries a surrounding scope pre-seeded survive — and the error re-thrown unchanged.

The eager pending_effects queue is deliberately never touched here: its pool writes run outside the transaction and intentionally survive rollback (attempt audits). emit_after_commit thus reads as "run iff the wrapping transaction commits."

Both dispatch sites — the REST route wrapper (http/route_spec.ts) and the action dispatcher (actions/perform_action.ts, the RPC + WS path) — wrap their handler call in this so the discard contract lives in one place. The Rust fuz_actions spine pins the same contract.

post_commit_effects is undefined when a handler runs without the app-server pending-effects middleware (bare route / dispatch harnesses): absent ⇒ nothing queued ⇒ nothing to discard.

post_commit_effects

the deferred queue to truncate on throw, or undefined

type (() => void | Promise<void>)[] | undefined

dispatch

invokes the handler (and any wrapping transaction)

type () => T | Promise<T>

returns

Promise<Awaited<T>>

generics

dispatch_with_post_commit_rollback<T>
T

mutates

  • post_commit_effects — truncated to its pre-dispatch depth on throw

dispatch_ws_message
#

testing/ws_round_trip.ts view source

(on_message: (evt: MessageEvent<WSMessageReceive>, ws: WSContext<unknown>) => void, event: MessageEvent<any>, ws: WSContext<unknown>): Promise<...> import {dispatch_ws_message} from '@fuzdev/fuz_app/testing/ws_round_trip.js';

Hono types WSEvents.onMessage as () => void | Promise<void>. Awaits only the Promise branch so tests observe full dispatch (auth, validation, handler, send).

on_message

type (evt: MessageEvent<WSMessageReceive>, ws: WSContext<unknown>) => void

event

type MessageEvent<any>

ws

type WSContext<unknown>

returns

Promise<void>

display_db_url
#

db/status.ts view source

(url: string): string import {display_db_url} from '@fuzdev/fuz_app/db/status.js';

Render a database URL for printing from the parse node-postgres dials — new URL, with pg-connection-string's precedence, where a user, host, or port query parameter overrides the URL's own — so the line names the server dialed and no credential can leak however the input spells it.

A postgres:// or postgresql:// URL renders as postgres://[user@]host:port[/dbname]: never the password, and never the query or fragment (sslmode and the like are dropped by design). The scheme always prints as postgres://. The port always prints, 5432 when the URL names none. The user and database name print percent-encoded with encodeURIComponent, so no control character, escape sequence, or bidi override reaches the terminal and an @ can't pose as the separator; each prints only when non-empty. An IP-literal host prints canonically (IPv6 bracketed); a Unix-socket directory prints with each / as %2F. A PGlite file:// or memory:// URL prints as scheme, host, and path.

The whole line is (not a URL; hidden) for any other scheme, when new URL rejects the input, when it holds a malformed percent-escape (%ZZ), when there is no host, when the host is neither an IP literal, a hostname of A-Z a-z 0-9 . _ -, nor a socket directory of those characters and /, when the user holds ?, =, &, /, or #, or when the port isn't a number up to 65535. The host and user rules match the Rust twin's, where they catch tokio-postgres's split at the first @.

Twin of the Rust fuz_db CLI's display_db_url, byte-identical on the URLs both drivers read alike. They part where the drivers do: a host= or port= query parameter overrides the URL's here (tokio-postgres appends it to a list whose first entry the pool dials); a dbname= query parameter, which tokio-postgres honours and node-postgres ignores, and a #… fragment, which tokio-postgres keeps as part of the database name; tokio-postgres also accepts key=value strings, host lists, and an unencoded / or ? in the password (here, the placeholder); an unencoded @ in the userinfo splits at the last @ here, rendering the user and host node-postgres dials, where the Rust twin prints the placeholder; node-postgres decodes the database name with decodeURI, so a reserved-character escape such as %2F stays literal and prints as %252F; an IPv4-mapped IPv6 address serializes differently; and tokio-postgres refuses an unknown query parameter (sslpassword=) that node-postgres passes.

url

the database URL

type string

returns

string

the URL without its password, query, or fragment, or the placeholder

Divergence
#

db/status.ts view source

Divergence

type { kind: "name_mismatch"; position: number; applied: string; expected: string; } | { kind: "binary_older"; applied: number; declared: number; }

import type {Divergence} from '@fuzdev/fuz_app/db/status.js';

A divergence between the recorded migration tracker and the code's list.

Either variant is a state the migration runner refuses to boot against — a re-bootstrap (drop + migrate) is needed. Structured (not a pre-formatted string) so programmatic consumers can branch on kind; format_db_status renders the operator-facing line. The discriminated-union twin of the Rust fuz_db Divergence enum.

DualSpawnGlobalSetupOptions
#

testing/cross_backend/create_dual_spawn_global_setup.ts view source

DualSpawnGlobalSetupOptions import type {DualSpawnGlobalSetupOptions} from '@fuzdev/fuz_app/testing/cross_backend/create_dual_spawn_global_setup.js';

configs

The two backend config factories to spawn. a is spawned first; on its success b is spawned (with a torn down if b throws). Label them in the consuming test (e.g. via assert_schema_snapshots_equal's labels).

type { readonly a: () => BackendConfig; readonly b: () => BackendConfig; }

readonly

provide_keys?

project.provide keys for the two serialized handles (read by inject in the test). Default parity_handle_a / parity_handle_b (the primary parity use); override for other dual-spawn gates. Augment vitest's ProvidedContext for whichever keys you use.

type { readonly a: string; readonly b: string; }

readonly

EffectErrorContext
#

server/app_server.ts view source

EffectErrorContext import type {EffectErrorContext} from '@fuzdev/fuz_app/server/app_server.js';

Context passed to on_effect_error when a pending effect rejects.

method

HTTP method of the request that spawned the effect.

type string

path

URL path of the request that spawned the effect.

type string

Email
#

primitive_schemas.ts view source

value + type

string import {Email} from '@fuzdev/fuz_app/primitive_schemas.js';

Email validation. Every current consumer pairs it with Username (signup, invites, audit log), so the two live together with the other Zod primitives.

Deliberately permissive — a structural shape check (EMAIL_REGEX plus the EMAIL_LENGTH_MAX byte bound), not RFC 5322 conformance or deliverability (real delivery is proven by a confirmation email). Replaces Zod's stricter z.email() so the rule is one explicit regex the Rust spine's is_valid_email mirrors; z.email()'s internal regex (2+ char TLD, no consecutive dots) was brittle to keep in cross-impl parity and rejected addresses like a@b.c. The length bound is the UTF-8 byte count (RFC 5321 measures octets), so a multibyte address is bounded identically to the Rust spine's s.len() rather than diverging on JS's UTF-16 .length. No transform: case is preserved and surrounding whitespace is rejected (not trimmed), so storage is verbatim and the case-insensitive lookup rides the DB-side LOWER(email).

EMAIL_LENGTH_MAX
#

primitive_schemas.ts view source

254 import {EMAIL_LENGTH_MAX} from '@fuzdev/fuz_app/primitive_schemas.js';

Maximum email length in bytes — RFC 5321 §4.5.3.1.3 path-length limit (the limit is octets). Email bounds the UTF-8 byte length, matching the Rust spine's s.len() check; for an all-ASCII address bytes == characters.

emit_after_commit
#

http/pending_effects.ts view source

(ctx: EmitAfterCommitContext, fn: () => void | Promise<void>): void import {emit_after_commit} from '@fuzdev/fuz_app/http/pending_effects.js';

Defer a side effect until after the handler's transaction commits.

Pushes a raw thunk onto ctx.post_commit_effects — the flush middleware (in server/app_server.ts and the per-message WS dispatcher) is the only site that ever invokes fn. This is load-bearing: a previous implementation queued Promise.resolve().then(fn), which JS's microtask scheduler drains before the wrapping await db.query('COMMIT') resumes — fn fired mid-transaction and a rollback would leak a notification for state that never landed.

The thunk shape closes that gap by deferring the work to flush time. The flush owns the per-thunk try/catch + log.error so any directly-pushed thunk (tests included) cannot escape the safety net.

Deferral is only half the contract: a queued thunk is also discarded if the handler's transaction rolls back — via dispatch_with_post_commit_rollback (see its TSDoc). So fn runs iff the wrapping transaction commits, never for state that rolled back. Rollback-resilient writes (attempt audits that must land even when the handler fails) belong on the eager pending_effects queue instead.

ctx

context carrying the post_commit_effects queue

fn

side effect to run after commit; may return void or Promise<void>

type () => void | Promise<void>

returns

void

mutates

  • ctx.post_commit_effects — appends `fn` verbatim

EmitAfterCommitContext
#

http/pending_effects.ts view source

EmitAfterCommitContext import type {EmitAfterCommitContext} from '@fuzdev/fuz_app/http/pending_effects.js';

Minimal structural context required by emit_after_commit — just the deferred queue. Both RouteContext and ActionContext satisfy this. (No log here: the flush middleware owns the per-thunk try/catch + log.error, with a logger of its own.)

post_commit_effects

type (() => void | Promise<void>)[]

EmitDecorator
#

auth/audit_emitter.ts view source

EmitDecorator import type {EmitDecorator} from '@fuzdev/fuz_app/auth/audit_emitter.js';

Wrap the bound emit before it gets captured by emit_role_grant_target's closure and exposed on the returned AuditEmitter. Test instrumentation uses this to record emit invocations against external markers on a frozen emitter, whose slots can't be patched after construction.

Because the inner closure captures the decorated function (not the outer slot reference), emit_role_grant_target also routes through the wrap — the decorator sees role-grant-shape emissions, not just bare emit calls. Production never sets this.

(call)

type (inner: AuditEmitFn): AuditEmitFn

inner

returns AuditEmitFn

EnumTypeSnapshot
#

testing/schema_introspect.ts view source

value + type

{ labels: string[]; } import {EnumTypeSnapshot} from '@fuzdev/fuz_app/testing/schema_introspect.js';

Enum-type metadata — the labels of a CREATE TYPE ... AS ENUM, captured in pg_enum.enumsortorder (declaration) order. Order is significant: a Postgres enum's labels are an ordered set, and reordering them is a schema change, so the parity diff compares the arrays positionally.

labels

Enum labels in declared order.

type string[]

env_schema_to_surface
#

http/surface.ts view source

(schema: ZodObject<$ZodLooseShape, $strip>): AppSurfaceEnv[] import {env_schema_to_surface} from '@fuzdev/fuz_app/http/surface.js';

Convert env schema to surface entries using .meta() metadata.

schema

Zod object schema with .meta() on fields

type ZodObject<$ZodLooseShape, $strip>

returns

AppSurfaceEnv[]

EnvDeps
#

runtime/deps.ts view source

EnvDeps import type {EnvDeps} from '@fuzdev/fuz_app/runtime/deps.js';

Environment variable access.

env_get

Get an environment variable value.

type (name: string): string | undefined

name

type string
returns string | undefined

env_set

Set an environment variable.

type (name: string, value: string): void

name

type string

value

type string
returns void

EnvValidationError
#

env/load.ts view source

import {EnvValidationError} from '@fuzdev/fuz_app/env/load.js';

Error thrown when environment validation fails.

Contains structured information for apps to format their own error messages.

inheritance

extends: Error

raw

The raw env values that were read.

type Record<string, string | undefined>

readonly

zod_error

The Zod validation error.

type $ZodError<unknown>

readonly

all_undefined

True if every env var was undefined (nothing loaded).

type boolean

readonly

constructor

type new (raw: Record<string, string | undefined>, zod_error: $ZodError<unknown>): EnvValidationError

raw

type Record<string, string | undefined>

zod_error

type $ZodError<unknown>

format_issues

Format Zod validation issues as human-readable strings.

type (): string[]

returns string[]

array of formatted issue strings like "PORT: Expected number"

ERROR_ACCOUNT_NOT_FOUND
#

http/error_schemas.ts view source

"account_not_found" import {ERROR_ACCOUNT_NOT_FOUND} from '@fuzdev/fuz_app/http/error_schemas.js';

Token references a deleted account.

ERROR_ACCOUNT_VANISHED
#

http/error_schemas.ts view source

"account_vanished" import {ERROR_ACCOUNT_VANISHED} from '@fuzdev/fuz_app/http/error_schemas.js';

Authentication validated an account, but a follow-up read in the authorization phase came back null — the account or its named actor row was deleted between the credential check and the dispatcher's build_request_context / build_account_context step. Torn read, not a missing-actor invariant violation. Surfaced as 500 so the operator sees the race signal; clients can retry. Distinct from ERROR_ACCOUNT_NOT_FOUND (stale token referencing a long-deleted account, raised at credential validation) and ERROR_NO_ACTORS_ON_ACCOUNT (the actor list enumerated empty).

ERROR_ACTOR_NOT_ON_ACCOUNT
#

http/error_schemas.ts view source

"actor_not_on_account" import {ERROR_ACTOR_NOT_ON_ACCOUNT} from '@fuzdev/fuz_app/http/error_schemas.js';

Supplied acting field does not name an actor on the authenticated account.

ERROR_ACTOR_REQUIRED
#

http/error_schemas.ts view source

"actor_required" import {ERROR_ACTOR_REQUIRED} from '@fuzdev/fuz_app/http/error_schemas.js';

Multi-actor account requires the request to carry an explicit acting field naming the actor the request is acting as, so the dispatcher's authorization phase doesn't pick a default actor silently. Returned with the available actors so the client can prompt.

ERROR_ACTOR_SEARCH_SCOPE_REQUIRED
#

auth/actor_search_action_specs.ts view source

"actor_search_scope_required" import {ERROR_ACTOR_SEARCH_SCOPE_REQUIRED} from '@fuzdev/fuz_app/auth/actor_search_action_specs.js';

Reason: scope_ids was empty and the caller is not admin. Distinct from standard invalid_params issues so the visiones picker can surface a specific "pick a scope first" message rather than echoing Zod issues.

ERROR_ALREADY_BOOTSTRAPPED
#

http/error_schemas.ts view source

"already_bootstrapped" import {ERROR_ALREADY_BOOTSTRAPPED} from '@fuzdev/fuz_app/http/error_schemas.js';

Bootstrap lock already acquired — system already bootstrapped.

ERROR_AUTHENTICATION_REQUIRED
#

http/error_schemas.ts view source

"authentication_required" import {ERROR_AUTHENTICATION_REQUIRED} from '@fuzdev/fuz_app/http/error_schemas.js';

No valid session or bearer token.

ERROR_CANNOT_DELETE_KEEPER
#

auth/admin_action_specs.ts view source

"cannot_delete_keeper" import {ERROR_CANNOT_DELETE_KEEPER} from '@fuzdev/fuz_app/auth/admin_action_specs.js';

data.reason (403) on account_delete / account_purge when the target account holds an active keeper role_grant. The keeper account is never deletable or purgeable through the API: auth resolution and daemon-token resolution both pivot on the keeper account, so tombstoning or cascading it away would brick keeper/daemon auth with no recovery path (the keeper role is not web-revocable, and account_purge itself requires keeper auth). Keeper-account removal stays out-of-band (bootstrap / DB surgery). Mirrors the Rust ERROR_CANNOT_DELETE_KEEPER.

ERROR_CANNOT_DELETE_LAST_ADMIN
#

auth/admin_action_specs.ts view source

"cannot_delete_last_admin" import {ERROR_CANNOT_DELETE_LAST_ADMIN} from '@fuzdev/fuz_app/auth/admin_action_specs.js';

data.reason (403) on account_delete / account_purge when the target is the sole remaining active admin — removing it would leave the system with no account that can authenticate into the admin surface (and account_undelete is itself admin-gated). Unlike the keeper guard this is keeper-recoverable (a keeper can re-grant admin), but the guard avoids the foot-gun of an admin tombstoning the last admin in one call. Soft-deleted admins don't count toward the tally (they can't log in). Mirrors the Rust ERROR_CANNOT_DELETE_LAST_ADMIN.

ERROR_CELL_CREATE_FORBIDDEN
#

auth/cell_action_specs.ts view source

"cell_create_forbidden" import {ERROR_CELL_CREATE_FORBIDDEN} from '@fuzdev/fuz_app/auth/cell_action_specs.js';

Error reason — the parent-aware CellCreateAuthorize denied the create for a viewable parent (or a root creation): "you see it, you can't contribute here." A 403 forbidden, distinct from the create-path 404 mask used when the parent itself isn't viewable (twin of the Rust ERROR_CELL_CREATE_FORBIDDEN).

ERROR_CELL_FIELD_LIST_REQUIRES_SOURCE_OR_TARGET
#

auth/cell_field_action_specs.ts view source

"cell_field_list_requires_source_or_target" import {ERROR_CELL_FIELD_LIST_REQUIRES_SOURCE_OR_TARGET} from '@fuzdev/fuz_app/auth/cell_field_action_specs.js';

Error reason — cell_field_list got neither source_id nor target_id.

ERROR_CELL_GET_REQUIRES_ID_OR_PATH
#

auth/cell_action_specs.ts view source

"cell_get_requires_id_or_path" import {ERROR_CELL_GET_REQUIRES_ID_OR_PATH} from '@fuzdev/fuz_app/auth/cell_action_specs.js';

Error reason — input shape for cell_get lacked both id and path.

ERROR_CELL_GRANT_NOT_FOUND
#

auth/cell_grant_action_specs.ts view source

"cell_grant_not_found" import {ERROR_CELL_GRANT_NOT_FOUND} from '@fuzdev/fuz_app/auth/cell_grant_action_specs.js';

Error reason — grant id did not resolve, or caller may not see it.

ERROR_CELL_GRANT_PRINCIPAL_IS_OWNER
#

auth/cell_grant_action_specs.ts view source

"cell_grant_principal_is_owner" import {ERROR_CELL_GRANT_PRINCIPAL_IS_OWNER} from '@fuzdev/fuz_app/auth/cell_grant_action_specs.js';

Error reason — cell_grant_create principal resolves to the cell's owner. Owner access is implicit (cell.created_by); a self-grant row would shadow it without changing access and create a confusing self-leave path.

ERROR_CELL_GRANT_UNKNOWN_ROLE
#

auth/cell_grant_action_specs.ts view source

"cell_grant_unknown_role" import {ERROR_CELL_GRANT_UNKNOWN_ROLE} from '@fuzdev/fuz_app/auth/cell_grant_action_specs.js';

Error reason — role-shaped principal references a role string not registered in the role schema. Would produce a dead grant row that no role_grant could match.

ERROR_CELL_ITEM_LIST_REQUIRES_PARENT_OR_CHILD
#

auth/cell_item_action_specs.ts view source

"cell_item_list_requires_parent_or_child" import {ERROR_CELL_ITEM_LIST_REQUIRES_PARENT_OR_CHILD} from '@fuzdev/fuz_app/auth/cell_item_action_specs.js';

Error reason — cell_item_list got neither parent_id nor child_id.

ERROR_CELL_ITEM_POSITION_TAKEN
#

auth/cell_item_action_specs.ts view source

"cell_item_position_taken" import {ERROR_CELL_ITEM_POSITION_TAKEN} from '@fuzdev/fuz_app/auth/cell_item_action_specs.js';

Error reason — (parent_id, position) collision on cell_item_insert or cell_item_move. Surfaces when two clients computed the same fractional-indexing key (rare given helper-side jitter, which is on by default; the safety net for the residual race). A client that opts out with {jitter: false} gets bare deterministic mids, so two of them racing the same bracket collide every time and this stops being rare — that mode is for reproducible callers (seeders, fixtures), not concurrent ones. Client refreshes its bracket and retries.

ERROR_CELL_KIND_EMPTY
#

auth/cell_action_specs.ts view source

"cell_kind_empty" import {ERROR_CELL_KIND_EMPTY} from '@fuzdev/fuz_app/auth/cell_action_specs.js';

Error reason — cell_create was given an empty-string kind. kind is a non-empty capability tag or absent (null = typeless cell); "" (a tag that tags nothing) is rejected fail-loud so kind stays a clean null | non-empty-string (twin of the Rust ERROR_CELL_KIND_EMPTY).

ERROR_CELL_KIND_IN_DATA
#

auth/cell_action_specs.ts view source

"cell_kind_in_data" import {ERROR_CELL_KIND_IN_DATA} from '@fuzdev/fuz_app/auth/cell_action_specs.js';

Error reason — a kind key was supplied inside data. kind is a top-level column (cell.kind), not content metadata; accepting it inside data would create two sources of truth. Rejected fail-loud at the create / update / clone-patch boundary so the column stays canonical.

ERROR_CELL_LIST_CREATED_BY_REQUIRES_AUTH
#

auth/cell_action_specs.ts view source

"cell_list_created_by_requires_auth" import {ERROR_CELL_LIST_CREATED_BY_REQUIRES_AUTH} from '@fuzdev/fuz_app/auth/cell_action_specs.js';

Error reason — null-auth cell_list caller passed a created_by filter. The filter is a soft account-id enumeration vector ("does account X have any public cells?"), so we require an authenticated caller to use it.

ERROR_CELL_LIST_SHARED_WITH_REQUIRES_AUTH
#

auth/cell_action_specs.ts view source

"cell_list_shared_with_requires_auth" import {ERROR_CELL_LIST_SHARED_WITH_REQUIRES_AUTH} from '@fuzdev/fuz_app/auth/cell_action_specs.js';

Error reason — null-auth cell_list caller passed shared_with: 'me'. The filter resolves to the caller's account + role_grants, which only exist for an authenticated session.

ERROR_CELL_MODERATE_FORBIDDEN
#

auth/cell_action_specs.ts view source

"cell_moderate_forbidden" import {ERROR_CELL_MODERATE_FORBIDDEN} from '@fuzdev/fuz_app/auth/cell_action_specs.js';

Error reason — cell_moderate caller can view the contribution but lacks moderation authority over its governing root (not admin / not the root's manager). A 403 forbidden. The author can view their own pending contribution, so they reach this gate — and are denied here, which is the anti-self-approval guard (twin of the Rust ERROR_CELL_MODERATE_FORBIDDEN).

ERROR_CELL_NOT_A_CONTRIBUTION
#

auth/cell_action_specs.ts view source

"cell_not_a_contribution" import {ERROR_CELL_NOT_A_CONTRIBUTION} from '@fuzdev/fuz_app/auth/cell_action_specs.js';

Error reason — cell_moderate target has no governing root (root_id is null): it's a root or an unparented cell, not a gated contribution, so there is nothing to moderate. A 400 invalid_params (twin of the Rust ERROR_CELL_NOT_A_CONTRIBUTION).

ERROR_CELL_NOT_FOUND
#

auth/cell_action_specs.ts view source

"cell_not_found" import {ERROR_CELL_NOT_FOUND} from '@fuzdev/fuz_app/auth/cell_action_specs.js';

Error reason — cell id did not resolve, or caller can't view it.

ERROR_CELL_PATH_ADMIN_ONLY
#

auth/cell_action_specs.ts view source

"cell_path_admin_only" import {ERROR_CELL_PATH_ADMIN_ONLY} from '@fuzdev/fuz_app/auth/cell_action_specs.js';

Error reason — caller is not an admin and supplied a path write.

ERROR_CELL_PATH_TAKEN
#

auth/cell_action_specs.ts view source

"cell_path_taken" import {ERROR_CELL_PATH_TAKEN} from '@fuzdev/fuz_app/auth/cell_action_specs.js';

Error reason — a path write collided with an existing active cell's path. path is globally unique on active rows (idx_cell_path_unique); the create / update handlers translate the unique-index violation into this conflict (409) reason rather than leaking a raw internal error. Soft-deleted rows free their path (the index is partial on deleted_at IS NULL), so reusing a deleted cell's path does not collide.

ERROR_CELL_VISIBILITY_MANAGE_ONLY
#

auth/cell_action_specs.ts view source

"cell_visibility_manage_only" import {ERROR_CELL_VISIBILITY_MANAGE_ONLY} from '@fuzdev/fuz_app/auth/cell_action_specs.js';

Error reason — caller tried to write cell.visibility without the manage tier (can_manage_cell = admin / owner). Editor-grant holders may edit data but cannot flip a cell's visibility — that is a manage-tier-only operation.

ERROR_CREDENTIAL_TYPE_REQUIRED
#

http/error_schemas.ts view source

"credential_type_required" import {ERROR_CREDENTIAL_TYPE_REQUIRED} from '@fuzdev/fuz_app/http/error_schemas.js';

Route requires a credential type the request didn't arrive on. Symmetric with ERROR_INSUFFICIENT_PERMISSIONS + required_roles: the body carries required_credential_types: ReadonlyArray<string> — what the route demanded, not what arrived. Today the only credential gate is keeper (['daemon_token']); future gates (agent_token, group_actor_token) reuse the same literal and label themselves through the array.

ERROR_DATABASE_CONNECTION_FAILED
#

http/error_schemas.ts view source

"database_connection_failed" import {ERROR_DATABASE_CONNECTION_FAILED} from '@fuzdev/fuz_app/http/error_schemas.js';

Database health-check query failed (connectivity or query error).

ERROR_DUPLICATE_REQUEST_ID
#

actions/register_action_ws.ts view source

"duplicate_request_id" import {ERROR_DUPLICATE_REQUEST_ID} from '@fuzdev/fuz_app/actions/register_action_ws.js';

The data.reason of the invalid_request a WebSocket request gets when its id names a request still running on the same socket. The duplicate is not dispatched; the running request keeps its id, so its reply, and a cancel naming that id, stay its own. An id is free again once its request has been answered.

ERROR_FORBIDDEN_ORIGIN
#

http/error_schemas.ts view source

"forbidden_origin" import {ERROR_FORBIDDEN_ORIGIN} from '@fuzdev/fuz_app/http/error_schemas.js';

Request origin not in allowlist.

ERROR_FOREIGN_KEY_VIOLATION
#

http/error_schemas.ts view source

"foreign_key_violation" import {ERROR_FOREIGN_KEY_VIOLATION} from '@fuzdev/fuz_app/http/error_schemas.js';

DELETE blocked by a foreign key constraint.

ERROR_INSUFFICIENT_PERMISSIONS
#

http/error_schemas.ts view source

"insufficient_permissions" import {ERROR_INSUFFICIENT_PERMISSIONS} from '@fuzdev/fuz_app/http/error_schemas.js';

Authenticated but missing required role.

ERROR_INVALID_CONTENT_LENGTH
#

http/error_schemas.ts view source

"invalid_content_length" import {ERROR_INVALID_CONTENT_LENGTH} from '@fuzdev/fuz_app/http/error_schemas.js';

Request Content-Length header is not a plain run of ASCII digits.

ERROR_INVALID_CREDENTIALS
#

http/error_schemas.ts view source

"invalid_credentials" import {ERROR_INVALID_CREDENTIALS} from '@fuzdev/fuz_app/http/error_schemas.js';

Username or password is wrong (intentionally vague for enumeration prevention).

ERROR_INVALID_EVENT_TYPE
#

http/error_schemas.ts view source

"invalid_event_type" import {ERROR_INVALID_EVENT_TYPE} from '@fuzdev/fuz_app/http/error_schemas.js';

Query parameter event_type is not a valid audit event type.

ERROR_INVALID_JSON_BODY
#

http/error_schemas.ts view source

"invalid_json_body" import {ERROR_INVALID_JSON_BODY} from '@fuzdev/fuz_app/http/error_schemas.js';

Request body is not valid JSON or not an object.

ERROR_INVALID_QUERY_PARAMS
#

http/error_schemas.ts view source

"invalid_query_params" import {ERROR_INVALID_QUERY_PARAMS} from '@fuzdev/fuz_app/http/error_schemas.js';

URL query params failed Zod validation.

ERROR_INVALID_REQUEST_BODY
#

http/error_schemas.ts view source

"invalid_request_body" import {ERROR_INVALID_REQUEST_BODY} from '@fuzdev/fuz_app/http/error_schemas.js';

Request body failed Zod validation.

ERROR_INVALID_ROUTE_PARAMS
#

http/error_schemas.ts view source

"invalid_route_params" import {ERROR_INVALID_ROUTE_PARAMS} from '@fuzdev/fuz_app/http/error_schemas.js';

URL path params failed Zod validation.

ERROR_INVALID_TOKEN
#

http/error_schemas.ts view source

"invalid_token" import {ERROR_INVALID_TOKEN} from '@fuzdev/fuz_app/http/error_schemas.js';

Bearer token failed validation (missing, malformed, or revoked).

ERROR_INVITE_ACCOUNT_EXISTS_EMAIL
#

http/error_schemas.ts view source

"invite_account_exists_email" import {ERROR_INVITE_ACCOUNT_EXISTS_EMAIL} from '@fuzdev/fuz_app/http/error_schemas.js';

An account already exists with this invite's email.

ERROR_INVITE_ACCOUNT_EXISTS_USERNAME
#

http/error_schemas.ts view source

"invite_account_exists_username" import {ERROR_INVITE_ACCOUNT_EXISTS_USERNAME} from '@fuzdev/fuz_app/http/error_schemas.js';

An account already exists with this invite's username.

ERROR_INVITE_DUPLICATE
#

http/error_schemas.ts view source

"invite_duplicate" import {ERROR_INVITE_DUPLICATE} from '@fuzdev/fuz_app/http/error_schemas.js';

An unclaimed invite already exists for this email or username.

ERROR_INVITE_NOT_FOUND
#

http/error_schemas.ts view source

"invite_not_found" import {ERROR_INVITE_NOT_FOUND} from '@fuzdev/fuz_app/http/error_schemas.js';

Invite not found (for delete operations).

ERROR_KEEPER_ACCOUNT_NOT_FOUND
#

http/error_schemas.ts view source

"keeper_account_not_found" import {ERROR_KEEPER_ACCOUNT_NOT_FOUND} from '@fuzdev/fuz_app/http/error_schemas.js';

Keeper account ID set but account row not found.

ERROR_NO_ACTORS_ON_ACCOUNT
#

http/error_schemas.ts view source

"no_actors_on_account" import {ERROR_NO_ACTORS_ON_ACCOUNT} from '@fuzdev/fuz_app/http/error_schemas.js';

Authenticated account exists but has no actors. Server invariant violation — signup / bootstrap always create an actor in the same transaction. Surfaced from the dispatcher's authorization phase as a 500 so the operator sees the corruption signal rather than a confusing 4xx. Distinct from ERROR_ACCOUNT_VANISHED: the actor list was enumerated successfully and came back empty.

ERROR_NO_MATCHING_INVITE
#

http/error_schemas.ts view source

"no_matching_invite" import {ERROR_NO_MATCHING_INVITE} from '@fuzdev/fuz_app/http/error_schemas.js';

No unclaimed invite matches the signup credentials.

ERROR_PAYLOAD_TOO_LARGE
#

http/error_schemas.ts view source

"payload_too_large" import {ERROR_PAYLOAD_TOO_LARGE} from '@fuzdev/fuz_app/http/error_schemas.js';

Request body exceeds the maximum allowed size.

ERROR_PURGE_NOT_CONFIRMED
#

auth/admin_action_specs.ts view source

"purge_not_confirmed" import {ERROR_PURGE_NOT_CONFIRMED} from '@fuzdev/fuz_app/auth/admin_action_specs.js';

data.reason on account_purge when confirm: true is absent. Fail-loud: the irreversible purge refuses to run without explicit confirmation. Mirrors the Rust ERROR_PURGE_NOT_CONFIRMED.

ERROR_RATE_LIMIT_EXCEEDED
#

http/error_schemas.ts view source

"rate_limit_exceeded" import {ERROR_RATE_LIMIT_EXCEEDED} from '@fuzdev/fuz_app/http/error_schemas.js';

Rate limiter rejected the request.

error_reason
#

ERROR_RESPONSE_OUTPUT_INVALID
#

actions/action_event.ts view source

"response_output_invalid" import {ERROR_RESPONSE_OUTPUT_INVALID} from '@fuzdev/fuz_app/actions/action_event.js';

error.data.reason on the internal_error a client reports when a response's result fails spec.output — a remote bug, distinct from the invalid_params / validation_error a caller's own bad input produces.

ERROR_ROLE_GRANT_BUILTIN_SCOPED
#

auth/role_grant_offer_action_specs.ts view source

"role_grant_builtin_scoped" import {ERROR_ROLE_GRANT_BUILTIN_SCOPED} from '@fuzdev/fuz_app/auth/role_grant_offer_action_specs.js';

Error reason — a grant path was asked to bind a builtin role to a scope (builtin roles are global-only, see is_builtin_role). Refused by role_grant_assign, role_grant_offer_create, and role_grant_offer_accept.

ERROR_ROLE_GRANT_NOT_FOUND
#

http/error_schemas.ts view source

"role_grant_not_found" import {ERROR_ROLE_GRANT_NOT_FOUND} from '@fuzdev/fuz_app/http/error_schemas.js';

Role grant ID not found or not owned by the target actor.

ERROR_ROLE_GRANT_OFFER_ACTOR_ACCOUNT_MISMATCH
#

auth/role_grant_offer_action_specs.ts view source

"role_grant_offer_actor_account_mismatch" import {ERROR_ROLE_GRANT_OFFER_ACTOR_ACCOUNT_MISMATCH} from '@fuzdev/fuz_app/auth/role_grant_offer_action_specs.js';

Error reason — role_grant_offer_create was called with a to_actor_id that does not belong to to_account_id.

ERROR_ROLE_GRANT_OFFER_ACTOR_MISMATCH
#

auth/role_grant_offer_action_specs.ts view source

"role_grant_offer_actor_mismatch" import {ERROR_ROLE_GRANT_OFFER_ACTOR_MISMATCH} from '@fuzdev/fuz_app/auth/role_grant_offer_action_specs.js';

Error reason — actor-targeted offer was accepted by an actor other than to_actor_id.

ERROR_ROLE_GRANT_OFFER_EXPIRED
#

ERROR_ROLE_GRANT_OFFER_NOT_AUTHORIZED
#

auth/role_grant_offer_action_specs.ts view source

"role_grant_offer_not_authorized" import {ERROR_ROLE_GRANT_OFFER_NOT_AUTHORIZED} from '@fuzdev/fuz_app/auth/role_grant_offer_action_specs.js';

Error reason — caller is not authorized to offer this role (default policy: caller lacks the role; consumer authorize callback may add further policy).

ERROR_ROLE_GRANT_OFFER_NOT_FOUND
#

auth/role_grant_offer_action_specs.ts view source

"role_grant_offer_not_found" import {ERROR_ROLE_GRANT_OFFER_NOT_FOUND} from '@fuzdev/fuz_app/auth/role_grant_offer_action_specs.js';

Error reason — offer does not exist or belongs to a different recipient (404-over-403 IDOR mask).

ERROR_ROLE_GRANT_OFFER_ROLE_NOT_GRANTABLE
#

auth/role_grant_offer_action_specs.ts view source

"role_grant_offer_role_not_grantable" import {ERROR_ROLE_GRANT_OFFER_ROLE_NOT_GRANTABLE} from '@fuzdev/fuz_app/auth/role_grant_offer_action_specs.js';

Error reason — the offered role does not include 'admin' in its RoleSpec.grant_paths (nobody may offer it via this surface).

ERROR_ROLE_GRANT_OFFER_SELF_TARGET
#

auth/role_grant_offer_action_specs.ts view source

"role_grant_offer_self_target" import {ERROR_ROLE_GRANT_OFFER_SELF_TARGET} from '@fuzdev/fuz_app/auth/role_grant_offer_action_specs.js';

Error reason — caller tried to offer themselves a role_grant.

ERROR_ROLE_GRANT_OFFER_TERMINAL
#

auth/role_grant_offer_action_specs.ts view source

"role_grant_offer_terminal" import {ERROR_ROLE_GRANT_OFFER_TERMINAL} from '@fuzdev/fuz_app/auth/role_grant_offer_action_specs.js';

Error reason — offer is declined, retracted, or superseded.

ERROR_ROLE_NOT_SELF_SERVICE_ELIGIBLE
#

auth/self_service_role_action_specs.ts view source

"role_not_self_service_eligible" import {ERROR_ROLE_NOT_SELF_SERVICE_ELIGIBLE} from '@fuzdev/fuz_app/auth/self_service_role_action_specs.js';

Error reason — caller asked to self-toggle a role outside the configured allowlist.

ERROR_ROLE_NOT_WEB_GRANTABLE
#

http/error_schemas.ts view source

"role_not_web_grantable" import {ERROR_ROLE_NOT_WEB_GRANTABLE} from '@fuzdev/fuz_app/http/error_schemas.js';

Admin tried to grant a role that is not web-grantable.

ERROR_ROW_NOT_FOUND
#

http/error_schemas.ts view source

"row_not_found" import {ERROR_ROW_NOT_FOUND} from '@fuzdev/fuz_app/http/error_schemas.js';

Row with the given PK value not found.

ERROR_SIGNUP_CONFLICT
#

http/error_schemas.ts view source

"signup_conflict" import {ERROR_SIGNUP_CONFLICT} from '@fuzdev/fuz_app/http/error_schemas.js';

Signup conflict — username or email already taken (intentionally vague for enumeration prevention).

ERROR_TABLE_NO_PRIMARY_KEY
#

http/error_schemas.ts view source

"table_no_primary_key" import {ERROR_TABLE_NO_PRIMARY_KEY} from '@fuzdev/fuz_app/http/error_schemas.js';

Table has no single-column primary key, so there is no column the row-DELETE can target: either no primary key at all, or a composite one (a single-column WHERE on one member of a composite key over-matches).

ERROR_TABLE_NOT_DELETABLE
#

http/error_schemas.ts view source

"table_not_deletable" import {ERROR_TABLE_NOT_DELETABLE} from '@fuzdev/fuz_app/http/error_schemas.js';

Table is excluded from row deletion by policy — the audit trail and the framework's singleton bookkeeping rows (bootstrap_lock, app_settings, schema_version), whose invariants live in the domain layer rather than in a generic storage endpoint. See http/db_routes.ts's NON_DELETABLE_TABLES.

ERROR_TABLE_NOT_FOUND
#

http/error_schemas.ts view source

"table_not_found" import {ERROR_TABLE_NOT_FOUND} from '@fuzdev/fuz_app/http/error_schemas.js';

Table name not found in information_schema.

ERROR_TESTING_FLOOD_NO_CONNECTION
#

ERROR_TOKEN_FILE_MISSING
#

http/error_schemas.ts view source

"token_file_missing" import {ERROR_TOKEN_FILE_MISSING} from '@fuzdev/fuz_app/http/error_schemas.js';

Bootstrap token file not found on disk.

ERROR_TOKEN_SCOPE_REQUIRED
#

http/error_schemas.ts view source

"token_scope_required" import {ERROR_TOKEN_SCOPE_REQUIRED} from '@fuzdev/fuz_app/http/error_schemas.js';

The credential is admitted on this channel, but the specific token's scope does not grant the requested capability.

The body carries required_scope: string in the stable <section>:<id> capability format — rpc:<method> for an action, surface:<name> for a non-RPC surface. Distinct from credential_type_required (which is about the *channel*) and from insufficient_permissions (about the *account's* roles): this one says the account could do it but this token was minted narrower.

ERROR_UNSUPPORTED_CONTENT_TYPE
#

http/error_schemas.ts view source

"unsupported_content_type" import {ERROR_UNSUPPORTED_CONTENT_TYPE} from '@fuzdev/fuz_app/http/error_schemas.js';

A POST whose Content-Type the route refuses: anything but application/json on a JSON route, a CORS-safelisted type or none on a raw_body one. See http/simple_request.ts.

ErrorCoverageCollector
#

testing/error_coverage.ts view source

import {ErrorCoverageCollector} from '@fuzdev/fuz_app/testing/error_coverage.js';

Tracks which route × status (and route × status × code) combinations have been exercised in tests.

Use record() to log an observed status (optionally with the body's error code), or assert_and_record() to combine response validation with tracking (auto-extracts body.error from the response when present). After all tests, call uncovered() to find declared error paths never exercised.

An observation recorded without a code still satisfies "any-code" coverage requirements for the same status — i.e., if a caller records just the status, all declared codes for that status are considered covered. Per-code tracking is additive: callers who know the body's error value should pass it to get precise per-code gap reporting on routes with literal/enum error schemas.

observed

Observed keys: "METHOD /spec-path:STATUS" or "METHOD /spec-path:STATUS:CODE".

Both shapes coexist — the code-less key marks the status as covered at any code; a code-bearing key adds per-code precision.

type Set<string>

readonly

record

Record an observed error status (optionally with the body's error code) for a route.

Resolves the concrete request path back to the spec template path (e.g., /api/accounts/abc → /api/accounts/:id). When code is provided, it is stored alongside the status for per-code coverage tracking.

type (route_specs: RouteSpec[], method: string, path: string, status: number, code?: string | undefined): void

route_specs

type RouteSpec[]

method

type string

path

request path (may be concrete)

type string

status

type number

code?

observed body error code (pass when the route's error schema declares specific codes via z.literal or z.enum)

type string
optional
returns void

mutates

  • this.observed — adds the resolved `"METHOD /spec-path:STATUS"` key (and the `:CODE` variant when `code` is provided).

assert_and_record

Validate a response against its route spec and record the status.

Wraps assert_response_matches_spec and records the status code. For error responses, auto-extracts body.error from the JSON body (via a cloned response, so the original stream stays usable) and records it for per-code coverage. Pass an explicit code to override the auto-extracted value or when the body was already consumed.

type (route_specs: RouteSpec[], method: string, path: string, response: Response, code?: string | undefined): Promise<void>

route_specs

type RouteSpec[]

method

type string

path

type string

response

type Response

code?

observed body error code (override; if omitted and the response body is a JSON object with a string error field, that value is auto-extracted)

type string
optional
returns Promise<void>

throws

  • Error - if the response body fails the route spec's declared schemas (propagated from `assert_response_matches_spec`).

mutates

  • this.observed — via `record` after `assert_response_matches_spec` succeeds.

uncovered

Find declared error paths that were never observed.

Computes the declared set from merge_error_schemas for each route spec. For statuses whose error schema names specific codes (via z.literal or z.enum), reports per-code rows; otherwise reports one row per status. A status-only observation (no code) satisfies all declared codes for that status — the "any-code" rule.

type (route_specs: RouteSpec[], options?: CoverageFilterOptions | undefined): UncoveredEntry[]

route_specs

type RouteSpec[]

options?

optional
returns UncoveredEntry[]

ErrorCoverageOptions
#

ErrorSchemaAuditEntry
#

testing/surface_invariants.ts view source

ErrorSchemaAuditEntry import type {ErrorSchemaAuditEntry} from '@fuzdev/fuz_app/testing/surface_invariants.js';

A single entry in the error schema tightness audit report.

method

type string

route_path

type string

status

type string

specificity

type ErrorSchemaSpecificity

error_codes

The literal value or enum values, if specific.

type string[] | null

ErrorSchemaSpecificity
#

testing/surface_invariants.ts view source

ErrorSchemaSpecificity

type "literal" | "enum" | "generic"

import type {ErrorSchemaSpecificity} from '@fuzdev/fuz_app/testing/surface_invariants.js';

Specificity level of an error schema's error field.

ErrorSchemaTightnessOptions
#

testing/surface_invariants.ts view source

ErrorSchemaTightnessOptions import type {ErrorSchemaTightnessOptions} from '@fuzdev/fuz_app/testing/surface_invariants.js';

min_specificity?

Minimum specificity level. Error schemas below this threshold fail. Default: 'enum'.

type ErrorSchemaSpecificity

ignore_statuses?

HTTP status codes to skip (e.g., middleware-injected codes).

type number[]

allowlist?

Routes to skip, in 'METHOD /path' format.

type string[]

events_to_surface
#

EventSpec
#

realtime/sse.ts view source

EventSpec import type {EventSpec} from '@fuzdev/fuz_app/realtime/sse.js';

Spec for a push event — declares params schema, description, and channel.

method

Event method name, used as the JSON-RPC notification method.

type string

params

Zod schema for the notification params payload.

type ZodType<unknown, unknown, $ZodTypeInternals<unknown, unknown>>

description

Human-readable description for surface output and docs.

type string

channel?

Channel this event broadcasts on. Omit for cross-channel events.

type string

expect_output
#

testing/cross_backend/cell_cross_helpers.ts view source

<T>(r: RpcResult, schema: ZodType<T, unknown, $ZodTypeInternals<T, unknown>>): T import {expect_output} from '@fuzdev/fuz_app/testing/cross_backend/cell_cross_helpers.js';

Assert the call succeeded and the result matches the verb's declared output schema — the wire-shape parity gate. Returns the parsed output.

r

schema

type ZodType<T, unknown, $ZodTypeInternals<T, unknown>>

returns

T

generics

expect_output<T>
T

ExpectedSchema
#

db/schema_ready.ts view source

ExpectedSchema import type {ExpectedSchema} from '@fuzdev/fuz_app/db/schema_ready.js';

Expected schema: table name → sorted column names, from a fresh bootstrap.

[key: string]

type readonly string[]

EXPIRED_SESSION_OFFSET_SECONDS
#

testing/cross_backend/setup.ts view source

-60 import {EXPIRED_SESSION_OFFSET_SECONDS} from '@fuzdev/fuz_app/testing/cross_backend/setup.js';

Backdating offset (seconds) the mint_expired_session seam passes to mint_test_session / _testing_mint_session. A minute in the past is comfortably past NOW() for the DB-row expiry gate without depending on clock precision.

EXPIRES_IN_MS_SQL
#

auth/credential_deadline.ts view source

"(EXTRACT(EPOCH FROM (expires_at - NOW())) * 1000)::float8" import {EXPIRES_IN_MS_SQL} from '@fuzdev/fuz_app/auth/credential_deadline.js';

SQL expression for a row's remaining lifetime in milliseconds, measured on the database clock — NULL when expires_at is NULL (an eternal API token). float8, so every driver reads it as a JS number.

ExtraAccountFixture
#

testing/cross_backend/setup.ts view source

ExtraAccountFixture import type {ExtraAccountFixture} from '@fuzdev/fuz_app/testing/cross_backend/setup.js';

Bootstrap-time-seeded secondary account exposed on the fixture.

account

type { readonly id: string & $brand<"Uuid">; readonly username: string; }

readonly

actor

type { readonly id: string & $brand<"Uuid">; }

readonly

api_token

type string

readonly

session_cookie

type string

readonly

create_session_headers

type (extra?: Record<string, string> | undefined): Record<string, string>

readonly

extra?

type Record<string, string>
optional
returns Record<string, string>

create_bearer_headers

type (extra?: Record<string, string> | undefined): Record<string, string>

readonly

extra?

type Record<string, string>
optional
returns Record<string, string>

ExtraAccountSpec
#

testing/cross_backend/setup.ts view source

ExtraAccountSpec import type {ExtraAccountSpec} from '@fuzdev/fuz_app/testing/cross_backend/setup.js';

Spec for a bootstrap-time secondary account seeded alongside the keeper by _testing_reset (cross-process) or create_test_app (in-process). Used for accounts whose required roles aren't admin-grantable via offer/accept — primarily ROLE_KEEPER (whose RoleSpec.grant_paths is bootstrap-only), where the only way to land the grant is at the bootstrap-equivalent setup step.

For admin-grantable roles, prefer fixture.create_account({roles}) — that goes through the production offer/accept handlers and observes audit + WS fan-out. extra_accounts is the cradle-only bypass; the runtime has no equivalent action.

username

type string

readonly

password_value?

type string

readonly

roles

type readonly string[]

readonly

extract_action_result
#

actions/action_event_helpers.ts view source

(event: ActionEvent<string, "send_request" | "receive_request" | "send_response" | "receive_response" | "send_error" | "receive_error" | "send" | "receive" | "execute", "initial" | "parsed" | "handling" | "handled" | "failed">): Result<...> import {extract_action_result} from '@fuzdev/fuz_app/actions/action_event_helpers.js';

Pull the terminal Result from an action event.

data.error populated → error path (covers both explicit failed and the unhandled receive_error / send_error case where no handler was registered for the error phase). step === 'handled' → success path.

event

type ActionEvent<string, "send_request" | "receive_request" | "send_response" | "receive_response" | "send_error" | "receive_error" | "send" | "receive" | "execute", "initial" | "parsed" | "handling" | "handled" | "failed">

returns

Result

throws

  • Error - if the event is in a non-terminal state (programming error — callers should check `is_action_complete` first)

extract_declared_error_codes
#

testing/error_coverage.ts view source

(schema: ZodType<unknown, unknown, $ZodTypeInternals<unknown, unknown>>): string[] | null import {extract_declared_error_codes} from '@fuzdev/fuz_app/testing/error_coverage.js';

Extract declared error code values from an error response schema.

Recognizes schemas shaped like z.object({error: z.literal(...)}) or z.object({error: z.enum([...])}) (incl. looseObject/strictObject). Returns the set of declared code values, or null if the schema doesn't expose a literal/enum error field (e.g., bare ApiError with z.string()).

Used by coverage reporting to split a single declared status into per-code rows when the route's error schema names specific codes.

schema

type ZodType<unknown, unknown, $ZodTypeInternals<unknown, unknown>>

returns

string[] | null

FACT_COLUMNS
#

fact_disk_path
#

db/file_fact_url.ts view source

(hash: FactHash): { shard: string; rest: string; } import {fact_disk_path} from '@fuzdev/fuz_app/db/file_fact_url.js';

Split a FactHash into its on-disk <shard>/<rest> parts — the first 2 hex chars of the digest (shard subdir) + the remaining 62. The single source of truth for the disk layout, so the write path (put / put_stream) and the URL minted into the fact row can't disagree. Mirrors the Rust fact_disk_path in fuz_fact.

hash

type FactHash

returns

{ shard: string; rest: string; }

FACT_DROP_TABLES
#

FACT_EMBEDDED_THRESHOLD_DEFAULT
#

db/fact_store.ts view source

number import {FACT_EMBEDDED_THRESHOLD_DEFAULT} from '@fuzdev/fuz_app/db/fact_store.js';

Default embedded-vs-referenced cutoff (1 MiB).

FACT_MIGRATION_NAMESPACE
#

db/fact_ddl.ts view source

"fuz_facts" import {FACT_MIGRATION_NAMESPACE} from '@fuzdev/fuz_app/db/fact_ddl.js';

Namespace identifier for fact + memo migrations.

FACT_MIGRATION_NS
#

FACT_MIGRATIONS
#

FACT_REFS_SCHEMA
#

db/fact_ddl.ts view source

"\nCREATE TABLE IF NOT EXISTS fact_ref (\n\tsource_hash TEXT NOT NULL REFERENCES fact(hash) ON DELETE CASCADE,\n\ttarget_hash TEXT NOT NULL,\n\tPRIMARY KEY (source_hash, target_hash)\n)" import {FACT_REFS_SCHEMA} from '@fuzdev/fuz_app/db/fact_ddl.js';

fact_ref table — declared dependency edges between facts.

target_hash is not a foreign key (federation: target may live remotely).

FACT_REFS_TARGET_INDEX
#

db/fact_ddl.ts view source

"\nCREATE INDEX IF NOT EXISTS idx_fact_ref_target ON fact_ref(target_hash)" import {FACT_REFS_TARGET_INDEX} from '@fuzdev/fuz_app/db/fact_ddl.js';

Reverse lookup: which facts reference a given target?

FACT_TMP_DIRNAME
#

db/fact_disk_storage.ts view source

".tmp" import {FACT_TMP_DIRNAME} from '@fuzdev/fuz_app/db/fact_disk_storage.js';

Subdirectory under facts_dir for in-flight atomic temp files.

FACT_TMP_ORPHAN_MAX_AGE_MS
#

db/fact_disk_storage.ts view source

number import {FACT_TMP_ORPHAN_MAX_AGE_MS} from '@fuzdev/fuz_app/db/fact_disk_storage.js';

Default age (1 hour) past which a .tmp/* file is considered orphaned.

FactDiskStorageDeps
#

db/fact_disk_storage.ts view source

FactDiskStorageDeps

type Pick<FsReadDeps, "stat" | "readdir" | "read_file"> & Pick<FsWriteDeps, "mkdir" | "rename" | "write_file" | "fsync"> & Pick<FsStreamDeps, "write_file_stream"> & Pick<FsRemoveDeps, "remove">

import type {FactDiskStorageDeps} from '@fuzdev/fuz_app/db/fact_disk_storage.js';

Filesystem capabilities the disk CAS needs, drawn from runtime/deps.ts. A full RuntimeDeps (Node or Deno) satisfies this; each function below picks the narrow subset it actually uses.

stat

Get file/directory stats, or null if path doesn't exist.

type (path: string): Promise<StatResult | null>

path

type string
returns Promise<StatResult | null>

readdir

List directory entries (names, not full paths). Throws if the directory does not exist.

type (path: string): Promise<string[]>

path

type string
returns Promise<string[]>

read_file

Read a file as bytes. Throws if the file does not exist.

type (path: string): Promise<Uint8Array<ArrayBufferLike>>

path

type string
returns Promise<Uint8Array<ArrayBufferLike>>

mkdir

Create a directory. mode applies at creation (no-op where modes don't apply).

type (path: string, options?: { recursive?: boolean | undefined; mode?: number | undefined; } | undefined): Promise<void>

path

type string

options?

type { recursive?: boolean | undefined; mode?: number | undefined; }
optional
returns Promise<void>

rename

Rename (move) a file.

type (old_path: string, new_path: string): Promise<void>

old_path

type string

new_path

type string
returns Promise<void>

write_file

Write bytes to a file.

type (path: string, data: Uint8Array<ArrayBufferLike>): Promise<void>

path

type string

data

type Uint8Array<ArrayBufferLike>
returns Promise<void>

fsync

Flush a file's data to stable storage (fsync). Call on a temp file after writing it and *before* rename-ing it into place when the renamed path is later served without re-verification — otherwise a host crash after the rename can surface a torn/zero file as authentic content. The fact disk CAS (db/fact_disk_storage.ts) is the one such path; it twins the Rust fuz_fact §fsync posture (data-sync before rename; the parent-dir fsync stays deliberately waived — a lost dirent is regenerable under content addressing). Real runtimes open the path, fsync, and close; create_mock_runtime no-ops (it models no durability).

type (path: string): Promise<void>

path

type string
returns Promise<void>

write_file_stream

Write a ReadableStream of bytes to a file, consuming it with backpressure (peak memory is one chunk). Creates or truncates path. Throws on any I/O error; a partially-written file may remain, so callers needing atomicity write to a temp path then rename.

type (path: string, data: ReadableStream<Uint8Array<ArrayBufferLike>>): Promise<void>

path

type string

data

type ReadableStream<Uint8Array<ArrayBufferLike>>
returns Promise<void>

remove

Remove a file or directory.

type (path: string, options?: { recursive?: boolean | undefined; } | undefined): Promise<void>

path

type string

options?

type { recursive?: boolean | undefined; }
optional
returns Promise<void>

FactHashSchema
#

primitive_schemas.ts view source

ZodString & ZodType<FactHash, string, $ZodTypeInternals<FactHash, string>>

type ZodString & ZodType<FactHash, string, $ZodTypeInternals<FactHash, string>>

import {FactHashSchema} from '@fuzdev/fuz_app/primitive_schemas.js';

Wire-form schema for a blake3:-prefixed fact hash, whose output is FactHash from @fuzdev/fuz_util/hash_schemas.ts — the type fact_hash_bytes, fact_hash_stream, and FactStore use — so a parsed value passes straight to them. Client-side (cell payloads) and server-side (DB-row hashes) consumers reuse this same schema.

The output type is a cast over a plain regex check: Zod's .brand('FactHash') would output Zod's own brand, which doesn't fit fuz_util's fuz_schema-branded FactHash, and a .transform or z.custom that produced the right type makes z.toJSONSchema throw, as neither can be represented in JSON Schema. The cast costs nothing at runtime and keeps the ZodString API and the pattern in the generated JSON Schema, so .meta(), .nullable(), and z.array() compose as before.

FactMetaRow
#

db/fact_queries.ts view source

FactMetaRow import type {FactMetaRow} from '@fuzdev/fuz_app/db/fact_queries.js';

Subset returned by metadata-only queries (no bytes payload).

hash

type FactHash

external_url

type string | null

content_type

type string | null

size

type number | string

created_at

type Date

FactPayloadTooLargeError
#

db/fact_store_errors.ts view source

import {FactPayloadTooLargeError} from '@fuzdev/fuz_app/db/fact_store_errors.js';

The streamed upload exceeded the byte cap. Thrown by put_stream when its mid-stream counter passes max_bytes — the backstop for a chunked or mis-declared Content-Length that the cheap header pre-check can't catch. A consumer route maps this to 413.

inheritance

extends: Error

bytes_read

Bytes read before the cap tripped (may exceed max_bytes by one chunk).

type number

readonly

max_bytes

type number

readonly

constructor

type new (bytes_read: number, max_bytes: number): FactPayloadTooLargeError

bytes_read

type number

max_bytes

type number

FactRow
#

db/fact_queries.ts view source

FactRow import type {FactRow} from '@fuzdev/fuz_app/db/fact_queries.js';

Row shape for SELECT … FROM fact.

hash

type FactHash

bytes

type Uint8Array<ArrayBufferLike> | null

external_url

type string | null

content_type

type string | null

size

type number | string

created_at

type Date

FACTS_SCHEMA
#

db/fact_ddl.ts view source

"\nCREATE TABLE IF NOT EXISTS fact (\n\thash TEXT PRIMARY KEY,\n\tbytes BYTEA,\n\texternal_url TEXT,\n\tcontent_type TEXT,\n\tsize BIGINT NOT NULL,\n\tcreated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),\n\tCONSTRAINT fact_storage_present CHECK (bytes IS NOT NULL OR external_url IS NOT NULL)\n)" import {FACTS_SCHEMA} from '@fuzdev/fuz_app/db/fact_ddl.js';

fact table — content-addressed byte store.

FactServingCrossTestOptions
#

testing/cross_backend/fact_serving.ts view source

FactServingCrossTestOptions import type {FactServingCrossTestOptions} from '@fuzdev/fuz_app/testing/cross_backend/fact_serving.js';

The fact suite adds one optional knob to the shared cell options: a setup variant whose keeper carries a second actor. Only the multi-actor case needs it; the rest of the suite runs single-actor, so wiring it is opt-in. Omit it and the multi-actor case silently skips.

inheritance

setup_test_multi_actor?

type (): Promise<TestFixtureBase>

readonly
returns Promise<TestFixtureBase>

FactStorageFullError
#

db/fact_store_errors.ts view source

import {FactStorageFullError} from '@fuzdev/fuz_app/db/fact_store_errors.js';

The disk filled mid-stream (ENOSPC). Thrown by put_stream when the temp-file write fails for lack of space — the real disk-full guarantee that a best-effort free-space preflight can't promise (chunked uploads, TOCTOU races). A consumer route maps this to 507.

inheritance

extends: Error

constructor

type new (cause?: unknown): FactStorageFullError

cause?

type unknown
optional

FakeHonoContextOptions
#

testing/ws_round_trip.ts view source

FakeHonoContextOptions import type {FakeHonoContextOptions} from '@fuzdev/fuz_app/testing/ws_round_trip.js';

credential_type

type CredentialType

role?

A single role to grant via create_test_request_context.

type string

auth_session_id?

type string | null

api_token_id?

type string | null

request_context?

Override the RequestContext outright (for multi-role or custom account/actor fixtures). Takes precedence over role.

type RequestContext

FakeWs
#

testing/ws_round_trip.ts view source

FakeWs import type {FakeWs} from '@fuzdev/fuz_app/testing/ws_round_trip.js';

A WSContext paired with capture arrays. Use sends to assert on outgoing frames; use closes to assert on revocation / close; use socket to model a client that stopped reading and to assert on a cut.

ws

type WSContext<unknown>

sends

type string[]

closes

type { code?: number | undefined; reason?: string | undefined; }[]

socket

The fake runtime socket (ws.raw).

type FakeWsSocket

FakeWsSocket
#

testing/ws_round_trip.ts view source

FakeWsSocket import type {FakeWsSocket} from '@fuzdev/fuz_app/testing/ws_round_trip.js';

The runtime socket behind a FakeWs — what WSContext.raw exposes, shaped like Node's ws: a bufferedAmount the outbound writer reads for backpressure, and a terminate it cuts a stalled socket with.

bufferedAmount

Bytes "handed to the socket and not yet written". Grows by each sent frame's UTF-8 length while stalled; set it (e.g. to 0) to model the client reading.

type number

stalled

While true, sent frames stay in bufferedAmount — a client that is not reading.

type boolean

terminated

Whether terminate was called — the socket was cut with no close frame.

type boolean

terminate

Cut the socket. Records terminated and empties bufferedAmount.

type (): void

returns void

FetchDeps
#

runtime/deps.ts view source

FetchDeps import type {FetchDeps} from '@fuzdev/fuz_app/runtime/deps.js';

HTTP fetch capability.

fetch

Fetch a URL. Same signature as the global fetch.

type { (input: URL | RequestInfo, init?: RequestInit | undefined): Promise<Response>; (input: string | URL | Request, init?: RequestInit | undefined): Promise<...>; }

FetchTransport
#

testing/transports/fetch_transport.ts view source

FetchTransport import type {FetchTransport} from '@fuzdev/fuz_app/testing/transports/fetch_transport.js';

The transport shape: callable as RpcTestTransport plus a cookies() accessor that returns the current jar state. The accessor exists so ws_transport can thread the session cookie onto the WS upgrade without an HTTP round trip.

inheritance

cookies

Snapshot of every cookie currently in the jar, formatted as full Set-Cookie values (name=value). Used by ws_transport to compose the Cookie header on the upgrade request.

type (): readonly string[]

readonly
returns readonly string[]

FetchTransportOptions
#

testing/transports/fetch_transport.ts view source

FetchTransportOptions import type {FetchTransportOptions} from '@fuzdev/fuz_app/testing/transports/fetch_transport.js';

Construction options for create_fetch_transport.

base_url

Base URL the binary is reachable at — e.g. http://localhost:8788.

type string

readonly

initial_cookies?

Initial cookie values to seed the jar. Pass the Set-Cookie values captured from a prior bootstrap() call to keep the keeper session across a transport-recreation boundary. Each entry is a full Set-Cookie value (the same string Headers.getSetCookie() returns).

type readonly string[]

readonly

origin?

Origin header threaded onto every request when the caller hasn't set one. Defaults to base_url; backends running with ALLOWED_ORIGINS=http://localhost:* accept http://localhost:<port> matching the spawned binary.

Pass null to disable the default — useful for bearer-only probes that must not look like browser-context requests (the auth middleware silently discards bearer credentials when Origin/Referer is present). Callers can still set Origin per-call via init.headers.

type string | null

readonly

FieldJson
#

auth/cell_field_action_specs.ts view source

value + type

{ source_id: string & $brand<"Uuid">; name: string; target_id: string & $brand<"Uuid">; created_at: string; } import {FieldJson} from '@fuzdev/fuz_app/auth/cell_field_action_specs.js';

Wire-format for a cell_field row. ISO created_at, branded UUIDs.

source_id

type string & $brand<"Uuid">

name

type string

target_id

type string & $brand<"Uuid">

created_at

type string

FILE_FACT_URL_PATTERN
#

db/file_fact_url.ts view source

RegExp import {FILE_FACT_URL_PATTERN} from '@fuzdev/fuz_app/db/file_fact_url.js';

Anchored, capture-group form: ^file:(<shard>)/(<rest>)$.

FileFactUrl
#

db/file_fact_url.ts view source

value + type

string & $brand<"FileFactUrl">

type string & $brand<"FileFactUrl">

import {FileFactUrl} from '@fuzdev/fuz_app/db/file_fact_url.js';

Branded URL form. Construct only via parse_file_fact_url or mint_file_fact_url — those are the validated boundaries. Direct string literals don't satisfy the brand.

external types

z.infer<typeof FileFactUrl>

filter_authenticated_routes
#

http/surface_query.ts view source

(surface: AppSurface): AppSurfaceRoute[] import {filter_authenticated_routes} from '@fuzdev/fuz_app/http/surface_query.js';

Filter routes that require basic authentication only — account === 'required' with no role / credential gate.

surface

returns

AppSurfaceRoute[]

filter_credential_gated_routes
#

http/surface_query.ts view source

(surface: AppSurface): AppSurfaceRoute[] import {filter_credential_gated_routes} from '@fuzdev/fuz_app/http/surface_query.js';

Filter routes gated to a credential channel (any non-empty auth.credential_types), whichever channel that is.

Superset of filter_keeper_routes: keeper is the daemon_token gate, and the same axis points the other way on the session-only routes (the credential-minting actions, POST /logout / POST /password, the audit SSE stream). Adversarial runners need the whole bucket, since every route in it answers credential_type_required to a channel it does not admit.

surface

returns

AppSurfaceRoute[]

filter_keeper_routes
#

filter_mutation_routes
#

filter_protected_routes
#

filter_public_routes
#

filter_rate_limited_routes
#

filter_role_routes
#

filter_routes_by_prefix
#

filter_routes_for_role
#

http/surface_query.ts view source

(surface: AppSurface, role: string): AppSurfaceRoute[] import {filter_routes_for_role} from '@fuzdev/fuz_app/http/surface_query.js';

Filter routes whose auth.roles includes the named role.

surface

role

type string

returns

AppSurfaceRoute[]

filter_routes_with_input
#

filter_routes_with_params
#

filter_routes_with_query
#

filter_visible_target_ids
#

auth/cell_relation_visibility.ts view source

(deps: QueryDeps, auth: RequestContext | null, target_ids: readonly (string & $brand<"Uuid">)[]): Promise<Set<string & $brand<"Uuid">>> import {filter_visible_target_ids} from '@fuzdev/fuz_app/auth/cell_relation_visibility.js';

Return the subset of target_ids the caller may view.

Soft-deleted targets and ids with no matching cell are absent from the result (treated as not-viewable). Grants are loaded only for authenticated callers — null auth admits solely via the public branch of can_view_cell, so the grant load is skipped entirely.

deps

query deps

auth

request context, or null for unauthenticated callers

type RequestContext | null

target_ids

candidate cell ids (duplicates are harmless)

type readonly Uuid[]

returns

Promise<Set<Uuid>>

the set of ids the caller may view

FilterableBroadcastTransport
#

actions/transports_ws_backend.ts view source

FilterableBroadcastTransport import type {FilterableBroadcastTransport} from '@fuzdev/fuz_app/actions/transports_ws_backend.js';

Structural capability for transports that can broadcast with a per-connection ACL predicate. Named separately from Transport so the broadcast API can feature-detect without importing a concrete class.

ConnectionIdentity is the auth-gated identity shape used today. When a second implementation (e.g. SSE backend transport) lands with a different identity, consider parameterizing on TIdentity.

inheritance

extends: Transport

broadcast_filtered

type (message: { [x: string]: unknown; jsonrpc: "2.0"; id: string | number; result: JSONType; } | { [x: string]: unknown; jsonrpc: "2.0"; id: string | number | null; error: { [x: string]: unknown; code: -32700 | -32600 | -32601 | -32602 | -32603 | (number & $brand<...>); message: string; data?: unknown; }; } | { ...; }, predicate: (identity: ConnectionIdentity) => boolean): number

message

predicate

type (identity: ConnectionIdentity) => boolean
returns number

find_auth_route
#

testing/integration_helpers.ts view source

(specs: RouteSpec[], suffix: "/login" | "/logout" | "/password" | "/verify" | "/signup" | "/bootstrap", method: RouteMethod): RouteSpec | undefined import {find_auth_route} from '@fuzdev/fuz_app/testing/integration_helpers.js';

Find a REST auth route by suffix and method.

Decouples tests from consumer route prefix (/api/account/login, /api/auth/login, etc.). suffix must be one of rest_auth_route_suffixes — throws otherwise so an RPC-only method path (e.g. /sessions/revoke-all) fails loudly at the call site instead of silently returning undefined.

specs

type RouteSpec[]

suffix

method

returns

RouteSpec | undefined

throws

  • Error - if `suffix` is not in `rest_auth_route_suffixes`.

find_route_spec
#

testing/integration_helpers.ts view source

(specs: RouteSpec[], method: string, path: string): RouteSpec | undefined import {find_route_spec} from '@fuzdev/fuz_app/testing/integration_helpers.js';

Find a route spec matching the given method and path.

Supports both exact matches and parameterized paths (:param segments).

specs

type RouteSpec[]

method

type string

path

request path (exact or with concrete param values)

type string

returns

RouteSpec | undefined

find_rpc_action
#

testing/rpc_helpers.ts view source

(rpc_endpoints: readonly RpcEndpointSpec[], method: string): { path: string; action: RpcAction; } | undefined import {find_rpc_action} from '@fuzdev/fuz_app/testing/rpc_helpers.js';

Find the RpcAction for a method within a set of RPC endpoint specs. Returns both the endpoint path and the matched action. undefined when the method is not registered.

rpc_endpoints

type readonly RpcEndpointSpec[]

method

type string

returns

{ path: string; action: RpcAction; } | undefined

find_rpc_method
#

testing/rpc_helpers.ts view source

(rpc_endpoints: readonly AppSurfaceRpcEndpoint[], method: string): { path: string; method_spec: AppSurfaceRpcMethod; } | undefined import {find_rpc_method} from '@fuzdev/fuz_app/testing/rpc_helpers.js';

Find the generated surface entry for a method — the shape returned by generate_app_surface (JSON-serializable, useful for schema assertions at the boundary of a consumer test).

rpc_endpoints

type readonly AppSurfaceRpcEndpoint[]

method

type string

returns

{ path: string; method_spec: AppSurfaceRpcMethod; } | undefined

FINGERPRINT_HEADERS
#

testing/cross_backend/conformance_table.ts view source

readonly string[] import {FINGERPRINT_HEADERS} from '@fuzdev/fuz_app/testing/cross_backend/conformance_table.js';

Response headers that fingerprint the backend implementation or framework. Neither spine emits these; the runner asserts they stay absent on EVERY conformance response so a framework upgrade or consumer middleware that adds (say) Server: to one spine can't silently become a backend-identifying oracle. Lowercased — matched against the lowercased-key snapshot headers_to_record produces.

flush_pending_effects
#

http/pending_effects.ts view source

(effects: readonly Promise<void>[], log: Logger, on_rejection?: ((reason: unknown) => void) | undefined): Promise<void> import {flush_pending_effects} from '@fuzdev/fuz_app/http/pending_effects.js';

Drain an eager pending_effects queue: Promise.allSettled the in-flight handles, route every rejection through log.error, and fan out to on_rejection when supplied (production wires this to on_effect_error for monitoring).

Returned promise resolves once every effect has settled. Never rejects. No-op when effects is empty (common on read-only requests).

Symmetric with flush_post_commit_effects for the deferred queue.

effects

type readonly Promise<void>[]

log

type Logger

on_rejection?

type (reason: unknown) => void
optional

returns

Promise<void>

flush_post_commit_effects
#

http/pending_effects.ts view source

(effects: readonly (() => void | Promise<void>)[], log: Logger): Promise<void> import {flush_post_commit_effects} from '@fuzdev/fuz_app/http/pending_effects.js';

Drain a post_commit_effects queue: invoke each thunk under try/catch, collect any returned promises, and Promise.allSettled them. Synchronous throws and async rejections are routed through log.error so one failing effect cannot starve siblings.

Returned promise resolves once every thunk has finished. Never rejects.

effects

type readonly (() => void | Promise<void>)[]

log

type Logger

returns

Promise<void>

ForeignKeyError
#

http/error_schemas.ts view source

value + type

{ [x: string]: unknown; error: "foreign_key_violation"; } import {ForeignKeyError} from '@fuzdev/fuz_app/http/error_schemas.js';

Foreign key violation error — returned when a delete is blocked by references.

error

type "foreign_key_violation"

[key: string]

type unknown

format_action_manifest_diffs
#

format_allowed_origins_error
#

http/origin.ts view source

(error: AllowedOriginsError): string import {format_allowed_origins_error} from '@fuzdev/fuz_app/http/origin.js';

Render one refusal for a boot error — the entry in double quotes with every character but visible ASCII and the space escaped as \uXXXX (and " and \ backslash-escaped), so an env value can't put a control, bidi, or other non-printing character in a log, nor close the quotes it is shown in.

error

one of parse_allowed_origins's errors

returns

string

the reason as one line

format_arg_default
#

cli/help.ts view source

(value: unknown): string import {format_arg_default} from '@fuzdev/fuz_app/cli/help.js';

Format a default value for display, '' for none.

Strings and arrays are written as JSON, as the type column writes a string literal.

value

the default

type unknown

returns

string

display text

format_arg_name
#

cli/help.ts view source

(prop: HelpArgProperty): string import {format_arg_name} from '@fuzdev/fuz_app/cli/help.js';

Format argument name with short aliases for display.

Only single-char aliases are shown (e.g., -h, --help). Flags use snake_case (e.g., --env_file, --detect_only).

prop

schema property

returns

string

formatted name string

format_audit_metadata
#

ui/ui_format.ts view source

(event_type: string, metadata: Record<string, unknown> | null): string import {format_audit_metadata} from '@fuzdev/fuz_app/ui/ui_format.js';

Format audit event metadata for display based on event type.

event_type

builtin or consumer-registered audit event type

type string

metadata

type Record<string, unknown> | null

returns

string

format_cross_impl_comparison
#

testing/cross_backend/bench/bench_report.ts view source

(entries: readonly CrossImplComparisonEntry[]): string import {format_cross_impl_comparison} from '@fuzdev/fuz_app/testing/cross_backend/bench/bench_report.js';

Render the comparison entries as one line each. The prefix lists the reference first to match benchmark_stats_compare's "First" (= the a arg = reference) / "Second" (= the b arg = backend) in the recommendation text — compare_cross_impl passes (reference, backend).

entries

type readonly CrossImplComparisonEntry[]

returns

string

format_cross_impl_json
#

testing/cross_backend/bench/bench_report.ts view source

(result: CrossImplBenchResult): string import {format_cross_impl_json} from '@fuzdev/fuz_app/testing/cross_backend/bench/bench_report.js';

Self-describing JSON artifact: one entry per backend × scenario with the percentiles (raw-sample tail), the resolved budget, and iteration count. Diffable and reviewable without a TS runtime; the seed for the deferred static-docs comparison surface.

result

returns

string

format_cross_impl_markdown
#

format_datetime_local
#

ui/ui_format.ts view source

(timestamp: string | number | Date): string import {format_datetime_local} from '@fuzdev/fuz_app/ui/ui_format.js';

Format a timestamp as an absolute datetime string for title attributes.

timestamp

type string | number | Date

returns

string

readable absolute datetime like "2026-03-21 14:30:00 UTC"

format_db_status
#

db/status.ts view source

(status: DbStatus): string import {format_db_status} from '@fuzdev/fuz_app/db/status.js';

Format a DbStatus as the body of the human-facing operator report.

A tables: count with one aligned name N rows line per table, then a migrations: header (always printed, even with no namespaces) and one aligned line per namespace: its state (up-to-date, PENDING <n>, or DIVERGED), applied/declared, and the latest applied name ((none) when nothing is applied). A diverged namespace adds a diverged: line, a pending one a pending: line naming the tail. A failed connection renders the single line connection: FAILED (<error>). Every line ends in a newline.

The body only — the database status / url: header belongs to the CLI script, which holds the URL a DbStatus never sees. For a connected status the output is byte-identical with the Rust twin's format_db_status; both test suites pin the same fixture report.

status

the status to format

returns

string

multi-line string suitable for console output

format_env_display_value
#

env/mask.ts view source

(value: unknown, secret: boolean): string import {format_env_display_value} from '@fuzdev/fuz_app/env/mask.js';

Format an env value for display, masking secrets.

value

the env value to format

type unknown

secret

whether the value is secret and should be masked

type boolean

returns

string

display string — masked placeholder for secrets, string values as-is, non-strings JSON-stringified

format_migration_tracker_diffs
#

testing/schema_parity.ts view source

(diffs: readonly MigrationTrackerDiff[], labels?: SchemaDiffLabels): string import {format_migration_tracker_diffs} from '@fuzdev/fuz_app/testing/schema_parity.js';

Render migration-tracker diffs as a human-readable multi-line string. Empty diffs produce an empty string.

diffs

type readonly MigrationTrackerDiff[]

labels

default {}

returns

string

format_rate_limit_message
#

ui/auth_state.svelte.ts view source

(retry_after: unknown): string import {format_rate_limit_message} from '@fuzdev/fuz_app/ui/auth_state.svelte.js';

The message a 429 shows, from its retry_after (seconds).

A short wait reads as one — the spines answer retry_after: 1 when the cap is held only by attempts still in flight — rather than rounding up to a minute; a missing value reads as a minute.

retry_after

the response's retry_after, when it carried one

type unknown

returns

string

format_relative_time
#

ui/ui_format.ts view source

(timestamp: string | number | Date, now?: number): string import {format_relative_time} from '@fuzdev/fuz_app/ui/ui_format.js';

Format a timestamp as a relative time string.

timestamp

type string | number | Date

now

type number
default Date.now()

returns

string

human-friendly relative time (e.g. "2m ago", "3h ago", "5d ago", "2mo ago", "1y ago")

format_route_key
#

http/surface_query.ts view source

(route: AppSurfaceRoute): string import {format_route_key} from '@fuzdev/fuz_app/http/surface_query.js';

Format a route as 'METHOD /path' (e.g. 'GET /health').

route

returns

string

format_schema_diffs
#

testing/schema_parity.ts view source

(diffs: readonly SchemaDiff[], labels?: SchemaDiffLabels): string import {format_schema_diffs} from '@fuzdev/fuz_app/testing/schema_parity.js';

Render a diff list as a human-readable multi-line string. Empty diffs produce an empty string.

diffs

type readonly SchemaDiff[]

labels

default {}

returns

string

format_schema_drift
#

db/schema_ready.ts view source

(drift: SchemaDriftResult): string import {format_schema_drift} from '@fuzdev/fuz_app/db/schema_ready.js';

Render a drift result as a one-issue-per-line operator string.

drift

returns

string

format_scope_context
#

ui/format_scope.ts view source

{ get: () => () => FormatScope; set: (value?: (() => FormatScope) | undefined) => () => FormatScope; } import {format_scope_context} from '@fuzdev/fuz_app/ui/format_scope.js';

Svelte context carrying a getter for the consumer's FormatScope. Provisioned by provide_admin_rpc_contexts from its format_scope option. Default getter returns default_format_scope so unprovisioned trees render the raw uuid.

format_uptime
#

ui/ui_format.ts view source

(ms: number): string import {format_uptime} from '@fuzdev/fuz_app/ui/ui_format.js';

Format milliseconds as a human-friendly uptime string.

ms

type number

returns

string

human-friendly duration (e.g. "45s", "12m", "3h 15m", "2d 5h")

format_value
#

ui/ui_format.ts view source

(value: unknown): string import {format_value} from '@fuzdev/fuz_app/ui/ui_format.js';

Format an arbitrary value for table cell display.

value

type unknown

returns

string

FormatScope
#

ui/format_scope.ts view source

FormatScope import type {FormatScope} from '@fuzdev/fuz_app/ui/format_scope.js';

Render a {scope_id, role} pair as a human label. Return null to fall back to the raw scope uuid (or a caller-chosen global_label when scope_id is null).

Returning null for unknown scope ids (stale cache, revoked resource) is the recommended pattern — components show the raw uuid rather than a misleading blank.

(call)

type (args: { scope_id: string | null; role: string; }): string | null

args

type { scope_id: string | null; role: string; }
returns string | null

FormState
#

ui/form_state.svelte.ts view source

import {FormState} from '@fuzdev/fuz_app/ui/form_state.svelte.js';

generics

FormState<TField extends string = string>
TField
constraint string
default string

form

Creates a form attachment that handles the Enter key (advance focus to the next input, or submit from the last one) and tracks field touched state via delegated focusout.

Fields are identified by their name attribute.

type (): Attachment<HTMLFormElement>

returns Attachment<HTMLFormElement>

throws

  • Error - in DEV when called while already attached, or when an input loses focus without a `name` attribute

is_touched

Whether a field has been blurred at least once.

type (field: TField): boolean

field

type TField
returns boolean

show

Whether to show validation errors for a field. Returns true if the field has been blurred or a submit attempt was made.

type (field: TField): boolean

field

type TField
returns boolean

touch

Programmatically marks a field as touched without requiring a blur event.

type (field: TField): void

field

type TField
returns void

mutates

  • this

focus

Focuses the named input within the form.

type (field: TField): void

field

type TField
returns void

attempt

Marks the form as having been submitted, causing all field errors to show.

type (): void

returns void

mutates

  • this

reset

Resets all touched and attempted state.

type (): void

returns void

mutates

  • this

attempted

Whether a submit attempt has been made.

type boolean

getter

freeze_origin_carve_outs
#

auth/middleware.ts view source

(carve_outs: readonly MiddlewareCarveOut[]): readonly MiddlewareCarveOut[] import {freeze_origin_carve_outs} from '@fuzdev/fuz_app/auth/middleware.js';

Copy origin carve-outs into a frozen array of frozen {path, reason} records, so a caller that mutates its own array or objects after assembly cannot change what was validated or what the check skips.

carve_outs

the caller's carve-outs

type readonly MiddlewareCarveOut[]

returns

readonly MiddlewareCarveOut[]

the frozen copy

FrontendHttpTransport
#

actions/transports_http.ts view source

import {FrontendHttpTransport} from '@fuzdev/fuz_app/actions/transports_http.js';

Thin fetch adapter for the JSON-RPC endpoint. POST by default; GET when the optional has_side_effects(method) callback returns false for the method (matches create_rpc_endpoint's GET convention). Always reports ready.

On non-OK HTTP responses, a body that is a JSON-RPC error response for this request (same id, or null when the server couldn't read one) is returned as-is, so the server's code, message, and data reach the caller — the status → code map is lossy (queue_overflow and rate_limited share 429). Any other body (HTML from a proxy, empty, malformed) gets a synthesized envelope whose code comes from http_status_to_jsonrpc_error_code. A 2xx whose body isn't a JSON-RPC response (an HTML fallback page, say) becomes an internal_error naming the status and content type.

inheritance

implements: Transport

transport_name

type "frontend_http_rpc"

readonly

constructor

type new (url: string, headers?: Record<string, string> | undefined, has_side_effects?: ((method: string) => boolean) | undefined): FrontendHttpTransport

url

the JSON-RPC endpoint

type string

headers?

extra headers, merged over the JSON content-type and accept defaults case-insensitively — a POST without application/json is refused (415)

type Record<string, string>
optional

has_side_effects?

when it returns false for a method, the call is a GET

type (method: string) => boolean
optional

send

type (message: { [x: string]: unknown; jsonrpc: "2.0"; id: string | number; method: string; params?: { [x: string]: unknown; } | undefined; }, options?: TransportSendOptions | undefined): Promise<...>

message

options?

optional
returns Promise<JsonrpcResponseOrError>

is_ready

type (): boolean

returns boolean

FrontendRpcClient
#

actions/frontend_rpc_client.ts view source

FrontendRpcClient<TApi> import type {FrontendRpcClient} from '@fuzdev/fuz_app/actions/frontend_rpc_client.js';

Bundle returned by create_frontend_rpc_client.

generics

FrontendRpcClient<TApi>
TApi

api

Typed throwing Proxy. await api.method(input) returns the unwrapped value or throws an Error with {code, data} from the JSON-RPC error. Default for call sites that don't inspect errors.

type ThrowingApi<TApi>

api_result

Typed Result-shaped Proxy. await api_result.method(input) returns Result<{value}, {error: JsonrpcErrorObject}>. Use when call sites inspect error.data.reason without try/catch, or when Error allocation per {ok: false} would be wasteful.

type TApi

peer

Underlying peer — exposed for consumers that need to register more transports or send raw messages.

type ActionDispatcher

environment

Action environment — exposed for consumers that need to share it (e.g. attach a notification handler registry).

type ActionEventEnvironment

FrontendWebsocketClient
#

actions/socket.svelte.ts view source

import {FrontendWebsocketClient} from '@fuzdev/fuz_app/actions/socket.svelte.js';

Reactive WebSocket client implementing WebsocketConnection.

Construct with a URL and optional config; call connect() to open the socket and begin auto-reconnect. Register message/error handlers via add_message_handler / add_error_handler — both return unsubscribe functions. FrontendWebsocketTransport consumes this as its connection.

Session-revocation close codes (WS_CLOSE_SESSION_REVOKED) put the client in a permanently-closed state; reconnecting would just loop on 401.

A connection-limit close (WS_CLOSE_CONNECTION_LIMIT) leaves the client closed with superseded set and no reconnect scheduled — the server closed this socket to admit a newer one on the same account, and reconnecting would close that one in turn. An explicit connect() reopens.

A permission-revoked close (WS_CLOSE_PERMISSION_REVOKED) leaves the client closed with no reconnect scheduled, and neither revoked nor superseded set: the credential is still good, but the account lost the role the endpoint requires, so a reconnect would be refused at its role gate. Pending and queued requests reject forbidden. last_close_code says why; an explicit connect() reopens, for when the role has been granted again.

inheritance

implements: WebsocketConnection,Disposable

ws

type WebSocket | null

$state.raw

status

type SocketStatus

$state.raw

reconnect_count

type number

$state.raw

current_reconnect_delay

type number

$state.raw

last_connect_time

Epoch ms of the most recent successful open. Never cleared on close.

type number | null

$state.raw

last_close_time

Epoch ms of the most recent close event or client-initiated close.

type number | null

$state.raw

last_close_code

Close code from the most recent close. Initial null means "never closed."

type number | null

$state.raw

last_close_reason

Reason string from the most recent close event (may be empty).

type string | null

$state.raw

last_send_time

Epoch ms of the most recent frame written to the socket — requests, notifications, heartbeats, and cancels alike. null until the first send.

type number | null

$state.raw

last_receive_time

Epoch ms of the most recent frame received — responses (including ones request consumes), notifications, and requests alike. null until the first receive.

type number | null

$state.raw

last_send_error

The error thrown by the most recent attempted send(), or null if the most recent attempt succeeded or none has been attempted yet. Populated when the underlying ws.send throws (e.g., buffer full, serialization error); reset to null on the next successful send. Not touched when send() short-circuits because the socket is not connected — consult connected for that case. Wrappers surfacing per-message failure reasons can read this after a false return from send().

type Error | null

$state.raw

connected

type boolean

readonly $derived

constructor

type new (url: string, options?: FrontendWebsocketClientOptions): FrontendWebsocketClient

url

type string

options

default {}

set_reconnect

Swap the auto-reconnect policy in place. Accepts the same shape as the constructor's reconnect option: false disables reconnect, true or null/omitted restores the defaults, or a config object customizes specific fields (missing fields fall back to defaults, not "keep current" — each call defines the whole policy atomically, same as the constructor).

In-flight reconnect schedules are monotonically shortened: the effective total wait from arm-time never exceeds what the new policy prescribes. If the new target is already past the time already elapsed, the reconnect fires immediately (on the next tick). The wait is never extended.

Turning reconnect off while a reconnect timer is pending cancels that timer and transitions status to closed (since the lie of 'reconnecting' would be visible to UI indicators). Turning it back on does not synthesize a reconnect — wait for the next close.

type (reconnect?: boolean | FrontendWebsocketReconnectOptions | null): void

reconnect

type boolean | FrontendWebsocketReconnectOptions | null
default null
returns void

mutates

  • this — replaces the reconnect policy fields; may also reset `status`, `reconnect_count`, and `current_reconnect_delay` when an in-flight reconnect timer is cancelled or rescheduled

set_heartbeat

Swap the heartbeat policy in place. Accepts the same shape as the constructor's heartbeat option: false disables the timer, true or null/omitted restores the defaults, or a config object customizes specific fields (missing fields fall back to defaults, not "keep current" — each call defines the whole policy atomically, same as the constructor and set_reconnect).

When connected, the live timer is restarted immediately so the new interval / receive_timeout take effect without a reconnect; when disconnected, just stashes the policy for the next open.

type (heartbeat?: boolean | FrontendWebsocketHeartbeatOptions | null): void

heartbeat

type boolean | FrontendWebsocketHeartbeatOptions | null
default null
returns void

mutates

  • this — replaces the heartbeat policy fields; restarts the live heartbeat timer when connected

cancel_reconnect

Cancel a scheduled reconnect without closing the client or disabling auto-reconnect. Transitions status from reconnecting → closed and resets the backoff counters — the next close still triggers a fresh reconnect cycle under the current policy. No-op when no reconnect is pending.

Use this when UI state asks "stop trying for now" without the finality of disconnect (which also rejects pending/queued requests and clears heartbeat) or the policy change of set_reconnect(false) (which disables future reconnects). The queue stays intact so that calling connect later flushes buffered work.

type (): void

returns void

mutates

  • this — clears the pending reconnect timer, sets `status` to `closed`, resets `reconnect_count` and `current_reconnect_delay`

connect

Open the WebSocket. No-op on SSR, or if the session has been revoked. Cancels any pending reconnect and tears down any existing connection first; an open prior socket is closed with a normal-closure code. Reopens after a superseded close, clearing the flag.

type (): void

returns void

mutates

  • this — replaces `ws`, clears `superseded`, sets `status` to `connecting` (or `closed` on construction failure), and on construction failure may schedule a reconnect (mutating `reconnect_count` / `current_reconnect_delay`)

disconnect

Close the WebSocket, cancel any pending reconnect, and reset the reconnect backoff counters. Puts the client in closed status; call connect() to reopen. Safe to call more than once.

type (code?: number): void

code

type number
default DEFAULT_CLOSE_CODE
returns void

mutates

  • this — clears `ws`, heartbeat, and reconnect timers; sets `status` to `closed`; rejects every pending and queued request with `service_unavailable`

[Symbol.dispose]

Explicit-resource-management hook — supports using client = new FrontendWebsocketClient(url).

type (): void

returns void

send

type (data: object): boolean

data

type object
returns boolean

request

Promise-based JSON-RPC over the socket. Auto-assigns a monotonic request id (or uses an explicit one supplied via options.id — used by FrontendWebsocketTransport which delegates to this method and has its own peer-minted UUID), tracks the pending promise, and resolves when the server sends a matching response.

An undefined params is omitted from the frame rather than sent as {} — the absent form is the parameterless call every server admits for a z.void() input, including servers that refuse {}.

Callers supplying an explicit options.id are responsible for uniqueness — the pending map is keyed by id, and a duplicate silently overwrites the prior entry. Auto-minted ids are monotonic and never collide with themselves or with peer-minted UUIDs (the types differ: integer vs string).

While the socket is disconnected, the request is buffered in a bounded queue (default-on, DEFAULT_QUEUE_MAX_SIZE) and flushed on reopen. Pass {queue: false} to reject immediately when disconnected — used internally by the heartbeat, which must not fight the queue for the disconnect-detection slot.

On AbortSignal fire: rejects the local promise *and* sends the shared cancel notification (cancel_action_spec.method) so the server-side dispatcher can abort the matching handler's ctx.signal. Suppressed for queued-but-never-sent (server doesn't know about it) and response-beat-cancel races.

type <R = unknown>(method: string, params?: unknown, options?: { signal?: AbortSignal | undefined; queue?: boolean | undefined; id?: string | number | undefined; }): Promise<R>

method

type string

params?

type unknown
optional

options

type { signal?: AbortSignal | undefined; queue?: boolean | undefined; id?: string | number | undefined; }
default {}
returns Promise<R>

throws

  • ThrownJsonrpcError - on the returned promise — never thrown synchronously. Rejection codes: - `unauthenticated` — session revoked (entry check or close code) - `request_cancelled` — caller's `AbortSignal` fired - `queue_overflow` — durable queue full - `service_unavailable` — socket not connected / closed / torn down mid-flight, including a `superseded` close - `forbidden` — in flight or queued at a `WS_CLOSE_PERMISSION_REVOKED` close - `internal_error` — `ws.send` threw (serialization, buffer full) - server's wire code verbatim — JSON-RPC error frame from peer

mutates

  • this — inserts the new pending entry into `#pending` (or buffers into `#queue` when disconnected) and may bump `#next_request_id`, `last_send_time`, `last_send_error`

add_message_handler

type (handler: SocketMessageHandler): () => void

handler

returns () => void

add_error_handler

type (handler: SocketErrorHandler): () => void

handler

returns () => void

url

type string

getter

revoked

Whether the server has permanently closed the session. Once true, all connect() calls are no-ops. Distinct from status:'closed', which reflects any closed state (incl. user-initiated disconnect()).

type boolean

getter

superseded

Whether the server closed this client's socket to admit a newer connection on the same account (WS_CLOSE_CONNECTION_LIMIT) and it hasn't been reopened since. While true the client is closed and schedules no reconnect — reconnecting on its own would supersede a newer socket in turn, and clients past the cap would close each other in a loop.

Unlike revoked this is not terminal: the credential is still good, and the next connect() clears it and reopens — only connect() does; disconnect() leaves it set. Requests in flight or queued at the close reject with service_unavailable; ones made afterwards buffer in the durable queue like any other call on a closed client. Reactive, so UI can say why the connection is closed and offer the reconnect.

socket_status_to_async_status reads a superseded client as initial, the same as one that was never connected. An app that connects on its own when its status reads initial must also check superseded, or it rebuilds the loop.

type boolean

getter

FrontendWebsocketClientOptions
#

actions/socket.svelte.ts view source

FrontendWebsocketClientOptions import type {FrontendWebsocketClientOptions} from '@fuzdev/fuz_app/actions/socket.svelte.js';

reconnect?

Auto-reconnect policy. false disables reconnect entirely; true or omit for default timing; pass an object to customize.

type boolean | FrontendWebsocketReconnectOptions | null

heartbeat?

Activity-aware heartbeat. true/null/omit for defaults; false disables the timer entirely (only do this if the server side is also running without heartbeat); pass an object to tune interval / receive_timeout.

type boolean | FrontendWebsocketHeartbeatOptions | null

queue?

Durable queue for FrontendWebsocketClient.request. true or omit for defaults; false disables buffering (requests while disconnected reject immediately). Raw FrontendWebsocketClient.send is never queued — use request() for RPC semantics.

type boolean | FrontendWebsocketQueueOptions

log?

Optional logger for diagnostic messages.

type Logger | null

FrontendWebsocketHeartbeatOptions
#

actions/socket.svelte.ts view source

FrontendWebsocketHeartbeatOptions import type {FrontendWebsocketHeartbeatOptions} from '@fuzdev/fuz_app/actions/socket.svelte.js';

interval?

Idle duration (ms) after which a heartbeat is sent — when either nothing has been sent or nothing has been received for this long, so both ends' silence detectors stay fed. Traffic in both directions defers it, so chatty connections never emit extras. Defaults to DEFAULT_HEARTBEAT_INTERVAL.

type number

receive_timeout?

Receive-silence (ms) after which the client closes the socket with WS_CLOSE_CLIENT_HEARTBEAT_TIMEOUT, letting auto-reconnect kick in. Defaults to DEFAULT_HEARTBEAT_RECEIVE_TIMEOUT; either way it's raised to at least HEARTBEAT_RECEIVE_TIMEOUT_MIN_FACTOR × interval (see resolve_heartbeat_receive_timeout), so a long interval alone is safe.

type number

FrontendWebsocketQueueOptions
#

actions/socket.svelte.ts view source

FrontendWebsocketQueueOptions import type {FrontendWebsocketQueueOptions} from '@fuzdev/fuz_app/actions/socket.svelte.js';

max_size?

Maximum number of requests held while the socket is disconnected. Enqueue past this rejects the new call with a queue_overflow error. Defaults to DEFAULT_QUEUE_MAX_SIZE.

type number

FrontendWebsocketReconnectOptions
#

actions/socket.svelte.ts view source

FrontendWebsocketReconnectOptions import type {FrontendWebsocketReconnectOptions} from '@fuzdev/fuz_app/actions/socket.svelte.js';

delay?

Base reconnect delay in ms. Defaults to 1000.

type number

delay_max?

Max reconnect delay in ms (cap on exponential backoff). Defaults to 10000.

type number

factor?

Exponential backoff factor. Defaults to 1.5.

type number

FrontendWebsocketTransport
#

actions/transports_ws.ts view source

import {FrontendWebsocketTransport} from '@fuzdev/fuz_app/actions/transports_ws.js';

Thin adapter over WebsocketRpcConnection (canonical implementation: FrontendWebsocketClient). Routes inbound server-pushed requests and notifications into the supplied receive callback and sends the request response back over the socket; an inbound peer/ping is answered by the built-in responder before receive. Responses to requests *we* sent are owned by the connection's own request() pending map and are ignored here. An oversized outbound message (max_message_bytes) fails with an error envelope instead of being sent (see FrontendWebsocketTransportOptions).

inheritance

implements: Transport

transport_name

type "frontend_websocket_rpc"

readonly

constructor

type new (connection: WebsocketRpcConnection, receive: (data: unknown) => Promise<unknown>, options?: FrontendWebsocketTransportOptions | undefined): FrontendWebsocketTransport

connection

receive

type (data: unknown) => Promise<unknown>

options?

optional

send

type (message: { [x: string]: unknown; jsonrpc: "2.0"; id: string | number; method: string; params?: { [x: string]: unknown; } | undefined; }, options?: TransportSendOptions | undefined): Promise<...>

message

options?

optional
returns Promise<JsonrpcResponseOrError>

is_ready

type (): boolean

returns boolean

dispose

Detach the inbound message and error handlers registered on the connection. Idempotent — subsequent calls no-op. Does not close the underlying connection (that lifecycle is owned by the caller).

type (): void

returns void

mutates

  • this — clears the two stored unsubscribe references after invoking them

FrontendWebsocketTransportOptions
#

actions/transports_ws.ts view source

FrontendWebsocketTransportOptions import type {FrontendWebsocketTransportOptions} from '@fuzdev/fuz_app/actions/transports_ws.js';

max_message_bytes?

Largest outbound message, in UTF-8 bytes of its JSON encoding. A larger request or notification is refused with an invalid_request error (data.reason payload_too_large) without being sent — the server closes the socket on an oversized one (failing every request in flight on it) rather than replying with an error. Defaults to DEFAULT_WS_MAX_MESSAGE_BYTES, the server's default cap; a consumer that raises the server's max_message_bytes passes the same value here. null skips the measurement.

type number | null

default DEFAULT_WS_MAX_MESSAGE_BYTES

FsReadDeps
#

runtime/deps.ts view source

FsReadDeps import type {FsReadDeps} from '@fuzdev/fuz_app/runtime/deps.js';

File system read operations.

stat

Get file/directory stats, or null if path doesn't exist.

type (path: string): Promise<StatResult | null>

path

type string
returns Promise<StatResult | null>

read_text_file

Read a file as text. Throws if the file does not exist.

type (path: string): Promise<string>

path

type string
returns Promise<string>

read_file

Read a file as bytes. Throws if the file does not exist.

type (path: string): Promise<Uint8Array<ArrayBufferLike>>

path

type string
returns Promise<Uint8Array<ArrayBufferLike>>

read_text_from_offset

Read text starting from a byte offset. Throws if the file does not exist.

Returns content, bytes_read, and file_size so callers can detect truncation (when file_size < offset) and tail incrementally without re-reading the whole file.

type (path: string, offset: number): Promise<ReadTextFromOffsetResult>

path

type string

offset

type number
returns Promise<ReadTextFromOffsetResult>

readdir

List directory entries (names, not full paths). Throws if the directory does not exist.

type (path: string): Promise<string[]>

path

type string
returns Promise<string[]>

FsRemoveDeps
#

runtime/deps.ts view source

FsRemoveDeps import type {FsRemoveDeps} from '@fuzdev/fuz_app/runtime/deps.js';

File system remove operations.

remove

Remove a file or directory.

type (path: string, options?: { recursive?: boolean | undefined; } | undefined): Promise<void>

path

type string

options?

type { recursive?: boolean | undefined; }
optional
returns Promise<void>

FsSecureReadDeps
#

runtime/deps.ts view source

FsSecureReadDeps import type {FsSecureReadDeps} from '@fuzdev/fuz_app/runtime/deps.js';

Hardened read for secret files (bootstrap tokens, keys) — the TS twin of the Rust spine's fuz_sys::secure_file::load_secure_file.

read_secure_file

Read a small secret file with fail-loud checks: rejects symlinks, non-regular files (FIFO, device, directory — real runtimes only; the mock has no file types), any group/other-accessible mode (must be 0600/0400; skipped where POSIX modes don't apply), and files over a 4 KiB cap. Permission checks run on the open descriptor where the platform allows, so the file can't be swapped between check and read. Throws on any refusal — a refused secret must never degrade to an empty read.

type (path: string): Promise<Uint8Array<ArrayBufferLike>>

path

type string
returns Promise<Uint8Array<ArrayBufferLike>>

FsStreamDeps
#

runtime/deps.ts view source

FsStreamDeps import type {FsStreamDeps} from '@fuzdev/fuz_app/runtime/deps.js';

Streaming file I/O — read a file as a byte stream, or write a byte stream to a file, both bounded in memory (peak ≈ one chunk, not the whole file).

Kept separate from FsReadDeps / FsWriteDeps so the whole-buffer read_file / write_file consumers and their partial test stubs are unaffected; only the full runtime factories implement these. Used for GB-scale artifact transfer (the fuzf file get / put path) where buffering the whole file would OOM the client.

read_file_stream

Open a file as a ReadableStream of its bytes — read incrementally, so peak memory is one chunk rather than the whole file. Throws if the file does not exist. Use as a streaming upload body or for an incremental hash pass over a large file.

type (path: string): Promise<ReadableStream<Uint8Array<ArrayBufferLike>>>

path

type string
returns Promise<ReadableStream<Uint8Array<ArrayBufferLike>>>

write_file_stream

Write a ReadableStream of bytes to a file, consuming it with backpressure (peak memory is one chunk). Creates or truncates path. Throws on any I/O error; a partially-written file may remain, so callers needing atomicity write to a temp path then rename.

type (path: string, data: ReadableStream<Uint8Array<ArrayBufferLike>>): Promise<void>

path

type string

data

type ReadableStream<Uint8Array<ArrayBufferLike>>
returns Promise<void>

FsWriteDeps
#

runtime/deps.ts view source

FsWriteDeps import type {FsWriteDeps} from '@fuzdev/fuz_app/runtime/deps.js';

File system write operations.

mkdir

Create a directory. mode applies at creation (no-op where modes don't apply).

type (path: string, options?: { recursive?: boolean | undefined; mode?: number | undefined; } | undefined): Promise<void>

path

type string

options?

type { recursive?: boolean | undefined; mode?: number | undefined; }
optional
returns Promise<void>

write_text_file

Write text to a file. See WriteFileOptions for mode / exclusive.

type (path: string, content: string, options?: WriteFileOptions | undefined): Promise<void>

path

type string

content

type string

options?

optional
returns Promise<void>

write_file

Write bytes to a file.

type (path: string, data: Uint8Array<ArrayBufferLike>): Promise<void>

path

type string

data

type Uint8Array<ArrayBufferLike>
returns Promise<void>

rename

Rename (move) a file.

type (old_path: string, new_path: string): Promise<void>

old_path

type string

new_path

type string
returns Promise<void>

fsync

Flush a file's data to stable storage (fsync). Call on a temp file after writing it and *before* rename-ing it into place when the renamed path is later served without re-verification — otherwise a host crash after the rename can surface a torn/zero file as authentic content. The fact disk CAS (db/fact_disk_storage.ts) is the one such path; it twins the Rust fuz_fact §fsync posture (data-sync before rename; the parent-dir fsync stays deliberately waived — a lost dirent is regenerable under content addressing). Real runtimes open the path, fsync, and close; create_mock_runtime no-ops (it models no durability).

type (path: string): Promise<void>

path

type string
returns Promise<void>

FullSpineMount
#

testing/cross_backend/full_spine_mount.ts view source

FullSpineMount import type {FullSpineMount} from '@fuzdev/fuz_app/testing/cross_backend/full_spine_mount.js';

The spine binary's RPC and WS action sets, built from one action list.

rpc_actions

Every action on the RPC endpoint — build_full_spine_rpc_actions's list.

type RpcAction[]

readonly

ws_actions

The WS endpoint's actions: protocol_actions first, then every RPC action not already among them (peer/ping is on both lists). The same handler instances as rpc_actions.

type Action<ActionSpecUnion>[]

readonly

FullSpineMountOptions
#

testing/cross_backend/full_spine_mount.ts view source

FullSpineMountOptions import type {FullSpineMountOptions} from '@fuzdev/fuz_app/testing/cross_backend/full_spine_mount.js';

daemon_token_state

Daemon-token runtime state threaded into create_testing_actions — the _testing_reset handler mutates keeper_account_id after re-seeding. Pass the same instance the daemon-token middleware reads. For the coverage test (method enumeration only, handlers never run) any stub state satisfies it.

type DaemonTokenState

readonly

notification_sender?

WS notification sender for the role-grant-offer fan-out. Pass the SAME BackendWebsocketTransport the WS endpoint registers connections against (the transport is the connection registry). Omitted for enumeration.

type NotificationSender | null

readonly

fuz_app_stock_route_tightness_allowlist
#

testing/surface_invariants.ts view source

readonly string[] import {fuz_app_stock_route_tightness_allowlist} from '@fuzdev/fuz_app/testing/surface_invariants.js';

Routes shipped by fuz_app whose error schemas require a tightness exemption.

Currently empty — every fuz_app-shipped route (account login/password/ bootstrap/signup, db health/tables/:name/tables/:name/rows/:id) was tightened in place to z.enum([...]) / z.literal(...) against every emit-site error code.

Kept as a forward-compatibility hook: when new stock routes ship with heterogeneous error surfaces that need an interim generic schema, add them here instead of forcing every consumer to hand-maintain the entry.

Paths assume the standard /api/account + /api/db prefixes used by every fuz_app consumer. Merged into default_error_schema_tightness.allowlist so consumers calling assert_error_schema_tightness directly inherit the exemptions; the standard attack-surface suite also prepends these entries underneath any consumer-supplied allowlist so project-specific entries are additive.

fuz_auth_guard_resolver
#

auth/auth_guard_resolver.ts view source

(auth: { account: "optional" | "none" | "required"; actor: "optional" | "none" | "required"; roles?: readonly string[] | undefined; credential_types?: readonly string[] | undefined; required_scope?: string | undefined; }): AuthGuards import {fuz_auth_guard_resolver} from '@fuzdev/fuz_app/auth/auth_guard_resolver.js';

Standard auth guard resolver for fuz_app.

Reads each axis of the RouteAuth shape and emits the corresponding middleware:

  • account === 'required' or actor === 'required' → pre-authorization require_auth
  • credential_types?.length → pre-authorization require_credential_types(types)
  • required_scope → pre-authorization require_token_scope(capability)
  • roles?.length → post-authorization require_role(roles) (multi-role any-of)

Guards run in declaration order, which is the coarse-to-fine order above: presence first (an anonymous caller holds neither a credential type nor a scope to check), then the channel, then the scope, then the role — the last being the only one that needs the resolved RequestContext.

auth

returns

AuthGuards

throws

  • Error - if `auth.required_scope` is not a well-formed capability string (via `require_token_scope`) — `RouteAuth` types it as a plain string to keep `http/` free of an `auth/` import, so this is where the value is checked. The check is on *shape*, not membership: the vocabulary is deliberately open so a consumer can name its own surface, and a `surface:` name decides nothing (rule 3 is all-or-nothing), so the closed set would only have been a spelling check on a diagnostic string. fuz_app's own declarations are pinned to the surfaces it actually mounts by the surface census.
  • Error - if `auth.required_scope` sits on an unrestricted route (`account: 'none' && actor: 'none'`). The same holder reaches that route by dropping the credential, so the guard reads as a control and enforces nothing — the shape `compile_action_registry` refuses on an `ActionSpec` for the same reason. Extends `RouteAuth`'s leaf invariant, which the route pipeline never parses.

fuz_session_config
#

FuzAuthActionSpecRegistry
#

auth/all_action_spec_registries.ts view source

FuzAuthActionSpecRegistry import type {FuzAuthActionSpecRegistry} from '@fuzdev/fuz_app/auth/all_action_spec_registries.js';

One named entry in the registry-of-registries.

name

Stable identifier matching the source bundle name ('admin', 'role_grant_offer', etc.).

type string

specs

The bundle's spec array — kept readonly here even when the source declares it mutable.

type readonly RequestResponseActionSpec[]

generate_action_event_datas
#

actions/action_codegen.ts view source

(specs: readonly ({ method: string; initiator: "frontend" | "backend" | "both"; side_effects: boolean; input: ZodType<unknown, unknown, $ZodTypeInternals<unknown, unknown>>; output: ZodType<unknown, unknown, $ZodTypeInternals<unknown, unknown>>; ... 6 more ...; rate_limit?: "both" | ... 2 more ... | undefined; } | { ...; } | { ...; })[], imports: ImportBuilder, options?: { ...; } | undefined): string import {generate_action_event_datas} from '@fuzdev/fuz_app/actions/action_codegen.js';

Emit the ActionEventDatas interface — one ActionEvent*Data variant per method, parameterized by the spec's kind:

  • request_response → ActionEventRequestResponseData<method, input, output>
  • remote_notification → ActionEventRemoteNotificationData<method, input>
  • local_call → ActionEventLocalCallData<method, input, output>

Adds the per-kind data type imports (only the kinds that appear in specs).

specs

type readonly ActionSpecUnion[]

imports

options?

type { same_file?: boolean | undefined; collections_path?: string | undefined; include_protocol_actions?: boolean | undefined; }
optional

returns

string

generate_action_inputs_outputs
#

actions/action_codegen.ts view source

(specs: readonly ({ method: string; initiator: "frontend" | "backend" | "both"; side_effects: boolean; input: ZodType<unknown, unknown, $ZodTypeInternals<unknown, unknown>>; output: ZodType<unknown, unknown, $ZodTypeInternals<unknown, unknown>>; ... 6 more ...; rate_limit?: "both" | ... 2 more ... | undefined; } | { ...; } | { ...; })[], imports: ImportBuilder, options?: { ...; } | undefined): string import {generate_action_inputs_outputs} from '@fuzdev/fuz_app/actions/action_codegen.js';

Emit ActionInputs + ActionOutputs runtime consts and matching interfaces. The runtime consts reference specs.{method}_action_spec.input / .output; the interfaces use z.infer.

Adds import {z} from 'zod'; and the * as specs namespace import.

specs

type readonly ActionSpecUnion[]

imports

options?

type { specs_module?: string | undefined; qualify_spec?: ((spec: { method: string; initiator: "frontend" | "backend" | "both"; side_effects: boolean; input: ZodType<unknown, unknown, $ZodTypeInternals<unknown, unknown>>; ... 7 more ...; rate_limit?: "both" | ... 2 more ... | undefined; } | { ...; } | { ...; }) => string)...
optional

returns

string

generate_action_method_enum_block
#

actions/action_codegen.ts view source

(specs: readonly ({ method: string; initiator: "frontend" | "backend" | "both"; side_effects: boolean; input: ZodType<unknown, unknown, $ZodTypeInternals<unknown, unknown>>; output: ZodType<unknown, unknown, $ZodTypeInternals<unknown, unknown>>; ... 6 more ...; rate_limit?: "both" | ... 2 more ... | undefined; } | { ...; } | { ...; })[], imports: ImportBuilder, options: { ...; }): string import {generate_action_method_enum_block} from '@fuzdev/fuz_app/actions/action_codegen.js';

Emit a single named z.enum([...]) + z.infer block for an arbitrary spec subset. Lower-level escape hatch from generate_action_method_enums — for cross-product or domain-specific enums the built-in discriminator doesn't cover.

Mirrors the built-in helper's contract: protocol actions filtered by default, empty subsets return '' (skip rather than emit z.enum([])), zod import registered idempotently only when at least one method qualifies.

The cross-product space is open-ended; rather than grow the ActionMethodEnumKind discriminator one cross-product at a time, callers own the subset shape — name, jsdoc, predicate.

specs

type readonly ActionSpecUnion[]

imports

options

type { name: string; jsdoc: string; predicate: (spec: { method: string; initiator: "frontend" | "backend" | "both"; side_effects: boolean; input: ZodType<unknown, unknown, $ZodTypeInternals<unknown, unknown>>; ... 7 more ...; rate_limit?: "both" | ... 2 more ... | undefined; } | { ...; } | { ...; }) => boolean; include_p...

returns

string

generate_action_method_enums
#

actions/action_codegen.ts view source

(specs: readonly ({ method: string; initiator: "frontend" | "backend" | "both"; side_effects: boolean; input: ZodType<unknown, unknown, $ZodTypeInternals<unknown, unknown>>; output: ZodType<unknown, unknown, $ZodTypeInternals<unknown, unknown>>; ... 6 more ...; rate_limit?: "both" | ... 2 more ... | undefined; } | { ...; } | { ...; })[], imports: ImportBuilder, options?: { ...; } | undefined): string import {generate_action_method_enums} from '@fuzdev/fuz_app/actions/action_codegen.js';

Emit one or more z.enum([...]) declarations for action method names — ActionMethod, RequestResponseActionMethod, RemoteNotificationActionMethod, LocalCallActionMethod, FrontendActionMethod, BackendActionMethod, FrontendRequestResponseMethod, BackendRequestResponseMethod, BroadcastActionMethod. Pairs each runtime const with a z.infer type alias under the same identifier.

Protocol-action methods (heartbeat, cancel) are filtered out by default — pass include_protocol_actions: true if a consumer genuinely wants them on their typed surface. Empty kinds are skipped so the helper never emits z.enum([]) (zod runtime-throws on that).

Adds import {z} from 'zod'; to imports only when at least one block is emitted (idempotent).

For genuinely cross-product enums the discriminator doesn't cover, use generate_action_method_enum_block — caller owns the predicate, name, and jsdoc.

specs

type readonly ActionSpecUnion[]

imports

options?

type { emit?: ReadonlySet<ActionMethodEnumKind> | undefined; include_protocol_actions?: boolean | undefined; }
optional

returns

string

generate_action_specs_record
#

actions/action_codegen.ts view source

(specs: readonly ({ method: string; initiator: "frontend" | "backend" | "both"; side_effects: boolean; input: ZodType<unknown, unknown, $ZodTypeInternals<unknown, unknown>>; output: ZodType<unknown, unknown, $ZodTypeInternals<unknown, unknown>>; ... 6 more ...; rate_limit?: "both" | ... 2 more ... | undefined; } | { ...; } | { ...; })[], imports: ImportBuilder, options?: { ...; } | undefined): string import {generate_action_specs_record} from '@fuzdev/fuz_app/actions/action_codegen.js';

Emit the ActionSpecs runtime const + interface + the `action_specs: Array<ActionSpecUnion> value bundling every spec. Adds the * as specs` namespace import + the ActionSpecUnion type import.

specs

type readonly ActionSpecUnion[]

imports

options?

type { specs_module?: string | undefined; qualify_spec?: ((spec: { method: string; initiator: "frontend" | "backend" | "both"; side_effects: boolean; input: ZodType<unknown, unknown, $ZodTypeInternals<unknown, unknown>>; ... 7 more ...; rate_limit?: "both" | ... 2 more ... | undefined; } | { ...; } | { ...; }) => string)...
optional

returns

string

generate_actions_api_method_signature
#

actions/action_codegen.ts view source

(spec: { method: string; initiator: "frontend" | "backend" | "both"; side_effects: boolean; input: ZodType<unknown, unknown, $ZodTypeInternals<unknown, unknown>>; output: ZodType<unknown, unknown, $ZodTypeInternals<unknown, unknown>>; ... 6 more ...; rate_limit?: "both" | ... 2 more ... | undefined; } | { ...; } | { ...; }, imports: ImportBuilder, options?: { ...; } | undefined): string import {generate_actions_api_method_signature} from '@fuzdev/fuz_app/actions/action_codegen.js';

Generates one method line of the typed FrontendActionsApi interface for a single spec. Encapsulates the input/options/return-type signature shape so the surface evolves in one place when fields like signal or transport_name are added to per-call options.

Async methods (request_response, remote_notification, async local_call) get an optional second options?: RpcClientCallOptions arg ({signal?, transport_name?, queue?}) and a Promise<Result<...>> return type. Sync local_call methods omit the options arg — signal can't cooperatively interrupt a synchronous handler and there's no transport to select. remote_notification is async because create_remote_notification_method returns a Promise that resolves to a Result<{value: void}> (success) or Result<{error}> (transport send failure). Earlier emit shapes declared notifications as => void — regenerate consumer typed clients to pick up the corrected return.

Registers exactly the imports the emitted line references on imports: ActionInputs (when the spec has input), ActionOutputs (always), RpcClientCallOptions (async only), and Result + JsonrpcErrorObject (any return shape that wraps the value in Result<{value}, {error}> — every async method, plus sync local_call when `sync_returns_value: false). Mirrors the leaf-level pattern get_handler_return_type` already follows so wrappers no longer pre-register imports a per-spec emit might not actually use.

Optional-input detection. The emitted parameter is input?: (caller may omit the argument) when either (a) the schema accepts undefined — z.optional(z.strictObject(...)) and similar wrappers — or (b) the schema accepts the empty object {} — `z.strictObject({acting: ActingActor})` and other all-optional-fields strict objects. The second probe mirrors the dispatcher's HTTP convention (raw_params ?? {} for non-z.void() schemas in actions/action_rpc.ts / http/route_spec.ts): if a request with no params reaches the handler, this is the value the schema is asked to validate. A schema with required fields fails both probes and stays input: (required at the typed surface). Refinements and transforms run as part of safeParse, so their accept/reject decisions feed into the optional/required choice naturally.

spec

imports

options?

type { sync_returns_value?: boolean | undefined; collections_path?: string | undefined; }
optional

returns

string

one line like foo: (input: ActionInputs['foo'], options?: RpcClientCallOptions) => Promise<Result<...>>;

generate_api_token
#

auth/api_token.ts view source

(): { token: string; id: string; token_hash: string; } import {generate_api_token} from '@fuzdev/fuz_app/auth/api_token.js';

Generate a new API token with its hash and public id.

The raw token is returned exactly once — callers must present it to the user immediately.

returns

{ token: string; id: string; token_hash: string; }

the raw token, a public id, and the blake3 hash for storage

generate_app_surface
#

http/surface.ts view source

(options: GenerateAppSurfaceOptions): AppSurface import {generate_app_surface} from '@fuzdev/fuz_app/http/surface.js';

Generate a JSON-serializable attack surface from middleware, route specs, and optional env/event metadata.

options

returns

AppSurface

generate_backend_action_handlers_map
#

actions/action_codegen.ts view source

(imports: ImportBuilder, options?: { type_name?: string | undefined; method_enum_name?: string | undefined; context_type?: string | undefined; collections_path?: string | undefined; metatypes_path?: string | undefined; } | undefined): string import {generate_backend_action_handlers_map} from '@fuzdev/fuz_app/actions/action_codegen.js';

Emit the BackendActionHandlers mapped type — one entry per BackendRequestResponseMethod, each (input, ctx) => output | Promise<output>. Replaces the hand-maintained Exclude<> + parallel mapped-type pattern (zzz had this at zzz/src/lib/server/zzz_action_handlers.ts:42-66).

The context type is consumer-defined (e.g. zzz's ZzzHandlerContext). Pass context_type to name it; the helper assumes it's importable or defined in the emitted module's scope (consumer's responsibility).

Adds ActionInputs / ActionOutputs type imports from collections_path and the BackendRequestResponseMethod import from metatypes_path.

imports

options?

type { type_name?: string | undefined; method_enum_name?: string | undefined; context_type?: string | undefined; collections_path?: string | undefined; metatypes_path?: string | undefined; }
optional

returns

string

generate_backend_actions_api
#

actions/action_codegen.ts view source

(specs: readonly ({ method: string; initiator: "frontend" | "backend" | "both"; side_effects: boolean; input: ZodType<unknown, unknown, $ZodTypeInternals<unknown, unknown>>; output: ZodType<unknown, unknown, $ZodTypeInternals<unknown, unknown>>; ... 6 more ...; rate_limit?: "both" | ... 2 more ... | undefined; } | { ...; } | { ...; })[], imports: ImportBuilder, options?: { ...; } | undefined): string import {generate_backend_actions_api} from '@fuzdev/fuz_app/actions/action_codegen.js';

Emit BOTH the typed BackendActionsApi interface AND the broadcast_action_specs runtime array. The interface is shaped for create_broadcast_api: backend-initiated remote_notification methods, each (input) => Promise<void>. The array bundles the matching specs as a ReadonlyArray<ActionSpecUnion>.

Filter: kind === 'remote_notification' && initiator !== 'frontend', additionally excluding methods that are the target of another spec's streams field. Streams targets (e.g. completion_progress, ollama_progress) are request-scoped notifications invoked via ctx.notify inside their parent handler — they're never callable through the broadcast API. The discriminator is ActionSpec.streams, not a manual exclusion list.

Adds the * as specs namespace import (from specs_module), the ActionInputs type import (from collections_path), and the ActionSpecUnion type import.

Method signature shape today is (input) => Promise<void> — matches the fire-and-forget runtime of create_broadcast_api. Generalizing per-kind via generate_actions_api_method_signature is deferred until a second backend runtime constructor lands.

specs

type readonly ActionSpecUnion[]

imports

options?

type { specs_module?: string | undefined; collections_path?: string | undefined; qualify_spec?: ((spec: { method: string; initiator: "frontend" | "backend" | "both"; side_effects: boolean; ... 8 more ...; rate_limit?: "both" | ... 2 more ... | undefined; } | { ...; } | { ...; }) => string) | undefined; include_protocol_a...
optional

returns

string

generate_daemon_token
#

auth/daemon_token.ts view source

(): string import {generate_daemon_token} from '@fuzdev/fuz_app/auth/daemon_token.js';

Generate a new daemon token (256-bit random, base64url).

returns

string

a 43-character base64url string

generate_frontend_action_handlers
#

actions/action_codegen.ts view source

(specs: readonly ({ method: string; initiator: "frontend" | "backend" | "both"; side_effects: boolean; input: ZodType<unknown, unknown, $ZodTypeInternals<unknown, unknown>>; output: ZodType<unknown, unknown, $ZodTypeInternals<unknown, unknown>>; ... 6 more ...; rate_limit?: "both" | ... 2 more ... | undefined; } | { ...; } | { ...; })[], imports: ImportBuilder, options?: { ...; } | undefined): string import {generate_frontend_action_handlers} from '@fuzdev/fuz_app/actions/action_codegen.js';

Emit the FrontendActionHandlers interface — wraps generate_phase_handlers with the TypedActionEvent action-event type and standard 1-tab per-method indentation. Pairs with generate_typed_action_event_alias (emits the matching TypedActionEvent alias) — call both in the same gen producer.

specs

type readonly ActionSpecUnion[]

imports

options?

type { collections_path?: string | undefined; include_protocol_actions?: boolean | undefined; }
optional

returns

string

generate_frontend_actions_api
#

actions/action_codegen.ts view source

(specs: readonly ({ method: string; initiator: "frontend" | "backend" | "both"; side_effects: boolean; input: ZodType<unknown, unknown, $ZodTypeInternals<unknown, unknown>>; output: ZodType<unknown, unknown, $ZodTypeInternals<unknown, unknown>>; ... 6 more ...; rate_limit?: "both" | ... 2 more ... | undefined; } | { ...; } | { ...; })[], imports: ImportBuilder, options?: { ...; } | undefined): string import {generate_frontend_actions_api} from '@fuzdev/fuz_app/actions/action_codegen.js';

Emit the FrontendActionsApi interface — one method signature per spec via generate_actions_api_method_signature. Optionally filter the spec set (e.g. omit additional methods alongside the default protocol-action filter) via method_filter.

Imports are registered by the leaf generate_actions_api_method_signature per emitted line — only what the spec set actually references shows up on imports. A spec set with no async methods skips RpcClientCallOptions; one with no inputs skips ActionInputs; sync local_call methods with sync_returns_value: true (the default) skip Result / JsonrpcErrorObject.

The interface name is fixed at FrontendActionsApi — the symmetric counterpart of BackendActionsApi. Earlier consumer-named variants (MyActionsApi, VisionesActionsApi) were retired in API review III to make the side-of-the-wire intent visible at every call site. If a consumer needs a different name they hand-roll the interface (the helper's job is the standard symmetric shape).

specs

type readonly ActionSpecUnion[]

imports

options?

type { method_filter?: ((spec: { method: string; initiator: "frontend" | "backend" | "both"; side_effects: boolean; input: ZodType<unknown, unknown, $ZodTypeInternals<unknown, unknown>>; ... 7 more ...; rate_limit?: "both" | ... 2 more ... | undefined; } | { ...; } | { ...; }) => boolean) | undefined; collections_path?: ...
optional

returns

string

generate_input_test_cases
#

testing/adversarial_input.ts view source

(input_schema: ZodType<unknown, unknown, $ZodTypeInternals<unknown, unknown>>): InputTestCase[] import {generate_input_test_cases} from '@fuzdev/fuz_app/testing/adversarial_input.js';

Generate adversarial test cases for a route's input schema.

Produces focused, non-redundant cases:

  • Whole-body: send array instead of object, extra unknown key
  • Missing required fields (without defaults)
  • One wrong-type value per field
  • Null for required non-nullable fields
  • One format violation per constrained field

input_schema

type ZodType<unknown, unknown, $ZodTypeInternals<unknown, unknown>>

returns

InputTestCase[]

throws

  • Error - if the seed body built by `generate_valid_value` fails `input_schema.safeParse` — surfaces broken generation logic with the Zod issues path rather than producing nonsense adversarial cases.

generate_params_test_cases
#

testing/adversarial_input.ts view source

(params_schema: ZodObject<$ZodLooseShape, $strip>): ParamsTestCase[] import {generate_params_test_cases} from '@fuzdev/fuz_app/testing/adversarial_input.js';

Generate adversarial test cases for a route's params schema.

Params are always strings from URL segments. Only generates cases for format-constrained fields (uuid, pattern) since unconstrained string params accept any string value.

params_schema

type ZodObject<$ZodLooseShape, $strip>

returns

ParamsTestCase[]

generate_phase_handlers
#

actions/action_codegen.ts view source

(spec: { method: string; initiator: "frontend" | "backend" | "both"; side_effects: boolean; input: ZodType<unknown, unknown, $ZodTypeInternals<unknown, unknown>>; output: ZodType<unknown, unknown, $ZodTypeInternals<unknown, unknown>>; ... 6 more ...; rate_limit?: "both" | ... 2 more ... | undefined; } | { ...; } | { ...; }, executor: "frontend" | "backend", imports: ImportBuilder, options?: { ...; } | undefined): string import {generate_phase_handlers} from '@fuzdev/fuz_app/actions/action_codegen.js';

Generates the phase handlers for an action spec using the unified ActionEvent type with the new phase/step type parameters.

Returns '' when the spec contributes no phases on the given executor side (e.g. a backend-only local_call asked for 'frontend'). Upstream wrappers compose blocks with .filter(Boolean) so empty entries are dropped from the generated handler map. The earlier shape was ${method}?: never, which read as "calling this here is a type error" but in practice produced useless rows on FrontendActionHandlers for methods that don't belong on this side at all — drop the row instead so the typed surface only carries methods the executor actually handles.

spec

executor

type "frontend" | "backend"

imports

options?

type { action_event_type?: string | undefined; collections_path?: string | undefined; }
optional

returns

string

generate_position_styles
#

ui/position_helpers.ts view source

(position?: Position, align?: Alignment, offset?: string): Record<string, string> import {generate_position_styles} from '@fuzdev/fuz_app/ui/position_helpers.js';

Generates CSS positioning styles for UI elements.

position

default 'center'

align

default 'center'

offset

distance from the position (CSS value)

type string
default '0'

returns

Record<string, string>

throws

  • UnreachableError - if `position` is not a known `Position` value

generate_query_test_cases
#

testing/adversarial_input.ts view source

(query_schema: ZodObject<$ZodLooseShape, $strip>): QueryTestCase[] import {generate_query_test_cases} from '@fuzdev/fuz_app/testing/adversarial_input.js';

Generate adversarial test cases for a route's query schema.

Query params are always strings from the URL. Generates cases for:

  • Missing required fields
  • Format violations on constrained fields (uuid, pattern)

query_schema

type ZodObject<$ZodLooseShape, $strip>

returns

QueryTestCase[]

generate_random_base64url
#

crypto.ts view source

(byte_length?: number): string import {generate_random_base64url} from '@fuzdev/fuz_app/crypto.js';

Generate a cryptographically random base64url string.

byte_length

number of random bytes (default 32 = 256 bits)

type number
default 32

returns

string

base64url-encoded string without padding

generate_random_key
#

dev/setup.ts view source

(deps: CommandDeps): Promise<string> import {generate_random_key} from '@fuzdev/fuz_app/dev/setup.js';

Generate a random base64 key using openssl.

deps

command execution capability

returns

Promise<string>

a random 32-byte base64-encoded key

throws

  • Error - if `openssl rand` fails or is unavailable

generate_session_token
#

auth/session_queries.ts view source

(): string import {generate_session_token} from '@fuzdev/fuz_app/auth/session_queries.js';

Generate a cryptographically random session token.

returns

string

a 32-byte base64url-encoded token

generate_typed_action_event_alias
#

actions/action_codegen.ts view source

(imports: ImportBuilder, options?: { collections_path?: string | undefined; metatypes_path?: string | undefined; } | undefined): string import {generate_typed_action_event_alias} from '@fuzdev/fuz_app/actions/action_codegen.js';

Emit the fixed-shape TypedActionEvent alias used by FrontendActionHandlers to narrow ActionEvent.data against the consumer's generated ActionEventDatas map. Registers the four fuz_app type imports it needs (ActionEvent, ActionEventPhase, ActionEventStep, ActionEventDatas) plus the ActionMethod type import — sourced from collections_path and metatypes_path respectively.

Pair with generate_action_method_enums (emits ActionMethod into metatypes_path) and generate_action_event_datas (emits ActionEventDatas into collections_path).

imports

options?

type { collections_path?: string | undefined; metatypes_path?: string | undefined; }
optional

returns

string

generate_valid_body
#

testing/schema_generators.ts view source

(input_schema: ZodType<unknown, unknown, $ZodTypeInternals<unknown, unknown>>): Record<string, unknown> | undefined import {generate_valid_body} from '@fuzdev/fuz_app/testing/schema_generators.js';

Generate a valid request body for a route's input schema.

input_schema

type ZodType<unknown, unknown, $ZodTypeInternals<unknown, unknown>>

returns

Record<string, unknown> | undefined

a generated body that passes safeParse, or undefined for null / non-object schemas

throws

  • Error - if the generated body fails `input_schema.safeParse` — catches broken `generate_valid_value` logic early with a descriptive Zod-issues summary instead of a confusing 400 in downstream tests.

generate_valid_value
#

testing/schema_generators.ts view source

(field: ZodFieldInfo, field_schema: ZodType<unknown, unknown, $ZodTypeInternals<unknown, unknown>>): unknown import {generate_valid_value} from '@fuzdev/fuz_app/testing/schema_generators.js';

Generate a valid-ish value for a field based on its base type.

field

field_schema

type ZodType<unknown, unknown, $ZodTypeInternals<unknown, unknown>>

returns

unknown

GenerateAppSurfaceOptions
#

http/surface.ts view source

GenerateAppSurfaceOptions import type {GenerateAppSurfaceOptions} from '@fuzdev/fuz_app/http/surface.js';

route_specs

type RouteSpec[]

middleware_specs

type MiddlewareSpec[]

env_schema?

type ZodObject<$ZodLooseShape, $strip>

event_specs?

type EventSpec[]

rpc_endpoints?

type RpcEndpointSpec[]

ws_endpoints?

Mounted WS endpoints (the same array create_app_server.ws_endpoints auto-mounts). Each entry's actions surface into AppSurface.ws_endpoints[i].methods for attack-surface tests + startup logging. Each carries its effective allowed_origins — resolve_ws_endpoints fills in the server's list where a spec omits it — so the surface records the exact patterns the upgrade gate matches.

type readonly ResolvedWsEndpointSpec[]

get_app_dir
#

cli/config.ts view source

(runtime: Pick<EnvDeps, "env_get">, name: string): string | null import {get_app_dir} from '@fuzdev/fuz_app/cli/config.js';

Get the CLI config directory path (~/.{name}).

runtime

runtime with env_get capability

type Pick<EnvDeps, "env_get">

name

application name (e.g., "tx", "zzz")

type string

returns

string | null

path to config directory, or null if $HOME is not set

get_audit_metadata
#

auth/audit_log_schema.ts view source

<T extends AuditEventType>(event: AuditLogEvent & { event_type: T; }): AuditMetadataMap[T] | null import {get_audit_metadata} from '@fuzdev/fuz_app/auth/audit_log_schema.js';

Narrow metadata type for a known event type.

Use after checking event_type to get typed metadata access.

event

type AuditLogEvent & { event_type: T; }

returns

AuditMetadataMap[T] | null

generics

get_audit_metadata<T extends AuditEventType>
T
constraint AuditEventType

get_audit_metadata_validation_failures
#

auth/audit_log_queries.ts view source

(): number import {get_audit_metadata_validation_failures} from '@fuzdev/fuz_app/auth/audit_log_queries.js';

Number of audit metadata validation failures observed since process start.

returns

number

get_audit_unknown_event_type_failures
#

auth/audit_log_queries.ts view source

(): number import {get_audit_unknown_event_type_failures} from '@fuzdev/fuz_app/auth/audit_log_queries.js';

Number of audit unknown-event-type failures observed since process start.

returns

number

get_audit_write_failures
#

auth/audit_emitter.ts view source

(): number import {get_audit_write_failures} from '@fuzdev/fuz_app/auth/audit_emitter.js';

Number of audit rows an emitter failed to write since process start.

returns

number

get_client_ip
#

http/client_ip.ts view source

(c: Context<any, any, {}>): string import {get_client_ip} from '@fuzdev/fuz_app/http/client_ip.js';

Client IP resolved by the trusted-proxy middleware, or 'unknown' if unset.

c

type Context<any, any, {}>

returns

string

get_config_path
#

cli/config.ts view source

(runtime: Pick<EnvDeps, "env_get">, name: string): string | null import {get_config_path} from '@fuzdev/fuz_app/cli/config.js';

Get the CLI config file path (~/.{name}/config.json).

runtime

runtime with env_get capability

type Pick<EnvDeps, "env_get">

name

application name

type string

returns

string | null

path to config.json, or null if $HOME is not set

get_daemon_info_path
#

cli/daemon.ts view source

(runtime: Pick<EnvDeps, "env_get">, name: string): string | null import {get_daemon_info_path} from '@fuzdev/fuz_app/cli/daemon.js';

Get the daemon info file path (~/.{name}/run/daemon.json).

runtime

runtime with env_get capability

type Pick<EnvDeps, "env_get">

name

application name

type string

returns

string | null

path to daemon.json, or null if $HOME is not set

get_daemon_token_path
#

testing/daemon_token_rotation.ts view source

(runtime: Pick<EnvDeps, "env_get">, name: string): string | null import {get_daemon_token_path} from '@fuzdev/fuz_app/testing/daemon_token_rotation.js';

Get the daemon token file path (~/.{name}/run/daemon_token).

runtime

runtime with env_get capability

type Pick<EnvDeps, "env_get">

name

application name

type string

returns

string | null

path to daemon_token, or null if $HOME is not set

get_datetime_now
#

primitive_schemas.ts view source

(): string & $brand<"Datetime"> import {get_datetime_now} from '@fuzdev/fuz_app/primitive_schemas.js';

Returns an ISO 8601 datetime string for the current time.

returns

Datetime

get_executor_phases
#

actions/action_codegen.ts view source

(spec: { method: string; initiator: "frontend" | "backend" | "both"; side_effects: boolean; input: ZodType<unknown, unknown, $ZodTypeInternals<unknown, unknown>>; output: ZodType<unknown, unknown, $ZodTypeInternals<unknown, unknown>>; ... 6 more ...; rate_limit?: "both" | ... 2 more ... | undefined; } | { ...; } | { ...; }, executor: "frontend" | "backend"): ("send_request" | ... 7 more ... | "execute")[] import {get_executor_phases} from '@fuzdev/fuz_app/actions/action_codegen.js';

Phases an executor can handle for the given spec — kind + initiator → set of phases.

spec

executor

type "frontend" | "backend"

returns

ActionEventPhase[]

get_handler_return_type
#

actions/action_codegen.ts view source

(spec: { method: string; initiator: "frontend" | "backend" | "both"; side_effects: boolean; input: ZodType<unknown, unknown, $ZodTypeInternals<unknown, unknown>>; output: ZodType<unknown, unknown, $ZodTypeInternals<unknown, unknown>>; ... 6 more ...; rate_limit?: "both" | ... 2 more ... | undefined; } | { ...; } | { ...; }, phase: "send_request" | ... 7 more ... | "execute", imports: ImportBuilder, collections_path?: string): string import {get_handler_return_type} from '@fuzdev/fuz_app/actions/action_codegen.js';

Gets the handler return type for a specific phase and spec. Adds an ActionOutputs import (from collections_path) when the phase carries an output (request_response receive_request, local_call execute).

spec

phase

imports

collections_path

type string
default DEFAULT_COLLECTIONS_PATH

returns

string

get_initial_phase
#

actions/action_event_helpers.ts view source

(kind: "request_response" | "remote_notification" | "local_call", initiator: "frontend" | "backend" | "both", executor: "frontend" | "backend"): "send_request" | "receive_request" | ... 7 more ... | null import {get_initial_phase} from '@fuzdev/fuz_app/actions/action_event_helpers.js';

kind

initiator

executor

returns

"send_request" | "receive_request" | "send_response" | "receive_response" | "send_error" | "receive_error" | "send" | "receive" | "execute" | null

get_request_context
#

auth/request_context.ts view source

(c: Context<any, any, {}>): RequestContext | null import {get_request_context} from '@fuzdev/fuz_app/auth/request_context.js';

Get the request context from a Hono context, or null if unauthenticated.

c

the Hono context

type Context<any, any, {}>

returns

RequestContext | null

the request context, or null

get_resolved_auth
#

auth/resolved_auth.ts view source

(c: Context<any, any, {}>): ResolvedAuth | null import {get_resolved_auth} from '@fuzdev/fuz_app/auth/resolved_auth.js';

Gather the credential the auth middleware resolved for this request.

Reads the keys the session, bearer, and daemon-token middleware set and keeps only the one that belongs to the winning credential type — a daemon token overrides a session cookie sent alongside it without clearing that session's hash, so the hash is carried for a session credential alone.

c

the request context, after the auth middleware chain

type Context<any, any, {}>

returns

ResolvedAuth | null

the resolved credential, or null for an unauthenticated request

get_route_error_schema
#

testing/assertions.ts view source

(lookup: Map<string, Partial<Record<number, ZodType<unknown, unknown, $ZodTypeInternals<unknown, unknown>>>>>, route: AppSurfaceRoute, status: number): ZodType<...> | undefined import {get_route_error_schema} from '@fuzdev/fuz_app/testing/assertions.js';

Look up the merged error schema for a route+status from a pre-built schema lookup.

lookup

map from "METHOD /path" to merged error schemas

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

route

the surface route to look up

status

HTTP status code

type number

returns

ZodType<unknown, unknown, $ZodTypeInternals<unknown, unknown>> | undefined

the Zod schema for that route+status, or undefined when no error schema is declared for the status code

get_route_input
#

http/route_spec.ts view source

<S extends z.ZodType>(c: Context<any, any, {}>, schema: S): output<S> import {get_route_input} from '@fuzdev/fuz_app/http/route_spec.js';

Get validated input from the Hono context.

Call after the input validation middleware has run. Pass the route's input Zod schema to infer the typed shape directly:

const input = get_route_input(c, my_route_spec.input);

Or pass an explicit type argument when the schema isn't in scope:

const input = get_route_input<MyInput>(c);

c

type Context<any, any, {}>

schema

type S

overloads

<S extends z.ZodType>(c: Context<any, any, {}>, schema: S): output<S>

Get validated input from the Hono context.

Call after the input validation middleware has run. Pass the route's input Zod schema to infer the typed shape directly:

const input = get_route_input(c, my_route_spec.input);

Or pass an explicit type argument when the schema isn't in scope:

const input = get_route_input<MyInput>(c);
c Context<any, any, {}>
schema S
returns output<S>
<T = unknown>(c: Context<any, any, {}>): T
c Context<any, any, {}>
returns T

returns

output<S>

generics

get_route_input<S extends z.ZodType>
S
constraint z.ZodType

get_route_params
#

http/route_spec.ts view source

<S extends z.ZodType>(c: Context<any, any, {}>, schema: S): output<S> import {get_route_params} from '@fuzdev/fuz_app/http/route_spec.js';

Get validated URL path params from the Hono context.

Call after the params validation middleware has run. Pass the route's params schema to infer the typed shape, or supply an explicit type argument. See get_route_input for the two call shapes.

c

type Context<any, any, {}>

schema

type S

overloads

<S extends z.ZodType>(c: Context<any, any, {}>, schema: S): output<S>

Get validated URL path params from the Hono context.

Call after the params validation middleware has run. Pass the route's params schema to infer the typed shape, or supply an explicit type argument. See get_route_input for the two call shapes.

c Context<any, any, {}>
schema S
returns output<S>
<T = unknown>(c: Context<any, any, {}>): T
c Context<any, any, {}>
returns T

returns

output<S>

generics

get_route_params<S extends z.ZodType>
S
constraint z.ZodType

get_route_query
#

http/route_spec.ts view source

<S extends z.ZodType>(c: Context<any, any, {}>, schema: S): output<S> import {get_route_query} from '@fuzdev/fuz_app/http/route_spec.js';

Get validated URL query params from the Hono context.

Call after the query validation middleware has run. Pass the route's query schema to infer the typed shape, or supply an explicit type argument. See get_route_input for the two call shapes.

c

type Context<any, any, {}>

schema

type S

overloads

<S extends z.ZodType>(c: Context<any, any, {}>, schema: S): output<S>

Get validated URL query params from the Hono context.

Call after the query validation middleware has run. Pass the route's query schema to infer the typed shape, or supply an explicit type argument. See get_route_input for the two call shapes.

c Context<any, any, {}>
schema S
returns output<S>
<T = unknown>(c: Context<any, any, {}>): T
c Context<any, any, {}>
returns T

returns

output<S>

generics

get_route_query<S extends z.ZodType>
S
constraint z.ZodType

get_session_cookie
#

grant_key
#

ui/admin_accounts_state.svelte.ts view source

(account_id: string & $brand<"Uuid">, role: string, to_actor_id?: (string & $brand<"Uuid">) | null | undefined): string import {grant_key} from '@fuzdev/fuz_app/ui/admin_accounts_state.svelte.js';

Compose the grant keyed-slot key for an offer. Account-grain offers key on ${account_id}:${role}; actor-targeted offers add the actor suffix so the two variants can be in flight simultaneously without colliding on per-row spinners.

account_id

type Uuid

role

type string

to_actor_id?

type Uuid | null
optional

returns

string

GRANT_PATH_ADMIN
#

auth/grant_path_schema.ts view source

"admin" import {GRANT_PATH_ADMIN} from '@fuzdev/fuz_app/auth/grant_path_schema.js';

Admin-mediated grant — role_grant_offer_create plus admin-direct flows.

GRANT_PATH_BOOTSTRAP
#

auth/grant_path_schema.ts view source

"bootstrap" import {GRANT_PATH_BOOTSTRAP} from '@fuzdev/fuz_app/auth/grant_path_schema.js';

Bootstrap grant — one-shot flow during the keep's first-run bootstrap.

GRANT_PATH_NAME_REGEX
#

GRANT_PATH_SELF_SERVICE
#

auth/grant_path_schema.ts view source

"self_service" import {GRANT_PATH_SELF_SERVICE} from '@fuzdev/fuz_app/auth/grant_path_schema.js';

Self-service grant — caller toggles their own role_grant via self_service_role_set.

GRANT_PATH_SYSTEM
#

auth/grant_path_schema.ts view source

"system" import {GRANT_PATH_SYSTEM} from '@fuzdev/fuz_app/auth/grant_path_schema.js';

System-mediated grant — signup hooks, automation, internal service flows.

GRANT_SCOPE_PAIRED_MESSAGE
#

auth/role_grant_offer_action_specs.ts view source

"'scope_kind' and 'scope_id' must be given together (both for a scoped grant, neither for a global one)" import {GRANT_SCOPE_PAIRED_MESSAGE} from '@fuzdev/fuz_app/auth/role_grant_offer_action_specs.js';

The message a half-scoped grant input fails with — the grant inputs' refine, and the UI's pre-RPC guard.

GrantJson
#

auth/cell_grant_action_specs.ts view source

value + type

{ id: string & $brand<"Uuid">; cell_id: string & $brand<"Uuid">; level: "viewer" | "editor"; actor_id: (string & $brand<"Uuid">) | null; role: string | null; scope_id: (string & $brand<...>) | null; granted_by: (string & $brand<...>) | null; created_at: string; } import {GrantJson} from '@fuzdev/fuz_app/auth/cell_grant_action_specs.js';

Wire-format for a cell_grant row. Mirrors CellJson's shape — ISO-string created_at, branded UUIDs, principal columns surfaced as-is. Caller inspects actor_id xor role to render the right principal label.

id

type string & $brand<"Uuid">

cell_id

type string & $brand<"Uuid">

level

type "viewer" | "editor"

actor_id

type (string & $brand<"Uuid">) | null

role

type string | null

scope_id

type (string & $brand<"Uuid">) | null

granted_by

type (string & $brand<"Uuid">) | null

created_at

type string

GrantPathMeta
#

auth/grant_path_schema.ts view source

GrantPathMeta import type {GrantPathMeta} from '@fuzdev/fuz_app/auth/grant_path_schema.js';

Per-grant-path metadata. description is admin-UI-facing copy (mirrors RoleSpec.description and ScopeKindMeta.description). Open shape so v2 can extend without a breaking change.

description?

type string

GrantPathName
#

GrantPathSchemaResult
#

auth/grant_path_schema.ts view source

GrantPathSchemaResult import type {GrantPathSchemaResult} from '@fuzdev/fuz_app/auth/grant_path_schema.js';

The result of create_grant_path_schema — a Zod schema and metadata map.

GrantPath

Zod schema that validates grant-path name strings against the registered set (builtins + consumer-declared). Use at I/O boundaries (admin UIs, codegen) and as the construction-time check inside create_role_schema for every RoleSpec.grant_paths entry.

type ZodType<string, unknown, $ZodTypeInternals<string, unknown>>

grant_paths

Map of every registered grant-path to its metadata. Keyed by name. Read at startup by admin / codegen surfaces.

type ReadonlyMap<string, GrantPathMeta>

HandlerForSpec
#

actions/action_rpc.ts view source

HandlerForSpec<TSpec> import type {HandlerForSpec} from '@fuzdev/fuz_app/actions/action_rpc.js';

Conditional handler shape for rpc_action — picks the narrowest ctx.auth type the dispatcher's runtime guarantee allows:

  • auth.actor === 'required' → ActorActionHandler (ctx.auth: RequestActorContext).
  • auth.account === 'required' && auth.actor === 'none' → AuthActionHandler (ctx.auth: RequestContext).
  • else (public, optional axes) → ActionHandler (ctx.auth: RequestContext | null).

The bracketed form [T] extends ['required'] defeats distributive conditionals so a degraded AuthAxisState union (when the spec was typed without preserving its literal) falls through to the loosest tier instead of collapsing to the narrowest.

generics

HandlerForSpec<TSpec extends RequestResponseActionSpec>
TSpec

has_any_scoped_role
#

auth/request_context.ts view source

(ctx: RequestContext | null, roles: readonly string[], scope_id: string | null, now?: Date): boolean import {has_any_scoped_role} from '@fuzdev/fuz_app/auth/request_context.js';

Whether the request context holds an active role_grant for any role in roles at scope_id. Empty roles short-circuits to false — documents intent at the call site ("zero roles trivially admit no-one"). Same scope and null-tolerance semantics as has_scoped_role.

ctx

the request context, or null for unauthenticated callers

type RequestContext | null

roles

the roles that would admit the caller (any-of)

type readonly string[]

scope_id

the scope to check (null for global)

type string | null

now

current time (defaults to new Date(), pass for testability)

type Date
default new Date()

returns

boolean

true iff the actor holds an active role_grant for any role in roles at the requested scope

has_global_role
#

auth/account_schema.ts view source

(role_grants: readonly { role: string; scope_id: string | null; revoked_at?: string | null | undefined; expires_at: string | null; }[], role: string, now?: Date): boolean import {has_global_role} from '@fuzdev/fuz_app/auth/account_schema.js';

Whether role_grants holds a live global grant of role — scope_id is null and the grant passes is_role_grant_active.

This is the question a role gate asks. A scoped grant confers its role on one resource only, and a scoped grant of a builtin role (admin, keeper) confers nothing, so matching on the role name alone would read a grant for one resource as authority everywhere. Server-side the same rule is has_scoped_role(ctx, role, null) over a request context and query_account_has_global_role in SQL; this is the form for a bare list of grants — AuthState.role_grants, an admin listing row.

The scope match is scope_id === null, so a grant decoded without the field is not read as global. Client-side the answer shapes the UI and nothing else: the server re-checks every request.

role_grants

the grants to search, e.g. AuthState.role_grants

type readonly { role: string; scope_id: string | null; revoked_at?: string | null | undefined; expires_at: string | null; }[]

role

the role to check

type string

now

current time (defaults to new Date(), pass for testability)

type Date
default new Date()

returns

boolean

true iff a live grant of role with no scope is present

has_role
#

auth/request_context.ts view source

(ctx: RequestContext | null, role: string, now?: Date): boolean import {has_role} from '@fuzdev/fuz_app/auth/request_context.js';

Check if a request context has an active role_grant for a given role at any scope.

Checks the role_grants already loaded in the context (no DB query). Null-tolerant — null ctx (unauthenticated) returns false. Symmetric with has_scoped_role / has_any_scoped_role so the three helpers compose freely in the same predicate.

Scope-blind, so never the gate for a builtin role (admin / keeper) or for any capability that isn't itself scoped: a grant bound to one resource would pass it. Those gates read the global grant through has_scoped_role(ctx, role, null) (e.g. has_scoped_role(auth, ROLE_ADMIN, null) || has_scoped_role(auth, role, scope)).

ctx

the request context, or null for unauthenticated callers

type RequestContext | null

role

the role to check

type string

now

current time (defaults to new Date(), pass for testability and hot-path efficiency)

type Date
default new Date()

returns

boolean

true if the actor has an active role_grant for the role

has_scoped_role
#

auth/request_context.ts view source

(ctx: RequestContext | null, role: string, scope_id: string | null, now?: Date): boolean import {has_scoped_role} from '@fuzdev/fuz_app/auth/request_context.js';

Whether the request context holds an active role_grant for role at scope_id.

Walks the in-memory ctx.role_grants snapshot loaded once per request by the route-spec / RPC dispatcher's authorization phase (when the route declares acting?: ActingActor or has role_grant-requiring auth); zero DB roundtrip per check. The "freshness" framing of a SQL re-query is illusory because the race window is between predicate and the actual mutation, not predicate and authorization load. Closing that race needs a transactional re-check inside the UPDATE/INSERT, which neither style provides.

Null-tolerant — null ctx (unauthenticated) and account-grain contexts (actor: null, empty role_grants) both return false. Same convention as has_role; lets the helper drop into public ({account: 'none', actor: 'none'}) and account-grain ({account: 'required', actor: 'none'}) handlers without a manual narrow. See cell_authorize for the resource-side analog.

has_scoped_role(ctx, ROLE_ADMIN, null) is the admin check everywhere admin confers authority (builtin roles are global-only, see is_builtin_role).

scope_id semantics: in-memory role_grant.scope_id is string | null, so JS === matches the SQL IS NOT DISTINCT FROM semantics exactly:

  • scope_id === null matches global role_grants (scope_id IS NULL).
  • scope_id === '<uuid>' matches role_grants bound to that exact scope.

ctx

the request context, or null for unauthenticated callers

type RequestContext | null

role

the role to check

type string

scope_id

the scope to check (null for global)

type string | null

now

current time (defaults to new Date(), pass for testability and hot-path efficiency)

type Date
default new Date()

returns

boolean

true iff the actor holds an active role_grant for the role at the requested scope

hash_api_token
#

auth/api_token.ts view source

(token: string): string import {hash_api_token} from '@fuzdev/fuz_app/auth/api_token.js';

Hash an API token for storage using blake3.

token

the raw API token

type string

returns

string

hex-encoded blake3 hash

hash_password
#

auth/password_argon2.ts view source

(password: string): Promise<string> import {hash_password} from '@fuzdev/fuz_app/auth/password_argon2.js';

Hash a password using Argon2id.

password

the plaintext password to hash

type string

returns

Promise<string>

the Argon2id hash string

hash_session_token
#

auth/session_queries.ts view source

(token: string): string & $brand<"SessionId"> import {hash_session_token} from '@fuzdev/fuz_app/auth/session_queries.js';

Hash a session token to its storage key using blake3.

The sole minting point for SessionId — hash_blake3 returns bare hex, so the brand is applied here, where the value gains its meaning.

token

the raw session token

type string

returns

SessionId

hex-encoded blake3 hash

headers_to_record
#

testing/rpc_helpers.ts view source

(headers: Headers): Record<string, string> import {headers_to_record} from '@fuzdev/fuz_app/testing/rpc_helpers.js';

Snapshot a Headers object into a plain Record with lowercased keys (Headers iteration normalizes the casing already). Multiple values for one header collapse to the comma-joined form Headers exposes; Set-Cookie is the lone platform exception these call sites never assert on. Lets a response's headers travel back through RpcCallResult for header-level assertions (no-fingerprint, expect.headers).

headers

type Headers

returns

Record<string, string>

heartbeat_action
#

actions/heartbeat.ts view source

Action<{ method: string; initiator: "frontend" | "backend" | "both"; side_effects: boolean; input: ZodType<unknown, unknown, $ZodTypeInternals<unknown, unknown>>; output: ZodType<unknown, unknown, $ZodTypeInternals<unknown, unknown>>; ... 6 more ...; rate_limit?: "both" | ... 2 more ... | undefined; } | { ...; } | {...

type Action<ActionSpecUnion>

import {heartbeat_action} from '@fuzdev/fuz_app/actions/heartbeat.js';

Protocol-action tuple — spread into the server's actions array for dispatch (or via protocol_actions from actions/protocol.ts) so the dispatcher resolves the heartbeat handler. The frontend-side spread happens via protocol_action_specs — the client doesn't run the echo handler, but the spec must be in ActionRegistry so create_rpc_client types app.api.heartbeat() against the shared spec.

heartbeat_action_spec
#

actions/heartbeat.ts view source

{ method: string; kind: "request_response"; initiator: "frontend"; auth: { account: "required"; actor: "none"; }; side_effects: false; input: ZodVoid; output: ZodObject<{}, $strict>; async: true; description: string; } import {heartbeat_action_spec} from '@fuzdev/fuz_app/actions/heartbeat.js';

ActionSpec for the shared heartbeat. Account-required, actor-none — upgrade-time auth has already admitted the socket; heartbeats don't need role gating or actor resolution. side_effects: false keeps it orthogonal to state changes.

heartbeat_handler
#

actions/heartbeat.ts view source

(): Record<string, never> import {heartbeat_handler} from '@fuzdev/fuz_app/actions/heartbeat.js';

Handler — nullary echo. Stateless, suitable for high-frequency pings.

returns

Record<string, never>

HEARTBEAT_RECEIVE_TIMEOUT_MIN_FACTOR
#

actions/socket.svelte.ts view source

2 import {HEARTBEAT_RECEIVE_TIMEOUT_MIN_FACTOR} from '@fuzdev/fuz_app/actions/socket.svelte.js';

Minimum ratio of the heartbeat receive timeout to the heartbeat interval.

A heartbeat goes out once a direction has been idle for interval, and the check runs every interval / 2, so on an idle socket it can leave up to 1.5 × interval after the last receive — and its response needs a round trip on top. A receive timeout under 2 × interval can close a healthy idle socket before its heartbeat is answered, so it's clamped up to this.

HelpArgProperty
#

cli/help.ts view source

HelpArgProperty import type {HelpArgProperty} from '@fuzdev/fuz_app/cli/help.js';

One arg as help output shows it, read from an entry of an object schema.

name

The entry's key, _ for positionals.

type string

type

The type the entry accepts, as format_schema_type renders it.

type string

description

The description metadata, or ''.

type string

default

The default, made by calling a with_default_fn's function, or undefined.

type unknown

aliases

The aliases metadata, or [].

type string[]

HelpCategory
#

cli/help.ts view source

HelpCategory<TCategory> import type {HelpCategory} from '@fuzdev/fuz_app/cli/help.js';

Category configuration for help display.

generics

HelpCategory<TCategory extends string = string>
TCategory
constraint string
default string

key

type TCategory

title

type string

HelpGenerator
#

cli/help.ts view source

HelpGenerator import type {HelpGenerator} from '@fuzdev/fuz_app/cli/help.js';

Help generator returned by create_help.

generate_main_help

Generate main help text with all commands grouped by category.

type (): string

returns string

generate_command_help

Generate help text for a specific command.

type (command: string, meta: CommandMeta<string>): string

command

type string

meta

type CommandMeta<string>
returns string

get_help_text

Get help text for a command or main help.

type (command?: string | undefined, subcommand?: string | undefined): string

command?

type string
optional

subcommand?

type string
optional
returns string

HelpOptions
#

cli/help.ts view source

HelpOptions<TCategory> import type {HelpOptions} from '@fuzdev/fuz_app/cli/help.js';

Configuration for create_help.

generics

HelpOptions<TCategory extends string = string>
TCategory
constraint string
default string

name

Application name (e.g., "tx", "zzz").

type string

version

Application version string.

type string

description

Short description for the main help header.

type string

commands

Command registry keyed by command path (e.g., "apply", "daemon start").

type Record<string, CommandMeta<TCategory>>

categories

Category display order for main help.

type HelpCategory<TCategory>[]

examples

Example commands for main help.

type string[]

global_args_schema

fuz_schema object schema for global arguments (shown in all help output).

type AnySchema

use_colors?

Whether to use ANSI colors in output. Defaults to true.

type boolean

http_status_to_jsonrpc_error_code
#

http/jsonrpc_errors.ts view source

(status: number): -32700 | -32600 | -32601 | -32602 | -32603 | (number & $brand<"JsonrpcServerErrorCode">) import {http_status_to_jsonrpc_error_code} from '@fuzdev/fuz_app/http/jsonrpc_errors.js';

Map an HTTP status code to a JSON-RPC error code.

Returns internal_error (-32603) for unrecognized status codes.

status

type number

returns

JsonrpcErrorCode

HTTP_STATUS_TO_JSONRPC_ERROR_CODE
#

http/jsonrpc_errors.ts view source

Record<number, -32700 | -32600 | -32601 | -32602 | -32603 | (number & $brand<"JsonrpcServerErrorCode">)>

type Record<number, JsonrpcErrorCode>

import {HTTP_STATUS_TO_JSONRPC_ERROR_CODE} from '@fuzdev/fuz_app/http/jsonrpc_errors.js';

Maps HTTP status codes to JSON-RPC error codes (reverse mapping).

When multiple error codes map to the same HTTP status (e.g. parse_error and invalid_request both map to 400), the last one wins. Use for best-effort HTTP → JSON-RPC translation.

http_transport
#

testing/rpc_helpers.ts view source

(app: { request: (input: string, init: RequestInit) => Response | Promise<Response>; }): RpcTestTransport import {http_transport} from '@fuzdev/fuz_app/testing/rpc_helpers.js';

Adapt a Hono-style app into an RpcTestTransport.

app

type { request: (input: string, init: RequestInit) => Response | Promise<Response>; }

returns

RpcTestTransport

IdentityParityCrossTestOptions
#

testing/cross_backend/identity_parity.ts view source

IdentityParityCrossTestOptions import type {IdentityParityCrossTestOptions} from '@fuzdev/fuz_app/testing/cross_backend/identity_parity.js';

Options for the identity-primitive parity suite.

setup_test

Per-test fixture producer (in-process or cross-process).

type (): Promise<TestFixtureBase>

readonly
returns Promise<TestFixtureBase>

login_path?

REST login route path. Default /api/account/login (the spine convention).

type string

readonly

signup_path?

REST signup route path. Default /api/account/signup (the spine convention).

type string

readonly

ImportBuilder
#

actions/action_codegen.ts view source

import {ImportBuilder} from '@fuzdev/fuz_app/actions/action_codegen.js';

Manages imports for generated code, building them on demand. Automatically optimizes type-only imports to use import type syntax.

Why this matters:

  • import type statements are completely removed during compilation
  • Mixed imports like import { type A, B } cannot be safely removed
  • This ensures optimal tree-shaking and smaller bundle sizes

examples

const imports = new ImportBuilder(); imports.add_types('./types.ts', 'Foo', 'Bar'); imports.add('./utils.ts', 'helper'); imports.add_type('./utils.ts', 'HelperOptions'); imports.add('./action_specs.ts', '* as specs'); // Generates: // import type {Foo, Bar} from './types.ts'; // import {helper, type HelperOptions} from './utils.ts'; // import * as specs from './action_specs.ts';

imports

type Map<string, Map<string, ImportItem>>

add

Add a value import. Accepts * as ns strings as namespace imports.

type (from: string, what: string): this

from

type string

what

type string
returns this

this for chaining

mutates

  • this — inserts into the internal `imports` map

add_type

Add a type-only import.

type (from: string, what: string): this

from

type string

what

type string
returns this

this for chaining

mutates

  • this — inserts into the internal `imports` map (downgrade to type is suppressed if already registered as a value)

add_many

type (from: string, ...items: string[]): this

from

type string

items

type string[]
returns this

add_types

type (from: string, ...items: string[]): this

from

type string

items

type string[]
returns this

build

Generate the import statements. When every import from a module is a type, emits import type {…} so the whole statement disappears at compile time.

type (): string

returns string

has_imports

type (): boolean

returns boolean

preview

Build the same statement list as build without joining — for inspection in tests.

type (): string[]

returns string[]

clear

Clear all imports.

type (): this

returns this

this for chaining

mutates

  • this — empties the internal `imports` map

import_count

type number

getter

IN_FLIGHT_RETRY_AFTER_SECONDS
#

rate_limiter.ts view source

1 import {IN_FLIGHT_RETRY_AFTER_SECONDS} from '@fuzdev/fuz_app/rate_limiter.js';

retry_after reported while a key is at its cap only because of attempts still in flight. Those settle within a request's lifetime, so the earliest moment worth retrying is the next second, not the oldest recorded attempt's expiry. Mirrors the Rust spine's IN_FLIGHT_RETRY_AFTER_SECONDS.

in_process_capabilities
#

testing/cross_backend/capabilities.ts view source

BackendCapabilities import {in_process_capabilities} from '@fuzdev/fuz_app/testing/cross_backend/capabilities.js';

Capability declarations for the in-process Hono transport. Nearly every flag is true because in-process testing exercises the full backend with no missing optional behaviors. The exceptions are the flags whose cases are cross-process-only: peer_request (describe_peer_ping_ws_tests needs a real bound socket with an on_request responder via create_ws_transport, so the in-process driver never runs it — the transport itself supports server-initiated requests), ws_handshake_pipelining (raw bytes on a real socket), ws_account_actions (a real socket's reply-then-close ordering), cell_gated_create, and ws_role_gated. Cross-process consumers declare each flag explicitly per backend.

in_process_shape_notes
#

testing/cross_backend/capabilities.ts view source

BackendShapeNotes import {in_process_shape_notes} from '@fuzdev/fuz_app/testing/cross_backend/capabilities.js';

Shape notes for the in-process Hono transport — every wiring fact present (the in-process app exercises the full middleware stack). Documentation only, like every BackendShapeNotes (nothing reads it).

InProcessSetupOptions
#

testing/cross_backend/in_process_setup.ts view source

InProcessSetupOptions import type {InProcessSetupOptions} from '@fuzdev/fuz_app/testing/cross_backend/in_process_setup.js';

Options for default_in_process_setup. Extends CreateTestAppOptions with the same extra_accounts slot the cross-process variant accepts — both transports observe the same bootstrap-time secondary set so suite bodies can read fixture.extra_accounts[username] uniformly.

inheritance

extra_accounts?

Additional accounts seeded at this transport's bootstrap-equivalent step. See ExtraAccountSpec for the cradle-only-bypass rationale. Most suites pass undefined / []; the ROLE_KEEPER probe (in describe_standard_admin_integration_tests) is the primary user.

type readonly ExtraAccountSpec[]

readonly

extra_actors?

Additional actor names to seed on the bootstrapped keeper — exposed on fixture.extra_actors. See CrossProcessSetupOptions.extra_actors / TestFixtureBase.extra_actors. Seeded directly against the live backend DB (in-process has no wire hop).

type readonly string[]

readonly

input_schema_declares_acting
#

http/auth_shape.ts view source

(schema: ZodType<unknown, unknown, $ZodTypeInternals<unknown, unknown>>): boolean import {input_schema_declares_acting} from '@fuzdev/fuz_app/http/auth_shape.js';

Whether a schema declares the canonical acting?: ActingActor field. Reference-equality on the exported ActingActor schema — consumer schemas with unrelated acting fields don't trip this check.

Peels through Zod wrappers (optional, nullable, default, transform, pipe, prefault) via zod_unwrap_to_object so a spec authored as z.optional(z.strictObject({acting: ActingActor})) or z.strictObject({acting: ActingActor}).default({}) still trips the predicate.

schema

type ZodType<unknown, unknown, $ZodTypeInternals<unknown, unknown>>

returns

boolean

install_audit_drift_guard
#

testing/audit_drift_guard.ts view source

(): void import {install_audit_drift_guard} from '@fuzdev/fuz_app/testing/audit_drift_guard.js';

Register per-test beforeEach + afterEach hooks that catch any audit emission with a metadata shape that fails its audit_metadata_schemas entry, an event_type not present in the active AuditLogConfig, or a row the emitter failed to write.

The production validation in query_audit_log is fail-open — it bumps process-wide counters and proceeds, so a regression that emits an undeclared metadata field or a typo'd event-type lands a row that passes downstream queries but breaks forensics. The emitter's pool write is fail-open too: a refused INSERT is logged and counted, the request succeeds, and the row is gone. Tests that exercise audit emits should fail loudly when either happens.

Call at the top of every describe / describe_db block that fires audit writes through deps.audit.emit. Resets counters before each test and asserts zero on completion. A test that makes a write fail on purpose calls reset_audit_write_failures (auth/audit_emitter.ts) once it has asserted the count.

Pair with await_pending_effects: true (the default for create_test_app) so fire-and-forget audit writes have completed by the time the after-each check observes counter state.

returns

void

Invite
#

auth/invite_schema.ts view source

Invite import type {Invite} from '@fuzdev/fuz_app/auth/invite_schema.js';

Invite row from the database.

id

type Uuid

email

type string | null

username

type string | null

claimed_by

type Uuid | null

claimed_at

type string | null

created_at

type string

created_by

type Uuid | null

INVITE_COLUMNS
#

auth/invite_queries.ts view source

readonly ["id", "email", "username", "claimed_by", "claimed_at", "created_at", "created_by"] import {INVITE_COLUMNS} from '@fuzdev/fuz_app/auth/invite_queries.js';

The full invite column set, named explicitly so a row read fails loud on schema drift — invite rows ride the invite_create / invite_list RPC responses raw, so a SELECT * would silently carry a dropped or leftover column into the strict-validated wire shapes (see ACCOUNT_COLUMNS in auth/account_queries.ts for the outage class; the Rust twin names columns at every invite site). Keep in sync with Invite and the migration chain's end state.

invite_create_action_spec
#

auth/admin_action_specs.ts view source

{ method: string; kind: "request_response"; initiator: "frontend"; auth: { account: "required"; actor: "required"; roles: string[]; }; side_effects: true; input: ZodObject<{ email: ZodOptional<ZodNullable<ZodString>>; username: ZodOptional<...>; acting: ZodOptional<...>; }, $strict>; output: ZodObject<...>; async: t... import {invite_create_action_spec} from '@fuzdev/fuz_app/auth/admin_action_specs.js';

invite_delete_action_spec
#

auth/admin_action_specs.ts view source

{ method: string; kind: "request_response"; initiator: "frontend"; auth: { account: "required"; actor: "required"; roles: string[]; }; side_effects: true; input: ZodObject<{ invite_id: $ZodBranded<ZodUUID, "Uuid", "out">; acting: ZodOptional<...>; }, $strict>; output: ZodObject<...>; async: true; description: string... import {invite_delete_action_spec} from '@fuzdev/fuz_app/auth/admin_action_specs.js';

INVITE_INDEXES
#

invite_list_action_spec
#

auth/admin_action_specs.ts view source

{ method: string; kind: "request_response"; initiator: "frontend"; auth: { account: "required"; actor: "required"; roles: string[]; }; side_effects: false; input: ZodDefault<ZodObject<{ acting: ZodOptional<$ZodBranded<ZodUUID, "Uuid", "out">>; }, $strict>>; output: ZodObject<...>; async: true; description: string; r... import {invite_list_action_spec} from '@fuzdev/fuz_app/auth/admin_action_specs.js';

rate_limit: 'account' bounds admin-side scraping of the invite table — bounded by table size, but every row carries email + username + creator/claimer identifiers worth defense-in-depth against an admin mutation oracle running scripted reads alongside invite_create.

INVITE_SCHEMA
#

auth/auth_ddl.ts view source

"\nCREATE TABLE IF NOT EXISTS invite (\n id UUID PRIMARY KEY DEFAULT gen_random_uuid(),\n email TEXT,\n username TEXT,\n claimed_by UUID REFERENCES account(id) ON DELETE SET NULL,\n claimed_at TIMESTAMPTZ,\n created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),\n created_by UUID REFERENCES actor(id) ON DELETE SET NU... import {INVITE_SCHEMA} from '@fuzdev/fuz_app/auth/auth_ddl.js';

InviteCreateInput
#

auth/admin_action_specs.ts view source

value + type

{ email?: string | null | undefined; username?: string | null | undefined; acting?: (string & $brand<"Uuid">) | undefined; } import {InviteCreateInput} from '@fuzdev/fuz_app/auth/admin_action_specs.js';

Input for invite_create. At least one of email / username must be provided.

email?

type string | null

username?

type string | null

acting?

type string & $brand<"Uuid">

InviteCreateOutput
#

auth/admin_action_specs.ts view source

value + type

{ ok: true; invite: { id: string & $brand<"Uuid">; email: string | null; username: string | null; claimed_by: (string & $brand<"Uuid">) | null; claimed_at: string | null; created_at: string; created_by: (string & $brand<...>) | null; }; } import {InviteCreateOutput} from '@fuzdev/fuz_app/auth/admin_action_specs.js';

Output for invite_create.

ok

type true

invite

type InviteJson

InviteDeleteInput
#

auth/admin_action_specs.ts view source

value + type

{ invite_id: string & $brand<"Uuid">; acting?: (string & $brand<"Uuid">) | undefined; } import {InviteDeleteInput} from '@fuzdev/fuz_app/auth/admin_action_specs.js';

Input for invite_delete.

invite_id

type string & $brand<"Uuid">

acting?

type string & $brand<"Uuid">

InviteDeleteOutput
#

InviteJson
#

auth/invite_schema.ts view source

value + type

{ id: string & $brand<"Uuid">; email: string | null; username: string | null; claimed_by: (string & $brand<"Uuid">) | null; claimed_at: string | null; created_at: string; created_by: (string & $brand<...>) | null; } import {InviteJson} from '@fuzdev/fuz_app/auth/invite_schema.js';

Zod schema for client-safe invite data.

id

type string & $brand<"Uuid">

email

type string | null

username

type string | null

claimed_by

type (string & $brand<"Uuid">) | null

claimed_at

type string | null

created_at

type string

created_by

type (string & $brand<"Uuid">) | null

InviteListInput
#

auth/admin_action_specs.ts view source

value + type

{ acting?: (string & $brand<"Uuid">) | undefined; } import {InviteListInput} from '@fuzdev/fuz_app/auth/admin_action_specs.js';

Input for invite_list.

acting?

type string & $brand<"Uuid">

InviteListOutput
#

auth/admin_action_specs.ts view source

value + type

{ invites: { id: string & $brand<"Uuid">; email: string | null; username: string | null; claimed_by: (string & $brand<"Uuid">) | null; claimed_at: string | null; created_at: string; created_by: (string & $brand<...>) | null; created_by_username: string | null; claimed_by_username: string | null; }[]; } import {InviteListOutput} from '@fuzdev/fuz_app/auth/admin_action_specs.js';

Output for invite_list. Uses the enriched row including creator/claimer usernames.

invites

type InviteWithUsernamesJson[]

InviteWithUsernamesJson
#

auth/invite_schema.ts view source

value + type

{ id: string & $brand<"Uuid">; email: string | null; username: string | null; claimed_by: (string & $brand<"Uuid">) | null; claimed_at: string | null; created_at: string; created_by: (string & $brand<...>) | null; created_by_username: string | null; claimed_by_username: string | null; } import {InviteWithUsernamesJson} from '@fuzdev/fuz_app/auth/invite_schema.js';

Zod schema for invite data with resolved creator/claimer usernames.

id

type string & $brand<"Uuid">

email

type string | null

username

type string | null

claimed_by

type (string & $brand<"Uuid">) | null

claimed_at

type string | null

created_at

type string

created_by

type (string & $brand<"Uuid">) | null

created_by_username

type string | null

claimed_by_username

type string | null

IP_LITERAL_CHARS
#

http/ip_canonical.ts view source

RegExp import {IP_LITERAL_CHARS} from '@fuzdev/fuz_app/http/ip_canonical.js';

Allowed character set for a bare IP literal.

Covers the union of IPv4 (digits + .), IPv6 (hex digits + :), and IPv4-mapped IPv6 forms (::ffff:127.0.0.1). Anything outside this set — brackets, whitespace, control bytes, letters g–z — disqualifies the input from parsing.

Same regex http/proxy.ts's validate_ip_strict uses; exported here so both modules can share one source of truth.

ipv6_bigint_to_canonical
#

http/ip_canonical.ts view source

(bits: bigint): string import {ipv6_bigint_to_canonical} from '@fuzdev/fuz_app/http/ip_canonical.js';

Convert a 128-bit IPv6 binary value into its RFC 5952 canonical string form.

  • IPv4-mapped (groups[0..5] = 0, groups[5] = 0xffff) emits the ::ffff:a.b.c.d dotted form per RFC 5952 §5.
  • Otherwise: lowercase hex with no leading zeros per group (§4.1), the longest run of consecutive zero groups (≥ 2 groups) is replaced with :: (§4.2.1, §4.2.3), and on equal-length runs the first one wins (§4.2.3). Single-zero groups stay as 0 (§4.2.2).

Pure helper exported for the test suite to exercise the canonicalization invariants directly without a full convertIPv6ToBinary round-trip.

bits

the 128-bit IPv6 value as bigint. Must satisfy 0n <= bits < 2n ** 128n; throws RangeError otherwise. Silent truncation would mask caller bugs since the bit-extraction loop only consumes the low 128 bits.

type bigint

returns

string

throws

  • RangeError - when `bits` is negative or exceeds 128 bits

is_action_complete
#

actions/action_event_helpers.ts view source

(data: { kind: "request_response" | "remote_notification" | "local_call"; phase: "send_request" | "receive_request" | "send_response" | "receive_response" | "send_error" | "receive_error" | "send" | "receive" | "execute"; ... 9 more ...; notification: { ...; } | null; }): boolean import {is_action_complete} from '@fuzdev/fuz_app/actions/action_event_helpers.js';

data

returns

boolean

is_action_spec
#

actions/action_spec.ts view source

(value: unknown): value is { method: string; initiator: "frontend" | "backend" | "both"; side_effects: boolean; input: ZodType<unknown, unknown, $ZodTypeInternals<unknown, unknown>>; output: ZodType<...>; ... 6 more ...; rate_limit?: "both" | ... 2 more ... | undefined; } | { ...; } | { ...; } import {is_action_spec} from '@fuzdev/fuz_app/actions/action_spec.js';

Structural type guard for any ActionSpecUnion variant — checks kind is one of the three known values.

value

type unknown

returns

boolean

is_api_token_shaped
#

auth/api_token.ts view source

(raw: string): boolean import {is_api_token_shaped} from '@fuzdev/fuz_app/auth/api_token.js';

Whether raw has the shape of a raw API token — the bearer path's precheck, so a value that cannot be a token costs no database work. A true says nothing about whether the token exists. Twin of the Rust spine's is_api_token_shaped.

raw

the value from the Authorization: Bearer header

type string

returns

boolean

true when raw matches API_TOKEN_REGEX

is_browser_context
#

http/origin.ts view source

(c: Context<any, any, {}>): boolean import {is_browser_context} from '@fuzdev/fuz_app/http/origin.js';

True when the request looks like it originated from a browser context — it carries an Origin or Referer header. Browsers attach these automatically on the state-changing surface; loopback / CLI clients don't. Uses !== undefined so an empty-string header still counts as browser context. Checks Referer too (not just Origin) because some browser requests send only Referer. Twin of the Rust spine's bearer_auth::is_browser_context.

The bearer (auth/bearer_auth.ts) and daemon-token (auth/daemon_token_middleware.ts) middleware both silently discard their credential when this is true: a stolen bearer token can't be replayed from a browser, and the loopback daemon token never legitimately carries an Origin. Distinct from verify_request_source — that gates browser requests against an allowlist; this only detects browser context.

c

type Context<any, any, {}>

returns

boolean

is_builtin_role
#

auth/role_schema.ts view source

(name: string): name is "admin" | "keeper" import {is_builtin_role} from '@fuzdev/fuz_app/auth/role_schema.js';

Whether name is a builtin role (keeper / admin).

Builtin roles are global-only: no grant path binds one to a scope (role_grant_assign, role_grant_offer_create, and role_grant_offer_accept refuse it), and every gate that reads one checks the global grant, so a scoped builtin row confers nothing.

name

the role name to check

type string

returns

boolean

true for keeper or admin

is_cors_simple_content_type
#

http/simple_request.ts view source

(content_type: string | undefined): boolean import {is_cors_simple_content_type} from '@fuzdev/fuz_app/http/simple_request.js';

True when a browser could have sent a POST with this Content-Type cross-origin without a preflight: none, or a CORS-safelisted one.

Matches more than browsers do (a value they'd refuse to call safelisted — one with odd bytes, say — counts as safelisted here), so it only ever refuses too much, never too little.

content_type

the request's Content-Type header, if any

type string | undefined

returns

boolean

is_credential_gated_auth
#

http/auth_shape.ts view source

(auth: { account: "optional" | "none" | "required"; actor: "optional" | "none" | "required"; roles?: readonly string[] | undefined; credential_types?: readonly string[] | undefined; required_scope?: string | undefined; }): boolean import {is_credential_gated_auth} from '@fuzdev/fuz_app/http/auth_shape.js';

True iff the route declares any credential-type gate (auth.credential_types?.length).

auth

returns

boolean

is_daemon_running
#

cli/daemon.ts view source

(runtime: CommandDeps, pid: number): Promise<boolean> import {is_daemon_running} from '@fuzdev/fuz_app/cli/daemon.js';

Check if a process is running by PID.

runtime

runtime with command execution capability

pid

process ID to check

type number

returns

Promise<boolean>

true if the process is running

is_enospc_error
#

db/fact_store_errors.ts view source

(err: unknown): boolean import {is_enospc_error} from '@fuzdev/fuz_app/db/fact_store_errors.js';

Whether a thrown value is a Node filesystem ENOSPC (no space left on device). Used by the streaming disk write to translate the raw FS error into a FactStorageFullError.

err

type unknown

returns

boolean

is_execute
#

actions/action_event_helpers.ts view source

(data: { kind: "request_response" | "remote_notification" | "local_call"; phase: "send_request" | "receive_request" | "send_response" | "receive_response" | "send_error" | "receive_error" | "send" | "receive" | "execute"; ... 9 more ...; notification: { ...; } | null; }): data is ActionEventLocalCallData & { ...; } import {is_execute} from '@fuzdev/fuz_app/actions/action_event_helpers.js';

data

returns

boolean

is_failed
#

actions/action_event_helpers.ts view source

(data: { kind: "request_response" | "remote_notification" | "local_call"; phase: "send_request" | "receive_request" | "send_response" | "receive_response" | "send_error" | "receive_error" | "send" | "receive" | "execute"; ... 9 more ...; notification: { ...; } | null; }): data is { ...; } & { ...; } import {is_failed} from '@fuzdev/fuz_app/actions/action_event_helpers.js';

data

returns

boolean

is_file_fact_url
#

db/file_fact_url.ts view source

(s: string): s is string & $brand<"FileFactUrl"> import {is_file_fact_url} from '@fuzdev/fuz_app/db/file_fact_url.js';

Type guard. Useful when discriminating a string | null column.

s

type string

returns

boolean

is_filterable_broadcast_transport
#

is_grant_scope_paired
#

auth/role_grant_offer_action_specs.ts view source

(input: { scope_kind?: string | null | undefined; scope_id?: string | null | undefined; }): boolean import {is_grant_scope_paired} from '@fuzdev/fuz_app/auth/role_grant_offer_action_specs.js';

Whether a grant input's scope_kind + scope_id are paired — both absent (a global grant) or both present (a scoped one), the role_grant_scope_kind_paired / role_grant_offer_scope_kind_paired CHECKs. The grant inputs refine on it so a half-scoped call is a -32602 rather than a constraint-violation 500.

input

the grant input's scope pair

type { scope_kind?: string | null | undefined; scope_id?: string | null | undefined; }

returns

boolean

true when both are set or both are absent

is_handled
#

actions/action_event_helpers.ts view source

(data: { kind: "request_response" | "remote_notification" | "local_call"; phase: "send_request" | "receive_request" | "send_response" | "receive_response" | "send_error" | "receive_error" | "send" | "receive" | "execute"; ... 9 more ...; notification: { ...; } | null; }): data is { ...; } & { ...; } import {is_handled} from '@fuzdev/fuz_app/actions/action_event_helpers.js';

data

returns

boolean

is_handling
#

actions/action_event_helpers.ts view source

(data: { kind: "request_response" | "remote_notification" | "local_call"; phase: "send_request" | "receive_request" | "send_response" | "receive_response" | "send_error" | "receive_error" | "send" | "receive" | "execute"; ... 9 more ...; notification: { ...; } | null; }): data is { ...; } & { ...; } import {is_handling} from '@fuzdev/fuz_app/actions/action_event_helpers.js';

data

returns

boolean

is_initial
#

actions/action_event_helpers.ts view source

(data: { kind: "request_response" | "remote_notification" | "local_call"; phase: "send_request" | "receive_request" | "send_response" | "receive_response" | "send_error" | "receive_error" | "send" | "receive" | "execute"; ... 9 more ...; notification: { ...; } | null; }): data is { ...; } & { ...; } import {is_initial} from '@fuzdev/fuz_app/actions/action_event_helpers.js';

data

returns

boolean

is_iso8601_seconds_live
#

timestamp.ts view source

(stamp: string, now_ms: number): boolean import {is_iso8601_seconds_live} from '@fuzdev/fuz_app/timestamp.js';

Whether the second a wire timestamp names has not yet ended.

The one in-memory expiry rule over a truncated stamp: stamp stands for [stamp, stamp + ISO8601_SECOND_MS), so this admits until that whole second is over and can never report an expiry the server's raw-column expires_at > NOW() gate would still accept. A stamp that does not parse is never live.

stamp

a second-precision wire timestamp

type string

now_ms

the current time in epoch milliseconds

type number

returns

boolean

true while now_ms lies before the end of stamp's second

is_json_content_type
#

http/simple_request.ts view source

(content_type: string | undefined): boolean import {is_json_content_type} from '@fuzdev/fuz_app/http/simple_request.js';

True when Content-Type is application/json (any parameters, such as charset, allowed).

content_type

the request's Content-Type header, if any

type string | undefined

returns

boolean

is_jsonrpc_error_response
#

http/jsonrpc_helpers.ts view source

(message: unknown): message is { [x: string]: unknown; jsonrpc: "2.0"; id: string | number | null; error: { [x: string]: unknown; code: -32700 | -32600 | -32601 | -32602 | -32603 | (number & $brand<"JsonrpcServerErrorCode">); message: string; data?: unknown; }; } import {is_jsonrpc_error_response} from '@fuzdev/fuz_app/http/jsonrpc_helpers.js';

Checks if a value is a JSON-RPC error response (has error + id).

message

type unknown

returns

boolean

is_jsonrpc_message
#

http/jsonrpc_helpers.ts view source

(message: unknown): message is { [x: string]: unknown; jsonrpc: "2.0"; id: string | number; method: string; params?: { [x: string]: unknown; } | undefined; } | { [x: string]: unknown; jsonrpc: "2.0"; id: string | number; result: JSONType; } | { ...; } | { ...; } | ({ ...; } | ... 2 more ... | { ...; })[] import {is_jsonrpc_message} from '@fuzdev/fuz_app/http/jsonrpc_helpers.js';

Checks if a value is any valid JSON-RPC message or batch array.

message

type unknown

returns

boolean

is_jsonrpc_notification
#

http/jsonrpc_helpers.ts view source

(message: unknown): message is { [x: string]: unknown; jsonrpc: "2.0"; method: string; params?: { [x: string]: unknown; } | undefined; } import {is_jsonrpc_notification} from '@fuzdev/fuz_app/http/jsonrpc_helpers.js';

Checks if a value is a JSON-RPC notification (has method, no id).

message

type unknown

returns

boolean

is_jsonrpc_object
#

http/jsonrpc_helpers.ts view source

(message: unknown): message is { jsonrpc: "2.0"; } import {is_jsonrpc_object} from '@fuzdev/fuz_app/http/jsonrpc_helpers.js';

Checks if a value is a JSON-RPC object (has jsonrpc: '2.0').

message

type unknown

returns

boolean

is_jsonrpc_request
#

http/jsonrpc_helpers.ts view source

(message: unknown): message is { [x: string]: unknown; jsonrpc: "2.0"; id: string | number; method: string; params?: { [x: string]: unknown; } | undefined; } import {is_jsonrpc_request} from '@fuzdev/fuz_app/http/jsonrpc_helpers.js';

Checks if a value is a JSON-RPC request (has method + id).

message

type unknown

returns

boolean

is_jsonrpc_request_id
#

http/jsonrpc_helpers.ts view source

(id: unknown): id is string | number import {is_jsonrpc_request_id} from '@fuzdev/fuz_app/http/jsonrpc_helpers.js';

Checks if a value is a valid JSON-RPC request id (string or finite number).

id

type unknown

returns

boolean

is_jsonrpc_response
#

http/jsonrpc_helpers.ts view source

(message: unknown): message is { [x: string]: unknown; jsonrpc: "2.0"; id: string | number; result: JSONType; } import {is_jsonrpc_response} from '@fuzdev/fuz_app/http/jsonrpc_helpers.js';

Checks if a value is a JSON-RPC success response (has result + id).

message

type unknown

returns

boolean

is_keeper_auth
#

http/auth_shape.ts view source

(auth: { account: "optional" | "none" | "required"; actor: "optional" | "none" | "required"; roles?: readonly string[] | undefined; credential_types?: readonly string[] | undefined; required_scope?: string | undefined; }): boolean import {is_keeper_auth} from '@fuzdev/fuz_app/http/auth_shape.js';

True iff the route is the keeper bucket — a credential gate admitting daemon_token. Knows the literal directly, because keeper is a named authority level rather than one channel among many: it is the ceiling sessions and api tokens cannot reach.

Narrower than is_credential_gated_auth, which is true of every channel gate — including the session-only gates pointing the other way (the credential-minting actions, POST /logout / POST /password, the audit SSE stream). Reach for the general predicate when the question is "does a channel gate answer here"; reach for this one only when the question is about keeper specifically.

auth

returns

boolean

is_local_call
#

actions/action_event_helpers.ts view source

(data: { kind: "request_response" | "remote_notification" | "local_call"; phase: "send_request" | "receive_request" | "send_response" | "receive_response" | "send_error" | "receive_error" | "send" | "receive" | "execute"; ... 9 more ...; notification: { ...; } | null; }): data is ActionEventLocalCallData import {is_local_call} from '@fuzdev/fuz_app/actions/action_event_helpers.js';

data

returns

boolean

is_loopback_host
#

testing/cross_backend/testing_server_core.ts view source

(host: string): boolean import {is_loopback_host} from '@fuzdev/fuz_app/testing/cross_backend/testing_server_core.js';

Loopback bind hosts — the only ones the test binary may serve on. It ships deterministic dev secrets (fixed cookie keys + bootstrap token in default_secrets.ts), so binding any network-reachable interface would let anyone who knows those fixed keys forge cookies against it. An allowlist (not an 0.0.0.0/:: blocklist) closes the gap a concrete LAN/public interface IP — e.g. --host 192.168.1.50 — would otherwise slip through. Covers localhost, the IPv4 loopback 127.0.0.0/8, and IPv6 ::1.

host

type string

returns

boolean

is_notification
#

testing/transports/ws_client.ts view source

(method: string): (msg: unknown) => boolean import {is_notification} from '@fuzdev/fuz_app/testing/transports/ws_client.js';

Predicate matching a JSON-RPC notification with the given method name.

method

type string

returns

(msg: unknown) => boolean

is_notification_receive
#

actions/action_event_helpers.ts view source

(data: { kind: "request_response" | "remote_notification" | "local_call"; phase: "send_request" | "receive_request" | "send_response" | "receive_response" | "send_error" | "receive_error" | "send" | "receive" | "execute"; ... 9 more ...; notification: { ...; } | null; }): data is ({ ...; } & { ...; }) | ... 3 more ... | ({ ...; } & { ...; }) import {is_notification_receive} from '@fuzdev/fuz_app/actions/action_event_helpers.js';

data

returns

boolean

is_notification_send
#

actions/action_event_helpers.ts view source

(data: { kind: "request_response" | "remote_notification" | "local_call"; phase: "send_request" | "receive_request" | "send_response" | "receive_response" | "send_error" | "receive_error" | "send" | "receive" | "execute"; ... 9 more ...; notification: { ...; } | null; }): data is ({ ...; } & { ...; }) | ... 3 more ... | ({ ...; } & { ...; }) import {is_notification_send} from '@fuzdev/fuz_app/actions/action_event_helpers.js';

data

returns

boolean

is_notification_send_with_parsed_input
#

actions/action_event_helpers.ts view source

<TMethod extends string = string>(data: { kind: "request_response" | "remote_notification" | "local_call"; phase: "send_request" | "receive_request" | "send_response" | "receive_response" | "send_error" | "receive_error" | "send" | "receive" | "execute"; ... 9 more ...; notification: { ...; } | null; }): data is ({ ...; } & { ...; }) | ({ ...; } & { ...; }) import {is_notification_send_with_parsed_input} from '@fuzdev/fuz_app/actions/action_event_helpers.js';

data

returns

boolean

generics

is_notification_send_with_parsed_input<TMethod extends string = string>
TMethod
constraint string
default string

is_notification_with
#

testing/transports/ws_client.ts view source

<P>(method: string, match: (params: P) => boolean): (msg: unknown) => msg is JsonrpcNotificationFrame<P> import {is_notification_with} from '@fuzdev/fuz_app/testing/transports/ws_client.js';

Type-guard combinator: match a notification whose typed params satisfies match. Collapses the common test pattern of casting msg to JsonrpcNotificationFrame<P> in every predicate body.

const match_roster_for = (id: Uuid) => is_notification_with<RosterChangedParams>( WORLD_METHODS.roster_changed, (params) => params.character_id === id && !params.removed, ); const roster = await client.wait_for(match_roster_for(char_id));

method

type string

match

type (params: P) => boolean

returns

(msg: unknown) => msg is JsonrpcNotificationFrame<P>

generics

is_notification_with<P>
P

is_null_schema
#

http/schema_helpers.ts view source

(schema: ZodType<unknown, unknown, $ZodTypeInternals<unknown, unknown>>): boolean import {is_null_schema} from '@fuzdev/fuz_app/http/schema_helpers.js';

Check if a schema is exactly z.null().

Uses instanceof rather than runtime parsing to avoid false positives from z.nullable(z.string()) or similar schemas that accept null but also accept other values.

schema

type ZodType<unknown, unknown, $ZodTypeInternals<unknown, unknown>>

returns

boolean

is_parsed
#

actions/action_event_helpers.ts view source

(data: { kind: "request_response" | "remote_notification" | "local_call"; phase: "send_request" | "receive_request" | "send_response" | "receive_response" | "send_error" | "receive_error" | "send" | "receive" | "execute"; ... 9 more ...; notification: { ...; } | null; }): data is { ...; } & { ...; } import {is_parsed} from '@fuzdev/fuz_app/actions/action_event_helpers.js';

data

returns

boolean

is_pg_unique_violation
#

db/pg_error.ts view source

(error: unknown): boolean import {is_pg_unique_violation} from '@fuzdev/fuz_app/db/pg_error.js';

Check if an error is a PostgreSQL unique constraint violation (error code 23505).

error

the caught error

type unknown

returns

boolean

true if the error is a unique constraint violation

is_plain_authenticated_auth
#

http/auth_shape.ts view source

(auth: { account: "optional" | "none" | "required"; actor: "optional" | "none" | "required"; roles?: readonly string[] | undefined; credential_types?: readonly string[] | undefined; required_scope?: string | undefined; }): boolean import {is_plain_authenticated_auth} from '@fuzdev/fuz_app/http/auth_shape.js';

True iff the route is plain authenticated — account === 'required' with no role gate and no credential gate: any credential the auth middleware resolved gets in.

A channel gate takes a route out of this bucket, which is why the account-grain routes one might expect here are not — POST /logout, POST /password, and the account_* credential-lifecycle actions all declare credential_types: ['session'] and answer to is_credential_gated_auth instead.

auth

returns

boolean

is_post_content_type_refused
#

http/simple_request.ts view source

(content_type: string | undefined, raw_body: boolean): boolean import {is_post_content_type_refused} from '@fuzdev/fuz_app/http/simple_request.js';

True when a POST route refuses this Content-Type: a JSON route anything but application/json, a raw_body route a CORS-safelisted type or none.

content_type

the request's Content-Type header, if any

type string | undefined

raw_body

the route's RouteSpec.raw_body

type boolean

returns

boolean

is_protocol_action_method
#

actions/action_codegen.ts view source

(method: string): method is "heartbeat" | "cancel" | "peer/ping" import {is_protocol_action_method} from '@fuzdev/fuz_app/actions/action_codegen.js';

Type predicate for filtering protocol-action methods out of a typed FrontendActionsApi method_filter. Avoids the (... as never) cast required to call Array.prototype.includes on the readonly tuple at narrow string types.

method

type string

returns

boolean

examples

generate_frontend_actions_api(specs, imports, { method_filter: (s) => !is_protocol_action_method(s.method), });

is_public_auth
#

http/auth_shape.ts view source

(auth: { account: "optional" | "none" | "required"; actor: "optional" | "none" | "required"; roles?: readonly string[] | undefined; credential_types?: readonly string[] | undefined; required_scope?: string | undefined; }): boolean import {is_public_auth} from '@fuzdev/fuz_app/http/auth_shape.js';

True iff the route is fully public — both account and actor axes are 'none'. Public routes skip the dispatcher's authorization phase entirely (per registry-time invariant 4 they also cannot declare roles or credential gates).

auth

returns

boolean

is_raw_ws_close
#

is_receive_request
#

actions/action_event_helpers.ts view source

(data: { kind: "request_response" | "remote_notification" | "local_call"; phase: "send_request" | "receive_request" | "send_response" | "receive_response" | "send_error" | "receive_error" | "send" | "receive" | "execute"; ... 9 more ...; notification: { ...; } | null; }): data is ({ ...; } & { ...; }) | ... 3 more ... | ({ ...; } & { ...; }) import {is_receive_request} from '@fuzdev/fuz_app/actions/action_event_helpers.js';

data

returns

boolean

is_receive_response
#

actions/action_event_helpers.ts view source

(data: { kind: "request_response" | "remote_notification" | "local_call"; phase: "send_request" | "receive_request" | "send_response" | "receive_response" | "send_error" | "receive_error" | "send" | "receive" | "execute"; ... 9 more ...; notification: { ...; } | null; }): data is ({ ...; } & { ...; }) | ... 3 more ... | ({ ...; } & { ...; }) import {is_receive_response} from '@fuzdev/fuz_app/actions/action_event_helpers.js';

data

returns

boolean

is_remote_notification
#

actions/action_event_helpers.ts view source

(data: { kind: "request_response" | "remote_notification" | "local_call"; phase: "send_request" | "receive_request" | "send_response" | "receive_response" | "send_error" | "receive_error" | "send" | "receive" | "execute"; ... 9 more ...; notification: { ...; } | null; }): data is ActionEventRemoteNotificationData import {is_remote_notification} from '@fuzdev/fuz_app/actions/action_event_helpers.js';

data

returns

boolean

is_request_response
#

actions/action_event_helpers.ts view source

(data: { kind: "request_response" | "remote_notification" | "local_call"; phase: "send_request" | "receive_request" | "send_response" | "receive_response" | "send_error" | "receive_error" | "send" | "receive" | "execute"; ... 9 more ...; notification: { ...; } | null; }): data is ActionEventRequestResponseData import {is_request_response} from '@fuzdev/fuz_app/actions/action_event_helpers.js';

data

returns

boolean

is_response_for
#

testing/transports/ws_client.ts view source

(id: string | number): (msg: unknown) => boolean import {is_response_for} from '@fuzdev/fuz_app/testing/transports/ws_client.js';

Predicate matching a JSON-RPC response frame (success or error) for the given request id.

id

type number | string

returns

(msg: unknown) => boolean

is_role_auth
#

http/auth_shape.ts view source

(auth: { account: "optional" | "none" | "required"; actor: "optional" | "none" | "required"; roles?: readonly string[] | undefined; credential_types?: readonly string[] | undefined; required_scope?: string | undefined; }): boolean import {is_role_auth} from '@fuzdev/fuz_app/http/auth_shape.js';

True iff the route declares any role gate (auth.roles?.length).

auth

returns

boolean

is_role_grant_active
#

auth/account_schema.ts view source

(p: { revoked_at?: string | null | undefined; expires_at: string | null; }, now?: Date): boolean import {is_role_grant_active} from '@fuzdev/fuz_app/auth/account_schema.js';

Whether a role grant is live, from its wire fields — not revoked, and not past expires_at.

Server-side this is belt-and-suspenders: every role_grant a request context holds came through query_role_grant_find_active_for_actor, whose revoked_at IS NULL AND (expires_at IS NULL OR expires_at > NOW()) is the authoritative gate on both spines (the Rust twin's in-memory RoleGrantRow carries no timestamps at all and re-checks nothing).

expires_at arrives second-truncated, so the expiry check is is_iso8601_seconds_live (timestamp.ts): it admits through the end of the second the stamp names, so the predicate can never report a grant expired *before* the SQL gate would — a bare expires_at > now would deny an admitted grant for up to a second.

p

the grant's revocation + expiry fields

type { revoked_at?: string | null | undefined; expires_at: string | null; }

now

current time (defaults to new Date(), pass for testability and hot-path efficiency)

type Date
default new Date()

returns

boolean

true when the grant is neither revoked nor expired

is_send_request
#

actions/action_event_helpers.ts view source

(data: { kind: "request_response" | "remote_notification" | "local_call"; phase: "send_request" | "receive_request" | "send_response" | "receive_response" | "send_error" | "receive_error" | "send" | "receive" | "execute"; ... 9 more ...; notification: { ...; } | null; }): data is ({ ...; } & { ...; }) | ... 3 more ... | ({ ...; } & { ...; }) import {is_send_request} from '@fuzdev/fuz_app/actions/action_event_helpers.js';

data

returns

boolean

is_send_request_with_parsed_input
#

actions/action_event_helpers.ts view source

<TMethod extends string = string>(data: { kind: "request_response" | "remote_notification" | "local_call"; phase: "send_request" | "receive_request" | "send_response" | "receive_response" | "send_error" | "receive_error" | "send" | "receive" | "execute"; ... 9 more ...; notification: { ...; } | null; }): data is ({ ...; } & { ...; }) | ({ ...; } & { ...; }) import {is_send_request_with_parsed_input} from '@fuzdev/fuz_app/actions/action_event_helpers.js';

data

returns

boolean

generics

is_send_request_with_parsed_input<TMethod extends string = string>
TMethod
constraint string
default string

is_send_response
#

actions/action_event_helpers.ts view source

(data: { kind: "request_response" | "remote_notification" | "local_call"; phase: "send_request" | "receive_request" | "send_response" | "receive_response" | "send_error" | "receive_error" | "send" | "receive" | "execute"; ... 9 more ...; notification: { ...; } | null; }): data is ({ ...; } & { ...; }) | ... 3 more ... | ({ ...; } & { ...; }) import {is_send_response} from '@fuzdev/fuz_app/actions/action_event_helpers.js';

data

returns

boolean

is_strict_object_schema
#

http/schema_helpers.ts view source

(schema: ZodType<unknown, unknown, $ZodTypeInternals<unknown, unknown>>): boolean import {is_strict_object_schema} from '@fuzdev/fuz_app/http/schema_helpers.js';

Check if a schema is a strict object (z.strictObject()).

Strict objects set catchall to ZodNever to reject unknown keys. Regular z.object() has catchall: undefined (strips unknown keys in Zod 4).

schema

type ZodType<unknown, unknown, $ZodTypeInternals<unknown, unknown>>

returns

boolean

is_token_surface
#

auth/token_scope.ts view source

(value: string): value is "db_admin" | "fact_bare" | "audit_stream" | "ws_upgrade" | "app_surface" import {is_token_surface} from '@fuzdev/fuz_app/auth/token_scope.js';

Is value one of the surfaces the spine mounts?

Membership, not well-formedness — parse_token_scope_capability is the format check the guard resolver runs on a declaration, and it is deliberately looser. Used where the *spine's* own surfaces are the question: the direct-call denial helpers, and the census assertion that fuz_app declares no surface it doesn't mount.

value

type string

returns

boolean

is_trusted_ip
#

http/proxy.ts view source

(ip: string, proxies: ParsedProxy[]): boolean import {is_trusted_ip} from '@fuzdev/fuz_app/http/proxy.js';

Check whether ip matches any entry in the trusted proxy list.

Normalizes ip before matching (lowercase, IPv4-mapped IPv6 stripped). Uses validate_ip_strict to reject malformed input — without strict validation, Hono's lax distinctRemoteAddr would let an entry like '203.0.113.1:8080' (false-positive 'IPv6') reach convertIPv6ToBinary in the CIDR-match branch and throw.

ip

type string

proxies

returns

boolean

is_void_schema
#

http/schema_helpers.ts view source

(schema: ZodType<unknown, unknown, $ZodTypeInternals<unknown, unknown>>): boolean import {is_void_schema} from '@fuzdev/fuz_app/http/schema_helpers.js';

Check if a schema is exactly z.void().

RPC action specs use z.void() to declare a parameterless method — JSON-RPC 2.0 forbids params: null (params must be omitted or be a Structured value), so z.void() is the correct schema for "no params" and the dispatcher maps absent params to undefined for these specs.

schema

type ZodType<unknown, unknown, $ZodTypeInternals<unknown, unknown>>

returns

boolean

ISO8601_SECOND_MS
#

timestamp.ts view source

1000 import {ISO8601_SECOND_MS} from '@fuzdev/fuz_app/timestamp.js';

The width of one wire timestamp, in milliseconds.

A wire stamp is truncated, not rounded, so the instant it names lies anywhere in [stamp, stamp + ISO8601_SECOND_MS). A predicate that must not fire early against a stamp — "has this expiry passed?" — adds this to reach the end of the second the stamp stands for.

ISO8601_SECONDS
#

timestamp.ts view source

RegExp import {ISO8601_SECONDS} from '@fuzdev/fuz_app/timestamp.js';

The canonical wire timestamp shape as a regex — second-precision UTC, exactly 20 characters.

Lives in library code rather than a test helper so the unit suites, the db suites, and the cross-backend suites all pin the same pattern the server renders.

iso8601_timestamp_column
#

db/sql_columns.ts view source

(alias: string, column: string): string import {iso8601_timestamp_column} from '@fuzdev/fuz_app/db/sql_columns.js';

Build a `to_char({alias}.{column} AT TIME ZONE 'UTC', 'YYYY-MM-DD"T"HH24:MI:SS"Z"')` fragment for embedding in a SELECT projection, via a ColumnExpr override.

The format literal is the spine's canonical timestamp wire shape — second-precision UTC ISO-8601, exactly 20 characters — byte-identical to fuz_db::iso8601_timestamp_column, so the two backends serialize the same instant to the same bytes. to_iso8601_seconds (timestamp.ts) is the same shape for stamps minted in TS rather than read from Postgres.

alias

the table alias or name to qualify the column with, or the empty string for an unqualified reference

type string

column

the timestamp column name

type string

returns

string

the to_char(…) fragment

iso8601_timestamp_expr
#

db/sql_columns.ts view source

<T extends string>(columns: readonly T[], timestamp_columns: readonly NoInfer<T>[]): (alias: string) => ColumnExpr import {iso8601_timestamp_expr} from '@fuzdev/fuz_app/db/sql_columns.js';

Build a table's timestamp override: a ColumnExpr factory that projects each of timestamp_columns through iso8601_timestamp_column and declines every other column. The result takes the row qualifier — the empty string for a bare-table read, the alias for a JOIN — so one definition per query module serves every read in it.

timestamp_columns is typed against columns (and checked at build time), so a misspelled name is a compile error and a stale one throws at module load rather than silently leaving a column projected as a Date.

columns

the table's *_COLUMNS const

type readonly T[]

timestamp_columns

the members of columns that carry timestamps

type readonly NoInfer<T>[]

returns

(alias: string) => ColumnExpr

(alias) => ColumnExpr

generics

iso8601_timestamp_expr<T extends string>
T
constraint string

throws

  • Error - when a name in `timestamp_columns` is not in `columns`

ItemJson
#

auth/cell_item_action_specs.ts view source

value + type

{ parent_id: string & $brand<"Uuid">; position: string & $brand<"CellItemPosition">; child_id: string & $brand<"Uuid">; created_at: string; } import {ItemJson} from '@fuzdev/fuz_app/auth/cell_item_action_specs.js';

Wire-format for a cell_item row.

position is branded CellItemPosition so consumers that round-trip the value back into a position_after / position input field don't need a cast at every call site. Wire ingress is validated by the CellItemPosition Zod schema (alphabet + length); wire egress trusts the DB CHECK constraint that backs cell_item.position, so the server-side to_item_json casts a raw string from CellItemRow.

parent_id

type string & $brand<"Uuid">

position

type string & $brand<"CellItemPosition">

child_id

type string & $brand<"Uuid">

created_at

type string

jsonrpc_error_code_to_http_status
#

http/jsonrpc_errors.ts view source

(code: -32700 | -32600 | -32601 | -32602 | -32603 | (number & $brand<"JsonrpcServerErrorCode">)): ContentfulStatusCode import {jsonrpc_error_code_to_http_status} from '@fuzdev/fuz_app/http/jsonrpc_errors.js';

Map a JSON-RPC error code to an HTTP status code.

Returns 500 for unrecognized codes (consumer-defined codes without a mapping default to internal server error). The return is narrowed to Hono's ContentfulStatusCode so call sites can pass the result to c.json(body, status) without as any — 499 (nginx "client closed request") is non-standard and gets absorbed by the cast here rather than at every dispatcher branch.

code

returns

ContentfulStatusCode

JSONRPC_ERROR_CODE_TO_HTTP_STATUS
#

http/jsonrpc_errors.ts view source

Record<number, number>

type Record<number, number>

import {JSONRPC_ERROR_CODE_TO_HTTP_STATUS} from '@fuzdev/fuz_app/http/jsonrpc_errors.js';

Maps JSON-RPC error codes to HTTP status codes.

Extensible — consumers with domain-specific error codes assign directly (JSONRPC_ERROR_CODE_TO_HTTP_STATUS[-32020] = 502) at module load. The lookup function reads at call time, so mutation is the supported extension mechanism.

jsonrpc_error_code_to_name
#

http/jsonrpc_errors.ts view source

(code: -32700 | -32600 | -32601 | -32602 | -32603 | (number & $brand<"JsonrpcServerErrorCode">)): JsonrpcErrorName import {jsonrpc_error_code_to_name} from '@fuzdev/fuz_app/http/jsonrpc_errors.js';

Map a JSON-RPC error code to its canonical name ('not_found', 'forbidden', etc.). Falls back to 'internal_error' for codes outside the standard taxonomy so REST emitters that read this for their error field always have a stable string to emit.

code

returns

JsonrpcErrorName

JSONRPC_ERROR_CODE_TO_NAME
#

http/jsonrpc_errors.ts view source

Readonly<Record<number, JsonrpcErrorName>>

type Readonly<Record<number, JsonrpcErrorName>>

import {JSONRPC_ERROR_CODE_TO_NAME} from '@fuzdev/fuz_app/http/jsonrpc_errors.js';

Reverse map of JSONRPC_ERROR_CODES — JSON-RPC error code → name.

Used by REST emitters that need a stable string identifier for the code in their flat-shape error body ({error: '<name>', ...}) without inventing a separate vocabulary. Built once at module load from the canonical JSONRPC_ERROR_CODES map so the two cannot drift.

Consumer-defined codes outside the standard taxonomy are not present; jsonrpc_error_code_to_name falls back to 'internal_error' so the REST shape always carries some reason rather than undefined.

JSONRPC_ERROR_CODES
#

http/jsonrpc_errors.ts view source

Readonly<Record<JsonrpcErrorName, -32700 | -32600 | -32601 | -32602 | -32603 | (number & $brand<"JsonrpcServerErrorCode">)>>

type Readonly<Record<JsonrpcErrorName, -32700 | -32600 | -32601 | -32602 | -32603 | (number & $brand<"JsonrpcServerErrorCode">)>>

import {JSONRPC_ERROR_CODES} from '@fuzdev/fuz_app/http/jsonrpc_errors.js';

Standard JSON-RPC error codes (5) plus general application codes (10).

Extensible — consumers add domain-specific codes to their own objects by casting as JsonrpcErrorCode. Application codes use the -32000 to -32099 range reserved by the JSON-RPC spec.

Frozen with Object.freeze to convert accidental mutation (test cross-contamination, cast escapes) into loud TypeErrors. Spread into a fresh object to extend.

jsonrpc_error_messages
#

http/jsonrpc_errors.ts view source

Readonly<Record<JsonrpcErrorName, (...args: any[]) => { [x: string]: unknown; code: -32700 | -32600 | -32601 | -32602 | -32603 | (number & $brand<"JsonrpcServerErrorCode">); message: string; data?: unknown; }>>

type Readonly<Record<JsonrpcErrorName, (...args: any[]) => { [x: string]: unknown; code: -32700 | -32600 | -32601 | -32602 | -32603 | (number & $brand<"JsonrpcServerErrorCode">); message: string; data?: unknown; }>>

import {jsonrpc_error_messages} from '@fuzdev/fuz_app/http/jsonrpc_errors.js';

Named constructors for JsonrpcErrorObject values.

Each function creates a JSON-RPC error object with the correct code and a sensible default message. Used by the catch layer in apply_route_specs to build response bodies.

Frozen so tests must compose new objects rather than monkey-patch.

jsonrpc_errors
#

http/jsonrpc_errors.ts view source

{ readonly parse_error: (...args: any[]) => ThrownJsonrpcError; readonly invalid_request: (...args: any[]) => ThrownJsonrpcError; readonly method_not_found: (...args: any[]) => ThrownJsonrpcError; ... 11 more ...; readonly request_cancelled: (...args: any[]) => ThrownJsonrpcError; } import {jsonrpc_errors} from '@fuzdev/fuz_app/http/jsonrpc_errors.js';

Named constructors for ThrownJsonrpcError.

Usage: throw jsonrpc_errors.not_found('user') or throw jsonrpc_errors.forbidden().

JSONRPC_INTERNAL_ERROR
#

JSONRPC_INVALID_PARAMS
#

JSONRPC_INVALID_REQUEST
#

JSONRPC_METHOD_NOT_FOUND
#

JSONRPC_PARSE_ERROR
#

JSONRPC_SERVER_ERROR_END
#

http/jsonrpc.ts view source

-32099 import {JSONRPC_SERVER_ERROR_END} from '@fuzdev/fuz_app/http/jsonrpc.js';

End of the server-defined error code range (-32099).

JSONRPC_SERVER_ERROR_START
#

http/jsonrpc.ts view source

-32000 import {JSONRPC_SERVER_ERROR_START} from '@fuzdev/fuz_app/http/jsonrpc.js';

Start of the server-defined error code range (-32000).

JSONRPC_VERSION
#

JsonrpcErrorCode
#

http/jsonrpc.ts view source

value + type

-32700 | -32600 | -32601 | -32602 | -32603 | (number & $brand<"JsonrpcServerErrorCode">)

type -32700 | -32600 | -32601 | -32602 | -32603 | (number & $brand<"JsonrpcServerErrorCode">)

import {JsonrpcErrorCode} from '@fuzdev/fuz_app/http/jsonrpc.js';

A valid JSON-RPC error code — one of the 5 standard codes or a server-defined code in the -32000 to -32099 range.

JsonrpcErrorName
#

http/jsonrpc_errors.ts view source

JsonrpcErrorName

type "parse_error" | "invalid_request" | "method_not_found" | "invalid_params" | "internal_error" | "unauthenticated" | "forbidden" | "not_found" | "conflict" | "validation_error" | "rate_limited" | "service_unavailable" | "timeout" | "queue_overflow" | "request_cancelled"

import type {JsonrpcErrorName} from '@fuzdev/fuz_app/http/jsonrpc_errors.js';

Names of standard and general application JSON-RPC error codes.

JsonrpcErrorObject
#

http/jsonrpc.ts view source

value + type

{ [x: string]: unknown; code: -32700 | -32600 | -32601 | -32602 | -32603 | (number & $brand<"JsonrpcServerErrorCode">); message: string; data?: unknown; } import {JsonrpcErrorObject} from '@fuzdev/fuz_app/http/jsonrpc.js';

Error object within a JSON-RPC error response.

code

type -32700 | -32600 | -32601 | -32602 | -32603 | (number & $brand<"JsonrpcServerErrorCode">)

message

type string

data?

type unknown

[key: string]

type unknown

JsonrpcErrorResponse
#

http/jsonrpc.ts view source

value + type

{ [x: string]: unknown; jsonrpc: "2.0"; id: string | number | null; error: { [x: string]: unknown; code: -32700 | -32600 | -32601 | -32602 | -32603 | (number & $brand<"JsonrpcServerErrorCode">); message: string; data?: unknown; }; } import {JsonrpcErrorResponse} from '@fuzdev/fuz_app/http/jsonrpc.js';

A response that indicates an error occurred.

jsonrpc

type "2.0"

id

type string | number | null

error

type JsonrpcErrorObject

[key: string]

type unknown

JsonrpcErrorResponseFrame
#

testing/transports/ws_client.ts view source

JsonrpcErrorResponseFrame<D> import type {JsonrpcErrorResponseFrame} from '@fuzdev/fuz_app/testing/transports/ws_client.js';

generics

JsonrpcErrorResponseFrame<D = unknown>
D
default unknown

jsonrpc

type "2.0"

id

type number | string

error

type { code: number; message: string; data?: D | undefined; }

JsonrpcMcpMeta
#

http/jsonrpc.ts view source

value + type

{ [x: string]: unknown; } import {JsonrpcMcpMeta} from '@fuzdev/fuz_app/http/jsonrpc.js';

MCP metadata object — loose to allow additional properties and .extend.

[key: string]

type unknown

JsonrpcMessage
#

http/jsonrpc.ts view source

value + type

{ [x: string]: unknown; jsonrpc: "2.0"; id: string | number; method: string; params?: { [x: string]: unknown; } | undefined; } | { [x: string]: unknown; jsonrpc: "2.0"; id: string | number; result: JSONType; } | { ...; } | { ...; }

type JsonrpcRequest | JsonrpcResponse | JsonrpcErrorResponse | JsonrpcNotification

import {JsonrpcMessage} from '@fuzdev/fuz_app/http/jsonrpc.js';

Any valid JSON-RPC message (request, notification, response, or error response).

JsonrpcMessageFromClientToServer
#

http/jsonrpc.ts view source

value + type

{ [x: string]: unknown; jsonrpc: "2.0"; id: string | number; method: string; params?: { [x: string]: unknown; } | undefined; } | { [x: string]: unknown; jsonrpc: "2.0"; method: string; params?: { [x: string]: unknown; } | undefined; }

type JsonrpcRequest | JsonrpcNotification

import {JsonrpcMessageFromClientToServer} from '@fuzdev/fuz_app/http/jsonrpc.js';

Messages a client can send to a server (request or notification).

JsonrpcMessageFromServerToClient
#

http/jsonrpc.ts view source

value + type

{ [x: string]: unknown; jsonrpc: "2.0"; id: string | number; result: JSONType; } | { [x: string]: unknown; jsonrpc: "2.0"; id: string | number | null; error: { [x: string]: unknown; code: -32700 | -32600 | -32601 | -32602 | -32603 | (number & $brand<...>); message: string; data?: unknown; }; } | { ...; }

type JsonrpcResponse | JsonrpcErrorResponse | JsonrpcNotification

import {JsonrpcMessageFromServerToClient} from '@fuzdev/fuz_app/http/jsonrpc.js';

Messages a server can send to a client (notification, response, or error response).

JsonrpcMethod
#

http/jsonrpc.ts view source

value + type

string import {JsonrpcMethod} from '@fuzdev/fuz_app/http/jsonrpc.js';

A JSON-RPC method name.

JsonrpcNotification
#

http/jsonrpc.ts view source

value + type

{ [x: string]: unknown; jsonrpc: "2.0"; method: string; params?: { [x: string]: unknown; } | undefined; } import {JsonrpcNotification} from '@fuzdev/fuz_app/http/jsonrpc.js';

A notification which does not expect a response.

jsonrpc

type "2.0"

method

type string

params?

type JsonrpcNotificationParams

[key: string]

type unknown

JsonrpcNotificationFrame
#

testing/transports/ws_client.ts view source

JsonrpcNotificationFrame<P> import type {JsonrpcNotificationFrame} from '@fuzdev/fuz_app/testing/transports/ws_client.js';

generics

JsonrpcNotificationFrame<P = unknown>
P
default unknown

jsonrpc

type "2.0"

method

type string

params

type P

JsonrpcNotificationParams
#

http/jsonrpc.ts view source

value + type

{ [x: string]: unknown; } import {JsonrpcNotificationParams} from '@fuzdev/fuz_app/http/jsonrpc.js';

Notification params — loose object. Per-action schemas validate _meta content.

[key: string]

type unknown

JsonrpcProgressToken
#

http/jsonrpc.ts view source

value + type

string | number

type string | number

import {JsonrpcProgressToken} from '@fuzdev/fuz_app/http/jsonrpc.js';

A progress token, used to associate progress notifications with the original request.

JsonrpcRequest
#

http/jsonrpc.ts view source

value + type

{ [x: string]: unknown; jsonrpc: "2.0"; id: string | number; method: string; params?: { [x: string]: unknown; } | undefined; } import {JsonrpcRequest} from '@fuzdev/fuz_app/http/jsonrpc.js';

A request that expects a response.

jsonrpc

type "2.0"

id

type string | number

method

type string

params?

type JsonrpcRequestParams

[key: string]

type unknown

JsonrpcRequestId
#

http/jsonrpc.ts view source

value + type

string | number

type string | number

import {JsonrpcRequestId} from '@fuzdev/fuz_app/http/jsonrpc.js';

A uniquely identifying id for a request in JSON-RPC. Like MCP, excludes null.

JsonrpcRequestParams
#

http/jsonrpc.ts view source

value + type

{ [x: string]: unknown; } import {JsonrpcRequestParams} from '@fuzdev/fuz_app/http/jsonrpc.js';

Request params — loose object. Per-action schemas validate _meta content.

[key: string]

type unknown

JsonrpcRequestParamsMeta
#

http/jsonrpc.ts view source

value + type

{ [x: string]: unknown; progressToken?: string | number | undefined; } import {JsonrpcRequestParamsMeta} from '@fuzdev/fuz_app/http/jsonrpc.js';

Request params metadata — extends MCP meta with optional progress token.

progressToken?

If specified, the caller is requesting out-of-band progress notifications for this request. The value is an opaque token attached to subsequent notifications. The receiver is not obligated to provide these notifications.

type string | number

[key: string]

type unknown

JsonrpcResponse
#

http/jsonrpc.ts view source

value + type

{ [x: string]: unknown; jsonrpc: "2.0"; id: string | number; result: JSONType; } import {JsonrpcResponse} from '@fuzdev/fuz_app/http/jsonrpc.js';

A successful (non-error) response to a request.

jsonrpc

type "2.0"

id

type string | number

result

type JSONType

[key: string]

type unknown

JsonrpcResponseOrError
#

http/jsonrpc.ts view source

value + type

{ [x: string]: unknown; jsonrpc: "2.0"; id: string | number; result: JSONType; } | { [x: string]: unknown; jsonrpc: "2.0"; id: string | number | null; error: { [x: string]: unknown; code: -32700 | -32600 | -32601 | -32602 | -32603 | (number & $brand<...>); message: string; data?: unknown; }; }

type JsonrpcResponse | JsonrpcErrorResponse

import {JsonrpcResponseOrError} from '@fuzdev/fuz_app/http/jsonrpc.js';

A successful response or an error response.

JsonrpcResult
#

http/jsonrpc.ts view source

value + type

JSONType

type JSONType

import {JsonrpcResult} from '@fuzdev/fuz_app/http/jsonrpc.js';

Result — any JSON value per JSON-RPC 2.0 §5. Per-action spec.output is the actual contract; the envelope only asserts presence + JSON-ness.

z.json() is required (not implicitly optional like z.unknown() / z.any(), which would let an error envelope {jsonrpc, id, error} parse successfully against the success envelope and break union discrimination).

JsonrpcServerErrorCode
#

http/jsonrpc.ts view source

value + type

number & $brand<"JsonrpcServerErrorCode">

type number & $brand<"JsonrpcServerErrorCode">

import {JsonrpcServerErrorCode} from '@fuzdev/fuz_app/http/jsonrpc.js';

A server-defined error code in the -32000 to -32099 range.

external types

z.infer<typeof JsonrpcServerErrorCode>

JsonrpcSuccessResponseFrame
#

testing/transports/ws_client.ts view source

JsonrpcSuccessResponseFrame<R> import type {JsonrpcSuccessResponseFrame} from '@fuzdev/fuz_app/testing/transports/ws_client.js';

generics

JsonrpcSuccessResponseFrame<R = unknown>
R
default unknown

jsonrpc

type "2.0"

id

type number | string

result

type R

KeeperHeaderProvider
#

testing/integration_helpers.ts view source

KeeperHeaderProvider import type {KeeperHeaderProvider} from '@fuzdev/fuz_app/testing/integration_helpers.js';

Header-builder triple shared by TestApp (in-process) and TestFixture (cross-backend fixture protocol). Both satisfy this shape structurally — pick_auth_headers accepts either without a cast.

create_session_headers

type (extra?: Record<string, string> | undefined): Record<string, string>

extra?

type Record<string, string>
optional
returns Record<string, string>

create_bearer_headers

type (extra?: Record<string, string> | undefined): Record<string, string>

extra?

type Record<string, string>
optional
returns Record<string, string>

create_daemon_token_headers

type (extra?: Record<string, string> | undefined): Record<string, string>

extra?

type Record<string, string>
optional
returns Record<string, string>

KeyedAsyncSlot
#

ui/keyed_async_slot.svelte.ts view source

import {KeyedAsyncSlot} from '@fuzdev/fuz_app/ui/keyed_async_slot.svelte.js';

Reactive container for many concurrent async operations keyed by K.

generics

KeyedAsyncSlot<K, T = void, E = string>
K
T
default void
E
default string

constructor

type new <K, T = void, E = string>(options?: KeyedAsyncSlotOptions<T, E>): KeyedAsyncSlot<K, T, E>

options

default {}

has

Reactive — true once run(key, ...) has been called and the entry hasn't been deleted.

type (key: K): boolean

key

type K
returns boolean

get

Direct access to the underlying AsyncSlot for key, or undefined if no run() has been issued for it yet. Reactive on map population and on the slot's $state.raw fields.

Prefer the sugar getters (loading, error) for templates; reach for get(key) when you need error_data, data, or to call abort() / set() / reset() on the underlying slot.

type (key: K): AsyncSlot<T, E> | undefined

key

type K
returns AsyncSlot<T, E> | undefined

loading

Reactive — false for keys that have never been used.

type (key: K): boolean

key

type K
returns boolean

error

Reactive — null when the key has no entry or hasn't failed.

type (key: K): E | null

key

type K
returns E | null

failed

Reactive — false for keys that have never been used.

type (key: K): boolean

key

type K
returns boolean

succeeded

Reactive — false for keys that have never been used.

type (key: K): boolean

key

type K
returns boolean

keys

Reactive iterator over every key with state.

type (): IterableIterator<K>

returns IterableIterator<K, any, any>

values

Reactive iterator over every slot.

type (): IterableIterator<AsyncSlot<T, E>>

returns IterableIterator<AsyncSlot<T, E>, any, any>

entries

Reactive iterator over [key, slot] pairs.

type (): IterableIterator<[K, AsyncSlot<T, E>]>

returns IterableIterator<[K, AsyncSlot<T, E>], any, any>

run

Run an async operation for key. Lazily creates an AsyncSlot for the key on first use, inheriting the constructor's map_error / preserve_error_on_retry options.

Supersession is scoped to key: a second run(key, ...) aborts the first's signal AND drops its commit. Calls on different keys are fully independent (each has its own AbortController).

type (key: K, fn: (signal: AbortSignal) => Promise<T>, options?: RunOptions | undefined): Promise<T | undefined>

key

type K

fn

type (signal: AbortSignal) => Promise<T>

options?

optional
returns Promise<T | undefined>

the resolved value on success; undefined on failure, abort, or supersession.

abort

Abort the in-flight run for key, if any. No-op when the key has no entry. The slot stays in the map at its prior resolved status — call delete to remove the entry entirely.

type (key: K, reason?: unknown): void

key

type K

reason?

type unknown
optional
returns void

abort_all

Abort every in-flight run. Resolved entries stay in the map — call reset to clear them too.

type (reason?: unknown): void

reason?

type unknown
optional
returns void

delete

Abort the in-flight run for key (if any) and remove the entry from the map. After delete(key), has(key) returns false and the sugar getters report the no-entry defaults — typically how a UI dismisses a per-row error indicator.

type (key: K): boolean

key

type K
returns boolean

true if the key had an entry.

mutates

  • this

reset

Abort every in-flight run and clear the map. The keyed slot looks like a fresh instance afterwards.

type (): void

returns void

mutates

  • this

size

Total number of keys with state (pending OR resolved). Reactive.

type number

getter

KeyedAsyncSlotOptions
#

ui/keyed_async_slot.svelte.ts view source

KeyedAsyncSlotOptions<T, E> import type {KeyedAsyncSlotOptions} from '@fuzdev/fuz_app/ui/keyed_async_slot.svelte.js';

Constructor options for KeyedAsyncSlot. Propagated to every child AsyncSlot at lazy creation time.

initial from AsyncSlotOptions is deliberately omitted — keyed slots have no per-key seed concept (the entries don't exist until run() creates them).

generics

KeyedAsyncSlotOptions<T, E = string>
T
E
default string

map_error?

Convert a caught throw into the error value stored in AsyncSlot.error. Default extracts Error.message (falling back to 'Request failed' for non-Error throws). Pass to_rpc_error_message to unwrap JSON-RPC data.reason codes.

type (e: unknown): E

e

type unknown
returns E

preserve_error_on_retry?

When true, the previous error / error_data survive the start of a new run() until the next success (or another failure overwrites them). Useful for retry UX that wants to keep the failure message visible alongside an inline spinner. Default false — run() clears the error at the start so the pending state reads "no current error."

type boolean

Keyring
#

auth/keyring.ts view source

Keyring import type {Keyring} from '@fuzdev/fuz_app/auth/keyring.js';

Opaque keyring that encapsulates secret keys. Only exposes sign/verify operations, never the raw keys.

sign

Sign a value with HMAC SHA-256.

type (value: string): Promise<string>

value

type string
returns Promise<string>

signed value in format: value.signature

verify

Verify a signed value and extract the original. Tries all keys in order to support key rotation.

type (signed_value: string): Promise<{ value: string; key_index: number; } | null>

signed_value

type string
returns Promise<{ value: string; key_index: number; } | null>

object with value and key_index, or null if invalid

list_roles_with_grant_path
#

auth/role_schema.ts view source

(role_specs: ReadonlyMap<string, RoleSpec>, grant_path: string): string[] import {list_roles_with_grant_path} from '@fuzdev/fuz_app/auth/role_schema.js';

Filter helper: list every role whose grant_paths includes the given path.

role_specs

type ReadonlyMap<string, RoleSpec>

grant_path

type string

returns

string[]

load_config
#

cli/config.ts view source

<T>(runtime: Pick<FsReadDeps, "stat" | "read_text_file"> & LogDeps, path: string, schema: Schema<T, any, string, unknown>): Promise<T | null> import {load_config} from '@fuzdev/fuz_app/cli/config.js';

Load CLI configuration from a JSON file validated by a @fuzdev/fuz_schema schema.

runtime

runtime with file read capability

type Pick<FsReadDeps, "stat" | "read_text_file"> & LogDeps

path

path to the config JSON file

type string

schema

fuz_schema schema to validate against

type Schema<T, any, string, unknown>

returns

Promise<T | null>

parsed config, or null if file doesn't exist or is invalid

generics

load_config<T>
T

load_env
#

env/load.ts view source

<T extends z.ZodObject>(schema: T, get_env: (key: string) => string | undefined): output<T> import {load_env} from '@fuzdev/fuz_app/env/load.js';

Load and validate env vars against a Zod schema.

schema

Zod object schema defining expected env vars

type T

get_env

function to read an env var by key

type (key: string) => string | undefined

returns

output<T>

validated env object

generics

load_env<T extends z.ZodObject>
T
constraint z.ZodObject

throws

  • EnvValidationError - if Zod validation fails

load_env_file
#

env/dotenv.ts view source

(runtime: Pick<FsReadDeps, "read_text_file">, path: string): Promise<Record<string, string> | null> import {load_env_file} from '@fuzdev/fuz_app/env/dotenv.js';

Load and parse an env file.

Returns null only when the file does not exist. Other read errors (permission denied, I/O failure, etc.) are re-thrown so callers can distinguish "no file" from "couldn't read".

runtime

runtime with read_text_file capability

type Pick<FsReadDeps, "read_text_file">

path

path to env file

type string

returns

Promise<Record<string, string> | null>

parsed env record, or null if file doesn't exist

throws

  • Error - if reading fails for any reason other than `ENOENT` / `NotFound`

load_expected_schema
#

http/common_routes.ts view source

(url: string | URL): ExpectedSchema import {load_expected_schema} from '@fuzdev/fuz_app/http/common_routes.js';

Load a consumer's committed expected_schema.json fixture (cached by URL).

The spine ships the readiness *mechanism* but not the *expectation* — the expected column map is per-consumer (each adds its own tables), so the consumer commits the fixture and passes the loaded map to create_ready_route_spec. Call with an import.meta.url-relative URL:

create_ready_route_spec({ expected: load_expected_schema(new URL('./expected_schema.json', import.meta.url)), log: deps.log, });

The fixture is regenerated against a fresh bootstrap by the consumer's gen-time test (see testing/schema_ready_fixture.ts), so it can't silently fall behind the migration chain.

url

the fixture location (a file URL or path)

type URL | string

returns

ExpectedSchema

load_origin_conformance
#

testing/cross_backend/origin_conformance.ts view source

(): { parse: ({ raw: string; ok: true; note?: string | undefined; } | { raw: string; errors: { kind: "empty_entry" | "not_printable_ascii" | "missing_scheme" | "unsupported_scheme" | "has_path" | ... 11 more ... | "no_origins"; index?: number | undefined; }[]; note?: string | undefined; })[]; match: { ...; }[]; } import {load_origin_conformance} from '@fuzdev/fuz_app/testing/cross_backend/origin_conformance.js';

Read and validate the committed conformance table.

returns

OriginConformance

the parsed table

throws

  • Error - when the file doesn't match `OriginConformance`

load_secure_file_node
#

runtime/secure_file.ts view source

(path: string): Promise<Uint8Array<ArrayBufferLike>> import {load_secure_file_node} from '@fuzdev/fuz_app/runtime/secure_file.js';

Read a secret file with fail-loud checks (Node implementation).

  • O_NOFOLLOW atomically rejects symlinks during open (no TOCTOU window)
  • O_NONBLOCK keeps the open from waiting on a FIFO that has no writer (it has no effect on a regular file); the regular-file check on the open descriptor then refuses it, a device node, or a directory
  • O_NOCTTY keeps a terminal device at the path from becoming the process's controlling terminal in the moment before that check refuses it
  • the permission check runs on the open descriptor, not the path, so the file can't be swapped between check and read — any group/other access (not 0600/0400) is refused
  • a size cap bounds the read; the read itself is limited to cap + 1 so a file growing between stat and read is still caught

path

path to the secret file

type string

returns

Promise<Uint8Array<ArrayBufferLike>>

the file's bytes

throws

  • Error - on a missing file, symlink, non-regular file, permissive mode, oversized file, or I/O failure

LocalCallActionSpec
#

actions/action_spec.ts view source

value + type

{ method: string; initiator: "frontend" | "backend" | "both"; side_effects: boolean; input: ZodType<unknown, unknown, $ZodTypeInternals<unknown, unknown>>; output: ZodType<unknown, unknown, $ZodTypeInternals<unknown, unknown>>; ... 6 more ...; rate_limit?: "both" | ... 2 more ... | undefined; } import {LocalCallActionSpec} from '@fuzdev/fuz_app/actions/action_spec.js';

Local calls can wrap synchronous or asynchronous actions, and are the escape hatch for remote APIs that do not support SAES.

method

type string

initiator

type "frontend" | "backend" | "both"

side_effects

type boolean

input

type ZodType<unknown, unknown, $ZodTypeInternals<unknown, unknown>>

output

type ZodType<unknown, unknown, $ZodTypeInternals<unknown, unknown>>

async

type boolean

description

type string

kind

type "local_call"

auth

type null

streams?

Names the notification method this action emits as request-scoped progress. Forward-compatible handshake — transport-agnostic, does not imply a specific delivery mechanism. Registry-time validation (e.g., ensuring the named method is a remote_notification spec) is a consumer-side concern.

type string

error_reasons?

Error reason codes this action may surface via error.data.reason on failure. Declarative metadata mirroring the streams precedent — codegen, UI form-state matching, and docs read it off the spec instead of scanning handler implementations. Reuses the same as const string constants the handler throws (e.g. ERROR_ROLE_GRANT_OFFER_*) so call sites can import either side. Optional — actions that surface only standard transport errors leave it unset.

type readonly string[]

rate_limit?

Rate limit key the RPC dispatcher consults for this action. Optional — actions without it skip the rate-limit hook entirely.

  • 'ip' — keyed on the resolved client IP (get_client_ip(c)).
  • 'account' — keyed on the post-auth account id (request_context.account.id). Registration-time error if paired with auth.account !== 'required' (no account to key on).
  • 'both' — both checks run; either can block.

Throttle-requests semantics — every invocation records, regardless of outcome (different from REST login's throttle-failures, which resets on success). Suits admin mutation oracles (invite_create account- existence probe) where the *successful* invocation is the threat.

Today only RequestResponseActionSpec is consulted by the RPC dispatcher. The field is on the base for shape symmetry with streams and error_reasons; remote_notification / local_call dispatchers don't read it.

type "both" | "account" | "ip"

log_db_factory_status
#

testing/db.ts view source

(factories: DbFactory[]): void import {log_db_factory_status} from '@fuzdev/fuz_app/testing/db.js';

Log factory status to console.

factories

type DbFactory[]

returns

void

log_env_validation_error
#

env/load.ts view source

(error: EnvValidationError, label?: string | undefined): void import {log_env_validation_error} from '@fuzdev/fuz_app/env/load.js';

Log formatted env validation issues to stderr.

Handles the common case: labels each Zod issue with an optional prefix. Callers who want app-specific "getting started" instructions should check error.all_undefined before calling this.

error

the env validation error

label?

optional prefix for log lines (e.g., 'server', 'env')

type string
optional

returns

void

log_startup_summary
#

server/startup.ts view source

(surface: AppSurface, log: Logger, env_values?: Record<string, unknown> | undefined): void import {log_startup_summary} from '@fuzdev/fuz_app/server/startup.js';

Log a startup summary from an AppSurface.

Logs route count, middleware count, env breakdown (when non-empty), and event/channel counts (when non-empty). When env_values is provided, non-secret values are logged and secrets are masked with ***.

surface

log

type Logger

env_values?

optional env values to log (secrets are masked)

type Record<string, unknown>
optional

returns

void

LogDeps
#

runtime/deps.ts view source

LogDeps import type {LogDeps} from '@fuzdev/fuz_app/runtime/deps.js';

Warning/diagnostic output.

warn

Log a warning message.

type (...args: unknown[]): void

args

type unknown[]
returns void

LOGIN_RATE_LIMIT_ENABLED_ENV
#

testing/cross_backend/spine_surface_constants.ts view source

"FUZ_LOGIN_RATE_LIMIT_ENABLED" import {LOGIN_RATE_LIMIT_ENABLED_ENV} from '@fuzdev/fuz_app/testing/cross_backend/spine_surface_constants.js';

Env var both spine binaries read to enable their login rate limiters ('true' on / unset off). The cross-language contract for the login-security cross project: the TS binary reads it via runtime.env_get (a test-only flag, not in BaseServerEnv); the Rust testing_spine_stub reads it via std::env::var — so one backend-config option drives both impls (ts_spine_backend_config and rust_spine_stub_backend_config set it).

LoginForm
#

ui/LoginForm.svelte view source

import LoginForm from '@fuzdev/fuz_app/ui/LoginForm.svelte';

username_label?

Label and placeholder for the username field — set when the surface accepts only one of username/email so the UX matches.

type string
optional default 'username or email'

redirect_on_login?

Path to navigate to on successful login.

type string
optional default resolve('/')

LoginInput
#

auth/account_route_schema.ts view source

value + type

{ username: string; password: string; } import {LoginInput} from '@fuzdev/fuz_app/auth/account_route_schema.js';

Input for POST /login. Accepts a username or email in the username field.

username

type string

password

type string

LoginOutput
#

auth/account_route_schema.ts view source

value + type

{ ok: true; } import {LoginOutput} from '@fuzdev/fuz_app/auth/account_route_schema.js';

Output for POST /login. Session cookie is the operative side effect.

ok

type true

LoginSecurityCrossTestOptions
#

testing/cross_backend/login_security.ts view source

LoginSecurityCrossTestOptions import type {LoginSecurityCrossTestOptions} from '@fuzdev/fuz_app/testing/cross_backend/login_security.js';

Options for the login-security parity suite.

setup_test

Per-test fixture producer (cross-process only — see the module doc).

type (): Promise<TestFixtureBase>

readonly
returns Promise<TestFixtureBase>

login_path?

REST login route path. Default /api/account/login (the spine convention).

type string

readonly

concurrent_transactions?

Whether the backend serves concurrent logins — each its own database transaction — at once, for the burst-of-correct-logins case. Default false: a backend on one in-process connection (pglet-wasm, PGlite) refuses a second top-level transaction while one runs, so concurrent successful logins answer 500 there whatever the limiter does.

type boolean

readonly

base_url?

The backend's base URL, for the repeated-X-Forwarded-For case, which needs a raw socket to send two header lines. null (the default) skips that case.

type string | null

readonly

LogoutButton
#

ui/LogoutButton.svelte view source

accepts children

import LogoutButton from '@fuzdev/fuz_app/ui/LogoutButton.svelte';

onclick?

type (MouseEventHandler<HTMLButtonElement>) | null
optional

children?

type Snippet<[]>
optional

external types

Omit<ComponentProps<typeof PendingButton>, 'pending' | 'onclick' | 'children'>

LogoutInput
#

auth/account_route_schema.ts view source

value + type

null import {LogoutInput} from '@fuzdev/fuz_app/auth/account_route_schema.js';

Input for POST /logout. Session identity flows through the cookie.

LogoutOutput
#

auth/account_route_schema.ts view source

value + type

{ ok: true; username: string; } import {LogoutOutput} from '@fuzdev/fuz_app/auth/account_route_schema.js';

Output for POST /logout. Includes the revoked account's username for UI redraw.

ok

type true

username

type string

make_cross_backend_project
#

testing/cross_backend/make_cross_backend_project.ts view source

({ name, global_setup, include, exclude, group_order, test_timeout, hook_timeout }: CrossBackendProjectOptions): { extends: true; test: { name: string; include: string[]; exclude: string[]; globalSetup: string[]; isolate: false; fileParallelism: false; sequence: { ...; }; testTimeout: number; hookTimeout: number; }; } import {make_cross_backend_project} from '@fuzdev/fuz_app/testing/cross_backend/make_cross_backend_project.js';

Build a single cross-backend vitest project config. Spread the results into test.projects in the consumer's vite.config.ts. isolate: false + fileParallelism: false because a project shares one spawned backend across its files, and testTimeout / hookTimeout are always emitted so a spawned-backend case is never held to vitest's 5 s / 10 s defaults.

__0

returns

{ extends: true; test: { name: string; include: string[]; exclude: string[]; globalSetup: string[]; isolate: false; fileParallelism: false; sequence: { groupOrder: number; }; testTimeout: number; hookTimeout: number; }; }

make_default_rust_backend_config
#

testing/cross_backend/default_backend_configs.ts view source

(opts: MakeDefaultRustBackendConfigOptions): BackendConfig import {make_default_rust_backend_config} from '@fuzdev/fuz_app/testing/cross_backend/default_backend_configs.js';

Shared builder for Rust-family backends. Owns the common env baseline (RUST_LOG, HOST, port, real Postgres, cookie keys, bootstrap token path, the FUZ_TESTING_RESET_DB_ON_STARTUP=true self-wipe gate) plus the 120s startup window for cargo's first-run build cost.

opts

returns

BackendConfig

make_default_ts_backend_config
#

testing/cross_backend/default_backend_configs.ts view source

(opts: MakeDefaultTsBackendConfigOptions): BackendConfig import {make_default_ts_backend_config} from '@fuzdev/fuz_app/testing/cross_backend/default_backend_configs.js';

Shared builder for TS-family backends (Deno + Node). Owns the common env baseline (NODE_ENV, HOST, PORT, in-memory pglet, cookie keys, bootstrap token path) so per-backend factories only declare what genuinely differs.

opts

returns

BackendConfig

MakeDefaultRustBackendConfigOptions
#

testing/cross_backend/default_backend_configs.ts view source

MakeDefaultRustBackendConfigOptions import type {MakeDefaultRustBackendConfigOptions} from '@fuzdev/fuz_app/testing/cross_backend/default_backend_configs.js';

name

Diagnostic label; also used as the tmpdir prefix when paths is omitted.

type string

readonly

port

TCP port the binary listens on.

type number

readonly

start_command

argv passed to the spawn (first entry is the binary).

type readonly string[]

readonly

database_url

Required — Rust needs real Postgres (an in-process WASM database isn't reachable from tokio-postgres). Consumers typically supply 'postgres://localhost/{repo}_test_{name}'.

type string

readonly

extra_env?

Merged on top of the generic env baseline; later keys win.

type Readonly<Record<string, string>>

readonly

capabilities?

type BackendCapabilities

readonly

paths?

Pre-computed paths; defaults to build_test_backend_paths(name).

type TestBackendPaths

readonly

bootstrap_overrides?

Override individual bootstrap fields (username/password/token).

type Partial<BackendBootstrapConfig>

readonly

port_env_var?

Env-var name the binary reads for its port. Defaults to 'PORT'. Consumers whose binary reads a different name (e.g. 'ZZZ_PORT') override.

type string

readonly

rust_log?

Initial value for RUST_LOG. Defaults to 'info'. Consumers pass their binary-specific module filter (e.g. 'info,zzz_server=info,testing_zzz_server=info').

type string

readonly

cookie_name?

Session cookie name the binary uses. Defaults to 'fuz_session'. Must match the consumer's session config (see the TS builder's note).

type string

readonly

MakeDefaultTsBackendConfigOptions
#

testing/cross_backend/default_backend_configs.ts view source

MakeDefaultTsBackendConfigOptions import type {MakeDefaultTsBackendConfigOptions} from '@fuzdev/fuz_app/testing/cross_backend/default_backend_configs.js';

name

Diagnostic label; also used as the tmpdir prefix when paths is omitted.

type string

readonly

port

TCP port the binary listens on.

type number

readonly

start_command

argv passed to the spawn (first entry is the binary).

type readonly string[]

readonly

database_url?

Defaults to 'memory://' (in-memory pglet).

type string

readonly

extra_env?

Merged on top of the generic env baseline; later keys win.

type Readonly<Record<string, string>>

readonly

capabilities?

type BackendCapabilities

readonly

paths?

Pre-computed paths; defaults to build_test_backend_paths(name).

type TestBackendPaths

readonly

bootstrap_overrides?

Override individual bootstrap fields (username/password/token).

type Partial<BackendBootstrapConfig>

readonly

port_env_var?

Env-var name the binary reads for its port. Defaults to 'PORT'. Consumers whose binary reads a different name (e.g. 'ZZZ_PORT') override.

type string

readonly

cookie_name?

Session cookie name the binary's create_session_config uses. Defaults to 'fuz_session'. Must match the consumer's session config — the harness threads the _testing_reset-returned keeper cookie into its jar under this name, so a mismatch surfaces as 401s on the create_account path (e.g. fuz_forge uses 'fuz_forge_session').

type string

readonly

MASKED_VALUE
#

env/mask.ts view source

"***" import {MASKED_VALUE} from '@fuzdev/fuz_app/env/mask.js';

Placeholder displayed in place of secret values.

MAX_IN_FLIGHT_PEER_REQUESTS_PER_CONNECTION
#

actions/peer_request.ts view source

256 import {MAX_IN_FLIGHT_PEER_REQUESTS_PER_CONNECTION} from '@fuzdev/fuz_app/actions/peer_request.js';

Per-connection cap on concurrent in-flight server→client requests. A caller past it gets too_many_in_flight instead of growing the pending map unbounded. Twin of the Rust spine's DEFAULT_MAX_OUTBOUND_PEER_REQUESTS_PER_CONN.

MAX_INBOUND_DISPATCHES_PER_CONNECTION
#

actions/register_action_ws.ts view source

128 import {MAX_INBOUND_DISPATCHES_PER_CONNECTION} from '@fuzdev/fuz_app/actions/register_action_ws.js';

Max requests one socket may have dispatching at once. A request that arrives past it is answered queue_overflow with its id (WS_INBOUND_DISPATCH_OVERFLOW_MESSAGE) and not dispatched; the socket stays open, and the requests already running keep answering.

A runtime adapter can hand frames over without awaiting the previous one's dispatch (@hono/node-ws does not await onMessage), so without a ceiling one socket could hold any number of handlers — and their pooled database work — at once. Shedding rather than queueing keeps the socket reading, so a reply to a server-initiated request still reaches the handler awaiting it. Generous, so ordinary pipelining never meets it. Same value as the Rust spine's MAX_INBOUND_DISPATCHES_PER_CONN.

Counted are the requests that passed envelope validation and are not yet answered — the entries of the socket's cancellation map. Notifications, replies to server-initiated requests, and refused frames never count.

MAX_PRE_ADMISSION_FRAMES
#

actions/register_action_ws.ts view source

256 import {MAX_PRE_ADMISSION_FRAMES} from '@fuzdev/fuz_app/actions/register_action_ws.js';

Max inbound frames one socket may have queued while it is still opening — between the handshake and the end of the credential re-check plus on_socket_open. One more closes the socket with WS_CLOSE_POLICY_VIOLATION.

Frames sent before the socket is open wait rather than dispatch (nothing runs on a credential that has not been re-read), and a runtime adapter hands them over with no backpressure, so the wait needs a bound. This is the count half; PRE_ADMISSION_QUEUE_BYTES_FACTOR is the byte half.

Sized so a client flushing its durable queue on reopen never meets it at the default DEFAULT_QUEUE_MAX_SIZE (actions/socket.svelte.ts). A consumer that raises the client's queue above this cap makes a full flush overflow it: the socket closes with WS_CLOSE_POLICY_VIOLATION, the flushed requests fail, and the client reconnects.

MAX_SECURE_FILE_SIZE
#

runtime/secure_file.ts view source

4096 import {MAX_SECURE_FILE_SIZE} from '@fuzdev/fuz_app/runtime/secure_file.js';

Maximum size for secure files. Prevents DoS from unexpectedly large files; 4 KiB is generous for token/key material. Matches the Rust twin's MAX_SECURE_FILE_SIZE.

MAX_TOKEN_SCOPE_BYTES
#

MEMOS_SCHEMA
#

db/fact_ddl.ts view source

"\nCREATE TABLE IF NOT EXISTS memo (\n\tfn_id TEXT NOT NULL,\n\tinput_hash TEXT NOT NULL,\n\toutput_hash TEXT NOT NULL,\n\tcreated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),\n\tPRIMARY KEY (fn_id, input_hash)\n)" import {MEMOS_SCHEMA} from '@fuzdev/fuz_app/db/fact_ddl.js';

memo table — (fn_id, input_hash) → output_hash for memoized computations.

MenuLink
#

merge_error_schemas
#

http/schema_helpers.ts view source

(spec: { method: RouteMethod; auth: { account: "optional" | "none" | "required"; actor: "optional" | "none" | "required"; roles?: readonly string[] | undefined; credential_types?: readonly string[] | undefined; required_scope?: string | undefined; }; ... 4 more ...; errors?: Partial<...> | undefined; }, middleware_errors?: Partial<...> | ... 1 more ... | undefined): Partial<...> | null import {merge_error_schemas} from '@fuzdev/fuz_app/http/schema_helpers.js';

Merge auto-derived, middleware, and explicit error schemas for a route spec.

Merge order: derived -> middleware -> explicit route errors. Later layers override earlier ones for the same status code.

spec

the route spec (reads method, auth, input, params, query, rate_limit, errors)

type { method: RouteMethod; auth: { account: "optional" | "none" | "required"; actor: "optional" | "none" | "required"; roles?: readonly string[] | undefined; credential_types?: readonly string[] | undefined; required_scope?: string | undefined; }; ... 4 more ...; errors?: Partial<...> | undefined; }

middleware_errors?

errors contributed by middleware whose path matches the route

type Partial<Record<number, ZodType<unknown, unknown, $ZodTypeInternals<unknown, unknown>>>> | null
optional

returns

Partial<Record<number, ZodType<unknown, unknown, $ZodTypeInternals<unknown, unknown>>>> | null

merged error schemas, or null if empty

MethodCoverageEntry
#

testing/cross_backend/method_coverage.ts view source

MethodCoverageEntry import type {MethodCoverageEntry} from '@fuzdev/fuz_app/testing/cross_backend/method_coverage.js';

One row of the live-RPC-method coverage manifest.

method

The RPC method name (action.spec.method).

type string

readonly

tier

How this method is covered.

type MethodCoverageTier

readonly

capability?

The BackendCapabilities flag gating this method's cross suite, when it is capability-gated. Typed against the real interface so a stale flag is a compile error. Omit for ungated off-surface families (always-mounted, the suite runs unconditionally) and for declared / backdoor tiers.

type "sse" | "ws" | "cell_crud" | "cell_relations" | "account_lifecycle" | "fact_serving" | "ready" | "account_status" | "peer_request" | "ws_handshake_pipelining" | "ws_account_actions" | "ws_self_revocation_reply" | "cell_gated_create" | "ws_role_gated" | "ws_refusal_body"

readonly

suite?

The cross-backend suite (or test file) that covers this method. Required for off_surface — the manifest's whole point is to name the suite a method without auto-enumeration relies on. Optional for declared (auto-enumerated) / backdoor (infra).

type string

readonly

note?

Optional free-text note printed nowhere — documentation for the reader.

type string

readonly

MethodCoverageTier
#

testing/cross_backend/method_coverage.ts view source

MethodCoverageTier

type "declared" | "off_surface" | "backdoor"

import type {MethodCoverageTier} from '@fuzdev/fuz_app/testing/cross_backend/method_coverage.js';

How a live-mounted RPC method earns its cross-backend coverage.

middleware_applies
#

http/schema_helpers.ts view source

(mw_path: string, route_path: string): boolean import {middleware_applies} from '@fuzdev/fuz_app/http/schema_helpers.js';

Check if a middleware path pattern applies to a route path.

Supports Hono-style patterns:

  • /api/* matches /api/anything
  • /api/zap/* matches /api/zap/runs but not /api/account/login
  • Exact match: /health matches /health

mw_path

type string

route_path

type string

returns

boolean

middleware_paths_overlap
#

http/schema_helpers.ts view source

(a: string, b: string): boolean import {middleware_paths_overlap} from '@fuzdev/fuz_app/http/schema_helpers.js';

Check if two middleware path patterns can match a common route path — the pattern-against-pattern form of middleware_applies, over the same grammar (*, an exact path, or a /prefix/* that also matches /prefix).

a

one Hono-style pattern

type string

b

the other

type string

returns

boolean

middleware_spec_applies
#

http/schema_helpers.ts view source

(spec: { readonly path: string; readonly carve_outs?: readonly { readonly path: string; }[] | null | undefined; }, route_path: string): boolean import {middleware_spec_applies} from '@fuzdev/fuz_app/http/schema_helpers.js';

Check if a middleware layer runs on a route path: its path pattern applies (middleware_applies) and none of its carve_outs does.

spec

the layer's path and optional carve_outs

type { readonly path: string; readonly carve_outs?: readonly { readonly path: string; }[] | null | undefined; }

route_path

the route path to check

type string

returns

boolean

MiddlewareCarveOut
#

http/middleware_spec.ts view source

MiddlewareCarveOut import type {MiddlewareCarveOut} from '@fuzdev/fuz_app/http/middleware_spec.js';

Paths a middleware layer explicitly does not run on, and why — the named exception to a layer that covers every path by default (the origin check).

path

Hono-style pattern (/hooks/*, /health), matched like MiddlewareSpec.path.

type string

readonly

reason

Why these paths are safe without the layer — required, so a carve-out is an argument, not an omission.

type string

readonly

MiddlewareSpec
#

http/middleware_spec.ts view source

MiddlewareSpec import type {MiddlewareSpec} from '@fuzdev/fuz_app/http/middleware_spec.js';

A named middleware layer.

name

type string

path

type string

handler

type MiddlewareHandler

errors?

Error response schemas this middleware can produce, keyed by HTTP status code.

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

carve_outs?

Paths under path the layer does not run on. The handler enforces them itself (Hono mounts by path alone); the field is what keeps the generated surface from claiming the layer's errors on those routes.

type readonly MiddlewareCarveOut[]

Migration
#

db/migrate.ts view source

Migration import type {Migration} from '@fuzdev/fuz_app/db/migrate.js';

A single migration: a name + an up function applied inside a transaction.

Throw from up to roll back the entire chain.

name

type string

up

type (db: Db): Promise<void>

db

type Db
returns Promise<void>

MigrationError
#

db/migrate.ts view source

import {MigrationError} from '@fuzdev/fuz_app/db/migrate.js';

Tagged error thrown by run_migrations and baseline.

Branch on .kind; the message carries an operator-facing remediation hint.

inheritance

extends: Error

kind

type MigrationErrorKind

readonly

namespace?

type string

readonly

at_index?

type number

readonly

unknown_names?

type readonly string[]

readonly

constructor

type new (kind: MigrationErrorKind, message: string, context?: MigrationErrorContext | undefined): MigrationError

kind

message

type string

context?

optional

MigrationErrorContext
#

db/migrate.ts view source

MigrationErrorContext import type {MigrationErrorContext} from '@fuzdev/fuz_app/db/migrate.js';

Structured context passed alongside a MigrationError.

namespace?

type string

at_index?

type number

unknown_names?

type readonly string[]

cause?

type unknown

MigrationErrorKind
#

db/migrate.ts view source

MigrationErrorKind

type "binary-older-than-db" | "name-divergence-at-N" | "migration-failed" | "baseline-name-not-in-code" | "baseline-name-out-of-order" | "baseline-namespace-already-populated"

import type {MigrationErrorKind} from '@fuzdev/fuz_app/db/migrate.js';

Tagged error vocabulary for run_migrations and baseline.

Callers branch on .kind rather than matching error messages — message text is for operators, not control flow.

MigrationNamespace
#

db/migrate.ts view source

MigrationNamespace import type {MigrationNamespace} from '@fuzdev/fuz_app/db/migrate.js';

A named group of ordered migrations.

Array index = position in the chain. Pre-stable: bodies, names, and positions can change between versions (consumers re-bootstrap on upgrade).

namespace

type string

migrations

type Migration[]

MigrationResult
#

db/migrate.ts view source

MigrationResult import type {MigrationResult} from '@fuzdev/fuz_app/db/migrate.js';

Result of running migrations for a single namespace.

namespace

type string

applied_names

Migrations applied in this run, in sequence-ascending (execution) order.

type string[]

MigrationStatus
#

db/status.ts view source

MigrationStatus import type {MigrationStatus} from '@fuzdev/fuz_app/db/status.js';

Migration status for a single namespace.

namespace

type string

applied_names

Names of migrations recorded in the tracker, sequence-ascending.

type string[]

pending_names

Names of code migrations not yet applied (suffix of the code array).

type string[]

up_to_date

Whether applied_names is the full code array with no name divergence (no pending work, no diverged history).

type boolean

divergence?

The first applied/code divergence, if any. Absent when the applied names are a clean prefix of the code's list (the only state the runner boots against). Present means a divergent bootstrap history — a re-bootstrap (drop + migrate) is needed.

type Divergence

MigrationTracker
#

testing/schema_introspect.ts view source

value + type

{ entries: { namespace: string; name: string; sequence: number; }[]; } import {MigrationTracker} from '@fuzdev/fuz_app/testing/schema_introspect.js';

The full schema_version tracker as a deterministically-ordered list, wrapped in an object so it round-trips cleanly as a JSON-RPC result. Sorted by (namespace, sequence) on capture.

entries

type MigrationTrackerEntry[]

MigrationTrackerDiff
#

testing/schema_parity.ts view source

MigrationTrackerDiff

type { readonly kind: "tracker_row_only_in"; readonly where: "a" | "b"; readonly namespace: string; readonly name: string; } | { readonly kind: "tracker_sequence_differs"; readonly namespace: string; readonly name: string; readonly a: number; readonly b: number; }

import type {MigrationTrackerDiff} from '@fuzdev/fuz_app/testing/schema_parity.js';

Structured migration-identity drift entry. Keyed on (namespace, name) — the schema_version PK — so a name rename and a partitioning change both surface as tracker_row_only_in, and a re-order surfaces as tracker_sequence_differs.

MigrationTrackerEntry
#

testing/schema_introspect.ts view source

value + type

{ namespace: string; name: string; sequence: number; } import {MigrationTrackerEntry} from '@fuzdev/fuz_app/testing/schema_introspect.js';

A single schema_version tracker row — the migration-identity primitive.

Where SchemaSnapshot is provenance-agnostic (it captures the resulting tables and deliberately *excludes* the tracker), this is the tracker: the (namespace, name, sequence) the migration runner records per applied migration. sequence carries order; name carries identity (the PK is (namespace, name)). The cross-impl gate diffs these between the two bootstrapped spines so a migration-name or partitioning drift — invisible to the schema snapshot — is a gated fact, not a latent interop break.

namespace

Migration namespace (e.g. fuz_cell, fuz_cell_history).

type string

name

Migration name within the namespace (e.g. full_cell_schema).

type string

sequence

Per-namespace apply order, starting at 0.

type number

MinimalActionEnvironment
#

testing/ws_round_trip.ts view source

import {MinimalActionEnvironment} from '@fuzdev/fuz_app/testing/ws_round_trip.js';

Minimal ActionEventEnvironment for tests that instantiate an ActionDispatcher without pulling in the full runtime. Pre-loads a spec map from the supplied list.

inheritance

executor

type "frontend" | "backend"

constructor

type new (specs: readonly ({ method: string; initiator: "frontend" | "backend" | "both"; side_effects: boolean; input: ZodType<unknown, unknown, $ZodTypeInternals<unknown, unknown>>; output: ZodType<unknown, unknown, $ZodTypeInternals<unknown, unknown>>; ... 6 more ...; rate_limit?: "both" | ... 2 more ... | undefined; } | { ...; } | { ...; })[]): MinimalActionEnvironment

specs

type readonly ActionSpecUnion[]

lookup_action_handler

type (): undefined

returns undefined

lookup_action_spec

type (method: string): { method: string; initiator: "frontend" | "backend" | "both"; side_effects: boolean; input: ZodType<unknown, unknown, $ZodTypeInternals<unknown, unknown>>; ... 7 more ...; rate_limit?: "both" | ... 2 more ... | undefined; } | { ...; } | { ...; } | undefined

method

type string

mint_file_fact_url
#

db/file_fact_url.ts view source

(shard: string, rest: string): string & $brand<"FileFactUrl"> import {mint_file_fact_url} from '@fuzdev/fuz_app/db/file_fact_url.js';

Construct a canonical file:<shard>/<rest> URL. The writer side (db/fact_disk_storage.ts) assembles the shape from a freshly-computed hash via fact_disk_path; this helper centralizes the literal so a future shape change is a single edit.

shard

type string

rest

type string

returns

FileFactUrl

mint_narrowed_api_token
#

testing/cross_backend/narrowed_api_token.ts view source

(options: MintNarrowedApiTokenOptions): Promise<string> import {mint_narrowed_api_token} from '@fuzdev/fuz_app/testing/cross_backend/narrowed_api_token.js';

Mint a narrowed api token and return its raw bearer value.

options

returns

Promise<string>

the raw token, for an Authorization: Bearer header

throws

  • Error - when the mint is refused, naming the status and error

mint_test_session
#

testing/app_server.ts view source

(options: MintTestSessionOptions): Promise<{ session_cookie: string; }> import {mint_test_session} from '@fuzdev/fuz_app/testing/app_server.js';

Mint a real auth_session row for an existing account and return a validly-signed session cookie value referencing it. Test-only — the forge behind the cross-backend expiry conformance cases (the expired_session principal): pass a negative expires_in_seconds to produce an *expired server-side session* whose signed cookie envelope is still well-formed. Both the TS _testing_mint_session action and the in-process fixture.mint_expired_session() seam call this so the write semantics match across transports.

options

returns

Promise<{ session_cookie: string; }>

mutates

  • options.db — inserts one auth_session row.

MintNarrowedApiTokenOptions
#

testing/cross_backend/narrowed_api_token.ts view source

MintNarrowedApiTokenOptions import type {MintNarrowedApiTokenOptions} from '@fuzdev/fuz_app/testing/cross_backend/narrowed_api_token.js';

app

Transport the mint goes over — it must carry an allowed Origin (the default rpc_call one does).

type RpcCallApp

readonly

rpc_path

RPC endpoint path account_token_create is mounted on.

type string

readonly

session_headers

Session credential headers of the account that owns the token — the mint is session-only.

type Record<string, string>

readonly

methods

The RPC methods the token admits; every other method and every non-RPC surface refuses it.

type readonly string[]

readonly

name?

Token label. Default 'narrowed test token'.

type string

readonly

MintTestSessionOptions
#

testing/app_server.ts view source

MintTestSessionOptions import type {MintTestSessionOptions} from '@fuzdev/fuz_app/testing/app_server.js';

Options for mint_test_session.

db

type Db

keyring

type Keyring

session_options

type SessionOptions<string>

account_id

Account the minted session belongs to.

type string

expires_in_seconds

Session lifetime offset in seconds applied to NOW() for the auth_session.expires_at row. A negative value backdates the row so the authoritative DB-row expiry gate (query_session_get_valid — WHERE expires_at > NOW()) rejects it, while the returned cookie's own signed payload stays valid (future). Resolution therefore passes the cookie-payload check in parse_session and is refused at the DB-row gate — the gate the in-process payload-expiry tests never reach and the one that structurally needs a server-side mint.

type number

MissingColumns
#

db/schema_ready.ts view source

MissingColumns import type {MissingColumns} from '@fuzdev/fuz_app/db/schema_ready.js';

Columns the live DB is missing for a table the expected schema declares.

table

type string

columns

type string[]

MockExitError
#

runtime/mock.ts view source

import {MockExitError} from '@fuzdev/fuz_app/runtime/mock.js';

Error thrown when mock runtime.exit() is called.

Tests can catch this to verify exit behavior.

inheritance

extends: Error

code

type number

readonly

constructor

type new (code: number): MockExitError

code

type number

MockFs
#

testing/mock_fs.ts view source

MockFs import type {MockFs} from '@fuzdev/fuz_app/testing/mock_fs.js';

read_file

type (path: string, encoding: string): Promise<string>

path

type string

encoding

type string
returns Promise<string>

write_file

type (path: string, content: string, encoding: string): Promise<void>

path

type string

content

type string

encoding

type string
returns Promise<void>

get_file

type (path: string): string | undefined

path

type string
returns string | undefined

MockRuntime
#

runtime/mock.ts view source

MockRuntime import type {MockRuntime} from '@fuzdev/fuz_app/runtime/mock.js';

Mock RuntimeDeps with observable state for assertions.

inheritance

extends: RuntimeDeps

mock_env

Mock environment variables.

type Map<string, string>

mock_fs

Mock file system (path -> content).

type Map<string, string>

mock_fs_bytes

Mock binary file system (path -> bytes).

type Map<string, Uint8Array<ArrayBufferLike>>

mock_fs_modes

Recorded POSIX modes (path -> mode), written by write_text_file's mode option. A file with no recorded mode reads as 0o644 (the default-umask hazard) — so read_secure_file refuses it unless the writer asked for 0o600 explicitly, mirroring the real runtimes.

type Map<string, number>

mock_dirs

Mock directories that exist.

type Set<string>

exit_calls

Exit calls recorded (exit codes).

type number[]

command_calls

Commands executed. Captures options when passed so tests can assert cwd/timeout/signal.

type { cmd: string; args: string[]; options?: RunCommandOptions | undefined; }[]

command_inherit_calls

Commands executed with inherit.

type { cmd: string; args: string[]; }[]

stdout_writes

Stdout writes recorded.

type string[]

mock_command_results

Mock command results (cmd -> result).

type Map<string, CommandResult>

stdin_buffer

Stdin buffer for input simulation.

type Uint8Array<ArrayBufferLike> | null

fetch_calls

Fetch calls recorded.

type { input: string | URL | Request; init?: RequestInit | undefined; }[]

mock_fetch_responses

Mock fetch responses (URL substring -> Response).

type Map<string, Response>

needs_account
#

http/auth_shape.ts view source

(auth: { account: "optional" | "none" | "required"; actor: "optional" | "none" | "required"; roles?: readonly string[] | undefined; credential_types?: readonly string[] | undefined; required_scope?: string | undefined; }): boolean import {needs_account} from '@fuzdev/fuz_app/http/auth_shape.js';

True iff the route declares an account axis ('optional' or 'required'). Per registry-time invariant 3 this is implied by needs_actor(auth) in v1 (no accountless actors yet).

auth

returns

boolean

needs_actor
#

http/auth_shape.ts view source

(auth: { account: "optional" | "none" | "required"; actor: "optional" | "none" | "required"; roles?: readonly string[] | undefined; credential_types?: readonly string[] | undefined; required_scope?: string | undefined; }): boolean import {needs_actor} from '@fuzdev/fuz_app/http/auth_shape.js';

True iff the route declares an actor axis ('optional' or 'required'). Equivalent to "the dispatcher's authorization phase may resolve an actor for this request" — which by registry-time invariant 2 also means the input (or query, on REST GETs) declares acting?: ActingActor.

auth

returns

boolean

next_credential_deadline_delay
#

auth/credential_deadline.ts view source

(deadline: number, now: number): number | null import {next_credential_deadline_delay} from '@fuzdev/fuz_app/auth/credential_deadline.js';

The next timer toward deadline from now: what remains, capped at CREDENTIAL_DEADLINE_TIMER_CAP_MS, or null once the deadline is reached.

deadline

the monotonic deadline

type number

now

the monotonic clock reading

type number

returns

number | null

the next delay in ms, or null when the deadline has passed

NginxFactsValidation
#

server/x_accel.ts view source

NginxFactsValidation import type {NginxFactsValidation} from '@fuzdev/fuz_app/server/x_accel.js';

Result of the facts-location check.

ok

true when the facts location exists and is internal;.

type boolean

errors

Fatal issues — a missing or non-internal facts location.

type string[]

no_nested_transaction
#

db/db.ts view source

<T>(fn: (tx_db: Db) => Promise<T>): Promise<T> import {no_nested_transaction} from '@fuzdev/fuz_app/db/db.js';

Sentinel transaction function for transaction-scoped Db instances.

Used by driver adapters when constructing the inner Db passed to transaction callbacks.

fn

type (tx_db: Db) => Promise<T>

returns

Promise<T>

throws

  • Error - always — nested transactions are not supported

NoActorsOnAccountError
#

http/error_schemas.ts view source

value + type

{ [x: string]: unknown; error: "no_actors_on_account"; } import {NoActorsOnAccountError} from '@fuzdev/fuz_app/http/error_schemas.js';

error

type "no_actors_on_account"

[key: string]

type unknown

NON_BROWSABLE_TABLES
#

http/db_routes.ts view source

readonly string[] import {NON_BROWSABLE_TABLES} from '@fuzdev/fuz_app/http/db_routes.js';

Tables the browser can never expose, whatever browsable_tables names — the credential floor, subtracted from the consumer's allowlist and not overridable:

  • account — the one surface that would return the argon2 password-hash corpus in bulk (every other read path excludes the hash by projection).
  • auth_session / api_token — credential digests plus per-account session/token metadata.
  • bootstrap_lock — the first-admin gate; its row is load-bearing state, not content.

A table on the floor answers exactly like one that doesn't exist (404 table_not_found) so the browser doesn't confirm its presence.

NON_DELETABLE_TABLES
#

http/db_routes.ts view source

readonly string[] import {NON_DELETABLE_TABLES} from '@fuzdev/fuz_app/http/db_routes.js';

Tables this endpoint refuses to delete rows from, whatever their key shape.

Two kinds, one rule — a generic storage endpoint has no business deleting a row whose meaning lives in the domain layer:

  • audit_log — the trail and the tamper path would otherwise be the same surface. It is also part of how revocation propagates: realtime/sse_auth_guard.ts and the WS auth guard close live connections by listening to audit events, so a raw row delete here is invisible to them. bootstrap_lock — deleting the singleton leaves check_bootstrap_status advertising an open bootstrap window on the next boot that bootstrap_account.ts's atomic flip then always refuses.
  • app_settings — deleting the singleton makes every settings read throw.
  • schema_version — the migration tracker. Already refused by the single-column-primary-key rule (its key is (namespace, name)), but that is incidental: naming it here keeps the protection if the key shape ever changes.

Consumers extend this with DbRouteOptions.non_deletable_tables — the builtin set is a floor, never replaced.

noop_connection_closer
#

normalize_ip
#

http/proxy.ts view source

(ip: string): string import {normalize_ip} from '@fuzdev/fuz_app/http/proxy.js';

Normalize an IP address for consistent matching and storage.

Delegates to canonicalize_ip from http/ip_canonical.ts — collapses RFC 5952-equivalent IPv6 forms (::1, ::0001, 0:0:0:0:0:0:0:1) into a single key, emits IPv4-mapped IPv6 in dotted form, and strips the ::ffff: prefix from dotted IPv4-mapped values so the bucket collapses to plain IPv4.

  • Lowercases for case-insensitive IPv6 comparison.
  • Idempotent: calling twice produces the same result.
  • Safe on non-IP strings: normalize_ip('unknown') returns 'unknown'. Malformed inputs ('attacker:controlled', '::1\n', '203.0.113.1:8080') pass through unchanged so downstream validate_ip_strict can still reject them — canonicalization never erases the malformed-form signal.

ip

type string

returns

string

NotificationSender
#

auth/role_grant_offer_notifications.ts view source

NotificationSender import type {NotificationSender} from '@fuzdev/fuz_app/auth/role_grant_offer_notifications.js';

Narrow structural capability for sending a JSON-RPC notification to every socket bound to an account.

BackendWebsocketTransport satisfies this interface — its send_to_account(account_id, message) signature accepts the broader JsonrpcMessageFromServerToClient type, which is contravariantly compatible with JsonrpcNotification here. The interface stays local so handlers don't couple to the concrete transport, and tests can inject a capturing stub with no WS machinery.

Returns the number of sockets the notification was sent to — callers typically ignore it (used by telemetry / tests).

send_to_account

type (account_id: string & $brand<"Uuid">, message: { [x: string]: unknown; jsonrpc: "2.0"; method: string; params?: { [x: string]: unknown; } | undefined; }): number

account_id

type Uuid

message

returns number

OFFER_GRANTOR_USERNAME
#

testing/cross_backend/role_grant_offer_enumeration.ts view source

"offer_grantor" import {OFFER_GRANTOR_USERNAME} from '@fuzdev/fuz_app/testing/cross_backend/role_grant_offer_enumeration.js';

Username of the bootstrap-seeded single-actor admin grantor. The entrypoint seeds it via `extra_accounts: [{username: OFFER_GRANTOR_USERNAME, roles: [ROLE_ADMIN]}]`. A *separate* account is required (an account can't offer to itself), and it must be single-actor so its role_grant_offer_create resolves without an acting selector — which rules out the now-multi-actor keeper and fixture.create_account (whose internal invite_create runs as the multi-actor keeper and would hit actor_required).

omit_columns
#

db/sql_columns.ts view source

<T extends string>(columns: readonly T[], ...omitted: NoInfer<T>[]): readonly T[] import {omit_columns} from '@fuzdev/fuz_app/db/sql_columns.js';

Drop named columns from a *_COLUMNS const, preserving projection order — for a read that deliberately narrows the row (a client-safe listing, a metadata read that skips a payload column).

With an as const column array the omitted names are checked at compile time; the runtime check covers plain string[] consts (a consumer's).

columns

the column names

type readonly T[]

omitted

column names to leave out

type NoInfer<T>[]

returns

readonly T[]

the columns without the omitted ones, in projection order

generics

omit_columns<T extends string>
T
constraint string

throws

  • Error - when an omitted name isn't in the list — a typo here would silently keep the column it meant to hide

OpenSignupToggle
#

ORIGIN_CONFORMANCE_URL
#

ORIGIN_MIDDLEWARE_PATH
#

auth/middleware.ts view source

"*" import {ORIGIN_MIDDLEWARE_PATH} from '@fuzdev/fuz_app/auth/middleware.js';

The Hono path pattern the origin check mounts on — every path. A request to any route, the static fallback included, carrying a foreign Origin is refused; one with no Origin (a probe, a CLI client) passes. The only way out is a named carve-out with a reason (AuthMiddlewareOptions.origin_carve_outs). The Rust spine's fuz_http::compose_stack checks every route group the same way.

OriginConformance
#

testing/cross_backend/origin_conformance.ts view source

value + type

{ parse: ({ raw: string; ok: true; note?: string | undefined; } | { raw: string; errors: { kind: "empty_entry" | "not_printable_ascii" | "missing_scheme" | "unsupported_scheme" | "has_path" | ... 11 more ... | "no_origins"; index?: number | undefined; }[]; note?: string | undefined; })[]; match: { ...; }[]; } import {OriginConformance} from '@fuzdev/fuz_app/testing/cross_backend/origin_conformance.js';

The conformance table.

parse

type OriginConformanceParseRow[]

match

type OriginConformanceMatchRow[]

OriginConformanceCheck
#

OriginConformanceCrossTestOptions
#

OriginConformanceError
#

testing/cross_backend/origin_conformance.ts view source

value + type

{ kind: "empty_entry" | "not_printable_ascii" | "missing_scheme" | "unsupported_scheme" | "has_path" | "has_userinfo" | "missing_host" | "invalid_host_char" | "unbracketed_ipv6" | ... 7 more ... | "no_origins"; index?: number | undefined; } import {OriginConformanceError} from '@fuzdev/fuz_app/testing/cross_backend/origin_conformance.js';

One expected refusal of a parse row.

kind

type "empty_entry" | "not_printable_ascii" | "missing_scheme" | "unsupported_scheme" | "has_path" | "has_userinfo" | "missing_host" | "invalid_host_char" | "unbracketed_ipv6" | "partial_wildcard" | "non_canonical_ipv4" | "unclosed_ipv6" | "invalid_ipv6" | "invalid_after_ipv6" | "invalid_port" | "default_port" | "no_origins"

index?

type number

OriginConformanceMatchRow
#

testing/cross_backend/origin_conformance.ts view source

value + type

{ patterns: string; origin: string | null; allowed: boolean; note?: string | undefined; } import {OriginConformanceMatchRow} from '@fuzdev/fuz_app/testing/cross_backend/origin_conformance.js';

A match row: a valid allowlist, one Origin value or null, and the answer.

patterns

type string

origin

type string | null

allowed

type boolean

note?

type string

OriginConformanceParseRow
#

testing/cross_backend/origin_conformance.ts view source

value + type

{ raw: string; ok: true; note?: string | undefined; } | { raw: string; errors: { kind: "empty_entry" | "not_printable_ascii" | "missing_scheme" | "unsupported_scheme" | "has_path" | "has_userinfo" | ... 10 more ... | "no_origins"; index?: number | undefined; }[]; note?: string | undefined; }

type { raw: string; ok: true; note?: string | undefined; } | { raw: string; errors: { kind: "empty_entry" | "not_printable_ascii" | "missing_scheme" | "unsupported_scheme" | "has_path" | "has_userinfo" | "missing_host" | "invalid_host_char" | "unbracketed_ipv6" | "partial_wildcard" | "non_canonical_ipv4" | "unclosed_ipv6" | "invalid_ipv6" | "invalid_after_ipv6" | "invalid_port" | "default_port" | "no_origins"; index?: number | undefined; }[]; note?: string | undefined; }

import {OriginConformanceParseRow} from '@fuzdev/fuz_app/testing/cross_backend/origin_conformance.js';

A parse row: the value parses (ok: true), or is refused with errors.

OriginCrossTestOptions
#

testing/cross_backend/origin.ts view source

RpcPathCrossSuiteOptions import type {OriginCrossTestOptions} from '@fuzdev/fuz_app/testing/cross_backend/origin.js';

Options for the origin parity suite. The standard RPC-dispatched cross-suite shape (setup_test / rpc_path); aliases the shared RpcPathCrossSuiteOptions rather than minting a duplicate. Origin middleware is on every spine, so no case is capability-gated and the flag bundle is not on the shape.

rpc_path?

RPC endpoint path the methods are mounted on. Default /api/rpc.

type string

readonly

setup_test

Per-test fixture-producing function (fresh keeper + db per call).

type (): Promise<TestFixtureBase>

readonly
returns Promise<TestFixtureBase>

OriginPatternErrorKind
#

http/origin.ts view source

value + type

"empty_entry" | "not_printable_ascii" | "missing_scheme" | "unsupported_scheme" | "has_path" | "has_userinfo" | "missing_host" | "invalid_host_char" | "unbracketed_ipv6" | ... 6 more ... | "default_port"

type "empty_entry" | "not_printable_ascii" | "missing_scheme" | "unsupported_scheme" | "has_path" | "has_userinfo" | "missing_host" | "invalid_host_char" | "unbracketed_ipv6" | "partial_wildcard" | "non_canonical_ipv4" | "unclosed_ipv6" | "invalid_ipv6" | "invalid_after_ipv6" | "invalid_port" | "default_port"

import {OriginPatternErrorKind} from '@fuzdev/fuz_app/http/origin.js';

Why one allowlist entry was refused. The wire names both spines emit — the Rust spine's OriginPatternError::kind returns the same strings.

OriginScopeCrossTestOptions
#

testing/cross_backend/origin_scope.ts view source

OriginScopeCrossTestOptions import type {OriginScopeCrossTestOptions} from '@fuzdev/fuz_app/testing/cross_backend/origin_scope.js';

Options for the origin-scope parity suite.

inheritance

probe_path?

A public path outside /api the backend serves with 200. Default /ready.

type string

readonly

base_url?

The backend's base URL, for the over-cap row: given one, the request head is written to the socket alone, declaring a length one byte over the cap, so the 413 has to arrive before any body byte. null (the default) sends the whole body through the fixture's in-process transport.

type string | null

readonly

OrphanFactsListResult
#

db/fact_queries.ts view source

OrphanFactsListResult import type {OrphanFactsListResult} from '@fuzdev/fuz_app/db/fact_queries.js';

Summary + sample shape returned by query_orphan_facts_list. The sample is a small page (default 20 rows) shown in the admin panel so the operator has *some* visibility into what they're about to delete. Total count and total_size_bytes are over the full orphan set (matching the same predicate the delete handler will run).

count

type number

total_size_bytes

type number

sample

type { hash: FactHash; size: number; created_at: string; external_url: string | null; }[]

OUTBOUND_FRAME_OVERHEAD_BYTES
#

realtime/outbound_budget.ts view source

64 import {OUTBOUND_FRAME_OVERHEAD_BYTES} from '@fuzdev/fuz_app/realtime/outbound_budget.js';

What a waiting frame is charged against its budget on top of its payload bytes.

It stands for what a queued frame costs beyond its text, so a frame with little or no payload is not free: a budget of B never holds more than B divided by this many frames, whatever their size. A fixed charge, not a measurement of memory use. Same value as the Rust spine's constant.

OutboundBudget
#

realtime/outbound_budget.ts view source

import {OutboundBudget} from '@fuzdev/fuz_app/realtime/outbound_budget.js';

The accounting for one connection's waiting frames (module doc).

admit charges a frame that fits, release frees the charge of one that is no longer waiting, and clear frees them all.

max_bytes

The budget, in bytes.

type number

readonly

constructor

type new (max_bytes: number): OutboundBudget

max_bytes

the budget, in bytes; 0 admits one frame at a time

type number

throws

  • Error - when `max_bytes` is not a non-negative safe integer

admit

Charge a frame of frame_bytes payload bytes if it fits: when nothing is waiting, or when the charges waiting plus its own stay within the budget.

type (frame_bytes: number): boolean

frame_bytes

the frame's payload length in UTF-8 bytes

type number
returns boolean

whether the frame was admitted and charged

mutates

  • this — adds the frame's charge when it is admitted

release

Free the charge of one admitted frame that is no longer waiting.

type (frame_bytes: number): void

frame_bytes

the payload length the frame was admitted with

type number
returns void

mutates

  • this — removes the frame's charge

clear

Free every charge — the owner discarded what was waiting.

type (): void

returns void

mutates

  • this — removes every charge

to_overflow

What is waiting, for the log line of a frame that did not fit.

type (frame_bytes: number): OutboundOverflow

frame_bytes

the payload length of the frame that did not fit

type number

queued_bytes

Payload bytes waiting, without the per-frame overhead.

type number

getter

queued_frames

Frames waiting.

type number

getter

OutboundOverflow
#

realtime/outbound_budget.ts view source

OutboundOverflow import type {OutboundOverflow} from '@fuzdev/fuz_app/realtime/outbound_budget.js';

What was waiting when a frame did not fit its connection's budget.

queued_bytes

Payload bytes waiting, without the per-frame overhead the budget also charges.

type number

queued_frames

Frames waiting.

type number

frame_bytes

Payload bytes of the frame that did not fit, without its overhead.

type number

parse_acting
#

http/auth_shape.ts view source

(value: unknown): string | undefined import {parse_acting} from '@fuzdev/fuz_app/http/auth_shape.js';

Read an acting selector out of an unvalidated value.

The authorization phase runs before input validation on both dispatchers (see http/CLAUDE.md §Validation pipeline), so it reads the selector raw.

A malformed selector reads as omitted, not as an error. Safe because acting only ever picks among actors the resolved account already owns — a value that names nothing grants nothing — and because the typed input schema rejects it a step later, so the caller still gets a 400. Refusing here instead would put an input-shape complaint ahead of the authority gates, which is the ordering this pipeline exists to avoid.

Both callers pass through here so that rule has one home: the action dispatcher's read_acting and the REST authorization handler's read_route_acting. Twin of the Rust dispatcher's read_acting.

value

the raw acting field, from params / query / body

type unknown

returns

string | undefined

the actor id, or undefined when absent or malformed

parse_action_event
#

actions/action_event.ts view source

(raw_json: unknown, environment: ActionEventEnvironment): ActionEvent<string, "send_request" | "receive_request" | "send_response" | "receive_response" | "send_error" | "receive_error" | "send" | "receive" | "execute", "initial" | ... 3 more ... | "failed"> import {parse_action_event} from '@fuzdev/fuz_app/actions/action_event.js';

raw_json

type unknown

environment

returns

ActionEvent<string, "send_request" | "receive_request" | "send_response" | "receive_response" | "send_error" | "receive_error" | "send" | "receive" | "execute", "initial" | "parsed" | "handling" | "handled" | "failed">

parse_allowed_origins
#

http/origin.ts view source

(raw: string): Result<{ origins: RegExp[]; }, { errors: AllowedOriginsError[]; }> import {parse_allowed_origins} from '@fuzdev/fuz_app/http/origin.js';

Parses an allowlist value (conventionally the FUZ_ALLOWED_ORIGINS env var) into regex matchers for request source verification. Origin allowlisting for locally-running services — not the CSRF layer (that's SameSite: strict on session cookies plus http/simple_request.ts).

Entries are comma-separated and trimmed of ASCII whitespace. Each is written the way a browser writes an Origin — scheme://host[:port] — where a whole host label or the port may be *:

  • Exact origins: https://api.fuz.dev, http://localhost:3000
  • Wildcard labels: https://*.fuz.dev (each * is exactly one label, in any position — api.fuz.dev matches, fuz.dev and a.b.fuz.dev don't)
  • Wildcard ports: http://localhost:* (any port or none)
  • IPv6 addresses: http://[::1]:3000, https://[2001:db8::1] (exact)
  • Combined: https://*.fuz.dev:*

Refused, nothing normalized: anything but printable ASCII (punycode an international hostname), a scheme other than http or https (null, file://, chrome-extension://), a path, query, fragment or trailing slash, userinfo, a character no hostname holds, a partial wildcard, an IPv4 address not written as four canonical decimal numbers (127.1), an IPv6 address not in the form a browser sends, a port outside 1..=65535 or with leading zeros, the scheme's default port, and an empty entry. Every refused entry is reported, not just the first.

raw

the comma-separated allowlist value

type string

returns

Result

the matchers, or every refusal — no_origins when the value is blank

parse_command_args
#

cli/args.ts view source

<T extends Record<string, unknown>>(remaining: ParsedArgs, schema: Schema<T, any, string, unknown>): ParseCommandArgsResult<T> import {parse_command_args} from '@fuzdev/fuz_app/cli/args.js';

Parse command-specific args against a fuz_schema object schema.

Validates remaining args (after global flag extraction) with alias expansion and returns a typed result or an error string, each issue formatted by format_issue on a line of its own.

remaining

remaining args after global flag extraction

type ParsedArgs

schema

fuz_schema object schema for the command

type Schema<T, any, string, unknown>

returns

ParseCommandArgsResult

parse result with the typed value or an error message

generics

parse_command_args<T extends Record<string, unknown>>
T
constraint Record<string, unknown>

parse_db_name
#

dev/setup.ts view source

(url: string): string | null import {parse_db_name} from '@fuzdev/fuz_app/dev/setup.js';

Extract the database name from a PostgreSQL URL.

url

type string

returns

string | null

the database name, or null if the URL is invalid or has no path

parse_dotenv
#

env/dotenv.ts view source

(content: string): Record<string, string> import {parse_dotenv} from '@fuzdev/fuz_app/env/dotenv.js';

Parse a dotenv-format string into a record.

Values wrapped in "..." have \\ → \, \" → ", \n → newline, and \r → carriage-return decoded (symmetric with the writer in update_env_variable). Values wrapped in '...' are taken literally — no escape processing. Unquoted values are unchanged.

Inline comments are stripped after a closing quote (e.g. KEY="v" # c → v) and after whitespace on unquoted values (e.g. KEY=v # c → v). Unquoted values keep # literal when no whitespace precedes it so URL fragments like KEY=https://x.com#frag round-trip unchanged.

A leading export on a line is ignored, so a shell-sourceable .env (export KEY=value) parses identically to a plain KEY=value.

Trailing whitespace on unquoted values is lost (the raw value is trimmed); wrap the value in "..." or '...' to preserve surrounding spacing.

content

dotenv file content

type string

returns

Record<string, string>

parsed key-value pairs

parse_file_fact_url
#

db/file_fact_url.ts view source

(url: string): { url: string & $brand<"FileFactUrl">; shard: string; rest: string; } | null import {parse_file_fact_url} from '@fuzdev/fuz_app/db/file_fact_url.js';

Validate a string against the canonical shape. Returns the branded URL plus its parsed parts, or null on shape mismatch — callers decide whether that's a 404 (read), a skip (GC), or a hard reject (write).

url

type string

returns

{ url: string & $brand<"FileFactUrl">; shard: string; rest: string; } | null

parse_proxy_entry
#

http/proxy.ts view source

(entry: string): ParsedProxy import {parse_proxy_entry} from '@fuzdev/fuz_app/http/proxy.js';

Parse a trusted proxy entry string into a structured form.

Accepts plain IPs ('127.0.0.1', '::1') and CIDR notation ('10.0.0.0/8', 'fe80::/10'). Plain IPs are normalized (lowercase, IPv4-mapped IPv6 stripped) and validated. CIDR prefixes are validated against address family bounds.

entry

IP address or CIDR notation

type string

returns

ParsedProxy

throws

  • Error - on invalid IP, invalid CIDR network, or NaN/negative/over-range prefix

parse_response_error
#

ui/ui_fetch.ts view source

(response: Response, fallback?: string | undefined): Promise<string> import {parse_response_error} from '@fuzdev/fuz_app/ui/ui_fetch.js';

Safely extract an error message from a non-ok response.

Handles responses with non-JSON bodies (e.g. HTML 404 pages) that would throw on response.json().

response

type Response

fallback?

message when no .error field is found

type string
optional

returns

Promise<string>

parse_session
#

auth/session_cookie.ts view source

<TIdentity>(signed_value: string | undefined, keyring: Keyring, options: SessionOptions<TIdentity>, now_seconds?: number | undefined): Promise<ParsedSession<...> | null | undefined> import {parse_session} from '@fuzdev/fuz_app/auth/session_cookie.js';

Parse a signed session cookie value.

The signed value format is ${encode(identity)}:${expires_at}. Tries all keys in order to support key rotation.

signed_value

the raw cookie value (signed)

type string | undefined

keyring

key ring for verification

type Keyring

options

session configuration with decode logic

type SessionOptions<TIdentity>

now_seconds?

current time in seconds (for testing)

type number
optional

returns

Promise<ParsedSession<TIdentity> | null | undefined>

ParsedSession if valid, null if invalid/expired, undefined if empty/missing

generics

parse_session<TIdentity>
TIdentity

parse_stored_token_scope
#

auth/token_scope.ts view source

(value: unknown): { version: 1; kind: "full"; grandfathered?: boolean | undefined; } | { version: 1; kind: "methods"; methods: string[]; grandfathered?: boolean | undefined; } | null import {parse_stored_token_scope} from '@fuzdev/fuz_app/auth/token_scope.js';

Read a stored api_token.scope value back into a TokenScope, or null when the document is unreadable.

The DB driver may hand scope back already-parsed (JSONB → object) or as text depending on backend, so both are normalized here rather than at each call site. Lives here rather than beside the queries so the pure scope module owns every parse — and so mocking the query module doesn't drag this along.

value

type unknown

returns

{ version: 1; kind: "full"; grandfathered?: boolean | undefined; } | { version: 1; kind: "methods"; methods: string[]; grandfathered?: boolean | undefined; } | null

parse_token_scope
#

auth/token_scope.ts view source

(raw: string): { version: 1; kind: "full"; grandfathered?: boolean | undefined; } | { version: 1; kind: "methods"; methods: string[]; grandfathered?: boolean | undefined; } | null import {parse_token_scope} from '@fuzdev/fuz_app/auth/token_scope.js';

Parse a stored document, enforcing the size cap and the schema.

Fail-closed: returns null on an unreadable document, and every caller on the enforcement path treats null as "refuse the credential" rather than "allow everything". fuz_forge's hydrate path deliberately widens on a DB blip — safe there only because a role gate still applies. Here the scope *is* the gate, so widening on a parse failure would hand a narrowed token full authority exactly when something is already wrong.

raw

type string

returns

{ version: 1; kind: "full"; grandfathered?: boolean | undefined; } | { version: 1; kind: "methods"; methods: string[]; grandfathered?: boolean | undefined; } | null

parse_token_scope_capability
#

auth/token_scope.ts view source

(value: string): TokenScopeCapability | null import {parse_token_scope_capability} from '@fuzdev/fuz_app/auth/token_scope.js';

Parse a capability string into the question it asks, or null when it is not a capability at all.

Callers turn null into a registration-time throw: RouteAuth.required_scope is typed as a plain string so http/ needs no auth/ import, and this is the single point where that string becomes a mounted guard, so it is where the value gets checked. Refusing an unknown prefix is what keeps the surface: namespace fuz_app's own — a route cannot mint an rpc:-looking capability out of a surface gate, which would put a method name in front of a whole-surface refusal.

value

the declared capability string, e.g. surface:audit_stream

type string

returns

TokenScopeCapability | null

the parsed capability, or null on an unknown prefix / empty or malformed identifier

ParseCommandArgsResult
#

cli/args.ts view source

ParseCommandArgsResult<T>

type { ok: true; value: T; } | { ok: false; error: string; }

import type {ParseCommandArgsResult} from '@fuzdev/fuz_app/cli/args.js';

Discriminated union result for CLI argument parsing.

generics

ParseCommandArgsResult<T>
T

ParsedProxy
#

http/proxy.ts view source

ParsedProxy

type { type: "ip"; address: string; } | { type: "cidr"; network: bigint; prefix: number; address_type: "IPv4" | "IPv6"; }

import type {ParsedProxy} from '@fuzdev/fuz_app/http/proxy.js';

A parsed proxy entry — either an exact IP or a CIDR range.

ParsedSession
#

auth/session_cookie.ts view source

ParsedSession<TIdentity> import type {ParsedSession} from '@fuzdev/fuz_app/auth/session_cookie.js';

Result of parsing a signed session cookie.

generics

ParsedSession<TIdentity>
TIdentity

identity

The decoded identity.

type TIdentity

key_index

Index of the key that verified the signature (0 = primary).

type number

expires_at

The signed expiry, in seconds since the Unix epoch — later than the parse.

type number

PARTICIPATION_ROLE
#

testing/cross_backend/test_cell_gated_create_authorize.ts view source

"participant" import {PARTICIPATION_ROLE} from '@fuzdev/fuz_app/testing/cross_backend/test_cell_gated_create_authorize.js';

The app-role a space policy references as min_role (besides admin) — the participant role both reference spines register. Mirrors the Rust policy's "participant" literal.

Password
#

auth/password.ts view source

value + type

string import {Password} from '@fuzdev/fuz_app/auth/password.js';

Password for account creation or password change — enforces current length policy. Also usable for client-side UX validation.

PASSWORD_LENGTH_MAX
#

auth/password.ts view source

300 import {PASSWORD_LENGTH_MAX} from '@fuzdev/fuz_app/auth/password.js';

Maximum password length, in UTF-16 code units. Caps hashing cost to prevent DoS via oversized passwords.

PASSWORD_LENGTH_MIN
#

auth/password.ts view source

12 import {PASSWORD_LENGTH_MIN} from '@fuzdev/fuz_app/auth/password.js';

Minimum password length, in UTF-16 code units (OWASP recommendation).

PasswordChangeInput
#

auth/account_route_schema.ts view source

value + type

{ current_password: string; new_password: string; } import {PasswordChangeInput} from '@fuzdev/fuz_app/auth/account_route_schema.js';

Input for POST /password. current_password is minimally validated; new_password enforces the full policy.

current_password

type string

new_password

type string

PasswordChangeOutput
#

auth/account_route_schema.ts view source

value + type

{ ok: true; sessions_revoked: number; tokens_revoked: number; } import {PasswordChangeOutput} from '@fuzdev/fuz_app/auth/account_route_schema.js';

Output for POST /password. Counts are returned so the UI can summarize the revoke-all cascade.

ok

type true

sessions_revoked

type number

tokens_revoked

type number

PasswordHashDeps
#

auth/password.ts view source

PasswordHashDeps import type {PasswordHashDeps} from '@fuzdev/fuz_app/auth/password.js';

Injectable password hashing dependencies.

Groups all three password operations for injection in route factories and other callers. Use Pick<PasswordHashDeps, ...> when only a subset is needed:

examples

// Login handler only needs verification password: Pick<PasswordHashDeps, 'verify_password' | 'verify_dummy'>; // Bootstrap only needs hashing password: Pick<PasswordHashDeps, 'hash_password'>;

hash_password

type (password: string): Promise<string>

password

type string
returns Promise<string>

verify_password

type (password: string, password_hash: string): Promise<boolean>

password

type string

password_hash

type string
returns Promise<boolean>

verify_dummy

type (password: string): Promise<boolean>

password

type string
returns Promise<boolean>

PasswordProvided
#

auth/password.ts view source

value + type

string import {PasswordProvided} from '@fuzdev/fuz_app/auth/password.js';

Password submitted for login or verification — minimal validation for forward-compatibility if length requirements change.

PayloadTooLargeError
#

http/error_schemas.ts view source

value + type

{ [x: string]: unknown; error: "payload_too_large"; } import {PayloadTooLargeError} from '@fuzdev/fuz_app/http/error_schemas.js';

Payload too large error — returned when the request body exceeds the size limit.

error

type "payload_too_large"

[key: string]

type unknown

peer_ping_action
#

actions/peer_ping.ts view source

RpcAction import {peer_ping_action} from '@fuzdev/fuz_app/actions/peer_ping.js';

Protocol-action tuple — spread into the server's actions array (via protocol_actions from actions/protocol.ts) so the dispatcher resolves the peer/ping handler on both the WS endpoint and (for the no-transport refusal) the HTTP RPC endpoint. A plain RpcAction literal (not rpc_action(...)) so this module stays free of the runtime action_rpc.ts import — the same frontend-safety discipline heartbeat_action / cancel_action follow; the handler's input/output are already pinned by peer_ping_handler's signature. Usable directly in the spine's RpcAction[] full mount and the Action[] protocol bundle alike.

peer_ping_action_spec
#

actions/peer_ping.ts view source

{ method: string; kind: "request_response"; initiator: "both"; auth: { account: "none"; actor: "none"; }; side_effects: false; input: ZodDefault<ZodObject<{ nonce: ZodOptional<ZodNumber>; timeout_ms: ZodOptional<...>; }, $strict>>; output: ZodObject<...>; async: true; rate_limit: "ip"; description: string; } import {peer_ping_action_spec} from '@fuzdev/fuz_app/actions/peer_ping.js';

ActionSpec for the shared peer/ping. initiator: 'both' (the client→server invocation drives a server→client request); auth: public (liveness is non-sensitive; the upgrade authenticated the socket); side_effects: false (no state change — the handler only round-trips a ping).

Public is not ungoverned: rate_limit: 'ip' is the one throttle an unauthenticated method can be given (there is no account to key on). It bounds anonymous fan-out and keeps the class at parity with the Rust spec's RateLimitClass::Ip — there the no-transport refusal costs two pool checkouts per call; here the public auth shape skips the authorization phase, so the refusal is free and the class is carried for the bound alone.

peer_ping_handler
#

actions/peer_ping.ts view source

(input: { nonce?: number | undefined; timeout_ms?: number | undefined; }, ctx: ActionContext): Promise<{ nonce: number; protocol_version: number; }> import {peer_ping_handler} from '@fuzdev/fuz_app/actions/peer_ping.js';

Handler — initiates a peer/ping request back to the originating client, awaits the reply, validates it against PingResponse, and returns it.

input

ctx

returns

Promise<PingResponse>

throws

  • ThrownJsonrpcError - with `data.reason` of `peer_no_transport` (HTTP), `peer_timeout` / `peer_connection_gone` / `peer_too_many_in_flight` (transport failures), the client's own envelope (`client_error`), or `peer_ping_invalid_reply` / `peer_ping_nonce_mismatch` (bad echo).

PEER_PING_METHOD
#

peer_ping_responder
#

actions/peer_ping.ts view source

(params: unknown): { nonce: number; protocol_version: number; } import {peer_ping_responder} from '@fuzdev/fuz_app/actions/peer_ping.js';

Reusable responder for an inbound *server→client* peer/ping request — the mirror of peer_ping_handler, run by the client (frontend). Validates the request params and echoes a PingResponse carrying the issued nonce. Pure + transport-agnostic (the production twin of the cross-process test transport's echo_responder); FrontendWebsocketTransport wires it so a real frontend answers the server's liveness probe with zero consumer plumbing. Falls back to nonce: 0 on a malformed request — the server then surfaces a nonce mismatch, the correct outcome for a bad probe.

params

the inbound request's params (PingRequestParams shape)

type unknown

returns

PingResponse

the PingResponse echo to send back as the reply's result

PEER_PROTOCOL_VERSION
#

actions/peer_ping.ts view source

1 import {PEER_PROTOCOL_VERSION} from '@fuzdev/fuz_app/actions/peer_ping.js';

Wire-protocol version reported in peer/ping replies (PingResponse.protocol_version). Twin of the Rust spine's value — bump in lockstep with a breaking peer-protocol change. The far side validates the reply's *shape*, not this value, so it's informational telemetry today, but it's the hook a future protocol negotiation reads.

PeerPingWsTestOptions
#

testing/cross_backend/peer_ping_ws.ts view source

PeerPingWsTestOptions import type {PeerPingWsTestOptions} from '@fuzdev/fuz_app/testing/cross_backend/peer_ping_ws.js';

Configuration for describe_peer_ping_ws_tests.

setup_test

Per-test fixture producer (default_cross_process_setup(handle, ...)).

type (): Promise<TestFixtureBase>

readonly
returns Promise<TestFixtureBase>

capabilities

Backend capability flags; every case gates on capabilities.peer_request.

type BackendCapabilities

readonly

base_url

Base URL the backend is reachable at (e.g. http://localhost:1177).

type string

readonly

ws_path

WebSocket endpoint path on the backend (e.g. /api/ws).

type string

readonly

PeerRequestError
#

actions/peer_request.ts view source

PeerRequestError

type { kind: "timeout"; } | { kind: "connection_gone"; } | { kind: "too_many_in_flight"; } | { kind: "client_error"; error: { [x: string]: unknown; code: -32700 | -32600 | -32601 | -32602 | -32603 | (number & $brand<"JsonrpcServerErrorCode">); message: string; data?: unknown; }; }

import type {PeerRequestError} from '@fuzdev/fuz_app/actions/peer_request.js';

Why a server→client peer request did not yield a client success reply.

  • timeout — the client did not answer within the deadline.
  • connection_gone — the socket closed (or was never registered) before a reply.
  • too_many_in_flight — the per-connection in-flight cap was hit.
  • client_error — the client answered with a JSON-RPC error; error is forwarded verbatim so the initiating handler can surface the client's own code / message / data.

PeerRequestOptions
#

actions/peer_request.ts view source

PeerRequestOptions import type {PeerRequestOptions} from '@fuzdev/fuz_app/actions/peer_request.js';

Per-call options for a server→client peer request.

timeout_ms?

Deadline (ms) before the request resolves timeout. Defaults to the registry's default_timeout_ms (DEFAULT_PEER_REQUEST_TIMEOUT). Untrusted remote-supplied values should be clamped shorten-only (never lengthen the server's hold on a pooled connection) — see actions/peer_ping.ts.

type number

PeerRequestOutcome
#

actions/peer_request.ts view source

PeerRequestOutcome

type { ok: true; value: JSONType; } | { ok: false; error: PeerRequestError; }

import type {PeerRequestOutcome} from '@fuzdev/fuz_app/actions/peer_request.js';

Outcome of a server→client peer request: the client's success result, or a PeerRequestError.

PendingOfferSummaryJson
#

auth/account_schema.ts view source

value + type

{ id: string & $brand<"Uuid">; role: string; scope_kind: string | null; scope_id: (string & $brand<"Uuid">) | null; from_actor_id: string & $brand<"Uuid">; from_username: string; created_at: string; expires_at: string; } import {PendingOfferSummaryJson} from '@fuzdev/fuz_app/auth/account_schema.js';

Zod schema for a pending role_grant offer surfaced in admin account listings.

Deliberately narrower than RoleGrantOfferJson: omits message and decline_reason so cross-admin visibility of the listing does not expose grantor-authored text that the audit log also withholds. Full offer payloads remain available through the offer-specific RPC surface and the audit log when admins need them.

from_username is resolved server-side so multi-admin deployments can see at a glance whose pending offer is blocking a "+ {role}" button; the resolution runs inside the listing query's parallel batch.

id

type string & $brand<"Uuid">

role

type string

scope_kind

type string | null

scope_id

type (string & $brand<"Uuid">) | null

from_actor_id

type string & $brand<"Uuid">

from_username

type string

created_at

type string

expires_at

type string

PendingPeerRequests
#

actions/peer_request.ts view source

import {PendingPeerRequests} from '@fuzdev/fuz_app/actions/peer_request.js';

Correlation registry for in-flight server→client requests, nested by connection then by the server-issued request id.

The per-connection nesting makes the in-flight count and the close-time drain O(1) and is the isolation boundary — a reply on connection B can never settle a request issued on connection A (it lands in a different inner map). An inner map is removed as soon as it empties, so an idle connection holds no entry. The registry never sends on the socket; the caller does the send between register and the first await, and routes inbound replies back via resolve.

constructor

type new (options?: PendingPeerRequestsOptions | undefined): PendingPeerRequests

options?

optional

register

Register a pending request for connection_id with a deadline, returning its server-issued s{n} id and the outcome promise (settled by a reply, the deadline, or drain). Returns null when the connection is at its in-flight cap — the caller maps that to too_many_in_flight. The promise executor runs synchronously, so the entry is registered before this returns.

type (connection_id: string & $brand<"Uuid">, timeout_ms?: number | undefined): { id: string | number; outcome: Promise<PeerRequestOutcome>; } | null

connection_id

type Uuid

timeout_ms?

type number
optional
returns { id: string | number; outcome: Promise<PeerRequestOutcome>; } | null

the allocated id + outcome promise, or null at the cap

mutates

  • this — inserts an entry into `#pending`

resolve

Settle the pending request matching an inbound reply. A success response resolves {ok: true, value: result}; an error response resolves {ok: false, error: {kind: 'client_error', error}} (forwarded verbatim).

Returns false when no entry matches — an unsolicited, cross-connection, or already-settled reply — so the caller drops it.

type (connection_id: string & $brand<"Uuid">, response: { [x: string]: unknown; jsonrpc: "2.0"; id: string | number; result: JSONType; } | { [x: string]: unknown; jsonrpc: "2.0"; id: string | number | null; error: { ...; }; }): boolean

connection_id

type Uuid

response

returns boolean

whether a pending request was settled

mutates

  • this — clears the matched entry from `#pending`

settle

Force-settle one pending request with an explicit outcome (the send-failure path uses this for connection_gone). Clears the timer, drops the entry, resolves the promise. Idempotent — a no-op if the entry is already gone.

type (connection_id: string & $brand<"Uuid">, id: string | number, outcome: PeerRequestOutcome): void

connection_id

type Uuid

id

outcome

returns void

mutates

  • this — clears the entry from `#pending`

drain

Settle every pending request on a closing connection as connection_gone. O(1) — drops the connection's inner map in one hop.

type (connection_id: string & $brand<"Uuid">): void

connection_id

type Uuid
returns void

mutates

  • this — removes the connection's entry from `#pending`

size

In-flight request count — for connection_id when given, else across all connections. Telemetry / tests.

type (connection_id?: (string & $brand<"Uuid">) | undefined): number

connection_id?

type Uuid
optional
returns number

PendingPeerRequestsOptions
#

PendingSubscription
#

realtime/subscriber_registry.ts view source

PendingSubscription import type {PendingSubscription} from '@fuzdev/fuz_app/realtime/subscriber_registry.js';

A registered subscription awaiting admission — what SubscriberRegistry.subscribe_pending returns and SubscriberRegistry.admit takes.

Its entry is already in the registry, so every close_by_identity can reach it, but nothing is delivered to it and it does not count toward the per-scope cap until it is admitted.

unsubscribe

Remove the registration, pending or admitted. Idempotent. Every path that refuses a pending subscription calls it, and an admitted stream's on_close listener does.

type (): void

returns void

perform_action
#

actions/perform_action.ts view source

(input: PerformActionInput, deps: PerformActionDeps): Promise<PerformActionResult> import {perform_action} from '@fuzdev/fuz_app/actions/perform_action.js';

The shared dispatch core. Pure data — no Hono context, no socket. Each transport calls into this with pre-parsed inputs and binds the result to its wire shape.

Phase order: 401 → authz → 403 → 429 → 400 → handler. On the test-preset path the dispatcher skips the live authorization phase and uses the supplied pre-baked context for post-authorization checks; the 401 still fires when the harness omits account_id.

input

deps

returns

Promise<PerformActionResult>

perform_action_result_to_envelope
#

actions/perform_action.ts view source

(id: string | number, result: PerformActionResult): { jsonrpc: string; id: string | number; } & ({ result: unknown; } | { error: { [x: string]: unknown; code: -32700 | -32600 | -32601 | -32602 | -32603 | (number & $brand<...>); message: string; data?: unknown; }; }) import {perform_action_result_to_envelope} from '@fuzdev/fuz_app/actions/perform_action.js';

Build a JSON-RPC response envelope from a PerformActionResult for transports that wire over the JSON-RPC 2.0 message shape (HTTP RPC + WS).

id

result

returns

({ jsonrpc: string; id: string | number; } & { result: unknown; }) | ({ jsonrpc: string; id: string | number; } & { error: { [x: string]: unknown; code: -32700 | -32600 | -32601 | -32602 | -32603 | (number & $brand<"JsonrpcServerErrorCode">); message: string; data?: unknown; }; })

PerformActionDeps
#

actions/perform_action.ts view source

PerformActionDeps import type {PerformActionDeps} from '@fuzdev/fuz_app/actions/perform_action.js';

Per-deps inputs to perform_action. Each transport supplies its own pool-level Db and rate limiters; the dispatcher wraps in a transaction iff spec.side_effects is true.

Pool-resilient fire-and-forget effects (audit writes) run through AppDeps.audit.emit from the action factory's closure — the dispatcher never sees the audit emitter. The bound emitter owns the pool.

db

Pool-level DB. The dispatcher wraps in db.transaction for side_effects: true actions.

type Db

pending_effects

Eager fire-and-forget pool-write queue, flushed by the transport's try/finally via flush_pending_effects.

type Promise<void>[]

post_commit_effects

Deferred post-commit thunks pushed via emit_after_commit, flushed by the transport's try/finally after the handler returns.

type (() => void | Promise<void>)[]

log

Logger threaded into ActionContext.log.

type Logger

action_ip_rate_limiter

Per-IP limiter (shared across transports). null disables.

type RateLimiter | null

action_account_rate_limiter

Per-account limiter (shared across transports). null disables.

type RateLimiter | null

PerformActionInput
#

actions/perform_action.ts view source

PerformActionInput import type {PerformActionInput} from '@fuzdev/fuz_app/actions/perform_action.js';

Per-call inputs to perform_action. Each transport assembles this from its wire envelope + connection identity.

action

The resolved spec + handler (transport does method lookup).

type RpcAction

raw_params

Raw params from the wire envelope (post-JsonrpcRequest.parse, pre-spec.input.safeParse).

type unknown

request_id

JSON-RPC request id — echoed onto the response.

type JsonrpcRequestId

account_id

Authenticated account id, or null for anonymous.

type string | null

credential_type

Credential type the request arrived on, or null for anonymous.

type "session" | "api_token" | "daemon_token" | null

token_scope

The credential's authority narrowing — null only for the anonymous caller, who holds no credential to narrow. Every resolved credential carries one, so this mirrors the null shape credential_type already has rather than introducing a permissive absent case.

type { version: 1; kind: "full"; grandfathered?: boolean | undefined; } | { version: 1; kind: "methods"; methods: string[]; grandfathered?: boolean | undefined; } | null

client_ip

Resolved client IP ('unknown' if upstream couldn't resolve).

type string

signal

Per-request abort signal. HTTP: c.req.raw.signal. WS: AbortSignal.any([socket, request]).

type AbortSignal

notify

Send a request-scoped notification. HTTP: DEV-warn-and-drop. WS: socket-scoped.

type (method: string, params: unknown): void

method

type string

params

type unknown
returns void

connection_id?

Stable per-socket id on WS; undefined on HTTP.

type Uuid

request_client?

Initiate a server→client request on the originating WS socket and await the typed reply (ActionPeer). Present only on WebSocket dispatch; undefined on HTTP RPC (no return socket). Handlers must handle its absence — e.g. peer/ping surfaces peer_no_transport.

type (method: string, params: { [x: string]: unknown; } | undefined, options?: PeerRequestOptions | undefined): Promise<PeerRequestOutcome>

method

type string

params

type JsonrpcRequestParams | undefined

options?

optional
returns Promise<PeerRequestOutcome>

preset?

Test-harness escape hatch. When set, the live authorization phase is skipped and request_context is used directly for post-authorization checks + handler dispatch. Production callers leave this undefined.

type { request_context: RequestContext | null; }

PerformActionResult
#

actions/perform_action.ts view source

PerformActionResult

type { kind: "ok"; result: unknown; } | { kind: "error"; error: { [x: string]: unknown; code: -32700 | -32600 | -32601 | -32602 | -32603 | (number & $brand<"JsonrpcServerErrorCode">); message: string; data?: unknown; }; status: number; }

import type {PerformActionResult} from '@fuzdev/fuz_app/actions/perform_action.js';

Discriminated result of perform_action. Each transport binds this to its wire shape: HTTP RPC folds the error into a JSON-RPC envelope and returns via c.json; WS sends the response over the socket.

PERIODIC_INTERVAL_MS_MAX
#

periodic.ts view source

number import {PERIODIC_INTERVAL_MS_MAX} from '@fuzdev/fuz_app/periodic.js';

The longest interval start_periodic accepts: the largest delay a timer honors (a signed 32-bit count of milliseconds, about 24.8 days). Past it setTimeout fires after one millisecond instead, which would turn a long interval into a pass loop.

PeriodicTask
#

periodic.ts view source

PeriodicTask import type {PeriodicTask} from '@fuzdev/fuz_app/periodic.js';

A running periodic task — the result of start_periodic.

stop

End the schedule. No pass starts once this is called, and the returned promise resolves when the pass in progress, if any, has finished — it never rejects. Await it before tearing down what the pass uses (the database, say). The wait has no bound of its own; a shutdown with a deadline races it. Safe to call more than once.

type (): Promise<void>

returns Promise<void>

PermissionError
#

http/error_schemas.ts view source

value + type

{ [x: string]: unknown; error: "insufficient_permissions"; required_roles: readonly string[]; } import {PermissionError} from '@fuzdev/fuz_app/http/error_schemas.js';

Permission error — returned by require_role() and the dispatcher's post-authorization role gate when the actor's role_grants don't include any of the route's auth.roles.

required_roles carries the full disjunction the route declared (auth.roles from the new flat-record shape). Single-role specs surface as a one-element array; multi-role disjunctions show every admittable role so clients can render targeted copy ("requires admin or steward").

error

type "insufficient_permissions"

required_roles

type readonly string[]

[key: string]

type unknown

pg_error_code
#

db/pg_error.ts view source

(error: unknown): string | null import {pg_error_code} from '@fuzdev/fuz_app/db/pg_error.js';

Extract the error-code string from a caught error, for SQLSTATE checks.

Other error families also carry string codes (Node FS errors: ENOENT, ENOSPC, …) — compare the result against specific SQLSTATE values, never against truthiness.

error

the caught error

type unknown

returns

string | null

the code string, or null when the value isn't an Error or carries no string code

PgFactStore
#

db/fact_store.ts view source

import {PgFactStore} from '@fuzdev/fuz_app/db/fact_store.js';

PG-backed FactStore. Delegates to db/fact_queries.ts for I/O and adds the lifecycle layer described in the module doc.

inheritance

implements: FactStore

constructor

type new (options: PgFactStoreDeps): PgFactStore

options

put

Store fully-buffered bytes, routing by size: <= embedded_threshold into the PG bytes column; larger into the disk CAS (when disk_root + fs are configured) at <facts_dir>/<shard>/<rest> with a file: URL. Oversize without a disk root throws: the store has nowhere to put bytes it cannot embed. Idempotent — ON CONFLICT DO NOTHING + content-addressed disk filenames make a re-write a no-op.

type (bytes: Uint8Array<ArrayBufferLike>, options?: FactPutOptions | undefined): Promise<FactHash>

bytes

type Uint8Array<ArrayBufferLike>

options?

type FactPutOptions
optional
returns Promise<FactHash>

put_stream

Stream bytes into the store with bounded memory, returning the finalized digests + size. Delegates the byte path to stream_fact_to_disk (hash BLAKE3 + SHA-256 in one pass, buffer to the embedded threshold, spill to the disk CAS), then inserts the fact row by placement — embedded bytes go to the PG bytes column, disk-spilled bytes record the file: external_url. The cap is enforced mid-stream (FactPayloadTooLargeError); a disk-full mid- stream throws FactStorageFullError.

Refs: explicit options.refs are recorded; JSON auto-extraction is NOT attempted (it would need a buffered re-read, defeating the bounded-memory contract) — streamed uploads are opaque blobs.

Requires fs (and, for the over-threshold spill, disk_root) to be configured. The streaming twin of put; mirrors the Rust FactStore::put_stream.

type (stream: ReadableStream<Uint8Array<ArrayBufferLike>>, max_bytes: number, options?: FactPutOptions | undefined): Promise<...>

stream

type ReadableStream<Uint8Array<ArrayBufferLike>>

max_bytes

type number

options?

type FactPutOptions
optional
returns Promise<PutStreamOutcome>

get

Retrieve bytes. Embedded reads return PG bytes directly. An external read comes from the disk CAS and nowhere else: the bytes at the row's file:<shard>/<rest> URL are read and verified against the hash.

Returns null, with a warning log, for an external row the store cannot serve — an external_url that is not a file:<shard>/<rest> URL, no disk CAS configured (disk_root + fs), a failed disk read, or bytes that don't match the hash. Never makes a network request.

type (hash: FactHash): Promise<Uint8Array<ArrayBufferLike> | null>

hash

type FactHash
returns Promise<Uint8Array<ArrayBufferLike> | null>

has

type (hash: FactHash): Promise<boolean>

hash

type FactHash
returns Promise<boolean>

get_meta

type (hash: FactHash): Promise<FactMeta | null>

hash

type FactHash
returns Promise<FactMeta | null>

get_refs

type (hash: FactHash): Promise<FactHash[]>

hash

type FactHash
returns Promise<FactHash[]>

delete

Drop a fact row. fact_ref rows referencing this hash as a source cascade via the FK; fact_ref targeting this hash do not — they remain as dangling pointers, consistent with the federation model where target_hash is intentionally not a FK.

Idempotent: deleting an absent fact returns null. The store does NOT verify the fact is unreferenced — that policy lives one layer up (the orphan-fact admin surface in the consumer; a future GC walker).

The disk file of a disk-backed fact is NOT unlinked — that is the caller's responsibility, using the returned external_url (when non-null): a file:<shard>/<rest> URL names <disk_root>/<shard>/<rest>. The value is the row's free text — parse it with parse_file_fact_url before joining a path.

type (hash: FactHash): Promise<{ size: number; external_url: string | null; } | null>

hash

type FactHash
returns Promise<{ size: number; external_url: string | null; } | null>

{size, external_url} for the deleted row, or null if no row matched the hash.

PgFactStoreDeps
#

db/fact_store.ts view source

PgFactStoreDeps import type {PgFactStoreDeps} from '@fuzdev/fuz_app/db/fact_store.js';

Construction-time deps for PgFactStore.

embedded_threshold (bytes) is the inline-vs-external cutoff: payloads at or under it store embedded in the fact row, larger ones route to the disk CAS. Defaults to FACT_EMBEDDED_THRESHOLD_DEFAULT (1 MiB). Consumers tune it per workload — e.g. a much lower bound (~16 KiB) keeps only small JSON inline and routes image originals + thumbnails to disk.

disk_root is the facts directory backing the <shard>/<rest> disk CAS; fs supplies the filesystem capabilities (a RuntimeDeps satisfies it). When both are set, oversize put + put_stream write to disk and get reads disk-backed facts from it. When either is unset the store writes embedded rows only and get returns null for an external row: an oversize put throws; put_stream throws for a body of any size when fs is unset, and with fs but no disk_root throws FactPayloadTooLargeError for a body that passes the threshold. log is optional — its call sites are the warnings on the paths where get returns null for an external row.

deps

type QueryDeps

embedded_threshold?

type number

disk_root?

type string

fs?

type FactDiskStorageDeps

log?

type Logger

PGLET_COERCION
#

db/db_pglet.ts view source

CoercionOptions import {PGLET_COERCION} from '@fuzdev/fuz_app/db/db_pglet.js';

The value coercion fuz_app creates every pglet instance with.

The adapter defaults int8 to bigint; fuz_app reads int8 as a JS number on every driver (register_pg_type_parsers for pg, PGlite's own default), so AuditLogEvent.seq and friends validate identically across drivers.

pick_auth_headers
#

testing/integration_helpers.ts view source

(spec: RouteSpec, keeper: KeeperHeaderProvider, authed_account: TestAccount, admin_account: TestAccount): Record<...> import {pick_auth_headers} from '@fuzdev/fuz_app/testing/integration_helpers.js';

Pick request headers matching a route spec's auth requirement.

Maps RouteAuth onto a test account's credentials:

  • none — origin headers only
  • authenticated — the authed account's session cookie
  • role: admin — the admin account's session cookie
  • role: <other> — the keeper provider's session
  • keeper — the keeper provider's daemon token

spec

keeper

authed_account

admin_account

returns

Record<string, string>

PingActionInput
#

actions/peer_ping.ts view source

value + type

{ nonce?: number | undefined; timeout_ms?: number | undefined; } import {PingActionInput} from '@fuzdev/fuz_app/actions/peer_ping.js';

Input to the client→server peer/ping invocation. Both fields optional (mirrors the Rust PingActionInput): nonce defaults to a server-issued value, timeout_ms to DEFAULT_PEER_REQUEST_TIMEOUT (clamped shorten-only).

nonce?

type number

timeout_ms?

type number

PingRequestParams
#

actions/peer_ping.ts view source

value + type

{ nonce: number; } import {PingRequestParams} from '@fuzdev/fuz_app/actions/peer_ping.js';

Params of the server→client peer/ping request frame. Twin of the Rust PingRequest.

nonce

type number

PingResponse
#

actions/peer_ping.ts view source

value + type

{ nonce: number; protocol_version: number; } import {PingResponse} from '@fuzdev/fuz_app/actions/peer_ping.js';

The client's reply shape — also the action's output (the handler returns the validated echo). Twin of the Rust PingResponse.

nonce

type number

protocol_version

type number

Popover
#

ui/popover.svelte.ts view source

import {Popover} from '@fuzdev/fuz_app/ui/popover.svelte.js';

Class that manages state and provides actions for popovers.

visible

Whether the popover is currently visible.

type boolean

$state.raw

position

Position of the popover relative to its trigger.

type Position

$state.raw

align

Alignment along the position edge.

type Alignment

$state.raw

offset

Distance from the position.

type string

$state.raw

disable_outside_click

Whether to disable closing when clicking outside.

type boolean

$state.raw

popover_class

Custom class for the popover.

type string

$state.raw

constructor

type new (params?: PopoverParameters | undefined): Popover

params?

optional

update

Updates the popover configuration. Swaps popover_class on the live content element when supplied and re-syncs the outside-click handler if disable_outside_click changed.

type (params: PopoverParameters): void

params

returns void

mutates

  • this

show

Shows the popover, registering the outside-click handler and updating aria-expanded on the trigger. No-ops when already visible.

type (): void

returns void

mutates

  • this

hide

Hides the popover, removing the outside-click handler. No-ops when already hidden.

type (): void

returns void

mutates

  • this

toggle

Toggles the popover visibility, or sets it to the given value.

type (visible?: boolean): void

visible

desired visibility (defaults to the inverse of current)

type boolean
default !this.visible
returns void

mutates

  • this

container

Attachment for the container element.

type Attachment<HTMLElement>

trigger

Attachment factory for the trigger element that shows/hides the popover.

type (params?: PopoverTriggerParameters | undefined) => Attachment<HTMLElement>

content

Attachment factory for the popover content element.

type (params?: PopoverContentParameters | undefined) => Attachment<HTMLElement>

PopoverButton
#

ui/PopoverButton.svelte view source

accepts children

import PopoverButton from '@fuzdev/fuz_app/ui/PopoverButton.svelte';

position?

optional default 'bottom'

align?

optional default 'center'

disable_outside_click?

type boolean
optional default false

popover_class?

type string
optional

popover_attrs?

type HTMLAttributes<HTMLDivElement>
optional

popover_content

type Snippet<[popover: Popover]>
snippet parameters
popover Popover

popover_container_attrs?

type HTMLAttributes<HTMLDivElement>
optional

button?

type Snippet<[popover: Popover]>
optional
snippet parameters
popover Popover

children?

type Snippet<[popover: Popover]>
optional
snippet parameters
popover Popover

external types

OmitStrict<SvelteHTMLElements['button'], 'children'>

PopoverContentParameters
#

PopoverParameters
#

ui/popover.svelte.ts view source

PopoverParameters import type {PopoverParameters} from '@fuzdev/fuz_app/ui/popover.svelte.js';

Parameters for configuring the popover.

position?

Position of the popover relative to its trigger.

type Position

align?

Alignment along the position edge.

type Alignment

offset?

Distance from the position.

type string

disable_outside_click?

Whether to disable closing when clicking outside.

type boolean

popover_class?

Custom class for the popover content.

type string

onshow?

Optional callback when popover is shown.

type (): void

returns void

onhide?

Optional callback when popover is hidden.

type (): void

returns void

PopoverTriggerParameters
#

ui/popover.svelte.ts view source

PopoverTriggerParameters import type {PopoverTriggerParameters} from '@fuzdev/fuz_app/ui/popover.svelte.js';

Parameters for the popover trigger action.

inheritance

content?

Content to render in the popover (as a snippet).

type Snippet<[]>

Position
#

PRE_ADMISSION_QUEUE_BYTES_FACTOR
#

actions/register_action_ws.ts view source

4 import {PRE_ADMISSION_QUEUE_BYTES_FACTOR} from '@fuzdev/fuz_app/actions/register_action_ws.js';

The byte budget of one socket's pre-admission queue, as a multiple of the endpoint's max_message_bytes: the frames queued while the socket is still opening may total at most this many maximum-size messages. The frame that would pass it closes the socket with WS_CLOSE_POLICY_VIOLATION.

The count cap alone (MAX_PRE_ADMISSION_FRAMES) would let each pending socket hold that many full-size messages, and pending sockets are not capped per account — a registration that is never admitted must not be able to pin that much memory. Small requests, which is what a client flushes on reopen, meet the count cap long before this one.

A client whose queued requests total more than this budget and flushes them all on reopen overflows it: the socket closes, the flushed requests fail, and the client reconnects.

prefix_route_specs
#

http/route_spec.ts view source

(prefix: string, specs: RouteSpec[]): RouteSpec[] import {prefix_route_specs} from '@fuzdev/fuz_app/http/route_spec.js';

Prepend a prefix to all route spec paths.

prefix

the path prefix (e.g. /api/account)

type string

specs

type RouteSpec[]

returns

RouteSpec[]

a new array — the input specs are not mutated

PreparedWebsocket
#

testing/cross_backend/testing_server_core.ts view source

PreparedWebsocket import type {PreparedWebsocket} from '@fuzdev/fuz_app/testing/cross_backend/testing_server_core.js';

Result of an adapter's WS preparation step.

create_upgrade_websocket is the factory a build passes to create_app_server as AppServerOptions.create_upgrade_websocket. attach_to_server runs after serve() returns a ServeHandle — Node uses it for injectWebSocket(server), and it does nothing when the factory was never called (no ws_endpoints); Deno and Bun leave it undefined.

create_upgrade_websocket

type (app: Hono<BlankEnv, BlankSchema, "/">): UpgradeWebSocket<unknown, any, WSEvents<unknown>>

app

type Hono<BlankEnv, BlankSchema, "/">
returns UpgradeWebSocket<unknown, any, WSEvents<unknown>>

attach_to_server?

type (handle: ServeHandle): void

handle

returns void

PrepareWebsocketOptions
#

testing/cross_backend/testing_server_core.ts view source

PrepareWebsocketOptions import type {PrepareWebsocketOptions} from '@fuzdev/fuz_app/testing/cross_backend/testing_server_core.js';

Options for TestingServerAdapter.prepare_websocket.

max_message_bytes?

Largest inbound WebSocket message, in bytes — pass the largest WsEndpointSpec.max_message_bytes of the mounted endpoints; a smaller value refuses messages the endpoint would accept. Node's ws server takes it as its frame cap (maxPayload), so an oversized message is refused before it is buffered whole. Bun takes twice it as its frame cap (maxPayloadLength): past its cap Bun drops the socket without a close frame, so the headroom leaves a message just over the limit to the per-message check register_ws_endpoint makes, while a grossly oversized one is still refused before it is buffered whole. Deno ignores it — its upgrade takes no frame cap — leaving that per-message check alone. Every runtime closes a message just over the limit with WS_CLOSE_MESSAGE_TOO_BIG. The default sits far below ws's own 100 MiB and Bun's 16 MiB, so a build raising an endpoint's cap must pass it here too.

type number

default DEFAULT_WS_MAX_MESSAGE_BYTES

PrimaryKeyInfo
#

http/db_routes.ts view source

PrimaryKeyInfo import type {PrimaryKeyInfo} from '@fuzdev/fuz_app/http/db_routes.js';

Primary key constraint info.

column_name

type string

process_session_cookie
#

ProcessDeps
#

runtime/deps.ts view source

ProcessDeps import type {ProcessDeps} from '@fuzdev/fuz_app/runtime/deps.js';

Process lifecycle.

exit

Exit the process with a code.

type (code: number): never

code

type number
returns never

ProcessSessionResult
#

auth/session_cookie.ts view source

ProcessSessionResult<TIdentity> import type {ProcessSessionResult} from '@fuzdev/fuz_app/auth/session_cookie.js';

Result of processing a session cookie.

generics

ProcessSessionResult<TIdentity>
TIdentity

valid

Whether the session is valid.

type boolean

action

Action the adapter should take.

type "none" | "clear"

identity?

The decoded identity if the cookie was valid.

type TIdentity

expires_at?

The cookie's signed expiry, epoch seconds, if the cookie was valid.

type number

PROTOCOL_ACTION_METHODS
#

actions/action_codegen.ts view source

readonly ["heartbeat", "cancel", "peer/ping"] import {PROTOCOL_ACTION_METHODS} from '@fuzdev/fuz_app/actions/action_codegen.js';

Method names of fuz_app's protocol actions — heartbeat (auth-aware client liveness probe) and cancel (request-scoped abort signal). Consumers spread this list when filtering backend request_response methods so the dispatcher-owned protocol actions don't show up in BackendRequestResponseMethod / handler maps. Pairs with protocol_actions / protocol_action_specs from actions/protocol.ts (the runtime bundles).

protocol_action_specs
#

actions/protocol.ts view source

readonly ({ method: string; initiator: "frontend" | "backend" | "both"; side_effects: boolean; input: ZodType<unknown, unknown, $ZodTypeInternals<unknown, unknown>>; output: ZodType<unknown, unknown, $ZodTypeInternals<unknown, unknown>>; ... 6 more ...; rate_limit?: "both" | ... 2 more ... | undefined; } | { ...; } ...

type readonly ActionSpecUnion[]

import {protocol_action_specs} from '@fuzdev/fuz_app/actions/protocol.js';

Canonical protocol specs for ActionRegistry construction on the frontend. Spread before consumer-owned specs so dispatcher-owned methods are present in the lookup map even though codegen excludes them from the generated action_specs array:

new ActionRegistry([...protocol_action_specs, ...action_specs])

Derived from protocol_actions so a future protocol action lands in one place — the two arrays cannot drift.

protocol_actions
#

actions/protocol.ts view source

readonly Action<{ method: string; initiator: "frontend" | "backend" | "both"; side_effects: boolean; input: ZodType<unknown, unknown, $ZodTypeInternals<unknown, unknown>>; output: ZodType<unknown, unknown, $ZodTypeInternals<unknown, unknown>>; ... 6 more ...; rate_limit?: "both" | ... 2 more ... | undefined; } | { ....

type readonly Action<ActionSpecUnion>[]

import {protocol_actions} from '@fuzdev/fuz_app/actions/protocol.js';

Canonical protocol {spec, handler} tuples for the server's register_action_ws actions array. Spread before consumer-owned actions so disconnect detection and per-request cancel work uniformly:

register_action_ws({actions: [...protocol_actions, ...consumer_actions], ...})

ProtocolActionMethod
#

actions/action_codegen.ts view source

"heartbeat" | "cancel" | "peer/ping"

type "heartbeat" | "cancel" | "peer/ping"

import type {ProtocolActionMethod} from '@fuzdev/fuz_app/actions/action_codegen.js';

Methods that ship from fuz_app, kept out of consumer-owned method enums + handler maps.

provide_admin_rpc_contexts
#

ui/admin_rpc_adapters.ts view source

(adapters: AdminRpcAdapters, options?: ProvideAdminRpcContextsOptions | undefined): void import {provide_admin_rpc_contexts} from '@fuzdev/fuz_app/ui/admin_rpc_adapters.js';

Wire all four admin RPC contexts in a single call.

Call once at the admin shell layout (e.g. src/routes/admin/+layout.svelte) with adapters built from create_admin_rpc_adapters. Every Admin*.svelte component that reads a context below this point sees the adapters.

Each context accessor reads adapters.{domain} on every invocation, so mutating an adapter field on the same object propagates. Replacing the whole adapter set requires calling provide_admin_rpc_contexts again during init — in practice this is one-shot at layout mount.

Pass options.format_scope to render role_grant/offer scope_id values as human labels across AdminAccounts, AdminRoleGrantHistory, RoleGrantOfferInbox, RoleGrantOfferForm, and RoleGrantOfferHistory. Components that accept a format_scope prop honor the prop first; the context is the fallback.

adapters

options?

optional

returns

void

ProvideAdminRpcContextsOptions
#

ui/admin_rpc_adapters.ts view source

ProvideAdminRpcContextsOptions import type {ProvideAdminRpcContextsOptions} from '@fuzdev/fuz_app/ui/admin_rpc_adapters.js';

Optional knobs alongside the adapters when wiring admin contexts.

format_scope?

Render {scope_id, role} as a human label across role-grant-display components. Omit (or return null) to fall back to the raw uuid.

type (args: { scope_id: string | null; role: string; }): string | null

args

type { scope_id: string | null; role: string; }
returns string | null

ProxyOptions
#

http/proxy.ts view source

ProxyOptions import type {ProxyOptions} from '@fuzdev/fuz_app/http/proxy.js';

Configuration for trusted proxy resolution.

trusted_proxies

Trusted proxy IPs or CIDR ranges (e.g. '127.0.0.1', '10.0.0.0/8', '::1').

type string[]

get_connection_ip

Extract the raw TCP connection IP from the Hono context.

type (c: Context<any, any, {}>): string | undefined

c

type Context<any, any, {}>
returns string | undefined

log?

Optional logger for proxy resolution diagnostics.

type Logger

qualify_columns
#

db/sql_columns.ts view source

(columns: readonly string[], alias: string, expr?: ColumnExpr | undefined): string import {qualify_columns} from '@fuzdev/fuz_app/db/sql_columns.js';

Render a *_COLUMNS const as a SQL select list with every column qualified by a table alias, for reads that alias the table (JOINs, the c-aliased cell_list scan).

The alias is interpolated into SQL, so it must be a plain identifier — every in-repo call passes a literal, but a consumer threading a computed alias through gets a thrown error instead of a malformed (or injectable) projection.

An expr override costs one rule: an ORDER BY in a projected SELECT must qualify its column. An override is aliased back to its own name (… AS created_at), and Postgres resolves a bare ORDER BY created_at against the *output* names first, so it would sort on the to_char(…) text — collapsing sub-second order into ties and losing the btree index (a Sort node over the expression instead of an Index Scan). Writing ORDER BY <alias>.created_at (or <table>.created_at) names the input column and both go away. The Rust twin needs no such rule: it decodes positionally, so fuz_db::qualify_columns emits no alias and its bare ORDER BY created_at already means the column.

columns

the column names

type readonly string[]

alias

the table alias or name to prefix each column with

type string

expr?

optional per-column expression override; an overridden column is aliased back to its own name

optional

returns

string

alias.a, alias.b, alias.c

throws

  • Error - when `alias` is not a valid SQL identifier

query_accept_offer
#

auth/role_grant_offer_queries.ts view source

(deps: QueryDeps, input: AcceptOfferInput): Promise<AcceptOfferResult> import {query_accept_offer} from '@fuzdev/fuz_app/auth/role_grant_offer_queries.js';

Accept an offer atomically: mark accepted, insert the role_grant, stamp resulting_role_grant_id, supersede sibling pending offers for the same (to_account, role, scope), and emit role_grant_offer_accept + role_grant_create + one role_grant_offer_supersede per sibling. Must run inside a transaction — the caller's route spec should declare transaction: true (or wrap explicitly).

Idempotent on replay and on race: a call that observes the offer already accepted returns the existing role_grant (created: false) rather than creating a duplicate or throwing — while that grant is still held. A grant revoked since the accept, or gone, makes the offer terminal.

Error map:

Sibling supersede is what closes the "accept a pre-revoke sibling offer to bypass a revoke" path: once A is accepted, B/C/... can no longer be accepted even if the resulting role_grant is later revoked.

deps

input

returns

Promise<AcceptOfferResult>

throws

  • RoleGrantOfferNotFoundError - if the offer is missing or belongs to another recipient
  • RoleGrantOfferAlreadyTerminalError - if the offer is declined, retracted, or superseded, or accepted with its grant revoked, gone, or bound to another actor
  • RoleGrantOfferExpiredError - if the offer is pending but past `expires_at`
  • RoleGrantOfferActorMismatchError - if the offer is actor-targeted to a different actor
  • RoleGrantOfferBuiltinRefusedError - if a builtin-role offer must not mint (see `BuiltinOfferRefusal`)
  • Error - if the accepting `actor_id` does not belong to `to_account_id`, or invariant assertions fail

mutates

  • role_grant_offer row — stamps `accepted_at` and `resulting_role_grant_id`
  • role_grant table — inserts the resulting role_grant (idempotent on race)
  • role_grant_offer siblings — stamps `superseded_at` on every other pending offer for the tuple
  • audit_log table — emits `role_grant_offer_accept` + `role_grant_create` + one `role_grant_offer_supersede` per sibling

query_account_by_email
#

auth/account_queries.ts view source

(deps: QueryDeps, email: string): Promise<Account | undefined> import {query_account_by_email} from '@fuzdev/fuz_app/auth/account_queries.js';

Find an account by email (case-insensitive).

deps

email

type string

returns

Promise<Account | undefined>

query_account_by_id
#

auth/account_queries.ts view source

(deps: QueryDeps, id: string): Promise<Account | undefined> import {query_account_by_id} from '@fuzdev/fuz_app/auth/account_queries.js';

Find an active account by id (deleted_at IS NULL).

This is the auth-resolution workhorse (build_request_context / build_account_context) and the admin-target lookup, so it deliberately excludes soft-deleted accounts: a tombstoned account must not authenticate (delete = soft, purge = hard). Purge, which must operate on soft-deleted rows too, uses query_purge_account directly.

deps

id

type string

returns

Promise<Account | undefined>

query_account_by_username
#

auth/account_queries.ts view source

(deps: QueryDeps, username: string): Promise<Account | undefined> import {query_account_by_username} from '@fuzdev/fuz_app/auth/account_queries.js';

Find an account by username (case-insensitive).

deps

username

type string

returns

Promise<Account | undefined>

query_account_by_username_or_email
#

auth/account_queries.ts view source

(deps: QueryDeps, input: string): Promise<Account | undefined> import {query_account_by_username_or_email} from '@fuzdev/fuz_app/auth/account_queries.js';

Find an account by username or email.

If the input contains @, tries email lookup first then username. Otherwise tries username first then email. This supports a single login field that accepts either format.

Excludes soft-deleted accounts (deleted_at IS NULL) — this is the login lookup, and a tombstoned account must not authenticate. The underlying query_account_by_username / query_account_by_email stay unfiltered because the invite-collision checks need to see soft-deleted accounts (usernames/emails stay reserved after a soft-delete).

deps

query dependencies

input

username or email address

type string

returns

Promise<Account | undefined>

the matching active account, or undefined

query_account_has_active_global_role
#

auth/role_grant_queries.ts view source

(deps: QueryDeps, account_id: string, role: string): Promise<boolean> import {query_account_has_active_global_role} from '@fuzdev/fuz_app/auth/role_grant_queries.js';

Like query_account_has_global_role, but only counts the grant when the account and the granting actor are both active (deleted_at IS NULL). Used by the last-admin branch of the removability guard: a soft-deleted admin can't log in and is excluded from query_count_active_accounts_with_global_role, so the guard must use the same active predicate when testing whether the *target* is an admin — otherwise removing an already-tombstoned admin is falsely blocked as cannot_delete_last_admin. Soft-delete does not revoke role_grant rows (it's reversible), so the actor join filters `deleted_at IS NULL` too: a grant held only by a tombstoned actor must not keep the account reading as an admin (the same tombstone-exclusion the acting-actor resolution applies). The keeper branch deliberately uses the unconditional query_account_has_global_role (a keeper is never removable regardless of tombstone state).

deps

query dependencies

account_id

the account to check

type string

role

the role to check for (e.g. ROLE_ADMIN)

type string

returns

Promise<boolean>

true if the account is active and any of its actors holds an active global role grant

query_account_has_any
#

auth/account_queries.ts view source

(deps: QueryDeps): Promise<boolean> import {query_account_has_any} from '@fuzdev/fuz_app/auth/account_queries.js';

Check if any account exists.

deps

returns

Promise<boolean>

query_account_has_global_role
#

auth/role_grant_queries.ts view source

(deps: QueryDeps, account_id: string, role: string): Promise<boolean> import {query_account_has_global_role} from '@fuzdev/fuz_app/auth/role_grant_queries.js';

Account-grain check: does any actor on account_id hold an active global role_grant for role?

Symmetric with query_role_grant_has_role but keyed on the account instead of a single actor — for surfaces with auth: actor: 'none' that don't load auth.role_grants and can't use the in-memory has_scoped_role predicate. Joins role_grant → actor; matches only global role_grants (scope_id IS NULL) since the use case is "is the caller's account broadly admin", not scope-aware.

Deliberately unconditional — no deleted_at filter on the account or the granting actor. This backs the keeper removability guard, where the keeper must read as present *even if* its account or keeper-actor is already tombstoned (deleting it would brick keeper/daemon auth with no in-band recovery). That is the opposite posture from resolve_keeper_account_id, which *resolves* the daemon identity and so excludes a tombstoned keeper actor.

Fast under the existing idx_role_grant_actor index — the inner actor_id IN (...) subquery is index-scan, and the outer EXISTS stops at the first match.

deps

query dependencies

account_id

the account to check

type string

role

the role to check for (e.g. ROLE_ADMIN)

type string

returns

Promise<boolean>

true if any actor on the account has an active global role_grant for role

query_account_soft_delete
#

auth/account_queries.ts view source

(deps: QueryDeps, id: string, deleted_by: string | null): Promise<AccountIdentitySnapshot | undefined> import {query_account_soft_delete} from '@fuzdev/fuz_app/auth/account_queries.js';

Soft-delete an account — the reversible tombstone (delete = soft).

Stamps deleted_at + deleted_by (the initiator's actor) on the active row — paired like role_grant's revoked_at / revoked_by, and deliberately leaving updated_at / updated_by untouched (deletion isn't a content edit). Returns the identity snapshot for the account_delete audit event, or undefined when no active row matched (missing or already soft-deleted). Auth resolution (query_account_by_id) already excludes soft-deleted accounts; the caller revokes sessions/tokens.

deps

id

type string

deleted_by

type string | null

returns

Promise<AccountIdentitySnapshot | undefined>

mutates

  • account row — sets `deleted_at` + `deleted_by` on one active row

query_account_undelete
#

auth/account_queries.ts view source

(deps: QueryDeps, id: string): Promise<AccountIdentitySnapshot | undefined> import {query_account_undelete} from '@fuzdev/fuz_app/auth/account_queries.js';

Reactivate a soft-deleted account — clears the deleted_at / deleted_by tombstone (the inverse of query_account_soft_delete).

Operates only on a currently soft-deleted row (deleted_at IS NOT NULL) and returns the identity snapshot for the account_undelete audit event, or undefined when no soft-deleted row matched (missing or already active). Reactivation does not restore the revoked sessions/tokens — the account is live again but its principals re-auth fresh. updated_at / updated_by stay untouched.

deps

id

type string

returns

Promise<AccountIdentitySnapshot | undefined>

mutates

  • account row — clears `deleted_at` + `deleted_by` on one soft-deleted row

query_active_actors_by_account
#

auth/account_queries.ts view source

(deps: QueryDeps, account_id: string): Promise<Actor[]> import {query_active_actors_by_account} from '@fuzdev/fuz_app/auth/account_queries.js';

List active (non-tombstoned) actors on an account, ordered by created_at.

Filters deleted_at IS NULL so a soft-deleted actor can never be resolved as the acting actor (and carry its role_grants into the role gate). Used by resolve_acting_actor to resolve the acting actor for a request: 1 actor picks transparently, multiple require an explicit acting field on the request payload. The admin/snapshot handlers that legitimately need tombstoned rows stay on the unfiltered query_actors_by_account.

deps

account_id

type string

returns

Promise<Actor[]>

query_actor_by_id
#

auth/account_queries.ts view source

(deps: QueryDeps, id: string): Promise<Actor | undefined> import {query_actor_by_id} from '@fuzdev/fuz_app/auth/account_queries.js';

Find an actor by id.

deps

id

type string

returns

Promise<Actor | undefined>

query_actor_search
#

query_actor_soft_delete
#

auth/account_queries.ts view source

(deps: QueryDeps, id: string, deleted_by: string | null): Promise<boolean> import {query_actor_soft_delete} from '@fuzdev/fuz_app/auth/account_queries.js';

Soft-delete one actor (sets deleted_at + deleted_by). Returns true when an active row flipped, false when none matched. Emitted per actor alongside the account-level soft-delete.

deps

id

type string

deleted_by

type string | null

returns

Promise<boolean>

mutates

  • actor row — sets `deleted_at` + `deleted_by` on one active row

query_actor_undelete
#

auth/account_queries.ts view source

(deps: QueryDeps, id: string): Promise<boolean> import {query_actor_undelete} from '@fuzdev/fuz_app/auth/account_queries.js';

Reactivate one soft-deleted actor (clears deleted_at / deleted_by). Returns true when a soft-deleted row flipped back, false when none matched. Emitted per actor alongside the account-level reactivation.

deps

id

type string

returns

Promise<boolean>

mutates

  • actor row — clears `deleted_at` + `deleted_by` on one soft-deleted row

query_actors_by_account
#

auth/account_queries.ts view source

(deps: QueryDeps, account_id: string): Promise<Actor[]> import {query_actors_by_account} from '@fuzdev/fuz_app/auth/account_queries.js';

List every actor on an account, ordered by created_at, including soft-deleted (tombstoned) rows.

Used by the admin/snapshot handlers (account_delete / account_purge / account_undelete) that must enumerate every actor that ever existed on the account to snapshot names into per-actor audit events. For the acting-actor resolution path — which must never resolve a tombstoned actor — use query_active_actors_by_account. For lookups by id, use query_actor_by_id instead.

deps

account_id

type string

returns

Promise<Actor[]>

query_actors_by_ids
#

auth/actor_lookup_queries.ts view source

(deps: QueryDeps, ids: readonly (string & $brand<"Uuid">)[]): Promise<ActorLookupRow[]> import {query_actors_by_ids} from '@fuzdev/fuz_app/auth/actor_lookup_queries.js';

Resolve a batch of actor ids to (id, username, display_name). Empty input fast-paths to []. Hard-deleted actors (or account-cascade- orphaned rows) drop out of the result silently.

deps

ids

type readonly Uuid[]

returns

Promise<ActorLookupRow[]>

query_admin_account_list
#

auth/account_queries.ts view source

(deps: QueryDeps, options?: AdminAccountListOptions | undefined): Promise<{ account: { id: string & $brand<"Uuid">; username: string; ... 5 more ...; deleted_at: string | null; }; actor: { ...; } | null; role_grants: { ...; }[]; pending_offers: { ...; }[]; }[]> import {query_admin_account_list} from '@fuzdev/fuz_app/auth/account_queries.js';

List accounts with their actors, active role_grants, and pending inbound role_grant offers for admin display.

Pages the accounts query (one round-trip), then fans out three parallel lookups scoped to the page's account_ids (one round-trip). The role_grants and offers queries use a subquery on actor.account_id so the page bound pushes through to the DB without round-tripping actor.ids back to the application. Pending offers surface the "offer pending — awaiting acceptance" UX; message is intentionally excluded (cross-admin visibility of grantor notes would expand beyond what the audit log discloses).

deps

query dependencies

options?

optional {limit, offset}. Default limit is ADMIN_ACCOUNT_LIST_DEFAULT_LIMIT; pass limit: null to disable.

optional

returns

Promise<AdminAccountEntryJson[]>

admin account entries sorted by creation date (oldest first)

query_api_token_enforce_limit
#

auth/api_token_queries.ts view source

(deps: QueryDeps, account_id: string, max_tokens: number): Promise<string[]> import {query_api_token_enforce_limit} from '@fuzdev/fuz_app/auth/api_token_queries.js';

Enforce a per-account token limit by evicting the oldest tokens.

Race safety: this function must run inside a transaction alongside the INSERT that created the new token. The caller (the account_token_create RPC handler) runs under the dispatcher's transaction path because the spec declares side_effects: true, making one creator's INSERT + enforce_limit pair atomic. That does not serialize concurrent creators: under Read Committed, two transactions can't see each other's uncommitted token row, so each evicts against a stale count and both can commit above max_tokens. Closing this needs one serialization point per (account_id, credential_kind) before count/evict/insert — see the matching note on query_session_enforce_limit.

Ordering carries the same id DESC stability tie-breaker, with the same caveat, as query_session_enforce_limit — deterministic survivors under a created_at tie, not a guarantee that the row just inserted survives.

Deleting a token row does not end the connections it opened, so the returned ids are what the caller closes once the transaction commits (queue_connection_close with a token target) — account_token_create does.

deps

query dependencies (must be transaction-scoped)

account_id

the account to enforce the limit for

type string

max_tokens

maximum number of tokens to keep

type number

returns

Promise<string[]>

the evicted tokens' ids, empty when nothing was evicted

mutates

  • api_token table — deletes the oldest rows past the cap

query_api_token_list_for_account
#

auth/api_token_queries.ts view source

(deps: QueryDeps, account_id: string): Promise<Omit<ApiToken, "token_hash">[]> import {query_api_token_list_for_account} from '@fuzdev/fuz_app/auth/api_token_queries.js';

List all tokens for an account, newest first — API_TOKEN_CLIENT_COLUMNS, so token_hash never rides the listing.

deps

account_id

type string

returns

Promise<Omit<ApiToken, "token_hash">[]>

query_api_token_live_account
#

auth/api_token_queries.ts view source

(deps: QueryDeps, id: string): Promise<string | undefined> import {query_api_token_live_account} from '@fuzdev/fuz_app/auth/api_token_queries.js';

Look up the owning account of a live API token — one that still exists and has not expired — by its public id.

The by-id counterpart of query_validate_api_token, for re-checking a token that already authenticated (revalidate_resolved_auth): no hash, no scope decode, and no touch of last_used_at / last_used_ip — a re-check is not a use. "Live" is the same predicate in both (API_TOKEN_IS_LIVE). The scope document is not re-read: the resolved credential carries the scope it was admitted with, and nothing rewrites the column after the mint.

deps

query dependencies

id

the public token id (e.g. tok_abc123)

type string

returns

Promise<string | undefined>

the owning account id, or undefined when the token is gone or expired

query_app_settings_load
#

auth/app_settings_queries.ts view source

(deps: QueryDeps): Promise<AppSettings> import {query_app_settings_load} from '@fuzdev/fuz_app/auth/app_settings_queries.js';

Load the current app settings.

deps

query dependencies

returns

Promise<AppSettings>

the app settings row

throws

  • Error - if the singleton `app_settings` row is missing (migration drift — should not occur in practice)

query_app_settings_load_with_username
#

auth/app_settings_queries.ts view source

(deps: QueryDeps): Promise<{ open_signup: boolean; updated_at: string | null; updated_by: (string & $brand<"Uuid">) | null; updated_by_username: string | null; }> import {query_app_settings_load_with_username} from '@fuzdev/fuz_app/auth/app_settings_queries.js';

Load the current app settings with resolved updater username.

deps

query dependencies

returns

Promise<AppSettingsWithUsernameJson>

the app settings with updated_by_username

throws

  • Error - if the singleton `app_settings` row is missing

query_app_settings_update
#

auth/app_settings_queries.ts view source

(deps: QueryDeps, open_signup: boolean, actor_id: string): Promise<AppSettings> import {query_app_settings_update} from '@fuzdev/fuz_app/auth/app_settings_queries.js';

Update app settings and return the updated row.

deps

query dependencies

open_signup

new value for the open_signup toggle

type boolean

actor_id

the actor making the change

type string

returns

Promise<AppSettings>

the updated app settings row

throws

  • Error - if the singleton `app_settings` row is missing

mutates

  • app_settings row — sets `open_signup`, `updated_at`, and `updated_by`

query_audit_log
#

auth/audit_log_queries.ts view source

<T extends string>(deps: QueryDeps, input: AuditLogInput<T>, config?: AuditLogConfig): Promise<AuditLogEvent> import {query_audit_log} from '@fuzdev/fuz_app/auth/audit_log_queries.js';

Insert an audit log entry.

Returns the full row (AUDIT_LOG_COLUMNS) so callers receive DB-assigned fields (id, seq, created_at). Validates metadata against config.metadata_schemas; unknown event_type and metadata mismatches log + bump their counters but write the row anyway. Consumers extend the recognized set via create_audit_log_config({extra_events}).

In-transaction call site for query helpers that must atomically write the row alongside other mutations (e.g. query_accept_offer). Fire-and-forget call sites should reach for AppDeps.audit.emit instead — that wrapper closes over the pool so audit rows persist when the parent transaction rolls back.

deps

query dependencies

input

the audit event to record

config

audit-log config. Defaults to builtin_audit_log_config.

default builtin_audit_log_config

returns

Promise<AuditLogEvent>

the inserted audit log row

generics

query_audit_log<T extends string>
T
constraint string

mutates

  • audit_log table — inserts the new row
  • drift counters — bumps `audit_unknown_event_type_failures` and/or `audit_metadata_validation_failures` on mismatch

query_audit_log_cleanup_before
#

auth/audit_log_queries.ts view source

(deps: QueryDeps, before: Date): Promise<number> import {query_audit_log_cleanup_before} from '@fuzdev/fuz_app/auth/audit_log_queries.js';

Delete audit log entries older than the given date.

deps

query dependencies

before

delete entries created before this date

type Date

returns

Promise<number>

the number of entries deleted

mutates

  • audit_log table — deletes every row with `created_at < before`

query_audit_log_list
#

auth/audit_log_queries.ts view source

(deps: QueryDeps, options?: AuditLogListOptions | undefined): Promise<AuditLogEvent[]> import {query_audit_log_list} from '@fuzdev/fuz_app/auth/audit_log_queries.js';

List audit log entries, newest first.

deps

query dependencies

options?

filters and pagination

optional

returns

Promise<AuditLogEvent[]>

matching audit log entries

query_audit_log_list_by_cell
#

db/cell_audit_queries.ts view source

(deps: QueryDeps, cell_id: string & $brand<"Uuid">, options: CellAuditListOptions): Promise<AuditLogEvent[]> import {query_audit_log_list_by_cell} from '@fuzdev/fuz_app/db/cell_audit_queries.js';

Fetch audit rows mentioning cell_id on any cell-domain metadata key. Ordered newest-first by seq for cursor pagination through before.

deps

cell_id

type Uuid

options

returns

Promise<AuditLogEvent[]>

query_audit_log_list_role_grant_history
#

auth/audit_log_queries.ts view source

(deps: QueryDeps, limit?: number, offset?: number): Promise<{ id: string & $brand<"Uuid">; seq: number; event_type: string; outcome: "success" | "failure"; actor_id: (string & $brand<...>) | null; ... 7 more ...; target_username: string | null; }[]> import {query_audit_log_list_role_grant_history} from '@fuzdev/fuz_app/auth/audit_log_queries.js';

List role_grant grant/revoke events with resolved usernames.

deps

query dependencies

limit

maximum entries to return

type number
default AUDIT_LOG_DEFAULT_LIMIT

offset

number of entries to skip

type number
default 0

returns

Promise<RoleGrantHistoryEventJson[]>

role_grant history events with username and target_username

query_audit_log_list_with_usernames
#

auth/audit_log_queries.ts view source

(deps: QueryDeps, options?: AuditLogListOptions | undefined): Promise<{ id: string & $brand<"Uuid">; seq: number; event_type: string; outcome: "success" | "failure"; ... 8 more ...; target_username: string | null; }[]> import {query_audit_log_list_with_usernames} from '@fuzdev/fuz_app/auth/audit_log_queries.js';

List audit log entries with resolved usernames, newest first.

deps

query dependencies

options?

filters and pagination

optional

returns

Promise<AuditLogEventWithUsernamesJson[]>

matching audit log entries with username and target_username

query_cell_create
#

db/cell_queries.ts view source

(deps: QueryDeps, input: CellCreateQueryInput): Promise<CellRow> import {query_cell_create} from '@fuzdev/fuz_app/db/cell_queries.js';

Insert a cell row, deriving refs from data.

updated_by is left NULL on insert — same convention as updated_at (NULL until first update). The "last modifier" stamp is meaningful only after a real edit; copying the creator's id into updated_by at create time would make a no-op update by a different actor look authored by the creator.

deps

query deps

input

data, optional visibility, path, and ownership

returns

Promise<CellRow>

the inserted row

mutates

  • cell — inserts one row

query_cell_delete
#

db/cell_queries.ts view source

(deps: QueryDeps, id: string & $brand<"Uuid">, options?: { deleted_by?: (string & $brand<"Uuid">) | null | undefined; } | undefined): Promise<boolean> import {query_cell_delete} from '@fuzdev/fuz_app/db/cell_queries.js';

Soft-delete a cell. Sets deleted_at = NOW(), updated_at = NOW(), and updated_by = options.deleted_by (or NULL). No-op when the row is already deleted.

deps

query deps

id

cell id

type Uuid

options?

deleted_by records who triggered the delete

type { deleted_by?: (string & $brand<"Uuid">) | null | undefined; }
optional

returns

Promise<boolean>

true when a row was soft-deleted, false when no active row matched

mutates

  • cell — sets `deleted_at` on one row

query_cell_field_delete
#

db/cell_field_queries.ts view source

(deps: QueryDeps, source_id: string & $brand<"Uuid">, name: string): Promise<CellFieldRow | null> import {query_cell_field_delete} from '@fuzdev/fuz_app/db/cell_field_queries.js';

Delete a field row by primary key. Returns the deleted row so callers can audit the prior target_id without a pre-fetch.

deps

source_id

type Uuid

name

type string

returns

Promise<CellFieldRow | null>

the deleted row, or null when no row matched (idempotent delete: a 200 response is correct even when nothing was deleted)

mutates

  • cell_field — deletes one row

query_cell_field_get
#

db/cell_field_queries.ts view source

(deps: QueryDeps, source_id: string & $brand<"Uuid">, name: string): Promise<CellFieldRow | null> import {query_cell_field_get} from '@fuzdev/fuz_app/db/cell_field_queries.js';

Fetch one field row by primary key.

Does NOT JOIN cell — the caller decides whether to filter by deleted_at. Used by handlers that need the row's current target_id for audit envelopes before issuing the delete.

deps

query deps

source_id

source cell id

type Uuid

name

field name

type string

returns

Promise<CellFieldRow | null>

the row or null when not found

query_cell_field_list_for_source
#

db/cell_field_queries.ts view source

(deps: QueryDeps, source_id: string & $brand<"Uuid">, options?: { limit?: number | undefined; name_after?: string | undefined; } | undefined): Promise<CellFieldRow[]> import {query_cell_field_list_for_source} from '@fuzdev/fuz_app/db/cell_field_queries.js';

Forward fields list (source.fields[]).

Filters target by deleted_at IS NULL so relations to tombstoned cells don't surface; the source filter is the caller's responsibility (gated upstream by can_view_cell(source)).

deps

query deps

source_id

source cell id

type Uuid

options?

limit caps the row count; name_after resumes after a name (keyset pagination)

type { limit?: number | undefined; name_after?: string | undefined; }
optional

returns

Promise<CellFieldRow[]>

matching rows in lex name order

query_cell_field_list_for_target
#

db/cell_field_queries.ts view source

(deps: QueryDeps, target_id: string & $brand<"Uuid">, options?: { limit?: number | undefined; } | undefined): Promise<CellFieldRow[]> import {query_cell_field_list_for_target} from '@fuzdev/fuz_app/db/cell_field_queries.js';

Reverse fields list (target.upfields[]).

Returns rows whose target_id = $1, joined to cell on source_id so relations from tombstoned sources don't surface. The caller-side authz filter (per-source can_view_cell) runs after the SQL fetch — see the 2-layer authz contract on cell_field_list({target_id}).

Bounded by limit (the wire cell_field_list cap) so a heavily inbound-linked target can't force an unbounded fetch + per-source authz pass on the public, IP-rate-limited reverse endpoint.

deps

query deps

target_id

target cell id

type Uuid

options?

limit caps the row count

type { limit?: number | undefined; }
optional

returns

Promise<CellFieldRow[]>

matching rows, oldest field first (by the field row's created_at)

query_cell_field_set
#

db/cell_field_queries.ts view source

(deps: QueryDeps, input: CellFieldSetQueryInput): Promise<CellFieldRow> import {query_cell_field_set} from '@fuzdev/fuz_app/db/cell_field_queries.js';

Insert or update a field row.

UPSERT on (source_id, name) — re-setting the same name updates target_id and bumps created_at (timestamp reflects last write). Idempotent at the row level: caller can re-issue with the same input without checking existence first.

deps

query deps

input

source, name, target

returns

Promise<CellFieldRow>

the inserted-or-updated row

mutates

  • cell_field — inserts or updates one row

query_cell_get
#

db/cell_queries.ts view source

(deps: QueryDeps, id: string & $brand<"Uuid">, options?: { include_deleted?: boolean | undefined; } | undefined): Promise<CellRow | null> import {query_cell_get} from '@fuzdev/fuz_app/db/cell_queries.js';

Fetch a cell by id. Excludes soft-deleted rows by default.

deps

query deps

id

cell id

type Uuid

options?

include_deleted: true returns tombstones

type { include_deleted?: boolean | undefined; }
optional

returns

Promise<CellRow | null>

the row or null when not found (or soft-deleted and not requested)

query_cell_get_by_path
#

db/cell_queries.ts view source

(deps: QueryDeps, path: string): Promise<CellRow | null> import {query_cell_get_by_path} from '@fuzdev/fuz_app/db/cell_queries.js';

Fetch a cell by path. Excludes soft-deleted rows; the global partial unique index on path WHERE deleted_at IS NULL guarantees at most one result.

deps

query deps

path

the named lookup alias (e.g. /map/main)

type string

returns

Promise<CellRow | null>

the row or null when not found

query_cell_grant_create
#

db/cell_grant_queries.ts view source

(deps: QueryDeps, input: CellGrantCreateQueryInput): Promise<CellGrantRow> import {query_cell_grant_create} from '@fuzdev/fuz_app/db/cell_grant_queries.js';

Insert a grant, or update the existing row's level + granted_by when one already exists for the same (cell_id, principal) pair.

Idempotent re-share: caller doesn't need to check existence first. The UPSERT path runs even when the existing row's level matches — handlers reading the row's prior state for audit ("create vs. update") must do so before this call.

deps

query deps

input

cell, level, principal, grantor

returns

Promise<CellGrantRow>

the inserted-or-updated row

mutates

  • cell_grant — inserts or updates one row

query_cell_grant_delete
#

db/cell_grant_queries.ts view source

(deps: QueryDeps, grant_id: string & $brand<"Uuid">): Promise<CellGrantRow | null> import {query_cell_grant_delete} from '@fuzdev/fuz_app/db/cell_grant_queries.js';

Delete a grant by id, returning the deleted row.

Returning the row lets the caller audit the principal + level after the delete and (for self-revoke) recompute still_admitted against the remaining grants on the cell without a second fetch.

deps

query deps

grant_id

grant id

type Uuid

returns

Promise<CellGrantRow | null>

the deleted row or null when no row matched

mutates

  • cell_grant — deletes one row

query_cell_grant_get
#

db/cell_grant_queries.ts view source

(deps: QueryDeps, grant_id: string & $brand<"Uuid">): Promise<CellGrantRow | null> import {query_cell_grant_get} from '@fuzdev/fuz_app/db/cell_grant_queries.js';

Fetch a grant by id.

deps

query deps

grant_id

grant id

type Uuid

returns

Promise<CellGrantRow | null>

the row or null when not found

query_cell_grant_list_for_cell
#

db/cell_grant_queries.ts view source

(deps: QueryDeps, cell_id: string & $brand<"Uuid">): Promise<CellGrantRow[]> import {query_cell_grant_list_for_cell} from '@fuzdev/fuz_app/db/cell_grant_queries.js';

List all grants on a cell, oldest first.

Used by cell_grant_list (RPC) and by handlers that need grants alongside the cell row for the authorize predicate.

deps

query deps

cell_id

cell id

type Uuid

returns

Promise<CellGrantRow[]>

matching rows

query_cell_grant_list_for_cells
#

db/cell_grant_queries.ts view source

(deps: QueryDeps, cell_ids: readonly (string & $brand<"Uuid">)[]): Promise<CellGrantRow[]> import {query_cell_grant_list_for_cells} from '@fuzdev/fuz_app/db/cell_grant_queries.js';

List all grants across a set of cells, ordered by cell then creation. Used by the strict relation-read filter to test can_view_cell per target in memory — the caller groups the flat result by cell_id. Returns every grant on each cell (not caller-filtered), because can_view_cell needs the full grant list to decide admission.

deps

query deps

cell_ids

cells to fetch grants for (duplicates are harmless)

type readonly Uuid[]

returns

Promise<CellGrantRow[]>

matching grant rows (group by cell_id caller-side)

query_cell_grants_for_caller_in_cells
#

db/cell_grant_queries.ts view source

(deps: QueryDeps, cell_ids: readonly (string & $brand<"Uuid">)[], caller_actor_id: (string & $brand<"Uuid">) | null, role_grant_roles: readonly string[], role_grant_scope_ids: readonly ((string & $brand<...>) | null)[]): Promise<...> import {query_cell_grants_for_caller_in_cells} from '@fuzdev/fuz_app/db/cell_grant_queries.js';

Load grants that admit the caller (by actor or role-scoped role_grants) across multiple cells. Used to enrich cell_list responses with context about what granted access. Returns grants for the given cells that match the caller's identity or role_grant set.

deps

query deps

cell_ids

cells to fetch grants for

type readonly Uuid[]

caller_actor_id

actor id of the caller (null for unauth)

type Uuid | null

role_grant_roles

active role_grant roles (parallel array)

type readonly string[]

role_grant_scope_ids

active role_grant scope ids (parallel array, parallel to roles)

type readonly (Uuid | null)[]

returns

Promise<CellGrantRow[]>

matching grants (may include grants the caller doesn't match; caller's list handler must filter when returning to the API)

query_cell_item_delete
#

db/cell_item_queries.ts view source

(deps: QueryDeps, parent_id: string & $brand<"Uuid">, position: string): Promise<CellItemRow | null> import {query_cell_item_delete} from '@fuzdev/fuz_app/db/cell_item_queries.js';

Delete one item row by (parent_id, position). Returns the deleted row so callers can audit child_id after the delete without a pre-fetch.

deps

parent_id

type Uuid

position

type string

returns

Promise<CellItemRow | null>

the deleted row, or null when nothing matched

mutates

  • cell_item — deletes one row

query_cell_item_get
#

db/cell_item_queries.ts view source

(deps: QueryDeps, parent_id: string & $brand<"Uuid">, position: string): Promise<CellItemRow | null> import {query_cell_item_get} from '@fuzdev/fuz_app/db/cell_item_queries.js';

Fetch one item row by (parent_id, position). Used by move + delete handlers to confirm the row exists before issuing the mutation.

deps

parent_id

type Uuid

position

type string

returns

Promise<CellItemRow | null>

the row or null when not found

query_cell_item_insert
#

db/cell_item_queries.ts view source

(deps: QueryDeps, input: CellItemInsertQueryInput): Promise<CellItemRow> import {query_cell_item_insert} from '@fuzdev/fuz_app/db/cell_item_queries.js';

Insert one item row at the caller-supplied position.

Throws on (parent_id, position) collision (Postgres 23505); handler callers detect via is_pg_unique_violation and surface as cell_item_position_taken. Helper-side jitter (fractional_index) makes the collision rate negligible at realistic UX concurrency, so the throw is the cold-path safety net, not the hot path.

deps

input

returns

Promise<CellItemRow>

mutates

  • cell_item — inserts one row

query_cell_item_list_for_child
#

db/cell_item_queries.ts view source

(deps: QueryDeps, child_id: string & $brand<"Uuid">, options?: { limit?: number | undefined; } | undefined): Promise<CellItemRow[]> import {query_cell_item_list_for_child} from '@fuzdev/fuz_app/db/cell_item_queries.js';

Reverse items list (child.lists[]).

Returns rows whose child_id = $1, joined to cell on parent_id so items from tombstoned parents don't surface. The caller-side authz filter (per-parent can_view_cell) runs after the SQL fetch — see the 2-layer authz contract on cell_item_list({child_id}).

Bounded by limit (the wire cell_item_list cap) so a heavily inbound-linked child can't force an unbounded fetch + per-parent authz pass on the public, IP-rate-limited reverse endpoint.

deps

child_id

type Uuid

options?

type { limit?: number | undefined; }
optional

returns

Promise<CellItemRow[]>

query_cell_item_list_for_parent
#

db/cell_item_queries.ts view source

(deps: QueryDeps, parent_id: string & $brand<"Uuid">, options?: { limit?: number | undefined; position_after?: string | undefined; } | undefined): Promise<CellItemRow[]> import {query_cell_item_list_for_parent} from '@fuzdev/fuz_app/db/cell_item_queries.js';

Forward items list (parent.items[]), ordered by lex position.

Filters child by deleted_at IS NULL so items pointing at tombstoned cells don't surface; the parent filter is the caller's responsibility (gated upstream by can_view_cell(parent)).

deps

query deps

parent_id

parent cell id

type Uuid

options?

limit caps the row count (passes through to SQL LIMIT); position_after resumes after a position (keyset pagination)

type { limit?: number | undefined; position_after?: string | undefined; }
optional

returns

Promise<CellItemRow[]>

matching rows in lex position order

query_cell_item_move
#

db/cell_item_queries.ts view source

(deps: QueryDeps, parent_id: string & $brand<"Uuid">, position_old: string, position_new: string): Promise<CellItemRow | null> import {query_cell_item_move} from '@fuzdev/fuz_app/db/cell_item_queries.js';

Move an item row from position_old to position_new (same parent).

Implemented as an UPDATE on the PK; throws 23505 on collision with an existing row at position_new so handlers can surface cell_item_position_taken. The caller-supplied position_new is what fractional-indexing produced for the new slot — collisions are rare but the error path keeps the client truthful.

deps

parent_id

type Uuid

position_old

type string

position_new

type string

returns

Promise<CellItemRow | null>

the updated row, or null when the source row was missing (raced with a deleter)

mutates

  • cell_item — updates one row's `position`

query_cell_list
#

db/cell_queries.ts view source

(deps: QueryDeps, params: CellListParams): Promise<CellRow[]> import {query_cell_list} from '@fuzdev/fuz_app/db/cell_queries.js';

Filterable list query for the generic cell_list RPC.

Takes a flat filter shape (single optional clause per dimension; the cell_list API explicitly does NOT support OR'd alternatives within a dimension — keep it simple) plus an optional viewer-aware visibility predicate.

The visibility predicate mirrors can_view_cell in SQL form:

(viewer_is_admin OR cell.visibility = 'public' OR (viewer_actor_id IS NOT NULL AND created_by = viewer_actor_id) OR (viewer_actor_id IS NOT NULL AND <grant admits caller>))

The grants branch closes parity with can_view_cell: a SQL EXISTS over cell_grant, parameterized by the caller's actor_id and the parallel (role[], scope_id[]) projection of auth.role_grants. The caller's role_grants are materialized once via a caller_role_grants CTE so the role-grant unnest isn't re-scanned per outer row. Empty role_grant arrays are fine: the CTE yields zero rows, the inner EXISTS returns false, and the actor-grant branch still fires for actor-shaped grants.

shared_with_caller_only: true (shared_with: 'me' at the wire layer) takes a different SQL shape: instead of layering an extra conjunction on the cell-driven scan, it semi-joins through cell_grant, letting the planner drive from the (typically tiny) admitted-grant set via idx_cell_grant_actor / idx_cell_grant_role_scope rather than scanning every cell row. For a sharee with N grants over a table of M cells, the cost drops from O(M) to O(N + matched-cells). Owner-is-implicit (a cell's owner never appears as a grant principal) means the grants branch is itself owner-excluding, but the explicit created_by IS DISTINCT FROM caller guards against any future deviation. The shared_with branch does NOT bypass for admin: an admin asking "what's shared with me" wants their own grant footprint, not every cell.

Soft-deleted rows are excluded by default; opt-in via include_deleted.

deps

query deps

params

filter + visibility + ordering + pagination

returns

Promise<CellRow[]>

matching rows, ordered per order_by / order_direction

query_cell_list_by_creator
#

db/cell_queries.ts view source

(deps: QueryDeps, actor_id: string & $brand<"Uuid">, options?: Pick<CellListOptions, "limit" | "offset"> | undefined): Promise<...> import {query_cell_list_by_creator} from '@fuzdev/fuz_app/db/cell_queries.js';

List active cells created by an actor, newest first. Backed by the idx_cell_created_by partial index.

deps

query deps

actor_id

the creator's actor id

type Uuid

options?

pagination

type Pick<CellListOptions, "limit" | "offset">
optional

returns

Promise<CellRow[]>

matching active rows

query_cell_list_by_kind
#

db/cell_queries.ts view source

(deps: QueryDeps, kind: string, options?: Pick<CellListOptions, "limit" | "offset"> | undefined): Promise<CellRow[]> import {query_cell_list_by_kind} from '@fuzdev/fuz_app/db/cell_queries.js';

List active cells with the given kind, newest first. Uses the idx_cell_kind index (cell.kind = ?).

deps

query deps

kind

cell.kind value to match (e.g. 'collection', 'entry')

type string

options?

pagination

type Pick<CellListOptions, "limit" | "offset">
optional

returns

Promise<CellRow[]>

matching active rows

query_cell_load_many
#

db/cell_queries.ts view source

(deps: QueryDeps, ids: readonly (string & $brand<"Uuid">)[]): Promise<CellRow[]> import {query_cell_load_many} from '@fuzdev/fuz_app/db/cell_queries.js';

Bulk-load active cell rows by id, no visibility filter applied. Used by the strict relation-read filter (auth/cell_relation_visibility.ts's filter_visible_target_ids), which runs can_view_cell per row in memory rather than in SQL. Soft-deleted rows are excluded so relations to tombstones never surface.

deps

query deps

ids

cell ids to load (duplicates are harmless)

type readonly Uuid[]

returns

Promise<CellRow[]>

active rows in arbitrary order (caller indexes by id)

query_cell_set_moderation
#

db/cell_queries.ts view source

(deps: QueryDeps, id: string & $brand<"Uuid">, moderation: string, options: { set_visibility_public: boolean; updated_by?: (string & $brand<"Uuid">) | null | undefined; }): Promise<...> import {query_cell_set_moderation} from '@fuzdev/fuz_app/db/cell_queries.js';

Transition a contribution's moderation marker (the cell_moderate verb's write). On set_visibility_public (an approval) it also flips visibility to 'public' so the approved contribution goes live; a rejection leaves visibility untouched (stays private). updated_at / updated_by are stamped.

moderation is deliberately not writable through query_cell_update (it's absent from CellUpdatePatch) — gating the transition on a dedicated query (with the manage-tier authority check in the handler) is what stops an author self-approving their own pending cell.

deps

query deps

id

cell id

type Uuid

moderation

the terminal marker to write ('approved' | 'rejected')

type string

options

set_visibility_public flips visibility on approval; updated_by records the moderator

type { set_visibility_public: boolean; updated_by?: (string & $brand<"Uuid">) | null | undefined; }

returns

Promise<CellRow | null>

the updated row, or null when no active row matched (raced delete)

mutates

  • cell — sets `moderation` (+ maybe `visibility`) on one row

query_cell_update
#

db/cell_queries.ts view source

(deps: QueryDeps, id: string & $brand<"Uuid">, patch: CellUpdatePatch): Promise<CellRow | null> import {query_cell_update} from '@fuzdev/fuz_app/db/cell_queries.js';

Update a cell. Fields left undefined in the patch keep their existing value; explicit null writes NULL. refs is re-derived from data whenever the patch updates data. updated_at is bumped to NOW() on every successful update.

deps

query deps

id

cell id

type Uuid

patch

subset of mutable fields

returns

Promise<CellRow | null>

the updated row, or null when no row matched (already deleted or never existed)

mutates

  • cell — updates one row

query_count_active_accounts_with_global_role
#

auth/role_grant_queries.ts view source

(deps: QueryDeps, role: string): Promise<number> import {query_count_active_accounts_with_global_role} from '@fuzdev/fuz_app/auth/role_grant_queries.js';

Count active accounts (deleted_at IS NULL) holding an active global role_grant for role, counting only grants on active actors (act.deleted_at IS NULL). Used by the last-admin guard on account_delete / account_purge: soft-delete revokes neither a tombstoned account's nor a tombstoned actor's admin grant, so an unfiltered count would include unusable admins — this joins account *and* filters the actor, so an account whose only admin grant sits on a tombstoned actor no longer counts (otherwise the guard could fail-open and let the last usable admin be deleted).

deps

query dependencies

role

the role to count (e.g. ROLE_ADMIN)

type string

returns

Promise<number>

the number of distinct active accounts with an active global role grant

query_create_account
#

auth/account_queries.ts view source

(deps: QueryDeps, input: CreateAccountInput): Promise<Account> import {query_create_account} from '@fuzdev/fuz_app/auth/account_queries.js';

Create a new account.

deps

query dependencies

input

the account fields

returns

Promise<Account>

the created account

mutates

  • account table — inserts the new row

query_create_account_with_actor
#

auth/account_queries.ts view source

(deps: QueryDeps, input: CreateAccountInput): Promise<{ account: Account; actor: Actor; }> import {query_create_account_with_actor} from '@fuzdev/fuz_app/auth/account_queries.js';

Create an account and its actor in a single operation.

For v1, every account gets exactly one actor with the same name as the username.

deps

query dependencies

input

the account fields

returns

Promise<{ account: Account; actor: Actor; }>

the created account and actor

mutates

  • account and actor tables — inserts one row in each

query_create_actor
#

auth/account_queries.ts view source

(deps: QueryDeps, account_id: string, name: string): Promise<Actor> import {query_create_actor} from '@fuzdev/fuz_app/auth/account_queries.js';

Create a new actor for an account.

deps

query dependencies

account_id

the owning account

type string

name

display name (defaults to account username)

type string

returns

Promise<Actor>

the created actor

mutates

  • actor table — inserts the new row

query_create_api_token
#

auth/api_token_queries.ts view source

(deps: QueryDeps, id: string, account_id: string, name: string, token_hash: string, scope: { version: 1; kind: "full"; grandfathered?: boolean | undefined; } | { version: 1; kind: "methods"; methods: string[]; grandfathered?: boolean | undefined; }, expires_at?: Date | ... 1 more ... | undefined): Promise<...> import {query_create_api_token} from '@fuzdev/fuz_app/auth/api_token_queries.js';

Store a new API token (the hash, not the raw token).

deps

query dependencies

id

the public token id (e.g. tok_abc123)

type string

account_id

the owning account

type string

name

human-readable name

type string

token_hash

blake3 hash of the raw token

type string

scope

expires_at?

optional expiration

type Date | null
optional

returns

Promise<ApiToken>

the stored token record

mutates

  • api_token table — inserts the new row keyed by `id`

query_create_invite
#

auth/invite_queries.ts view source

(deps: QueryDeps, input: CreateInviteInput): Promise<Invite> import {query_create_invite} from '@fuzdev/fuz_app/auth/invite_queries.js';

Create a new invite.

deps

query dependencies

input

the invite fields

returns

Promise<Invite>

the created invite

mutates

  • invite table — inserts the new row

query_create_role_grant
#

auth/role_grant_queries.ts view source

(deps: QueryDeps, input: CreateRoleGrantInput): Promise<RoleGrant> import {query_create_role_grant} from '@fuzdev/fuz_app/auth/role_grant_queries.js';

Grant a role_grant to an actor. Idempotent — if an active role_grant already exists for this actor, role, and scope, returns the existing role_grant instead of creating a duplicate.

The ON CONFLICT target and the fallback SELECT both collapse NULL scopes via the same sentinel + index-side 'GLOBAL' token used by the partial unique index (role_grant_actor_role_scope_active_unique). The IS NOT DISTINCT FROM form on the fallback is deliberate — plain = would miss the NULL-scope case where the conflict fired.

scope_kind is paired-null with scope_id per the role_grant_scope_kind_paired CHECK; mismatched pairs raise at the DB layer rather than producing silent rows.

deps

query dependencies

input

the role_grant fields

returns

Promise<RoleGrant>

the created or existing active role_grant

mutates

  • role_grant table — inserts a row when no active role_grant matches `(actor_id, role, scope_kind, scope_id)`

query_create_session
#

auth/session_queries.ts view source

(deps: QueryDeps, token_hash: string, account_id: string, expires_at: Date): Promise<void> import {query_create_session} from '@fuzdev/fuz_app/auth/session_queries.js';

Create a new auth session.

deps

query dependencies

token_hash

blake3 hash of the session token (use hash_session_token)

type string

account_id

the account this session belongs to

type string

expires_at

when the session expires

type Date

returns

Promise<void>

mutates

  • auth_session table — inserts a row keyed by `token_hash`

query_db_status
#

db/status.ts view source

(db: Db, namespaces?: MigrationNamespace[] | undefined): Promise<DbStatus> import {query_db_status} from '@fuzdev/fuz_app/db/status.js';

Query database status including connectivity, tables, and migration state.

Designed for CLI db:status commands. Does not modify the database.

db

the database instance

type Db

namespaces?

migration namespaces to check status for

optional

returns

Promise<DbStatus>

a snapshot of database status; connected: false with error set when the initial connectivity probe fails

throws

  • Error - propagated from the driver if a query fails after the connectivity probe (e.g. a table is dropped mid-scan)

query_delete_fact
#

db/fact_queries.ts view source

(deps: QueryDeps, hash: FactHash): Promise<{ size: number; external_url: string | null; } | null> import {query_delete_fact} from '@fuzdev/fuz_app/db/fact_queries.js';

Drop a fact row. Cascades fact_ref rows via the ON DELETE CASCADE FK on source_hash. Returns the deleted row's (size, external_url) so the caller can unlink the disk file (if any) and tally freed bytes, or null when no row matched (idempotent: deleting an absent fact is not an error).

NOTE: this is a low-level primitive — callers MUST verify the fact is truly orphan (no referencing cell) before calling. The orphan check lives in query_orphan_facts_* below; the lifecycle wrapper in PgFactStore.delete handles the disk-file unlink.

deps

hash

type FactHash

returns

Promise<{ size: number; external_url: string | null; } | null>

query_get_fact
#

db/fact_queries.ts view source

(deps: QueryDeps, hash: FactHash): Promise<FactRow | null> import {query_get_fact} from '@fuzdev/fuz_app/db/fact_queries.js';

Fetch a fact's full row (including embedded bytes). Use this from FactStore.get; cheaper accessors live below.

deps

hash

type FactHash

returns

Promise<FactRow | null>

query_get_fact_meta
#

db/fact_queries.ts view source

(deps: QueryDeps, hash: FactHash): Promise<FactMetaRow | null> import {query_get_fact_meta} from '@fuzdev/fuz_app/db/fact_queries.js';

Fetch metadata only — skips the (potentially large) bytes column.

deps

hash

type FactHash

returns

Promise<FactMetaRow | null>

query_get_fact_refs
#

db/fact_queries.ts view source

(deps: QueryDeps, source_hash: FactHash): Promise<FactHash[]> import {query_get_fact_refs} from '@fuzdev/fuz_app/db/fact_queries.js';

List declared targets for a source fact. Order is unspecified; callers that need stable ordering should sort.

deps

source_hash

type FactHash

returns

Promise<FactHash[]>

query_has_fact
#

db/fact_queries.ts view source

(deps: QueryDeps, hash: FactHash): Promise<boolean> import {query_has_fact} from '@fuzdev/fuz_app/db/fact_queries.js';

Cheap existence check. Backed by the fact PK index.

deps

hash

type FactHash

returns

Promise<boolean>

query_invite_claim_unscoped
#

auth/invite_queries.ts view source

(deps: QueryDeps, invite_id: string, account_id: string): Promise<boolean> import {query_invite_claim_unscoped} from '@fuzdev/fuz_app/auth/invite_queries.js';

Claim an invite by setting the claimed_by and claimed_at fields.

The _unscoped suffix is the safety signal — the SQL only checks the row state (claimed_at IS NULL), not whether the claiming account's email or username matches the invite. Callers must scope the lookup upstream via one of the _find_unclaimed_match* siblings (production uses _for_update to make find + claim atomic). Skipping the find step lets a caller claim any unclaimed invite by id.

Mirrors the query_session_revoke_by_hash_unscoped precedent — there is no scoped sibling because the scoping is provided by a separate find query, not by an alternate variant of this query.

deps

query dependencies

invite_id

the invite to claim

type string

account_id

the account claiming the invite

type string

returns

Promise<boolean>

true if the invite was claimed, false if already claimed or not found

mutates

  • invite row — sets `claimed_by` and `claimed_at` when still unclaimed

query_invite_delete_unclaimed
#

auth/invite_queries.ts view source

(deps: QueryDeps, id: string): Promise<boolean> import {query_invite_delete_unclaimed} from '@fuzdev/fuz_app/auth/invite_queries.js';

Delete an unclaimed invite.

deps

query dependencies

id

the invite id

type string

returns

Promise<boolean>

true if deleted, false if not found or already claimed

mutates

  • invite table — deletes the row when still unclaimed

query_invite_find_unclaimed_by_email
#

auth/invite_queries.ts view source

(deps: QueryDeps, email: string): Promise<Invite | undefined> import {query_invite_find_unclaimed_by_email} from '@fuzdev/fuz_app/auth/invite_queries.js';

Find an unclaimed invite by email (case-insensitive).

deps

email

type string

returns

Promise<Invite | undefined>

query_invite_find_unclaimed_by_username
#

auth/invite_queries.ts view source

(deps: QueryDeps, username: string): Promise<Invite | undefined> import {query_invite_find_unclaimed_by_username} from '@fuzdev/fuz_app/auth/invite_queries.js';

Find an unclaimed invite by username (case-insensitive).

deps

username

type string

returns

Promise<Invite | undefined>

query_invite_find_unclaimed_match_for_update
#

auth/invite_queries.ts view source

(deps: QueryDeps, email: string | null, username: string): Promise<Invite | undefined> import {query_invite_find_unclaimed_match_for_update} from '@fuzdev/fuz_app/auth/invite_queries.js';

Find an unclaimed invite matching email and/or username, taking a row-level write lock on the matched row.

Three scoping modes:

  • Email-only invite (email set, username NULL) → matches only if signup provides matching email.
  • Username-only invite (username set, email NULL) → matches only if signup provides matching username.
  • Both-field invite (both set) → requires BOTH email and username to match.

Must run inside the same transaction as query_invite_claim_unscoped: FOR UPDATE makes find + claim atomic, so a concurrent signup that matched the same invite blocks on the lock until this transaction commits/rolls back. After commit, the loser's find_for_update returns no row (the winner flipped claimed_at) and falls through to ERROR_NO_MATCHING_INVITE — no race window between find and claim.

deps

query dependencies — deps.db MUST be a transaction

email

email to match (or null if signup provides none)

type string | null

username

username to match

type string

returns

Promise<Invite | undefined>

the matching invite (locked), or undefined

query_invite_list_all
#

query_invite_list_all_with_usernames
#

auth/invite_queries.ts view source

(deps: QueryDeps): Promise<{ id: string & $brand<"Uuid">; email: string | null; username: string | null; claimed_by: (string & $brand<"Uuid">) | null; ... 4 more ...; claimed_by_username: string | null; }[]> import {query_invite_list_all_with_usernames} from '@fuzdev/fuz_app/auth/invite_queries.js';

List all invites with resolved creator/claimer usernames, newest first.

deps

query dependencies

returns

Promise<InviteWithUsernamesJson[]>

invites with created_by_username and claimed_by_username

query_migration_tracker
#

testing/schema_introspect.ts view source

(db: Db): Promise<{ entries: { namespace: string; name: string; sequence: number; }[]; }> import {query_migration_tracker} from '@fuzdev/fuz_app/testing/schema_introspect.js';

Read every schema_version row into a deterministic MigrationTracker.

The migration-identity twin of query_schema_snapshot: that captures the resulting schema (and excludes this tracker); this captures the tracker rows themselves, so the cross-impl gate can assert the two spines record byte-identical migration identity.

db

type Db

returns

Promise<MigrationTracker>

query_orphan_facts_list
#

db/fact_queries.ts view source

(deps: QueryDeps, older_than: Date | null, sample_limit: number): Promise<OrphanFactsListResult> import {query_orphan_facts_list} from '@fuzdev/fuz_app/db/fact_queries.js';

Compute the "orphan facts" set: rows in fact where no active (non-tombstone) cell.refs array contains the hash.

The cell join is deliberately app-coupled — fact lives in the fuz_facts namespace and cell.refs lives in fuz_cell, but the orphan predicate only makes sense in apps that route content through cells. When a non-cell fact consumer ever appears (signed memo outputs? external fact mirrors?) the predicate moves to a generic fact_consumers registry; today the cell layer is the only consumer.

The older_than filter applies to fact.created_at. Pass null to skip the filter (used by the list-summary preview); the delete handler always passes a non-null cutoff (default 0, meaning "any orphan").

deps

query deps

older_than

filter to facts created before this Date (or null to skip)

type Date | null

sample_limit

row cap for the returned sample

type number

returns

Promise<OrphanFactsListResult>

query_orphan_facts_select_for_delete
#

db/fact_queries.ts view source

(deps: QueryDeps, older_than: Date): Promise<{ hash: FactHash; size: number; external_url: string | null; }[]> import {query_orphan_facts_select_for_delete} from '@fuzdev/fuz_app/db/fact_queries.js';

Select the orphan-fact hashes for deletion. Returns the rows directly (no row-count limit) — callers iterate to unlink disk files. The older_than cutoff is required (non-null) here: bulk delete should always be operator-scoped to a time window. A "delete all" sweep passes a far-future cutoff, not null.

deps

older_than

type Date

returns

Promise<{ hash: FactHash; size: number; external_url: string | null; }[]>

query_public_columns
#

db/schema_ready.ts view source

(db: Db, options?: { table?: string | undefined; } | undefined): Promise<Record<string, string[]>> import {query_public_columns} from '@fuzdev/fuz_app/db/schema_ready.js';

Introspect every column in the public schema, grouped by relation. Shared by the runtime /ready check and the fixture-generating helper so both observe the exact same shape. information_schema.columns spans tables and views; for the drift check that's harmless (a never-bootstrapped schema has neither, and extra relations are ignored — see check_schema_drift).

Unlike query_schema_snapshot (which excludes the schema_version migration tracker as framework bookkeeping), this keeps schema_version — a never-migrated DB then correctly fails readiness instead of passing on an empty expectation.

db

the database to introspect

type Db

options?

table narrows the scan to one relation (the drift guard's one-table question); omitted, every public relation is returned

type { table?: string | undefined; }
optional

returns

Promise<Record<string, string[]>>

relation name → sorted column names

query_purge_account
#

auth/account_queries.ts view source

(deps: QueryDeps, id: string): Promise<AccountIdentitySnapshot | undefined> import {query_purge_account} from '@fuzdev/fuz_app/auth/account_queries.js';

Hard-purge an account — irreversible cascading removal (purge = hard).

Physically deletes the row, cascading to actors, role_grants, sessions, and tokens. Operates on active OR already-soft-deleted rows. Returns the identity snapshot for the account_purge audit event, or undefined when no row matched. The audit_log identity columns carry no FK, so the purged id survives on historical rows for forensic correlation back to the purge event.

Keeper-gated, loud, irreversible — restrict to the keeper credential and confirm explicitly at the call site. The purge name flags the danger.

deps

id

type string

returns

Promise<AccountIdentitySnapshot | undefined>

mutates

  • account table and downstream FK rows — DELETE cascades through actors/role_grants/sessions/tokens

query_put_fact
#

db/fact_queries.ts view source

(deps: QueryDeps, input: { hash: FactHash; bytes: Uint8Array<ArrayBufferLike> | null; external_url: string | null; content_type: string | null; size: number; }): Promise<...> import {query_put_fact} from '@fuzdev/fuz_app/db/fact_queries.js';

Idempotently insert a fact row.

bytes xor external_url per the fact_storage_present CHECK constraint; the caller is responsible for satisfying it (the queries layer does not second-guess). Returns true when a new row was inserted, false when a row already existed (caller can use this to decide whether to also write fact_ref).

deps

input

type { hash: FactHash; bytes: Uint8Array<ArrayBufferLike> | null; external_url: string | null; content_type: string | null; size: number; }

returns

Promise<boolean>

query_put_fact_refs
#

db/fact_queries.ts view source

(deps: QueryDeps, source_hash: FactHash, target_hashes: FactHash[]): Promise<void> import {query_put_fact_refs} from '@fuzdev/fuz_app/db/fact_queries.js';

Idempotently insert declared refs for a fact. No-ops on `(source_hash, target_hash)` collisions and skips the round trip entirely when target_hashes is empty.

deps

source_hash

type FactHash

target_hashes

type FactHash[]

returns

Promise<void>

query_revoke_all_api_tokens_for_account
#

auth/api_token_queries.ts view source

(deps: QueryDeps, account_id: string): Promise<number> import {query_revoke_all_api_tokens_for_account} from '@fuzdev/fuz_app/auth/api_token_queries.js';

Revoke all tokens for an account.

deps

query dependencies

account_id

the account whose tokens to revoke

type string

returns

Promise<number>

the number of tokens revoked

mutates

  • api_token table — deletes every row for `account_id`

query_revoke_api_token_for_account
#

auth/api_token_queries.ts view source

(deps: QueryDeps, id: string, account_id: string): Promise<boolean> import {query_revoke_api_token_for_account} from '@fuzdev/fuz_app/auth/api_token_queries.js';

Revoke a token only if it belongs to the specified account.

Prevents cross-account token revocation.

deps

query dependencies

id

the public token id

type string

account_id

the account that must own the token

type string

returns

Promise<boolean>

true if a token was revoked, false if not found or wrong account

mutates

  • api_token table — deletes the row when account ownership matches

query_revoke_role_grant
#

auth/role_grant_queries.ts view source

(deps: QueryDeps, role_grant_id: string & $brand<"Uuid">, actor_id: string & $brand<"Uuid">, revoked_by: (string & $brand<"Uuid">) | null, reason?: string | ... 1 more ... | undefined): Promise<...> import {query_revoke_role_grant} from '@fuzdev/fuz_app/auth/role_grant_queries.js';

Revoke a role_grant by id, constrained to a specific actor.

Requires actor_id to prevent cross-account revocation (IDOR guard). Returns null if the role_grant is not found, already revoked, or belongs to a different actor.

Supersedes any pending offers for the revoked role_grant's (to_account, role, scope) in the same transaction. Prevents the "accept a pre-revoke offer to bypass the revoke" path — any stale offer becomes terminal at revoke time. A fresh post-revoke grant requires the grantor to call query_role_grant_offer_create again.

deps

query dependencies

role_grant_id

the role_grant to revoke

type Uuid

actor_id

the actor that must own the role_grant

type Uuid

revoked_by

the actor who revoked it (for audit trail)

type Uuid | null

reason?

optional free-form reason, stamped on role_grant.revoked_reason and surfaced to the revokee notification.

type string | null
optional

returns

Promise<RevokeRoleGrantResult | null>

mutates

  • role_grant row — sets `revoked_at`, `revoked_by`, and `revoked_reason`
  • role_grant_offer rows — stamps `superseded_at` on every pending sibling for the same `(account, role, scope)`

query_role_grant_by_id
#

auth/role_grant_queries.ts view source

(deps: QueryDeps, id: string & $brand<"Uuid">): Promise<RoleGrant | undefined> import {query_role_grant_by_id} from '@fuzdev/fuz_app/auth/role_grant_queries.js';

Find a role_grant by id, regardless of revoked / expired state.

Unscoped by design — the one caller is query_accept_offer's race-loser path, which resolves an already-accepted offer's resulting_role_grant_id (a row the offer's own IDOR guard has already vouched for). Authority checks stay at the call site.

deps

id

type Uuid

returns

Promise<RoleGrant | undefined>

query_role_grant_find_account_id_for_role
#

auth/role_grant_queries.ts view source

(deps: QueryDeps, role: string): Promise<string | null> import {query_role_grant_find_account_id_for_role} from '@fuzdev/fuz_app/auth/role_grant_queries.js';

Find the account ID of an account that holds an active role_grant for a given role.

Joins role_grant → actor → account, counting only an active account + active actor (deleted_at IS NULL on both). This *resolves* an identity (the daemon-token keeper via resolve_keeper_account_id), so a tombstoned actor must not grant the role — the opposite posture from the removability guard query_account_has_global_role, which stays unconditional. Returns the first match by ascending account.id (deterministic under multiple holders), or null if none.

deps

query dependencies

role

the role to search for

type string

returns

Promise<string | null>

the account ID, or null

query_role_grant_find_active_for_actor
#

auth/role_grant_queries.ts view source

(deps: QueryDeps, actor_id: string): Promise<RoleGrant[]> import {query_role_grant_find_active_for_actor} from '@fuzdev/fuz_app/auth/role_grant_queries.js';

Find all active (non-revoked, non-expired) role_grants for an actor.

deps

actor_id

type string

returns

Promise<RoleGrant[]>

query_role_grant_find_active_role_for_actor
#

auth/role_grant_queries.ts view source

(deps: QueryDeps, role_grant_id: string, actor_id: string): Promise<{ role: string; account_id: string & $brand<"Uuid">; } | null> import {query_role_grant_find_active_role_for_actor} from '@fuzdev/fuz_app/auth/role_grant_queries.js';

Look up the role of an active role_grant (constrained to a specific actor) plus the actor's account_id.

Used by admin routes to inspect the role_grant's role before acting (e.g., enforcing the admin-grant-path gate on revoke). The actor constraint mirrors query_revoke_role_grant so IDOR protection is consistent: a caller can only see role_grants belonging to the target actor.

The JOIN to actor collapses what used to be a second query_actor_by_id round-trip in the revoke handler into one read, which closes the small TOCTOU window where the actor row could be deleted between the IDOR check and the actor lookup. The account_id is needed by the audit envelope's target_account_id field and the SSE/WS socket-close fan-out targeting.

Returns null if the role_grant is not found, already revoked, or belongs to a different actor.

deps

query dependencies

role_grant_id

the role_grant id to look up

type string

actor_id

the actor that must own the role_grant

type string

returns

Promise<{ role: string; account_id: string & $brand<"Uuid">; } | null>

{role, account_id} on a match, or null

query_role_grant_has_role
#

auth/role_grant_queries.ts view source

(deps: QueryDeps, actor_id: string, role: string, scope_id?: string | null | undefined): Promise<boolean> import {query_role_grant_has_role} from '@fuzdev/fuz_app/auth/role_grant_queries.js';

Check if an actor has an active role_grant for a given role.

The scope_id parameter selects between global and scoped checks:

  • Omitted or null — matches a global role_grant (scope_id IS NULL). Pre-scope callers keep their existing semantics.
  • A scope uuid — matches a role_grant bound to that exact scope.

The IS NOT DISTINCT FROM comparison handles the NULL case uniformly.

deps

actor_id

type string

role

type string

scope_id?

type string | null
optional

returns

Promise<boolean>

query_role_grant_list_for_actor
#

auth/role_grant_queries.ts view source

(deps: QueryDeps, actor_id: string): Promise<RoleGrant[]> import {query_role_grant_list_for_actor} from '@fuzdev/fuz_app/auth/role_grant_queries.js';

List all role_grants for an actor (including revoked/expired).

deps

actor_id

type string

returns

Promise<RoleGrant[]>

query_role_grant_offer_create
#

auth/role_grant_offer_queries.ts view source

(deps: QueryDeps, input: CreateRoleGrantOfferInput): Promise<RoleGrantOffer> import {query_role_grant_offer_create} from '@fuzdev/fuz_app/auth/role_grant_offer_queries.js';

Create a new role_grant offer, or refresh an existing pending offer for the same (to_account_id, role, scope_id, from_actor_id) tuple.

Re-offer semantics: a second call by the same grantor with the same (to_account, role, scope) while pending upserts the existing row, refreshing message and expires_at (and to_actor_id — supplying a different to_actor_id on re-offer narrows the existing row to the named actor; supplying null widens it back to account-grain) and clearing expire_audited_at, so a re-offer of an expired, already-audited row gets its own role_grant_offer_expire when the refreshed expiry passes. A different grantor offering the same (to_account, role, scope) creates a distinct row — multiple pending grantors coexist. After a terminal state, a re-offer is a fresh INSERT.

Self-offer rejection: throws RoleGrantOfferSelfTargetError if the offering actor belongs to the recipient account.

Actor-targeted offers: when to_actor_id is supplied, query_accept_offer rejects any actor other than the named one. Closes the audit hole where offer-shape events would otherwise leave target_actor_id null even when the recipient binding is known at offer time. The actor↔account binding is verified here in one SELECT.

deps

input

returns

Promise<RoleGrantOffer>

throws

  • RoleGrantOfferSelfTargetError - if the offering actor belongs to `to_account_id`
  • RoleGrantOfferActorAccountMismatchError - if `to_actor_id` is set but does not belong to `to_account_id`

mutates

  • role_grant_offer table — inserts a new offer or upserts the matching pending row

query_role_grant_offer_decline
#

auth/role_grant_offer_queries.ts view source

(deps: QueryDeps, offer_id: string, to_account_id: string, reason: string | null): Promise<DeclinedOffer | null> import {query_role_grant_offer_decline} from '@fuzdev/fuz_app/auth/role_grant_offer_queries.js';

Mark an offer declined.

Guarded by to_account_id (IDOR). Returns null if the offer does not exist or belongs to a different account. Throws RoleGrantOfferAlreadyTerminalError if the offer exists for the caller but is already in a terminal state.

Returns the declined offer with the grantor's from_account_id joined in via CTE — the decline audit envelope populates both target_actor_id (the grantor actor) and target_account_id (the grantor account), satisfying the "both populated → same account" invariant the audit-log column comments describe.

deps

offer_id

type string

to_account_id

type string

reason

type string | null

returns

Promise<DeclinedOffer | null>

throws

  • RoleGrantOfferAlreadyTerminalError - if the offer is already accepted, declined, retracted, or superseded

mutates

  • role_grant_offer row — sets `declined_at` and `decline_reason`

query_role_grant_offer_find_pending
#

auth/role_grant_offer_queries.ts view source

(deps: QueryDeps, offer_id: string): Promise<RoleGrantOffer | null> import {query_role_grant_offer_find_pending} from '@fuzdev/fuz_app/auth/role_grant_offer_queries.js';

Look up a pending offer by id. Returns null if the offer is terminal, expired (server-side filter), or missing.

deps

offer_id

type string

returns

Promise<RoleGrantOffer | null>

query_role_grant_offer_history_for_account
#

auth/role_grant_offer_queries.ts view source

(deps: QueryDeps, account_id: string, limit?: number, offset?: number): Promise<RoleGrantOffer[]> import {query_role_grant_offer_history_for_account} from '@fuzdev/fuz_app/auth/role_grant_offer_queries.js';

List every offer involving an account (either direction), newest first.

Includes terminal offers — used by the grantor-side admin / history view.

deps

account_id

type string

limit

type number
default 100

offset

type number
default 0

returns

Promise<RoleGrantOffer[]>

query_role_grant_offer_list
#

auth/role_grant_offer_queries.ts view source

(deps: QueryDeps, to_account_id: string): Promise<RoleGrantOffer[]> import {query_role_grant_offer_list} from '@fuzdev/fuz_app/auth/role_grant_offer_queries.js';

List pending, non-expired offers for an account, soonest expiry first.

Expired offers are filtered server-side (expires_at > NOW()) so the inbox never surfaces a row that can no longer be accepted. The periodic sweep (query_role_grant_offer_sweep_expired) audits each expiry once.

deps

to_account_id

type string

returns

Promise<RoleGrantOffer[]>

query_role_grant_offer_retract
#

auth/role_grant_offer_queries.ts view source

(deps: QueryDeps, offer_id: string, from_actor_id: string): Promise<RoleGrantOffer | null> import {query_role_grant_offer_retract} from '@fuzdev/fuz_app/auth/role_grant_offer_queries.js';

Mark an offer retracted by the grantor.

Guarded by from_actor_id (IDOR). Returns null if the offer does not exist or was issued by a different actor. Throws RoleGrantOfferAlreadyTerminalError if the offer exists for this grantor but is already in a terminal state.

deps

offer_id

type string

from_actor_id

type string

returns

Promise<RoleGrantOffer | null>

throws

  • RoleGrantOfferAlreadyTerminalError - if the offer is already accepted, declined, retracted, or superseded

mutates

  • role_grant_offer row — sets `retracted_at`

query_role_grant_offer_sweep_expired
#

auth/role_grant_offer_queries.ts view source

(deps: QueryDeps): Promise<RoleGrantOffer[]> import {query_role_grant_offer_sweep_expired} from '@fuzdev/fuz_app/auth/role_grant_offer_queries.js';

Claim the pending offers whose expires_at has passed and whose expiry has not been audited yet, stamping expire_audited_at on each, and return them soonest-expiry first.

The stamp is the sweep's idempotency: a claimed row never matches again, so each expiry is audited exactly once. It must share a transaction with the caller's role_grant_offer_expire audit inserts — committed together, or rolled back together so the next sweep retries. Concurrent sweeps claim disjoint rows: a row another sweep has stamped but not committed blocks on its row lock, then fails the re-checked expire_audited_at IS NULL. cleanup_expired_role_grant_offers is the caller.

deps

returns

Promise<RoleGrantOffer[]>

mutates

  • role_grant_offer rows — stamps `expire_audited_at` on each claimed row

query_role_grant_revoke_for_scope
#

auth/role_grant_queries.ts view source

(deps: QueryDeps, scope_id: string & $brand<"Uuid">, revoked_by: (string & $brand<"Uuid">) | null, reason?: string | null | undefined): Promise<RevokeForScopeResult> import {query_role_grant_revoke_for_scope} from '@fuzdev/fuz_app/auth/role_grant_queries.js';

Revoke every active role_grant bound to a scope and supersede every pending offer at the scope, in one cascade.

Use this from a consumer's parent-scope delete handler (e.g., classroom deletion) — role_grant.scope_id and role_grant_offer.scope_id are polymorphic with no FK constraint by design, so a parent row deletion would otherwise orphan role_grants and offers. The cascade is role-agnostic: anything attached to the destroyed scope is cleaned up.

Both updates run as separate statements inside the caller's transaction (mirrors query_role_grant_revoke_role's shape). The two halves are independent — orphan pending offers can exist at a scope with no active role_grants, so the supersede half always runs even when no role_grant was revoked.

deps

query dependencies

scope_id

the scope whose role_grants and offers to terminate

type Uuid

revoked_by

the actor performing the cascade (audit trail)

type Uuid | null

reason?

optional free-form reason, stamped on role_grant.revoked_reason.

type string | null
optional

returns

Promise<RevokeForScopeResult>

the revoked role_grants (with account_id for fan-out) and superseded offers (with from_account_id for fan-out)

mutates

  • role_grant table — sets `revoked_at`/`revoked_by`/`revoked_reason` on every active row at `scope_id`
  • role_grant_offer table — stamps `superseded_at` on every pending row at `scope_id`

query_role_grant_revoke_role
#

auth/role_grant_queries.ts view source

(deps: QueryDeps, actor_id: string, role: string, revoked_by: string | null, reason?: string | null | undefined): Promise<RevokeRoleResult> import {query_role_grant_revoke_role} from '@fuzdev/fuz_app/auth/role_grant_queries.js';

Revoke every active role_grant an actor holds for a given role.

With scoped role_grants a single actor+role tuple can hold several active role_grants (one per scope), so this revokes all of them. Pass query_revoke_role_grant(role_grant_id, ...) when a single scoped role_grant is the target.

Also supersedes pending offers for the actor's account across every scope of this role (the actor can no longer hold the role, so any pending offer of the same role is a bypass vector).

deps

query dependencies

actor_id

the actor whose role_grants to revoke

type string

role

the role to revoke

type string

revoked_by

the actor who revoked it (for audit trail)

type string | null

reason?

optional free-form reason, stamped on role_grant.revoked_reason.

type string | null
optional

returns

Promise<RevokeRoleResult>

the list of revoked role_grants (empty if none were active) and superseded pending offers

mutates

  • role_grant table — sets `revoked_at`/`revoked_by`/`revoked_reason` on every active row for `(actor, role)`
  • role_grant_offer table — stamps `superseded_at` on every matching pending offer

query_schema_snapshot
#

testing/schema_introspect.ts view source

(db: Db, options?: QuerySchemaSnapshotOptions): Promise<{ tables: Record<string, { columns: Record<string, { data_type: string; udt_name: string; is_nullable: boolean; column_default: string | null; is_identity: boolean; }>; indexes: { ...; }[]; constraints: { ...; }[]; }>; sequences: Record<...>; enums: Record<...>; }> import {query_schema_snapshot} from '@fuzdev/fuz_app/testing/schema_introspect.js';

Introspect a live database into a deterministic SchemaSnapshot.

Reads information_schema and pg_catalog to capture tables, columns, indexes, constraints, and sequences.

The schema_version migration tracker never appears in the tables field — it's framework bookkeeping created by the migration runner, identical across consumers, and would only add noise.

db

the database to introspect

type Db

options

schema, table excludes, and the pglet opt-in

default {}

returns

Promise<SchemaSnapshot>

the snapshot, every collection sorted

throws

  • Error - on pglet when the snapshot holds an index and `accept_pglet_index_definitions` is not set — its index definitions omit modifiers, so a comparison built on them is wrong in both directions

query_session_cleanup_expired
#

auth/session_queries.ts view source

(deps: QueryDeps): Promise<string[]> import {query_session_cleanup_expired} from '@fuzdev/fuz_app/auth/session_queries.js';

Delete every expired session, returning the ids (token hashes) of the rows it deleted.

The ids are the keys ConnectionCloser.close_sockets_for_session closes by. A row past expires_at already fails query_session_get_valid, but deleting it does not end the connections it opened: a WebSocket keeps the authority it resolved at upgrade and an SSE stream never rechecks. The row was also the only per-session handle on those connections — account_session_revoke closes only for a row it deleted — so the caller closes each returned session's connections once the delete has committed. cleanup_expired_sessions (auth/cleanup.ts) is that caller; reach for it rather than this query.

One statement, so the returned ids are exactly the rows this call removed: a concurrent pass gets a disjoint set. Run it in a transaction, as the caller above does, so the ids are in hand before the delete commits — on a pool-level Db a connection lost on the response leaves the rows deleted and their ids unread. Served by idx_auth_session_expires. Twin of the Rust query_session_cleanup_expired.

deps

query dependencies (transaction-scoped, see above)

returns

Promise<string[]>

the deleted sessions' ids (their token hashes), empty when nothing had expired

mutates

  • auth_session table — deletes every row past `expires_at`

query_session_enforce_limit
#

auth/session_queries.ts view source

(deps: QueryDeps, account_id: string, max_sessions: number): Promise<string[]> import {query_session_enforce_limit} from '@fuzdev/fuz_app/auth/session_queries.js';

Enforce a per-account session limit by evicting the oldest sessions.

Keeps the newest max_sessions sessions and deletes the rest.

Race safety: this function must run inside a transaction alongside the INSERT that created the new session. All callers satisfy this requirement:

  • POST /login uses the default transaction: true (framework-managed transaction wrapping in apply_route_specs)
  • The account_token_create RPC handler runs under the dispatcher's transaction path because its spec declares side_effects: true
  • POST /bootstrap and POST /signup manage their own transactions and pass the transaction-scoped deps to create_session_and_set_cookie

The transaction makes one creator's INSERT + enforce_limit pair atomic, but it does not serialize concurrent creators. Under Read Committed, two transactions can't see each other's uncommitted session row, so each computes its OFFSET eviction against a stale count, each preserves its own row, and both commit above max_sessions. A transaction is necessary here but not sufficient. Closing this needs one serialization point per (account_id, credential_kind) — a locked parent row or a transaction-scoped advisory lock — taken before count/evict/insert.

Ordering is created_at DESC, id DESC. The id leg is a stability tie-breaker, not a recency one: created_at defaults to NOW(), the *transaction* timestamp, so two sessions born in one transaction — or in two transactions that started in the same microsecond — tie, and an untied OFFSET may keep a different set on two evaluations of the same rows. id is the blake3 token hash, so the tie-break is arbitrary but deterministic; it makes the survivors reproducible, it does not make the row just inserted a guaranteed survivor. Only the serialization point above does that.

Expired-but-unreaped rows count toward the cap (the predicate is account_id alone). Matches the Rust twin query_session_enforce_limit.

Deleting a session row does not end the connections it opened: a WebSocket keeps the authority it resolved at upgrade and an SSE stream never rechecks. The returned ids are the token hashes those connections are registered under, so the caller closes them once the transaction commits (queue_connection_close) — POST /login does.

deps

query dependencies (must be transaction-scoped)

account_id

the account to enforce the limit for

type string

max_sessions

maximum number of sessions to keep

type number

returns

Promise<string[]>

the evicted sessions' ids (their token hashes), empty when nothing was evicted

mutates

  • auth_session table — deletes the oldest rows past the cap

query_session_get_valid
#

auth/session_queries.ts view source

(deps: QueryDeps, token_hash: string): Promise<ValidAuthSession | undefined> import {query_session_get_valid} from '@fuzdev/fuz_app/auth/session_queries.js';

Get a session if it exists, is not expired, and has not been revoked, with how long it has left.

The remaining lifetime is read in the same statement that checks the expiry, on the database clock, so it agrees with the check whatever the server's clock says (auth/credential_deadline.ts).

deps

query dependencies

token_hash

blake3 hash of the session token

type string

returns

Promise<ValidAuthSession | undefined>

query_session_list_all_active
#

auth/session_queries.ts view source

(deps: QueryDeps, limit?: number): Promise<({ id: string & $brand<"SessionId">; account_id: string & $brand<"Uuid">; created_at: string; expires_at: string; } & { ...; })[]> import {query_session_list_all_active} from '@fuzdev/fuz_app/auth/session_queries.js';

List all active sessions across all accounts with usernames.

Ordered by created_at DESC (newest session first), matching the per-account listing.

deps

query dependencies

limit

maximum entries to return

type number
default 200

returns

Promise<(AuthSession & { username: string; })[]>

active sessions joined with account usernames, newest first

query_session_list_for_account
#

auth/session_queries.ts view source

(deps: QueryDeps, account_id: string, limit?: number): Promise<{ id: string & $brand<"SessionId">; account_id: string & $brand<"Uuid">; created_at: string; expires_at: string; }[]> import {query_session_list_for_account} from '@fuzdev/fuz_app/auth/session_queries.js';

List sessions for an account, newest first.

deps

account_id

type string

limit

type number
default 50

returns

Promise<AuthSession[]>

query_session_revoke_all_for_account
#

auth/session_queries.ts view source

(deps: QueryDeps, account_id: string): Promise<number> import {query_session_revoke_all_for_account} from '@fuzdev/fuz_app/auth/session_queries.js';

Revoke all sessions for an account.

deps

account_id

type string

returns

Promise<number>

the number of sessions revoked

mutates

  • auth_session table — deletes every row for `account_id`

query_session_revoke_by_hash_unscoped
#

auth/session_queries.ts view source

(deps: QueryDeps, token_hash: string): Promise<void> import {query_session_revoke_by_hash_unscoped} from '@fuzdev/fuz_app/auth/session_queries.js';

Revoke (delete) a session by its token hash, with no account scoping.

The _unscoped suffix is the safety signal — there is no account_id constraint, so callers must guarantee the hash came from a trusted source (the authenticated session cookie path is the only safe production caller — see auth/account_routes.ts /logout). For user-facing revocation of a specific session by ID, use query_session_revoke_for_account (IDOR-guarded).

deps

token_hash

type string

returns

Promise<void>

mutates

  • auth_session table — deletes the row keyed by `token_hash`

query_session_revoke_for_account
#

auth/session_queries.ts view source

(deps: QueryDeps, token_hash: string, account_id: string): Promise<boolean> import {query_session_revoke_for_account} from '@fuzdev/fuz_app/auth/session_queries.js';

Revoke a session only if it belongs to the specified account.

Prevents cross-account session revocation.

deps

query dependencies

token_hash

blake3 hash of the session token

type string

account_id

the account that must own the session

type string

returns

Promise<boolean>

true if a session was revoked, false if not found or wrong account

mutates

  • auth_session table — deletes the row when account ownership matches

query_update_account_password
#

auth/account_queries.ts view source

(deps: QueryDeps, id: string, password_hash: string, updated_by: string | null, expected_hash: string): Promise<boolean> import {query_update_account_password} from '@fuzdev/fuz_app/auth/account_queries.js';

Update the password hash for an account, conditional on the current stored hash matching expected_hash — the verify-write atomic guard.

The condition closes the race where two concurrent password changes both verify against the pre-update hash (loaded by the authorization phase outside the route's transaction) and would otherwise both UPDATE, silently clobbering whichever lands first. With the conditional WHERE, the second UPDATE matches zero rows; the route reads the boolean return and surfaces 401 instead of pretending success.

Pass the same hash the verify ran against — typically ctx.account.password_hash from the request context.

deps

id

type string

password_hash

type string

updated_by

type string | null

expected_hash

type string

returns

Promise<boolean>

true if the row was updated, false if expected_hash no longer matched (concurrent change won — caller should treat as a stale-credential failure).

mutates

  • account row — updates `password_hash`, `updated_at`, and `updated_by` only when the stored hash equals `expected_hash`

query_validate_api_token
#

auth/api_token_queries.ts view source

(deps: ApiTokenQueryDeps, raw_token: string, ip: string | undefined, pending_effects: Promise<void>[] | undefined): Promise<ValidatedApiToken | undefined> import {query_validate_api_token} from '@fuzdev/fuz_app/auth/api_token_queries.js';

Validate a raw API token and return the token record with its scope parsed.

Hashes the token with blake3, looks up the hash, and checks expiration, reading how long the token has left on the database clock in the same statement. A row whose stored scope is unreadable is refused like an unknown hash. Updates last_used_at and last_used_ip on success (fire-and-forget — errors logged, never thrown).

deps

query dependencies with logger

raw_token

the raw API token from the Authorization header

type string

ip

the client IP address (for audit)

type string | undefined

pending_effects

optional array to register the usage-tracking effect for later awaiting

type Promise<void>[] | undefined

returns

Promise<ValidatedApiToken | undefined>

the token record with its parsed scope if valid, or undefined

mutates

  • api_token row — fire-and-forget UPDATE of `last_used_at` / `last_used_ip` on a valid token
  • pending_effects — pushes the in-flight tracking promise when provided

QueryDeps
#

db/query_deps.ts view source

QueryDeps import type {QueryDeps} from '@fuzdev/fuz_app/db/query_deps.js';

Base dependency for all query functions.

db

type Db

QuerySchemaSnapshotOptions
#

testing/schema_introspect.ts view source

QuerySchemaSnapshotOptions import type {QuerySchemaSnapshotOptions} from '@fuzdev/fuz_app/testing/schema_introspect.js';

Filter options for query_schema_snapshot.

schema?

Schema name to introspect — defaults to 'public'. Single-schema only; cross-schema introspection isn't a current need.

type string

readonly

exclude_tables?

Tables to exclude from the snapshot. The schema_version migration tracker is always excluded — it's framework bookkeeping created by the migration runner, identical across impls, and not part of any consumer's domain schema.

type readonly string[]

readonly

accept_pglet_index_definitions?

Take the snapshot on pglet even though it holds indexes. Default false: pglet's pg_indexes.indexdef omits index modifiers (sort direction, NULLS placement, operator classes, NULLS NOT DISTINCT), so the snapshot's index definitions cannot be compared with another engine's, and do not tell two pglet schemas apart when they differ only in a modifier. Pass true only where no index definition is compared — reading tables, columns, constraints, sequences or enums off the snapshot. No effect on PostgreSQL or PGlite.

type boolean

readonly

QueryShapeCrossTestOptions
#

testing/cross_backend/query_shape.ts view source

RpcPathCapabilityGatedCrossSuiteOptions import type {QueryShapeCrossTestOptions} from '@fuzdev/fuz_app/testing/cross_backend/query_shape.js';

Options for the query-shape parity suite. The standard capability-gating RPC-dispatched cross-suite shape (setup_test / capabilities / rpc_path); aliases the shared RpcPathCapabilityGatedCrossSuiteOptions rather than minting a duplicate.

rpc_path?

RPC endpoint path the methods are mounted on. Default /api/rpc.

type string

readonly

setup_test

Per-test fixture-producing function (fresh keeper + db per call).

type (): Promise<TestFixtureBase>

readonly
returns Promise<TestFixtureBase>

capabilities

Backend capability declarations — each suite gates on its own flag.

type BackendCapabilities

readonly

queue_connection_close
#

actions/connection_closer.ts view source

(ctx: EmitAfterCommitContext, closer: ConnectionCloser, target: ConnectionCloseTarget): void import {queue_connection_close} from '@fuzdev/fuz_app/actions/connection_closer.js';

Queue a connection close on ctx's post-commit queue, so it runs only after the request's transaction commits and never when the handler throws.

The one way a revocation handler closes connections — see the module doc for why an in-transaction revocation must not close inline.

In a handler with no wrapping transaction a throw still drops the queued close while an autocommit write stays; such a handler closes inline straight after its own commit instead, with close_connections_after_commit, which catches and logs a failing close as the post-commit flush does.

ctx

the route or action context of the request doing the revoking

closer

the closer to run, deps.connection_closer in a handler

target

the connections to close

returns

void

mutates

  • ctx.post_commit_effects — appends the close

rate_limit_exceeded_response
#

rate_limiter.ts view source

(c: Context<any, any, {}>, retry_after: number): Response import {rate_limit_exceeded_response} from '@fuzdev/fuz_app/rate_limiter.js';

Build a 429 rate-limit-exceeded JSON response with Retry-After header.

c

Hono context

type Context<any, any, {}>

retry_after

seconds until the client should retry

type number

returns

Response

a 429 Response

RATE_LIMITERS_DISABLED_BANNER
#

rate_limiter.ts view source

"!!! RATE LIMITERS DISABLED — test-only mode. A production server must never print this." import {RATE_LIMITERS_DISABLED_BANNER} from '@fuzdev/fuz_app/rate_limiter.js';

The stderr line a process prints the first time 'disabled_for_testing' leaves a limiter null — the same text as the Rust spine's.

RateLimiter
#

rate_limiter.ts view source

import {RateLimiter} from '@fuzdev/fuz_app/rate_limiter.js';

In-memory sliding window rate limiter.

Stores an array of timestamps per key, plus a count of attempts reserved (try_reserve) and not yet settled. On check/record, timestamps outside the window are pruned. retry_after reports seconds until the oldest active timestamp expires.

The backing store is an LruMap when options.max_keys is a positive number (default DEFAULT_RATE_LIMITER_MAX_KEYS) and a plain Map when max_keys is null. The LruMap path bounds memory under key-enumeration attack at the cost of a slight per-op overhead and the LRU trade-off described on RateLimiterOptions.max_keys.

Parameters that accept RateLimiter | null (e.g. login_ip_rate_limiter, login_account_rate_limiter) disable rate limiting when null is passed — no checks are performed and all requests are allowed through. create_app_server surfaces each disabled limiter as a warning diagnostic.

options

type RateLimiterOptions

readonly

constructor

type new (options: RateLimiterOptions): RateLimiter

options

[Symbol.for('nodejs.util.inspect.custom')]

Custom inspect output that exposes only options and size, never the tracked keys. The key set is sensitive (usernames / IP addresses) and unbounded, so it must not leak into logs via util.inspect / console.log. The #attempts field is already #private, but this keeps the boundary explicit against inspect(…, {showHidden: true}) and future field-visibility changes.

type (): { options: RateLimiterOptions; size: number; }

returns { options: RateLimiterOptions; size: number; }

check

Check whether key is allowed without recording an attempt, counting recorded attempts and reserved ones still in flight.

Prunes timestamps that fell outside the window as a side effect (and removes the key entirely when none remain and none are in flight), so the backing map stays bounded even under read-only traffic.

A check followed by an awaited operation and then a record lets a concurrent burst past the cap; reserve with try_reserve instead.

type (key: string, now?: number): RateLimitResult

key

rate limit key (e.g. IP address)

type string

now

current timestamp in ms (defaults to Date.now())

type number
default Date.now()

mutates

  • internal map — prunes expired timestamps for `key`

record

Record a failed attempt for key and return the updated result.

type (key: string, now?: number): RateLimitResult

key

rate limit key (e.g. IP address)

type string

now

current timestamp in ms (defaults to Date.now())

type number
default Date.now()

mutates

  • internal map — appends `now` to the timestamp list for `key` (after pruning expired entries)

try_reserve

Admit an attempt for key and count it in flight, in one synchronous step.

The attempt counts against the cap — for this request and every concurrent one — until the returned reservation is settled: recorded as an attempt, or released. So no burst of concurrent requests gets more than max_attempts past the limiter between them. Release in a finally: nothing settles a reservation for you, so one left unsettled holds its slot for the life of the process.

type (key: string, now?: number): RateLimitReserveResult

key

rate limit key (e.g. IP address)

type string

now

current timestamp in ms (defaults to Date.now())

type number
default Date.now()

the reservation, or {ok: false, retry_after} when recorded plus in-flight attempts already reach the cap (nothing is reserved then)

mutates

  • internal map — increments `key`'s in-flight count, creating its entry when absent

reserve

try_reserve, waiting up to max_wait_ms for a slot when the key is at its cap only because of attempts still in flight.

Each settle (and record and reset) on the key wakes its waiters to re-decide — that key's only, so a settle costs nothing for waiters parked elsewhere: a freed slot is reserved; recorded attempts reaching the cap refuse at once with their real retry_after; reaching max_wait_ms (or signal aborting) refuses with IN_FLIGHT_RETRY_AFTER_SECONDS. At most max_attempts reservations wait on one key — one more is refused at once — so a flood can't park requests without bound. A waiter that leaves, by any path, leaves the key's waiter count as it found it. A waiter holds no place in a queue: each wake, every waiter re-decides, so one can lose every race for a freed slot until max_wait_ms refuses it.

type (key: string, options?: RateLimitReserveOptions): Promise<RateLimitReserveResult>

key

rate limit key (e.g. IP address)

type string

options

the wait bound and an abort signal

default {}
returns Promise<RateLimitReserveResult>

the reservation, or {ok: false, retry_after} as described (nothing is reserved then)

mutates

  • internal map — increments `key`'s in-flight count on admission; counts the call in `key`'s waiters while it waits

reset

Clear all attempts for key.

Account-grain keys only. Called after a successful auth to forgive a user's own typos, which is safe because the key *is* the account being attacked — clearing it requires that account's credential and can only widen the budget against an account the caller already holds.

Never call this on an IP-aggregate key. That bucket is the distributed-spray backstop, and success on one account must not zero the budget shared by every other account reachable from the same IP; otherwise an attacker interleaves their own logins (or, under open signup, throwaway account creations needing no credential at all) with guesses against arbitrary victims and sprays indefinitely from one address. The only success that records on an IP bucket is an open signup (charged like a failure); no success clears one, so with no reset each is a monotone-within-window counter that only time clears.

Because they are monotone, the IP buckets are one instance per auth surface (login_ip_rate_limiter, signup_ip_rate_limiter, bootstrap_ip_rate_limiter) rather than one shared across all of them. A single instance meant a failure on any surface spent the budget that bounds guessing on every other one, and one caller's exhaustion denied four routes at once — a cost that used to be masked by the reset this doc forbids.

Attempts still in flight on key (reserved by concurrent requests) are kept: a reset forgives what was recorded, not what is still running.

type (key: string): void

key

type string
returns void

mutates

  • internal map — removes the entry for `key`, or clears its timestamps while attempts are in flight

cleanup

Remove entries whose timestamps are all outside the window and that have no attempt in flight.

type (now?: number): void

now

current timestamp in ms (defaults to Date.now())

type number
default Date.now()
returns void

mutates

  • internal map — prunes expired timestamps and deletes empty keys

dispose

Stop the cleanup timer. Safe to call multiple times.

type (): void

returns void

mutates

  • timer — clears the cleanup `setInterval` and nulls the handle

size

Number of tracked keys.

type number

getter

waiter_keys

Keys holding a parked-waiter slot right now (see reserve): each slot is removed with its key's last waiter. For tests pinning that a waiter leaves nothing behind; the twin of the Rust spine's test-only waiter_keys.

type number

getter

internal

RateLimiterMode
#

rate_limiter.ts view source

RateLimiterMode

type "enforced" | "disabled_for_testing"

import type {RateLimiterMode} from '@fuzdev/fuz_app/rate_limiter.js';

Whether a server builds the rate limiters it is not handed — AppServerOptions.rate_limiters.

  • 'enforced' — every limiter left out of the options is a default instance. The only mode a production server uses, and the default.
  • 'disabled_for_testing' — every limiter left out is null, so nothing is throttled. For in-process test apps and test binaries: they drive many round-trips from one address, and the IP buckets are monotone within their window (a success never refunds them, see RateLimiter.reset), so a suite's failures pile up until every later call is refused.

A limiter passed explicitly — an instance or null — wins over the mode either way, so a rate-limit test enables just the limiter it pins. The disabled mode is loud: create_app_server adds a surface warning, which log_startup_summary logs at assembly, and the first limiter it leaves null prints RATE_LIMITERS_DISABLED_BANNER to stderr once per module instance.

Twin of the Rust spine's fuz_auth::RateLimiterMode (Enforced / DisabledForTesting).

RateLimiterOptions
#

rate_limiter.ts view source

RateLimiterOptions import type {RateLimiterOptions} from '@fuzdev/fuz_app/rate_limiter.js';

Configuration for a rate limiter instance.

max_attempts

Maximum allowed attempts within the window.

type number

window_ms

Sliding window duration in milliseconds.

type number

cleanup_interval_ms

Interval for pruning stale entries (0 disables the timer).

type number

max_keys?

Maximum tracked keys. When exceeded, the least-recently-used key is evicted — bounds memory under key-enumeration attacks. Default: DEFAULT_RATE_LIMITER_MAX_KEYS (100_000). Pass null to disable the cap (falls back to an unbounded Map — only recommended when the key set is known to be closed, e.g. a per-account limiter keyed to a bounded-size account table).

LRU trade-off: every check / record call marks the key as most-recently-used, so keys under active attack stay fresh and won't be evicted. A slow-burn attacker spread across many low-volume keys can, however, drop out of the table and reset their budget — set max_keys high enough to fit the expected legitimate key set and this stays theoretical.

type number | null

RateLimitError
#

http/error_schemas.ts view source

value + type

{ [x: string]: unknown; error: "rate_limit_exceeded"; retry_after: number; } import {RateLimitError} from '@fuzdev/fuz_app/http/error_schemas.js';

Rate limit error — returned when a rate limiter rejects the request.

error

type "rate_limit_exceeded"

retry_after

type number

[key: string]

type unknown

RateLimitingTestOptions
#

testing/rate_limiting.ts view source

RateLimitingTestOptions import type {RateLimitingTestOptions} from '@fuzdev/fuz_app/testing/rate_limiting.js';

Configuration for describe_rate_limiting_tests.

session_options

Session config for cookie-based auth.

type SessionOptions<string>

create_route_specs

Route spec factory — same one used in production.

type (ctx: AppServerContext): RouteSpec[]

ctx

returns RouteSpec[]

app_options?

Optional overrides for AppServerOptions.

type Partial<Omit<AppServerOptions, "backend" | "bootstrap" | "session_options" | "create_route_specs" | "rpc_endpoints">>

db_factories?

Database factories to run tests against. Default: the default factory (create_default_db_factory) only.

type DbFactory[]

max_attempts?

Maximum attempts before rate limiting kicks in. Default: 2 (tight limit for fast tests).

type number

rpc_endpoints?

RPC endpoint specs mounted on the test app. Optional — both suites below exercise REST auth routes, so nothing here needs an RPC path.

Accepts either an array (eager) or a factory (ctx: AppServerContext) => Array<RpcEndpointSpec> — the factory form is required when action handlers must close over the per-test ctx.deps.

type RpcEndpointsSuiteOption

RateLimitKey
#

http/error_schemas.ts view source

value + type

"both" | "account" | "ip"

type "both" | "account" | "ip"

import {RateLimitKey} from '@fuzdev/fuz_app/http/error_schemas.js';

Rate limit key type — declares what a route or RPC action's rate limiter is keyed on.

  • 'ip' — per-IP rate limiting (login, bootstrap, password change, signup)
  • 'account' — per-account rate limiting. On REST auth routes the key is the submitted identifier (login). On RPC actions (post-auth) the key is the resolved actor id (request_context.actor.id) — separate namespace.
  • 'both' — both keys.

RateLimitReservation
#

rate_limiter.ts view source

RateLimitReservation import type {RateLimitReservation} from '@fuzdev/fuz_app/rate_limiter.js';

An attempt admitted by RateLimiter.try_reserve or RateLimiter.reserve, counted against the key's cap until settled.

Settle it exactly once: record keeps it as an attempt (a failure, on the failure-counting surfaces), release gives it back. Both are idempotent and the first call wins, so a handler calls record where the outcome is known and release in a finally. When the key's entry was evicted or reset meanwhile, record still records (into the key's current entry) and release is a no-op. A reservation made against no limiter (try_reserve_optional / reserve_optional with null) is inert.

record

Settle as a recorded attempt.

type (now?: number | undefined): void

now?

current timestamp in ms (defaults to Date.now())

type number
optional
returns void

release

Settle without recording an attempt.

type (): void

returns void

RateLimitReserveOptions
#

rate_limiter.ts view source

RateLimitReserveOptions import type {RateLimitReserveOptions} from '@fuzdev/fuz_app/rate_limiter.js';

Options for RateLimiter.reserve.

max_wait_ms?

How long to wait for a slot. Default RESERVATION_WAIT_MAX_MS.

type number

signal?

Stops the wait (a refusal, retry_after: IN_FLIGHT_RETRY_AFTER_SECONDS) — pass the request's signal.

type AbortSignal

RateLimitReserveResult
#

rate_limiter.ts view source

RateLimitReserveResult

type { ok: true; reservation: RateLimitReservation; } | { ok: false; retry_after: number; }

import type {RateLimitReserveResult} from '@fuzdev/fuz_app/rate_limiter.js';

Result of RateLimiter.try_reserve and RateLimiter.reserve.

RateLimitResult
#

rate_limiter.ts view source

RateLimitResult import type {RateLimitResult} from '@fuzdev/fuz_app/rate_limiter.js';

Result of a rate limit check or record operation.

allowed

Whether the request is allowed.

type boolean

remaining

Remaining attempts before blocking, counting attempts still in flight.

type number

retry_after

Seconds until the oldest active attempt expires (0 if allowed).

type number

raw_http_count_responses
#

testing/transports/raw_http.ts view source

(raw: string): number import {raw_http_count_responses} from '@fuzdev/fuz_app/testing/transports/raw_http.js';

Count HTTP response status lines in a raw byte stream. Deliberately unanchored (no ^/m): a second pipelined response is concatenated straight after the first response's body, which carries no trailing newline, so a line-anchored match would miss it — and missing a smuggled second response is a silent false negative. It counts exactly the status lines as long as no response header or body contains the literal HTTP/1.x NNN.

raw

type string

returns

number

raw_http_exchange
#

testing/transports/raw_http.ts view source

(base_url: string, request_bytes: string, options: RawHttpExchangeOptions): Promise<RawHttpExchange> import {raw_http_exchange} from '@fuzdev/fuz_app/testing/transports/raw_http.js';

Open a raw TCP socket to base_url, write request_bytes once, and collect what the server sends back until it closes the connection, a whole response has arrived (until: 'response'), or read_timeout_ms elapses.

Write errors are swallowed: a server that refuses a request and closes mid-upload surfaces as EPIPE / ECONNRESET on the unfinished write, and what a caller asserts on is what was read back. A write error also ends the socket, so bytes the server sent just before it can go unread — a caller that needs to read a refusal reliably sends nothing the server will leave unread.

base_url

the server's base URL; only its host and port are used

type string

request_bytes

the bytes to send, latin1-encoded

type string

options

returns

Promise<RawHttpExchange>

raw_http_first_status
#

testing/transports/raw_http.ts view source

(raw: string): number | null import {raw_http_first_status} from '@fuzdev/fuz_app/testing/transports/raw_http.js';

The status code of the first HTTP response in a raw byte stream, or null if none.

raw

type string

returns

number | null

raw_http_parse_response
#

testing/transports/raw_http.ts view source

(raw: string): RawHttpResponse | null import {raw_http_parse_response} from '@fuzdev/fuz_app/testing/transports/raw_http.js';

Parse the first response in a raw byte stream, once all of it has arrived.

The body is delimited by Transfer-Encoding: chunked or Content-Length. A response carrying neither is delimited by the connection closing, which the bytes alone don't show, so its body is whatever followed the head.

raw

the bytes read so far, latin1-decoded

type string

returns

RawHttpResponse | null

the response, or null when the head or the framed body is incomplete

raw_ws_close_code
#

testing/transports/ws_raw_client.ts view source

(frame: RawWsFrame): number | null import {raw_ws_close_code} from '@fuzdev/fuz_app/testing/transports/ws_raw_client.js';

The close code a close frame carries, or null when it carries none.

frame

returns

number | null

RAW_WS_OPCODE_CLOSE
#

RAW_WS_OPCODE_TEXT
#

RawHttpExchange
#

testing/transports/raw_http.ts view source

RawHttpExchange import type {RawHttpExchange} from '@fuzdev/fuz_app/testing/transports/raw_http.js';

What a raw exchange read back, and how it ended.

received

Every byte the server sent, latin1-decoded.

type string

readonly

closed_by_server

true when the connection ended before the exchange stopped reading — the server closed it, or it never opened (a refused or unresolvable address reads as closed, with nothing received).

type boolean

readonly

RawHttpExchangeOptions
#

testing/transports/raw_http.ts view source

RawHttpExchangeOptions import type {RawHttpExchangeOptions} from '@fuzdev/fuz_app/testing/transports/raw_http.js';

Options for raw_http_exchange.

read_timeout_ms

How long to read before giving up on the server.

type number

readonly

until?

When the exchange stops reading. 'close' reads until the server closes the connection; 'response' stops as soon as one whole response has arrived (raw_http_parse_response), for a server that answers and then keeps the connection open. Either way the read timeout bounds it.

'response' stops at the first response the bytes frame, so it is for a final response carrying Content-Length or chunked framing: a 1xx interim response ends the read, and so does the head of a response delimited by the connection closing, before its body.

type "close" | "response"

readonly
default 'close'

RawHttpResponse
#

testing/transports/raw_http.ts view source

RawHttpResponse import type {RawHttpResponse} from '@fuzdev/fuz_app/testing/transports/raw_http.js';

One parsed HTTP/1.1 response.

status

type number

readonly

headers

Response headers, names lowercased; a repeated name keeps its last value.

type Readonly<Record<string, string>>

readonly

body

The response body, with any chunked framing removed.

type string

readonly

RawWsClient
#

testing/transports/ws_raw_client.ts view source

RawWsClient import type {RawWsClient} from '@fuzdev/fuz_app/testing/transports/ws_raw_client.js';

A WebSocket client that sends frames and never answers a close.

frames

Every frame the server sent, in order.

type RawWsFrame[]

send_text

Send one masked text frame.

type (text: string): void

text

type string
returns void

wait_for

Resolves with the first frame, already received or not, matching predicate.

type (predicate: (frame: RawWsFrame) => boolean, timeout_ms?: number | undefined): Promise<RawWsFrame>

predicate

type (frame: RawWsFrame) => boolean

timeout_ms?

type number
optional
returns Promise<RawWsFrame>

destroy

Drop the TCP connection, with no close handshake.

type (): void

returns void

RawWsConnectOptions
#

testing/transports/ws_raw_client.ts view source

RawWsConnectOptions import type {RawWsConnectOptions} from '@fuzdev/fuz_app/testing/transports/ws_raw_client.js';

Options for connect_raw_ws.

port

type number

hostname?

type string

default '127.0.0.1'

path

The WebSocket endpoint path (e.g. /api/ws).

type string

headers?

Extra upgrade request headers — Cookie, Origin.

type Record<string, string>

pipelined?

Text frames to write in the same TCP write as the upgrade request, so they are in the server's hands before the handshake is answered — and so before the connection can have been admitted.

type readonly string[]

RawWsFrame
#

read_daemon_info
#

cli/daemon.ts view source

(runtime: Pick<EnvDeps, "env_get"> & Pick<FsReadDeps, "stat" | "read_text_file"> & LogDeps, name: string): Promise<{ version: number; pid: number; port: number; started: string; app_version: string; } | null> import {read_daemon_info} from '@fuzdev/fuz_app/cli/daemon.js';

Read and validate daemon info from the PID file.

runtime

runtime with file read and env capabilities

type Pick<EnvDeps, "env_get"> & Pick<FsReadDeps, "stat" | "read_text_file"> & LogDeps

name

application name

type string

returns

Promise<DaemonInfo | null>

parsed daemon info, or null if missing or invalid

read_env_var
#

dev/setup.ts view source

(deps: Pick<FsReadDeps, "read_text_file">, env_path: string, name: string): Promise<string | undefined> import {read_env_var} from '@fuzdev/fuz_app/dev/setup.js';

Read a single env var from a dotenv-style file.

Delegates to load_env_file so the value is tokenized exactly like every other dotenv read — surrounding quotes, inline comments, and escapes are handled, and a leading export is tolerated. Returns undefined when the file is absent or the variable is unset; a read error other than the file not existing (permission denied, I/O failure) propagates rather than being masked as "not found".

deps

file read capability

type Pick<FsReadDeps, "read_text_file">

env_path

path to the .env file

type string

name

the variable name to read

type string

returns

Promise<string | undefined>

the value, or undefined if the file or variable doesn't exist

throws

  • Error - if reading fails for any reason other than `ENOENT` / `NotFound`

read_fact_bytes_from_disk
#

db/fact_disk_storage.ts view source

(deps: Pick<FactDiskStorageDeps, "read_file">, facts_dir: string, url: string): Promise<Uint8Array<ArrayBufferLike>> import {read_fact_bytes_from_disk} from '@fuzdev/fuz_app/db/fact_disk_storage.js';

Read a disk-backed fact's bytes from the <facts_dir>/<shard>/<rest> layout the writers above produce, addressed by the row's file:<shard>/<rest> external_url. The only read of external bytes PgFactStore.get performs. Does NOT verify hash content — PgFactStore.get calls fact_hash_verify(hash, bytes) on the result and returns null on mismatch.

Defense at the read seam is the FILE_FACT_URL_PATTERN regex (via parse_file_fact_url) — .. segments, foreign schemes, and non-hex chars fail before any disk access.

deps

type Pick<FactDiskStorageDeps, "read_file">

facts_dir

type string

url

type string

returns

Promise<Uint8Array<ArrayBufferLike>>

throws

  • Error - when `url` is not a `file:<shard>/<rest>` URL, or when the read fails

read_secure_bounded
#

runtime/secure_file.ts view source

(path: string, read_chunk: (target: Uint8Array<ArrayBufferLike>) => Promise<number | null>): Promise<Uint8Array<ArrayBufferLike>> import {read_secure_bounded} from '@fuzdev/fuz_app/runtime/secure_file.js';

Drain read_chunk into a cap-bounded buffer.

Reads up to cap + 1 bytes so a file growing between stat and read is still caught (the stat-time size check alone races). read_chunk fills the given target from the file's current position and returns the bytes read (null/0 = EOF) — the seam that lets the Node and Deno handle APIs share one loop.

path

type string

read_chunk

type (target: Uint8Array<ArrayBufferLike>) => Promise<number | null>

returns

Promise<Uint8Array<ArrayBufferLike>>

throws

  • Error - when more than `MAX_SECURE_FILE_SIZE` bytes arrive

ReadTextFromOffsetResult
#

runtime/deps.ts view source

ReadTextFromOffsetResult import type {ReadTextFromOffsetResult} from '@fuzdev/fuz_app/runtime/deps.js';

Result of reading text from a byte offset.

content

Decoded text content read from the offset.

type string

bytes_read

Number of bytes actually read.

type number

file_size

Total file size at the time of the read (for truncation detection).

type number

READY_ERROR
#

db/schema_ready.ts view source

{ readonly schema_drift: "schema_drift"; readonly db_unreachable: "db_unreachable"; } import {READY_ERROR} from '@fuzdev/fuz_app/db/schema_ready.js';

Error codes a readiness check returns at 503 (conforms to {error: string}).

ReadyCrossTestOptions
#

ReadyRouteOptions
#

http/common_routes.ts view source

ReadyRouteOptions import type {ReadyRouteOptions} from '@fuzdev/fuz_app/http/common_routes.js';

Options for the readiness probe route.

expected

The committed expected column map — typically load_expected_schema(url). DI'd because the spine can't resolve a path relative to the consumer's fixture.

type ExpectedSchema

log?

Logger for server-side drift diagnostics (the public body stays minimal).

type Logger

RealtimeCloser
#

actions/connection_closer.ts view source

RealtimeCloser import type {RealtimeCloser} from '@fuzdev/fuz_app/actions/connection_closer.js';

One ConnectionCloser over every live-connection transport of a backend.

Each close fans out to every member and returns the sum of what each closed. A revocation handler holds this rather than one transport, so its close reaches the WebSocket transports and the SSE registries alike, and AppServer.close runs its close_all_sockets to end every live connection of the backend at shutdown.

A member that throws does not stop the fan-out: every member is still called, and the first error is thrown once they all have been — so the caller (the post-commit flush, an audit listener) logs it.

inheritance

add

Add a transport's closer, returning a remover for this addition.

Membership is by reference: a closer added twice — two endpoints sharing one transport — is one member, and every close reaches it once. Each add is counted, and its remover releases only that count: the closer stays a member until every addition is removed, so one owner releasing a shared transport leaves it reachable for the others. Calling a remover again is a no-op.

Members must be the same instances connections are registered on — a closer over a fresh registry closes nothing.

type (closer: ConnectionCloser): () => void

closer

the transport's closer

returns () => void

a remover for this addition, idempotent

member_count

Count of distinct members — introspection for tests and diagnostics.

type (): number

returns number

REASON_PEER_CONNECTION_GONE
#

actions/peer_ping.ts view source

"peer_connection_gone" import {REASON_PEER_CONNECTION_GONE} from '@fuzdev/fuz_app/actions/peer_ping.js';

The socket closed before the peer replied.

REASON_PEER_NO_TRANSPORT
#

actions/peer_ping.ts view source

"peer_no_transport" import {REASON_PEER_NO_TRANSPORT} from '@fuzdev/fuz_app/actions/peer_ping.js';

No return socket — the action ran over a transport that can't initiate a server→client request (HTTP RPC).

REASON_PEER_PING_INVALID_REPLY
#

REASON_PEER_PING_NONCE_MISMATCH
#

actions/peer_ping.ts view source

"peer_ping_nonce_mismatch" import {REASON_PEER_PING_NONCE_MISMATCH} from '@fuzdev/fuz_app/actions/peer_ping.js';

The peer replied with a PingResponse whose nonce didn't echo the issued one.

REASON_PEER_TIMEOUT
#

actions/peer_ping.ts view source

"peer_timeout" import {REASON_PEER_TIMEOUT} from '@fuzdev/fuz_app/actions/peer_ping.js';

The peer did not reply within the deadline.

REASON_PEER_TOO_MANY_IN_FLIGHT
#

actions/peer_ping.ts view source

"peer_too_many_in_flight" import {REASON_PEER_TOO_MANY_IN_FLIGHT} from '@fuzdev/fuz_app/actions/peer_ping.js';

The per-connection in-flight server→client request cap was hit.

reconstruct_bootstrapped_handle
#

testing/cross_backend/setup.ts view source

(serialized: SerializableBootstrappedBackendHandle): ReconstructedBootstrappedBackendHandle import {reconstruct_bootstrapped_handle} from '@fuzdev/fuz_app/testing/cross_backend/setup.js';

Rebuild a usable handle from the serialized subset. Synthesizes a fresh FetchTransport primed with the keeper's Set-Cookie values so _testing_reset and other keeper-authenticated calls work. The returned shape omits child and teardown — lifecycle stays with globalSetup; tests that try to teardown themselves wouldn't have a serializable reference anyway.

serialized

returns

ReconstructedBootstrappedBackendHandle

ReconstructedBootstrappedBackendHandle
#

testing/cross_backend/setup.ts view source

ReconstructedBootstrappedBackendHandle import type {ReconstructedBootstrappedBackendHandle} from '@fuzdev/fuz_app/testing/cross_backend/setup.js';

BootstrappedBackendHandle minus the live child / teardown references that only make sense in the globalSetup process. The cross-process provide/inject path strips them on serialization, and the per-test helpers (mint_account, fire_testing_reset, default_cross_process_setup) never read either field — so the test-worker view of the handle has this shape. Also the return type of reconstruct_bootstrapped_handle.

daemon_token

Deterministic daemon token captured from config.bootstrap.daemon_token_path after the binary booted. default_cross_process_setup builds keeper-daemon-token headers from this for _testing_reset calls.

type string

readonly

keeper_transport

Transport carrying the keeper session cookie + cookie jar.

type (url: string, init: RequestInit): Promise<Response>

readonly

url

type string

init

type RequestInit
returns Promise<Response>

keeper_daemon_transport

Daemon-token transport for the keeper — same cookie jar as keeper_transport but with origin: null so it sends no Origin header. The daemon-token middleware discards the credential in a browser context (any Origin / Referer), so the _testing_* daemon-token calls (_testing_reset / _testing_schema_snapshot / _testing_action_manifest / _testing_mint_session) must route through this Origin-free transport. CSRF/cookie-session calls (invite_create, role_grant_offer_create) stay on keeper_transport, which keeps its default Origin.

type (url: string, init: RequestInit): Promise<Response>

readonly

url

type string

init

type RequestInit
returns Promise<Response>

keeper_account

Keeper account JSON captured from POST /bootstrap.

type { readonly id: string & $brand<"Uuid">; readonly username: string; }

readonly

keeper_actor

Keeper actor JSON captured from POST /bootstrap.

type { readonly id: string & $brand<"Uuid">; }

readonly

keeper_cookies

Raw keeper Set-Cookie values — thread into ws_transport for keeper-authenticated WS upgrades.

type readonly string[]

readonly

config

The config used to spawn this backend. Carried for diagnostic + downstream access.

type BackendConfig

readonly

RecordedClose
#

testing/connection_closer_helpers.ts view source

RecordedClose

type { method: "account" | "token" | "session"; id: string; } | { method: "role"; id: string; role: string; } | { method: "all"; id: null; }

import type {RecordedClose} from '@fuzdev/fuz_app/testing/connection_closer_helpers.js';

Record of a single ConnectionCloser method invocation — a credential close carries the id it was given, a role close ('role') the account id and the role, close_all_sockets ('all') none.

RecordingAuditEmitter
#

testing/audit_drift_guard.ts view source

RecordingAuditEmitter import type {RecordingAuditEmitter} from '@fuzdev/fuz_app/testing/audit_drift_guard.js';

Pair returned by create_recording_audit_emitter — the AuditEmitter to inject as deps.audit, plus the shared calls array that records every captured emission. Both fields are live — callers read calls after exercising the handler to assert on the audit metadata shape.

emitter

type AuditEmitter

calls

type AuditLogInput<"login" | "logout" | "bootstrap" | "signup" | "password_change" | "session_revoke" | "session_revoke_all" | "token_create" | "token_revoke" | "token_revoke_all" | "role_grant_create" | "role_grant_revoke" | "role_grant_offer_create" | "role_grant_offer_accept" | "role_grant_offer_decline" | "role_grant_offer_retract" | "role_grant_offer_expire" | "role_grant_offer_supersede" | "invite_create" | "invite_delete" | "account_delete" | "account_purge" | "account_undelete" | "actor_delete" | "actor_purge" | "actor_undelete" | "app_settings_update" | "db_admin_row_delete">[]

RecordingCloser
#

refresh_role_grants
#

auth/request_context.ts view source

(ctx: RequestContext, deps: QueryDeps): Promise<RequestContext> import {refresh_role_grants} from '@fuzdev/fuz_app/auth/request_context.js';

Reload active role_grants from the database, returning a new request context.

Useful for long-lived WebSocket connections where role_grants may change (grant or revoke) during the connection lifetime. Call periodically or after receiving a revocation signal.

Returns a new RequestContext with updated role_grants — the original context is not mutated, making concurrent calls safe. Throws when ctx.actor is null; account-grain contexts have no role_grants to refresh.

ctx

the request context to refresh

deps

query dependencies

returns

Promise<RequestContext>

a new RequestContext with fresh role_grants

throws

  • Error - when called on an account-grain context (`actor: null`)

register_action_ws
#

actions/register_action_ws.ts view source

(options: RegisterActionWsOptions): RegisterActionWsResult import {register_action_ws} from '@fuzdev/fuz_app/actions/register_action_ws.js';

Mount a JSON-RPC WebSocket endpoint that dispatches via the shared perform_action core.

Wire behavior:

  • Batch JSON-RPC is rejected (single-message only).
  • Notifications (method + no id) are silently dropped per JSON-RPC spec. Exception: cancel notifications abort the matching pending request's ctx.signal before bubbling out.
  • A request whose id names a request still running on the socket is answered invalid_request with data.reason ERROR_DUPLICATE_REQUEST_ID and not dispatched.
  • At most MAX_INBOUND_DISPATCHES_PER_CONNECTION requests run at once per socket; one more is answered queue_overflow and not dispatched, and the socket stays open.
  • Per-message dispatch goes through perform_action: pre-authorization auth (401) → authorization phase → post-authorization auth (403) → rate limit (429) → input validation (400) → handler (with transaction wrap iff spec.side_effects: true) → DEV output validation.
  • Authorization phase runs per message — role_grant changes during a connection lifetime are picked up on the next message without any in-place refresh. Authentication invalidation closes the socket: the revocation handlers through connection_closer, and create_ws_auth_guard on the audit row.
  • Admission re-reads the credential once, after the handshake: a socket whose session or token was revoked while it was upgrading closes with WS_CLOSE_SESSION_REVOKED, one that lost a role required_roles lists with WS_CLOSE_PERMISSION_REVOKED, and one whose re-check failed with WS_CLOSE_INTERNAL_ERROR. Frames sent before admission wait for it.
  • A revoked global grant of a role required_roles lists closes the account's sockets on this endpoint with WS_CLOSE_PERMISSION_REVOKED, through create_ws_auth_guard; nothing more is sent on them.
  • A socket the server has closed dispatches nothing more, whether or not its client answers the close.
  • Every frame sent — responses, error frames, notifications — goes through the transport's outbound queue. A connection with more waiting than its outbound_queue_bytes budget closes with WS_CLOSE_OUTBOUND_OVERFLOW, and the response of the request whose handler overflowed it is not sent.

options

returns

RegisterActionWsResult

the transport (supplied or freshly created) — retain it to wire create_ws_auth_guard or broadcast on audit events.

throws

  • Error - when `max_connections_per_account` or `outbound_queue_bytes` is passed alongside `transport`, when the cap is neither `null` nor a positive integer, when the budget is not a positive integer, or when `actions` fails to compile (`compile_action_registry`)

mutates

  • options.app — registers a `GET path` route via `upgradeWebSocket`
  • options.transport — per socket, registers, admits, and removes a connection via `register_pending` / `admit` / `remove_connection`
  • options.connection_closer — adds the endpoint's transport, once the mount has succeeded

register_pg_type_parsers
#

db/db_pg.ts view source

(): Promise<void> import {register_pg_type_parsers} from '@fuzdev/fuz_app/db/db_pg.js';

Register the shared pg type-parser overrides on the module-global pg.types.

Dynamically imports the pg runtime (so pglite-only consumers, who never call this, don't need pg installed) and coerces int8 (BIGINT, OID 20) to a JS number. pg defaults to returning int8 as a string to avoid 2^53 precision loss; our int8 columns today (audit_log.seq, cell_history.id, fact.size) stay well under that bound, and reading as a number keeps the wire shape uniform with PGlite — which returns int8 as a number — so AuditLogEvent.seq and friends validate identically across both drivers.

Both create_db (production) and the test pg factory (testing/db.ts) register through this single site so test and prod read the same shape; a divergence here is exactly the test/prod write-semantics gap the parser exists to close.

CAVEAT: setTypeParser mutates pg.types globally — every pg.Pool in the process inherits the coercion, including pools the consumer constructs against unrelated databases. Any future int8 column that could legitimately exceed 2^53 (byte offsets, counters) will silently round; if one lands, localize via a per-pool types override instead of widening this global.

returns

Promise<void>

mutates

  • pg.types — registers the int8 parser on the global pg type registry

register_ws_endpoint
#

actions/register_ws_endpoint.ts view source

(options: RegisterWsEndpointOptions): RegisterActionWsResult import {register_ws_endpoint} from '@fuzdev/fuz_app/actions/register_ws_endpoint.js';

Mount a WebSocket endpoint with the standard upgrade stack (origin check + auth + token scope + actor resolution + optional role) and JSON-RPC dispatch.

Returns the BackendWebsocketTransport (supplied or freshly created), same as register_action_ws — retain it to wire create_ws_auth_guard on on_audit_event or to broadcast.

options

returns

RegisterActionWsResult

mutates

  • options.app — applies origin/auth/scope/authorization/role middleware via `app.use`, then registers the `GET path` route via the inner `register_action_ws`
  • options.connection_closer — adds the endpoint's transport, once the mount has succeeded, for good — pass `null` and add it yourself to hold the remover

RegisterActionWsOptions
#

actions/register_action_ws.ts view source

RegisterActionWsOptions import type {RegisterActionWsOptions} from '@fuzdev/fuz_app/actions/register_action_ws.js';

Options for register_action_ws.

path

Mount path (e.g., /api/ws).

type string

app

The Hono app to mount on.

type Hono<BlankEnv, BlankSchema, "/">

upgradeWebSocket

Hono's upgradeWebSocket helper from the runtime adapter.

type UpgradeWebSocket<unknown, any, WSEvents<unknown>>

actions

The actions registered on this endpoint — each carries a spec (drives method lookup, per-action auth, input/output validation) and an optional handler (omit for client-only specs like inbound notifications). Spread protocol_actions from actions/protocol.ts here to complete the disconnect-detection + per-request cancel pairing with the frontend client.

type readonly Action<ActionSpecUnion>[]

db

Pool-level DB. The dispatcher wraps in db.transaction for side_effects: true actions, the same way HTTP RPC does. Per-message authorization phase reads through this pool.

Audit writes and other rollback-resilient fire-and-forget calls run through AppDeps.audit.emit from the action factory's closure — the dispatcher never holds an audit-side pool reference; the bound emitter owns the pool.

type Db

required_roles?

The roles the endpoint's upgrade gate requires (any-of) — what register_ws_endpoint mounts as require_role(required_roles) ahead of this handler. Recorded on every registration (ConnectionIdentity.required_roles), so a revoked global grant of a listed role closes the socket (close_sockets_for_role), and re-read at admission against the acting actor's grants, so a revocation that landed between the gate and the registration refuses the upgrade with WS_CLOSE_PERMISSION_REVOKED. This handler does not gate on it before the upgrade; mount the 403 gate in front (register_ws_endpoint does). Omit, or pass [], for an ungated endpoint.

type readonly string[]

transport?

Existing transport to register connections with. When omitted, a fresh one is created and returned in the result. Pass your own to keep a handle for create_ws_auth_guard and send_to/broadcast.

type BackendWebsocketTransport

connection_closer

The backend's closer — deps.connection_closer. This endpoint's transport is added to it, so every revocation handler's close reaches the sockets opened here. Required so it can't be forgotten: left out of the closer, a transport's sockets close only through the audit listener, which a failed audit write or a session / token cap eviction never reaches. Pass null only when no revocation has to reach this transport (a harness driving the dispatcher directly), or when the transport was added by hand.

The transport is added once the mount succeeds, so a mount that throws adds nothing. The addition is permanent — its remover is not returned. A caller that must release the membership (as create_app_server does on close) passes null and adds the transport itself, holding the remover RealtimeCloser.add returns.

type RealtimeCloser | null

max_connections_per_account?

Per-account connection cap for the transport this call creates — see BackendWebsocketTransportOptions.max_connections_per_account (evict-oldest, closing with WS_CLOSE_CONNECTION_LIMIT; null disables). Rejected alongside transport: a supplied transport carries its own cap, set where it was constructed.

type number | null

default DEFAULT_MAX_CONNECTIONS_PER_ACCOUNT

outbound_queue_bytes?

Byte budget of the frames waiting to be written to one connection, for the transport this call creates — see BackendWebsocketTransportOptions.outbound_queue_bytes (a frame that does not fit closes the socket with WS_CLOSE_OUTBOUND_OVERFLOW). Not derived from max_message_bytes. Rejected alongside transport: a supplied transport carries its own budget, set where it was constructed.

type number

default DEFAULT_WS_OUTBOUND_QUEUE_BYTES

heartbeat?

Server-side heartbeat policy. Default-on (receive-silence detection, 60s timeout). false disables the timer entirely — only do this if the upstream stack (TCP keepalive, Cloudflare idle timeout, etc.) already owns disconnect detection. Pass an object to tune the timeout.

type boolean | ServerHeartbeatOptions

artificial_delay?

Optional per-message delay for testing loading states. Ignored when 0.

type number

max_message_bytes?

Cap on one inbound message, in bytes (UTF-8 for text frames). A larger message closes the socket with WS_CLOSE_MESSAGE_TOO_BIG before it's parsed — there's no per-message error reply, matching the Rust spine. The cap is checked after the runtime adapter has buffered the message, so it bounds dispatch work, not memory; bound the adapter's own frame limit too (e.g. ws's maxPayload) where memory matters. A consumer that raises it passes the same value to the client's FrontendWebsocketTransportOptions.max_message_bytes.

type number

default DEFAULT_WS_MAX_MESSAGE_BYTES

log?

Optional logger; defaults to [ws] namespace.

type Logger

on_socket_open?

Called once per socket, after the connection is admitted (module doc, "Admission"). Awaited before any inbound frame is dispatched — every frame the client sends meanwhile waits in arrival order, replies to server-initiated requests included: a hook that awaits transport.request_connection on its own connection gets no reply until it returns, so start such a request without awaiting it. Throwing logs an error and closes the socket with an internal_error frame and WS_CLOSE_INTERNAL_ERROR — a failing bootstrap should not leave a partially-initialized socket alive.

type (ctx: SocketOpenContext): void | Promise<void>

ctx

returns void | Promise<void>

on_socket_close?

Called once per admitted socket on close, *after* the transport has removed the connection — a slow hook never holds a dead entry that broadcasts still target and the per-account cap still counts. Receives connection_id and identity captured at open time, the same whether the close came from the client, a revocation, or a connection-cap eviction. Errors are logged and swallowed.

type (ctx: SocketCloseContext): void | Promise<void>

ctx

returns void | Promise<void>

action_ip_rate_limiter

Per-IP rate limiter consulted for actions whose spec declares rate_limit: 'ip' or 'both', keyed on the client IP read at upgrade time. Required, with no default: pass null to turn the IP check off, so an unthrottled socket is a choice the call site states rather than an option it forgot. Share one instance with the HTTP RPC dispatcher so one budget covers both transports per action — create_app_server does, for the endpoints it mounts from ws_endpoints.

type RateLimiter | null

action_account_rate_limiter

Per-account rate limiter consulted for actions whose spec declares rate_limit: 'account' or 'both'. Keyed on request_context.account.id. Required, with no default: pass null to turn the account check off. Share one instance with the HTTP RPC dispatcher.

type RateLimiter | null

RegisterActionWsResult
#

RegisterWsEndpointOptions
#

actions/register_ws_endpoint.ts view source

RegisterWsEndpointOptions import type {RegisterWsEndpointOptions} from '@fuzdev/fuz_app/actions/register_ws_endpoint.js';

inheritance

allowed_origins

Origin allowlist regexes — typically parsed from the FUZ_ALLOWED_ORIGINS env var via parse_allowed_origins. Passed straight to verify_request_source.

type readonly RegExp[]

required_roles?

Roles permitted to upgrade — any-of disjunction (matches the underlying require_role semantics, global grants only). Omit (or pass []) for any authenticated account (require_auth + actor resolution alone); set to e.g. [ROLE_ADMIN] to gate the endpoint behind a single role or [ROLE_ADMIN, ROLE_KEEPER] to permit either. The per-action auth in each spec still applies at dispatch time — this is a coarse upgrade-time gate on who may hold a socket, and so on who receives the endpoint's server pushes.

It holds for the socket's life: register_action_ws re-reads it at admission, and a revoked global grant of a listed role closes the account's sockets here with WS_CLOSE_PERMISSION_REVOKED (any listed role — see close_sockets_for_role on the over-close).

type readonly string[]

RemoteNotificationActionSpec
#

actions/action_spec.ts view source

value + type

{ method: string; initiator: "frontend" | "backend" | "both"; input: ZodType<unknown, unknown, $ZodTypeInternals<unknown, unknown>>; description: string; kind: "remote_notification"; ... 6 more ...; rate_limit?: "both" | ... 2 more ... | undefined; } import {RemoteNotificationActionSpec} from '@fuzdev/fuz_app/actions/action_spec.js';

method

type string

initiator

type "frontend" | "backend" | "both"

input

type ZodType<unknown, unknown, $ZodTypeInternals<unknown, unknown>>

description

type string

kind

type "remote_notification"

auth

type null

side_effects

type true

output

type ZodVoid

async

type true

streams?

Names the notification method this action emits as request-scoped progress. Forward-compatible handshake — transport-agnostic, does not imply a specific delivery mechanism. Registry-time validation (e.g., ensuring the named method is a remote_notification spec) is a consumer-side concern.

type string

error_reasons?

Error reason codes this action may surface via error.data.reason on failure. Declarative metadata mirroring the streams precedent — codegen, UI form-state matching, and docs read it off the spec instead of scanning handler implementations. Reuses the same as const string constants the handler throws (e.g. ERROR_ROLE_GRANT_OFFER_*) so call sites can import either side. Optional — actions that surface only standard transport errors leave it unset.

type readonly string[]

rate_limit?

Rate limit key the RPC dispatcher consults for this action. Optional — actions without it skip the rate-limit hook entirely.

  • 'ip' — keyed on the resolved client IP (get_client_ip(c)).
  • 'account' — keyed on the post-auth account id (request_context.account.id). Registration-time error if paired with auth.account !== 'required' (no account to key on).
  • 'both' — both checks run; either can block.

Throttle-requests semantics — every invocation records, regardless of outcome (different from REST login's throttle-failures, which resets on success). Suits admin mutation oracles (invite_create account- existence probe) where the *successful* invocation is the threat.

Today only RequestResponseActionSpec is consulted by the RPC dispatcher. The field is on the base for shape symmetry with streams and error_reasons; remote_notification / local_call dispatchers don't read it.

type "both" | "account" | "ip"

REQUEST_CONTEXT_KEY
#

auth/request_context.ts view source

"request_context" import {REQUEST_CONTEXT_KEY} from '@fuzdev/fuz_app/auth/request_context.js';

Hono context variable name for the request context.

RequestActorContext
#

auth/request_context.ts view source

RequestActorContext import type {RequestActorContext} from '@fuzdev/fuz_app/auth/request_context.js';

Request context narrowed to a resolved acting actor.

Used by handlers bound through rpc_action against an actor-implying spec (auth.actor === 'required') — the binder's conditional return type tightens ctx.auth to this shape because the dispatcher's authorization phase always resolves an actor before the handler runs. The biconditional actor !== 'none' ⟺ input declares acting?: ActingActor is enforced at registry time.

inheritance

actor

type Actor

RequestClient
#

actions/peer_request.ts view source

RequestClient import type {RequestClient} from '@fuzdev/fuz_app/actions/peer_request.js';

Initiate a JSON-RPC request to the connected client and await its reply — the server→client direction of ActionPeer. Threaded onto ActionContext.request_client for WebSocket handlers (absent on HTTP RPC, where there is no return socket). Returns a PeerRequestOutcome; never throws.

(call)

type (method: string, params: { [x: string]: unknown; } | undefined, options?: PeerRequestOptions | undefined): Promise<PeerRequestOutcome>

method

type string

params

type JsonrpcRequestParams | undefined

options?

optional
returns Promise<PeerRequestOutcome>

RequestContext
#

auth/request_context.ts view source

RequestContext import type {RequestContext} from '@fuzdev/fuz_app/auth/request_context.js';

The resolved identity context for an authenticated request.

actor is null on account-grain routes (no acting field on input, no role / keeper auth) — those handlers don't trigger actor resolution. role_grants is empty in that case. Role grant checks (has_role, has_scoped_role, has_any_scoped_role) are null-tolerant on RequestContext | null; they additionally treat actor: null as "no role_grants" so callers don't have to narrow.

Multi-actor invariant: when populated, actor.account_id === account.id. build_request_context enforces this; the dispatcher's authorization phase rejects with actor_not_on_account before reaching the handler.

account

type Account

actor

type Actor | null

role_grants

type RoleGrant[]

RequestResponseActionSpec
#

actions/action_spec.ts view source

value + type

{ method: string; initiator: "frontend" | "backend" | "both"; side_effects: boolean; input: ZodType<unknown, unknown, $ZodTypeInternals<unknown, unknown>>; output: ZodType<unknown, unknown, $ZodTypeInternals<unknown, unknown>>; ... 6 more ...; rate_limit?: "both" | ... 2 more ... | undefined; } import {RequestResponseActionSpec} from '@fuzdev/fuz_app/actions/action_spec.js';

method

type string

initiator

type "frontend" | "backend" | "both"

side_effects

type boolean

input

type ZodType<unknown, unknown, $ZodTypeInternals<unknown, unknown>>

output

type ZodType<unknown, unknown, $ZodTypeInternals<unknown, unknown>>

description

type string

kind

type "request_response"

auth

type RouteAuth

async

type true

streams?

Names the notification method this action emits as request-scoped progress. Forward-compatible handshake — transport-agnostic, does not imply a specific delivery mechanism. Registry-time validation (e.g., ensuring the named method is a remote_notification spec) is a consumer-side concern.

type string

error_reasons?

Error reason codes this action may surface via error.data.reason on failure. Declarative metadata mirroring the streams precedent — codegen, UI form-state matching, and docs read it off the spec instead of scanning handler implementations. Reuses the same as const string constants the handler throws (e.g. ERROR_ROLE_GRANT_OFFER_*) so call sites can import either side. Optional — actions that surface only standard transport errors leave it unset.

type readonly string[]

rate_limit?

Rate limit key the RPC dispatcher consults for this action. Optional — actions without it skip the rate-limit hook entirely.

  • 'ip' — keyed on the resolved client IP (get_client_ip(c)).
  • 'account' — keyed on the post-auth account id (request_context.account.id). Registration-time error if paired with auth.account !== 'required' (no account to key on).
  • 'both' — both checks run; either can block.

Throttle-requests semantics — every invocation records, regardless of outcome (different from REST login's throttle-failures, which resets on success). Suits admin mutation oracles (invite_create account- existence probe) where the *successful* invocation is the threat.

Today only RequestResponseActionSpec is consulted by the RPC dispatcher. The field is on the base for shape symmetry with streams and error_reasons; remote_notification / local_call dispatchers don't read it.

type "both" | "account" | "ip"

require_audit_sse
#

server/app_server.ts view source

(source: { audit_sse: AuditLogSse | null; }): AuditLogSse import {require_audit_sse} from '@fuzdev/fuz_app/server/app_server.js';

Assert that audit_sse was wired by create_app_server and return it as a non-null AuditLogSse. Throws a labelled error when the audit_log_sse option was not passed to create_app_server.

Use in route factories that depend on factory-managed audit SSE:

create_route_specs: (ctx) => create_audit_log_route_specs({ stream: require_audit_sse(ctx), }),

Preferred over ctx.audit_sse! — ! lies to the type system and produces a downstream cannot-read-property crash if a consumer wires the route without enabling the option.

source

type { audit_sse: AuditLogSse | null; }

returns

AuditLogSse

require_auth
#

auth/request_context.ts view source

(c: Context<any, string, {}>, next: Next): Promise<void | Response> import {require_auth} from '@fuzdev/fuz_app/auth/request_context.js';

Middleware that requires authentication.

Returns 401 if the auth middleware did not set c.var.auth_account_id.

c

type Context<any, string, {}>

next

type Next

returns

Promise<void | Response>

require_credential_types
#

auth/request_context.ts view source

(credential_types: readonly string[]): MiddlewareHandler import {require_credential_types} from '@fuzdev/fuz_app/auth/request_context.js';

Create middleware that requires the request's credential_type to be one of the given values.

Returns 401 if unauthenticated, 403 with ERROR_CREDENTIAL_TYPE_REQUIRED + required_credential_types echoing the spec's allowlist when the wire-side credential isn't in it.

Reads only ACCOUNT_ID_KEY + CREDENTIAL_TYPE_KEY, both set by the auth middleware — never the resolved RequestContext. That is why fuz_auth_guard_resolver mounts it in the pre_authorization phase: a wrong channel is refused before the route resolves an actor for it, so the denial costs no DB work and discloses no account state. Body shape is symmetric with the role gate (ERROR_INSUFFICIENT_PERMISSIONS + required_roles) and matches what the RPC dispatcher's post-auth gate emits for the same condition. Today's only credential gate is keeper (['daemon_token']); future gates (agent_token, group_actor_token) reuse this literal and label themselves through the array.

credential_types

allowed credential types (any-of)

type readonly string[]

returns

MiddlewareHandler

require_request_context
#

auth/request_context.ts view source

(c: Context<any, any, {}>): RequestContext import {require_request_context} from '@fuzdev/fuz_app/auth/request_context.js';

Get the request context, throwing if unauthenticated.

Use in route handlers where the dispatcher's authorization phase guarantees a context exists (i.e., routes with auth: {type: 'authenticated'} or stricter). Prefer this over get_request_context(c)! for explicit error handling.

c

the Hono context

type Context<any, any, {}>

returns

RequestContext

the request context (never null)

throws

  • Error - if no request context is set (dispatcher misconfiguration)

require_role
#

auth/request_context.ts view source

(roles: readonly string[]): MiddlewareHandler import {require_role} from '@fuzdev/fuz_app/auth/request_context.js';

Create middleware that requires the actor to hold any of the given roles globally (scope_id IS NULL).

Returns 401 if unauthenticated, 403 if none of the roles are present. Reads REQUEST_CONTEXT_KEY because role-gated routes always run the dispatcher's authorization phase before this guard (the phase sets the actor-bound RequestContext).

Uses has_any_scoped_role(ctx, roles, null) so the gate matches global / unscoped role_grants only. A scoped role_grant ({role: 'admin', scope_id: <some uuid>}) does not unlock route-spec gates that are inherently global. The same scope-aware check is mirrored in actions/action_rpc.ts (HTTP RPC dispatcher) and actions/register_action_ws.ts (WS dispatcher) so all three transports agree.

Multi-role disjunction (any-of) lets auth.roles: ['admin', 'steward'] specs translate to one middleware that admits either role. Single-role routes pass [role_name]; the array shape is uniform.

roles

the roles to admit (any-of)

type readonly string[]

returns

MiddlewareHandler

require_rpc_endpoint_path
#

testing/rpc_helpers.ts view source

(rpc_endpoints: readonly RpcEndpointSpec[]): string import {require_rpc_endpoint_path} from '@fuzdev/fuz_app/testing/rpc_helpers.js';

Resolve a single RPC endpoint path — the common case where a consumer mounts exactly one create_rpc_endpoint.

Used at suite setup time to hard-fail integration suites (admin / audit / SSE / rate-limiting) when the consumer omitted rpc_endpoints rather than letting tests fail mid-run with confusing errors.

Callers that need multi-endpoint support should iterate rpc_endpoints directly.

rpc_endpoints

type readonly RpcEndpointSpec[]

returns

string

throws

  • Error - if `rpc_endpoints` is empty (hard-fail; see the suite options docs) or has more than one entry (ambiguous).

require_token_scope
#

auth/request_context.ts view source

(capability: string): MiddlewareHandler import {require_token_scope} from '@fuzdev/fuz_app/auth/request_context.js';

Create middleware refusing a route to a credential whose scope does not admit capability.

The route-spec form, mounted by fuz_auth_guard_resolver from auth.required_scope. Lands in the pre_authorization phase, so a narrowed token is told about its scope rather than about the role it also lacks — and learns nothing about the route's input shape on the way.

Parses at construction, so a malformed capability is a registration-time throw rather than a guard that mounts and reports nonsense.

capability

the capability string this route demands, e.g. surface:audit_stream

type string

returns

MiddlewareHandler

middleware that 403s a credential whose scope lacks it

throws

  • Error - if `capability` is not a well-formed `rpc:<method>` / `surface:<name>` string

RESERVATION_WAIT_MAX_MS
#

rate_limiter.ts view source

5000 import {RESERVATION_WAIT_MAX_MS} from '@fuzdev/fuz_app/rate_limiter.js';

How long a password-surface reservation (RateLimiter.reserve) waits for a slot when the key is at its cap only because of attempts still in flight, before answering 429. Mirrors the Rust spine's RESERVATION_WAIT_MAX.

Counting attempts while they are in flight caps *successful* concurrent attempts from one key too — a class behind one NAT logging in together. Those attempts settle within a request's lifetime, so a short wait turns most of those refusals into a late success, while a key at its cap on *recorded* attempts is still refused at once.

reserve_optional
#

rate_limiter.ts view source

(limiter: RateLimiter | null, key: string | null, options?: RateLimitReserveOptions | undefined): Promise<RateLimitReserveResult> import {reserve_optional} from '@fuzdev/fuz_app/rate_limiter.js';

RateLimiter.reserve against a limiter that may be absent — an inert reservation when limiter or key is null. Twin of the Rust spine's reserve_optional.

limiter

the limiter, or null when disabled

type RateLimiter | null

key

rate limit key, or null to skip

type string | null

options?

the wait bound and an abort signal

optional

returns

Promise<RateLimitReserveResult>

reserved_migration_namespaces
#

auth/migrations.ts view source

readonly string[] import {reserved_migration_namespaces} from '@fuzdev/fuz_app/auth/migrations.js';

Migration namespaces reserved by fuz_app. Consumers passing migration_namespaces to create_app_backend must choose a name not in this list — the runtime check rejects matches with a thrown error. Typed as ReadonlyArray<string> (not a literal tuple) so .includes() accepts any consumer-supplied namespace string without a cast.

reset_audit_metadata_validation_failures
#

auth/audit_log_queries.ts view source

(): void import {reset_audit_metadata_validation_failures} from '@fuzdev/fuz_app/auth/audit_log_queries.js';

Reset the counter — for tests only.

returns

void

reset_audit_unknown_event_type_failures
#

auth/audit_log_queries.ts view source

(): void import {reset_audit_unknown_event_type_failures} from '@fuzdev/fuz_app/auth/audit_log_queries.js';

Reset the counter — for tests only.

returns

void

reset_audit_write_failures
#

auth/audit_emitter.ts view source

(): void import {reset_audit_write_failures} from '@fuzdev/fuz_app/auth/audit_emitter.js';

Reset the counter — for tests only.

returns

void

reset_bootstrap_token
#

dev/setup.ts view source

(deps: FsReadDeps & FsWriteDeps & FsRemoveDeps & CommandDeps & EnvDeps, app_name: string, options?: SetupBootstrapTokenOptions | undefined): Promise<...> import {reset_bootstrap_token} from '@fuzdev/fuz_app/dev/setup.js';

Remove an existing bootstrap token and create a new one.

deps

file, command, env, and remove capabilities

app_name

application name

type string

options?

state_dir override, logger

optional

returns

Promise<SetupTokenResult>

result from creating the new token

mutates

  • filesystem — removes the existing token file (if any) then writes a fresh one

reset_database
#

dev/setup.ts view source

(deps: CommandDeps & FsReadDeps & FsRemoveDeps, database_url: string, options?: ResetDatabaseOptions | undefined): Promise<...> import {reset_database} from '@fuzdev/fuz_app/dev/setup.js';

Reset a database to a clean slate.

For PostgreSQL: drops and recreates the database. For pglite: removes the data directory if pglite_data_dir is provided. For empty/missing URLs: skips.

deps

command and file capabilities

database_url

the DATABASE_URL value

type string

options?

pglite_data_dir, logger

optional

returns

Promise<ResetDbResult>

result describing what happened

mutates

  • external database — drops and recreates the PostgreSQL database, or removes the PGlite data directory
  • filesystem — removes `options.pglite_data_dir` recursively for PGlite URLs

reset_mock_runtime
#

runtime/mock.ts view source

(runtime: MockRuntime): void import {reset_mock_runtime} from '@fuzdev/fuz_app/runtime/mock.js';

Reset a mock runtime to initial state.

runtime

returns

void

mutates

  • runtime — clears all mock state (env, fs, dirs, exit/command/stdout/fetch call records, mock results, stdin buffer)

reset_public_schema
#

testing/db.ts view source

(db: Db): Promise<void> import {reset_public_schema} from '@fuzdev/fuz_app/testing/db.js';

Reset a test database to a clean slate by dropping and recreating the public schema.

DROP SCHEMA public CASCADE; CREATE SCHEMA public in plain SQL, so it resets every driver: the in-memory factories call it on each create(), and it is recommended at the start of init_schema callbacks for create_pg_factory, whose persistent databases accumulate stale DDL across fuz_app versions. Drift-proof — it wipes every table, type, and sequence regardless of namespace, so it needs no upkeep when the schema gains a table. The database instance remains usable after reset.

db

type Db

returns

Promise<void>

mutates

  • db — drops the `public` schema and recreates it; all rows in all tables are gone after this returns.

ResetDatabaseOptions
#

dev/setup.ts view source

ResetDatabaseOptions import type {ResetDatabaseOptions} from '@fuzdev/fuz_app/dev/setup.js';

Options for reset_database.

pglite_data_dir?

Directory to remove for file-based pglite.

type string

log?

type SetupLogger

ResetDbResult
#

dev/setup.ts view source

ResetDbResult import type {ResetDbResult} from '@fuzdev/fuz_app/dev/setup.js';

Result of reset_database.

reset

Whether the database was actually reset.

type boolean

skipped

Whether the operation was skipped (e.g. pglite with no data dir).

type boolean

db_type

What type of database was detected.

type "postgres" | "pglite" | "none"

resolve_acting_actor
#

auth/request_context.ts view source

(deps: QueryDeps, account_id: string, acting_actor_id: string | undefined): Promise<ResolveActingActorResult> import {resolve_acting_actor} from '@fuzdev/fuz_app/auth/request_context.js';

Resolve the acting actor for an authenticated request.

Called from the route-spec / RPC dispatcher's authorization phase with the authenticated account id and the validated acting value (from the request payload). Applies the uniform resolution rules:

  • acting_actor_id omitted + 1 actor → use it.
  • acting_actor_id omitted + 0 actors → no_actors (defensive — signup / bootstrap always create an actor in the same tx, so this is a server error).
  • acting_actor_id omitted + multiple actors → actor_required with the available list so the client can prompt; never pick silently.
  • acting_actor_id present + matches an actor on the account → use it.
  • acting_actor_id present + does not match → actor_not_on_account. The available list is intentionally not echoed in this branch (treat as opaque rejection).

deps

query dependencies

account_id

the authenticated account

type string

acting_actor_id

the requested acting actor id, or undefined

type string | undefined

returns

Promise<ResolveActingActorResult>

resolve_action_log
#

resolve_client_ip
#

http/proxy.ts view source

(forwarded_for: string, proxies: ParsedProxy[]): string | undefined import {resolve_client_ip} from '@fuzdev/fuz_app/http/proxy.js';

Resolve the real client IP from an X-Forwarded-For header value.

Walks right-to-left, skipping trusted proxy entries AND any entry that fails strict IP validation (validate_ip_strict). The first untrusted, strictly-valid entry is the client IP. If every walked entry is trusted or malformed, returns the leftmost strictly-valid (trusted) entry (likely-misconfigured all-trusted case) or undefined (everything was malformed — middleware falls back to the connection IP). All entries are normalized before matching and in the returned value.

Skipping malformed entries is the rate-limit-key fix for the "attacker controls XFF and the proxy passes it through" surface — without the skip, an attacker could rotate arbitrary strings (incl. 'attacker:controlled', which Hono's lax distinctRemoteAddr misclassifies as IPv6) as XFF values to get fresh per-IP rate-limit buckets. Tradeoff: legitimate non-standard proxies that include ports in XFF entries (e.g. 203.0.113.1:8080) also fail strict validation, so those entries get skipped and the rate-limit bucket collapses to the proxy's connection IP (one bucket for everyone behind that proxy). Standard proxies (nginx, cloud LBs) don't include ports.

forwarded_for

the X-Forwarded-For header value

type string

proxies

parsed trusted proxy entries

returns

string | undefined

the normalized client IP, or undefined if the header is empty / all entries malformed

resolve_fixture_path
#

testing/assertions.ts view source

(filename: string, import_meta_url: string): string import {resolve_fixture_path} from '@fuzdev/fuz_app/testing/assertions.js';

Resolve an absolute path relative to the caller's module.

filename

type string

import_meta_url

the caller's import.meta.url

type string

returns

string

resolve_heartbeat_receive_timeout
#

actions/socket.svelte.ts view source

(interval: number, receive_timeout?: number): number import {resolve_heartbeat_receive_timeout} from '@fuzdev/fuz_app/actions/socket.svelte.js';

Resolve the effective heartbeat receive timeout for interval: the given receive_timeout (or DEFAULT_HEARTBEAT_RECEIVE_TIMEOUT), raised to at least HEARTBEAT_RECEIVE_TIMEOUT_MIN_FACTOR × interval.

interval

heartbeat idle interval in ms

type number

receive_timeout

requested receive timeout in ms

type number
default DEFAULT_HEARTBEAT_RECEIVE_TIMEOUT

returns

number

the receive timeout in ms the client enforces

resolve_keeper_account_id
#

auth/daemon_token_middleware.ts view source

(deps: QueryDeps): Promise<string | null> import {resolve_keeper_account_id} from '@fuzdev/fuz_app/auth/daemon_token_middleware.js';

Resolve the keeper account ID by querying for the account with an active keeper role_grant.

There is exactly one keeper account (the bootstrap account). Runs once at server startup — the result is cached in DaemonTokenState.keeper_account_id. The acting actor is resolved per-request by the dispatcher's authorization phase (which runs resolve_acting_actor against this account id), so multi-actor keeper accounts surface actor_required if a daemon caller doesn't pass an explicit acting.

deps

query dependencies

returns

Promise<string | null>

the keeper account ID, or null if no keeper exists yet (pre-bootstrap)

resolve_rate_limiters
#

server/app_server.ts view source

internal

(options: Pick<AppServerOptions, "login_ip_rate_limiter" | "signup_ip_rate_limiter" | "bootstrap_ip_rate_limiter" | "login_account_rate_limiter" | "signup_account_rate_limiter" | "action_ip_rate_limiter" | "action_account_rate_limiter" | "rate_limiters">): ResolvedRateLimiters import {resolve_rate_limiters} from '@fuzdev/fuz_app/server/app_server.js';

Resolve each rate-limiter option: an explicit option (instance or null) wins; an omitted one is a default instance under 'enforced' and null under 'disabled_for_testing', which also announces the mode on stderr (announce_rate_limiters_disabled). One instance per surface — see AppServerOptions.login_ip_rate_limiter for why these are not a single shared bucket.

options

the rate-limiter fields and rate_limiters mode of AppServerOptions

type Pick<AppServerOptions, "login_ip_rate_limiter" | "signup_ip_rate_limiter" | "bootstrap_ip_rate_limiter" | "login_account_rate_limiter" | "signup_account_rate_limiter" | "action_ip_rate_limiter" | "action_account_rate_limiter" | "rate_limiters">

returns

ResolvedRateLimiters

resolve_rpc_endpoints_for_setup
#

testing/rpc_helpers.ts view source

(rpc_endpoints: RpcEndpointsSuiteOption, session_options: SessionOptions<string>): RpcEndpointSpec[] import {resolve_rpc_endpoints_for_setup} from '@fuzdev/fuz_app/testing/rpc_helpers.js';

Resolve a suite's rpc_endpoints option to an array for setup-time inspection (path lookup, action presence checks).

For the factory form this invokes the factory twice with stub AppServerContexts and asserts that both invocations produce the same (path, method-list) shape — catching factories that close over mutable state or otherwise diverge across calls. The first array is returned; the second is discarded after the comparison. create_app_server invokes the factory again per-test with its real ctx, and those are the handlers that actually serve requests.

Safe as long as the factory is pure with respect to the endpoint path and the action spec.method list — the canonical helpers (create_standard_rpc_actions, create_admin_actions, create_account_actions, etc.) are. Factories that return a different path based on ctx will produce a setup/runtime mismatch; the path-purity assert below surfaces that as a clear gro check error rather than a silent test/runtime drift.

rpc_endpoints

session_options

type SessionOptions<string>

returns

RpcEndpointSpec[]

throws

  • Error - if the factory's two stub-ctx invocations produce different `(path, method-list)` shapes — surfaces non-pure factories at setup time.

resolve_scope_label
#

ui/format_scope.ts view source

<G extends string | null>(scope_id: string | null, role: string, format_scope: FormatScope, global_label: G): string | G import {resolve_scope_label} from '@fuzdev/fuz_app/ui/format_scope.js';

Resolve a scope label across the context → raw-uuid fallback chain.

global_label is returned for scope_id === null. Callers pass null to render no chip (admin tables — global is the implicit default) or 'global' for explicit labels (offer surfaces). The return type propagates null only when global_label is null.

scope_id

type string | null

role

type string

format_scope

global_label

type G

returns

string | G

generics

resolve_scope_label<G extends string | null>
G
constraint string | null

resolve_spec_qualifier
#

actions/action_codegen.ts view source

(imports: ImportBuilder, options?: { specs_module?: string | undefined; qualify_spec?: ((spec: { method: string; initiator: "frontend" | "backend" | "both"; side_effects: boolean; ... 8 more ...; rate_limit?: "both" | ... 2 more ... | undefined; } | { ...; } | { ...; }) => string) | undefined; } | undefined): (spec: { ...; } | ... 1 more ... | { ...; }) => string import {resolve_spec_qualifier} from '@fuzdev/fuz_app/actions/action_codegen.js';

Resolve a per-spec identifier qualifier with the standard default-vs-callback dance. When qualify_spec is set, returns the caller's callback verbatim and registers no imports — the caller owns its namespace setup (the multi-source case where specs come from several modules). Otherwise, registers * as specs from specs_module (defaulting to './action_specs.ts') on imports and returns (spec) => 'specs.' + to_action_spec_identifier(spec.method).

Used internally by every multi-source-aware helper in this module (generate_action_specs_record, generate_action_inputs_outputs, generate_backend_actions_api); exported so consumers writing their own codegen helpers can reuse the same defaulting + import-registration behavior instead of reimplementing it.

imports

options?

type { specs_module?: string | undefined; qualify_spec?: ((spec: { method: string; initiator: "frontend" | "backend" | "both"; side_effects: boolean; input: ZodType<unknown, unknown, $ZodTypeInternals<unknown, unknown>>; ... 7 more ...; rate_limit?: "both" | ... 2 more ... | undefined; } | { ...; } | { ...; }) => string)...
optional

returns

(spec: { method: string; initiator: "frontend" | "backend" | "both"; side_effects: boolean; input: ZodType<unknown, unknown, $ZodTypeInternals<unknown, unknown>>; output: ZodType<unknown, unknown, $ZodTypeInternals<unknown, unknown>>; ... 6 more ...; rate_limit?: "both" | ... 2 more ... | undefined; } | { ...; } | {...

resolve_standard_error_schema_tightness
#

testing/attack_surface.ts view source

(consumer: ErrorSchemaTightnessOptions | null | undefined): ErrorSchemaTightnessOptions | null import {resolve_standard_error_schema_tightness} from '@fuzdev/fuz_app/testing/attack_surface.js';

Merge a consumer's error_schema_tightness option with default_error_schema_tightness so allowlist and ignore_statuses are additive rather than replacing.

  • undefined → return the default as-is.
  • null → return null (opt out of the assertion).
  • object → spread the default, then consumer overrides for scalar fields (min_specificity), then concat stock-then-consumer for the list fields (allowlist, ignore_statuses) so consumer entries extend rather than replace.

Exported for direct use when a consumer calls assert_error_schema_tightness outside the standard suite but still wants the additive merge.

consumer

type ErrorSchemaTightnessOptions | null | undefined

returns

ErrorSchemaTightnessOptions | null

resolve_valid_path
#

testing/schema_generators.ts view source

(path: string, params_schema?: ZodObject<$ZodLooseShape, $strip> | undefined): string import {resolve_valid_path} from '@fuzdev/fuz_app/testing/schema_generators.js';

Resolve a route path with valid-ish param values so params validation passes. Used when testing input on routes that also have params.

Without a params schema (or for a param the schema doesn't name) each :name becomes test_<name> — fine for an unconstrained segment. With one, test_<name> is still preferred wherever the field schema accepts it, because the param name in the URL is what makes a failure message readable. Only a segment that refuses it falls through: a uuid param takes the nil UUID (the fast path — by far the most common shape), and anything else is synthesized by generate_valid_string, so a format-constrained segment (a blake3:… fact hash, a tok_… id) resolves to a value its schema accepts instead of a test_<name> that 400s before the route's real gates run.

The synthesized value is percent-encoded, and a value containing / is refused (it would silently add a path segment and route elsewhere) — both fall back to test_<name>.

path

type string

params_schema?

type ZodObject<$ZodLooseShape, $strip>
optional

returns

string

resolve_ws_endpoints
#

actions/ws_endpoint_spec.ts view source

(ws_endpoints: readonly WsEndpointSpec[] | undefined, allowed_origins: readonly RegExp[]): ResolvedWsEndpointSpec[] | undefined import {resolve_ws_endpoints} from '@fuzdev/fuz_app/actions/ws_endpoint_spec.js';

Fill in the server's allowed_origins on each WS endpoint spec that omits its own, so every consumer of the resolved list — the mount, the surface — reads the effective patterns.

ws_endpoints

the resolved ws_endpoints option

type readonly WsEndpointSpec[] | undefined

allowed_origins

the server's allowlist, the default

type readonly RegExp[]

returns

ResolvedWsEndpointSpec[] | undefined

the specs, each with allowed_origins set

ResolveActingActorResult
#

auth/request_context.ts view source

ResolveActingActorResult

type { ok: true; actor_id: string; } | { ok: false; reason: "no_actors"; } | { ok: false; reason: "actor_required"; available: { id: string; name: string; }[]; } | { ok: false; reason: "actor_not_on_account"; }

import type {ResolveActingActorResult} from '@fuzdev/fuz_app/auth/request_context.js';

Result of resolve_acting_actor — either an actor id or a structured error the caller maps to an HTTP response.

ResolvedAuth
#

auth/resolved_auth.ts view source

ResolvedAuth import type {ResolvedAuth} from '@fuzdev/fuz_app/auth/resolved_auth.js';

An authenticated request's credential, account-grain — no actor.

token_hash is set for a session, api_token_id for an API token, and both are null for a daemon token. The twin of the Rust spine's ResolvedAuth, which carries the account row and token scope as well; here those stay on the Hono context.

account_id

The authenticated account.

type string

credential_type

How the request was authenticated.

type CredentialType

token_hash

Blake3 hash of the session token, or null for non-session credentials.

type string | null

api_token_id

api_token.id for a bearer credential, else null.

type string | null

credential_deadline

When the credential stops authenticating, as a monotonic performance.now() timestamp — the moment a long-lived connection opened on it is closed (schedule_credential_deadline), since expiry writes nothing a revocation listener could hear. The earliest of what the credential carries: a session's cookie and row expiry, an API token's row expiry (null for an eternal one); null for the daemon token. A row's expiry is read as time remaining on the database clock (auth/credential_deadline.ts). The Rust twin's credential_deadline.

type number | null

ResolvedRateLimiters
#

server/app_server.ts view source

internal

ResolvedRateLimiters import type {ResolvedRateLimiters} from '@fuzdev/fuz_app/server/app_server.js';

The rate limiters create_app_server resolved from its options.

mode

The rate_limiters mode, defaulted to 'enforced'.

type RateLimiterMode

limiters

Each limiter by option name — null when off.

type Record<AppServerRateLimiterKey, RateLimiter | null>

built

The limiters built here for omitted options, in build order — the server's to dispose. Never one passed in, which its caller owns.

type RateLimiter[]

ResolvedWsEndpointSpec
#

actions/ws_endpoint_spec.ts view source

ResolvedWsEndpointSpec

type WsEndpointSpec & { allowed_origins: readonly RegExp[]; }

import type {ResolvedWsEndpointSpec} from '@fuzdev/fuz_app/actions/ws_endpoint_spec.js';

A WsEndpointSpec whose allowed_origins is resolved — its own, or the server's.

path

Hono mount path (e.g. /api/ws).

type string

allowed_origins

Origin allowlist regexes for the upgrade — passed straight to verify_request_source. Defaults to the server's AppServerOptions.allowed_origins, the list the HTTP routes check. Set it only to narrow one endpoint: the auth middleware's origin check runs on the upgrade too, so a request reaches the endpoint only when its origin passes both lists.

type readonly RegExp[]

actions

The actions registered on this endpoint. Spread protocol_actions from actions/protocol.ts first to complete the disconnect-detection + per-request cancel pairing with the frontend client.

type readonly Action<ActionSpecUnion>[]

required_roles?

Roles permitted to upgrade — any-of disjunction, global grants only. Omit (or pass []) to skip the upgrade-time role gate; per-action auth on each spec still applies at dispatch time via perform_action. Pass [ROLE_ADMIN] for an admin-only WS endpoint. Held for the socket's life — see register_ws_endpoint's required_roles.

type readonly string[]

transport?

Existing transport to register connections with. Auto-created when omitted. Either way the mounted transport is reachable on AppServer.ws_endpoints[path] for broadcast / fan-out.

type BackendWebsocketTransport

max_connections_per_account?

Per-account connection cap for the auto-created transport. See RegisterActionWsOptions.max_connections_per_account — rejected alongside transport, which carries its own cap.

type number | null

default DEFAULT_MAX_CONNECTIONS_PER_ACCOUNT

outbound_queue_bytes?

Byte budget of the frames waiting to be written to one connection, for the auto-created transport. See RegisterActionWsOptions.outbound_queue_bytes — rejected alongside transport, which carries its own budget.

type number

default DEFAULT_WS_OUTBOUND_QUEUE_BYTES

heartbeat?

Server-side heartbeat policy. Default-on (60s receive-silence timeout). Set false only when an upstream stack (TCP keepalive, Cloudflare idle timeout) already owns disconnect detection.

type boolean | ServerHeartbeatOptions

artificial_delay?

Optional per-message delay for testing loading states.

type number

max_message_bytes?

Cap on one inbound message, in bytes. See RegisterActionWsOptions.max_message_bytes.

type number

default DEFAULT_WS_MAX_MESSAGE_BYTES

on_socket_open?

Called once per socket after the connection is admitted — registered and its credential re-checked — and before the first message dispatches. See RegisterActionWsOptions.on_socket_open.

type (ctx: SocketOpenContext): void | Promise<void>

ctx

returns void | Promise<void>

on_socket_close?

Called once per admitted socket on close, after transport.remove_connection. See RegisterActionWsOptions.on_socket_close.

type (ctx: SocketCloseContext): void | Promise<void>

ctx

returns void | Promise<void>

auth_guard?

Default true — registers create_ws_auth_guard against this endpoint's transport via deps.audit.add_listener, so a revocation's audit row closes its sockets. Wiring is deduped by transport reference identity (WeakSet<BackendWebsocketTransport>), so two WsEndpointSpecs sharing the exact same instance get a single listener.

The guard repeats what the revocation handlers already do: the transport is always in deps.connection_closer, whatever this flag says, and the handlers close through it after their commit. Turning the guard off leaves those closes in place.

Shared-transport OR-semantics. When multiple WsEndpointSpecs share one transport, the guard is wired iff any of those specs has auth_guard !== false. To opt out for a shared transport, every sibling spec must pass auth_guard: false. The default is "fail safe" — easier to enable than disable, and predictable regardless of spec order.

Reference-identity dedupe means wrapped or proxied transports dedupe as separate entries — a consumer threading every transport through a tracing / DI / metrics shim will get a fresh listener per shimmed reference, even when the underlying transport is the same. If you wrap or proxy, set `auth_guard: false on the duplicate WsEndpointSpec`s and compose create_ws_auth_guard against the underlying transport once.

Set false when a consumer needs to compose their own callback from scratch — or to opt out of the auto-wiring entirely.

The guard is also the one closer of a role revocation: a revoked global grant of a role in required_roles closes the account's sockets on this endpoint with WS_CLOSE_PERMISSION_REVOKED. With the guard off for a transport, nothing closes them.

type boolean

extra_audit_handlers?

Extra audit-event handlers registered via deps.audit.add_listener AFTER the standard auth_guard wiring (when enabled). By the time these run, the standard guard may have already closed sockets. Use for custom analytics, a consumer's own revocation events, etc.

Never deduped — consumer-owned; pass the same handler twice and it fires twice.

type readonly AuditEventHandler[]

rest_auth_route_suffixes
#

testing/integration_helpers.ts view source

readonly ["/login", "/logout", "/password", "/verify", "/signup", "/bootstrap"] import {rest_auth_route_suffixes} from '@fuzdev/fuz_app/testing/integration_helpers.js';

REST auth route suffixes on the account/bootstrap surface — the only routes still REST. find_auth_route rejects any other suffix at runtime; session/token CRUD, admin operations, and role_grant flows live on the RPC surface and should be reached via rpc_call.

RestAuthRouteSuffix
#

testing/integration_helpers.ts view source

"/login" | "/logout" | "/password" | "/verify" | "/signup" | "/bootstrap"

type "/login" | "/logout" | "/password" | "/verify" | "/signup" | "/bootstrap"

import type {RestAuthRouteSuffix} from '@fuzdev/fuz_app/testing/integration_helpers.js';

revalidate_resolved_auth
#

auth/resolved_auth.ts view source

(deps: QueryDeps, resolved: ResolvedAuth): Promise<boolean> import {revalidate_resolved_auth} from '@fuzdev/fuz_app/auth/resolved_auth.js';

Re-check that a resolved credential still authenticates: true when it does, false when it has been revoked, has expired, or its account is gone.

For a connection that outlives its request — a WebSocket, an SSE stream — and is authorized once, at open. Such a connection is closed on revocation by identity, but only once it is *registered*; between the credential read and the registration a revocation closes nothing. So the opener registers first (un-admitted — BackendWebsocketTransport.register_pending / SubscriberRegistry.subscribe_pending), then calls this, then admits. A revocation that committed before this read is seen by it; one whose close ran after the registration found the pending entry.

That needs each statement here to see everything committed before it started, so deps.db must be the pool-level Db (autocommit) or a READ COMMITTED transaction — never a REPEATABLE READ / SERIALIZABLE transaction opened before the registration, whose snapshot would predate it.

What is re-read, per credential — each through the same predicate the resolve path used, so the two cannot disagree about what "valid" means (the session and account legs call the middleware's own queries; the token leg shares its WHERE fragment):

  • Session — the auth_session row still exists, is unexpired, and belongs to the resolved account.
  • API token — the api_token row still exists, is unexpired, and belongs to the resolved account (by id; the raw token is not kept).
  • Daemon token — nothing of its own. The token has no row, and no revocation event: rotation closes no connection, so there is no close an opening connection could have missed. Its keeper account is still re-read.
  • Every credential — the account row still exists and is not soft-deleted.

It re-checks the credential, not what a revocation chose to close: a logout closes every connection of the account but deletes only its own session, so another session's connection opening concurrently passes this check and is refused only if that account-wide close found its pending registration — the same two outcomes an already-open connection has.

No side effects. Unlike the bearer middleware this never touches api_token.last_used_at / last_used_ip — re-checking a credential is not using it — and it writes nothing else.

A ResolvedAuth whose credential type lacks its own key (a session with no token_hash, a bearer with no api_token_id) is a shape the middleware never builds; it is answered false.

The twin of the Rust spine's revalidate_resolved_auth.

deps

query dependencies; the pool-level Db, not a snapshot transaction

resolved

the credential the request authenticated with

returns

Promise<boolean>

whether the credential still authenticates

throws

  • Error - propagated from the database. The caller must fail closed — an unchecked credential is not a valid one.

RevocationScope
#

auth/audit_log_schema.ts view source

RevocationScope

type "none" | "session" | "token" | "account" | "role"

import type {RevocationScope} from '@fuzdev/fuz_app/auth/audit_log_schema.js';

What a successful audit row invalidates — the axis both revocation listeners (create_ws_auth_guard, create_sse_auth_guard) dispatch on.

  • none — the row invalidates no live connection.
  • session — one session: the connections whose session-token hash equals the row's metadata.session_id.
  • token — one API token: the connections whose api_token.id equals the row's metadata.token_id.
  • account — every connection of target_account_id ?? account_id.
  • role — the target account's connections gated on the row's metadata.role, when the revoked grant was global (to_global_role_revocation).

The scope says what the row invalidated; both listeners act on every scope alike. On role that matters for the WebSocket too: per-message dispatch re-authorizes each request against its own action, but a socket admitted by a role gate also receives every server push its endpoint sends, which no dispatch ever re-checks — so a socket that lost the gating role is closed, like a stream.

Twin of the Rust spine's RevocationScope.

RevokeForScopeResult
#

auth/role_grant_queries.ts view source

RevokeForScopeResult import type {RevokeForScopeResult} from '@fuzdev/fuz_app/auth/role_grant_queries.js';

Result of query_role_grant_revoke_for_scope — every role_grant revoked plus every pending offer superseded by the scope-wide cascade.

revoked

One entry per role_grant revoked by this call. Carries both the revokee's actor_id (the role_grant's grantee — drives target_actor_id audit envelopes) and account_id (the actor's account — drives target_account_id for SSE/WS socket-close fan-out). Empty array means no active role_grant was bound to the scope. scope_kind is surfaced for forensic completeness; the cascade itself keys on scope_id regardless of kind.

type { role_grant_id: string & $brand<"Uuid">; role: string; scope_kind: string | null; scope_id: string & $brand<"Uuid">; actor_id: string & $brand<"Uuid">; account_id: string & $brand<...>; }[]

superseded_offers

Every pending offer at the scope — tuple-matched and orphan, undifferentiated — superseded in the same cascade. Each entry carries its grantor's from_account_id for role_grant_offer_supersede notification fan-out.

The caller is responsible for emitting role_grant_offer_supersede audit events with reason: 'scope_destroyed' and cause_id: <destroyed scope row id> per entry — the cause of every supersede here is the scope deletion, not any individual role_grant revoke (the revokes are themselves consequences of the scope going away).

type SupersededOffer[]

RevokeRoleGrantResult
#

auth/role_grant_queries.ts view source

RevokeRoleGrantResult import type {RevokeRoleGrantResult} from '@fuzdev/fuz_app/auth/role_grant_queries.js';

Result of query_revoke_role_grant — the revoked role_grant plus any pending offers superseded by the revoke.

id

type Uuid

role

type string

scope_kind

type string | null

scope_id

type Uuid | null

superseded_offers

Pending offers for the revoked role_grant's (account, role, scope) that were marked superseded as a side effect. Each entry carries its grantor's from_account_id so callers can fan out role_grant_offer_supersede notifications without a second round-trip. The caller is responsible for emitting a role_grant_offer_supersede audit event per entry (with reason: 'role_grant_revoked' and cause_id: <revoked role_grant id>).

type SupersededOffer[]

RevokeRoleResult
#

auth/role_grant_queries.ts view source

RevokeRoleResult import type {RevokeRoleResult} from '@fuzdev/fuz_app/auth/role_grant_queries.js';

Result of query_role_grant_revoke_role — every role_grant revoked plus the pending offers superseded by the bulk revoke.

revoked

One entry per role_grant revoked by this call. Carries the revokee's account_id so callers can fan out a role_grant_revoke notification per scope-instance. Empty array means nothing was active for (actor, role).

type { role_grant_id: string; role: string; scope_kind: string | null; scope_id: string | null; account_id: string; }[]

superseded_offers

Pending offers for the actor's account+role (all scopes) superseded by the bulk revoke. Each entry carries its grantor's from_account_id so callers can fan out role_grant_offer_supersede notifications without a second round-trip.

type SupersededOffer[]

RevokingActionFactoryDeps
#

auth/deps.ts view source

RevokingActionFactoryDeps import type {RevokingActionFactoryDeps} from '@fuzdev/fuz_app/auth/deps.js';

Capabilities for the action factories whose handlers end credentials — create_account_actions, create_admin_actions, and the create_standard_rpc_actions bundle over them. ActionFactoryDeps plus the closer their revocations close live connections through. AppDeps and RouteFactoryDeps satisfy it structurally.

inheritance

connection_closer

Closes the live connections of a revoked credential, after the revocation commits. Required rather than optional: a revocation that closes nothing leaves an open WebSocket or SSE stream running on a dead credential. A backend with no live-connection surface passes noop_connection_closer.

type ConnectionCloser

ROLE_ADMIN
#

auth/role_schema.ts view source

"admin" import {ROLE_ADMIN} from '@fuzdev/fuz_app/auth/role_schema.js';

App-level administrative role. Granted via the admin path.

role_grant_assign_action_spec
#

auth/role_grant_offer_action_specs.ts view source

{ method: string; kind: "request_response"; initiator: "frontend"; auth: { account: "required"; actor: "required"; roles: string[]; }; side_effects: true; input: ZodObject<{ to_account_id: $ZodBranded<ZodUUID, "Uuid", "out">; ... 4 more ...; acting: ZodOptional<...>; }, $strict>; ... 4 more ...; rate_limit: "account... import {role_grant_assign_action_spec} from '@fuzdev/fuz_app/auth/role_grant_offer_action_specs.js';

rate_limit: 'account' bounds admin-side burn of role_grant_assign — the action is admin-gated and audit-trailed, but the per-account cap matches role_grant_revoke so a single admin script can't churn grants in a loop.

ROLE_GRANT_COLUMNS
#

auth/role_grant_queries.ts view source

readonly ["id", "actor_id", "role", "scope_kind", "scope_id", "created_at", "expires_at", "revoked_at", "revoked_by", "revoked_reason", "granted_by", "source_offer_id"] import {ROLE_GRANT_COLUMNS} from '@fuzdev/fuz_app/auth/role_grant_queries.js';

The full role_grant column set, named explicitly so a row read fails loud on schema drift (see ACCOUNT_COLUMNS in auth/account_queries.ts for the outage class). Column order follows RoleGrant — the frozen fuz_auth v0 body plus the appended scope_kind / scope_id / revoked_reason / source_offer_id columns. Keep in sync with RoleGrant and the role_grant migration chain in auth/auth_ddl.ts.

ROLE_GRANT_INDEXES
#

role_grant_offer_accept_action_spec
#

auth/role_grant_offer_action_specs.ts view source

{ method: string; kind: "request_response"; initiator: "frontend"; auth: { account: "required"; actor: "required"; credential_types: string[]; }; side_effects: true; input: ZodObject<{ offer_id: $ZodBranded<ZodUUID, "Uuid", "out">; acting: ZodOptional<...>; }, $strict>; ... 4 more ...; rate_limit: "account"; } import {role_grant_offer_accept_action_spec} from '@fuzdev/fuz_app/auth/role_grant_offer_action_specs.js';

rate_limit: 'account', matching role_grant_offer_create: every call locks the offer row and reads its grant, and a replay of an accepted offer answers without minting anything, so the account bucket is what bounds a loop of them.

credential_types: ['session'] — see docs/security.md §Credential-channel gating. The only offer verb that moves the *caller's own* authority: accepting a pending admin offer over a leaked API token is a privilege pivot in the same family the credential-lifecycle mutations are gated to prevent (the token is account-wide and non-expiring, and the resulting role_grant outlives revoking it). Consent inherently belongs to a human at a browser, and no automation accepts an offer on its own behalf. The sibling verbs stay ungated deliberately — _create is the offering side, and _decline / _retract destroy a pending offer and confer nothing.

ROLE_GRANT_OFFER_ACCEPTED_NOTIFICATION_METHOD
#

role_grant_offer_accepted_notification_spec
#

auth/role_grant_offer_notifications.ts view source

{ method: string; kind: "remote_notification"; initiator: "backend"; auth: null; side_effects: true; input: ZodObject<{ offer: ZodObject<{ id: $ZodBranded<ZodUUID, "Uuid", "out">; from_actor_id: $ZodBranded<...>; ... 13 more ...; resulting_role_grant_id: ZodNullable<...>; }, $strict>; }, $strict>; output: ZodVoid; a... import {role_grant_offer_accepted_notification_spec} from '@fuzdev/fuz_app/auth/role_grant_offer_notifications.js';

role_grant_offer_and_accept
#

testing/role_grant_helpers.ts view source

(args: RoleGrantOfferAndAcceptArgs): Promise<{ offer_id: string & $brand<"Uuid">; role_grant_id: string & $brand<"Uuid">; }> import {role_grant_offer_and_accept} from '@fuzdev/fuz_app/testing/role_grant_helpers.js';

Drive the full consent flow (grantor offer → recipient accept) over the production RPC surface and return the materialized role_grant id.

grantor and recipient carry both the account id (for to_account_id derivation) and the create_session_headers factory (for cookie-threaded auth) — closing that loop on a single object per party rules out caller-side header/account mismatch.

args

returns

Promise<{ offer_id: string & $brand<"Uuid">; role_grant_id: string & $brand<"Uuid">; }>

ROLE_GRANT_OFFER_COLUMNS
#

auth/role_grant_offer_ddl.ts view source

readonly ["id", "from_actor_id", "to_account_id", "to_actor_id", "role", "scope_kind", "scope_id", "message", "created_at", "expires_at", "accepted_at", "declined_at", "decline_reason", "retracted_at", "superseded_at", "resulting_role_grant_id", "expire_audited_at"] import {ROLE_GRANT_OFFER_COLUMNS} from '@fuzdev/fuz_app/auth/role_grant_offer_ddl.js';

The full role_grant_offer column set, named explicitly so a row read fails loud on schema drift (see ACCOUNT_COLUMNS in auth/account_queries.ts for the outage class). Column order follows RoleGrantOffer and ROLE_GRANT_OFFER_SCHEMA below, with expire_audited_at last — the appended role_grant_offer_expire_audited_at migration adds it after the frozen CREATE TABLE.

Lives here rather than in auth/role_grant_offer_queries.ts because two query modules project this table — see the placement rule in db/sql_columns.ts.

role_grant_offer_create_action_spec
#

auth/role_grant_offer_action_specs.ts view source

{ method: string; kind: "request_response"; initiator: "frontend"; auth: { account: "required"; actor: "required"; }; side_effects: true; input: ZodObject<{ to_account_id: $ZodBranded<ZodUUID, "Uuid", "out">; ... 5 more ...; acting: ZodOptional<...>; }, $strict>; ... 4 more ...; rate_limit: "account"; } import {role_grant_offer_create_action_spec} from '@fuzdev/fuz_app/auth/role_grant_offer_action_specs.js';

rate_limit: 'account' throttles offer-spam at the authenticated grantor and bounds the account-existence oracle on to_account_id — the same shape as invite_create_action_spec upstream addresses, where a hostile authed caller iterates recipients to probe ERROR_ACCOUNT_NOT_FOUND (and the actor-binding via ERROR_ROLE_GRANT_OFFER_ACTOR_ACCOUNT_MISMATCH) as an enumeration vector. Failure-outcome audit rows preserve the forensic trail; the rate cap closes the budget.

role_grant_offer_decline_action_spec
#

auth/role_grant_offer_action_specs.ts view source

{ method: string; kind: "request_response"; initiator: "frontend"; auth: { account: "required"; actor: "required"; }; side_effects: true; input: ZodObject<{ offer_id: $ZodBranded<ZodUUID, "Uuid", "out">; reason: ZodOptional<...>; acting: ZodOptional<...>; }, $strict>; output: ZodObject<...>; async: true; description... import {role_grant_offer_decline_action_spec} from '@fuzdev/fuz_app/auth/role_grant_offer_action_specs.js';

ROLE_GRANT_OFFER_DECLINED_NOTIFICATION_METHOD
#

role_grant_offer_declined_notification_spec
#

auth/role_grant_offer_notifications.ts view source

{ method: string; kind: "remote_notification"; initiator: "backend"; auth: null; side_effects: true; input: ZodObject<{ offer: ZodObject<{ id: $ZodBranded<ZodUUID, "Uuid", "out">; from_actor_id: $ZodBranded<...>; ... 13 more ...; resulting_role_grant_id: ZodNullable<...>; }, $strict>; }, $strict>; output: ZodVoid; a... import {role_grant_offer_declined_notification_spec} from '@fuzdev/fuz_app/auth/role_grant_offer_notifications.js';

ROLE_GRANT_OFFER_DEFAULT_TTL_MS
#

auth/role_grant_offer_schema.ts view source

number import {ROLE_GRANT_OFFER_DEFAULT_TTL_MS} from '@fuzdev/fuz_app/auth/role_grant_offer_schema.js';

Default TTL for a newly created offer — 30 days. Matches GitHub org-invite expiry.

role_grant_offer_expr
#

auth/role_grant_offer_ddl.ts view source

(alias: string) => ColumnExpr import {role_grant_offer_expr} from '@fuzdev/fuz_app/auth/role_grant_offer_ddl.js';

The role_grant_offer timestamp override, by row qualifier ('' bare, o for the history JOIN, u for the updated CTE's outer select). Lives beside the const for the same reason the const does — both query modules that project this table need it.

A cascade's inner RETURNING deliberately projects the *raw* columns: the outer select over the updated CTE applies this, and to_char over an already-formatted text column would fail.

role_grant_offer_history_action_spec
#

auth/role_grant_offer_action_specs.ts view source

{ method: string; kind: "request_response"; initiator: "frontend"; auth: { account: "required"; actor: "required"; }; side_effects: false; input: ZodDefault<ZodObject<{ account_id: ZodOptional<ZodNullable<$ZodBranded<ZodUUID, "Uuid", "out">>>; limit: ZodOptional<...>; offset: ZodOptional<...>; acting: ZodOptional<..... import {role_grant_offer_history_action_spec} from '@fuzdev/fuz_app/auth/role_grant_offer_action_specs.js';

ROLE_GRANT_OFFER_INBOX_INDEX
#

auth/role_grant_offer_ddl.ts view source

"\nCREATE INDEX IF NOT EXISTS role_grant_offer_inbox\n ON role_grant_offer (to_account_id, expires_at)\n WHERE accepted_at IS NULL\n AND declined_at IS NULL\n AND retracted_at IS NULL\n AND superseded_at IS NULL" import {ROLE_GRANT_OFFER_INBOX_INDEX} from '@fuzdev/fuz_app/auth/role_grant_offer_ddl.js';

Inbox lookup — pending offers for an account, ordered by soonest expiry.

role_grant_offer_list_action_spec
#

auth/role_grant_offer_action_specs.ts view source

{ method: string; kind: "request_response"; initiator: "frontend"; auth: { account: "required"; actor: "required"; }; side_effects: false; input: ZodDefault<ZodObject<{ account_id: ZodOptional<ZodNullable<$ZodBranded<ZodUUID, "Uuid", "out">>>; acting: ZodOptional<...>; }, $strict>>; output: ZodObject<...>; async: tr... import {role_grant_offer_list_action_spec} from '@fuzdev/fuz_app/auth/role_grant_offer_action_specs.js';

ROLE_GRANT_OFFER_MESSAGE_LENGTH_MAX
#

auth/role_grant_offer_schema.ts view source

500 import {ROLE_GRANT_OFFER_MESSAGE_LENGTH_MAX} from '@fuzdev/fuz_app/auth/role_grant_offer_schema.js';

Maximum length of the optional message attached to an offer, and of a decline reason, in UTF-16 code units.

role_grant_offer_notification_specs
#

ROLE_GRANT_OFFER_PENDING_UNIQUE_INDEX
#

auth/role_grant_offer_ddl.ts view source

"\nCREATE UNIQUE INDEX IF NOT EXISTS role_grant_offer_pending_unique\n ON role_grant_offer (\n to_account_id,\n role,\n COALESCE(scope_kind, 'GLOBAL'),\n COALESCE(scope_id, '00000000-0000-0000-0000-000000000000'::uuid),\n from_actor_id\n )\n WHERE accepted_at IS NULL\n AND declined_at IS NULL\n ... import {ROLE_GRANT_OFFER_PENDING_UNIQUE_INDEX} from '@fuzdev/fuz_app/auth/role_grant_offer_ddl.js';

At most one pending offer per (to_account, role, scope_kind, scope, from_actor).

Including from_actor_id in the tuple lets multiple grantors coexist — teacher A and teacher B can each have a pending classroom_student offer for the same student and scope. A same-grantor re-offer upserts the existing pending row. COALESCE collapses NULL scopes into the sentinel values so Postgres's NULL-in-unique-index quirk does not allow duplicate global pending offers; the scope_kind / scope_id pair is always either both null (global) or both non-null (scoped) per the role_grant_offer_scope_kind_paired CHECK, so the two COALESCE expressions always agree. The ON CONFLICT target in query_role_grant_offer_create must match this expression literally.

ROLE_GRANT_OFFER_RECEIVED_NOTIFICATION_METHOD
#

role_grant_offer_received_notification_spec
#

auth/role_grant_offer_notifications.ts view source

{ method: string; kind: "remote_notification"; initiator: "backend"; auth: null; side_effects: true; input: ZodObject<{ offer: ZodObject<{ id: $ZodBranded<ZodUUID, "Uuid", "out">; from_actor_id: $ZodBranded<...>; ... 13 more ...; resulting_role_grant_id: ZodNullable<...>; }, $strict>; }, $strict>; output: ZodVoid; a... import {role_grant_offer_received_notification_spec} from '@fuzdev/fuz_app/auth/role_grant_offer_notifications.js';

role_grant_offer_retract_action_spec
#

auth/role_grant_offer_action_specs.ts view source

{ method: string; kind: "request_response"; initiator: "frontend"; auth: { account: "required"; actor: "required"; }; side_effects: true; input: ZodObject<{ offer_id: $ZodBranded<ZodUUID, "Uuid", "out">; acting: ZodOptional<...>; }, $strict>; output: ZodObject<...>; async: true; description: string; error_reasons: (... import {role_grant_offer_retract_action_spec} from '@fuzdev/fuz_app/auth/role_grant_offer_action_specs.js';

ROLE_GRANT_OFFER_RETRACTED_NOTIFICATION_METHOD
#

role_grant_offer_retracted_notification_spec
#

auth/role_grant_offer_notifications.ts view source

{ method: string; kind: "remote_notification"; initiator: "backend"; auth: null; side_effects: true; input: ZodObject<{ offer: ZodObject<{ id: $ZodBranded<ZodUUID, "Uuid", "out">; from_actor_id: $ZodBranded<...>; ... 13 more ...; resulting_role_grant_id: ZodNullable<...>; }, $strict>; }, $strict>; output: ZodVoid; a... import {role_grant_offer_retracted_notification_spec} from '@fuzdev/fuz_app/auth/role_grant_offer_notifications.js';

ROLE_GRANT_OFFER_SCHEMA
#

auth/role_grant_offer_ddl.ts view source

"\nCREATE TABLE IF NOT EXISTS role_grant_offer (\n id UUID PRIMARY KEY DEFAULT gen_random_uuid(),\n from_actor_id UUID NOT NULL REFERENCES actor(id) ON DELETE CASCADE,\n to_account_id UUID NOT NULL REFERENCES account(id) ON DELETE CASCADE,\n to_actor_id UUID NULL REFERENCES actor(id) ON DELETE CASCADE,\n role T... import {ROLE_GRANT_OFFER_SCHEMA} from '@fuzdev/fuz_app/auth/role_grant_offer_ddl.js';

ROLE_GRANT_OFFER_SCOPE_KIND_GLOBAL_TOKEN
#

auth/role_grant_offer_ddl.ts view source

"GLOBAL" import {ROLE_GRANT_OFFER_SCOPE_KIND_GLOBAL_TOKEN} from '@fuzdev/fuz_app/auth/role_grant_offer_ddl.js';

Index-side token for the global case in the partial unique index. Uppercase so it cannot collide with consumer-declared ScopeKindName values (which are lowercase by regex). Never appears as a column value — column-level scope_kind = NULL and scope_id = NULL together encode the global case.

ROLE_GRANT_OFFER_SCOPE_SENTINEL_UUID
#

auth/role_grant_offer_ddl.ts view source

"00000000-0000-0000-0000-000000000000" import {ROLE_GRANT_OFFER_SCOPE_SENTINEL_UUID} from '@fuzdev/fuz_app/auth/role_grant_offer_ddl.js';

Sentinel UUID used inside the partial unique indexes to collapse scope_id IS NULL into a comparable value.

ROLE_GRANT_OFFER_SUPERSEDE_NOTIFICATION_METHOD
#

role_grant_offer_supersede_notification_spec
#

auth/role_grant_offer_notifications.ts view source

{ method: string; kind: "remote_notification"; initiator: "backend"; auth: null; side_effects: true; input: ZodObject<{ offer: ZodObject<{ id: $ZodBranded<ZodUUID, "Uuid", "out">; from_actor_id: $ZodBranded<...>; ... 13 more ...; resulting_role_grant_id: ZodNullable<...>; }, $strict>; reason: ZodEnum<...>; cause_id:... import {role_grant_offer_supersede_notification_spec} from '@fuzdev/fuz_app/auth/role_grant_offer_notifications.js';

ROLE_GRANT_OFFER_WITH_GRANTOR_SELECT
#

auth/role_grant_offer_ddl.ts view source

string import {ROLE_GRANT_OFFER_WITH_GRANTOR_SELECT} from '@fuzdev/fuz_app/auth/role_grant_offer_ddl.js';

Outer SELECT over an updated CTE of role_grant_offer rows, joining the grantor's account_id on as from_account_id — the SupersededOffer / DeclinedOffer shape every offer-superseding cascade returns (the revoke cascades in auth/role_grant_queries.ts, decline + accept-supersede in auth/role_grant_offer_queries.ts). The CTE must be named updated and RETURNING the full offer row.

role_grant_offers_state_context
#

ui/role_grant_offers_state.svelte.ts view source

{ get: (error_message?: string | undefined) => RoleGrantOffersState; get_maybe: () => RoleGrantOffersState | undefined; set: (value: RoleGrantOffersState) => RoleGrantOffersState; } import {role_grant_offers_state_context} from '@fuzdev/fuz_app/ui/role_grant_offers_state.svelte.js';

Svelte context for RoleGrantOffersState. Use role_grant_offers_state_context.set(state) in the provider and role_grant_offers_state_context.get() to access.

role_grant_revoke_action_spec
#

auth/role_grant_offer_action_specs.ts view source

{ method: string; kind: "request_response"; initiator: "frontend"; auth: { account: "required"; actor: "required"; roles: string[]; }; side_effects: true; input: ZodObject<{ actor_id: $ZodBranded<ZodUUID, "Uuid", "out">; role_grant_id: $ZodBranded<...>; reason: ZodOptional<...>; acting: ZodOptional<...>; }, $strict>... import {role_grant_revoke_action_spec} from '@fuzdev/fuz_app/auth/role_grant_offer_action_specs.js';

rate_limit: 'account' bounds admin-side burn of role_grant_revoke — the action is admin-gated and audit-trailed, but the per-account cap keeps a single admin script from churning role_grants in a loop and obscuring audit context for unrelated activity.

ROLE_GRANT_REVOKE_NOTIFICATION_METHOD
#

role_grant_revoke_notification_spec
#

auth/role_grant_offer_notifications.ts view source

{ method: string; kind: "remote_notification"; initiator: "backend"; auth: null; side_effects: true; input: ZodObject<{ role_grant_id: $ZodBranded<ZodUUID, "Uuid", "out">; role: ZodString; scope_id: ZodNullable<...>; reason: ZodNullable<...>; }, $strict>; output: ZodVoid; async: true; description: string; } import {role_grant_revoke_notification_spec} from '@fuzdev/fuz_app/auth/role_grant_offer_notifications.js';

ROLE_GRANT_REVOKED_REASON_LENGTH_MAX
#

auth/account_schema.ts view source

500 import {ROLE_GRANT_REVOKED_REASON_LENGTH_MAX} from '@fuzdev/fuz_app/auth/account_schema.js';

Maximum length of the optional free-form revoked_reason attached to a revoked role_grant. Bounds the value at the schema layer so both the admin input (when the route surfaces a reason field) and the revokee-facing role_grant_revoke WS notification validate against the same ceiling. Counted in UTF-16 code units.

ROLE_GRANT_SCHEMA
#

auth/auth_ddl.ts view source

"\nCREATE TABLE IF NOT EXISTS role_grant (\n id UUID PRIMARY KEY DEFAULT gen_random_uuid(),\n actor_id UUID NOT NULL REFERENCES actor(id) ON DELETE CASCADE,\n role TEXT NOT NULL,\n created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),\n expires_at TIMESTAMPTZ,\n revoked_at TIMESTAMPTZ,\n revoked_by UUID REFERENCES a... import {ROLE_GRANT_SCHEMA} from '@fuzdev/fuz_app/auth/auth_ddl.js';

role_has_grant_path
#

auth/role_schema.ts view source

(role_specs: ReadonlyMap<string, RoleSpec>, role: string, grant_path: string): boolean import {role_has_grant_path} from '@fuzdev/fuz_app/auth/role_schema.js';

Predicate over a RoleSpec map: does the named role include the given grant path? Returns false for unknown roles. Used by admin_actions.create_admin_actions (path = 'admin') and self_service_role_actions.create_self_service_role_actions (path = 'self_service') to derive their default eligibility filters.

role_specs

type ReadonlyMap<string, RoleSpec>

role

type string

grant_path

type string

returns

boolean

ROLE_KEEPER
#

auth/role_schema.ts view source

"keeper" import {ROLE_KEEPER} from '@fuzdev/fuz_app/auth/role_schema.js';

System-level role. Requires daemon token (filesystem proof). Controls the keep.

ROLE_NAME_LENGTH_MAX
#

auth/role_schema.ts view source

64 import {ROLE_NAME_LENGTH_MAX} from '@fuzdev/fuz_app/auth/role_schema.js';

The longest role name RoleName admits, in characters. A role string is caller-chosen text until it passes the grammar, and the grant verbs record the role they refuse in a failure audit row, so the grammar bounds its length too. Same value as the Rust spine's ROLE_NAME_LENGTH_MAX.

RoleGrant
#

auth/account_schema.ts view source

RoleGrant import type {RoleGrant} from '@fuzdev/fuz_app/auth/account_schema.js';

Role grant — time-bounded, revocable grant of a role to an actor.

id

type Uuid

actor_id

type Uuid

role

type string

scope_kind

Machine-readable kind tag for the polymorphic scope_id. Paired-null with scope_id per the role_grant_scope_kind_paired CHECK: both null (global) or both non-null (scoped). Consumer-declared via create_scope_kind_schema(...); v1 keeps validation registry-membership only, with no INSERT-time (role, scope_kind) enforcement.

type string | null

scope_id

Resource scope this grant applies to (e.g. a classroom id). null for global role_grants.

type Uuid | null

created_at

type string

expires_at

type string | null

revoked_at

type string | null

revoked_by

type Uuid | null

revoked_reason

Optional free-form reason attached on revoke (rides on the role_grant_revoke WS notification to the revokee).

type string | null

granted_by

type Uuid | null

source_offer_id

Offer that produced this role_grant (set by query_accept_offer). null for direct grants.

type Uuid | null

RoleGrantAssignInput
#

auth/role_grant_offer_action_specs.ts view source

value + type

{ to_account_id: string & $brand<"Uuid">; role: string; to_actor_id?: (string & $brand<"Uuid">) | null | undefined; scope_kind?: string | null | undefined; scope_id?: (string & $brand<...>) | ... 1 more ... | undefined; acting?: (string & $brand<...>) | undefined; } import {RoleGrantAssignInput} from '@fuzdev/fuz_app/auth/role_grant_offer_action_specs.js';

Input for role_grant_assign — the immediate admin-only conferral path, the consent-free sibling of role_grant_offer_create. An admin assigns a role_grant straight onto the target actor; there is no offer for a grantee to accept. No message (no offer to carry it on). scope_kind pairs with scope_id exactly as on role_grant_offer_create — both or neither — and the grant row carries the pair.

to_account_id

type string & $brand<"Uuid">

role

type string

to_actor_id?

type (string & $brand<"Uuid">) | null

scope_kind?

type string | null

scope_id?

type (string & $brand<"Uuid">) | null

acting?

type string & $brand<"Uuid">

RoleGrantAssignOutput
#

auth/role_grant_offer_action_specs.ts view source

value + type

{ ok: true; role_grant_id: string & $brand<"Uuid">; } import {RoleGrantAssignOutput} from '@fuzdev/fuz_app/auth/role_grant_offer_action_specs.js';

Output for role_grant_assign.

ok

type true

role_grant_id

type string & $brand<"Uuid">

RoleGrantFixtureOptions
#

testing/cross_backend/role_grant_cross_helpers.ts view source

RoleGrantFixtureOptions import type {RoleGrantFixtureOptions} from '@fuzdev/fuz_app/testing/cross_backend/role_grant_cross_helpers.js';

Options for the role-grant fixtures.

role?

The role to offer. Default ROLE_ADMIN, the one builtin admin-grantable role.

type string

readonly

rpc_path?

The JSON-RPC endpoint path. Default SPINE_RPC_PATH.

type string

readonly

RoleGrantHistoryEventJson
#

auth/audit_log_schema.ts view source

value + type

{ id: string & $brand<"Uuid">; seq: number; event_type: string; outcome: "success" | "failure"; actor_id: (string & $brand<"Uuid">) | null; account_id: (string & $brand<...>) | null; ... 6 more ...; target_username: string | null; } import {RoleGrantHistoryEventJson} from '@fuzdev/fuz_app/auth/audit_log_schema.js';

Zod schema for role_grant history events with resolved usernames.

id

type string & $brand<"Uuid">

seq

type number

event_type

type string

outcome

type "success" | "failure"

actor_id

type (string & $brand<"Uuid">) | null

account_id

type (string & $brand<"Uuid">) | null

target_account_id

type (string & $brand<"Uuid">) | null

target_actor_id

type (string & $brand<"Uuid">) | null

ip

type string | null

created_at

type string

metadata

type Record<string, unknown> | null

username

type string | null

target_username

type string | null

RoleGrantOffer
#

auth/role_grant_offer_schema.ts view source

RoleGrantOffer import type {RoleGrantOffer} from '@fuzdev/fuz_app/auth/role_grant_offer_schema.js';

Role grant offer row as returned by the database.

id

type Uuid

from_actor_id

type Uuid

to_account_id

type Uuid

to_actor_id

Optional actor-grain target on the recipient account. When set, accept is gated to this specific actor — query_accept_offer rejects any other actor with role_grant_offer_actor_mismatch even when they belong to to_account_id. When null the offer is account-grain and any actor on to_account_id may accept (the v1 default).

Drives the audit envelope's target_actor_id on offer-shape events (role_grant_offer_create / _expire / _retract / _supersede) — when set, the actor-grain forensic field carries the named actor; when null the offer-shape events leave it null by design.

type Uuid | null

role

type string

scope_kind

Machine-readable kind tag for the polymorphic scope_id. Paired-null with scope_id per the role_grant_offer_scope_kind_paired CHECK: both null (global) or both non-null (scoped). Consumer-declared via create_scope_kind_schema(...); v1 keeps validation registry-membership only, with no INSERT-time (role, scope_kind) enforcement.

type string | null

scope_id

type Uuid | null

message

type string | null

created_at

type string

expires_at

type string

accepted_at

type string | null

declined_at

type string | null

decline_reason

type string | null

retracted_at

type string | null

superseded_at

Set when the offer was obsoleted by an external event — a sibling offer was accepted (yielding the role_grant this offer's role+scope maps to) or the resulting role_grant for this (to_account, role, scope) was revoked. Closes the "accept a pre-revoke offer to bypass the revoke" path.

type string | null

resulting_role_grant_id

type Uuid | null

expire_audited_at

When the expiry sweep audited this offer's role_grant_offer_expire — the claim stamp query_role_grant_offer_sweep_expired sets in the same transaction as the audit row, so each expiry is audited once. Not a lifecycle state (an expired offer is still pending by the terminal columns) and server-only: to_role_grant_offer_json drops it, and the strict RoleGrantOfferJson would reject it. A re-offer clears it.

type string | null

RoleGrantOfferAcceptedParams
#

auth/role_grant_offer_notifications.ts view source

value + type

{ offer: { id: string & $brand<"Uuid">; from_actor_id: string & $brand<"Uuid">; to_account_id: string & $brand<"Uuid">; to_actor_id: (string & $brand<"Uuid">) | null; ... 11 more ...; resulting_role_grant_id: (string & $brand<...>) | null; }; } import {RoleGrantOfferAcceptedParams} from '@fuzdev/fuz_app/auth/role_grant_offer_notifications.js';

Params for role_grant_offer_accepted — recipient accepted the offer.

offer

type RoleGrantOfferJson

RoleGrantOfferAcceptInput
#

auth/role_grant_offer_action_specs.ts view source

value + type

{ offer_id: string & $brand<"Uuid">; acting?: (string & $brand<"Uuid">) | undefined; } import {RoleGrantOfferAcceptInput} from '@fuzdev/fuz_app/auth/role_grant_offer_action_specs.js';

Input for role_grant_offer_accept.

offer_id

type string & $brand<"Uuid">

acting?

type string & $brand<"Uuid">

RoleGrantOfferAcceptOutput
#

auth/role_grant_offer_action_specs.ts view source

value + type

{ role_grant_id: string & $brand<"Uuid">; offer: { id: string & $brand<"Uuid">; from_actor_id: string & $brand<"Uuid">; to_account_id: string & $brand<"Uuid">; ... 12 more ...; resulting_role_grant_id: (string & $brand<...>) | null; }; superseded_offer_ids: (string & $brand<...>)[]; } import {RoleGrantOfferAcceptOutput} from '@fuzdev/fuz_app/auth/role_grant_offer_action_specs.js';

Output for role_grant_offer_accept.

role_grant_id

type string & $brand<"Uuid">

offer

type RoleGrantOfferJson

superseded_offer_ids

type (string & $brand<"Uuid">)[]

RoleGrantOfferActionOptions
#

auth/role_grant_offer_actions.ts view source

RoleGrantOfferActionOptions import type {RoleGrantOfferActionOptions} from '@fuzdev/fuz_app/auth/role_grant_offer_actions.js';

roles?

Role schema result from create_role_schema(). Defaults to builtin roles only. Drives the grantability gate: a role is offerable / revocable through this surface only when its RoleSpec.grant_paths includes 'admin' (the GRANT_PATH_ADMIN constant).

type RoleSchemaResult

default_ttl_ms?

TTL applied to newly-created offers. Defaults to ROLE_GRANT_OFFER_DEFAULT_TTL_MS.

type number

authorize?

Custom authorization for role_grant_offer_create. The default requires the caller to hold a global admin grant; the role's RoleSpec.grant_paths must include 'admin' either way. Consumers with richer policies (scope-aware, chained roles) override this. A builtin role never reaches the callback unless the caller holds a global admin and the offer is unscoped — the spine decides builtin-role offers itself, so a callback needs no builtin handling of its own.

type (auth: RequestContext, input: { to_account_id: string; role: string; scope_id: string | null; }, deps: { log: Logger; }, ctx: ActionContext): boolean | Promise<...>

auth

input

type { to_account_id: string; role: string; scope_id: string | null; }

deps

type { log: Logger; }

ctx

returns boolean | Promise<boolean>

RoleGrantOfferActorAccountMismatchError
#

auth/role_grant_offer_queries.ts view source

import {RoleGrantOfferActorAccountMismatchError} from '@fuzdev/fuz_app/auth/role_grant_offer_queries.js';

Error thrown when query_role_grant_offer_create is called with a to_actor_id that does not exist or does not belong to to_account_id. Surfaces the actor↔account binding mismatch at the boundary instead of letting the FK silently disagree with the recipient field.

inheritance

extends: Error

constructor

type new (): RoleGrantOfferActorAccountMismatchError

RoleGrantOfferActorMismatchError
#

auth/role_grant_offer_queries.ts view source

import {RoleGrantOfferActorMismatchError} from '@fuzdev/fuz_app/auth/role_grant_offer_queries.js';

Error thrown when an actor-targeted offer is being accepted by an actor other than offer.to_actor_id. Distinct from RoleGrantOfferNotFoundError (the IDOR mask): once an offer has been resolved to the recipient account, a wrong-actor accept on a same-account actor is a contract violation, not a privacy boundary — surface a specific error so the client UI can distinguish "this offer isn't for you" from "no such offer".

inheritance

extends: Error

constructor

type new (offer_id: string): RoleGrantOfferActorMismatchError

offer_id

type string

RoleGrantOfferAlreadyTerminalError
#

auth/role_grant_offer_queries.ts view source

import {RoleGrantOfferAlreadyTerminalError} from '@fuzdev/fuz_app/auth/role_grant_offer_queries.js';

Error thrown by offer-lifecycle queries when the offer is in a non-pending state (accepted / declined / retracted / superseded) and therefore not actionable. Distinct from RoleGrantOfferExpiredError — expiry has its own user-facing story ("ask the grantor to re-send") so it travels separately.

inheritance

extends: Error

constructor

type new (offer_id: string): RoleGrantOfferAlreadyTerminalError

offer_id

type string

RoleGrantOfferAndAcceptArgs
#

RoleGrantOfferBuiltinRefusedError
#

auth/role_grant_offer_queries.ts view source

import {RoleGrantOfferBuiltinRefusedError} from '@fuzdev/fuz_app/auth/role_grant_offer_queries.js';

Error thrown by query_accept_offer when the offer confers a builtin role (admin / keeper) that this accept must not mint — refusal names the rule. Carries the locked (still pending, untouched) offer so the caller can audit the refusal with the offer's role and scope.

inheritance

extends: Error

offer

type RoleGrantOffer

readonly

refusal

type BuiltinOfferRefusal

readonly

constructor

type new (offer: RoleGrantOffer, refusal: BuiltinOfferRefusal): RoleGrantOfferBuiltinRefusedError

offer

refusal

RoleGrantOfferCreateAuthorize
#

auth/role_grant_offer_actions.ts view source

RoleGrantOfferCreateAuthorize import type {RoleGrantOfferCreateAuthorize} from '@fuzdev/fuz_app/auth/role_grant_offer_actions.js';

Authorization callback for role_grant_offer_create. Returns true to allow, false to reject (handler converts to forbidden).

Provided with the fully-resolved request context and the parsed input (pre-TTL, pre-normalization). Consumers override the default to implement policies like "teacher may offer classroom_student only in classrooms they teach".

(call)

type (auth: RequestContext, input: { to_account_id: string; role: string; scope_id: string | null; }, deps: { log: Logger; }, ctx: ActionContext): boolean | Promise<...>

auth

input

type { to_account_id: string; role: string; scope_id: string | null; }

deps

type { log: Logger; }

ctx

returns boolean | Promise<boolean>

RoleGrantOfferCreateInput
#

auth/role_grant_offer_action_specs.ts view source

value + type

{ to_account_id: string & $brand<"Uuid">; role: string; to_actor_id?: (string & $brand<"Uuid">) | null | undefined; scope_kind?: string | null | undefined; scope_id?: (string & $brand<...>) | ... 1 more ... | undefined; message?: string | ... 1 more ... | undefined; acting?: (string & $brand<...>) | undefined; } import {RoleGrantOfferCreateInput} from '@fuzdev/fuz_app/auth/role_grant_offer_action_specs.js';

Input for role_grant_offer_create.

to_actor_id (optional) narrows the offer to a specific actor on the recipient account. When supplied, role_grant_offer_accept will only admit the named actor — wrong-actor accepts reject with role_grant_offer_actor_mismatch. The audit envelope's target_actor_id is stamped from this column on the create / supersede / expire / retract events. Omit (or pass null) for the account-grain default — any actor on to_account_id may accept.

to_account_id

type string & $brand<"Uuid">

role

type string

to_actor_id?

type (string & $brand<"Uuid">) | null

scope_kind?

type string | null

scope_id?

type (string & $brand<"Uuid">) | null

message?

type string | null

acting?

type string & $brand<"Uuid">

RoleGrantOfferCreateOutput
#

auth/role_grant_offer_action_specs.ts view source

value + type

{ offer: { id: string & $brand<"Uuid">; from_actor_id: string & $brand<"Uuid">; to_account_id: string & $brand<"Uuid">; to_actor_id: (string & $brand<"Uuid">) | null; ... 11 more ...; resulting_role_grant_id: (string & $brand<...>) | null; }; } import {RoleGrantOfferCreateOutput} from '@fuzdev/fuz_app/auth/role_grant_offer_action_specs.js';

Output for role_grant_offer_create.

offer

type RoleGrantOfferJson

RoleGrantOfferCreateParams
#

ui/role_grant_offers_state.svelte.ts view source

RoleGrantOfferCreateParams

type ({ to_account_id: string; to_actor_id?: string | null | undefined; role: string; message?: string | null | undefined; } & { scope_kind?: null | undefined; scope_id?: null | undefined; }) | ({ to_account_id: string; to_actor_id?: string | null | undefined; role: string; message?: string | null | undefined; } & { scope_kind: string; scope_id: string; })

import type {RoleGrantOfferCreateParams} from '@fuzdev/fuz_app/ui/role_grant_offers_state.svelte.js';

Params for creating an offer — RoleGrantOffersRpc.create and submit_create.

RoleGrantOfferDeclinedParams
#

auth/role_grant_offer_notifications.ts view source

value + type

{ offer: { id: string & $brand<"Uuid">; from_actor_id: string & $brand<"Uuid">; to_account_id: string & $brand<"Uuid">; to_actor_id: (string & $brand<"Uuid">) | null; ... 11 more ...; resulting_role_grant_id: (string & $brand<...>) | null; }; } import {RoleGrantOfferDeclinedParams} from '@fuzdev/fuz_app/auth/role_grant_offer_notifications.js';

Params for role_grant_offer_declined. The decline reason (if any) rides along inside offer.decline_reason — the DB stamps it on the offer row during decline, so a sibling reason field would just duplicate it.

offer

type RoleGrantOfferJson

RoleGrantOfferDeclineInput
#

auth/role_grant_offer_action_specs.ts view source

value + type

{ offer_id: string & $brand<"Uuid">; reason?: string | null | undefined; acting?: (string & $brand<"Uuid">) | undefined; } import {RoleGrantOfferDeclineInput} from '@fuzdev/fuz_app/auth/role_grant_offer_action_specs.js';

Input for role_grant_offer_decline.

offer_id

type string & $brand<"Uuid">

reason?

type string | null

acting?

type string & $brand<"Uuid">

RoleGrantOfferEnumerationCrossTestOptions
#

testing/cross_backend/role_grant_offer_enumeration.ts view source

RoleGrantOfferEnumerationCrossTestOptions import type {RoleGrantOfferEnumerationCrossTestOptions} from '@fuzdev/fuz_app/testing/cross_backend/role_grant_offer_enumeration.js';

Options for the offer-enumeration parity suite.

setup_test

Per-test fixture producer. Must be configured with extra_actors (≥1) so the keeper is multi-actor and the sibling-actor mismatch arm is reachable — the entrypoint passes default_cross_process_setup(handle, {extra_actors: [...]}).

type (): Promise<TestFixtureBase>

readonly
returns Promise<TestFixtureBase>

rpc_path?

RPC endpoint path. Default /api/rpc.

type string

readonly

RoleGrantOfferExpiredError
#

auth/role_grant_offer_queries.ts view source

import {RoleGrantOfferExpiredError} from '@fuzdev/fuz_app/auth/role_grant_offer_queries.js';

Error thrown when an offer's expires_at has passed. The accept path enforces this independently of the sweep — a stale offer past its expiry must not be accepted, even in the race window between expiry and the sweep stamping the audit event.

inheritance

extends: Error

constructor

type new (offer_id: string): RoleGrantOfferExpiredError

offer_id

type string

RoleGrantOfferForm
#

ui/RoleGrantOfferForm.svelte view source

import RoleGrantOfferForm from '@fuzdev/fuz_app/ui/RoleGrantOfferForm.svelte';

scope_kind?

type string | null
optional

scope_id?

type string | null
optional

to_account_id

type string

to_actor_id?

Narrow the offer to a specific actor on to_account_id. Omit (or null, the default) for the account-grain default — any actor on the recipient account may accept.

type string | null
optional default null

roles

Roles the caller may offer — caller filters upstream (default: admin-grant-path).

type string[]

on_created?

type (offer: { id: string & $brand<"Uuid">; from_actor_id: string & $brand<"Uuid">; to_account_id: string & $brand<"Uuid">; to_actor_id: (string & $brand<"Uuid">) | null; ... 11 more ...; resulting_role_grant_id: (string & $brand<...>) | null; }) => void
optional
snippet parameters

format_role?

type (role: string) => string
optional default (role: string) => role
snippet parameters
role string

RoleGrantOfferHistory
#

ui/RoleGrantOfferHistory.svelte view source

import RoleGrantOfferHistory from '@fuzdev/fuz_app/ui/RoleGrantOfferHistory.svelte';

current_actor_id

Used to label a row as sent vs received. When null, direction shows as -.

type string | null

format_actor?

type (from_actor_id: string) => string
optional default truncate_uuid
snippet parameters
from_actor_id string

format_scope?

Display label for an offer's scope. Bypasses format_scope_context when supplied — return null to fall back to a truncated uuid (or 'global' for null scope_id). Omit to use the context value directly.

optional
snippet parameters
args { scope_id: string | null; role: string; }

format_role?

type (role: string) => string
optional default (role: string) => role
snippet parameters
role string

RoleGrantOfferHistoryInput
#

auth/role_grant_offer_action_specs.ts view source

value + type

{ account_id?: (string & $brand<"Uuid">) | null | undefined; limit?: number | null | undefined; offset?: number | null | undefined; acting?: (string & $brand<"Uuid">) | undefined; } import {RoleGrantOfferHistoryInput} from '@fuzdev/fuz_app/auth/role_grant_offer_action_specs.js';

Input for role_grant_offer_history. Returns every offer involving the account in either direction (recipient or grantor), including terminal rows, newest first. account_id is admin-only.

account_id?

type (string & $brand<"Uuid">) | null

limit?

type number | null

offset?

type number | null

acting?

type string & $brand<"Uuid">

RoleGrantOfferHistoryOutput
#

auth/role_grant_offer_action_specs.ts view source

value + type

{ offers: { id: string & $brand<"Uuid">; from_actor_id: string & $brand<"Uuid">; to_account_id: string & $brand<"Uuid">; to_actor_id: (string & $brand<"Uuid">) | null; ... 11 more ...; resulting_role_grant_id: (string & $brand<...>) | null; }[]; } import {RoleGrantOfferHistoryOutput} from '@fuzdev/fuz_app/auth/role_grant_offer_action_specs.js';

Output for role_grant_offer_history.

offers

type RoleGrantOfferJson[]

RoleGrantOfferInbox
#

ui/RoleGrantOfferInbox.svelte view source

import RoleGrantOfferInbox from '@fuzdev/fuz_app/ui/RoleGrantOfferInbox.svelte';

format_actor?

Display label for from_actor_id. Defaults to a truncated uuid.

type (from_actor_id: string) => string
optional default truncate_uuid
snippet parameters
from_actor_id string

format_scope?

Display label for an offer's scope. Bypasses format_scope_context when supplied — return null to fall back to a truncated uuid (or 'global' for null scope_id). Omit to use the context value directly.

optional
snippet parameters
args { scope_id: string | null; role: string; }

format_role?

Display label for a role constant. Defaults to identity.

type (role: string) => string
optional default (role: string) => role
snippet parameters
role string

RoleGrantOfferJson
#

auth/role_grant_offer_schema.ts view source

value + type

{ id: string & $brand<"Uuid">; from_actor_id: string & $brand<"Uuid">; to_account_id: string & $brand<"Uuid">; to_actor_id: (string & $brand<"Uuid">) | null; ... 11 more ...; resulting_role_grant_id: (string & $brand<...>) | null; } import {RoleGrantOfferJson} from '@fuzdev/fuz_app/auth/role_grant_offer_schema.js';

Zod schema for client-safe role_grant offer data.

id

type string & $brand<"Uuid">

from_actor_id

type string & $brand<"Uuid">

to_account_id

type string & $brand<"Uuid">

to_actor_id

type (string & $brand<"Uuid">) | null

role

type string

scope_kind

type string | null

scope_id

type (string & $brand<"Uuid">) | null

message

type string | null

created_at

type string

expires_at

type string

accepted_at

type string | null

declined_at

type string | null

decline_reason

type string | null

retracted_at

type string | null

superseded_at

type string | null

resulting_role_grant_id

type (string & $brand<"Uuid">) | null

RoleGrantOfferListInput
#

auth/role_grant_offer_action_specs.ts view source

value + type

{ account_id?: (string & $brand<"Uuid">) | null | undefined; acting?: (string & $brand<"Uuid">) | undefined; } import {RoleGrantOfferListInput} from '@fuzdev/fuz_app/auth/role_grant_offer_action_specs.js';

Input for role_grant_offer_list. account_id is admin-only (inspect another account's inbox).

account_id?

type (string & $brand<"Uuid">) | null

acting?

type string & $brand<"Uuid">

RoleGrantOfferListOutput
#

auth/role_grant_offer_action_specs.ts view source

value + type

{ offers: { id: string & $brand<"Uuid">; from_actor_id: string & $brand<"Uuid">; to_account_id: string & $brand<"Uuid">; to_actor_id: (string & $brand<"Uuid">) | null; ... 11 more ...; resulting_role_grant_id: (string & $brand<...>) | null; }[]; } import {RoleGrantOfferListOutput} from '@fuzdev/fuz_app/auth/role_grant_offer_action_specs.js';

Output for role_grant_offer_list.

offers

type RoleGrantOfferJson[]

RoleGrantOfferNotFoundError
#

auth/role_grant_offer_queries.ts view source

import {RoleGrantOfferNotFoundError} from '@fuzdev/fuz_app/auth/role_grant_offer_queries.js';

Error thrown when an offer cannot be located for the caller. Covers both "offer does not exist" and "offer belongs to a different recipient" (IDOR guard) — the standard 404-over-403 pattern that avoids disclosing whether an offer id exists.

inheritance

extends: Error

constructor

type new (offer_id: string): RoleGrantOfferNotFoundError

offer_id

type string

RoleGrantOfferNotification
#

RoleGrantOfferNotificationWsTestOptions
#

testing/cross_backend/role_grant_offer_notification_ws.ts view source

RoleGrantOfferNotificationWsTestOptions import type {RoleGrantOfferNotificationWsTestOptions} from '@fuzdev/fuz_app/testing/cross_backend/role_grant_offer_notification_ws.js';

setup_test

Per-test fixture producer (default_cross_process_setup(handle, ...)).

type (): Promise<TestFixtureBase>

readonly
returns Promise<TestFixtureBase>

capabilities

Backend capability flags; every case gates on capabilities.ws.

type BackendCapabilities

readonly

base_url

Base URL the backend is reachable at (e.g. http://localhost:1178).

type string

readonly

ws_path

WebSocket endpoint path on the backend (e.g. /api/ws).

type string

readonly

ws_required_roles?

The roles ws_path gates its upgrade on (its required_roles), or omitted / [] for an ungated endpoint. Declare them for a backend whose socket is role-gated: revoking ROLE_ADMIN then closes the revokee's socket with WS_CLOSE_PERMISSION_REVOKED, and the revoke case asserts that close instead of the notification frame.

type readonly string[]

readonly

RoleGrantOfferOkOutput
#

auth/role_grant_offer_action_specs.ts view source

value + type

{ ok: true; } import {RoleGrantOfferOkOutput} from '@fuzdev/fuz_app/auth/role_grant_offer_action_specs.js';

Output for role_grant_offer_decline / role_grant_offer_retract.

ok

type true

RoleGrantOfferReceivedParams
#

auth/role_grant_offer_notifications.ts view source

value + type

{ offer: { id: string & $brand<"Uuid">; from_actor_id: string & $brand<"Uuid">; to_account_id: string & $brand<"Uuid">; to_actor_id: (string & $brand<"Uuid">) | null; ... 11 more ...; resulting_role_grant_id: (string & $brand<...>) | null; }; } import {RoleGrantOfferReceivedParams} from '@fuzdev/fuz_app/auth/role_grant_offer_notifications.js';

Params for role_grant_offer_received — offer delivered to its recipient.

offer

type RoleGrantOfferJson

RoleGrantOfferRetractedParams
#

auth/role_grant_offer_notifications.ts view source

value + type

{ offer: { id: string & $brand<"Uuid">; from_actor_id: string & $brand<"Uuid">; to_account_id: string & $brand<"Uuid">; to_actor_id: (string & $brand<"Uuid">) | null; ... 11 more ...; resulting_role_grant_id: (string & $brand<...>) | null; }; } import {RoleGrantOfferRetractedParams} from '@fuzdev/fuz_app/auth/role_grant_offer_notifications.js';

Params for role_grant_offer_retracted — grantor-side retraction.

offer

type RoleGrantOfferJson

RoleGrantOfferRetractInput
#

auth/role_grant_offer_action_specs.ts view source

value + type

{ offer_id: string & $brand<"Uuid">; acting?: (string & $brand<"Uuid">) | undefined; } import {RoleGrantOfferRetractInput} from '@fuzdev/fuz_app/auth/role_grant_offer_action_specs.js';

Input for role_grant_offer_retract.

offer_id

type string & $brand<"Uuid">

acting?

type string & $brand<"Uuid">

RoleGrantOfferScope
#

ui/role_grant_offers_state.svelte.ts view source

RoleGrantOfferScope

type { scope_kind?: null | undefined; scope_id?: null | undefined; } | { scope_kind: string; scope_id: string; }

import type {RoleGrantOfferScope} from '@fuzdev/fuz_app/ui/role_grant_offers_state.svelte.js';

The scope of an offer: global (both absent / null) or scoped (both set). scope_kind and scope_id are paired on the wire — a lone scope_id is an invalid_params refusal — so the pair is one union rather than two optional fields.

RoleGrantOfferSelfTargetError
#

auth/role_grant_offer_queries.ts view source

import {RoleGrantOfferSelfTargetError} from '@fuzdev/fuz_app/auth/role_grant_offer_queries.js';

Error thrown when a grantor attempts to offer a role_grant to their own account.

Enforced via a single SELECT on the grantor's actor.account_id (rather than via a CHECK constraint or a denormalized column). Resolving from the grantor side keeps the check multi-actor-correct: under multi-actor the recipient account may host many actors, but the grantor → account binding remains 1:1 by definition of actor.

inheritance

extends: Error

constructor

type new (): RoleGrantOfferSelfTargetError

RoleGrantOffersRpc
#

ui/role_grant_offers_state.svelte.ts view source

RoleGrantOffersRpc import type {RoleGrantOffersRpc} from '@fuzdev/fuz_app/ui/role_grant_offers_state.svelte.js';

Narrow RPC surface consumed by RoleGrantOffersState. Consumers adapt their typed client (e.g. a create_rpc_client Proxy) to this shape — the state class stays decoupled from the client's Result return type so tests can inject plain-function stubs.

list

type (): Promise<{ offers: { id: string & $brand<"Uuid">; from_actor_id: string & $brand<"Uuid">; to_account_id: string & $brand<"Uuid">; to_actor_id: (string & $brand<...>) | null; ... 11 more ...; resulting_role_grant_id: (string & $brand<...>) | null; }[]; }>

returns Promise<{ offers: { id: string & $brand<"Uuid">; from_actor_id: string & $brand<"Uuid">; to_account_id: string & $brand<"Uuid">; to_actor_id: (string & $brand<"Uuid">) | null; role: string; scope_kind: string | null; scope_id: (string & $brand<"Uuid">) | null; message: string | null; created_at: string; expires_at: string; accepted_at: string | null; declined_at: string | null; decline_reason: string | null; retracted_at: string | null; superseded_at: string | null; resulting_role_grant_id: (string & $brand<"Uuid">) | null; }[]; }>

history

type (options?: { limit?: number | undefined; offset?: number | undefined; } | undefined): Promise<{ offers: { id: string & $brand<"Uuid">; from_actor_id: string & $brand<"Uuid">; ... 13 more ...; resulting_role_grant_id: (string & $brand<...>) | null; }[]; }>

options?

type { limit?: number | undefined; offset?: number | undefined; }
optional
returns Promise<{ offers: { id: string & $brand<"Uuid">; from_actor_id: string & $brand<"Uuid">; to_account_id: string & $brand<"Uuid">; to_actor_id: (string & $brand<"Uuid">) | null; role: string; scope_kind: string | null; scope_id: (string & $brand<"Uuid">) | null; message: string | null; created_at: string; expires_at: string; accepted_at: string | null; declined_at: string | null; decline_reason: string | null; retracted_at: string | null; superseded_at: string | null; resulting_role_grant_id: (string & $brand<"Uuid">) | null; }[]; }>

create

type (params: RoleGrantOfferCreateParams): Promise<{ offer: { id: string & $brand<"Uuid">; from_actor_id: string & $brand<"Uuid">; to_account_id: string & $brand<...>; ... 12 more ...; resulting_role_grant_id: (string & $brand<...>) | null; }; }>

params

returns Promise<{ offer: { id: string & $brand<"Uuid">; from_actor_id: string & $brand<"Uuid">; to_account_id: string & $brand<"Uuid">; to_actor_id: (string & $brand<"Uuid">) | null; role: string; scope_kind: string | null; scope_id: (string & $brand<"Uuid">) | null; message: string | null; created_at: string; expires_at: string; accepted_at: string | null; declined_at: string | null; decline_reason: string | null; retracted_at: string | null; superseded_at: string | null; resulting_role_grant_id: (string & $brand<"Uuid">) | null; }; }>

accept

type (offer_id: string): Promise<{ role_grant_id: string; offer: { id: string & $brand<"Uuid">; from_actor_id: string & $brand<"Uuid">; to_account_id: string & $brand<"Uuid">; ... 12 more ...; resulting_role_grant_id: (string & $brand<...>) | null; }; superseded_offer_ids: string[]; }>

offer_id

type string
returns Promise<{ role_grant_id: string; offer: { id: string & $brand<"Uuid">; from_actor_id: string & $brand<"Uuid">; to_account_id: string & $brand<"Uuid">; to_actor_id: (string & $brand<"Uuid">) | null; role: string; scope_kind: string | null; scope_id: (string & $brand<"Uuid">) | null; message: string | null; created_at: string; expires_at: string; accepted_at: string | null; declined_at: string | null; decline_reason: string | null; retracted_at: string | null; superseded_at: string | null; resulting_role_grant_id: (string & $brand<"Uuid">) | null; }; superseded_offer_ids: string[]; }>

decline

type (offer_id: string, reason?: string | null | undefined): Promise<{ ok: true; }>

offer_id

type string

reason?

type string | null
optional
returns Promise<{ ok: true; }>

retract

type (offer_id: string): Promise<{ ok: true; }>

offer_id

type string
returns Promise<{ ok: true; }>

RoleGrantOffersState
#

ui/role_grant_offers_state.svelte.ts view source

import {RoleGrantOffersState} from '@fuzdev/fuz_app/ui/role_grant_offers_state.svelte.js';

list

type AsyncSlot<void, string>

readonly

list_history

type AsyncSlot<void, string>

readonly

create

type AsyncSlot<RoleGrantOfferJson, string>

readonly

accept

type AsyncSlot<void, string>

readonly

decline

type AsyncSlot<void, string>

readonly

retract

type AsyncSlot<void, string>

readonly

incoming

Pending offers for the current account, soonest-expiring first.

type RoleGrantOfferJson[]

readonly $derived.by

outgoing

Pending offers from the current actor, newest-created first.

type RoleGrantOfferJson[]

readonly $derived.by

history

Every offer known to this state, newest-created first. Feeds the history view.

type RoleGrantOfferJson[]

readonly $derived.by

incoming_count

type number

readonly $derived

constructor

type new (options: RoleGrantOffersStateOptions): RoleGrantOffersState

options

fetch

Seed the cache with the recipient-side pending inbox.

type (): Promise<void>

returns Promise<void>

fetch_history

Seed both-directions history (includes terminal rows).

type (options?: { limit?: number | undefined; offset?: number | undefined; } | undefined): Promise<void>

options?

type { limit?: number | undefined; offset?: number | undefined; }
optional
returns Promise<void>

submit_create

Issue a new offer; merges the returned offer into the cache on success.

to_actor_id (optional) narrows the offer to a specific actor on to_account_id; omit / null for the account-grain default (any actor on the recipient account may accept). A scoped offer passes scope_kind and scope_id together; an unpaired scope (reachable from untyped callers) fails with GRANT_SCOPE_PAIRED_MESSAGE without an RPC.

type (params: RoleGrantOfferCreateParams): Promise<{ id: string & $brand<"Uuid">; from_actor_id: string & $brand<"Uuid">; to_account_id: string & $brand<...>; ... 12 more ...; resulting_role_grant_id: (string & $brand<...>) | null; } | undefined>

params

returns Promise<RoleGrantOfferJson | undefined>

submit_accept

Accept an offer; stamps it terminal in the cache and drops any siblings the server superseded.

type (offer_id: string): Promise<void>

offer_id

type string
returns Promise<void>

submit_decline

type (offer_id: string, reason?: string | null | undefined): Promise<void>

offer_id

type string

reason?

type string | null
optional
returns Promise<void>

submit_retract

type (offer_id: string): Promise<void>

offer_id

type string
returns Promise<void>

subscribe

Wire a notification subscription. The handler dispatches each matching notification into apply_notification; the returned disposer unwires.

type (subscribe_fn: RoleGrantOfferSubscribe): () => void

subscribe_fn

returns () => void

apply_notification

Reduce a single WS notification into the cache. Exposed so consumers wiring their WS receiver directly (without subscribe) and tests can drive the reducer without allocating a subscription.

type (notification: RoleGrantOfferNotification): void

notification

returns void

mutates

  • this

reset

Clear the cache and reset every slot.

type (): void

returns void

RoleGrantOffersStateOptions
#

ui/role_grant_offers_state.svelte.ts view source

RoleGrantOffersStateOptions import type {RoleGrantOffersStateOptions} from '@fuzdev/fuz_app/ui/role_grant_offers_state.svelte.js';

rpc

type RoleGrantOffersRpc

account_id

Reactive accessor for the current account id; returns null when logged out.

type (): string | null

returns string | null

actor_id

Reactive accessor for the current actor id — required to classify offers as outgoing. Returns null when unknown.

type (): string | null

returns string | null

RoleGrantOfferSubscribe
#

ui/role_grant_offers_state.svelte.ts view source

RoleGrantOfferSubscribe import type {RoleGrantOfferSubscribe} from '@fuzdev/fuz_app/ui/role_grant_offers_state.svelte.js';

Subscription primitive — consumer wires their WS receiver; returns a disposer.

(call)

type (handler: (notification: RoleGrantOfferNotification) => void): () => void

handler

type (notification: RoleGrantOfferNotification) => void
returns () => void

RoleGrantOfferSupersedeParams
#

auth/role_grant_offer_notifications.ts view source

value + type

{ offer: { id: string & $brand<"Uuid">; from_actor_id: string & $brand<"Uuid">; to_account_id: string & $brand<"Uuid">; to_actor_id: (string & $brand<"Uuid">) | null; ... 11 more ...; resulting_role_grant_id: (string & $brand<...>) | null; }; reason: "sibling_accepted" | ... 1 more ... | "scope_destroyed"; cause_id:... import {RoleGrantOfferSupersedeParams} from '@fuzdev/fuz_app/auth/role_grant_offer_notifications.js';

Params for role_grant_offer_supersede. Fires to the grantor's sockets when their pending offer is obsoleted — either by a sibling accept (reason: 'sibling_accepted'), by revoke of the resulting role_grant (reason: 'role_grant_revoked'), or by deletion of the parent scope row the offer was bound to (reason: 'scope_destroyed'). cause_id points at the accepted offer id, the revoked role_grant id, or the destroyed scope row id respectively.

offer

type RoleGrantOfferJson

reason

type "sibling_accepted" | "role_grant_revoked" | "scope_destroyed"

cause_id

type string & $brand<"Uuid">

RoleGrantParticipationCrossTestOptions
#

testing/cross_backend/role_grant_participation.ts view source

RoleGrantParticipationCrossTestOptions import type {RoleGrantParticipationCrossTestOptions} from '@fuzdev/fuz_app/testing/cross_backend/role_grant_participation.js';

Options for the role-gated-participation success-path parity suite.

setup_test

Per-test fixture producer (in-process or cross-process).

type (): Promise<TestFixtureBase>

readonly
returns Promise<TestFixtureBase>

rpc_path?

RPC endpoint path. Default /api/rpc.

type string

readonly

RoleGrantRevokeInput
#

auth/role_grant_offer_action_specs.ts view source

value + type

{ actor_id: string & $brand<"Uuid">; role_grant_id: string & $brand<"Uuid">; reason?: string | null | undefined; acting?: (string & $brand<"Uuid">) | undefined; } import {RoleGrantRevokeInput} from '@fuzdev/fuz_app/auth/role_grant_offer_action_specs.js';

Input for role_grant_revoke. Admin-only mutation that revokes an active role_grant on a target actor. actor_id is the natural key — role_grants are actor-scoped, and the admin UI reads row.actor.id straight from the listing. Deriving actor_id from account_id would collapse under multi-actor accounts.

actor_id

type string & $brand<"Uuid">

role_grant_id

type string & $brand<"Uuid">

reason?

type string | null

acting?

type string & $brand<"Uuid">

RoleGrantRevokeOutput
#

auth/role_grant_offer_action_specs.ts view source

value + type

{ ok: true; revoked: true; } import {RoleGrantRevokeOutput} from '@fuzdev/fuz_app/auth/role_grant_offer_action_specs.js';

Output for role_grant_revoke.

ok

type true

revoked

type true

RoleGrantRevokeParams
#

auth/role_grant_offer_notifications.ts view source

value + type

{ role_grant_id: string & $brand<"Uuid">; role: string; scope_id: (string & $brand<"Uuid">) | null; reason: string | null; } import {RoleGrantRevokeParams} from '@fuzdev/fuz_app/auth/role_grant_offer_notifications.js';

Params for role_grant_revoke. Delivered to the revokee's sockets when one of their active role_grants is revoked. Flat wire shape — revoked_by is admin-UI-visible but deliberately omitted here (the revokee doesn't need to learn the admin's identity). Target account is implicit in the send target.

role_grant_id

type string & $brand<"Uuid">

role

type string

scope_id

type (string & $brand<"Uuid">) | null

reason

type string | null

RoleGrantRoleGrammarCrossTestOptions
#

testing/cross_backend/role_grant_role_grammar.ts view source

RpcPathCrossSuiteOptions import type {RoleGrantRoleGrammarCrossTestOptions} from '@fuzdev/fuz_app/testing/cross_backend/role_grant_role_grammar.js';

Options for the role-grammar suite (the standard RPC-dispatched shape).

rpc_path?

RPC endpoint path the methods are mounted on. Default /api/rpc.

type string

readonly

setup_test

Per-test fixture-producing function (fresh keeper + db per call).

type (): Promise<TestFixtureBase>

readonly
returns Promise<TestFixtureBase>

RoleGrantSummaryJson
#

auth/account_schema.ts view source

value + type

{ id: string & $brand<"Uuid">; role: string; scope_kind: string | null; scope_id: (string & $brand<"Uuid">) | null; created_at: string; expires_at: string | null; granted_by: (string & $brand<...>) | null; } import {RoleGrantSummaryJson} from '@fuzdev/fuz_app/auth/account_schema.js';

Zod schema for the role_grant summary returned in admin account listings.

id

type string & $brand<"Uuid">

role

type string

scope_kind

type string | null

scope_id

type (string & $brand<"Uuid">) | null

created_at

type string

expires_at

type string | null

granted_by

type (string & $brand<"Uuid">) | null

RoleName
#

RoleSchemaResult
#

auth/role_schema.ts view source

RoleSchemaResult import type {RoleSchemaResult} from '@fuzdev/fuz_app/auth/role_schema.js';

The result of create_role_schema — a Zod schema and spec map for all roles.

Role

Zod schema that validates role strings. Use at I/O boundaries (grant endpoint, role_grant queries).

type ZodType<string, unknown, $ZodTypeInternals<string, unknown>>

role_specs

Specs for every role (builtins + app-defined). Keyed by role name.

type ReadonlyMap<string, RoleSpec>

RoleSpec
#

auth/role_schema.ts view source

RoleSpec import type {RoleSpec} from '@fuzdev/fuz_app/auth/role_schema.js';

Configuration for a role.

Each role declares the credential types its holders must use, the scope kinds it applies to, and the grant paths through which it can be granted. Every cross-axis field is an open-registry string array — required_credential_types against create_credential_type_schema, applicable_scope_kinds against create_scope_kind_schema, grant_paths against create_grant_path_schema. Pass the registry results to create_role_schema and every entry is checked at construction time.

Empty arrays carry meaning:

  • required_credential_types: [] — any authenticated credential type may exercise the role (the default for app-defined roles).
  • applicable_scope_kinds: [] — the role applies at the global scope only (no scope_kind / scope_id set on its role_grants). This is the default for app-defined roles; consumers add scope kinds explicitly.
  • grant_paths: [] — the role has no grant path declared in this registry; it is unreachable through admin / self-service / system flows. Only useful for diagnostic snapshotting.

Builtins (keeper, admin) ship preconfigured in builtin_role_specs_by_name.

name

Unique role name. Must match RoleName — its regex and length cap; collisions with builtins throw.

type string

description?

Admin-UI-facing copy describing the role's intent.

type string

required_credential_types?

Credential types whose holders are permitted to exercise this role. Each entry is checked at construction time against the credential_types registry passed to create_role_schema. Empty array = any authenticated credential type.

type readonly string[]

applicable_scope_kinds?

Scope kinds at which this role's role_grants may be granted. Each entry is checked at construction time against the scope_kinds registry passed to create_role_schema. Empty array = global only. v1 keeps this informative-only (no INSERT-time enforcement).

type readonly string[]

grant_paths?

Grant paths through which this role can be granted. Each entry is checked at construction time against the grant_paths registry passed to create_role_schema. Drives downstream defaults:

  • admin_actions.grantable_roles ⊇ {role : 'admin' ∈ grant_paths}
  • self_service_role_actions default eligibility ⊇ {role : 'self_service' ∈ grant_paths}

Empty array = role is not granted via any registered path (only exists for diagnostic / future use).

type readonly string[]

RoundTripTestOptions
#

testing/round_trip.ts view source

RoundTripTestOptions import type {RoundTripTestOptions} from '@fuzdev/fuz_app/testing/round_trip.js';

setup_test

Per-test fixture-producing function. describe_round_trip_validation invokes this once in beforeAll (per-describe cadence — see module docstring) to share a single bootstrapped keeper + accounts across every route case.

type (): Promise<TestFixtureBase>

returns Promise<TestFixtureBase>

surface_source

App surface (with route specs) for route iteration. Constructed in TS by the consumer; same shape for in-process and cross-process tests.

type AppSurfaceSpec

skip_routes?

Routes to skip, in 'METHOD /path' format.

type string[]

input_overrides?

Override generated bodies for specific routes ('METHOD /path' → body).

type Map<string, Record<string, unknown>>

success_fixtures?

Success-case fixtures for routes whose populated success body the generic nil-id input can't reach — referential REST routes whose path params / body must point at existing rows. Maps 'METHOD /path' to an async factory that receives the per-test fixture (so it can seed the referenced state) and returns {url?, body?}: an explicit resolved url (when the factory built it from the ids it just seeded) and/or a request body. Omit url to fall back to the generated valid path.

Distinct from input_overrides (body-only, accepts a valid error envelope): a success_fixtures entry asserts a 2xx response and validates it against the route's output schema — the success-shape parity check the nil-id round-trip can't perform.

type Map<string, (fixture: TestFixtureBase) => Promise<{ url?: string | undefined; body?: Record<string, unknown> | undefined; }>>

RouteAuth
#

http/auth_shape.ts view source

value + type

{ account: "optional" | "none" | "required"; actor: "optional" | "none" | "required"; roles?: readonly string[] | undefined; credential_types?: readonly string[] | undefined; required_scope?: string | undefined; } import {RouteAuth} from '@fuzdev/fuz_app/http/auth_shape.js';

The canonical auth shape used by both ActionSpec.auth and RouteSpec.auth.

Cross-axis registry invariants enforced via .superRefine:

  1. Roles imply actor. roles?.length ⟹ actor === 'required'. Role checks read the actor's role_grants, so a role-gated spec without a resolved actor would have nothing to check.
  2. No accountless actors yet. account === 'none' && actor !== 'none' is invalid in v1. The credential resolver always binds account before actor today; agent-token / group-actor credentials will lift this.
  3. Unrestricted is leaf. account === 'none' && actor === 'none' ⟹ no roles, no credential_types (nothing left to gate). The same rule covers required_scope, but enforcing it needs the capability vocabulary from auth/, so it lives with the other required_scope check in fuz_auth_guard_resolver — which is also the only site every route spec traverses, since the route pipeline never parses this schema.

Invariant 2 — the `actor !== 'none' ⟺ input or query declares acting?: ActingActor` biconditional — needs introspection of the spec's input/query schemas, so it is checked at registration time, not on this schema. See assert_route_auth_acting_biconditional below.

account

type "optional" | "none" | "required"

actor

type "optional" | "none" | "required"

roles?

type readonly string[]

credential_types?

type readonly string[]

required_scope?

The capability a narrowed api token's scope must admit to reach this route, in the <section>:<id> vocabulary a denial reports back as required_scope:

  • surface:<name> — a non-RPC surface, refused to every narrowed token whatever its method list says (token scoping's rule that a narrowed token is RPC-only). surface:audit_stream, surface:fact_bare, and surface:app_surface are the spine's route-spec ones; a consumer names its own without registering it.
  • rpc:<method> — one action method, refused unless the token lists it. What a route bridged from an ActionSpec declares: it carries the method's identity but never reaches the dispatcher's per-method gate.

Declaring it makes the guard resolver mount the refusal ahead of the role gate, instead of the route hand-rolling the check in its handler (which the auth phase has already role-gated by then, so a narrowed token whose account lacks the role would be told about the role rather than about the scope).

Opt-in rather than blanket: the account REST routes cross no boundary, and the RPC endpoint runs its own per-method check inside the dispatcher — and both are route specs too.

Typed as a plain string here to keep http/ from importing auth/; the resolver parses it at registration and throws on a malformed one.

type string

RouteAuthCategory
#

http/surface_query.ts view source

RouteAuthCategory

type "none" | "authenticated" | "optional" | "keeper" | `role:${string}` | `credential:${string}` | "other"

import type {RouteAuthCategory} from '@fuzdev/fuz_app/http/surface_query.js';

Categorize a RouteAuth into one of the legacy auth buckets.

Returns:

  • 'none' for fully public routes (account === 'none' && actor === 'none')
  • 'keeper' when credential_types includes 'daemon_token'
  • 'role:<name>' for each role declared on auth.roles (multi-role specs are emitted multiple times; callers that need single-bucket grouping should pre-collapse)
  • 'credential:<type>' for each channel a role-less route's credential_types admits — the session-only gates (POST /logout, POST /password, the account_* credential-lifecycle actions), plus any channel a consumer registers
  • 'authenticated' for account === 'required' without role / credential gate
  • 'optional' when either axis is 'optional' and no other bucket fits
  • 'other' as a last-resort bucket for shapes that don't match above

Keeper is tested before the role bucket and the general credential bucket after it, which is not an inconsistency: keeper is an authority *level* — a ceiling no session or api token reaches — so it names the route, while a session gate is a *channel* restriction layered over whatever authority the route already declares. So the audit SSE stream, gated to the session channel and to the admin role, buckets as role:admin.

RouteContext
#

http/route_spec.ts view source

RouteContext import type {RouteContext} from '@fuzdev/fuz_app/http/route_spec.js';

Per-request deps provided by the framework to route handlers.

Audit writes and other rollback-resilient fire-and-forget calls run through AppDeps.audit.emit(ctx, input) (see auth/audit_emitter.ts), which captures the pool inside its closure — handlers can never accidentally write audits against the request transaction.

Routes whose body manages its own transaction (signup, bootstrap) declare transaction: false on the spec, which makes route.db the pool — they reach for it directly.

db

Transaction-scoped when RouteSpec.transaction is true (the default for non-GET); pool-level otherwise.

type Db

pending_effects

Eager fire-and-forget queue — push the in-flight Promise<void> for pool writes already running (audit emits, api-token usage tracking). The flush middleware drains via flush_pending_effects after the handler returns.

type Promise<void>[]

post_commit_effects

Deferred post-commit thunks — do not push directly; reach for emit_after_commit(ctx, fn) from http/pending_effects.ts. The flush middleware invokes each thunk after the handler (and any wrapping db.transaction) returns, closing the microtask-ordering window that an eager Promise.resolve().then(fn) leaves open inside the transaction.

type (() => void | Promise<void>)[]

RouteErrorSchemas
#

http/error_schemas.ts view source

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

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

import type {RouteErrorSchemas} from '@fuzdev/fuz_app/http/error_schemas.js';

Error schema map — maps HTTP status codes to Zod schemas.

Used on RouteSpec.errors and internally by derive_error_schemas.

[key: number]

type ZodType<unknown, unknown, $ZodTypeInternals<unknown, unknown>> | undefined

RouteFactoryDeps
#

auth/deps.ts view source

RouteFactoryDeps import type {RouteFactoryDeps} from '@fuzdev/fuz_app/auth/deps.js';

Capabilities for route spec factories.

AppDeps without db — route handlers receive database connections via RouteContext, so factories don't capture a pool-level Db.

password

Password hashing operations. Use argon2_password_deps in production.

type PasswordHashDeps

read_secure_file

Hardened secret-file read — used for the bootstrap token (the file that mints the keeper account). Production wiring passes the runtime's read_secure_file (FsSecureReadDeps), which rejects symlinks, non-regular files, group/other-accessible modes, and oversized files; both the boot-time availability probe and the request-time read go through this one capability so the probe can never be laxer than the read it gates.

type (path: string): Promise<Uint8Array<ArrayBufferLike>>

path

type string
returns Promise<Uint8Array<ArrayBufferLike>>

delete_file

Delete a file.

type (path: string): Promise<void>

path

type string
returns Promise<void>

keyring

HMAC-SHA256 cookie signing keyring.

type Keyring

log

Structured logger instance.

type Logger

audit

Bound audit emitter. Closes over the pool, its registered listeners, and the optional AuditLogConfig. Built once at backend assembly via create_audit_emitter so handlers can never accidentally write audits against the request transaction — there is no pool slot on the handler context.

type AuditEmitter

connection_closer

Closes live connections — WebSocket and SSE — when a credential is revoked. Every revocation handler queues its close on this (queue_connection_close), so it must reach every transport of the backend: create_app_backend creates it empty, and create_app_server adds each WebSocket transport it mounts and its audit stream registry. A transport mounted by hand is added through the connection_closer option of register_ws_endpoint / create_audit_log_sse.

type RealtimeCloser

fact_store?

Optional content-addressed byte store. Present only on backends that serve binary content (facts) — minimal consumers leave it unset. The consumer constructs a PgFactStore (db/fact_store.ts) over its facts directory (disk_root + fs) at its own backend assembly and assigns it here; create_app_backend stays facts-agnostic.

type FactStore

RouteHandler
#

http/route_spec.ts view source

RouteHandler import type {RouteHandler} from '@fuzdev/fuz_app/http/route_spec.js';

Route handler function — receives the Hono context and a RouteContext with per-request deps (db, pending_effects).

TypeScript allows fewer params, so handlers that don't need route can use (c) => ... without changes.

(call)

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

c

type Context<any, any, {}>

route

returns Response | Promise<Response>

RouteMethod
#

http/route_spec.ts view source

RouteMethod

type "GET" | "POST" | "PUT" | "DELETE" | "PATCH"

import type {RouteMethod} from '@fuzdev/fuz_app/http/route_spec.js';

HTTP methods supported by route specs.

routes_by_auth_type
#

RouteSpec
#

http/route_spec.ts view source

RouteSpec import type {RouteSpec} from '@fuzdev/fuz_app/http/route_spec.js';

A single route definition — the unit of the surface map.

input and output schemas align with SAES ActionSpec naming. Use z.null() for routes with no request body (GET, DELETE without body).

method

type RouteMethod

path

type string

auth

type RouteAuth

handler

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

c

type Context<any, any, {}>

route

returns Response | Promise<Response>

description

type string

params?

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

REST-only — actions dispatch through a single JSON-RPC endpoint and encode everything in input, so params doesn't appear on ActionSpec.

type ZodObject<$ZodLooseShape, $strip>

query?

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

type ZodObject<$ZodLooseShape, $strip>

input

Request body schema. Use z.null() for routes with no body.

type ZodType<unknown, unknown, $ZodTypeInternals<unknown, unknown>>

output

Success response body schema.

type ZodType<unknown, unknown, $ZodTypeInternals<unknown, unknown>>

raw_body?

Marks a route whose request and/or response carries raw bytes or a streaming protocol rather than JSON — git smart-HTTP, file-store binary uploads/downloads, raw internal callbacks. Disambiguates the overloaded input: z.null(), which otherwise can't distinguish "no body" (GET /health) from "raw bytes" (a binary upload).

A raw_body POST takes any Content-Type a browser can't send cross-origin without a preflight; every other POST route requires application/json, bodyless or not (415 unsupported_content_type otherwise — see http/simple_request.ts). The schema-driven round-trip test suite auto-skips raw_body routes (it can neither synthesize a meaningful body nor assert a JSON output shape), so consumers don't hand-maintain a skip_routes entry per binary route. Also surfaces in AppSurfaceRoute so generated docs render "raw" instead of a misleading null body.

type boolean

mints_credential?

Marks a route that issues a credential — a session cookie — while declaring no auth itself: login, signup, bootstrap. A forged cross-site request to one is login CSRF, so it must take the origin check, and assembly refuses it outside the auth middleware's scope, where no origin carve-out can reach (assert_credential_routes_in_auth_scope). A route that reads a credential (auth.account not 'none') is held there without the mark.

type boolean

reads_credential?

Marks a route that reads the resolved credential in its handler while declaring no auth gate — it answers an anonymous caller itself instead of taking the dispatcher's 401 (account status, the cell-scoped fact read, the JSON-RPC endpoint whose dispatcher gates each action). Outside the auth middleware's scope no session or bearer parsing runs and it would see every caller as anonymous, so assembly refuses it there (assert_credential_routes_in_auth_scope), as it does a route whose auth.account is not 'none'.

type boolean

rate_limit?

Rate limit key type — declares what this route's rate limiter is keyed on.

When set, 429 (RateLimitError) is auto-derived in derive_error_schemas. The actual RateLimiter instance is still wired imperatively in the handler — this field is metadata for surface introspection and policy invariants.

type RateLimitKey

errors?

Handler-specific error response schemas keyed by HTTP status code.

Middleware errors (auth 401/403, validation 400, rate limit 429) are auto-derived from auth, input, and rate_limit. Declare handler-specific errors here (e.g., 404 for not-found, 409 for conflicts).

Explicit entries override auto-derived ones for the same status code.

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

transaction?

Whether to wrap the handler in a database transaction.

When omitted, defaults are derived from the HTTP method:

  • GET → false (read-only, no transaction)
  • All others (POST, PUT, DELETE, PATCH) → true

Set explicitly to override the default (e.g., false for a POST that manages its own transaction like signup).

type boolean

rpc_action
#

actions/action_rpc.ts view source

<TSpec extends RequestResponseActionSpec>(spec: TSpec, handler: HandlerForSpec<TSpec>): RpcAction import {rpc_action} from '@fuzdev/fuz_app/actions/action_rpc.js';

Pair a spec with a handler while preserving per-method input/output types and selecting the narrowest ctx.auth shape the spec literal admits.

Constructing {spec, handler} literals widens handler to ActionHandler<any, any>, so spec/handler drift (renamed Zod schema, output field removal, input shape change) slips past the typechecker. rpc_action(spec, handler) binds the handler signature to (input: z.infer<spec.input>, ctx) => z.infer<spec.output> via the generic spec parameter — drift surfaces at the call site.

The ctx.auth narrowing follows the spec's auth.account / auth.actor literals (see HandlerForSpec): an actor-implying spec gets ctx.auth: RequestActorContext; an account-grain spec gets ctx.auth: RequestContext; everything else stays `ctx.auth: RequestContext | null`. Handlers can rely on the dispatcher's runtime guarantee without a manual narrowing call.

Fits fuz_app's factory-closure pattern (handlers close over grantable_roles, app_settings ref, notification_sender, etc.). zzz uses a different shape — a codegen-keyed Record<Method, Handler> map typed via generated ActionInputs/ActionOutputs — which works when handlers are pure (no closure state) and specs are codegen-enumerated. fuz_app's admin + role-grant-offer actions have neither, so per-pair typing at the registration site is the right fit.

Spec-literal preservation is load-bearing: declare specs with satisfies RequestResponseActionSpec (canonical) so auth.actor keeps its 'required' / 'none' literal type. A spec typed directly as RequestResponseActionSpec widens the axes to AuthAxisState and the handler defaults to the loosest tier — sound, but loses the ergonomic narrowing.

spec

type TSpec

handler

type HandlerForSpec<TSpec>

returns

RpcAction

generics

rpc_action<TSpec extends RequestResponseActionSpec>
TSpec

examples

// actor-implying spec → ctx.auth: RequestActorContext rpc_action(role_grant_revoke_action_spec, async (input, ctx) => { const revoker_id = ctx.auth.actor.id; // no narrowing needed }); // account-grain spec → ctx.auth: RequestContext (actor: null) rpc_action(account_verify_action_spec, (_input, ctx) => { return to_session_account(ctx.auth.account); // no narrowing needed });

rpc_call
#

testing/rpc_helpers.ts view source

(args: RpcCallArgs): Promise<RpcCallResult> import {rpc_call} from '@fuzdev/fuz_app/testing/rpc_helpers.js';

One-shot JSON-RPC call over a Hono app.

Merges sensible defaults (host, origin, Content-Type) under caller-provided headers, fires POST (default) or GET, parses the envelope, and returns a discriminated result.

args

returns

Promise<RpcCallResult>

throws

  • Error - if the response body is neither a valid `JsonrpcResponse` nor `JsonrpcErrorResponse` envelope — protocol-level failures the caller should never tolerate. All JSON-RPC errors come back via `{ok: false, error}` so assertions can focus on `error.code` / `error.data.reason`.

rpc_call_for_spec
#

testing/rpc_helpers.ts view source

<TSpec extends RequestResponseActionSpec>(args: RpcCallForSpecArgs<TSpec>): Promise<RpcCallResultForSpec<TSpec>> import {rpc_call_for_spec} from '@fuzdev/fuz_app/testing/rpc_helpers.js';

Typed wrapper over rpc_call — binds params to z.infer<spec.input> and the success result to z.infer<spec.output> via the generic.

Success results are validated at runtime against spec.output (same contract as rpc_call_typed); a mismatch throws. Error responses come back on the discriminated {ok: false, error} branch — use this for happy-path + denial-path assertions where the error data.reason shape is still asserted manually. For adversarial input tests that send malformed params, use the untyped rpc_call.

args

returns

Promise<RpcCallResultForSpec>

generics

rpc_call_for_spec<TSpec extends RequestResponseActionSpec>
TSpec

throws

  • Error - if the success `result` does not parse against `spec.output`, or if `rpc_call` itself throws on an envelope violation.

rpc_call_non_browser
#

testing/rpc_helpers.ts view source

(args: Omit<RpcCallArgs, "suppress_default_origin">): Promise<RpcCallResult> import {rpc_call_non_browser} from '@fuzdev/fuz_app/testing/rpc_helpers.js';

Same as rpc_call but without the default origin header. Use for bearer-auth probes: bearer_auth discards the token when Origin or Referer is present (browser context), so a bearer probe via rpc_call would short-circuit to 401 before the token is ever validated.

Equivalent to rpc_call({...args, suppress_default_origin: true}).

args

type Omit<RpcCallArgs, "suppress_default_origin">

returns

Promise<RpcCallResult>

rpc_call_typed
#

testing/rpc_helpers.ts view source

<T>(args: RpcCallArgs, output_schema: ZodType<T, unknown, $ZodTypeInternals<T, unknown>>): Promise<T> import {rpc_call_typed} from '@fuzdev/fuz_app/testing/rpc_helpers.js';

Same as rpc_call but parses the success result through the given output schema and returns typed data. Envelope-level failures or error responses throw — use the untyped rpc_call for tests that need to assert on specific error shapes.

args

output_schema

type ZodType<T, unknown, $ZodTypeInternals<T, unknown>>

returns

Promise<T>

generics

rpc_call_typed<T>
T

throws

  • Error - if the response is a JSON-RPC error, if `rpc_call` throws on an envelope violation, or if the result fails `output_schema.safeParse`.

RpcAction
#

actions/action_rpc.ts view source

RpcAction import type {RpcAction} from '@fuzdev/fuz_app/actions/action_rpc.js';

An RPC action — combines an action spec with its handler.

The spec defines the contract (method, auth, schemas, side effects). The handler implements the behavior.

spec

type RequestResponseActionSpec

handler

type (input: any, ctx: ActionContext): any

input

type any

ctx

returns any

RpcAttackSurfaceOptions
#

RpcCallApp
#

testing/rpc_helpers.ts view source

RpcCallApp

type { request: (input: string, init: RequestInit) => Response | Promise<Response>; } | (RpcTestTransport)

import type {RpcCallApp} from '@fuzdev/fuz_app/testing/rpc_helpers.js';

App shape accepted by rpc_call. Either a Hono-like object with a .request(input, init) method (in-process TestApp.app directly) or a bare RpcTestTransport callable (cross-process fixture.transport).

RpcCallArgs
#

testing/rpc_helpers.ts view source

RpcCallArgs import type {RpcCallArgs} from '@fuzdev/fuz_app/testing/rpc_helpers.js';

Arguments for rpc_call.

app

Hono-like app or bare RpcTestTransport callable.

type RpcCallApp

path

RPC endpoint path, e.g. '/api/rpc'.

type string

method

JSON-RPC method name.

type string

params?

Params for the call. Omit (or pass undefined) for parameterless (z.void()) methods — the helper drops params from the envelope either way. See create_rpc_post_init for the null-stripping affordance and JSON-RPC 2.0 §4.2's prohibition on params: null.

type unknown

headers?

Extra request headers (session cookie, bearer, etc.). Overrides defaults.

type Record<string, string>

id?

Request id. Defaults to 'test'.

type string | number

verb?

HTTP verb — 'POST' (default) or 'GET' for side_effects: false methods.

type "POST" | "GET"

suppress_default_origin?

Suppress the default origin header. Required for bearer-auth paths: bearer_auth discards the token when Origin or Referer is present (browser context), so probing it via rpc_call needs this flag — or use rpc_call_non_browser, which sets it for you.

type boolean

RpcCallForSpecArgs
#

testing/rpc_helpers.ts view source

RpcCallForSpecArgs<TSpec>

type Omit<RpcCallArgs, "method" | "params"> & { spec: TSpec; params: output<TSpec["input"]>; }

import type {RpcCallForSpecArgs} from '@fuzdev/fuz_app/testing/rpc_helpers.js';

Arguments for rpc_call_for_spec. spec replaces the loose method field.

generics

RpcCallForSpecArgs<TSpec extends RequestResponseActionSpec>
TSpec

id?

Request id. Defaults to 'test'.

type string | number

path

RPC endpoint path, e.g. '/api/rpc'.

type string

verb?

HTTP verb — 'POST' (default) or 'GET' for side_effects: false methods.

type "POST" | "GET"

headers?

Extra request headers (session cookie, bearer, etc.). Overrides defaults.

type Record<string, string>

app

Hono-like app or bare RpcTestTransport callable.

type RpcCallApp

suppress_default_origin?

Suppress the default origin header. Required for bearer-auth paths: bearer_auth discards the token when Origin or Referer is present (browser context), so probing it via rpc_call needs this flag — or use rpc_call_non_browser, which sets it for you.

type boolean

spec

Action spec whose method drives the envelope and whose input/output types pin params + result.

type TSpec

params

Params, typed against spec.input.

type output<TSpec["input"]>

RpcCallResult
#

testing/rpc_helpers.ts view source

RpcCallResult

type { ok: true; status: number; headers: Record<string, string>; result: unknown; } | { ok: false; status: number; headers: Record<string, string>; error: { code: number; message: string; data?: unknown; }; }

import type {RpcCallResult} from '@fuzdev/fuz_app/testing/rpc_helpers.js';

Discriminated return from rpc_call. status is the HTTP status; headers is the response-headers snapshot (lowercased keys) so callers can assert header-level properties (e.g. no backend-fingerprinting headers).

RpcCallResultForSpec
#

testing/rpc_helpers.ts view source

RpcCallResultForSpec<TSpec>

type { ok: true; status: number; headers: Record<string, string>; result: output<TSpec["output"]>; } | { ok: false; status: number; headers: Record<string, string>; error: { code: number; message: string; data?: unknown; }; }

import type {RpcCallResultForSpec} from '@fuzdev/fuz_app/testing/rpc_helpers.js';

Typed discriminated result returned by rpc_call_for_spec. The success branch's result is inferred from TSpec['output']. The error branch stays untyped because JSON-RPC error.data shapes vary per error and are asserted per call site.

generics

RpcCallResultForSpec<TSpec extends RequestResponseActionSpec>
TSpec

RpcClientCallOptions
#

actions/rpc_client.ts view source

RpcClientCallOptions import type {RpcClientCallOptions} from '@fuzdev/fuz_app/actions/rpc_client.js';

Per-call options accepted by every typed Proxy method. Same shape as ActionDispatcherSendOptions — the client threads these through unchanged to the underlying peer. transport_name overrides the per-method transport_for_method selector for this call.

inheritance

RpcEndpointSpec
#

http/surface.ts view source

RpcEndpointSpec import type {RpcEndpointSpec} from '@fuzdev/fuz_app/http/surface.js';

An RPC endpoint definition for surface generation.

path

type string

actions

type RpcAction[]

RpcEndpointsSuiteOption
#

testing/rpc_helpers.ts view source

RpcEndpointsSuiteOption

type RpcEndpointSpec[] | ((ctx: AppServerContext) => RpcEndpointSpec[])

import type {RpcEndpointsSuiteOption} from '@fuzdev/fuz_app/testing/rpc_helpers.js';

Union accepted by the suite-level rpc_endpoints option — eager array or a factory that takes an AppServerContext and returns endpoint specs. The factory form is required when action handlers must close over the per-test ctx.deps (e.g. the canonical create_standard_rpc_actions(ctx.deps) pattern). create_app_server resolves either shape natively; test helpers forward the raw value to the top-level rpc_endpoints slot on CreateTestAppOptions for live dispatch.

RpcMethodCoverageInput
#

testing/cross_backend/method_coverage.ts view source

RpcMethodCoverageInput import type {RpcMethodCoverageInput} from '@fuzdev/fuz_app/testing/cross_backend/method_coverage.js';

live_methods

Every method the live RPC endpoint mounts (action.spec.method).

type readonly string[]

readonly

declared_methods

The declared-surface method names (from create_*_surface_spec).

type readonly string[]

readonly

manifest

The tagged manifest the live set must reconcile against.

type readonly MethodCoverageEntry[]

readonly

testing_method_prefix?

Backdoor method prefix. Defaults to '_testing_'.

type string

readonly

RpcPathCapabilityGatedCrossSuiteOptions
#

testing/cross_backend/setup.ts view source

RpcPathCapabilityGatedCrossSuiteOptions import type {RpcPathCapabilityGatedCrossSuiteOptions} from '@fuzdev/fuz_app/testing/cross_backend/setup.js';

The RPC-dispatched shape for a capability-gating suite — the intersection of RpcPathCrossSuiteOptions and CapabilityGatedCrossSuiteOptions, and the most common option shape among the imperative cross suites (every cell_* suite, query_shape, token_scope_surface, fact_serving, account_lifecycle).

inheritance

RpcPathCrossSuiteOptions
#

testing/cross_backend/setup.ts view source

RpcPathCrossSuiteOptions import type {RpcPathCrossSuiteOptions} from '@fuzdev/fuz_app/testing/cross_backend/setup.js';

CrossSuiteOptions plus the RPC endpoint path the suite dispatches against — the shape every JSON-RPC-driven imperative suite takes (cell verbs, origin, body-size, actor lookup/search, account lifecycle, app settings, testing backdoor). Suites alias this under a self-documenting per-suite name (e.g. OriginCrossTestOptions = RpcPathCrossSuiteOptions).

inheritance

rpc_path?

RPC endpoint path the methods are mounted on. Default /api/rpc.

type string

readonly

RpcResult
#

testing/cross_backend/cell_cross_helpers.ts view source

RpcResult import type {RpcResult} from '@fuzdev/fuz_app/testing/cross_backend/cell_cross_helpers.js';

Minimal JSON-RPC envelope shape the suites read off responses.

ok

type boolean

readonly

result?

type unknown

readonly

error?

type { readonly code: number; readonly message: string; readonly data?: unknown; }

readonly

RpcRoundTripTestOptions
#

testing/rpc_round_trip.ts view source

RpcRoundTripTestOptions import type {RpcRoundTripTestOptions} from '@fuzdev/fuz_app/testing/rpc_round_trip.js';

setup_test

Per-test fixture-producing function (per-describe cadence).

type (): Promise<TestFixtureBase>

returns Promise<TestFixtureBase>

surface_source

App surface (with route + RPC endpoint specs) for RPC endpoint enumeration. Constructed in TS by the consumer; same shape for in-process and cross-process tests.

type AppSurfaceSpec

session_options

Session config — only needed to resolve factory-form rpc_endpoints against a stub AppServerContext at setup time (the actions' input schemas drive params generation; auth/dispatch run against the real backend through fixture.transport).

type SessionOptions<string>

rpc_endpoints

RPC endpoint specs — eager array or factory. The factory must return the same endpoint path + spec.method list regardless of ctx (invoked once at setup with a stub ctx; the real per-test live dispatch goes through whatever the backend was started with).

type RpcEndpointsSuiteOption

skip_methods?

Methods to skip, by name (e.g., 'zap_plan').

type string[]

input_overrides?

Override generated params for specific methods (method name → params).

type Map<string, Record<string, unknown>>

success_fixtures?

Success-case fixtures for methods whose populated success body the generic nil-id input can't reach — referential reads (*_get, *_log) whose required ids must point at existing rows. Maps method name to an async factory that receives the per-test fixture (so it can seed the referenced state — e.g. create a repo via fixture.transport + fixture.create_session_headers()) and returns the params that drive a success response.

Distinct from input_overrides, which only swaps the request params; the response may still be a valid *error* envelope (missing-row not_found), which the generic loop accepts. A success_fixtures entry asserts the response is ok and validates result against the method's output schema — so a backend that drops a field, or errors where the other backend succeeds, fails loud. This is the success-shape parity check the nil-id round-trip structurally cannot perform (it only ever sees error envelopes for referential methods).

Fired as POST. The factory runs against the shared per-describe fixture, so it must not assume a clean slate between entries (seed unique state).

type Map<string, (fixture: TestFixtureBase) => Promise<Record<string, unknown>>>

RpcTestTransport
#

testing/rpc_helpers.ts view source

RpcTestTransport import type {RpcTestTransport} from '@fuzdev/fuz_app/testing/rpc_helpers.js';

Minimal transport surface — the duck type Hono.request already satisfies. Extracted so test setups that want an in-process / WS / mock path can plug a different dispatcher without changing call sites.

(call)

type (url: string, init: RequestInit): Promise<Response>

url

type string

init

type RequestInit
returns Promise<Response>

run_auth_cleanup
#

auth/cleanup.ts view source

(deps: AuthCleanupDeps): Promise<AuthCleanupResult> import {run_auth_cleanup} from '@fuzdev/fuz_app/auth/cleanup.js';

Run every auth cleanup sweep — expired sessions, then expired role_grant offers — and return both counts. Twin of the Rust run_auth_cleanup.

The session sweep (cleanup_expired_sessions) commits its delete and closes the swept sessions' connections before the offer sweep starts, so a failed offer sweep takes neither back; a session sweep whose commit failed has closed too, and the offer sweep does not run.

start_auth_cleanup is the scheduler over this; call it directly for a one-off pass or from a scheduler of your own. Errors are re-thrown so the caller can log or alert; use the per-sweep helpers (cleanup_expired_sessions, cleanup_expired_role_grant_offers) for error isolation per sweep.

deps

returns

Promise<AuthCleanupResult>

throws

  • Error - re-thrown from the first sweep that fails (no per-sweep isolation here)

mutates

  • auth_session table — deletes expired sessions
  • role_grant_offer rows — stamps `expire_audited_at` on expired offers
  • audit_log table — emits `role_grant_offer_expire` rows for expired offers

run_cross_impl_bench
#

run_local
#

cli/util.ts view source

(runtime: CommandDeps, command: string, args: string[]): Promise<CommandResult> import {run_local} from '@fuzdev/fuz_app/cli/util.js';

Run a local command and return the result.

runtime

runtime with run_command capability

command

command to run

type string

args

command arguments

type string[]

returns

Promise<CommandResult>

command result

run_migrations
#

db/migrate.ts view source

(db: Db, namespaces: MigrationNamespace[]): Promise<MigrationResult[]> import {run_migrations} from '@fuzdev/fuz_app/db/migrate.js';

Run pending migrations for each namespace.

For each namespace: acquires an advisory lock, reads applied rows ordered by sequence, length-checks (binary-older-than-db short-circuits), name- prefix-verifies, then runs the pending tail in a single chain transaction. Each migration's row is INSERTed with sequence = max(sequence) + 1 for the namespace.

Length check before name verify is load-bearing: a binary-older case with a rename in the overlap would otherwise fire name-divergence-at-N first and the operator would chase a phantom source-revert before discovering the binary is the real problem.

Atomicity: any failure rolls back every migration that ran in that invocation. Namespaces are independent: a later namespace's failure does not roll back an earlier namespace that already committed.

Concurrency: per-namespace advisory locks reduce contention in multi-instance deployments but are best-effort on pool drivers (see the module docstring's "Advisory locking" notes). Correctness on concurrent boots falls out of chain-tx atomicity + the (namespace, name) PK — the loser's INSERT triggers PK violation and rollback; subsequent boots see the committed state.

db

the database instance

type Db

namespaces

migration namespaces, processed in the order passed

returns

Promise<MigrationResult[]>

one result per namespace where work happened (already-up-to-date namespaces are omitted)

throws

  • MigrationError - with `kind` of `binary-older-than-db`, `name-divergence-at-N`, or `migration-failed`

mutates

  • schema_version — inserts one row per applied migration

run_testing_origin_check
#

testing/cross_backend/testing_reset_actions.ts view source

(input: { patterns: string; origin: string | null; }): { ok: true; allowed: boolean; } | { ok: false; errors: { kind: "empty_entry" | "not_printable_ascii" | "missing_scheme" | "unsupported_scheme" | ... 12 more ... | "no_origins"; index?: number | undefined; }[]; } import {run_testing_origin_check} from '@fuzdev/fuz_app/testing/cross_backend/testing_reset_actions.js';

Answer one _testing_origin_check input: parse patterns with parse_allowed_origins, then run the request-level check for one Origin carrying origin (null — no Origin — passes). The action handler and the conformance table's unit test both call this, so the table checks the same function the wire serves.

input

the allowlist value and the Origin value

returns

TestingOriginCheckOutput

the answer the Rust twin returns for the same input

RunCommandOptions
#

runtime/deps.ts view source

RunCommandOptions import type {RunCommandOptions} from '@fuzdev/fuz_app/runtime/deps.js';

Options for run_command.

cwd?

Working directory for the child process.

type string

signal?

AbortSignal to terminate the child process.

type AbortSignal

timeout_ms?

Kill the process and return timed_out: true after this many milliseconds.

type number

RunCrossImplBenchOptions
#

testing/cross_backend/bench/run_cross_impl_bench.ts view source

RunCrossImplBenchOptions import type {RunCrossImplBenchOptions} from '@fuzdev/fuz_app/testing/cross_backend/bench/run_cross_impl_bench.js';

handles

Already-bootstrapped backends to benchmark. Each one's keeper_transport is the pre-authed transport scenarios fire against — bootstrap once, then hammer; no per-iteration reset.

type readonly BootstrappedBackendHandle[]

readonly

scenarios

type readonly BenchScenario[]

readonly

config?

Overrides merged over the network-tuned defaults below.

type Partial<BenchmarkConfig>

readonly

RunOptions
#

ui/async_slot.svelte.ts view source

RunOptions import type {RunOptions} from '@fuzdev/fuz_app/ui/async_slot.svelte.js';

signal?

External signal chained into the slot's internal controller. Aborts the in-flight run when fired (alongside automatic supersession by the next run() and manual AsyncSlot.abort calls).

type AbortSignal

RuntimeDeps
#

runtime/deps.ts view source

RuntimeDeps import type {RuntimeDeps} from '@fuzdev/fuz_app/runtime/deps.js';

Full runtime capabilities returned by create_deno_runtime or create_node_runtime.

Extends all *Deps interfaces with additional app-level capabilities. Functions should accept narrow *Deps interfaces, not this full type — this type is for the wiring layer that creates and passes the runtime.

inheritance

env_all

Get all environment variables.

type (): Record<string, string>

returns Record<string, string>

args

CLI arguments passed to the program.

type readonly string[]

readonly

cwd

Get current working directory.

type (): string

returns string

run_command_inherit

Run a command with inherited stdout/stderr (output goes directly to terminal).

type (cmd: string, args: string[]): Promise<number>

cmd

type string

args

type string[]
returns Promise<number>

rust_default_capabilities
#

rust_default_shape_notes
#

testing/cross_backend/default_backend_configs.ts view source

BackendShapeNotes import {rust_default_shape_notes} from '@fuzdev/fuz_app/testing/cross_backend/default_backend_configs.js';

Shape notes for Rust-family backends — wiring facts, not gating flags. Adds trusted_proxy: true (the Rust spine's client-IP middleware is always wired; the env-gate only chooses XFF vs the TCP peer IP) and login_rate_limit: true (env-gated bucket on /login + /password). Documentation only — see BackendShapeNotes.

rust_spine_stub_backend_config
#

testing/cross_backend/rust_spine_stub_backend_config.ts view source

(options?: SpineStubBackendConfigOptions): BackendConfig import {rust_spine_stub_backend_config} from '@fuzdev/fuz_app/testing/cross_backend/rust_spine_stub_backend_config.js';

Build the BackendConfig for testing_spine_stub. Resolves the binary from options.binary_path or FUZ_TESTING_RUST_SPINE_STUB_BIN; throws when neither is set so a missing build surfaces as a clear error rather than a confusing spawn failure. Reconciles the binary's env contract: port via --port (and FUZ_RUST_SPINE_STUB_PORT), daemon-token dir via FUZ_RUST_SPINE_STUB_DIR (anchored to paths.root so the written {dir}/run/daemon_token matches the path spawn_backend reads).

options

default {}

returns

BackendConfig

throws

  • Error - when no binary path is available.

RUST_SPINE_STUB_BIN_ENV
#

RUST_SPINE_STUB_DATABASE_URL_ENV
#

testing/cross_backend/rust_spine_stub_backend_config.ts view source

"FUZ_TESTING_RUST_SPINE_STUB_DATABASE_URL" import {RUST_SPINE_STUB_DATABASE_URL_ENV} from '@fuzdev/fuz_app/testing/cross_backend/rust_spine_stub_backend_config.js';

Env var overriding the stub's Postgres URL when database_url is omitted — e.g. a scratch cluster on a spare port, keeping the run off the machine's default server. A runner that creates the stub's database before the spawn must create the one this URL names, on the server it names.

RUST_SPINE_STUB_DEFAULT_DATABASE_URL
#

testing/cross_backend/rust_spine_stub_backend_config.ts view source

"postgres://localhost/fuz_app_test_rust_spine_stub" import {RUST_SPINE_STUB_DEFAULT_DATABASE_URL} from '@fuzdev/fuz_app/testing/cross_backend/rust_spine_stub_backend_config.js';

Default Postgres database — real PG (PGlite isn't reachable from tokio-postgres).

RUST_SPINE_STUB_DEFAULT_PORT
#

RUST_SPINE_STUB_EXPECTED_SCHEMA_PATH_ENV
#

testing/cross_backend/rust_spine_stub_backend_config.ts view source

"FUZ_RUST_SPINE_STUB_EXPECTED_SCHEMA_PATH" import {RUST_SPINE_STUB_EXPECTED_SCHEMA_PATH_ENV} from '@fuzdev/fuz_app/testing/cross_backend/rust_spine_stub_backend_config.js';

Env var the stub reads for the absolute path of the committed expected_schema.json its /ready gate introspects against. Pointed at the same fixture the TS spine reads (SPINE_EXPECTED_SCHEMA_URL) — column-presence is engine-portable, so one file is the cross-impl contract.

safe_parse_dropping_unknown_keys
#

http/schema_helpers.ts view source

<T extends z.ZodType>(schema: T, value: unknown): ZodSafeParseResult<output<T>> import {safe_parse_dropping_unknown_keys} from '@fuzdev/fuz_app/http/schema_helpers.js';

schema.safeParse(value), except that keys the schema doesn't declare are dropped rather than refused — the forward-compatible read of a payload a remote authored.

Output schemas are strict so the server's DEV output validation catches a handler leaking fields it shouldn't. The receiving side, though, is often older than the remote it talks to — a deploy lands before open tabs reload — and a remote that adds a field is making a compatible change. So when every issue is an unrecognized key, those keys are stripped from a copy of value and it's parsed again; any other issue (a wrong type, a missing field, null where a value is required) still fails. value itself is never mutated. This matches serde's default on the Rust spine, which ignores unknown fields. Keys nested in a z.union branch surface as a union issue and aren't stripped; a z.discriminatedUnion reports its matched branch's issues directly, so those are.

Used for a response's result (ActionEvent) and for a peer's reply to a server-initiated request (peer/ping).

schema

the declared shape

type T

value

the remote-authored payload

type unknown

returns

ZodSafeParseResult

the parse result, with undeclared keys dropped on success

generics

safe_parse_dropping_unknown_keys<T extends z.ZodType>
T
constraint z.ZodType

save_config
#

cli/config.ts view source

<T>(runtime: Pick<FsWriteDeps, "mkdir" | "write_text_file">, path: string, dir: string, config: T): Promise<void> import {save_config} from '@fuzdev/fuz_app/cli/config.js';

Save CLI configuration to a JSON file.

runtime

runtime with file write capability

type Pick<FsWriteDeps, "mkdir" | "write_text_file">

path

path to the config JSON file

type string

dir

directory containing the config file (created if missing)

type string

config

configuration to save

type T

returns

Promise<void>

generics

save_config<T>
T

mutates

  • filesystem — creates `dir` (recursive) and writes JSON to `path`

schedule_credential_deadline
#

auth/credential_deadline.ts view source

(deadline: number | null, on_expire: () => void): () => void import {schedule_credential_deadline} from '@fuzdev/fuz_app/auth/credential_deadline.js';

Call on_expire once deadline has passed, waiting in timers of at most CREDENTIAL_DEADLINE_TIMER_CAP_MS, each re-reading the clock against the absolute deadline — so a session's 30 days (past setTimeout's ceiling) does not fire at once. Each wake-up also checks the same deadline on the wall clock, so a host suspend, which stops the monotonic clock, delays the close by at most one timer after the resume (module doc).

A null deadline schedules nothing. The returned function cancels the wait and is safe to call more than once, before or after it fires.

deadline

the monotonic deadline (ResolvedAuth.credential_deadline)

type number | null

on_expire

run once, when the deadline passes

type () => void

returns

() => void

a cancel function

schema_to_surface
#

http/schema_helpers.ts view source

(schema: ZodType<unknown, unknown, $ZodTypeInternals<unknown, unknown>>): unknown import {schema_to_surface} from '@fuzdev/fuz_app/http/schema_helpers.js';

Convert a Zod schema to a JSON-serializable representation for the surface.

Returns null for null schemas, JSON Schema for object schemas.

schema

type ZodType<unknown, unknown, $ZodTypeInternals<unknown, unknown>>

returns

unknown

SchemaDiff
#

testing/schema_parity.ts view source

SchemaDiff

type { readonly kind: "table_only_in"; readonly where: "a" | "b"; readonly table: string; } | { readonly kind: "column_only_in"; readonly where: "a" | "b"; readonly table: string; readonly column: string; } | { readonly kind: "column_field_differs"; readonly table: string; readonly column: string; readonly field: "data_type" | "udt_name" | "is_nullable" | "column_default" | "is_identity"; readonly a: unknown; readonly b: unknown; } | { readonly kind: "index_only_in"; readonly where: "a" | "b"; readonly table: string; readonly index: string; } | { readonly kind: "index_definition_differs"; readonly table: string; readonly index: string; readonly a: string; readonly b: string; } | { readonly kind: "constraint_only_in"; readonly where: "a" | "b"; readonly table: string; readonly constraint: string; } | { readonly kind: "constraint_differs"; readonly table: string; readonly constraint: string; readonly a: { type: string; definition: string; }; readonly b: { type: string; definition: string; }; } | { readonly kind: "sequence_only_in"; readonly where: "a" | "b"; readonly sequence: string; } | { readonly kind: "sequence_data_type_differs"; readonly sequence: string; readonly a: string; readonly b: string; } | { readonly kind: "enum_only_in"; readonly where: "a" | "b"; readonly enum_name: string; } | { readonly kind: "enum_labels_differ"; readonly enum_name: string; readonly a: readonly string[]; readonly b: readonly string[]; }

import type {SchemaDiff} from '@fuzdev/fuz_app/testing/schema_parity.js';

Structured drift entry. where is the named source impl ('a' or 'b').

SchemaDiffLabels
#

testing/schema_parity.ts view source

SchemaDiffLabels import type {SchemaDiffLabels} from '@fuzdev/fuz_app/testing/schema_parity.js';

Labels used in formatted output — defaults to 'a' and 'b'.

a?

type string

readonly

b?

type string

readonly

SchemaDriftResult
#

db/schema_ready.ts view source

SchemaDriftResult import type {SchemaDriftResult} from '@fuzdev/fuz_app/db/schema_ready.js';

Outcome of a schema-drift check.

ok

type boolean

missing_tables

Expected tables absent from the live DB.

type string[]

missing_columns

Per-table columns the expected schema declares that the live DB lacks.

type MissingColumns[]

SchemaFieldMeta
#

schema_meta.ts view source

SchemaFieldMeta import type {SchemaFieldMeta} from '@fuzdev/fuz_app/schema_meta.js';

Zod .meta() shape for fuz_app schema metadata conventions.

description?

type string

sensitivity?

Sensitivity level for masking/redaction. 'secret' masks the value.

type "secret"

SchemaSnapshot
#

testing/schema_introspect.ts view source

value + type

{ tables: Record<string, { columns: Record<string, { data_type: string; udt_name: string; is_nullable: boolean; column_default: string | null; is_identity: boolean; }>; indexes: { name: string; definition: string; }[]; constraints: { ...; }[]; }>; sequences: Record<...>; enums: Record<...>; } import {SchemaSnapshot} from '@fuzdev/fuz_app/testing/schema_introspect.js';

Normalized database schema snapshot for parity comparison — the single source of truth for the snapshot shape across the introspection query (query_schema_snapshot), the diff comparator (testing/schema_parity.ts), and the cross-impl RPC action's wire validator (testing/cross_backend/testing_reset_actions.ts).

All fields are deterministically ordered on capture so structural equality via JSON.stringify or per-key comparison yields stable results.

tables

Tables keyed by name.

type Record<string, TableSnapshot>

sequences

Sequences keyed by name.

type Record<string, SequenceSnapshot>

enums

Enum types (CREATE TYPE ... AS ENUM) keyed by name; labels in declared order.

type Record<string, EnumTypeSnapshot>

SCOPE_KIND_LENGTH_MAX
#

auth/scope_kind_schema.ts view source

64 import {SCOPE_KIND_LENGTH_MAX} from '@fuzdev/fuz_app/auth/scope_kind_schema.js';

Maximum length of a scope_kind, in UTF-16 code units (what String.length measures; enforced by bounded_string). Bounds the tag where it enters — the grant inputs (role_grant_offer_create, role_grant_assign) copy it into the row and its audit metadata — and a registered name, so no registry declares a kind the grant inputs would refuse. Same value as ROLE_NAME_LENGTH_MAX, whose grammar the name mirrors, and as the Rust spine's SCOPE_KIND_LENGTH_MAX.

SCOPE_KIND_NAME_REGEX
#

auth/scope_kind_schema.ts view source

RegExp import {SCOPE_KIND_NAME_REGEX} from '@fuzdev/fuz_app/auth/scope_kind_schema.js';

Letter (lowercase a-z) start and end (or single letter), with letters and underscores in between. Mirrors RoleName. Rejects empty strings, leading or trailing underscores, uppercase, digits, and the index-side 'GLOBAL' token.

SCOPED_TOKEN_ADMITTED_METHOD
#

testing/cross_backend/conformance_case.ts view source

"account_verify" import {SCOPED_TOKEN_ADMITTED_METHOD} from '@fuzdev/fuz_app/testing/cross_backend/conformance_case.js';

The single RPC method the scoped_token principal's narrowed token admits.

Part of the principal's contract, so the runner (which mints the token) and the cases (which assert the denials) agree on one value. account_verify is the minimal credential_types: any read on the spine: it clears the credential gate a bearer must pass first, and holds no role gate that could shadow the scope denial — so a case naming any *other* method isolates the scope gate and nothing else.

ScopeKindMeta
#

auth/scope_kind_schema.ts view source

ScopeKindMeta import type {ScopeKindMeta} from '@fuzdev/fuz_app/auth/scope_kind_schema.js';

Per-scope-kind metadata. description is admin-UI-facing copy (mirrors RoleSpec.description). Open shape so v2 can extend without a breaking change.

description?

type string

ScopeKindName
#

ScopeKindSchemaResult
#

auth/scope_kind_schema.ts view source

ScopeKindSchemaResult import type {ScopeKindSchemaResult} from '@fuzdev/fuz_app/auth/scope_kind_schema.js';

The result of create_scope_kind_schema — a Zod schema and metadata map.

ScopeKind

Zod schema that validates scope-kind name strings against the registered set. Use at I/O boundaries (admin UIs, codegen) and as the construction-time check inside create_role_schema for every RoleSpec.applicable_scope_kinds entry.

type ZodType<string, unknown, $ZodTypeInternals<string, unknown>>

scope_kinds

Map of every registered scope-kind to its metadata. Keyed by name. Read at startup by admin / codegen surfaces.

type ReadonlyMap<string, ScopeKindMeta>

seed_dev_account
#

dev/setup.ts view source

(deps: SeedDevAccountDeps, input: SeedDevAccountInput, options?: { log?: SetupLogger | undefined; } | undefined): Promise<...> import {seed_dev_account} from '@fuzdev/fuz_app/dev/setup.js';

Seed a development test account, bypassing username/password policy.

Idempotent by username — if an account with the given username already exists, reuses it and only reconciles the requested role grants. Never updates an existing password (rerun would silently rotate it).

Intended for scripts/dev_setup.ts — do not call in production.

deps

input

options?

type { log?: SetupLogger | undefined; }
optional

returns

Promise<SeedDevAccountResult>

throws

  • Error - if an existing account is found without an associated actor row

mutates

  • database — inserts an account/actor pair when missing and grants any requested role role_grants

SeedDevAccountDeps
#

dev/setup.ts view source

SeedDevAccountDeps import type {SeedDevAccountDeps} from '@fuzdev/fuz_app/dev/setup.js';

Dependencies for seed_dev_account.

inheritance

extends: QueryDeps

hash_password

Password hasher (e.g., argon2_password_deps.hash_password).

type (password: string): Promise<string>

password

type string
returns Promise<string>

SeedDevAccountInput
#

dev/setup.ts view source

SeedDevAccountInput import type {SeedDevAccountInput} from '@fuzdev/fuz_app/dev/setup.js';

username

Account username. Policy is bypassed — any non-empty string is accepted.

type string

password

Account password. Policy is bypassed — any non-empty string is accepted.

type string

roles?

Roles to grant via role_grant (idempotent).

type readonly string[]

SeedDevAccountResult
#

dev/setup.ts view source

SeedDevAccountResult import type {SeedDevAccountResult} from '@fuzdev/fuz_app/dev/setup.js';

Result of seed_dev_account.

account_id

type string

actor_id

type string

created

True if a new account was created; false if one already existed.

type boolean

select_auth_app
#

testing/auth_apps.ts view source

(apps: AuthTestApps, auth: { account: "optional" | "none" | "required"; actor: "optional" | "none" | "required"; roles?: readonly string[] | undefined; credential_types?: readonly string[] | undefined; required_scope?: string | undefined; }): Hono<...> import {select_auth_app} from '@fuzdev/fuz_app/testing/auth_apps.js';

Select the Hono test app with correct auth for a route.

The credential channel is decided first, because the channel gate is pre-authorization: an app whose credential the route refuses never reaches the role gate, so a role-correct app on the wrong channel answers credential_type_required, and a "correct auth passes guard" case reads it as a failure of the role gate that never ran.

A gate admitting 'session' falls through to the by_role / authed apps, which carry that channel. Any other gate leaves the by_credential_type app as the only candidate — those hold the keeper role, which is why a route pairing a non-session channel with some *other* role has no app and says so.

apps

auth

returns

Hono<BlankEnv, BlankSchema, "/">

throws

  • Error - if `auth.roles` names a role not present in `apps.by_role` — surfaces a missing entry in the `roles` array passed to `create_auth_test_apps`.
  • Error - if the route pairs a non-session channel with a role the channel apps don't hold. Silently handing back a session app would turn every case on the route into a misattributed 403.

self_service_role_set_action_spec
#

auth/self_service_role_action_specs.ts view source

{ method: string; kind: "request_response"; initiator: "frontend"; auth: { account: "required"; actor: "required"; credential_types: string[]; }; side_effects: true; input: ZodObject<{ role: ZodString; enabled: ZodBoolean; acting: ZodOptional<...>; }, $strict>; output: ZodObject<...>; async: true; description: strin... import {self_service_role_set_action_spec} from '@fuzdev/fuz_app/auth/self_service_role_action_specs.js';

rate_limit: 'account' bounds audit-row churn. The toggle is idempotent (changed: false re-grants/re-revokes), but every call still writes a role_grant_create or role_grant_revoke audit row with self_service: true. Without the cap, a caller could flap the role in a loop to inflate the audit log and obscure other activity.

credential_types: ['session'] — see docs/security.md §Credential-channel gating. Self-toggling a role moves the caller's own authority, the same privilege-pivot shape as role_grant_offer_accept. The verb exists to serve a UI affordance, so the bearer channel buys nothing and a leaked account-wide token self-granting an eligible role is what the gate refuses.

SelfServiceRoleActionsOptions
#

auth/self_service_role_actions.ts view source

SelfServiceRoleActionsOptions import type {SelfServiceRoleActionsOptions} from '@fuzdev/fuz_app/auth/self_service_role_actions.js';

eligible_roles?

Optional override allowlist of role strings eligible for self-service. When omitted, eligibility is derived from roles.role_specs (or builtin_role_specs_by_name when roles is also omitted) by selecting every role whose RoleSpec.grant_paths includes 'self_service'. Pass an empty array to lock the surface down (every call comes back as forbidden with reason role_not_self_service_eligible).

When supplied alongside roles, every entry is checked against roles.role_specs at factory time so typos throw at startup.

type readonly string[]

roles?

Optional role schema. Drives default eligibility derivation from RoleSpec.grant_paths and validates the eligible_roles override (when supplied) against the registered role set.

type RoleSchemaResult

SelfServiceRoleSetInput
#

auth/self_service_role_action_specs.ts view source

value + type

{ role: string; enabled: boolean; acting?: (string & $brand<"Uuid">) | undefined; } import {SelfServiceRoleSetInput} from '@fuzdev/fuz_app/auth/self_service_role_action_specs.js';

Input for self_service_role_set.

role

type string

enabled

type boolean

acting?

type string & $brand<"Uuid">

SelfServiceRoleSetOutput
#

auth/self_service_role_action_specs.ts view source

value + type

{ ok: true; enabled: boolean; changed: boolean; } import {SelfServiceRoleSetOutput} from '@fuzdev/fuz_app/auth/self_service_role_action_specs.js';

Output for self_service_role_set. enabled echoes the post-call state (always equals the input enabled on success). changed is true only when the call mutated — re-grants / re-revokes return false.

ok

type true

enabled

type boolean

changed

type boolean

sensitive_field_blocklist
#

testing/integration_helpers.ts view source

readonly string[] import {sensitive_field_blocklist} from '@fuzdev/fuz_app/testing/integration_helpers.js';

Field names that must never appear in any HTTP response body.

Sensitivity
#

sensitivity.ts view source

"secret" import type {Sensitivity} from '@fuzdev/fuz_app/sensitivity.js';

Sensitivity level for a schema field.

  • 'secret' — value is masked in logs and UI (e.g. passwords, API keys, signing keys)

SequenceSnapshot
#

testing/schema_introspect.ts view source

value + type

{ data_type: string; } import {SequenceSnapshot} from '@fuzdev/fuz_app/testing/schema_introspect.js';

Sequence metadata — data_type is bigint (BIGSERIAL) or integer (SERIAL).

data_type

type string

SerializableBootstrappedBackendHandle
#

testing/cross_backend/setup.ts view source

SerializableBootstrappedBackendHandle import type {SerializableBootstrappedBackendHandle} from '@fuzdev/fuz_app/testing/cross_backend/setup.js';

Serializable subset of BootstrappedBackendHandle suitable for vitest's project.provide() — vitest 4 hard-rejects non-serializable values, so the live child: ChildProcess + teardown: () => Promise<void> + keeper_transport: FetchTransport (closure) must stay in the globalSetup process. The handful of fields tests actually read (config, daemon_token, keeper_account, keeper_actor, keeper_cookies) round-trip through structured clone fine.

globalSetup calls serialize_bootstrapped_handle before project.provide; test files call reconstruct_bootstrapped_handle on the injected value to rebuild a usable handle (without child / teardown — lifecycle stays with globalSetup).

config

type BackendConfig

readonly

daemon_token

type string

readonly

keeper_account

type { readonly id: string & $brand<"Uuid">; readonly username: string; }

readonly

keeper_actor

type { readonly id: string & $brand<"Uuid">; }

readonly

keeper_cookies

type readonly string[]

readonly

serialize_bootstrapped_handle
#

testing/cross_backend/setup.ts view source

(handle: BootstrappedBackendHandle): SerializableBootstrappedBackendHandle import {serialize_bootstrapped_handle} from '@fuzdev/fuz_app/testing/cross_backend/setup.js';

Strip the non-serializable members so the result can be passed to vitest's project.provide. Call in globalSetup before provide.

handle

returns

SerializableBootstrappedBackendHandle

serialize_token_scope
#

auth/token_scope.ts view source

(scope: { version: 1; kind: "full"; grandfathered?: boolean | undefined; } | { version: 1; kind: "methods"; methods: string[]; grandfathered?: boolean | undefined; }): string import {serialize_token_scope} from '@fuzdev/fuz_app/auth/token_scope.js';

Serialize for storage, enforcing the size cap.

Throws on an over-cap document — the mint path maps that to a 400, matching the Rust side's TokenScopeError::TooLarge.

scope

returns

string

ServeHandle
#

testing/cross_backend/testing_server_core.ts view source

ServeHandle import type {ServeHandle} from '@fuzdev/fuz_app/testing/cross_backend/testing_server_core.js';

Adapter-built handle to a bound HTTP server.

shutdown stops accepting new connections at once and settles when the in-flight ones have drained — the core starts it, closes the app (which ends live WebSockets), then awaits it. native is an adapter-specific server reference — used by Node's @hono/node-ws injectWebSocket(server) post-serve hook.

shutdown

type (): Promise<void>

returns Promise<void>

native?

Adapter-specific server ref for post-serve hooks. Type-erased at the seam.

type unknown

ServerEnvOptions
#

server/env.ts view source

ServerEnvOptions import type {ServerEnvOptions} from '@fuzdev/fuz_app/server/env.js';

Validated server env config — the artifacts create_app_server() needs.

ok

type true

keyring

type Keyring

allowed_origins

type RegExp[]

bootstrap_token_path

type string | null

ServerEnvOptionsError
#

server/env.ts view source

ServerEnvOptionsError import type {ServerEnvOptionsError} from '@fuzdev/fuz_app/server/env.js';

Error from validate_server_env — keyring or origin validation failed.

ok

type false

field

type "SECRET_FUZ_COOKIE_KEYS" | "FUZ_ALLOWED_ORIGINS"

errors

type string[]

ServerEnvOptionsResult
#

ServerHeartbeatOptions
#

actions/register_action_ws.ts view source

ServerHeartbeatOptions import type {ServerHeartbeatOptions} from '@fuzdev/fuz_app/actions/register_action_ws.js';

timeout?

Receive-silence (ms) past which the server closes the socket with WS_CLOSE_SERVER_HEARTBEAT_TIMEOUT. Any incoming message resets the counter — chatty clients never trip it. First timeout window after socket open is exempt (cold-start grace).

type number

ServerStatusOptions
#

http/common_routes.ts view source

ServerStatusOptions import type {ServerStatusOptions} from '@fuzdev/fuz_app/http/common_routes.js';

Options for the authenticated server status route.

version

Application version string.

type string

get_uptime_ms

Returns milliseconds since server start.

type (): number

returns number

ServeStaticFactory
#

server/static.ts view source

ServeStaticFactory import type {ServeStaticFactory} from '@fuzdev/fuz_app/server/static.js';

Factory function that creates a static file serving middleware.

Matches the signature of serveStatic from @hono/deno and @hono/node-server/serve-static.

(call)

type (options: ServeStaticOptions): MiddlewareHandler

options

returns MiddlewareHandler

ServeStaticOptions
#

server/static.ts view source

ServeStaticOptions import type {ServeStaticOptions} from '@fuzdev/fuz_app/server/static.js';

Options for serve_static factory functions (matches Hono's serveStatic signature).

root

type string

rewriteRequestPath?

type (path: string): string

path

type string
returns string

mimes?

type Record<string, string>

SESSION_AGE_MAX
#

auth/session_cookie.ts view source

number import {SESSION_AGE_MAX} from '@fuzdev/fuz_app/auth/session_cookie.js';

Cookie max age in seconds (30 days — aligned with AUTH_SESSION_LIFETIME_MS).

This is an absolute session lifetime: the cookie is signed once at mint (expires_at = mint + max_age) and never re-signed, matching the DB row's fixed expires_at. There is deliberately no sliding renewal on either leg — see docs/security.md §Session Security.

SESSION_COOKIE_EXPIRES_AT_KEY
#

session_cookie_options
#

SessionAccount
#

auth/account_schema.ts view source

SessionAccount import type {SessionAccount} from '@fuzdev/fuz_app/auth/account_schema.js';

Account without sensitive fields, scoped to the authenticated user's own session.

id

type Uuid

username

type string

email

type string | null

email_verified

type boolean

created_at

type string

SessionAccountJson
#

auth/account_schema.ts view source

value + type

{ id: string & $brand<"Uuid">; username: string; email: string | null; email_verified: boolean; created_at: string; } import {SessionAccountJson} from '@fuzdev/fuz_app/auth/account_schema.js';

Zod schema for SessionAccount — account without sensitive fields.

id

type string & $brand<"Uuid">

username

type string

email

type string | null

email_verified

type boolean

created_at

type string

SessionCapCrossTestOptions
#

testing/cross_backend/session_cap.ts view source

SessionCapCrossTestOptions import type {SessionCapCrossTestOptions} from '@fuzdev/fuz_app/testing/cross_backend/session_cap.js';

Options for the session-cap parity suite.

setup_test

Per-test fixture producer (cross-process only — see the module doc).

type (): Promise<TestFixtureBase>

readonly
returns Promise<TestFixtureBase>

max_sessions?

The cap both spines enforce. Defaults to DEFAULT_MAX_SESSIONS — the TS route default and the Rust const. A consumer that overrides max_sessions on the TS side passes its value here.

type number

readonly

login_path?

REST login route path. Default /api/account/login.

type string

readonly

status_path?

REST account-status route path. Default /api/account/status.

type string

readonly

ws?

Where the backend's WebSocket endpoint is, for the case that an evicted session's socket is closed. Pass it when the backend has a WS transport (capabilities.ws); omitted, that case is skipped.

type { readonly base_url: string; readonly ws_path: string; readonly origin?: string | undefined; }

readonly

SessionCookieOptions
#

auth/session_cookie.ts view source

SessionCookieOptions import type {SessionCookieOptions} from '@fuzdev/fuz_app/auth/session_cookie.js';

Cookie options for session cookies.

path

type string

httpOnly

type boolean

secure

type boolean

sameSite

type "strict" | "lax" | "none"

maxAge

type number

SessionId
#

auth/account_schema.ts view source

value + type

string & $brand<"SessionId">

type string & $brand<"SessionId">

import {SessionId} from '@fuzdev/fuz_app/auth/account_schema.js';

A session's storage key — the blake3 hash of its raw token, branded so a bare string can't stand in for one. Blake3Hash (primitive_schemas.ts) is the shape; this is the meaning. Mint only via hash_session_token, or SessionId.parse at an external boundary.

The raw token itself is never typed — it stays a string that exists only long enough to be hashed.

external types

z.infer<typeof SessionId>

SessionListInput
#

SessionListOutput
#

auth/account_action_specs.ts view source

value + type

{ sessions: { id: string & $brand<"SessionId">; account_id: string & $brand<"Uuid">; created_at: string; expires_at: string; }[]; } import {SessionListOutput} from '@fuzdev/fuz_app/auth/account_action_specs.js';

Output for account_session_list.

sessions

type AuthSession[]

SessionOptions
#

auth/session_cookie.ts view source

SessionOptions<TIdentity> import type {SessionOptions} from '@fuzdev/fuz_app/auth/session_cookie.js';

Configuration for a session cookie format.

Apps provide encode/decode to control the identity portion of the cookie payload.

The TIdentity type parameter determines the trust model:

  • string (e.g. a session_id) — the cookie references a server-side session record, enabling per-session revocation and metadata. Use when you need admin controls like "revoke all sessions" or per-session audit trails.
  • number (e.g. an account_id) — the cookie directly encodes the user identity, requiring no server-side session state. Simpler, but individual sessions can only be invalidated by rotating the signing key (which invalidates all sessions).

generics

SessionOptions<TIdentity>
TIdentity

examples

// zap: 3-part format (admin:session_id) const zap_config: SessionOptions<string> = { cookie_name: 'zap_session', context_key: 'auth_session_id', encode_identity: (session_id) => `admin:${session_id}`, decode_identity: (payload) => { const parts = payload.split(':'); if (parts.length !== 2 || parts[0] !== 'admin') return null; return parts[1] || null; }, }; // visiones: 1-part format (account_id) const visiones_config: SessionOptions<number> = { cookie_name: 'session_id', context_key: 'auth_session_id', encode_identity: (id) => String(id), decode_identity: (payload) => { const n = parseInt(payload, 10); return Number.isFinite(n) && n > 0 ? n : null; }, };

cookie_name

type string

context_key

Hono context variable name for the identity.

type string

max_age?

Cookie lifetime in seconds. Single source of truth for both the embedded expires_at (via create_session_cookie_value) and the cookie's HTTP Max-Age attribute (via set_session_cookie). Defaults to SESSION_AGE_MAX (30 days). The cookie_options slot intentionally cannot carry maxAge so the two values can't drift.

type number

cookie_options?

type Partial<Omit<SessionCookieOptions, "maxAge">>

encode_identity

Encode identity into the cookie payload (before the :expires_at suffix).

type (identity: TIdentity): string

identity

type TIdentity
returns string

decode_identity

Decode identity from cookie payload. Return null if invalid.

type (payload: string): TIdentity | null

payload

type string
returns TIdentity | null

SessionRevokeAllInput
#

auth/account_action_specs.ts view source

value + type

void import {SessionRevokeAllInput} from '@fuzdev/fuz_app/auth/account_action_specs.js';

Input for account_session_revoke_all. No parameters.

SessionRevokeAllOutput
#

auth/account_action_specs.ts view source

value + type

{ ok: true; count: number; } import {SessionRevokeAllOutput} from '@fuzdev/fuz_app/auth/account_action_specs.js';

Output for account_session_revoke_all.

ok

type true

count

type number

SessionRevokeInput
#

auth/account_action_specs.ts view source

value + type

{ session_id: string & $brand<"SessionId">; } import {SessionRevokeInput} from '@fuzdev/fuz_app/auth/account_action_specs.js';

Input for account_session_revoke. session_id is the blake3 hash.

session_id

type string & $brand<"SessionId">

SessionRevokeOutput
#

auth/account_action_specs.ts view source

value + type

{ ok: true; revoked: boolean; } import {SessionRevokeOutput} from '@fuzdev/fuz_app/auth/account_action_specs.js';

Output for account_session_revoke. revoked is false for IDOR misses.

ok

type true

revoked

type boolean

set_default_db_factory
#

testing/db.ts view source

(create_factory: DbFactoryBuilder | null): DbFactoryBuilder | null import {set_default_db_factory} from '@fuzdev/fuz_app/testing/db.js';

Install the builder create_default_db_factory uses, so suites written against the default run against another driver without being edited — create_pglite_factory to run them on PGlite. Pass null to restore pglet-wasm.

The slot is read when a factory is built, not when it creates a database, so install before the first create_default_db_factory call — a vitest setupFiles entry, which runs ahead of every test file's imports, is the shape fuz_app's own db project uses. A factory built earlier (including the module-level fallback in testing/app_server.ts) keeps the driver it was built with, and uninstalling does not revert it.

create_factory

the builder to install, or null to restore the default

type (DbFactoryBuilder) | null

returns

(DbFactoryBuilder) | null

the builder this replaced (null for pglet-wasm), so a caller can put it back

set_mock_stdin
#

runtime/mock.ts view source

(runtime: MockRuntime, input: string): void import {set_mock_stdin} from '@fuzdev/fuz_app/runtime/mock.js';

Set stdin buffer for simulating user input.

runtime

input

string to provide as stdin input

type string

returns

void

mutates

  • runtime.stdin_buffer

set_session_cookie
#

setup_bootstrap_token
#

dev/setup.ts view source

(deps: FsReadDeps & FsWriteDeps & CommandDeps & EnvDeps, app_name: string, options?: SetupBootstrapTokenOptions | undefined): Promise<...> import {setup_bootstrap_token} from '@fuzdev/fuz_app/dev/setup.js';

Create a bootstrap token file if it doesn't exist.

The token is a one-shot secret used to create the first admin account. Stored at ~/.{app_name}/secret_bootstrap_token by default.

deps

file, command, and env capabilities

app_name

application name (used for default state directory)

type string

options?

state_dir override, logger

optional

returns

Promise<SetupTokenResult>

result indicating whether a token was created

mutates

  • filesystem — creates the state directory (`0o700`) and writes the token file atomically at `0o600`

setup_env_file
#

dev/setup.ts view source

(deps: FsReadDeps & FsWriteDeps & CommandDeps, env_path: string, example_path: string, options?: SetupEnvOptions | undefined): Promise<...> import {setup_env_file} from '@fuzdev/fuz_app/dev/setup.js';

Create an env file from its example template, auto-generating SECRET_FUZ_COOKIE_KEYS.

If the file already exists, backfills any empty values that have generators. Idempotent — safe to re-run.

deps

file read, write, and command capabilities

env_path

path for the env file (e.g. .env.development)

type string

example_path

path to the example template

type string

options?

extra replacements, logger

optional

returns

Promise<SetupEnvResult>

result indicating whether the file was created or updated

mutates

  • filesystem — writes `env_path` atomically at mode `0o600` (creating from `example_path` if missing)

SetupBootstrapTokenOptions
#

SetupEnvOptions
#

dev/setup.ts view source

SetupEnvOptions import type {SetupEnvOptions} from '@fuzdev/fuz_app/dev/setup.js';

Options for setup_env_file.

replacements?

Extra env var replacements beyond the default SECRET_FUZ_COOKIE_KEYS.

Keys are env var names, values are async generators. Replaces ^KEY=$ (empty value) patterns in the env file.

type Record<string, () => Promise<string>>

log?

type SetupLogger

SetupEnvResult
#

dev/setup.ts view source

SetupEnvResult import type {SetupEnvResult} from '@fuzdev/fuz_app/dev/setup.js';

Result of setup_env_file.

created

Whether a new file was created (vs updating existing).

type boolean

updated

Whether any values were generated/replaced.

type boolean

path

The env file path.

type string

SetupLogger
#

dev/setup.ts view source

SetupLogger import type {SetupLogger} from '@fuzdev/fuz_app/dev/setup.js';

Optional logger for setup helpers.

Functions that accept a logger use it for status messages. When omitted, a default bracket-format logger writes to console.

ok

type (msg: string): void

msg

type string
returns void

skip

type (msg: string): void

msg

type string
returns void

error

type (msg: string): void

msg

type string
returns void

SetupTest
#

testing/cross_backend/setup.ts view source

SetupTest import type {SetupTest} from '@fuzdev/fuz_app/testing/cross_backend/setup.js';

Per-test fixture-producing function. Invoked once inside every test() body. The implementation captures factory inputs (in-process) or a long-running backend handle (cross-process) and creates a fresh per-test bundle on each call.

(call)

type (): Promise<TestFixtureBase>

returns Promise<TestFixtureBase>

SetupTokenResult
#

dev/setup.ts view source

SetupTokenResult import type {SetupTokenResult} from '@fuzdev/fuz_app/dev/setup.js';

created

Whether a new token was created (false if already existed).

type boolean

token_path

The token file path.

type string

should_allow_origin
#

http/origin.ts view source

(origin: string, allowed_patterns: readonly RegExp[]): boolean import {should_allow_origin} from '@fuzdev/fuz_app/http/origin.js';

Tests if an Origin value matches any of the allowed patterns. Pattern matching is case-insensitive (web hosts and schemes are). A value that isn't visible ASCII matches nothing — a browser never sends one.

origin

type string

allowed_patterns

type readonly RegExp[]

returns

boolean

should_validate_output
#

actions/action_event_helpers.ts view source

(kind: "request_response" | "remote_notification" | "local_call", phase: "send_request" | "receive_request" | "send_response" | "receive_response" | "send_error" | "receive_error" | "send" | "receive" | "execute"): boolean import {should_validate_output} from '@fuzdev/fuz_app/actions/action_event_helpers.js';

kind

phase

returns

boolean

ShouldDeliverFn
#

actions/broadcast_api.ts view source

ShouldDeliverFn import type {ShouldDeliverFn} from '@fuzdev/fuz_app/actions/broadcast_api.js';

Per-connection delivery predicate for subscription ACLs.

Called once per connection for every broadcast send. Returning false skips that connection. Keep it fast — this runs in the broadcast hot path.

input is the already-validated payload (matches the spec's input schema); method is the action method name.

(call)

type (connection: ConnectionIdentity, method: string, input: unknown): boolean

connection

method

type string

input

type unknown
returns boolean

SIDEBAR_NARROW_QUERY_DEFAULT
#

sidebar_state_context
#

SidebarState
#

ui/sidebar_state.svelte.ts view source

import {SidebarState} from '@fuzdev/fuz_app/ui/sidebar_state.svelte.js';

narrow_query

The media query for narrow mode, or null when narrow mode is disabled.

type string | null

readonly

sidebar_id

The sidebar element's id, or null to let AppShell generate one.

type string | null

readonly

show_sidebar_wide

Whether the sidebar shows beside the content outside narrow mode — the desktop preference. Narrow-mode interactions never touch it.

type boolean

$state.raw

show_sidebar_narrow

Whether the narrow-mode overlay is open.

type boolean

$state.raw

constructor

Creates the state, subscribing to narrow_query unless it is null.

type new (options?: SidebarStateOptions | undefined): SidebarState

options?

enabled getter, narrow-mode query, and sidebar id

optional

throws

  • Error - in DEV when `narrow_query` has no parentheses — `MediaQuery` would auto-wrap it but CSS `media` would not, so they'd disagree

toggle_sidebar

Sets the current mode's visibility, routed by narrow at call time.

type (value?: boolean): void

value

the new visibility, defaults to the inverse of show_sidebar

type boolean
default !this.show_sidebar
returns void

close_narrow

Closes the narrow-mode overlay, leaving the wide preference untouched. AppShell calls it on navigation, Escape, scrim click, and when the viewport widens out of narrow mode.

type (): void

returns void

activate

Show the sidebar and enable the toggle. The returned disposer hides and disables on cleanup — pair with $effect for scoped activation.

type (): () => void

returns () => void

mutates

  • this — sets `enabled` and `show_sidebar_wide`; the disposer clears `enabled`, `show_sidebar_wide`, and `show_sidebar_narrow`

enabled

type boolean

gettersetter

narrow

Whether the viewport matches narrow_query. Always false during SSR and when narrow_query is null. Read it for input handling and ARIA only — markup that must match the server render should key on show_sidebar_wide / show_sidebar_narrow instead.

type boolean

getter

show_sidebar

Effective visibility for the current mode: false while disabled, otherwise show_sidebar_narrow in narrow mode and show_sidebar_wide outside it. The setter writes the current mode's field.

type boolean

gettersetter

SidebarStateOptions
#

ui/sidebar_state.svelte.ts view source

SidebarStateOptions import type {SidebarStateOptions} from '@fuzdev/fuz_app/ui/sidebar_state.svelte.js';

enabled?

Reactive getter that controls whether the sidebar is enabled. When supplied, overrides the internal enabled state — show_sidebar auto-returns false while the getter returns false.

type (): boolean

returns boolean

narrow_query?

Media query under which the sidebar overlays the content and starts closed. It feeds both AppShell's CSS media attribute and a MediaQuery, so it must be a full query with parentheses ('(max-width: 900px)', not 'max-width: 900px'). null disables narrow mode.

type string | null

default SIDEBAR_NARROW_QUERY_DEFAULT

sidebar_id?

The sidebar element's id. Set it when a toggle outside AppShell needs aria-controls — the relationship closing the overlay also follows to return focus when the opener didn't take it (Safari doesn't focus a clicked button). Omitted, AppShell generates one, which only its own toggle knows. Set, it assumes one shell per state: shells mounted at the same time with this state would render the same id.

type string

SignupForm
#

ui/SignupForm.svelte view source

import SignupForm from '@fuzdev/fuz_app/ui/SignupForm.svelte';

redirect_on_signup?

Path to navigate to after a successful signup.

type string
optional default resolve('/')

SignupInput
#

auth/signup_route_schema.ts view source

value + type

{ username: string; password: string; email?: string | null | undefined; } import {SignupInput} from '@fuzdev/fuz_app/auth/signup_route_schema.js';

Input for POST /signup. email is optional (absent or null = no email) and must match any referenced invite.

username

type string

password

type string

email?

type string | null

SignupOutput
#

auth/signup_route_schema.ts view source

value + type

{ ok: true; account: { id: string & $brand<"Uuid">; username: string; }; actor: { id: string & $brand<"Uuid">; }; } import {SignupOutput} from '@fuzdev/fuz_app/auth/signup_route_schema.js';

Output for POST /signup.

Session cookie is the operative side effect. The returned account and actor mirror BootstrapOutput so cross-process per-test setup can read the per-test identity straight off the signup response.

ok

type true

account

type { id: string & $brand<"Uuid">; username: string; }

actor

type { id: string & $brand<"Uuid">; }

SignupRouteOptions
#

auth/signup_routes.ts view source

SignupRouteOptions import type {SignupRouteOptions} from '@fuzdev/fuz_app/auth/signup_routes.js';

Per-factory configuration for signup route specs.

inheritance

signup_ip_rate_limiter

Rate limiter for signup attempts, keyed by client IP. Pass null to disable. Its own instance, not login's: signup failures bound username *enumeration* (the 403/409 split), not credential guessing, and an open-signup deployment lets any unauthenticated caller spend this budget — which must not cost anyone their login attempts. Charged by every classified failure and by every successful open signup (an invite-gated success spends an invite instead); never refunded on success (see RateLimiter.reset). Mirrors the Rust spine's signup_ip_rate_limiter.

type RateLimiter | null

signup_account_rate_limiter

Rate limiter for signup attempts, keyed by submitted username. Pass null to disable.

type RateLimiter | null

signup_fail_floor_ms?

Minimum wall-clock time (ms) for signup denial responses (403 / 409). Set to 0 or a negative number to disable (e.g., in tests). Default DEFAULT_SIGNUP_FAIL_FLOOR_MS. 429 responses are not floored.

type number

signup_fail_jitter_ms?

Uniform jitter window (±ms) layered on the floor. Set to 0 to disable jitter while keeping the floor. Default DEFAULT_SIGNUP_FAIL_JITTER_MS.

type number

SignupRouteShapeOptions
#

auth/signup_route_schema.ts view source

SignupRouteShapeOptions import type {SignupRouteShapeOptions} from '@fuzdev/fuz_app/auth/signup_route_schema.js';

Option inputs that shape the signup route metadata (not its handler).

signup_account_rate_limited

Whether a per-account signup rate limiter is wired — toggles rate_limit.

type boolean

SimpleRequestCrossTestOptions
#

testing/cross_backend/simple_request.ts view source

SimpleRequestCrossTestOptions import type {SimpleRequestCrossTestOptions} from '@fuzdev/fuz_app/testing/cross_backend/simple_request.js';

Options for the simple-request parity suite.

inheritance

login_path?

REST login route path. Default /api/account/login (the spine convention).

type string

readonly

logout_path?

REST logout route path. Default /api/account/logout (the spine convention).

type string

readonly

socket_status_to_async_status
#

actions/socket.svelte.ts view source

(status: SocketStatus, revoked: boolean): AsyncStatus import {socket_status_to_async_status} from '@fuzdev/fuz_app/actions/socket.svelte.js';

Project SocketStatus onto fuz_util's AsyncStatus — the 5-way → 4-way mapping every consumer re-derives to surface connection state to UI (loading indicators, retry banners). Collapses reconnecting into failure (UI shows "lost, retrying") and splits closed by revoked so a terminal session-revocation read as failure while a clean client- initiated close reads as initial (the "not connected, not trying" state). A superseded close reads as initial too — the client is not trying and an explicit connect() reopens it; read FrontendWebsocketClient.superseded to tell the user why, and before connecting on an initial status without the user asking (connecting a superseded client on its own closes a newer socket in turn, which is the loop the close code exists to stop). A WS_CLOSE_PERMISSION_REVOKED close reads as initial the same way, with neither flag set: check FrontendWebsocketClient.last_close_code before connecting on your own, since without the role the upgrade is refused at the gate.

status

revoked

whether the session has been permanently revoked (typically FrontendWebsocketClient.revoked)

type boolean

returns

AsyncStatus

SocketCloseContext
#

actions/register_action_ws.ts view source

SocketCloseContext import type {SocketCloseContext} from '@fuzdev/fuz_app/actions/register_action_ws.js';

Context passed to the on_socket_close hook.

Fires after the transport has removed the connection — connection_id and identity are the values captured at open, so consumer cleanup reads them from here, not from the transport. Fires for both client-initiated closes (Hono onClose) and server-initiated closes — a revocation, the per-account connection cap's eviction, and a heartbeat timeout all call ws.close(), which triggers Hono's onClose. Fires only for a socket that was admitted: one refused at admission never ran on_socket_open, so it has no close hook either.

ws

The WebSocket context the close event carried. Not necessarily the object on_socket_open received — an adapter may build one per event — so key per-socket state on connection_id.

type WSContext<unknown>

connection_id

Connection id captured at open time.

type Uuid

identity

Auth identity captured at open time — the transport no longer holds it.

type ConnectionIdentity

SocketErrorHandler
#

actions/socket.svelte.ts view source

SocketErrorHandler import type {SocketErrorHandler} from '@fuzdev/fuz_app/actions/socket.svelte.js';

(call)

type (event: Event): void

event

type Event
returns void

SocketMessageHandler
#

actions/socket.svelte.ts view source

SocketMessageHandler import type {SocketMessageHandler} from '@fuzdev/fuz_app/actions/socket.svelte.js';

(call)

type (event: MessageEvent<any>): void

event

type MessageEvent<any>
returns void

SocketOpenContext
#

actions/register_action_ws.ts view source

SocketOpenContext import type {SocketOpenContext} from '@fuzdev/fuz_app/actions/register_action_ws.js';

Context passed to the on_socket_open hook.

Fires once the connection is admitted — registered in the transport and its credential re-checked (so connection_id is valid, and broadcasts reach it) — and before any client message dispatches. A socket refused at admission never reaches it. Consumers use this to bootstrap per-socket domain state — e.g. spawning a per-account unit and pushing an initial state snapshot.

ws

The raw WebSocket context — exposed for edge cases; prefer notify for sends. A frame sent on it directly bypasses the connection's outbound budget and its ordering with queued frames.

type WSContext<unknown>

connection_id

Connection id assigned by BackendWebsocketTransport.register_pending.

type Uuid

identity

Auth identity registered for this connection.

type ConnectionIdentity

notify

Send a JSON-RPC notification to just this socket. Mirrors ctx.notify on per-message handler contexts — same socket-scoped semantics.

type (method: string, params: unknown): void

method

type string

params

type unknown
returns void

signal

Fires when this socket ends — the client closed it, or the server did (revocation, cap eviction, heartbeat timeout), at the moment the server closes rather than when the close handshake lands. Threaded through to every handler's ctx.signal.

type AbortSignal

SocketStatus
#

actions/socket.svelte.ts view source

SocketStatus

type "initial" | "connecting" | "connected" | "reconnecting" | "closed"

import type {SocketStatus} from '@fuzdev/fuz_app/actions/socket.svelte.js';

Client-side WebSocket status.

  • initial — never connected; connect() has not been called.
  • connecting — WebSocket readyState === CONNECTING.
  • connected — WebSocket readyState === OPEN.
  • reconnecting — close fired; waiting out backoff before next attempt.
  • closed — socket is not open, and nothing is scheduled to reopen it. Terminal only when revoked is true; otherwise connect() reopens, including after a superseded or permission-revoked close.

soft_delete_test_actor
#

testing/db_entities.ts view source

(db: Db, actor_id: string): Promise<boolean> import {soft_delete_test_actor} from '@fuzdev/fuz_app/testing/db_entities.js';

Soft-delete (tombstone) one actor via the production query_actor_soft_delete — returns true when an active row flipped. The TS twin of the Rust query_actor_soft_delete the guard tests use, for seeding the latent multi-actor tombstone state directly (no per-actor-delete RPC ships yet).

db

type Db

actor_id

type string

returns

Promise<boolean>

SPACE_CELL_KIND
#

spawn_backend
#

testing/cross_backend/spawn_backend.ts view source

(config: BackendConfig): Promise<BackendHandle> import {spawn_backend} from '@fuzdev/fuz_app/testing/cross_backend/spawn_backend.js';

Spawn config.start_command and return a handle once the binary is health-probe-ready and the daemon-token file is readable.

Errors at any stage SIGTERM the child group before rethrowing — the caller never sees a half-started backend.

config

returns

Promise<BackendHandle>

SpecSource
#

actions/action_codegen.ts view source

SpecSource import type {SpecSource} from '@fuzdev/fuz_app/actions/action_codegen.js';

One source in a multi-source consumer's namespace map. ns is the local alias used inside the generated file; module is the import path; specs is the runtime spec array. create_namespace_qualifier consumes a list of these.

ns

type string

module

type string

specs

type readonly ActionSpecUnion[]

SPINE_CELL_EDITOR_ROLE
#

testing/cross_backend/spine_surface_constants.ts view source

"cell_editor" import {SPINE_CELL_EDITOR_ROLE} from '@fuzdev/fuz_app/testing/cross_backend/spine_surface_constants.js';

App role the role-shaped-cell_grant cross suite exercises. Registered with no grant path (grant_paths: []) so it stays a valid registry member without entering the admin / self-service grant flows — holders are seeded directly via extra_accounts. Must match the cell_editor entry in the Rust testing_spine_stub's known_roles (cross-language test contract).

SPINE_EXPECTED_SCHEMA_URL
#

testing/cross_backend/spine_surface_constants.ts view source

URL import {SPINE_EXPECTED_SCHEMA_URL} from '@fuzdev/fuz_app/testing/cross_backend/spine_surface_constants.js';

Committed expected-schema fixture for the spine /ready deploy gate — the column map a fresh full spine bootstrap (auth + cell + cell_history + fact) produces. Resolved relative to this module so the spawned TS binary (which imports this source under its loader) reads it off disk via node:fs. Regenerated + guarded by src/test/cross_backend/spine_expected_schema.db.test.ts.

The Rust testing_spine_stub reads the same committed file (its absolute path passed via env by rust_spine_stub_backend_config) — column-presence is engine-portable, so one fixture is the cross-impl contract.

SPINE_PARTICIPANT_ROLE
#

testing/cross_backend/spine_surface_constants.ts view source

"participant" import {SPINE_PARTICIPANT_ROLE} from '@fuzdev/fuz_app/testing/cross_backend/spine_surface_constants.js';

Admin-grantable app role the role-gated-participation cross suite exercises. Registered with grant_paths: ['admin'] so it enters the admin grant flow — the cross-backend proof that an app-defined role is conferrable (offer / role_grant_assign) admin-only on both spines. Must match the participant entry in the Rust testing_spine_stub's RoleRegistry and its known_roles (the registry feeds the cell vocabulary too) — a cross-language test contract. Distinct from SPINE_CELL_EDITOR_ROLE (no grant path): this one is the *grantable* role, that one is the bootstrap-seed-only cell role.

SPINE_ROLE_GATED_WS_PATH
#

testing/cross_backend/spine_surface_constants.ts view source

"/api/gated/ws" import {SPINE_ROLE_GATED_WS_PATH} from '@fuzdev/fuz_app/testing/cross_backend/spine_surface_constants.js';

Mount path of the spine binaries' second, role-gated WebSocket endpoint — the same actions as the ungated /api/ws, behind SPINE_ROLE_GATED_WS_ROLES. Both spines mount it (the TS binary sharing /api/ws's transport, the Rust stub its connection registry), so one account can hold a gated and an ungated socket side by side. Driven by describe_ws_role_gated_cross_tests, behind capabilities.ws_role_gated. Must match the Rust testing_spine_stub's gated mount — a cross-language test contract.

SPINE_ROLE_GATED_WS_ROLES
#

testing/cross_backend/spine_surface_constants.ts view source

readonly string[] import {SPINE_ROLE_GATED_WS_ROLES} from '@fuzdev/fuz_app/testing/cross_backend/spine_surface_constants.js';

The roles SPINE_ROLE_GATED_WS_PATH requires, any-of: admin, the gate a privileged-broadcast socket uses, and participant, a role that can be granted scoped — so the suite can revoke a scoped grant of a listed role and see nothing close. Must match the Rust testing_spine_stub's gated mount.

spine_roles
#

testing/cross_backend/default_spine_surface.ts view source

RoleSchemaResult import {spine_roles} from '@fuzdev/fuz_app/testing/cross_backend/default_spine_surface.js';

The spine's closed role registry: built-ins plus two app roles — SPINE_CELL_EDITOR_ROLE (no grant path; the role-shaped-cell_grant suite's bootstrap-seeded role) and SPINE_PARTICIPANT_ROLE (grant_paths: ['admin']; the role-gated-participation suite's admin-grantable role). Threaded into the cell spec set's role-validity gate and the auth grantability gates; the Rust stub mirrors the same membership in both its RoleRegistry and known_roles. The participant entry also gives the admin suite real app-role grant-path coverage (admin_account_list.grantable_roles carries it on both spines).

spine_rpc_endpoints
#

testing/cross_backend/default_spine_surface.ts view source

(ctx: AppServerContext, options?: SpineRpcEndpointsOptions | undefined): RpcEndpointSpec[] import {spine_rpc_endpoints} from '@fuzdev/fuz_app/testing/cross_backend/default_spine_surface.js';

Factory-form RPC endpoints over the per-test ctx.deps. create_app_server (in the binary) owns live dispatch; the surface builder invokes the factory once with a stub ctx for setup-time path/method lookup, so the handler closures are never called across the process boundary.

Test binaries append their own _testing_reset action to this endpoint's actions (see testing/cross_backend/testing_reset_actions.ts); it is intentionally excluded here so it stays off the declared surface (the harness calls it directly over the daemon-token channel).

options.notification_sender, when supplied, reaches the role-grant-offer sub-factory so the spine emits the WS notification family — see SpineRpcEndpointsOptions.

ctx

options?

optional

returns

RpcEndpointSpec[]

SPINE_RPC_PATH
#

spine_session_options
#

testing/cross_backend/default_spine_surface.ts view source

SessionOptions<string>

type SessionOptions<string>

import {spine_session_options} from '@fuzdev/fuz_app/testing/cross_backend/default_spine_surface.js';

Session config — cookie name matches the binary's issued session cookie (fuz_session) so cookie-attribute assertions + jar extraction line up.

SPINE_SSE_PATH
#

testing/cross_backend/spine_surface_constants.ts view source

"/api/admin/audit/stream" import {SPINE_SSE_PATH} from '@fuzdev/fuz_app/testing/cross_backend/spine_surface_constants.js';

Audit-log SSE stream path — /api/admin prefix + the create_audit_log_route_specs /audit/stream route. Matches the default BackendConfig.sse_path and the cross-process SSE suite's default. Only mounted by the TS spine binary (which wires audit_log_sse); the shared surface stub leaves ctx.audit_sse null so the snapshot stays SSE-free.

SpineRpcEndpointsOptions
#

testing/cross_backend/default_spine_surface.ts view source

SpineRpcEndpointsOptions import type {SpineRpcEndpointsOptions} from '@fuzdev/fuz_app/testing/cross_backend/default_spine_surface.js';

Options for spine_rpc_endpoints.

notification_sender?

WS notification sender threaded into the role-grant-offer sub-factory for server-initiated fan-out (role_grant_offer_received / _accepted / _declined / _retracted / _supersede, flat role_grant_revoke).

Shared-instance trap. Pass the SAME BackendWebsocketTransport instance the WS endpoint registers connections against — the transport *is* the connection registry, so a separate instance would fan out to an empty registry and reach nobody (silently). The TS spine binary constructs one ws_transport and threads it both here and into register_ws_endpoint.

Omitted (the default) for the shared create_spine_surface_spec path — surface generation doesn't depend on it, and it must stay absent there so the declared snapshot is unaffected.

type NotificationSender | null

readonly

SpineStubBackendConfigOptions
#

testing/cross_backend/rust_spine_stub_backend_config.ts view source

SpineStubBackendConfigOptions import type {SpineStubBackendConfigOptions} from '@fuzdev/fuz_app/testing/cross_backend/rust_spine_stub_backend_config.js';

port?

Listening port. Default RUST_SPINE_STUB_DEFAULT_PORT.

type number

readonly

database_url?

Postgres connection URL. Default $FUZ_TESTING_RUST_SPINE_STUB_DATABASE_URL, then RUST_SPINE_STUB_DEFAULT_DATABASE_URL.

type string

readonly

binary_path?

Prebuilt binary path. Overrides the FUZ_TESTING_RUST_SPINE_STUB_BIN env var. When neither is set the preset throws.

type string

readonly

enable_login_rate_limit?

Enable the per-IP + per-account login rate limiters on the stub (FUZ_LOGIN_RATE_LIMIT_ENABLED=true). Off by default — the standard cross suites fire many loopback logins a live limiter would 429. Set true only for the dedicated login-security cross project (global_setup_login_security.ts). Pair with trusted_proxies so the limiter keys on the resolved X-Forwarded-For client IP. Mirrors TsSpineBackendConfigOptions.enable_login_rate_limit.

type boolean

readonly

trusted_proxies?

Comma-separated trusted-proxy allowlist passed as FUZ_TRUSTED_PROXIES (e.g. '127.0.0.1,::1'). Unset by default (the stub leaves XFF parsing off, keying on the raw TCP peer). The login-security project sets the loopback set so the limiter keys on the X-Forwarded-For client IP — the TS spine binary wires the equivalent set unconditionally.

type string

readonly

enable_action_rate_limit?

Enable the action rate limiters (FUZ_ACTION_RATE_LIMIT_ENABLED=true) — one per-IP and one per-account limiter shared by the RPC and WS endpoints. Off by default, like enable_login_rate_limit; set only for the dedicated security cross project, which drives them through ws_action_rate_limit.ts. Mirrors TsSpineBackendConfigOptions.enable_action_rate_limit.

type boolean

readonly

action_rate_limit_max_attempts?

Replace both action limiters' max_attempts (FUZ_ACTION_RATE_LIMIT_MAX_ATTEMPTS) so a suite reaches the cap in a few calls. Requires enable_action_rate_limit — the binary refuses to boot otherwise. Mirrors TsSpineBackendConfigOptions.action_rate_limit_max_attempts.

type number

readonly

SSE_CONNECTED_COMMENT
#

realtime/sse_constants.ts view source

": connected\n\n" import {SSE_CONNECTED_COMMENT} from '@fuzdev/fuz_app/realtime/sse_constants.js';

The comment line written immediately on SSE stream open. Flushes headers + confirms the connection is live before the first real event. Cross-process SSE tests assert the stream emits this on connect.

SSE_FRAME_READ_TIMEOUT_MS
#

SSE_OUTBOUND_QUEUE_BYTES
#

realtime/sse_constants.ts view source

number import {SSE_OUTBOUND_QUEUE_BYTES} from '@fuzdev/fuz_app/realtime/sse_constants.js';

The byte budget of what one SSE stream holds for its client to read (realtime/sse.ts module doc, "Outbound bound").

1 MiB — equal to the default cap on one request body (DEFAULT_MAX_BODY_SIZE). An audit row is bounded by the request that produced it, so on a server that keeps that default the budget holds a burst of ordinary rows or one at the largest. The value is fixed and does not follow the server's body limit. Same value as the Rust spine's SSE_OUTBOUND_QUEUE_BYTES.

SseFrameReader
#

testing/transports/sse_frame_reader.ts view source

SseFrameReader import type {SseFrameReader} from '@fuzdev/fuz_app/testing/transports/sse_frame_reader.js';

Frame-level reader returned by create_sse_frame_reader.

read_frame

Read one complete SSE frame (up to the next \n\n), without the trailing terminator. Throws if the per-read timeout elapses or the stream ends before a frame arrives.

type (timeout_ms?: number | undefined): Promise<string>

timeout_ms?

type number
optional
returns Promise<string>

wait_for_close

Drain until the server closes the stream. Resolves true if the stream closes within timeout_ms, false on timeout.

type (timeout_ms?: number | undefined): Promise<boolean>

timeout_ms?

type number
optional
returns Promise<boolean>

cancel

Cancel the underlying reader. Safe to call when already closed.

type (): Promise<void>

returns Promise<void>

SseNotification
#

realtime/sse.ts view source

SseNotification import type {SseNotification} from '@fuzdev/fuz_app/realtime/sse.js';

Notification shape aligned with JSON-RPC 2.0.

Uses {method, params} to match the JSON-RPC notification format.

method

Notification method name (e.g. 'run_created', 'host_updated').

type string

params

Method-specific payload.

type unknown

SseResponseOptions
#

realtime/sse.ts view source

SseResponseOptions import type {SseResponseOptions} from '@fuzdev/fuz_app/realtime/sse.js';

Options for create_sse_response.

account_id?

The account the stream belongs to, named in the warning logged when the stream ends on its outbound budget.

type string | null

SseRouteTestOptions
#

testing/sse_round_trip.ts view source

SseRouteTestOptions import type {SseRouteTestOptions} from '@fuzdev/fuz_app/testing/sse_round_trip.js';

session_options

Session config for cookie-based auth.

type SessionOptions<string>

create_route_specs

Route spec factory — same shape as production.

type (ctx: AppServerContext): RouteSpec[]

ctx

returns RouteSpec[]

app_options?

Optional overrides for AppServerOptions.

type Partial<Omit<AppServerOptions, "backend" | "bootstrap" | "session_options" | "create_route_specs" | "rpc_endpoints">>

db_factories?

Database factories to run tests against. Default: the default factory (create_default_db_factory) only.

type DbFactory[]

on_audit_event?

Backend audit event callback — threaded to create_test_app_server. Use to wire a close-on-revoke guard for consumer SSE registries (e.g., via create_sse_auth_guard) so session_revoke_all closes the tested streams — not needed for a registry the route joined to ctx.deps.connection_closer, which the revoking handler closes itself.

type (event: AuditLogEvent): void

event

returns void

rpc_endpoints

RPC endpoint specs — required so the close-on-revoke assertion can dispatch account_session_revoke_all via RPC (there is no REST equivalent). Hard-fails via require_rpc_endpoint_path on setup.

Accepts either an array (eager) or a factory (ctx: AppServerContext) => Array<RpcEndpointSpec> — the factory form is required when action handlers must close over the per-test ctx.deps. The factory must return the same endpoint path regardless of ctx — it is invoked once at setup with a stub ctx for path lookup and again per-test by create_app_server for live dispatch.

type RpcEndpointsSuiteOption

routes

SSE routes to exercise.

type SseRouteTestSpec[]

SseRouteTestSpec
#

testing/sse_round_trip.ts view source

SseRouteTestSpec import type {SseRouteTestSpec} from '@fuzdev/fuz_app/testing/sse_round_trip.js';

Config for a single SSE route under test.

path

Full HTTP path of the SSE endpoint (e.g., '/api/zap/subscribe').

type string

trigger

Fire an event matching one of the declared event_specs that should reach the open stream. Called after the : connected comment is observed. The triggered frame must be a JSON-serializable {method, params} payload.

type (ctx: { test_app: TestApp; account: TestAccount; }): Promise<void>

ctx

type { test_app: TestApp; account: TestAccount; }
returns Promise<void>

event_specs?

Event specs to validate the triggered payload against. When omitted, the payload is only asserted to be well-formed {method, params}.

type EventSpec[]

assert_closes_on_revoke?

Whether to assert the stream closes after session_revoke_all. Default true. Set false for endpoints whose registry no revocation reaches — neither joined to the backend's closer nor guarded (leaves a TODO to fix, rather than silently passing).

type boolean

SseStream
#

realtime/sse.ts view source

SseStream<T> import type {SseStream} from '@fuzdev/fuz_app/realtime/sse.js';

Generic SSE stream controller interface.

Transport-agnostic — works with any serializable type.

generics

SseStream<T = unknown>
T
default unknown

send

Send data to the client as a JSON SSE event. Never waits: a frame the stream's outbound budget does not fit ends the stream instead (module doc, "Outbound bound").

type (data: T): void

data

type T
returns void

comment

Send a comment (for keep-alive pings).

type (text: string): void

text

type string
returns void

close

Close the stream.

type (): void

returns void

on_close

Register a listener called when the stream closes (client disconnect or explicit close). Called at once when the stream has already closed — a client can leave before the caller registers — so a cleanup registered through it always runs.

type (fn: () => void): void

fn

type () => void
returns void

SseTransport
#

testing/transports/sse_transport.ts view source

SseTransport import type {SseTransport} from '@fuzdev/fuz_app/testing/transports/sse_transport.js';

A cross-process SSE client: read frames, await server close, cancel.

read_frame

Read one complete SSE frame (up to the next \n\n), without the trailing terminator. Throws if the per-read timeout elapses or the stream ends before a frame arrives.

type (timeout_ms?: number | undefined): Promise<string>

timeout_ms?

type number
optional
returns Promise<string>

wait_for_close

Drain until the server closes the stream. Resolves true if the stream closes within timeout_ms, false on timeout. The signal for a revocation dropping a live stream — mirrors WsClient.wait_for_close.

type (timeout_ms?: number | undefined): Promise<boolean>

timeout_ms?

type number
optional
returns Promise<boolean>

close

Cancel the reader (client-initiated close). Safe to call when already closed.

type (): Promise<void>

returns Promise<void>

SseTransportOptions
#

testing/transports/sse_transport.ts view source

SseTransportOptions import type {SseTransportOptions} from '@fuzdev/fuz_app/testing/transports/sse_transport.js';

Construction options for create_sse_transport.

base_url

Base URL the binary is reachable at — e.g. http://localhost:1178.

type string

readonly

sse_path

SSE endpoint path on the binary (e.g. /api/admin/audit/stream).

type string

readonly

cookies

Session cookie values (full Set-Cookie strings as FetchTransport.cookies() returns them) threaded onto the request Cookie header. Without these the stream is anonymous and the connect is refused (the audit stream requires an admin session).

type readonly string[]

readonly

origin?

Origin header for the request. Backends running with ALLOWED_ORIGINS=http://localhost:* accept http://localhost:<port>. Defaults to base_url — acceptable because cross-process tests always run against localhost.

type string

readonly

default_timeout_ms?

Default per-read / wait-for-close timeout. Falls back to 2000ms.

type number

readonly

StandardAdminIntegrationTestOptions
#

testing/admin_integration.ts view source

StandardAdminIntegrationTestOptions import type {StandardAdminIntegrationTestOptions} from '@fuzdev/fuz_app/testing/admin_integration.js';

setup_test

Per-test fixture-producing function. The admin suite calls this in every test() body — auth_integration_truncate_tables clears account, so each test re-bootstraps the keeper.

type (): Promise<TestFixtureBase>

returns Promise<TestFixtureBase>

surface_source

App surface (with route specs + middleware specs) for route iteration and error-coverage scoping. Constructed in TS by the consumer (same shape for in-process and cross-process tests).

type AppSurfaceSpec

session_options

Session config — needed for cookie_name + factory-form rpc_endpoints resolution.

type SessionOptions<string>

roles

Role schema result from create_role_schema() — used to determine valid/invalid/web-grantable roles.

type RoleSchemaResult

rpc_endpoints

RPC endpoint specs — the source RpcAction arrays. Required; role_grant grant/revoke are RPC-only and the suite hard-fails without them.

type RpcEndpointsSuiteOption

admin_prefix?

Path prefix where admin routes are mounted (e.g., '/api/admin'). Used by the 401/403 error-coverage probe to scope to fuz_app admin routes only, avoiding app-specific admin-gated routes that may use stub deps. Default '/api/admin'.

type string

StandardAttackSurfaceOptions
#

testing/attack_surface.ts view source

StandardAttackSurfaceOptions import type {StandardAttackSurfaceOptions} from '@fuzdev/fuz_app/testing/attack_surface.js';

Options for the standard attack surface test suite.

build

Build the app surface bundle (surface + route specs + middleware specs).

type (): AppSurfaceSpec

snapshot_path

Absolute path to the committed snapshot JSON file.

type string

expected_public_routes

Expected public routes, e.g. ['GET /health', 'POST /api/account/login'].

type string[]

expected_api_middleware

Expected middleware names for API routes, e.g. ['trusted_proxy', 'origin', 'session', 'request_context', 'bearer_auth'].

type string[]

roles

All roles in the app (e.g. ['admin', 'keeper']).

type string[]

api_path_prefix?

Path prefix for middleware stack assertion. Default '/api/'.

type string

security_policy?

Security policy configuration. Omit for sensible defaults.

type SurfaceSecurityPolicyOptions

error_schema_tightness?

Error schema tightness assertion config. Defaults to default_error_schema_tightness (ignores 401/403/429, min_specificity: 'enum', allowlist seeded with fuz_app_stock_route_tightness_allowlist).

Consumer-supplied allowlist and ignore_statuses are additive — the suite merges them underneath the stock defaults, so project-specific entries don't need to re-list fuz_app's own stock routes. Pass a narrower config to extend either list or tighten min_specificity; pass null to skip the assertion and keep the audit log informational-only.

type ErrorSchemaTightnessOptions | null

skip_routes?

Routes the three adversarial sub-suites skip, in 'METHOD /path' form — the escape hatch every sibling suite carries (round_trip, data_exposure). Reach for it only when a route cannot be driven generically at all; a route whose path params merely carry a format is handled by the params-schema-aware synthesizer.

type string[]

StandardCrossProcessTestOptions
#

testing/cross_backend/standard.ts view source

StandardCrossProcessTestOptions import type {StandardCrossProcessTestOptions} from '@fuzdev/fuz_app/testing/cross_backend/standard.js';

Configuration for describe_standard_cross_process_tests.

Mirrors StandardTestOptions minus the in-process-only knobs (create_route_specs, bootstrap, rate_limiting_app_options, bootstrap_token) — those drive the three omitted suites.

setup_test

Per-test fixture-producing function.

type (): Promise<TestFixtureBase>

returns Promise<TestFixtureBase>

surface_source

App surface. Constructed in TS by the consumer; same shape for in-process and cross-process tests.

type AppSurfaceSpec

capabilities

Backend capability declarations. Forwarded to the one bundled suite that gates on them (describe_standard_integration_tests); the other suites in the bundle read no flags and take no capability field.

type BackendCapabilities

session_options

Session config — needed for cookie_name + factory-form rpc_endpoints resolution.

type SessionOptions<string>

rpc_endpoints

RPC endpoint specs — required. The standard integration tests drive account_verify, account_session_*, account_token_* through the RPC surface (and admin tests, when wired, drive role_grant grant/revoke through it too).

type RpcEndpointsSuiteOption

roles?

Role schema result from create_role_schema(). When provided, the admin integration suite is included.

type RoleSchemaResult

admin_prefix?

Path prefix where admin routes are mounted. Default '/api/admin'.

type string

error_coverage_min?

Forwarded to describe_standard_integration_tests — overrides the default error-coverage threshold on the scoped REST surface. Set to 0 to skip the assertion entirely.

type number

round_trip_skip_routes?

Forwarded to describe_round_trip_validation as skip_routes ('METHOD /path' keys). For consumer REST routes whose responses aren't JSON-with-an-output-schema and so can't be round-tripped — e.g. fuz_forge's git smart-HTTP routes (git-upload-pack / git-receive-pack / info/refs) which stream git protocol bytes.

type string[]

data_exposure_skip_routes?

Forwarded to describe_data_exposure_tests as skip_routes ('METHOD /path' keys), as the in-process bundle's consumers pass it directly.

type string[]

rpc_success_fixtures?

Forwarded to describe_rpc_round_trip_tests as success_fixtures (method name → async params factory). Drives a populated success body for referential RPC reads (*_get, *_log) the nil-id round-trip can only ever error on, and validates it against the method's output schema on each backend — the success-shape parity check. See RpcRoundTripTestOptions.success_fixtures.

type Map<string, (fixture: TestFixtureBase) => Promise<Record<string, unknown>>>

rest_success_fixtures?

Forwarded to describe_round_trip_validation as success_fixtures ('METHOD /path' → async {url?, body?} factory) for referential REST routes. See RoundTripTestOptions.success_fixtures.

type Map<string, (fixture: TestFixtureBase) => Promise<{ url?: string | undefined; body?: Record<string, unknown> | undefined; }>>

StandardIntegrationTestOptions
#

testing/integration.ts view source

StandardIntegrationTestOptions import type {StandardIntegrationTestOptions} from '@fuzdev/fuz_app/testing/integration.js';

setup_test

Per-test fixture-producing function. The integration suite calls this in every test() body — auth_integration_truncate_tables clears account, so each test re-bootstraps the keeper.

type (): Promise<TestFixtureBase>

returns Promise<TestFixtureBase>

surface_source

App surface (with route specs + middleware specs) for route iteration and error-coverage scoping. The same shape feeds both in-process and cross-process tests — the test process always constructs the spec in TS (via create_test_app_surface_spec or a consumer's equivalent); cross-process-ness is a property of the transport + per-test fixture, not the schema source. The on-disk auth_attack_surface.json is an observability artifact for human inspection + gen-time drift gating, not the source the test runtime reads from.

type AppSurfaceSpec

capabilities

Backend capability declarations for capability-gated cases.

type BackendCapabilities

session_options

Session config — needed to resolve factory-form rpc_endpoints against a stub AppServerContext at setup time and to read cookie_name for manual cookie composition in the session cases.

type SessionOptions<string>

rpc_endpoints

RPC endpoint specs — required. This suite dispatches account_verify, account_session_*, and account_token_* via rpc_call_for_spec (the /api/account/verify REST route is a status-only nginx shim with no payload). Hard-fails via require_rpc_endpoint_path on setup so consumer projects see a clear setup error instead of confusing test failures.

Accepts either an array (eager) or a factory — see testing/rpc_helpers.ts for the union semantics. The factory must return the same endpoint path regardless of ctx — invoked once at setup with a stub ctx for path lookup; the running backend handles live dispatch.

type RpcEndpointsSuiteOption

error_coverage_min?

Minimum error-coverage ratio to enforce on the scoped REST surface (login / logout / password / signup + the shared RPC endpoint). Default DEFAULT_INTEGRATION_ERROR_COVERAGE (0.2). Set to 0 to skip the assertion entirely — useful for consumers with minimal route sets whose declared error codes outpace the suite's denial-path drivers.

type number

StandardRpcActionsDeps
#

auth/standard_rpc_actions.ts view source

StandardRpcActionsDeps import type {StandardRpcActionsDeps} from '@fuzdev/fuz_app/auth/standard_rpc_actions.js';

Dependencies for create_standard_rpc_actions.

Stack-standard RevokingActionFactoryDeps (log, audit, connection_closer) plus an optional notification_sender consumed only by the role-grant-offer sub-factory for WS fan-out. Admin and account sub-factories ignore notification_sender.

inheritance

notification_sender?

type NotificationSender | null

StandardRpcActionsOptions
#

auth/standard_rpc_actions.ts view source

StandardRpcActionsOptions import type {StandardRpcActionsOptions} from '@fuzdev/fuz_app/auth/standard_rpc_actions.js';

Options for create_standard_rpc_actions.

Composes AdminActionOptions (roles), RoleGrantOfferActionOptions (roles, default_ttl_ms, authorize), and AccountActionOptions (max_tokens). roles is shared between admin and role-grant-offer — the caller supplies it once and the helper threads the same reference to both.

inheritance

StandardTestOptions
#

testing/standard.ts view source

StandardTestOptions import type {StandardTestOptions} from '@fuzdev/fuz_app/testing/standard.js';

Configuration for describe_standard_tests.

setup_test

Per-test fixture-producing function.

type (): Promise<TestFixtureBase>

returns Promise<TestFixtureBase>

surface_source

App surface. Constructed in TS by the consumer; same shape for in-process and cross-process tests.

type AppSurfaceSpec

capabilities

Backend capability declarations. Forwarded to the one bundled suite that gates on them (describe_standard_integration_tests); the other suites in the bundle read no flags and take no capability field.

type BackendCapabilities

session_options

Session config — needed for cookie_name + factory-form rpc_endpoints resolution.

type SessionOptions<string>

create_route_specs

Route spec factory — same one used in production. Required by describe_rate_limiting_tests, which builds a fresh TestApp per test (bypasses the shared setup_test fixture) so it can pass tight per-test rate-limiter overrides.

type (ctx: AppServerContext): RouteSpec[]

ctx

returns RouteSpec[]

rpc_endpoints

RPC endpoint specs — required. The standard integration tests drive account_verify, account_session_*, account_token_* through the RPC surface (and admin tests, when wired, drive role_grant grant/revoke through it too).

type RpcEndpointsSuiteOption

roles?

Role schema result from create_role_schema(). When provided, admin integration + audit completeness suites are included.

type RoleSchemaResult

bootstrap?

Bootstrap config — when set to mode: 'live', the bootstrap success suite runs against create_test_app_for_bootstrap. Other modes ('disabled' / 'surface_only' / omission) silent-skip the suite.

type BootstrapServerOptions

rate_limiting_app_options?

Optional overrides forwarded to describe_rate_limiting_tests.

type Partial<Omit<AppServerOptions, "backend" | "bootstrap" | "session_options" | "create_route_specs" | "rpc_endpoints">>

admin_prefix?

Path prefix where admin routes are mounted. Default '/api/admin'.

type string

error_coverage_min?

Forwarded to describe_standard_integration_tests — overrides the default error-coverage threshold on the scoped REST surface. Set to 0 to skip the assertion entirely.

type number

bootstrap_token?

Override the bootstrap-success suite's synthetic token.

type string

start_auth_cleanup
#

auth/cleanup.ts view source

(deps: AuthCleanupDeps, options?: AuthCleanupScheduleOptions): PeriodicTask import {start_auth_cleanup} from '@fuzdev/fuz_app/auth/cleanup.js';

Run run_auth_cleanup beside the server: a pass now, then one every interval_ms, until stop is called.

The auth surface's upkeep in one call — start it once migrations have run and stop it before the database closes (see the module doc's § Scheduling). create_app_server's auth_cleanup option does both.

Built on start_periodic, which fixes the loop's rules: the startup pass starts before this returns, passes never overlap, a slow pass delays the schedule rather than bursting, a failed pass — the database out of reach — is logged at warn with its error and retried on the next interval, the timer is unref'd, and stop ends the schedule at once and resolves when a pass in progress has finished. A pass that swept something logs its counts at info, and one that closed a swept session's connections logs that count too.

Twin of the Rust spawn_auth_cleanup over fuz_sys::periodic::spawn_periodic. The schedule is the same; shutdown differs — the Rust task drops a pass in progress when its token is cancelled, which a promise cannot do.

deps

what a pass needs; backend.deps in a server

options

the interval

default {}

returns

PeriodicTask

the schedule's stop

throws

  • Error - if `interval_ms` is not a positive finite number or exceeds `PERIODIC_INTERVAL_MS_MAX` — at the call site, before any pass starts

start_daemon_token_rotation
#

testing/daemon_token_rotation.ts view source

(runtime: Pick<EnvDeps, "env_get"> & Pick<FsWriteDeps, "mkdir" | "rename" | "write_text_file"> & FsRemoveDeps, deps: QueryDeps, options: DaemonTokenRotationOptions, log: Logger): Promise<...> import {start_daemon_token_rotation} from '@fuzdev/fuz_app/testing/daemon_token_rotation.js';

Start daemon token rotation.

Generates an initial token, writes it to disk, resolves the keeper account, and sets up periodic rotation. Returns the mutable state object and a stop function.

runtime

runtime with file and remove capabilities

type Pick<EnvDeps, "env_get"> & Pick<FsWriteDeps, "mkdir" | "rename" | "write_text_file"> & FsRemoveDeps

deps

query dependencies for resolving keeper account

options

rotation configuration

log

the logger instance

type Logger

returns

Promise<DaemonTokenRotation>

rotation state and stop function

mutates

  • filesystem — writes the token file on each rotation; `stop` removes it

start_periodic
#

periodic.ts view source

(name: string, interval_ms: number, pass: () => Promise<unknown>, log: Logger): PeriodicTask import {start_periodic} from '@fuzdev/fuz_app/periodic.js';

Run pass now, then once per interval_ms, until stop is called.

  • Startup pass. The first pass starts before this returns; later passes follow one interval_ms apart, start to start.
  • A slow pass delays the schedule. Passes never overlap, and a pass that overruns its interval pushes the next one back: the next pass starts when the slow one ends, the one after a full interval_ms later — no burst of catch-up passes.
  • A failed pass is logged, never fatal. A pass that throws or rejects is logged at warn with name and the error object itself — so the log carries its stack and its cause — and the loop goes on to the next interval, so an outage is retried rather than ending the upkeep for the life of the process.
  • stop finishes the pass in progress. It ends the schedule at once and resolves when a pass in progress has finished; nothing is dropped mid-flight.

The timer is unref'd where the runtime hands back a timer object (Node, Bun), so a task nobody stopped does not hold the process open.

pass is called once per pass and returns that pass's promise. name labels the task in the failure log.

Twin of the Rust spawn_periodic. The schedule is that loop's MissedTickBehavior::Delay. Shutdown differs: the Rust task drops a pass in progress when its token is cancelled, so joining it is prompt and a pass must be safe to drop; here the pass runs to its end and stop waits for it.

name

labels the task in the failure log

type string

interval_ms

milliseconds between the start of one pass and the start of the next

type number

pass

one pass; called once per pass, its resolved value is ignored

type () => Promise<unknown>

log

where a failed pass is logged

type Logger

returns

PeriodicTask

the task's stop

throws

  • Error - if `interval_ms` is not a positive finite number or exceeds `PERIODIC_INTERVAL_MS_MAX` — at the call site, before any pass starts

start_testing_server
#

testing/cross_backend/testing_server_core.ts view source

(options: StartTestingServerOptions): Promise<void> import {start_testing_server} from '@fuzdev/fuz_app/testing/cross_backend/testing_server_core.js';

Boot a test-mode server using the supplied runtime adapter.

Mirrors a production start_server at the surface level — stale-daemon check, daemon-info write, bind, graceful shutdown — but the app is the caller's no-domain (or domain) StartTestingServerOptions.build_app and the runtime boundary is the TestingServerAdapter. Refuses any non-loopback bind host (the test binary must stay on loopback — see is_loopback_host).

options

returns

Promise<void>

StartTestingServerOptions
#

testing/cross_backend/testing_server_core.ts view source

StartTestingServerOptions import type {StartTestingServerOptions} from '@fuzdev/fuz_app/testing/cross_backend/testing_server_core.js';

adapter

Runtime-boundary adapter (Node or Deno).

type TestingServerAdapter

daemon_name

Daemon-info namespace — the cli/daemon key the daemon.json is written under (e.g. 'fuz_app_spine'). The cross-process harness reads the daemon token from the rotation file, not this; daemon.json is for stale-process detection + parity with production daemon lifecycle.

type string

host

Bind host (e.g. 'localhost').

type string

port

Bind port.

type number

app_version?

App version recorded in daemon.json.

type string

build_app

Build the app. Closes over the entry's runtime + connection-IP getter + password deps + resolved config — so this core never touches the domain. Returns the assembled app and a close teardown.

type (context: BuildTestingAppContext): Promise<BuiltTestingApp>

context

returns Promise<BuiltTestingApp>

log?

Optional logger; defaults to a [daemon_name]-namespaced Logger.

type Logger

StatResult
#

runtime/deps.ts view source

StatResult import type {StatResult} from '@fuzdev/fuz_app/runtime/deps.js';

Result of a stat operation.

is_file

type boolean

is_directory

type boolean

size?

Byte length of a regular file. Meaningful only when is_file is true; for directories it is runtime-defined (real OS stat reports the directory entry's on-disk size, not 0 — only create_mock_runtime reports 0). Populated by every runtime factory (create_node_runtime / create_deno_runtime / create_mock_runtime); optional so loose test stubs that only assert is_file / is_directory don't have to supply it. Callers that need an exact size (e.g. a streaming upload's Content-Length) read it from a real runtime, where it is always present.

type number

mtime_ms?

Last-modification time in epoch milliseconds, when the runtime reports it. Populated by create_node_runtime / create_deno_runtime; create_mock_runtime omits it (so a mock-backed sweep treats every temp as unknown-age and never reaps). Optional so loose test stubs that only assert is_file / is_directory don't have to supply it. The orphan-temp sweep (db/fact_disk_storage.ts) reads it to age out stale .tmp spill files.

type number

stop_daemon
#

cli/daemon.ts view source

(runtime: Pick<EnvDeps, "env_get"> & Pick<FsReadDeps, "stat" | "read_text_file"> & FsRemoveDeps & CommandDeps & LogDeps, name: string): Promise<...> import {stop_daemon} from '@fuzdev/fuz_app/cli/daemon.js';

Stop a running daemon by sending SIGTERM and cleaning up the PID file.

Returns a result object instead of logging directly, separating lifecycle from presentation. Errors removing the PID file are swallowed (the daemon's own shutdown handler may have removed it concurrently).

runtime

runtime with command, file, and env capabilities

type Pick<EnvDeps, "env_get"> & Pick<FsReadDeps, "stat" | "read_text_file"> & FsRemoveDeps & CommandDeps & LogDeps

name

application name

type string

returns

Promise<StopDaemonResult>

result describing the outcome

mutates

  • filesystem — removes `~/.{name}/run/daemon.json` on success or when corrupt
  • external process — sends `SIGTERM` to the daemon process via `kill`

StopDaemonResult
#

cli/daemon.ts view source

StopDaemonResult import type {StopDaemonResult} from '@fuzdev/fuz_app/cli/daemon.js';

Result of a stop_daemon operation.

stopped

Whether a daemon was stopped.

type boolean

pid?

PID of the stopped daemon, if any.

type number

message

Human-readable message describing the outcome.

type string

stream_fact_to_disk
#

db/fact_disk_storage.ts view source

(deps: Pick<FactDiskStorageDeps, "stat" | "mkdir" | "rename" | "fsync" | "write_file_stream" | "remove">, facts_dir: string | undefined, source: ReadableStream<...>, max_bytes: number, embedded_threshold: number): Promise<...> import {stream_fact_to_disk} from '@fuzdev/fuz_app/db/fact_disk_storage.js';

Stream source to storage with bounded memory: hash BLAKE3 + SHA-256 incrementally in one pass, buffer in memory until the bytes cross embedded_threshold, then spill the buffer + remaining chunks through a temp file and atomically land it in the disk CAS. Peak heap is O(chunk + embedded_threshold), never O(artifact), so a multi-GB upload never buffers in RAM.

  • Embedded vs disk. A body <= embedded_threshold stays in memory and is returned as {kind: 'embedded'} for the PG bytes column. Above it (with a facts_dir), the buffer + remaining chunks spill to <facts_dir>/.tmp/…, then rename into <facts_dir>/<shard>/<rest> once the hash is known — {kind: 'disk'}. A body over the threshold with facts_dir === undefined throws FactPayloadTooLargeError (matches PgFactStore.put).
  • Cap enforcement. Aborts with FactPayloadTooLargeError the moment the running byte count passes max_bytes — the mid-stream backstop for a chunked or mis-declared Content-Length.
  • Disk-full. An ENOSPC from the temp-file write surfaces as FactStorageFullError.

deps

type Pick<FactDiskStorageDeps, "mkdir" | "rename" | "remove" | "write_file_stream" | "fsync" | "stat">

facts_dir

type string | undefined

source

type ReadableStream<Uint8Array<ArrayBufferLike>>

max_bytes

type number

embedded_threshold

type number

returns

Promise<StreamFactToDiskResult>

mutates

  • facts_dir filesystem

StreamFactToDiskResult
#

db/fact_disk_storage.ts view source

StreamFactToDiskResult import type {StreamFactToDiskResult} from '@fuzdev/fuz_app/db/fact_disk_storage.js';

Outcome of streaming an upload to storage: the blake3:-prefixed fact hash, the bare-hex SHA-256, the byte count, and where the bytes landed. PgFactStore.put_stream turns this into the fact row insert.

hash

type FactHash

sha256

type string

size

type number

placement

type StreamPlacement

StreamPlacement
#

db/fact_disk_storage.ts view source

StreamPlacement

type { kind: "embedded"; bytes: Uint8Array<ArrayBufferLike>; } | { kind: "disk"; external_url: string & $brand<"FileFactUrl">; }

import type {StreamPlacement} from '@fuzdev/fuz_app/db/fact_disk_storage.js';

Where a streamed body landed — embedded carries the in-memory bytes (under the embedded threshold, bound for the PG fact.bytes column); disk means the bytes are already at <facts_dir>/<shard>/<rest> and the row carries the file: URL.

stub
#

testing/stubs.ts view source

any import {stub} from '@fuzdev/fuz_app/testing/stubs.js';

Throwing stub — use for deps that should never be reached.

stub_handler
#

testing/stubs.ts view source

(): Response import {stub_handler} from '@fuzdev/fuz_app/testing/stubs.js';

Stub handler that returns a 200 response.

returns

Response

stub_mw
#

testing/stubs.ts view source

(_c: any, next: any): Promise<void> import {stub_mw} from '@fuzdev/fuz_app/testing/stubs.js';

Stub middleware that passes through.

_c

type any

next

type any

returns

Promise<void>

stub_password_deps
#

testing/app_server.ts view source

PasswordHashDeps import {stub_password_deps} from '@fuzdev/fuz_app/testing/app_server.js';

Fast password stub for tests that don't exercise login/password flows.

Hashes are deterministic (stub_hash_<password>) and verify correctly, so auth bootstrap and session creation work without Argon2 overhead.

StubUpgrade
#

testing/ws_round_trip.ts view source

StubUpgrade import type {StubUpgrade} from '@fuzdev/fuz_app/testing/ws_round_trip.js';

The return of create_stub_upgrade — fake upgradeWebSocket + factory and upgrade capture.

upgradeWebSocket

type UpgradeWebSocket<unknown, any, WSEvents<unknown>>

get_create_events

The events factory of the most recently mounted endpoint, for tests that build events from a hand-made context. Throws when no endpoint was mounted.

type (): (c: Context<any, any, {}>) => WSEvents<unknown> | Promise<WSEvents<unknown>>

returns (c: Context<any, any, {}>) => WSEvents<unknown> | Promise<WSEvents<unknown>>

upgrades

Every upgrade request that reached a stub route, in arrival order — what app.request(path, {headers: {upgrade: 'websocket', ...}}) leaves behind once the app's middleware passed it. Build the socket's events with record.create_events(record.c).

type readonly StubUpgradeRecord[]

StubUpgradeRecord
#

testing/ws_round_trip.ts view source

StubUpgradeRecord import type {StubUpgradeRecord} from '@fuzdev/fuz_app/testing/ws_round_trip.js';

One upgrade that reached a create_stub_upgrade route.

c

The real Hono Context the request carried through the app's middleware chain — session, request context, client IP, and every other value the middleware set.

type Context<any, any, {}>

create_events

The events factory of the endpoint the upgrade reached.

type (c: Context<any, any, {}>): WSEvents<unknown> | Promise<WSEvents<unknown>>

c

type Context<any, any, {}>
returns WSEvents<unknown> | Promise<WSEvents<unknown>>

SubscribeOptions
#

realtime/subscriber_registry.ts view source

SubscribeOptions import type {SubscribeOptions} from '@fuzdev/fuz_app/realtime/subscriber_registry.js';

Options for SubscriberRegistry.subscribe and subscribe_pending.

channels?

Channels to subscribe to. Empty/absent = all channels.

type readonly string[]

scope?

Primary (capped) identity — e.g., session hash. Subject to max_per_scope and matched by close_by_identity.

type string

groups?

Grouping identities — e.g., account id. Matched by close_by_identity but NOT subject to the cap. Use for coarse-targeted close.

type readonly string[]

Subscriber
#

realtime/subscriber_registry.ts view source

Subscriber<T> import type {Subscriber} from '@fuzdev/fuz_app/realtime/subscriber_registry.js';

One admitted subscriber, as SubscriberRegistry.broadcast sees it.

generics

Subscriber<T>
T

stream

type SseStream<T>

channels

Channels this subscriber listens to. null means all channels.

type Set<string> | null

scope

Primary (capped) identity. null when none.

type string | null

groups

Grouping identities for close_by_identity. null when none.

type Set<string> | null

SubscriberRegistry
#

realtime/subscriber_registry.ts view source

import {SubscriberRegistry} from '@fuzdev/fuz_app/realtime/subscriber_registry.js';

Generic subscriber registry with channel-based filtering and identity-keyed disconnection.

Subscribers connect with optional channel filters, a capped scope, and uncapped groups. Broadcasts go to a specific channel and reach only matching subscribers. close_by_identity force-closes all subscribers whose scope or groups contain the given key — use for auth revocation.

Two-phase registration

A stream is authorized once, at open, so a revocation has to be able to reach it from the moment its credential is read. A route's gates run before the stream exists, and each revocation closes only the streams *registered* under what it revoked — registering after the gates leaves a window in which a revocation closes nothing and the stream then opens on a dead session, or under a role its actor no longer holds.

So a route that authenticated a revocable credential registers in two phases: subscribe_pending registers the subscription un-admitted — closeable by every close_by_identity, and otherwise inert: it has no stream, so no broadcast reaches it, the cap neither counts nor evicts for it, and count does not see it (pending_count does). The route then re-reads the credential and the role, and admit attaches the stream and opens the feed — or refuses when a revocation closed the registration in between, evicting nothing. The cap's eviction happens at admission.

subscribe stays as the one-step form for a stream with nothing to revoke behind it. The twin of the Rust fuz_realtime SseRegistry (subscribe_pending / admit / subscribe).

Shutdown

close_all — the close AppServer.close runs — closes every stream, pending or admitted, and leaves the registry closing for good: every later registration is born closed. subscribe_pending registers nothing, so admit refuses the handle and the route's stream ends after its connect comment; subscribe closes the stream it is given at once. A stream still opening when the server shuts down therefore ends rather than outliving it. The flag is set in the same synchronous step that closes the live streams.

generics

SubscriberRegistry<T>
T

examples

const registry = new SubscriberRegistry<SseNotification>(); // subscriber connects (from SSE endpoint) const unsubscribe = registry.subscribe(stream, {channels: ['runs']}); // when a run changes registry.broadcast('runs', {method: 'run_created', params: {run}}); // subscriber disconnects unsubscribe();
// scope = session hash (capped), groups = [account id] (close-only) const unsubscribe = registry.subscribe(stream, { channels: ['audit_log'], scope: session_hash, groups: [account_id], }); // coarse — close all of a user's streams on role revocation registry.close_by_identity(account_id); // fine — close just the stream(s) tied to a specific session registry.close_by_identity(session_hash);
// two-phase, for a route behind a revocable credential const pending = registry.subscribe_pending({scope: session_hash, groups: [account_id]}); if (!(await credential_is_still_live())) { pending.unsubscribe(); return unauthorized(); } const {response, stream} = create_sse_response(c, log); // first: a client that already left closes the stream, its listener runs at // once, and `admit` then refuses the removed registration stream.on_close(pending.unsubscribe); if (!registry.admit(pending, stream)) { stream.close(); // the body is the connect comment alone } else { // expiry writes nothing a revocation closes on: end at the credential's deadline stream.on_close( schedule_credential_deadline(get_resolved_auth(c)?.credential_deadline ?? null, () => stream.close() ) ); } return response;

constructor

type new <T>(options?: SubscriberRegistryOptions | undefined): SubscriberRegistry<T>

options?

optional

subscribe

Add a subscriber, admitted at once — the one-step form of subscribe_pending + admit.

Not for a route that authenticated a revocable credential: a revocation landing between that route's gates and this call closes nothing, and the stream would open on a dead credential (class doc, "Two-phase registration").

Once the registry is closing, the subscriber is born closed: stream is closed at once, nothing is registered, and the returned function is a no-op.

type (stream: SseStream<T>, options?: SubscribeOptions | undefined): () => void

stream

SSE stream to send data to

type SseStream<T>

options?

channel filter and identity slots (scope + groups)

optional
returns () => void

unsubscribe function

mutates

  • registry — adds the new subscriber; closes oldest matching subscribers when `max_per_scope` is exceeded

subscribe_pending

Register a subscription un-admitted — phase one of the two-phase registration (class doc).

The registration is closeable at once by every close_by_identity matching its scope or groups, and until admit it receives no broadcast, is not counted, and evicts nothing. A caller that then refuses the request calls the returned handle's unsubscribe.

Once the registry is closing, the registration is born closed: nothing is registered, and admit refuses the handle.

type (options?: SubscribeOptions | undefined): PendingSubscription

options?

channel filter and identity slots (scope + groups)

optional

the pending handle admit takes

mutates

  • registry — adds the un-admitted registration, unless the registry is closing

admit

Admit a pending subscription, attaching the stream it delivers to — phase two (class doc).

If the registration is gone — a revocation or the shutdown closed it while it was pending, it was born closed, or it was unsubscribed — nothing is evicted and the subscription is refused. Otherwise the per-scope cap is enforced as in subscribe, counting only admitted subscribers, and the stream starts receiving broadcast data. Admitting twice is refused the second time.

type (pending: PendingSubscription, stream: SseStream<T>): boolean

pending

the handle subscribe_pending returned

stream

SSE stream to send data to

type SseStream<T>
returns boolean

false when the subscription was not admitted — the caller ends the stream

mutates

  • registry — attaches `stream`; closes oldest matching subscribers when `max_per_scope` is exceeded

broadcast

Broadcast data to all admitted subscribers on a channel.

Subscribers with no channel filter receive all broadcasts. Subscribers with a channel filter only receive matching broadcasts. A registration pending admission is not an open stream and is skipped — nothing is queued toward a subscriber not yet authorized.

type (channel: string, data: T): void

channel

type string

data

type T
returns void

close_by_identity

Force-close all subscribers whose scope or groups match the given key.

Closes each matching stream and removes the subscriber from the registry. Use for auth revocation — when a user's permissions change, close their SSE connections so they must reconnect and re-authenticate.

Matches every registration, pending or admitted — never filter on admission here: reaching a registration in flight is the point of the pending phase, and admit then refuses it.

A stream whose close throws does not stop the loop: every match is removed and closed, and the first error is thrown afterward.

type (identity: string): number

identity

the identity key to match (checked against scope and groups)

type string
returns number

the number of subscribers closed

throws

  • the - first error a stream's `close` threw, after every match was attempted

mutates

  • registry — removes matching subscribers and closes their streams

close_all

Force-close every subscriber, pending or admitted — the shutdown close AppServer.close runs through the registry's ConnectionCloser — and leave the registry closing, for good: every later registration is born closed (class doc, "Shutdown"). A second call closes nothing.

A stream whose close throws does not stop the loop: every subscriber is removed and closed, and the first error is thrown afterward.

type (): number

returns number

the number of subscribers closed

throws

  • the - first error a stream's `close` threw, after every subscriber was attempted

mutates

  • registry — sets the registry closing, removes every subscriber, and closes its stream

count

Number of open — admitted — subscribers. A registration still pending admission is counted by pending_count instead.

type number

getter

pending_count

Number of registrations awaiting admission — between subscribe_pending and admit (or their refusal). For telemetry and tests.

type number

getter

closing

Whether close_all has run — the registry is shutting down and every registration from now on is born closed (class doc, "Shutdown"). The audit stream route reads it to skip the re-reads for a registration admission will refuse anyway.

type boolean

getter

SubscriberRegistryOptions
#

realtime/subscriber_registry.ts view source

SubscriberRegistryOptions import type {SubscriberRegistryOptions} from '@fuzdev/fuz_app/realtime/subscriber_registry.js';

Options for SubscriberRegistry.

max_per_scope?

Max admitted subscribers sharing a single scope. When a subscriber is admitted and the count of admitted subscribers with the same scope has reached this limit, the oldest matching subscriber(s) are closed first. null (default) disables the cap. groups identities are never capped.

type number | null

log?

Logger for a stream whose close throws while the cap evicts it — the evicted stream is another subscriber's, so its failure is logged rather than failing the admission that evicted it. Unset falls back to an [sse] logger; null silences it.

type Logger | null

SuiteAppOptions
#

testing/app_server.ts view source

Partial<Omit<AppServerOptions, "backend" | "bootstrap" | "session_options" | "create_route_specs" | "rpc_endpoints">>

type Partial<Omit<AppServerOptions, "backend" | "session_options" | "create_route_specs" | "rpc_endpoints" | "bootstrap">>

import type {SuiteAppOptions} from '@fuzdev/fuz_app/testing/app_server.js';

app_options shape accepted by create_test_app and the DB-backed suite helpers. Excludes fields the helpers manage directly — backend / session_options / create_route_specs are constructed by the helper itself; rpc_endpoints and bootstrap live on top-level options so setup-time surface lookup and runtime dispatch read from one source of truth.

login_ip_rate_limiter?

Per-IP rate limiter for login + password change — the distributed-spray backstop. Omit or undefined to use a default limiter (5 attempts per 15 minutes) under rate_limiters: 'enforced', none under 'disabled_for_testing'. Pass null to explicitly disable rate limiting. Also available on AppServerContext for route factory callbacks.

One instance per auth surface, not one shared across all four. These buckets are monotone within their window — a success never refunds them (see RateLimiter.reset) — so a shared instance let a failure on any surface spend the budget that bounds guessing on every other one, and let one caller's exhaustion deny four routes at once. Pass the same limiter to two of these fields to opt back into a shared budget.

type RateLimiter | null

signup_ip_rate_limiter?

Per-IP rate limiter for signup. Omit or undefined for the mode's default (5 attempts per 15 minutes under rate_limiters: 'enforced'). Pass null to explicitly disable. Separate from login_ip_rate_limiter because an open-signup deployment lets any unauthenticated caller spend it. Also available on AppServerContext for route factory callbacks.

type RateLimiter | null

bootstrap_ip_rate_limiter?

Per-IP rate limiter for bootstrap. Omit or undefined for the mode's default (5 attempts per 15 minutes under rate_limiters: 'enforced'). Pass null to explicitly disable. Separate from login_ip_rate_limiter so a fumbled bootstrap token can't spend the operator's login budget. Wired directly into the factory-managed bootstrap route; also on AppServerContext for symmetry.

type RateLimiter | null

login_account_rate_limiter?

Per-account rate limiter for login attempts. Omit or undefined for the mode's default (10 attempts per 30 minutes under rate_limiters: 'enforced'). Pass null to explicitly disable. Also available on AppServerContext for route factory callbacks.

type RateLimiter | null

signup_account_rate_limiter?

Per-account rate limiter for signup attempts, keyed by submitted username. Omit or undefined for the mode's default (10 attempts per 30 minutes under rate_limiters: 'enforced'). Pass null to explicitly disable. Also available on AppServerContext for route factory callbacks.

type RateLimiter | null

action_ip_rate_limiter?

Per-IP rate limiter for the action dispatchers (HTTP RPC + WebSocket). Consulted for actions whose spec declares rate_limit: 'ip' or 'both'. Same limiter applies across transports — one budget per action. Omit or undefined for the mode's default (600 attempts per 15 minutes — permissive — under rate_limiters: 'enforced'). Pass null to explicitly disable. Threaded into every endpoint mounted from rpc_endpoints and ws_endpoints; also available on AppServerContext.

type RateLimiter | null

action_account_rate_limiter?

Per-account rate limiter for the action dispatchers (HTTP RPC + WebSocket). Consulted for actions whose spec declares rate_limit: 'account' or 'both'. Keyed on the authenticated account's id, so every actor and credential of one account shares a budget. Omit or undefined for the mode's default (1200 attempts per 15 minutes — permissive — under rate_limiters: 'enforced'). Pass null to explicitly disable. Threaded into every endpoint mounted from rpc_endpoints and ws_endpoints; also available on AppServerContext.

type RateLimiter | null

allowed_origins?

Parsed allowed origin patterns.

type RegExp[]

origin_carve_outs?

Paths the origin check skips, each with its reason. The check covers every path by default (ORIGIN_MIDDLEWARE_PATH) — a request with no Origin passes it anyway, so probes and CLI clients need no carve-out; one is for a path that refuses browser callers by other means. Copied at assembly, so a later change to this array changes nothing; assembly throws on a carve-out with no reason, or one that overlaps the auth middleware's scope (AUTH_MIDDLEWARE_PATH) — every route that reads or mints a credential, and every RPC, WS, and bootstrap endpoint, sits there (assert_origin_carve_outs_valid).

type MiddlewareCarveOut[]

proxy?

Trusted proxy options.

type { trusted_proxies: string[]; get_connection_ip: (c: Context<any, any, {}>) => string | undefined; }

rate_limiters?

What an omitted rate limiter becomes — 'enforced' (the default) builds a default instance for each *_rate_limiter option left out, 'disabled_for_testing' leaves each of them null. An explicit option (an instance or null) always wins over the mode. The disabled mode adds one surface warning, and prints a stderr banner once per module instance (announce_rate_limiters_disabled). Never 'disabled_for_testing' in production — see RateLimiterMode.

type RateLimiterMode

max_body_size?

Maximum allowed request body size in bytes. Omit or undefined to use the default (1 MiB). Pass null to explicitly disable body size limiting.

type number | null

daemon_token_state?

Daemon token state for keeper auth. Omit to disable.

type DaemonTokenState

surface_route?

Mount GET /api/surface, which serves this server's generated AppSurface (every route, its auth, and its schemas) to an admin UI such as AdminSurface.svelte. Off by default — the Rust spine serves no such route, and a map of the API is nothing to publish unasked.

When mounted the route is admin-only (roles: ['admin'], so it takes the ?acting= selector) and a rule-3 token surface (required_scope: 'surface:app_surface'): a narrowed API token is refused it whatever its method list says. See server/surface_route.ts.

type boolean

default false

transform_middleware?

Optional: transform middleware specs before applying.

Receives shallow copies of the trusted-proxy spec followed by the auth stack, in a fresh array — mutating them leaves the originals untouched. Add layers anywhere (before the proxy spec, between it and the auth stack, between the auth layers, or after), but keep each of those specs — its handler, not just its name — mounted once at its original path ('*' for the proxy and the origin check, AUTH_MIDDLEWARE_PATH for the rest of the auth stack) in its original order: the endpoint auth-scope check (assert_endpoint_in_auth_scope) and client-IP rate limiting (get_client_ip) assume it, so assembly throws (assert_middleware_stack_mounted) if the transform moves, drops, duplicates, reorders, wraps, or replaces one. The check pins where those layers sit, not what added layers do — a layer you add runs with full context access.

type (specs: MiddlewareSpec[]): MiddlewareSpec[]

specs

returns MiddlewareSpec[]

audit_log_sse?

Enable factory-managed audit log SSE.

When truthy, creates an AuditLogSse instance internally, registers the SSE listener via backend.deps.audit.add_listener (composing with the consumer's on_audit_event callback rather than rebuilding AppDeps), adds its registry to backend.deps.connection_closer so revocations close its streams, and auto-includes audit_log_event_specs in the surface. The result is exposed on AppServerContext (for route factories) and AppServer (for the caller), always typed as AuditLogSse | null — when this option is set, the field is non-null. Use require_audit_sse(ctx) to assert the invariant in route factories that depend on it.

Pass true for defaults (admin role), or {role: 'custom'} for a custom role. Omit to wire audit SSE manually.

type true | { role?: string | undefined; }

auth_cleanup?

Schedule the auth cleanup beside the server: start_auth_cleanup over backend.deps — a pass as assembly finishes, then one per interval — stopped by AppServer.close. Each pass deletes expired sessions and closes the connections they opened through deps.connection_closer (every WebSocket transport and audit stream this server mounts), and audits each newly expired role_grant offer once.

Off by default; a production server should turn it on. Without it nothing sweeps: expired session rows accumulate and an offer's expiry is never audited. Connections do not depend on it — each closes at its credential's deadline (auth/credential_deadline.ts), and the sweep's close repeats that one. The Rust spine's servers all schedule the twin (spawn_auth_cleanup).

Pass true for the default interval (DEFAULT_AUTH_CLEANUP_INTERVAL_MS), or {interval_ms} for another. Leave it off in an in-process test harness — a background pass deletes rows and writes audit rows mid-test — and in a process that runs run_auth_cleanup on a schedule of its own.

type boolean | AuthCleanupScheduleOptions

event_specs?

SSE event specs for surface generation. Defaults to [] (no SSE events).

type EventSpec[]

create_upgrade_websocket?

Returns the Hono runtime adapter's upgradeWebSocket helper for the assembled app. create_app_server calls it once, with the app it builds (the same instance as AppServer.app), after creating the app and before mounting ws_endpoints — and only when ws_endpoints resolves non-empty, so it is never called for a server with no WebSocket endpoints. Required in that case: assembly throws without it. One helper services every mounted endpoint.

A factory rather than the helper itself because Node's adapter needs the app: @hono/node-ws has no module-level helper, only createNodeWebSocket({app}), and create_app_server is what builds the app. Deno and Bun ignore the argument:

import {upgradeWebSocket} from '@hono/deno'; // or '@hono/bun' create_upgrade_websocket: () => upgradeWebSocket,

Node keeps the adapter so it can attach to the server after serve():

let node_ws: NodeWebSocket | undefined; const server = await create_app_server({ ..., create_upgrade_websocket: (app) => (node_ws = createNodeWebSocket({app})).upgradeWebSocket, }); node_ws?.injectWebSocket(serve({fetch: server.app.fetch, port}));

Test harnesses pass () => stub.upgradeWebSocket with create_stub_upgrade from testing/ws_round_trip.ts.

type (app: Hono<BlankEnv, BlankSchema, "/">): UpgradeWebSocket<unknown, any, WSEvents<unknown>>

app

type Hono<BlankEnv, BlankSchema, "/">
returns UpgradeWebSocket<unknown, any, WSEvents<unknown>>

ws_endpoints?

WebSocket endpoint specs — single source of truth for both surface generation *and* live dispatch. Each entry is auto-mounted via register_ws_endpoint against the assembled Hono app on every runtime, so consumers don't call register_ws_endpoint themselves.

Accepts either an array (evaluated eagerly) or a factory (ctx: AppServerContext) => ReadonlyArray<WsEndpointSpec> (evaluated after the server context is assembled). Use the factory form when action lists depend on ctx.deps — e.g. when spreading create_standard_rpc_actions({...ctx.deps, notification_sender}) over WS.

When non-empty, create_upgrade_websocket must be supplied (throws otherwise). A factory returning [] does NOT trip the check — feature-flag gated WS surfaces stay safe.

Every mount gets what the HTTP surface gets: the auth middleware (a path outside AUTH_MIDDLEWARE_PATH throws at assembly, like rpc_endpoints), the server's allowed_origins unless the spec narrows them, and the same action_ip_rate_limiter / action_account_rate_limiter instances the RPC endpoints charge — one budget per action across both transports. The upgrade routes register after the route specs and before post_route_middleware and static_serving, so neither runs ahead of an upgrade.

Duplicate path values across two WsEndpointSpecs, and a path a GET RouteSpec already claims, throw at assembly before any endpoint mounts (Hono would silently shadow them otherwise).

Every mounted transport is added to deps.connection_closer, so the revocation handlers' post-commit closes reach its sockets — unconditionally, since those closes are what ends a revoked credential's connections. AppServer.close removes it again.

Each spec's auth_guard? defaults to true — the factory registers create_ws_auth_guard against the mounted transport via deps.audit.add_listener, repeating the close on the audit row. Wiring is deduped by transport reference identity so two specs sharing one BackendWebsocketTransport instance get a single listener; wrapped / proxied transports dedupe as separate entries (set auth_guard: false on duplicates and compose against the underlying transport once).

type readonly WsEndpointSpec[] | ((context: AppServerContext) => readonly WsEndpointSpec[])

env_schema?

Env schema for surface generation. Defaults to BaseServerEnv — pass an extended schema (typically BaseServerEnv.extend({...})) when the consumer adds app-specific env vars.

type ZodObject<$ZodLooseShape, $strip>

post_route_middleware?

Middleware applied after routes, before static serving. Included in surface.

type MiddlewareSpec[]

static_serving?

Static file serving. Omit if not serving static files.

type { serve_static: ServeStaticFactory; root?: string | undefined; spa_fallback?: string | undefined; is_spa_route?: ((path: string) => boolean) | undefined; }

await_pending_effects?

Await all pending fire-and-forget effects before returning the response. Use in tests so audit log assertions don't need polling. Default false (production: true fire-and-forget).

type boolean

on_effect_error?

Called when a pending effect rejects. Use for monitoring, metrics, or alerting in production. Only called when await_pending_effects is false (production mode).

type (error: unknown, context: EffectErrorContext): void

error

type unknown

context

returns void

env_values?

Env values for startup summary logging.

type Record<string, unknown>

SupersededOffer
#

auth/role_grant_offer_schema.ts view source

SupersededOffer import type {SupersededOffer} from '@fuzdev/fuz_app/auth/role_grant_offer_schema.js';

A superseded offer row annotated with the grantor's account_id.

Carried by superseded_offers in accept/revoke query results so callers can fan out role_grant_offer_supersede notifications to the grantor's sockets without a second round-trip. Populated via a CTE join on actor in the supersede UPDATE.

inheritance

from_account_id

type Uuid

surface_auth_summary
#

http/surface_query.ts view source

(surface: AppSurface): { none: number; authenticated: number; optional: number; role: Map<string, number>; credential: Map<string, number>; keeper: number; other: number; } import {surface_auth_summary} from '@fuzdev/fuz_app/http/surface_query.js';

Summarize route auth distribution across the surface.

Categorical view over the flat auth record, bucketing exactly as routes_by_auth_type. Multi-role and multi-channel specs contribute one count per role / channel they admit.

surface

returns

{ none: number; authenticated: number; optional: number; role: Map<string, number>; credential: Map<string, number>; keeper: number; other: number; }

counts by auth category, with role and channel counts broken out by name

SurfaceExplorer
#

SurfaceRouteOptions
#

server/surface_route.ts view source

SurfaceRouteOptions import type {SurfaceRouteOptions} from '@fuzdev/fuz_app/server/surface_route.js';

Options for the surface explorer route.

surface

The generated app surface to serve — read per request, so a backfilled ref serves the final surface.

type AppSurface

SurfaceSecurityPolicyOptions
#

testing/surface_invariants.ts view source

SurfaceSecurityPolicyOptions import type {SurfaceSecurityPolicyOptions} from '@fuzdev/fuz_app/testing/surface_invariants.js';

Configuration for security policy invariants.

All fields have sensible defaults. Pass overrides for project-specific needs.

sensitive_route_patterns?

Path patterns for routes that should be rate-limited. Default: common sensitive REST patterns (login, password, bootstrap). account_token_create lives on the RPC surface; per-method RPC rate limiting is a separate invariant if consumers want it.

type (string | RegExp)[]

public_mutation_allowlist?

Routes explicitly allowed to be public mutations (e.g., webhooks, bootstrap). Format: 'METHOD /path' (e.g., 'POST /api/account/login').

type string[]

keeper_route_prefixes?

Allowed path prefixes for keeper-protected routes. Default: ['/api/']. Catches keeper routes outside expected namespaces.

type string[]

sweep_orphan_temps
#

db/fact_disk_storage.ts view source

(deps: Pick<FactDiskStorageDeps, "stat" | "readdir" | "remove">, facts_dir: string, options?: { max_age_ms?: number | undefined; log?: Pick<Logger, "warn"> | undefined; } | undefined): Promise<...> import {sweep_orphan_temps} from '@fuzdev/fuz_app/db/fact_disk_storage.js';

Reap stale temp files left under <facts_dir>/.tmp/ by a hard crash (SIGKILL / OOM / host crash) mid-write — the finally cleanup in the writers above never ran. Removes .tmp entries whose mtime is older than max_age_ms (so an in-flight upload isn't yanked out from under itself). The TS twin of the Rust sweep_orphan_temps; call on startup + on an interval.

Best-effort: a missing .tmp/ dir (no oversize upload has ever run) is a no-op; a runtime that doesn't report mtime_ms (a mock) leaves every temp untouched; a per-file stat/remove failure is logged and skipped rather than aborting the sweep. Returns the count removed.

deps

type Pick<FactDiskStorageDeps, "readdir" | "stat" | "remove">

facts_dir

type string

options?

type { max_age_ms?: number | undefined; log?: Pick<Logger, "warn"> | undefined; }
optional

returns

Promise<number>

mutates

  • facts_dir filesystem

sync_expected_schema_fixture
#

testing/schema_ready_fixture.ts view source

(options: SyncExpectedSchemaFixtureOptions): Promise<SyncExpectedSchemaFixtureResult> import {sync_expected_schema_fixture} from '@fuzdev/fuz_app/testing/schema_ready_fixture.js';

Introspect the live (bootstrapped) DB's columns, write them to the committed fixture when update, then read the committed fixture back. The caller asserts deepEqual(live, committed):

const {live, committed} = await sync_expected_schema_fixture({ db, fixture_url: new URL('../../lib/server/expected_schema.json', import.meta.url), update: process.env.UPDATE_SCHEMA_READY === '1', }); assert.deepEqual(live, committed);

When update writes the fixture it emits raw JSON.stringify (one array element per line); Prettier collapses short arrays inline, so run gro format after UPDATE_SCHEMA_READY=1 before committing or the format check will flag the regenerated file. (The content is identical either way — the regen test compares values, not formatting.)

options

returns

Promise<SyncExpectedSchemaFixtureResult>

the live column map and the committed map (post-write when update)

SyncExpectedSchemaFixtureOptions
#

testing/schema_ready_fixture.ts view source

SyncExpectedSchemaFixtureOptions import type {SyncExpectedSchemaFixtureOptions} from '@fuzdev/fuz_app/testing/schema_ready_fixture.js';

db

A bootstrapped DB — the consumer has run its full migration chain on it.

type Db

fixture_url

Committed fixture location — an import.meta.url-relative URL or a path.

type URL | string

update?

When true, overwrite the fixture with the live column map instead of just reading it. Drive from an env flag (e.g. UPDATE_SCHEMA_READY === '1').

type boolean

SyncExpectedSchemaFixtureResult
#

testing/schema_ready_fixture.ts view source

SyncExpectedSchemaFixtureResult import type {SyncExpectedSchemaFixtureResult} from '@fuzdev/fuz_app/testing/schema_ready_fixture.js';

The live column map and the committed fixture, for a deepEqual assertion.

live

Columns introspected from the live, freshly-bootstrapped DB.

type Record<string, string[]>

committed

The committed fixture (re-read after writing when update).

type ExpectedSchema

TableInfo
#

http/db_routes.ts view source

TableInfo import type {TableInfo} from '@fuzdev/fuz_app/http/db_routes.js';

Table metadata from information_schema.

table_name

type string

TableSnapshot
#

testing/schema_introspect.ts view source

value + type

{ columns: Record<string, { data_type: string; udt_name: string; is_nullable: boolean; column_default: string | null; is_identity: boolean; }>; indexes: { name: string; definition: string; }[]; constraints: { ...; }[]; } import {TableSnapshot} from '@fuzdev/fuz_app/testing/schema_introspect.js';

Per-table structural metadata.

columns

Column metadata keyed by column name (sorted on serialization).

type Record<string, ColumnSnapshot>

indexes

Index definitions as Postgres renders them via pg_indexes.indexdef.

type { name: string; definition: string; }[]

constraints

Constraint definitions as Postgres renders them via pg_get_constraintdef.

type { name: string; type: string; definition: string; }[]

TableState
#

ui/table_state.svelte.ts view source

import {TableState} from '@fuzdev/fuz_app/ui/table_state.svelte.js';

list

type AsyncSlot<void, string>

readonly

table_name

type string

$state.raw

columns

type ColumnInfo[]

$state.raw

rows

type Record<string, unknown>[]

$state.raw

total

type number

$state.raw

offset

type number

$state.raw

limit

type number

$state.raw

primary_key

type string | null

$state.raw

deletable

Whether the server will accept a row DELETE on this table. False for a table with no single-column primary key *and* for one the server excludes by policy (the audit trail, singleton bookkeeping rows), which primary_key alone can't distinguish — it reports the key shape, not the policy.

type boolean

$state.raw

deleting

type string | null

$state.raw

delete_error

type string | null

$state.raw

can_delete

Whether to offer a delete affordance at all. Gate rendering on this rather than on primary_key, or a policy-excluded table shows a button whose only outcome is a table_not_deletable refusal.

type boolean

readonly $derived

showing_start

type number

readonly $derived

showing_end

type number

readonly $derived

has_prev

type boolean

readonly $derived

has_next

type boolean

readonly $derived

fetch

Fetch a page of rows for table_name from GET /api/db/tables/{table_name}. limit is clamped to [1, DB_TABLE_ROWS_LIMIT_MAX] — the same bounds the route 400s on, single-sourced so the client guard can't drift into sending a request the server refuses.

type (table_name: string, offset?: number, limit?: number): Promise<void>

table_name

type string

offset

type number
default 0

limit

type number
default 100
returns Promise<void>

mutates

  • this

go_prev

type (): void

returns void

go_next

type (): void

returns void

delete_row

Delete a row by its primary key via DELETE /api/db/tables/{table_name}/rows/{pk}. Optimistically drops it from rows and decrements total on success; surfaces server errors on delete_error.

type (row: Record<string, unknown>): Promise<boolean>

row

type Record<string, unknown>
returns Promise<boolean>

true when the row was removed; false when the table isn't deletable or the server refused

mutates

  • this

TableStatus
#

db/status.ts view source

TableStatus import type {TableStatus} from '@fuzdev/fuz_app/db/status.js';

Table info with row count.

name

type string

row_count

type number

TableWithCount
#

http/db_routes.ts view source

TableWithCount import type {TableWithCount} from '@fuzdev/fuz_app/http/db_routes.js';

Table info with row count.

name

type string

row_count

type number

TerminalDeps
#

runtime/deps.ts view source

TerminalDeps import type {TerminalDeps} from '@fuzdev/fuz_app/runtime/deps.js';

Terminal I/O operations.

stdout_write

Write bytes to stdout.

type (data: Uint8Array<ArrayBufferLike>): Promise<number>

data

type Uint8Array<ArrayBufferLike>
returns Promise<number>

stdin_read

Read bytes from stdin, or null on EOF.

type (buffer: Uint8Array<ArrayBufferLike>): Promise<number | null>

buffer

type Uint8Array<ArrayBufferLike>
returns Promise<number | null>

test_cell_gated_create_authorize
#

testing/cross_backend/test_cell_gated_create_authorize.ts view source

(auth: RequestActorContext, input: CellCreateAuthorizeInput): CellCreateVerdict | Promise<CellCreateVerdict> import {test_cell_gated_create_authorize} from '@fuzdev/fuz_app/testing/cross_backend/test_cell_gated_create_authorize.js';

The directory-model test authorizer — a pure function of the input (it reads the governing root's policy off input.root_data, supplied by the handler). Byte-equivalent with the Rust TestCellGatedCreateAuthorize.

auth

input

returns

CellCreateVerdict | Promise<CellCreateVerdict>

TEST_CLIENT_IP
#

testing/middleware.ts view source

"127.0.0.1" import {TEST_CLIENT_IP} from '@fuzdev/fuz_app/testing/middleware.js';

Default client IP set by the proxy stub in test apps.

TEST_CONTEXT_PRESET_KEY
#

hono_context.ts view source

"test_context_preset" import {TEST_CONTEXT_PRESET_KEY} from '@fuzdev/fuz_app/hono_context.js';

Hono context variable name for the test-harness pre-baked context flag.

Test harnesses (create_test_app_from_specs, create_fake_hono_context, the WS round-trip connect() helper, plus per-test middleware that pre-populates REQUEST_CONTEXT_KEY) set this to true so apply_authorization_phase skips its DB-backed actor resolution and trusts the supplied RequestContext. Production middleware never sets this key — only test code does. The flag is the explicit escape hatch that replaced the implicit "is REQUEST_CONTEXT_KEY already set?" probe, so that future production code consulting REQUEST_CONTEXT_KEY cannot silently bypass the live build.

TEST_COOKIE_SECRET
#

test_if
#

testing/cross_backend/capabilities.ts view source

(cond: boolean, name: string, fn: () => void | Promise<void>): void import {test_if} from '@fuzdev/fuz_app/testing/cross_backend/capabilities.js';

Conditional test() wrapper — registers a vitest case only when cond is true; otherwise registers it as .skip so the run still surfaces the gated coverage in the report.

Thin wrapper around vitest's test.skipIf(!cond) with the argument order flipped to match the more readable test_if(capabilities.ws, ...) call pattern.

cond

type boolean

name

type string

fn

type () => void | Promise<void>

returns

void

TEST_MIDDLEWARE_PATH
#

TestAccount
#

testing/app_server.ts view source

TestAccount import type {TestAccount} from '@fuzdev/fuz_app/testing/app_server.js';

A bootstrapped test account with credentials.

account

type { id: string & $brand<"Uuid">; username: string; }

actor

type { id: string & $brand<"Uuid">; }

session_cookie

Signed session cookie value.

type string

api_token

Raw API token for Bearer auth.

type string

create_session_headers

Build request headers with this account's session cookie.

type (extra?: Record<string, string> | undefined): Record<string, string>

extra?

type Record<string, string>
optional
returns Record<string, string>

create_bearer_headers

Build request headers with this account's Bearer token.

type (extra?: Record<string, string> | undefined): Record<string, string>

extra?

type Record<string, string>
optional
returns Record<string, string>

TestAccountFixture
#

testing/cross_backend/setup.ts view source

TestAccount import type {TestAccountFixture} from '@fuzdev/fuz_app/testing/cross_backend/setup.js';

Shape returned by TestFixture.create_account. Aliased to the existing TestAccount interface from testing/app_server.ts — same shape, stable name on the cross-backend testing surface so call sites read fixture.create_account(...) returning TestAccountFixture without crossing module boundaries.

account

type { id: string & $brand<"Uuid">; username: string; }

actor

type { id: string & $brand<"Uuid">; }

session_cookie

Signed session cookie value.

type string

api_token

Raw API token for Bearer auth.

type string

create_session_headers

Build request headers with this account's session cookie.

type (extra?: Record<string, string> | undefined): Record<string, string>

extra?

type Record<string, string>
optional
returns Record<string, string>

create_bearer_headers

Build request headers with this account's Bearer token.

type (extra?: Record<string, string> | undefined): Record<string, string>

extra?

type Record<string, string>
optional
returns Record<string, string>

TestAccountOverrides
#

testing/entities.ts view source

TestAccountOverrides

type Partial<Omit<Account, "id" | "created_by" | "updated_by">> & { id?: string | undefined; created_by?: string | null | undefined; updated_by?: string | null | undefined; }

import type {TestAccountOverrides} from '@fuzdev/fuz_app/testing/entities.js';

Override type for create_test_account — id-like fields accept plain string.

created_at?

type string

username?

type string

email?

type string | null

email_verified?

type boolean

updated_at?

type string

deleted_at?

Soft-delete tombstone. Non-null means the account is deleted (delete = soft); auth resolution treats it as absent. A hard purge removes the row entirely. See auth/account_queries.ts.

type string | null

password_hash?

type string

deleted_by?

Actor that performed the soft-delete (initiator: self / admin / keeper). Paired with deleted_at, mirroring role_grant's revoked_at / revoked_by. Plain UUID (no FK, like created_by / updated_by on this table).

type Uuid | null

id?

type string

created_by?

type string | null

updated_by?

type string | null

TestAccountWithActor
#

TestActorOverrides
#

testing/entities.ts view source

TestActorOverrides

type Partial<Omit<Actor, "id" | "account_id" | "updated_by">> & { id?: string | undefined; account_id?: string | undefined; updated_by?: string | null | undefined; }

import type {TestActorOverrides} from '@fuzdev/fuz_app/testing/entities.js';

Override type for create_test_actor — id-like fields accept plain string.

created_at?

type string

name?

type string

updated_at?

type string | null

deleted_at?

Soft-delete tombstone — set alongside the owning account's soft-delete.

type string | null

deleted_by?

Actor that performed the soft-delete. Paired with deleted_at.

type Uuid | null

id?

type string

account_id?

type string

updated_by?

type string | null

TestApp
#

testing/app_server.ts view source

TestApp import type {TestApp} from '@fuzdev/fuz_app/testing/app_server.js';

A fully assembled test app — Hono app + backend + helpers.

app

type Hono<BlankEnv, BlankSchema, "/">

backend

type TestAppServer

server

The assembled AppServer — reach for what create_app_server built beyond the fields lifted onto TestApp: audit_sse (with audit_log_sse in app_options), the ws_endpoints transports, bootstrap_status, migration_results.

type AppServer

surface_spec

type AppSurfaceSpec

surface

type AppSurface

route_specs

type RouteSpec[]

create_session_headers

Build request headers with the bootstrapped session cookie.

type (extra?: Record<string, string> | undefined): Record<string, string>

extra?

type Record<string, string>
optional
returns Record<string, string>

create_bearer_headers

Build request headers with the bootstrapped Bearer token.

type (extra?: Record<string, string> | undefined): Record<string, string>

extra?

type Record<string, string>
optional
returns Record<string, string>

create_daemon_token_headers

Build request headers with the daemon token (keeper auth).

type (extra?: Record<string, string> | undefined): Record<string, string>

extra?

type Record<string, string>
optional
returns Record<string, string>

create_account

Create an additional account with credentials.

type (options?: CreateTestAppAccountArgs | undefined): Promise<TestAccount>

options?

optional
returns Promise<TestAccount>

cleanup

Cleanup resources — AppServer.close: stops an opted-in auth cleanup schedule, closes live connections, disposes the limiters the server built and removes the audit listeners and closer members it added, then the backend's close. Idempotent.

type (): Promise<void>

returns Promise<void>

TestAppForBootstrap
#

testing/app_server.ts view source

TestAppForBootstrap import type {TestAppForBootstrap} from '@fuzdev/fuz_app/testing/app_server.js';

A fully assembled test app in the pre-bootstrap state — empty DB, bootstrap_lock.bootstrapped = false, no keeper account. Test drives POST /bootstrap itself.

app

type Hono<BlankEnv, BlankSchema, "/">

backend

type AppBackend

server

The assembled AppServer, as on TestApp — bootstrap_status, audit_sse, the ws_endpoints transports.

type AppServer

surface_spec

type AppSurfaceSpec

surface

type AppSurface

route_specs

type RouteSpec[]

create_request_headers

Build host/origin request headers for the anonymous bootstrap POST.

type (extra?: Record<string, string> | undefined): Record<string, string>

extra?

type Record<string, string>
optional
returns Record<string, string>

cleanup

Cleanup resources — AppServer.close, as on TestApp; the backend's own close leaves the injected or factory-cached database open. Idempotent.

type (): Promise<void>

returns Promise<void>

TestAppServer
#

testing/app_server.ts view source

TestAppServer import type {TestAppServer} from '@fuzdev/fuz_app/testing/app_server.js';

An AppBackend with a bootstrapped account, API token, and session cookie.

inheritance

extends: AppBackend

account

The bootstrapped account.

type { id: string & $brand<"Uuid">; username: string; }

actor

The actor linked to the account.

type { id: string & $brand<"Uuid">; }

api_token

Raw API token for Bearer auth.

type string

session_cookie

Signed session cookie value for cookie auth.

type string

keyring

Keyring used for cookie signing — exposed for forging expired/tampered cookies in tests.

type Keyring

cleanup

Release test resources (no-op when DB is injected or factory-cached).

type (): Promise<void>

returns Promise<void>

TestAppServerOptions
#

testing/app_server.ts view source

TestAppServerOptions import type {TestAppServerOptions} from '@fuzdev/fuz_app/testing/app_server.js';

Configuration for create_test_app_server.

session_options

Session options — needed for cookie signing.

type SessionOptions<string>

db?

Existing database — skips internal DB creation when provided. Caller owns the DB lifecycle.

type Db

db_type?

Database driver type — only used when db is provided. Default: 'pglet-memory'.

type DbType

migration_namespaces?

Extra migration namespaces run after the builtin auth namespace in the auto-created in-memory database, mirroring create_app_backend's migration_namespaces. For suites whose backend needs tables beyond auth — the cell parity suite passes [CELL_MIGRATION_NS]. The harness builds + caches a fresh-per-test factory migrating [auth_migration_ns, ...migration_namespaces]; the reset-on-create gives the same fresh-db isolation as the auth-only default. Mutually exclusive with db (which assumes the caller already migrated).

type readonly MigrationNamespace[]

password?

Password implementation. Default: stub_password_deps. Pass argon2_password_deps for tests that exercise login.

type PasswordHashDeps

username?

Username for the bootstrapped account. Default: 'keeper'.

type string

password_value?

Password for the bootstrapped account. Default: DEFAULT_TEST_PASSWORD.

type string

roles?

Roles to grant. Default: [ROLE_KEEPER].

type string[]

audit_factory?

Build the bound AuditEmitter used by the test backend. Defaults to default_audit_factory (a no-listener create_audit_emitter over the test backend's {db, log}). Pass a custom factory when a test needs:

  • to capture audit events (compose on_audit_event inside the body)
  • to register consumer event-type schemas (pass audit_log_config)
  • to instrument emit (emit_decorator)
  • to wrap or replace the emitter for some other reason

Matches the production shape — create_app_backend requires an audit_factory and create_test_app_server mirrors that contract end-to-end.

type (params: { db: Db; log: Logger; }): AuditEmitter

params

type { db: Db; log: Logger; }
returns AuditEmitter

TestAuditEventOverrides
#

testing/entities.ts view source

TestAuditEventOverrides

type Partial<Omit<AuditLogEvent, "id" | "actor_id" | "account_id" | "target_account_id" | "target_actor_id">> & { id?: string | undefined; actor_id?: string | null | undefined; account_id?: string | null | undefined; target_account_id?: string | null | undefined; target_actor_id?: string | null | undefined; }

import type {TestAuditEventOverrides} from '@fuzdev/fuz_app/testing/entities.js';

Override type for create_test_audit_event — id-like fields accept plain string.

ip?

type string | null

created_at?

type string

event_type?

type string

outcome?

type AuditOutcome

seq?

type number

metadata?

type Record<string, unknown> | null

id?

type string

actor_id?

type string | null

account_id?

type string | null

target_account_id?

type string | null

target_actor_id?

type string | null

TestBackendPaths
#

testing/cross_backend/build_test_backend_paths.ts view source

TestBackendPaths import type {TestBackendPaths} from '@fuzdev/fuz_app/testing/cross_backend/build_test_backend_paths.js';

Generic per-backend paths every cross-process test binary needs. Consumers extend this with their own domain paths.

  • root — the per-backend subtree under os.tmpdir(). Compose consumer-specific paths under here.
  • bootstrap_token_path — FUZ_BOOTSTRAP_TOKEN_PATH; harness writes the bootstrap token here before spawn.
  • daemon_token_path — where init_daemon_token (Rust) and the TS server's daemon-token writer land the token (under {root}/run/).

root

type string

readonly

bootstrap_token_path

type string

readonly

daemon_token_path

type string

readonly

TestFixture
#

testing/cross_backend/setup.ts view source

TestFixtureBase import type {TestFixture} from '@fuzdev/fuz_app/testing/cross_backend/setup.js';

The per-test bundle returned by SetupTest. Every Tier 1 suite body reads exclusively from this shape — no test_app.backend.* reads remain in the suite bodies.

Transport-agnostic: in-process and cross-process producers return the same shape. Behaviors that once needed raw backend access (keyring for forging cookies) are reached through wire-shaped seams instead — mint_expired_session() mints over the _testing_mint_session channel cross-process and directly in-process, so suite bodies never branch on the transport.

transport

Transport for this test's HTTP requests. Typed as FetchTransport so cross-process tests can call transport.cookies() for WS upgrade cookie threading; in-process provides a no-op cookies() returning [] (in-process tests construct cookies via create_session_headers directly and don't thread WS through this channel).

type (url: string, init: RequestInit): Promise<Response>

readonly

url

type string

init

type RequestInit
returns Promise<Response>

fresh_transport

Build a brand-new FetchTransport with an empty cookie jar pinned to the same backend. Use for unauthed assertions (`no cookie on protected route returns 401`, bearer-only calls expected to fall through to the unauthenticated path) where the per-test session cookie carried by transport's jar would otherwise leak into the request and convert a 401 into a 200.

New-per-call, not memoized — each invocation returns a fresh instance. If a call mutates the jar (e.g. an unauthed login attempt returning Set-Cookie) it can't pollute sibling calls.

Pass origin: null for bearer-only probes that must look like non-browser callers — the auth middleware silently discards bearer credentials when Origin/Referer is present, so a default Origin: <base_url> would convert "bearer + no Origin → 200" into "bearer + Origin → discarded → 401" cross-process. In-process the wrapper is stateless and the option is a no-op (no auto-Origin to suppress).

In-process this is functionally identical to transport (the wrapper's cookies(): [] is a no-op already); cross-process the returned transport starts with an empty jar at the same base_url.

type (options?: { readonly origin?: string | null | undefined; } | undefined): FetchTransport

readonly

options?

type { readonly origin?: string | null | undefined; }
optional

account

The freshly-bootstrapped keeper account.

type { readonly id: string & $brand<"Uuid">; readonly username: string; }

readonly

actor

The actor linked to the keeper account.

type { readonly id: string & $brand<"Uuid">; }

readonly

create_session_headers

Build request headers with the keeper's session cookie.

type (extra?: Record<string, string> | undefined): Record<string, string>

readonly

extra?

type Record<string, string>
optional
returns Record<string, string>

create_bearer_headers

Build request headers with the keeper's bearer token.

type (extra?: Record<string, string> | undefined): Record<string, string>

readonly

extra?

type Record<string, string>
optional
returns Record<string, string>

create_daemon_token_headers

Build request headers with the daemon token (keeper auth).

type (extra?: Record<string, string> | undefined): Record<string, string>

readonly

extra?

type Record<string, string>
optional
returns Record<string, string>

create_account

Mint an additional bootstrapped account for cross-account / multi-user tests. In-process: re-uses create_test_account_with_credentials against the same DB; cross-process: goes through the consumer-supplied DB-admin channel.

type (options?: CreateTestAccountOptions | undefined): Promise<TestAccount>

readonly

options?

optional
returns Promise<TestAccount>

extra_accounts

Bootstrap-time-seeded secondaries, keyed by their declared username. Populated from the extra_accounts option passed to default_in_process_setup / default_cross_process_setup. Empty for suites that don't declare any.

type Readonly<Record<string, ExtraAccountFixture>>

readonly

extra_actors

Additional actors seeded on the keeper account (beyond its single bootstrap actor), in declaration order. Populated from the extra_actors option. Empty unless a suite declares any. Use to drive the multi-actor acting-selector branches: with extra_actors non-empty the keeper has >1 actor, so a keeper request omitting acting resolves to actor_required with these ids in its available[] list. Each id is a valid acting value the keeper can supply explicitly.

type readonly { readonly id: string & $brand<"Uuid">; readonly name: string; }[]

readonly

mint_expired_session

Forge an *expired server-side session* for the keeper account and return the ready-to-send Cookie header value (name=value). The minted auth_session row is backdated while the signed cookie payload stays valid — so resolution clears the cookie-payload gate (parse_session) and is refused at the authoritative DB-row gate (query_session_get_valid — WHERE expires_at > NOW()). Backs the expired_session conformance principal. In-process mints directly via mint_test_session; cross-process drives the _testing_mint_session RPC over the keeper's daemon-token channel (the driver has no keyring).

type (): Promise<string>

readonly
returns Promise<string>

TestFixtureBase
#

testing/cross_backend/setup.ts view source

TestFixtureBase import type {TestFixtureBase} from '@fuzdev/fuz_app/testing/cross_backend/setup.js';

Fields shared by every TestFixture regardless of transport. The discriminated union below adds in-process-only fields conditionally.

Keeper ≠ admin. fixture.account / fixture.actor refer to the fresh keeper seeded per test. The keeper account holds ROLE_KEEPER + ROLE_ADMIN by default — matching the production bootstrap_account flow. The ROLE_KEEPER role itself does *not* grant admin reach; the bootstrap account just happens to hold both as separate grants. Tests probing the keeper-vs-admin separation (e.g. "non-admin cannot list accounts") declare a secondary at setup-time via extra_accounts: [{username, roles: [ROLE_KEEPER]}] and read it from fixture.extra_accounts[username].

transport

Transport for this test's HTTP requests. Typed as FetchTransport so cross-process tests can call transport.cookies() for WS upgrade cookie threading; in-process provides a no-op cookies() returning [] (in-process tests construct cookies via create_session_headers directly and don't thread WS through this channel).

type (url: string, init: RequestInit): Promise<Response>

readonly

url

type string

init

type RequestInit
returns Promise<Response>

fresh_transport

Build a brand-new FetchTransport with an empty cookie jar pinned to the same backend. Use for unauthed assertions (`no cookie on protected route returns 401`, bearer-only calls expected to fall through to the unauthenticated path) where the per-test session cookie carried by transport's jar would otherwise leak into the request and convert a 401 into a 200.

New-per-call, not memoized — each invocation returns a fresh instance. If a call mutates the jar (e.g. an unauthed login attempt returning Set-Cookie) it can't pollute sibling calls.

Pass origin: null for bearer-only probes that must look like non-browser callers — the auth middleware silently discards bearer credentials when Origin/Referer is present, so a default Origin: <base_url> would convert "bearer + no Origin → 200" into "bearer + Origin → discarded → 401" cross-process. In-process the wrapper is stateless and the option is a no-op (no auto-Origin to suppress).

In-process this is functionally identical to transport (the wrapper's cookies(): [] is a no-op already); cross-process the returned transport starts with an empty jar at the same base_url.

type (options?: { readonly origin?: string | null | undefined; } | undefined): FetchTransport

readonly

options?

type { readonly origin?: string | null | undefined; }
optional

account

The freshly-bootstrapped keeper account.

type { readonly id: string & $brand<"Uuid">; readonly username: string; }

readonly

actor

The actor linked to the keeper account.

type { readonly id: string & $brand<"Uuid">; }

readonly

create_session_headers

Build request headers with the keeper's session cookie.

type (extra?: Record<string, string> | undefined): Record<string, string>

readonly

extra?

type Record<string, string>
optional
returns Record<string, string>

create_bearer_headers

Build request headers with the keeper's bearer token.

type (extra?: Record<string, string> | undefined): Record<string, string>

readonly

extra?

type Record<string, string>
optional
returns Record<string, string>

create_daemon_token_headers

Build request headers with the daemon token (keeper auth).

type (extra?: Record<string, string> | undefined): Record<string, string>

readonly

extra?

type Record<string, string>
optional
returns Record<string, string>

create_account

Mint an additional bootstrapped account for cross-account / multi-user tests. In-process: re-uses create_test_account_with_credentials against the same DB; cross-process: goes through the consumer-supplied DB-admin channel.

type (options?: CreateTestAccountOptions | undefined): Promise<TestAccount>

readonly

options?

optional
returns Promise<TestAccount>

extra_accounts

Bootstrap-time-seeded secondaries, keyed by their declared username. Populated from the extra_accounts option passed to default_in_process_setup / default_cross_process_setup. Empty for suites that don't declare any.

type Readonly<Record<string, ExtraAccountFixture>>

readonly

extra_actors

Additional actors seeded on the keeper account (beyond its single bootstrap actor), in declaration order. Populated from the extra_actors option. Empty unless a suite declares any. Use to drive the multi-actor acting-selector branches: with extra_actors non-empty the keeper has >1 actor, so a keeper request omitting acting resolves to actor_required with these ids in its available[] list. Each id is a valid acting value the keeper can supply explicitly.

type readonly { readonly id: string & $brand<"Uuid">; readonly name: string; }[]

readonly

mint_expired_session

Forge an *expired server-side session* for the keeper account and return the ready-to-send Cookie header value (name=value). The minted auth_session row is backdated while the signed cookie payload stays valid — so resolution clears the cookie-payload gate (parse_session) and is refused at the authoritative DB-row gate (query_session_get_valid — WHERE expires_at > NOW()). Backs the expired_session conformance principal. In-process mints directly via mint_test_session; cross-process drives the _testing_mint_session RPC over the keeper's daemon-token channel (the driver has no keyring).

type (): Promise<string>

readonly
returns Promise<string>

testing_action_manifest_action_spec
#

testing/cross_backend/testing_reset_actions.ts view source

{ readonly method: "_testing_action_manifest"; readonly kind: "request_response"; readonly initiator: "frontend"; readonly auth: { readonly account: "required"; readonly actor: "none"; readonly credential_types: readonly [...]; }; ... 4 more ...; readonly description: string; } import {testing_action_manifest_action_spec} from '@fuzdev/fuz_app/testing/cross_backend/testing_reset_actions.js';

_testing_action_manifest — dump the backend's live RPC method set as a normalized ActionManifest (one entry per method: `{method, side_effects, account, actor, roles, credential_types}`) for cross-impl parity diffing. The action-surface twin of _testing_schema_snapshot: where that introspects the live *database*, this introspects the live *RPC registry*. The cross-backend harness calls it on each backend, then assert_action_manifests_equals the results (the Rust mirror is fuz_testing::create_testing_action_manifest_action_spec, whose normalization matches by design). Complements the in-repo spine_method_coverage gate (mounted ⟹ covered) by proving the TS mount-set ≡ the Rust mount-set, method-for-method and auth-shape-for-shape.

Unlike its _testing_* siblings this action is not bundled by create_testing_actions — the manifest must enumerate *every* mounted method, which create_testing_actions (one sub-factory among many) can't see. It's appended at the full-mount layer (build_full_spine_rpc_actions) via create_testing_action_manifest_action, where the complete list exists.

auth gates on the daemon-token credential, matching _testing_reset.

testing_drain_effects_action_spec
#

testing/cross_backend/testing_reset_actions.ts view source

{ readonly method: "_testing_drain_effects"; readonly kind: "request_response"; readonly initiator: "frontend"; readonly auth: { readonly account: "required"; readonly actor: "none"; readonly credential_types: readonly [...]; }; ... 4 more ...; readonly description: "Test-binary only — await in-flight fire-and-forge... import {testing_drain_effects_action_spec} from '@fuzdev/fuz_app/testing/cross_backend/testing_reset_actions.js';

_testing_drain_effects — await in-flight fire-and-forget audit writes so a following audit_log_list is authoritative. The deterministic barrier the cross-backend conformance suite uses in place of a poll/sleep before asserting on audit rows.

The handler awaits AuditEmitter.drain_inflight, which waits until no tracked emit write is outstanding — so the binary's emitter must be built with track_inflight: true, or the drain resolves at once and is no barrier. Tracking is what makes it a barrier whatever the transport: an HTTP mutation on a binary running await_pending_effects: true has its writes settled by the time it responds, but a WS mutation is answered before its effects flush (reply-then-close for a self-revocation), so its audit write can still be in flight when the caller reads the reply. The Rust spine does the same await in AuditEmitter::drain_inflight, over an emitter built with new_with_inflight_tracking.

Covered: every emit that ran before the drain was dispatched, and any started while it waits. A write that starts after the drain resolves is not — nor are eager writes outside emit (session touch, token usage), which only the dispatching transport's own flush awaits.

auth gates on the daemon-token credential, matching _testing_reset.

TESTING_FLOOD_MAX_COUNT
#

testing/cross_backend/testing_reset_actions.ts view source

65536 import {TESTING_FLOOD_MAX_COUNT} from '@fuzdev/fuz_app/testing/cross_backend/testing_reset_actions.js';

The most notifications one _testing_flood_notifications call queues. Empty ones at this count are charged more than DEFAULT_WS_OUTBOUND_QUEUE_BYTES between them — each frame's envelope and its overhead — so a burst of tiny frames can overflow a connection on a default transport too. Same value as the Rust spine's.

TESTING_FLOOD_MAX_SIZE
#

TESTING_FLOOD_MAX_TOTAL_BYTES
#

testing/cross_backend/testing_reset_actions.ts view source

number import {TESTING_FLOOD_MAX_TOTAL_BYTES} from '@fuzdev/fuz_app/testing/cross_backend/testing_reset_actions.js';

The most payload bytes one _testing_flood_notifications call builds across all its notifications — the bound on count × size. The handler serializes each notification without yielding, so the product is how long one call holds the event loop.

TESTING_FLOOD_NOTIFICATION_METHOD
#

testing_flood_notifications_action_spec
#

testing/cross_backend/testing_reset_actions.ts view source

{ readonly method: "_testing_flood_notifications"; readonly kind: "request_response"; readonly initiator: "frontend"; readonly auth: { readonly account: "required"; readonly actor: "none"; readonly credential_types: readonly [...]; }; ... 5 more ...; readonly description: string; } import {testing_flood_notifications_action_spec} from '@fuzdev/fuz_app/testing/cross_backend/testing_reset_actions.js';

_testing_flood_notifications — queue a burst of notifications onto the caller's own WebSocket connection, all in one synchronous run, so a suite driving the server from outside can push a connection's outbound queue past its byte budget deterministically — without a second producer, and whether or not its client is reading: the connection's write pump gets no turn while the handler runs, so the burst waits at once. Past the budget the server closes the socket with WS_CLOSE_OUTBOUND_OVERFLOW.

Each notification is {"jsonrpc":"2.0","method":"_testing_flood","params":{"index":<i>,"payload":"<size × 'x'>"}} with index counting from 0, so every frame is a little longer than size bytes and a client can tell which frames reached it.

The result is {queued: count} — the number asked for, not the number that fit: when the burst overflows the queue the connection is closed and this response is never written. A burst that fits arrives in order, ahead of the response.

A call past TESTING_FLOOD_MAX_COUNT, TESTING_FLOOD_MAX_SIZE, or TESTING_FLOOD_MAX_TOTAL_BYTES (count × size) is refused with invalid_params and queues nothing. Over HTTP there is no connection to queue onto, so the call is refused with invalid_params, reason ERROR_TESTING_FLOOD_NO_CONNECTION, rather than answering as though it had flooded something.

auth gates on the daemon-token credential, matching _testing_reset. The Rust mirror is fuz_testing::create_testing_flood_notifications_action_spec.

TESTING_METHOD_PREFIX
#

testing/surface_invariants.ts view source

"_testing_" import {TESTING_METHOD_PREFIX} from '@fuzdev/fuz_app/testing/surface_invariants.js';

Reserved method-name prefix for the daemon-token-gated test-backdoor actions (_testing_reset, _testing_mint_session, _testing_put_fact, _testing_drain_effects, _testing_schema_snapshot). Test binaries live-mount these on their RPC endpoint but they must never appear on a declared surface.

testing_migration_tracker_action_spec
#

testing/cross_backend/testing_reset_actions.ts view source

{ readonly method: "_testing_migration_tracker"; readonly kind: "request_response"; readonly initiator: "frontend"; readonly auth: { readonly account: "required"; readonly actor: "none"; readonly credential_types: readonly [...]; }; ... 4 more ...; readonly description: string; } import {testing_migration_tracker_action_spec} from '@fuzdev/fuz_app/testing/cross_backend/testing_reset_actions.js';

_testing_migration_tracker — dump the schema_version tracker rows as a normalized MigrationTracker ([{namespace, name, sequence}]) for cross-impl migration-identity diffing. The provenance half of _testing_schema_snapshot: where that captures the resulting *schema* (and excludes the tracker by design), this captures the tracker *itself*, so the cross-backend harness can assert the two spines record byte-identical migration identity. This closes the gap that let the cell/fact migration-name divergence reach the visiones cutover undetected (name-divergence-at-N — same schema, divergent recorded names).

auth gates on the daemon-token credential, matching _testing_reset. The Rust mirror is fuz_testing::create_testing_migration_tracker_action_spec.

testing_mint_session_action_spec
#

testing/cross_backend/testing_reset_actions.ts view source

{ readonly method: "_testing_mint_session"; readonly kind: "request_response"; readonly initiator: "frontend"; readonly auth: { readonly account: "required"; readonly actor: "none"; readonly credential_types: readonly [...]; }; ... 4 more ...; readonly description: string; } import {testing_mint_session_action_spec} from '@fuzdev/fuz_app/testing/cross_backend/testing_reset_actions.js';

_testing_mint_session — mint an expired-by-construction server-side session for an existing account and return its signed cookie value.

expires_in_seconds is constrained negative (z.number().int().negative()) so the action is structurally incapable of minting a *usable* session: it can only produce an already-backdated, already-dead auth_session row. The daemon-token gate + loopback binding already fence the backdoor, but the negative constraint is the make-impossible-states floor — even a misuse can't forge a valid session for an arbitrary account_id. The Rust mirror (fuz_testing::create_testing_mint_session_action_spec) enforces the same floor.

The minted auth_session row's expires_at is backdated while the returned cookie's own signed payload stays valid (future). Cross-process auth resolution therefore passes the cookie-payload gate (parse_session) and is refused by the authoritative DB-row gate (query_session_get_valid — WHERE expires_at > NOW()) — the gate the in-process payload-expiry tests never reach and the one that structurally needs a server-side mint (the cross-process driver has no keyring / DB access). The expired_session conformance principal drives this.

auth gates on the daemon-token credential, matching _testing_reset — effectively keeper-only. Like its siblings the action is internally privileged (a direct auth_session insert the production wire never exposes); daemon-token auth is the structural fence and the module's assert_dev_env import (TS) plus the Rust cargo xtask check-release dep-graph audit keep the _testing_ surface out of every shipped build.

testing_origin_check_action_spec
#

testing/cross_backend/testing_reset_actions.ts view source

{ readonly method: "_testing_origin_check"; readonly kind: "request_response"; readonly initiator: "frontend"; readonly auth: { readonly account: "required"; readonly actor: "none"; readonly credential_types: readonly [...]; }; ... 4 more ...; readonly description: string; } import {testing_origin_check_action_spec} from '@fuzdev/fuz_app/testing/cross_backend/testing_reset_actions.js';

_testing_origin_check — run the origin allowlist's parse and request check for one {patterns, origin} pair, so the shared conformance table (origin_conformance.json) checks each backend's grammar and matcher over the wire, row by row. Pure: no database, no state. The Rust mirror is fuz_testing::create_testing_origin_check_action_spec.

Like _testing_action_manifest, it is not bundled by create_testing_actions: a consumer whose Rust binary doesn't mount the mirror would fail its action-manifest parity gate. The spine test binary appends it at the full-mount layer (build_full_spine_rpc_actions).

auth gates on the daemon-token credential, matching _testing_reset.

testing_put_fact_action_spec
#

testing/cross_backend/testing_reset_actions.ts view source

{ readonly method: "_testing_put_fact"; readonly kind: "request_response"; readonly initiator: "frontend"; readonly auth: { readonly account: "required"; readonly actor: "none"; readonly credential_types: readonly [...]; }; ... 4 more ...; readonly description: string; } import {testing_put_fact_action_spec} from '@fuzdev/fuz_app/testing/cross_backend/testing_reset_actions.js';

_testing_put_fact — seed an embedded fact (fact.bytes) for the cross-process fact-serving suite, which drives over real HTTP and has no PgFactStore to call. Hashes the UTF-8 content (blake3, via fact_hash_bytes — the same hash the Rust _testing_put_fact computes), inserts the row idempotently, and returns {hash}. The referencing cell is seeded separately via the cell_create RPC. Embedded-only is enough for the authz assertions (cell-scoped admit, cross-owner-no-leak, 404-mask, bare-hash admin-only); external / X-Accel parity stays covered by the forge's own gate.

auth gates on the daemon-token credential, matching _testing_reset — the action does a direct fact insert the production wire never exposes. The Rust mirror is fuz_testing::create_testing_put_fact_action_spec.

testing_reset_action_spec
#

testing/cross_backend/testing_reset_actions.ts view source

{ readonly method: "_testing_reset"; readonly kind: "request_response"; readonly initiator: "frontend"; readonly auth: { readonly account: "required"; readonly actor: "none"; readonly credential_types: readonly [...]; }; ... 4 more ...; readonly description: "Test-binary only — wipe auth tables, re-bootstrap a fresh... import {testing_reset_action_spec} from '@fuzdev/fuz_app/testing/cross_backend/testing_reset_actions.js';

The _testing_reset action spec.

Input:

  • extra_keeper_roles — roles to grant the fresh keeper *in addition to* [ROLE_KEEPER, ROLE_ADMIN] (matching production bootstrap).
  • extra_accounts — additional accounts to seed at this same bootstrap-equivalent step. Each entry's roles are direct-granted (bypassing offer/accept) because the seed is *part of bootstrap*, not a post-bootstrap action. Use this for accounts whose required roles aren't admin-grantable via offer/accept (e.g. ROLE_KEEPER, whose RoleSpec.grant_paths is bootstrap-only). For admin-grantable roles, prefer fixture.create_account({roles}) (offer/accept production path).

Output: keeper credentials plus a parallel array of seeded extra_accounts (same order as input). The per-test fixture closes over the returned values; subsequent calls in the same test see the fresh keeper and any requested secondaries.

auth gates on the daemon-token credential — the keeper holds it exclusively. The action is internally privileged (it runs direct DB writes the production wire never exposes); daemon-token auth is the structural fence.

testing_schema_snapshot_action_spec
#

testing/cross_backend/testing_reset_actions.ts view source

{ readonly method: "_testing_schema_snapshot"; readonly kind: "request_response"; readonly initiator: "frontend"; readonly auth: { readonly account: "required"; readonly actor: "none"; readonly credential_types: readonly [...]; }; ... 4 more ...; readonly description: "Test-binary only — introspect the live schema i... import {testing_schema_snapshot_action_spec} from '@fuzdev/fuz_app/testing/cross_backend/testing_reset_actions.js';

_testing_schema_snapshot — introspect the live database into a normalized SchemaSnapshot for cross-impl parity diffing. The cross-backend harness calls this on each backend, then assert_schema_snapshots_equals the results (a Rust backend answers from fuz_db::query_schema_snapshot; the shapes match by design). Optional exclude_tables drops documented divergences from both sides before comparison.

A backend running on pglet (a TS backend config's memory:// default) answers with an error when its schema holds an index: the snapshot exists to be compared, and pglet's index definitions omit the modifiers a comparison needs (query_schema_snapshot). Run the backend on PostgreSQL.

auth gates on the daemon-token credential, matching _testing_reset.

testing_shorten_credential_expiry_action_spec
#

testing/cross_backend/testing_reset_actions.ts view source

{ readonly method: "_testing_shorten_credential_expiry"; readonly kind: "request_response"; readonly initiator: "frontend"; readonly auth: { readonly account: "required"; readonly actor: "none"; readonly credential_types: readonly [...]; }; ... 4 more ...; readonly description: string; } import {testing_shorten_credential_expiry_action_spec} from '@fuzdev/fuz_app/testing/cross_backend/testing_reset_actions.js';

_testing_shorten_credential_expiry — pull the expiry of an account's stored credentials (every auth_session and api_token row it owns) in to seconds from now, so the cross-backend credential-deadline suite can open a WebSocket or audit stream on a credential that expires a moment later and watch it close at the deadline (ResolvedAuth.credential_deadline) without waiting out a real lifetime.

Applied before the open: it rewrites the stored rows, and a connection reads its deadline when its credential resolves, so only one opened afterwards sees the near deadline.

Shorten-only. The new expiry is LEAST(expires_at, NOW() + seconds) — an eternal token gains one, and nothing already due sooner moves later, so the action can neither extend a credential's life nor revive an expired one; seconds is an integer from TESTING_SHORTEN_CREDENTIAL_EXPIRY_MIN_SECONDS to TESTING_SHORTEN_CREDENTIAL_EXPIRY_MAX_SECONDS. The Rust mirror is fuz_testing::create_testing_shorten_credential_expiry_action_spec.

Like _testing_origin_check, it is not bundled by create_testing_actions: a consumer whose Rust binary doesn't mount the mirror would fail its action-manifest parity gate. The spine test binary appends it at the full-mount layer (build_full_spine_rpc_actions).

auth gates on the daemon-token credential, matching _testing_reset.

TESTING_SHORTEN_CREDENTIAL_EXPIRY_MAX_SECONDS
#

testing/cross_backend/testing_reset_actions.ts view source

60 import {TESTING_SHORTEN_CREDENTIAL_EXPIRY_MAX_SECONDS} from '@fuzdev/fuz_app/testing/cross_backend/testing_reset_actions.js';

The longest expiry _testing_shorten_credential_expiry sets, in seconds from now — a minute: the action exists to make a deadline arrive inside a test.

TESTING_SHORTEN_CREDENTIAL_EXPIRY_MIN_SECONDS
#

TestingBackdoorCrossTestOptions
#

testing/cross_backend/testing_backdoor.ts view source

TestingBackdoorCrossTestOptions import type {TestingBackdoorCrossTestOptions} from '@fuzdev/fuz_app/testing/cross_backend/testing_backdoor.js';

Options for the testing-backdoor negative-credential suite.

inheritance

ws?

Where the backend's WebSocket endpoint is, for the session-socket cases. Pass it when the backend serves the _testing_* actions on its WS endpoint too; omitted, those cases are skipped.

type { readonly base_url: string; readonly ws_path: string; readonly origin?: string | undefined; }

readonly

TestingOriginCheckInput
#

testing/cross_backend/testing_reset_actions.ts view source

value + type

{ patterns: string; origin: string | null; } import {TestingOriginCheckInput} from '@fuzdev/fuz_app/testing/cross_backend/testing_reset_actions.js';

Input of _testing_origin_check.

patterns

An allowlist value, as FUZ_ALLOWED_ORIGINS would carry it.

type string

origin

One Origin header's value, or null for a request without one.

type string | null

TestingOriginCheckOutput
#

testing/cross_backend/testing_reset_actions.ts view source

value + type

{ ok: true; allowed: boolean; } | { ok: false; errors: { kind: "empty_entry" | "not_printable_ascii" | "missing_scheme" | "unsupported_scheme" | "has_path" | "has_userinfo" | "missing_host" | ... 9 more ... | "no_origins"; index?: number | undefined; }[]; }

type { ok: true; allowed: boolean; } | { ok: false; errors: { kind: "empty_entry" | "not_printable_ascii" | "missing_scheme" | "unsupported_scheme" | "has_path" | "has_userinfo" | "missing_host" | "invalid_host_char" | "unbracketed_ipv6" | "partial_wildcard" | "non_canonical_ipv4" | "unclosed_ipv6" | "invalid_ipv6" | "invalid_after_ipv6" | "invalid_port" | "default_port" | "no_origins"; index?: number | undefined; }[]; }

import {TestingOriginCheckOutput} from '@fuzdev/fuz_app/testing/cross_backend/testing_reset_actions.js';

Output of _testing_origin_check: the request-level answer when the list parses, else every refusal — index the 1-based entry position, absent for a list-level refusal.

TestingRateLimiter
#

testing/testing_rate_limiter.ts view source

import {TestingRateLimiter} from '@fuzdev/fuz_app/testing/testing_rate_limiter.js';

RateLimiter plus bucket tracking. Every check/record call adds its key to #seen_keys; reset removes it; reset_all clears every tracked bucket. Drop-in replacement anywhere a RateLimiter is expected — the type is nominally compatible via subclassing.

inheritance

extends: RateLimiter

check

type (key: string, now?: number | undefined): RateLimitResult

key

type string

now?

type number
optional

record

type (key: string, now?: number | undefined): RateLimitResult

key

type string

now?

type number
optional

reset

type (key: string): void

key

type string
returns void

reset_all

Clear every bucket this limiter has been asked about. Idempotent; safe to call before any check/record activity. Designed to be invoked from a _testing_reset handler's reset_state callback so the test binary's rate-limit buckets don't leak across test cases.

This is the one sanctioned caller that clears IP-aggregate keys — RateLimiter.reset forbids that for production code, because refunding the IP bucket on a successful auth reopens the distributed-spray path. Here it is test-state teardown between cases, not success-forgiveness, so the rule doesn't apply.

type (): void

returns void

tracked_keys

Snapshot of every bucket key this limiter has observed via check/record. Doesn't reflect post-cleanup pruning — keys that cleanup() removed remain in tracked_keys until reset/reset_all runs (or the limiter is disposed). Useful for assertions like "limiter saw exactly N IPs" in tests.

type ReadonlySet<string>

getter

TestingServerAdapter
#

testing/cross_backend/testing_server_core.ts view source

TestingServerAdapter import type {TestingServerAdapter} from '@fuzdev/fuz_app/testing/cross_backend/testing_server_core.js';

Runtime adapter contract for the test-binary entry. Each adapter (testing/cross_backend/testing_server_node.ts, testing/cross_backend/testing_server_deno.ts, testing/cross_backend/testing_server_bun.ts) implements this and hands the shape to start_testing_server.

runtime_label

Human-readable runtime label for log output (e.g. "Node", "Deno").

type string

runtime

type RuntimeDeps

get_connection_ip

Extract the raw TCP connection IP from a Hono context.

type (c: Context<any, any, {}>): string | undefined

c

type Context<any, any, {}>
returns string | undefined

prepare_websocket

Prepare the runtime's WS upgrade helper and its post-serve attach step.

type (options?: PrepareWebsocketOptions | undefined): PreparedWebsocket

options?

optional

serve

Bind app.fetch to port on hostname; return a ServeHandle.

type (options: { fetch: (request: Request, env?: unknown, executionCtx?: ExecutionContext | undefined) => Response | Promise<Response>; port: number; hostname: string; }): ServeHandle

options

type { fetch: (request: Request, env?: unknown, executionCtx?: ExecutionContext | undefined) => Response | Promise<Response>; port: number; hostname: string; }
returns ServeHandle

pid

Current process pid (for daemon.json).

type number

register_shutdown_signals

Register SIGINT/SIGTERM listeners that invoke handler once each.

type (handler: () => Promise<void>): void

handler

type () => Promise<void>
returns void

exit

Forceful exit on graceful-shutdown completion or fatal error.

type (code: number): never

code

type number
returns never

TestMiddlewareStackApp
#

testing/middleware.ts view source

TestMiddlewareStackApp import type {TestMiddlewareStackApp} from '@fuzdev/fuz_app/testing/middleware.js';

app

type Hono<BlankEnv, BlankSchema, "/">

mock_validate

type Mock

mock_find_by_id

type Mock

mock_find_actor_by_id

type Mock

mock_find_actors_by_account

type Mock

mock_find_active_for_actor

type Mock

TestMiddlewareStackOptions
#

testing/middleware.ts view source

TestMiddlewareStackOptions import type {TestMiddlewareStackOptions} from '@fuzdev/fuz_app/testing/middleware.js';

trusted_proxies?

Trusted proxy IPs.

type string[]

default `['10.0.0.1']`

allowed_origins?

Comma-separated allowed origin patterns.

type string

default `'https://app.example.com'`

connection_ip?

Connection IP or factory.

type string | (() => string | undefined)

default first trusted proxy

TestRoleGrantOverrides
#

testing/entities.ts view source

TestRoleGrantOverrides

type Partial<Omit<RoleGrant, "id" | "actor_id" | "scope_kind" | "scope_id" | "revoked_by" | "granted_by" | "source_offer_id">> & { id?: string | undefined; actor_id?: string | undefined; scope_kind?: string | null | undefined; scope_id?: string | null | undefined; revoked_by?: string | null | undefined; granted_by?: string | null | undefined; source_offer_id?: string | null | undefined; }

import type {TestRoleGrantOverrides} from '@fuzdev/fuz_app/testing/entities.js';

Override type for create_test_role_grant — id-like fields accept plain string.

created_at?

type string

expires_at?

type string | null

role?

type string

revoked_at?

type string | null

revoked_reason?

Optional free-form reason attached on revoke (rides on the role_grant_revoke WS notification to the revokee).

type string | null

id?

type string

actor_id?

type string

scope_kind?

type string | null

scope_id?

type string | null

revoked_by?

type string | null

granted_by?

type string | null

source_offer_id?

type string | null

TestValidatedApiTokenOverrides
#

testing/entities.ts view source

TestValidatedApiTokenOverrides

type Partial<Omit<ValidatedApiToken, "account_id">> & { account_id?: string | undefined; }

import type {TestValidatedApiTokenOverrides} from '@fuzdev/fuz_app/testing/entities.js';

Override type for create_test_validated_api_token — account_id accepts plain string.

id?

type string

created_at?

type string

expires_at?

type string | null

name?

type string

scope?

type TokenScope

last_used_at?

type string | null

last_used_ip?

type string | null

token_hash?

type string

expires_in_ms?

Milliseconds until the token expires — expires_at - NOW(), measured on the database clock in the statement that found it live (EXPIRES_IN_MS_SQL); null for an eternal token. The Rust twin's ApiTokenRow.expires_in.

type number | null

account_id?

type string

ThrowingApi
#

actions/rpc_client.ts view source

ThrowingApi<TApi> import type {ThrowingApi} from '@fuzdev/fuz_app/actions/rpc_client.js';

Maps a typed ActionsApi to a throwing variant.

For each method whose return type matches the create_rpc_client shape (Promise<Result<{value: T}, {error: JsonrpcErrorObject}>>), the wrapped method returns Promise<T> directly. Other shapes (notifications typed as => void, sync local_call methods) pass through unchanged — there is nothing to unwrap.

Input + options parameters are preserved verbatim via ...args: infer TArgs so the conditional matches both required-input (input: T) and optional-input (input?: T / nullary) signatures uniformly. Required-input shapes (e.g. admin_session_revoke_all(input: AdminSessionRevokeAllInput)) are not assignable to a (input?: TInput) => … pattern under --strictFunctionTypes, so an earlier (input?, options?) => form silently fell through to TApi[K] and left those methods Result-shaped — create_admin_rpc_adapters(api) would then reject the typed throwing Proxy because half its surface still returned Result<...>. The rest-args form preserves both required and optional parameters and resolves the gap.

generics

ThrowingApi<TApi>
TApi

ThrownJsonrpcError
#

http/jsonrpc_errors.ts view source

import {ThrownJsonrpcError} from '@fuzdev/fuz_app/http/jsonrpc_errors.js';

Error class carrying a JSON-RPC error code — thrown by handlers, caught by apply_route_specs and mapped to HTTP status + JSON-RPC error response.

Named for what it is: an error with a JSON-RPC error code that gets thrown.

inheritance

extends: Error

code

type JsonrpcErrorCode

data?

type unknown

constructor

type new (code: -32700 | -32600 | -32601 | -32602 | -32603 | (number & $brand<"JsonrpcServerErrorCode">), message: string, data?: unknown, options?: ErrorOptions | undefined): ThrownJsonrpcError

code

message

type string

data?

type unknown
optional

options?

type ErrorOptions
optional

to_action_property_key
#

actions/action_codegen.ts view source

(method: string): string import {to_action_property_key} from '@fuzdev/fuz_app/actions/action_codegen.js';

Render a method name as an object/interface property key — bare when it's a valid JS identifier, single-quoted otherwise. Method names like peer/ping aren't legal as bare keys, so quoting keeps the emitted ActionSpecs / ActionInputs / FrontendActionsApi members valid TypeScript. Pairs with the bracketed indexed-access form (ActionInputs['peer/ping']) used for value/type lookups, which already accepts the raw method string.

method

type string

returns

string

to_action_spec_identifier
#

actions/action_codegen.ts view source

(method: string): string import {to_action_spec_identifier} from '@fuzdev/fuz_app/actions/action_codegen.js';

Derive the exported spec identifier for a method. Method names that aren't legal JS identifiers — e.g. the / in the peer/ping protocol action — have each run of illegal characters collapsed to a single _, so peer/ping resolves to peer_ping_action_spec (the export in actions/peer_ping.ts). Already-valid names pass through unchanged.

method

type string

returns

string

to_action_spec_input_identifier
#

actions/action_codegen.ts view source

(method: string): string import {to_action_spec_input_identifier} from '@fuzdev/fuz_app/actions/action_codegen.js';

method

type string

returns

string

to_action_spec_output_identifier
#

actions/action_codegen.ts view source

(method: string): string import {to_action_spec_output_identifier} from '@fuzdev/fuz_app/actions/action_codegen.js';

method

type string

returns

string

to_admin_account
#

auth/account_schema.ts view source

(account: Account): { id: string & $brand<"Uuid">; username: string; email: string | null; email_verified: boolean; created_at: string; updated_at: string; updated_by: (string & $brand<...>) | null; deleted_at: string | null; } import {to_admin_account} from '@fuzdev/fuz_app/auth/account_schema.js';

Convert an Account to an AdminAccountJson for admin listings.

account

the full account record

type Account

returns

AdminAccountJson

the admin-safe account with audit fields

to_cell_json
#

auth/cell_actions.ts view source

(row: CellRow): { id: string & $brand<"Uuid">; path: (string & $brand<"CellPath">) | null; data: { [x: string]: unknown; label?: string | undefined; summary?: string | undefined; }; ... 11 more ...; grant_count: number; } import {to_cell_json} from '@fuzdev/fuz_app/auth/cell_actions.js';

row

type CellRow

returns

CellJson

to_field_json
#

to_global_role_revocation
#

auth/audit_log_schema.ts view source

(event: AuditLogEvent): { account_id: string & $brand<"Uuid">; role: string; } | null import {to_global_role_revocation} from '@fuzdev/fuz_app/auth/audit_log_schema.js';

The account and role a successful role-scope row took away, when the revoked grant was global — null otherwise.

A role gate (register_ws_endpoint's required_roles, the audit stream's require_role) admits on global grants only, so revoking a scoped grant takes away nothing a gate admitted on and closes nothing. A row with no metadata.scope_id reads as global: when unsure, closing is the safe side. The target is target_account_id ?? account_id, as for the account scope. Both revocation listeners read it, so a socket and a stream gated on one role close on the same rows.

Twin of the Rust spine's global_role_revocation.

event

a row whose scope is role (the caller dispatched on it)

returns

{ account_id: string & $brand<"Uuid">; role: string; } | null

the account and role to close on, or null when the row names no role or target, or revoked a scoped grant

to_grant_json
#

auth/cell_grant_actions.ts view source

(row: CellGrantRow): { id: string & $brand<"Uuid">; cell_id: string & $brand<"Uuid">; level: "viewer" | "editor"; actor_id: (string & $brand<"Uuid">) | null; role: string | null; scope_id: (string & $brand<...>) | null; granted_by: (string & $brand<...>) | null; created_at: string; } import {to_grant_json} from '@fuzdev/fuz_app/auth/cell_grant_actions.js';

row

returns

GrantJson

to_help_arg_properties
#

cli/help.ts view source

(schema: AnySchema): HelpArgProperty[] import {to_help_arg_properties} from '@fuzdev/fuz_app/cli/help.js';

Read the args of an object schema for help output, in order.

An entry whose no- negation is also an entry is left out, so a pair like color and no-color is shown once, as --no-color.

schema

fuz_schema object schema

type AnySchema

returns

HelpArgProperty[]

the args, none when schema is not an object schema

to_input_params
#

http/schema_helpers.ts view source

(input: ZodType<unknown, unknown, $ZodTypeInternals<unknown, unknown>>, raw_params: unknown): unknown import {to_input_params} from '@fuzdev/fuz_app/http/schema_helpers.js';

Normalize the raw params of an RPC call for input to validate: for a z.void() input, to_void_params; for any other input, an absent or null params reads as {}, so an all-optional object input accepts a call that omits them. Every receiving end — the server dispatch (perform_action) and a peer receiving a request or notification (ActionDispatcher) — reads params through this, so a peer is never stricter than the server.

input

the action's input schema

type ZodType<unknown, unknown, $ZodTypeInternals<unknown, unknown>>

raw_params

the call's params as parsed off the wire

type unknown

returns

unknown

the value to run input.safeParse on

to_iso8601_seconds
#

timestamp.ts view source

(date: Date): string import {to_iso8601_seconds} from '@fuzdev/fuz_app/timestamp.js';

Render a Date as the canonical second-precision UTC wire timestamp (YYYY-MM-DDTHH:MM:SSZ).

Truncates rather than rounds, mirroring Postgres' to_char(…, 'YYYY-MM-DD"T"HH24:MI:SS"Z"') and the Rust spine's fuz_sys::rfc3339_now.

date

the instant to render

type Date

returns

string

the 20-character UTC timestamp

to_item_json
#

auth/cell_item_actions.ts view source

(row: CellItemRow): { parent_id: string & $brand<"Uuid">; position: string & $brand<"CellItemPosition">; child_id: string & $brand<"Uuid">; created_at: string; } import {to_item_json} from '@fuzdev/fuz_app/auth/cell_item_actions.js';

row

returns

ItemJson

to_jsonrpc_envelope_id
#

http/jsonrpc_helpers.ts view source

(frame: unknown): string | number | null import {to_jsonrpc_envelope_id} from '@fuzdev/fuz_app/http/jsonrpc_helpers.js';

The id an error response to a rejected envelope can echo: the frame's id when the frame is an object carrying a valid one, else null.

Unlike to_jsonrpc_message_id, a scalar frame (5, "x") is never read as its own id — a non-object body has no id to echo, which is the Rust twin's classify answer too. Both transports use this for the invalid_request reply.

frame

the parsed body, of any shape

type unknown

returns

string | number | null

the echoable id, or null

to_jsonrpc_message_id
#

http/jsonrpc_helpers.ts view source

(message_or_id: unknown): string | number | null import {to_jsonrpc_message_id} from '@fuzdev/fuz_app/http/jsonrpc_helpers.js';

Extracts a JSON-RPC request id from a message or raw value. Returns null if no valid id can be extracted.

message_or_id

type unknown

returns

string | number | null

to_jsonrpc_params
#

http/jsonrpc_helpers.ts view source

(input: unknown): Record<string, any> | undefined import {to_jsonrpc_params} from '@fuzdev/fuz_app/http/jsonrpc_helpers.js';

Normalizes input to JSON-RPC params format. Returns undefined for null/undefined, wraps primitives in {value}.

input

type unknown

returns

Record<string, any> | undefined

to_max_length
#

cli/help.ts view source

<T>(items: T[], to_string: (item: T) => string): number import {to_max_length} from '@fuzdev/fuz_app/cli/help.js';

Get maximum length from array.

items

array of items

type T[]

to_string

function to convert item to string for length measurement

type (item: T) => string

returns

number

maximum string length

generics

to_max_length<T>
T

to_origin_conformance_checks
#

testing/cross_backend/origin_conformance.ts view source

(table: { parse: ({ raw: string; ok: true; note?: string | undefined; } | { raw: string; errors: { kind: "empty_entry" | "not_printable_ascii" | "missing_scheme" | "unsupported_scheme" | "has_path" | ... 11 more ... | "no_origins"; index?: number | undefined; }[]; note?: string | undefined; })[]; match: { ...; }[]; }): OriginConformanceCheck[] import {to_origin_conformance_checks} from '@fuzdev/fuz_app/testing/cross_backend/origin_conformance.js';

Flatten the table into action inputs and the answers they must get. A parse row sends a null origin, so a value that parses answers {ok: true, allowed: true}.

table

the conformance table

returns

OriginConformanceCheck[]

one check per row, parse rows first

to_revocation_scope
#

auth/audit_log_schema.ts view source

(event_type: string): RevocationScope import {to_revocation_scope} from '@fuzdev/fuz_app/auth/audit_log_schema.js';

The RevocationScope of an audit_log.event_type string.

An event type with no builtin entry — a consumer's own event — resolves to none: an event fuz_app has never heard of must not close connections. A consumer whose own event revokes closes the connections from its handler, with queue_connection_close.

event_type

type string

returns

RevocationScope

to_role_grant_offer_json
#

auth/role_grant_offer_schema.ts view source

(offer: RoleGrantOffer): { id: string & $brand<"Uuid">; from_actor_id: string & $brand<"Uuid">; to_account_id: string & $brand<"Uuid">; ... 12 more ...; resulting_role_grant_id: (string & $brand<...>) | null; } import {to_role_grant_offer_json} from '@fuzdev/fuz_app/auth/role_grant_offer_schema.js';

Convert a RoleGrantOffer row to its JSON payload shape.

offer

returns

RoleGrantOfferJson

to_route_content_type
#

testing/schema_generators.ts view source

(route: { raw_body?: boolean | undefined; }): string import {to_route_content_type} from '@fuzdev/fuz_app/testing/schema_generators.js';

The Content-Type a well-formed request to a route sends: application/json, or application/octet-stream for a raw_body route. Every POST route refuses anything else with 415 before its other gates run (see http/simple_request.ts), so a probe of those gates must send it, body or not. Other methods don't check it, so sending it there is harmless.

route

the route spec or surface route (reads raw_body)

type { raw_body?: boolean | undefined; }

returns

string

to_session_account
#

auth/account_schema.ts view source

(account: Account): SessionAccount import {to_session_account} from '@fuzdev/fuz_app/auth/account_schema.js';

Convert an Account to a SessionAccount by stripping sensitive fields.

account

the full account record

type Account

returns

SessionAccount

the client-safe account

to_void_params
#

http/schema_helpers.ts view source

(raw_params: unknown): unknown import {to_void_params} from '@fuzdev/fuz_app/http/schema_helpers.js';

Normalize the raw params of a z.void() input: an absent params, an empty object, and the null a GET ?params=null parses to are all the no-arg call (undefined); anything else passes through for z.void() to refuse.

JSON-RPC 2.0 lets a parameterless call omit params, and an empty by-name structure carries no parameters, so refusing {} would only break clients that send it for a parameterless call. Arrays — [] included — are not normalized: RPC params are by-name only. The Rust spine's require_void_params accepts the same shapes. Receiving ends reach it through to_input_params.

raw_params

the request's params as parsed off the wire

type unknown

returns

unknown

undefined for an empty shape, else raw_params unchanged

token_lifetime_to_expires_at
#

auth/token_lifetime.ts view source

(lifetime: { kind: "eternal"; } | { kind: "ttl"; days: number; }, now?: Date): Date | null import {token_lifetime_to_expires_at} from '@fuzdev/fuz_app/auth/token_lifetime.js';

Resolve a lifetime input to the expires_at value the mint query stores.

lifetime

the validated lifetime input

now

mint time (injectable for tests; defaults to the current time)

type Date
default new Date()

returns

Date | null

the expiry Date, or null for an eternal token

token_scope_admits_capability
#

auth/token_scope.ts view source

(scope: { version: 1; kind: "full"; grandfathered?: boolean | undefined; } | { version: 1; kind: "methods"; methods: string[]; grandfathered?: boolean | undefined; }, capability: TokenScopeCapability): boolean import {token_scope_admits_capability} from '@fuzdev/fuz_app/auth/token_scope.js';

Does scope admit capability?

The one place the two arms of the vocabulary meet their two predicates, so a surface: capability can never be answered by a method-list membership test (which would let a narrowed token onto a surface by listing a method named after it) and an rpc: capability can never be answered by rule 3 (which would refuse a token the method it was minted for).

scope

capability

returns

boolean

token_scope_admits_method
#

auth/token_scope.ts view source

(scope: { version: 1; kind: "full"; grandfathered?: boolean | undefined; } | { version: 1; kind: "methods"; methods: string[]; grandfathered?: boolean | undefined; }, method: string): boolean import {token_scope_admits_method} from '@fuzdev/fuz_app/auth/token_scope.js';

Does scope admit dispatching method?

Consulted by perform_action between the credential gate and the role gate.

scope

method

type string

returns

boolean

token_scope_admits_non_rpc
#

auth/token_scope.ts view source

(scope: { version: 1; kind: "full"; grandfathered?: boolean | undefined; } | { version: 1; kind: "methods"; methods: string[]; grandfathered?: boolean | undefined; }): boolean import {token_scope_admits_non_rpc} from '@fuzdev/fuz_app/auth/token_scope.js';

Does scope admit a non-RPC spine surface?

A narrowed token reaches no non-RPC surface at all — the load-bearing half of token scoping, and what "rule 3" names wherever this module's callers and the Rust twin use that shorthand. See docs/security.md §Token scoping.

A method-name allowlist alone would have been a false promise: the db-admin browser serves paginated rows of any public table (including account.password_hash, auth_session, and api_token) plus row DELETE, gated on a global role a bearer credential satisfies. A token whose UI badge read "scoped to cell_get" could still delete an account row.

Denying every non-RPC surface outright is strictly more restrictive than naming each one, keeps no vocabulary to maintain, and fits in one sentence a user can hold: *a scoped token is RPC-only*.

scope

returns

boolean

token_scope_denied_body
#

auth/token_scope.ts view source

(capability: string): { error: "token_scope_required"; required_scope: string; } import {token_scope_denied_body} from '@fuzdev/fuz_app/auth/token_scope.js';

Build the flat denial body a non-RPC route returns when a narrowed token reaches a capability it lacks: {error: 'token_scope_required', required_scope: '<section>:<id>'} at 403.

One shape for every capability, matching the Rust token_scope_surface_denied_response. These are REST-ish routes whose sibling denials are already flat, so the JSON-RPC envelope stays with the dispatcher's per-method gate and stops there.

capability

type string

returns

{ error: "token_scope_required"; required_scope: string; }

token_scope_from_input
#

auth/token_scope.ts view source

(input: { kind: "full"; } | { kind: "methods"; methods: string[]; }): { version: 1; kind: "full"; grandfathered?: boolean | undefined; } | { version: 1; kind: "methods"; methods: string[]; grandfathered?: boolean | undefined; } import {token_scope_from_input} from '@fuzdev/fuz_app/auth/token_scope.js';

Build the stored scope from a mint-time input.

Duplicate method names collapse into one, first occurrence kept: the set is what the scope admits, and the Rust spine stores a set, so both count the same methods (the account_token_list label reads N methods).

input

returns

TokenScope

token_scope_full
#

auth/token_scope.ts view source

(): { version: 1; kind: "full"; grandfathered?: boolean | undefined; } | { version: 1; kind: "methods"; methods: string[]; grandfathered?: boolean | undefined; } import {token_scope_full} from '@fuzdev/fuz_app/auth/token_scope.js';

A deliberately-minted full-authority scope.

returns

TokenScope

TOKEN_SCOPE_KEY
#

hono_context.ts view source

"token_scope" import {TOKEN_SCOPE_KEY} from '@fuzdev/fuz_app/hono_context.js';

Hono context variable name for the credential's TokenScope.

Set by every auth middleware that resolves a credential — bearer reads it from the token row; session and daemon-token set full, because a browser session *is* full account authority (narrowing it is what role grants are for) and the daemon token is singular, filesystem-proved, and keeper-bound.

Unset only for the anonymous caller, who holds no credential to narrow. Downstream must not read an unset value as permissive for an *authenticated* caller — that absent case is exactly the permissive default this design removes.

token_scope_label
#

auth/token_scope.ts view source

(scope: { version: 1; kind: "full"; grandfathered?: boolean | undefined; } | { version: 1; kind: "methods"; methods: string[]; grandfathered?: boolean | undefined; }): string import {token_scope_label} from '@fuzdev/fuz_app/auth/token_scope.js';

Stable label for the token list / UI.

scope

returns

string

token_scope_method_capability
#

auth/token_scope.ts view source

(method: string): string import {token_scope_method_capability} from '@fuzdev/fuz_app/auth/token_scope.js';

The capability string an action method is refused under: rpc:<method>.

Exported because two sites *compute* one — the dispatcher's per-method gate and the REST bridge, which must agree — where a surface: capability is always written as a literal on a route spec, so nothing computes one.

method

type string

returns

string

token_scope_methods
#

auth/token_scope.ts view source

(methods: string[]): { version: 1; kind: "full"; grandfathered?: boolean | undefined; } | { version: 1; kind: "methods"; methods: string[]; grandfathered?: boolean | undefined; } import {token_scope_methods} from '@fuzdev/fuz_app/auth/token_scope.js';

A narrowed scope over methods.

methods

type string[]

returns

TokenScope

token_scope_surface_denial
#

auth/request_context.ts view source

(c: Context<any, any, {}>, capability: string): Response | null import {token_scope_surface_denial} from '@fuzdev/fuz_app/auth/request_context.js';

Refuse a non-RPC surface to a narrowed token — rule 3, for callers that are not route specs. Under the Rust twin's name (token_scope_surface_denied_response).

The WS upgrade is the spine's one such caller: upgradeWebSocket's callback must return WSEvents and so cannot produce a denial, which is why the gate runs as pre-upgrade middleware calling this rather than as a route-spec declaration. A consumer hand-rolling its own upgrade is in the same position and reaches for the same function.

Takes the whole capability string, not a bare surface name — one spelling across route-spec declarations and direct calls alike, and the twin of the Rust signature. It needs no parse: the surface arm's identifier is pure label, since rule 3 never asks *which* surface. And it is deliberately not narrowed to the spine's own — the name decides nothing, so there is nothing for a closed set to protect, while a consumer surface would have nothing to pass. fuz_app's own uses are pinned two ways by the surface census: the exact call site, and the scan asserting every 'surface:<name>' literal in src/lib names a surface the spine actually mounts.

c

the request context, after the auth middleware chain

type Context<any, any, {}>

capability

the surface:<name> this route demands; names itself in the denial

type string

returns

Response | null

the 403 to return, or null when the caller may proceed

TOKEN_SCOPE_VERSION
#

TOKEN_SURFACE_NAME_REGEX
#

auth/token_scope.ts view source

RegExp import {TOKEN_SURFACE_NAME_REGEX} from '@fuzdev/fuz_app/auth/token_scope.js';

Name format for the identifier half of a capability string. Mirrors RoleName / CredentialTypeName / ScopeKindName.

Applied to the surface: arm only. The rpc: arm's identifier is an action method name, whose vocabulary belongs to ActionSpec (and includes shapes like peer/ping this regex would refuse) — the capability parser checks it is non-empty and leaves the rest to the registry that owns it.

TOKEN_SURFACES
#

auth/token_scope.ts view source

readonly ["db_admin", "fact_bare", "audit_stream", "ws_upgrade", "app_surface"] import {TOKEN_SURFACES} from '@fuzdev/fuz_app/auth/token_scope.js';

The non-RPC surfaces the spine itself mounts (rule 3). Twin of the Rust TokenSurface enum, name for name, except app_surface — the opt-in GET /api/surface explorer (server/surface_route.ts), which only the TS spine serves.

Not a registry a consumer registers into. RouteAuth.required_scope accepts any well-formed surface:<name> (see parse_token_scope_capability), so a consumer names its own non-RPC surface without an upstream release. This tuple is what the *spine* may declare, and what its surface census iterates — the closure is a check on fuz_app's own routes, not a vocabulary consumers have to ask permission to extend.

A surface name never decides anything: rule 3 is all-or-nothing (token_scope_admits_non_rpc), so the name only labels *which* surface refused, in the required_scope a denial reports. That is the same field whose rpc:<method> arm has always carried arbitrary consumer method names.

TOKEN_TTL_DAYS_MAX
#

auth/token_lifetime.ts view source

36500 import {TOKEN_TTL_DAYS_MAX} from '@fuzdev/fuz_app/auth/token_lifetime.js';

Upper bound on ttl.days — a sanity cap (100 years), not a policy ceiling. Shared with the Rust twin so out-of-range requests 400 identically on both spines.

TokenCreateInput
#

auth/account_action_specs.ts view source

value + type

{ name: string; scope: { kind: "full"; } | { kind: "methods"; methods: string[]; }; lifetime: { kind: "eternal"; } | { kind: "ttl"; days: number; }; } import {TokenCreateInput} from '@fuzdev/fuz_app/auth/account_action_specs.js';

Input for account_token_create.

scope is required — default-deny at mint. There is deliberately no permissive default: a caller who wants full authority spells {kind: 'full'}, and the stored document records that they did. That is the reversal of the 2026-02 design, where the field existed but nothing depended on it. Because scope has no default, the object as a whole no longer carries .prefault({}).

lifetime follows the same rule — a never-expiring token is {kind: 'eternal'}, spelled out, so expires_at IS NULL always means "deliberately eternal" (see auth/token_lifetime.ts).

name

type string

scope

type TokenScopeInput

lifetime

type TokenLifetimeInput

TokenCreateOutput
#

auth/account_action_specs.ts view source

value + type

{ ok: true; token: string; id: string; name: string; expires_at: string | null; } import {TokenCreateOutput} from '@fuzdev/fuz_app/auth/account_action_specs.js';

Output for account_token_create. token is returned exactly once.

ok

type true

token

type string

id

type string

name

type string

expires_at

type string | null

TokenLifetimeCrossTestOptions
#

testing/cross_backend/token_lifetime.ts view source

TokenLifetimeCrossTestOptions import type {TokenLifetimeCrossTestOptions} from '@fuzdev/fuz_app/testing/cross_backend/token_lifetime.js';

Options for the token-lifetime parity suite. Ungated — the mint + list pair is on every spine's standard surface, so there is no capability flag (the sibling of identity_parity.ts rather than the cell suites).

setup_test

Per-test fixture-producing function (fresh keeper + db per call).

type (): Promise<TestFixtureBase>

readonly
returns Promise<TestFixtureBase>

rpc_path?

RPC endpoint path the methods are mounted on. Default /api/rpc.

type string

readonly

TokenLifetimeInput
#

auth/token_lifetime.ts view source

value + type

{ kind: "eternal"; } | { kind: "ttl"; days: number; }

type { kind: "eternal"; } | { kind: "ttl"; days: number; }

import {TokenLifetimeInput} from '@fuzdev/fuz_app/auth/token_lifetime.js';

Caller-provided lifetime for account_token_create.

eternal mints a never-expiring token (expires_at NULL); ttl bounds it to days from mint time. eternal is deliberately the first variant so schema-driven test generators produce it by default.

TokenListInput
#

TokenListOutput
#

auth/account_action_specs.ts view source

value + type

{ tokens: { id: string; account_id: string & $brand<"Uuid">; name: string; expires_at: string | null; last_used_at: string | null; last_used_ip: string | null; created_at: string; scope: string; }[]; } import {TokenListOutput} from '@fuzdev/fuz_app/auth/account_action_specs.js';

Output for account_token_list. Hashes are excluded.

tokens

type ClientApiTokenJson[]

TokenRevokeInput
#

auth/account_action_specs.ts view source

value + type

{ token_id: string; } import {TokenRevokeInput} from '@fuzdev/fuz_app/auth/account_action_specs.js';

Input for account_token_revoke.

token_id

type string

TokenRevokeOutput
#

auth/account_action_specs.ts view source

value + type

{ ok: true; revoked: boolean; } import {TokenRevokeOutput} from '@fuzdev/fuz_app/auth/account_action_specs.js';

Output for account_token_revoke. revoked is false for IDOR misses.

ok

type true

revoked

type boolean

TokenScope
#

auth/token_scope.ts view source

value + type

{ version: 1; kind: "full"; grandfathered?: boolean | undefined; } | { version: 1; kind: "methods"; methods: string[]; grandfathered?: boolean | undefined; }

type { version: 1; kind: "full"; grandfathered?: boolean | undefined; } | { version: 1; kind: "methods"; methods: string[]; grandfathered?: boolean | undefined; }

import {TokenScope} from '@fuzdev/fuz_app/auth/token_scope.js';

The stored api_token.scope document.

grandfathered is a fact about the *row* — the api_token_scope migration wrote it; no minter chose it — that only the token list cares about. The enforcement path never branches on it, which is what stops "grandfathered" from becoming a third authority level.

Strict on both arms so a v2 document carrying a grant arm a v1 reader doesn't understand is refused rather than parsed as permissively-narrower-than-stored. The discriminated union also refuses the contradiction {kind: 'full', methods: [...]}.

TokenScopeCapability
#

auth/token_scope.ts view source

TokenScopeCapability

type { kind: "rpc"; capability: string; method: string; } | { kind: "surface"; capability: string; }

import type {TokenScopeCapability} from '@fuzdev/fuz_app/auth/token_scope.js';

A parsed required_scope capability — what a route demands of the calling credential's TokenScope.

capability is the wire string verbatim, carried rather than re-derived so a denial reports exactly what the route declared. The arms are asymmetric on purpose, and the asymmetry is the whole design: the rpc: identifier is consumed by a predicate (token_scope_admits_method), so it is kept; the surface: identifier is pure label, because rule 3 is all-or-nothing (token_scope_admits_non_rpc never looks at which surface). That is why one half of the vocabulary needs no registry.

TokenScopeInput
#

auth/token_scope.ts view source

value + type

{ kind: "full"; } | { kind: "methods"; methods: string[]; }

type { kind: "full"; } | { kind: "methods"; methods: string[]; }

import {TokenScopeInput} from '@fuzdev/fuz_app/auth/token_scope.js';

The scope input accepted by account_token_create — the stored shape minus the row-owned fields.

A minter cannot set version or grandfathered: the first is stamped by token_scope_full / token_scope_methods, and the second is the migration's mark for tokens that predate scoping. Letting a caller forge grandfathered would turn a tracked debt into a laundered one.

TokenScopeRequiredError
#

http/error_schemas.ts view source

value + type

{ [x: string]: unknown; error: "token_scope_required"; required_scope: string; } import {TokenScopeRequiredError} from '@fuzdev/fuz_app/http/error_schemas.js';

Token-scope error — returned by the require_token_scope guard (and its direct-call twin token_scope_surface_denial) when a narrowed api token reaches a capability its scope does not admit. See docs/security.md §Token scoping.

required_scope carries the capability string the route demands, verbatim from its auth.required_scope — surface:audit_stream for a non-RPC surface, rpc:<method> for a bridged action — matching the Rust token_scope_surface_denied_response and the dispatcher's own per-method denial. Third in the family with PermissionError and CredentialTypeRequiredError — each names what the route demanded rather than what the caller holds.

error

type "token_scope_required"

required_scope

type string

[key: string]

type unknown

TokenScopeSurfaceCrossTestOptions
#

TokenSurface
#

auth/token_scope.ts view source

"db_admin" | "fact_bare" | "audit_stream" | "ws_upgrade" | "app_surface"

type "db_admin" | "fact_bare" | "audit_stream" | "ws_upgrade" | "app_surface"

import type {TokenSurface} from '@fuzdev/fuz_app/auth/token_scope.js';

TransitionFunction
#

ui/popover.svelte.ts view source

TransitionFunction import type {TransitionFunction} from '@fuzdev/fuz_app/ui/popover.svelte.js';

Support both Svelte transitions and custom transitions.

(call)

type (node: HTMLElement): TransitionConfig | { destroy?: (() => void) | undefined; }

node

type HTMLElement
returns TransitionConfig | { destroy?: (() => void) | undefined; }

Transport
#

actions/transports.ts view source

Transport import type {Transport} from '@fuzdev/fuz_app/actions/transports.js';

transport_name

type string

send

type (message: { [x: string]: unknown; jsonrpc: "2.0"; id: string | number; method: string; params?: { [x: string]: unknown; } | undefined; }, options?: TransportSendOptions | undefined): Promise<...>

message

options?

optional
returns Promise<JsonrpcResponseOrError>

is_ready

type (): boolean

returns boolean

dispose?

type (): void

returns void

transport_composition_cases
#

TransportCompositionCase
#

testing/cross_backend/transport_composition.ts view source

TransportCompositionCase import type {TransportCompositionCase} from '@fuzdev/fuz_app/testing/cross_backend/transport_composition.js';

One cell of the front-door table.

surface

type TransportCompositionSurface

readonly

probe

type TransportCompositionProbe

readonly

expect

type TransportCompositionExpect

readonly

gate?

The capability the surface needs; the case skips on a backend without it.

type "sse" | "ws" | "cell_crud" | "cell_relations" | "account_lifecycle" | "fact_serving" | "ready" | "account_status" | "peer_request" | "ws_handshake_pipelining" | "ws_account_actions" | "ws_self_revocation_reply" | "cell_gated_create" | "ws_role_gated" | "ws_refusal_body"

readonly

TransportCompositionCrossTestOptions
#

testing/cross_backend/transport_composition.ts view source

TransportCompositionCrossTestOptions import type {TransportCompositionCrossTestOptions} from '@fuzdev/fuz_app/testing/cross_backend/transport_composition.js';

inheritance

base_url

Base URL the spawned backend is reachable at, or null for an in-process run. Given a URL, every probe is written to the socket; with null the probes go through the fixture's transport and the WebSocket rows skip.

type string | null

readonly

origin?

An Origin the backend allows. Default: the origin of base_url, or http://localhost:5173 in-process (which create_test_app's allow list admits).

type string

readonly

rest_path?

Session-only REST POST route the REST row probes. Default /api/account/logout.

type string

readonly

password_path?

Password-change route the /password row probes. Default /api/account/password.

type string

readonly

ws_path?

WebSocket endpoint path. Default /api/ws.

type string

readonly

sse_path?

Audit-log SSE path. Default /api/admin/audit/stream.

type string

readonly

TransportCompositionExpect
#

testing/cross_backend/transport_composition.ts view source

TransportCompositionExpect import type {TransportCompositionExpect} from '@fuzdev/fuz_app/testing/cross_backend/transport_composition.js';

The refusal a case expects.

status

type number

readonly

error

The refusal reason — the flat body's error, or the envelope's error.data.reason.

type string

readonly

fields?

Further fields the refusal must carry, beside the reason.

type Readonly<Record<string, unknown>>

readonly

envelope?

true when the refusal is a JSON-RPC error envelope rather than a flat {error} body.

type boolean

readonly

TransportCompositionProbe
#

testing/cross_backend/transport_composition.ts view source

TransportCompositionProbe

type "foreign_origin" | "unreadable_origin" | "duplicated_origin" | "over_cap_foreign_origin" | "no_credential" | "narrowed_token"

import type {TransportCompositionProbe} from '@fuzdev/fuz_app/testing/cross_backend/transport_composition.js';

What a probe sends, as the table's columns name it.

TransportCompositionSurface
#

testing/cross_backend/transport_composition.ts view source

TransportCompositionSurface

type "rest_post" | "rest_password" | "rpc_post" | "rpc_get" | "ws_upgrade" | "sse"

import type {TransportCompositionSurface} from '@fuzdev/fuz_app/testing/cross_backend/transport_composition.js';

A transport's front door, as the table's rows name it.

TransportForMethod
#

actions/rpc_client.ts view source

TransportForMethod import type {TransportForMethod} from '@fuzdev/fuz_app/actions/rpc_client.js';

Optional per-method transport selector. Return the transport to use for a given method, or undefined to let the peer pick via its fallback rules.

Useful when methods are registered on different backend dispatchers — e.g. a streaming action mounted on the WebSocket endpoint while the rest of the RPC surface lives on HTTP.

(call)

type (method: string): string | undefined

method

type string
returns string | undefined

TransportName
#

Transports
#

actions/transports.ts view source

import {Transports} from '@fuzdev/fuz_app/actions/transports.js';

allow_fallback

Whether to allow fallback to other transports if the current one is not available.

type boolean

default true

register_transport

Registers a transport. The first transport registered also becomes the current.

type (transport: Transport): void

transport

returns void

mutates

  • this — inserts into `#transport_by_name`; sets `#current_transport` if no current is set

set_current_transport

Switch the current transport selection by name.

type (transport_name: string): void

transport_name

type string
returns void

throws

  • Error - if no transport with `transport_name` has been registered

mutates

  • this — sets `#current_transport`

get_transport

Resolve a transport. With allow_fallback, walks specified → current → any-ready; without, returns the named transport (or current) only when it's ready.

type (transport_name?: string | undefined): Transport | null

transport_name?

type string
optional
returns Transport | null

the resolved transport, or null when none is ready

is_ready

type (): boolean | null

returns boolean | null

get_current_transport

type (): Transport | null

returns Transport | null

get_current_transport_name

type (): string | null

returns string | null

get_transport_by_name

type (transport_name: string): Transport | null

transport_name

type string
returns Transport | null

TransportSendOptions
#

actions/transports.ts view source

TransportSendOptions import type {TransportSendOptions} from '@fuzdev/fuz_app/actions/transports.js';

Per-call options accepted by every transport's send. Optional and extensible — adding a field is non-breaking. Source of truth for the shared option shape; ActionDispatcherSendOptions and RpcClientCallOptions extend it.

signal?

Per-call cancellation. Bottoms out at FrontendWebsocketClient.request({signal}) on the WS path (sends the shared cancel notification on abort) and at fetch({signal}) on HTTP. Backend transport has no per-call abort surface to honor.

type AbortSignal

queue?

Per-call durable-queue opt-in. Names the client-authoritative vs server-authoritative distinction — server-authoritative consumers (e.g. zzz completion calls) fail fast with service_unavailable when the transport is down; client-authoritative consumers (games, real-time apps) buffer and replay on reconnect because the user already committed to the action at click time. Honored only by FrontendWebsocketTransport on the request_response path (default false). HTTP and backend transports ignore it; WS notifications also ignore it and always fail-fast when disconnected (fire-and-forget connection.send has no queue semantic).

type boolean

truncate_middle
#

ui/ui_format.ts view source

(str: string, max_length: number, separator?: string): string import {truncate_middle} from '@fuzdev/fuz_app/ui/ui_format.js';

Truncate a string by keeping the start and end, with a separator in the middle.

str

type string

max_length

total length including separator

type number

separator

type string
default '…'

returns

string

the truncated string, or the original if it fits

truncate_uuid
#

ui/ui_format.ts view source

(uuid: string): string import {truncate_uuid} from '@fuzdev/fuz_app/ui/ui_format.js';

Truncate a UUID for display, keeping start and end visible.

uuid

type string

returns

string

a 12-character truncated UUID like a1b2c…7890

try_reserve_optional
#

rate_limiter.ts view source

(limiter: RateLimiter | null, key: string | null, now?: number): RateLimitReserveResult import {try_reserve_optional} from '@fuzdev/fuz_app/rate_limiter.js';

RateLimiter.try_reserve against a limiter that may be absent.

An inert reservation when limiter is null (disabled) or key is null (no resolvable key — e.g. no client IP), so a handler reserves and settles the same way whether or not the surface is limited. Twin of the Rust spine's try_reserve_optional.

limiter

the limiter, or null when disabled

type RateLimiter | null

key

rate limit key, or null to skip

type string | null

now

current timestamp in ms (defaults to Date.now())

type number
default Date.now()

returns

RateLimitReserveResult

ts_default_capabilities
#

ts_default_shape_notes
#

testing/cross_backend/default_backend_configs.ts view source

BackendShapeNotes import {ts_default_shape_notes} from '@fuzdev/fuz_app/testing/cross_backend/default_backend_configs.js';

Shape notes for TS-family backends — wiring facts, not gating flags. trusted_proxy: false (the test binary doesn't enable proxy parsing) and login_rate_limit: false (the TS canonical path leaves the limiter null in test mode); bearer auth is always wired. Documentation only — see BackendShapeNotes.

ts_spine_bun_backend_config
#

testing/cross_backend/ts_spine_backend_config.ts view source

(options?: TsSpineBackendConfigOptions): BackendConfig import {ts_spine_bun_backend_config} from '@fuzdev/fuz_app/testing/cross_backend/ts_spine_backend_config.js';

BackendConfig for the Bun TS spine binary — spawned via bun run. Bun resolves the entry's relative .js→.ts source specifiers natively (no flag needed — unlike Deno's --sloppy-imports, and like Gro's loader on the Node path). Bun.serve is built in; the WS upgrade comes from the optional @hono/bun peer dep.

options

default {}

returns

BackendConfig

TS_SPINE_BUN_DEFAULT_PORT
#

TS_SPINE_BUN_ENTRY
#

ts_spine_deno_backend_config
#

testing/cross_backend/ts_spine_backend_config.ts view source

(options?: TsSpineBackendConfigOptions): BackendConfig import {ts_spine_deno_backend_config} from '@fuzdev/fuz_app/testing/cross_backend/ts_spine_backend_config.js';

BackendConfig for the Deno TS spine binary. The --allow-* set mirrors the cross-process needs (net + read/write for the daemon-token file + env + sys); --unstable-detect-cjs matches the ecosystem's Deno test entries.

--sloppy-imports is required because the binary imports fuz_app source via relative .js specifiers (the src/lib convention) — Deno resolves .js→.ts only under this flag, whereas Gro's loader (the Node path) does so natively. (zzz's Deno entry sidesteps it by importing fuz_app as a built package; this binary tests live source instead.)

options

default {}

returns

BackendConfig

TS_SPINE_DENO_DEFAULT_PORT
#

TS_SPINE_DENO_ENTRY
#

TS_SPINE_DIR_ENV
#

testing/cross_backend/spine_surface_constants.ts view source

"FUZ_TESTING_TS_SPINE_DIR" import {TS_SPINE_DIR_ENV} from '@fuzdev/fuz_app/testing/cross_backend/spine_surface_constants.js';

Env var naming the TS spine backend's root dir, set by ts_spine_backend_config; the spawned binary writes its daemon token to {dir}/run/daemon_token, which must match bootstrap.daemon_token_path.

ts_spine_node_backend_config
#

TS_SPINE_NODE_DEFAULT_PORT
#

TS_SPINE_NODE_ENTRY
#

TsSpineBackendConfigOptions
#

testing/cross_backend/ts_spine_backend_config.ts view source

TsSpineBackendConfigOptions import type {TsSpineBackendConfigOptions} from '@fuzdev/fuz_app/testing/cross_backend/ts_spine_backend_config.js';

port?

Listening port. Defaults per runtime (1178 Node, 1179 Deno, 1180 Bun).

type number

readonly

database_url?

Database URL. Default 'memory://' (in-memory pglet).

type string

readonly

enable_login_rate_limit?

Enable the per-IP + per-account login rate limiters on the spawned binary (FUZ_LOGIN_RATE_LIMIT_ENABLED=true). Off by default — the standard cross suites fire many loopback logins a live limiter would 429. Set true only for the dedicated login-security cross project (global_setup_login_security.ts), which drives the 429 + Retry-After path and XFF-keyed bucketing over the wire (login_security.ts). The binary always wires trusted_proxies for 127.0.0.1/::1, so the limiter keys on the resolved X-Forwarded-For client IP. Mirrors SpineStubBackendConfigOptions.enable_login_rate_limit.

type boolean

readonly

enable_action_rate_limit?

Enable the action rate limiters (FUZ_ACTION_RATE_LIMIT_ENABLED=true) — one per-IP and one per-account limiter shared by the RPC and WS endpoints. Off by default, like enable_login_rate_limit; set only for the dedicated security cross project, which drives them through ws_action_rate_limit.ts. Mirrors SpineStubBackendConfigOptions.enable_action_rate_limit.

type boolean

readonly

action_rate_limit_max_attempts?

Replace both action limiters' max_attempts (FUZ_ACTION_RATE_LIMIT_MAX_ATTEMPTS) so a suite reaches the cap in a few calls. Requires enable_action_rate_limit — the binary refuses to boot otherwise. Mirrors SpineStubBackendConfigOptions.action_rate_limit_max_attempts.

type number

readonly

ui_fetch
#

ui/ui_fetch.ts view source

(input: URL | RequestInfo, init?: RequestInit | undefined): Promise<Response> import {ui_fetch} from '@fuzdev/fuz_app/ui/ui_fetch.js';

Fetch with credentials included (sends cookies).

input

type RequestInfo | URL

init?

type RequestInit
optional

returns

Promise<Response>

UncoveredEntry
#

testing/error_coverage.ts view source

UncoveredEntry import type {UncoveredEntry} from '@fuzdev/fuz_app/testing/error_coverage.js';

Uncovered entry — either a status-level row (no code) or a specific-code row.

method

type string

path

type string

status

type number

code?

Declared code value missing, when the status's error schema names specific codes.

type string

UNKNOWN_ERROR_MESSAGE
#

http/jsonrpc_errors.ts view source

"unknown error" import {UNKNOWN_ERROR_MESSAGE} from '@fuzdev/fuz_app/http/jsonrpc_errors.js';

Default message for unknown errors.

UnsupportedContentTypeError
#

http/error_schemas.ts view source

value + type

{ [x: string]: unknown; error: "unsupported_content_type"; } import {UnsupportedContentTypeError} from '@fuzdev/fuz_app/http/error_schemas.js';

Unsupported content type error — returned when a POST route refuses the request's Content-Type.

error

type "unsupported_content_type"

[key: string]

type unknown

update_env_variable
#

env/update_env_variable.ts view source

(key: string, value: string, options: UpdateEnvVariableOptions): Promise<void> import {update_env_variable} from '@fuzdev/fuz_app/env/update_env_variable.js';

Updates or adds an environment variable in the .env file. Preserves existing formatting, comments, and other variables.

Behavior:

  • Duplicate keys: updates the LAST occurrence (matches dotenv behavior)
  • Inline comments: preserved after the value (e.g., KEY=value # comment)
  • Quote style: preserved from original (quoted/unquoted)
  • export prefix: preserved when updating an export KEY=… line

key

the environment variable name (e.g., 'SOME_CONFIGURATION_KEY')

type string

value

the new value for the environment variable

type string

options

file path and optional read/write overrides

returns

Promise<void>

throws

  • Error - if the file read fails for any reason other than `ENOENT`, or if the write fails

mutates

  • filesystem — writes the updated content back to `options.env_file_path`

UpdateAppSettingsInput
#

auth/app_settings_schema.ts view source

value + type

{ open_signup: boolean; } import {UpdateAppSettingsInput} from '@fuzdev/fuz_app/auth/app_settings_schema.js';

Zod schema for updating app settings.

open_signup

type boolean

UpdateEnvVariableOptions
#

env/update_env_variable.ts view source

UpdateEnvVariableOptions import type {UpdateEnvVariableOptions} from '@fuzdev/fuz_app/env/update_env_variable.js';

Options for updating environment variables in a .env file.

env_file_path

Path to the .env file.

type string

read_file?

Function to read file contents (defaults to node:fs/promises readFile).

type (path: string, encoding: string): Promise<string>

path

type string

encoding

type string
returns Promise<string>

write_file?

Function to write file contents (defaults to node:fs/promises writeFile).

type (path: string, content: string, encoding: string): Promise<void>

path

type string

content

type string

encoding

type string
returns Promise<void>

Username
#

primitive_schemas.ts view source

value + type

string import {Username} from '@fuzdev/fuz_app/primitive_schemas.js';

Username for account creation — starts with letter, alphanumeric/dash/underscore middle, ends with alphanumeric. No @ or . allowed.

Canonicalized to lowercase at parse time. The regex rejects whitespace outright, so .trim() is unnecessary here. Storage is canonical across every creation site (bootstrap, signup, admin-create, invite acceptance) because the schema is the single source of truth — eliminates the per-caller trim().toLowerCase() ritual and keeps the LOWER(username) = LOWER($1) lookup contract simple.

USERNAME_LENGTH_MAX
#

primitive_schemas.ts view source

39 import {USERNAME_LENGTH_MAX} from '@fuzdev/fuz_app/primitive_schemas.js';

Maximum username length (matches GitHub's limit). The grammar is ASCII, so units and characters agree.

USERNAME_LENGTH_MIN
#

primitive_schemas.ts view source

3 import {USERNAME_LENGTH_MIN} from '@fuzdev/fuz_app/primitive_schemas.js';

Minimum username length (must have start + middle + end characters).

USERNAME_PROVIDED_LENGTH_MAX
#

primitive_schemas.ts view source

255 import {USERNAME_PROVIDED_LENGTH_MAX} from '@fuzdev/fuz_app/primitive_schemas.js';

Maximum length for username input on login/lookup, in UTF-16 code units, measured before trimming — more permissive than USERNAME_LENGTH_MAX for forward-compatibility if the creation limit is raised.

UsernameProvided
#

primitive_schemas.ts view source

value + type

string import {UsernameProvided} from '@fuzdev/fuz_app/primitive_schemas.js';

Username submitted for login or lookup — minimal validation for forward-compatibility if format rules change.

Canonicalized via .trim().toLowerCase() at parse time so login's per-account rate-limit key and DB lookup see a uniform value regardless of casing or surrounding whitespace. Mirrors the storage canonicalization on Username so submission and storage agree.

The trailing .refine rejects a whitespace-only identifier: .min(1) runs on the raw string (so " " passes it), and without the post-trim check the value would canonicalize to "" and fall through to a lookup-miss 401 instead of a 400 — an empty identifier is malformed input, not a wrong credential. Keeps the Rust spine's account_login (which rejects empty-after-trim) in parity.

utf8_length
#

actions/transports.ts view source

(text: string): number import {utf8_length} from '@fuzdev/fuz_app/actions/transports.js';

The UTF-8 byte length of text.

text

the string to measure

type string

returns

number

its length in UTF-8 bytes

utf8_length_over
#

actions/transports.ts view source

(text: string, max_bytes: number): number | null import {utf8_length_over} from '@fuzdev/fuz_app/actions/transports.js';

The UTF-8 byte length of text when it exceeds max_bytes, else null. Skips encoding when the UTF-16 length alone rules it out — a code unit takes at most 3 UTF-8 bytes.

text

the string to measure

type string

max_bytes

the byte cap

type number

returns

number | null

the UTF-8 byte length when it exceeds max_bytes, else null

Uuid
#

primitive_schemas.ts view source

value + type

string & $brand<"Uuid">

type string & $brand<"Uuid">

import {Uuid} from '@fuzdev/fuz_app/primitive_schemas.js';

Branded UUID schema.

external types

z.infer<typeof Uuid>

UuidWithDefault
#

primitive_schemas.ts view source

value + type

string & $brand<"Uuid">

type string & $brand<"Uuid">

import {UuidWithDefault} from '@fuzdev/fuz_app/primitive_schemas.js';

Uuid that defaults to a fresh create_uuid() when the input is undefined.

external types

z.infer<typeof UuidWithDefault>

VALID_SQL_IDENTIFIER
#

db/sql_identifier.ts view source

RegExp import {VALID_SQL_IDENTIFIER} from '@fuzdev/fuz_app/db/sql_identifier.js';

Pattern matching valid SQL identifiers: starts with a letter or underscore, followed by letters, digits, or underscores.

validate_daemon_token
#

auth/daemon_token.ts view source

(provided: string, state: DaemonTokenState): boolean import {validate_daemon_token} from '@fuzdev/fuz_app/auth/daemon_token.js';

Validate a daemon token against the current state.

Accepts both the current and previous token (2-token race window). Uses timing-safe comparison.

provided

the token from the X-Daemon-Token header

type string

state

the daemon token state

returns

boolean

true if the token is valid

validate_facts_internal_location
#

server/x_accel.ts view source

(config: string, facts_location: string): NginxFactsValidation import {validate_facts_internal_location} from '@fuzdev/fuz_app/server/x_accel.js';

Assert the nginx location serving the X-Accel facts prefix is internal;.

facts_location is the path the X-Accel redirect prefix points at (e.g. /_facts/). Returns a fatal error when no matching location block exists, or when the matching block is not marked internal; — either is a public facts location that bypasses cell visibility.

config

the nginx config template string to check

type string

facts_location

the facts location path the redirect prefix points at

type string

returns

NginxFactsValidation

{ok, errors} — ok is true only when the location exists and is internal;

validate_ip_strict
#

http/proxy.ts view source

(ip: string): "IPv4" | "IPv6" | undefined import {validate_ip_strict} from '@fuzdev/fuz_app/http/proxy.js';

Strict IP validity check.

Defense in depth around Hono's hono/utils/ipaddr helpers, which are lax in two ways:

  1. distinctRemoteAddr classifies anything-with-a-colon as 'IPv6', including 'host:port', 'attacker:controlled', '203.0.113.1:8080'.
  2. convertIPv6ToBinary silently accepts malformed forms like '[::1]:8080' and '::1\n', parsing them as inconsistent binary values that would still serve as distinct rate-limit keys for an attacker rotating the suffix.

Strict validation here is two-layered: a character-set pre-filter (IP_LITERAL_CHARS), then a round-trip through convertIPv*ToBinary to confirm the input parses cleanly. Either layer alone has holes; together they reject every input form we've seen Hono mis-handle.

Used as the security primitive for any code path that takes an IP string from an untrusted source (XFF, query params) and uses it as a key (rate limiting, audit subject) or compares it against trusted proxies via CIDR (where the latent throw would otherwise bubble out).

ip

type string

returns

"IPv4" | "IPv6" | undefined

the address family on success, undefined if the string is not a strictly-valid IP

validate_keyring
#

auth/keyring.ts view source

(env_value: string | undefined): string[] import {validate_keyring} from '@fuzdev/fuz_app/auth/keyring.js';

Validate key ring configuration.

Returns an error when no keys are configured (undefined, empty string, or all-separator input like '____'), and for each key shorter than MIN_KEY_LENGTH characters.

env_value

the SECRET_FUZ_COOKIE_KEYS environment variable

type string | undefined

returns

string[]

array of validation errors (empty if valid)

validate_phase_for_kind
#

actions/action_event_helpers.ts view source

(kind: "request_response" | "remote_notification" | "local_call", phase: "send_request" | "receive_request" | "send_response" | "receive_response" | "send_error" | "receive_error" | "send" | "receive" | "execute"): void import {validate_phase_for_kind} from '@fuzdev/fuz_app/actions/action_event_helpers.js';

Validate that phase is one of the phases allowed for kind per action_event_phase_by_kind.

kind

phase

returns

void

throws

  • Error - if `phase` is not valid for `kind`

validate_phase_transition
#

actions/action_event_helpers.ts view source

(from: "send_request" | "receive_request" | "send_response" | "receive_response" | "send_error" | "receive_error" | "send" | "receive" | "execute", to: "send_request" | "receive_request" | ... 6 more ... | "execute"): void import {validate_phase_transition} from '@fuzdev/fuz_app/actions/action_event_helpers.js';

Validate that a phase chain is legal per action_event_phase_transitions.

from

to

returns

void

throws

  • Error - if `from → to` is not the permitted next phase (or `from` is terminal)

validate_self_service_eligible_roles
#

auth/self_service_role_actions.ts view source

(roles: Iterable<string>): void import {validate_self_service_eligible_roles} from '@fuzdev/fuz_app/auth/self_service_role_actions.js';

Validate a deployment's self-service eligible-role set at construction.

Refuses a builtin role (admin / keeper) — self-toggling one would hand any account instance-wide authority, so no eligible set may name it — and a name outside the RoleName grammar. The same fail-loud-at-boot shape as create_role_schema, which refuses a builtin name where only app roles belong. Twin of the Rust validate_self_service_eligible_roles.

roles

the eligible role names

type Iterable<string, any, any>

returns

void

throws

  • Error - on a builtin role or a name outside the role grammar

validate_server_env
#

server/env.ts view source

(env: { NODE_ENV: "development" | "production"; PORT: number; HOST: string; DATABASE_URL: string; SECRET_FUZ_COOKIE_KEYS: string; FUZ_ALLOWED_ORIGINS: string; PUBLIC_FUZ_API_URL: string; ... 7 more ...; FUZ_FACTS_X_ACCEL_REDIRECT_PREFIX?: string | undefined; }): ServerEnvOptionsResult import {validate_server_env} from '@fuzdev/fuz_app/server/env.js';

Validate a loaded BaseServerEnv and produce the artifacts needed for server init.

Handles keyring validation, origin parsing (every refused allowlist entry is reported, each on its own line), and bootstrap token path extraction. Returns a Result so callers handle errors their own way (exit, logging, etc).

env

a loaded and Zod-validated BaseServerEnv

returns

ServerEnvOptionsResult

{ok: true, keyring, allowed_origins, bootstrap_token_path} or {ok: false, field, errors}

validate_step_transition
#

ValidatedApiToken
#

auth/api_token_queries.ts view source

ValidatedApiToken

type Omit<ApiToken, "scope"> & { scope: { version: 1; kind: "full"; grandfathered?: boolean | undefined; } | { version: 1; kind: "methods"; methods: string[]; grandfathered?: boolean | undefined; }; expires_in_ms: number | null; }

import type {ValidatedApiToken} from '@fuzdev/fuz_app/auth/api_token_queries.js';

A live api_token row as query_validate_api_token returns it: the stored scope document already parsed, so a caller holds the narrowing the credential was minted with and never decides what an unreadable one means. The Rust twin's ApiTokenRow carries the parsed scope the same way.

id

type string

account_id

type Uuid

created_at

type string

expires_at

type string | null

name

type string

last_used_at

type string | null

last_used_ip

type string | null

token_hash

type string

scope

type TokenScope

expires_in_ms

Milliseconds until the token expires — expires_at - NOW(), measured on the database clock in the statement that found it live (EXPIRES_IN_MS_SQL); null for an eternal token. The Rust twin's ApiTokenRow.expires_in.

type number | null

ValidatedKeyringResult
#

auth/keyring.ts view source

ValidatedKeyringResult

type { ok: true; keyring: Keyring; } | { ok: false; errors: string[]; }

import type {ValidatedKeyringResult} from '@fuzdev/fuz_app/auth/keyring.js';

Result of create_validated_keyring. Discriminated union — callers handle the error case their own way.

ValidationError
#

http/error_schemas.ts view source

value + type

{ [x: string]: unknown; error: "invalid_request_body" | "invalid_json_body" | "invalid_route_params" | "invalid_query_params"; issues?: { [x: string]: unknown; code: string; message: string; path: (string | number)[]; }[] | undefined; } import {ValidationError} from '@fuzdev/fuz_app/http/error_schemas.js';

Input validation error — returned when params / query / body fails Zod parsing, or when the request body is not valid JSON.

error is one of the four validation codes the framework emits. issues carries Zod's validation issues for diagnostic display on the three schema-failure cases (invalid_request_body, invalid_route_params, invalid_query_params). The field is optional: the invalid_json_body case (request body parse failure or non-object root) emits no issues, and the schema-failure cases emit them only in development — production omits them (via dev_only) so error responses don't leak input-schema structure to callers.

error

type "invalid_request_body" | "invalid_json_body" | "invalid_route_params" | "invalid_query_params"

issues?

type { [x: string]: unknown; code: string; message: string; path: (string | number)[]; }[]

[key: string]

type unknown

ValidAuthSession
#

auth/session_queries.ts view source

ValidAuthSession import type {ValidAuthSession} from '@fuzdev/fuz_app/auth/session_queries.js';

A live auth_session row as query_session_get_valid returns it, with how long it has left.

inheritance

extends: AuthSession

expires_in_ms

Milliseconds until the session expires — expires_at - NOW(), measured on the database clock in the statement that found it unexpired (EXPIRES_IN_MS_SQL).

type number

verify_dummy
#

auth/password_argon2.ts view source

(password: string): Promise<boolean> import {verify_dummy} from '@fuzdev/fuz_app/auth/password_argon2.js';

Verify a password against a dummy hash for timing attack resistance.

Always returns false, but takes the same time as a real verification. Call when account lookup fails to prevent timing-based user enumeration.

password

the plaintext password to "verify"

type string

returns

Promise<boolean>

always false

verify_password
#

auth/password_argon2.ts view source

(password: string, password_hash: string): Promise<boolean> import {verify_password} from '@fuzdev/fuz_app/auth/password_argon2.js';

Verify a password against an Argon2id hash.

password

the plaintext password to verify

type string

password_hash

the Argon2id hash to verify against

type string

returns

Promise<boolean>

true if the password matches

verify_request_source
#

http/origin.ts view source

(allowed_patterns: readonly RegExp[], carve_outs?: readonly MiddlewareCarveOut[]): Handler import {verify_request_source} from '@fuzdev/fuz_app/http/origin.js';

Middleware that verifies the request source against an allowlist.

Origin allowlisting (not the CSRF layer — that's SameSite: strict cookies plus http/simple_request.ts):

  • Checks the Origin header (if present) against the allowlist
  • Allows requests without an Origin header (direct access, curl, etc.)
  • Refuses an Origin that isn't visible ASCII, and a duplicated one — the runtimes join a repeated header into one value (a, b), which carries a space and so matches nothing

Origin-only by design — Fetch spec mandates Origin on every unsafe method (POST / PUT / DELETE / PATCH) regardless of Referrer-Policy, so every real browser request on the state-changing surface carries it. Non-browser clients (curl, server-to-server, CLI) don't ship auto- attached session cookies, so CSRF isn't the relevant threat there — auth (bearer / daemon token) is the actual control. A Referer fallback would only widen the accepted-shape envelope without closing a real CSRF hole. The Rust spine's origin_layer is the twin.

create_app_server mounts it on every path (ORIGIN_MIDDLEWARE_PATH); a request whose path a carve_outs entry matches passes unchecked — the named, explicit exception, each with its reason, as the Rust stack's origin_carve_out is.

allowed_patterns

compiled regex patterns from parse_allowed_origins

type readonly RegExp[]

carve_outs

paths the check skips, matched like a middleware path (middleware_applies)

type readonly MiddlewareCarveOut[]
default []

returns

Handler

VerifyInput
#

auth/account_action_specs.ts view source

value + type

void import {VerifyInput} from '@fuzdev/fuz_app/auth/account_action_specs.js';

Input for account_verify. No parameters — the caller is the subject.

WebsocketConnection
#

actions/transports_ws.ts view source

WebsocketConnection import type {WebsocketConnection} from '@fuzdev/fuz_app/actions/transports_ws.js';

Minimal interface for a WebSocket connection, decoupled from the concrete Socket Cell.

send

type (data: object): boolean

data

type object
returns boolean

connected

type boolean

readonly

add_message_handler

type (handler: (event: MessageEvent<any>) => void): () => void

handler

type (event: MessageEvent<any>) => void
returns () => void

add_error_handler

type (handler: (event: Event) => void): () => void

handler

type (event: Event) => void
returns () => void

WebsocketRpcConnection
#

actions/transports_ws.ts view source

WebsocketRpcConnection import type {WebsocketRpcConnection} from '@fuzdev/fuz_app/actions/transports_ws.js';

RPC-capable WebSocket connection — a WebsocketConnection that also handles request/response correlation with timeout, queue, AbortSignal cancel, and explicit-id support. Required by FrontendWebsocketTransport so it can delegate the pending-map bookkeeping to one canonical implementation (FrontendWebsocketClient) instead of running a parallel one.

Consumer wrappers around FrontendWebsocketClient (e.g. zzz's Socket) implement this by adding a one-line delegate to the underlying client's request.

inheritance

request

type (method: string, params: unknown, options?: { signal?: AbortSignal | undefined; queue?: boolean | undefined; id?: string | number | undefined; } | undefined): Promise<unknown>

method

type string

params

type unknown

options?

type { signal?: AbortSignal | undefined; queue?: boolean | undefined; id?: string | number | undefined; }
optional
returns Promise<unknown>

write_daemon_info
#

cli/daemon.ts view source

(runtime: Pick<EnvDeps, "env_get"> & Pick<FsWriteDeps, "mkdir" | "rename" | "write_text_file">, name: string, info: { version: number; pid: number; port: number; started: string; app_version: string; }): Promise<...> import {write_daemon_info} from '@fuzdev/fuz_app/cli/daemon.js';

Write daemon info to the PID file, creating directories as needed.

runtime

runtime with file write and env capabilities

type Pick<EnvDeps, "env_get"> & Pick<FsWriteDeps, "mkdir" | "write_text_file" | "rename">

name

application name

type string

info

daemon info to write

returns

Promise<void>

throws

  • Error - if `$HOME` is not set

mutates

  • filesystem — creates `~/.{name}/run/` and atomically writes `daemon.json`

write_daemon_token
#

testing/daemon_token_rotation.ts view source

(runtime: DaemonTokenWriteDeps, token_path: string, token: string): Promise<void> import {write_daemon_token} from '@fuzdev/fuz_app/testing/daemon_token_rotation.js';

Write the current token to disk atomically at mode 0600.

On-disk format is JSON {"token": "..."} — the wrapper leaves room for future fields (rotated_at, version) without changing every reader. Both the TS cross-backend harness reader (spawn_backend.read_daemon_token) and the Rust daemon-token writer match this shape.

runtime

runtime with file write capabilities

token_path

path to write the token

type string

token

the raw token string

type string

returns

Promise<void>

mutates

  • filesystem — writes `token_path` atomically at mode `0600`

write_fact_bytes_to_disk
#

db/fact_disk_storage.ts view source

(deps: Pick<FactDiskStorageDeps, "stat" | "mkdir" | "rename" | "write_file" | "fsync" | "remove">, facts_dir: string, hash: FactHash, bytes: Uint8Array<...>): Promise<...> import {write_fact_bytes_to_disk} from '@fuzdev/fuz_app/db/fact_disk_storage.js';

Write fully-buffered bytes for hash to the canonical <facts_dir>/<shard>/<rest> path, then publish via commit_temp_to_cas (fsync'd temp + atomic rename, dedup-aware). The buffering twin of stream_fact_to_disk, used by PgFactStore.put for oversize sync bytes. Returns the file: external_url for the fact row.

deps

type Pick<FactDiskStorageDeps, "mkdir" | "rename" | "remove" | "write_file" | "fsync" | "stat">

facts_dir

type string

hash

type FactHash

bytes

type Uint8Array<ArrayBufferLike>

returns

Promise<FileFactUrl>

mutates

  • facts_dir filesystem

write_file_atomic
#

runtime/fs.ts view source

(deps: Pick<FsWriteDeps, "rename" | "write_text_file"> & Partial<FsRemoveDeps>, path: string, content: string, options?: WriteFileAtomicOptions | undefined): Promise<...> import {write_file_atomic} from '@fuzdev/fuz_app/runtime/fs.js';

Write a file atomically via temp file + rename.

Writes to a unique .{name}.tmp.{pid}.{counter} sibling with exclusive create (O_EXCL), then renames over path so readers either see the old contents or the full new contents — never a partial write. The unique name + exclusive create are load-bearing for secret files: a fixed temp name would let a crash-leftover temp be reopened O_TRUNC with its old (possibly permissive) mode — mode only applies at creation — and the rename would publish that stale inode. Twin of the Rust spine's fuz_sys::secure_file::write_secure_file naming scheme.

deps

write capabilities; pass remove too for best-effort temp cleanup on failure

type Pick<FsWriteDeps, "write_text_file" | "rename"> & Partial<FsRemoveDeps>

path

destination path

type string

content

full file content

type string

options?

optional mode applied at creation

optional

returns

Promise<void>

throws

  • Error - if `write_text_file` or `rename` rejects (permissions, disk full, cross-device rename, etc.)

mutates

  • filesystem — creates the unique temp file then renames it to `path`

WriteFileAtomicOptions
#

runtime/fs.ts view source

WriteFileAtomicOptions import type {WriteFileAtomicOptions} from '@fuzdev/fuz_app/runtime/fs.js';

Options for write_file_atomic.

mode?

POSIX mode for the written file (e.g. 0o600 for secrets). Applied at temp-file creation, so the published file never exists with a laxer mode. Omitted = the runtime default (umask).

type number

WriteFileOptions
#

runtime/deps.ts view source

WriteFileOptions import type {WriteFileOptions} from '@fuzdev/fuz_app/runtime/deps.js';

Options for FsWriteDeps.write_text_file.

mode?

POSIX mode applied at creation (e.g. 0o600). Creation-time only — an existing file keeps its mode (pair with exclusive when the mode must hold). No-op where modes don't apply (mock runtime, Windows).

type number

exclusive?

Fail if the path already exists (O_EXCL). Load-bearing for secret files: without it a crash-leftover temp file is reopened O_TRUNC and keeps its old (possibly permissive) mode, since mode only applies at creation.

type boolean

WS_ADMISSION_PROBE_ID
#

WS_CLIENT_DEFAULT_TIMEOUT_MS
#

testing/transports/ws_client.ts view source

1000 import {WS_CLIENT_DEFAULT_TIMEOUT_MS} from '@fuzdev/fuz_app/testing/transports/ws_client.js';

Default wait-for timeout shared across in-process + cross-process impls. Tunable per-call via the timeout_ms parameter.

WS_CLOSE_CLIENT_HEARTBEAT_TIMEOUT
#

actions/transports.ts view source

4002 import {WS_CLOSE_CLIENT_HEARTBEAT_TIMEOUT} from '@fuzdev/fuz_app/actions/transports.js';

WebSocket close code — client timed out waiting for a response.

WS_CLOSE_CONNECTION_LIMIT
#

actions/transports.ts view source

4004 import {WS_CLOSE_CONNECTION_LIMIT} from '@fuzdev/fuz_app/actions/transports.js';

WebSocket close code — the server closed this socket to admit a newer connection on the same account, past the per-account connection cap (BackendWebsocketTransport's max_connections_per_account, evict-oldest).

Distinct from WS_CLOSE_SESSION_REVOKED: the credential is still good, so a client must not enter its revoked state — but it must not auto-reconnect either, since a reconnect would supersede a newer socket in turn and two tabs past the cap would close each other in a loop. FrontendWebsocketClient treats it as closed until the user acts (superseded). The twin of the Rust spine's WS_CLOSE_CONNECTION_LIMIT.

WS_CLOSE_CREDENTIAL_EXPIRED_REASON
#

actions/transports.ts view source

"Credential expired" import {WS_CLOSE_CREDENTIAL_EXPIRED_REASON} from '@fuzdev/fuz_app/actions/transports.js';

The close reason sent with WS_CLOSE_SESSION_REVOKED when the socket's credential expired while it was open — its session or API token passed its expiry (ResolvedAuth.credential_deadline). The code is the revocation's on purpose: the client cannot reconnect on the credential either way, so FrontendWebsocketClient enters the same revoked state. The Rust spine sends the same text.

WS_CLOSE_GOING_AWAY
#

actions/transports.ts view source

1001 import {WS_CLOSE_GOING_AWAY} from '@fuzdev/fuz_app/actions/transports.js';

WebSocket close code — the server is shutting down (RFC 6455 §7.4.1 "Going Away"). Sent to every live socket by close_all_sockets, which AppServer.close runs, and to an upgrade the shutdown refuses — one pending at the close-all, or born closed after it. Not a revocation: the credential is still good, so a client reconnects under its ordinary backoff once a server is back.

WS_CLOSE_GOING_AWAY_REASON
#

WS_CLOSE_INTERNAL_ERROR
#

actions/transports.ts view source

1011 import {WS_CLOSE_INTERNAL_ERROR} from '@fuzdev/fuz_app/actions/transports.js';

WebSocket close code — the server hit an unexpected condition (RFC 6455 §7.4.1 "Internal Error").

Sent when an upgraded socket can't be admitted because its credential re-check itself failed (a database error), when an on_socket_open hook throws, and when a frame arrives on a socket whose connection was removed from the transport without a close (a bare remove_connection). The handshake has already answered 101, so an HTTP 500 is no longer available. Not a revocation: the client's credential may be fine, so it reconnects under its ordinary backoff rather than entering its revoked state. The twin of the Rust spine's WS_CLOSE_INTERNAL_ERROR.

WS_CLOSE_MESSAGE_TOO_BIG
#

actions/transports.ts view source

1009 import {WS_CLOSE_MESSAGE_TOO_BIG} from '@fuzdev/fuz_app/actions/transports.js';

WebSocket close code — an inbound message exceeded the receiver's size cap (RFC 6455 §7.4.1 "Message Too Big").

WS_CLOSE_OUTBOUND_OVERFLOW
#

actions/transports.ts view source

4005 import {WS_CLOSE_OUTBOUND_OVERFLOW} from '@fuzdev/fuz_app/actions/transports.js';

WebSocket close code — the server had more waiting to be written to this socket than the connection's outbound budget holds (BackendWebsocketTransport's outbound_queue_bytes), because the client is reading slower than the server is sending to it, or not at all. The frame that did not fit and everything still waiting are dropped, so the close is the client's notice that it missed messages.

Distinct from WS_CLOSE_SESSION_REVOKED: the credential is still good, so a client must not enter its revoked state. Unlike WS_CLOSE_CONNECTION_LIMIT, nothing took the connection's place: FrontendWebsocketClient reconnects under its ordinary backoff, and the consumer re-reads whatever state it tracks. A client that reads nothing at all may never see the frame — a socket whose write makes no progress is dropped without one, which a browser reports as 1006. The twin of the Rust spine's WS_CLOSE_OUTBOUND_OVERFLOW.

WS_CLOSE_OUTBOUND_OVERFLOW_REASON
#

WS_CLOSE_PERMISSION_REVOKED
#

actions/transports.ts view source

4006 import {WS_CLOSE_PERMISSION_REVOKED} from '@fuzdev/fuz_app/actions/transports.js';

WebSocket close code — the socket's account lost a role its endpoint requires (register_ws_endpoint's required_roles): a global grant of one of those roles was revoked while the socket was open, or while its upgrade was in flight (the admission re-read, register_action_ws). Nothing more is sent on the socket.

Distinct from WS_CLOSE_SESSION_REVOKED: the credential is still good, so a client must not enter its revoked state — but a reconnect would be refused at the role gate, so FrontendWebsocketClient stays closed until connect() is called and rejects its pending work as forbidden. The twin of the Rust spine's WS_CLOSE_PERMISSION_REVOKED.

WS_CLOSE_PERMISSION_REVOKED_REASON
#

WS_CLOSE_POLICY_VIOLATION
#

actions/transports.ts view source

1008 import {WS_CLOSE_POLICY_VIOLATION} from '@fuzdev/fuz_app/actions/transports.js';

WebSocket close code — the server closed a socket that sent too much while it was still opening: past MAX_PRE_ADMISSION_FRAMES frames or the PRE_ADMISSION_QUEUE_BYTES_FACTOR byte budget, before its admission and on_socket_open had completed (RFC 6455 §7.4.1 "Policy Violation"). Not a revocation: the client reconnects under its ordinary backoff.

WS_CLOSE_PRE_ADMISSION_OVERFLOW_REASON
#

WS_CLOSE_SERVER_HEARTBEAT_TIMEOUT
#

actions/transports.ts view source

4003 import {WS_CLOSE_SERVER_HEARTBEAT_TIMEOUT} from '@fuzdev/fuz_app/actions/transports.js';

WebSocket close code — server timed out with no incoming activity.

WS_CLOSE_SESSION_REVOKED
#

actions/transports.ts view source

4001 import {WS_CLOSE_SESSION_REVOKED} from '@fuzdev/fuz_app/actions/transports.js';

WebSocket close code for session revocation — and for a credential that expired while the socket was open (WS_CLOSE_CREDENTIAL_EXPIRED_REASON).

Also the close for an upgrade refused at admission because its credential was revoked while the upgrade was in flight (register_action_ws) — the same outcome, reached a moment earlier.

WS_CLOSE_SESSION_REVOKED_REASON
#

actions/transports.ts view source

"Session revoked" import {WS_CLOSE_SESSION_REVOKED_REASON} from '@fuzdev/fuz_app/actions/transports.js';

The close reason sent with WS_CLOSE_SESSION_REVOKED — one definition, so a socket revoked mid-life and an upgrade refused at admission are indistinguishable on the wire. The Rust spine sends the same text.

WS_CLOSE_WRITE_TIMEOUT
#

actions/ws_outbound_writer.ts view source

1000 import {WS_CLOSE_WRITE_TIMEOUT} from '@fuzdev/fuz_app/actions/ws_outbound_writer.js';

How long a close frame may stay unwritten before the socket is cut without it (module doc, "Deadlines"). Same value as the Rust spine's bound on the same write.

WS_INBOUND_DISPATCH_OVERFLOW_MESSAGE
#

WS_WRITE_HIGH_WATER_BYTES
#

actions/ws_outbound_writer.ts view source

number import {WS_WRITE_HIGH_WATER_BYTES} from '@fuzdev/fuz_app/actions/ws_outbound_writer.js';

How many unwritten bytes a socket's buffer may hold before the pump stops handing it waiting frames. Below it the pump keeps writing, so a runtime that reports a frame as buffered until its write completes (Deno) is not held to one frame per turn; at or over it frames wait in the budgeted queue instead of the runtime's unbounded buffer.

WS_WRITE_STALL_TIMEOUT
#

actions/ws_outbound_writer.ts view source

10000 import {WS_WRITE_STALL_TIMEOUT} from '@fuzdev/fuz_app/actions/ws_outbound_writer.js';

How long bytes handed to a socket may stay unwritten in its buffer before the connection is dropped (module doc, "Deadlines"). Equal to DEFAULT_PEER_REQUEST_TIMEOUT: a server-initiated request that cannot even be written within its own reply deadline has already failed. Same value as the Rust spine's WS_WRITE_STALL_TIMEOUT.

The deadline covers the bytes in the buffer when it starts, not progress: a client taking them too slowly to finish inside it is dropped the same way, and reconnects.

WsActionRateLimitCrossTestOptions
#

testing/cross_backend/ws_action_rate_limit.ts view source

WsActionRateLimitCrossTestOptions import type {WsActionRateLimitCrossTestOptions} from '@fuzdev/fuz_app/testing/cross_backend/ws_action_rate_limit.js';

setup_test

Per-test fixture producer (cross-process only — see the module doc).

type (): Promise<TestFixtureBase>

readonly
returns Promise<TestFixtureBase>

base_url

Base URL the backend is reachable at (e.g. http://localhost:1177).

type string

readonly

ws_path

WebSocket endpoint path (e.g. /api/ws).

type string

readonly

rpc_path?

JSON-RPC endpoint path. Default /api/rpc (the spine convention).

type string

readonly

max_attempts

The max_attempts both action limiters were started with (action_rate_limit_max_attempts on the backend config). At least 2 — the shared-bucket case splits the budget across the two transports.

type number

readonly

WsClient
#

testing/transports/ws_client.ts view source

WsClient import type {WsClient} from '@fuzdev/fuz_app/testing/transports/ws_client.js';

A test WS client: send requests, inspect / await incoming messages.

send

Send a JSON-RPC message (request or notification) to the server.

type (message: unknown): Promise<void>

message

type unknown
returns Promise<void>

throws

  • Error - if called after `close()` resolves — every impl rejects sends on a closed socket so post-close test bugs surface immediately rather than silently dropping.

request

Send a JSON-RPC request and await its response. Resolves with the result; throws with a useful message (code, text, and any data payload) on an error frame — without this, asserting on result.foo for a failed request throws Cannot read property 'foo' of undefined, hiding the real cause. Use send + wait_for(is_response_for(id)) directly when the test needs to assert on the error frame itself.

type <R = unknown>(id: string | number, method: string, params: unknown, timeout_ms?: number | undefined): Promise<R>

id

type number | string

method

type string

params

type unknown

timeout_ms?

type number
optional
returns Promise<R>

throws

  • Error - if the server returns a JSON-RPC error frame for `id`, or if `wait_for` times out before a matching response arrives.

close

Close the connection. Returns a promise that resolves once the transport's own cleanup (and any on_socket_close for the in-process driver) has completed — tests that assert on post-close state should await.

type (code?: number | undefined, reason?: string | undefined): Promise<void>

code?

type number
optional

reason?

type string
optional
returns Promise<void>

wait_for_close

Wait for the server to close the connection. Resolves true if the socket closed within timeout_ms, false on timeout. The signal for server-initiated close — used by close-on-revoke tests that fire a revocation over a side channel and assert the live socket drops.

Resolves true immediately when the socket is already closed. Distinct from close() (client-initiated): this awaits a close the test did not request. Mirrors wait_for_close on the SSE frame reader in testing/sse_round_trip.ts.

type (timeout_ms?: number | undefined): Promise<boolean>

timeout_ms?

type number
optional
returns Promise<boolean>

close_code

The close code of the connection once it has closed — the server's (e.g. WS_CLOSE_MESSAGE_TOO_BIG), the test's own, or 1006 when the connection dropped without a close frame. null while open.

type number | null

readonly

close_reason

The close reason of the connection once it has closed — the text sent with close_code (e.g. 'connection limit' with WS_CLOSE_CONNECTION_LIMIT), empty when the close carried none. null while open.

type string | null

readonly

messages

Every message the server has sent, in arrival order.

type readonly unknown[]

readonly

wait_for

Wait until a message satisfies predicate. Matches are checked against already-received messages first, then new arrivals until the timeout (defaults to WS_CLIENT_DEFAULT_TIMEOUT_MS).

When predicate is a type guard (e.g. is_notification_with<P>), the result is narrowed automatically and callers don't need to spell <JsonrpcNotificationFrame<P>> on the call site.

type <T>(predicate: (msg: unknown) => msg is T, timeout_ms?: number | undefined): Promise<T>

predicate

type (msg: unknown) => msg is T

timeout_ms?

type number
optional
returns Promise<T>

throws

  • Error - if `timeout_ms` elapses before a matching message arrives — the pending waiter is dropped from the internal list so later messages don't keep iterating it.

WsConnectIdentity
#

testing/ws_round_trip.ts view source

WsConnectIdentity import type {WsConnectIdentity} from '@fuzdev/fuz_app/testing/ws_round_trip.js';

Auth identity for a mock connection.

account_id?

Account id for the connection. Defaults to a fresh uuid per call.

type Uuid

credential_type?

Credential type. Defaults to 'session'. Keeper actions require 'daemon_token'.

type CredentialType

session_id?

Session id (any string). Defaults to a fresh uuid. The connection is registered under hash_session_token(session_id), the hash the request context middleware would have resolved.

type string

api_token_id?

Api token id; set for bearer connections, null otherwise.

type string | null

roles?

Roles to grant via active role_grants. Pass [ROLE_KEEPER] for keeper actions.

type string[]

WsConnectionCapCrossTestOptions
#

testing/cross_backend/ws_connection_cap.ts view source

WsConnectionCapCrossTestOptions import type {WsConnectionCapCrossTestOptions} from '@fuzdev/fuz_app/testing/cross_backend/ws_connection_cap.js';

Options for the WebSocket connection-cap parity suite.

setup_test

Per-test fixture producer (default_cross_process_setup(handle)). Every socket in a case upgrades with the fresh-per-test keeper's session cookies, so they all land on one account.

type (): Promise<TestFixtureBase>

readonly
returns Promise<TestFixtureBase>

capabilities

Backend capability flags; every case gates on capabilities.ws.

type BackendCapabilities

readonly

base_url

Base URL the backend is reachable at (e.g. http://localhost:1178).

type string

readonly

ws_path

WebSocket endpoint path on the backend (e.g. /api/ws).

type string

readonly

origin?

Origin for the upgrades. Defaults to base_url.

type string

readonly

max_connections_per_account?

The backend's per-account connection cap, which each case opens past. Defaults to DEFAULT_MAX_CONNECTIONS_PER_ACCOUNT, the cap both spines apply unless overridden; pass the consumer's value when it sets one, or null (a backend with the cap disabled) to skip the cases.

type number | null

readonly

WsEndpointSpec
#

actions/ws_endpoint_spec.ts view source

WsEndpointSpec import type {WsEndpointSpec} from '@fuzdev/fuz_app/actions/ws_endpoint_spec.js';

Declarative description of a WebSocket endpoint to be auto-mounted by create_app_server.

Single source of truth for mount + surface — the same array drives register_ws_endpoint-style upgrade wiring AND the surface.ws_endpoints slot emitted into AppSurface, so consumers cannot drift their declared actions from what dispatch actually serves.

path

Hono mount path (e.g. /api/ws).

type string

allowed_origins?

Origin allowlist regexes for the upgrade — passed straight to verify_request_source. Defaults to the server's AppServerOptions.allowed_origins, the list the HTTP routes check. Set it only to narrow one endpoint: the auth middleware's origin check runs on the upgrade too, so a request reaches the endpoint only when its origin passes both lists.

type readonly RegExp[]

actions

The actions registered on this endpoint. Spread protocol_actions from actions/protocol.ts first to complete the disconnect-detection + per-request cancel pairing with the frontend client.

type readonly Action<ActionSpecUnion>[]

required_roles?

Roles permitted to upgrade — any-of disjunction, global grants only. Omit (or pass []) to skip the upgrade-time role gate; per-action auth on each spec still applies at dispatch time via perform_action. Pass [ROLE_ADMIN] for an admin-only WS endpoint. Held for the socket's life — see register_ws_endpoint's required_roles.

type readonly string[]

transport?

Existing transport to register connections with. Auto-created when omitted. Either way the mounted transport is reachable on AppServer.ws_endpoints[path] for broadcast / fan-out.

type BackendWebsocketTransport

max_connections_per_account?

Per-account connection cap for the auto-created transport. See RegisterActionWsOptions.max_connections_per_account — rejected alongside transport, which carries its own cap.

type number | null

default DEFAULT_MAX_CONNECTIONS_PER_ACCOUNT

outbound_queue_bytes?

Byte budget of the frames waiting to be written to one connection, for the auto-created transport. See RegisterActionWsOptions.outbound_queue_bytes — rejected alongside transport, which carries its own budget.

type number

default DEFAULT_WS_OUTBOUND_QUEUE_BYTES

heartbeat?

Server-side heartbeat policy. Default-on (60s receive-silence timeout). Set false only when an upstream stack (TCP keepalive, Cloudflare idle timeout) already owns disconnect detection.

type boolean | ServerHeartbeatOptions

artificial_delay?

Optional per-message delay for testing loading states.

type number

max_message_bytes?

Cap on one inbound message, in bytes. See RegisterActionWsOptions.max_message_bytes.

type number

default DEFAULT_WS_MAX_MESSAGE_BYTES

on_socket_open?

Called once per socket after the connection is admitted — registered and its credential re-checked — and before the first message dispatches. See RegisterActionWsOptions.on_socket_open.

type (ctx: SocketOpenContext): void | Promise<void>

ctx

returns void | Promise<void>

on_socket_close?

Called once per admitted socket on close, after transport.remove_connection. See RegisterActionWsOptions.on_socket_close.

type (ctx: SocketCloseContext): void | Promise<void>

ctx

returns void | Promise<void>

auth_guard?

Default true — registers create_ws_auth_guard against this endpoint's transport via deps.audit.add_listener, so a revocation's audit row closes its sockets. Wiring is deduped by transport reference identity (WeakSet<BackendWebsocketTransport>), so two WsEndpointSpecs sharing the exact same instance get a single listener.

The guard repeats what the revocation handlers already do: the transport is always in deps.connection_closer, whatever this flag says, and the handlers close through it after their commit. Turning the guard off leaves those closes in place.

Shared-transport OR-semantics. When multiple WsEndpointSpecs share one transport, the guard is wired iff any of those specs has auth_guard !== false. To opt out for a shared transport, every sibling spec must pass auth_guard: false. The default is "fail safe" — easier to enable than disable, and predictable regardless of spec order.

Reference-identity dedupe means wrapped or proxied transports dedupe as separate entries — a consumer threading every transport through a tracing / DI / metrics shim will get a fresh listener per shimmed reference, even when the underlying transport is the same. If you wrap or proxy, set `auth_guard: false on the duplicate WsEndpointSpec`s and compose create_ws_auth_guard against the underlying transport once.

Set false when a consumer needs to compose their own callback from scratch — or to opt out of the auto-wiring entirely.

The guard is also the one closer of a role revocation: a revoked global grant of a role in required_roles closes the account's sockets on this endpoint with WS_CLOSE_PERMISSION_REVOKED. With the guard off for a transport, nothing closes them.

type boolean

extra_audit_handlers?

Extra audit-event handlers registered via deps.audit.add_listener AFTER the standard auth_guard wiring (when enabled). By the time these run, the standard guard may have already closed sockets. Use for custom analytics, a consumer's own revocation events, etc.

Never deduped — consumer-owned; pass the same handler twice and it fires twice.

type readonly AuditEventHandler[]

WsInboundDispatchCrossTestOptions
#

testing/cross_backend/ws_inbound_dispatch.ts view source

WsInboundDispatchCrossTestOptions import type {WsInboundDispatchCrossTestOptions} from '@fuzdev/fuz_app/testing/cross_backend/ws_inbound_dispatch.js';

setup_test

Per-test fixture producer (default_cross_process_setup(handle)).

type (): Promise<TestFixtureBase>

readonly
returns Promise<TestFixtureBase>

capabilities

Backend capability flags; every case gates on capabilities.peer_request.

type BackendCapabilities

readonly

base_url

Base URL the backend is reachable at (e.g. http://localhost:1177).

type string

readonly

ws_path

WebSocket endpoint path on the backend (e.g. /api/ws).

type string

readonly

WsOutboundBacklog
#

actions/ws_outbound_writer.ts view source

WsOutboundBacklog

type "flush" | "discard"

import type {WsOutboundBacklog} from '@fuzdev/fuz_app/actions/ws_outbound_writer.js';

What WsOutboundWriter.close does with the frames still waiting.

WsOutboundOverflowCrossTestOptions
#

testing/cross_backend/ws_outbound_overflow.ts view source

WsOutboundOverflowCrossTestOptions import type {WsOutboundOverflowCrossTestOptions} from '@fuzdev/fuz_app/testing/cross_backend/ws_outbound_overflow.js';

Options for the outbound-overflow parity suite.

setup_test

Per-test fixture producer (default_cross_process_setup(handle)).

type (): Promise<TestFixtureBase>

readonly
returns Promise<TestFixtureBase>

capabilities

Backend capability flags; every WebSocket case gates on capabilities.ws.

type BackendCapabilities

readonly

base_url

Base URL the backend is reachable at (e.g. http://localhost:1178).

type string

readonly

ws_path

WebSocket endpoint path on the backend (e.g. /api/ws).

type string

readonly

origin?

Origin for the session socket's upgrade. Defaults to base_url.

type string

readonly

rpc_path?

The backend's RPC endpoint path, for the over-HTTP refusal. Omitted, that case is skipped.

type string

readonly

WsOutboundPush
#

actions/ws_outbound_writer.ts view source

WsOutboundPush

type { kind: "accepted"; } | { kind: "refused"; } | { kind: "overflow"; overflow: OutboundOverflow; }

import type {WsOutboundPush} from '@fuzdev/fuz_app/actions/ws_outbound_writer.js';

The outcome of one WsOutboundWriter.push.

WsOutboundStall
#

actions/ws_outbound_writer.ts view source

WsOutboundStall import type {WsOutboundStall} from '@fuzdev/fuz_app/actions/ws_outbound_writer.js';

What a stalled connection held when its write deadline passed.

queued_bytes

Payload bytes that were waiting, now discarded.

type number

queued_frames

Frames that were waiting, now discarded.

type number

buffered_bytes

Bytes the socket's buffer still held.

type number

WsOutboundWriter
#

actions/ws_outbound_writer.ts view source

import {WsOutboundWriter} from '@fuzdev/fuz_app/actions/ws_outbound_writer.js';

The byte-budgeted outbound queue and write pump of one WebSocket connection (module doc).

constructor

type new (options: WsOutboundWriterOptions): WsOutboundWriter

options

push

Send text as one frame: straight to the socket when the connection is idle, else queued behind what waits when it fits the budget (module doc, "What waits"). Never waits and never throws.

type (text: string, bytes?: number | undefined): WsOutboundPush

text

the frame's text

type string

bytes?

the UTF-8 length of text when the caller has measured it — a fan-out measures one frame once for its recipients; measured here when omitted. Trusted, not checked against text: a wrong value corrupts the budget

type number
optional

accepted, refused once the writer has closed, or overflow when the frame did not fit — the caller closes the connection

mutates

  • this — queues the frame or writes it to the socket, and schedules the pump

close

Close the socket with code and reason, and take nothing more.

With backlog: 'flush' the frames still waiting are handed to the socket ahead of the close frame under the pump's rule — while the socket's buffer is below WS_WRITE_HIGH_WATER_BYTES — so a reply sent to an otherwise idle connection just before a close is not lost to it. What still waits after that is dropped, as all of it is with 'discard' (module doc, "Closing"). Either way the close frame gets WS_CLOSE_WRITE_TIMEOUT to be written, after which a socket still holding unwritten bytes is cut where the runtime can cut one. A no-op once the writer has closed.

type (code: number, reason: string, backlog: WsOutboundBacklog, on_settled?: (() => void) | undefined): void

code

the close code

type number

reason

the close reason

type string

backlog

what to do with the frames still waiting

on_settled?

called once the close deadline has passed, unless dispose ran first

type () => void
optional
returns void

throws

  • whatever - the socket's `close` throws, with the writer already closed

mutates

  • this — empties the queue, stops the pump, starts the close deadline

dispose

The socket is gone: drop what waits and stop every timer. Idempotent.

type (): void

returns void

mutates

  • this — empties the queue and clears the pump and deadline timers

queued_bytes

Payload bytes waiting for the socket, without the per-frame overhead.

type number

getter

queued_frames

Frames waiting for the socket.

type number

getter

WsOutboundWriterOptions
#

actions/ws_outbound_writer.ts view source

WsOutboundWriterOptions import type {WsOutboundWriterOptions} from '@fuzdev/fuz_app/actions/ws_outbound_writer.js';

Options for WsOutboundWriter.

ws

The socket to write to.

type WSContext<unknown>

max_bytes

The byte budget of the frames waiting for the socket.

type number

on_stall

Called once when a write deadline passes, after the writer discarded what was waiting and before it cuts the socket. The owner removes the connection and logs.

type (stall: WsOutboundStall): void

stall

returns void

log

Logger for a socket that throws on a send or a cut; null silences it.

type Logger | null

WsRequestResponder
#

testing/transports/ws_client.ts view source

WsRequestResponder import type {WsRequestResponder} from '@fuzdev/fuz_app/testing/transports/ws_client.js';

Answers a server-initiated request (the server→client direction ActionPeer adds). Passed at client construction so it's attached before the upgrade completes — otherwise it races a connect-time ping. Receives the parsed request frame and returns the reply outcome (or a promise of it).

(call)

type (request: { [x: string]: unknown; jsonrpc: "2.0"; id: string | number; method: string; params?: { [x: string]: unknown; } | undefined; }): WsResponderOutcome | Promise<WsResponderOutcome>

request

WsResponderOutcome
#

testing/transports/ws_client.ts view source

WsResponderOutcome

type { result: unknown; } | { error: { code: number; message: string; data?: unknown; }; } | undefined

import type {WsResponderOutcome} from '@fuzdev/fuz_app/testing/transports/ws_client.js';

What a WsRequestResponder returns for a server-initiated request:

  • {result} → reply with a JSON-RPC success
  • {error} → reply with a JSON-RPC error envelope
  • undefined → send nothing (models a never-replying peer — exercises the server-side Timeout)

WsRoleGatedCrossTestOptions
#

testing/cross_backend/ws_role_gated.ts view source

WsRoleGatedCrossTestOptions import type {WsRoleGatedCrossTestOptions} from '@fuzdev/fuz_app/testing/cross_backend/ws_role_gated.js';

setup_test

Per-test fixture producer (default_cross_process_setup(handle)).

type (): Promise<TestFixtureBase>

readonly
returns Promise<TestFixtureBase>

capabilities

Backend capability flags; every case gates on ws and ws_role_gated.

type BackendCapabilities

readonly

base_url

Base URL the backend is reachable at.

type string

readonly

ws_path

The ungated WebSocket endpoint path (e.g. /api/ws).

type string

readonly

gated_ws_path

The role-gated WebSocket endpoint path.

type string

readonly

gated_roles

The roles gated_ws_path requires: ROLE_ADMIN and scoped_role among them.

type readonly string[]

readonly

scoped_role

A role in gated_roles an admin can grant both globally and scoped.

type string

readonly

rpc_path?

JSON-RPC endpoint path. Default SPINE_RPC_PATH.

type string

readonly

WsTestHarness
#

testing/ws_round_trip.ts view source

WsTestHarness import type {WsTestHarness} from '@fuzdev/fuz_app/testing/ws_round_trip.js';

A harness instance — transport handle + connection factory.

transport

type BackendWebsocketTransport

connect

Open a mock connection. Resolves after on_socket_open (and the transport's register_ws) completes, so broadcasts issued immediately after the await reach the connection.

Returns the shared WsClient interface — same surface the cross-process driver in transports/ws_transport.ts implements, so assertion helpers and suite bodies work against either impl.

type (identity?: WsConnectIdentity | undefined): Promise<WsClient>

identity?

optional
returns Promise<WsClient>

WsTransportOptions
#

testing/transports/ws_transport.ts view source

WsTransportOptions import type {WsTransportOptions} from '@fuzdev/fuz_app/testing/transports/ws_transport.js';

Construction options for create_ws_transport.

base_url

Base URL the binary is reachable at — e.g. http://localhost:8788. Converted to ws:// for the upgrade.

type string

readonly

ws_path

WebSocket endpoint path on the binary (e.g. /api/ws).

type string

readonly

cookies

Session cookie values (full Set-Cookie strings as FetchTransport.cookies() returns them) threaded onto the upgrade Cookie header. Without these the upgrade is anonymous and per-action auth fails on the first message.

type readonly string[]

readonly

origin?

Origin header for the upgrade. Backends running with ALLOWED_ORIGINS=http://localhost:* accept http://localhost:<port>. Defaults to base_url — acceptable because cross-process tests always run against localhost. null sends no Origin at all, which a daemon-token upgrade needs: both spines discard a daemon token (and a bearer token) presented in a browser context.

type string | null

readonly

default_timeout_ms?

Optional per-call default for wait_for timeouts. Falls back to WS_CLIENT_DEFAULT_TIMEOUT_MS if omitted.

type number

readonly

on_request?

Optional responder for server-initiated requests (the server→client direction ActionPeer adds). Attached before the upgrade so a connect-time or on-demand peer/ping is answered as soon as it arrives. Omit for the default observe-only client — a server-initiated request is then surfaced as a normal message (today's behavior).

type (request: { [x: string]: unknown; jsonrpc: "2.0"; id: string | number; method: string; params?: { [x: string]: unknown; } | undefined; }): WsResponderOutcome | Promise<WsResponderOutcome>

readonly

request

headers?

Extra headers for the upgrade request — e.g. an X-Forwarded-For that a backend trusting the loopback proxy resolves as the connection's client IP, which keys the per-IP action rate limiter for every message on the socket. cookies and origin set their own headers.

type Readonly<Record<string, string>>

readonly

WsWaiter
#

testing/transports/ws_client.ts view source

WsWaiter import type {WsWaiter} from '@fuzdev/fuz_app/testing/transports/ws_client.js';

A pending wait_for entry — predicate + its resolver.

predicate

type (msg: unknown): boolean

msg

type unknown
returns boolean

resolve

type (msg: unknown): void

msg

type unknown
returns void

XAccelConfig
#

server/x_accel.ts view source

value + type

{ redirect_prefix: string; } & $brand<"XAccelConfig">

type { redirect_prefix: string; } & $brand<"XAccelConfig">

import {XAccelConfig} from '@fuzdev/fuz_app/server/x_accel.js';

A validated X-Accel redirect configuration — the only handle that enables the X-Accel-Redirect serving path in server/serve_fact_route.ts.

The redirect prefix can be obtained only by passing the nginx config through validate_facts_internal_location (via create_x_accel_config), so X-Accel serving is impossible to enable without proving the facts location is internal; at boot — a public facts location would bypass every cell-visibility check. A Zod-branded type: the brand can't be forged without an explicit cast, so the factory is the only ordinary construction path.

redirect_prefix

The validated redirect prefix the serving path prepends (e.g. /_facts/).

type string

XAccelConfigError
#

server/x_accel.ts view source

import {XAccelConfigError} from '@fuzdev/fuz_app/server/x_accel.js';

A misconfigured X-Accel facts location — a fail-loud boot error thrown by create_x_accel_config.

inheritance

extends: Error

errors

The validator errors that made the location unsafe.

type string[]

readonly

constructor

type new (errors: string[]): XAccelConfigError

errors

type string[]

xfail_until
#

testing/cross_backend/xfail.ts view source

(tracking_id: string, reason: string, name: string, fn: () => void | Promise<void>): void import {xfail_until} from '@fuzdev/fuz_app/testing/cross_backend/xfail.js';

Register fn as an expected-failure test. Passes while fn throws / rejects; fails once fn succeeds (signalling the gap closed and the marker should be removed).

tracking_id

descriptive id of the tracked gap (e.g. 'audit-log-sse-rust-spine') — a feature/behavior name, not a process/milestone label.

type string

reason

why the case is deferred-by-design.

type string

name

the assertion / test label.

type string

fn

the test body (expected to throw/reject until the gap closes).

type () => void | Promise<void>

returns

void

zod_extract_fields
#

zod.ts view source

(schema: ZodObject<$ZodLooseShape, $strip>): ZodFieldInfo[] import {zod_extract_fields} from '@fuzdev/fuz_app/zod.js';

Extract field metadata from a Zod object schema.

schema

type ZodObject<$ZodLooseShape, $strip>

returns

ZodFieldInfo[]

zod_get_base_type
#

zod.ts view source

(schema: ZodType<unknown, unknown, $ZodTypeInternals<unknown, unknown>>): string import {zod_get_base_type} from '@fuzdev/fuz_app/zod.js';

Get the base type name for a Zod schema, unwrapping all wrappers.

schema

type ZodType<unknown, unknown, $ZodTypeInternals<unknown, unknown>>

returns

string

base type name (e.g. 'string', 'object', 'uuid')

zod_get_field_schema
#

zod.ts view source

(schema: ZodType<unknown, unknown, $ZodTypeInternals<unknown, unknown>>, key: string): ZodType<unknown, unknown, $ZodTypeInternals<unknown, unknown>> import {zod_get_field_schema} from '@fuzdev/fuz_app/zod.js';

Get the field schema for a key in an object schema.

schema

Zod schema (typically a ZodObject with possible wrappers)

type ZodType<unknown, unknown, $ZodTypeInternals<unknown, unknown>>

key

type string

returns

ZodType<unknown, unknown, $ZodTypeInternals<unknown, unknown>>

throws

  • Error - if the schema is not an object or the key is missing

zod_get_innermost_type
#

zod.ts view source

(schema: ZodType<unknown, unknown, $ZodTypeInternals<unknown, unknown>>): ZodType<unknown, unknown, $ZodTypeInternals<unknown, unknown>> import {zod_get_innermost_type} from '@fuzdev/fuz_app/zod.js';

Unwrap a schema through every wrapper type (optional, nullable, default, transform, pipe, prefault) to reach the innermost schema. Like zod_unwrap_def but returns the schema (so callers can .shape, instanceof-check, etc.) instead of the def.

schema

type ZodType<unknown, unknown, $ZodTypeInternals<unknown, unknown>>

returns

ZodType<unknown, unknown, $ZodTypeInternals<unknown, unknown>>

the innermost non-wrapper schema

zod_get_schema_keys
#

zod.ts view source

<T extends z.ZodType>(schema: T): (keyof output<T> & string)[] import {zod_get_schema_keys} from '@fuzdev/fuz_app/zod.js';

Get all property keys from an object schema, unwrapping wrappers. Returns an empty array if the innermost type is not an object.

schema

Zod schema (typically a ZodObject with possible wrappers)

type T

returns

((keyof output<T>) & string)[]

generics

zod_get_schema_keys<T extends z.ZodType>
T
constraint z.ZodType

zod_unwrap_def
#

zod.ts view source

(schema: ZodType<unknown, unknown, $ZodTypeInternals<unknown, unknown>>): $ZodTypeDef import {zod_unwrap_def} from '@fuzdev/fuz_app/zod.js';

Unwrap Zod wrappers (optional, default, nullable, pipe, transform) to get the base type definition.

schema

type ZodType<unknown, unknown, $ZodTypeInternals<unknown, unknown>>

returns

$ZodTypeDef

the innermost non-wrapper type definition

zod_unwrap_to_object
#

zod.ts view source

(schema: ZodType<unknown, unknown, $ZodTypeInternals<unknown, unknown>>): ZodObject<$ZodLooseShape, $strip> | null import {zod_unwrap_to_object} from '@fuzdev/fuz_app/zod.js';

Unwrap a schema to find the inner ZodObject, or null if not an object schema. Handles wrappers like z.strictObject({...}).default({...}).

schema

type ZodType<unknown, unknown, $ZodTypeInternals<unknown, unknown>>

returns

ZodObject<$ZodLooseShape, $strip> | null

ZodFieldInfo
#

zod.ts view source

ZodFieldInfo import type {ZodFieldInfo} from '@fuzdev/fuz_app/zod.js';

Metadata extracted from a single field of a Zod object schema.

name

type string

base_type

type string

required

type boolean

has_default

type boolean

nullable

type boolean