Skip to main content

Runtime

import * as mod from "jsr:@nzip/lofi";

Build local-first browser applications with durable storage, reactive tables, optional Jazz sync, recoverable accounts, and an installable PWA lifecycle.

Application code composes these APIs; storage, identity, sync, PWA, and lifecycle implementations remain versioned package code.

Start with defineLofiApp and bootLofi. Use LofiRuntime.store to bind declared Jazz tables to application UI, and add account backup only after managed sync is configured.

Every mutation returns a WriteHandle: await it for local durability (saved), observe synced/rejected for the store's verdict, and read the reload-safe pending set through getWriteLedger or the usePendingWrites hook. Status UI reads RuntimeDiagnostics (via getRuntimeDiagnostics), the schema gate through getSchemaCompatStatedata-ahead means this device is read-only until applyPwaUpdate lands a newer bundle — and install/update state through getPwaState.

Shared encrypted fields span three entry points: declaring an encrypted column lives in @nzip/lofi/schema, group policy and key lifecycle live in @nzip/lofi/access, and pin remediation lives here — a peer-key-changed alert in RuntimeDiagnostics.sharedFieldAlerts is resolved with trustPeerKey after out-of-band verification.

AccountReplacementError

class

class AccountReplacementError extends Error {
constructor();
readonly name: string;
readonly code: string;
}

Raised when account recovery needs explicit acknowledgement of local replacement.

acquireLiveQuery

function

function acquireLiveQuery<T extends TableRow>(query: QueryBuilder<T>): LiveQueryLease<T>

Acquires the package-wide shared store for an arbitrary typed query.

acquireTableMutations

function

function acquireTableMutations<T extends TableRow, Init>(table: TableProxy<T, Init>): TableMutationLease<T, Init>

Acquires the package-wide typed mutation surface for one table.

applyPwaUpdate

function

function applyPwaUpdate(): boolean

Activates a waiting service worker; returns false when no update is ready.

armWriteLedger

function

function armWriteLedger(): Promise<void>

Arms the package-wide ledger at boot so journaled writes reconcile and outstanding effect obligations re-run before any island mounts.

assertDurableBrowser

function

function assertDurableBrowser(): void

Throws unless the current browser can open lofi's persistent local driver.

authenticateAndDerivePrfSecret

function

async function authenticateAndDerivePrfSecret(salt: BufferSource, options: AuthenticateOptions): Promise<{ credential: DeviceCredential; secret: Uint8Array }>

Authenticates and derives the PRF secret in one user-verifying ceremony: the PRF evaluation rides the assertion's extensions, so a flow that needs both the asserted credential and a credential-bound key costs one prompt, not two. Pass credentialId to pin the ceremony to one enrolled credential. Throws prf-unavailable when the client or authenticator returns no PRF result — the secret is never faked — and credential-mismatch when a different credential asserts than the one pinned.

authenticateDeviceCredential

function

async function authenticateDeviceCredential(options: AuthenticateOptions): Promise<DeviceCredential>

Authenticates with a device passkey (user-verifying). Pass credentialId to pin the ceremony to one enrolled credential; without it the request is discoverable and any resident credential for the RP ID can assert.

AuthError

class

class AuthError extends Error {
constructor(code: AuthError["code"], message?: string);
readonly name: string;
readonly code: "cancelled" | "origin-rejected" | "unsupported" | "prf-unavailable" | "credential-missing" | "credential-mismatch" | "unknown";
}

A precise, non-leaking failure reason for a credential operation.

bootLofi

function

async function bootLofi(): Promise<void>

Starts lofi's browser lifecycle once for the current document.

Import the module that calls defineLofiApp first so the runtime can resolve the app definition. Safe to call more than once; later calls are no-ops.

Returns: A promise that resolves once boot has been scheduled for this document.

Example

import { bootLofi } from "@nzip/lofi";
// Importing app.ts registers defineLofiApp before the runtime boots.
import { app } from "../app.ts";

void app;
void bootLofi();

checkPwaUpdate

function

function checkPwaUpdate(): Promise<boolean>

Runs the shared controller's bounded update check when registration is ready. Resolves true when a check ran or was already in flight; inspect PwaState.update for outcome.

classifyCredentialOrigin

function

function classifyCredentialOrigin(url: URL, trustedOrigins: readonly string[]): CredentialOriginReport

Classifies whether url is a safe origin to enroll a device credential on, against the author's committed-stable hostnames (app.ts credentialOrigins, exact hostnames or *. suffix patterns). Guidance is platform-agnostic — the requirement is a stable HTTPS origin you control, however you serve it.

classifyMutationError

function

function classifyMutationError(code: string | null | undefined): MutationErrorClass

Classifies a sync-node rejection code. permission_denied is permanent; known delivery-condition codes are transient; anything else — including a missing code — is unknown. Compensation gates on permanent only.

clearDeclaredSink

function

function clearDeclaredSink(): void

Removes the declared sink. Existing local data and elections are untouched.

clearProvisionCapability

function

function clearProvisionCapability(): void

Forgets the held capability and removes any sealed record.

confirmPhraseAccess

function

async function confirmPhraseAccess(): Promise<void>

Runs the user-verifying passkey ceremony that must precede revealing the phrase on a guarded device. The ceremony is pinned to the enrolled credential, so no other passkey for the same RP ID can satisfy it (guards enrolled before pinning remain discoverable until re-enrolled). A no-op when the device is not guarded. Throws cancelled if the user dismisses the prompt — the phrase is then not revealed.

createBackupPasskey

function

async function createBackupPasskey(label?: string): Promise<boolean>

Enrolls a passkey to guard the recovery phrase on this device. Enrollment is a user-verifying ceremony, so it doubles as the confirmation for the reveal that immediately follows. Returns true when a guard was set; false (without throwing) when this browser or origin cannot enroll a passkey, so the caller can still show the phrase while telling the user it is unguarded. Re-throws a cancelled ceremony so the caller can abort.

createBootProgressTracker

function

function createBootProgressTracker(dependencies: BootProgressTrackerDependencies): BootProgressTracker

Creates an isolated progress tracker over injectable surfaces. The package-wide wrappers (getBootProgress, subscribeBootProgress) read the shared tracker the runtime drives; this factory exists for isolated instances — deterministic tests and custom hosts that manage their own tracker.

createDefaultJournalStorage

function

function createDefaultJournalStorage(appId: string): JournalStorage

Resolves the default journal storage for one app id: an OPFS file when the browser provides it, localStorage otherwise, and memory as a last resort, so the journal is always readable at boot before sync connects.

createMemoryJournalStorage

function

function createMemoryJournalStorage(initial: string | null): JournalStorage & { text(): string | null }

An in-memory JournalStorage for tests and storage-less runtimes.

createPwaController

function

function createPwaController(dependencies: PwaControllerDependencies): PwaController

Creates an isolated PWA controller over injectable browser surfaces. The package-wide wrappers (getPwaState, subscribePwaState, requestPwaInstall, checkPwaUpdate, applyPwaUpdate) drive the shared pwaController; this factory exists for isolated instances — deterministic tests and custom hosts that manage their own controller.

createRecoverablePasskeyBackup

function

async function createRecoverablePasskeyBackup(displayName?: string): Promise<PasskeyBackupReceipt>

Stores the current 32-byte local-first secret inside Jazz's resident, user-verifying passkey backup. Unlike createBackupPasskey, this credential can restore the account and is not merely a local phrase-reveal guard.

createSchemaCompatGate

function

function createSchemaCompatGate(dependencies: SchemaCompatGateDependencies): SchemaCompatGate

Creates an isolated compatibility gate over injectable browser surfaces. The package-wide wrappers (getSchemaCompatState, subscribeSchemaCompat, assertSchemaWritable) all read the shared gate that boot starts; this factory exists for isolated instances — deterministic tests and custom hosts that manage their own gate.

createStorageForkGuard

function

function createStorageForkGuard(dependencies: StorageForkGuardDependencies): StorageForkGuard

Creates an isolated storage-fork guard over injectable browser surfaces. The package-wide wrappers (getStorageForkState, subscribeStorageFork, dismissStorageFork) read the shared storageForkGuard that boot arms; this factory exists for isolated instances — deterministic tests and custom hosts that manage their own guard.

