@tinycloud/replica 0.2.4-beta.3 → 0.3.0-beta.11

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/errors.ts","../../sdk-services/src/kv/replication/grantPolicyConstants.ts","../../sdk-services/src/kv/replication/contractConstants.ts","../src/browser/idb.ts","../src/kv-storage/gateKeys.ts","../src/kv-storage/sealed.ts","../src/kv-storage/schema.ts","../src/kv-storage/gateTx.ts","../src/kv-storage/lifecycle.ts","../src/kv-storage/testing.ts"],"sourcesContent":["/**\n * Typed replica errors. Every failure a caller can act on has its own code;\n * hosts (CLI, worker) map codes to their own exit codes or UI states.\n */\nexport const ReplicaErrorCode = {\n /** Another process holds the sync lease. */\n BUSY: \"REPLICA_BUSY\",\n /** No replica with this name exists. */\n NOT_FOUND: \"REPLICA_NOT_FOUND\",\n /** The replica exists but its stored configuration differs from the request. */\n CONFIG_MISMATCH: \"REPLICA_CONFIG_MISMATCH\",\n /** The runtime has no supported durable store (e.g. Node < 22.13). */\n RUNTIME_UNSUPPORTED: \"RUNTIME_UNSUPPORTED\",\n /** A required open() argument is missing or malformed (e.g. `principal`). */\n INVALID_ARGUMENT: \"REPLICA_INVALID_ARGUMENT\",\n /** The local store failed (I/O, corruption, schema). */\n STORAGE_ERROR: \"STORAGE_ERROR\",\n /** The local disk is full. */\n STORAGE_FULL: \"STORAGE_FULL\",\n /** The key is outside the replica's prefix. */\n NOT_COVERED: \"NOT_COVERED\",\n /** Syncing the secrets space or the vault namespace needs an explicit opt-in. */\n SECRETS_OPT_IN_REQUIRED: \"SECRETS_OPT_IN_REQUIRED\",\n /** No grant is installed, or none covers the requested scope. */\n GRANT_MISSING: \"GRANT_MISSING\",\n /** The grant bytes are not a verifiable UCAN, or its signature is wrong. */\n GRANT_INVALID: \"GRANT_INVALID\",\n /** SIWE/CACAO and other non-UCAN grants cannot be used for a replica. */\n GRANT_FORMAT_UNSUPPORTED: \"GRANT_FORMAT_UNSUPPORTED\",\n /** The grant was issued to a different device. */\n GRANT_AUDIENCE_MISMATCH: \"GRANT_AUDIENCE_MISMATCH\",\n /** The grant lacks `tinycloud.kv/sync` or `tinycloud.kv/get` on the prefix. */\n GRANT_NOT_COVERING: \"GRANT_NOT_COVERING\",\n GRANT_NOT_YET_VALID: \"GRANT_NOT_YET_VALID\",\n GRANT_EXPIRED: \"GRANT_EXPIRED\",\n /** The configured node does not advertise whole-space KV sync support. */\n REPLICATION_ROOT_UNSUPPORTED: \"REPLICATION_ROOT_UNSUPPORTED\",\n GRANT_REVOKED: \"GRANT_REVOKED\",\n /** The node refused the invocation for a reason other than revocation. */\n GRANT_UNAUTHORIZED: \"GRANT_UNAUTHORIZED\",\n /** The node refused the retention grant. */\n RETENTION_GRANT_REFUSED: \"RETENTION_GRANT_REFUSED\",\n /** The node could not be reached. */\n NETWORK_ERROR: \"NETWORK_ERROR\",\n /** The node answered with an unexpected status. */\n NODE_ERROR: \"NODE_ERROR\",\n /** The node's answer violates the wire contract. */\n PROTOCOL_ERROR: \"PROTOCOL_ERROR\",\n /** The node asked for a fresh bootstrap (410). Handled inside sync. */\n RESET_REQUIRED: \"RESET_REQUIRED\",\n /** The feed now comes from a different node than the one pinned. */\n SOURCE_CHANGED: \"SOURCE_CHANGED\",\n /** The feed delivered a key outside the replica's prefix. */\n SCOPE_VIOLATION: \"SCOPE_VIOLATION\",\n /** Fetched bytes do not hash to the ETag the node attested for them. */\n CONTENT_MISMATCH: \"CONTENT_MISMATCH\",\n /** A stored blob no longer hashes to its name. */\n INTEGRITY_ERROR: \"INTEGRITY_ERROR\",\n /** The replica handle was closed; in-flight calls settle with this code. */\n CLOSED: \"REPLICA_CLOSED\",\n /** The lifecycle gate refused the operation (shared contract `REPLICA_GATED`); `detail.gate` says why. */\n GATED: \"REPLICA_GATED\",\n /** Quota admission refused a page (`maxBytes` or the free-space floor); nothing committed. */\n STORAGE_BUDGET: \"STORAGE_BUDGET\",\n /** The consent deadline passed: the grant is not served until the user consents again. */\n CONSENT_EXPIRED: \"CONSENT_EXPIRED\",\n} as const;\n\nexport type ReplicaErrorCode = (typeof ReplicaErrorCode)[keyof typeof ReplicaErrorCode];\n\n/**\n * The package ships separate entry bundles (`.`, `./sqlite`), each with its own\n * copy of this class, so identity is a registered symbol, not `instanceof`.\n */\nconst REPLICA_ERROR = Symbol.for(\"tinycloud.replica.error\");\n\nexport class ReplicaError extends Error {\n readonly [REPLICA_ERROR] = true;\n readonly code: ReplicaErrorCode;\n readonly detail?: Record<string, unknown>;\n\n constructor(code: ReplicaErrorCode, message: string, detail?: Record<string, unknown>, options?: { cause?: unknown }) {\n super(message, options);\n this.name = \"ReplicaError\";\n this.code = code;\n if (detail !== undefined) this.detail = detail;\n }\n}\n\nexport function isReplicaError(error: unknown, code?: ReplicaErrorCode): error is ReplicaError {\n return (\n typeof error === \"object\" &&\n error !== null &&\n (error as { [REPLICA_ERROR]?: unknown })[REPLICA_ERROR] === true &&\n (code === undefined || (error as ReplicaError).code === code)\n );\n}\n\n/**\n * A lifecycle-gate refusal in the shared contract's shape:\n * `Error & { code: \"REPLICA_GATED\"; gate }`, with `gate` also in `detail`\n * so it survives the worker boundary.\n */\nexport function gatedError(gate: string, message: string): ReplicaError & { gate: string } {\n return Object.assign(new ReplicaError(ReplicaErrorCode.GATED, message, { gate }), { gate });\n}\n","/** Grant lifetime constants without browser storage identifiers. */\nconst HOUR_MS = 60 * 60 * 1000;\nconst DAY_MS = 24 * HOUR_MS;\n\n/** Default device-grant lifetime from install (24 h). All durations are milliseconds. */\nexport const DEFAULT_GRANT_TTL_MS = DAY_MS;\n/** `grantTtlMs` above this (7 d) is a config error: TypeError at construction. */\nexport const MAX_GRANT_TTL_MS = 7 * DAY_MS;\n/** Consent deadline = consent anchor + this window (7 d). */\nexport const CONSENT_WINDOW_MS = 7 * DAY_MS;\n/** Silent renewal starts when remaining effective lifetime < this fraction of the effective lifetime. */\nexport const RENEW_BELOW_FRACTION = 0.25;\n","/**\n * Frozen constants of the shared replica contract (TC-858 Phase 2, see CONTRACT.md).\n *\n * Exported via `@tinycloud/sdk-services/kv/replication/contractConstants`, never from the\n * package root. Runtime grant policy imports the numeric-only `grantPolicyConstants` subpath\n * instead, so the default web bundle does not carry browser storage identifiers.\n */\nexport {\n DEFAULT_GRANT_TTL_MS,\n MAX_GRANT_TTL_MS,\n CONSENT_WINDOW_MS,\n RENEW_BELOW_FRACTION,\n} from \"./grantPolicyConstants\";\n\n/** Error codes the contract adds or gives new meaning. Values equal keys. */\nexport const ReplicaContractCode = {\n /**\n * The lifecycle gate refused the operation: the handle's session stamp or principal no longer\n * matches the gate, a purge intent covers the partition, or the logout marker is set. The error\n * carries `gate: ReplicaGateReason`. Read-through answers from the network; nothing is erased.\n */\n REPLICA_GATED: \"REPLICA_GATED\",\n /**\n * Reserved for handles whose partition was erased (purge or revocation) after they opened. The\n * gate check runs first, so a handle that is both gated and erased reports REPLICA_GATED.\n */\n RESET_REQUIRED: \"RESET_REQUIRED\",\n /** A page was refused by quota admission (`maxBytes` or the free-space floor). Nothing committed. */\n STORAGE_BUDGET: \"STORAGE_BUDGET\",\n /** The platform store refused the write (IndexedDB `QuotaExceededError`, ENOSPC). Nothing committed. */\n STORAGE_FULL: \"STORAGE_FULL\",\n /** The consent deadline has passed: no grant may be minted, renewed or served until re-consent. */\n CONSENT_EXPIRED: \"CONSENT_EXPIRED\",\n /** The installed grant's effective expiry (not necessarily its signed `exp`) has passed. */\n GRANT_EXPIRED: \"GRANT_EXPIRED\",\n} as const;\nexport type ReplicaContractCode = (typeof ReplicaContractCode)[keyof typeof ReplicaContractCode];\n\n/** Browser free-space floor in bytes (64 MiB, `navigator.storage.estimate()`); not applied on node/CLI. */\nexport const DEFAULT_MIN_FREE_BYTES = 64 * 1024 * 1024;\n\n/** The single per-origin IndexedDB database of the browser `KVReplicaStorage` adapter. */\nexport const REPLICA_KV_DATABASE = \"tinycloud-replica-kv\";\n/** Origin database schema: v2 destructively replaces v1 plaintext replica-owned stores. */\nexport const REPLICA_KV_SCHEMA_VERSION = 2;\n\n/**\n * Object-store names in {@link REPLICA_KV_DATABASE}. Every store uses out-of-line keys (no\n * `keyPath`, no autoIncrement) so the schema upgrade can create control stores without knowing\n * their record shapes. Data stores are owned by the adapter (S1); control stores by the lifecycle\n * gate (S3) and quota ledger (S5).\n */\nexport const REPLICA_KV_STORES = {\n /** Data: partition meta (opaque fences, sealed config/grants), private gate-HMAC partition id. */\n meta: \"meta\",\n /** Data: sealed KV entries, key `[privatePartitionId, accountHmac(path)]`. */\n entries: \"entries\",\n /** Data: AES-GCM content blobs, key `[privatePartitionId, accountHmac(hash)]`. */\n blobs: \"blobs\",\n /** Data: AES-sealed pending state, key private gate-HMAC `identityKeyHash`. */\n pending: \"pending\",\n /** Data: sealed owner device, private nonextractable key records and opaque install anchors. */\n device: \"device\",\n /** Control: the account gate, singleton key `\"account\"`, value `ReplicaAccountGate`. */\n gate: \"gate\",\n /** Control: partition index keyed by private `partitionId`; nonunique `byLookupId` index. */\n partitions: \"partitions\",\n /** Control: purge intents, key `ReplicaPurgeIntent.id`, value `ReplicaPurgeIntent`. */\n purgeIntents: \"purgeIntents\",\n /** Control: quota ledger, keys owned by the ledger (S5). */\n quota: \"quota\",\n} as const;\n\n/** Synchronous, cross-tab `localStorage` marker set when the deny transaction and fallback delete both failed. */\nexport const REPLICA_LOGOUT_MARKER = \"tinycloud-replica:logout-pending\";\n\n/** Origin-wide deny floor outside the deletable replica database; JSON `{ epoch, deniedAt }`. */\nexport const REPLICA_DENY_TOMBSTONE = \"tinycloud-replica:deny-tombstone\";\n\n/** BroadcastChannel name used for lifecycle nudges and peer-commit notifications. */\nexport const REPLICA_KV_CHANNEL = \"tinycloud-replica-kv\";\n\n/** Web Lock name prefix for pending-write read-modify-write; suffix is the private gate-HMAC identity id. */\nexport const REPLICA_PENDING_LOCK_PREFIX = \"tinycloud-replica-kv:pending:\";\n\n/** Node/CLI gate file name. CLI places it at `<profile>/replica-gate.json`. */\nexport const REPLICA_GATE_FILE = \"replica-gate.json\";\n","/**\n * IndexedDB plumbing for the browser replica store: request promises and a\n * transaction-completer. A transaction's promise resolves on `oncomplete`\n * (never on request success — the SignatureCache mistake the design calls\n * out) and rejects on `onabort`/`onerror`.\n */\nimport { ReplicaError, ReplicaErrorCode } from \"../errors.js\";\n\nexport function requestAsPromise<T>(request: IDBRequest<T>): Promise<T> {\n const { promise, resolve, reject } = Promise.withResolvers<T>();\n request.onsuccess = () => resolve(request.result);\n request.onerror = () => reject(request.error ?? new DOMException(\"IndexedDB request failed\", \"UnknownError\"));\n return promise;\n}\n\n/**\n * Resolves when the transaction commits; rejects when it aborts or errors.\n * Listeners, not `on*` handlers, so several owners of one transaction (the\n * store and the lifecycle gate's `run`) can each await it.\n */\nexport function transactionDone(tx: IDBTransaction): Promise<void> {\n const { promise, resolve, reject } = Promise.withResolvers<void>();\n tx.addEventListener(\"complete\", () => resolve(), { once: true });\n tx.addEventListener(\"abort\", () => reject(tx.error ?? new DOMException(\"Transaction aborted\", \"AbortError\")), { once: true });\n tx.addEventListener(\"error\", () => reject(tx.error ?? new DOMException(\"Transaction failed\", \"UnknownError\")), { once: true });\n return promise;\n}\n\nexport function openDatabase(name: string, upgrade: (db: IDBDatabase) => void): Promise<IDBDatabase> {\n if (typeof indexedDB === \"undefined\") {\n return Promise.reject(\n new ReplicaError(\n ReplicaErrorCode.RUNTIME_UNSUPPORTED,\n \"IndexedDB is unavailable (private browsing or a non-browser runtime); the replica cannot persist.\",\n ),\n );\n }\n const { promise, resolve, reject } = Promise.withResolvers<IDBDatabase>();\n const request = indexedDB.open(name, 1);\n request.onupgradeneeded = () => upgrade(request.result);\n request.onsuccess = () => {\n const db = request.result;\n // A newer copy in another tab asks us to close; refuse silently so the\n // caller's transactions still work, then surface the version error to\n // the next opener instead of dropping writes mid-sync.\n db.onversionchange = () => db.close();\n resolve(db);\n };\n request.onerror = () => reject(request.error ?? new DOMException(`Opening ${name} failed`, \"UnknownError\"));\n request.onblocked = () =>\n reject(\n new ReplicaError(\n ReplicaErrorCode.BUSY,\n `Opening ${name} is blocked by another connection; close the replica in its other tab.`,\n ),\n );\n return promise;\n}\n\nconst byteEncoder = new TextEncoder();\n\n/** UTF-8 byte order for keys — the order SQLite, the node and `batchGet` share. */\nexport function compareKeyBytes(a: string, b: string): number {\n const x = byteEncoder.encode(a);\n const y = byteEncoder.encode(b);\n for (let index = 0; index < Math.min(x.length, y.length); index += 1) {\n if (x[index] !== y[index]) return x[index]! - y[index]!;\n }\n return x.length - y.length;\n}\n\ntype NamedError = { name: string };\n\nfunction hasName(error: unknown): error is NamedError {\n return (\n typeof error === \"object\" &&\n error !== null &&\n \"name\" in error &&\n typeof (error as Record<string, unknown>).name === \"string\"\n );\n}\n\n/** Map an IndexedDB failure to a replica error. Quota exceeded is STORAGE_FULL. */\nexport function idbError(error: unknown, action: string): ReplicaError {\n if (error instanceof ReplicaError) return error;\n const message = error instanceof Error ? error.message : String(error);\n if (hasName(error) && error.name === \"QuotaExceededError\") {\n return new ReplicaError(ReplicaErrorCode.STORAGE_FULL, `${action}: the browser's storage quota is exceeded.`, undefined, {\n cause: error,\n });\n }\n return new ReplicaError(ReplicaErrorCode.STORAGE_ERROR, `${action}: ${message}`, undefined, { cause: error });\n}\n","import type { ReplicaAccountGate } from \"@tinycloud/sdk-services/kv/replication\";\n\n/** Browser-only gate fields; node profiles keep the shared salt-based gate layout. */\nexport interface BrowserGate extends ReplicaAccountGate {\n keyId: string;\n accountKeyId: string;\n consentDeadline: number | null;\n}\n\nexport interface GateHmacRecord {\n v: 1;\n incarnation: string;\n keyId: string;\n hmac: CryptoKey;\n}\n\nexport interface AccountKeyRecord {\n v: 1;\n incarnation: string;\n lifecycleEpoch: number;\n principalHash: string;\n keyId: string;\n aesGcm: CryptoKey;\n hmac: CryptoKey;\n}\n\nexport const GATE_HMAC_KEY = \"gateHmac\";\nexport const ACCOUNT_KEY = \"accountKey\";\n\nconst HEX = \"0123456789abcdef\";\nconst ENCODER = new TextEncoder();\n\nfunction hex(bytes: Uint8Array): string {\n let value = \"\";\n for (const byte of bytes) value += HEX.charAt(byte >>> 4) + HEX.charAt(byte & 15);\n return value;\n}\n\nfunction keyId(): string {\n return hex(crypto.getRandomValues(new Uint8Array(16)));\n}\n\nexport async function newGateHmac(incarnation: string): Promise<GateHmacRecord> {\n const hmac = await crypto.subtle.generateKey({ name: \"HMAC\", hash: \"SHA-256\", length: 256 }, false, [\"sign\", \"verify\"]);\n return { v: 1, incarnation, keyId: keyId(), hmac };\n}\n\nexport async function newAccountKey(incarnation: string, lifecycleEpoch: number, principalHash: string): Promise<AccountKeyRecord> {\n const [aesGcm, hmac] = await Promise.all([\n crypto.subtle.generateKey({ name: \"AES-GCM\", length: 256 }, false, [\"encrypt\", \"decrypt\"]),\n crypto.subtle.generateKey({ name: \"HMAC\", hash: \"SHA-256\", length: 256 }, false, [\"sign\", \"verify\"]),\n ]);\n return { v: 1, incarnation, lifecycleEpoch, principalHash, keyId: keyId(), aesGcm, hmac };\n}\n\n/** Domain separation prevents one identifier class from revealing another's HMAC. */\nexport async function gateHash(key: CryptoKey, domain: \"principal\" | \"identity\" | \"partition\" | \"lookup\", material: string): Promise<string> {\n const bytes = ENCODER.encode(`${domain}\\0${material}`);\n return hex(new Uint8Array(await crypto.subtle.sign(\"HMAC\", key, bytes)));\n}\n\nfunction isHmac(key: unknown): key is CryptoKey {\n if (!(key instanceof CryptoKey) || key.extractable || key.algorithm.name !== \"HMAC\") return false;\n const algorithm = key.algorithm;\n return \"hash\" in algorithm && algorithm.hash !== null && typeof algorithm.hash === \"object\" &&\n \"name\" in algorithm.hash && algorithm.hash.name === \"SHA-256\" &&\n key.usages.length === 2 && key.usages.includes(\"sign\") && key.usages.includes(\"verify\");\n}\n\nfunction isAesGcm(key: unknown): key is CryptoKey {\n if (!(key instanceof CryptoKey) || key.extractable || key.algorithm.name !== \"AES-GCM\") return false;\n return \"length\" in key.algorithm && key.algorithm.length === 256 &&\n key.usages.length === 2 && key.usages.includes(\"encrypt\") && key.usages.includes(\"decrypt\");\n}\n\nexport function validGateHmac(record: GateHmacRecord | undefined, gate: BrowserGate): record is GateHmacRecord {\n return record?.v === 1 && record.incarnation === gate.incarnation && record.keyId === gate.keyId &&\n /^[0-9a-f]{32}$/.test(record.keyId) && isHmac(record.hmac);\n}\n\nexport function validAccountKey(record: AccountKeyRecord | undefined, gate: BrowserGate): record is AccountKeyRecord {\n return record?.v === 1 && record.incarnation === gate.incarnation && record.lifecycleEpoch === gate.lifecycleEpoch &&\n record.principalHash === gate.principalHash && record.keyId === gate.accountKeyId &&\n /^[0-9a-f]{32}$/.test(record.keyId) && isAesGcm(record.aesGcm) && isHmac(record.hmac);\n}\n","import { bytesToHex } from \"@noble/hashes/utils\";\n\nimport { ReplicaError, ReplicaErrorCode } from \"../errors.js\";\n\n/** v1 AES-256-GCM record: 96-bit fresh random IV per write, 128-bit tag; the account gate owns the nonextractable CryptoKey. */\nexport type SealedRecord = { v: 1; iv: Uint8Array; bytes: Uint8Array };\n\nconst encoder = new TextEncoder();\nconst NONCE_LENGTH = 12;\nconst TAG_LENGTH = 16;\n\n/** AAD binds format version, store, partition and row id; the caller supplies an unambiguous context. */\nfunction aad(context: string): Uint8Array {\n return encoder.encode(`tinycloud-replica-kv:v1\\u0000${context}`);\n}\n\nexport async function seal(key: CryptoKey, plaintext: Uint8Array, context: string): Promise<SealedRecord> {\n const iv = crypto.getRandomValues(new Uint8Array(NONCE_LENGTH));\n const bytes = new Uint8Array(await crypto.subtle.encrypt(\n { name: \"AES-GCM\", iv, additionalData: aad(context) }, key, plaintext,\n ));\n return { v: 1, iv, bytes };\n}\n\nexport async function openSealed(key: CryptoKey, record: unknown, context: string): Promise<Uint8Array> {\n if (record === null || typeof record !== \"object\" || !(\"v\" in record) || record.v !== 1\n || !(\"iv\" in record) || !(record.iv instanceof Uint8Array) || record.iv.length !== NONCE_LENGTH\n || !(\"bytes\" in record) || !(record.bytes instanceof Uint8Array) || record.bytes.length < TAG_LENGTH) {\n throw new ReplicaError(ReplicaErrorCode.STORAGE_ERROR, \"The replica record is unsealed or has an unknown ciphertext version.\");\n }\n try {\n return new Uint8Array(await crypto.subtle.decrypt(\n { name: \"AES-GCM\", iv: record.iv, additionalData: aad(context) }, key, record.bytes,\n ));\n } catch (cause) {\n throw new ReplicaError(ReplicaErrorCode.STORAGE_ERROR, \"The replica record failed authentication.\", undefined, { cause });\n }\n}\n\n/** A private, deterministic primary key. The gate's public salt never enters this derivation. */\nexport async function opaqueId(key: CryptoKey, store: string, partition: string, value: string): Promise<string> {\n const input = encoder.encode(JSON.stringify([\"tinycloud-replica-kv\", 1, store, partition, value]));\n return bytesToHex(new Uint8Array(await crypto.subtle.sign(\"HMAC\", key, input)));\n}\n\n/** Count the canonical UTF-8 encoding without allocating an encoded copy of a stored value. */\nfunction utf8Length(value: string): number {\n let bytes = 0;\n for (let i = 0; i < value.length; i += 1) {\n const code = value.charCodeAt(i);\n if (code < 0x80) bytes += 1;\n else if (code < 0x800) bytes += 2;\n else if (code >= 0xd800 && code <= 0xdbff && i + 1 < value.length\n && value.charCodeAt(i + 1) >= 0xdc00 && value.charCodeAt(i + 1) <= 0xdfff) {\n bytes += 4;\n i += 1;\n } else bytes += 3;\n }\n return bytes;\n}\n\ntype MeasuredRow = Partial<SealedRecord> & {\n pid?: string;\n hashId?: string;\n size?: number;\n revision?: number;\n generation?: number;\n coverageCode?: number;\n syncedThroughEpoch?: number;\n keys?: number;\n contentMissing?: number;\n tombstones?: number;\n contentBytes?: number;\n purged?: boolean;\n};\n\n/** Canonical application-encoded bytes for the final persisted row, not IDB engine allocation. */\nexport function measureSealedRow(key: IDBValidKey, row: MeasuredRow): number {\n if (row.purged === true\n ? row.v !== undefined || row.iv !== undefined || row.bytes !== undefined\n : row.v !== 1 || !(row.iv instanceof Uint8Array) || !(row.bytes instanceof Uint8Array)) {\n throw new ReplicaError(ReplicaErrorCode.STORAGE_ERROR, \"The replica row has an invalid sealed envelope.\");\n }\n const primaryKey = JSON.stringify(key);\n if (typeof primaryKey !== \"string\") {\n throw new ReplicaError(ReplicaErrorCode.STORAGE_ERROR, \"The replica row has an invalid primary key.\");\n }\n let total = 2 + 4 + utf8Length(primaryKey);\n let hasSize = false;\n for (const name in row) {\n if (!Object.hasOwn(row, name)) continue;\n const value: unknown = Reflect.get(row, name);\n total += 1 + name.length; // All persisted field names below are ASCII.\n switch (name) {\n case \"pid\":\n case \"hashId\":\n if (typeof value !== \"string\") throw new ReplicaError(ReplicaErrorCode.STORAGE_ERROR, \"The replica row has an invalid identifier.\");\n total += 4 + utf8Length(value);\n break;\n case \"v\":\n if (value !== 1) throw new ReplicaError(ReplicaErrorCode.STORAGE_ERROR, \"The replica row has an unknown version.\");\n total += 1;\n break;\n case \"coverageCode\":\n if (value !== 0 && value !== 1 && value !== 2) throw new ReplicaError(ReplicaErrorCode.STORAGE_ERROR, \"The replica row has invalid coverage.\");\n total += 1;\n break;\n case \"purged\":\n if (typeof value !== \"boolean\") throw new ReplicaError(ReplicaErrorCode.STORAGE_ERROR, \"The replica row has invalid purge state.\");\n total += 1;\n break;\n case \"revision\":\n case \"generation\":\n case \"syncedThroughEpoch\":\n case \"keys\":\n case \"contentMissing\":\n case \"tombstones\":\n case \"contentBytes\":\n case \"size\":\n if (typeof value !== \"number\" || !Number.isFinite(value)) {\n throw new ReplicaError(ReplicaErrorCode.STORAGE_ERROR, \"The replica row has an invalid numeric field.\");\n }\n if (name === \"size\") hasSize = true;\n total += 8;\n break;\n case \"iv\":\n case \"bytes\":\n if (!(value instanceof Uint8Array)) throw new ReplicaError(ReplicaErrorCode.STORAGE_ERROR, \"The replica row has invalid ciphertext.\");\n total += 4 + value.byteLength;\n break;\n default:\n throw new ReplicaError(ReplicaErrorCode.STORAGE_ERROR, \"The replica row has an unknown persisted field.\");\n }\n }\n if (!hasSize) total += 1 + \"size\".length + 8;\n return total;\n}\n","/**\n * The origin database of `@tinycloud/replica/kv-storage` (TC-858 Phase 2\n * plan §3): ONE IndexedDB database per origin, `tinycloud-replica-kv`, so\n * that a page commit, its quota accounting and its lifecycle-gate check, or\n * an erase and its purge-intent update, each run as a single transaction.\n *\n * Stores (names and keys frozen in the shared contract; every store keys\n * out-of-line):\n * meta — one sealed state row per partition, key partitionId\n * entries — key [pid, opaque key HMAC]; index byHash on [pid, opaque hash HMAC]\n * blobs — key [pid, opaque hash HMAC]; index bySize on [pid, ciphertext row size]\n * pending — one sealed PendingWriteState per identity, key private identity HMAC\n * device — sealed owner device and grant install timestamps; gate crypto keys\n * gate, partitions, purgeIntents — lifecycle control stores; partition lookup index byLookupId\n * quota — origin-wide measured ciphertext byte ledger\n *\n * The RC1 `openReplica` databases (`tinycloud-replica/<id>`) are separate\n * and untouched.\n */\nimport { replicationIdentityKey, type ReplicaPartitionEraser, type ReplicationIdentity } from \"@tinycloud/sdk-services/kv/replication\";\nimport { REPLICA_KV_DATABASE, REPLICA_KV_STORES } from \"@tinycloud/sdk-services/kv/replication/contractConstants\";\n\nimport { principalOf } from \"../did.js\";\nimport { ReplicaError, ReplicaErrorCode } from \"../errors.js\";\nimport { requestAsPromise } from \"../browser/idb.js\";\nimport type { ReplicaStoreLayout } from \"../browser/store.js\";\nimport { gateHash } from \"./gateKeys.js\";\nimport { measureSealedRow } from \"./sealed.js\";\n\nexport const KV_DATABASE = REPLICA_KV_DATABASE;\nexport const KV_DATABASE_VERSION = 2;\nexport const KV_STORE = REPLICA_KV_STORES;\n\n/**\n * `device` store keys `firstInstall:<identityKeyHash>` → `{ at }` (ms): the\n * `first_install` consent anchor of an identity whose session has neither\n * SIWE nor delegation `iat`/`nbf`. Written set-if-absent by the install\n * transaction; survives partition reset, purge and reinstall; only the\n * lifecycle gate's `deny` deletes the range.\n */\nexport const FIRST_INSTALL_KEY_PREFIX = \"firstInstall:\";\n/** CID install timestamps live outside partitions, so purge/reinstall cannot refresh a CID. */\nexport const GRANT_INSTALL_KEY_PREFIX = \"grantInstall:\";\n\n\n/** The scope a partition erase (and the identity-scoped pending erase) needs. */\nexport const ERASE_STORES: readonly string[] = [KV_STORE.meta, KV_STORE.entries, KV_STORE.blobs, KV_STORE.pending];\n\nconst READ_SCOPE: readonly string[] = [KV_STORE.gate, KV_STORE.purgeIntents];\nconst WRITE_SCOPE: readonly string[] = [KV_STORE.gate, KV_STORE.partitions, KV_STORE.purgeIntents, KV_STORE.quota, KV_STORE.device];\n\nfunction createStores(db: IDBDatabase): void {\n db.createObjectStore(KV_STORE.meta);\n const entries = db.createObjectStore(KV_STORE.entries);\n entries.createIndex(\"byHash\", [\"pid\", \"hash\"], { unique: false });\n const blobs = db.createObjectStore(KV_STORE.blobs);\n blobs.createIndex(\"bySize\", [\"pid\", \"size\"], { unique: false });\n const partitions = db.createObjectStore(KV_STORE.partitions);\n partitions.createIndex(\"byLookupId\", \"lookupId\", { unique: false });\n for (const name of [KV_STORE.pending, KV_STORE.device, KV_STORE.gate, KV_STORE.purgeIntents, KV_STORE.quota]) {\n db.createObjectStore(name);\n }\n}\n\n/**\n * Private gate-HMAC identity id (`pending` key, `firstInstall:` suffix and\n * pending Web Lock suffix). Never derive account identifiers from the public\n * gate salt; precompute them before opening a transaction.\n */\nexport function identityKeyHashOf(identity: ReplicationIdentity, gateHmac: CryptoKey): Promise<string> {\n return gateHash(gateHmac, \"identity\", replicationIdentityKey(identity));\n}\n\n/**\n * Open the origin database. With `create: false` a missing database stays\n * missing — the upgrade that would create it is aborted — and the result\n * is null; inspection and purge use this so they never create storage.\n * Every connection closes itself on `versionchange`, so a\n * `deleteDatabase` from any tab is never blocked by an idle connection.\n */\nexport function openKvDatabase(options: { create: boolean }): Promise<IDBDatabase | null> {\n if (typeof indexedDB === \"undefined\") {\n return Promise.reject(\n new ReplicaError(\n ReplicaErrorCode.RUNTIME_UNSUPPORTED,\n \"IndexedDB is unavailable (private browsing or a non-browser runtime); the replica cannot persist.\",\n ),\n );\n }\n const { promise, resolve, reject } = Promise.withResolvers<IDBDatabase | null>();\n const request = options.create ? indexedDB.open(KV_DATABASE, KV_DATABASE_VERSION) : indexedDB.open(KV_DATABASE);\n let refused = false;\n request.onupgradeneeded = (event) => {\n if (!options.create && event.oldVersion === 0) {\n refused = true;\n request.transaction?.abort();\n return;\n }\n if (event.oldVersion > 0) {\n // v1 stored plaintext values and recognizable IndexedDB keys. No data or\n // account gate from that format can be retained in the ciphertext layout.\n for (const name of Array.from(request.result.objectStoreNames)) request.result.deleteObjectStore(name);\n }\n createStores(request.result);\n };\n request.onsuccess = () => {\n const db = request.result;\n if (db.version !== KV_DATABASE_VERSION) {\n db.close();\n resolve(null);\n return;\n }\n db.onversionchange = () => db.close();\n resolve(db);\n };\n request.onerror = (event) => {\n if (refused) {\n // The aborted upgrade rolls the creation back; this is \"missing\", not a failure.\n event.preventDefault();\n resolve(null);\n return;\n }\n reject(request.error ?? new DOMException(`Opening ${KV_DATABASE} failed`, \"UnknownError\"));\n };\n request.onblocked = () =>\n reject(new ReplicaError(ReplicaErrorCode.BUSY, `Opening ${KV_DATABASE} is blocked by an older connection in another tab.`));\n return promise;\n}\n\n/**\n * Partition id (and opaque `replicaId`): gate-HMAC over the canonical\n * identity, folder prefix and device principal. Dropping the DID fragment\n * makes two names of one key share a partition. Derive before any transaction.\n */\nexport function partitionIdOf(input: { identity: ReplicationIdentity; prefix: string; deviceDid: string }, gateHmac: CryptoKey): Promise<string> {\n return gateHash(gateHmac, \"partition\", `${replicationIdentityKey(input.identity)}\\n${input.prefix}\\n${principalOf(input.deviceDid)}`);\n}\n\n/** The partition-scoped key layout `IndexedDbReplicaStore` runs over. */\nexport function kvLayout(partitionId: string): ReplicaStoreLayout {\n return {\n partitionId,\n readScope: READ_SCOPE,\n writeScope: WRITE_SCOPE,\n metaKey: partitionId,\n outOfLineKeys: true,\n entryKey: (key) => [partitionId, key],\n blobKey: (hash) => [partitionId, hash],\n blobHashOf: (key) => (Array.isArray(key) && key[0] === partitionId && typeof key[1] === \"string\" ? key[1] : undefined),\n // Arrays sort after every string and number, so [pid, []] bounds every [pid, <string|number>].\n range: IDBKeyRange.bound([partitionId], [partitionId, []]),\n tag: { pid: partitionId },\n blobSizeIndex: \"bySize\",\n };\n}\n\n/** Delete ciphertext rows by opaque partition range without needing the account's shredded key. */\nfunction eraseSealedRows(tx: IDBTransaction, name: string, partitionId: string): Promise<number> {\n const { promise, resolve, reject } = Promise.withResolvers<number>();\n const range = IDBKeyRange.bound([partitionId], [partitionId, []]);\n const request = tx.objectStore(name).openCursor(range);\n let removed = 0;\n request.onsuccess = () => {\n const cursor = request.result;\n if (cursor === null) return resolve(removed);\n try {\n const row: unknown = cursor.value;\n if (row === null || typeof row !== \"object\" || !(\"pid\" in row) || row.pid !== partitionId ||\n !(\"size\" in row) || typeof row.size !== \"number\") {\n throw new ReplicaError(ReplicaErrorCode.STORAGE_ERROR, \"The encrypted replica row has an invalid partition or byte measure.\");\n }\n const measured = measureSealedRow(cursor.primaryKey, row as Parameters<typeof measureSealedRow>[1]);\n if (row.size !== measured || !Number.isSafeInteger(removed + measured)) {\n throw new ReplicaError(ReplicaErrorCode.STORAGE_ERROR, \"The encrypted replica row has an invalid byte measure.\");\n }\n removed += measured;\n cursor.delete();\n cursor.continue();\n } catch (error) {\n reject(error);\n }\n };\n request.onerror = () => reject(request.error);\n return promise;\n}\n\n/**\n * The lifecycle erase seam. Entries/blobs are deleted and encrypted metadata\n * becomes a contentless generation tombstone: stale handles reject RESET_REQUIRED\n * without requiring an account key after deny. `bytes` is the exact net measured\n * row release, including metadata replacement, for idempotent quota accounting.\n */\nexport const kvPartitionEraser: ReplicaPartitionEraser<IDBTransaction> = {\n async erasePartition(tx, partitionId) {\n const meta = tx.objectStore(KV_STORE.meta);\n const old: unknown = await requestAsPromise(meta.get(partitionId));\n if (old !== undefined && (old === null || typeof old !== \"object\" ||\n !(\"pid\" in old) || old.pid !== partitionId ||\n !(\"revision\" in old) || typeof old.revision !== \"number\" || !Number.isSafeInteger(old.revision) || old.revision < 0 ||\n !(\"generation\" in old) || typeof old.generation !== \"number\" || !Number.isSafeInteger(old.generation) || old.generation < 0 ||\n !(\"size\" in old) || typeof old.size !== \"number\" || old.size !== measureSealedRow(partitionId, old as Parameters<typeof measureSealedRow>[1]))) {\n throw new ReplicaError(ReplicaErrorCode.STORAGE_ERROR, \"The encrypted replica metadata is invalid.\");\n }\n const [entries, blobs] = await Promise.all([\n eraseSealedRows(tx, KV_STORE.entries, partitionId),\n eraseSealedRows(tx, KV_STORE.blobs, partitionId),\n ]);\n if (old === undefined) return { bytes: entries + blobs };\n const current = old as { pid: string; revision: number; generation: number; size: number; purged?: boolean };\n if (current.purged === true) {\n if (entries !== 0 || blobs !== 0) throw new ReplicaError(ReplicaErrorCode.STORAGE_ERROR, \"An erased replica still owns ciphertext rows.\");\n return { bytes: 0 };\n }\n const tombstone = {\n pid: partitionId, revision: current.revision + 1, generation: current.generation + 1,\n coverageCode: 0 as const, syncedThroughEpoch: 0, keys: 0,\n contentMissing: 0, tombstones: 0, contentBytes: 0, purged: true as const, size: 0,\n };\n tombstone.size = measureSealedRow(partitionId, tombstone);\n const bytes = current.size + entries + blobs - tombstone.size;\n if (!Number.isSafeInteger(bytes) || bytes < 0) throw new ReplicaError(ReplicaErrorCode.STORAGE_ERROR, \"The erased replica has an invalid byte delta.\");\n meta.put(tombstone, partitionId);\n return { bytes };\n },\n async eraseIdentityPending(tx, identityKeyHash) {\n tx.objectStore(KV_STORE.pending).delete(identityKeyHash);\n },\n};\n","/**\n * The browser `ReplicaGateTx` over the origin database's control stores\n * (`gate`, `partitions`, `purgeIntents`, `quota`). Every data transaction goes\n * through `run`, which reads the gate record as its FIRST request and refuses\n * — aborting, so nothing commits — unless the incarnation, the lifecycle epoch\n * and the principal hash all match, and no purge intent covers the partition\n * or identity. `run` resolves only after `oncomplete`.\n *\n * Erase transactions are exempt from `run`: they call `indexPartition`,\n * `unindexPartition` and `clearIntent` on their own transaction.\n */\nimport type {\n ReplicaBrowserGateAccess,\n ReplicaBrowserGateSnapshot,\n ReplicaBrowserGateTx,\n ReplicaGateReason,\n ReplicaGateScope,\n ReplicaPartitionIndexEntry,\n ReplicaDenyOutcome,\n} from \"@tinycloud/sdk-services/kv/replication\";\nimport { ReplicaError, ReplicaErrorCode, gatedError } from \"../errors.js\";\nimport { requestAsPromise, transactionDone } from \"../browser/idb.js\";\nimport { ACCOUNT_KEY, GATE_HMAC_KEY, validAccountKey, validGateHmac, type AccountKeyRecord, type BrowserGate, type GateHmacRecord } from \"./gateKeys.js\";\nimport { KV_STORE } from \"./schema.js\";\n\nconst ACCOUNT = \"account\";\nconst CONTROL = [KV_STORE.gate, KV_STORE.partitions, KV_STORE.purgeIntents, KV_STORE.quota, KV_STORE.device];\n\n/**\n * Opens the origin database. `create` false yields `null` when it does not\n * exist; `create` true creates it (the first `run` may precede any schema).\n */\nexport type KvDatabaseOpener = (create: boolean) => Promise<IDBDatabase | null>;\n\nfunction refusal(reason: ReplicaGateReason): Error {\n return gatedError(reason, `The replica gate refused: ${reason}.`);\n}\n\nexport async function withDatabase<T>(open: KvDatabaseOpener, create: boolean, use: (db: IDBDatabase) => Promise<T>): Promise<T | null> {\n const db = await open(create);\n if (db === null) return null;\n try {\n return await use(db);\n } finally {\n db.close();\n }\n}\n\n/** Browser IDs and key ids are precomputed before opening a data transaction. */\nexport type BrowserGateAccess = ReplicaBrowserGateAccess;\nexport type BrowserGateSnapshot = ReplicaBrowserGateSnapshot;\n\nexport interface BrowserReplicaGateTx extends ReplicaBrowserGateTx<IDBTransaction> {\n snapshot(): Promise<BrowserGateSnapshot | null>;\n readAccountKey(access: BrowserGateAccess): Promise<AccountKeyRecord>;\n}\n\nexport function createIndexedDbGateTx(\n open: KvDatabaseOpener,\n options: { now?: () => number; onConsentExpired: () => Promise<ReplicaDenyOutcome> },\n): BrowserReplicaGateTx {\n if (typeof options?.onConsentExpired !== \"function\") throw refusal(\"gate_unavailable\");\n const now = options.now ?? Date.now;\n function checkedNow(): number {\n const at = now();\n if (!Number.isSafeInteger(at) || at < 0) throw refusal(\"gate_unavailable\");\n return at;\n }\n\n let expiryFlight: Promise<ReplicaDenyOutcome> | undefined;\n\n async function snapshot(): Promise<BrowserGateSnapshot | null> {\n return withDatabase(open, false, async (db) => {\n const tx = db.transaction([KV_STORE.gate, KV_STORE.device]);\n const gate = await requestAsPromise<BrowserGate | undefined>(tx.objectStore(KV_STORE.gate).get(ACCOUNT));\n if (gate === undefined || gate.principalHash === null) return null;\n if (typeof gate.consentDeadline !== \"number\" || !Number.isSafeInteger(gate.consentDeadline) || gate.consentDeadline < 0) throw refusal(\"gate_unavailable\");\n const hash = await requestAsPromise<GateHmacRecord | undefined>(tx.objectStore(KV_STORE.device).get(GATE_HMAC_KEY));\n if (!validGateHmac(hash, gate)) throw refusal(\"gate_unavailable\");\n return { incarnation: gate.incarnation, salt: gate.salt, lifecycleEpoch: gate.lifecycleEpoch, keyId: hash.keyId, accountKeyId: gate.accountKeyId, gateHmac: hash.hmac };\n });\n }\n\n async function expire(access: Pick<BrowserGateAccess, \"stamp\" | \"incarnation\">): Promise<void> {\n let flight: Promise<ReplicaDenyOutcome> | undefined;\n try {\n flight = expiryFlight ?? options.onConsentExpired();\n expiryFlight = flight;\n const outcome = await flight;\n if (outcome.status !== \"denied\" && outcome.status !== \"deleted\" && outcome.status !== \"stale\") throw refusal(\"gate_unavailable\");\n // `stale` can also mean the main clock has not reached the deadline. Verify that\n // the old incarnation/epoch was actually removed before reporting expiry.\n const gate = await withDatabase(open, false, (db) =>\n requestAsPromise<BrowserGate | undefined>(db.transaction(KV_STORE.gate).objectStore(KV_STORE.gate).get(ACCOUNT)),\n );\n if (gate !== null && (gate === undefined ||\n (gate.incarnation === access.incarnation && (gate.lifecycleEpoch <= access.stamp.lifecycleEpoch || gate.principalHash !== null)))) {\n throw refusal(\"gate_unavailable\");\n }\n } catch {\n throw refusal(\"gate_unavailable\");\n } finally {\n if (flight !== undefined && expiryFlight === flight) expiryFlight = undefined;\n }\n }\n\n async function runChecked<T>(scope: ReplicaGateScope, access: BrowserGateAccess, fn: (tx: IDBTransaction, account: AccountKeyRecord) => Promise<T>): Promise<T> {\n if (access === null || typeof access !== \"object\" || access.stamp === null || typeof access.stamp !== \"object\") {\n throw refusal(\"not_established\");\n }\n if (!(\"principalHash\" in access) || typeof access.principalHash !== \"string\" ||\n !(\"keyId\" in access) || typeof access.keyId !== \"string\" ||\n !(\"accountKeyId\" in access) || typeof access.accountKeyId !== \"string\") throw refusal(\"gate_unavailable\");\n try {\n const result = await withDatabase(open, false, async (db) => {\n const tx = db.transaction([...scope.stores, ...CONTROL], scope.mode, { durability: \"strict\" });\n const done = transactionDone(tx);\n try {\n // The first request is always the gate. No WebCrypto work occurs inside this transaction.\n const gate = await requestAsPromise<BrowserGate | undefined>(tx.objectStore(KV_STORE.gate).get(ACCOUNT));\n if (gate === undefined) throw refusal(\"not_established\");\n if (gate.incarnation !== access.incarnation || gate.incarnation !== access.stamp.incarnation ||\n gate.lifecycleEpoch !== access.stamp.lifecycleEpoch || gate.principalHash === null) throw refusal(\"stamp_stale\");\n if (gate.keyId !== access.keyId || gate.accountKeyId !== access.accountKeyId) throw refusal(\"stamp_stale\");\n if (gate.principalHash !== access.principalHash) throw refusal(\"principal_mismatch\");\n if (typeof gate.consentDeadline !== \"number\" || !Number.isSafeInteger(gate.consentDeadline) || gate.consentDeadline < 0) throw refusal(\"gate_unavailable\");\n\n const device = tx.objectStore(KV_STORE.device);\n const hash = await requestAsPromise<GateHmacRecord | undefined>(device.get(GATE_HMAC_KEY));\n const account = await requestAsPromise<AccountKeyRecord | undefined>(device.get(ACCOUNT_KEY));\n if (!validGateHmac(hash, gate) || !validAccountKey(account, gate)) throw refusal(\"gate_unavailable\");\n const intents = tx.objectStore(KV_STORE.purgeIntents);\n if (access.partitionId !== undefined && (await requestAsPromise(intents.count(access.partitionId))) > 0) throw refusal(\"purge_pending\");\n if (access.identityKeyHash !== undefined && (await requestAsPromise(intents.count(`pending:${access.identityKeyHash}`))) > 0) throw refusal(\"purge_pending\");\n if (checkedNow() >= gate.consentDeadline) throw new ReplicaError(ReplicaErrorCode.CONSENT_EXPIRED, \"The account consent has expired.\");\n const value = await fn(tx, account);\n await done;\n return { value };\n } catch (error) {\n try {\n tx.abort();\n } catch {\n // Already settled.\n }\n await done.catch(() => undefined);\n throw error;\n }\n });\n if (result === null) throw refusal(\"gate_unavailable\");\n return result.value;\n } catch (error) {\n if (error !== null && typeof error === \"object\" && \"code\" in error && error.code === ReplicaErrorCode.CONSENT_EXPIRED) {\n await expire(access);\n }\n throw error;\n }\n }\n\n return {\n snapshot,\n run: (scope, access, fn) => runChecked(scope, access, (tx) => fn(tx)),\n readAccountKey: (access) => runChecked({ stores: [KV_STORE.device], mode: \"readonly\" }, access, async (_tx, account) => account),\n\n async indexPartition(tx: IDBTransaction, entry: ReplicaPartitionIndexEntry): Promise<void> {\n tx.objectStore(KV_STORE.partitions).put(entry, entry.partitionId);\n },\n\n async unindexPartition(tx: IDBTransaction, partitionId: string): Promise<void> {\n tx.objectStore(KV_STORE.partitions).delete(partitionId);\n },\n\n async clearIntent(tx: IDBTransaction, id: string): Promise<void> {\n tx.objectStore(KV_STORE.purgeIntents).delete(id);\n },\n };\n}\n","/**\n * The browser `ReplicaLifecycleGate` (CONTRACT.md lifecycle): sign-in stamping, deny with purge\n * intents, ordered erase and startup recovery over the origin database's control stores\n * (`gate`, `partitions`, `purgeIntents`, `device`).\n *\n * Main thread only: `deliverable` and the logout marker/tombstone live in `localStorage`, which\n * workers don't have. Gate-mutating paths (establish, deny, recovery delete) take the Web Lock\n * `GATE_LOCK`; without it no mutation proceeds. Every transaction commits whole or aborts.\n */\nimport type {\n ReplicaConsentAnchor,\n ReplicaDeliveryDecision,\n ReplicaDenyInput,\n ReplicaDenyOutcome,\n ReplicaEraseReport,\n ReplicaLifecycleGate,\n ReplicaPartitionEraser,\n ReplicaPartitionIndexEntry,\n ReplicaPurgeIntent,\n ReplicaQuotaLedger,\n ReplicaSessionStamp,\n} from \"@tinycloud/sdk-services/kv/replication\";\nimport { CONSENT_WINDOW_MS, REPLICA_DENY_TOMBSTONE, REPLICA_LOGOUT_MARKER } from \"@tinycloud/sdk-services/kv/replication/contractConstants\";\n\nimport { ReplicaError, ReplicaErrorCode, gatedError } from \"../errors.js\";\nimport { requestAsPromise, transactionDone } from \"../browser/idb.js\";\nimport { ACCOUNT_KEY, GATE_HMAC_KEY, gateHash, newAccountKey, newGateHmac, validAccountKey, validGateHmac, type AccountKeyRecord, type BrowserGate, type GateHmacRecord } from \"./gateKeys.js\";\nimport { createIndexedDbGateTx, withDatabase } from \"./gateTx.js\";\nimport { ERASE_STORES, FIRST_INSTALL_KEY_PREFIX, KV_DATABASE, KV_STORE, kvPartitionEraser, openKvDatabase } from \"./schema.js\";\n\ntype Gate = BrowserGate;\ntype Tx = IDBTransaction;\ntype Stamp = { incarnation: string; lifecycleEpoch: number; consentDeadline: number; principal: string };\ntype DeleteOutcome = { status: \"deleted\" } | { status: \"failed\"; code: string };\ntype Tombstone = { epoch: number; deniedAt: number };\n\nconst ACCOUNT = \"account\";\nconst DEVICE_KEY = \"device\";\nconst GRANT_INSTALL_KEY_PREFIX = \"grantInstall:\";\nconst GATE_LOCK = \"tinycloud-replica-kv:gate\";\nconst DELETE_BLOCKED = \"DELETE_BLOCKED\";\n\n\nexport type DenyFaultPoint = \"deny_tx\" | \"delete_database\";\n\n/**\n * Test seam. `@tinycloud/replica/kv-storage/testing` plans a failure here; production leaves it\n * empty. `once` (default true) removes the plan after its first use.\n */\nexport const denyFaults: Partial<Record<DenyFaultPoint, { once: boolean }>> = {};\n\nfunction takeFault(point: DenyFaultPoint): boolean {\n const fault = denyFaults[point];\n if (fault === undefined) return false;\n if (fault.once) delete denyFaults[point];\n return true;\n}\n\nfunction billedDeviceBytes(row: unknown): number {\n if (row === undefined || (typeof row === \"number\" && Number.isSafeInteger(row) && row >= 0)) return 0;\n if (row !== null && typeof row === \"object\" && \"size\" in row &&\n typeof row.size === \"number\" && Number.isSafeInteger(row.size) && row.size >= 0) {\n return row.size;\n }\n throw gatedError(\"gate_unavailable\", \"A device quota row has no valid encoded size.\");\n}\n\nfunction openKvStore(create: boolean): Promise<IDBDatabase | null> {\n return openKvDatabase({ create });\n}\n\n/** Test-only presence probe. Never returns, clones or exports CryptoKey material. */\nexport async function inspectReplicaKeyPresence(): Promise<{ gateHmac: boolean; accountKey: boolean }> {\n const db = await openKvStore(false);\n if (db === null) return { gateHmac: false, accountKey: false };\n try {\n const device = db.transaction(KV_STORE.device).objectStore(KV_STORE.device);\n const gateHmac = (await requestAsPromise<number>(device.count(GATE_HMAC_KEY))) === 1;\n const accountKey = (await requestAsPromise<number>(device.count(ACCOUNT_KEY))) === 1;\n return { gateHmac, accountKey };\n } finally {\n db.close();\n }\n}\n\ninterface LegacyGateFloor {\n epoch: number;\n deniedAt: number | null;\n}\n\n/** Versionless, readonly v1 probe; no data row is read and a missing DB is never created. */\nasync function readLegacyGateFloor(): Promise<LegacyGateFloor | null> {\n const { promise, resolve, reject } = Promise.withResolvers<LegacyGateFloor | null>();\n const request = indexedDB.open(KV_DATABASE);\n let absent = false;\n request.onupgradeneeded = () => {\n absent = true;\n request.transaction?.abort();\n };\n request.onsuccess = async () => {\n const db = request.result;\n try {\n if (db.version !== 1) return resolve(null);\n if (!db.objectStoreNames.contains(KV_STORE.gate)) return resolve({ epoch: 0, deniedAt: null });\n const value = await requestAsPromise<unknown>(db.transaction(KV_STORE.gate).objectStore(KV_STORE.gate).get(ACCOUNT));\n if (value === undefined) return resolve({ epoch: 0, deniedAt: null });\n if (value === null || typeof value !== \"object\" || !(\"lifecycleEpoch\" in value) || !(\"deniedAt\" in value) ||\n typeof value.lifecycleEpoch !== \"number\" || !Number.isSafeInteger(value.lifecycleEpoch) || value.lifecycleEpoch < 0 ||\n (value.deniedAt !== null && (typeof value.deniedAt !== \"number\" || !Number.isSafeInteger(value.deniedAt) || value.deniedAt < 0)) ||\n (value.lifecycleEpoch === 0) !== (value.deniedAt === null)) {\n throw gatedError(\"gate_unavailable\", \"The legacy gate deny floor is invalid.\");\n }\n resolve({ epoch: value.lifecycleEpoch, deniedAt: value.deniedAt });\n } catch (error) {\n reject(error);\n } finally {\n db.close();\n }\n };\n request.onerror = (event) => {\n if (absent) {\n event.preventDefault();\n resolve(null);\n } else {\n reject(gatedError(\"gate_unavailable\", \"The legacy gate deny floor cannot be read.\"));\n }\n };\n request.onblocked = () => reject(gatedError(\"gate_unavailable\", \"The legacy gate read is blocked.\"));\n return promise;\n}\n\n/** Preserve a legacy denial outside the store before v2 versionchange can destroy its gate. */\nfunction preserveLegacyFloor(legacy: LegacyGateFloor | null, tombstone: Tombstone | null): { tombstone: Tombstone | null; advanced: boolean } {\n if (legacy?.deniedAt === null || legacy === null) return { tombstone, advanced: false };\n const floor = {\n epoch: Math.max(legacy.epoch, tombstone?.epoch ?? 0),\n deniedAt: Math.max(legacy.deniedAt, tombstone?.deniedAt ?? 0),\n };\n if (tombstone !== null && tombstone.epoch >= floor.epoch && tombstone.deniedAt >= floor.deniedAt) {\n return { tombstone, advanced: false };\n }\n writeTombstone(floor);\n return { tombstone: floor, advanced: true };\n}\n\nfunction codeOf(error: unknown): string {\n if (error !== null && typeof error === \"object\" && \"code\" in error && error.code === \"REPLICA_GATED\" && \"gate\" in error && typeof error.gate === \"string\") return error.gate;\n return error !== null && typeof error === \"object\" && \"code\" in error && typeof error.code === \"string\" ? error.code : \"STORAGE_ERROR\";\n}\n\n/** Fail closed: no readable `localStorage` means no proof the logout completed. */\nfunction logoutMarkerSet(): boolean {\n try {\n return localStorage.getItem(REPLICA_LOGOUT_MARKER) !== null;\n } catch {\n return true;\n }\n}\n\n/** Missing is valid only before the first deny. Corruption or denied storage is not. */\nfunction readTombstone(): Tombstone | null {\n try {\n const stored = localStorage.getItem(REPLICA_DENY_TOMBSTONE);\n if (stored === null) return null;\n const value: unknown = JSON.parse(stored);\n if (\n value === null || typeof value !== \"object\" ||\n !(\"epoch\" in value) || typeof value.epoch !== \"number\" || !Number.isSafeInteger(value.epoch) || value.epoch < 1 ||\n !(\"deniedAt\" in value) || typeof value.deniedAt !== \"number\" || !Number.isSafeInteger(value.deniedAt) || value.deniedAt < 0 ||\n Object.keys(value).length !== 2\n ) throw new Error(\"Invalid deny tombstone.\");\n return value as Tombstone;\n } catch {\n throw gatedError(\"gate_unavailable\", \"The origin deny tombstone cannot be read.\");\n }\n}\n\nfunction writeTombstone(value: Tombstone): void {\n try {\n const serialized = JSON.stringify(value);\n localStorage.setItem(REPLICA_DENY_TOMBSTONE, serialized);\n if (localStorage.getItem(REPLICA_DENY_TOMBSTONE) !== serialized) throw new Error(\"The deny tombstone was not persisted.\");\n } catch {\n throw gatedError(\"gate_unavailable\", \"The origin deny tombstone cannot be written.\");\n }\n}\n\n/** A readable but read-only origin must not establish a session it could not later deny. */\nfunction assertTombstoneWritable(): void {\n try {\n const stored = localStorage.getItem(REPLICA_DENY_TOMBSTONE);\n if (stored === null) {\n const probe = `${REPLICA_DENY_TOMBSTONE}:probe`;\n localStorage.setItem(probe, \"1\");\n if (localStorage.getItem(probe) !== \"1\") throw new Error(\"The deny tombstone probe was not persisted.\");\n localStorage.removeItem(probe);\n } else {\n localStorage.setItem(REPLICA_DENY_TOMBSTONE, stored);\n if (localStorage.getItem(REPLICA_DENY_TOMBSTONE) !== stored) throw new Error(\"The deny tombstone was not persisted.\");\n }\n } catch {\n throw gatedError(\"gate_unavailable\", \"The origin deny tombstone cannot be written.\");\n }\n}\n\nfunction denyFloor(gate: Gate | undefined, tombstone: Tombstone | null): { epoch: number; deniedAt: number | null } {\n if (gate !== undefined && tombstone === null && (gate.lifecycleEpoch > 0 || gate.deniedAt !== null)) {\n throw gatedError(\"gate_unavailable\", \"A denied gate has no origin deny tombstone.\");\n }\n if (gate !== undefined && tombstone !== null && gate.lifecycleEpoch < tombstone.epoch) {\n throw gatedError(\"gate_unavailable\", \"The gate is behind the origin deny tombstone.\");\n }\n return {\n epoch: Math.max(gate?.lifecycleEpoch ?? 0, tombstone?.epoch ?? 0),\n deniedAt: gate?.deniedAt === null || gate?.deniedAt === undefined\n ? tombstone?.deniedAt ?? null\n : Math.max(gate.deniedAt, tombstone?.deniedAt ?? 0),\n };\n}\n\n\nfunction exclusive<T>(name: string, section: () => Promise<T>, signal?: AbortSignal): Promise<T> {\n if (typeof navigator === \"undefined\" || typeof navigator.locks?.request !== \"function\") {\n return Promise.reject(gatedError(\"gate_unavailable\", \"The origin Web Lock is unavailable.\"));\n }\n return signal === undefined\n ? navigator.locks.request(name, section)\n : navigator.locks.request(name, { signal }, section);\n}\n\nfunction deleteKvDatabase(): Promise<DeleteOutcome> {\n const { promise, resolve } = Promise.withResolvers<DeleteOutcome>();\n if (takeFault(\"delete_database\")) {\n resolve({ status: \"failed\", code: \"STORAGE_ERROR\" });\n return promise;\n }\n const request = indexedDB.deleteDatabase(KV_DATABASE);\n request.onsuccess = () => resolve({ status: \"deleted\" });\n request.onerror = () => resolve({ status: \"failed\", code: \"STORAGE_ERROR\" });\n // The request stays queued and may still complete later; the marker covers that until a retry\n // confirms the delete.\n request.onblocked = () => resolve({ status: \"failed\", code: DELETE_BLOCKED });\n return promise;\n}\n\nfunction setLogoutMarker(): boolean {\n try {\n const marker = String(Date.now());\n localStorage.setItem(REPLICA_LOGOUT_MARKER, marker);\n return localStorage.getItem(REPLICA_LOGOUT_MARKER) === marker;\n } catch {\n return false;\n }\n}\n\n/**\n * Runs `use` in one transaction. `use` may only await requests on `tx`. A throw aborts the\n * transaction so nothing partial commits, then rethrows. Resolves only after `oncomplete`.\n */\nasync function transact<T>(db: IDBDatabase, stores: string[], mode: IDBTransactionMode, use: (tx: Tx) => Promise<T>): Promise<T> {\n const tx = db.transaction(stores, mode, { durability: \"strict\" });\n const done = transactionDone(tx);\n let value: T;\n try {\n value = await use(tx);\n } catch (error) {\n try {\n tx.abort();\n } catch {\n // Already finished.\n }\n await done.catch(() => undefined);\n throw error;\n }\n await done;\n return value;\n}\n\nexport interface BrowserReplicaLifecycle extends ReplicaLifecycleGate {\n consentDeadline(stamp: ReplicaSessionStamp): number | null;\n subscribeConsentExpired(listener: (stamp: ReplicaSessionStamp) => void): () => void;\n expireConsent(): Promise<ReplicaDenyOutcome>;\n}\n\nexport function createIndexedDbLifecycle(options: {\n eraser?: ReplicaPartitionEraser<Tx>;\n ledger?: Pick<ReplicaQuotaLedger<Tx>, \"accountErase\">;\n now?: () => number;\n onConsentExpired?: (stamp: ReplicaSessionStamp) => void;\n} = {}): BrowserReplicaLifecycle {\n const eraser = options.eraser ?? kvPartitionEraser;\n const ledger = options.ledger;\n const now = options.now ?? Date.now;\n function checkedNow(): number {\n const at = now();\n if (!Number.isSafeInteger(at) || at < 0) throw gatedError(\"gate_unavailable\", \"The replica clock is unavailable.\");\n return at;\n }\n\n function deadlineOf(gate: Gate): number {\n const deadline = gate.consentDeadline;\n if (typeof deadline !== \"number\" || !Number.isSafeInteger(deadline) || deadline < 0) {\n throw gatedError(\"gate_unavailable\", \"The account gate has no valid consent deadline.\");\n }\n return deadline;\n }\n\n const gateTx = createIndexedDbGateTx(openKvStore, { now, onConsentExpired: expireConsent });\n let view: Stamp | undefined;\n let expiryTimer: Parameters<typeof clearTimeout>[0];\n const expiryObservers = new Set<(stamp: ReplicaSessionStamp) => void>();\n\n function adopt(gate: Gate, principal: string): ReplicaSessionStamp {\n const stamp = { incarnation: gate.incarnation, lifecycleEpoch: gate.lifecycleEpoch };\n view = { ...stamp, consentDeadline: deadlineOf(gate), principal: principal.toLowerCase() };\n clearTimeout(expiryTimer);\n armExpiryTimer();\n return stamp;\n }\n\n function armExpiryTimer(): void {\n if (view === undefined) return;\n const delay = Math.max(0, Math.min(view.consentDeadline - checkedNow(), 2_147_483_647));\n expiryTimer = setTimeout(() => {\n expiryTimer = undefined;\n if (view === undefined) return;\n try {\n if (checkedNow() < view.consentDeadline) armExpiryTimer();\n else void expireConsent();\n } catch {\n view = undefined;\n }\n }, delay);\n }\n\n async function readGate(): Promise<Gate | undefined> {\n const found = await withDatabase(openKvStore, false, (db) =>\n requestAsPromise<Gate | undefined>(db.transaction(KV_STORE.gate).objectStore(KV_STORE.gate).get(ACCOUNT)),\n );\n return found ?? undefined;\n }\n\n async function activeKeys(gate: Gate): Promise<{ hash: GateHmacRecord; account: AccountKeyRecord }> {\n const records = await withDatabase(openKvStore, false, async (db) => {\n const device = db.transaction(KV_STORE.device).objectStore(KV_STORE.device);\n const hash = await requestAsPromise<GateHmacRecord | undefined>(device.get(GATE_HMAC_KEY));\n const account = await requestAsPromise<AccountKeyRecord | undefined>(device.get(ACCOUNT_KEY));\n return { hash, account };\n });\n if (records === null || !validGateHmac(records.hash, gate) || !validAccountKey(records.account, gate)) {\n throw gatedError(\"gate_unavailable\", \"The account gate encryption keys are missing or mismatched.\");\n }\n return { hash: records.hash, account: records.account };\n }\n\n async function readIntents(): Promise<ReplicaPurgeIntent[]> {\n const found = await withDatabase(openKvStore, false, (db) =>\n requestAsPromise<ReplicaPurgeIntent[]>(db.transaction(KV_STORE.purgeIntents).objectStore(KV_STORE.purgeIntents).getAll()),\n );\n return found ?? [];\n }\n\n async function writeGate(gate: Gate, previous: Gate | undefined, keys: { hash: GateHmacRecord; account: AccountKeyRecord }): Promise<void> {\n const db = await openKvStore(true);\n if (db === null) throw gatedError(\"gate_unavailable\", \"The origin database could not be created.\");\n try {\n await transact(db, [KV_STORE.gate, KV_STORE.device], \"readwrite\", async (tx) => {\n const current = await requestAsPromise<Gate | undefined>(tx.objectStore(KV_STORE.gate).get(ACCOUNT));\n if (current?.incarnation !== previous?.incarnation || current?.lifecycleEpoch !== previous?.lifecycleEpoch || current?.principalHash !== previous?.principalHash) {\n throw gatedError(\"stamp_stale\", \"The origin gate changed during establishment.\");\n }\n const device = tx.objectStore(KV_STORE.device);\n device.put(keys.hash, GATE_HMAC_KEY);\n device.put(keys.account, ACCOUNT_KEY);\n tx.objectStore(KV_STORE.gate).put(gate, ACCOUNT);\n });\n } finally {\n db.close();\n }\n }\n\n async function establishSessionStamp(principal: string, o: { consentAnchor: ReplicaConsentAnchor }): Promise<ReplicaSessionStamp> {\n return exclusive(GATE_LOCK, async () => {\n const tombstone = readTombstone();\n if (logoutMarkerSet()) throw gatedError(\"logout_pending\", \"A logout is still pending on this origin.\");\n const anchor = o.consentAnchor;\n if (anchor === undefined ||\n (anchor.source !== \"siwe\" && anchor.source !== \"delegation_iat\" && anchor.source !== \"delegation_nbf\") ||\n !Number.isSafeInteger(anchor.at) || anchor.at < 0) {\n throw gatedError(\"not_established\", \"A verified finite consent anchor is required.\");\n }\n const issuedAt = Math.min(anchor.at, checkedNow());\n if ((await readIntents()).length > 0) throw gatedError(\"purge_pending\", \"A purge is still pending on this origin.\");\n const current = await readGate();\n const legacy = current === undefined ? await readLegacyGateFloor() : null;\n const preserved = preserveLegacyFloor(legacy, tombstone);\n const floor = denyFloor(current, preserved.tombstone);\n if (preserved.advanced) throw gatedError(\"stamp_stale\", \"The legacy deny floor was preserved; a new consent proof is required.\");\n if (floor.deniedAt !== null && issuedAt <= floor.deniedAt) {\n throw gatedError(\"stamp_stale\", \"This sign-in predates the last sign-out.\");\n }\n assertTombstoneWritable();\n if (current === undefined || current.principalHash === null) {\n const incarnation = crypto.randomUUID();\n const hash = await newGateHmac(incarnation);\n const principalHash = await gateHash(hash.hmac, \"principal\", principal.toLowerCase());\n const account = await newAccountKey(incarnation, floor.epoch, principalHash);\n const created: Gate = {\n incarnation,\n salt: crypto.randomUUID(),\n principalHash,\n lifecycleEpoch: floor.epoch,\n deniedAt: floor.deniedAt,\n keyId: hash.keyId,\n accountKeyId: account.keyId,\n consentDeadline: issuedAt + CONSENT_WINDOW_MS,\n };\n if (!Number.isSafeInteger(created.consentDeadline)) throw gatedError(\"not_established\", \"The consent deadline is invalid.\");\n await writeGate(created, current, { hash, account });\n return adopt(created, principal);\n }\n const keys = await activeKeys(current);\n const principalHash = await gateHash(keys.hash.hmac, \"principal\", principal.toLowerCase());\n if (current.principalHash !== principalHash) throw gatedError(\"principal_mismatch\", \"The origin is bound to another account.\");\n if (checkedNow() >= deadlineOf(current)) {\n await expireConsentLocked();\n throw new ReplicaError(ReplicaErrorCode.CONSENT_EXPIRED, \"The account consent has expired.\");\n }\n return adopt(current, principal);\n });\n }\n\n async function restoreSessionStamp(principal: string, persisted: ReplicaSessionStamp | undefined): Promise<ReplicaSessionStamp> {\n const gate = await readGate();\n denyFloor(gate, readTombstone());\n if (gate === undefined || persisted === undefined) throw gatedError(\"not_established\", \"No session stamp is established for this origin.\");\n if (\n typeof persisted !== \"object\" || persisted === null || Array.isArray(persisted) ||\n Object.keys(persisted).length !== 2 ||\n !Object.prototype.hasOwnProperty.call(persisted, \"incarnation\") ||\n !Object.prototype.hasOwnProperty.call(persisted, \"lifecycleEpoch\") ||\n typeof persisted.incarnation !== \"string\" || persisted.incarnation.length === 0 ||\n !Number.isSafeInteger(persisted.lifecycleEpoch) || persisted.lifecycleEpoch < 0\n ) {\n throw gatedError(\"stamp_stale\", \"The persisted replica stamp is malformed.\");\n }\n if (persisted.incarnation !== gate.incarnation || persisted.lifecycleEpoch !== gate.lifecycleEpoch) {\n throw gatedError(\"stamp_stale\", \"The persisted session stamp is stale.\");\n }\n if (gate.principalHash === null) throw gatedError(\"principal_mismatch\", \"The origin has no active account.\");\n const keys = await activeKeys(gate);\n const hash = await gateHash(keys.hash.hmac, \"principal\", principal.toLowerCase());\n if (hash !== gate.principalHash) throw gatedError(\"principal_mismatch\", \"The origin is bound to another account.\");\n if (logoutMarkerSet()) throw gatedError(\"logout_pending\", \"A logout is still pending on this origin.\");\n if ((await readIntents()).length > 0) throw gatedError(\"purge_pending\", \"A purge is still pending on this origin.\");\n assertTombstoneWritable();\n if (checkedNow() >= deadlineOf(gate)) {\n await expireConsent();\n throw new ReplicaError(ReplicaErrorCode.CONSENT_EXPIRED, \"The account consent has expired.\");\n }\n return adopt(gate, principal);\n }\n\n async function activationAllowed(next: string): Promise<boolean> {\n try {\n if (logoutMarkerSet()) return false;\n const gate = await readGate();\n denyFloor(gate, readTombstone());\n if (gate === undefined || gate.principalHash === null) return false;\n if (checkedNow() >= deadlineOf(gate)) {\n await expireConsent();\n return false;\n }\n const keys = await activeKeys(gate);\n const hash = await gateHash(keys.hash.hmac, \"principal\", next.toLowerCase());\n if (hash !== gate.principalHash) return false;\n return (await readIntents()).every((intent) => intent.principalHash === hash);\n } catch {\n return false;\n }\n }\n\n function checkDelivery(stamp: ReplicaSessionStamp): ReplicaDeliveryDecision {\n if (stamp === undefined) return { status: \"gated\", gate: \"not_established\" };\n if (view === undefined) return { status: \"gated\", gate: \"stamp_stale\" };\n if (view.incarnation !== stamp.incarnation || view.lifecycleEpoch !== stamp.lifecycleEpoch) {\n return { status: \"gated\", gate: \"stamp_stale\" };\n }\n try {\n const tombstone = readTombstone();\n if (logoutMarkerSet()) return { status: \"gated\", gate: \"logout_pending\" };\n if (tombstone === null && stamp.lifecycleEpoch !== 0) return { status: \"gated\", gate: \"gate_unavailable\" };\n if (tombstone !== null && stamp.lifecycleEpoch < tombstone.epoch) return { status: \"gated\", gate: \"stamp_stale\" };\n if (checkedNow() >= view.consentDeadline) return { status: \"consent_expired\", code: \"CONSENT_EXPIRED\" };\n return { status: \"allow\" };\n } catch {\n return { status: \"gated\", gate: \"gate_unavailable\" };\n }\n }\n\n function consentDeadline(stamp: ReplicaSessionStamp): number | null {\n return view !== undefined && checkDelivery(stamp).status === \"allow\"\n ? view.consentDeadline\n : null;\n }\n\n function deliverable(stamp: ReplicaSessionStamp): boolean {\n return checkDelivery(stamp).status === \"allow\";\n }\n\n /**\n * One transaction: moves the gate to the already persisted origin deny floor, records intents\n * for the caller, and removes its device, first-install and durable grant-install anchors.\n */\n async function commitDeny(plan: { gate: Gate; callerHash: string; tombstone: Tombstone }): Promise<ReplicaDenyOutcome> {\n if (takeFault(\"deny_tx\")) throw new Error(\"injected deny_tx failure\");\n const db = await openKvStore(false);\n if (db === null) return { status: \"stale\" };\n try {\n return await transact<ReplicaDenyOutcome>(\n db,\n [KV_STORE.gate, KV_STORE.partitions, KV_STORE.purgeIntents, KV_STORE.device, KV_STORE.pending, KV_STORE.quota],\n \"readwrite\",\n async (tx) => {\n const current = await requestAsPromise<Gate | undefined>(tx.objectStore(KV_STORE.gate).get(ACCOUNT));\n if (\n current === undefined ||\n current.incarnation !== plan.gate.incarnation ||\n current.principalHash !== plan.callerHash ||\n current.lifecycleEpoch !== plan.gate.lifecycleEpoch ||\n current.keyId !== plan.gate.keyId ||\n current.accountKeyId !== plan.gate.accountKeyId\n ) {\n return { status: \"stale\" };\n }\n const now = plan.tombstone.deniedAt;\n const partitions = await requestAsPromise<ReplicaPartitionIndexEntry[]>(tx.objectStore(KV_STORE.partitions).getAll());\n const pendingKeys = await requestAsPromise<IDBValidKey[]>(tx.objectStore(KV_STORE.pending).getAllKeys());\n\n const intents: ReplicaPurgeIntent[] = [];\n const identities = new Set<string>();\n for (const entry of partitions) {\n if (entry.principalHash !== plan.callerHash) continue;\n intents.push({ id: entry.partitionId, kind: \"partition\", partitionId: entry.partitionId, principalHash: plan.callerHash, recordedAt: now });\n identities.add(entry.identityKeyHash);\n }\n // Activation never admits another principal while old purge intents remain.\n // Pending values may be sealed; only the opaque keys are needed for erase.\n for (const key of pendingKeys) {\n if (typeof key === \"string\") identities.add(key);\n }\n for (const identityKeyHash of identities) {\n intents.push({ id: `pending:${identityKeyHash}`, kind: \"pending\", identityKeyHash, principalHash: plan.callerHash, recordedAt: now });\n }\n\n const purge = tx.objectStore(KV_STORE.purgeIntents);\n for (const intent of intents) purge.put(intent, intent.id);\n const device = tx.objectStore(KV_STORE.device);\n let deviceBytes = billedDeviceBytes(await requestAsPromise<unknown>(device.get(DEVICE_KEY)));\n device.delete(DEVICE_KEY);\n device.delete(ACCOUNT_KEY);\n device.delete(GATE_HMAC_KEY);\n for (const prefix of [FIRST_INSTALL_KEY_PREFIX, GRANT_INSTALL_KEY_PREFIX]) {\n const keys = await requestAsPromise<IDBValidKey[]>(device.getAllKeys(IDBKeyRange.bound(prefix, `${prefix}\\uffff`)));\n for (const key of keys) {\n deviceBytes += billedDeviceBytes(await requestAsPromise<unknown>(device.get(key)));\n if (!Number.isSafeInteger(deviceBytes)) throw gatedError(\"gate_unavailable\", \"Device quota bytes overflow.\");\n device.delete(key);\n }\n }\n await ledger?.accountErase(tx, { partitionId: \"device\", bytes: deviceBytes });\n\n const next: Gate = {\n ...current,\n principalHash: null,\n consentDeadline: null,\n lifecycleEpoch: plan.tombstone.epoch,\n deniedAt: plan.tombstone.deniedAt,\n };\n tx.objectStore(KV_STORE.gate).put(next, ACCOUNT);\n return { status: \"denied\", lifecycleEpoch: next.lifecycleEpoch, intents: intents.length };\n },\n );\n } finally {\n db.close();\n }\n }\n\n /** Commit failed: delete the whole origin database. Blocked or failed delete: set the marker. */\n async function wipe(): Promise<Extract<ReplicaDenyOutcome, { status: \"deleted\" | \"marker\" | \"unguarded\" }>> {\n const deleted = await deleteKvDatabase();\n if (deleted.status === \"deleted\") return { status: \"deleted\" };\n return setLogoutMarker() ? { status: \"marker\", code: deleted.code } : { status: \"unguarded\", code: deleted.code };\n }\n\n async function advanceDeny(gate: Gate, floor: { epoch: number; deniedAt: number | null }): Promise<ReplicaDenyOutcome> {\n const callerHash = gate.principalHash;\n if (callerHash === null) return { status: \"stale\" };\n const tombstone = { epoch: floor.epoch + 1, deniedAt: Math.max(checkedNow(), (floor.deniedAt ?? -1) + 1) };\n if (!Number.isSafeInteger(tombstone.epoch) || !Number.isSafeInteger(tombstone.deniedAt)) {\n throw gatedError(\"gate_unavailable\", \"The origin deny tombstone cannot advance.\");\n }\n writeTombstone(tombstone);\n try {\n return await commitDeny({ gate, callerHash, tombstone });\n } catch {\n return wipe();\n }\n }\n\n async function denyLocked(input: ReplicaDenyInput): Promise<ReplicaDenyOutcome> {\n const gate = await readGate();\n const floor = denyFloor(gate, readTombstone());\n if (gate === undefined || gate.principalHash === null) return { status: \"stale\" };\n const keys = await activeKeys(gate);\n const callerHash = await gateHash(keys.hash.hmac, \"principal\", input.principal.toLowerCase());\n if (callerHash !== gate.principalHash) return { status: \"stale\" };\n if (input.reason === \"account_switch\" && input.next === undefined) {\n throw new Error(\"account_switch requires the next principal\");\n }\n return advanceDeny(gate, floor);\n }\n\n async function expireConsentLocked(): Promise<ReplicaDenyOutcome> {\n const gate = await readGate();\n const floor = denyFloor(gate, readTombstone());\n if (gate === undefined || gate.principalHash === null) return { status: \"stale\" };\n if (checkedNow() < deadlineOf(gate)) return { status: \"stale\" };\n const stamp = { incarnation: gate.incarnation, lifecycleEpoch: gate.lifecycleEpoch };\n view = undefined;\n clearTimeout(expiryTimer);\n const result = await advanceDeny(gate, floor);\n if (result.status === \"denied\" || result.status === \"deleted\") {\n try {\n options.onConsentExpired?.(stamp);\n } catch {\n // An observer cannot undo a committed denial or relabel it as an unguarded gate.\n }\n for (const observer of expiryObservers) {\n try {\n observer(stamp);\n } catch {\n // Other subscribers still observe this committed denial.\n }\n }\n }\n return result;\n }\n\n async function expireConsent(): Promise<ReplicaDenyOutcome> {\n try {\n return await exclusive(GATE_LOCK, expireConsentLocked);\n } catch (error) {\n return { status: \"unguarded\", code: codeOf(error) };\n }\n }\n\n async function deny(input: ReplicaDenyInput): Promise<ReplicaDenyOutcome> {\n // Disable this tab before the first await, but never clear a successor's view\n // when a delayed sign-out from a previous account arrives.\n if (view?.principal === input.principal.toLowerCase()) {\n view = undefined;\n clearTimeout(expiryTimer);\n }\n try {\n return await exclusive(GATE_LOCK, () => denyLocked(input));\n } catch (error) {\n return { status: \"unguarded\", code: codeOf(error) };\n }\n }\n\n /** One exempt transaction per intent. A thrown error aborts it, so nothing partial commits. */\n async function eraseIntent(intent: ReplicaPurgeIntent): Promise<void> {\n const db = await openKvStore(false);\n if (db === null) return;\n try {\n await transact(db, [...ERASE_STORES, KV_STORE.partitions, KV_STORE.purgeIntents, KV_STORE.quota], \"readwrite\", async (tx) => {\n if (intent.kind === \"partition\") {\n const { bytes } = await eraser.erasePartition(tx, intent.partitionId);\n await gateTx.unindexPartition(tx, intent.partitionId);\n await ledger?.accountErase(tx, { partitionId: intent.partitionId, bytes });\n } else {\n await eraser.eraseIdentityPending(tx, intent.identityKeyHash);\n }\n await gateTx.clearIntent(tx, intent.id);\n });\n } finally {\n db.close();\n }\n }\n\n async function erasePendingLocked({ signal }: { signal: AbortSignal }): Promise<ReplicaEraseReport> {\n const erased: string[] = [];\n const failed: Array<{ id: string; code: string }> = [];\n for (const intent of await readIntents()) {\n if (signal.aborted) break;\n try {\n await eraseIntent(intent);\n erased.push(intent.id);\n } catch (error) {\n failed.push({ id: intent.id, code: codeOf(error) });\n }\n }\n return { erased, failed, remaining: (await readIntents()).length };\n }\n\n function erasePending(o: { signal: AbortSignal }): Promise<ReplicaEraseReport> {\n return exclusive(GATE_LOCK, () => erasePendingLocked(o));\n }\n\n async function recover({ signal }: { signal: AbortSignal }): Promise<ReplicaEraseReport> {\n try {\n return await exclusive(GATE_LOCK, async () => {\n const failed: Array<{ id: string; code: string }> = [];\n if (logoutMarkerSet()) {\n // A marker without a durable deny floor cannot authorize destruction of the last gate.\n if (readTombstone() === null) throw gatedError(\"gate_unavailable\", \"The pending logout has no deny tombstone.\");\n const deleted = await deleteKvDatabase();\n if (deleted.status === \"deleted\") {\n try {\n localStorage.removeItem(REPLICA_LOGOUT_MARKER);\n if (localStorage.getItem(REPLICA_LOGOUT_MARKER) !== null) throw new Error(\"The logout marker was not cleared.\");\n } catch {\n failed.push({ id: \"marker\", code: \"STORAGE_ERROR\" });\n }\n } else {\n failed.push({ id: \"database\", code: deleted.code });\n }\n }\n if (failed.length > 0) return { erased: [], failed, remaining: 1 };\n // A crash between persisting the tombstone and committing the denial leaves\n // an older gate behind. The external floor already authorizes a full wipe.\n const gate = await readGate();\n const tombstone = readTombstone();\n if (gate !== undefined && tombstone !== null && gate.lifecycleEpoch < tombstone.epoch) {\n const wiped = await wipe();\n if (wiped.status !== \"deleted\") {\n return { erased: [], failed: [{ id: \"database\", code: wiped.code }], remaining: 1 };\n }\n }\n const legacy = await readLegacyGateFloor();\n if (legacy !== null) {\n const tombstone = readTombstone();\n preserveLegacyFloor(legacy, tombstone);\n assertTombstoneWritable();\n const db = await openKvStore(true);\n if (db === null) throw gatedError(\"gate_unavailable\", \"The legacy origin could not be migrated.\");\n db.close();\n }\n const expiry = await expireConsentLocked();\n if (expiry.status === \"marker\" || expiry.status === \"unguarded\") {\n return { erased: [], failed: [{ id: \"expiry\", code: expiry.code }], remaining: 1 };\n }\n const pass = await erasePendingLocked({ signal });\n return { erased: pass.erased, failed: [...failed, ...pass.failed], remaining: pass.remaining, consentExpired: expiry.status === \"denied\" || expiry.status === \"deleted\" };\n }, signal);\n } catch (error) {\n // Outcome unknown: report one outstanding intent so `purgePending` stays set.\n return { erased: [], failed: [{ id: \"recover\", code: codeOf(error) }], remaining: 1 };\n }\n }\n\n function subscribeConsentExpired(listener: (stamp: ReplicaSessionStamp) => void): () => void {\n expiryObservers.add(listener);\n return () => { expiryObservers.delete(listener); };\n }\n\n return {\n kind: \"indexeddb\",\n recover,\n expireConsent,\n checkDelivery,\n consentDeadline,\n subscribeConsentExpired,\n establishSessionStamp,\n restoreSessionStamp,\n activationAllowed,\n deliverable,\n deny,\n erasePending,\n pendingPurge: readIntents,\n };\n}\n","/**\n * `@tinycloud/replica/kv-storage/testing`: fault seams for acceptance\n * suites. Never imported by a production entry.\n */\nimport type { KVReplicaStorage, ReplicaPartitionEraser } from \"@tinycloud/sdk-services/kv/replication\";\n\nimport { ReplicaError, ReplicaErrorCode } from \"../errors.js\";\nimport type { DenyFaultPoint } from \"./lifecycle.js\";\nimport { denyFaults } from \"./lifecycle.js\";\nimport { kvPartitionEraser } from \"./schema.js\";\n\n/**\n * Construct a lifecycle with a test eraser without exposing fault wiring to the production entry.\n * Test seam: pass `ledger` (ledgerFor()) or denial/erase will not release the device quota bucket; production always injects one.\n */\nexport { createIndexedDbLifecycle } from \"./lifecycle.js\";\n/** Inspect encrypted raw-row sizes without exposing the sealed codec on the production entry. */\nexport { measureSealedRow } from \"./sealed.js\";\n\n/**\n * Terminate the storage's worker as a crash would: in-flight calls reject,\n * and the next call on any handle respawns the worker and reopens its spec.\n */\nexport function killReplicaWorker(storage: KVReplicaStorage): void {\n const connection: unknown = Reflect.get(storage, \"connection\");\n if (typeof connection !== \"function\") throw new TypeError(\"killReplicaWorker needs a storage from indexedDbReplicaStorage().\");\n const active: unknown = connection.call(storage, true);\n if (active === undefined) return;\n if (typeof active !== \"object\" || active === null) throw new TypeError(\"killReplicaWorker needs a storage from indexedDbReplicaStorage().\");\n const kill: unknown = Reflect.get(active, \"kill\");\n if (typeof kill !== \"function\") throw new TypeError(\"killReplicaWorker needs a storage from indexedDbReplicaStorage().\");\n kill.call(active, new ReplicaError(ReplicaErrorCode.STORAGE_ERROR, \"The replica worker was terminated.\"));\n}\n\n/**\n * An eraser whose `erasePartition` fails (aborting the erase transaction)\n * for the given partitions, or for every partition when none are given,\n * while `remaining()` is above zero. Pass it to the lifecycle gate in place\n * of `kvPartitionEraser`.\n */\nexport function injectEraseFailure(\n options: { partitionIds?: readonly string[]; times?: number; base?: ReplicaPartitionEraser<IDBTransaction> } = {},\n): ReplicaPartitionEraser<IDBTransaction> & { remaining(): number } {\n const base = options.base ?? kvPartitionEraser;\n let remaining = options.times ?? Number.POSITIVE_INFINITY;\n return {\n remaining: () => remaining,\n async erasePartition(tx, partitionId) {\n if (remaining > 0 && (options.partitionIds === undefined || options.partitionIds.includes(partitionId))) {\n remaining -= 1;\n throw new ReplicaError(ReplicaErrorCode.STORAGE_ERROR, `Injected erase failure for ${partitionId}.`);\n }\n return base.erasePartition(tx, partitionId);\n },\n eraseIdentityPending: (tx, identityKeyHash) => base.eraseIdentityPending(tx, identityKeyHash),\n };\n}\n\n/**\n * Plans a failure at one deny step: `deny_tx` makes the deny transaction fail\n * (forcing the fallback delete), `delete_database` makes the fallback delete\n * fail (forcing the logout marker). `once` (default true) consumes the plan on\n * first use. Returns a disposer that clears the plan.\n */\nexport function injectDenyFailure(point: DenyFaultPoint, options: { once?: boolean } = {}): () => void {\n denyFaults[point] = { once: options.once ?? true };\n return () => {\n delete denyFaults[point];\n };\n}\n"],"mappings":"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","names":["ACCOUNT","ACCOUNT","principalHash","now","tombstone"]}
@@ -0,0 +1,155 @@
1
+ import { ReplicaBrowserGateTx, ReplicaBrowserGateSnapshot, ReplicaBrowserGateAccess, ReplicaLifecycleGate, ReplicaQuotaLedger, ReplicaGatedStorage } from '@tinycloud/sdk-services/kv/replication';
2
+
3
+ interface AccountKeyRecord {
4
+ v: 1;
5
+ incarnation: string;
6
+ lifecycleEpoch: number;
7
+ principalHash: string;
8
+ keyId: string;
9
+ aesGcm: CryptoKey;
10
+ hmac: CryptoKey;
11
+ }
12
+
13
+ /**
14
+ * The browser `ReplicaGateTx` over the origin database's control stores
15
+ * (`gate`, `partitions`, `purgeIntents`, `quota`). Every data transaction goes
16
+ * through `run`, which reads the gate record as its FIRST request and refuses
17
+ * — aborting, so nothing commits — unless the incarnation, the lifecycle epoch
18
+ * and the principal hash all match, and no purge intent covers the partition
19
+ * or identity. `run` resolves only after `oncomplete`.
20
+ *
21
+ * Erase transactions are exempt from `run`: they call `indexPartition`,
22
+ * `unindexPartition` and `clearIntent` on their own transaction.
23
+ */
24
+
25
+ /** Browser IDs and key ids are precomputed before opening a data transaction. */
26
+ type BrowserGateAccess = ReplicaBrowserGateAccess;
27
+ type BrowserGateSnapshot = ReplicaBrowserGateSnapshot;
28
+ interface BrowserReplicaGateTx extends ReplicaBrowserGateTx<IDBTransaction> {
29
+ snapshot(): Promise<BrowserGateSnapshot | null>;
30
+ readAccountKey(access: BrowserGateAccess): Promise<AccountKeyRecord>;
31
+ }
32
+
33
+ /**
34
+ * `indexedDbReplicaStorage()`: the browser `ReplicaGatedStorage` (TC-858 Phase 2
35
+ * plan §3, CONTRACT.md §1). Main-thread half: a thin RPC client over one
36
+ * dedicated worker per storage instance, which owns the engine, the network
37
+ * and the device key. Pending writes, `inspectStatus` and `purge` run here
38
+ * directly against the origin database: none needs WASM or the network, so
39
+ * none spawns the worker.
40
+ *
41
+ * Gate (CONTRACT.md §2): with no lifecycle gate there is no ungated mode —
42
+ * `open`, pending writes, `purge` and `inspectStatus` refuse REPLICA_GATED
43
+ * `gate_unavailable`. Every replica-served value is delivered only when the
44
+ * worker's reply carries exactly the handle's stamp and `deliverable(stamp)`.
45
+ */
46
+
47
+ type IndexedDbReplicaStorageOptions = {
48
+ /**
49
+ * Spawn this worker instead of the content-hashed default
50
+ * (custom bundler layouts). A factory runs on first `open`.
51
+ */
52
+ worker?: Worker | URL | string | (() => Worker);
53
+ /** The lifecycle gate (`storage.lifecycle`): consulted before every open, sync and delivery. */
54
+ lifecycle?: ReplicaLifecycleGate;
55
+ /**
56
+ * The main-thread transaction guard (`createIndexedDbGateTx`): the pending
57
+ * store runs every read and update through it, and `purge`/`inspectStatus`
58
+ * derive private gate-HMAC ids from its `snapshot()`.
59
+ */
60
+ gateTx?: BrowserReplicaGateTx;
61
+ /** Releases quota for partitions `purge()` erases, in the erase transaction. */
62
+ ledger?: Pick<ReplicaQuotaLedger<IDBTransaction>, "accountErase">;
63
+ };
64
+ /**
65
+ * The IndexedDB `ReplicaGatedStorage`: one origin database
66
+ * (`tinycloud-replica-kv`) and one dedicated worker per storage instance.
67
+ * Pass it as `replication.storage`; the worker is spawned lazily on the
68
+ * first `open`, so an app with replication off never starts it.
69
+ */
70
+ declare function indexedDbReplicaStorage(options?: IndexedDbReplicaStorageOptions): ReplicaGatedStorage;
71
+
72
+ /**
73
+ * Typed replica errors. Every failure a caller can act on has its own code;
74
+ * hosts (CLI, worker) map codes to their own exit codes or UI states.
75
+ */
76
+ declare const ReplicaErrorCode: {
77
+ /** Another process holds the sync lease. */
78
+ readonly BUSY: "REPLICA_BUSY";
79
+ /** No replica with this name exists. */
80
+ readonly NOT_FOUND: "REPLICA_NOT_FOUND";
81
+ /** The replica exists but its stored configuration differs from the request. */
82
+ readonly CONFIG_MISMATCH: "REPLICA_CONFIG_MISMATCH";
83
+ /** The runtime has no supported durable store (e.g. Node < 22.13). */
84
+ readonly RUNTIME_UNSUPPORTED: "RUNTIME_UNSUPPORTED";
85
+ /** A required open() argument is missing or malformed (e.g. `principal`). */
86
+ readonly INVALID_ARGUMENT: "REPLICA_INVALID_ARGUMENT";
87
+ /** The local store failed (I/O, corruption, schema). */
88
+ readonly STORAGE_ERROR: "STORAGE_ERROR";
89
+ /** The local disk is full. */
90
+ readonly STORAGE_FULL: "STORAGE_FULL";
91
+ /** The key is outside the replica's prefix. */
92
+ readonly NOT_COVERED: "NOT_COVERED";
93
+ /** Syncing the secrets space or the vault namespace needs an explicit opt-in. */
94
+ readonly SECRETS_OPT_IN_REQUIRED: "SECRETS_OPT_IN_REQUIRED";
95
+ /** No grant is installed, or none covers the requested scope. */
96
+ readonly GRANT_MISSING: "GRANT_MISSING";
97
+ /** The grant bytes are not a verifiable UCAN, or its signature is wrong. */
98
+ readonly GRANT_INVALID: "GRANT_INVALID";
99
+ /** SIWE/CACAO and other non-UCAN grants cannot be used for a replica. */
100
+ readonly GRANT_FORMAT_UNSUPPORTED: "GRANT_FORMAT_UNSUPPORTED";
101
+ /** The grant was issued to a different device. */
102
+ readonly GRANT_AUDIENCE_MISMATCH: "GRANT_AUDIENCE_MISMATCH";
103
+ /** The grant lacks `tinycloud.kv/sync` or `tinycloud.kv/get` on the prefix. */
104
+ readonly GRANT_NOT_COVERING: "GRANT_NOT_COVERING";
105
+ readonly GRANT_NOT_YET_VALID: "GRANT_NOT_YET_VALID";
106
+ readonly GRANT_EXPIRED: "GRANT_EXPIRED";
107
+ /** The configured node does not advertise whole-space KV sync support. */
108
+ readonly REPLICATION_ROOT_UNSUPPORTED: "REPLICATION_ROOT_UNSUPPORTED";
109
+ readonly GRANT_REVOKED: "GRANT_REVOKED";
110
+ /** The node refused the invocation for a reason other than revocation. */
111
+ readonly GRANT_UNAUTHORIZED: "GRANT_UNAUTHORIZED";
112
+ /** The node refused the retention grant. */
113
+ readonly RETENTION_GRANT_REFUSED: "RETENTION_GRANT_REFUSED";
114
+ /** The node could not be reached. */
115
+ readonly NETWORK_ERROR: "NETWORK_ERROR";
116
+ /** The node answered with an unexpected status. */
117
+ readonly NODE_ERROR: "NODE_ERROR";
118
+ /** The node's answer violates the wire contract. */
119
+ readonly PROTOCOL_ERROR: "PROTOCOL_ERROR";
120
+ /** The node asked for a fresh bootstrap (410). Handled inside sync. */
121
+ readonly RESET_REQUIRED: "RESET_REQUIRED";
122
+ /** The feed now comes from a different node than the one pinned. */
123
+ readonly SOURCE_CHANGED: "SOURCE_CHANGED";
124
+ /** The feed delivered a key outside the replica's prefix. */
125
+ readonly SCOPE_VIOLATION: "SCOPE_VIOLATION";
126
+ /** Fetched bytes do not hash to the ETag the node attested for them. */
127
+ readonly CONTENT_MISMATCH: "CONTENT_MISMATCH";
128
+ /** A stored blob no longer hashes to its name. */
129
+ readonly INTEGRITY_ERROR: "INTEGRITY_ERROR";
130
+ /** The replica handle was closed; in-flight calls settle with this code. */
131
+ readonly CLOSED: "REPLICA_CLOSED";
132
+ /** The lifecycle gate refused the operation (shared contract `REPLICA_GATED`); `detail.gate` says why. */
133
+ readonly GATED: "REPLICA_GATED";
134
+ /** Quota admission refused a page (`maxBytes` or the free-space floor); nothing committed. */
135
+ readonly STORAGE_BUDGET: "STORAGE_BUDGET";
136
+ /** The consent deadline passed: the grant is not served until the user consents again. */
137
+ readonly CONSENT_EXPIRED: "CONSENT_EXPIRED";
138
+ };
139
+ type ReplicaErrorCode = (typeof ReplicaErrorCode)[keyof typeof ReplicaErrorCode];
140
+ /**
141
+ * The package ships separate entry bundles (`.`, `./sqlite`), each with its own
142
+ * copy of this class, so identity is a registered symbol, not `instanceof`.
143
+ */
144
+ declare const REPLICA_ERROR: unique symbol;
145
+ declare class ReplicaError extends Error {
146
+ readonly [REPLICA_ERROR] = true;
147
+ readonly code: ReplicaErrorCode;
148
+ readonly detail?: Record<string, unknown>;
149
+ constructor(code: ReplicaErrorCode, message: string, detail?: Record<string, unknown>, options?: {
150
+ cause?: unknown;
151
+ });
152
+ }
153
+ declare function isReplicaError(error: unknown, code?: ReplicaErrorCode): error is ReplicaError;
154
+
155
+ export { type IndexedDbReplicaStorageOptions, ReplicaError, ReplicaErrorCode, indexedDbReplicaStorage, isReplicaError };