@tearleads/client-sdk 0.1.23 → 0.1.24

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 (29) hide show
  1. package/data/persistence/documents/documentsPersistence.js +7 -0
  2. package/data/persistence/documents/documentsPersistence.js.map +1 -1
  3. package/data/persistence/documents/orphanBlobPersistence.d.ts +13 -0
  4. package/data/persistence/documents/orphanBlobPersistence.js +2 -0
  5. package/data/persistence/documents/orphanBlobPersistence.js.map +1 -0
  6. package/data/persistence/documents/types.d.ts +2 -0
  7. package/data/persistence/documents/types.js.map +1 -1
  8. package/package.json +1 -1
  9. package/stores/documents/documentStore/attachmentHydration.js +2 -2
  10. package/stores/documents/documentStore/attachmentHydration.js.map +1 -1
  11. package/stores/documents/documentStore/attachments.js +7 -5
  12. package/stores/documents/documentStore/attachments.js.map +1 -1
  13. package/stores/documents/documentStore/discard.js +3 -2
  14. package/stores/documents/documentStore/discard.js.map +1 -1
  15. package/stores/documents/documentStore/orphanMaintenance.js +3 -5
  16. package/stores/documents/documentStore/orphanMaintenance.js.map +1 -1
  17. package/stores/documents/documentStore/persistence.js +3 -4
  18. package/stores/documents/documentStore/persistence.js.map +1 -1
  19. package/stores/documents/documentStore/syncDetachedAttachments.js +3 -2
  20. package/stores/documents/documentStore/syncDetachedAttachments.js.map +1 -1
  21. package/stores/documents/documentStore/syncRequest.js +3 -4
  22. package/stores/documents/documentStore/syncRequest.js.map +1 -1
  23. package/workflows/container-contents/documentPurge.js +4 -4
  24. package/workflows/container-contents/documentPurge.js.map +1 -1
  25. package/workflows/documents/orphanBlobReclaimState.js +20 -0
  26. package/workflows/documents/orphanBlobReclaimState.js.map +1 -0
  27. package/workflows/documents/orphanBlobReclaims.d.ts +3 -2
  28. package/workflows/documents/orphanBlobReclaims.js +49 -47
  29. package/workflows/documents/orphanBlobReclaims.js.map +1 -1