DataSinkError

class

class DataSinkError extends Error {
constructor(code: "invalid-ticket" | "invalid-server-url" | "app-id-mismatch" | "sink-already-declared", message: string);
readonly name: string;
readonly code: "invalid-ticket" | "invalid-server-url" | "app-id-mismatch" | "sink-already-declared";
}

Raised when a sink declaration or sync ticket is rejected.

declareDataSink

function

async function declareDataSink(declaration: DataSinkDeclaration, keyStore: DeviceKeyStore): Promise<DataSinkDeclaration>

Declares the sync location for this device. Validates the URL, refuses an app id that contradicts a compiled-in managed app, and refuses to replace a different declared sink (one store, one active sink — clear the existing declaration first; switching stores is deliberately not a silent overwrite). Re-declaring the same store updates the label. The declaration persists as a sealed envelope; in a context that cannot store it (private mode) it lasts for this document only. Declaration alone changes no runtime state; electing sync (see session.ts) is what connects.

decryptAtRest

function

async function decryptAtRest(key: CryptoKey, blob: { iv: Uint8Array; ciphertext: Uint8Array }): Promise<Uint8Array>

Decrypts a blob produced by encryptAtRest.

defineLofiApp

function

function defineLofiApp<Schema>(config: LofiAppConfig<Schema>): LofiAppConfig<Schema>

Defines the application-facing values the versioned runtime needs.

The returned value retains the exact schema type so author code can select tables without importing or editing framework internals. Calling it also registers the configuration globally so runtime entry points (bootLofi, getRuntime) can resolve it — import the defining module before booting.

ParameterDescription
configThe author-owned app configuration, including the raw Jazz schema.

Returns: The same configuration, typed to the exact schema for table selection.

Example

import { defineLofiApp } from "@nzip/lofi";
import { app as schema } from "./schema.ts";

export const app = defineLofiApp({
name: "my-app",
databaseName: "my-app",
schema,
storage: "durable",
sync: { adapter: "jazz" },
});

deriveAtRestKey

function

async function deriveAtRestKey(prfSecret: Uint8Array, info: string, salt: Uint8Array): Promise<CryptoKey>

Turns a PRF secret into an AES-GCM key for at-rest encryption via HKDF-SHA-256. Bind unrelated data to different keys by varying info.

derivePrfSecret

function

async function derivePrfSecret(salt: BufferSource, dependencies: AuthDependencies): Promise<Uint8Array>

Derives a credential-bound secret from the passkey via the WebAuthn PRF extension in one user-verifying get(). The same salt on the same authenticator yields the same 32-byte secret; it never leaves the device. Throws prf-unavailable if the client or authenticator does not return a PRF result — the secret is never faked.

describeSchemaCompat

function

function describeSchemaCompat(state: SchemaCompatState): string

One-line label for diagnostic surfaces (development inspector, DeviceStatus).

describeStorageFork

function

function describeStorageFork(state: StorageForkState): string

One-line label for diagnostic surfaces (development inspector, DeviceStatus).

describeStoreStatus

function

function describeStoreStatus(status: RuntimeStoreStatus): string

One-line label for diagnostic surfaces (development inspector, DeviceStatus).

dismissAllNotices

function

function dismissAllNotices(): void

Dismisses every durable notice.

dismissNotice

function

function dismissNotice(id: string): void

Dismisses one durable notice by id.

dismissStorageFork

function

function dismissStorageFork(): void

Acknowledges a detected fork on the shared guard.

durableCapabilityReport

function

function durableCapabilityReport(): DurableCapabilityReport

Reads synchronous browser capabilities needed by the durable Jazz driver.

DurableStorageUnsupportedError

class

class DurableStorageUnsupportedError extends Error {
constructor(report: DurableCapabilityReport);
name: string;
readonly report: DurableCapabilityReport;
}

Raised when the browser cannot provide the durable local-storage contract.

enableSyncBackup

function

async function enableSyncBackup(): Promise<Session>

Elects to back up and sync this account: replication to the managed Jazz app turns on and the existing local data pushes up under the same identity. Pair it with revealRecoveryPhrase so the user has a way back in. No-op when no Jazz app is configured.

In browsers with SharedWorker support, first-time election reloads the page to reopen the runtime on the elected namespace; the returned promise never settles there, and the app resumes after reload. Do not put follow-up UI behind the await — read the post-reload session state instead.

Throws SyncOwnerError when sync on this device was elected by a different account: stop sync (releasing the election) or restore the owning account first. Electing under an unclaimed election records this account as the owner.

encryptAtRest

function

async function encryptAtRest(key: CryptoKey, plaintext: Uint8Array): Promise<{ iv: Uint8Array; ciphertext: Uint8Array }>

Encrypts plaintext with an at-rest key, returning a self-contained blob.

enrollDeviceCredential

function

async function enrollDeviceCredential(options: EnrollOptions): Promise<DeviceCredential>

Enrolls a resident, user-verifying device passkey with the PRF extension enabled. Refuses with an AuthError("origin-rejected") unless the current origin is stable or a loopback local-only host (pass rpId to test). The returned portable flag says whether the credential roams across devices; label names it in the user's password manager. Returns the opaque id.

enrollSyncTicket

function

async function enrollSyncTicket(ticket: string, options: EnrollSyncTicketOptions): Promise<Session>

Enrolls a lofisync1. app-connect ticket as this device's sync location and elects to back up and sync in one step: the ticket's URL becomes the sync server, the local data pushes up under the same identity, and the session reflects the declared sink. Throws DataSinkError (see data-sink.ts) for malformed tickets, ws URLs, an app id that contradicts a compiled-in managed app, or a different sink already declared on this device.

A provision-scoped ticket is split before anything persists: the node's scope-down exchange mints a derived sync ticket, that becomes the declared sink, and the provision capability is only held in memory (see provision.ts — sealing it behind a passkey is a separate, explicit ceremony). The exchange is offered this device's public key (see pop.ts), and when the node binds the derived ticket to it, connecting thereafter requires the proof-of-possession exchange — the enrolled credential stops being a pure bearer string. Against a node without the exchange, the ticket enrolls as pasted, exactly as before.

Before electing, the node's store answers a bounded metadata preflight and that answer decides whether enrollment is kept: a store with no schema for this app or a rejected ticket rolls the declaration back and throws SyncEnrollmentError — the device stays exactly as it was. A store that is merely unreachable enrolls anyway, with the warning recorded in runtime diagnostics (storeStatus). SyncOwnerError propagates from the election when sync on this device belongs to a different account.

Enrollment ends by electing sync, so the enableSyncBackup reload caveat applies: on SharedWorker-capable browsers the page reloads and the returned promise never settles.

EnvelopeError

class

class EnvelopeError extends Error {
constructor(code: "locked" | "corrupt", message: string);
readonly name: string;
readonly code: "locked" | "corrupt";
}

Raised when an envelope cannot be opened or is not a valid envelope.

getAuthCapability

function

async function getAuthCapability(dependencies: AuthDependencies): Promise<AuthCapability>

Reports what the current device/browser/origin can do for credential auth.

getBootProgress

function

function getBootProgress(): BootProgress

Returns the current first-load progress snapshot.

getNoticeQueue

function

function getNoticeQueue(): NoticeQueue

The package-wide durable notice queue behind s.notice, created lazily and kept in sync with the activeNotices diagnostic. Public read/subscribe/ dismiss surfaces (listNotices, subscribeNotices, dismissNotice) are re-exported from the runtime entry.

getPwaState

function

function getPwaState(): PwaState

Returns the shared controller's current state snapshot.

getRuntime

function

async function getRuntime(): Promise<LofiRuntime>

Opens or reuses the one package runtime for the current browser document.

getRuntimeDiagnostics

function

function getRuntimeDiagnostics(): RuntimeDiagnostics

Returns a value-only snapshot of runtime resource and durability counters.

getRuntimePrincipal

function

function getRuntimePrincipal(): string | null

The stable Jazz principal currently opened by the package runtime.

getSchemaCompatState

function

function getSchemaCompatState(): SchemaCompatState

Returns the shared gate's current compatibility state snapshot.

getStorageForkState

function

function getStorageForkState(): StorageForkState

Returns the shared guard's current fork state snapshot.

