@tinycloud/replica 0.1.0-beta.0 → 0.1.0-beta.2
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.
- package/README.md +100 -0
- package/dist/browser.js +279 -0
- package/dist/browser.js.map +1 -0
- package/dist/index.cjs +15 -4
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +11 -2
- package/dist/index.d.ts +11 -2
- package/dist/index.js +15 -4
- package/dist/index.js.map +1 -1
- package/dist/replica.worker.js +17930 -0
- package/dist/replica.worker.js.map +1 -0
- package/dist/sqlite.cjs +15 -6
- package/dist/sqlite.cjs.map +1 -1
- package/dist/sqlite.d.cts +1 -1
- package/dist/sqlite.d.ts +1 -1
- package/dist/sqlite.js +15 -6
- package/dist/sqlite.js.map +1 -1
- package/dist/{types-BNu2RYwE.d.cts → types-f3-kYLQZ.d.cts} +8 -0
- package/dist/{types-BNu2RYwE.d.ts → types-f3-kYLQZ.d.ts} +8 -0
- package/package.json +11 -1
package/dist/index.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/index.ts","../src/engine.ts","../src/errors.ts","../src/scope.ts","../src/grant.ts","../src/transport.ts"],"sourcesContent":["export { Replica, contentHash } from \"./engine.js\";\nexport type { ReadMeta, ReplicaListEntry, ReplicaListResult, ReplicaOptions, ReplicaReadResult, SyncReport } from \"./engine.js\";\nexport { ReplicaError, ReplicaErrorCode, isReplicaError } from \"./errors.js\";\nexport { assertGrantInstallable, grantCovers, parseUcanGrant, syncGrantSpaces, ucanCid } from \"./grant.js\";\nexport type { ParsedUcanGrant } from \"./grant.js\";\nexport { assertReadable, effectiveAuthority, hashFromEtag, kvPrefixCovers, requiresSecretsOptIn } from \"./scope.js\";\nexport type { EffectiveAuthority } from \"./scope.js\";\nexport { kvSyncTransport, replicaErrorFromService } from \"./transport.js\";\nexport type { KVSyncClient } from \"./transport.js\";\nexport type * from \"./types.js\";\n","import { blake3 } from \"@noble/hashes/blake3\";\nimport { bytesToHex } from \"@noble/hashes/utils\";\n\nimport { ReplicaError, ReplicaErrorCode, isReplicaError } from \"./errors.js\";\nimport { assertReadable, effectiveAuthority, hashFromEtag, isInstant, kvPrefixCovers } from \"./scope.js\";\nimport type {\n AuthorityState,\n AuthorityWindow,\n Coverage,\n FetchedContent,\n GrantRecord,\n LeaseToken,\n ListOpts,\n LocalEntry,\n ReplicaLastError,\n ReplicaState,\n ReplicaStatus,\n ReplicaStore,\n ReplicaTransport,\n SyncPage,\n VerifiedChange,\n} from \"./types.js\";\n\n/** Metadata every local read carries: the replica reports observed state, never finality. */\nexport type ReadMeta = {\n consistency: \"observed\";\n source: { host: string; nodeDid: string | null; space: string; prefix: string };\n asOf: string | null;\n coverage: Coverage;\n /** `expired` while reads continue under a node-attested retention. */\n authority: AuthorityState;\n};\n\nexport type ReplicaReadResult =\n | { status: \"present\"; key: string; value: Uint8Array; etag: string; metadata: Record<string, string>; meta: ReadMeta }\n | { status: \"content_missing\"; key: string; etag: string; metadata: Record<string, string>; meta: ReadMeta }\n | { status: \"deleted\" | \"absent\"; key: string; meta: ReadMeta }\n | { status: \"coverage_incomplete\" | \"not_covered\"; key: string; meta: ReadMeta };\n\nexport type ReplicaListEntry = { key: string; etag: string; metadata: Record<string, string>; content: boolean };\n\nexport type ReplicaListResult = { entries: ReplicaListEntry[]; meta: ReadMeta };\n\nexport type SyncReport = {\n pages: number;\n changes: number;\n deleted: number;\n fetched: number;\n contentMissing: number;\n repaired: number;\n resets: number;\n coverage: Coverage;\n cursorAdvanced: boolean;\n promotedGrant: boolean;\n blobsCollected: number;\n};\n\nexport type ReplicaOptions = {\n store: ReplicaStore;\n /** One transport for whichever grant the replica syncs under (tests, single-grant hosts). */\n transport?: ReplicaTransport;\n /**\n * A transport that invokes with exactly `grant`. Lets the engine try a\n * pending grant and fall back to the active one if the node refuses it.\n */\n transportFor?: (grant: GrantRecord) => ReplicaTransport;\n now?: () => number;\n leaseTtlMs?: number;\n};\n\nconst DEFAULT_PAGE_LIMIT = 500;\nconst DEFAULT_LEASE_TTL_MS = 120_000;\nconst FETCH_CHUNK = 100;\nconst REPAIR_BATCH = 100;\nconst RETENTION_REVOKED = new Set([\"retention-grant-revoked\", \"retention-grant-ancestor-revoked\"]);\n/** Refusals of a pending grant that leave the active grant to serve. */\nconst PENDING_REFUSALS = new Set<string>([\n ReplicaErrorCode.GRANT_UNAUTHORIZED,\n ReplicaErrorCode.GRANT_EXPIRED,\n ReplicaErrorCode.GRANT_NOT_YET_VALID,\n]);\n\n/** `\"blake3-\" + hex(blake3(bytes))`, the strong ETag the node attests for content. */\nexport function contentHash(bytes: Uint8Array): string {\n return bytesToHex(blake3(bytes));\n}\n\nfunction sameEtag(a: string, b: string): boolean {\n return a.replace(/^\"|\"$/g, \"\") === b.replace(/^\"|\"$/g, \"\");\n}\n\nfunction lastErrorOf(error: unknown, at: number): ReplicaLastError {\n return {\n at: new Date(at).toISOString(),\n code: isReplicaError(error) ? error.code : \"ERROR\",\n message: error instanceof Error ? error.message : String(error),\n };\n}\n\n/** One sync attempt under one grant. */\ntype Attempt = {\n lease: LeaseToken;\n grant: GrantRecord;\n transport: ReplicaTransport;\n /** The pending grant until the first page under it commits (and promotes it). */\n promote: GrantRecord | null;\n signal: AbortSignal | undefined;\n};\n\n/**\n * A durable, read-only local replica of one KV prefix. Reads never touch the\n * network; `sync` pulls `tinycloud.kv/sync` pages, verifies every byte\n * against the node-attested ETag, and commits each page with its cursor.\n */\nexport class Replica {\n readonly #store: ReplicaStore;\n readonly #transportFor: ((grant: GrantRecord) => ReplicaTransport) | undefined;\n readonly #now: () => number;\n readonly #leaseTtlMs: number;\n\n constructor(options: ReplicaOptions) {\n this.#store = options.store;\n const transport = options.transport;\n this.#transportFor = options.transportFor ?? (transport === undefined ? undefined : () => transport);\n this.#now = options.now ?? Date.now;\n this.#leaseTtlMs = options.leaseTtlMs ?? DEFAULT_LEASE_TTL_MS;\n }\n\n async #state(): Promise<ReplicaState> {\n const state = await this.#store.open();\n if (state === null) throw new ReplicaError(ReplicaErrorCode.NOT_FOUND, \"The replica has not been created.\");\n return state;\n }\n\n /** Throws unless local reads are allowed now; returns the meta every read reports. */\n #readMeta(state: ReplicaState): ReadMeta {\n const now = this.#now();\n const authority = assertReadable(\n effectiveAuthority({\n window: state.authority,\n grant: state.grant,\n revoked: state.revoked,\n retentionRevoked: state.retentionRevoked,\n policy: state.config.localReadPolicy,\n now,\n }),\n now,\n );\n return {\n consistency: \"observed\",\n source: { host: state.config.host, nodeDid: state.nodeDid, space: state.config.space, prefix: state.config.prefix },\n asOf: state.lastSyncAt,\n coverage: state.coverage,\n authority,\n };\n }\n\n /**\n * Authority again, from the store's current state and the clock, once the\n * read has its data: a revocation (and its purge) or an expiry that landed\n * while the read ran wins, and the caller gets the error, not the data.\n */\n async #confirmedMeta(): Promise<ReadMeta> {\n return this.#readMeta(await this.#state());\n }\n\n async get(key: string, options: { verify?: boolean } = {}): Promise<ReplicaReadResult> {\n const state = await this.#state();\n this.#readMeta(state);\n if (!kvPrefixCovers(state.config.prefix, key)) return { status: \"not_covered\", key, meta: await this.#confirmedMeta() };\n // Readers take no lock: a sync may replace this entry and collect its old\n // blob between the two reads below. One re-read sees the new entry.\n for (let attempt = 0; ; attempt += 1) {\n const entry = await this.#store.get(key);\n if (entry === undefined) {\n const status = state.coverage === \"complete\" ? \"absent\" : \"coverage_incomplete\";\n return { status, key, meta: await this.#confirmedMeta() };\n }\n if (entry.deleted) return { status: \"deleted\", key, meta: await this.#confirmedMeta() };\n if (!entry.content) {\n return { status: \"content_missing\", key, etag: entry.etag, metadata: entry.metadata, meta: await this.#confirmedMeta() };\n }\n const value = await this.#store.readContent(entry.hash);\n const intact = value !== undefined && (options.verify === false || contentHash(value) === entry.hash);\n if (intact) {\n return { status: \"present\", key, value, etag: entry.etag, metadata: entry.metadata, meta: await this.#confirmedMeta() };\n }\n if (attempt === 0) continue;\n // A purge that ran under this read surfaces as its revocation, not as corruption.\n await this.#confirmedMeta();\n throw new ReplicaError(\n ReplicaErrorCode.INTEGRITY_ERROR,\n value === undefined\n ? `The stored content for ${JSON.stringify(key)} is missing.`\n : `The stored content for ${JSON.stringify(key)} does not match its hash.`,\n { key },\n );\n }\n }\n\n /** Live keys under `prefix` (a plain string prefix within the replica's scope). */\n async list(options: ListOpts = {}): Promise<ReplicaListResult> {\n const state = await this.#state();\n this.#readMeta(state);\n const scope = state.config.prefix;\n const prefix = options.prefix ?? \"\";\n if (prefix !== \"\" && !scope.startsWith(prefix) && !kvPrefixCovers(scope, prefix)) {\n throw new ReplicaError(\n ReplicaErrorCode.NOT_COVERED,\n `${JSON.stringify(prefix)} is outside the replica's prefix ${JSON.stringify(scope)}.`,\n );\n }\n const rows = await this.#store.list(options);\n const entries: ReplicaListEntry[] = [];\n for (const row of rows) {\n if (row.deleted) continue;\n entries.push({ key: row.key, etag: row.etag, metadata: row.metadata, content: row.content });\n }\n return { entries, meta: await this.#confirmedMeta() };\n }\n\n async status(): Promise<ReplicaStatus> {\n await this.#state();\n return this.#store.status(this.#now());\n }\n\n /** Clear entries, blobs, cursor and the source pin; keep the configuration and grant. */\n async reset(reason: string): Promise<void> {\n await this.#state();\n const lease = await this.#store.acquireSyncLease(this.#leaseTtlMs);\n if (lease === null) throw new ReplicaError(ReplicaErrorCode.BUSY, \"Another process is syncing this replica.\");\n try {\n await this.#store.reset(lease, reason);\n } finally {\n await this.#store.releaseLease(lease);\n }\n }\n\n /**\n * Sync under the pending grant if there is one, else the active grant. If\n * the node refuses a pending grant before anything committed under it, the\n * active grant serves: a revoked pending grant is discarded, any other\n * refusal is recorded on it. A revocation of the grant serving the replica,\n * learned anywhere in the sync, purges the replica before this returns.\n */\n async sync(options: { limit?: number; signal?: AbortSignal } = {}): Promise<SyncReport> {\n const transportFor = this.#transportFor;\n if (transportFor === undefined) throw new Error(\"Replica.sync needs a transport\");\n const initial = await this.#state();\n if (initial.revoked !== null) {\n throw new ReplicaError(ReplicaErrorCode.GRANT_REVOKED, `The replica's grant was revoked: ${initial.revoked}`);\n }\n const candidates = [initial.pendingGrant, initial.grant].filter((grant): grant is GrantRecord => grant !== null);\n if (candidates.length === 0) throw new ReplicaError(ReplicaErrorCode.GRANT_MISSING, \"No grant is installed for this replica.\");\n\n const lease = await this.#store.acquireSyncLease(this.#leaseTtlMs);\n if (lease === null) throw new ReplicaError(ReplicaErrorCode.BUSY, \"Another process is syncing this replica.\");\n try {\n for (const [index, grant] of candidates.entries()) {\n const pending = grant.cid === initial.pendingGrant?.cid;\n const attempt: Attempt = {\n lease,\n grant,\n transport: transportFor(grant),\n promote: pending ? grant : null,\n signal: options.signal,\n };\n try {\n return await this.#syncUnder(attempt, options.limit ?? DEFAULT_PAGE_LIMIT);\n } catch (error) {\n const canFallBack = pending && attempt.promote !== null && index < candidates.length - 1;\n if (canFallBack && isReplicaError(error, ReplicaErrorCode.GRANT_REVOKED)) {\n await this.#store.discardPendingGrant(grant.cid, error.message);\n continue;\n }\n if (canFallBack && isReplicaError(error) && PENDING_REFUSALS.has(error.code)) {\n await this.#store.recordPendingGrantError(grant.cid, lastErrorOf(error, this.#now()));\n continue;\n }\n throw error;\n }\n }\n throw new Error(\"unreachable: the last candidate either returns or throws\");\n } catch (error) {\n await this.#learn(error);\n throw error;\n } finally {\n await this.#store.releaseLease(lease).catch(() => undefined);\n }\n }\n\n /** Persist what a failed sync taught us before the error reaches the caller. */\n async #learn(error: unknown): Promise<void> {\n if (isReplicaError(error, ReplicaErrorCode.GRANT_REVOKED)) {\n // Already revoked (the store refused a commit): nothing more to learn.\n if ((await this.#store.open())?.revoked != null) return;\n // Persisted before the error returns: later reads raise GRANT_REVOKED, also after restart.\n await this.#store.markRevoked(error.message);\n return;\n }\n if (isReplicaError(error, ReplicaErrorCode.RETENTION_GRANT_REFUSED)) {\n const cid = error.detail?.retentionGrantCid;\n const reason = error.detail?.reason;\n if (typeof cid === \"string\" && typeof reason === \"string\" && RETENTION_REVOKED.has(reason)) {\n // Bound to the grant the refused request presented: a replacement installed meanwhile stays.\n await this.#store.markRetentionRevoked(cid, error.message);\n return;\n }\n }\n await this.#store.recordError(lastErrorOf(error, this.#now())).catch(() => undefined);\n }\n\n /** The effective window for `grant` under `window`; throws unless it holds now (no retention for syncing). */\n #syncWindow(window: AuthorityWindow | null, grant: GrantRecord): { notBefore: string | null; expiresAt: string | null } {\n const now = this.#now();\n const authority = effectiveAuthority({ window, grant, revoked: null, policy: \"whileGrantValid\", now });\n if (authority.state !== \"valid\") assertReadable({ ...authority, retainUntil: null }, now);\n return { notBefore: authority.notBefore, expiresAt: authority.expiresAt };\n }\n\n async #syncUnder(attempt: Attempt, limit: number): Promise<SyncReport> {\n const state = await this.#state();\n const { config } = state;\n // A replacement grant is judged on its own leaf window until the node attests it.\n this.#syncWindow(attempt.promote === null ? state.authority : null, attempt.grant);\n const report: SyncReport = {\n pages: 0,\n changes: 0,\n deleted: 0,\n fetched: 0,\n contentMissing: 0,\n repaired: 0,\n resets: 0,\n coverage: state.coverage,\n cursorAdvanced: false,\n promotedGrant: false,\n blobsCollected: 0,\n };\n let cursor = state.cursor;\n // The source pin survives an automatic reset; only an explicit reset clears it.\n let pinned = state.nodeDid;\n let coverage = state.coverage;\n\n for (;;) {\n let page: SyncPage;\n const retentionGrant = config.retentionGrantCid;\n try {\n page = await attempt.transport.syncPage({\n prefix: config.prefix,\n ...(cursor === null ? {} : { cursor }),\n limit,\n ...(retentionGrant === null ? {} : { retentionGrant }),\n ...(attempt.signal === undefined ? {} : { signal: attempt.signal }),\n });\n } catch (error) {\n if (isReplicaError(error, ReplicaErrorCode.RESET_REQUIRED) && report.resets === 0) {\n const reason = typeof error.detail?.reason === \"string\" ? error.detail.reason : \"reset-required\";\n await this.#store.reset(attempt.lease, `node: ${reason}`, { keepSource: true });\n report.resets += 1;\n cursor = null;\n coverage = \"empty\";\n continue;\n }\n if (isReplicaError(error, ReplicaErrorCode.RETENTION_GRANT_REFUSED) && retentionGrant !== null) {\n throw new ReplicaError(error.code, error.message, { ...error.detail, retentionGrantCid: retentionGrant }, { cause: error });\n }\n throw error;\n }\n\n this.#checkPage(page, config.space, config.prefix, pinned);\n // Fetch only while the attested window holds; the store re-checks it at commit.\n const window = this.#syncWindow(page.authority, attempt.grant);\n const verified = await this.#verify(page, attempt);\n coverage = page.more ? (coverage === \"complete\" ? \"complete\" : \"bootstrapping\") : \"complete\";\n const at = new Date(this.#now()).toISOString();\n // The store checks `window` against its clock inside the commit transaction.\n await this.#store.applyPage(attempt.lease, {\n changes: verified.changes,\n blobs: verified.blobs,\n cursor: page.cursor,\n source: page.source,\n authority: page.authority,\n coverage,\n at,\n window,\n complete: !page.more,\n promoteGrant: attempt.promote,\n });\n if (attempt.promote !== null) {\n report.promotedGrant = true;\n attempt.promote = null;\n }\n report.pages += 1;\n report.changes += page.changes.length;\n report.deleted += page.changes.filter((change) => change.deleted).length;\n report.fetched += verified.fetched;\n if (page.cursor !== cursor) report.cursorAdvanced = true;\n cursor = page.cursor;\n pinned = page.source.nodeDid;\n await this.#store.renewLease(attempt.lease, this.#leaseTtlMs);\n if (!page.more) break;\n }\n\n report.repaired = await this.#repair(attempt, cursor, pinned, coverage);\n report.blobsCollected = await this.#store.collectGarbage(attempt.lease);\n report.coverage = coverage;\n report.contentMissing = (await this.#store.pendingRepairs(Number.MAX_SAFE_INTEGER)).length;\n return report;\n }\n\n #checkPage(page: SyncPage, space: string, prefix: string, pinnedNodeDid: string | null): void {\n if (page.source.space !== space || page.source.prefix !== prefix) {\n throw new ReplicaError(\n ReplicaErrorCode.PROTOCOL_ERROR,\n `The node answered for ${page.source.space}/${page.source.prefix}, not ${space}/${prefix}.`,\n );\n }\n // A bound that does not parse would widen the window (NaN compares false): fail closed.\n for (const [name, value] of Object.entries(page.authority)) {\n if (value !== null && (typeof value !== \"string\" || !isInstant(value))) {\n throw new ReplicaError(\n ReplicaErrorCode.PROTOCOL_ERROR,\n `The node attested authority.${name} = ${JSON.stringify(value)}, which is not a timestamp; nothing was committed.`,\n );\n }\n }\n if (pinnedNodeDid !== null && page.source.nodeDid !== pinnedNodeDid) {\n throw new ReplicaError(\n ReplicaErrorCode.SOURCE_CHANGED,\n `The feed now comes from ${page.source.nodeDid}, not the pinned source ${pinnedNodeDid}. Reset the replica to follow a new source.`,\n { pinned: pinnedNodeDid, received: page.source.nodeDid },\n );\n }\n for (const change of page.changes) {\n if (!kvPrefixCovers(prefix, change.key)) {\n throw new ReplicaError(\n ReplicaErrorCode.SCOPE_VIOLATION,\n `The feed delivered ${JSON.stringify(change.key)}, outside the prefix ${JSON.stringify(prefix)}.`,\n { key: change.key },\n );\n }\n }\n }\n\n async #verify(\n page: SyncPage,\n attempt: Attempt,\n ): Promise<{ changes: VerifiedChange[]; blobs: Map<string, Uint8Array>; fetched: number }> {\n const blobs = new Map<string, Uint8Array>();\n const changes: VerifiedChange[] = [];\n const candidates: Array<{ index: number; key: string; etag: string; hash: string }> = [];\n\n for (const change of page.changes) {\n if (change.deleted) {\n changes.push({ key: change.key, deleted: true });\n continue;\n }\n const hash = hashFromEtag(change.etag);\n if (hash === undefined) {\n throw new ReplicaError(ReplicaErrorCode.PROTOCOL_ERROR, `The feed sent an ETag that is not blake3: ${change.etag}`, {\n key: change.key,\n });\n }\n const previous = await this.#store.get(change.key);\n // A metadata-only change moves position without changing the ETag.\n const unchanged = previous !== undefined && !previous.deleted && previous.etag === change.etag && previous.content;\n changes.push({ key: change.key, deleted: false, etag: change.etag, hash, metadata: change.metadata, content: unchanged });\n if (!unchanged) candidates.push({ index: changes.length - 1, key: change.key, etag: change.etag, hash });\n }\n\n const held = await this.#store.hasContent([...new Set(candidates.map((candidate) => candidate.hash))]);\n const needed = candidates.filter((candidate) => {\n if (!held.has(candidate.hash)) return true;\n (changes[candidate.index] as Extract<VerifiedChange, { deleted: false }>).content = true;\n return false;\n });\n\n let fetched = 0;\n for (let start = 0; start < needed.length; start += FETCH_CHUNK) {\n const chunk = needed.slice(start, start + FETCH_CHUNK);\n const contents = await attempt.transport.fetchContent(\n chunk.map((candidate) => candidate.key),\n attempt.signal === undefined ? undefined : { signal: attempt.signal },\n );\n for (const candidate of chunk) {\n const ok = acceptContent(candidate, contents.get(candidate.key));\n if (ok === undefined) continue; // stale or gone: commit with content=0, repaired later\n blobs.set(candidate.hash, ok);\n (changes[candidate.index] as Extract<VerifiedChange, { deleted: false }>).content = true;\n fetched += 1;\n }\n await this.#store.renewLease(attempt.lease, this.#leaseTtlMs);\n }\n return { changes, blobs, fetched };\n }\n\n async #repair(attempt: Attempt, cursor: string | null, nodeDid: string | null, coverage: Coverage): Promise<number> {\n const pending = await this.#store.pendingRepairs(Number.MAX_SAFE_INTEGER);\n let repaired = 0;\n for (let start = 0; start < pending.length; start += REPAIR_BATCH) {\n const chunk = pending.slice(start, start + REPAIR_BATCH);\n const contents = await attempt.transport.fetchContent(\n chunk.map((entry) => entry.key),\n attempt.signal === undefined ? undefined : { signal: attempt.signal },\n );\n const blobs = new Map<string, Uint8Array>();\n const changes: VerifiedChange[] = [];\n for (const entry of chunk) {\n const bytes = acceptContent(entry, contents.get(entry.key));\n if (bytes === undefined) continue;\n blobs.set(entry.hash, bytes);\n changes.push({ ...entry, content: true });\n }\n await this.#store.renewLease(attempt.lease, this.#leaseTtlMs);\n if (changes.length === 0) continue;\n const state = await this.#state();\n await this.#store.applyPage(attempt.lease, {\n changes,\n blobs,\n cursor,\n source: { nodeDid: nodeDid ?? \"\", space: state.config.space, prefix: state.config.prefix },\n authority: null,\n coverage,\n at: new Date(this.#now()).toISOString(),\n window: this.#syncWindow(state.authority, attempt.grant),\n complete: false,\n promoteGrant: null,\n });\n repaired += changes.length;\n }\n return repaired;\n }\n}\n\n/**\n * Accept bytes only if they hash to the change row's ETag. A fetch that saw a\n * newer ETag (or none) is stale, not an attack: the next feed page carries\n * the newer state. Bytes that claim the attested ETag but hash differently are.\n */\nfunction acceptContent(\n expected: { key: string; etag: string; hash: string },\n fetched: FetchedContent | undefined,\n): Uint8Array | undefined {\n if (fetched === undefined || \"missing\" in fetched) return undefined;\n if (contentHash(fetched.bytes) === expected.hash) return fetched.bytes;\n if (sameEtag(fetched.etag, expected.etag)) {\n throw new ReplicaError(\n ReplicaErrorCode.CONTENT_MISMATCH,\n `The content of ${JSON.stringify(expected.key)} does not hash to its ETag ${expected.etag}.`,\n { key: expected.key },\n );\n }\n return undefined;\n}\n\nexport type { LocalEntry };\n","/**\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 /** 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 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} 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","import { ReplicaError, ReplicaErrorCode } from \"./errors.js\";\nimport type { AuthorityState, AuthorityWindow, GrantRecord, LocalReadPolicy } from \"./types.js\";\n\n/**\n * The node's `kv_prefix_covers`: whole path segments, byte-exact. `notes/`\n * covers everything under `notes/` but not `notes`; `notes` covers `notes`\n * and `notes/…` but not `notes-secret/x`.\n */\nexport function kvPrefixCovers(prefix: string, key: string): boolean {\n if (prefix === \"\") return true;\n if (prefix.endsWith(\"/\")) return key.startsWith(prefix);\n return key === prefix || key.startsWith(`${prefix}/`);\n}\n\n/**\n * Syncing the owner's secrets space, or any prefix overlapping the vault\n * namespace (including bare `vault`), copies encrypted secret material to\n * disk and needs an explicit opt-in (TC-733).\n */\nexport function requiresSecretsOptIn(space: string, prefix: string): boolean {\n if (space === \"secrets\" || space.endsWith(\":secrets\")) return true;\n const firstSegment = prefix.split(\"/\", 1)[0];\n return firstSegment === \"vault\";\n}\n\n/** `\"blake3-<hex>\"` (quoted or not) → the 64-hex hash, or undefined. */\nexport function hashFromEtag(etag: string): string | undefined {\n const match = /^\"?blake3-([0-9a-f]{64})\"?$/.exec(etag);\n return match?.[1];\n}\n\nconst RFC3339 = /^\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}(?:\\.\\d+)?(?:Z|[+-]\\d{2}:\\d{2})$/;\n\n/** An RFC 3339 instant that parses: the only form an authority bound may take. */\nexport function isInstant(value: string): boolean {\n return RFC3339.test(value) && Number.isFinite(Date.parse(value));\n}\n\nexport type EffectiveAuthority = {\n state: AuthorityState;\n notBefore: string | null;\n expiresAt: string | null;\n retainUntil: string | null;\n /** The retention grant was revoked: after expiry, reads are revoked rather than expired. */\n retentionRevoked: boolean;\n};\n\nfunction minIso(a: string | null, bSeconds: number | null): string | null {\n if (bSeconds === null) return a;\n const b = new Date(bSeconds * 1000).toISOString();\n if (a === null) return b;\n return Date.parse(a) <= Date.parse(b) ? a : b;\n}\n\nfunction maxIso(a: string | null, bSeconds: number | null): string | null {\n if (bSeconds === null) return a;\n const b = new Date(bSeconds * 1000).toISOString();\n if (a === null) return b;\n return Date.parse(a) >= Date.parse(b) ? a : b;\n}\n\n/**\n * The window local reads are enforced against: the node-attested window from\n * the last successful sync, bounded by the installed leaf grant (an upper\n * bound only — a multi-parent ancestor can end the chain earlier, which only\n * the node can see).\n */\nexport function effectiveAuthority(input: {\n window: AuthorityWindow | null;\n grant: GrantRecord | null;\n revoked: string | null;\n retentionRevoked?: string | null;\n policy: LocalReadPolicy;\n now: number;\n}): EffectiveAuthority {\n const notBefore = maxIso(input.window?.notBefore ?? null, input.grant?.notBefore ?? null);\n const expiresAt = minIso(input.window?.expiresAt ?? null, input.grant?.expiresAt ?? null);\n const retainUntil = input.policy === \"retainAfterExpiry\" ? input.window?.retainUntil ?? null : null;\n let state: AuthorityState = \"valid\";\n if (input.revoked !== null) state = \"revoked\";\n else if (notBefore !== null && input.now < Date.parse(notBefore)) state = \"not-yet-valid\";\n else if (expiresAt !== null && input.now >= Date.parse(expiresAt)) state = \"expired\";\n return { state, notBefore, expiresAt, retainUntil, retentionRevoked: (input.retentionRevoked ?? null) !== null };\n}\n\n/**\n * Throws unless local reads are allowed now. Returns the reported authority\n * state: `valid`, or `expired` while a node-attested retention holds.\n */\nexport function assertReadable(authority: EffectiveAuthority, now: number): AuthorityState {\n switch (authority.state) {\n case \"valid\":\n return \"valid\";\n case \"revoked\":\n throw new ReplicaError(ReplicaErrorCode.GRANT_REVOKED, \"The replica's grant was revoked; local reads are blocked.\");\n case \"not-yet-valid\":\n throw new ReplicaError(\n ReplicaErrorCode.GRANT_NOT_YET_VALID,\n `The replica's grant is not valid before ${authority.notBefore}.`,\n );\n case \"expired\":\n if (authority.retentionRevoked) {\n throw new ReplicaError(\n ReplicaErrorCode.GRANT_REVOKED,\n `The replica's grant expired at ${authority.expiresAt} and its retention grant was revoked; local reads are blocked.`,\n );\n }\n if (authority.retainUntil !== null && now < Date.parse(authority.retainUntil)) return \"expired\";\n throw new ReplicaError(\n ReplicaErrorCode.GRANT_EXPIRED,\n authority.retainUntil === null\n ? `The replica's grant expired at ${authority.expiresAt}; local reads are blocked.`\n : `The replica's retention ended at ${authority.retainUntil}; local reads are blocked.`,\n );\n }\n}\n","import { ed25519 } from \"@noble/curves/ed25519\";\nimport { blake3 } from \"@noble/hashes/blake3\";\n\nimport { ReplicaError, ReplicaErrorCode } from \"./errors.js\";\nimport { kvPrefixCovers } from \"./scope.js\";\nimport type { GrantRecord } from \"./types.js\";\n\n/** A compact UCAN grant whose Ed25519 signature checked out against its issuer. */\nexport type ParsedUcanGrant = GrantRecord & {\n /** The signed attenuation: resource → ability → caveats. */\n att: Record<string, Record<string, unknown>>;\n /** Signed proof CIDs. */\n prf: string[];\n /** The compact JWT, without any `Bearer ` prefix. */\n jwt: string;\n};\n\nconst BASE58 = \"123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz\";\nconst BASE32 = \"abcdefghijklmnopqrstuvwxyz234567\";\n\nfunction base58Decode(text: string): Uint8Array {\n let value = 0n;\n for (const char of text) {\n const digit = BASE58.indexOf(char);\n if (digit < 0) throw new Error(\"invalid base58\");\n value = value * 58n + BigInt(digit);\n }\n const bytes: number[] = [];\n while (value > 0n) {\n bytes.unshift(Number(value & 0xffn));\n value >>= 8n;\n }\n for (const char of text) {\n if (char !== \"1\") break;\n bytes.unshift(0);\n }\n return Uint8Array.from(bytes);\n}\n\nfunction base32Encode(bytes: Uint8Array): string {\n let out = \"\";\n let buffer = 0;\n let bits = 0;\n for (const byte of bytes) {\n buffer = (buffer << 8) | byte;\n bits += 8;\n while (bits >= 5) {\n out += BASE32[(buffer >>> (bits - 5)) & 31];\n bits -= 5;\n }\n }\n if (bits > 0) out += BASE32[(buffer << (5 - bits)) & 31];\n return out;\n}\n\nfunction base64UrlDecode(text: string): Uint8Array {\n if (!/^[A-Za-z0-9_-]*$/.test(text)) throw new Error(\"invalid base64url\");\n const padded = text.replace(/-/g, \"+\").replace(/_/g, \"/\") + \"===\".slice((text.length + 3) % 4);\n const binary = globalThis.atob(padded);\n return Uint8Array.from(binary, (char) => char.charCodeAt(0));\n}\n\n/**\n * The canonical TinyCloud delegation CID of a compact UCAN: CIDv1, raw codec\n * (0x55), blake3-256 multihash over the JWT's UTF-8 bytes, base32 multibase.\n * Matches `computeCid(bytes, 0x55)` in the SDK's WASM bindings.\n */\nexport function ucanCid(jwt: string): string {\n const digest = blake3(new TextEncoder().encode(jwt));\n const bytes = new Uint8Array(4 + digest.length);\n bytes.set([0x01, 0x55, 0x1e, 0x20]);\n bytes.set(digest, 4);\n return `b${base32Encode(bytes)}`;\n}\n\n/** The raw Ed25519 public key of a `did:key:z6Mk…` DID (fragment ignored). */\nfunction ed25519KeyOfDid(did: string): Uint8Array | undefined {\n const bare = did.split(\"#\", 1)[0]!;\n if (!bare.startsWith(\"did:key:z\")) return undefined;\n let decoded: Uint8Array;\n try {\n decoded = base58Decode(bare.slice(\"did:key:z\".length));\n } catch {\n return undefined;\n }\n // multicodec ed25519-pub = 0xed (varint 0xed 0x01)\n if (decoded.length !== 34 || decoded[0] !== 0xed || decoded[1] !== 0x01) return undefined;\n return decoded.subarray(2);\n}\n\nfunction invalid(message: string): ReplicaError {\n return new ReplicaError(ReplicaErrorCode.GRANT_INVALID, message);\n}\n\n/**\n * Parse and verify a compact UCAN grant. Worker-safe (no Node APIs).\n * Everything returned — audience, window, attenuation, proofs — comes from\n * the signed payload, never from wrapper fields around it. Verifying the\n * signature proves the issuer signed exactly these claims; whether the chain\n * holds is the node's call on every sync.\n */\nexport function parseUcanGrant(input: Uint8Array | string): ParsedUcanGrant {\n const raw = typeof input === \"string\" ? input : new TextDecoder().decode(input);\n const jwt = raw.trim().replace(/^Bearer\\s+/i, \"\");\n const parts = jwt.split(\".\");\n if (parts.length !== 3) {\n throw new ReplicaError(\n ReplicaErrorCode.GRANT_FORMAT_UNSUPPORTED,\n \"The grant is not a compact UCAN. SIWE/CACAO grants cannot back a replica; ask the owner for a device grant (tc auth grant).\",\n );\n }\n const [headerPart, payloadPart, signaturePart] = parts as [string, string, string];\n let header: Record<string, unknown>;\n let payload: Record<string, unknown>;\n let signature: Uint8Array;\n try {\n header = JSON.parse(new TextDecoder().decode(base64UrlDecode(headerPart)));\n payload = JSON.parse(new TextDecoder().decode(base64UrlDecode(payloadPart)));\n signature = base64UrlDecode(signaturePart);\n } catch {\n throw invalid(\"The grant is not well-formed base64url JSON.\");\n }\n if (header?.alg !== \"EdDSA\") {\n throw new ReplicaError(\n ReplicaErrorCode.GRANT_FORMAT_UNSUPPORTED,\n `The grant is signed with ${String(header?.alg)}; a replica needs an Ed25519 (EdDSA) device grant.`,\n );\n }\n const issuer = payload?.iss;\n const audience = payload?.aud;\n if (typeof issuer !== \"string\" || typeof audience !== \"string\" || audience.length === 0) {\n throw invalid(\"The grant has no signed issuer or audience.\");\n }\n const key = ed25519KeyOfDid(issuer);\n if (key === undefined) {\n throw new ReplicaError(\n ReplicaErrorCode.GRANT_FORMAT_UNSUPPORTED,\n `The grant issuer ${issuer} is not an Ed25519 did:key.`,\n );\n }\n let verified = false;\n try {\n verified = ed25519.verify(signature, new TextEncoder().encode(`${headerPart}.${payloadPart}`), key);\n } catch {\n verified = false;\n }\n if (!verified) throw invalid(\"The grant's signature does not verify against its issuer.\");\n\n const att = payload.att;\n if (att === null || typeof att !== \"object\" || Array.isArray(att)) throw invalid(\"The grant has no signed attenuation.\");\n for (const abilities of Object.values(att)) {\n if (abilities === null || typeof abilities !== \"object\" || Array.isArray(abilities)) {\n throw invalid(\"The grant has a malformed signed attenuation.\");\n }\n }\n const prf = payload.prf ?? [];\n if (!Array.isArray(prf) || prf.some((proof) => typeof proof !== \"string\")) throw invalid(\"The grant has malformed signed proofs.\");\n const window = (name: \"nbf\" | \"exp\"): number | null => {\n const value = payload[name];\n if (value === undefined || value === null) return null;\n if (typeof value !== \"number\" || !Number.isFinite(value)) throw invalid(`The grant's signed ${name} is not a number.`);\n return value;\n };\n\n return {\n cid: ucanCid(jwt),\n bytes: new TextEncoder().encode(jwt),\n jwt,\n issuer,\n audience,\n notBefore: window(\"nbf\"),\n expiresAt: window(\"exp\"),\n att: att as Record<string, Record<string, unknown>>,\n prf: prf as string[],\n };\n}\n\n/** The KV path a signed resource names in `space`, or undefined for any other resource. */\nfunction kvPathIn(resource: string, space: string): string | undefined {\n for (const base of [`${space}/kv/`, `tinycloud://${space}/kv/`]) {\n if (resource.startsWith(base)) return resource.slice(base.length);\n }\n return undefined;\n}\n\n/** True when the signed attenuation grants `ability` on a KV path covering `prefix`. */\nexport function grantCovers(grant: Pick<ParsedUcanGrant, \"att\">, space: string, prefix: string, ability: string): boolean {\n for (const [resource, abilities] of Object.entries(grant.att)) {\n const path = kvPathIn(resource, space);\n if (path === undefined || !kvPrefixCovers(path, prefix)) continue;\n // `tinycloud.kv/sync` is never implied by a wildcard (TC-732).\n if (ability in abilities) return true;\n if (ability !== \"tinycloud.kv/sync\" && ability !== \"tinycloud.kv/retain\" && \"tinycloud.kv/*\" in abilities) return true;\n }\n return false;\n}\n\n/** The space ids (native form) for which the grant carries `tinycloud.kv/sync`. */\nexport function syncGrantSpaces(grant: Pick<ParsedUcanGrant, \"att\">): string[] {\n const spaces = new Set<string>();\n for (const [resource, abilities] of Object.entries(grant.att)) {\n if (!(\"tinycloud.kv/sync\" in abilities)) continue;\n const match = /^(?:tinycloud:\\/\\/)?(tinycloud:[^/]+)\\/kv\\//.exec(resource);\n if (match) spaces.add(match[1]!);\n }\n return [...spaces];\n}\n\n/**\n * Install-time checks: the grant must name this device, cover both\n * `tinycloud.kv/sync` and `tinycloud.kv/get` on the prefix, and be inside its\n * own signed window. The node re-checks the whole chain on every sync.\n */\nexport function assertGrantInstallable(\n grant: ParsedUcanGrant,\n input: { deviceDid: string; space: string; prefix: string; now: number },\n): void {\n if (grant.audience.split(\"#\", 1)[0] !== input.deviceDid.split(\"#\", 1)[0]) {\n throw new ReplicaError(\n ReplicaErrorCode.GRANT_AUDIENCE_MISMATCH,\n `The grant was issued to ${grant.audience}, not this device (${input.deviceDid}).`,\n );\n }\n for (const ability of [\"tinycloud.kv/sync\", \"tinycloud.kv/get\"]) {\n if (!grantCovers(grant, input.space, input.prefix, ability)) {\n throw new ReplicaError(\n ReplicaErrorCode.GRANT_NOT_COVERING,\n `The grant does not carry ${ability} on ${input.space}/kv/${input.prefix}.`,\n { ability },\n );\n }\n }\n const nowSeconds = input.now / 1000;\n if (grant.notBefore !== null && nowSeconds < grant.notBefore) {\n throw new ReplicaError(\n ReplicaErrorCode.GRANT_NOT_YET_VALID,\n `The grant is not valid before ${new Date(grant.notBefore * 1000).toISOString()}.`,\n );\n }\n if (grant.expiresAt !== null && nowSeconds >= grant.expiresAt) {\n throw new ReplicaError(\n ReplicaErrorCode.GRANT_EXPIRED,\n `The grant expired at ${new Date(grant.expiresAt * 1000).toISOString()}.`,\n );\n }\n}\n","import { ReplicaError, ReplicaErrorCode } from \"./errors.js\";\nimport { isInstant } from \"./scope.js\";\nimport type { AuthorityWindow, Change, FetchedContent, ReplicaTransport, SyncPage } from \"./types.js\";\n\n/** The sdk-services `ServiceError` fields the transport reads. */\ntype ServiceError = { code: string; message: string; meta?: Record<string, unknown> };\ntype Result<T> = { ok: true; data: T } | { ok: false; error: ServiceError };\n\ntype KVChangesResponse = {\n changes: Change[];\n more: boolean;\n cursor: string;\n source: { nodeDid: string; space: string; prefix: string };\n authority: AuthorityWindow;\n};\n\ntype KVValue = { data: unknown; headers: { etag?: string } };\n\n/**\n * The slice of sdk-services `KVService` a replica needs: the `tinycloud.kv/sync`\n * feed (`changes`, TC-736) and binary reads. Typed structurally so this\n * package never imports an SDK entry point (web-sdk defines custom elements\n * on load; node-sdk pulls in Node-only code).\n */\nexport interface KVSyncClient {\n changes(options: {\n prefix: string;\n cursor?: string;\n limit?: number;\n retentionGrant?: string;\n signal?: AbortSignal;\n }): Promise<Result<KVChangesResponse>>;\n get(key: string, options: { binary: true; signal?: AbortSignal }): Promise<Result<KVValue>>;\n batchGet(\n keys: string[],\n options: { binary: true; signal?: AbortSignal },\n ): Promise<Result<{ results: Array<{ key: string; result: Result<KVValue> }> }>>;\n}\n\n/** TC-736 maps a revoked chain on `kv/sync` to these codes. */\nconst REVOKED_CODES = new Set([\"AUTH_DELEGATION_REVOKED\", \"AUTH_DELEGATION_ANCESTOR_REVOKED\"]);\n/**\n * The node's whole 401 body for a revoked chain (`InvocationError` in\n * tinycloud-core): only CIDs follow the denial code, never a key or prefix.\n */\nconst REVOKED_BODY =\n /^(?:Invalid invocation: )?(?:delegation-revoked: [A-Za-z0-9]+|delegation-ancestor-revoked: ancestor=[A-Za-z0-9]+ (?:invoked|parent)=[A-Za-z0-9]+)$/;\n\n/**\n * Revocation only from typed evidence: the SDK's revocation codes, or (for\n * reads, which the SDK reports as `AUTH_UNAUTHORIZED`) a 401 whose message is\n * exactly the SDK's `<request>: 401 - <body>` with the body the node's\n * revocation denial. Free text is never searched: messages carry keys and\n * prefixes, which anyone with write access chooses.\n */\nfunction isRevocation(error: ServiceError, status: number | undefined, request: string | undefined): boolean {\n if (REVOKED_CODES.has(error.code)) return true;\n if (status !== 401 || request === undefined) return false;\n const lead = `${request}: 401 - `;\n return error.message.startsWith(lead) && REVOKED_BODY.test(error.message.slice(lead.length));\n}\n\n/**\n * Map an SDK failure to the replica's typed errors. `request` is the SDK's\n * own description of the call (`Failed to get key \"k\"`), which lets a read's\n * 401 body be told apart from the key it names.\n */\nexport function replicaErrorFromService(error: ServiceError, context: string, request?: string): ReplicaError {\n const meta = error.meta ?? {};\n const status = typeof meta.status === \"number\" ? meta.status : undefined;\n const detail = { serviceCode: error.code, ...(status === undefined ? {} : { status }) };\n const reason = typeof meta.reason === \"string\" ? meta.reason : undefined;\n if (error.code === \"KV_SYNC_RESET_REQUIRED\" || status === 410) {\n return new ReplicaError(\n ReplicaErrorCode.RESET_REQUIRED,\n `${context}: the node requires a fresh bootstrap (${reason ?? \"reason not given\"}).`,\n { ...detail, reason: reason ?? \"reset-required\" },\n );\n }\n if (isRevocation(error, status, request)) {\n return new ReplicaError(ReplicaErrorCode.GRANT_REVOKED, `${context}: ${error.message}`, detail);\n }\n if (error.code === \"KV_RETENTION_GRANT_REFUSED\") {\n return new ReplicaError(ReplicaErrorCode.RETENTION_GRANT_REFUSED, `${context}: ${error.message}`, {\n ...detail,\n ...(reason === undefined ? {} : { reason }),\n });\n }\n if (status === 401 || status === 403 || /^AUTH_/.test(error.code)) {\n // Not destructive either way (both refuse with exit 5 and keep the replica).\n if (/expired/i.test(error.message)) {\n return new ReplicaError(ReplicaErrorCode.GRANT_EXPIRED, `${context}: ${error.message}`, detail);\n }\n return new ReplicaError(ReplicaErrorCode.GRANT_UNAUTHORIZED, `${context}: ${error.message}`, detail);\n }\n if (status !== undefined) {\n return new ReplicaError(ReplicaErrorCode.NODE_ERROR, `${context}: ${error.message}`, detail);\n }\n if (error.code === \"NETWORK_ERROR\" || /TIMEOUT|ABORT/.test(error.code)) {\n return new ReplicaError(ReplicaErrorCode.NETWORK_ERROR, `${context}: ${error.message}`, detail);\n }\n return new ReplicaError(ReplicaErrorCode.NODE_ERROR, `${context}: ${error.message}`, detail);\n}\n\nconst encoder = new TextEncoder();\n\n/** Byte order of UTF-8 keys: how the node orders batch results. */\nfunction compareKeyBytes(a: string, b: string): number {\n const x = encoder.encode(a);\n const y = encoder.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\nfunction isNotFound(error: ServiceError): boolean {\n return error.code === \"KV_NOT_FOUND\" || error.code === \"NOT_FOUND\";\n}\n\n/** An authority bound: null, or an RFC 3339 instant. Anything else fails closed. */\nfunction instant(value: unknown): string | null {\n if (value === null) return null;\n if (typeof value === \"string\" && isInstant(value)) return value;\n throw new ReplicaError(ReplicaErrorCode.PROTOCOL_ERROR, `The kv/sync authority holds a bound that is not a timestamp: ${JSON.stringify(value)}.`);\n}\n\nfunction checkPage(data: KVChangesResponse): SyncPage {\n const ok =\n data !== null &&\n typeof data === \"object\" &&\n Array.isArray(data.changes) &&\n typeof data.more === \"boolean\" &&\n typeof data.cursor === \"string\" &&\n typeof data.source?.nodeDid === \"string\" &&\n typeof data.source?.space === \"string\" &&\n typeof data.source?.prefix === \"string\" &&\n data.authority !== null &&\n typeof data.authority === \"object\" &&\n data.changes.every(\n (change) =>\n typeof change?.key === \"string\" &&\n (change.deleted === true ||\n (change.deleted === false &&\n typeof change.etag === \"string\" &&\n change.metadata !== null &&\n typeof change.metadata === \"object\" &&\n Object.values(change.metadata).every((value) => typeof value === \"string\"))),\n );\n if (!ok) throw new ReplicaError(ReplicaErrorCode.PROTOCOL_ERROR, \"The kv/sync response does not match the wire contract.\");\n return {\n changes: data.changes.map((change) =>\n change.deleted ? { key: change.key, deleted: true } : { key: change.key, deleted: false, etag: change.etag, metadata: { ...change.metadata } },\n ),\n more: data.more,\n cursor: data.cursor,\n source: { nodeDid: data.source.nodeDid, space: data.source.space, prefix: data.source.prefix },\n authority: {\n notBefore: instant(data.authority.notBefore),\n expiresAt: instant(data.authority.expiresAt),\n retainUntil: instant(data.authority.retainUntil),\n },\n };\n}\n\nfunction fetched(key: string, result: Result<KVValue>, request: string): FetchedContent {\n if (!result.ok) {\n if (isNotFound(result.error)) return { missing: true };\n throw replicaErrorFromService(result.error, `Fetching ${JSON.stringify(key)}`, request);\n }\n if (!(result.data.data instanceof Uint8Array)) {\n throw new ReplicaError(ReplicaErrorCode.PROTOCOL_ERROR, `The value of ${JSON.stringify(key)} did not arrive as bytes.`);\n }\n return { bytes: result.data.data, etag: result.data.headers.etag ?? \"\" };\n}\n\n/** A `ReplicaTransport` over a KV service bound to the replica's space and grant. */\nexport function kvSyncTransport(kv: KVSyncClient): ReplicaTransport {\n return {\n async syncPage({ prefix, cursor, limit, retentionGrant, signal }) {\n const result = await kv.changes({\n prefix,\n limit,\n ...(cursor === undefined ? {} : { cursor }),\n ...(retentionGrant === undefined ? {} : { retentionGrant }),\n ...(signal === undefined ? {} : { signal }),\n });\n if (!result.ok) throw replicaErrorFromService(result.error, \"Syncing\");\n return checkPage(result.data);\n },\n\n async fetchContent(keys, options) {\n const contents = new Map<string, FetchedContent>();\n if (keys.length === 0) return contents;\n const signal = options?.signal;\n // The node answers a batch in key byte order and the SDK matches results\n // to requests by position, so ask in that order.\n const ordered = [...keys].sort(compareKeyBytes);\n const batch = await kv.batchGet(ordered, { binary: true, ...(signal === undefined ? {} : { signal }) });\n if (batch.ok) {\n for (const item of batch.data.results) {\n const result = item.result;\n // One oversize value is fetched alone below.\n if (!result.ok && result.error.code === \"KV_RESPONSE_TOO_LARGE\") continue;\n contents.set(item.key, fetched(item.key, result, `Failed to get key ${JSON.stringify(item.key)}`));\n }\n } else if (batch.error.code !== \"KV_RESPONSE_TOO_LARGE\") {\n throw replicaErrorFromService(batch.error, `Fetching ${keys.length} value(s)`, `Failed to batch read ${ordered.length} key(s)`);\n }\n for (const key of keys) {\n if (contents.has(key)) continue;\n const request = `Failed to get key ${JSON.stringify(key)}`;\n contents.set(key, fetched(key, await kv.get(key, { binary: true, ...(signal === undefined ? {} : { signal }) }), request));\n }\n return contents;\n },\n };\n}\n"],"mappings":"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","names":["fetched","import_blake3"]}
|
|
1
|
+
{"version":3,"sources":["../src/index.ts","../src/engine.ts","../src/errors.ts","../src/scope.ts","../src/grant.ts","../src/transport.ts"],"sourcesContent":["export { Replica, contentHash } from \"./engine.js\";\nexport type { ReadMeta, ReplicaListEntry, ReplicaListResult, ReplicaOptions, ReplicaReadResult, SyncReport } from \"./engine.js\";\nexport { ReplicaError, ReplicaErrorCode, isReplicaError } from \"./errors.js\";\nexport { assertGrantInstallable, grantCovers, parseUcanGrant, syncGrantSpaces, ucanCid } from \"./grant.js\";\nexport type { ParsedUcanGrant } from \"./grant.js\";\nexport { assertReadable, effectiveAuthority, hashFromEtag, kvPrefixCovers, requiresSecretsOptIn } from \"./scope.js\";\nexport type { EffectiveAuthority } from \"./scope.js\";\nexport { kvSyncTransport, replicaErrorFromService } from \"./transport.js\";\nexport type { KVSyncClient } from \"./transport.js\";\nexport type * from \"./types.js\";\n","import { blake3 } from \"@noble/hashes/blake3\";\nimport { bytesToHex } from \"@noble/hashes/utils\";\n\nimport { ReplicaError, ReplicaErrorCode, isReplicaError } from \"./errors.js\";\nimport { assertReadable, effectiveAuthority, hashFromEtag, isInstant, kvPrefixCovers } from \"./scope.js\";\nimport type {\n AuthorityState,\n AuthorityWindow,\n Coverage,\n FetchedContent,\n GrantRecord,\n LeaseToken,\n ListOpts,\n LocalEntry,\n ReplicaLastError,\n ReplicaState,\n ReplicaStatus,\n ReplicaStore,\n ReplicaTransport,\n SyncPage,\n VerifiedChange,\n} from \"./types.js\";\n\n/** Metadata every local read carries: the replica reports observed state, never finality. */\nexport type ReadMeta = {\n consistency: \"observed\";\n source: { host: string; nodeDid: string | null; space: string; prefix: string };\n asOf: string | null;\n coverage: Coverage;\n /** `expired` while reads continue under a node-attested retention. */\n authority: AuthorityState;\n};\n\nexport type ReplicaReadResult =\n | { status: \"present\"; key: string; value: Uint8Array; etag: string; metadata: Record<string, string>; meta: ReadMeta }\n | { status: \"content_missing\"; key: string; etag: string; metadata: Record<string, string>; meta: ReadMeta }\n | { status: \"deleted\" | \"absent\"; key: string; meta: ReadMeta }\n | { status: \"coverage_incomplete\" | \"not_covered\"; key: string; meta: ReadMeta };\n\nexport type ReplicaListEntry = { key: string; etag: string; metadata: Record<string, string>; content: boolean };\n\nexport type ReplicaListResult = { entries: ReplicaListEntry[]; meta: ReadMeta };\n\nexport type SyncReport = {\n pages: number;\n changes: number;\n deleted: number;\n fetched: number;\n contentMissing: number;\n repaired: number;\n resets: number;\n coverage: Coverage;\n cursorAdvanced: boolean;\n promotedGrant: boolean;\n blobsCollected: number;\n};\n\nexport type ReplicaOptions = {\n store: ReplicaStore;\n /** One transport for whichever grant the replica syncs under (tests, single-grant hosts). */\n transport?: ReplicaTransport;\n /**\n * A transport that invokes with exactly `grant`. Lets the engine try a\n * pending grant and fall back to the active one if the node refuses it.\n */\n transportFor?: (grant: GrantRecord) => ReplicaTransport;\n now?: () => number;\n leaseTtlMs?: number;\n /**\n * Called after every committed page and repair batch — also those of a sync\n * that later fails. Lets the caller report partial progress immediately.\n */\n onCommit?: () => void;\n};\n\nconst DEFAULT_PAGE_LIMIT = 500;\nconst DEFAULT_LEASE_TTL_MS = 120_000;\nconst FETCH_CHUNK = 100;\nconst REPAIR_BATCH = 100;\nconst RETENTION_REVOKED = new Set([\"retention-grant-revoked\", \"retention-grant-ancestor-revoked\"]);\n/** Refusals of a pending grant that leave the active grant to serve. */\nconst PENDING_REFUSALS = new Set<string>([\n ReplicaErrorCode.GRANT_UNAUTHORIZED,\n ReplicaErrorCode.GRANT_EXPIRED,\n ReplicaErrorCode.GRANT_NOT_YET_VALID,\n]);\n\n/** `\"blake3-\" + hex(blake3(bytes))`, the strong ETag the node attests for content. */\nexport function contentHash(bytes: Uint8Array): string {\n return bytesToHex(blake3(bytes));\n}\n\nfunction sameEtag(a: string, b: string): boolean {\n return a.replace(/^\"|\"$/g, \"\") === b.replace(/^\"|\"$/g, \"\");\n}\n\nfunction lastErrorOf(error: unknown, at: number): ReplicaLastError {\n return {\n at: new Date(at).toISOString(),\n code: isReplicaError(error) ? error.code : \"ERROR\",\n message: error instanceof Error ? error.message : String(error),\n };\n}\n\n/** One sync attempt under one grant. */\ntype Attempt = {\n lease: LeaseToken;\n grant: GrantRecord;\n transport: ReplicaTransport;\n /** The pending grant until the first page under it commits (and promotes it). */\n promote: GrantRecord | null;\n signal: AbortSignal | undefined;\n};\n\n/**\n * A durable, read-only local replica of one KV prefix. Reads never touch the\n * network; `sync` pulls `tinycloud.kv/sync` pages, verifies every byte\n * against the node-attested ETag, and commits each page with its cursor.\n */\nexport class Replica {\n readonly #store: ReplicaStore;\n readonly #transportFor: ((grant: GrantRecord) => ReplicaTransport) | undefined;\n readonly #now: () => number;\n readonly #leaseTtlMs: number;\n readonly #onCommit: (() => void) | undefined;\n\n constructor(options: ReplicaOptions) {\n this.#store = options.store;\n const transport = options.transport;\n this.#transportFor = options.transportFor ?? (transport === undefined ? undefined : () => transport);\n this.#now = options.now ?? Date.now;\n this.#leaseTtlMs = options.leaseTtlMs ?? DEFAULT_LEASE_TTL_MS;\n this.#onCommit = options.onCommit;\n }\n\n async #state(): Promise<ReplicaState> {\n const state = await this.#store.open();\n if (state === null) throw new ReplicaError(ReplicaErrorCode.NOT_FOUND, \"The replica has not been created.\");\n return state;\n }\n\n /** Throws unless local reads are allowed now; returns the meta every read reports. */\n #readMeta(state: ReplicaState): ReadMeta {\n const now = this.#now();\n const authority = assertReadable(\n effectiveAuthority({\n window: state.authority,\n grant: state.grant,\n revoked: state.revoked,\n retentionRevoked: state.retentionRevoked,\n policy: state.config.localReadPolicy,\n now,\n }),\n now,\n );\n return {\n consistency: \"observed\",\n source: { host: state.config.host, nodeDid: state.nodeDid, space: state.config.space, prefix: state.config.prefix },\n asOf: state.lastSyncAt,\n coverage: state.coverage,\n authority,\n };\n }\n\n /**\n * Authority again, from the store's current state and the clock, once the\n * read has its data: a revocation (and its purge) or an expiry that landed\n * while the read ran wins, and the caller gets the error, not the data.\n */\n async #confirmedMeta(): Promise<ReadMeta> {\n return this.#readMeta(await this.#state());\n }\n\n async get(key: string, options: { verify?: boolean } = {}): Promise<ReplicaReadResult> {\n const state = await this.#state();\n this.#readMeta(state);\n if (!kvPrefixCovers(state.config.prefix, key)) return { status: \"not_covered\", key, meta: await this.#confirmedMeta() };\n // Readers take no lock: a sync may replace this entry and collect its old\n // blob between the two reads below. One re-read sees the new entry.\n for (let attempt = 0; ; attempt += 1) {\n const entry = await this.#store.get(key);\n if (entry === undefined) {\n const status = state.coverage === \"complete\" ? \"absent\" : \"coverage_incomplete\";\n return { status, key, meta: await this.#confirmedMeta() };\n }\n if (entry.deleted) return { status: \"deleted\", key, meta: await this.#confirmedMeta() };\n if (!entry.content) {\n return { status: \"content_missing\", key, etag: entry.etag, metadata: entry.metadata, meta: await this.#confirmedMeta() };\n }\n const value = await this.#store.readContent(entry.hash);\n const intact = value !== undefined && (options.verify === false || contentHash(value) === entry.hash);\n if (intact) {\n return { status: \"present\", key, value, etag: entry.etag, metadata: entry.metadata, meta: await this.#confirmedMeta() };\n }\n if (attempt === 0) continue;\n // A purge that ran under this read surfaces as its revocation, not as corruption.\n await this.#confirmedMeta();\n throw new ReplicaError(\n ReplicaErrorCode.INTEGRITY_ERROR,\n value === undefined\n ? `The stored content for ${JSON.stringify(key)} is missing.`\n : `The stored content for ${JSON.stringify(key)} does not match its hash.`,\n { key },\n );\n }\n }\n\n /** Live keys under `prefix` (a plain string prefix within the replica's scope). */\n async list(options: ListOpts = {}): Promise<ReplicaListResult> {\n const state = await this.#state();\n this.#readMeta(state);\n const scope = state.config.prefix;\n const prefix = options.prefix ?? \"\";\n if (prefix !== \"\" && !scope.startsWith(prefix) && !kvPrefixCovers(scope, prefix)) {\n throw new ReplicaError(\n ReplicaErrorCode.NOT_COVERED,\n `${JSON.stringify(prefix)} is outside the replica's prefix ${JSON.stringify(scope)}.`,\n );\n }\n const rows = await this.#store.list(options);\n const entries: ReplicaListEntry[] = [];\n for (const row of rows) {\n if (row.deleted) continue;\n entries.push({ key: row.key, etag: row.etag, metadata: row.metadata, content: row.content });\n }\n return { entries, meta: await this.#confirmedMeta() };\n }\n\n async status(): Promise<ReplicaStatus> {\n await this.#state();\n return this.#store.status(this.#now());\n }\n\n /** Clear entries, blobs, cursor and the source pin; keep the configuration and grant. */\n async reset(reason: string): Promise<void> {\n await this.#state();\n const lease = await this.#store.acquireSyncLease(this.#leaseTtlMs);\n if (lease === null) throw new ReplicaError(ReplicaErrorCode.BUSY, \"Another process is syncing this replica.\");\n try {\n await this.#store.reset(lease, reason);\n } finally {\n await this.#store.releaseLease(lease);\n }\n }\n\n /**\n * Sync under the pending grant if there is one, else the active grant. If\n * the node refuses a pending grant before anything committed under it, the\n * active grant serves: a revoked pending grant is discarded, any other\n * refusal is recorded on it. A revocation of the grant serving the replica,\n * learned anywhere in the sync, purges the replica before this returns.\n */\n async sync(options: { limit?: number; signal?: AbortSignal } = {}): Promise<SyncReport> {\n const transportFor = this.#transportFor;\n if (transportFor === undefined) throw new Error(\"Replica.sync needs a transport\");\n const initial = await this.#state();\n if (initial.revoked !== null) {\n throw new ReplicaError(ReplicaErrorCode.GRANT_REVOKED, `The replica's grant was revoked: ${initial.revoked}`);\n }\n const candidates = [initial.pendingGrant, initial.grant].filter((grant): grant is GrantRecord => grant !== null);\n if (candidates.length === 0) throw new ReplicaError(ReplicaErrorCode.GRANT_MISSING, \"No grant is installed for this replica.\");\n\n const lease = await this.#store.acquireSyncLease(this.#leaseTtlMs);\n if (lease === null) throw new ReplicaError(ReplicaErrorCode.BUSY, \"Another process is syncing this replica.\");\n try {\n for (const [index, grant] of candidates.entries()) {\n const pending = grant.cid === initial.pendingGrant?.cid;\n const attempt: Attempt = {\n lease,\n grant,\n transport: transportFor(grant),\n promote: pending ? grant : null,\n signal: options.signal,\n };\n try {\n return await this.#syncUnder(attempt, options.limit ?? DEFAULT_PAGE_LIMIT);\n } catch (error) {\n const canFallBack = pending && attempt.promote !== null && index < candidates.length - 1;\n if (canFallBack && isReplicaError(error, ReplicaErrorCode.GRANT_REVOKED)) {\n await this.#store.discardPendingGrant(grant.cid, error.message);\n continue;\n }\n if (canFallBack && isReplicaError(error) && PENDING_REFUSALS.has(error.code)) {\n await this.#store.recordPendingGrantError(grant.cid, lastErrorOf(error, this.#now()));\n continue;\n }\n throw error;\n }\n }\n throw new Error(\"unreachable: the last candidate either returns or throws\");\n } catch (error) {\n await this.#learn(error);\n throw error;\n } finally {\n await this.#store.releaseLease(lease).catch(() => undefined);\n }\n }\n\n /** Persist what a failed sync taught us before the error reaches the caller. */\n async #learn(error: unknown): Promise<void> {\n if (isReplicaError(error, ReplicaErrorCode.GRANT_REVOKED)) {\n // Already revoked (the store refused a commit): nothing more to learn.\n if ((await this.#store.open())?.revoked != null) return;\n // Persisted before the error returns: later reads raise GRANT_REVOKED, also after restart.\n await this.#store.markRevoked(error.message);\n return;\n }\n if (isReplicaError(error, ReplicaErrorCode.RETENTION_GRANT_REFUSED)) {\n const cid = error.detail?.retentionGrantCid;\n const reason = error.detail?.reason;\n if (typeof cid === \"string\" && typeof reason === \"string\" && RETENTION_REVOKED.has(reason)) {\n // Bound to the grant the refused request presented: a replacement installed meanwhile stays.\n await this.#store.markRetentionRevoked(cid, error.message);\n return;\n }\n }\n await this.#store.recordError(lastErrorOf(error, this.#now())).catch(() => undefined);\n }\n\n /** The effective window for `grant` under `window`; throws unless it holds now (no retention for syncing). */\n #syncWindow(window: AuthorityWindow | null, grant: GrantRecord): { notBefore: string | null; expiresAt: string | null } {\n const now = this.#now();\n const authority = effectiveAuthority({ window, grant, revoked: null, policy: \"whileGrantValid\", now });\n if (authority.state !== \"valid\") assertReadable({ ...authority, retainUntil: null }, now);\n return { notBefore: authority.notBefore, expiresAt: authority.expiresAt };\n }\n\n async #syncUnder(attempt: Attempt, limit: number): Promise<SyncReport> {\n const state = await this.#state();\n const { config } = state;\n // A replacement grant is judged on its own leaf window until the node attests it.\n this.#syncWindow(attempt.promote === null ? state.authority : null, attempt.grant);\n const report: SyncReport = {\n pages: 0,\n changes: 0,\n deleted: 0,\n fetched: 0,\n contentMissing: 0,\n repaired: 0,\n resets: 0,\n coverage: state.coverage,\n cursorAdvanced: false,\n promotedGrant: false,\n blobsCollected: 0,\n };\n let cursor = state.cursor;\n // The source pin survives an automatic reset; only an explicit reset clears it.\n let pinned = state.nodeDid;\n let coverage = state.coverage;\n // The retention CID this request presents: captured once so every page\n // commits under the same presented CID even if `setRetentionGrant` races.\n const retentionGrantCid = config.retentionGrantCid;\n\n for (;;) {\n let page: SyncPage;\n const retentionGrant = retentionGrantCid;\n try {\n page = await attempt.transport.syncPage({\n prefix: config.prefix,\n ...(cursor === null ? {} : { cursor }),\n limit,\n ...(retentionGrant === null ? {} : { retentionGrant }),\n ...(attempt.signal === undefined ? {} : { signal: attempt.signal }),\n });\n } catch (error) {\n if (isReplicaError(error, ReplicaErrorCode.RESET_REQUIRED) && report.resets === 0) {\n const reason = typeof error.detail?.reason === \"string\" ? error.detail.reason : \"reset-required\";\n await this.#store.reset(attempt.lease, `node: ${reason}`, { keepSource: true });\n report.resets += 1;\n cursor = null;\n coverage = \"empty\";\n continue;\n }\n if (isReplicaError(error, ReplicaErrorCode.RETENTION_GRANT_REFUSED) && retentionGrant !== null) {\n throw new ReplicaError(error.code, error.message, { ...error.detail, retentionGrantCid: retentionGrant }, { cause: error });\n }\n throw error;\n }\n\n this.#checkPage(page, config.space, config.prefix, pinned);\n // Fetch only while the attested window holds; the store re-checks it at commit.\n const window = this.#syncWindow(page.authority, attempt.grant);\n const verified = await this.#verify(page, attempt);\n coverage = page.more ? (coverage === \"complete\" ? \"complete\" : \"bootstrapping\") : \"complete\";\n const at = new Date(this.#now()).toISOString();\n // The store checks `window` against its clock inside the commit transaction.\n await this.#store.applyPage(attempt.lease, {\n changes: verified.changes,\n blobs: verified.blobs,\n cursor: page.cursor,\n source: page.source,\n authority: page.authority,\n coverage,\n at,\n retentionGrantCid: retentionGrant,\n window,\n complete: !page.more,\n promoteGrant: attempt.promote,\n });\n if (attempt.promote !== null) {\n report.promotedGrant = true;\n attempt.promote = null;\n }\n report.pages += 1;\n this.#onCommit?.();\n report.changes += page.changes.length;\n report.deleted += page.changes.filter((change) => change.deleted).length;\n report.fetched += verified.fetched;\n if (page.cursor !== cursor) report.cursorAdvanced = true;\n cursor = page.cursor;\n pinned = page.source.nodeDid;\n await this.#store.renewLease(attempt.lease, this.#leaseTtlMs);\n if (!page.more) break;\n }\n report.repaired = await this.#repair(attempt, cursor, pinned, coverage, retentionGrantCid);\n report.blobsCollected = await this.#store.collectGarbage(attempt.lease);\n report.coverage = coverage;\n report.contentMissing = (await this.#store.pendingRepairs(Number.MAX_SAFE_INTEGER)).length;\n return report;\n }\n\n #checkPage(page: SyncPage, space: string, prefix: string, pinnedNodeDid: string | null): void {\n if (page.source.space !== space || page.source.prefix !== prefix) {\n throw new ReplicaError(\n ReplicaErrorCode.PROTOCOL_ERROR,\n `The node answered for ${page.source.space}/${page.source.prefix}, not ${space}/${prefix}.`,\n );\n }\n // A bound that does not parse would widen the window (NaN compares false): fail closed.\n for (const [name, value] of Object.entries(page.authority)) {\n if (value !== null && (typeof value !== \"string\" || !isInstant(value))) {\n throw new ReplicaError(\n ReplicaErrorCode.PROTOCOL_ERROR,\n `The node attested authority.${name} = ${JSON.stringify(value)}, which is not a timestamp; nothing was committed.`,\n );\n }\n }\n if (pinnedNodeDid !== null && page.source.nodeDid !== pinnedNodeDid) {\n throw new ReplicaError(\n ReplicaErrorCode.SOURCE_CHANGED,\n `The feed now comes from ${page.source.nodeDid}, not the pinned source ${pinnedNodeDid}. Reset the replica to follow a new source.`,\n { pinned: pinnedNodeDid, received: page.source.nodeDid },\n );\n }\n for (const change of page.changes) {\n if (!kvPrefixCovers(prefix, change.key)) {\n throw new ReplicaError(\n ReplicaErrorCode.SCOPE_VIOLATION,\n `The feed delivered ${JSON.stringify(change.key)}, outside the prefix ${JSON.stringify(prefix)}.`,\n { key: change.key },\n );\n }\n }\n }\n\n async #verify(\n page: SyncPage,\n attempt: Attempt,\n ): Promise<{ changes: VerifiedChange[]; blobs: Map<string, Uint8Array>; fetched: number }> {\n const blobs = new Map<string, Uint8Array>();\n const changes: VerifiedChange[] = [];\n const candidates: Array<{ index: number; key: string; etag: string; hash: string }> = [];\n\n for (const change of page.changes) {\n if (change.deleted) {\n changes.push({ key: change.key, deleted: true });\n continue;\n }\n const hash = hashFromEtag(change.etag);\n if (hash === undefined) {\n throw new ReplicaError(ReplicaErrorCode.PROTOCOL_ERROR, `The feed sent an ETag that is not blake3: ${change.etag}`, {\n key: change.key,\n });\n }\n const previous = await this.#store.get(change.key);\n // A metadata-only change moves position without changing the ETag.\n const unchanged = previous !== undefined && !previous.deleted && previous.etag === change.etag && previous.content;\n changes.push({ key: change.key, deleted: false, etag: change.etag, hash, metadata: change.metadata, content: unchanged });\n if (!unchanged) candidates.push({ index: changes.length - 1, key: change.key, etag: change.etag, hash });\n }\n\n const held = await this.#store.hasContent([...new Set(candidates.map((candidate) => candidate.hash))]);\n const needed = candidates.filter((candidate) => {\n if (!held.has(candidate.hash)) return true;\n (changes[candidate.index] as Extract<VerifiedChange, { deleted: false }>).content = true;\n return false;\n });\n\n let fetched = 0;\n for (let start = 0; start < needed.length; start += FETCH_CHUNK) {\n const chunk = needed.slice(start, start + FETCH_CHUNK);\n const contents = await attempt.transport.fetchContent(\n chunk.map((candidate) => candidate.key),\n attempt.signal === undefined ? undefined : { signal: attempt.signal },\n );\n for (const candidate of chunk) {\n const ok = acceptContent(candidate, contents.get(candidate.key));\n if (ok === undefined) continue; // stale or gone: commit with content=0, repaired later\n blobs.set(candidate.hash, ok);\n (changes[candidate.index] as Extract<VerifiedChange, { deleted: false }>).content = true;\n fetched += 1;\n }\n await this.#store.renewLease(attempt.lease, this.#leaseTtlMs);\n }\n return { changes, blobs, fetched };\n }\n async #repair(attempt: Attempt, cursor: string | null, nodeDid: string | null, coverage: Coverage, retentionGrantCid: string | null): Promise<number> {\n const pending = await this.#store.pendingRepairs(Number.MAX_SAFE_INTEGER);\n let repaired = 0;\n for (let start = 0; start < pending.length; start += REPAIR_BATCH) {\n const chunk = pending.slice(start, start + REPAIR_BATCH);\n const contents = await attempt.transport.fetchContent(\n chunk.map((entry) => entry.key),\n attempt.signal === undefined ? undefined : { signal: attempt.signal },\n );\n const blobs = new Map<string, Uint8Array>();\n const changes: VerifiedChange[] = [];\n for (const entry of chunk) {\n const bytes = acceptContent(entry, contents.get(entry.key));\n if (bytes === undefined) continue;\n blobs.set(entry.hash, bytes);\n changes.push({ ...entry, content: true });\n }\n await this.#store.renewLease(attempt.lease, this.#leaseTtlMs);\n if (changes.length === 0) continue;\n const state = await this.#state();\n await this.#store.applyPage(attempt.lease, {\n changes,\n blobs,\n cursor,\n source: { nodeDid: nodeDid ?? \"\", space: state.config.space, prefix: state.config.prefix },\n authority: null,\n coverage,\n at: new Date(this.#now()).toISOString(),\n retentionGrantCid,\n window: this.#syncWindow(state.authority, attempt.grant),\n complete: false,\n promoteGrant: null,\n });\n repaired += changes.length;\n this.#onCommit?.();\n }\n return repaired;\n }\n}\n\n/**\n * Accept bytes only if they hash to the change row's ETag. A fetch that saw a\n * newer ETag (or none) is stale, not an attack: the next feed page carries\n * the newer state. Bytes that claim the attested ETag but hash differently are.\n */\nfunction acceptContent(\n expected: { key: string; etag: string; hash: string },\n fetched: FetchedContent | undefined,\n): Uint8Array | undefined {\n if (fetched === undefined || \"missing\" in fetched) return undefined;\n if (contentHash(fetched.bytes) === expected.hash) return fetched.bytes;\n if (sameEtag(fetched.etag, expected.etag)) {\n throw new ReplicaError(\n ReplicaErrorCode.CONTENT_MISMATCH,\n `The content of ${JSON.stringify(expected.key)} does not hash to its ETag ${expected.etag}.`,\n { key: expected.key },\n );\n }\n return undefined;\n}\n\nexport type { LocalEntry };\n","/**\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 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} 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","import { ReplicaError, ReplicaErrorCode } from \"./errors.js\";\nimport type { AuthorityState, AuthorityWindow, GrantRecord, LocalReadPolicy } from \"./types.js\";\n\n/**\n * The node's `kv_prefix_covers`: whole path segments, byte-exact. `notes/`\n * covers everything under `notes/` but not `notes`; `notes` covers `notes`\n * and `notes/…` but not `notes-secret/x`.\n */\nexport function kvPrefixCovers(prefix: string, key: string): boolean {\n if (prefix === \"\") return true;\n if (prefix.endsWith(\"/\")) return key.startsWith(prefix);\n return key === prefix || key.startsWith(`${prefix}/`);\n}\n\n/**\n * Syncing the owner's secrets space, or any prefix overlapping the vault\n * namespace (including bare `vault`), copies encrypted secret material to\n * disk and needs an explicit opt-in (TC-733).\n */\nexport function requiresSecretsOptIn(space: string, prefix: string): boolean {\n if (space === \"secrets\" || space.endsWith(\":secrets\")) return true;\n const firstSegment = prefix.split(\"/\", 1)[0];\n return firstSegment === \"vault\";\n}\n\n/** `\"blake3-<hex>\"` (quoted or not) → the 64-hex hash, or undefined. */\nexport function hashFromEtag(etag: string): string | undefined {\n const match = /^\"?blake3-([0-9a-f]{64})\"?$/.exec(etag);\n return match?.[1];\n}\n\nconst RFC3339 = /^\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}(?:\\.\\d+)?(?:Z|[+-]\\d{2}:\\d{2})$/;\n\n/** An RFC 3339 instant that parses: the only form an authority bound may take. */\nexport function isInstant(value: string): boolean {\n return RFC3339.test(value) && Number.isFinite(Date.parse(value));\n}\n\nexport type EffectiveAuthority = {\n state: AuthorityState;\n notBefore: string | null;\n expiresAt: string | null;\n retainUntil: string | null;\n /** The retention grant was revoked: after expiry, reads are revoked rather than expired. */\n retentionRevoked: boolean;\n};\n\nfunction minIso(a: string | null, bSeconds: number | null): string | null {\n if (bSeconds === null) return a;\n const b = new Date(bSeconds * 1000).toISOString();\n if (a === null) return b;\n return Date.parse(a) <= Date.parse(b) ? a : b;\n}\n\nfunction maxIso(a: string | null, bSeconds: number | null): string | null {\n if (bSeconds === null) return a;\n const b = new Date(bSeconds * 1000).toISOString();\n if (a === null) return b;\n return Date.parse(a) >= Date.parse(b) ? a : b;\n}\n\n/**\n * The window local reads are enforced against: the node-attested window from\n * the last successful sync, bounded by the installed leaf grant (an upper\n * bound only — a multi-parent ancestor can end the chain earlier, which only\n * the node can see).\n */\nexport function effectiveAuthority(input: {\n window: AuthorityWindow | null;\n grant: GrantRecord | null;\n revoked: string | null;\n retentionRevoked?: string | null;\n policy: LocalReadPolicy;\n now: number;\n}): EffectiveAuthority {\n const notBefore = maxIso(input.window?.notBefore ?? null, input.grant?.notBefore ?? null);\n const expiresAt = minIso(input.window?.expiresAt ?? null, input.grant?.expiresAt ?? null);\n const retainUntil = input.policy === \"retainAfterExpiry\" ? input.window?.retainUntil ?? null : null;\n let state: AuthorityState = \"valid\";\n if (input.revoked !== null) state = \"revoked\";\n else if (notBefore !== null && input.now < Date.parse(notBefore)) state = \"not-yet-valid\";\n else if (expiresAt !== null && input.now >= Date.parse(expiresAt)) state = \"expired\";\n return { state, notBefore, expiresAt, retainUntil, retentionRevoked: (input.retentionRevoked ?? null) !== null };\n}\n\n/**\n * Throws unless local reads are allowed now. Returns the reported authority\n * state: `valid`, or `expired` while a node-attested retention holds.\n */\nexport function assertReadable(authority: EffectiveAuthority, now: number): AuthorityState {\n switch (authority.state) {\n case \"valid\":\n return \"valid\";\n case \"revoked\":\n throw new ReplicaError(ReplicaErrorCode.GRANT_REVOKED, \"The replica's grant was revoked; local reads are blocked.\");\n case \"not-yet-valid\":\n throw new ReplicaError(\n ReplicaErrorCode.GRANT_NOT_YET_VALID,\n `The replica's grant is not valid before ${authority.notBefore}.`,\n );\n case \"expired\":\n if (authority.retentionRevoked) {\n throw new ReplicaError(\n ReplicaErrorCode.GRANT_REVOKED,\n `The replica's grant expired at ${authority.expiresAt} and its retention grant was revoked; local reads are blocked.`,\n );\n }\n if (authority.retainUntil !== null && now < Date.parse(authority.retainUntil)) return \"expired\";\n throw new ReplicaError(\n ReplicaErrorCode.GRANT_EXPIRED,\n authority.retainUntil === null\n ? `The replica's grant expired at ${authority.expiresAt}; local reads are blocked.`\n : `The replica's retention ended at ${authority.retainUntil}; local reads are blocked.`,\n );\n }\n}\n","import { ed25519 } from \"@noble/curves/ed25519\";\nimport { blake3 } from \"@noble/hashes/blake3\";\n\nimport { ReplicaError, ReplicaErrorCode } from \"./errors.js\";\nimport { kvPrefixCovers } from \"./scope.js\";\nimport type { GrantRecord } from \"./types.js\";\n\n/** A compact UCAN grant whose Ed25519 signature checked out against its issuer. */\nexport type ParsedUcanGrant = GrantRecord & {\n /** The signed attenuation: resource → ability → caveats. */\n att: Record<string, Record<string, unknown>>;\n /** Signed proof CIDs. */\n prf: string[];\n /** The compact JWT, without any `Bearer ` prefix. */\n jwt: string;\n};\n\nconst BASE58 = \"123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz\";\nconst BASE32 = \"abcdefghijklmnopqrstuvwxyz234567\";\n\nfunction base58Decode(text: string): Uint8Array {\n let value = 0n;\n for (const char of text) {\n const digit = BASE58.indexOf(char);\n if (digit < 0) throw new Error(\"invalid base58\");\n value = value * 58n + BigInt(digit);\n }\n const bytes: number[] = [];\n while (value > 0n) {\n bytes.unshift(Number(value & 0xffn));\n value >>= 8n;\n }\n for (const char of text) {\n if (char !== \"1\") break;\n bytes.unshift(0);\n }\n return Uint8Array.from(bytes);\n}\n\nfunction base32Encode(bytes: Uint8Array): string {\n let out = \"\";\n let buffer = 0;\n let bits = 0;\n for (const byte of bytes) {\n buffer = (buffer << 8) | byte;\n bits += 8;\n while (bits >= 5) {\n out += BASE32[(buffer >>> (bits - 5)) & 31];\n bits -= 5;\n }\n }\n if (bits > 0) out += BASE32[(buffer << (5 - bits)) & 31];\n return out;\n}\n\nfunction base64UrlDecode(text: string): Uint8Array {\n if (!/^[A-Za-z0-9_-]*$/.test(text)) throw new Error(\"invalid base64url\");\n const padded = text.replace(/-/g, \"+\").replace(/_/g, \"/\") + \"===\".slice((text.length + 3) % 4);\n const binary = globalThis.atob(padded);\n return Uint8Array.from(binary, (char) => char.charCodeAt(0));\n}\n\n/**\n * The canonical TinyCloud delegation CID of a compact UCAN: CIDv1, raw codec\n * (0x55), blake3-256 multihash over the JWT's UTF-8 bytes, base32 multibase.\n * Matches `computeCid(bytes, 0x55)` in the SDK's WASM bindings.\n */\nexport function ucanCid(jwt: string): string {\n const digest = blake3(new TextEncoder().encode(jwt));\n const bytes = new Uint8Array(4 + digest.length);\n bytes.set([0x01, 0x55, 0x1e, 0x20]);\n bytes.set(digest, 4);\n return `b${base32Encode(bytes)}`;\n}\n\n/** The raw Ed25519 public key of a `did:key:z6Mk…` DID (fragment ignored). */\nfunction ed25519KeyOfDid(did: string): Uint8Array | undefined {\n const bare = did.split(\"#\", 1)[0]!;\n if (!bare.startsWith(\"did:key:z\")) return undefined;\n let decoded: Uint8Array;\n try {\n decoded = base58Decode(bare.slice(\"did:key:z\".length));\n } catch {\n return undefined;\n }\n // multicodec ed25519-pub = 0xed (varint 0xed 0x01)\n if (decoded.length !== 34 || decoded[0] !== 0xed || decoded[1] !== 0x01) return undefined;\n return decoded.subarray(2);\n}\n\nfunction invalid(message: string): ReplicaError {\n return new ReplicaError(ReplicaErrorCode.GRANT_INVALID, message);\n}\n\n/**\n * Parse and verify a compact UCAN grant. Worker-safe (no Node APIs).\n * Everything returned — audience, window, attenuation, proofs — comes from\n * the signed payload, never from wrapper fields around it. Verifying the\n * signature proves the issuer signed exactly these claims; whether the chain\n * holds is the node's call on every sync.\n */\nexport function parseUcanGrant(input: Uint8Array | string): ParsedUcanGrant {\n const raw = typeof input === \"string\" ? input : new TextDecoder().decode(input);\n const jwt = raw.trim().replace(/^Bearer\\s+/i, \"\");\n const parts = jwt.split(\".\");\n if (parts.length !== 3) {\n throw new ReplicaError(\n ReplicaErrorCode.GRANT_FORMAT_UNSUPPORTED,\n \"The grant is not a compact UCAN. SIWE/CACAO grants cannot back a replica; ask the owner for a device grant (tc auth grant).\",\n );\n }\n const [headerPart, payloadPart, signaturePart] = parts as [string, string, string];\n let header: Record<string, unknown>;\n let payload: Record<string, unknown>;\n let signature: Uint8Array;\n try {\n header = JSON.parse(new TextDecoder().decode(base64UrlDecode(headerPart)));\n payload = JSON.parse(new TextDecoder().decode(base64UrlDecode(payloadPart)));\n signature = base64UrlDecode(signaturePart);\n } catch {\n throw invalid(\"The grant is not well-formed base64url JSON.\");\n }\n if (header?.alg !== \"EdDSA\") {\n throw new ReplicaError(\n ReplicaErrorCode.GRANT_FORMAT_UNSUPPORTED,\n `The grant is signed with ${String(header?.alg)}; a replica needs an Ed25519 (EdDSA) device grant.`,\n );\n }\n const issuer = payload?.iss;\n const audience = payload?.aud;\n if (typeof issuer !== \"string\" || typeof audience !== \"string\" || audience.length === 0) {\n throw invalid(\"The grant has no signed issuer or audience.\");\n }\n const key = ed25519KeyOfDid(issuer);\n if (key === undefined) {\n throw new ReplicaError(\n ReplicaErrorCode.GRANT_FORMAT_UNSUPPORTED,\n `The grant issuer ${issuer} is not an Ed25519 did:key.`,\n );\n }\n let verified = false;\n try {\n verified = ed25519.verify(signature, new TextEncoder().encode(`${headerPart}.${payloadPart}`), key);\n } catch {\n verified = false;\n }\n if (!verified) throw invalid(\"The grant's signature does not verify against its issuer.\");\n\n const att = payload.att;\n if (att === null || typeof att !== \"object\" || Array.isArray(att)) throw invalid(\"The grant has no signed attenuation.\");\n for (const abilities of Object.values(att)) {\n if (abilities === null || typeof abilities !== \"object\" || Array.isArray(abilities)) {\n throw invalid(\"The grant has a malformed signed attenuation.\");\n }\n }\n const prf = payload.prf ?? [];\n if (!Array.isArray(prf) || prf.some((proof) => typeof proof !== \"string\")) throw invalid(\"The grant has malformed signed proofs.\");\n const window = (name: \"nbf\" | \"exp\"): number | null => {\n const value = payload[name];\n if (value === undefined || value === null) return null;\n if (typeof value !== \"number\" || !Number.isFinite(value)) throw invalid(`The grant's signed ${name} is not a number.`);\n return value;\n };\n\n return {\n cid: ucanCid(jwt),\n bytes: new TextEncoder().encode(jwt),\n jwt,\n issuer,\n audience,\n notBefore: window(\"nbf\"),\n expiresAt: window(\"exp\"),\n att: att as Record<string, Record<string, unknown>>,\n prf: prf as string[],\n };\n}\n\n/** The KV path a signed resource names in `space`, or undefined for any other resource. */\nfunction kvPathIn(resource: string, space: string): string | undefined {\n for (const base of [`${space}/kv/`, `tinycloud://${space}/kv/`]) {\n if (resource.startsWith(base)) return resource.slice(base.length);\n }\n return undefined;\n}\n\n/** True when the signed attenuation grants `ability` on a KV path covering `prefix`. */\nexport function grantCovers(grant: Pick<ParsedUcanGrant, \"att\">, space: string, prefix: string, ability: string): boolean {\n for (const [resource, abilities] of Object.entries(grant.att)) {\n const path = kvPathIn(resource, space);\n if (path === undefined || !kvPrefixCovers(path, prefix)) continue;\n // `tinycloud.kv/sync` is never implied by a wildcard (TC-732).\n if (ability in abilities) return true;\n if (ability !== \"tinycloud.kv/sync\" && ability !== \"tinycloud.kv/retain\" && \"tinycloud.kv/*\" in abilities) return true;\n }\n return false;\n}\n\n/** The space ids (native form) for which the grant carries `tinycloud.kv/sync`. */\nexport function syncGrantSpaces(grant: Pick<ParsedUcanGrant, \"att\">): string[] {\n const spaces = new Set<string>();\n for (const [resource, abilities] of Object.entries(grant.att)) {\n if (!(\"tinycloud.kv/sync\" in abilities)) continue;\n const match = /^(?:tinycloud:\\/\\/)?(tinycloud:[^/]+)\\/kv\\//.exec(resource);\n if (match) spaces.add(match[1]!);\n }\n return [...spaces];\n}\n\n/**\n * Install-time checks: the grant must name this device, cover both\n * `tinycloud.kv/sync` and `tinycloud.kv/get` on the prefix, and be inside its\n * own signed window. The node re-checks the whole chain on every sync.\n */\nexport function assertGrantInstallable(\n grant: ParsedUcanGrant,\n input: { deviceDid: string; space: string; prefix: string; now: number },\n): void {\n if (grant.audience.split(\"#\", 1)[0] !== input.deviceDid.split(\"#\", 1)[0]) {\n throw new ReplicaError(\n ReplicaErrorCode.GRANT_AUDIENCE_MISMATCH,\n `The grant was issued to ${grant.audience}, not this device (${input.deviceDid}).`,\n );\n }\n for (const ability of [\"tinycloud.kv/sync\", \"tinycloud.kv/get\"]) {\n if (!grantCovers(grant, input.space, input.prefix, ability)) {\n throw new ReplicaError(\n ReplicaErrorCode.GRANT_NOT_COVERING,\n `The grant does not carry ${ability} on ${input.space}/kv/${input.prefix}.`,\n { ability },\n );\n }\n }\n const nowSeconds = input.now / 1000;\n if (grant.notBefore !== null && nowSeconds < grant.notBefore) {\n throw new ReplicaError(\n ReplicaErrorCode.GRANT_NOT_YET_VALID,\n `The grant is not valid before ${new Date(grant.notBefore * 1000).toISOString()}.`,\n );\n }\n if (grant.expiresAt !== null && nowSeconds >= grant.expiresAt) {\n throw new ReplicaError(\n ReplicaErrorCode.GRANT_EXPIRED,\n `The grant expired at ${new Date(grant.expiresAt * 1000).toISOString()}.`,\n );\n }\n}\n","import { ReplicaError, ReplicaErrorCode } from \"./errors.js\";\nimport { isInstant } from \"./scope.js\";\nimport type { AuthorityWindow, Change, FetchedContent, ReplicaTransport, SyncPage } from \"./types.js\";\n\n/** The sdk-services `ServiceError` fields the transport reads. */\ntype ServiceError = { code: string; message: string; meta?: Record<string, unknown> };\ntype Result<T> = { ok: true; data: T } | { ok: false; error: ServiceError };\n\ntype KVChangesResponse = {\n changes: Change[];\n more: boolean;\n cursor: string;\n source: { nodeDid: string; space: string; prefix: string };\n authority: AuthorityWindow;\n};\n\ntype KVValue = { data: unknown; headers: { etag?: string } };\n\n/**\n * The slice of sdk-services `KVService` a replica needs: the `tinycloud.kv/sync`\n * feed (`changes`, TC-736) and binary reads. Typed structurally so this\n * package never imports an SDK entry point (web-sdk defines custom elements\n * on load; node-sdk pulls in Node-only code).\n */\nexport interface KVSyncClient {\n changes(options: {\n prefix: string;\n cursor?: string;\n limit?: number;\n retentionGrant?: string;\n signal?: AbortSignal;\n }): Promise<Result<KVChangesResponse>>;\n get(key: string, options: { binary: true; signal?: AbortSignal }): Promise<Result<KVValue>>;\n batchGet(\n keys: string[],\n options: { binary: true; signal?: AbortSignal },\n ): Promise<Result<{ results: Array<{ key: string; result: Result<KVValue> }> }>>;\n}\n\n/** TC-736 maps a revoked chain on `kv/sync` to these codes. */\nconst REVOKED_CODES = new Set([\"AUTH_DELEGATION_REVOKED\", \"AUTH_DELEGATION_ANCESTOR_REVOKED\"]);\n/**\n * The node's whole 401 body for a revoked chain (`InvocationError` in\n * tinycloud-core): only CIDs follow the denial code, never a key or prefix.\n */\nconst REVOKED_BODY =\n /^(?:Invalid invocation: )?(?:delegation-revoked: [A-Za-z0-9]+|delegation-ancestor-revoked: ancestor=[A-Za-z0-9]+ (?:invoked|parent)=[A-Za-z0-9]+)$/;\n\n/**\n * Revocation only from typed evidence: the SDK's revocation codes, or (for\n * reads, which the SDK reports as `AUTH_UNAUTHORIZED`) a 401 whose message is\n * exactly the SDK's `<request>: 401 - <body>` with the body the node's\n * revocation denial. Free text is never searched: messages carry keys and\n * prefixes, which anyone with write access chooses.\n */\nfunction isRevocation(error: ServiceError, status: number | undefined, request: string | undefined): boolean {\n if (REVOKED_CODES.has(error.code)) return true;\n if (status !== 401 || request === undefined) return false;\n const lead = `${request}: 401 - `;\n return error.message.startsWith(lead) && REVOKED_BODY.test(error.message.slice(lead.length));\n}\n\n/**\n * Map an SDK failure to the replica's typed errors. `request` is the SDK's\n * own description of the call (`Failed to get key \"k\"`), which lets a read's\n * 401 body be told apart from the key it names.\n */\nexport function replicaErrorFromService(error: ServiceError, context: string, request?: string): ReplicaError {\n const meta = error.meta ?? {};\n const status = typeof meta.status === \"number\" ? meta.status : undefined;\n const detail = { serviceCode: error.code, ...(status === undefined ? {} : { status }) };\n const reason = typeof meta.reason === \"string\" ? meta.reason : undefined;\n if (error.code === \"KV_SYNC_RESET_REQUIRED\" || status === 410) {\n return new ReplicaError(\n ReplicaErrorCode.RESET_REQUIRED,\n `${context}: the node requires a fresh bootstrap (${reason ?? \"reason not given\"}).`,\n { ...detail, reason: reason ?? \"reset-required\" },\n );\n }\n if (isRevocation(error, status, request)) {\n return new ReplicaError(ReplicaErrorCode.GRANT_REVOKED, `${context}: ${error.message}`, detail);\n }\n if (error.code === \"KV_RETENTION_GRANT_REFUSED\") {\n return new ReplicaError(ReplicaErrorCode.RETENTION_GRANT_REFUSED, `${context}: ${error.message}`, {\n ...detail,\n ...(reason === undefined ? {} : { reason }),\n });\n }\n if (status === 401 || status === 403 || /^AUTH_/.test(error.code)) {\n // Not destructive either way (both refuse with exit 5 and keep the replica).\n if (/expired/i.test(error.message)) {\n return new ReplicaError(ReplicaErrorCode.GRANT_EXPIRED, `${context}: ${error.message}`, detail);\n }\n return new ReplicaError(ReplicaErrorCode.GRANT_UNAUTHORIZED, `${context}: ${error.message}`, detail);\n }\n if (status !== undefined) {\n return new ReplicaError(ReplicaErrorCode.NODE_ERROR, `${context}: ${error.message}`, detail);\n }\n if (error.code === \"NETWORK_ERROR\" || /TIMEOUT|ABORT/.test(error.code)) {\n return new ReplicaError(ReplicaErrorCode.NETWORK_ERROR, `${context}: ${error.message}`, detail);\n }\n return new ReplicaError(ReplicaErrorCode.NODE_ERROR, `${context}: ${error.message}`, detail);\n}\n\nconst encoder = new TextEncoder();\n\n/** Byte order of UTF-8 keys: how the node orders batch results. */\nfunction compareKeyBytes(a: string, b: string): number {\n const x = encoder.encode(a);\n const y = encoder.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\nfunction isNotFound(error: ServiceError): boolean {\n return error.code === \"KV_NOT_FOUND\" || error.code === \"NOT_FOUND\";\n}\n\n/** An authority bound: null, or an RFC 3339 instant. Anything else fails closed. */\nfunction instant(value: unknown): string | null {\n if (value === null) return null;\n if (typeof value === \"string\" && isInstant(value)) return value;\n throw new ReplicaError(ReplicaErrorCode.PROTOCOL_ERROR, `The kv/sync authority holds a bound that is not a timestamp: ${JSON.stringify(value)}.`);\n}\n\nfunction checkPage(data: KVChangesResponse): SyncPage {\n const ok =\n data !== null &&\n typeof data === \"object\" &&\n Array.isArray(data.changes) &&\n typeof data.more === \"boolean\" &&\n typeof data.cursor === \"string\" &&\n typeof data.source?.nodeDid === \"string\" &&\n typeof data.source?.space === \"string\" &&\n typeof data.source?.prefix === \"string\" &&\n data.authority !== null &&\n typeof data.authority === \"object\" &&\n data.changes.every(\n (change) =>\n typeof change?.key === \"string\" &&\n (change.deleted === true ||\n (change.deleted === false &&\n typeof change.etag === \"string\" &&\n change.metadata !== null &&\n typeof change.metadata === \"object\" &&\n Object.values(change.metadata).every((value) => typeof value === \"string\"))),\n );\n if (!ok) throw new ReplicaError(ReplicaErrorCode.PROTOCOL_ERROR, \"The kv/sync response does not match the wire contract.\");\n return {\n changes: data.changes.map((change) =>\n change.deleted ? { key: change.key, deleted: true } : { key: change.key, deleted: false, etag: change.etag, metadata: { ...change.metadata } },\n ),\n more: data.more,\n cursor: data.cursor,\n source: { nodeDid: data.source.nodeDid, space: data.source.space, prefix: data.source.prefix },\n authority: {\n notBefore: instant(data.authority.notBefore),\n expiresAt: instant(data.authority.expiresAt),\n retainUntil: instant(data.authority.retainUntil),\n },\n };\n}\n\nfunction fetched(key: string, result: Result<KVValue>, request: string): FetchedContent {\n if (!result.ok) {\n if (isNotFound(result.error)) return { missing: true };\n throw replicaErrorFromService(result.error, `Fetching ${JSON.stringify(key)}`, request);\n }\n if (!(result.data.data instanceof Uint8Array)) {\n throw new ReplicaError(ReplicaErrorCode.PROTOCOL_ERROR, `The value of ${JSON.stringify(key)} did not arrive as bytes.`);\n }\n return { bytes: result.data.data, etag: result.data.headers.etag ?? \"\" };\n}\n\n/** A `ReplicaTransport` over a KV service bound to the replica's space and grant. */\nexport function kvSyncTransport(kv: KVSyncClient): ReplicaTransport {\n return {\n async syncPage({ prefix, cursor, limit, retentionGrant, signal }) {\n const result = await kv.changes({\n prefix,\n limit,\n ...(cursor === undefined ? {} : { cursor }),\n ...(retentionGrant === undefined ? {} : { retentionGrant }),\n ...(signal === undefined ? {} : { signal }),\n });\n if (!result.ok) throw replicaErrorFromService(result.error, \"Syncing\");\n return checkPage(result.data);\n },\n\n async fetchContent(keys, options) {\n const contents = new Map<string, FetchedContent>();\n if (keys.length === 0) return contents;\n const signal = options?.signal;\n // The node answers a batch in key byte order and the SDK matches results\n // to requests by position, so ask in that order.\n const ordered = [...keys].sort(compareKeyBytes);\n const batch = await kv.batchGet(ordered, { binary: true, ...(signal === undefined ? {} : { signal }) });\n if (batch.ok) {\n for (const item of batch.data.results) {\n const result = item.result;\n // One oversize value is fetched alone below.\n if (!result.ok && result.error.code === \"KV_RESPONSE_TOO_LARGE\") continue;\n contents.set(item.key, fetched(item.key, result, `Failed to get key ${JSON.stringify(item.key)}`));\n }\n } else if (batch.error.code !== \"KV_RESPONSE_TOO_LARGE\") {\n throw replicaErrorFromService(batch.error, `Fetching ${keys.length} value(s)`, `Failed to batch read ${ordered.length} key(s)`);\n }\n for (const key of keys) {\n if (contents.has(key)) continue;\n const request = `Failed to get key ${JSON.stringify(key)}`;\n contents.set(key, fetched(key, await kv.get(key, { binary: true, ...(signal === undefined ? {} : { signal }) }), request));\n }\n return contents;\n },\n };\n}\n"],"mappings":"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","names":["fetched","import_blake3"]}
|
package/dist/index.d.cts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { C as Coverage, A as AuthorityState, R as ReplicaStore, a as ReplicaTransport, G as GrantRecord, L as ListOpts, b as ReplicaStatus, c as AuthorityWindow, d as LocalReadPolicy, e as Change } from './types-
|
|
2
|
-
export { F as FetchedContent, f as LeaseToken, g as LocalEntry, h as ReplicaConfig, i as ReplicaLastError, j as ReplicaState, S as SyncPage, k as SyncSource, V as VerifiedChange, l as VerifiedPage } from './types-
|
|
1
|
+
import { C as Coverage, A as AuthorityState, R as ReplicaStore, a as ReplicaTransport, G as GrantRecord, L as ListOpts, b as ReplicaStatus, c as AuthorityWindow, d as LocalReadPolicy, e as Change } from './types-f3-kYLQZ.cjs';
|
|
2
|
+
export { F as FetchedContent, f as LeaseToken, g as LocalEntry, h as ReplicaConfig, i as ReplicaLastError, j as ReplicaState, S as SyncPage, k as SyncSource, V as VerifiedChange, l as VerifiedPage } from './types-f3-kYLQZ.cjs';
|
|
3
3
|
|
|
4
4
|
/** Metadata every local read carries: the replica reports observed state, never finality. */
|
|
5
5
|
type ReadMeta = {
|
|
@@ -71,6 +71,11 @@ type ReplicaOptions = {
|
|
|
71
71
|
transportFor?: (grant: GrantRecord) => ReplicaTransport;
|
|
72
72
|
now?: () => number;
|
|
73
73
|
leaseTtlMs?: number;
|
|
74
|
+
/**
|
|
75
|
+
* Called after every committed page and repair batch — also those of a sync
|
|
76
|
+
* that later fails. Lets the caller report partial progress immediately.
|
|
77
|
+
*/
|
|
78
|
+
onCommit?: () => void;
|
|
74
79
|
};
|
|
75
80
|
/** `"blake3-" + hex(blake3(bytes))`, the strong ETag the node attests for content. */
|
|
76
81
|
declare function contentHash(bytes: Uint8Array): string;
|
|
@@ -116,6 +121,8 @@ declare const ReplicaErrorCode: {
|
|
|
116
121
|
readonly CONFIG_MISMATCH: "REPLICA_CONFIG_MISMATCH";
|
|
117
122
|
/** The runtime has no supported durable store (e.g. Node < 22.13). */
|
|
118
123
|
readonly RUNTIME_UNSUPPORTED: "RUNTIME_UNSUPPORTED";
|
|
124
|
+
/** A required open() argument is missing or malformed (e.g. `principal`). */
|
|
125
|
+
readonly INVALID_ARGUMENT: "REPLICA_INVALID_ARGUMENT";
|
|
119
126
|
/** The local store failed (I/O, corruption, schema). */
|
|
120
127
|
readonly STORAGE_ERROR: "STORAGE_ERROR";
|
|
121
128
|
/** The local disk is full. */
|
|
@@ -157,6 +164,8 @@ declare const ReplicaErrorCode: {
|
|
|
157
164
|
readonly CONTENT_MISMATCH: "CONTENT_MISMATCH";
|
|
158
165
|
/** A stored blob no longer hashes to its name. */
|
|
159
166
|
readonly INTEGRITY_ERROR: "INTEGRITY_ERROR";
|
|
167
|
+
/** The replica handle was closed; in-flight calls settle with this code. */
|
|
168
|
+
readonly CLOSED: "REPLICA_CLOSED";
|
|
160
169
|
};
|
|
161
170
|
type ReplicaErrorCode = (typeof ReplicaErrorCode)[keyof typeof ReplicaErrorCode];
|
|
162
171
|
/**
|
package/dist/index.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { C as Coverage, A as AuthorityState, R as ReplicaStore, a as ReplicaTransport, G as GrantRecord, L as ListOpts, b as ReplicaStatus, c as AuthorityWindow, d as LocalReadPolicy, e as Change } from './types-
|
|
2
|
-
export { F as FetchedContent, f as LeaseToken, g as LocalEntry, h as ReplicaConfig, i as ReplicaLastError, j as ReplicaState, S as SyncPage, k as SyncSource, V as VerifiedChange, l as VerifiedPage } from './types-
|
|
1
|
+
import { C as Coverage, A as AuthorityState, R as ReplicaStore, a as ReplicaTransport, G as GrantRecord, L as ListOpts, b as ReplicaStatus, c as AuthorityWindow, d as LocalReadPolicy, e as Change } from './types-f3-kYLQZ.js';
|
|
2
|
+
export { F as FetchedContent, f as LeaseToken, g as LocalEntry, h as ReplicaConfig, i as ReplicaLastError, j as ReplicaState, S as SyncPage, k as SyncSource, V as VerifiedChange, l as VerifiedPage } from './types-f3-kYLQZ.js';
|
|
3
3
|
|
|
4
4
|
/** Metadata every local read carries: the replica reports observed state, never finality. */
|
|
5
5
|
type ReadMeta = {
|
|
@@ -71,6 +71,11 @@ type ReplicaOptions = {
|
|
|
71
71
|
transportFor?: (grant: GrantRecord) => ReplicaTransport;
|
|
72
72
|
now?: () => number;
|
|
73
73
|
leaseTtlMs?: number;
|
|
74
|
+
/**
|
|
75
|
+
* Called after every committed page and repair batch — also those of a sync
|
|
76
|
+
* that later fails. Lets the caller report partial progress immediately.
|
|
77
|
+
*/
|
|
78
|
+
onCommit?: () => void;
|
|
74
79
|
};
|
|
75
80
|
/** `"blake3-" + hex(blake3(bytes))`, the strong ETag the node attests for content. */
|
|
76
81
|
declare function contentHash(bytes: Uint8Array): string;
|
|
@@ -116,6 +121,8 @@ declare const ReplicaErrorCode: {
|
|
|
116
121
|
readonly CONFIG_MISMATCH: "REPLICA_CONFIG_MISMATCH";
|
|
117
122
|
/** The runtime has no supported durable store (e.g. Node < 22.13). */
|
|
118
123
|
readonly RUNTIME_UNSUPPORTED: "RUNTIME_UNSUPPORTED";
|
|
124
|
+
/** A required open() argument is missing or malformed (e.g. `principal`). */
|
|
125
|
+
readonly INVALID_ARGUMENT: "REPLICA_INVALID_ARGUMENT";
|
|
119
126
|
/** The local store failed (I/O, corruption, schema). */
|
|
120
127
|
readonly STORAGE_ERROR: "STORAGE_ERROR";
|
|
121
128
|
/** The local disk is full. */
|
|
@@ -157,6 +164,8 @@ declare const ReplicaErrorCode: {
|
|
|
157
164
|
readonly CONTENT_MISMATCH: "CONTENT_MISMATCH";
|
|
158
165
|
/** A stored blob no longer hashes to its name. */
|
|
159
166
|
readonly INTEGRITY_ERROR: "INTEGRITY_ERROR";
|
|
167
|
+
/** The replica handle was closed; in-flight calls settle with this code. */
|
|
168
|
+
readonly CLOSED: "REPLICA_CLOSED";
|
|
160
169
|
};
|
|
161
170
|
type ReplicaErrorCode = (typeof ReplicaErrorCode)[keyof typeof ReplicaErrorCode];
|
|
162
171
|
/**
|
package/dist/index.js
CHANGED
|
@@ -12,6 +12,8 @@ var ReplicaErrorCode = {
|
|
|
12
12
|
CONFIG_MISMATCH: "REPLICA_CONFIG_MISMATCH",
|
|
13
13
|
/** The runtime has no supported durable store (e.g. Node < 22.13). */
|
|
14
14
|
RUNTIME_UNSUPPORTED: "RUNTIME_UNSUPPORTED",
|
|
15
|
+
/** A required open() argument is missing or malformed (e.g. `principal`). */
|
|
16
|
+
INVALID_ARGUMENT: "REPLICA_INVALID_ARGUMENT",
|
|
15
17
|
/** The local store failed (I/O, corruption, schema). */
|
|
16
18
|
STORAGE_ERROR: "STORAGE_ERROR",
|
|
17
19
|
/** The local disk is full. */
|
|
@@ -52,7 +54,9 @@ var ReplicaErrorCode = {
|
|
|
52
54
|
/** Fetched bytes do not hash to the ETag the node attested for them. */
|
|
53
55
|
CONTENT_MISMATCH: "CONTENT_MISMATCH",
|
|
54
56
|
/** A stored blob no longer hashes to its name. */
|
|
55
|
-
INTEGRITY_ERROR: "INTEGRITY_ERROR"
|
|
57
|
+
INTEGRITY_ERROR: "INTEGRITY_ERROR",
|
|
58
|
+
/** The replica handle was closed; in-flight calls settle with this code. */
|
|
59
|
+
CLOSED: "REPLICA_CLOSED"
|
|
56
60
|
};
|
|
57
61
|
var REPLICA_ERROR = /* @__PURE__ */ Symbol.for("tinycloud.replica.error");
|
|
58
62
|
var ReplicaError = class extends Error {
|
|
@@ -166,12 +170,14 @@ var Replica = class {
|
|
|
166
170
|
#transportFor;
|
|
167
171
|
#now;
|
|
168
172
|
#leaseTtlMs;
|
|
173
|
+
#onCommit;
|
|
169
174
|
constructor(options) {
|
|
170
175
|
this.#store = options.store;
|
|
171
176
|
const transport = options.transport;
|
|
172
177
|
this.#transportFor = options.transportFor ?? (transport === void 0 ? void 0 : () => transport);
|
|
173
178
|
this.#now = options.now ?? Date.now;
|
|
174
179
|
this.#leaseTtlMs = options.leaseTtlMs ?? DEFAULT_LEASE_TTL_MS;
|
|
180
|
+
this.#onCommit = options.onCommit;
|
|
175
181
|
}
|
|
176
182
|
async #state() {
|
|
177
183
|
const state = await this.#store.open();
|
|
@@ -366,9 +372,10 @@ var Replica = class {
|
|
|
366
372
|
let cursor = state.cursor;
|
|
367
373
|
let pinned = state.nodeDid;
|
|
368
374
|
let coverage = state.coverage;
|
|
375
|
+
const retentionGrantCid = config.retentionGrantCid;
|
|
369
376
|
for (; ; ) {
|
|
370
377
|
let page;
|
|
371
|
-
const retentionGrant =
|
|
378
|
+
const retentionGrant = retentionGrantCid;
|
|
372
379
|
try {
|
|
373
380
|
page = await attempt.transport.syncPage({
|
|
374
381
|
prefix: config.prefix,
|
|
@@ -404,6 +411,7 @@ var Replica = class {
|
|
|
404
411
|
authority: page.authority,
|
|
405
412
|
coverage,
|
|
406
413
|
at,
|
|
414
|
+
retentionGrantCid: retentionGrant,
|
|
407
415
|
window,
|
|
408
416
|
complete: !page.more,
|
|
409
417
|
promoteGrant: attempt.promote
|
|
@@ -413,6 +421,7 @@ var Replica = class {
|
|
|
413
421
|
attempt.promote = null;
|
|
414
422
|
}
|
|
415
423
|
report.pages += 1;
|
|
424
|
+
this.#onCommit?.();
|
|
416
425
|
report.changes += page.changes.length;
|
|
417
426
|
report.deleted += page.changes.filter((change) => change.deleted).length;
|
|
418
427
|
report.fetched += verified.fetched;
|
|
@@ -422,7 +431,7 @@ var Replica = class {
|
|
|
422
431
|
await this.#store.renewLease(attempt.lease, this.#leaseTtlMs);
|
|
423
432
|
if (!page.more) break;
|
|
424
433
|
}
|
|
425
|
-
report.repaired = await this.#repair(attempt, cursor, pinned, coverage);
|
|
434
|
+
report.repaired = await this.#repair(attempt, cursor, pinned, coverage, retentionGrantCid);
|
|
426
435
|
report.blobsCollected = await this.#store.collectGarbage(attempt.lease);
|
|
427
436
|
report.coverage = coverage;
|
|
428
437
|
report.contentMissing = (await this.#store.pendingRepairs(Number.MAX_SAFE_INTEGER)).length;
|
|
@@ -504,7 +513,7 @@ var Replica = class {
|
|
|
504
513
|
}
|
|
505
514
|
return { changes, blobs, fetched: fetched2 };
|
|
506
515
|
}
|
|
507
|
-
async #repair(attempt, cursor, nodeDid, coverage) {
|
|
516
|
+
async #repair(attempt, cursor, nodeDid, coverage, retentionGrantCid) {
|
|
508
517
|
const pending = await this.#store.pendingRepairs(Number.MAX_SAFE_INTEGER);
|
|
509
518
|
let repaired = 0;
|
|
510
519
|
for (let start = 0; start < pending.length; start += REPAIR_BATCH) {
|
|
@@ -532,11 +541,13 @@ var Replica = class {
|
|
|
532
541
|
authority: null,
|
|
533
542
|
coverage,
|
|
534
543
|
at: new Date(this.#now()).toISOString(),
|
|
544
|
+
retentionGrantCid,
|
|
535
545
|
window: this.#syncWindow(state.authority, attempt.grant),
|
|
536
546
|
complete: false,
|
|
537
547
|
promoteGrant: null
|
|
538
548
|
});
|
|
539
549
|
repaired += changes.length;
|
|
550
|
+
this.#onCommit?.();
|
|
540
551
|
}
|
|
541
552
|
return repaired;
|
|
542
553
|
}
|