@@ -1 +1 @@
1
- {"version":3,"file":"persistence.js","sourceRoot":"","sources":["persistence.ts"],"names":[],"mappings":"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","sourcesContent":["import { encodeVersionVector, exportUpdatesSince } from \"@tearleads/loro\";\nimport { normalizeEffectiveAccessLevel } from \"../../../data/accessLevel\";\nimport { DEFAULT_DOCUMENT_KIND } from \"../../../data/documents/documentConstants\";\nimport {\n type DocumentProjectorRegistry,\n projectStoredDocumentState,\n} from \"../../../data/documents/documentKinds\";\nimport type { DocumentSummary } from \"../../../data/documents/documentSummary\";\nimport { errorMessage } from \"../../../data/errorMessage\";\n\nimport {\n type DocumentRecord,\n defaultDocumentsPersistence,\n type ExecSql,\n enqueuePendingDocumentUpdate,\n listPendingDocumentUpdates,\n type PendingUpdateRecord,\n persistDocumentState,\n reclaimDocumentOrphanBlobs,\n runSerializedSqlMutation,\n} from \"../../../workflows/documents\";\nimport { maybeCompactDocumentHistory } from \"./documentHistoryCompaction\";\nimport {\n importDurableDocumentHistory,\n installDurableDocumentReload,\n} from \"./durableDocumentReload\";\nimport {\n type DocumentState,\n type DocumentStoreState,\n markDocumentStoreRemoved,\n type PersistedDocumentRecord,\n type SaveDocumentRecordOptions,\n setReadySnapshot,\n} from \"./state\";\nimport { createFreshPeerStoredDocument } from \"./storedDocument\";\nimport {\n type DocumentStoreSyncGeneration,\n isDocumentStoreSyncGenerationCurrent as isSyncGenerationCurrent,\n} from \"./syncGeneration\";\n\nexport function documentSummaryFromRecord(\n record: DocumentRecord,\n updatedAt: string,\n documentProjectors: DocumentProjectorRegistry,\n): DocumentSummary {\n return {\n accessStateHash: record.accessStateHash ?? null,\n effectiveAccessLevel: normalizeEffectiveAccessLevel(\n record.effectiveAccessLevel,\n ),\n id: record.id,\n containerId: record.containerId,\n documentKind: record.documentKind ?? DEFAULT_DOCUMENT_KIND,\n documentId: record.documentId,\n title:\n record.title ??\n projectStoredDocumentState(\n {\n documentKind: record.documentKind ?? DEFAULT_DOCUMENT_KIND,\n structuredFields: {},\n text: record.text,\n },\n documentProjectors,\n ).title,\n updatedAt,\n };\n}\n\nfunction toPersistedDocumentRecord(\n persisted: NonNullable<Awaited<ReturnType<typeof persistDocumentState>>>,\n): PersistedDocumentRecord {\n const { record, updatedAt } = persisted;\n return {\n ...(persisted.creationSuperseded\n ? { creationSuperseded: true as const }\n : {}),\n historyRestoreState: persisted.historyRestoreState,\n ...(persisted.pullContinuationSuperseded\n ? { pullContinuationSuperseded: true as const }\n : {}),\n record,\n ...(persisted.syncIdentitySuperseded\n ? { syncIdentitySuperseded: true as const }\n : {}),\n ...(updatedAt !== undefined ? { updatedAt } : {}),\n };\n}\n\n// Nullable without a generation too: an update-persist refuses (returns\n// null) when the durable row was deleted while the persist was queued — the\n// resurrect guard in persistDocumentState — so callers must stop follow-up\n// state changes on null.\nexport async function saveDocumentRecord(\n state: DocumentStoreState,\n currentDoc: DocumentState,\n patch: Partial<DocumentRecord> = {},\n options: SaveDocumentRecordOptions = {},\n expectedGeneration?: DocumentStoreSyncGeneration,\n commitSideEffect?: (transactionExecSql: ExecSql) => Promise<void>,\n): Promise<PersistedDocumentRecord | null> {\n const isCurrent = () =>\n (!expectedGeneration ||\n isSyncGenerationCurrent(state, expectedGeneration)) &&\n options.stillCurrent?.() !== false;\n const createsPlacement = state.record === null;\n const previousDocumentId = state.record?.documentId ?? null;\n const pendingBaseVersion =\n options.pendingBaseVersionOverride === undefined\n ? state.pendingBaseVersion\n : options.pendingBaseVersionOverride;\n const persistenceInput = {\n acceptedPendingUpdateIds: options.acceptedPendingUpdateIds,\n attachmentRemoval: options.attachmentRemoval,\n attachmentStaging: options.attachmentStaging,\n clearSyncFailure: options.clearSyncFailure,\n ...(commitSideEffect ? { commitSideEffect } : {}),\n containerId: state.runtime.state.containerId,\n currentDoc,\n currentRecord: state.record,\n documentProjectors: state.runtime.infra.documentProjectors,\n execSql: state.runtime.infra.execSql,\n expectedSyncState: options.expectedSyncState,\n historyCheckpoint: options.historyCheckpoint,\n historyUpdateOrigin: options.historyUpdateOrigin,\n historyUpdates: options.historyUpdates,\n pendingUpdate: options.pendingUpdate,\n localId: state.localId,\n // Persist the durable outgoing-delta marker with every snapshot write so a\n // restart restores it (see initializeDocumentStore). A device-first\n // deferRemoteSync write leaves it BEHIND the snapshot version on purpose;\n // capturing state.pendingBaseVersion here is what lets the next edit\n // re-derive that deferred op after a restart instead of dropping it.\n patch: { ...patch, pendingBaseVersion },\n persistence: state.persistence,\n };\n const persistedDocumentState =\n expectedGeneration || options.stillCurrent\n ? await persistDocumentState({\n ...persistenceInput,\n // persistDocumentState rechecks after its own pre-save awaits and then\n // immediately claims the executor mutation queue. A reset before that\n // point aborts; a reset afterward cannot let replacement writes overtake.\n canStartDurableMutation: isCurrent,\n })\n : await persistDocumentState(persistenceInput);\n if (!persistedDocumentState) {\n // With a stale generation the teardown that invalidated it owns the\n // state. With a current (or absent) generation, the refusal means the\n // durable row was deleted by ANOTHER subsystem while this persist was\n // queued (the resurrect guard): this store is a zombie — clear it so\n // callers that ignore the result stop advancing state or scheduling\n // sync against a document that no longer exists.\n if (isCurrent()) {\n markDocumentStoreRemoved(state);\n }\n if (state.persistence === defaultDocumentsPersistence) {\n void reclaimDocumentOrphanBlobs(state.runtime);\n }\n return null;\n }\n const { record: nextRecord, updatedAt } = persistedDocumentState;\n if (!isCurrent()) {\n return null;\n }\n\n state.record = persistedDocumentState.record;\n state.pullContinuation = nextRecord.pullContinuation ?? null;\n if (persistedDocumentState.pullContinuationSuperseded) {\n state.pendingBaseVersion =\n nextRecord.pendingBaseVersion ?? nextRecord.snapshotEndVersion;\n }\n if (previousDocumentId !== nextRecord.documentId) {\n state.effects.registerDocumentIdentity(\n state.runtime.state.domainScope,\n nextRecord.id,\n nextRecord.documentId,\n );\n }\n if (updatedAt !== undefined) {\n state.effects.emitPersistedDocument(\n state.runtime.state.domainScope,\n documentSummaryFromRecord(\n nextRecord,\n updatedAt,\n state.runtime.infra.documentProjectors,\n ),\n createsPlacement ||\n Object.hasOwn(patch, \"containerId\") ||\n previousDocumentId !== nextRecord.documentId,\n );\n }\n return toPersistedDocumentRecord(persistedDocumentState);\n}\n\nasync function reloadSupersededDocumentState(\n state: DocumentStoreState,\n isCurrent: () => boolean,\n historyRestoreState: PersistedDocumentRecord[\"historyRestoreState\"],\n previousDocumentId: string | null,\n durableRecord: DocumentRecord,\n): Promise<DocumentState | null> {\n if (historyRestoreState === undefined) {\n throw new Error(\"Superseded document state omitted its durable history\");\n }\n const replacementDoc = await createFreshPeerStoredDocument();\n importDurableDocumentHistory(replacementDoc, historyRestoreState);\n if (!isCurrent()) {\n return null;\n }\n installDurableDocumentReload({\n durableRecord,\n previousDocumentId,\n preserveQueuedWritesWhenIdentityMatches: true,\n registerIdentityChange: false,\n replacementDoc,\n state,\n });\n return replacementDoc;\n}\n\nexport async function persistDocument(\n state: DocumentStoreState,\n currentDoc: DocumentState,\n patch: Partial<DocumentRecord> = {},\n options: SaveDocumentRecordOptions = {},\n expectedGeneration?: DocumentStoreSyncGeneration,\n commitSideEffect?: (transactionExecSql: ExecSql) => Promise<void>,\n): Promise<PersistedDocumentRecord | null> {\n const isCurrent = () =>\n (!expectedGeneration ||\n isSyncGenerationCurrent(state, expectedGeneration)) &&\n options.stillCurrent?.() !== false;\n const previousDocumentId = state.record?.documentId ?? null;\n const persistedRecord = await saveDocumentRecord(\n state,\n currentDoc,\n patch,\n options,\n expectedGeneration,\n commitSideEffect,\n );\n return publishPersistedDocument(\n state,\n currentDoc,\n persistedRecord,\n previousDocumentId,\n options,\n isCurrent,\n );\n}\n\n/** Publish only after the durable claim succeeds and its live context remains. */\nexport async function publishPersistedDocument(\n state: DocumentStoreState,\n currentDoc: DocumentState,\n persistedRecord: PersistedDocumentRecord | null,\n previousDocumentId: string | null,\n options: SaveDocumentRecordOptions,\n isCurrent: () => boolean,\n): Promise<PersistedDocumentRecord | null> {\n if (!persistedRecord) return null;\n if (!isCurrent()) return null;\n\n if (\n persistedRecord.syncIdentitySuperseded ||\n persistedRecord.pullContinuationSuperseded\n ) {\n // Reload the winning durable checkpoint/tail on every CAS loss, including\n // another pane's history or security-context changes. A rejected sync\n // candidate remains isolated from the live document and editor.\n const replacementDoc = await reloadSupersededDocumentState(\n state,\n isCurrent,\n persistedRecord.historyRestoreState,\n previousDocumentId,\n persistedRecord.record,\n );\n if (!replacementDoc) return null;\n return persistedRecord;\n }\n\n setReadySnapshot(\n state,\n currentDoc,\n state.snapshot.syncing,\n options.preserveSnapshotText && !persistedRecord.pullContinuationSuperseded\n ? state.snapshot.text\n : persistedRecord.record.text,\n options.preserveSnapshotStructuredFields &&\n !persistedRecord.pullContinuationSuperseded\n ? state.snapshot.structuredFields\n : undefined,\n );\n // Compaction never fails the persist that triggered it: an unexpected\n // compaction error simply keeps the tail growing until a later pass\n // succeeds, and the content stays durable either way.\n try {\n await maybeCompactDocumentHistory(state, currentDoc, options.stillCurrent);\n } catch (error) {\n state.runtime.util.log(\n `Documents: history compaction skipped: ${errorMessage(error)}`,\n );\n }\n return persistedRecord;\n}\n\nexport async function listPendingUpdates(\n state: DocumentStoreState,\n): Promise<PendingUpdateRecord[]> {\n return listPendingDocumentUpdates({\n execSql: state.runtime.infra.execSql,\n localId: state.localId,\n persistence: state.persistence,\n });\n}\n\n/**\n * Export the outgoing delta for the current edit. The base is `pendingBaseVersion`\n * (the version through which ops are already durably enqueued or synced), NOT the\n * live doc version, so any delta orphaned by a prior failed enqueue is folded back\n * in here. In the success path the marker equals the pre-edit version, so this is\n * identical to exporting \"since the last edit\".\n *\n * Initialization sets `pendingBaseVersion` whenever it sets `doc`, and every\n * caller checks `doc` first, so the marker is non-null here. Assert that rather\n * than falling back to the live version: callers invoke this AFTER mutating the\n * doc, so a live-version fallback would export an empty (post-mutation) delta and\n * silently drop the edit.\n */\nexport function pendingDeltaSinceBase(\n state: DocumentStoreState,\n doc: DocumentState,\n): Uint8Array {\n if (state.pendingBaseVersion === null) {\n throw new Error(\n \"pendingDeltaSinceBase requires an initialized pendingBaseVersion\",\n );\n }\n return exportUpdatesSince(doc, state.pendingBaseVersion);\n}\n\n/**\n * Advance the durable marker to the doc's current version. Call this ONLY after\n * an edit's delta has been durably enqueued and persisted, or after remote\n * updates have been imported (those ops are already on the server).\n */\nexport function advancePendingBaseVersion(\n state: DocumentStoreState,\n doc: DocumentState,\n): void {\n state.pendingBaseVersion = encodeVersionVector(doc);\n}\n\n/**\n * Runs a durable write either ungated — no generation: the caller serializes\n * with teardown via the write-chain drain, and the persistence implementation\n * may legitimately park inside the write WITHOUT holding the global mutation\n * mutex — or inside the serialized mutation with an in-mutex currency check:\n * a teardown (reset/discard) that already ran was ordered strictly before the\n * mutation claim and invalidated the generation, so a stale caller can no\n * longer slip its write in after the teardown's wipe; a write that wins the\n * ordering instead lands before the teardown and is wiped by it. Returns\n * whether the write ran.\n */\nexport async function withGenerationGuardedMutation(\n state: DocumentStoreState,\n expectedGeneration: DocumentStoreSyncGeneration | undefined,\n write: (execSql: ExecSql) => Promise<void>,\n): Promise<boolean> {\n if (!expectedGeneration) {\n await write(state.runtime.infra.execSql);\n return true;\n }\n\n let ran = false;\n await runSerializedSqlMutation(\n state.runtime.infra.execSql,\n async (lockedExecSql) => {\n if (!isSyncGenerationCurrent(state, expectedGeneration)) {\n return;\n }\n await write(lockedExecSql);\n ran = true;\n },\n );\n return ran;\n}\n\nexport async function enqueuePendingUpdate(\n state: DocumentStoreState,\n update: Uint8Array,\n sourceVersionVector?: string | null,\n expectedGeneration?: DocumentStoreSyncGeneration,\n): Promise<boolean> {\n const expectedDocumentId = state.record?.documentId;\n const expectedRecoveryGeneration = state.record?.recoveryGeneration ?? 0;\n if (expectedDocumentId === undefined) {\n return false;\n }\n let enqueued = false;\n const ran = await withGenerationGuardedMutation(\n state,\n expectedGeneration,\n async (execSql) => {\n enqueued = await enqueuePendingDocumentUpdate({\n execSql,\n expectedDocumentId,\n expectedRecoveryGeneration,\n localId: state.localId,\n persistence: state.persistence,\n ...(sourceVersionVector === undefined ? {} : { sourceVersionVector }),\n update,\n });\n },\n );\n return ran && enqueued;\n}\n"]}
1
+ {"version":3,"file":"persistence.js","sourceRoot":"","sources":["persistence.ts"],"names":[],"mappings":"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","sourcesContent":["import { encodeVersionVector, exportUpdatesSince } from \"@tearleads/loro\";\nimport { normalizeEffectiveAccessLevel } from \"../../../data/accessLevel\";\nimport { DEFAULT_DOCUMENT_KIND } from \"../../../data/documents/documentConstants\";\nimport {\n type DocumentProjectorRegistry,\n projectStoredDocumentState,\n} from \"../../../data/documents/documentKinds\";\nimport type { DocumentSummary } from \"../../../data/documents/documentSummary\";\nimport { errorMessage } from \"../../../data/errorMessage\";\nimport {\n type DocumentRecord,\n type ExecSql,\n enqueuePendingDocumentUpdate,\n listPendingDocumentUpdates,\n type PendingUpdateRecord,\n persistDocumentState,\n runSerializedSqlMutation,\n} from \"../../../workflows/documents\";\nimport { maybeCompactDocumentHistory } from \"./documentHistoryCompaction\";\nimport {\n importDurableDocumentHistory,\n installDurableDocumentReload,\n} from \"./durableDocumentReload\";\nimport { runDocumentOrphanMaintenance } from \"./orphanMaintenance\";\nimport {\n type DocumentState,\n type DocumentStoreState,\n markDocumentStoreRemoved,\n type PersistedDocumentRecord,\n type SaveDocumentRecordOptions,\n setReadySnapshot,\n} from \"./state\";\nimport { createFreshPeerStoredDocument } from \"./storedDocument\";\nimport {\n type DocumentStoreSyncGeneration,\n isDocumentStoreSyncGenerationCurrent as isSyncGenerationCurrent,\n} from \"./syncGeneration\";\n\nexport function documentSummaryFromRecord(\n record: DocumentRecord,\n updatedAt: string,\n documentProjectors: DocumentProjectorRegistry,\n): DocumentSummary {\n return {\n accessStateHash: record.accessStateHash ?? null,\n effectiveAccessLevel: normalizeEffectiveAccessLevel(\n record.effectiveAccessLevel,\n ),\n id: record.id,\n containerId: record.containerId,\n documentKind: record.documentKind ?? DEFAULT_DOCUMENT_KIND,\n documentId: record.documentId,\n title:\n record.title ??\n projectStoredDocumentState(\n {\n documentKind: record.documentKind ?? DEFAULT_DOCUMENT_KIND,\n structuredFields: {},\n text: record.text,\n },\n documentProjectors,\n ).title,\n updatedAt,\n };\n}\n\nfunction toPersistedDocumentRecord(\n persisted: NonNullable<Awaited<ReturnType<typeof persistDocumentState>>>,\n): PersistedDocumentRecord {\n const { record, updatedAt } = persisted;\n return {\n ...(persisted.creationSuperseded\n ? { creationSuperseded: true as const }\n : {}),\n historyRestoreState: persisted.historyRestoreState,\n ...(persisted.pullContinuationSuperseded\n ? { pullContinuationSuperseded: true as const }\n : {}),\n record,\n ...(persisted.syncIdentitySuperseded\n ? { syncIdentitySuperseded: true as const }\n : {}),\n ...(updatedAt !== undefined ? { updatedAt } : {}),\n };\n}\n\n// Nullable without a generation too: an update-persist refuses (returns\n// null) when the durable row was deleted while the persist was queued — the\n// resurrect guard in persistDocumentState — so callers must stop follow-up\n// state changes on null.\nexport async function saveDocumentRecord(\n state: DocumentStoreState,\n currentDoc: DocumentState,\n patch: Partial<DocumentRecord> = {},\n options: SaveDocumentRecordOptions = {},\n expectedGeneration?: DocumentStoreSyncGeneration,\n commitSideEffect?: (transactionExecSql: ExecSql) => Promise<void>,\n): Promise<PersistedDocumentRecord | null> {\n const isCurrent = () =>\n (!expectedGeneration ||\n isSyncGenerationCurrent(state, expectedGeneration)) &&\n options.stillCurrent?.() !== false;\n const createsPlacement = state.record === null;\n const previousDocumentId = state.record?.documentId ?? null;\n const pendingBaseVersion =\n options.pendingBaseVersionOverride === undefined\n ? state.pendingBaseVersion\n : options.pendingBaseVersionOverride;\n const persistenceInput = {\n acceptedPendingUpdateIds: options.acceptedPendingUpdateIds,\n attachmentRemoval: options.attachmentRemoval,\n attachmentStaging: options.attachmentStaging,\n clearSyncFailure: options.clearSyncFailure,\n ...(commitSideEffect ? { commitSideEffect } : {}),\n containerId: state.runtime.state.containerId,\n currentDoc,\n currentRecord: state.record,\n documentProjectors: state.runtime.infra.documentProjectors,\n execSql: state.runtime.infra.execSql,\n expectedSyncState: options.expectedSyncState,\n historyCheckpoint: options.historyCheckpoint,\n historyUpdateOrigin: options.historyUpdateOrigin,\n historyUpdates: options.historyUpdates,\n pendingUpdate: options.pendingUpdate,\n localId: state.localId,\n // Persist the durable outgoing-delta marker with every snapshot write so a\n // restart restores it (see initializeDocumentStore). A device-first\n // deferRemoteSync write leaves it BEHIND the snapshot version on purpose;\n // capturing state.pendingBaseVersion here is what lets the next edit\n // re-derive that deferred op after a restart instead of dropping it.\n patch: { ...patch, pendingBaseVersion },\n persistence: state.persistence,\n };\n const persistedDocumentState =\n expectedGeneration || options.stillCurrent\n ? await persistDocumentState({\n ...persistenceInput,\n // persistDocumentState rechecks after its own pre-save awaits and then\n // immediately claims the executor mutation queue. A reset before that\n // point aborts; a reset afterward cannot let replacement writes overtake.\n canStartDurableMutation: isCurrent,\n })\n : await persistDocumentState(persistenceInput);\n if (!persistedDocumentState) {\n // With a stale generation the teardown that invalidated it owns the\n // state. With a current (or absent) generation, the refusal means the\n // durable row was deleted by ANOTHER subsystem while this persist was\n // queued (the resurrect guard): this store is a zombie — clear it so\n // callers that ignore the result stop advancing state or scheduling\n // sync against a document that no longer exists.\n if (isCurrent()) {\n markDocumentStoreRemoved(state);\n }\n void runDocumentOrphanMaintenance(state);\n return null;\n }\n const { record: nextRecord, updatedAt } = persistedDocumentState;\n if (!isCurrent()) {\n return null;\n }\n\n state.record = persistedDocumentState.record;\n state.pullContinuation = nextRecord.pullContinuation ?? null;\n if (persistedDocumentState.pullContinuationSuperseded) {\n state.pendingBaseVersion =\n nextRecord.pendingBaseVersion ?? nextRecord.snapshotEndVersion;\n }\n if (previousDocumentId !== nextRecord.documentId) {\n state.effects.registerDocumentIdentity(\n state.runtime.state.domainScope,\n nextRecord.id,\n nextRecord.documentId,\n );\n }\n if (updatedAt !== undefined) {\n state.effects.emitPersistedDocument(\n state.runtime.state.domainScope,\n documentSummaryFromRecord(\n nextRecord,\n updatedAt,\n state.runtime.infra.documentProjectors,\n ),\n createsPlacement ||\n Object.hasOwn(patch, \"containerId\") ||\n previousDocumentId !== nextRecord.documentId,\n );\n }\n return toPersistedDocumentRecord(persistedDocumentState);\n}\n\nasync function reloadSupersededDocumentState(\n state: DocumentStoreState,\n isCurrent: () => boolean,\n historyRestoreState: PersistedDocumentRecord[\"historyRestoreState\"],\n previousDocumentId: string | null,\n durableRecord: DocumentRecord,\n): Promise<DocumentState | null> {\n if (historyRestoreState === undefined) {\n throw new Error(\"Superseded document state omitted its durable history\");\n }\n const replacementDoc = await createFreshPeerStoredDocument();\n importDurableDocumentHistory(replacementDoc, historyRestoreState);\n if (!isCurrent()) {\n return null;\n }\n installDurableDocumentReload({\n durableRecord,\n previousDocumentId,\n preserveQueuedWritesWhenIdentityMatches: true,\n registerIdentityChange: false,\n replacementDoc,\n state,\n });\n return replacementDoc;\n}\n\nexport async function persistDocument(\n state: DocumentStoreState,\n currentDoc: DocumentState,\n patch: Partial<DocumentRecord> = {},\n options: SaveDocumentRecordOptions = {},\n expectedGeneration?: DocumentStoreSyncGeneration,\n commitSideEffect?: (transactionExecSql: ExecSql) => Promise<void>,\n): Promise<PersistedDocumentRecord | null> {\n const isCurrent = () =>\n (!expectedGeneration ||\n isSyncGenerationCurrent(state, expectedGeneration)) &&\n options.stillCurrent?.() !== false;\n const previousDocumentId = state.record?.documentId ?? null;\n const persistedRecord = await saveDocumentRecord(\n state,\n currentDoc,\n patch,\n options,\n expectedGeneration,\n commitSideEffect,\n );\n return publishPersistedDocument(\n state,\n currentDoc,\n persistedRecord,\n previousDocumentId,\n options,\n isCurrent,\n );\n}\n\n/** Publish only after the durable claim succeeds and its live context remains. */\nexport async function publishPersistedDocument(\n state: DocumentStoreState,\n currentDoc: DocumentState,\n persistedRecord: PersistedDocumentRecord | null,\n previousDocumentId: string | null,\n options: SaveDocumentRecordOptions,\n isCurrent: () => boolean,\n): Promise<PersistedDocumentRecord | null> {\n if (!persistedRecord) return null;\n if (!isCurrent()) return null;\n\n if (\n persistedRecord.syncIdentitySuperseded ||\n persistedRecord.pullContinuationSuperseded\n ) {\n // Reload the winning durable checkpoint/tail on every CAS loss, including\n // another pane's history or security-context changes. A rejected sync\n // candidate remains isolated from the live document and editor.\n const replacementDoc = await reloadSupersededDocumentState(\n state,\n isCurrent,\n persistedRecord.historyRestoreState,\n previousDocumentId,\n persistedRecord.record,\n );\n if (!replacementDoc) return null;\n return persistedRecord;\n }\n\n setReadySnapshot(\n state,\n currentDoc,\n state.snapshot.syncing,\n options.preserveSnapshotText && !persistedRecord.pullContinuationSuperseded\n ? state.snapshot.text\n : persistedRecord.record.text,\n options.preserveSnapshotStructuredFields &&\n !persistedRecord.pullContinuationSuperseded\n ? state.snapshot.structuredFields\n : undefined,\n );\n // Compaction never fails the persist that triggered it: an unexpected\n // compaction error simply keeps the tail growing until a later pass\n // succeeds, and the content stays durable either way.\n try {\n await maybeCompactDocumentHistory(state, currentDoc, options.stillCurrent);\n } catch (error) {\n state.runtime.util.log(\n `Documents: history compaction skipped: ${errorMessage(error)}`,\n );\n }\n return persistedRecord;\n}\n\nexport async function listPendingUpdates(\n state: DocumentStoreState,\n): Promise<PendingUpdateRecord[]> {\n return listPendingDocumentUpdates({\n execSql: state.runtime.infra.execSql,\n localId: state.localId,\n persistence: state.persistence,\n });\n}\n\n/**\n * Export the outgoing delta for the current edit. The base is `pendingBaseVersion`\n * (the version through which ops are already durably enqueued or synced), NOT the\n * live doc version, so any delta orphaned by a prior failed enqueue is folded back\n * in here. In the success path the marker equals the pre-edit version, so this is\n * identical to exporting \"since the last edit\".\n *\n * Initialization sets `pendingBaseVersion` whenever it sets `doc`, and every\n * caller checks `doc` first, so the marker is non-null here. Assert that rather\n * than falling back to the live version: callers invoke this AFTER mutating the\n * doc, so a live-version fallback would export an empty (post-mutation) delta and\n * silently drop the edit.\n */\nexport function pendingDeltaSinceBase(\n state: DocumentStoreState,\n doc: DocumentState,\n): Uint8Array {\n if (state.pendingBaseVersion === null) {\n throw new Error(\n \"pendingDeltaSinceBase requires an initialized pendingBaseVersion\",\n );\n }\n return exportUpdatesSince(doc, state.pendingBaseVersion);\n}\n\n/**\n * Advance the durable marker to the doc's current version. Call this ONLY after\n * an edit's delta has been durably enqueued and persisted, or after remote\n * updates have been imported (those ops are already on the server).\n */\nexport function advancePendingBaseVersion(\n state: DocumentStoreState,\n doc: DocumentState,\n): void {\n state.pendingBaseVersion = encodeVersionVector(doc);\n}\n\n/**\n * Runs a durable write either ungated — no generation: the caller serializes\n * with teardown via the write-chain drain, and the persistence implementation\n * may legitimately park inside the write WITHOUT holding the global mutation\n * mutex — or inside the serialized mutation with an in-mutex currency check:\n * a teardown (reset/discard) that already ran was ordered strictly before the\n * mutation claim and invalidated the generation, so a stale caller can no\n * longer slip its write in after the teardown's wipe; a write that wins the\n * ordering instead lands before the teardown and is wiped by it. Returns\n * whether the write ran.\n */\nexport async function withGenerationGuardedMutation(\n state: DocumentStoreState,\n expectedGeneration: DocumentStoreSyncGeneration | undefined,\n write: (execSql: ExecSql) => Promise<void>,\n): Promise<boolean> {\n if (!expectedGeneration) {\n await write(state.runtime.infra.execSql);\n return true;\n }\n\n let ran = false;\n await runSerializedSqlMutation(\n state.runtime.infra.execSql,\n async (lockedExecSql) => {\n if (!isSyncGenerationCurrent(state, expectedGeneration)) {\n return;\n }\n await write(lockedExecSql);\n ran = true;\n },\n );\n return ran;\n}\n\nexport async function enqueuePendingUpdate(\n state: DocumentStoreState,\n update: Uint8Array,\n sourceVersionVector?: string | null,\n expectedGeneration?: DocumentStoreSyncGeneration,\n): Promise<boolean> {\n const expectedDocumentId = state.record?.documentId;\n const expectedRecoveryGeneration = state.record?.recoveryGeneration ?? 0;\n if (expectedDocumentId === undefined) {\n return false;\n }\n let enqueued = false;\n const ran = await withGenerationGuardedMutation(\n state,\n expectedGeneration,\n async (execSql) => {\n enqueued = await enqueuePendingDocumentUpdate({\n execSql,\n expectedDocumentId,\n expectedRecoveryGeneration,\n localId: state.localId,\n persistence: state.persistence,\n ...(sourceVersionVector === undefined ? {} : { sourceVersionVector }),\n update,\n });\n },\n );\n return ran && enqueued;\n}\n"]}
@@ -1,8 +1,9 @@
1
1
  import { getDocumentAttachments } from "../../../data/documents/documentContent.js";