getWriteLedger

function

function getWriteLedger(): WriteLedger

The package-wide ledger for the current document, created and armed lazily.

heldProvisionCapability

function

function heldProvisionCapability(): string | null

The capability held in this document, or null (sealed-only or absent).

holdProvisionCapability

function

function holdProvisionCapability(url: string): void

Holds the provision bearer URL in memory for this document only. This is the no-ceremony path enrollment uses after a scope-down exchange, and the whole custody story on devices that cannot seal: nothing reaches storage.

isAccountReplacementError

function

function isAccountReplacementError(error: unknown): error is AccountReplacementError

True when recovery requires explicit confirmation before replacing a local account.

isAuthError

function

function isAuthError(error: unknown): error is AuthError

True when an error came from a passkey ceremony (enroll / confirm).

isDataSinkError

function

function isDataSinkError(error: unknown): error is DataSinkError

True when an error is a data-sink or ticket problem the user can fix and retry.

isDurableStorageUnsupportedError

function

function isDurableStorageUnsupportedError(error: unknown): error is DurableStorageUnsupportedError

True when an error is a browser unable to provide the durable-storage contract.

isEnvelopeError

function

function isEnvelopeError(error: unknown): error is EnvelopeError

True when an error is an envelope failure: locked (no available protector opens the record) or corrupt (the record failed authentication).

isLofiConfigurationError

function

function isLofiConfigurationError(error: unknown): error is LofiConfigurationError

True when an error is invalid or missing author-owned app configuration.

isRecoverablePasskeyError

function

function isRecoverablePasskeyError(error: unknown): error is RecoverablePasskeyError

True when an error came from a recoverable passkey backup or restore ceremony.

isRecoveryError

function

function isRecoveryError(error: unknown): error is RecoveryError

True when an error is a recovery-phrase problem the user can fix and retry.

isRuntimeStartupError

function

function isRuntimeStartupError(error: unknown): error is RuntimeStartupError

True when an error is a classified persistent-runtime startup failure.

isSchemaCompatibilityError

function

function isSchemaCompatibilityError(error: unknown): error is SchemaCompatibilityError

True when an error is a write refused because local data is ahead of this code.

isSyncEnrollmentError

function

function isSyncEnrollmentError(error: unknown): error is SyncEnrollmentError

True when an error is a refused ticket enrollment (see SyncEnrollmentError).

isSyncOwnerError

function

function isSyncOwnerError(error: unknown): error is SyncOwnerError

True when an error is the sync-owner refusal (see SyncOwnerError).

isWriteRejectedError

function

function isWriteRejectedError(error: unknown): error is WriteRejectedError

True when an error is a write the store adjudicated and denied permanently.

journalIdFor

function

function journalIdFor(writeId: string, effectName: string): string

The composed idempotency key for one (write id, effect name) obligation.

listNotices

function

function listNotices(): readonly NoticeEntry[]

The live durable notices for the current document.

LiveQueryStore

class

class LiveQueryStore<T extends TableRow> {
constructor(query: QueryBuilder<T>, environment: LiveQueryEnvironment, onIdle: () => void);
getSnapshot: () => LiveQuerySnapshot<T>;
subscribe: (listener: () => void) => () => void;
get consumerCount(): number;
dispose(): void;
}

A shared framework-neutral reactive store for one typed Jazz query.

lockProvisionCapability

function

function lockProvisionCapability(): void

Forgets the in-memory capability while keeping any sealed record — the lock half of the unlock ceremony, for callers that drop admin capability after an operation rather than holding it for the document's lifetime.

LofiConfigurationError

class

class LofiConfigurationError extends Error {
readonly name: string;
readonly code: string;
}

Raised when package runtime startup cannot resolve valid author-owned app configuration.

NoticeQueue

class

class NoticeQueue {
constructor(storage: JournalStorage, now: () => number, onCountChange?: (count: number) => void);
load(): Promise<void>;
enqueue(input: NoticeEnqueueInput): Promise<void>;
dismiss(id: string): void;
dismissAll(): void;
list(): readonly NoticeEntry[];
subscribe(listener: () => void): () => void;
flush(): Promise<void>;
sweep(): void;
}

The single reader and writer of the durable notice queue: an in-memory document with coalesced persistence, a live-notice snapshot, and change notification. Retirement (dismissal, TTL) is applied lazily on every read and on a periodic sweep, so a queue loaded at boot never surfaces a message whose window already closed.

parseSyncTicket

function

function parseSyncTicket(text: string): SyncTicket | null

Parses a pasted or scanned app-connect ticket. Returns null on any malformed input — paste paths never throw. The ticket URL is a bearer credential: hold it only as long as enrollment needs it.

pinnedFingerprint

function

function pinnedFingerprint(appId: string, userId: string): string | undefined

The pinned fingerprint for a peer, if this device has seen one.

provisionCapabilityStatus

function

function provisionCapabilityStatus(): ProvisionCapabilityStatus

Reports what provision capability exists on this device.

pwaController

const

const pwaController: PwaController;

Shared controller used by the root runtime and optional Preact bindings.

pwaFailureMessage

function

function pwaFailureMessage(code: PwaFailureCode): string

Returns actionable, non-technical recovery guidance for a PWA failure.

readAccountSession

function

async function readAccountSession(): Promise<Session>

Resolves the runtime before returning a session with a stable user_id.

readDeclaredSink

function

function readDeclaredSink(): DataSinkDeclaration | null

Reads the declared sink, or null when this device has not declared one. Answers from the state restored at boot; callers outside the booted app (tests, embedders) await restoreDeclaredSink first.

readDeviceCapabilityReport

function

async function readDeviceCapabilityReport(): Promise<DeviceCapabilityReport>

Reads the complete capability report without requesting new browser permission.

readSession

function

function readSession(): Session

Reads the current session. Synchronous — it never prompts or touches the network.

readSinkRestoreOutcome

function

function readSinkRestoreOutcome(): SinkRestoreOutcome

How the most recent restoreDeclaredSink resolved. An unopenable answer means a declaration is persisted but no available key opens it — the device runs local-only until the sink is cleared and re-enrolled, and a status surface should say so rather than showing plain local-only.

RecoverablePasskeyError

class

class RecoverablePasskeyError extends Error {
constructor(code: RecoverablePasskeyErrorCode, options?: ErrorOptions);
readonly name: string;
}

A non-secret, actionable recoverable-passkey failure.

RecoveryError

class

class RecoveryError extends Error {
constructor(code: RecoveryErrorCode, message?: string);
readonly name: string;
readonly code: RecoveryErrorCode;
}

A precise, non-leaking failure reason for a recovery-phrase operation.

recreateRuntime

function

function recreateRuntime(): Promise<LofiRuntime>

Replaces the active Jazz client while preserving the configured account secret.

reloadAfterRuntimeStartupFailure

function

function reloadAfterRuntimeStartupFailure(reload: () => void): void

Performs the explicit navigation required after closing incompatible app tabs.

reloadBrowserRuntime

function

async function reloadBrowserRuntime(): Promise<never>

Fully tears down a browser persistent worker and reloads the document. Jazz alpha.53 cannot attach a second OPFS worker reliably in the same document after shutdown; navigation is the supported clean-runtime boundary. Framework reloads are budgeted per tab: a sequence of reloads that never reaches a settled boot is refused with a reload-loop startup failure instead of cycling.

requestPersistentStorage

function

async function requestPersistentStorage(): Promise<DeviceCapabilityReport>

Requests eviction protection, then returns the browser's authoritative capability report.

requestPwaInstall

function

function requestPwaInstall(): Promise<PwaInstallState>

Requests the deferred browser installation prompt when one is available.

restoreDeclaredSink

function

async function restoreDeclaredSink(keyStore: DeviceKeyStore): Promise<SinkRestoreOutcome>

Unseals the persisted declaration into memory. Boot awaits this before any sync decision (see boot.ts); tests and non-boot embedders call it directly. Safe to call repeatedly — it re-reads storage each time.

restoreFromPasskey

function

async function restoreFromPasskey(options: AccountReplacementOptions): Promise<Session>

Restores a passkey-backed secret and recreates Jazz on that stable principal. The restoreFromRecoveryPhrase reload caveat applies: on SharedWorker-capable browsers the page reloads and the returned promise never settles.

