@earendil-works/pi-durable 0.87.0 → 0.87.1

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.
Files changed (38) hide show
  1. package/README.md +29 -0
  2. package/dist/index.d.ts +2 -2
  3. package/dist/index.d.ts.map +1 -1
  4. package/dist/index.js +1 -1
  5. package/dist/index.js.map +1 -1
  6. package/dist/{memory-storage.d.ts → storage/memory.d.ts} +12 -6
  7. package/dist/storage/memory.d.ts.map +1 -0
  8. package/dist/{memory-storage.js → storage/memory.js} +236 -24
  9. package/dist/storage/memory.js.map +1 -0
  10. package/dist/storage/sqlite/database.d.ts +26 -0
  11. package/dist/storage/sqlite/database.d.ts.map +1 -0
  12. package/dist/storage/sqlite/database.js +2 -0
  13. package/dist/storage/sqlite/database.js.map +1 -0
  14. package/dist/storage/sqlite/index.d.ts +4 -0
  15. package/dist/storage/sqlite/index.d.ts.map +1 -0
  16. package/dist/storage/sqlite/index.js +3 -0
  17. package/dist/storage/sqlite/index.js.map +1 -0
  18. package/dist/storage/sqlite/migrations.d.ts +11 -0
  19. package/dist/storage/sqlite/migrations.d.ts.map +1 -0
  20. package/dist/storage/sqlite/migrations.js +104 -0
  21. package/dist/storage/sqlite/migrations.js.map +1 -0
  22. package/dist/storage/sqlite/node.d.ts +25 -0
  23. package/dist/storage/sqlite/node.d.ts.map +1 -0
  24. package/dist/storage/sqlite/node.js +97 -0
  25. package/dist/storage/sqlite/node.js.map +1 -0
  26. package/dist/storage/sqlite/storage.d.ts +45 -0
  27. package/dist/storage/sqlite/storage.d.ts.map +1 -0
  28. package/dist/storage/sqlite/storage.js +524 -0
  29. package/dist/storage/sqlite/storage.js.map +1 -0
  30. package/dist/types.d.ts +113 -34
  31. package/dist/types.d.ts.map +1 -1
  32. package/dist/types.js.map +1 -1
  33. package/docs/pico-v5-chord-usage.md +64 -63
  34. package/docs/pico-v5-handoff.md +92 -57
  35. package/docs/pico-v5.md +519 -350
  36. package/package.json +20 -3
  37. package/dist/memory-storage.d.ts.map +0 -1
  38. package/dist/memory-storage.js.map +0 -1