2
2
  import { detachDocumentAttachment } from "../../../workflows/blobs/detach.js";
3
- import { reclaimDocumentOrphanBlobs, resolveDocumentCreateAuthor, } from "../../../workflows/documents/index.js";
3
+ import { resolveDocumentCreateAuthor, } from "../../../workflows/documents/index.js";
4
4
  import { createRuntimePrincipalPolicyWarmer } from "../../../workflows/principals/runtimePolicyWarmer.js";
5
5
  import { deleteLocalAttachmentRecord } from "./attachmentPersistence.js";
6
+ import { runDocumentOrphanMaintenance } from "./orphanMaintenance.js";
6
7
  import { isDocumentStoreSyncGenerationCurrent, } from "./syncGeneration.js";
7
8
  import { documentSyncContextMatches } from "./syncUpdateImport.js";
8
9
  import { installDocumentWriterProjection } from "./writerProjectionGeneration.js";
@@ -71,7 +72,7 @@ async function cleanupDetachedAttachmentMarker(state, currentDoc, requestRecord,
71
72
  }
72
73
  await deleteLocalAttachmentRecord(state, marker.slotId, marker.storageKey, currentDoc);
73
74
  // Deleting the row queued its bytes for the reference-checked reclaim.
74
- void reclaimDocumentOrphanBlobs(state.runtime);
75
+ void runDocumentOrphanMaintenance(state);
75
76
  return detachedSyncContextIsCurrent(state, generation, requestRecord);
76
77
  }