restoreFromRecoveryPhrase

function

async function restoreFromRecoveryPhrase(phrase: string, options: AccountReplacementOptions): Promise<Session>

Reconstructs an account from its recovery phrase, elects sync, and replaces the active runtime. Throws RecoveryError for malformed phrases and AccountReplacementError when confirmation is required. On SharedWorker-capable browsers the replacement reloads the page and the returned promise never settles; the app resumes on the restored account after reload.

revealRecoveryPhrase

function

async function revealRecoveryPhrase(): Promise<string>

Reveals the current account's recovery phrase — the same 32-byte secret encoded as words. Show it for the user to write down; never persist it. Works whether or not sync is on, but only matters once the account syncs, since the phrase recovers what has been backed up.

runtimeRecreatedEvent

const

const runtimeRecreatedEvent: "lofi:runtime-recreated";

Event dispatched after account, sync, or runtime replacement changes active state.

RuntimeStartupError

class

class RuntimeStartupError extends Error {
constructor(failure: RuntimeStartupFailure, cause?: unknown);
readonly name: string;
readonly code: RuntimeStartupFailureCode;
readonly failure: RuntimeStartupFailure;
}

Lofi-owned error boundary for a rejected persistent runtime startup.

SchemaCompatibilityError

class

class SchemaCompatibilityError extends Error {
readonly name: string;
readonly code: string;
}

Raised when a mutation is refused because the local data is ahead of the running code. It surfaces through the ordinary error path of whatever performed the write — a verb call's handle fails, a table insert/update/remove rejects — with no journal entry or effect; reads keep working. The remediation is updating the app: watch SchemaCompatState via useSchemaCompat or subscribeSchemaCompat, and offer applyPwaUpdate when the update is ready.

sealProvisionCapability

function

async function sealProvisionCapability(options: AuthenticateOptions): Promise<SealOutcome>

Seals the held provision capability under a passkey-PRF slot in one user-verifying ceremony. The PRF evaluation is attempted, never capability-detected: on success the envelope records the credential that actually evaluated it (and whether it roams); prf-unavailable or cancelled propagate as AuthError so the caller can keep the capability memory-only and point the user at their password manager instead.

settleUiMutation

function

async function settleUiMutation(mutation: PromiseLike<unknown>): Promise<void>

Lets event handlers await a UI mutation without leaking an unhandled rejection.

shutdownRuntime

function

function shutdownRuntime(): Promise<void>

Releases stores, subscriptions, the Jazz client, and persistent worker resources.

stopSyncBackup

function

async function stopSyncBackup(): Promise<Session>

Stops replicating this account to the server and returns to local-only. The local data and the account are untouched — this only detaches the network, so electing to sync again resumes against the same account. Also releases the sync-owner pin, so a different account on this device may elect afterwards. Safe to call whatever state the transport is in: a runtime that was created without a configured server has nothing to detach and none is forced open.

storageForkGuard

const

const storageForkGuard: StorageForkGuard;

Shared guard used by boot and the optional Preact bindings.

subscribeBootProgress

function

function subscribeBootProgress(listener: (progress: BootProgress) => void): () => void

Subscribes to first-load progress and returns an idempotent unsubscribe function.

subscribeNotices

function

function subscribeNotices(listener: () => void): () => void

Subscribes to notice-queue changes; returns an unsubscribe function.

subscribePwaState

function

function subscribePwaState(subscriber: (state: PwaState) => void): () => void

Subscribes to shared PWA state and returns an idempotent unsubscribe function.

subscribeRuntimeDiagnostics

function

function subscribeRuntimeDiagnostics(listener: () => void): () => void

Subscribes to diagnostics changes and returns an idempotent unsubscribe function.

subscribeSchemaCompat

function

function subscribeSchemaCompat(listener: (state: SchemaCompatState) => void): () => void

Subscribes to shared compatibility state; returns an unsubscribe function.

subscribeStorageFork

function

function subscribeStorageFork(listener: (state: StorageForkState) => void): () => void

Subscribes to shared fork state; returns an unsubscribe function.

SyncEnrollmentError

class

class SyncEnrollmentError extends Error {
constructor(code: SyncEnrollmentFailureCode, scope?: "sync" | "provision");
readonly name: string;
readonly code: SyncEnrollmentFailureCode;
}

Thrown by enrollSyncTicket when the node's store preflight refuses the enrollment: the sink declaration is rolled back, no provision capability is held, and sync is not elected, so the device stays exactly as it was before the attempt. The message is user-presentable and names the remediation for its SyncEnrollmentFailureCode. A merely unreachable store never throws this — it enrolls with the warning recorded in runtime diagnostics (storeStatus) instead, because a flaky network must not block a legitimate ticket.

SyncOwnerError

class

class SyncOwnerError extends Error {
constructor(owner_user_id: string | null);
readonly name: string;
readonly code: string;
readonly owner_user_id: string | null;
}

Thrown by enableSyncBackup (and through it enrollSyncTicket) when sync on this device was elected by a different account than the one in hand. The message is user-presentable and names the remediation: stop sync (which releases the pin) or restore the owning account, then elect again. The same mismatch found at boot never throws — the runtime opens with transport suppressed and reports it through the session's syncOwnerMismatch flag and the syncOwner runtime diagnostic instead, so local work continues.

TableMutationStore

class

class TableMutationStore<T extends TableRow, Init> {
constructor(table: TableProxy<T, Init>, environment: TableMutationEnvironment, onIdle: () => void);
getSnapshot: () => TableMutationSnapshot;
subscribe: (listener: () => void) => () => void;
get consumerCount(): number;
insert(values: Init): WriteHandle<T>;
update(id: string, patch: Partial<Init>): WriteHandle<void>;
remove(id: string): WriteHandle<void>;
dispose(): void;
}

Framework-neutral typed mutations and observable durability for one table.

TableStore

class

class TableStore<T extends TableRow, Init> {
constructor(db: Db, table: TableHandle<T, Init>, diagnostics: RuntimeDiagnostics, options: TableStoreOptions);
getSnapshot: () => TableSnapshot<T>;
subscribe: (listener: () => void) => () => void;
insert(values: Init): Promise<void>;
update(id: string, patch: Partial<Init>): Promise<void>;
remove(id: string): Promise<void>;
delete(id: string): Promise<void>;
reportMutationError(event: MutationErrorEvent): void;
close(): void;
}

A generic, reactive store over a single declared table. It has no knowledge of the application schema beyond TableRow: callers bind it to one of their own schema.<name> tables and read/write typed rows through it.

trustPeerKey

function

function trustPeerKey(appId: string, userId: string, fingerprint: string): void

Replaces a peer's pin after out-of-band verification — the explicit user action that resolves a peer-key-changed refusal.

unlockProvisionCapability

function

async function unlockProvisionCapability(options: AuthenticateOptions): Promise<string>

Unlocks the sealed provision capability through its passkey ceremony and holds it for this document. Returns the held capability directly when one is already in memory. Throws EnvelopeError("locked") when nothing is stored, and AuthError (cancelled, prf-unavailable, credential-mismatch) when the ceremony does not complete.

verifyAndPinFingerprint

function

function verifyAndPinFingerprint(appId: string, userId: string, observed: string): boolean

Verifies a peer's observed fingerprint against this device's pin, pinning on first sight. Returns false — and leaves the pin untouched — when a pin exists and disagrees; callers refuse the key and surface the mismatch.

WriteHandle

class

class WriteHandle<T> implements PromiseLike<T> {
constructor(writeId: string);
get writeId(): string;
get batchId(): string | null;
get stage(): WriteStage;
get reason(): WriteRejection | null;
get saved(): Promise<T>;
get synced(): Promise<T>;
then<Fulfilled = T, Rejected = never>(onfulfilled?: ((value: T) => Fulfilled | PromiseLike<Fulfilled>) | null, onrejected?: ((reason: unknown) => Rejected | PromiseLike<Rejected>) | null): Promise<Fulfilled | Rejected>;
subscribe(listener: () => void): () => void;
}

A single write observed through the author-facing lifecycle.

