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.
| Parameter | Description |
|---|---|
config | The 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 inBootProgress.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;requestPwaInstallopens 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 callapplyPwaUpdate.applying— covers the swap until controlled tabs reload.failed— the check or installation failed; the cause is inPwaState.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.failurecarries 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.