77
78
  async function syncDetachedAttachmentBinding(input) {
@@ -1 +1 @@
1
- {"version":3,"file":"syncDetachedAttachments.js","sourceRoot":"","sources":["syncDetachedAttachments.ts"],"names":[],"mappings":"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","sourcesContent":["import { getDocumentAttachments } from \"../../../data/documents/documentContent\";\nimport { detachDocumentAttachment } from \"../../../workflows/blobs/detach\";\nimport {\n type DocumentRecord,\n reclaimDocumentOrphanBlobs,\n resolveDocumentCreateAuthor,\n} from \"../../../workflows/documents\";\nimport { createRuntimePrincipalPolicyWarmer } from \"../../../workflows/principals/runtimePolicyWarmer\";\nimport { deleteLocalAttachmentRecord } from \"./attachmentPersistence\";\nimport type {\n DocumentAttachmentBinding,\n DocumentState,\n DocumentStoreState,\n} from \"./state\";\nimport {\n type DocumentStoreSyncGeneration,\n isDocumentStoreSyncGenerationCurrent,\n} from \"./syncGeneration\";\nimport { documentSyncContextMatches } from \"./syncUpdateImport\";\nimport { installDocumentWriterProjection } from \"./writerProjectionGeneration\";\n\ninterface DetachedAttachmentMarker {\n slotId: string;\n storageKey: string;\n}\n\nexport async function syncDetachedAttachmentBindings(\n state: DocumentStoreState,\n nextRecord: DocumentRecord,\n generation: DocumentStoreSyncGeneration,\n): Promise<void> {\n const currentDoc = generation.currentDoc;\n if (\n !currentDoc ||\n !detachedSyncContextIsCurrent(state, generation, nextRecord)\n ) {\n return;\n }\n\n const detachedMarkers = listDetachedAttachmentMarkers(state, currentDoc);\n if (detachedMarkers.length === 0) {\n return;\n }\n\n if (!nextRecord.documentId) {\n await cleanupDetachedAttachmentMarkers(\n state,\n currentDoc,\n nextRecord,\n detachedMarkers,\n generation,\n );\n return;\n }\n\n const remoteDocumentId = nextRecord.documentId;\n const remoteBindings =\n await state.runtime.apiClient.listDocumentAttachments(remoteDocumentId);\n if (\n !remoteBindings ||\n !detachedSyncContextIsCurrent(state, generation, nextRecord)\n ) {\n return;\n }\n\n const activeBindingBySlotId = new Map(\n remoteBindings.map((binding) => [binding.slotId, binding]),\n );\n\n for (const marker of detachedMarkers) {\n if (!detachedSyncContextIsCurrent(state, generation, nextRecord)) {\n return;\n }\n const activeBinding = activeBindingBySlotId.get(marker.slotId);\n if (activeBinding) {\n const detached = await syncDetachedAttachmentBinding({\n binding: activeBinding,\n generation,\n remoteDocumentId,\n requestRecord: nextRecord,\n state,\n });\n if (!detached) {\n return;\n }\n }\n\n const cleaned = await cleanupDetachedAttachmentMarker(\n state,\n currentDoc,\n nextRecord,\n marker,\n generation,\n );\n if (!cleaned) {\n return;\n }\n }\n}\n\nfunction detachedSyncContextIsCurrent(\n state: DocumentStoreState,\n generation: DocumentStoreSyncGeneration,\n requestRecord: DocumentRecord,\n): boolean {\n return (\n isDocumentStoreSyncGenerationCurrent(state, generation) &&\n documentSyncContextMatches(\n state.record,\n requestRecord,\n requestRecord.documentId,\n )\n );\n}\n\nfunction listDetachedAttachmentMarkers(\n state: DocumentStoreState,\n currentDoc: DocumentState,\n): DetachedAttachmentMarker[] {\n const currentSlotIds = new Set(\n getDocumentAttachments(currentDoc).map((attachment) => attachment.slotId),\n );\n\n return Object.entries(state.attachmentStorageKeyBySlotId).flatMap(\n ([slotId, storageKey]) =>\n currentSlotIds.has(slotId) ? [] : [{ slotId, storageKey }],\n );\n}\n\nasync function cleanupDetachedAttachmentMarkers(\n state: DocumentStoreState,\n currentDoc: DocumentState,\n requestRecord: DocumentRecord,\n markers: readonly DetachedAttachmentMarker[],\n generation: DocumentStoreSyncGeneration,\n): Promise<void> {\n for (const marker of markers) {\n if (\n !(await cleanupDetachedAttachmentMarker(\n state,\n currentDoc,\n requestRecord,\n marker,\n generation,\n ))\n ) {\n return;\n }\n }\n}\n\nasync function cleanupDetachedAttachmentMarker(\n state: DocumentStoreState,\n currentDoc: DocumentState,\n requestRecord: DocumentRecord,\n marker: DetachedAttachmentMarker,\n generation: DocumentStoreSyncGeneration,\n): Promise<boolean> {\n if (!detachedSyncContextIsCurrent(state, generation, requestRecord)) {\n return false;\n }\n await deleteLocalAttachmentRecord(\n state,\n marker.slotId,\n marker.storageKey,\n currentDoc,\n );\n // Deleting the row queued its bytes for the reference-checked reclaim.\n void reclaimDocumentOrphanBlobs(state.runtime);\n return detachedSyncContextIsCurrent(state, generation, requestRecord);\n}\n\nasync function syncDetachedAttachmentBinding(input: {\n binding: DocumentAttachmentBinding;\n generation: DocumentStoreSyncGeneration;\n remoteDocumentId: string;\n requestRecord: DocumentRecord;\n state: DocumentStoreState;\n}): Promise<boolean> {\n const { binding, generation, requestRecord, state } = input;\n const contextIsCurrent = () =>\n detachedSyncContextIsCurrent(state, generation, requestRecord);\n if (!contextIsCurrent()) return false;\n\n const author = resolveDocumentCreateAuthor(state.runtime);\n if (!author) {\n state.runtime.util.log(\n \"Documents: skipped attachment detach because the writer context is unavailable.\",\n );\n return false;\n }\n\n const writerProjection =\n state.writerProjection?.documentId === input.remoteDocumentId\n ? state.writerProjection\n : null;\n const writerProjectionGeneration = state.writerProjectionGeneration;\n let remoteSyncBlocked = false;\n const baseDetachInput = {\n apiClient: state.runtime.apiClient,\n author,\n bindingId: binding.bindingId,\n blobId: binding.blobId,\n documentId: input.remoteDocumentId,\n execSql: generation.execSql,\n isRemoteSyncBlocked: (organizationId: string) => {\n remoteSyncBlocked =\n state.runtime.util.isRemoteSyncBlocked?.(organizationId) ?? false;\n return remoteSyncBlocked;\n },\n resolveProjectionUserKey: generation.resolveProjectionUserKey,\n slotId: binding.slotId,\n stillCurrent: contextIsCurrent,\n warmReferencedPrincipalPolicies: createRuntimePrincipalPolicyWarmer(\n state.runtime,\n ),\n };\n let detached = await detachDocumentAttachment({\n ...baseDetachInput,\n writerProjection: writerProjection ?? undefined,\n });\n if (!contextIsCurrent()) return false;\n if (!detached && writerProjection && !remoteSyncBlocked) {\n state.writerProjection = null;\n detached = await detachDocumentAttachment(baseDetachInput);\n if (!contextIsCurrent()) return false;\n }\n if (!detached) {\n return false;\n }\n\n installDocumentWriterProjection(\n state,\n detached.writerProjection,\n writerProjectionGeneration,\n );\n state.runtime.util.log(\n `Detached attachment ${binding.slotId} from document ${input.remoteDocumentId}.`,\n );\n return true;\n}\n"]}
1
+ {"version":3,"file":"syncDetachedAttachments.js","sourceRoot":"","sources":["syncDetachedAttachments.ts"],"names":[],"mappings":"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","sourcesContent":["import { getDocumentAttachments } from \"../../../data/documents/documentContent\";\nimport { detachDocumentAttachment } from \"../../../workflows/blobs/detach\";\nimport {\n type DocumentRecord,\n resolveDocumentCreateAuthor,\n} from \"../../../workflows/documents\";\nimport { createRuntimePrincipalPolicyWarmer } from \"../../../workflows/principals/runtimePolicyWarmer\";\nimport { deleteLocalAttachmentRecord } from \"./attachmentPersistence\";\nimport { runDocumentOrphanMaintenance } from \"./orphanMaintenance\";\nimport type {\n DocumentAttachmentBinding,\n DocumentState,\n DocumentStoreState,\n} from \"./state\";\nimport {\n type DocumentStoreSyncGeneration,\n isDocumentStoreSyncGenerationCurrent,\n} from \"./syncGeneration\";\nimport { documentSyncContextMatches } from \"./syncUpdateImport\";\nimport { installDocumentWriterProjection } from \"./writerProjectionGeneration\";\n\ninterface DetachedAttachmentMarker {\n slotId: string;\n storageKey: string;\n}\n\nexport async function syncDetachedAttachmentBindings(\n state: DocumentStoreState,\n nextRecord: DocumentRecord,\n generation: DocumentStoreSyncGeneration,\n): Promise<void> {\n const currentDoc = generation.currentDoc;\n if (\n !currentDoc ||\n !detachedSyncContextIsCurrent(state, generation, nextRecord)\n ) {\n return;\n }\n\n const detachedMarkers = listDetachedAttachmentMarkers(state, currentDoc);\n if (detachedMarkers.length === 0) {\n return;\n }\n\n if (!nextRecord.documentId) {\n await cleanupDetachedAttachmentMarkers(\n state,\n currentDoc,\n nextRecord,\n detachedMarkers,\n generation,\n );\n return;\n }\n\n const remoteDocumentId = nextRecord.documentId;\n const remoteBindings =\n await state.runtime.apiClient.listDocumentAttachments(remoteDocumentId);\n if (\n !remoteBindings ||\n !detachedSyncContextIsCurrent(state, generation, nextRecord)\n ) {\n return;\n }\n\n const activeBindingBySlotId = new Map(\n remoteBindings.map((binding) => [binding.slotId, binding]),\n );\n\n for (const marker of detachedMarkers) {\n if (!detachedSyncContextIsCurrent(state, generation, nextRecord)) {\n return;\n }\n const activeBinding = activeBindingBySlotId.get(marker.slotId);\n if (activeBinding) {\n const detached = await syncDetachedAttachmentBinding({\n binding: activeBinding,\n generation,\n remoteDocumentId,\n requestRecord: nextRecord,\n state,\n });\n if (!detached) {\n return;\n }\n }\n\n const cleaned = await cleanupDetachedAttachmentMarker(\n state,\n currentDoc,\n nextRecord,\n marker,\n generation,\n );\n if (!cleaned) {\n return;\n }\n }\n}\n\nfunction detachedSyncContextIsCurrent(\n state: DocumentStoreState,\n generation: DocumentStoreSyncGeneration,\n requestRecord: DocumentRecord,\n): boolean {\n return (\n isDocumentStoreSyncGenerationCurrent(state, generation) &&\n documentSyncContextMatches(\n state.record,\n requestRecord,\n requestRecord.documentId,\n )\n );\n}\n\nfunction listDetachedAttachmentMarkers(\n state: DocumentStoreState,\n currentDoc: DocumentState,\n): DetachedAttachmentMarker[] {\n const currentSlotIds = new Set(\n getDocumentAttachments(currentDoc).map((attachment) => attachment.slotId),\n );\n\n return Object.entries(state.attachmentStorageKeyBySlotId).flatMap(\n ([slotId, storageKey]) =>\n currentSlotIds.has(slotId) ? [] : [{ slotId, storageKey }],\n );\n}\n\nasync function cleanupDetachedAttachmentMarkers(\n state: DocumentStoreState,\n currentDoc: DocumentState,\n requestRecord: DocumentRecord,\n markers: readonly DetachedAttachmentMarker[],\n generation: DocumentStoreSyncGeneration,\n): Promise<void> {\n for (const marker of markers) {\n if (\n !(await cleanupDetachedAttachmentMarker(\n state,\n currentDoc,\n requestRecord,\n marker,\n generation,\n ))\n ) {\n return;\n }\n }\n}\n\nasync function cleanupDetachedAttachmentMarker(\n state: DocumentStoreState,\n currentDoc: DocumentState,\n requestRecord: DocumentRecord,\n marker: DetachedAttachmentMarker,\n generation: DocumentStoreSyncGeneration,\n): Promise<boolean> {\n if (!detachedSyncContextIsCurrent(state, generation, requestRecord)) {\n return false;\n }\n await deleteLocalAttachmentRecord(\n state,\n marker.slotId,\n marker.storageKey,\n currentDoc,\n );\n // Deleting the row queued its bytes for the reference-checked reclaim.\n void runDocumentOrphanMaintenance(state);\n return detachedSyncContextIsCurrent(state, generation, requestRecord);\n}\n\nasync function syncDetachedAttachmentBinding(input: {\n binding: DocumentAttachmentBinding;\n generation: DocumentStoreSyncGeneration;\n remoteDocumentId: string;\n requestRecord: DocumentRecord;\n state: DocumentStoreState;\n}): Promise<boolean> {\n const { binding, generation, requestRecord, state } = input;\n const contextIsCurrent = () =>\n detachedSyncContextIsCurrent(state, generation, requestRecord);\n if (!contextIsCurrent()) return false;\n\n const author = resolveDocumentCreateAuthor(state.runtime);\n if (!author) {\n state.runtime.util.log(\n \"Documents: skipped attachment detach because the writer context is unavailable.\",\n );\n return false;\n }\n\n const writerProjection =\n state.writerProjection?.documentId === input.remoteDocumentId\n ? state.writerProjection\n : null;\n const writerProjectionGeneration = state.writerProjectionGeneration;\n let remoteSyncBlocked = false;\n const baseDetachInput = {\n apiClient: state.runtime.apiClient,\n author,\n bindingId: binding.bindingId,\n blobId: binding.blobId,\n documentId: input.remoteDocumentId,\n execSql: generation.execSql,\n isRemoteSyncBlocked: (organizationId: string) => {\n remoteSyncBlocked =\n state.runtime.util.isRemoteSyncBlocked?.(organizationId) ?? false;\n return remoteSyncBlocked;\n },\n resolveProjectionUserKey: generation.resolveProjectionUserKey,\n slotId: binding.slotId,\n stillCurrent: contextIsCurrent,\n warmReferencedPrincipalPolicies: createRuntimePrincipalPolicyWarmer(\n state.runtime,\n ),\n };\n let detached = await detachDocumentAttachment({\n ...baseDetachInput,\n writerProjection: writerProjection ?? undefined,\n });\n if (!contextIsCurrent()) return false;\n if (!detached && writerProjection && !remoteSyncBlocked) {\n state.writerProjection = null;\n detached = await detachDocumentAttachment(baseDetachInput);\n if (!contextIsCurrent()) return false;\n }\n if (!detached) {\n return false;\n }\n\n installDocumentWriterProjection(\n state,\n detached.writerProjection,\n writerProjectionGeneration,\n );\n state.runtime.util.log(\n `Detached attachment ${binding.slotId} from document ${input.remoteDocumentId}.`,\n );\n return true;\n}\n"]}
@@ -1,9 +1,10 @@
1
1
  import { encodeVersionVector, exportFullHistorySnapshot, } from "../../../internal/loro/index.js";
2
- import { createDocumentWriterPublicKeyResolver, defaultDocumentsPersistence, deletePersistedDocument, reclaimDocumentOrphanBlobs, resolveDocumentCreateAuthor, syncRemoteDocument, validateDocumentSyncUpdateImports, } from "../../../workflows/documents/index.js";
2
+ import { createDocumentWriterPublicKeyResolver, deletePersistedDocument, resolveDocumentCreateAuthor, syncRemoteDocument, validateDocumentSyncUpdateImports, } from "../../../workflows/documents/index.js";
3
3
  import { createRuntimePrincipalPolicyWarmer } from "../../../workflows/principals/runtimePolicyWarmer.js";
4
4
  import { requestDocumentStoreSync } from "../registry.js";
5
5
  import { parkForAncestorRepair } from "./ancestorRepairPark.js";
6
6
  import { chainIdentityWrite } from "./identityWriteChain.js";
7
+ import { runDocumentOrphanMaintenance } from "./orphanMaintenance.js";
7
8
  import { invalidateDocumentStorePullContinuation } from "./pullContinuationInvalidation.js";
8
9
  import { markDocumentStoreRemoved, } from "./state.js";
9
10
  import { isDocumentStoreSyncGenerationCurrent, } from "./syncGeneration.js";
@@ -46,9 +47,7 @@ export async function deleteUpstreamDeletedDocument(state, generation, requestRe
46
47
  if (!deletionStarted || !removedInMutation) {
47
48
  return;
48
49
  }
49
- if (state.persistence === defaultDocumentsPersistence) {
50
- void reclaimDocumentOrphanBlobs(state.runtime);
51
- }
50
+ void runDocumentOrphanMaintenance(state);
52
51
  state.runtime.util.log(`Documents: removed local document ${state.localId} after remote deletion.`);
53
52
  });
54
53
  }