await write (the thenable) resolves at saved with the write's value — for inserts, the created row. write.synced resolves when the store confirms the write and rejects with WriteRejectedError when the store denies it. stage and reason are current-state properties; subscribe notifies immediately and on every later change.

On a device without managed sync there is no store to confirm anything: local durability is settlement, and the handle reaches synced as soon as it is saved. In Preact components, render a handle with useWrite and the app-wide pending set with usePendingWrites.

Handles are issued by the runtime and are observe-only: the lifecycle mutators live on a controller the ledger keeps at construction, so no consumer of a handle can advance or settle it.

Example

const write = placeOrder({ sku, quantity }); // a verb returns a WriteHandle
const order = await write; // resolves at saved — safe to navigate
write.synced.catch((error) => {
if (error instanceof WriteRejectedError) showDenied(error.message);
});

WriteLedger

class

class WriteLedger {
constructor(environment: WriteLedgerEnvironment);
getPendingSnapshot: () => PendingWritesSnapshot;
subscribe: (listener: () => void) => () => void;
arm(): Promise<void>;
rowStatus(rowId: string): RowSyncStatus;
perform<T>(request: LedgerWriteRequest, options: LedgerWriteOptions): WriteHandle<T>;
performVerb(descriptor: MutationDescriptor, args: readonly unknown[], internal: Pick<LedgerWriteOptions, "writeId" | "retainedBy">): WriteHandle<unknown>;
performChainedVerb(descriptor: MutationDescriptor, args: readonly unknown[], parentJournalId: string): Promise<void>;
retryObligationsFor(effectName: string): void;
flush(): Promise<void>;
dispose(): void;
}

The per-app write ledger. One instance owns the journal, the pending-writes observable, boot reconciliation, and effect delivery. Application code reaches it through verbs and hooks; tests construct isolated instances over an injected WriteLedgerEnvironment.

WriteRejectedError

class

class WriteRejectedError extends Error {
constructor(writeId: string, rejection: WriteRejection);
readonly name: string;
readonly rejectionCause: "denied" | "expired";
readonly code: string | null;
readonly writeId: string;
}

Raised through WriteHandle.synced when a write settles as rejected: the store adjudicated the write and denied it permanently.

AccountReplacementOptions

type

type AccountReplacementOptions = {
confirmLocalReplacement?: boolean;
};

Confirmation required before recovery may replace a different local-only account.

AuthCapability

type

type AuthCapability = {
webAuthn: boolean;
prf: PrfSupport;
origin: CredentialOriginReport;
};

What the current device/browser/origin can do for credential auth.

AuthDependencies

type

type AuthDependencies = {
credentials?: CredentialsContainer;
rpId?: string;
trustedOrigins?: readonly string[];
};

Injected browser surfaces, so the flows are unit-testable without a device.

AuthenticateOptions

type

type AuthenticateOptions = AuthDependencies & { credentialId?: string };

Options for authenticateDeviceCredential.

BootProgress

type

type BootProgress = {
phase: BootProgressPhase;
loadedBytes: number;
totalBytes: number | null;
};

Live first-load progress for application status UI.

BootProgressPhase

type

type BootProgressPhase =
| "pending"
| "downloading"
| "opening"
| "ready"
| "failed";

Phases between a painted shell and an open runtime.

  • pending — the runtime has not been requested yet.
  • downloading — the engine binary is downloading; on a cold first visit this is the long phase, with byte progress in BootProgress.
  • opening — the engine is instantiating and persistent storage is opening.
  • ready — the runtime is open; live queries answer from local data.
  • failed — the runtime could not open; the cause is in runtime diagnostics (startupFailure).

BootProgressTracker

type

type BootProgressTracker = {
get(): BootProgress;
subscribe(listener: (progress: BootProgress) => void): () => void;
warmEngineDownload(): Promise<void>;
mark(phase: "opening" | "ready" | "failed"): void;
};

Stateful first-load progress shared by the runtime and application UI.

BootProgressTrackerDependencies

type

type BootProgressTrackerDependencies = {
readonly engineAsset?: () => EngineAssetReference | null;
readonly fetchImpl?: typeof fetch;
};

Injectable surfaces used to test the tracker deterministically.

CredentialOriginReport

type

type CredentialOriginReport = {
status: "stable" | "local-only" | "unverified" | "blocked";
rpId: string;
action: string;
};

How stable the current origin is for enrolling a passkey. A passkey is bound to the origin hostname (its RP ID); enrolling on an origin that later changes silently breaks the credential, so enrollment is allowed only on stable origins and on loopback hosts during development (local-only), and refused on unverified and blocked origins.

DataSinkDeclaration

type

type DataSinkDeclaration = {
appId: string;
serverUrl: string;
scope?: "sync" | "provision";
label?: string;
node?: string;
pop?: { ticketId: string };
};

A user-declared sync location: where this account's data syncs.

DeviceCapabilityReport

type

type DeviceCapabilityReport = {
secureContext: boolean;
opfs: boolean;
sharedWorker: boolean;
webLocks: boolean;
messageChannel: boolean;
durableDriverSupported: boolean;
webAuthn: boolean;
prf: PrfSupport;
persistentPermission: "granted" | "not-granted" | "unavailable" | "error";
displayMode: "standalone" | "browser";
};

Browser capabilities that determine whether lofi can provide its runtime guarantees.

DeviceCredential

type

type DeviceCredential = {
id: string;
rpId: string;
portable: boolean;
};

An enrolled or authenticated device credential.

DeviceKeyStore

interface

interface DeviceKeyStore {
getOrCreate(keyId: string): Promise<CryptoKey>;
get(keyId: string): Promise<CryptoKey | null>;
}

Holds the non-extractable device-bound wrapping keys for device-key slots. Browsers back this with IndexedDB; non-browser runtimes and tests get an in-memory store.

DurableCapabilityReport

type

type DurableCapabilityReport = Omit<DeviceCapabilityReport, "persistentPermission">;

Capability report that excludes the separately requested persistence permission.

EffectContext

type

type EffectContext = {
journalId: string;
writeId: string;
verb: string | null;
table: string;
op: "insert" | "update" | "remove";
rowId: string;
writeCreatedAt: number;
fate: "synced" | "rejected";
cause: "denied" | "expired" | null;
code: string | null;
reason: string | null;
};

Delivery metadata passed to every effect handler. Delivery is at-least-once: a crash between handler start and journal completion re-runs the handler at the next boot, so handlers calling external services should pass EffectContext.journalId as an idempotency key.

EffectDebugEvent

type

type EffectDebugEvent = {
verb: string | null;
journalId: string;
event: string;
at: number;
};

One development-only timeline event recorded by the built-in s.debug unit.

EffectHandlers

type

type EffectHandlers<Row> = {
onSynced?: (row: EffectRow<Row>, context: EffectContext) => void | Promise<void>;
onRejected?: (row: EffectRow<Row>, context: EffectContext) => void | Promise<void>;
};

The action and compensation handlers one effect unit pairs.

EffectLogEntry

type

type EffectLogEntry = {
label: string;
verb: string | null;
table: string;
rowId: string;
fate: "synced" | "rejected";
at: number;
};

One structured entry recorded by the built-in s.log effect unit.

EffectRow

type

type EffectRow<Row> = Partial<Row> & { id: string };

The row an effect handler receives. In the session that performed the write it is the write's snapshot: the full row for inserts, the changed columns for updates, only the id for removes. After a reload the journal holds no column values: a synced handler receives the row fetched live from the store — the final merged state — and a rejected handler receives the id alone, because the engine rolled the row back and identity plus EffectContext.cause is all that remains. Treat every column except id as optional.

EffectTraceEntry

type

type EffectTraceEntry = {
label: string | null;
verb: string | null;
rowId: string;
fate: "synced" | "rejected";
durationMs: number;
at: number;
};

One OpenTelemetry-shaped span recorded by the built-in s.trace effect unit: the write's journaling to its settled fate, with the elapsed latency. The framework emits these into diagnostics with no vendor coupling; an OTLP exporter is an adapter over this feed, never a concept the author sees.

EffectUnit

type

type EffectUnit<Row = { id: string }> = {
readonly effectName: string;
readonly handlers: EffectHandlers<Row>;
readonly expiresAfterMs?: number | null;
readonly maxAttempts?: number;
readonly anonymousPrefix?: string;
};

