server/app_backend.ts

App backend types and factory — database initialization + auth migrations + deps.

Provides AppBackend, CreateAppBackendOptions, and create_app_backend().

Vocabulary:

  • AppDeps — stateless capabilities: injectable, swappable per environment
  • *Options — static values set at startup, per-factory configuration
  • Runtime state — mutable values (e.g., bootstrap_status) — NOT in deps or options
view source

Declarations
#

5 declarations

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>

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

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`

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

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

Depends on
#

Imported by
#