@@ -1 +1 @@
1
- {"version":3,"file":"syncRequest.js","sourceRoot":"","sources":["syncRequest.ts"],"names":[],"mappings":"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","sourcesContent":["import {\n encodeVersionVector,\n exportFullHistorySnapshot,\n} from \"@tearleads/loro\";\nimport {\n createDocumentWriterPublicKeyResolver,\n type DocumentRecord,\n defaultDocumentsPersistence,\n deletePersistedDocument,\n type ExecSql,\n type PendingUpdateRecord,\n reclaimDocumentOrphanBlobs,\n resolveDocumentCreateAuthor,\n syncRemoteDocument,\n validateDocumentSyncUpdateImports,\n} from \"../../../workflows/documents\";\nimport { createRuntimePrincipalPolicyWarmer } from \"../../../workflows/principals/runtimePolicyWarmer\";\nimport { requestDocumentStoreSync } from \"../registry\";\nimport { parkForAncestorRepair } from \"./ancestorRepairPark\";\nimport { chainIdentityWrite } from \"./identityWriteChain\";\nimport { invalidateDocumentStorePullContinuation } from \"./pullContinuationInvalidation\";\nimport {\n type DocumentState,\n type DocumentStoreState,\n type DocumentSyncAttempt,\n type EncapsulationKeyPair,\n markDocumentStoreRemoved,\n} from \"./state\";\nimport {\n type DocumentStoreSyncGeneration,\n isDocumentStoreSyncGenerationCurrent,\n} from \"./syncGeneration\";\nimport {\n documentIncomingUpdateIsolationFailureHandler,\n documentRevalidationFailureHandler,\n documentTerminalSubmitFailureHandler,\n} from \"./syncShared\";\nimport { documentSyncContextMatches } from \"./syncUpdateImport\";\n\n/**\n * Destroying queued local edits here is settled policy, not an oversight\n * (docs/sync-edge-cases.md row 1): deletion is a privacy operation, so an\n * authoritative remote delete removes the document and its unsynced edits\n * everywhere with no preservation copy. Quarantine/export were rejected —\n * they retain or re-upload content the user deliberately destroyed.\n */\nexport async function deleteUpstreamDeletedDocument(\n state: DocumentStoreState,\n generation: DocumentStoreSyncGeneration,\n requestRecord: DocumentRecord,\n remoteDocumentId: string,\n commitPurgeProof?: (transactionExecSql: ExecSql) => Promise<void>,\n) {\n await chainIdentityWrite(state, async () => {\n if (\n !isDocumentStoreSyncGenerationCurrent(state, generation) ||\n !documentSyncContextMatches(state.record, requestRecord, remoteDocumentId)\n ) {\n return;\n }\n\n const canDeleteCapturedDocument = () =>\n isDocumentStoreSyncGenerationCurrent(state, generation) &&\n documentSyncContextMatches(state.record, requestRecord, remoteDocumentId);\n let removedInMutation = false;\n const deletionStarted = await deletePersistedDocument({\n beforeDeleteInTransaction: commitPurgeProof,\n canStartDurableMutation: canDeleteCapturedDocument,\n documentProjectors: state.runtime.infra.documentProjectors,\n execSql: generation.execSql,\n expectedRecord: requestRecord,\n localId: requestRecord.id,\n // Invalidate the store generation INSIDE the deletion mutation: a\n // terminal failure handler queued behind it then observes the stale\n // generation and cannot resurrect a failure row the deletes removed.\n onDeletedInMutation: () => {\n if (canDeleteCapturedDocument()) {\n markDocumentStoreRemoved(state);\n removedInMutation = true;\n }\n },\n persistence: state.persistence,\n });\n if (!deletionStarted || !removedInMutation) {\n return;\n }\n if (state.persistence === defaultDocumentsPersistence) {\n void reclaimDocumentOrphanBlobs(state.runtime);\n }\n state.runtime.util.log(\n `Documents: removed local document ${state.localId} after remote deletion.`,\n );\n });\n}\n\nfunction createDocumentSyncAttempt(input: {\n currentRecord: DocumentRecord;\n outgoingUpdateCount: number;\n requestedPullContinuation: DocumentStoreState[\"pullContinuation\"];\n synced: DocumentSyncAttempt[\"synced\"];\n writerProjectionGeneration: number;\n}): DocumentSyncAttempt {\n return {\n consumedPullContinuation:\n input.synced.plan.request.pullCursor === undefined\n ? null\n : input.requestedPullContinuation,\n outgoingUpdateCount: input.outgoingUpdateCount,\n requestRecord: input.currentRecord,\n synced: input.synced,\n writerProjectionGeneration: input.writerProjectionGeneration,\n };\n}\n\ninterface RequestRemoteDocumentSyncInput {\n allowRecoveryBaseline?: boolean | undefined;\n currentDoc: DocumentState;\n currentRecord: DocumentRecord;\n encapsulationKeyPair: EncapsulationKeyPair;\n generation: DocumentStoreSyncGeneration;\n onOutgoingUpdatesMaterialized?:\n | ((updateIds: readonly string[]) => void)\n | undefined;\n pendingUpdates: PendingUpdateRecord[];\n queuedUpdateCount?: number | undefined;\n state: DocumentStoreState;\n unavailableWriterLogMessage: string;\n}\n\nfunction runRemoteDocumentSync(\n input: RequestRemoteDocumentSyncInput,\n author: NonNullable<ReturnType<typeof resolveDocumentCreateAuthor>>,\n documentId: string,\n requestedPullContinuation: DocumentStoreState[\"pullContinuation\"],\n) {\n const { currentDoc, currentRecord, generation, pendingUpdates, state } =\n input;\n const runtime = state.runtime;\n const writerProjectionGenerationAtStart = state.writerProjectionGeneration;\n state.awaitingAncestorRepair = false;\n return syncRemoteDocument({\n apiClient: runtime.apiClient,\n author,\n // Heal a stale content-key bundle from this full-history snapshot. Rotation\n // settlement disables this: it may submit only the ordinary local stream.\n ...(input.allowRecoveryBaseline === false\n ? {}\n : {\n buildRotationSnapshot: async () =>\n exportFullHistorySnapshot(currentDoc),\n }),\n documentId,\n execSql: runtime.infra.execSql,\n isRemoteSyncBlocked: runtime.util.isRemoteSyncBlocked,\n localVersionVector: encodeVersionVector(currentDoc),\n minLsn: currentRecord.lastCommitLsn ?? undefined,\n onRemoteDocumentDeleted: ({ commitPurgeProof, documentId }) =>\n deleteUpstreamDeletedDocument(\n state,\n generation,\n currentRecord,\n documentId,\n commitPurgeProof,\n ),\n onSyncAbandoned: (reason) => {\n if (!isDocumentStoreSyncGenerationCurrent(state, generation)) return;\n const hintLandedMidPass = parkForAncestorRepair(\n state,\n reason,\n writerProjectionGenerationAtStart,\n );\n if (hintLandedMidPass) requestDocumentStoreSync(state);\n },\n onSyncTrace: (line) => runtime.util.log(`Documents: ${line}`),\n onReadOnlyProjectionFailure: documentRevalidationFailureHandler(\n state,\n generation,\n ),\n onIncomingUpdateIsolationFailure:\n documentIncomingUpdateIsolationFailureHandler(state, generation),\n onOutgoingUpdatesMaterialized: input.onOutgoingUpdatesMaterialized,\n onPullContinuationInvalidated: (continuation) =>\n invalidateDocumentStorePullContinuation({\n continuation,\n currentRecord,\n generation,\n state,\n }),\n onTerminalSubmitFailure: documentTerminalSubmitFailureHandler(\n state,\n generation,\n ),\n pendingUpdates,\n persistedState: currentRecord,\n pullContinuation: requestedPullContinuation ?? undefined,\n rekeyPendingUpdate: state.persistence.rekeyPendingUpdate,\n resolveProjectionUserKey: state.resolveProjectionUserKey,\n resolveWriterPublicKey: createDocumentWriterPublicKeyResolver({\n logPrefix: \"Documents\",\n runtime,\n writerKeyLabel: \"writer key\",\n }),\n stillCurrent: () => isDocumentStoreSyncGenerationCurrent(state, generation),\n targetSecretKey: input.encapsulationKeyPair.secretKey,\n validateIncomingUpdates: (result) =>\n validateDocumentSyncUpdateImports({\n currentDocument: currentDoc,\n decryptedUpdates: result.decryptedUpdates,\n responseUpdates: result.response.updates,\n }),\n warmReferencedPrincipalPolicies:\n createRuntimePrincipalPolicyWarmer(runtime),\n writerProjection:\n state.writerProjection?.documentId === currentRecord.documentId\n ? state.writerProjection\n : undefined,\n });\n}\n\nexport async function requestRemoteDocumentSync(\n input: RequestRemoteDocumentSyncInput,\n): Promise<DocumentSyncAttempt | null> {\n const {\n currentRecord,\n generation,\n pendingUpdates,\n state,\n unavailableWriterLogMessage,\n } = input;\n if (!isDocumentStoreSyncGenerationCurrent(state, generation)) return null;\n // Captured before the network round trip: a hint that lands meanwhile\n // must keep the response's projection out of the cache.\n const writerProjectionGeneration = state.writerProjectionGeneration;\n\n const runtime = state.runtime;\n if (!currentRecord.documentId) return null;\n\n const requestedPullContinuation = state.pullContinuation;\n\n const author = resolveDocumentCreateAuthor(runtime);\n if (!author) {\n runtime.util.log(unavailableWriterLogMessage);\n return null;\n }\n\n const synced = await runRemoteDocumentSync(\n input,\n author,\n currentRecord.documentId,\n requestedPullContinuation,\n );\n if (!isDocumentStoreSyncGenerationCurrent(state, generation)) return null;\n if (!synced) return null;\n\n const outgoingUpdateCount = input.queuedUpdateCount ?? pendingUpdates.length;\n return createDocumentSyncAttempt({\n currentRecord,\n outgoingUpdateCount,\n requestedPullContinuation,\n synced,\n writerProjectionGeneration,\n });\n}\n"]}
1
+ {"version":3,"file":"syncRequest.js","sourceRoot":"","sources":["syncRequest.ts"],"names":[],"mappings":"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","sourcesContent":["import {\n encodeVersionVector,\n exportFullHistorySnapshot,\n} from \"@tearleads/loro\";\nimport {\n createDocumentWriterPublicKeyResolver,\n type DocumentRecord,\n deletePersistedDocument,\n type ExecSql,\n type PendingUpdateRecord,\n resolveDocumentCreateAuthor,\n syncRemoteDocument,\n validateDocumentSyncUpdateImports,\n} from \"../../../workflows/documents\";\nimport { createRuntimePrincipalPolicyWarmer } from \"../../../workflows/principals/runtimePolicyWarmer\";\nimport { requestDocumentStoreSync } from \"../registry\";\nimport { parkForAncestorRepair } from \"./ancestorRepairPark\";\nimport { chainIdentityWrite } from \"./identityWriteChain\";\nimport { runDocumentOrphanMaintenance } from \"./orphanMaintenance\";\nimport { invalidateDocumentStorePullContinuation } from \"./pullContinuationInvalidation\";\nimport {\n type DocumentState,\n type DocumentStoreState,\n type DocumentSyncAttempt,\n type EncapsulationKeyPair,\n markDocumentStoreRemoved,\n} from \"./state\";\nimport {\n type DocumentStoreSyncGeneration,\n isDocumentStoreSyncGenerationCurrent,\n} from \"./syncGeneration\";\nimport {\n documentIncomingUpdateIsolationFailureHandler,\n documentRevalidationFailureHandler,\n documentTerminalSubmitFailureHandler,\n} from \"./syncShared\";\nimport { documentSyncContextMatches } from \"./syncUpdateImport\";\n\n/**\n * Destroying queued local edits here is settled policy, not an oversight\n * (docs/sync-edge-cases.md row 1): deletion is a privacy operation, so an\n * authoritative remote delete removes the document and its unsynced edits\n * everywhere with no preservation copy. Quarantine/export were rejected —\n * they retain or re-upload content the user deliberately destroyed.\n */\nexport async function deleteUpstreamDeletedDocument(\n state: DocumentStoreState,\n generation: DocumentStoreSyncGeneration,\n requestRecord: DocumentRecord,\n remoteDocumentId: string,\n commitPurgeProof?: (transactionExecSql: ExecSql) => Promise<void>,\n) {\n await chainIdentityWrite(state, async () => {\n if (\n !isDocumentStoreSyncGenerationCurrent(state, generation) ||\n !documentSyncContextMatches(state.record, requestRecord, remoteDocumentId)\n ) {\n return;\n }\n\n const canDeleteCapturedDocument = () =>\n isDocumentStoreSyncGenerationCurrent(state, generation) &&\n documentSyncContextMatches(state.record, requestRecord, remoteDocumentId);\n let removedInMutation = false;\n const deletionStarted = await deletePersistedDocument({\n beforeDeleteInTransaction: commitPurgeProof,\n canStartDurableMutation: canDeleteCapturedDocument,\n documentProjectors: state.runtime.infra.documentProjectors,\n execSql: generation.execSql,\n expectedRecord: requestRecord,\n localId: requestRecord.id,\n // Invalidate the store generation INSIDE the deletion mutation: a\n // terminal failure handler queued behind it then observes the stale\n // generation and cannot resurrect a failure row the deletes removed.\n onDeletedInMutation: () => {\n if (canDeleteCapturedDocument()) {\n markDocumentStoreRemoved(state);\n removedInMutation = true;\n }\n },\n persistence: state.persistence,\n });\n if (!deletionStarted || !removedInMutation) {\n return;\n }\n void runDocumentOrphanMaintenance(state);\n state.runtime.util.log(\n `Documents: removed local document ${state.localId} after remote deletion.`,\n );\n });\n}\n\nfunction createDocumentSyncAttempt(input: {\n currentRecord: DocumentRecord;\n outgoingUpdateCount: number;\n requestedPullContinuation: DocumentStoreState[\"pullContinuation\"];\n synced: DocumentSyncAttempt[\"synced\"];\n writerProjectionGeneration: number;\n}): DocumentSyncAttempt {\n return {\n consumedPullContinuation:\n input.synced.plan.request.pullCursor === undefined\n ? null\n : input.requestedPullContinuation,\n outgoingUpdateCount: input.outgoingUpdateCount,\n requestRecord: input.currentRecord,\n synced: input.synced,\n writerProjectionGeneration: input.writerProjectionGeneration,\n };\n}\n\ninterface RequestRemoteDocumentSyncInput {\n allowRecoveryBaseline?: boolean | undefined;\n currentDoc: DocumentState;\n currentRecord: DocumentRecord;\n encapsulationKeyPair: EncapsulationKeyPair;\n generation: DocumentStoreSyncGeneration;\n onOutgoingUpdatesMaterialized?:\n | ((updateIds: readonly string[]) => void)\n | undefined;\n pendingUpdates: PendingUpdateRecord[];\n queuedUpdateCount?: number | undefined;\n state: DocumentStoreState;\n unavailableWriterLogMessage: string;\n}\n\nfunction runRemoteDocumentSync(\n input: RequestRemoteDocumentSyncInput,\n author: NonNullable<ReturnType<typeof resolveDocumentCreateAuthor>>,\n documentId: string,\n requestedPullContinuation: DocumentStoreState[\"pullContinuation\"],\n) {\n const { currentDoc, currentRecord, generation, pendingUpdates, state } =\n input;\n const runtime = state.runtime;\n const writerProjectionGenerationAtStart = state.writerProjectionGeneration;\n state.awaitingAncestorRepair = false;\n return syncRemoteDocument({\n apiClient: runtime.apiClient,\n author,\n // Heal a stale content-key bundle from this full-history snapshot. Rotation\n // settlement disables this: it may submit only the ordinary local stream.\n ...(input.allowRecoveryBaseline === false\n ? {}\n : {\n buildRotationSnapshot: async () =>\n exportFullHistorySnapshot(currentDoc),\n }),\n documentId,\n execSql: runtime.infra.execSql,\n isRemoteSyncBlocked: runtime.util.isRemoteSyncBlocked,\n localVersionVector: encodeVersionVector(currentDoc),\n minLsn: currentRecord.lastCommitLsn ?? undefined,\n onRemoteDocumentDeleted: ({ commitPurgeProof, documentId }) =>\n deleteUpstreamDeletedDocument(\n state,\n generation,\n currentRecord,\n documentId,\n commitPurgeProof,\n ),\n onSyncAbandoned: (reason) => {\n if (!isDocumentStoreSyncGenerationCurrent(state, generation)) return;\n const hintLandedMidPass = parkForAncestorRepair(\n state,\n reason,\n writerProjectionGenerationAtStart,\n );\n if (hintLandedMidPass) requestDocumentStoreSync(state);\n },\n onSyncTrace: (line) => runtime.util.log(`Documents: ${line}`),\n onReadOnlyProjectionFailure: documentRevalidationFailureHandler(\n state,\n generation,\n ),\n onIncomingUpdateIsolationFailure:\n documentIncomingUpdateIsolationFailureHandler(state, generation),\n onOutgoingUpdatesMaterialized: input.onOutgoingUpdatesMaterialized,\n onPullContinuationInvalidated: (continuation) =>\n invalidateDocumentStorePullContinuation({\n continuation,\n currentRecord,\n generation,\n state,\n }),\n onTerminalSubmitFailure: documentTerminalSubmitFailureHandler(\n state,\n generation,\n ),\n pendingUpdates,\n persistedState: currentRecord,\n pullContinuation: requestedPullContinuation ?? undefined,\n rekeyPendingUpdate: state.persistence.rekeyPendingUpdate,\n resolveProjectionUserKey: state.resolveProjectionUserKey,\n resolveWriterPublicKey: createDocumentWriterPublicKeyResolver({\n logPrefix: \"Documents\",\n runtime,\n writerKeyLabel: \"writer key\",\n }),\n stillCurrent: () => isDocumentStoreSyncGenerationCurrent(state, generation),\n targetSecretKey: input.encapsulationKeyPair.secretKey,\n validateIncomingUpdates: (result) =>\n validateDocumentSyncUpdateImports({\n currentDocument: currentDoc,\n decryptedUpdates: result.decryptedUpdates,\n responseUpdates: result.response.updates,\n }),\n warmReferencedPrincipalPolicies:\n createRuntimePrincipalPolicyWarmer(runtime),\n writerProjection:\n state.writerProjection?.documentId === currentRecord.documentId\n ? state.writerProjection\n : undefined,\n });\n}\n\nexport async function requestRemoteDocumentSync(\n input: RequestRemoteDocumentSyncInput,\n): Promise<DocumentSyncAttempt | null> {\n const {\n currentRecord,\n generation,\n pendingUpdates,\n state,\n unavailableWriterLogMessage,\n } = input;\n if (!isDocumentStoreSyncGenerationCurrent(state, generation)) return null;\n // Captured before the network round trip: a hint that lands meanwhile\n // must keep the response's projection out of the cache.\n const writerProjectionGeneration = state.writerProjectionGeneration;\n\n const runtime = state.runtime;\n if (!currentRecord.documentId) return null;\n\n const requestedPullContinuation = state.pullContinuation;\n\n const author = resolveDocumentCreateAuthor(runtime);\n if (!author) {\n runtime.util.log(unavailableWriterLogMessage);\n return null;\n }\n\n const synced = await runRemoteDocumentSync(\n input,\n author,\n currentRecord.documentId,\n requestedPullContinuation,\n );\n if (!isDocumentStoreSyncGenerationCurrent(state, generation)) return null;\n if (!synced) return null;\n\n const outgoingUpdateCount = input.queuedUpdateCount ?? pendingUpdates.length;\n return createDocumentSyncAttempt({\n currentRecord,\n outgoingUpdateCount,\n requestedPullContinuation,\n synced,\n writerProjectionGeneration,\n });\n}\n"]}
@@ -71,8 +71,8 @@ export async function purgeRemoteContainerDocument(input) {
71
71
  runtime.util.log(`Container contents: failed to purge note ${noteId} (document ${documentId})`);
72
72
  return null;
73
73
  }