A named, reusable pairing of action and compensation. The name is the durable identity the journal uses to re-arm handlers after a reload; a mutation declares its units once, at the verb declaration.

EngineAssetReference

type

type EngineAssetReference = {
url: string;
bytes: number | null;
};

One engine binary reference declared by the built shell.

EnrollOptions

type

type EnrollOptions = AuthDependencies & { label?: string };

Options for enrollDeviceCredential.

EnrollSyncTicketOptions

type

type EnrollSyncTicketOptions = {
fetcher?: typeof fetch;
};

Options for enrollSyncTicket.

InstallEnvironment

type

type InstallEnvironment = {
displayModeStandalone: boolean;
navigatorStandalone: boolean;
platform: string;
maxTouchPoints: number;
secureContext: boolean;
serviceWorkerSupported: boolean;
};

Browser signals used to classify installed and manual-install experiences.

InstallPromptEvent

type

type InstallPromptEvent = Event & { userChoice: Promise<{ outcome: "accepted" | "dismissed" }>; prompt(): Promise<void> };

Chromium install event retained until application UI requests the prompt.

JournalDocument

type

type JournalDocument = {
version: 1;
hashKey: string;
writes: Record<string, JournalWriteRecord>;
};

The persisted journal document.

JournalEffectState

type

type JournalEffectState = {
status: JournalEffectStatus;
attempts: number;
lastError: string | null;
expiresAt: number | null;
};

One effect obligation's durable state within a journaled write.

JournalEffectStatus

type

type JournalEffectStatus =
| "pending"
| "done"
| "failed"
| "expired"
| "failed-permanent";

One obligation's durable status. pending until the handler completes; failed handlers re-arm at boot until quarantine retires them as failed-permanent; expired marks a delivery window that closed before the obligation could be delivered. Retired statuses never run again and make the entry prunable.

JournalStorage

type

type JournalStorage = {
load(): Promise<string | null>;
save(text: string): Promise<void>;
};

Where the journal document persists. The default resolves OPFS, then localStorage, then memory; tests inject a deterministic implementation.

JournalWriteRecord

type

type JournalWriteRecord = {
writeId: string;
retainedBy?: string | null;
verb: string | null;
table: string;
op: "insert" | "update" | "remove";
rowId: string;
batchId: string | null;
rowHashes: Record<string, string>;
stage: JournalWriteStage;
cause: "denied" | "expired" | null;
code: string | null;
reason: string | null;
createdAt: number;
expiresAt: number | null;
effects: Record<string, JournalEffectState>;
};

One journaled write: identity, snapshot, fate, and effect obligations.

JournalWriteStage

type

type JournalWriteStage = "saved" | "synced" | "rejected";

The durable fate of a journaled write.

LedgerWriteOptions

type

type LedgerWriteOptions = {
verb?: string | null;
units?: readonly EffectUnit<{ id: string }>[];
expiresAfterMs?: number | null;
writeId?: string;
retainedBy?: string | null;
};

Verb metadata carried by a ledger write.

LedgerWriteRequest

type

type LedgerWriteRequest = { kind: "insert"; table: TableProxy<unknown, unknown>; values: unknown } | { kind: "update"; table: TableProxy<unknown, unknown>; id: string; patch: Record<string, unknown> } | { kind: "remove"; table: TableProxy<unknown, unknown>; id: string };

A write the ledger can perform.

LiveQueryEnvironment

type

type LiveQueryEnvironment = {
getDb(): Promise<Pick<Db, "subscribeAll">>;
subscribeRuntimeRecreation(listener: () => void): () => void;
subscribeSharedKeyring?(listener: () => void): () => void;
updateDiagnostics(update: (diagnostics: RuntimeDiagnostics) => void): void;
};

Runtime seams used by a live-query registry. Exported for deterministic framework tests.

LiveQueryLease

type

type LiveQueryLease<T extends TableRow> = {
store: LiveQueryStore<T>;
release(): void;
};

A retained reference to a shared live-query store. Release it after its consumer unsubscribes.

LiveQuerySnapshot

type

type LiveQuerySnapshot<T extends TableRow> = {
status: "loading" | "ready" | "error";
rows: T[];
error: string | null;
};

Honest read state for an arbitrary typed Jazz query.

LofiAppConfig

type

type LofiAppConfig<Schema = unknown> = {
name: string;
databaseName: string;
schema: Schema;
storage: "durable";
credentialOrigins?: readonly string[];
passkey?: { rpId?: string };
pwa?: { updateBanner?: "default" | "none"; staleTabs?: "reload" | "prompt"; forkNotice?: "default" | "none" };
sync: { adapter: "jazz" };
repositoryUrl?: string;
};

Author-owned configuration consumed by the package runtime.

Example

const config: LofiAppConfig<typeof schema> = {
name: "my-app",
databaseName: "my-app",
schema,
storage: "durable",
sync: { adapter: "jazz" },
};

LofiRuntime

type

type LofiRuntime = {
db: Db;
diagnostics: RuntimeDiagnostics;
store<T extends TableRow, Init>(table: TableHandle<T, Init>): TableStore<T, Init>;
shutdown(): Promise<void>;
};

The shared, lazily opened Jazz client and its application-facing adapters.

MutationDescriptor

type

type MutationDescriptor = {
readonly verbName: string;
readonly op: MutationOp<unknown, unknown>;
readonly units: readonly EffectUnit<{ id: string }>[];
readonly expiresAfterMs: number | null;
};

The registered declaration the runtime dispatches for one verb.

MutationErrorClass

type

type MutationErrorClass = "permanent" | "transient" | "unknown";

How a rejection code is acted on: permanent verdicts settle the write as rejected and run compensation, transient and unknown codes leave the write pending until a later settlement attempt resolves it.

MutationOp

type

type MutationOp<T, Init, Kind extends MutationOpKind = MutationOpKind> = {
readonly kind: Kind;
readonly table: TableProxy<T, Init>;
};

A table operation a verb is declared over; see insert, update, remove.

MutationOpKind

type

type MutationOpKind = "insert" | "update" | "remove";

Which operation a MutationOp performs.

NoticeEnqueueInput

type

type NoticeEnqueueInput = {
id: string;
message: string;
tone: NoticeTone;
ttlMs: number | null;
};

What one enqueue call carries, before the queue stamps identity and time.

NoticeEntry

type

type NoticeEntry = {
id: string;
message: string;
tone: NoticeTone;
createdAt: number;
expiresAt: number | null;
};

One durable notice entry.

NoticeTone

type

type NoticeTone =
| "info"
| "success"
| "warning"
| "error";

How a notice is classified for rendering.

PasskeyBackupReceipt

type

type PasskeyBackupReceipt = {
user_id: string;
rpId: string;
};

Non-secret confirmation that a recoverable passkey was created for an account and RP-ID. user_id relays the sync principal's vocabulary verbatim, matching Session.user_id.

PendingWritesSnapshot

type

type PendingWritesSnapshot = {
count: number;
writes: readonly PendingWriteSummary[];
};

The reload-safe pending set powering "N changes waiting to sync".

PendingWriteSummary

type

type PendingWriteSummary = {
writeId: string;
verb: string | null;
table: string;
rowId: string;
op: "insert" | "update" | "remove";
createdAt: number;
expired: boolean;
};

One write not yet settled, as shown by pending-writes surfaces.

PrfSupport

type

type PrfSupport =
| "available"
| "not-reported"
| "unknown"
| "unavailable";

Whether the WebAuthn PRF extension can be used on this client.

ProbeTable

type

type ProbeTable = {
where(input: Record<string, unknown>): unknown;
};

The narrow table surface boot-reconciliation probes query by row id.

ProvisionCapabilityStatus

type

type ProvisionCapabilityStatus = {
held: boolean;
sealed: boolean;
portable?: boolean;
};

What provision capability exists on this device right now.

PwaController

type

type PwaController = {
getState(): PwaState;
subscribe(subscriber: (state: PwaState) => void): () => void;
requestInstall(): Promise<PwaInstallState>;
checkForUpdate(): Promise<boolean>;
applyUpdate(): boolean;
initialize(): void;
};

Stateful controller for browser installation and service-worker updates. Most apps use the shared pwaController through the wrapper functions (getPwaState, applyPwaUpdate) or the Preact bindings.

