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 getSchemaCompatState — data-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.