74
- if (deleted && persistence === defaultDocumentsPersistence) {
75
- void reclaimDocumentOrphanBlobs(runtime);
74
+ if (deleted) {
75
+ void reclaimDocumentOrphanBlobs(runtime, persistence);
76
76
  }
77
77
  runtime.util.log(`Container contents: purged note ${noteId} (document ${documentId})`);
78
78
  return response;
@@ -111,8 +111,8 @@ export async function purgeLocalContainerDocument(input) {
111
111
  runtime.util.log(`Container contents: local-only purge refused for note ${noteId} because its identity changed`);
112
112
  return null;
113
113
  }
114
- if (deleted && persistence === defaultDocumentsPersistence) {
115
- void reclaimDocumentOrphanBlobs(runtime);
114
+ if (deleted) {
115
+ void reclaimDocumentOrphanBlobs(runtime, persistence);
116
116
  }
117
117
  runtime.util.log(`Container contents: purged local-only note ${noteId}`);
118
118
  return {
@@ -1 +1 @@
1
- {"version":3,"file":"documentPurge.js","sourceRoot":"","sources":["documentPurge.ts"],"names":[],"mappings":"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","sourcesContent":["import type { DocumentPurgeResponse } from \"@tearleads/validators/response\";\nimport type { StoredDocumentKind } from \"../../data/documents/documentKinds\";\nimport { errorMessage } from \"../../data/errorMessage\";\nimport type { ProjectionUserKeyResolver } from \"../../data/keyingProjectionVerification\";\nimport { reportAndRethrowKeyingVerificationError } from \"../../data/keyingProjectionVerification/error\";\nimport { rethrowProjectionVerificationCancelled } from \"../../data/keyingProjectionVerification/types\";\nimport {\n type DocumentsPersistence,\n defaultDocumentsPersistence,\n deletePersistedDocument,\n purgeRemoteDocument,\n reclaimDocumentOrphanBlobs,\n resolveDocumentCreateAuthor,\n} from \"../documents\";\nimport { ensureDocumentClientProjectionTables } from \"../documents/persistence\";\nimport { createRuntimePrincipalPolicyWarmer } from \"../principals/runtimePolicyWarmer\";\nimport type { ContainerContentsWorkflowRuntime } from \"./runtime\";\n\ntype ContainerDocumentPurgeApi = Pick<\n ContainerContentsWorkflowRuntime[\"apiClient\"],\n | \"getPrincipalPolicyPages\"\n | \"getProjectionPolicyHistoryPages\"\n | \"getDocumentPurgeProof\"\n | \"getDocumentWriterProjectionResult\"\n | \"purgeDocument\"\n>;\ntype DocumentPurgeResult =\n | DocumentPurgeResponse\n | {\n readonly documentId: string;\n readonly purgedAt: string;\n readonly reclaimedBlobStorageKeys: readonly string[];\n }\n | null;\n\ninterface ContainerDocumentPurgeRuntime\n extends Pick<\n ContainerContentsWorkflowRuntime,\n | \"auth\"\n | \"crypto\"\n | \"infra\"\n | \"resolveTrustedUserIdentity\"\n | \"state\"\n | \"util\"\n > {\n apiClient: ContainerDocumentPurgeApi;\n}\n\ninterface PurgeRemoteContainerDocumentInput {\n documentId: string;\n documentKind: StoredDocumentKind;\n noteId: string;\n persistence?: DocumentsPersistence | undefined;\n resolveProjectionUserKey: ProjectionUserKeyResolver;\n runtime: ContainerDocumentPurgeRuntime;\n}\n\nasync function deleteAbsentDocumentState(input: {\n readonly commitPurgeProof: (\n transactionExecSql: ContainerDocumentPurgeRuntime[\"infra\"][\"execSql\"],\n ) => Promise<void>;\n readonly documentId: string;\n readonly documentKind: StoredDocumentKind;\n readonly noteId: string;\n readonly persistence: DocumentsPersistence;\n readonly runtime: ContainerDocumentPurgeRuntime;\n}): Promise<boolean> {\n return input.persistence.deleteDocumentSideRowsIfAbsent(\n input.runtime.infra.execSql,\n input.noteId,\n input.documentId,\n async (transactionExecSql) => {\n await input.runtime.infra.documentProjectors.deleteStoredDocumentClientProjection(\n {\n documentKind: input.documentKind,\n execSql: transactionExecSql,\n localId: input.noteId,\n },\n );\n await input.commitPurgeProof(transactionExecSql);\n },\n );\n}\n\nasync function commitVerifiedDocumentPurge(input: {\n readonly commitPurgeProof: (\n transactionExecSql: ContainerDocumentPurgeRuntime[\"infra\"][\"execSql\"],\n ) => Promise<void>;\n readonly documentId: string;\n readonly documentKind: StoredDocumentKind;\n readonly noteId: string;\n readonly persistence: DocumentsPersistence;\n readonly runtime: ContainerDocumentPurgeRuntime;\n}): Promise<boolean> {\n await input.persistence.ensureSchema(input.runtime.infra.execSql);\n await ensureDocumentClientProjectionTables({\n documentProjectors: input.runtime.infra.documentProjectors,\n execSql: input.runtime.infra.execSql,\n });\n const expectedRecord = await input.persistence.loadDocument(\n input.runtime.infra.execSql,\n input.noteId,\n );\n if (expectedRecord && expectedRecord.documentId !== input.documentId) {\n throw new Error(\n \"Local document identity changed while its remote purge was committing\",\n );\n }\n const deleted = expectedRecord\n ? await deletePersistedDocument({\n beforeDeleteInTransaction: input.commitPurgeProof,\n documentProjectors: input.runtime.infra.documentProjectors,\n execSql: input.runtime.infra.execSql,\n expectedRecord,\n localId: input.noteId,\n persistence: input.persistence,\n })\n : await deleteAbsentDocumentState(input);\n if (!deleted) {\n throw new Error(\n \"Local document state changed while its remote purge was committing\",\n );\n }\n return true;\n}\n\nexport async function purgeRemoteContainerDocument(\n input: PurgeRemoteContainerDocumentInput,\n): Promise<DocumentPurgeResult> {\n const { documentId, noteId, runtime } = input;\n const persistence = input.persistence ?? defaultDocumentsPersistence;\n try {\n const author = resolveDocumentCreateAuthor(runtime);\n if (!author) {\n runtime.util.log(\n `Container contents: failed to purge note ${noteId} because the local signing context is unavailable`,\n );\n return null;\n }\n let deleted = false;\n const response = await purgeRemoteDocument({\n apiClient: runtime.apiClient,\n author,\n documentId,\n execSql: runtime.infra.execSql,\n onVerifiedPurge: async ({ commitPurgeProof }) => {\n deleted = await commitVerifiedDocumentPurge({\n commitPurgeProof,\n documentId,\n documentKind: input.documentKind,\n noteId,\n persistence,\n runtime,\n });\n },\n resolveProjectionUserKey: input.resolveProjectionUserKey,\n warmReferencedPrincipalPolicies:\n createRuntimePrincipalPolicyWarmer(runtime),\n });\n if (!response) {\n runtime.util.log(\n `Container contents: failed to purge note ${noteId} (document ${documentId})`,\n );\n return null;\n }\n if (deleted && persistence === defaultDocumentsPersistence) {\n void reclaimDocumentOrphanBlobs(runtime);\n }\n runtime.util.log(\n `Container contents: purged note ${noteId} (document ${documentId})`,\n );\n return response;\n } catch (error) {\n // A cancelled verification is neither a failure nor a completed purge; the\n // caller that owns the generation decides what to do with it.\n rethrowProjectionVerificationCancelled(error);\n await reportAndRethrowKeyingVerificationError(\n error,\n runtime.util.reportSecurityIncident,\n {\n objectId: documentId,\n objectKind: \"document\",\n operation: \"document.purge\",\n },\n );\n runtime.util.log(\n `Container contents: failed to purge note ${noteId} (document ${documentId}): ${errorMessage(error)}`,\n );\n return null;\n }\n}\n\ninterface ContainerDocumentLocalPurgeRuntime\n extends Pick<ContainerContentsWorkflowRuntime, \"infra\" | \"util\"> {}\n\nexport async function purgeLocalContainerDocument(input: {\n noteId: string;\n persistence?: DocumentsPersistence | undefined;\n runtime: ContainerDocumentLocalPurgeRuntime;\n}): Promise<DocumentPurgeResult> {\n const { noteId, runtime } = input;\n const persistence = input.persistence ?? defaultDocumentsPersistence;\n try {\n await persistence.ensureSchema(runtime.infra.execSql);\n const expectedRecord = await persistence.loadDocument(\n runtime.infra.execSql,\n noteId,\n );\n if (!expectedRecord || expectedRecord.documentId !== null) {\n runtime.util.log(\n `Container contents: local-only purge refused for note ${noteId} because it is absent or remote-backed`,\n );\n return null;\n }\n const deleted = await deletePersistedDocument({\n documentProjectors: runtime.infra.documentProjectors,\n execSql: runtime.infra.execSql,\n expectedRecord,\n localId: noteId,\n persistence,\n });\n if (!deleted) {\n runtime.util.log(\n `Container contents: local-only purge refused for note ${noteId} because its identity changed`,\n );\n return null;\n }\n if (deleted && persistence === defaultDocumentsPersistence) {\n void reclaimDocumentOrphanBlobs(runtime);\n }\n runtime.util.log(`Container contents: purged local-only note ${noteId}`);\n return {\n documentId: noteId,\n purgedAt: new Date().toISOString(),\n reclaimedBlobStorageKeys: [],\n };\n } catch (error) {\n runtime.util.log(\n `Container contents: failed to purge local-only note ${noteId}: ${errorMessage(error)}`,\n );\n return null;\n }\n}\n"]}
1
+ {"version":3,"file":"documentPurge.js","sourceRoot":"","sources":["documentPurge.ts"],"names":[],"mappings":"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","sourcesContent":["import type { DocumentPurgeResponse } from \"@tearleads/validators/response\";\nimport type { StoredDocumentKind } from \"../../data/documents/documentKinds\";\nimport { errorMessage } from \"../../data/errorMessage\";\nimport type { ProjectionUserKeyResolver } from \"../../data/keyingProjectionVerification\";\nimport { reportAndRethrowKeyingVerificationError } from \"../../data/keyingProjectionVerification/error\";\nimport { rethrowProjectionVerificationCancelled } from \"../../data/keyingProjectionVerification/types\";\nimport {\n type DocumentsPersistence,\n defaultDocumentsPersistence,\n deletePersistedDocument,\n purgeRemoteDocument,\n reclaimDocumentOrphanBlobs,\n resolveDocumentCreateAuthor,\n} from \"../documents\";\nimport { ensureDocumentClientProjectionTables } from \"../documents/persistence\";\nimport { createRuntimePrincipalPolicyWarmer } from \"../principals/runtimePolicyWarmer\";\nimport type { ContainerContentsWorkflowRuntime } from \"./runtime\";\n\ntype ContainerDocumentPurgeApi = Pick<\n ContainerContentsWorkflowRuntime[\"apiClient\"],\n | \"getPrincipalPolicyPages\"\n | \"getProjectionPolicyHistoryPages\"\n | \"getDocumentPurgeProof\"\n | \"getDocumentWriterProjectionResult\"\n | \"purgeDocument\"\n>;\ntype DocumentPurgeResult =\n | DocumentPurgeResponse\n | {\n readonly documentId: string;\n readonly purgedAt: string;\n readonly reclaimedBlobStorageKeys: readonly string[];\n }\n | null;\n\ninterface ContainerDocumentPurgeRuntime\n extends Pick<\n ContainerContentsWorkflowRuntime,\n | \"auth\"\n | \"crypto\"\n | \"infra\"\n | \"resolveTrustedUserIdentity\"\n | \"state\"\n | \"util\"\n > {\n apiClient: ContainerDocumentPurgeApi;\n}\n\ninterface PurgeRemoteContainerDocumentInput {\n documentId: string;\n documentKind: StoredDocumentKind;\n noteId: string;\n persistence?: DocumentsPersistence | undefined;\n resolveProjectionUserKey: ProjectionUserKeyResolver;\n runtime: ContainerDocumentPurgeRuntime;\n}\n\nasync function deleteAbsentDocumentState(input: {\n readonly commitPurgeProof: (\n transactionExecSql: ContainerDocumentPurgeRuntime[\"infra\"][\"execSql\"],\n ) => Promise<void>;\n readonly documentId: string;\n readonly documentKind: StoredDocumentKind;\n readonly noteId: string;\n readonly persistence: DocumentsPersistence;\n readonly runtime: ContainerDocumentPurgeRuntime;\n}): Promise<boolean> {\n return input.persistence.deleteDocumentSideRowsIfAbsent(\n input.runtime.infra.execSql,\n input.noteId,\n input.documentId,\n async (transactionExecSql) => {\n await input.runtime.infra.documentProjectors.deleteStoredDocumentClientProjection(\n {\n documentKind: input.documentKind,\n execSql: transactionExecSql,\n localId: input.noteId,\n },\n );\n await input.commitPurgeProof(transactionExecSql);\n },\n );\n}\n\nasync function commitVerifiedDocumentPurge(input: {\n readonly commitPurgeProof: (\n transactionExecSql: ContainerDocumentPurgeRuntime[\"infra\"][\"execSql\"],\n ) => Promise<void>;\n readonly documentId: string;\n readonly documentKind: StoredDocumentKind;\n readonly noteId: string;\n readonly persistence: DocumentsPersistence;\n readonly runtime: ContainerDocumentPurgeRuntime;\n}): Promise<boolean> {\n await input.persistence.ensureSchema(input.runtime.infra.execSql);\n await ensureDocumentClientProjectionTables({\n documentProjectors: input.runtime.infra.documentProjectors,\n execSql: input.runtime.infra.execSql,\n });\n const expectedRecord = await input.persistence.loadDocument(\n input.runtime.infra.execSql,\n input.noteId,\n );\n if (expectedRecord && expectedRecord.documentId !== input.documentId) {\n throw new Error(\n \"Local document identity changed while its remote purge was committing\",\n );\n }\n const deleted = expectedRecord\n ? await deletePersistedDocument({\n beforeDeleteInTransaction: input.commitPurgeProof,\n documentProjectors: input.runtime.infra.documentProjectors,\n execSql: input.runtime.infra.execSql,\n expectedRecord,\n localId: input.noteId,\n persistence: input.persistence,\n })\n : await deleteAbsentDocumentState(input);\n if (!deleted) {\n throw new Error(\n \"Local document state changed while its remote purge was committing\",\n );\n }\n return true;\n}\n\nexport async function purgeRemoteContainerDocument(\n input: PurgeRemoteContainerDocumentInput,\n): Promise<DocumentPurgeResult> {\n const { documentId, noteId, runtime } = input;\n const persistence = input.persistence ?? defaultDocumentsPersistence;\n try {\n const author = resolveDocumentCreateAuthor(runtime);\n if (!author) {\n runtime.util.log(\n `Container contents: failed to purge note ${noteId} because the local signing context is unavailable`,\n );\n return null;\n }\n let deleted = false;\n const response = await purgeRemoteDocument({\n apiClient: runtime.apiClient,\n author,\n documentId,\n execSql: runtime.infra.execSql,\n onVerifiedPurge: async ({ commitPurgeProof }) => {\n deleted = await commitVerifiedDocumentPurge({\n commitPurgeProof,\n documentId,\n documentKind: input.documentKind,\n noteId,\n persistence,\n runtime,\n });\n },\n resolveProjectionUserKey: input.resolveProjectionUserKey,\n warmReferencedPrincipalPolicies:\n createRuntimePrincipalPolicyWarmer(runtime),\n });\n if (!response) {\n runtime.util.log(\n `Container contents: failed to purge note ${noteId} (document ${documentId})`,\n );\n return null;\n }\n if (deleted) {\n void reclaimDocumentOrphanBlobs(runtime, persistence);\n }\n runtime.util.log(\n `Container contents: purged note ${noteId} (document ${documentId})`,\n );\n return response;\n } catch (error) {\n // A cancelled verification is neither a failure nor a completed purge; the\n // caller that owns the generation decides what to do with it.\n rethrowProjectionVerificationCancelled(error);\n await reportAndRethrowKeyingVerificationError(\n error,\n runtime.util.reportSecurityIncident,\n {\n objectId: documentId,\n objectKind: \"document\",\n operation: \"document.purge\",\n },\n );\n runtime.util.log(\n `Container contents: failed to purge note ${noteId} (document ${documentId}): ${errorMessage(error)}`,\n );\n return null;\n }\n}\n\ninterface ContainerDocumentLocalPurgeRuntime\n extends Pick<ContainerContentsWorkflowRuntime, \"infra\" | \"util\"> {}\n\nexport async function purgeLocalContainerDocument(input: {\n noteId: string;\n persistence?: DocumentsPersistence | undefined;\n runtime: ContainerDocumentLocalPurgeRuntime;\n}): Promise<DocumentPurgeResult> {\n const { noteId, runtime } = input;\n const persistence = input.persistence ?? defaultDocumentsPersistence;\n try {\n await persistence.ensureSchema(runtime.infra.execSql);\n const expectedRecord = await persistence.loadDocument(\n runtime.infra.execSql,\n noteId,\n );\n if (!expectedRecord || expectedRecord.documentId !== null) {\n runtime.util.log(\n `Container contents: local-only purge refused for note ${noteId} because it is absent or remote-backed`,\n );\n return null;\n }\n const deleted = await deletePersistedDocument({\n documentProjectors: runtime.infra.documentProjectors,\n execSql: runtime.infra.execSql,\n expectedRecord,\n localId: noteId,\n persistence,\n });\n if (!deleted) {\n runtime.util.log(\n `Container contents: local-only purge refused for note ${noteId} because its identity changed`,\n );\n return null;\n }\n if (deleted) {\n void reclaimDocumentOrphanBlobs(runtime, persistence);\n }\n runtime.util.log(`Container contents: purged local-only note ${noteId}`);\n return {\n documentId: noteId,\n purgedAt: new Date().toISOString(),\n reclaimedBlobStorageKeys: [],\n };\n } catch (error) {\n runtime.util.log(\n `Container contents: failed to purge local-only note ${noteId}: ${errorMessage(error)}`,\n );\n return null;\n }\n}\n"]}
@@ -0,0 +1,20 @@
1
+ const states = new WeakMap();
2
+ export function orphanBlobReclaimState(persistence, execSql) {
3
+ let connections = states.get(persistence);
4
+ if (!connections) {
5
+ connections = new WeakMap();
6
+ states.set(persistence, connections);
7
+ }
8
+ let state = connections.get(execSql);
9
+ if (!state) {
10
+ state = {
11
+ deferredStorageKeys: new Map(),
12
+ rerun: false,
13
+ running: undefined,
14
+ sweepKey: `sweep:document-orphans:${crypto.randomUUID()}`,
15
+ };
16
+ connections.set(execSql, state);
17
+ }
18
+ return state;
19
+ }
20
+ //# sourceMappingURL=orphanBlobReclaimState.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"orphanBlobReclaimState.js","sourceRoot":"","sources":["orphanBlobReclaimState.ts"],"names":[],"mappings":"AAUA,MAAM,MAAM,GAAG,IAAI,OAAO,EAGvB,CAAC;AAEJ,MAAM,UAAU,sBAAsB,CACpC,WAA0C,EAC1C,OAAgB;IAEhB,IAAI,WAAW,GAAG,MAAM,CAAC,GAAG,CAAC,WAAW,CAAC,CAAC;IAC1C,IAAI,CAAC,WAAW,EAAE,CAAC;QACjB,WAAW,GAAG,IAAI,OAAO,EAAE,CAAC;QAC5B,MAAM,CAAC,GAAG,CAAC,WAAW,EAAE,WAAW,CAAC,CAAC;IACvC,CAAC;IACD,IAAI,KAAK,GAAG,WAAW,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC;IACrC,IAAI,CAAC,KAAK,EAAE,CAAC;QACX,KAAK,GAAG;YACN,mBAAmB,EAAE,IAAI,GAAG,EAAE;YAC9B,KAAK,EAAE,KAAK;YACZ,OAAO,EAAE,SAAS;YAClB,QAAQ,EAAE,0BAA0B,MAAM,CAAC,UAAU,EAAE,EAAE;SAC1D,CAAC;QACF,WAAW,CAAC,GAAG,CAAC,OAAO,EAAE,KAAK,CAAC,CAAC;IAClC,CAAC;IACD,OAAO,KAAK,CAAC;AACf,CAAC","sourcesContent":["import type { DocumentOrphanBlobPersistence } from \"../../data/persistence/documents/orphanBlobPersistence\";\nimport type { ExecSql } from \"../../data/sqlite/sqlSchema\";\n\nexport interface OrphanBlobReclaimState {\n deferredStorageKeys: Map<string, number>;\n rerun: boolean;\n running: Promise<void> | undefined;\n sweepKey: string;\n}\n\nconst states = new WeakMap<\n DocumentOrphanBlobPersistence,\n WeakMap<ExecSql, OrphanBlobReclaimState>\n>();\n\nexport function orphanBlobReclaimState(\n persistence: DocumentOrphanBlobPersistence,\n execSql: ExecSql,\n): OrphanBlobReclaimState {\n let connections = states.get(persistence);\n if (!connections) {\n connections = new WeakMap();\n states.set(persistence, connections);\n }\n let state = connections.get(execSql);\n if (!state) {\n state = {\n deferredStorageKeys: new Map(),\n rerun: false,\n running: undefined,\n sweepKey: `sweep:document-orphans:${crypto.randomUUID()}`,\n };\n connections.set(execSql, state);\n }\n return state;\n}\n"]}
@@ -1,5 +1,6 @@
1
+ import type { DocumentsPersistence } from "../../data/persistence/documents/types.js";
1
2
  import type { DocumentsWorkflowRuntimeGroups } from "./runtime.js";
2
3
  type DocumentOrphanBlobReclaimRuntime = Pick<DocumentsWorkflowRuntimeGroups, "infra" | "util">;
3
- /** Sweep orphan rows and reclaim their queued local attachment bytes. */
4
- export declare function reclaimDocumentOrphanBlobs(runtime: DocumentOrphanBlobReclaimRuntime): Promise<void>;
4
+ /** Await all requested batches; maintenance failures never fail committed writes. */
5
+ export declare function reclaimDocumentOrphanBlobs(runtime: DocumentOrphanBlobReclaimRuntime, persistence?: DocumentsPersistence): Promise<void>;
5
6
  export {};
@@ -1,17 +1,13 @@
1
1
  import { errorMessage } from "../../data/errorMessage.js";
2
- import { acknowledgeDocumentOrphanBlobReclaim, deleteOrphanedDocumentSideRows, isDocumentBlobStorageKeyReferenced, listDocumentOrphanBlobReclaims, } from "../../data/persistence/documents/internal/orphanSideRows.js";
3
2
  import { resolveCanonicalExecSql, runOncePerConnection, runSerializedSqlMutation, } from "../../data/sqlite/sqlSchema.js";
4
3
  import { runSerializedDocumentBlobMutation } from "./blobMutationLock.js";
4
+ import { orphanBlobReclaimState, } from "./orphanBlobReclaimState.js";
5
5
  import { defaultDocumentsPersistence } from "./persistence.js";
6
- const reclaimByExecSql = new WeakMap();
7
- const reclaimRerunByExecSql = new WeakSet();
8
- const deferredStorageKeysByExecSql = new WeakMap();
9
6
  const ORPHAN_BLOB_RECLAIM_BATCH_SIZE = 16;
10
7
  const ORPHAN_BLOB_RECLAIM_TIME_BUDGET_MS = 250;
11
8
  const ORPHAN_BLOB_RECLAIM_RETRY_DELAY_MS = 30_000;
12
9
  const ORPHAN_BLOB_RECLAIM_LOG_SAMPLE_SIZE = 3;
13
10
  const ORPHAN_BLOB_RECLAIM_YIELD_MS = 16;
14
- const DOCUMENT_ORPHAN_SWEEP_ONCE_KEY = "sweep:document-orphans";
15
11
  function clearExpiredDeferredStorageKeys(deferredStorageKeys, now) {
16
12
  for (const [storageKey, retryAt] of deferredStorageKeys) {
17
13
  if (retryAt <= now)
@@ -23,38 +19,36 @@ function waitForMaintenanceYield() {
23
19
  setTimeout(resolve, ORPHAN_BLOB_RECLAIM_YIELD_MS);
24
20
  });
25
21
  }
26
- async function sweepAgedDocumentOrphans(execSql) {
22
+ async function sweepAgedDocumentOrphans(execSql, persistence, state) {
27
23
  let swept = false;
28
- await runOncePerConnection(execSql, DOCUMENT_ORPHAN_SWEEP_ONCE_KEY, async () => {
24
+ await runOncePerConnection(execSql, state.sweepKey, async () => {
29
25
  swept = true;
30
- while (await deleteOrphanedDocumentSideRows(execSql)) {
26
+ while (await persistence.orphanBlobs.sweep(execSql)) {
31
27
  await waitForMaintenanceYield();
32
28
  }
33
29
  });
34
30
  return swept;
35
31
  }
36
- async function reclaimQueuedBlobs(runtime) {
32
+ async function reclaimQueuedBlobs(runtime, persistence, state) {
37
33
  const execSql = runtime.infra.execSql;
38
- await defaultDocumentsPersistence.ensureSchema(execSql);
34
+ await persistence.ensureSchema(execSql);
39
35
  let shouldContinue = false;
40
36
  const startedAt = Date.now();
41
- const canonicalExecSql = resolveCanonicalExecSql(execSql);
42
- const deferredStorageKeys = deferredStorageKeysByExecSql.get(canonicalExecSql) ??
43
- new Map();
44
- deferredStorageKeysByExecSql.set(canonicalExecSql, deferredStorageKeys);
37
+ const { deferredStorageKeys } = state;
45
38
  const failedStorageKeys = [];
46
39
  let warmedBlobStore = false;
47
40
  let sweptAgedOrphans = false;
48
41
  reclaimQueue: while (true) {
49
42
  clearExpiredDeferredStorageKeys(deferredStorageKeys, Date.now());
50
- const queuedStorageKeys = await listDocumentOrphanBlobReclaims(execSql, ORPHAN_BLOB_RECLAIM_BATCH_SIZE + deferredStorageKeys.size);
43
+ const queuedStorageKeys = await persistence.orphanBlobs.list(execSql, ORPHAN_BLOB_RECLAIM_BATCH_SIZE + deferredStorageKeys.size);
51
44
  const storageKeys = queuedStorageKeys
52
45
  .filter((storageKey) => !deferredStorageKeys.has(storageKey))
53
46
  .slice(0, ORPHAN_BLOB_RECLAIM_BATCH_SIZE);
54
47
  if (storageKeys.length === 0) {
55
48
  // Explicit delete paths get priority. Once their queue is drained, sweep
56
49
  // crash residue once per connection and immediately drain what it queues.
57
- if (!sweptAgedOrphans && (await sweepAgedDocumentOrphans(execSql))) {
50
+ if (!sweptAgedOrphans &&
51
+ (await sweepAgedDocumentOrphans(execSql, persistence, state))) {
58
52
  sweptAgedOrphans = true;
59
53
  continue;
60
54
  }
@@ -72,8 +66,8 @@ async function reclaimQueuedBlobs(runtime) {
72
66
  for (const storageKey of storageKeys) {
73
67
  await runSerializedDocumentBlobMutation(execSql, storageKey, async () => {
74
68
  const shouldDelete = await runSerializedSqlMutation(execSql, async (lockedExecSql) => {
75
- if (await isDocumentBlobStorageKeyReferenced(lockedExecSql, storageKey)) {
76
- await acknowledgeDocumentOrphanBlobReclaim(lockedExecSql, storageKey);
69
+ if (await persistence.orphanBlobs.isReferenced(lockedExecSql, storageKey)) {
70
+ await persistence.orphanBlobs.acknowledge(lockedExecSql, storageKey);
77
71
  return false;
78
72
  }
79
73
  return true;
@@ -82,7 +76,7 @@ async function reclaimQueuedBlobs(runtime) {
82
76
  return;
83
77
  try {
84
78
  await runtime.infra.blobStore.deleteBytes(storageKey);
85
- await runSerializedSqlMutation(execSql, (lockedExecSql) => acknowledgeDocumentOrphanBlobReclaim(lockedExecSql, storageKey));
79
+ await runSerializedSqlMutation(execSql, (lockedExecSql) => persistence.orphanBlobs.acknowledge(lockedExecSql, storageKey));
86
80
  }
87
81
  catch {
88
82
  failedStorageKeys.push(storageKey);
@@ -95,9 +89,6 @@ async function reclaimQueuedBlobs(runtime) {
95
89
  }
96
90
  }
97
91
  }
98
- if (deferredStorageKeys.size === 0) {
99
- deferredStorageKeysByExecSql.delete(canonicalExecSql);
100
- }
101
92
  if (failedStorageKeys.length > 0) {
102
93
  const sample = failedStorageKeys
103
94
  .slice(0, ORPHAN_BLOB_RECLAIM_LOG_SAMPLE_SIZE)
@@ -106,36 +97,47 @@ async function reclaimQueuedBlobs(runtime) {
106
97
  }
107
98
  return shouldContinue;
108
99
  }
109
- /** Sweep orphan rows and reclaim their queued local attachment bytes. */
110
- export function reclaimDocumentOrphanBlobs(runtime) {
100
+ function runDocumentOrphanReclaims(runtime, persistence = defaultDocumentsPersistence) {
111
101
  if (runtime.infra.dbStatus !== "ready") {
112
102
  return Promise.resolve();
113
103
  }
114
- const execSql = resolveCanonicalExecSql(runtime.infra.execSql);
115
- const existing = reclaimByExecSql.get(execSql);
116
- if (existing) {
117
- reclaimRerunByExecSql.add(execSql);
118
- return existing;
104
+ const state = orphanBlobReclaimState(persistence.orphanBlobs, resolveCanonicalExecSql(runtime.infra.execSql));
105
+ if (state.running) {
106
+ state.rerun = true;
107
+ return state.running;
119
108
  }
120
- let shouldContinue = false;
121
- const reclaim = reclaimQueuedBlobs(runtime)
122
- .then((hasMore) => {
123
- shouldContinue = hasMore;
124
- })
125
- .catch((error) => {
126
- const message = errorMessage(error);
127
- runtime.util.log(`Documents: orphan maintenance failed: ${message}`);
128
- })
129
- .finally(() => {
130
- reclaimByExecSql.delete(execSql);
131
- const wasRerunRequested = reclaimRerunByExecSql.delete(execSql);
132
- if (shouldContinue || wasRerunRequested) {
133
- setTimeout(() => {
134
- void reclaimDocumentOrphanBlobs(runtime);
135
- }, ORPHAN_BLOB_RECLAIM_YIELD_MS);
109
+ const reclaim = (async () => {
110
+ try {
111
+ let hasMore;
112
+ do {
113
+ state.rerun = false;
114
+ hasMore = false;
115
+ try {
116
+ hasMore = await reclaimQueuedBlobs(runtime, persistence, state);
117
+ }
118
+ catch (error) {
119
+ const message = errorMessage(error);
120
+ runtime.util.log(`Documents: orphan maintenance failed: ${message}`);
121
+ }
122
+ if (hasMore || state.rerun)
123
+ await waitForMaintenanceYield();
124
+ } while (hasMore || state.rerun);
136
125
  }
137
- });
138
- reclaimByExecSql.set(execSql, reclaim);
126
+ finally {
127
+ state.running = undefined;
128
+ state.rerun = false;
129
+ }
130
+ })();
131
+ state.running = reclaim;
139
132
  return reclaim;
140
133
  }
134
+ /** Await all requested batches; maintenance failures never fail committed writes. */
135
+ export async function reclaimDocumentOrphanBlobs(runtime, persistence = defaultDocumentsPersistence) {
136
+ try {
137
+ await runDocumentOrphanReclaims(runtime, persistence);
138
+ }
139
+ catch (error) {
140
+ runtime.util.log(`Documents: orphan maintenance failed: ${errorMessage(error)}`);
141
+ }
142
+ }
141
143
  //# sourceMappingURL=orphanBlobReclaims.js.map