PwaControllerDependencies

type

type PwaControllerDependencies = {
readonly eventTarget?: () => EventTarget;
readonly visibilityTarget?: () => EventTarget;
readonly serviceWorker?: () => ServiceWorkerContainer | undefined;
readonly installEnvironment?: () => InstallEnvironment;
readonly isVisible?: () => boolean;
readonly now?: () => number;
readonly setTimeout?: (callback: () => void, milliseconds: number) => unknown;
readonly clearTimeout?: (handle: unknown) => void;
readonly updateCheckIntervalMs?: number;
readonly updateCheckTimeoutMs?: number;
readonly production?: () => boolean;
readonly deploymentBaseUrl?: () => string;
readonly reload?: () => void;
readonly exposeState?: (state: PwaState) => void;
readonly prepareUpdateSwap?: () => Promise<void>;
readonly staleTabBehavior?: () => "reload" | "prompt";
readonly onStaleTab?: () => void;
};

Injectable browser surfaces used to test the PWA lifecycle deterministically.

PwaFailureCode

type

type PwaFailureCode =
| "registration"
| "installation"
| "install-prompt"
| "update-check"
| "precache"
| "runtime-cache";

Stable categories for recoverable offline/PWA failures.

PwaInstallState

type

type PwaInstallState =
| "installed"
| "available"
| "prompting"
| "accepted"
| "dismissed"
| "manual-ios"
| "manual-browser"
| "unsupported";

Browser installation states exposed to application UI.

  • installed — running standalone or the browser reports the app installed.
  • available — the browser offered an install prompt; requestPwaInstall opens it.
  • prompting — the deferred prompt is open.
  • accepted / dismissed — the prompt's outcome.
  • manual-ios — install via Share → Add to Home Screen; iOS exposes no prompt API.
  • manual-browser — install is available only through the browser menu.
  • unsupported — no secure context or no service-worker support.

PwaState

type

type PwaState = {
worker: PwaWorkerState;
install: PwaInstallState;
update: PwaUpdateState;
failure?: { code: PwaFailureCode; message: string };
};

Current install, service-worker, and offline-cache state.

PwaUpdateState

type

type PwaUpdateState =
| "idle"
| "checking"
| "installing"
| "ready"
| "applying"
| "failed";

Foreground update-check and waiting-worker states exposed to application UI.

  • idle — no check running and nothing staged.
  • checking — a bounded update check is in flight.
  • installing — a newly discovered worker is downloading and installing.
  • ready — a new worker is staged and waiting: show the update affordance and call applyPwaUpdate.
  • applying — covers the swap until controlled tabs reload.
  • failed — the check or installation failed; the cause is in PwaState.failure.

PwaWorkerState

type

type PwaWorkerState =
| "development-disabled"
| "unsupported"
| "registering"
| "ready"
| "failed";

Service-worker lifecycle states exposed to application UI.

  • development-disabled — registration is skipped outside production builds.
  • unsupported — the environment offers no service-worker container.
  • registering — registration and first activation are in progress.
  • ready — an active worker controls the app and serves the offline shell.
  • failed — registration, installation, or precaching failed; PwaState.failure carries the cause.

RecoverablePasskeyErrorCode

type

type RecoverablePasskeyErrorCode =
| "cancelled"
| "unsupported"
| "credential-missing"
| "rp-id-mismatch"
| "verification-failed"
| "invalid-credential"
| "backup-failed"
| "restore-failed";

Stable categories for recoverable passkey backup and restore failures.

RecoveryErrorCode

type

type RecoveryErrorCode =
| "invalid-length"
| "invalid-word"
| "invalid-checksum"
| "invalid-secret";

Stable recovery-phrase failure categories for actionable UI guidance.

RowOf

type

type RowOf<Table> = Table extends { readonly _rowType: infer Row } ? Row : never;

The row type of one declared schema table — RowOf<typeof app.schema.tasks>. Lets author code derive row types from its schema without importing the vendor module, keeping UI islands on public package seams.

RowSyncStatus

type

type RowSyncStatus = "synced" | "waiting" | "rejected";

Per-row sync state for badges: settled, still waiting, or denied.

RuntimeDiagnostics

type

type RuntimeDiagnostics = {
storageState: "persistent-requested" | "persistent-driver-open" | "failed";
startupFailure: RuntimeStartupFailure | null;
storeStatus: RuntimeStoreStatus;
syncOwner: SyncOwnerDiagnostic;
schemaCompat: SchemaCompatState;
clientsCreated: number;
activeClients: number;
activeConsumers: number;
activeVendorSubscriptions: number;
totalVendorSubscriptions: number;
activeMutationListeners: number;
totalMutationListeners: number;
unsubscribeCalls: number;
localWaitCalls: number;
pendingLocalWrites: number;
pendingGlobalWrites: number;
lastWriteDurability: WriteDurability;
mutationErrors: number;
journaledPendingWrites: number;
expiredPendingWrites: number;
effectHandlerFailures: number;
expiredObligations: number;
quarantinedObligations: number;
effectLog: readonly EffectLogEntry[];
effectTraces: readonly EffectTraceEntry[];
effectDebugTimeline: readonly EffectDebugEvent[];
activeNotices: number;
sharedFieldAlerts: readonly SharedFieldAlert[];
};

Runtime-owned observability counters. These describe the framework's storage, subscription, and write machinery and never reference any application schema.

For a device-status widget the load-bearing fields are RuntimeDiagnostics.storeStatus, RuntimeDiagnostics.schemaCompat, journaledPendingWrites (the "N changes waiting to sync" count), and lastWriteDurability; the remaining counters exist for the development inspector and bug reports.

RuntimeStartupFailure

type

type RuntimeStartupFailure = {
code: RuntimeStartupFailureCode;
runtimeMode: "local" | "managed";
message: string;
};

Non-sensitive runtime context retained for diagnostics and recovery UI.

RuntimeStartupFailureCode

type

type RuntimeStartupFailureCode =
| "broker-incompatible"
| "configuration-error"
| "reload-loop"
| "storage-startup-failed"
| "unsupported-capabilities";

Stable categories for failures that prevent Lofi's persistent runtime from opening.

RuntimeStoreStatus

type

type RuntimeStoreStatus =
| { state: "unchecked"; reason: "sync-not-connected" | "sink-not-ticket-gated" }
| { state: "deployed"; headHash: string }
| { state: "no_schema"; message: string }
| { state: "store_unavailable" }
| { state: "ticket_rejected" }
| { state: "unsupported" };

The boot store preflight carried in runtime diagnostics.

unchecked is the documented no-alarm value: the runtime is not connecting managed sync, or the active sink is not a ticket-gated node URL (first-party Jazz servers and open-mode nodes expose no store-status endpoint). The other states relay the node's metadata-only answer verbatim. no_schema carries the actionable message — writes against such a store hang until it is provisioned. deployed carries the store's newest schema hash so a mismatch with this app's deployment can be judged by a human; nothing is ever auto-repaired from this diagnostic.

SchemaCompatGate

type

type SchemaCompatGate = {
start(): void;
getState(): SchemaCompatState;
subscribe(listener: (state: SchemaCompatState) => void): () => void;
assertWritable(): Promise<void>;
markRuntimeWritable(): void;
markStaleTab(): void;
};

The boot compatibility gate between bundle schema range and local data.

SchemaCompatGateDependencies

type

type SchemaCompatGateDependencies = {
readonly production?: () => boolean;
readonly loadManifest?: () => Promise<unknown>;
readonly storage?: () => Pick<Storage, "getItem" | "setItem"> | undefined;
readonly storageKey?: () => string;
readonly storageEvents?: () => EventTarget | undefined;
readonly controller?: () => SchemaCompatUpdateSurface | undefined;
readonly settleTimeoutMs?: number;
};

Injectable browser surfaces used to test the gate deterministically.

SchemaCompatReason

type

type SchemaCompatReason = "schema" | "stale-tab";

Why writes are refused: newer-schema data, or a stale tab after a swap.

SchemaCompatState

type