@@ -0,0 +1 @@
1
+ {"version":3,"file":"memory.js","sourceRoot":"","sources":["../../src/storage/memory.ts"],"names":[],"mappings":"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","sourcesContent":["import type { Context, JsonValue } from \"@earendil-works/chord\";\nimport { applyImmutable } from \"@earendil-works/chord/delta\";\nimport type {\n\tConversationRecord,\n\tCursor,\n\tDocumentAddress,\n\tDocumentContent,\n\tDocumentCreate,\n\tDocumentPoint,\n\tDocumentQuery,\n\tDocumentRecord,\n\tEntryQuery,\n\tEntryRecord,\n\tId,\n\tJsonObject,\n\tPage,\n\tSeq,\n\tStorage,\n\tStorageWrite,\n\tStoredDocument,\n\tSubmissionRecord,\n\tTaskQuery,\n\tTaskRecord,\n} from \"../types.ts\";\n\ntype StoredTask = TaskRecord<JsonValue, JsonValue, JsonValue>;\ntype TaskStatus = StoredTask[\"state\"][\"status\"];\ntype TableName = \"conversation\" | \"entry\" | \"task\" | \"submission\" | \"document\";\ntype DocumentRevision = DocumentContent & { readonly seq: Seq };\ntype StoredDocumentState = {\n\trecord: DocumentRecord;\n\trevisions: DocumentRevision[];\n};\ntype DocumentAction = {\n\tcreate?: DocumentCreate;\n\tcontent?: DocumentContent;\n\tretire: boolean;\n};\n\ntype DocumentAddressIndex = {\n\tids: Id[];\n\tcurrentId?: Id;\n};\n\ntype State = {\n\tconversations: Map<Id, ConversationRecord>;\n\tconversationIds: Id[];\n\tentries: Map<Id, EntryRecord>;\n\tentryIds: Map<Id, Id[]>;\n\theadEntryIds: Map<Id, Id[]>;\n\tentryCommitSeqs: Map<Id, Seq>;\n\ttasks: Map<Id, StoredTask>;\n\ttaskIds: Id[];\n\ttaskIdsByStatus: Record<TaskStatus, Id[]>;\n\tsubmissions: Map<Id, SubmissionRecord>;\n\tsubmissionIdsByRequest: Map<Id, Map<string, Id>>;\n\tdocuments: Map<Id, StoredDocumentState>;\n\tdocumentAddresses: Map<string, DocumentAddressIndex>;\n\tdocumentIdsByScope: Map<string, Id[]>;\n};\n\nconst clone = <T>(value: T): T => {\n\tif (value === null || typeof value !== \"object\") return value;\n\tif (Array.isArray(value)) return value.map((item) => clone(item)) as T;\n\tconst source = value as Record<string, unknown>;\n\tconst nullPrototype = Object.getPrototypeOf(value) === null;\n\tconst result = (nullPrototype ? Object.create(null) : {}) as Record<string, unknown>;\n\tfor (const key of Object.keys(source)) {\n\t\tconst copied = clone(source[key]);\n\t\tif (!nullPrototype && key in result) {\n\t\t\tObject.defineProperty(result, key, {\n\t\t\t\tvalue: copied,\n\t\t\t\twritable: true,\n\t\t\t\tenumerable: true,\n\t\t\t\tconfigurable: true,\n\t\t\t});\n\t\t} else {\n\t\t\tresult[key] = copied;\n\t\t}\n\t}\n\treturn result as T;\n};\n\nconst cursorId = (cursor: Readonly<Record<string, JsonValue>> | undefined): Id | undefined =>\n\tcursor?.after as Id | undefined;\n\nconst lowerBound = (ids: readonly Id[], target: Id): number => {\n\tlet low = 0;\n\tlet high = ids.length;\n\twhile (low < high) {\n\t\tconst middle = (low + high) >>> 1;\n\t\tif (ids[middle] < target) low = middle + 1;\n\t\telse high = middle;\n\t}\n\treturn low;\n};\n\nconst upperBound = (ids: readonly Id[], target: Id): number => {\n\tlet low = 0;\n\tlet high = ids.length;\n\twhile (low < high) {\n\t\tconst middle = (low + high) >>> 1;\n\t\tif (ids[middle] <= target) low = middle + 1;\n\t\telse high = middle;\n\t}\n\treturn low;\n};\n\nconst insertSorted = (ids: Id[], id: Id): void => {\n\tif (ids.length === 0 || ids[ids.length - 1] < id) ids.push(id);\n\telse ids.splice(lowerBound(ids, id), 0, id);\n};\n\nconst removeSorted = (ids: Id[], id: Id): void => {\n\tconst index = lowerBound(ids, id);\n\tif (ids[index] === id) ids.splice(index, 1);\n};\n\nconst scopeKey = (scope: DocumentRecord[\"scope\"]): string => {\n\tswitch (scope.kind) {\n\t\tcase \"session\":\n\t\t\treturn JSON.stringify([\"session\"]);\n\t\tcase \"conversation\":\n\t\t\treturn JSON.stringify([\"conversation\", scope.conversationId]);\n\t\tcase \"task\":\n\t\t\treturn JSON.stringify([\"task\", scope.taskId]);\n\t}\n};\n\nconst addressKey = (address: DocumentAddress): string =>\n\tJSON.stringify([\n\t\taddress.kind,\n\t\tscopeKey(address.scope),\n\t\taddress.key === undefined ? [\"singleton\"] : [\"family\", address.key],\n\t]);\n\nconst recordAddressKey = (record: DocumentRecord | DocumentCreate): string =>\n\taddressKey({ kind: record.kind, scope: record.scope, key: record.key });\n\nconst isAliveAt = (record: DocumentRecord, at: DocumentPoint): boolean => {\n\tif (at === \"current\") return record.retiredAt === undefined;\n\treturn record.createdAt <= at && (record.retiredAt === undefined || at < record.retiredAt);\n};\n\nconst isCurrentOnly = (record: DocumentRecord): boolean =>\n\trecord.scope.kind !== \"conversation\" || record.history === \"latest\";\n\nconst tableContaining = (state: State, id: Id): TableName | undefined => {\n\tif (state.conversations.has(id)) return \"conversation\";\n\tif (state.entries.has(id)) return \"entry\";\n\tif (state.tasks.has(id)) return \"task\";\n\tif (state.submissions.has(id)) return \"submission\";\n\tif (state.documents.has(id)) return \"document\";\n\treturn undefined;\n};\n\nconst page = <T extends { readonly id: Id }>(values: readonly T[], limit: number): Page<T, Cursor> => {\n\tconst items = values.slice(0, limit);\n\tif (values.length <= limit) return { items: clone(items) };\n\treturn { items: clone(items), next: { after: items.at(-1)!.id } };\n};\n\n/**\n * Detached in-memory reference implementation of `Storage`.\n *\n * Reads and retained writes are cloned intentionally to match the ownership boundary\n * of serialization-backed stores. This is backend conformance, not validation.\n */\nexport class MemoryStorage implements Storage {\n\tprivate readonly state: State = {\n\t\tconversations: new Map(),\n\t\tconversationIds: [],\n\t\tentries: new Map(),\n\t\tentryIds: new Map(),\n\t\theadEntryIds: new Map(),\n\t\tentryCommitSeqs: new Map(),\n\t\ttasks: new Map(),\n\t\ttaskIds: [],\n\t\ttaskIdsByStatus: { pending: [], running: [], terminal: [] },\n\t\tsubmissions: new Map(),\n\t\tsubmissionIdsByRequest: new Map(),\n\t\tdocuments: new Map(),\n\t\tdocumentAddresses: new Map(),\n\t\tdocumentIdsByScope: new Map(),\n\t};\n\tprivate nextId = 2;\n\tprivate nextSeq = 1;\n\tprivate closed = false;\n\n\tasync commit(writes: readonly StorageWrite[], _context: Context): Promise<Seq> {\n\t\tthis.assertOpen();\n\t\tconst prepared = writes.map((write) => clone(write));\n\t\tconst seq = this.nextSeq;\n\t\tthis.checkGlobalIds(prepared);\n\t\tconst documentActions = this.prepareDocumentActions(prepared);\n\t\tthis.checkDocumentActions(documentActions);\n\n\t\tfor (const write of prepared) {\n\t\t\tswitch (write.type) {\n\t\t\t\tcase \"conversation\":\n\t\t\t\t\tthis.state.conversations.set(write.value.id, write.value);\n\t\t\t\t\tinsertSorted(this.state.conversationIds, write.value.id);\n\t\t\t\t\tthis.nextId = Math.max(this.nextId, write.value.id + 1);\n\t\t\t\t\tbreak;\n\t\t\t\tcase \"entry\": {\n\t\t\t\t\tthis.state.entries.set(write.value.id, write.value);\n\t\t\t\t\tthis.state.entryCommitSeqs.set(write.value.id, seq);\n\t\t\t\t\tlet ids = this.state.entryIds.get(write.value.conversationId);\n\t\t\t\t\tif (ids === undefined) {\n\t\t\t\t\t\tids = [];\n\t\t\t\t\t\tthis.state.entryIds.set(write.value.conversationId, ids);\n\t\t\t\t\t}\n\t\t\t\t\tinsertSorted(ids, write.value.id);\n\t\t\t\t\tif (write.value.head !== undefined) {\n\t\t\t\t\t\tlet headIds = this.state.headEntryIds.get(write.value.conversationId);\n\t\t\t\t\t\tif (headIds === undefined) {\n\t\t\t\t\t\t\theadIds = [];\n\t\t\t\t\t\t\tthis.state.headEntryIds.set(write.value.conversationId, headIds);\n\t\t\t\t\t\t}\n\t\t\t\t\t\tinsertSorted(headIds, write.value.id);\n\t\t\t\t\t}\n\t\t\t\t\tthis.nextId = Math.max(this.nextId, write.value.id + 1);\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t\tcase \"task\": {\n\t\t\t\t\tconst previous = this.state.tasks.get(write.value.id);\n\t\t\t\t\tif (previous === undefined) {\n\t\t\t\t\t\tinsertSorted(this.state.taskIds, write.value.id);\n\t\t\t\t\t\tinsertSorted(this.state.taskIdsByStatus[write.value.state.status], write.value.id);\n\t\t\t\t\t} else if (previous.state.status !== write.value.state.status) {\n\t\t\t\t\t\tremoveSorted(this.state.taskIdsByStatus[previous.state.status], write.value.id);\n\t\t\t\t\t\tinsertSorted(this.state.taskIdsByStatus[write.value.state.status], write.value.id);\n\t\t\t\t\t}\n\t\t\t\t\tthis.state.tasks.set(write.value.id, write.value);\n\t\t\t\t\tthis.nextId = Math.max(this.nextId, write.value.id + 1);\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t\tcase \"submission\": {\n\t\t\t\t\tconst previous = this.state.submissions.get(write.value.id);\n\t\t\t\t\tif (previous?.requestId !== undefined) {\n\t\t\t\t\t\tconst previousRequests = this.state.submissionIdsByRequest.get(previous.conversationId);\n\t\t\t\t\t\tif (previousRequests?.get(previous.requestId) === write.value.id) {\n\t\t\t\t\t\t\tpreviousRequests.delete(previous.requestId);\n\t\t\t\t\t\t\tif (previousRequests.size === 0) this.state.submissionIdsByRequest.delete(previous.conversationId);\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t\tthis.state.submissions.set(write.value.id, write.value);\n\t\t\t\t\tif (write.value.requestId !== undefined) {\n\t\t\t\t\t\tlet requests = this.state.submissionIdsByRequest.get(write.value.conversationId);\n\t\t\t\t\t\tif (requests === undefined) {\n\t\t\t\t\t\t\trequests = new Map();\n\t\t\t\t\t\t\tthis.state.submissionIdsByRequest.set(write.value.conversationId, requests);\n\t\t\t\t\t\t}\n\t\t\t\t\t\trequests.set(write.value.requestId, write.value.id);\n\t\t\t\t\t}\n\t\t\t\t\tthis.nextId = Math.max(this.nextId, write.value.id + 1);\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t\tcase \"document.create\":\n\t\t\t\tcase \"document.change\":\n\t\t\t\tcase \"document.retire\":\n\t\t\t\t\tbreak;\n\t\t\t}\n\t\t}\n\n\t\tthis.applyDocumentActions(documentActions, seq);\n\t\tthis.nextSeq++;\n\t\treturn seq;\n\t}\n\n\tasync mintId(): Promise<Id> {\n\t\tthis.assertOpen();\n\t\tif (!Number.isSafeInteger(this.nextId)) throw new Error(\"ID space is exhausted\");\n\t\treturn this.nextId++;\n\t}\n\n\tasync conversation(id: Id, _context: Context): Promise<ConversationRecord | undefined> {\n\t\tthis.assertOpen();\n\t\tconst value = this.state.conversations.get(id);\n\t\treturn value === undefined ? undefined : clone(value);\n\t}\n\n\tasync scanConversations(\n\t\tcursor: Cursor | undefined,\n\t\tlimit: number,\n\t\t_context: Context,\n\t): Promise<Page<ConversationRecord, Cursor>> {\n\t\tthis.assertOpen();\n\t\tconst after = cursorId(cursor);\n\t\tconst start = after === undefined ? 0 : upperBound(this.state.conversationIds, after);\n\t\tconst values = this.state.conversationIds\n\t\t\t.slice(start, start + limit + 1)\n\t\t\t.map((id) => this.state.conversations.get(id)!);\n\t\treturn page(values, limit);\n\t}\n\n\tasync entry(\n\t\tid: Id,\n\t\t_context: Context,\n\t): Promise<{ readonly entry: EntryRecord; readonly commitSeq: Seq } | undefined> {\n\t\tthis.assertOpen();\n\t\tconst entry = this.state.entries.get(id);\n\t\tif (entry === undefined) return undefined;\n\t\treturn { entry: clone(entry), commitSeq: this.state.entryCommitSeqs.get(id)! };\n\t}\n\n\tasync findLatestHeadMarker(\n\t\tconversationId: Id,\n\t\tatOrBeforeEntryId: Id | undefined,\n\t\t_context: Context,\n\t): Promise<(EntryRecord & { readonly head: Id }) | undefined> {\n\t\tthis.assertOpen();\n\t\tif (!this.state.conversations.has(conversationId)) {\n\t\t\tthrow new Error(`Unknown conversation: ${conversationId}`);\n\t\t}\n\t\tlet currentId = conversationId;\n\t\tlet upperEntryId = atOrBeforeEntryId ?? Number.POSITIVE_INFINITY;\n\t\twhile (true) {\n\t\t\tconst ids = this.state.headEntryIds.get(currentId) ?? [];\n\t\t\tconst index = upperBound(ids, upperEntryId) - 1;\n\t\t\tif (index >= 0) {\n\t\t\t\tconst entry = this.state.entries.get(ids[index])!;\n\t\t\t\treturn clone({ ...entry, head: entry.head! });\n\t\t\t}\n\t\t\tconst conversation = this.state.conversations.get(currentId)!;\n\t\t\tif (conversation.parent === undefined) return undefined;\n\t\t\tupperEntryId = Math.min(upperEntryId, conversation.parent.at);\n\t\t\tcurrentId = conversation.parent.conversationId;\n\t\t}\n\t}\n\n\tasync scanEntries(\n\t\tquery: EntryQuery,\n\t\tcursor: Cursor | undefined,\n\t\tlimit: number,\n\t\t_context: Context,\n\t): Promise<Page<EntryRecord, Cursor>> {\n\t\tthis.assertOpen();\n\t\tconst after = cursorId(cursor);\n\t\tconst maxEntryId =\n\t\t\tafter === undefined ? query.maxEntryId : Math.min(query.maxEntryId ?? Number.POSITIVE_INFINITY, after - 1);\n\t\tconst visible: EntryRecord[] = [];\n\t\tfor (const entry of this.visibleEntries(query.conversationId, query.minEntryId, maxEntryId)) {\n\t\t\tvisible.push(entry);\n\t\t\tif (visible.length > limit) break;\n\t\t}\n\t\treturn page(visible, limit);\n\t}\n\n\tasync task(id: Id, _context: Context): Promise<StoredTask | undefined> {\n\t\tthis.assertOpen();\n\t\tconst value = this.state.tasks.get(id);\n\t\treturn value === undefined ? undefined : clone(value);\n\t}\n\n\tasync scanTasks(\n\t\tquery: TaskQuery,\n\t\tcursor: Cursor | undefined,\n\t\tlimit: number,\n\t\t_context: Context,\n\t): Promise<Page<StoredTask, Cursor>> {\n\t\tthis.assertOpen();\n\t\tconst after = cursorId(cursor);\n\t\tconst ids = query.status === undefined ? this.state.taskIds : this.state.taskIdsByStatus[query.status];\n\t\tconst start = after === undefined ? 0 : upperBound(ids, after);\n\t\tconst values: StoredTask[] = [];\n\t\tfor (let index = start; index < ids.length && values.length <= limit; index++) {\n\t\t\tconst value = this.state.tasks.get(ids[index])!;\n\t\t\tif (query.conversationId !== undefined && value.conversationId !== query.conversationId) continue;\n\t\t\tif (query.kind !== undefined && value.kind !== query.kind) continue;\n\t\t\tif (query.abortRequested !== undefined && value.abortRequested !== query.abortRequested) continue;\n\t\t\tif (query.background !== undefined && value.background !== query.background) continue;\n\t\t\tvalues.push(value);\n\t\t}\n\t\treturn page(values, limit);\n\t}\n\n\tasync submission(id: Id, _context: Context): Promise<SubmissionRecord | undefined> {\n\t\tthis.assertOpen();\n\t\tconst value = this.state.submissions.get(id);\n\t\treturn value === undefined ? undefined : clone(value);\n\t}\n\n\tasync submissionByRequest(\n\t\tconversationId: Id,\n\t\trequestId: string,\n\t\t_context: Context,\n\t): Promise<SubmissionRecord | undefined> {\n\t\tthis.assertOpen();\n\t\tconst id = this.state.submissionIdsByRequest.get(conversationId)?.get(requestId);\n\t\tif (id === undefined) return undefined;\n\t\treturn clone(this.state.submissions.get(id)!);\n\t}\n\n\tasync findDocument(\n\t\taddress: DocumentAddress,\n\t\tat: DocumentPoint,\n\t\t_context: Context,\n\t): Promise<DocumentRecord | undefined> {\n\t\tthis.assertOpen();\n\t\tconst index = this.state.documentAddresses.get(addressKey(address));\n\t\tif (at === \"current\") {\n\t\t\treturn index?.currentId === undefined ? undefined : clone(this.state.documents.get(index.currentId)!.record);\n\t\t}\n\t\tfor (const id of index?.ids ?? []) {\n\t\t\tconst record = this.state.documents.get(id)!.record;\n\t\t\tif (isAliveAt(record, at)) return clone(record);\n\t\t}\n\t\treturn undefined;\n\t}\n\n\tasync document(id: Id, at: DocumentPoint, _context: Context): Promise<StoredDocument | undefined> {\n\t\tthis.assertOpen();\n\t\tconst stored = this.state.documents.get(id);\n\t\tif (stored === undefined) return undefined;\n\t\tif (at !== \"current\" && isCurrentOnly(stored.record)) {\n\t\t\tthrow new Error(`Document ${id} does not retain historical content`);\n\t\t}\n\t\tif (!isAliveAt(stored.record, at)) return undefined;\n\t\tconst revisions = at === \"current\" ? stored.revisions : stored.revisions.filter((revision) => revision.seq <= at);\n\t\tlet baseIndex = revisions.length - 1;\n\t\twhile (baseIndex >= 0 && revisions[baseIndex]!.kind !== \"base\") baseIndex--;\n\t\tconst base = revisions[baseIndex];\n\t\tif (base?.kind !== \"base\") throw new Error(`Document ${id} is missing a required base`);\n\t\tlet value = base.value;\n\t\tfor (let index = baseIndex + 1; index < revisions.length; index++) {\n\t\t\tconst revision = revisions[index]!;\n\t\t\tif (revision.kind !== \"delta\" || revision.version !== base.version) {\n\t\t\t\tthrow new Error(`Document ${id} crosses a stored version boundary without a base`);\n\t\t\t}\n\t\t\tvalue = applyImmutable(value, revision.ops) as JsonObject;\n\t\t}\n\t\treturn { record: clone(stored.record), version: base.version, value: clone(value) };\n\t}\n\n\tasync scanDocuments(\n\t\tquery: DocumentQuery,\n\t\tcursor: Cursor | undefined,\n\t\tlimit: number,\n\t\t_context: Context,\n\t): Promise<Page<DocumentRecord, Cursor>> {\n\t\tthis.assertOpen();\n\t\tconst ids = this.state.documentIdsByScope.get(scopeKey(query.scope)) ?? [];\n\t\tconst after = cursorId(cursor);\n\t\tconst start = after === undefined ? 0 : upperBound(ids, after);\n\t\tconst values: DocumentRecord[] = [];\n\t\tfor (let index = start; index < ids.length && values.length <= limit; index++) {\n\t\t\tconst record = this.state.documents.get(ids[index]!)!.record;\n\t\t\tif (query.kind !== undefined && record.kind !== query.kind) continue;\n\t\t\tif (isAliveAt(record, query.at)) values.push(record);\n\t\t}\n\t\treturn page(values, limit);\n\t}\n\n\tasync close(_context: Context): Promise<void> {\n\t\tthis.closed = true;\n\t}\n\n\tprivate *visibleEntries(\n\t\tconversationId: Id,\n\t\tminEntryId = Number.NEGATIVE_INFINITY,\n\t\tmaxEntryId = Number.POSITIVE_INFINITY,\n\t): Generator<EntryRecord> {\n\t\tif (!this.state.conversations.has(conversationId)) {\n\t\t\tthrow new Error(`Unknown conversation: ${conversationId}`);\n\t\t}\n\t\tlet currentId = conversationId;\n\t\tlet upperEntryId = maxEntryId;\n\t\twhile (true) {\n\t\t\tconst ids = this.state.entryIds.get(currentId) ?? [];\n\t\t\tfor (let index = upperBound(ids, upperEntryId) - 1; index >= 0; index--) {\n\t\t\t\tconst id = ids[index];\n\t\t\t\tif (id < minEntryId) break;\n\t\t\t\tyield this.state.entries.get(id)!;\n\t\t\t}\n\t\t\tconst conversation = this.state.conversations.get(currentId)!;\n\t\t\tif (conversation.parent === undefined) break;\n\t\t\tupperEntryId = Math.min(upperEntryId, conversation.parent.at);\n\t\t\tif (upperEntryId < minEntryId) break;\n\t\t\tcurrentId = conversation.parent.conversationId;\n\t\t}\n\t}\n\n\tprivate checkGlobalIds(writes: readonly StorageWrite[]): void {\n\t\tconst claimed = new Map<Id, TableName>();\n\t\tfor (const write of writes) {\n\t\t\tif (write.type === \"document.change\" || write.type === \"document.retire\") continue;\n\t\t\tconst table: TableName = write.type === \"document.create\" ? \"document\" : write.type;\n\t\t\tconst id = write.type === \"document.create\" ? write.record.id : write.value.id;\n\t\t\tconst existing = tableContaining(this.state, id);\n\t\t\tconst earlier = claimed.get(id);\n\t\t\tif (table === \"conversation\" || table === \"entry\" || table === \"document\") {\n\t\t\t\tif (existing !== undefined) throw new Error(`ID ${id} already belongs to ${existing}`);\n\t\t\t\tif (earlier !== undefined) throw new Error(`ID ${id} is written more than once`);\n\t\t\t} else {\n\t\t\t\tif (existing !== undefined && existing !== table) {\n\t\t\t\t\tthrow new Error(`ID ${id} already belongs to ${existing}`);\n\t\t\t\t}\n\t\t\t\tif (earlier !== undefined && earlier !== table) throw new Error(`ID ${id} is written as two record types`);\n\t\t\t}\n\t\t\tclaimed.set(id, table);\n\t\t}\n\t}\n\n\tprivate prepareDocumentActions(writes: readonly StorageWrite[]): Map<Id, DocumentAction> {\n\t\tconst actions = new Map<Id, DocumentAction>();\n\t\tfor (const write of writes) {\n\t\t\tif (write.type !== \"document.create\" && write.type !== \"document.change\" && write.type !== \"document.retire\") {\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\tconst id = write.type === \"document.create\" ? write.record.id : write.id;\n\t\t\tlet action = actions.get(id);\n\t\t\tif (action === undefined) {\n\t\t\t\taction = { retire: false };\n\t\t\t\tactions.set(id, action);\n\t\t\t}\n\t\t\tswitch (write.type) {\n\t\t\t\tcase \"document.create\":\n\t\t\t\t\tif (action.create !== undefined || action.content !== undefined) {\n\t\t\t\t\t\tthrow new Error(`Document ${id} has more than one content command`);\n\t\t\t\t\t}\n\t\t\t\t\taction.create = write.record;\n\t\t\t\t\taction.content = write.content;\n\t\t\t\t\tbreak;\n\t\t\t\tcase \"document.change\":\n\t\t\t\t\tif (action.content !== undefined) throw new Error(`Document ${id} has more than one content command`);\n\t\t\t\t\taction.content = write.content;\n\t\t\t\t\tbreak;\n\t\t\t\tcase \"document.retire\":\n\t\t\t\t\tif (action.retire) throw new Error(`Document ${id} is retired more than once`);\n\t\t\t\t\taction.retire = true;\n\t\t\t\t\tbreak;\n\t\t\t}\n\t\t}\n\t\treturn actions;\n\t}\n\n\tprivate checkDocumentActions(actions: ReadonlyMap<Id, DocumentAction>): void {\n\t\tconst liveCounts = new Map<string, number>();\n\t\tfor (const [id, action] of actions) {\n\t\t\tconst existing = this.state.documents.get(id);\n\t\t\tif (action.create === undefined && existing === undefined) throw new Error(`Unknown document: ${id}`);\n\t\t\tif (action.create !== undefined && existing !== undefined) throw new Error(`Document ${id} already exists`);\n\t\t\tif (existing?.record.retiredAt !== undefined) throw new Error(`Document ${id} is retired`);\n\t\t\tif (action.create !== undefined && action.content?.kind !== \"base\") {\n\t\t\t\tthrow new Error(`Document ${id} creation requires a base`);\n\t\t\t}\n\t\t\tconst previous = existing?.revisions.at(-1);\n\t\t\tif (action.content?.kind === \"delta\") {\n\t\t\t\tif (previous === undefined) throw new Error(`Document ${id} delta has no base`);\n\t\t\t\tif (previous.version !== action.content.version) {\n\t\t\t\t\tthrow new Error(`Document ${id} version transition requires a base`);\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tconst key = action.create === undefined ? recordAddressKey(existing!.record) : recordAddressKey(action.create);\n\t\t\tconst currentId = this.state.documentAddresses.get(key)?.currentId;\n\t\t\tlet live = liveCounts.get(key);\n\t\t\tif (live === undefined) live = currentId === undefined ? 0 : 1;\n\t\t\tif (action.retire && currentId === id) live--;\n\t\t\tif (action.create !== undefined && !action.retire) live++;\n\t\t\tliveCounts.set(key, live);\n\t\t}\n\n\t\tfor (const live of liveCounts.values()) {\n\t\t\tif (live > 1) throw new Error(`Document address already has a current incarnation`);\n\t\t}\n\t}\n\n\tprivate applyDocumentActions(actions: ReadonlyMap<Id, DocumentAction>, seq: Seq): void {\n\t\tfor (const [id, action] of actions) {\n\t\t\tlet stored = this.state.documents.get(id);\n\t\t\tif (action.create !== undefined) {\n\t\t\t\tconst record: DocumentRecord = {\n\t\t\t\t\t...action.create,\n\t\t\t\t\tcreatedAt: seq,\n\t\t\t\t\t...(action.retire ? { retiredAt: seq } : {}),\n\t\t\t\t};\n\t\t\t\tstored = { record, revisions: [{ ...action.content!, seq }] };\n\t\t\t\tthis.state.documents.set(id, stored);\n\n\t\t\t\tconst key = recordAddressKey(record);\n\t\t\t\tlet address = this.state.documentAddresses.get(key);\n\t\t\t\tif (address === undefined) {\n\t\t\t\t\taddress = { ids: [] };\n\t\t\t\t\tthis.state.documentAddresses.set(key, address);\n\t\t\t\t}\n\t\t\t\tinsertSorted(address.ids, id);\n\n\t\t\t\tlet scopeIds = this.state.documentIdsByScope.get(scopeKey(record.scope));\n\t\t\t\tif (scopeIds === undefined) {\n\t\t\t\t\tscopeIds = [];\n\t\t\t\t\tthis.state.documentIdsByScope.set(scopeKey(record.scope), scopeIds);\n\t\t\t\t}\n\t\t\t\tinsertSorted(scopeIds, id);\n\t\t\t\tthis.nextId = Math.max(this.nextId, id + 1);\n\t\t\t} else if (action.content !== undefined) {\n\t\t\t\tconst revision = { ...action.content, seq } as DocumentRevision;\n\t\t\t\tif (revision.kind === \"base\" && isCurrentOnly(stored!.record)) stored!.revisions = [revision];\n\t\t\t\telse stored!.revisions.push(revision);\n\t\t\t}\n\n\t\t\tif (action.retire && action.create === undefined) stored!.record = { ...stored!.record, retiredAt: seq };\n\t\t\tif (action.retire && isCurrentOnly(stored!.record)) stored!.revisions = [];\n\t\t\tif (action.create !== undefined || action.retire) {\n\t\t\t\tconst address = this.state.documentAddresses.get(recordAddressKey(stored!.record))!;\n\t\t\t\tif (action.retire && address.currentId === id) delete address.currentId;\n\t\t\t\tif (action.create !== undefined && !action.retire) address.currentId = id;\n\t\t\t}\n\t\t}\n\t}\n\n\tprivate assertOpen(): void {\n\t\tif (this.closed) throw new Error(\"MemoryStorage is closed\");\n\t}\n}\n"]}
@@ -0,0 +1,26 @@
1
+ /** Values supported by the portable SQLite storage core. */
2
+ export type SqliteValue = null | number | bigint | string | Uint8Array;
3
+ /**
4
+ * A prepared synchronous SQLite statement.
5
+ * Implementations must support repeated execution with new bindings across transactions.
6
+ */
7
+ export interface SqliteStatement {
8
+ run(...params: SqliteValue[]): void;
9
+ get<T extends object>(...params: SqliteValue[]): T | undefined;
10
+ all<T extends object>(...params: SqliteValue[]): T[];
11
+ }
12
+ /**
13
+ * Minimal database facade required by `SqliteStorage`.
14
+ *
15
+ * Queries and transaction callbacks are synchronous so the same storage core can
16
+ * run on Node, Bun, and Cloudflare Durable Object SQLite. An adapter may return a
17
+ * promise from `transaction` while it waits for the transaction to settle.
18
+ * Callers must not close the database while a returned settlement is pending.
19
+ */
20
+ export interface SqliteDatabase {
21
+ exec(sql: string): void;
22
+ prepare(sql: string): SqliteStatement;
23
+ transaction<T>(callback: () => T): T | Promise<T>;
24
+ close(): void | Promise<void>;
25
+ }
26
+ //# sourceMappingURL=database.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"database.d.ts","sourceRoot":"","sources":["../../../src/storage/sqlite/database.ts"],"names":[],"mappings":"AAAA,4DAA4D;AAC5D,MAAM,MAAM,WAAW,GAAG,IAAI,GAAG,MAAM,GAAG,MAAM,GAAG,MAAM,GAAG,UAAU,CAAC;AAEvE;;;GAGG;AACH,MAAM,WAAW,eAAe;IAC/B,GAAG,CAAC,GAAG,MAAM,EAAE,WAAW,EAAE,GAAG,IAAI,CAAC;IACpC,GAAG,CAAC,CAAC,SAAS,MAAM,EAAE,GAAG,MAAM,EAAE,WAAW,EAAE,GAAG,CAAC,GAAG,SAAS,CAAC;IAC/D,GAAG,CAAC,CAAC,SAAS,MAAM,EAAE,GAAG,MAAM,EAAE,WAAW,EAAE,GAAG,CAAC,EAAE,CAAC;CACrD;AAED;;;;;;;GAOG;AACH,MAAM,WAAW,cAAc;IAC9B,IAAI,CAAC,GAAG,EAAE,MAAM,GAAG,IAAI,CAAC;IACxB,OAAO,CAAC,GAAG,EAAE,MAAM,GAAG,eAAe,CAAC;IACtC,WAAW,CAAC,CAAC,EAAE,QAAQ,EAAE,MAAM,CAAC,GAAG,CAAC,GAAG,OAAO,CAAC,CAAC,CAAC,CAAC;IAClD,KAAK,IAAI,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;CAC9B","sourcesContent":["/** Values supported by the portable SQLite storage core. */\nexport type SqliteValue = null | number | bigint | string | Uint8Array;\n\n/**\n * A prepared synchronous SQLite statement.\n * Implementations must support repeated execution with new bindings across transactions.\n */\nexport interface SqliteStatement {\n\trun(...params: SqliteValue[]): void;\n\tget<T extends object>(...params: SqliteValue[]): T | undefined;\n\tall<T extends object>(...params: SqliteValue[]): T[];\n}\n\n/**\n * Minimal database facade required by `SqliteStorage`.\n *\n * Queries and transaction callbacks are synchronous so the same storage core can\n * run on Node, Bun, and Cloudflare Durable Object SQLite. An adapter may return a\n * promise from `transaction` while it waits for the transaction to settle.\n * Callers must not close the database while a returned settlement is pending.\n */\nexport interface SqliteDatabase {\n\texec(sql: string): void;\n\tprepare(sql: string): SqliteStatement;\n\ttransaction<T>(callback: () => T): T | Promise<T>;\n\tclose(): void | Promise<void>;\n}\n"]}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=database.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"database.js","sourceRoot":"","sources":["../../../src/storage/sqlite/database.ts"],"names":[],"mappings":"","sourcesContent":["/** Values supported by the portable SQLite storage core. */\nexport type SqliteValue = null | number | bigint | string | Uint8Array;\n\n/**\n * A prepared synchronous SQLite statement.\n * Implementations must support repeated execution with new bindings across transactions.\n */\nexport interface SqliteStatement {\n\trun(...params: SqliteValue[]): void;\n\tget<T extends object>(...params: SqliteValue[]): T | undefined;\n\tall<T extends object>(...params: SqliteValue[]): T[];\n}\n\n/**\n * Minimal database facade required by `SqliteStorage`.\n *\n * Queries and transaction callbacks are synchronous so the same storage core can\n * run on Node, Bun, and Cloudflare Durable Object SQLite. An adapter may return a\n * promise from `transaction` while it waits for the transaction to settle.\n * Callers must not close the database while a returned settlement is pending.\n */\nexport interface SqliteDatabase {\n\texec(sql: string): void;\n\tprepare(sql: string): SqliteStatement;\n\ttransaction<T>(callback: () => T): T | Promise<T>;\n\tclose(): void | Promise<void>;\n}\n"]}
@@ -0,0 +1,4 @@
1
+ export type { SqliteDatabase, SqliteStatement, SqliteValue } from "./database.ts";
2
+ export { applySqliteMigrations, CURRENT_SQLITE_SCHEMA_VERSION, SQLITE_MIGRATIONS, type SqliteMigration, } from "./migrations.ts";
3
+ export { SqliteStorage } from "./storage.ts";
4
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/storage/sqlite/index.ts"],"names":[],"mappings":"AAAA,YAAY,EAAE,cAAc,EAAE,eAAe,EAAE,WAAW,EAAE,MAAM,eAAe,CAAC;AAClF,OAAO,EACN,qBAAqB,EACrB,6BAA6B,EAC7B,iBAAiB,EACjB,KAAK,eAAe,GACpB,MAAM,iBAAiB,CAAC;AACzB,OAAO,EAAE,aAAa,EAAE,MAAM,cAAc,CAAC","sourcesContent":["export type { SqliteDatabase, SqliteStatement, SqliteValue } from \"./database.ts\";\nexport {\n\tapplySqliteMigrations,\n\tCURRENT_SQLITE_SCHEMA_VERSION,\n\tSQLITE_MIGRATIONS,\n\ttype SqliteMigration,\n} from \"./migrations.ts\";\nexport { SqliteStorage } from \"./storage.ts\";\n"]}
@@ -0,0 +1,3 @@
1
+ export { applySqliteMigrations, CURRENT_SQLITE_SCHEMA_VERSION, SQLITE_MIGRATIONS, } from "./migrations.js";
2
+ export { SqliteStorage } from "./storage.js";
3
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/storage/sqlite/index.ts"],"names":[],"mappings":"AACA,OAAO,EACN,qBAAqB,EACrB,6BAA6B,EAC7B,iBAAiB,GAEjB,MAAM,iBAAiB,CAAC;AACzB,OAAO,EAAE,aAAa,EAAE,MAAM,cAAc,CAAC","sourcesContent":["export type { SqliteDatabase, SqliteStatement, SqliteValue } from \"./database.ts\";\nexport {\n\tapplySqliteMigrations,\n\tCURRENT_SQLITE_SCHEMA_VERSION,\n\tSQLITE_MIGRATIONS,\n\ttype SqliteMigration,\n} from \"./migrations.ts\";\nexport { SqliteStorage } from \"./storage.ts\";\n"]}
@@ -0,0 +1,11 @@
1
+ import type { SqliteDatabase } from "./database.ts";
2
+ export type SqliteMigration = {
3
+ readonly version: number;
4
+ readonly statements: readonly string[];
5
+ };
6
+ /** Immutable, ordered schema history. Append new migrations; never edit released ones. */
7
+ export declare const SQLITE_MIGRATIONS: readonly SqliteMigration[];
8
+ export declare const CURRENT_SQLITE_SCHEMA_VERSION: number;
9
+ /** Apply all pending schema migrations atomically. */
10
+ export declare function applySqliteMigrations(database: SqliteDatabase, migrations?: readonly SqliteMigration[]): Promise<void>;
11
+ //# sourceMappingURL=migrations.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"migrations.d.ts","sourceRoot":"","sources":["../../../src/storage/sqlite/migrations.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,eAAe,CAAC;AAEpD,MAAM,MAAM,eAAe,GAAG;IAC7B,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,UAAU,EAAE,SAAS,MAAM,EAAE,CAAC;CACvC,CAAC;AA0EF,0FAA0F;AAC1F,eAAO,MAAM,iBAAiB,EAAE,SAAS,eAAe,EAAiD,CAAC;AAE1G,eAAO,MAAM,6BAA6B,QAAyC,CAAC;AAIpF,sDAAsD;AACtD,wBAAsB,qBAAqB,CAC1C,QAAQ,EAAE,cAAc,EACxB,UAAU,GAAE,SAAS,eAAe,EAAsB,GACxD,OAAO,CAAC,IAAI,CAAC,CA2Bf","sourcesContent":["import type { SqliteDatabase } from \"./database.ts\";\n\nexport type SqliteMigration = {\n\treadonly version: number;\n\treadonly statements: readonly string[];\n};\n\n// next_id is TEXT because node:sqlite rejects INTEGER results outside JavaScript's safe integer range.\nconst INITIAL_SCHEMA: readonly string[] = [\n\t`CREATE TABLE durable_metadata (\n\t\tsingleton INTEGER PRIMARY KEY CHECK (singleton = 1),\n\t\tnext_id TEXT NOT NULL,\n\t\tnext_seq INTEGER NOT NULL\n\t) STRICT`,\n\t`INSERT INTO durable_metadata (singleton, next_id, next_seq) VALUES (1, '2', 1)`,\n\t`CREATE TABLE record_ids (\n\t\tid INTEGER PRIMARY KEY,\n\t\trecord_type TEXT NOT NULL CHECK (record_type IN ('conversation', 'entry', 'task', 'submission', 'document'))\n\t) STRICT`,\n\t`CREATE TABLE conversations (\n\t\tid INTEGER PRIMARY KEY,\n\t\trecord TEXT NOT NULL CHECK (json_valid(record))\n\t) STRICT`,\n\t`CREATE TABLE entries (\n\t\tid INTEGER PRIMARY KEY,\n\t\tconversation_id INTEGER NOT NULL,\n\t\thead INTEGER,\n\t\tcommit_seq INTEGER NOT NULL,\n\t\trecord TEXT NOT NULL CHECK (json_valid(record))\n\t) STRICT`,\n\t\"CREATE INDEX entries_by_conversation ON entries (conversation_id, id DESC)\",\n\t\"CREATE INDEX entry_heads_by_conversation ON entries (conversation_id, id DESC) WHERE head IS NOT NULL\",\n\t`CREATE TABLE tasks (\n\t\tid INTEGER PRIMARY KEY,\n\t\tconversation_id INTEGER NOT NULL,\n\t\tkind TEXT NOT NULL,\n\t\tstatus TEXT NOT NULL CHECK (status IN ('pending', 'running', 'terminal')),\n\t\tabort_requested INTEGER NOT NULL CHECK (abort_requested IN (0, 1)),\n\t\tbackground INTEGER NOT NULL CHECK (background IN (0, 1)),\n\t\trecord TEXT NOT NULL CHECK (json_valid(record))\n\t) STRICT`,\n\t\"CREATE INDEX tasks_by_status ON tasks (status, id)\",\n\t\"CREATE INDEX tasks_by_conversation ON tasks (conversation_id, id)\",\n\t\"CREATE INDEX tasks_by_kind ON tasks (kind, id)\",\n\t\"CREATE INDEX tasks_by_abort_requested ON tasks (abort_requested, id)\",\n\t\"CREATE INDEX tasks_by_background ON tasks (background, id)\",\n\t`CREATE TABLE submissions (\n\t\tid INTEGER PRIMARY KEY,\n\t\tconversation_id INTEGER NOT NULL,\n\t\trequest_id TEXT,\n\t\trecord TEXT NOT NULL CHECK (json_valid(record))\n\t) STRICT`,\n\t\"CREATE INDEX submissions_by_request ON submissions (conversation_id, request_id)\",\n\t`CREATE TABLE documents (\n\t\tid INTEGER PRIMARY KEY,\n\t\tkind TEXT NOT NULL,\n\t\tfamily INTEGER NOT NULL CHECK (family IN (0, 1)),\n\t\tkey_value TEXT NOT NULL,\n\t\tscope_kind TEXT NOT NULL CHECK (scope_kind IN ('session', 'conversation', 'task')),\n\t\towner_id INTEGER NOT NULL,\n\t\tcreated_at INTEGER NOT NULL,\n\t\tretired_at INTEGER,\n\t\trecord TEXT NOT NULL CHECK (json_valid(record))\n\t) STRICT`,\n\t`CREATE INDEX documents_by_address\n\t\tON documents (kind, scope_kind, owner_id, family, key_value, created_at DESC, retired_at)`,\n\t\"CREATE INDEX documents_by_scope ON documents (scope_kind, owner_id, id)\",\n\t\"CREATE INDEX documents_by_scope_kind ON documents (scope_kind, owner_id, kind, id)\",\n\t`CREATE TABLE document_revisions (\n\t\tdocument_id INTEGER NOT NULL,\n\t\tseq INTEGER NOT NULL,\n\t\tkind TEXT NOT NULL CHECK (kind IN ('base', 'delta')),\n\t\tversion INTEGER NOT NULL,\n\t\tcontent TEXT NOT NULL CHECK (json_valid(content)),\n\t\tPRIMARY KEY (document_id, seq)\n\t) STRICT`,\n\t\"CREATE INDEX document_revisions_by_kind ON document_revisions (document_id, kind, seq DESC)\",\n];\n\n/** Immutable, ordered schema history. Append new migrations; never edit released ones. */\nexport const SQLITE_MIGRATIONS: readonly SqliteMigration[] = [{ version: 1, statements: INITIAL_SCHEMA }];\n\nexport const CURRENT_SQLITE_SCHEMA_VERSION = SQLITE_MIGRATIONS.at(-1)?.version ?? 0;\n\ntype SchemaRow = { readonly version: number };\n\n/** Apply all pending schema migrations atomically. */\nexport async function applySqliteMigrations(\n\tdatabase: SqliteDatabase,\n\tmigrations: readonly SqliteMigration[] = SQLITE_MIGRATIONS,\n): Promise<void> {\n\tfor (let index = 0; index < migrations.length; index++) {\n\t\tif (migrations[index]?.version !== index + 1) {\n\t\t\tthrow new Error(\"Durable SQLite migrations must have contiguous versions starting at 1\");\n\t\t}\n\t}\n\n\tawait database.transaction(() => {\n\t\tdatabase.exec(`CREATE TABLE IF NOT EXISTS durable_schema (\n\t\t\tsingleton INTEGER PRIMARY KEY CHECK (singleton = 1),\n\t\t\tversion INTEGER NOT NULL CHECK (version >= 0)\n\t\t) STRICT`);\n\t\tdatabase.prepare(\"INSERT OR IGNORE INTO durable_schema (singleton, version) VALUES (1, 0)\").run();\n\t\tconst row = database.prepare(\"SELECT version FROM durable_schema WHERE singleton = 1\").get<SchemaRow>();\n\t\tif (row === undefined) throw new Error(\"Durable SQLite schema metadata is missing\");\n\t\tconst currentVersion = migrations.at(-1)?.version ?? 0;\n\t\tif (row.version > currentVersion) {\n\t\t\tthrow new Error(\n\t\t\t\t`Durable SQLite schema version ${row.version} is newer than supported version ${currentVersion}`,\n\t\t\t);\n\t\t}\n\t\tfor (const migration of migrations) {\n\t\t\tif (migration.version <= row.version) continue;\n\t\t\tfor (const statement of migration.statements) database.exec(statement);\n\t\t\tdatabase.prepare(\"UPDATE durable_schema SET version = ? WHERE singleton = 1\").run(migration.version);\n\t\t}\n\t});\n}\n"]}
@@ -0,0 +1,104 @@
1
+ // next_id is TEXT because node:sqlite rejects INTEGER results outside JavaScript's safe integer range.
2
+ const INITIAL_SCHEMA = [
3
+ `CREATE TABLE durable_metadata (
4
+ singleton INTEGER PRIMARY KEY CHECK (singleton = 1),
5
+ next_id TEXT NOT NULL,
6
+ next_seq INTEGER NOT NULL
7
+ ) STRICT`,
8
+ `INSERT INTO durable_metadata (singleton, next_id, next_seq) VALUES (1, '2', 1)`,
9
+ `CREATE TABLE record_ids (
10
+ id INTEGER PRIMARY KEY,
11
+ record_type TEXT NOT NULL CHECK (record_type IN ('conversation', 'entry', 'task', 'submission', 'document'))
12
+ ) STRICT`,
13
+ `CREATE TABLE conversations (
14
+ id INTEGER PRIMARY KEY,
15
+ record TEXT NOT NULL CHECK (json_valid(record))
16
+ ) STRICT`,
17
+ `CREATE TABLE entries (
18
+ id INTEGER PRIMARY KEY,
19
+ conversation_id INTEGER NOT NULL,
20
+ head INTEGER,
21
+ commit_seq INTEGER NOT NULL,
22
+ record TEXT NOT NULL CHECK (json_valid(record))
23
+ ) STRICT`,
24
+ "CREATE INDEX entries_by_conversation ON entries (conversation_id, id DESC)",
25
+ "CREATE INDEX entry_heads_by_conversation ON entries (conversation_id, id DESC) WHERE head IS NOT NULL",
26
+ `CREATE TABLE tasks (
27
+ id INTEGER PRIMARY KEY,
28
+ conversation_id INTEGER NOT NULL,
29
+ kind TEXT NOT NULL,
30
+ status TEXT NOT NULL CHECK (status IN ('pending', 'running', 'terminal')),
31
+ abort_requested INTEGER NOT NULL CHECK (abort_requested IN (0, 1)),
32
+ background INTEGER NOT NULL CHECK (background IN (0, 1)),
33
+ record TEXT NOT NULL CHECK (json_valid(record))
34
+ ) STRICT`,
35
+ "CREATE INDEX tasks_by_status ON tasks (status, id)",
36
+ "CREATE INDEX tasks_by_conversation ON tasks (conversation_id, id)",
37
+ "CREATE INDEX tasks_by_kind ON tasks (kind, id)",
38
+ "CREATE INDEX tasks_by_abort_requested ON tasks (abort_requested, id)",
39
+ "CREATE INDEX tasks_by_background ON tasks (background, id)",
40
+ `CREATE TABLE submissions (
41
+ id INTEGER PRIMARY KEY,
42
+ conversation_id INTEGER NOT NULL,
43
+ request_id TEXT,
44
+ record TEXT NOT NULL CHECK (json_valid(record))
45
+ ) STRICT`,
46
+ "CREATE INDEX submissions_by_request ON submissions (conversation_id, request_id)",
47
+ `CREATE TABLE documents (
48
+ id INTEGER PRIMARY KEY,
49
+ kind TEXT NOT NULL,
50
+ family INTEGER NOT NULL CHECK (family IN (0, 1)),
51
+ key_value TEXT NOT NULL,
52
+ scope_kind TEXT NOT NULL CHECK (scope_kind IN ('session', 'conversation', 'task')),
53
+ owner_id INTEGER NOT NULL,
54
+ created_at INTEGER NOT NULL,
55
+ retired_at INTEGER,
56
+ record TEXT NOT NULL CHECK (json_valid(record))
57
+ ) STRICT`,
58
+ `CREATE INDEX documents_by_address
59
+ ON documents (kind, scope_kind, owner_id, family, key_value, created_at DESC, retired_at)`,
60
+ "CREATE INDEX documents_by_scope ON documents (scope_kind, owner_id, id)",
61
+ "CREATE INDEX documents_by_scope_kind ON documents (scope_kind, owner_id, kind, id)",
62
+ `CREATE TABLE document_revisions (
63
+ document_id INTEGER NOT NULL,
64
+ seq INTEGER NOT NULL,
65
+ kind TEXT NOT NULL CHECK (kind IN ('base', 'delta')),
66
+ version INTEGER NOT NULL,
67
+ content TEXT NOT NULL CHECK (json_valid(content)),
68
+ PRIMARY KEY (document_id, seq)
69
+ ) STRICT`,
70
+ "CREATE INDEX document_revisions_by_kind ON document_revisions (document_id, kind, seq DESC)",
71
+ ];
72
+ /** Immutable, ordered schema history. Append new migrations; never edit released ones. */
73
+ export const SQLITE_MIGRATIONS = [{ version: 1, statements: INITIAL_SCHEMA }];
74
+ export const CURRENT_SQLITE_SCHEMA_VERSION = SQLITE_MIGRATIONS.at(-1)?.version ?? 0;
75
+ /** Apply all pending schema migrations atomically. */
76
+ export async function applySqliteMigrations(database, migrations = SQLITE_MIGRATIONS) {
77
+ for (let index = 0; index < migrations.length; index++) {
78
+ if (migrations[index]?.version !== index + 1) {
79
+ throw new Error("Durable SQLite migrations must have contiguous versions starting at 1");
80
+ }
81
+ }
82
+ await database.transaction(() => {
83
+ database.exec(`CREATE TABLE IF NOT EXISTS durable_schema (
84
+ singleton INTEGER PRIMARY KEY CHECK (singleton = 1),
85
+ version INTEGER NOT NULL CHECK (version >= 0)
86
+ ) STRICT`);
87
+ database.prepare("INSERT OR IGNORE INTO durable_schema (singleton, version) VALUES (1, 0)").run();
88
+ const row = database.prepare("SELECT version FROM durable_schema WHERE singleton = 1").get();
89
+ if (row === undefined)
90
+ throw new Error("Durable SQLite schema metadata is missing");
91
+ const currentVersion = migrations.at(-1)?.version ?? 0;
92
+ if (row.version > currentVersion) {
93
+ throw new Error(`Durable SQLite schema version ${row.version} is newer than supported version ${currentVersion}`);
94
+ }
95
+ for (const migration of migrations) {
96
+ if (migration.version <= row.version)
97
+ continue;
98
+ for (const statement of migration.statements)
99
+ database.exec(statement);
100
+ database.prepare("UPDATE durable_schema SET version = ? WHERE singleton = 1").run(migration.version);
101
+ }
102
+ });
103
+ }
104
+ //# sourceMappingURL=migrations.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"migrations.js","sourceRoot":"","sources":["../../../src/storage/sqlite/migrations.ts"],"names":[],"mappings":"AAOA,uGAAuG;AACvG,MAAM,cAAc,GAAsB;IACzC;;;;UAIS;IACT,gFAAgF;IAChF;;;UAGS;IACT;;;UAGS;IACT;;;;;;UAMS;IACT,4EAA4E;IAC5E,uGAAuG;IACvG;;;;;;;;UAQS;IACT,oDAAoD;IACpD,mEAAmE;IACnE,gDAAgD;IAChD,sEAAsE;IACtE,4DAA4D;IAC5D;;;;;UAKS;IACT,kFAAkF;IAClF;;;;;;;;;;UAUS;IACT;4FAC2F;IAC3F,yEAAyE;IACzE,oFAAoF;IACpF;;;;;;;UAOS;IACT,6FAA6F;CAC7F,CAAC;AAEF,0FAA0F;AAC1F,MAAM,CAAC,MAAM,iBAAiB,GAA+B,CAAC,EAAE,OAAO,EAAE,CAAC,EAAE,UAAU,EAAE,cAAc,EAAE,CAAC,CAAC;AAE1G,MAAM,CAAC,MAAM,6BAA6B,GAAG,iBAAiB,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,EAAE,OAAO,IAAI,CAAC,CAAC;AAIpF,sDAAsD;AACtD,MAAM,CAAC,KAAK,UAAU,qBAAqB,CAC1C,QAAwB,EACxB,UAAU,GAA+B,iBAAiB,EAC1C;IAChB,KAAK,IAAI,KAAK,GAAG,CAAC,EAAE,KAAK,GAAG,UAAU,CAAC,MAAM,EAAE,KAAK,EAAE,EAAE,CAAC;QACxD,IAAI,UAAU,CAAC,KAAK,CAAC,EAAE,OAAO,KAAK,KAAK,GAAG,CAAC,EAAE,CAAC;YAC9C,MAAM,IAAI,KAAK,CAAC,uEAAuE,CAAC,CAAC;QAC1F,CAAC;IACF,CAAC;IAED,MAAM,QAAQ,CAAC,WAAW,CAAC,GAAG,EAAE,CAAC;QAChC,QAAQ,CAAC,IAAI,CAAC;;;WAGL,CAAC,CAAC;QACX,QAAQ,CAAC,OAAO,CAAC,yEAAyE,CAAC,CAAC,GAAG,EAAE,CAAC;QAClG,MAAM,GAAG,GAAG,QAAQ,CAAC,OAAO,CAAC,wDAAwD,CAAC,CAAC,GAAG,EAAa,CAAC;QACxG,IAAI,GAAG,KAAK,SAAS;YAAE,MAAM,IAAI,KAAK,CAAC,2CAA2C,CAAC,CAAC;QACpF,MAAM,cAAc,GAAG,UAAU,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,EAAE,OAAO,IAAI,CAAC,CAAC;QACvD,IAAI,GAAG,CAAC,OAAO,GAAG,cAAc,EAAE,CAAC;YAClC,MAAM,IAAI,KAAK,CACd,iCAAiC,GAAG,CAAC,OAAO,oCAAoC,cAAc,EAAE,CAChG,CAAC;QACH,CAAC;QACD,KAAK,MAAM,SAAS,IAAI,UAAU,EAAE,CAAC;YACpC,IAAI,SAAS,CAAC,OAAO,IAAI,GAAG,CAAC,OAAO;gBAAE,SAAS;YAC/C,KAAK,MAAM,SAAS,IAAI,SAAS,CAAC,UAAU;gBAAE,QAAQ,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC;YACvE,QAAQ,CAAC,OAAO,CAAC,2DAA2D,CAAC,CAAC,GAAG,CAAC,SAAS,CAAC,OAAO,CAAC,CAAC;QACtG,CAAC;IAAA,CACD,CAAC,CAAC;AAAA,CACH","sourcesContent":["import type { SqliteDatabase } from \"./database.ts\";\n\nexport type SqliteMigration = {\n\treadonly version: number;\n\treadonly statements: readonly string[];\n};\n\n// next_id is TEXT because node:sqlite rejects INTEGER results outside JavaScript's safe integer range.\nconst INITIAL_SCHEMA: readonly string[] = [\n\t`CREATE TABLE durable_metadata (\n\t\tsingleton INTEGER PRIMARY KEY CHECK (singleton = 1),\n\t\tnext_id TEXT NOT NULL,\n\t\tnext_seq INTEGER NOT NULL\n\t) STRICT`,\n\t`INSERT INTO durable_metadata (singleton, next_id, next_seq) VALUES (1, '2', 1)`,\n\t`CREATE TABLE record_ids (\n\t\tid INTEGER PRIMARY KEY,\n\t\trecord_type TEXT NOT NULL CHECK (record_type IN ('conversation', 'entry', 'task', 'submission', 'document'))\n\t) STRICT`,\n\t`CREATE TABLE conversations (\n\t\tid INTEGER PRIMARY KEY,\n\t\trecord TEXT NOT NULL CHECK (json_valid(record))\n\t) STRICT`,\n\t`CREATE TABLE entries (\n\t\tid INTEGER PRIMARY KEY,\n\t\tconversation_id INTEGER NOT NULL,\n\t\thead INTEGER,\n\t\tcommit_seq INTEGER NOT NULL,\n\t\trecord TEXT NOT NULL CHECK (json_valid(record))\n\t) STRICT`,\n\t\"CREATE INDEX entries_by_conversation ON entries (conversation_id, id DESC)\",\n\t\"CREATE INDEX entry_heads_by_conversation ON entries (conversation_id, id DESC) WHERE head IS NOT NULL\",\n\t`CREATE TABLE tasks (\n\t\tid INTEGER PRIMARY KEY,\n\t\tconversation_id INTEGER NOT NULL,\n\t\tkind TEXT NOT NULL,\n\t\tstatus TEXT NOT NULL CHECK (status IN ('pending', 'running', 'terminal')),\n\t\tabort_requested INTEGER NOT NULL CHECK (abort_requested IN (0, 1)),\n\t\tbackground INTEGER NOT NULL CHECK (background IN (0, 1)),\n\t\trecord TEXT NOT NULL CHECK (json_valid(record))\n\t) STRICT`,\n\t\"CREATE INDEX tasks_by_status ON tasks (status, id)\",\n\t\"CREATE INDEX tasks_by_conversation ON tasks (conversation_id, id)\",\n\t\"CREATE INDEX tasks_by_kind ON tasks (kind, id)\",\n\t\"CREATE INDEX tasks_by_abort_requested ON tasks (abort_requested, id)\",\n\t\"CREATE INDEX tasks_by_background ON tasks (background, id)\",\n\t`CREATE TABLE submissions (\n\t\tid INTEGER PRIMARY KEY,\n\t\tconversation_id INTEGER NOT NULL,\n\t\trequest_id TEXT,\n\t\trecord TEXT NOT NULL CHECK (json_valid(record))\n\t) STRICT`,\n\t\"CREATE INDEX submissions_by_request ON submissions (conversation_id, request_id)\",\n\t`CREATE TABLE documents (\n\t\tid INTEGER PRIMARY KEY,\n\t\tkind TEXT NOT NULL,\n\t\tfamily INTEGER NOT NULL CHECK (family IN (0, 1)),\n\t\tkey_value TEXT NOT NULL,\n\t\tscope_kind TEXT NOT NULL CHECK (scope_kind IN ('session', 'conversation', 'task')),\n\t\towner_id INTEGER NOT NULL,\n\t\tcreated_at INTEGER NOT NULL,\n\t\tretired_at INTEGER,\n\t\trecord TEXT NOT NULL CHECK (json_valid(record))\n\t) STRICT`,\n\t`CREATE INDEX documents_by_address\n\t\tON documents (kind, scope_kind, owner_id, family, key_value, created_at DESC, retired_at)`,\n\t\"CREATE INDEX documents_by_scope ON documents (scope_kind, owner_id, id)\",\n\t\"CREATE INDEX documents_by_scope_kind ON documents (scope_kind, owner_id, kind, id)\",\n\t`CREATE TABLE document_revisions (\n\t\tdocument_id INTEGER NOT NULL,\n\t\tseq INTEGER NOT NULL,\n\t\tkind TEXT NOT NULL CHECK (kind IN ('base', 'delta')),\n\t\tversion INTEGER NOT NULL,\n\t\tcontent TEXT NOT NULL CHECK (json_valid(content)),\n\t\tPRIMARY KEY (document_id, seq)\n\t) STRICT`,\n\t\"CREATE INDEX document_revisions_by_kind ON document_revisions (document_id, kind, seq DESC)\",\n];\n\n/** Immutable, ordered schema history. Append new migrations; never edit released ones. */\nexport const SQLITE_MIGRATIONS: readonly SqliteMigration[] = [{ version: 1, statements: INITIAL_SCHEMA }];\n\nexport const CURRENT_SQLITE_SCHEMA_VERSION = SQLITE_MIGRATIONS.at(-1)?.version ?? 0;\n\ntype SchemaRow = { readonly version: number };\n\n/** Apply all pending schema migrations atomically. */\nexport async function applySqliteMigrations(\n\tdatabase: SqliteDatabase,\n\tmigrations: readonly SqliteMigration[] = SQLITE_MIGRATIONS,\n): Promise<void> {\n\tfor (let index = 0; index < migrations.length; index++) {\n\t\tif (migrations[index]?.version !== index + 1) {\n\t\t\tthrow new Error(\"Durable SQLite migrations must have contiguous versions starting at 1\");\n\t\t}\n\t}\n\n\tawait database.transaction(() => {\n\t\tdatabase.exec(`CREATE TABLE IF NOT EXISTS durable_schema (\n\t\t\tsingleton INTEGER PRIMARY KEY CHECK (singleton = 1),\n\t\t\tversion INTEGER NOT NULL CHECK (version >= 0)\n\t\t) STRICT`);\n\t\tdatabase.prepare(\"INSERT OR IGNORE INTO durable_schema (singleton, version) VALUES (1, 0)\").run();\n\t\tconst row = database.prepare(\"SELECT version FROM durable_schema WHERE singleton = 1\").get<SchemaRow>();\n\t\tif (row === undefined) throw new Error(\"Durable SQLite schema metadata is missing\");\n\t\tconst currentVersion = migrations.at(-1)?.version ?? 0;\n\t\tif (row.version > currentVersion) {\n\t\t\tthrow new Error(\n\t\t\t\t`Durable SQLite schema version ${row.version} is newer than supported version ${currentVersion}`,\n\t\t\t);\n\t\t}\n\t\tfor (const migration of migrations) {\n\t\t\tif (migration.version <= row.version) continue;\n\t\t\tfor (const statement of migration.statements) database.exec(statement);\n\t\t\tdatabase.prepare(\"UPDATE durable_schema SET version = ? WHERE singleton = 1\").run(migration.version);\n\t\t}\n\t});\n}\n"]}
@@ -0,0 +1,25 @@
1
+ import { DatabaseSync } from "node:sqlite";
2
+ import type { SqliteDatabase, SqliteStatement } from "./database.ts";
3
+ import { SqliteStorage } from "./storage.ts";
4
+ /** Node SQLite connection settings for a durable storage file. */
5
+ export type NodeSqliteStorageOptions = {
6
+ /** SQLite WAL auto-checkpoint threshold. SQLite and this adapter default to 1,000 pages; 0 disables it. */
7
+ readonly walAutoCheckpointPages?: number;
8
+ /** Time SQLite waits for a competing file lock. SQLite defaults to 0; this adapter defaults to 5,000 ms. */
9
+ readonly busyTimeoutMs?: number;
10
+ };
11
+ /** `SqliteDatabase` adapter backed by Node's built-in `node:sqlite`. */
12
+ export declare class NodeSqliteDatabase implements SqliteDatabase {
13
+ private readonly database;
14
+ private closed;
15
+ constructor(database: DatabaseSync);
16
+ exec(sql: string): void;
17
+ prepare(sql: string): SqliteStatement;
18
+ transaction<T>(callback: () => T): T;
19
+ close(): void;
20
+ }
21
+ /** Open and configure a Node-backed SQLite database facade. */
22
+ export declare function openNodeSqliteDatabase(path: string, options?: NodeSqliteStorageOptions): Promise<NodeSqliteDatabase>;
23
+ /** Open or create file-backed durable storage using Node's built-in SQLite. */
24
+ export declare function openNodeSqliteStorage(path: string, options?: NodeSqliteStorageOptions): Promise<SqliteStorage>;
25
+ //# sourceMappingURL=node.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"node.d.ts","sourceRoot":"","sources":["../../../src/storage/sqlite/node.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAC3C,OAAO,KAAK,EAAE,cAAc,EAAE,eAAe,EAAe,MAAM,eAAe,CAAC;AAClF,OAAO,EAAE,aAAa,EAAE,MAAM,cAAc,CAAC;AAE7C,kEAAkE;AAClE,MAAM,MAAM,wBAAwB,GAAG;IACtC,2GAA2G;IAC3G,QAAQ,CAAC,sBAAsB,CAAC,EAAE,MAAM,CAAC;IACzC,4GAA4G;IAC5G,QAAQ,CAAC,aAAa,CAAC,EAAE,MAAM,CAAC;CAChC,CAAC;AAyBF,wEAAwE;AACxE,qBAAa,kBAAmB,YAAW,cAAc;IACxD,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAe;IACxC,OAAO,CAAC,MAAM,CAAS;IAEvB,YAAY,QAAQ,EAAE,YAAY,EAEjC;IAED,IAAI,CAAC,GAAG,EAAE,MAAM,GAAG,IAAI,CAEtB;IAED,OAAO,CAAC,GAAG,EAAE,MAAM,GAAG,eAAe,CAEpC;IAED,WAAW,CAAC,CAAC,EAAE,QAAQ,EAAE,MAAM,CAAC,GAAG,CAAC,CAqBnC;IAED,KAAK,IAAI,IAAI,CAQZ;CACD;AAED,+DAA+D;AAC/D,wBAAsB,sBAAsB,CAC3C,IAAI,EAAE,MAAM,EACZ,OAAO,GAAE,wBAA6B,GACpC,OAAO,CAAC,kBAAkB,CAAC,CAmB7B;AAED,+EAA+E;AAC/E,wBAAsB,qBAAqB,CAC1C,IAAI,EAAE,MAAM,EACZ,OAAO,GAAE,wBAA6B,GACpC,OAAO,CAAC,aAAa,CAAC,CAExB","sourcesContent":["import { mkdir } from \"node:fs/promises\";\nimport { dirname } from \"node:path\";\nimport type { SQLInputValue, StatementSync } from \"node:sqlite\";\nimport { DatabaseSync } from \"node:sqlite\";\nimport type { SqliteDatabase, SqliteStatement, SqliteValue } from \"./database.ts\";\nimport { SqliteStorage } from \"./storage.ts\";\n\n/** Node SQLite connection settings for a durable storage file. */\nexport type NodeSqliteStorageOptions = {\n\t/** SQLite WAL auto-checkpoint threshold. SQLite and this adapter default to 1,000 pages; 0 disables it. */\n\treadonly walAutoCheckpointPages?: number;\n\t/** Time SQLite waits for a competing file lock. SQLite defaults to 0; this adapter defaults to 5,000 ms. */\n\treadonly busyTimeoutMs?: number;\n};\n\nconst DEFAULT_WAL_AUTO_CHECKPOINT_PAGES = 1_000;\nconst DEFAULT_BUSY_TIMEOUT_MS = 5_000;\n\nclass NodeSqliteStatement implements SqliteStatement {\n\tprivate readonly statement: StatementSync;\n\n\tconstructor(statement: StatementSync) {\n\t\tthis.statement = statement;\n\t}\n\n\trun(...params: SqliteValue[]): void {\n\t\tthis.statement.run(...(params as SQLInputValue[]));\n\t}\n\n\tget<T extends object>(...params: SqliteValue[]): T | undefined {\n\t\treturn this.statement.get(...(params as SQLInputValue[])) as T | undefined;\n\t}\n\n\tall<T extends object>(...params: SqliteValue[]): T[] {\n\t\treturn this.statement.all(...(params as SQLInputValue[])) as T[];\n\t}\n}\n\n/** `SqliteDatabase` adapter backed by Node's built-in `node:sqlite`. */\nexport class NodeSqliteDatabase implements SqliteDatabase {\n\tprivate readonly database: DatabaseSync;\n\tprivate closed = false;\n\n\tconstructor(database: DatabaseSync) {\n\t\tthis.database = database;\n\t}\n\n\texec(sql: string): void {\n\t\tthis.database.exec(sql);\n\t}\n\n\tprepare(sql: string): SqliteStatement {\n\t\treturn new NodeSqliteStatement(this.database.prepare(sql));\n\t}\n\n\ttransaction<T>(callback: () => T): T {\n\t\tthis.database.exec(\"BEGIN IMMEDIATE\");\n\t\ttry {\n\t\t\tconst result = callback();\n\t\t\tif (\n\t\t\t\tresult !== null &&\n\t\t\t\t(typeof result === \"object\" || typeof result === \"function\") &&\n\t\t\t\ttypeof Reflect.get(result, \"then\") === \"function\"\n\t\t\t) {\n\t\t\t\tthrow new TypeError(\"SQLite transaction callbacks must be synchronous\");\n\t\t\t}\n\t\t\tthis.database.exec(\"COMMIT\");\n\t\t\treturn result;\n\t\t} catch (error) {\n\t\t\ttry {\n\t\t\t\tthis.database.exec(\"ROLLBACK\");\n\t\t\t} catch {\n\t\t\t\t// Preserve the error that caused the transaction to fail.\n\t\t\t}\n\t\t\tthrow error;\n\t\t}\n\t}\n\n\tclose(): void {\n\t\tif (this.closed) return;\n\t\tthis.closed = true;\n\t\ttry {\n\t\t\tthis.database.exec(\"PRAGMA wal_checkpoint(TRUNCATE)\");\n\t\t} finally {\n\t\t\tthis.database.close();\n\t\t}\n\t}\n}\n\n/** Open and configure a Node-backed SQLite database facade. */\nexport async function openNodeSqliteDatabase(\n\tpath: string,\n\toptions: NodeSqliteStorageOptions = {},\n): Promise<NodeSqliteDatabase> {\n\tconst checkpointPages = options.walAutoCheckpointPages ?? DEFAULT_WAL_AUTO_CHECKPOINT_PAGES;\n\tconst timeout = options.busyTimeoutMs ?? DEFAULT_BUSY_TIMEOUT_MS;\n\tif (path !== \":memory:\") await mkdir(dirname(path), { recursive: true });\n\tconst database = new DatabaseSync(path, { timeout });\n\tconst adapter = new NodeSqliteDatabase(database);\n\ttry {\n\t\tadapter.exec(\"PRAGMA journal_mode = WAL\");\n\t\tadapter.exec(\"PRAGMA synchronous = NORMAL\");\n\t\tadapter.exec(`PRAGMA wal_autocheckpoint = ${checkpointPages}`);\n\t\treturn adapter;\n\t} catch (error) {\n\t\ttry {\n\t\t\tadapter.close();\n\t\t} catch {\n\t\t\t// Preserve the configuration failure.\n\t\t}\n\t\tthrow error;\n\t}\n}\n\n/** Open or create file-backed durable storage using Node's built-in SQLite. */\nexport async function openNodeSqliteStorage(\n\tpath: string,\n\toptions: NodeSqliteStorageOptions = {},\n): Promise<SqliteStorage> {\n\treturn SqliteStorage.open(await openNodeSqliteDatabase(path, options));\n}\n"]}
@@ -0,0 +1,97 @@
1
+ import { mkdir } from "node:fs/promises";
2
+ import { dirname } from "node:path";
3
+ import { DatabaseSync } from "node:sqlite";
4
+ import { SqliteStorage } from "./storage.js";
5
+ const DEFAULT_WAL_AUTO_CHECKPOINT_PAGES = 1_000;
6
+ const DEFAULT_BUSY_TIMEOUT_MS = 5_000;
7
+ class NodeSqliteStatement {
8
+ statement;
9
+ constructor(statement) {
10
+ this.statement = statement;
11
+ }
12
+ run(...params) {
13
+ this.statement.run(...params);
14
+ }
15
+ get(...params) {
16
+ return this.statement.get(...params);
17
+ }
18
+ all(...params) {
19
+ return this.statement.all(...params);
20
+ }
21
+ }
22
+ /** `SqliteDatabase` adapter backed by Node's built-in `node:sqlite`. */
23
+ export class NodeSqliteDatabase {
24
+ database;
25
+ closed = false;
26
+ constructor(database) {
27
+ this.database = database;
28
+ }
29
+ exec(sql) {
30
+ this.database.exec(sql);
31
+ }
32
+ prepare(sql) {
33
+ return new NodeSqliteStatement(this.database.prepare(sql));
34
+ }
35
+ transaction(callback) {
36
+ this.database.exec("BEGIN IMMEDIATE");
37
+ try {
38
+ const result = callback();
39
+ if (result !== null &&
40
+ (typeof result === "object" || typeof result === "function") &&
41
+ typeof Reflect.get(result, "then") === "function") {
42
+ throw new TypeError("SQLite transaction callbacks must be synchronous");
43
+ }
44
+ this.database.exec("COMMIT");
45
+ return result;
46
+ }
47
+ catch (error) {
48
+ try {
49
+ this.database.exec("ROLLBACK");
50
+ }
51
+ catch {
52
+ // Preserve the error that caused the transaction to fail.
53
+ }
54
+ throw error;
55
+ }
56
+ }
57
+ close() {
58
+ if (this.closed)
59
+ return;
60
+ this.closed = true;
61
+ try {
62
+ this.database.exec("PRAGMA wal_checkpoint(TRUNCATE)");
63
+ }
64
+ finally {
65
+ this.database.close();
66
+ }
67
+ }
68
+ }
69
+ /** Open and configure a Node-backed SQLite database facade. */
70
+ export async function openNodeSqliteDatabase(path, options = {}) {
71
+ const checkpointPages = options.walAutoCheckpointPages ?? DEFAULT_WAL_AUTO_CHECKPOINT_PAGES;
72
+ const timeout = options.busyTimeoutMs ?? DEFAULT_BUSY_TIMEOUT_MS;
73
+ if (path !== ":memory:")
74
+ await mkdir(dirname(path), { recursive: true });
75
+ const database = new DatabaseSync(path, { timeout });
76
+ const adapter = new NodeSqliteDatabase(database);
77
+ try {
78
+ adapter.exec("PRAGMA journal_mode = WAL");
79
+ adapter.exec("PRAGMA synchronous = NORMAL");
80
+ adapter.exec(`PRAGMA wal_autocheckpoint = ${checkpointPages}`);
81
+ return adapter;
82
+ }
83
+ catch (error) {
84
+ try {
85
+ adapter.close();
86
+ }
87
+ catch {
88
+ // Preserve the configuration failure.
89
+ }
90
+ throw error;
91
+ }
92
+ }
93
+ /** Open or create file-backed durable storage using Node's built-in SQLite. */
94
+ export async function openNodeSqliteStorage(path, options = {}) {
95
+ return SqliteStorage.open(await openNodeSqliteDatabase(path, options));
96
+ }
97
+ //# sourceMappingURL=node.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"node.js","sourceRoot":"","sources":["../../../src/storage/sqlite/node.ts"],"names":[],"mappings":"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","sourcesContent":["import { mkdir } from \"node:fs/promises\";\nimport { dirname } from \"node:path\";\nimport type { SQLInputValue, StatementSync } from \"node:sqlite\";\nimport { DatabaseSync } from \"node:sqlite\";\nimport type { SqliteDatabase, SqliteStatement, SqliteValue } from \"./database.ts\";\nimport { SqliteStorage } from \"./storage.ts\";\n\n/** Node SQLite connection settings for a durable storage file. */\nexport type NodeSqliteStorageOptions = {\n\t/** SQLite WAL auto-checkpoint threshold. SQLite and this adapter default to 1,000 pages; 0 disables it. */\n\treadonly walAutoCheckpointPages?: number;\n\t/** Time SQLite waits for a competing file lock. SQLite defaults to 0; this adapter defaults to 5,000 ms. */\n\treadonly busyTimeoutMs?: number;\n};\n\nconst DEFAULT_WAL_AUTO_CHECKPOINT_PAGES = 1_000;\nconst DEFAULT_BUSY_TIMEOUT_MS = 5_000;\n\nclass NodeSqliteStatement implements SqliteStatement {\n\tprivate readonly statement: StatementSync;\n\n\tconstructor(statement: StatementSync) {\n\t\tthis.statement = statement;\n\t}\n\n\trun(...params: SqliteValue[]): void {\n\t\tthis.statement.run(...(params as SQLInputValue[]));\n\t}\n\n\tget<T extends object>(...params: SqliteValue[]): T | undefined {\n\t\treturn this.statement.get(...(params as SQLInputValue[])) as T | undefined;\n\t}\n\n\tall<T extends object>(...params: SqliteValue[]): T[] {\n\t\treturn this.statement.all(...(params as SQLInputValue[])) as T[];\n\t}\n}\n\n/** `SqliteDatabase` adapter backed by Node's built-in `node:sqlite`. */\nexport class NodeSqliteDatabase implements SqliteDatabase {\n\tprivate readonly database: DatabaseSync;\n\tprivate closed = false;\n\n\tconstructor(database: DatabaseSync) {\n\t\tthis.database = database;\n\t}\n\n\texec(sql: string): void {\n\t\tthis.database.exec(sql);\n\t}\n\n\tprepare(sql: string): SqliteStatement {\n\t\treturn new NodeSqliteStatement(this.database.prepare(sql));\n\t}\n\n\ttransaction<T>(callback: () => T): T {\n\t\tthis.database.exec(\"BEGIN IMMEDIATE\");\n\t\ttry {\n\t\t\tconst result = callback();\n\t\t\tif (\n\t\t\t\tresult !== null &&\n\t\t\t\t(typeof result === \"object\" || typeof result === \"function\") &&\n\t\t\t\ttypeof Reflect.get(result, \"then\") === \"function\"\n\t\t\t) {\n\t\t\t\tthrow new TypeError(\"SQLite transaction callbacks must be synchronous\");\n\t\t\t}\n\t\t\tthis.database.exec(\"COMMIT\");\n\t\t\treturn result;\n\t\t} catch (error) {\n\t\t\ttry {\n\t\t\t\tthis.database.exec(\"ROLLBACK\");\n\t\t\t} catch {\n\t\t\t\t// Preserve the error that caused the transaction to fail.\n\t\t\t}\n\t\t\tthrow error;\n\t\t}\n\t}\n\n\tclose(): void {\n\t\tif (this.closed) return;\n\t\tthis.closed = true;\n\t\ttry {\n\t\t\tthis.database.exec(\"PRAGMA wal_checkpoint(TRUNCATE)\");\n\t\t} finally {\n\t\t\tthis.database.close();\n\t\t}\n\t}\n}\n\n/** Open and configure a Node-backed SQLite database facade. */\nexport async function openNodeSqliteDatabase(\n\tpath: string,\n\toptions: NodeSqliteStorageOptions = {},\n): Promise<NodeSqliteDatabase> {\n\tconst checkpointPages = options.walAutoCheckpointPages ?? DEFAULT_WAL_AUTO_CHECKPOINT_PAGES;\n\tconst timeout = options.busyTimeoutMs ?? DEFAULT_BUSY_TIMEOUT_MS;\n\tif (path !== \":memory:\") await mkdir(dirname(path), { recursive: true });\n\tconst database = new DatabaseSync(path, { timeout });\n\tconst adapter = new NodeSqliteDatabase(database);\n\ttry {\n\t\tadapter.exec(\"PRAGMA journal_mode = WAL\");\n\t\tadapter.exec(\"PRAGMA synchronous = NORMAL\");\n\t\tadapter.exec(`PRAGMA wal_autocheckpoint = ${checkpointPages}`);\n\t\treturn adapter;\n\t} catch (error) {\n\t\ttry {\n\t\t\tadapter.close();\n\t\t} catch {\n\t\t\t// Preserve the configuration failure.\n\t\t}\n\t\tthrow error;\n\t}\n}\n\n/** Open or create file-backed durable storage using Node's built-in SQLite. */\nexport async function openNodeSqliteStorage(\n\tpath: string,\n\toptions: NodeSqliteStorageOptions = {},\n): Promise<SqliteStorage> {\n\treturn SqliteStorage.open(await openNodeSqliteDatabase(path, options));\n}\n"]}
@@ -0,0 +1,45 @@
1
+ import type { Context, JsonValue } from "@earendil-works/chord";
2
+ import type { ConversationRecord, Cursor, DocumentAddress, DocumentPoint, DocumentQuery, DocumentRecord, EntryQuery, EntryRecord, Id, Page, Seq, Storage, StorageWrite, StoredDocument, SubmissionRecord, TaskQuery, TaskRecord } from "../../types.ts";
3
+ import type { SqliteDatabase } from "./database.ts";
4
+ type StoredTask = TaskRecord<JsonValue, JsonValue, JsonValue>;
5
+ /** Portable SQLite implementation of the Pico storage contract. */
6
+ export declare class SqliteStorage implements Storage {
7
+ private readonly db;
8
+ private nextId;
9
+ private closed;
10
+ private constructor();
11
+ /** Initialize storage over an owned SQLite database facade. */
12
+ static open(db: SqliteDatabase): Promise<SqliteStorage>;
13
+ commit(writes: readonly StorageWrite[], _context: Context): Promise<Seq>;
14
+ mintId(): Promise<Id>;
15
+ conversation(id: Id, _context: Context): Promise<ConversationRecord | undefined>;
16
+ scanConversations(cursor: Cursor | undefined, limit: number, _context: Context): Promise<Page<ConversationRecord, Cursor>>;
17
+ entry(id: Id, _context: Context): Promise<{
18
+ readonly entry: EntryRecord;
19
+ readonly commitSeq: Seq;
20
+ } | undefined>;
21
+ findLatestHeadMarker(conversationId: Id, atOrBeforeEntryId: Id | undefined, _context: Context): Promise<(EntryRecord & {
22
+ readonly head: Id;
23
+ }) | undefined>;
24
+ scanEntries(query: EntryQuery, cursor: Cursor | undefined, limit: number, _context: Context): Promise<Page<EntryRecord, Cursor>>;
25
+ task(id: Id, _context: Context): Promise<StoredTask | undefined>;
26
+ scanTasks(query: TaskQuery, cursor: Cursor | undefined, limit: number, _context: Context): Promise<Page<StoredTask, Cursor>>;
27
+ submission(id: Id, _context: Context): Promise<SubmissionRecord | undefined>;
28
+ submissionByRequest(conversationId: Id, requestId: string, _context: Context): Promise<SubmissionRecord | undefined>;
29
+ findDocument(address: DocumentAddress, at: DocumentPoint, _context: Context): Promise<DocumentRecord | undefined>;
30
+ document(id: Id, at: DocumentPoint, _context: Context): Promise<StoredDocument | undefined>;
31
+ scanDocuments(query: DocumentQuery, cursor: Cursor | undefined, limit: number, _context: Context): Promise<Page<DocumentRecord, Cursor>>;
32
+ close(_context: Context): Promise<void>;
33
+ private readConversation;
34
+ private candidateNextId;
35
+ private checkGlobalIds;
36
+ private prepareDocumentActions;
37
+ private checkDocumentActions;
38
+ private currentDocumentId;
39
+ private applyTableWrite;
40
+ private claimId;
41
+ private applyDocumentActions;
42
+ private assertOpen;
43
+ }
44
+ export {};
45
+ //# sourceMappingURL=storage.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"storage.d.ts","sourceRoot":"","sources":["../../../src/storage/sqlite/storage.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,SAAS,EAAE,MAAM,uBAAuB,CAAC;AAEhE,OAAO,KAAK,EACX,kBAAkB,EAClB,MAAM,EACN,eAAe,EAGf,aAAa,EACb,aAAa,EACb,cAAc,EACd,UAAU,EACV,WAAW,EACX,EAAE,EAEF,IAAI,EACJ,GAAG,EACH,OAAO,EACP,YAAY,EACZ,cAAc,EACd,gBAAgB,EAChB,SAAS,EACT,UAAU,EACV,MAAM,gBAAgB,CAAC;AACxB,OAAO,KAAK,EAAE,cAAc,EAAgC,MAAM,eAAe,CAAC;AAGlF,KAAK,UAAU,GAAG,UAAU,CAAC,SAAS,EAAE,SAAS,EAAE,SAAS,CAAC,CAAC;AA0H9D,mEAAmE;AACnE,qBAAa,aAAc,YAAW,OAAO;IAC5C,OAAO,CAAC,QAAQ,CAAC,EAAE,CAAiB;IACpC,OAAO,CAAC,MAAM,CAAK;IACnB,OAAO,CAAC,MAAM,CAAS;IAEvB,OAAO,eAGN;IAED,+DAA+D;IAC/D,OAAa,IAAI,CAAC,EAAE,EAAE,cAAc,GAAG,OAAO,CAAC,aAAa,CAAC,CAgB5D;IAEK,MAAM,CAAC,MAAM,EAAE,SAAS,YAAY,EAAE,EAAE,QAAQ,EAAE,OAAO,GAAG,OAAO,CAAC,GAAG,CAAC,CAqB7E;IAEK,MAAM,IAAI,OAAO,CAAC,EAAE,CAAC,CAI1B;IAEK,YAAY,CAAC,EAAE,EAAE,EAAE,EAAE,QAAQ,EAAE,OAAO,GAAG,OAAO,CAAC,kBAAkB,GAAG,SAAS,CAAC,CAIrF;IAEK,iBAAiB,CACtB,MAAM,EAAE,MAAM,GAAG,SAAS,EAC1B,KAAK,EAAE,MAAM,EACb,QAAQ,EAAE,OAAO,GACf,OAAO,CAAC,IAAI,CAAC,kBAAkB,EAAE,MAAM,CAAC,CAAC,CAY3C;IAEK,KAAK,CACV,EAAE,EAAE,EAAE,EACN,QAAQ,EAAE,OAAO,GACf,OAAO,CAAC;QAAE,QAAQ,CAAC,KAAK,EAAE,WAAW,CAAC;QAAC,QAAQ,CAAC,SAAS,EAAE,GAAG,CAAA;KAAE,GAAG,SAAS,CAAC,CAI/E;IAEK,oBAAoB,CACzB,cAAc,EAAE,EAAE,EAClB,iBAAiB,EAAE,EAAE,GAAG,SAAS,EACjC,QAAQ,EAAE,OAAO,GACf,OAAO,CAAC,CAAC,WAAW,GAAG;QAAE,QAAQ,CAAC,IAAI,EAAE,EAAE,CAAA;KAAE,CAAC,GAAG,SAAS,CAAC,CA0B5D;IAEK,WAAW,CAChB,KAAK,EAAE,UAAU,EACjB,MAAM,EAAE,MAAM,GAAG,SAAS,EAC1B,KAAK,EAAE,MAAM,EACb,QAAQ,EAAE,OAAO,GACf,OAAO,CAAC,IAAI,CAAC,WAAW,EAAE,MAAM,CAAC,CAAC,CA+BpC;IAEK,IAAI,CAAC,EAAE,EAAE,EAAE,EAAE,QAAQ,EAAE,OAAO,GAAG,OAAO,CAAC,UAAU,GAAG,SAAS,CAAC,CAIrE;IAEK,SAAS,CACd,KAAK,EAAE,SAAS,EAChB,MAAM,EAAE,MAAM,GAAG,SAAS,EAC1B,KAAK,EAAE,MAAM,EACb,QAAQ,EAAE,OAAO,GACf,OAAO,CAAC,IAAI,CAAC,UAAU,EAAE,MAAM,CAAC,CAAC,CAiCnC;IAEK,UAAU,CAAC,EAAE,EAAE,EAAE,EAAE,QAAQ,EAAE,OAAO,GAAG,OAAO,CAAC,gBAAgB,GAAG,SAAS,CAAC,CAIjF;IAEK,mBAAmB,CACxB,cAAc,EAAE,EAAE,EAClB,SAAS,EAAE,MAAM,EACjB,QAAQ,EAAE,OAAO,GACf,OAAO,CAAC,gBAAgB,GAAG,SAAS,CAAC,CAQvC;IAEK,YAAY,CACjB,OAAO,EAAE,eAAe,EACxB,EAAE,EAAE,aAAa,EACjB,QAAQ,EAAE,OAAO,GACf,OAAO,CAAC,cAAc,GAAG,SAAS,CAAC,CAgBrC;IAEK,QAAQ,CAAC,EAAE,EAAE,EAAE,EAAE,EAAE,EAAE,aAAa,EAAE,QAAQ,EAAE,OAAO,GAAG,OAAO,CAAC,cAAc,GAAG,SAAS,CAAC,CAgChG;IAEK,aAAa,CAClB,KAAK,EAAE,aAAa,EACpB,MAAM,EAAE,MAAM,GAAG,SAAS,EAC1B,KAAK,EAAE,MAAM,EACb,QAAQ,EAAE,OAAO,GACf,OAAO,CAAC,IAAI,CAAC,cAAc,EAAE,MAAM,CAAC,CAAC,CAwBvC;IAEK,KAAK,CAAC,QAAQ,EAAE,OAAO,GAAG,OAAO,CAAC,IAAI,CAAC,CAI5C;IAED,OAAO,CAAC,gBAAgB;IAKxB,OAAO,CAAC,eAAe;IASvB,OAAO,CAAC,cAAc;IAqBtB,OAAO,CAAC,sBAAsB;IAiC9B,OAAO,CAAC,oBAAoB;IAoC5B,OAAO,CAAC,iBAAiB;IAczB,OAAO,CAAC,eAAe;IAoDvB,OAAO,CAAC,OAAO;IAIf,OAAO,CAAC,oBAAoB;IAwD5B,OAAO,CAAC,UAAU;CAGlB","sourcesContent":["import type { Context, JsonValue } from \"@earendil-works/chord\";\nimport { applyImmutable, type Op } from \"@earendil-works/chord/delta\";\nimport type {\n\tConversationRecord,\n\tCursor,\n\tDocumentAddress,\n\tDocumentContent,\n\tDocumentCreate,\n\tDocumentPoint,\n\tDocumentQuery,\n\tDocumentRecord,\n\tEntryQuery,\n\tEntryRecord,\n\tId,\n\tJsonObject,\n\tPage,\n\tSeq,\n\tStorage,\n\tStorageWrite,\n\tStoredDocument,\n\tSubmissionRecord,\n\tTaskQuery,\n\tTaskRecord,\n} from \"../../types.ts\";\nimport type { SqliteDatabase, SqliteStatement, SqliteValue } from \"./database.ts\";\nimport { applySqliteMigrations } from \"./migrations.ts\";\n\ntype StoredTask = TaskRecord<JsonValue, JsonValue, JsonValue>;\ntype TableName = \"conversation\" | \"entry\" | \"task\" | \"submission\" | \"document\";\ntype RecordIdRow = { readonly record_type: TableName };\ntype JsonRow = { readonly record: string };\ntype EntryJsonRow = { readonly record: string; readonly commit_seq: number };\ntype RevisionRow = {\n\treadonly seq: number;\n\treadonly kind: DocumentContent[\"kind\"];\n\treadonly version: number;\n\treadonly content: string;\n};\ntype IdRow = { readonly id: number };\ntype MetadataRow = { readonly next_id: string; readonly next_seq: number };\ntype DocumentAction = {\n\tcreate?: DocumentCreate;\n\tcontent?: DocumentContent;\n\tretire: boolean;\n};\ntype ScopeColumns = {\n\treadonly scopeKind: DocumentRecord[\"scope\"][\"kind\"];\n\treadonly ownerId: number;\n};\n\nconst parseJson = <T>(value: string): T => JSON.parse(value) as T;\nconst encodeJson = (value: unknown): string => JSON.stringify(value) as string;\n// Some SQLite bindings replace lone UTF-16 surrogates. JSON encoding keeps indexed identities lossless.\nconst encodeIndexedString = (value: string): string => JSON.stringify(value);\n\nconst getRow = <T extends object>(statement: SqliteStatement, ...params: SqliteValue[]): T | undefined =>\n\tstatement.get<T>(...params);\n\nconst allRows = <T extends object>(statement: SqliteStatement, ...params: SqliteValue[]): T[] =>\n\tstatement.all<T>(...params);\n\nconst cursorId = (cursor: Cursor | undefined): Id | undefined => cursor?.after as Id | undefined;\n\nconst page = <T extends { readonly id: Id }>(values: readonly T[], limit: number): Page<T, Cursor> => {\n\tconst items = values.slice(0, limit);\n\tif (values.length <= limit) return { items };\n\treturn { items, next: { after: items.at(-1)!.id } };\n};\n\nconst scopeColumns = (scope: DocumentRecord[\"scope\"]): ScopeColumns => {\n\tswitch (scope.kind) {\n\t\tcase \"session\":\n\t\t\treturn { scopeKind: \"session\", ownerId: 0 };\n\t\tcase \"conversation\":\n\t\t\treturn { scopeKind: \"conversation\", ownerId: scope.conversationId };\n\t\tcase \"task\":\n\t\t\treturn { scopeKind: \"task\", ownerId: scope.taskId };\n\t}\n};\n\nconst addressParts = (address: DocumentAddress | DocumentCreate | DocumentRecord) => {\n\tconst scope = scopeColumns(address.scope);\n\treturn {\n\t\tkind: encodeIndexedString(address.kind),\n\t\t...scope,\n\t\tfamily: address.key === undefined ? 0 : 1,\n\t\tkeyValue: encodeIndexedString(address.key ?? \"\"),\n\t};\n};\n\nconst addressKey = (address: DocumentAddress | DocumentCreate | DocumentRecord): string => {\n\tconst parts = addressParts(address);\n\treturn JSON.stringify([parts.kind, parts.scopeKind, parts.ownerId, parts.family, parts.keyValue]);\n};\n\nconst isAliveAt = (record: DocumentRecord, at: DocumentPoint): boolean => {\n\tif (at === \"current\") return record.retiredAt === undefined;\n\treturn record.createdAt <= at && (record.retiredAt === undefined || at < record.retiredAt);\n};\n\nconst isCurrentOnly = (record: DocumentRecord): boolean =>\n\trecord.scope.kind !== \"conversation\" || record.history === \"latest\";\n\nconst writeId = (write: StorageWrite): Id | undefined => {\n\tswitch (write.type) {\n\t\tcase \"conversation\":\n\t\tcase \"entry\":\n\t\tcase \"task\":\n\t\tcase \"submission\":\n\t\t\treturn write.value.id;\n\t\tcase \"document.create\":\n\t\t\treturn write.record.id;\n\t\tcase \"document.change\":\n\t\tcase \"document.retire\":\n\t\t\treturn undefined;\n\t}\n};\n\nclass StatementCachingDatabase implements SqliteDatabase {\n\tprivate readonly database: SqliteDatabase;\n\tprivate readonly statements = new Map<string, SqliteStatement>();\n\n\tconstructor(database: SqliteDatabase) {\n\t\tthis.database = database;\n\t}\n\n\texec(sql: string): void {\n\t\tthis.database.exec(sql);\n\t}\n\n\tprepare(sql: string): SqliteStatement {\n\t\tlet statement = this.statements.get(sql);\n\t\tif (statement === undefined) {\n\t\t\tstatement = this.database.prepare(sql);\n\t\t\tthis.statements.set(sql, statement);\n\t\t}\n\t\treturn statement;\n\t}\n\n\ttransaction<T>(callback: () => T): T | Promise<T> {\n\t\treturn this.database.transaction(callback);\n\t}\n\n\tclose(): void | Promise<void> {\n\t\tthis.statements.clear();\n\t\treturn this.database.close();\n\t}\n}\n\n/** Portable SQLite implementation of the Pico storage contract. */\nexport class SqliteStorage implements Storage {\n\tprivate readonly db: SqliteDatabase;\n\tprivate nextId: Id;\n\tprivate closed = false;\n\n\tprivate constructor(db: SqliteDatabase, nextId: Id) {\n\t\tthis.db = new StatementCachingDatabase(db);\n\t\tthis.nextId = nextId;\n\t}\n\n\t/** Initialize storage over an owned SQLite database facade. */\n\tstatic async open(db: SqliteDatabase): Promise<SqliteStorage> {\n\t\ttry {\n\t\t\tawait applySqliteMigrations(db);\n\t\t\tconst metadata = getRow<MetadataRow>(\n\t\t\t\tdb.prepare(\"SELECT next_id, next_seq FROM durable_metadata WHERE singleton = 1\"),\n\t\t\t);\n\t\t\tif (metadata === undefined) throw new Error(\"Durable SQLite metadata is missing\");\n\t\t\treturn new SqliteStorage(db, Number(metadata.next_id));\n\t\t} catch (error) {\n\t\t\ttry {\n\t\t\t\tawait db.close();\n\t\t\t} catch {\n\t\t\t\t// Preserve the initialization failure.\n\t\t\t}\n\t\t\tthrow error;\n\t\t}\n\t}\n\n\tasync commit(writes: readonly StorageWrite[], _context: Context): Promise<Seq> {\n\t\tthis.assertOpen();\n\t\tconst documentActions = this.prepareDocumentActions(writes);\n\t\tconst candidateNextId = this.candidateNextId(writes);\n\t\tconst seq = await this.db.transaction(() => {\n\t\t\tconst metadata = getRow<MetadataRow>(\n\t\t\t\tthis.db.prepare(\"SELECT next_id, next_seq FROM durable_metadata WHERE singleton = 1\"),\n\t\t\t);\n\t\t\tif (metadata === undefined) throw new Error(\"Durable SQLite metadata is missing\");\n\t\t\tconst committedSeq = metadata.next_seq;\n\t\t\tthis.checkGlobalIds(writes);\n\t\t\tthis.checkDocumentActions(documentActions);\n\t\t\tfor (const write of writes) this.applyTableWrite(write, committedSeq);\n\t\t\tthis.applyDocumentActions(documentActions, committedSeq);\n\t\t\tthis.db\n\t\t\t\t.prepare(\"UPDATE durable_metadata SET next_id = ?, next_seq = ? WHERE singleton = 1\")\n\t\t\t\t.run(String(Math.max(Number(metadata.next_id), candidateNextId)), committedSeq + 1);\n\t\t\treturn committedSeq;\n\t\t});\n\t\tthis.nextId = Math.max(this.nextId, candidateNextId);\n\t\treturn seq;\n\t}\n\n\tasync mintId(): Promise<Id> {\n\t\tthis.assertOpen();\n\t\tif (!Number.isSafeInteger(this.nextId)) throw new Error(\"ID space is exhausted\");\n\t\treturn this.nextId++;\n\t}\n\n\tasync conversation(id: Id, _context: Context): Promise<ConversationRecord | undefined> {\n\t\tthis.assertOpen();\n\t\tconst row = getRow<JsonRow>(this.db.prepare(\"SELECT record FROM conversations WHERE id = ?\"), id);\n\t\treturn row === undefined ? undefined : parseJson<ConversationRecord>(row.record);\n\t}\n\n\tasync scanConversations(\n\t\tcursor: Cursor | undefined,\n\t\tlimit: number,\n\t\t_context: Context,\n\t): Promise<Page<ConversationRecord, Cursor>> {\n\t\tthis.assertOpen();\n\t\tconst after = cursorId(cursor) ?? -1;\n\t\tconst rows = allRows<JsonRow>(\n\t\t\tthis.db.prepare(\"SELECT record FROM conversations WHERE id > ? ORDER BY id LIMIT ?\"),\n\t\t\tafter,\n\t\t\tlimit + 1,\n\t\t);\n\t\treturn page(\n\t\t\trows.map((row) => parseJson<ConversationRecord>(row.record)),\n\t\t\tlimit,\n\t\t);\n\t}\n\n\tasync entry(\n\t\tid: Id,\n\t\t_context: Context,\n\t): Promise<{ readonly entry: EntryRecord; readonly commitSeq: Seq } | undefined> {\n\t\tthis.assertOpen();\n\t\tconst row = getRow<EntryJsonRow>(this.db.prepare(\"SELECT record, commit_seq FROM entries WHERE id = ?\"), id);\n\t\treturn row === undefined ? undefined : { entry: parseJson<EntryRecord>(row.record), commitSeq: row.commit_seq };\n\t}\n\n\tasync findLatestHeadMarker(\n\t\tconversationId: Id,\n\t\tatOrBeforeEntryId: Id | undefined,\n\t\t_context: Context,\n\t): Promise<(EntryRecord & { readonly head: Id }) | undefined> {\n\t\tthis.assertOpen();\n\t\tlet conversation = this.readConversation(conversationId);\n\t\tif (conversation === undefined) throw new Error(`Unknown conversation: ${conversationId}`);\n\t\tlet upper = atOrBeforeEntryId;\n\t\twhile (true) {\n\t\t\tconst row =\n\t\t\t\tupper === undefined\n\t\t\t\t\t? getRow<JsonRow>(\n\t\t\t\t\t\t\tthis.db.prepare(\n\t\t\t\t\t\t\t\t\"SELECT record FROM entries WHERE conversation_id = ? AND head IS NOT NULL ORDER BY id DESC LIMIT 1\",\n\t\t\t\t\t\t\t),\n\t\t\t\t\t\t\tconversation.id,\n\t\t\t\t\t\t)\n\t\t\t\t\t: getRow<JsonRow>(\n\t\t\t\t\t\t\tthis.db.prepare(\n\t\t\t\t\t\t\t\t\"SELECT record FROM entries WHERE conversation_id = ? AND head IS NOT NULL AND id <= ? ORDER BY id DESC LIMIT 1\",\n\t\t\t\t\t\t\t),\n\t\t\t\t\t\t\tconversation.id,\n\t\t\t\t\t\t\tupper,\n\t\t\t\t\t\t);\n\t\t\tif (row !== undefined) return parseJson<EntryRecord & { readonly head: Id }>(row.record);\n\t\t\tif (conversation.parent === undefined) return undefined;\n\t\t\tupper = upper === undefined ? conversation.parent.at : Math.min(upper, conversation.parent.at);\n\t\t\tconversation = this.readConversation(conversation.parent.conversationId)!;\n\t\t}\n\t}\n\n\tasync scanEntries(\n\t\tquery: EntryQuery,\n\t\tcursor: Cursor | undefined,\n\t\tlimit: number,\n\t\t_context: Context,\n\t): Promise<Page<EntryRecord, Cursor>> {\n\t\tthis.assertOpen();\n\t\tlet conversation = this.readConversation(query.conversationId);\n\t\tif (conversation === undefined) throw new Error(`Unknown conversation: ${query.conversationId}`);\n\t\tconst after = cursorId(cursor);\n\t\tlet upper = query.maxEntryId;\n\t\tif (after !== undefined) upper = Math.min(upper ?? Number.MAX_SAFE_INTEGER, after - 1);\n\t\tconst values: EntryRecord[] = [];\n\t\twhile (true) {\n\t\t\tconst clauses = [\"conversation_id = ?\"];\n\t\t\tconst params: SqliteValue[] = [conversation.id];\n\t\t\tif (query.minEntryId !== undefined) {\n\t\t\t\tclauses.push(\"id >= ?\");\n\t\t\t\tparams.push(query.minEntryId);\n\t\t\t}\n\t\t\tif (upper !== undefined) {\n\t\t\t\tclauses.push(\"id <= ?\");\n\t\t\t\tparams.push(upper);\n\t\t\t}\n\t\t\tparams.push(limit + 1 - values.length);\n\t\t\tconst rows = allRows<JsonRow>(\n\t\t\t\tthis.db.prepare(`SELECT record FROM entries WHERE ${clauses.join(\" AND \")} ORDER BY id DESC LIMIT ?`),\n\t\t\t\t...params,\n\t\t\t);\n\t\t\tvalues.push(...rows.map((row) => parseJson<EntryRecord>(row.record)));\n\t\t\tif (values.length > limit || conversation.parent === undefined) break;\n\t\t\tupper = upper === undefined ? conversation.parent.at : Math.min(upper, conversation.parent.at);\n\t\t\tif (query.minEntryId !== undefined && upper < query.minEntryId) break;\n\t\t\tconversation = this.readConversation(conversation.parent.conversationId)!;\n\t\t}\n\t\treturn page(values, limit);\n\t}\n\n\tasync task(id: Id, _context: Context): Promise<StoredTask | undefined> {\n\t\tthis.assertOpen();\n\t\tconst row = getRow<JsonRow>(this.db.prepare(\"SELECT record FROM tasks WHERE id = ?\"), id);\n\t\treturn row === undefined ? undefined : parseJson<StoredTask>(row.record);\n\t}\n\n\tasync scanTasks(\n\t\tquery: TaskQuery,\n\t\tcursor: Cursor | undefined,\n\t\tlimit: number,\n\t\t_context: Context,\n\t): Promise<Page<StoredTask, Cursor>> {\n\t\tthis.assertOpen();\n\t\tconst clauses = [\"id > ?\"];\n\t\tconst params: SqliteValue[] = [cursorId(cursor) ?? -1];\n\t\tif (query.conversationId !== undefined) {\n\t\t\tclauses.push(\"conversation_id = ?\");\n\t\t\tparams.push(query.conversationId);\n\t\t}\n\t\tif (query.kind !== undefined) {\n\t\t\tclauses.push(\"kind = ?\");\n\t\t\tparams.push(encodeIndexedString(query.kind));\n\t\t}\n\t\tif (query.status !== undefined) {\n\t\t\tclauses.push(\"status = ?\");\n\t\t\tparams.push(query.status);\n\t\t}\n\t\tif (query.abortRequested !== undefined) {\n\t\t\tclauses.push(\"abort_requested = ?\");\n\t\t\tparams.push(query.abortRequested ? 1 : 0);\n\t\t}\n\t\tif (query.background !== undefined) {\n\t\t\tclauses.push(\"background = ?\");\n\t\t\tparams.push(query.background ? 1 : 0);\n\t\t}\n\t\tparams.push(limit + 1);\n\t\tconst rows = allRows<JsonRow>(\n\t\t\tthis.db.prepare(`SELECT record FROM tasks WHERE ${clauses.join(\" AND \")} ORDER BY id LIMIT ?`),\n\t\t\t...params,\n\t\t);\n\t\treturn page(\n\t\t\trows.map((row) => parseJson<StoredTask>(row.record)),\n\t\t\tlimit,\n\t\t);\n\t}\n\n\tasync submission(id: Id, _context: Context): Promise<SubmissionRecord | undefined> {\n\t\tthis.assertOpen();\n\t\tconst row = getRow<JsonRow>(this.db.prepare(\"SELECT record FROM submissions WHERE id = ?\"), id);\n\t\treturn row === undefined ? undefined : parseJson<SubmissionRecord>(row.record);\n\t}\n\n\tasync submissionByRequest(\n\t\tconversationId: Id,\n\t\trequestId: string,\n\t\t_context: Context,\n\t): Promise<SubmissionRecord | undefined> {\n\t\tthis.assertOpen();\n\t\tconst row = getRow<JsonRow>(\n\t\t\tthis.db.prepare(\"SELECT record FROM submissions WHERE conversation_id = ? AND request_id = ?\"),\n\t\t\tconversationId,\n\t\t\tencodeIndexedString(requestId),\n\t\t);\n\t\treturn row === undefined ? undefined : parseJson<SubmissionRecord>(row.record);\n\t}\n\n\tasync findDocument(\n\t\taddress: DocumentAddress,\n\t\tat: DocumentPoint,\n\t\t_context: Context,\n\t): Promise<DocumentRecord | undefined> {\n\t\tthis.assertOpen();\n\t\tconst parts = addressParts(address);\n\t\tconst statement =\n\t\t\tat === \"current\"\n\t\t\t\t? this.db.prepare(`SELECT record FROM documents\n\t\t\t\t\tWHERE kind = ? AND scope_kind = ? AND owner_id = ? AND family = ? AND key_value = ?\n\t\t\t\t\tAND retired_at IS NULL ORDER BY created_at DESC LIMIT 1`)\n\t\t\t\t: this.db.prepare(`SELECT record FROM documents\n\t\t\t\t\tWHERE kind = ? AND scope_kind = ? AND owner_id = ? AND family = ? AND key_value = ?\n\t\t\t\t\tAND created_at <= ? AND (retired_at IS NULL OR retired_at > ?)\n\t\t\t\t\tORDER BY created_at DESC LIMIT 1`);\n\t\tconst params: SqliteValue[] = [parts.kind, parts.scopeKind, parts.ownerId, parts.family, parts.keyValue];\n\t\tif (at !== \"current\") params.push(at, at);\n\t\tconst row = getRow<JsonRow>(statement, ...params);\n\t\treturn row === undefined ? undefined : parseJson<DocumentRecord>(row.record);\n\t}\n\n\tasync document(id: Id, at: DocumentPoint, _context: Context): Promise<StoredDocument | undefined> {\n\t\tthis.assertOpen();\n\t\tconst row = getRow<JsonRow>(this.db.prepare(\"SELECT record FROM documents WHERE id = ?\"), id);\n\t\tif (row === undefined) return undefined;\n\t\tconst record = parseJson<DocumentRecord>(row.record);\n\t\tif (at !== \"current\" && isCurrentOnly(record)) {\n\t\t\tthrow new Error(`Document ${id} does not retain historical content`);\n\t\t}\n\t\tif (!isAliveAt(record, at)) return undefined;\n\t\tconst upper = at === \"current\" ? Number.MAX_SAFE_INTEGER : at;\n\t\tconst base = getRow<RevisionRow>(\n\t\t\tthis.db.prepare(`SELECT seq, kind, version, content FROM document_revisions\n\t\t\t\tWHERE document_id = ? AND kind = 'base' AND seq <= ? ORDER BY seq DESC LIMIT 1`),\n\t\t\tid,\n\t\t\tupper,\n\t\t);\n\t\tif (base === undefined) throw new Error(`Document ${id} is missing a required base`);\n\t\tlet value = parseJson<JsonObject>(base.content);\n\t\tconst tail = allRows<RevisionRow>(\n\t\t\tthis.db.prepare(`SELECT seq, kind, version, content FROM document_revisions\n\t\t\t\tWHERE document_id = ? AND seq > ? AND seq <= ? ORDER BY seq`),\n\t\t\tid,\n\t\t\tbase.seq,\n\t\t\tupper,\n\t\t);\n\t\tfor (const revision of tail) {\n\t\t\tif (revision.kind !== \"delta\" || revision.version !== base.version) {\n\t\t\t\tthrow new Error(`Document ${id} crosses a stored version boundary without a base`);\n\t\t\t}\n\t\t\tvalue = applyImmutable(value, parseJson<readonly Op[]>(revision.content)) as JsonObject;\n\t\t}\n\t\treturn { record, version: base.version, value };\n\t}\n\n\tasync scanDocuments(\n\t\tquery: DocumentQuery,\n\t\tcursor: Cursor | undefined,\n\t\tlimit: number,\n\t\t_context: Context,\n\t): Promise<Page<DocumentRecord, Cursor>> {\n\t\tthis.assertOpen();\n\t\tconst scope = scopeColumns(query.scope);\n\t\tconst clauses = [\"scope_kind = ?\", \"owner_id = ?\", \"id > ?\"];\n\t\tconst params: SqliteValue[] = [scope.scopeKind, scope.ownerId, cursorId(cursor) ?? -1];\n\t\tif (query.kind !== undefined) {\n\t\t\tclauses.push(\"kind = ?\");\n\t\t\tparams.push(encodeIndexedString(query.kind));\n\t\t}\n\t\tif (query.at === \"current\") {\n\t\t\tclauses.push(\"retired_at IS NULL\");\n\t\t} else {\n\t\t\tclauses.push(\"created_at <= ?\", \"(retired_at IS NULL OR retired_at > ?)\");\n\t\t\tparams.push(query.at, query.at);\n\t\t}\n\t\tparams.push(limit + 1);\n\t\tconst rows = allRows<JsonRow>(\n\t\t\tthis.db.prepare(`SELECT record FROM documents WHERE ${clauses.join(\" AND \")} ORDER BY id LIMIT ?`),\n\t\t\t...params,\n\t\t);\n\t\treturn page(\n\t\t\trows.map((row) => parseJson<DocumentRecord>(row.record)),\n\t\t\tlimit,\n\t\t);\n\t}\n\n\tasync close(_context: Context): Promise<void> {\n\t\tif (this.closed) return;\n\t\tthis.closed = true;\n\t\tawait this.db.close();\n\t}\n\n\tprivate readConversation(id: Id): ConversationRecord | undefined {\n\t\tconst row = getRow<JsonRow>(this.db.prepare(\"SELECT record FROM conversations WHERE id = ?\"), id);\n\t\treturn row === undefined ? undefined : parseJson<ConversationRecord>(row.record);\n\t}\n\n\tprivate candidateNextId(writes: readonly StorageWrite[]): Id {\n\t\tlet nextId = this.nextId;\n\t\tfor (const write of writes) {\n\t\t\tconst id = writeId(write);\n\t\t\tif (id !== undefined) nextId = Math.max(nextId, id + 1);\n\t\t}\n\t\treturn nextId;\n\t}\n\n\tprivate checkGlobalIds(writes: readonly StorageWrite[]): void {\n\t\tconst claimed = new Map<Id, TableName>();\n\t\tconst lookup = this.db.prepare(\"SELECT record_type FROM record_ids WHERE id = ?\");\n\t\tfor (const write of writes) {\n\t\t\tif (write.type === \"document.change\" || write.type === \"document.retire\") continue;\n\t\t\tconst table: TableName = write.type === \"document.create\" ? \"document\" : write.type;\n\t\t\tconst id = write.type === \"document.create\" ? write.record.id : write.value.id;\n\t\t\tconst existing = getRow<RecordIdRow>(lookup, id)?.record_type;\n\t\t\tconst earlier = claimed.get(id);\n\t\t\tif (table === \"conversation\" || table === \"entry\" || table === \"document\") {\n\t\t\t\tif (existing !== undefined) throw new Error(`ID ${id} already belongs to ${existing}`);\n\t\t\t\tif (earlier !== undefined) throw new Error(`ID ${id} is written more than once`);\n\t\t\t} else {\n\t\t\t\tif (existing !== undefined && existing !== table)\n\t\t\t\t\tthrow new Error(`ID ${id} already belongs to ${existing}`);\n\t\t\t\tif (earlier !== undefined && earlier !== table) throw new Error(`ID ${id} is written as two record types`);\n\t\t\t}\n\t\t\tclaimed.set(id, table);\n\t\t}\n\t}\n\n\tprivate prepareDocumentActions(writes: readonly StorageWrite[]): Map<Id, DocumentAction> {\n\t\tconst actions = new Map<Id, DocumentAction>();\n\t\tfor (const write of writes) {\n\t\t\tif (write.type !== \"document.create\" && write.type !== \"document.change\" && write.type !== \"document.retire\") {\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\tconst id = write.type === \"document.create\" ? write.record.id : write.id;\n\t\t\tlet action = actions.get(id);\n\t\t\tif (action === undefined) {\n\t\t\t\taction = { retire: false };\n\t\t\t\tactions.set(id, action);\n\t\t\t}\n\t\t\tswitch (write.type) {\n\t\t\t\tcase \"document.create\":\n\t\t\t\t\tif (action.create !== undefined || action.content !== undefined) {\n\t\t\t\t\t\tthrow new Error(`Document ${id} has more than one content command`);\n\t\t\t\t\t}\n\t\t\t\t\taction.create = write.record;\n\t\t\t\t\taction.content = write.content;\n\t\t\t\t\tbreak;\n\t\t\t\tcase \"document.change\":\n\t\t\t\t\tif (action.content !== undefined) throw new Error(`Document ${id} has more than one content command`);\n\t\t\t\t\taction.content = write.content;\n\t\t\t\t\tbreak;\n\t\t\t\tcase \"document.retire\":\n\t\t\t\t\tif (action.retire) throw new Error(`Document ${id} is retired more than once`);\n\t\t\t\t\taction.retire = true;\n\t\t\t\t\tbreak;\n\t\t\t}\n\t\t}\n\t\treturn actions;\n\t}\n\n\tprivate checkDocumentActions(actions: ReadonlyMap<Id, DocumentAction>): void {\n\t\tconst liveCounts = new Map<string, number>();\n\t\tfor (const [id, action] of actions) {\n\t\t\tconst row = getRow<JsonRow>(this.db.prepare(\"SELECT record FROM documents WHERE id = ?\"), id);\n\t\t\tconst existing = row === undefined ? undefined : parseJson<DocumentRecord>(row.record);\n\t\t\tif (action.create === undefined && existing === undefined) throw new Error(`Unknown document: ${id}`);\n\t\t\tif (action.create !== undefined && existing !== undefined) throw new Error(`Document ${id} already exists`);\n\t\t\tif (existing?.retiredAt !== undefined) throw new Error(`Document ${id} is retired`);\n\t\t\tif (action.create !== undefined && action.content?.kind !== \"base\") {\n\t\t\t\tthrow new Error(`Document ${id} creation requires a base`);\n\t\t\t}\n\t\t\tif (action.content?.kind === \"delta\") {\n\t\t\t\tconst previous = getRow<{ readonly version: number }>(\n\t\t\t\t\tthis.db.prepare(\n\t\t\t\t\t\t\"SELECT version FROM document_revisions WHERE document_id = ? ORDER BY seq DESC LIMIT 1\",\n\t\t\t\t\t),\n\t\t\t\t\tid,\n\t\t\t\t);\n\t\t\t\tif (previous === undefined) throw new Error(`Document ${id} delta has no base`);\n\t\t\t\tif (previous.version !== action.content.version) {\n\t\t\t\t\tthrow new Error(`Document ${id} version transition requires a base`);\n\t\t\t\t}\n\t\t\t}\n\t\t\tconst record = action.create ?? existing!;\n\t\t\tconst key = addressKey(record);\n\t\t\tlet live = liveCounts.get(key);\n\t\t\tif (live === undefined) live = this.currentDocumentId(record) === undefined ? 0 : 1;\n\t\t\tif (action.retire && existing !== undefined) live--;\n\t\t\tif (action.create !== undefined && !action.retire) live++;\n\t\t\tliveCounts.set(key, live);\n\t\t}\n\t\tfor (const live of liveCounts.values()) {\n\t\t\tif (live > 1) throw new Error(\"Document address already has a current incarnation\");\n\t\t}\n\t}\n\n\tprivate currentDocumentId(address: DocumentAddress | DocumentCreate | DocumentRecord): Id | undefined {\n\t\tconst parts = addressParts(address);\n\t\treturn getRow<IdRow>(\n\t\t\tthis.db.prepare(`SELECT id FROM documents\n\t\t\t\tWHERE kind = ? AND scope_kind = ? AND owner_id = ? AND family = ? AND key_value = ? AND retired_at IS NULL\n\t\t\t\tLIMIT 1`),\n\t\t\tparts.kind,\n\t\t\tparts.scopeKind,\n\t\t\tparts.ownerId,\n\t\t\tparts.family,\n\t\t\tparts.keyValue,\n\t\t)?.id;\n\t}\n\n\tprivate applyTableWrite(write: StorageWrite, seq: Seq): void {\n\t\tswitch (write.type) {\n\t\t\tcase \"conversation\":\n\t\t\t\tthis.claimId(write.value.id, \"conversation\");\n\t\t\t\tthis.db\n\t\t\t\t\t.prepare(\"INSERT INTO conversations (id, record) VALUES (?, ?)\")\n\t\t\t\t\t.run(write.value.id, encodeJson(write.value));\n\t\t\t\tbreak;\n\t\t\tcase \"entry\":\n\t\t\t\tthis.claimId(write.value.id, \"entry\");\n\t\t\t\tthis.db\n\t\t\t\t\t.prepare(\"INSERT INTO entries (id, conversation_id, head, commit_seq, record) VALUES (?, ?, ?, ?, ?)\")\n\t\t\t\t\t.run(write.value.id, write.value.conversationId, write.value.head ?? null, seq, encodeJson(write.value));\n\t\t\t\tbreak;\n\t\t\tcase \"task\":\n\t\t\t\tthis.claimId(write.value.id, \"task\");\n\t\t\t\tthis.db\n\t\t\t\t\t.prepare(`INSERT INTO tasks (id, conversation_id, kind, status, abort_requested, background, record)\n\t\t\t\t\t\tVALUES (?, ?, ?, ?, ?, ?, ?)\n\t\t\t\t\t\tON CONFLICT(id) DO UPDATE SET conversation_id = excluded.conversation_id, kind = excluded.kind,\n\t\t\t\t\t\tstatus = excluded.status, abort_requested = excluded.abort_requested,\n\t\t\t\t\t\tbackground = excluded.background, record = excluded.record`)\n\t\t\t\t\t.run(\n\t\t\t\t\t\twrite.value.id,\n\t\t\t\t\t\twrite.value.conversationId,\n\t\t\t\t\t\tencodeIndexedString(write.value.kind),\n\t\t\t\t\t\twrite.value.state.status,\n\t\t\t\t\t\twrite.value.abortRequested ? 1 : 0,\n\t\t\t\t\t\twrite.value.background ? 1 : 0,\n\t\t\t\t\t\tencodeJson(write.value),\n\t\t\t\t\t);\n\t\t\t\tbreak;\n\t\t\tcase \"submission\":\n\t\t\t\tthis.claimId(write.value.id, \"submission\");\n\t\t\t\tthis.db\n\t\t\t\t\t.prepare(`INSERT INTO submissions (id, conversation_id, request_id, record) VALUES (?, ?, ?, ?)\n\t\t\t\t\t\tON CONFLICT(id) DO UPDATE SET conversation_id = excluded.conversation_id,\n\t\t\t\t\t\trequest_id = excluded.request_id, record = excluded.record`)\n\t\t\t\t\t.run(\n\t\t\t\t\t\twrite.value.id,\n\t\t\t\t\t\twrite.value.conversationId,\n\t\t\t\t\t\twrite.value.requestId === undefined ? null : encodeIndexedString(write.value.requestId),\n\t\t\t\t\t\tencodeJson(write.value),\n\t\t\t\t\t);\n\t\t\t\tbreak;\n\t\t\tcase \"document.create\":\n\t\t\tcase \"document.change\":\n\t\t\tcase \"document.retire\":\n\t\t\t\tbreak;\n\t\t}\n\t}\n\n\tprivate claimId(id: Id, table: TableName): void {\n\t\tthis.db.prepare(\"INSERT OR IGNORE INTO record_ids (id, record_type) VALUES (?, ?)\").run(id, table);\n\t}\n\n\tprivate applyDocumentActions(actions: ReadonlyMap<Id, DocumentAction>, seq: Seq): void {\n\t\tfor (const [id, action] of actions) {\n\t\t\tlet record: DocumentRecord;\n\t\t\tif (action.create !== undefined) {\n\t\t\t\trecord = {\n\t\t\t\t\t...action.create,\n\t\t\t\t\tcreatedAt: seq,\n\t\t\t\t\t...(action.retire ? { retiredAt: seq } : {}),\n\t\t\t\t};\n\t\t\t\tconst parts = addressParts(record);\n\t\t\t\tthis.claimId(id, \"document\");\n\t\t\t\tthis.db\n\t\t\t\t\t.prepare(`INSERT INTO documents\n\t\t\t\t\t\t(id, kind, family, key_value, scope_kind, owner_id, created_at, retired_at, record)\n\t\t\t\t\t\tVALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)`)\n\t\t\t\t\t.run(\n\t\t\t\t\t\tid,\n\t\t\t\t\t\tparts.kind,\n\t\t\t\t\t\tparts.family,\n\t\t\t\t\t\tparts.keyValue,\n\t\t\t\t\t\tparts.scopeKind,\n\t\t\t\t\t\tparts.ownerId,\n\t\t\t\t\t\tseq,\n\t\t\t\t\t\taction.retire ? seq : null,\n\t\t\t\t\t\tencodeJson(record),\n\t\t\t\t\t);\n\t\t\t} else {\n\t\t\t\tconst row = getRow<JsonRow>(this.db.prepare(\"SELECT record FROM documents WHERE id = ?\"), id)!;\n\t\t\t\trecord = parseJson<DocumentRecord>(row.record);\n\t\t\t}\n\n\t\t\tif (action.content !== undefined) {\n\t\t\t\tif (action.content.kind === \"base\" && isCurrentOnly(record)) {\n\t\t\t\t\tthis.db.prepare(\"DELETE FROM document_revisions WHERE document_id = ?\").run(id);\n\t\t\t\t}\n\t\t\t\tconst encodedContent =\n\t\t\t\t\taction.content.kind === \"base\" ? encodeJson(action.content.value) : encodeJson(action.content.ops);\n\t\t\t\tthis.db\n\t\t\t\t\t.prepare(\n\t\t\t\t\t\t\"INSERT INTO document_revisions (document_id, seq, kind, version, content) VALUES (?, ?, ?, ?, ?)\",\n\t\t\t\t\t)\n\t\t\t\t\t.run(id, seq, action.content.kind, action.content.version, encodedContent);\n\t\t\t}\n\n\t\t\tif (action.retire) {\n\t\t\t\tif (action.create === undefined) {\n\t\t\t\t\trecord = { ...record, retiredAt: seq };\n\t\t\t\t\tthis.db\n\t\t\t\t\t\t.prepare(\"UPDATE documents SET retired_at = ?, record = ? WHERE id = ?\")\n\t\t\t\t\t\t.run(seq, encodeJson(record), id);\n\t\t\t\t}\n\t\t\t\tif (isCurrentOnly(record)) this.db.prepare(\"DELETE FROM document_revisions WHERE document_id = ?\").run(id);\n\t\t\t}\n\t\t}\n\t}\n\n\tprivate assertOpen(): void {\n\t\tif (this.closed) throw new Error(\"SqliteStorage is closed\");\n\t}\n}\n"]}