type SchemaCompatState =
| { state: "unchecked"; reason: "inactive" | "development" | "no-manifest" | "pending" }
| { state: "compatible"; classification: "first-boot" | "equal" | "code-ahead" }
| { state: "data-ahead"; reason: SchemaCompatReason; message: string }
| { state: "updating"; message: string };

The compatibility state exposed through diagnostics and the Preact hook.

SchemaCompatUpdateSurface

type

type SchemaCompatUpdateSurface = Pick<PwaController, "getState" | "subscribe" | "checkForUpdate">;

The update surface the gate drives as its remediation.

SealOutcome

type

type SealOutcome = {
portable: boolean;
};

What a completed sealing ceremony reports about the sealed record.

Session

type

type Session = {
user_id: string | null;
syncAvailable: boolean;
sink: SessionSink | null;
backedUp: boolean;
syncing: boolean;
syncOwnerMismatch: boolean;
phraseGuarded: boolean;
passkeyRecoverable: boolean;
};

A snapshot of the account: what is possible and what the user has chosen.

SessionSink

type

type SessionSink = {
source: "declared" | "default";
host: string;
label?: string;
};

A non-secret description of the sync location in effect.

SharedFieldAlert

type

type SharedFieldAlert = {
code: "peer-key-changed" | "self-key-conflict" | "wrap-invalid";
userId: string;
detail: string;
at: string;
};

One detected shared-field key anomaly.

SinkRestoreOutcome

type

type SinkRestoreOutcome =
| "none"
| "restored"
| "migrated"
| "unopenable";

How restoreDeclaredSink resolved the persisted record: none (no record), restored (envelope opened), migrated (a pre-envelope cleartext record was read and resealed), or unopenable (an envelope exists but no available key opens it — the record is left intact and the device behaves as local-only until the sink is re-declared).

StorageForkDiagnostics

type

type StorageForkDiagnostics = Pick<RuntimeDiagnostics, "storageState" | "localWaitCalls" | "journaledPendingWrites" | "lastWriteDurability">;

The diagnostics fields the guard reads to recognize real write activity.

StorageForkDiagnosticsSurface

type

type StorageForkDiagnosticsSurface = {
get(): StorageForkDiagnostics;
subscribe(listener: () => void): () => void;
};

The diagnostics feed the guard observes; the runtime supplies the default.

StorageForkGuard

type

type StorageForkGuard = {
start(): void;
getState(): StorageForkState;
subscribe(listener: (state: StorageForkState) => void): () => void;
dismissFork(): void;
};

The storage-container fork guard for one browsing context.

StorageForkGuardDependencies

type

type StorageForkGuardDependencies = {
readonly production?: () => boolean;
readonly appId?: () => string;
readonly storage?: () => Pick<Storage, "getItem" | "setItem"> | undefined;
readonly storageKeys?: () => readonly string[];
readonly readCookies?: () => string;
readonly writeCookie?: (cookie: string) => void;
readonly cookiePath?: () => string;
readonly secureContext?: () => boolean;
readonly environment?: () => Pick<InstallEnvironment, "displayModeStandalone" | "navigatorStandalone">;
readonly syncing?: () => boolean;
readonly diagnostics?: () => StorageForkDiagnosticsSurface;
readonly probeLocalRows?: () => Promise<boolean>;
readonly storageEvents?: () => EventTarget | undefined;
};

Injectable browser surfaces used to test the guard deterministically.

StorageForkState

type

type StorageForkState =
| { state: "unarmed"; reason: "inactive" | "development" }
| { state: "idle" }
| { state: "browser-data-at-risk" }
| { state: "fork-detected"; message: string };

The storage-container fork state exposed through the guard and the Preact hook.

SyncEnrollmentFailureCode

type

type SyncEnrollmentFailureCode = "no_schema" | "ticket_rejected";

Stable categories for a refused ticket enrollment, relayed from the node's store preflight: no_schema — the store holds no deployed schema for this app, so nothing could sync; ticket_rejected — the node no longer accepts the ticket (revoked, or the node was reset). Both are definite answers from the node; an unreachable store is deliberately not a refusal category.

SyncOwnerDiagnostic

type

type SyncOwnerDiagnostic = { state: "unchecked" } | { state: "self" } | { state: "mismatch"; owner_user_id: string | null };

The boot-time sync-owner adjudication. unchecked means the runtime was not connecting (nothing to adjudicate); self means the account in hand owns the election; mismatch means sync on this device was elected by a different account, so the runtime booted with transport suppressed rather than writing into the owner's store. The remediation is explicit: stop sync (releasing the election) or restore the owning account.

SyncTicket

type

type SyncTicket = {
v: 1;
appId: string;
url: string;
scope?: "sync" | "provision";
label?: string;
node?: string;
};

A parsed lofisync1. app-connect ticket — the credential a self-hosted node issues so an app syncs against it. The format contract lives with the node (lofi-node docs/app-ticket.md, conformance fixtures vendored at package/testdata/app-ticket-fixtures.json); this parser mirrors its validation: version 1, an http(s) URL whose path is /t/<43-char base64url secret>, and a scope that is absent (meaning sync), sync, or provision — an unknown scope rejects the ticket rather than silently granting less than it claims.

TableHandle

type

type TableHandle<T extends TableRow, Init> = TableProxy<T, Init> & QueryBuilder<T>;

A declared schema table (schema.<name>). It is both an insert/update/remove target and a query source, so the store accepts the intersection Jazz expects. T is the row type and Init the insert type (row minus server-owned fields).

TableMutationEnvironment

type

type TableMutationEnvironment = {
getDb(): Promise<Db>;
syncConfigured(): boolean;
getLedger(): WriteLedger;
subscribeRuntimeRecreation(listener: () => void): () => void;
updateDiagnostics(update: (diagnostics: RuntimeDiagnostics) => void): void;
};

Runtime seams used by table-mutation tests and the package-wide registry.

TableMutationLease

type

type TableMutationLease<T extends TableRow, Init> = {
store: TableMutationStore<T, Init>;
release(): void;
};

Retained ownership of one shared table-mutation store.

TableMutationSnapshot

type

type TableMutationSnapshot = {
pending: number;
durability: WriteDurability;
error: string | null;
};

Observable state shared by every mutation consumer for one table.

TableRow

type

type TableRow = {
id: string;
};

The minimum shape every persisted row exposes to the framework.

TableSnapshot

type

type TableSnapshot<T extends TableRow> = {
status: "loading" | "ready" | "error";
rows: T[];
durability: WriteDurability;
error: string | null;
};

Reactive table state including rows and the last observed durability tier.

TableStoreOptions

type

type TableStoreOptions = {
syncConfigured?: () => boolean;
onDiagnosticsChange?: () => void;
guardWrite?: () => Promise<(() => void) | void>;
};

Runtime options controlling durability waits and diagnostics notifications.

WriteDurability

type

type WriteDurability =
| "none"
| "local"
| "global"
| "failed";

How far the latest write has travelled: none before any write settles, local once saved on this device (the resting state when sync is not configured), global once the store confirms it, failed when it was denied.

WriteLedgerEnvironment

type

type WriteLedgerEnvironment = {
storage: JournalStorage;
retryDelayMs?: (attempt: number) => number;
probeTimeoutMs?: number;
sweepIntervalMs?: number;
getDb(): Promise<Db>;
syncConfigured(): boolean;
resolveTable(name: string): ProbeTable | null;
resolveEffectUnit(name: string): EffectUnit<{ id: string }> | null;
subscribeRuntimeRecreation(listener: () => void): () => void;
updateDiagnostics(update: (diagnostics: RuntimeDiagnostics) => void): void;
now(): number;
guardWrite?(): Promise<(() => void) | void>;
};

Runtime seams used by the ledger; tests inject deterministic values.

WriteRejection

type

type WriteRejection = {
cause: "denied" | "expired";
code: string | null;
reason: string;
};

Why a write settled as rejected: the structured cause, code, and reason.

WriteStage

type

type WriteStage =
| "saving"
| "saved"
| "syncing"
| "synced"
| "rejected";

The closed, framework-owned write lifecycle. Stages are monotonic: saving → saved → syncing → synced | rejected. syncing is reserved for a runtime that can observe the transport; the current storage engine exposes no such signal, so today writes move from saved directly to synced or rejected and no handle ever reports syncing. Branch on saved vs settled, not on seeing syncing.