@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
@@ -22,6 +22,7 @@ import { loadStoredDocumentStoreState } from "./internal/documentStoreStatePersi
22
22
  import { listDocumentSummaries } from "./internal/documentSummaryQueries.js";
23
23
  import { ensureDocumentsSchema } from "./internal/ensureDocumentsSchema.js";
24
24
  import { saveHydratedAttachment } from "./internal/hydratedAttachmentPersistence.js";
25
+ import { acknowledgeDocumentOrphanBlobReclaim, deleteOrphanedDocumentSideRows, isDocumentBlobStorageKeyReferenced, listDocumentOrphanBlobReclaims, } from "./internal/orphanSideRows.js";
25
26
  import { loadPendingCreateSummary, mapPendingCreateLocalIds, } from "./internal/pendingCreateAdoption.js";
26
27
  import { documentRowQueryPersistence } from "./internal/rowQueryPersistence.js";
27
28
  import { documentSyncQueuePersistence } from "./internal/syncQueuePersistence.js";
@@ -152,6 +153,12 @@ export async function listDocumentsByContainerIdsOrDocumentIds(execSql, input) {
152
153
  return rows.map(mapDocumentSummary);
153
154
  }
154
155
  export const sqlDocumentsPersistence = {
156
+ orphanBlobs: {
157
+ acknowledge: acknowledgeDocumentOrphanBlobReclaim,
158
+ isReferenced: isDocumentBlobStorageKeyReferenced,
159
+ list: listDocumentOrphanBlobReclaims,
160
+ sweep: deleteOrphanedDocumentSideRows,
161
+ },
155
162
  ...documentRowQueryPersistence,
156
163
  ...documentSyncQueuePersistence,
157
164
  supportsAtomicRecoveryHistoryPruning: true,
@@ -1 +1 @@
1
- {"version":3,"file":"documentsPersistence.js","sourceRoot":"","sources":["documentsPersistence.ts"],"names":[],"mappings":"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","sourcesContent":["import { KeyingVerificationError } from \"@tearleads/crypto\";\nimport { and, desc, eq, inArray, notInArray, or, type SQL } from \"drizzle-orm\";\nimport { DEFAULT_DOCUMENT_KIND } from \"../../documents/documentConstants\";\nimport {\n type DiscoveredDocumentInput,\n type DocumentSummary,\n HIDDEN_DOCUMENT_SUMMARY_KINDS,\n} from \"../../documents/documentSummary\";\nimport { findLocalIdByDocumentId } from \"../../sqlite/documentPersistence\";\nimport {\n documentPendingUpdates,\n documentProjection,\n documentPurgeCheckpoints,\n documents,\n} from \"../../sqlite/schema\";\nimport {\n type ClientSQLiteTransactionScope,\n getClientSQLitePersistenceRuntime,\n} from \"../../sqlite/sqlitePersistenceRuntime\";\nimport { type ExecSql, runSerializedSqlMutation } from \"../../sqlite/sqlSchema\";\nimport { DOCUMENTS_APP_KIND } from \"./internal/constants\";\nimport { applyContainerDocumentTombstonesWithExec } from \"./internal/containerDocumentTombstones\";\nimport { createStoredDocumentWithHistoryCheckpoint } from \"./internal/createDocumentWithHistoryCheckpoint\";\nimport { discardStoredDocumentToShell } from \"./internal/discardDocument\";\nimport { resolveDiscoveredDocumentPlacement } from \"./internal/discoveredDocumentPlacement\";\nimport {\n deleteStoredDocument,\n deleteStoredDocumentIfMatches,\n deleteStoredDocumentSideRowsIfAbsent,\n} from \"./internal/documentDeletionPersistence\";\nimport { loadStoredDocumentWithHistoryRestoreState } from \"./internal/documentHistoryStatePersistence\";\nimport {\n commitStoredDocumentMutation,\n settleStoredDocumentPendingUpdates,\n} from \"./internal/documentMutationPersistence\";\nimport {\n documentSummaryJoin,\n documentSummarySelection,\n getProjectionUpdatedAt,\n mapDocumentSummary,\n toDocumentSummary,\n} from \"./internal/documentProjectionRows\";\nimport { invalidateStoredDocumentPullContinuation } from \"./internal/documentPullContinuationPersistence\";\nimport {\n resolveDocumentSaveTimestamp,\n saveDocumentRows,\n} from \"./internal/documentRows\";\nimport {\n resolvePersistedAccessStateHash,\n resolvePersistedDocumentRuntimeState,\n} from \"./internal/documentRuntimeState\";\nimport { loadStoredDocumentStoreState } from \"./internal/documentStoreStatePersistence\";\nimport { listDocumentSummaries } from \"./internal/documentSummaryQueries\";\nimport { ensureDocumentsSchema } from \"./internal/ensureDocumentsSchema\";\nimport { saveHydratedAttachment } from \"./internal/hydratedAttachmentPersistence\";\nimport {\n loadPendingCreateSummary,\n mapPendingCreateLocalIds,\n} from \"./internal/pendingCreateAdoption\";\nimport { documentRowQueryPersistence } from \"./internal/rowQueryPersistence\";\nimport { documentSyncQueuePersistence } from \"./internal/syncQueuePersistence\";\nimport type {\n ContainerDocumentTombstoneInput,\n DiscardDocumentToShellResult,\n DocumentsPersistence,\n RelinkPersistedDocumentInput,\n StoredDocumentRecord,\n} from \"./types\";\n\nexport { DOCUMENTS_APP_KIND } from \"./internal/constants\";\nexport type {\n AttachmentRemovalRows,\n AttachmentStagingRows,\n ContainerDocumentTombstoneInput,\n DiscardDocumentToShellResult,\n DocumentsPersistence,\n LocalAttachmentRecord,\n PendingAttachmentRecord,\n PendingAttachmentUploadIdentity,\n PendingUpdateInsert,\n PendingUpdateRecord,\n RelinkPersistedDocumentInput,\n StoredDocumentRecord,\n} from \"./types\";\n\nconst DISCOVERED_DOCUMENT_PLACEHOLDER_TITLE = \"Syncing document...\";\n\nasync function upsertDiscoveredDocumentWithExec(\n execSql: ExecSql,\n input: DiscoveredDocumentInput,\n pendingCreates: ReadonlyMap<string, string>,\n tx: ClientSQLiteTransactionScope,\n): Promise<DocumentSummary> {\n const [purgeCheckpoint] = await tx\n .select({ documentId: documentPurgeCheckpoints.documentId })\n .from(documentPurgeCheckpoints)\n .where(eq(documentPurgeCheckpoints.documentId, input.documentId))\n .limit(1);\n if (purgeCheckpoint) {\n throw new KeyingVerificationError(\n \"rollback\",\n \"Document discovery targets a permanently purged document\",\n );\n }\n const existingLocalId = await findLocalIdByDocumentId(\n execSql,\n DOCUMENTS_APP_KIND,\n input.documentId,\n );\n const localId = existingLocalId ?? input.documentId;\n const pendingLocalId = pendingCreates.get(input.documentId);\n if (!existingLocalId && pendingLocalId) {\n const pending = await loadPendingCreateSummary(execSql, pendingLocalId);\n if (!pending) throw new Error(\"Pending create projection is unavailable\");\n return pending;\n }\n const existingDocument = await sqlDocumentsPersistence.loadDocument(\n execSql,\n localId,\n );\n const placement = await resolveDiscoveredDocumentPlacement(\n tx,\n existingDocument,\n input,\n );\n\n const nextDocument: StoredDocumentRecord = {\n id: localId,\n documentId: input.documentId,\n documentKind: existingDocument?.documentKind ?? DEFAULT_DOCUMENT_KIND,\n text: existingDocument?.text ?? \"\",\n title: existingDocument?.title ?? DISCOVERED_DOCUMENT_PLACEHOLDER_TITLE,\n snapshotEndVersion: existingDocument?.snapshotEndVersion ?? \"\",\n recoveryGeneration: existingDocument?.recoveryGeneration ?? 0,\n ...placement,\n effectiveAccessLevel:\n input.effectiveAccessLevel ??\n existingDocument?.effectiveAccessLevel ??\n null,\n ...resolvePersistedDocumentRuntimeState(existingDocument, {\n accessEpoch: placement.accessEpoch,\n documentId: input.documentId,\n }),\n };\n\n const saveOptions =\n existingDocument === null || existingDocument === undefined\n ? { updatedAt: input.createdAt }\n : undefined;\n const updatedAt = await resolveDocumentSaveTimestamp({\n document: nextDocument,\n options: saveOptions,\n tx,\n });\n if (!(await saveDocumentRows({ document: nextDocument, tx, updatedAt }))) {\n throw new Error(\"Document recovery generation changed during discovery\");\n }\n\n return toDocumentSummary(nextDocument, updatedAt);\n}\n\nasync function relinkPersistedDocumentWithExec(\n execSql: ExecSql,\n input: RelinkPersistedDocumentInput,\n): Promise<DocumentSummary | null> {\n const existingDocument = await sqlDocumentsPersistence.loadDocument(\n execSql,\n input.localId,\n );\n if (!existingDocument) {\n return null;\n }\n\n const nextAccessEpoch = Math.max(\n existingDocument.accessEpoch,\n input.accessEpoch,\n );\n const nextDocument: StoredDocumentRecord = {\n ...existingDocument,\n accessEpoch: nextAccessEpoch,\n accessStateHash: resolvePersistedAccessStateHash(existingDocument, {\n accessEpoch: nextAccessEpoch,\n accessStateHash: input.accessStateHash,\n documentId: input.documentId,\n }),\n containerId: input.containerId,\n documentId: input.documentId,\n ...resolvePersistedDocumentRuntimeState(existingDocument, {\n accessEpoch: nextAccessEpoch,\n documentId: input.documentId,\n }),\n };\n\n await sqlDocumentsPersistence.saveDocument(execSql, nextDocument);\n\n const { db } = getClientSQLitePersistenceRuntime(execSql);\n const updatedAtRows = await db\n .select({ updatedAt: documentProjection.updatedAt })\n .from(documentProjection)\n .where(eq(documentProjection.localId, input.localId))\n .limit(1);\n\n return toDocumentSummary(\n nextDocument,\n getProjectionUpdatedAt(updatedAtRows[0]),\n );\n}\n\nexport async function upsertDiscoveredDocuments(\n execSql: ExecSql,\n inputs: ReadonlyArray<DiscoveredDocumentInput>,\n): Promise<DocumentSummary[]> {\n return runSerializedSqlMutation(execSql, async (lockedExecSql) => {\n await sqlDocumentsPersistence.ensureSchema(lockedExecSql);\n return getClientSQLitePersistenceRuntime(lockedExecSql).transaction(\n async (tx) => {\n const pendingCreates = await mapPendingCreateLocalIds(lockedExecSql);\n const nextSummaries: DocumentSummary[] = [];\n\n for (const input of inputs) {\n nextSummaries.push(\n await upsertDiscoveredDocumentWithExec(\n lockedExecSql,\n input,\n pendingCreates,\n tx,\n ),\n );\n }\n\n return nextSummaries;\n },\n { behavior: \"immediate\" },\n );\n });\n}\n\nexport async function applyContainerDocumentTombstones(\n execSql: ExecSql,\n tombstones: ReadonlyArray<ContainerDocumentTombstoneInput>,\n): Promise<DocumentSummary[]> {\n return runSerializedSqlMutation(execSql, async (lockedExecSql) => {\n await sqlDocumentsPersistence.ensureSchema(lockedExecSql);\n return applyContainerDocumentTombstonesWithExec(lockedExecSql, tombstones);\n });\n}\n\nexport async function listDocumentsByContainerIdsOrDocumentIds(\n execSql: ExecSql,\n input: {\n containerIds: ReadonlyArray<string>;\n documentIds: ReadonlyArray<string>;\n },\n): Promise<DocumentSummary[]> {\n const uniqueContainerIds = Array.from(new Set(input.containerIds));\n const uniqueDocumentIds = Array.from(new Set(input.documentIds));\n if (uniqueContainerIds.length === 0 && uniqueDocumentIds.length === 0) {\n return [];\n }\n\n const filters: SQL[] = [];\n if (uniqueContainerIds.length > 0) {\n filters.push(inArray(documentProjection.containerId, uniqueContainerIds));\n }\n\n if (uniqueDocumentIds.length > 0) {\n filters.push(inArray(documentProjection.documentId, uniqueDocumentIds));\n }\n\n const whereCondition = filters.length === 1 ? filters[0] : or(...filters);\n const { db } = getClientSQLitePersistenceRuntime(execSql);\n const rows = await db\n .select(documentSummarySelection)\n .from(documentProjection)\n .leftJoin(documents, documentSummaryJoin)\n .where(\n and(\n whereCondition,\n notInArray(documentProjection.documentKind, [\n ...HIDDEN_DOCUMENT_SUMMARY_KINDS,\n ]),\n ),\n )\n .orderBy(\n desc(documentProjection.updatedAt),\n desc(documentProjection.localId),\n );\n\n return rows.map(mapDocumentSummary);\n}\n\nexport const sqlDocumentsPersistence: DocumentsPersistence = {\n ...documentRowQueryPersistence,\n ...documentSyncQueuePersistence,\n supportsAtomicRecoveryHistoryPruning: true,\n saveHydratedAttachment,\n createDocumentWithHistoryCheckpoint:\n createStoredDocumentWithHistoryCheckpoint,\n commitDocumentMutation: (execSql, input, saveClientProjection) =>\n commitStoredDocumentMutation(\n execSql,\n input,\n saveClientProjection,\n sqlDocumentsPersistence.loadDocument,\n ),\n settleAcceptedPendingUpdates: (execSql, input) =>\n settleStoredDocumentPendingUpdates(\n execSql,\n input,\n sqlDocumentsPersistence.loadDocument,\n ),\n ensureSchema: ensureDocumentsSchema,\n listDocumentSummaries,\n listDocumentsByContainerIdsOrDocumentIds,\n invalidatePullContinuation: (execSql, input) =>\n invalidateStoredDocumentPullContinuation(\n execSql,\n input,\n sqlDocumentsPersistence.loadDocument,\n sqlDocumentsPersistence.loadHistoryRestoreState,\n ),\n loadDocumentWithHistoryRestoreState: (execSql, localId) =>\n loadStoredDocumentWithHistoryRestoreState(\n execSql,\n localId,\n sqlDocumentsPersistence,\n ),\n loadDocumentStoreState: (execSql, localId) =>\n loadStoredDocumentStoreState(execSql, localId, sqlDocumentsPersistence),\n async saveDocument(execSql, document, options) {\n return runSerializedSqlMutation(execSql, async (lockedExecSql) =>\n getClientSQLitePersistenceRuntime(lockedExecSql).transaction(\n async (tx) => {\n const updatedAt = await resolveDocumentSaveTimestamp({\n document,\n options,\n tx,\n });\n await saveDocumentRows({\n document,\n tx,\n updatedAt,\n });\n\n return updatedAt;\n },\n { behavior: \"immediate\" },\n ),\n );\n },\n async saveDocumentAndDeletePendingUpdates(\n execSql,\n document,\n pendingUpdateIds,\n options,\n ) {\n const uniquePendingUpdateIds = [...new Set(pendingUpdateIds)];\n\n return runSerializedSqlMutation(execSql, async (lockedExecSql) =>\n getClientSQLitePersistenceRuntime(lockedExecSql).transaction(\n async (tx) => {\n const updatedAt = await resolveDocumentSaveTimestamp({\n document,\n options,\n tx,\n });\n const saved = await saveDocumentRows({\n document,\n tx,\n updatedAt,\n });\n if (!saved) return updatedAt;\n if (uniquePendingUpdateIds.length > 0) {\n await tx\n .delete(documentPendingUpdates)\n .where(\n and(\n eq(documentPendingUpdates.appKind, DOCUMENTS_APP_KIND),\n eq(documentPendingUpdates.localId, document.id),\n inArray(documentPendingUpdates.id, uniquePendingUpdateIds),\n ),\n )\n .run();\n }\n return updatedAt;\n },\n { behavior: \"immediate\" },\n ),\n );\n },\n deleteDocument: deleteStoredDocument,\n deleteDocumentIfMatches: deleteStoredDocumentIfMatches,\n deleteDocumentSideRowsIfAbsent: deleteStoredDocumentSideRowsIfAbsent,\n async discardDocumentToShell(\n execSql,\n localId,\n expectedDocumentId,\n documentProjectors,\n ): Promise<DiscardDocumentToShellResult> {\n return discardStoredDocumentToShell({\n documentProjectors,\n execSql,\n expectedDocumentId,\n localId,\n persistence: sqlDocumentsPersistence,\n });\n },\n async upsertDiscoveredDocument(execSql, input) {\n const [nextSummary] = await upsertDiscoveredDocuments(execSql, [input]);\n if (!nextSummary) {\n throw new Error(\"Failed to upsert discovered document\");\n }\n\n return nextSummary;\n },\n async relinkPersistedDocument(execSql, input) {\n return runSerializedSqlMutation(execSql, async (lockedExecSql) => {\n await sqlDocumentsPersistence.ensureSchema(lockedExecSql);\n return getClientSQLitePersistenceRuntime(lockedExecSql).transaction(\n () => relinkPersistedDocumentWithExec(lockedExecSql, input),\n { behavior: \"immediate\" },\n );\n });\n },\n};\n"]}
1
+ {"version":3,"file":"documentsPersistence.js","sourceRoot":"","sources":["documentsPersistence.ts"],"names":[],"mappings":"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","sourcesContent":["import { KeyingVerificationError } from \"@tearleads/crypto\";\nimport { and, desc, eq, inArray, notInArray, or, type SQL } from \"drizzle-orm\";\nimport { DEFAULT_DOCUMENT_KIND } from \"../../documents/documentConstants\";\nimport {\n type DiscoveredDocumentInput,\n type DocumentSummary,\n HIDDEN_DOCUMENT_SUMMARY_KINDS,\n} from \"../../documents/documentSummary\";\nimport { findLocalIdByDocumentId } from \"../../sqlite/documentPersistence\";\nimport {\n documentPendingUpdates,\n documentProjection,\n documentPurgeCheckpoints,\n documents,\n} from \"../../sqlite/schema\";\nimport {\n type ClientSQLiteTransactionScope,\n getClientSQLitePersistenceRuntime,\n} from \"../../sqlite/sqlitePersistenceRuntime\";\nimport { type ExecSql, runSerializedSqlMutation } from \"../../sqlite/sqlSchema\";\nimport { DOCUMENTS_APP_KIND } from \"./internal/constants\";\nimport { applyContainerDocumentTombstonesWithExec } from \"./internal/containerDocumentTombstones\";\nimport { createStoredDocumentWithHistoryCheckpoint } from \"./internal/createDocumentWithHistoryCheckpoint\";\nimport { discardStoredDocumentToShell } from \"./internal/discardDocument\";\nimport { resolveDiscoveredDocumentPlacement } from \"./internal/discoveredDocumentPlacement\";\nimport {\n deleteStoredDocument,\n deleteStoredDocumentIfMatches,\n deleteStoredDocumentSideRowsIfAbsent,\n} from \"./internal/documentDeletionPersistence\";\nimport { loadStoredDocumentWithHistoryRestoreState } from \"./internal/documentHistoryStatePersistence\";\nimport {\n commitStoredDocumentMutation,\n settleStoredDocumentPendingUpdates,\n} from \"./internal/documentMutationPersistence\";\nimport {\n documentSummaryJoin,\n documentSummarySelection,\n getProjectionUpdatedAt,\n mapDocumentSummary,\n toDocumentSummary,\n} from \"./internal/documentProjectionRows\";\nimport { invalidateStoredDocumentPullContinuation } from \"./internal/documentPullContinuationPersistence\";\nimport {\n resolveDocumentSaveTimestamp,\n saveDocumentRows,\n} from \"./internal/documentRows\";\nimport {\n resolvePersistedAccessStateHash,\n resolvePersistedDocumentRuntimeState,\n} from \"./internal/documentRuntimeState\";\nimport { loadStoredDocumentStoreState } from \"./internal/documentStoreStatePersistence\";\nimport { listDocumentSummaries } from \"./internal/documentSummaryQueries\";\nimport { ensureDocumentsSchema } from \"./internal/ensureDocumentsSchema\";\nimport { saveHydratedAttachment } from \"./internal/hydratedAttachmentPersistence\";\nimport {\n acknowledgeDocumentOrphanBlobReclaim,\n deleteOrphanedDocumentSideRows,\n isDocumentBlobStorageKeyReferenced,\n listDocumentOrphanBlobReclaims,\n} from \"./internal/orphanSideRows\";\nimport {\n loadPendingCreateSummary,\n mapPendingCreateLocalIds,\n} from \"./internal/pendingCreateAdoption\";\nimport { documentRowQueryPersistence } from \"./internal/rowQueryPersistence\";\nimport { documentSyncQueuePersistence } from \"./internal/syncQueuePersistence\";\nimport type {\n ContainerDocumentTombstoneInput,\n DiscardDocumentToShellResult,\n DocumentsPersistence,\n RelinkPersistedDocumentInput,\n StoredDocumentRecord,\n} from \"./types\";\n\nexport { DOCUMENTS_APP_KIND } from \"./internal/constants\";\nexport type {\n AttachmentRemovalRows,\n AttachmentStagingRows,\n ContainerDocumentTombstoneInput,\n DiscardDocumentToShellResult,\n DocumentsPersistence,\n LocalAttachmentRecord,\n PendingAttachmentRecord,\n PendingAttachmentUploadIdentity,\n PendingUpdateInsert,\n PendingUpdateRecord,\n RelinkPersistedDocumentInput,\n StoredDocumentRecord,\n} from \"./types\";\n\nconst DISCOVERED_DOCUMENT_PLACEHOLDER_TITLE = \"Syncing document...\";\n\nasync function upsertDiscoveredDocumentWithExec(\n execSql: ExecSql,\n input: DiscoveredDocumentInput,\n pendingCreates: ReadonlyMap<string, string>,\n tx: ClientSQLiteTransactionScope,\n): Promise<DocumentSummary> {\n const [purgeCheckpoint] = await tx\n .select({ documentId: documentPurgeCheckpoints.documentId })\n .from(documentPurgeCheckpoints)\n .where(eq(documentPurgeCheckpoints.documentId, input.documentId))\n .limit(1);\n if (purgeCheckpoint) {\n throw new KeyingVerificationError(\n \"rollback\",\n \"Document discovery targets a permanently purged document\",\n );\n }\n const existingLocalId = await findLocalIdByDocumentId(\n execSql,\n DOCUMENTS_APP_KIND,\n input.documentId,\n );\n const localId = existingLocalId ?? input.documentId;\n const pendingLocalId = pendingCreates.get(input.documentId);\n if (!existingLocalId && pendingLocalId) {\n const pending = await loadPendingCreateSummary(execSql, pendingLocalId);\n if (!pending) throw new Error(\"Pending create projection is unavailable\");\n return pending;\n }\n const existingDocument = await sqlDocumentsPersistence.loadDocument(\n execSql,\n localId,\n );\n const placement = await resolveDiscoveredDocumentPlacement(\n tx,\n existingDocument,\n input,\n );\n\n const nextDocument: StoredDocumentRecord = {\n id: localId,\n documentId: input.documentId,\n documentKind: existingDocument?.documentKind ?? DEFAULT_DOCUMENT_KIND,\n text: existingDocument?.text ?? \"\",\n title: existingDocument?.title ?? DISCOVERED_DOCUMENT_PLACEHOLDER_TITLE,\n snapshotEndVersion: existingDocument?.snapshotEndVersion ?? \"\",\n recoveryGeneration: existingDocument?.recoveryGeneration ?? 0,\n ...placement,\n effectiveAccessLevel:\n input.effectiveAccessLevel ??\n existingDocument?.effectiveAccessLevel ??\n null,\n ...resolvePersistedDocumentRuntimeState(existingDocument, {\n accessEpoch: placement.accessEpoch,\n documentId: input.documentId,\n }),\n };\n\n const saveOptions =\n existingDocument === null || existingDocument === undefined\n ? { updatedAt: input.createdAt }\n : undefined;\n const updatedAt = await resolveDocumentSaveTimestamp({\n document: nextDocument,\n options: saveOptions,\n tx,\n });\n if (!(await saveDocumentRows({ document: nextDocument, tx, updatedAt }))) {\n throw new Error(\"Document recovery generation changed during discovery\");\n }\n\n return toDocumentSummary(nextDocument, updatedAt);\n}\n\nasync function relinkPersistedDocumentWithExec(\n execSql: ExecSql,\n input: RelinkPersistedDocumentInput,\n): Promise<DocumentSummary | null> {\n const existingDocument = await sqlDocumentsPersistence.loadDocument(\n execSql,\n input.localId,\n );\n if (!existingDocument) {\n return null;\n }\n\n const nextAccessEpoch = Math.max(\n existingDocument.accessEpoch,\n input.accessEpoch,\n );\n const nextDocument: StoredDocumentRecord = {\n ...existingDocument,\n accessEpoch: nextAccessEpoch,\n accessStateHash: resolvePersistedAccessStateHash(existingDocument, {\n accessEpoch: nextAccessEpoch,\n accessStateHash: input.accessStateHash,\n documentId: input.documentId,\n }),\n containerId: input.containerId,\n documentId: input.documentId,\n ...resolvePersistedDocumentRuntimeState(existingDocument, {\n accessEpoch: nextAccessEpoch,\n documentId: input.documentId,\n }),\n };\n\n await sqlDocumentsPersistence.saveDocument(execSql, nextDocument);\n\n const { db } = getClientSQLitePersistenceRuntime(execSql);\n const updatedAtRows = await db\n .select({ updatedAt: documentProjection.updatedAt })\n .from(documentProjection)\n .where(eq(documentProjection.localId, input.localId))\n .limit(1);\n\n return toDocumentSummary(\n nextDocument,\n getProjectionUpdatedAt(updatedAtRows[0]),\n );\n}\n\nexport async function upsertDiscoveredDocuments(\n execSql: ExecSql,\n inputs: ReadonlyArray<DiscoveredDocumentInput>,\n): Promise<DocumentSummary[]> {\n return runSerializedSqlMutation(execSql, async (lockedExecSql) => {\n await sqlDocumentsPersistence.ensureSchema(lockedExecSql);\n return getClientSQLitePersistenceRuntime(lockedExecSql).transaction(\n async (tx) => {\n const pendingCreates = await mapPendingCreateLocalIds(lockedExecSql);\n const nextSummaries: DocumentSummary[] = [];\n\n for (const input of inputs) {\n nextSummaries.push(\n await upsertDiscoveredDocumentWithExec(\n lockedExecSql,\n input,\n pendingCreates,\n tx,\n ),\n );\n }\n\n return nextSummaries;\n },\n { behavior: \"immediate\" },\n );\n });\n}\n\nexport async function applyContainerDocumentTombstones(\n execSql: ExecSql,\n tombstones: ReadonlyArray<ContainerDocumentTombstoneInput>,\n): Promise<DocumentSummary[]> {\n return runSerializedSqlMutation(execSql, async (lockedExecSql) => {\n await sqlDocumentsPersistence.ensureSchema(lockedExecSql);\n return applyContainerDocumentTombstonesWithExec(lockedExecSql, tombstones);\n });\n}\n\nexport async function listDocumentsByContainerIdsOrDocumentIds(\n execSql: ExecSql,\n input: {\n containerIds: ReadonlyArray<string>;\n documentIds: ReadonlyArray<string>;\n },\n): Promise<DocumentSummary[]> {\n const uniqueContainerIds = Array.from(new Set(input.containerIds));\n const uniqueDocumentIds = Array.from(new Set(input.documentIds));\n if (uniqueContainerIds.length === 0 && uniqueDocumentIds.length === 0) {\n return [];\n }\n\n const filters: SQL[] = [];\n if (uniqueContainerIds.length > 0) {\n filters.push(inArray(documentProjection.containerId, uniqueContainerIds));\n }\n\n if (uniqueDocumentIds.length > 0) {\n filters.push(inArray(documentProjection.documentId, uniqueDocumentIds));\n }\n\n const whereCondition = filters.length === 1 ? filters[0] : or(...filters);\n const { db } = getClientSQLitePersistenceRuntime(execSql);\n const rows = await db\n .select(documentSummarySelection)\n .from(documentProjection)\n .leftJoin(documents, documentSummaryJoin)\n .where(\n and(\n whereCondition,\n notInArray(documentProjection.documentKind, [\n ...HIDDEN_DOCUMENT_SUMMARY_KINDS,\n ]),\n ),\n )\n .orderBy(\n desc(documentProjection.updatedAt),\n desc(documentProjection.localId),\n );\n\n return rows.map(mapDocumentSummary);\n}\n\nexport const sqlDocumentsPersistence: DocumentsPersistence = {\n orphanBlobs: {\n acknowledge: acknowledgeDocumentOrphanBlobReclaim,\n isReferenced: isDocumentBlobStorageKeyReferenced,\n list: listDocumentOrphanBlobReclaims,\n sweep: deleteOrphanedDocumentSideRows,\n },\n ...documentRowQueryPersistence,\n ...documentSyncQueuePersistence,\n supportsAtomicRecoveryHistoryPruning: true,\n saveHydratedAttachment,\n createDocumentWithHistoryCheckpoint:\n createStoredDocumentWithHistoryCheckpoint,\n commitDocumentMutation: (execSql, input, saveClientProjection) =>\n commitStoredDocumentMutation(\n execSql,\n input,\n saveClientProjection,\n sqlDocumentsPersistence.loadDocument,\n ),\n settleAcceptedPendingUpdates: (execSql, input) =>\n settleStoredDocumentPendingUpdates(\n execSql,\n input,\n sqlDocumentsPersistence.loadDocument,\n ),\n ensureSchema: ensureDocumentsSchema,\n listDocumentSummaries,\n listDocumentsByContainerIdsOrDocumentIds,\n invalidatePullContinuation: (execSql, input) =>\n invalidateStoredDocumentPullContinuation(\n execSql,\n input,\n sqlDocumentsPersistence.loadDocument,\n sqlDocumentsPersistence.loadHistoryRestoreState,\n ),\n loadDocumentWithHistoryRestoreState: (execSql, localId) =>\n loadStoredDocumentWithHistoryRestoreState(\n execSql,\n localId,\n sqlDocumentsPersistence,\n ),\n loadDocumentStoreState: (execSql, localId) =>\n loadStoredDocumentStoreState(execSql, localId, sqlDocumentsPersistence),\n async saveDocument(execSql, document, options) {\n return runSerializedSqlMutation(execSql, async (lockedExecSql) =>\n getClientSQLitePersistenceRuntime(lockedExecSql).transaction(\n async (tx) => {\n const updatedAt = await resolveDocumentSaveTimestamp({\n document,\n options,\n tx,\n });\n await saveDocumentRows({\n document,\n tx,\n updatedAt,\n });\n\n return updatedAt;\n },\n { behavior: \"immediate\" },\n ),\n );\n },\n async saveDocumentAndDeletePendingUpdates(\n execSql,\n document,\n pendingUpdateIds,\n options,\n ) {\n const uniquePendingUpdateIds = [...new Set(pendingUpdateIds)];\n\n return runSerializedSqlMutation(execSql, async (lockedExecSql) =>\n getClientSQLitePersistenceRuntime(lockedExecSql).transaction(\n async (tx) => {\n const updatedAt = await resolveDocumentSaveTimestamp({\n document,\n options,\n tx,\n });\n const saved = await saveDocumentRows({\n document,\n tx,\n updatedAt,\n });\n if (!saved) return updatedAt;\n if (uniquePendingUpdateIds.length > 0) {\n await tx\n .delete(documentPendingUpdates)\n .where(\n and(\n eq(documentPendingUpdates.appKind, DOCUMENTS_APP_KIND),\n eq(documentPendingUpdates.localId, document.id),\n inArray(documentPendingUpdates.id, uniquePendingUpdateIds),\n ),\n )\n .run();\n }\n return updatedAt;\n },\n { behavior: \"immediate\" },\n ),\n );\n },\n deleteDocument: deleteStoredDocument,\n deleteDocumentIfMatches: deleteStoredDocumentIfMatches,\n deleteDocumentSideRowsIfAbsent: deleteStoredDocumentSideRowsIfAbsent,\n async discardDocumentToShell(\n execSql,\n localId,\n expectedDocumentId,\n documentProjectors,\n ): Promise<DiscardDocumentToShellResult> {\n return discardStoredDocumentToShell({\n documentProjectors,\n execSql,\n expectedDocumentId,\n localId,\n persistence: sqlDocumentsPersistence,\n });\n },\n async upsertDiscoveredDocument(execSql, input) {\n const [nextSummary] = await upsertDiscoveredDocuments(execSql, [input]);\n if (!nextSummary) {\n throw new Error(\"Failed to upsert discovered document\");\n }\n\n return nextSummary;\n },\n async relinkPersistedDocument(execSql, input) {\n return runSerializedSqlMutation(execSql, async (lockedExecSql) => {\n await sqlDocumentsPersistence.ensureSchema(lockedExecSql);\n return getClientSQLitePersistenceRuntime(lockedExecSql).transaction(\n () => relinkPersistedDocumentWithExec(lockedExecSql, input),\n { behavior: \"immediate\" },\n );\n });\n },\n};\n"]}
@@ -0,0 +1,13 @@
1
+ import type { ExecSql } from "../../sqlite/sqlSchema.js";
2
+ /**
3
+ * Attachment removals queue keys in the same commit that removes their rows.
4
+ * References include every pending upload and held copy sharing this byte store.
5
+ * Reference publication must use the SDK's per-key blob mutation lock.
6
+ */
7
+ export interface DocumentOrphanBlobPersistence {
8
+ acknowledge: (execSql: ExecSql, storageKey: string) => Promise<void>;
9
+ isReferenced: (execSql: ExecSql, storageKey: string) => Promise<boolean>;
10
+ list: (execSql: ExecSql, limit: number) => Promise<readonly string[]>;
11
+ /** Sweep one bounded batch of abandoned side rows; true means more remain. */
12
+ sweep: (execSql: ExecSql) => Promise<boolean>;
13
+ }
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=orphanBlobPersistence.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"orphanBlobPersistence.js","sourceRoot":"","sources":["orphanBlobPersistence.ts"],"names":[],"mappings":"","sourcesContent":["import type { ExecSql } from \"../../sqlite/sqlSchema\";\n\n/**\n * Attachment removals queue keys in the same commit that removes their rows.\n * References include every pending upload and held copy sharing this byte store.\n * Reference publication must use the SDK's per-key blob mutation lock.\n */\nexport interface DocumentOrphanBlobPersistence {\n acknowledge: (execSql: ExecSql, storageKey: string) => Promise<void>;\n isReferenced: (execSql: ExecSql, storageKey: string) => Promise<boolean>;\n list: (execSql: ExecSql, limit: number) => Promise<readonly string[]>;\n /** Sweep one bounded batch of abandoned side rows; true means more remain. */\n sweep: (execSql: ExecSql) => Promise<boolean>;\n}\n"]}
@@ -3,6 +3,7 @@ import type { DiscoveredDocumentInput, DocumentSummary } from "../../documents/d
3
3
  import type { DocumentSyncPullContinuation } from "../../documents/shared/pullContinuation.js";
4
4
  import type { DocumentRecord as BaseDocumentRecord, PendingUpdateFields, PendingUpdateRecord } from "../../sqlite/documentPersistence.js";
5
5
  import type { ExecSql } from "../../sqlite/sqlSchema.js";
6
+ import type { DocumentOrphanBlobPersistence } from "./orphanBlobPersistence.js";
6
7
  export type { PendingUpdateRecord } from "../../sqlite/documentPersistence.js";
7
8
  export interface StoredDocumentRecord extends BaseDocumentRecord {
8
9
  containerId: string | null;
@@ -104,6 +105,7 @@ export interface DocumentSummaryList {
104
105
  readonly totalCount: number;
105
106
  }
106
107
  export interface DocumentsPersistence {
108
+ readonly orphanBlobs: DocumentOrphanBlobPersistence;
107
109
  /**
108
110
  * The adapter guarantees that recovery checkpoint replacement, selection of
109
111
  * covered local history, rejection of unproven ordinary pending rows, and
@@ -1 +1 @@
1
- {"version":3,"file":"types.js","sourceRoot":"","sources":["types.ts"],"names":[],"mappings":"","sourcesContent":["import type {\n DocumentProjectorRegistryInput,\n StoredDocumentKind,\n} from \"../../documents/documentKinds\";\nimport type {\n DiscoveredDocumentInput,\n DocumentSummary,\n} from \"../../documents/documentSummary\";\nimport type { DocumentSyncPullContinuation } from \"../../documents/shared/pullContinuation\";\nimport type {\n DocumentRecord as BaseDocumentRecord,\n PendingUpdateFields,\n PendingUpdateRecord,\n} from \"../../sqlite/documentPersistence\";\nimport type { ExecSql } from \"../../sqlite/sqlSchema\";\n\nexport type { PendingUpdateRecord } from \"../../sqlite/documentPersistence\";\n\nexport interface StoredDocumentRecord extends BaseDocumentRecord {\n containerId: string | null;\n documentKind?: StoredDocumentKind;\n text: string;\n title?: string;\n}\n\nexport type DiscardDocumentToShellResult =\n | { discarded: false }\n | {\n discarded: true;\n documentKind: StoredDocumentKind;\n };\n\nexport interface PendingUpdateInsert extends PendingUpdateFields {\n localId: string;\n}\n\n/**\n * The identity a pending attachment upload reuses across attempts so a retry\n * (next sync pass or after a restart) reproduces byte-identical encrypted bytes\n * and resumes the same multipart stage instead of orphaning it. `contentKey` and\n * the chunk `nonceSeed` are base64. `partSize`/`stageId` are filled in once the\n * multipart stage has been opened and remain null before that.\n */\nexport interface PendingAttachmentUploadIdentity {\n blobId: string;\n contentKey: string;\n contentKeyEpoch: number;\n nonceSeed: string;\n partSize: number | null;\n plaintextSha256: string;\n stageId: string | null;\n}\n\nexport interface PendingAttachmentRecord {\n byteLength: number;\n contentSha256: string;\n localId: string;\n mimeType: string | null;\n name: string;\n slotId: string;\n storageKey: string;\n upload?: PendingAttachmentUploadIdentity | null;\n}\n\nexport interface LocalAttachmentRecord {\n blobId: string | null;\n byteLength: number;\n // Plaintext digest of the bytes at `storageKey`. Compared with the digest\n // the document content records for the slot: a held copy that differs is a\n // validly signed served binding the document has not recorded (yet).\n contentSha256: string;\n // Set once the slot is unlinked from a synced document and cleared when the\n // slot is written again. The record survives the unlink so the next sync can\n // still detach the remote binding; `detachedAt` is what keeps the local read\n // models from reporting the slot as a live blob reference in the meantime.\n detachedAt: string | null;\n localId: string;\n mimeType: string | null;\n slotId: string;\n storageKey: string;\n}\n\nexport interface AttachmentStagingRows {\n localAttachments: ReadonlyArray<LocalAttachmentRecord>;\n pendingAttachments: ReadonlyArray<PendingAttachmentRecord>;\n}\n\nexport interface AttachmentRemovalRows {\n mode: \"delete\" | \"detach\";\n slotId: string;\n storageKey: string;\n}\n\nexport interface DocumentHistoryRestoreState {\n snapshot: string;\n tailUpdates: readonly {\n origin: \"local\" | \"remote\";\n updateData: string;\n }[];\n}\n\nexport interface RelinkPersistedDocumentInput {\n accessEpoch: number;\n accessStateHash?: string | null;\n containerId: string;\n documentId: string | null;\n localId: string;\n}\n\nexport interface ContainerDocumentTombstoneInput {\n containerId: string;\n documentId: string;\n /** Epoch of the verified head; apply refuses a head below local state. */\n accessEpoch: number;\n /** Verified head link set (omits `containerId`); apply needs signed evidence. */\n linkedContainerIds: ReadonlyArray<string>;\n updatedAt: string;\n}\n\nexport type DocumentSummarySortDirection = \"asc\" | \"desc\";\nexport type DocumentSummarySortKey = \"kind\" | \"title\" | \"updated\";\n\nexport interface DocumentSummarySort {\n readonly direction: DocumentSummarySortDirection;\n readonly key: DocumentSummarySortKey;\n}\n\nexport interface ListDocumentSummariesInput {\n documentKind?: StoredDocumentKind | undefined;\n limit?: number | undefined;\n offset?: number | undefined;\n sort?: DocumentSummarySort | undefined;\n}\n\nexport interface DocumentSummaryList {\n readonly rows: ReadonlyArray<DocumentSummary>;\n readonly totalCount: number;\n}\n\nexport interface DocumentsPersistence {\n /**\n * The adapter guarantees that recovery checkpoint replacement, selection of\n * covered local history, rejection of unproven ordinary pending rows, and\n * quarantine of every queued checkpoint artifact share the guarded\n * `commitDocumentMutation` transaction, which also advances the canonical\n * recovery generation. An unrelated history tail not covered by the rebuild\n * must abort that transaction.\n */\n readonly supportsAtomicRecoveryHistoryPruning: boolean;\n /**\n * Atomically create the canonical row, standard projections, and birth\n * checkpoint. Returns null when another initializer already owns localId.\n */\n createDocumentWithHistoryCheckpoint: (\n execSql: ExecSql,\n document: StoredDocumentRecord,\n historyCheckpoint: {\n endVersionVector: string;\n snapshot: string;\n },\n options:\n | {\n pendingUpdate?: PendingUpdateFields;\n /** Recheck volatile caller ownership inside the create transaction. */\n stillCurrent?: (() => boolean) | undefined;\n updatedAt?: string;\n }\n | undefined,\n saveClientProjection: (\n transactionExecSql: ExecSql,\n updatedAt: string,\n ) => Promise<void>,\n ) => Promise<string | null>;\n /**\n * Conditionally commit one already-prepared mutation against the exact\n * durable record it was derived from. The identity/progress comparison,\n * attachment and history writes, accepted-queue settlement,\n * canonical/projection save, and client projection callback must share one\n * adapter transaction.\n */\n commitDocumentMutation: (\n execSql: ExecSql,\n input: {\n acceptedPendingUpdateIds: readonly string[];\n attachmentRemoval?: AttachmentRemovalRows | undefined;\n attachmentStaging?: AttachmentStagingRows | undefined;\n clearSyncFailure?: boolean | undefined;\n document: StoredDocumentRecord;\n expectedRecord: StoredDocumentRecord;\n historyCheckpoint?:\n | {\n coveredTailIds: readonly string[];\n endVersionVector: string;\n pruneCoveredLocalState: boolean;\n snapshot: string;\n }\n | undefined;\n historyUpdateOrigin?: \"local\" | \"remote\" | undefined;\n historyUpdates?: readonly string[] | undefined;\n pendingUpdate?: PendingUpdateFields | undefined;\n settleAcceptedPendingOnConflict: boolean;\n /**\n * Recheck volatile caller ownership inside the adapter transaction,\n * before any durable side effect. A false result aborts the commit.\n */\n stillCurrent?: (() => boolean) | undefined;\n updatedAt?: string | undefined;\n },\n saveClientProjection: (\n transactionExecSql: ExecSql,\n updatedAt: string,\n ) => Promise<void>,\n ) => Promise<\n | { committed: true; updatedAt: string }\n | { committed: false; currentRecord: StoredDocumentRecord | null }\n >;\n /** Settle acknowledged rows only while the response's security identity survives. */\n settleAcceptedPendingUpdates: (\n execSql: ExecSql,\n input: {\n expectedRecord: StoredDocumentRecord;\n pendingUpdateIds: readonly string[];\n },\n ) => Promise<StoredDocumentRecord | null>;\n ensureSchema: (execSql: ExecSql) => Promise<void>;\n listDocuments: (execSql: ExecSql) => Promise<DocumentSummary[]>;\n listDocumentSummaries: (\n execSql: ExecSql,\n input?: ListDocumentSummariesInput,\n ) => Promise<DocumentSummaryList>;\n listDocumentsByContainerIdsOrDocumentIds: (\n execSql: ExecSql,\n input: {\n containerIds: ReadonlyArray<string>;\n documentIds: ReadonlyArray<string>;\n },\n ) => Promise<DocumentSummary[]>;\n findDocumentLocalIdsByContainerId: (\n execSql: ExecSql,\n containerId: string,\n ) => Promise<string[]>;\n /**\n * Select one local row for a remote document identity. Prefer any row with\n * queued updates or a deferred-sync frontier, then break duplicates by\n * updatedAt and localId descending.\n */\n findLocalIdByDocumentId: (\n execSql: ExecSql,\n documentId: string,\n ) => Promise<string | null>;\n /** Canonical-row probe; false authorizes destructive orphan teardown. */\n hasDocument: (execSql: ExecSql, localId: string) => Promise<boolean>;\n /** Observe whether the canonical row still names the expected remote stream. */\n documentIdentityMatches: (\n execSql: ExecSql,\n localId: string,\n expectedDocumentId: string | null,\n expectedRecoveryGeneration: number,\n ) => Promise<boolean>;\n loadDocument: (\n execSql: ExecSql,\n localId: string,\n ) => Promise<StoredDocumentRecord | null>;\n /** Read the canonical record and its history from one database snapshot. */\n loadDocumentWithHistoryRestoreState: (\n execSql: ExecSql,\n localId: string,\n ) => Promise<{\n document: StoredDocumentRecord | null;\n historyRestoreState: DocumentHistoryRestoreState | null;\n }>;\n /** Read every document-store startup row from one database snapshot. */\n loadDocumentStoreState: (\n execSql: ExecSql,\n localId: string,\n ) => Promise<{\n document: StoredDocumentRecord | null;\n historyRestoreState: DocumentHistoryRestoreState | null;\n localAttachments: LocalAttachmentRecord[];\n pendingAttachments: PendingAttachmentRecord[];\n }>;\n /**\n * Atomically replace the exact rejected pull continuation with the durable\n * recovery marker when every supplied sync-identity field still matches.\n * Return the authoritative current record after the compare-and-set, or\n * null when the canonical row no longer exists. A CAS loss returns the\n * winning record so the live store can adopt its newer progress.\n */\n invalidatePullContinuation: (\n execSql: ExecSql,\n input: {\n accessEpoch: number;\n accessStateHash: string | null;\n continuation: DocumentSyncPullContinuation;\n contentKeyBundle: string | null;\n documentId: string;\n documentKekTargets: string | null;\n documentManifestBundle: string | null;\n lastCommitLsn: string | null;\n localId: string;\n },\n ) => Promise<{\n historyRestoreState: DocumentHistoryRestoreState | null;\n record: StoredDocumentRecord;\n } | null>;\n // Read the authoritative container placement for a locally persisted document\n // straight from its projection row. Returns `undefined` when no projection row\n // exists yet (a first create/discovery persist), so a caller can distinguish\n // that from a row that legitimately carries a null container (a document\n // unlinked from every container). Container placement is owned by the\n // link/tombstone/discovery layer, so a content-metadata persist reads this to\n // avoid republishing a stale in-memory container.\n loadDocumentContainer: (\n execSql: ExecSql,\n localId: string,\n ) => Promise<{ containerId: string | null } | undefined>;\n saveDocument: (\n execSql: ExecSql,\n document: StoredDocumentRecord,\n options?: {\n updatedAt?: string;\n },\n ) => Promise<string>;\n /**\n * Durable full-history support (checkpoint + append-only tail) — the ONLY\n * persisted content source. REQUIRED: a persistence without these could\n * report successful writes whose documents reopen empty.\n */\n appendHistoryUpdates: (\n execSql: ExecSql,\n input: {\n localId: string;\n origin: \"local\" | \"remote\";\n updates: readonly string[];\n },\n ) => Promise<void>;\n loadHistoryRestoreState: (\n execSql: ExecSql,\n localId: string,\n ) => Promise<DocumentHistoryRestoreState | null>;\n readHistoryTailSize: (\n execSql: ExecSql,\n localId: string,\n ) => Promise<{\n byteLength: number;\n hasCheckpoint: boolean;\n rowCount: number;\n }>;\n listHistoryTailEntries: (\n execSql: ExecSql,\n localId: string,\n ) => Promise<{ id: string; updateData: string }[]>;\n replaceHistoryCheckpoint: (\n execSql: ExecSql,\n input: {\n coveredTailIds: readonly string[];\n endVersionVector: string;\n force?: boolean;\n localId: string;\n snapshot: string;\n stillCurrent?: () => boolean;\n },\n ) => Promise<void>;\n saveDocumentAndDeletePendingUpdates: (\n execSql: ExecSql,\n document: StoredDocumentRecord,\n pendingUpdateIds: readonly string[],\n options?: {\n updatedAt?: string;\n },\n ) => Promise<string>;\n deleteDocument: (execSql: ExecSql, localId: string) => Promise<void>;\n /**\n * Delete the canonical row and every side row only when its security identity\n * still matches the captured record. The comparison, deletes, and client\n * projection callback must share one write transaction.\n */\n deleteDocumentIfMatches: (\n execSql: ExecSql,\n expectedRecord: StoredDocumentRecord,\n deleteClientProjection: (transactionExecSql: ExecSql) => Promise<void>,\n ) => Promise<boolean>;\n /**\n * Delete orphaned side rows only if the canonical document is still absent.\n * The absence check, row cleanup, and client projection callback must share\n * one write transaction so a concurrent create is preserved.\n */\n deleteDocumentSideRowsIfAbsent: (\n execSql: ExecSql,\n localId: string,\n expectedDocumentId: string | null,\n deleteClientProjection: (transactionExecSql: ExecSql) => Promise<void>,\n ) => Promise<boolean>;\n upsertDiscoveredDocument: (\n execSql: ExecSql,\n input: DiscoveredDocumentInput,\n ) => Promise<DocumentSummary>;\n relinkPersistedDocument: (\n execSql: ExecSql,\n input: RelinkPersistedDocumentInput,\n ) => Promise<DocumentSummary | null>;\n listPendingUpdates: (\n execSql: ExecSql,\n localId: string,\n ) => Promise<PendingUpdateRecord[]>;\n rekeyPendingUpdate: (execSql: ExecSql, id: string) => Promise<string | null>;\n listPendingAttachments: (\n execSql: ExecSql,\n localId: string,\n ) => Promise<PendingAttachmentRecord[]>;\n listLocalAttachments: (\n execSql: ExecSql,\n localId: string,\n ) => Promise<LocalAttachmentRecord[]>;\n /**\n * Durably append the outgoing row and matching local-history tail entry.\n * When expectations are supplied, compare the document identity and recovery\n * generation with the canonical row inside that same mutation and return\n * false on absence or mismatch. Adapters must not insert either row.\n */\n enqueuePendingUpdate: (\n execSql: ExecSql,\n pendingUpdate: PendingUpdateInsert,\n options?: {\n expectedDocumentId: string | null;\n expectedRecoveryGeneration: number;\n },\n ) => Promise<boolean>;\n /** Replace a hydrated copy only if its observed durable slot and live document still match. */\n saveHydratedAttachment: (\n execSql: ExecSql,\n input: {\n attachment: LocalAttachmentRecord;\n expectedStorageKey: string | null;\n expectedSnapshotEndVersion: string | null;\n stillCurrent: () => boolean;\n },\n ) => Promise<boolean>;\n saveLocalAttachment: (\n execSql: ExecSql,\n attachment: LocalAttachmentRecord,\n ) => Promise<void>;\n deleteLocalAttachment: (\n execSql: ExecSql,\n localId: string,\n slotId: string,\n storageKey: string,\n ) => Promise<void>;\n markLocalAttachmentDetached: (\n execSql: ExecSql,\n localId: string,\n slotId: string,\n storageKey: string,\n ) => Promise<void>;\n savePendingAttachment: (\n execSql: ExecSql,\n attachment: PendingAttachmentRecord,\n ) => Promise<void>;\n deletePendingUpdate: (execSql: ExecSql, id: string) => Promise<void>;\n deletePendingUpdates: (execSql: ExecSql, localId: string) => Promise<void>;\n deletePendingAttachment: (\n execSql: ExecSql,\n localId: string,\n slotId: string,\n storageKey: string,\n ) => Promise<void>;\n deletePendingAttachments: (\n execSql: ExecSql,\n localId: string,\n ) => Promise<void>;\n /**\n * Atomically convert a stuck document's local state to the\n * freshly-discovered-share shell (see the SQL implementation's doc\n * comment). The projector registry clears the document-kind client\n * projection in the same transaction — those tables live with the app's\n * projector definitions, so the CALLER'S registry is required (the default\n * registry knows no app kinds, and a discard that silently kept a\n * contact's or card's projected fields would leak the discarded values).\n * The method itself is optional: implementations without the full document\n * schema (container metadata, simple doubles) simply do not offer the\n * discard escape hatch.\n */\n /**\n * `expectedDocumentId` is revalidated against the loaded record inside the\n * serialized mutation: a stale caller (or a relink that raced the request)\n * must never discard a different identity's edits.\n */\n discardDocumentToShell?: (\n execSql: ExecSql,\n localId: string,\n expectedDocumentId: string,\n documentProjectors: DocumentProjectorRegistryInput,\n ) => Promise<DiscardDocumentToShellResult>;\n}\n"]}
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["types.ts"],"names":[],"mappings":"","sourcesContent":["import type {\n DocumentProjectorRegistryInput,\n StoredDocumentKind,\n} from \"../../documents/documentKinds\";\nimport type {\n DiscoveredDocumentInput,\n DocumentSummary,\n} from \"../../documents/documentSummary\";\nimport type { DocumentSyncPullContinuation } from \"../../documents/shared/pullContinuation\";\nimport type {\n DocumentRecord as BaseDocumentRecord,\n PendingUpdateFields,\n PendingUpdateRecord,\n} from \"../../sqlite/documentPersistence\";\nimport type { ExecSql } from \"../../sqlite/sqlSchema\";\nimport type { DocumentOrphanBlobPersistence } from \"./orphanBlobPersistence\";\n\nexport type { PendingUpdateRecord } from \"../../sqlite/documentPersistence\";\n\nexport interface StoredDocumentRecord extends BaseDocumentRecord {\n containerId: string | null;\n documentKind?: StoredDocumentKind;\n text: string;\n title?: string;\n}\n\nexport type DiscardDocumentToShellResult =\n | { discarded: false }\n | {\n discarded: true;\n documentKind: StoredDocumentKind;\n };\n\nexport interface PendingUpdateInsert extends PendingUpdateFields {\n localId: string;\n}\n\n/**\n * The identity a pending attachment upload reuses across attempts so a retry\n * (next sync pass or after a restart) reproduces byte-identical encrypted bytes\n * and resumes the same multipart stage instead of orphaning it. `contentKey` and\n * the chunk `nonceSeed` are base64. `partSize`/`stageId` are filled in once the\n * multipart stage has been opened and remain null before that.\n */\nexport interface PendingAttachmentUploadIdentity {\n blobId: string;\n contentKey: string;\n contentKeyEpoch: number;\n nonceSeed: string;\n partSize: number | null;\n plaintextSha256: string;\n stageId: string | null;\n}\n\nexport interface PendingAttachmentRecord {\n byteLength: number;\n contentSha256: string;\n localId: string;\n mimeType: string | null;\n name: string;\n slotId: string;\n storageKey: string;\n upload?: PendingAttachmentUploadIdentity | null;\n}\n\nexport interface LocalAttachmentRecord {\n blobId: string | null;\n byteLength: number;\n // Plaintext digest of the bytes at `storageKey`. Compared with the digest\n // the document content records for the slot: a held copy that differs is a\n // validly signed served binding the document has not recorded (yet).\n contentSha256: string;\n // Set once the slot is unlinked from a synced document and cleared when the\n // slot is written again. The record survives the unlink so the next sync can\n // still detach the remote binding; `detachedAt` is what keeps the local read\n // models from reporting the slot as a live blob reference in the meantime.\n detachedAt: string | null;\n localId: string;\n mimeType: string | null;\n slotId: string;\n storageKey: string;\n}\n\nexport interface AttachmentStagingRows {\n localAttachments: ReadonlyArray<LocalAttachmentRecord>;\n pendingAttachments: ReadonlyArray<PendingAttachmentRecord>;\n}\n\nexport interface AttachmentRemovalRows {\n mode: \"delete\" | \"detach\";\n slotId: string;\n storageKey: string;\n}\n\nexport interface DocumentHistoryRestoreState {\n snapshot: string;\n tailUpdates: readonly {\n origin: \"local\" | \"remote\";\n updateData: string;\n }[];\n}\n\nexport interface RelinkPersistedDocumentInput {\n accessEpoch: number;\n accessStateHash?: string | null;\n containerId: string;\n documentId: string | null;\n localId: string;\n}\n\nexport interface ContainerDocumentTombstoneInput {\n containerId: string;\n documentId: string;\n /** Epoch of the verified head; apply refuses a head below local state. */\n accessEpoch: number;\n /** Verified head link set (omits `containerId`); apply needs signed evidence. */\n linkedContainerIds: ReadonlyArray<string>;\n updatedAt: string;\n}\n\nexport type DocumentSummarySortDirection = \"asc\" | \"desc\";\nexport type DocumentSummarySortKey = \"kind\" | \"title\" | \"updated\";\n\nexport interface DocumentSummarySort {\n readonly direction: DocumentSummarySortDirection;\n readonly key: DocumentSummarySortKey;\n}\n\nexport interface ListDocumentSummariesInput {\n documentKind?: StoredDocumentKind | undefined;\n limit?: number | undefined;\n offset?: number | undefined;\n sort?: DocumentSummarySort | undefined;\n}\n\nexport interface DocumentSummaryList {\n readonly rows: ReadonlyArray<DocumentSummary>;\n readonly totalCount: number;\n}\n\nexport interface DocumentsPersistence {\n readonly orphanBlobs: DocumentOrphanBlobPersistence;\n /**\n * The adapter guarantees that recovery checkpoint replacement, selection of\n * covered local history, rejection of unproven ordinary pending rows, and\n * quarantine of every queued checkpoint artifact share the guarded\n * `commitDocumentMutation` transaction, which also advances the canonical\n * recovery generation. An unrelated history tail not covered by the rebuild\n * must abort that transaction.\n */\n readonly supportsAtomicRecoveryHistoryPruning: boolean;\n /**\n * Atomically create the canonical row, standard projections, and birth\n * checkpoint. Returns null when another initializer already owns localId.\n */\n createDocumentWithHistoryCheckpoint: (\n execSql: ExecSql,\n document: StoredDocumentRecord,\n historyCheckpoint: {\n endVersionVector: string;\n snapshot: string;\n },\n options:\n | {\n pendingUpdate?: PendingUpdateFields;\n /** Recheck volatile caller ownership inside the create transaction. */\n stillCurrent?: (() => boolean) | undefined;\n updatedAt?: string;\n }\n | undefined,\n saveClientProjection: (\n transactionExecSql: ExecSql,\n updatedAt: string,\n ) => Promise<void>,\n ) => Promise<string | null>;\n /**\n * Conditionally commit one already-prepared mutation against the exact\n * durable record it was derived from. The identity/progress comparison,\n * attachment and history writes, accepted-queue settlement,\n * canonical/projection save, and client projection callback must share one\n * adapter transaction.\n */\n commitDocumentMutation: (\n execSql: ExecSql,\n input: {\n acceptedPendingUpdateIds: readonly string[];\n attachmentRemoval?: AttachmentRemovalRows | undefined;\n attachmentStaging?: AttachmentStagingRows | undefined;\n clearSyncFailure?: boolean | undefined;\n document: StoredDocumentRecord;\n expectedRecord: StoredDocumentRecord;\n historyCheckpoint?:\n | {\n coveredTailIds: readonly string[];\n endVersionVector: string;\n pruneCoveredLocalState: boolean;\n snapshot: string;\n }\n | undefined;\n historyUpdateOrigin?: \"local\" | \"remote\" | undefined;\n historyUpdates?: readonly string[] | undefined;\n pendingUpdate?: PendingUpdateFields | undefined;\n settleAcceptedPendingOnConflict: boolean;\n /**\n * Recheck volatile caller ownership inside the adapter transaction,\n * before any durable side effect. A false result aborts the commit.\n */\n stillCurrent?: (() => boolean) | undefined;\n updatedAt?: string | undefined;\n },\n saveClientProjection: (\n transactionExecSql: ExecSql,\n updatedAt: string,\n ) => Promise<void>,\n ) => Promise<\n | { committed: true; updatedAt: string }\n | { committed: false; currentRecord: StoredDocumentRecord | null }\n >;\n /** Settle acknowledged rows only while the response's security identity survives. */\n settleAcceptedPendingUpdates: (\n execSql: ExecSql,\n input: {\n expectedRecord: StoredDocumentRecord;\n pendingUpdateIds: readonly string[];\n },\n ) => Promise<StoredDocumentRecord | null>;\n ensureSchema: (execSql: ExecSql) => Promise<void>;\n listDocuments: (execSql: ExecSql) => Promise<DocumentSummary[]>;\n listDocumentSummaries: (\n execSql: ExecSql,\n input?: ListDocumentSummariesInput,\n ) => Promise<DocumentSummaryList>;\n listDocumentsByContainerIdsOrDocumentIds: (\n execSql: ExecSql,\n input: {\n containerIds: ReadonlyArray<string>;\n documentIds: ReadonlyArray<string>;\n },\n ) => Promise<DocumentSummary[]>;\n findDocumentLocalIdsByContainerId: (\n execSql: ExecSql,\n containerId: string,\n ) => Promise<string[]>;\n /**\n * Select one local row for a remote document identity. Prefer any row with\n * queued updates or a deferred-sync frontier, then break duplicates by\n * updatedAt and localId descending.\n */\n findLocalIdByDocumentId: (\n execSql: ExecSql,\n documentId: string,\n ) => Promise<string | null>;\n /** Canonical-row probe; false authorizes destructive orphan teardown. */\n hasDocument: (execSql: ExecSql, localId: string) => Promise<boolean>;\n /** Observe whether the canonical row still names the expected remote stream. */\n documentIdentityMatches: (\n execSql: ExecSql,\n localId: string,\n expectedDocumentId: string | null,\n expectedRecoveryGeneration: number,\n ) => Promise<boolean>;\n loadDocument: (\n execSql: ExecSql,\n localId: string,\n ) => Promise<StoredDocumentRecord | null>;\n /** Read the canonical record and its history from one database snapshot. */\n loadDocumentWithHistoryRestoreState: (\n execSql: ExecSql,\n localId: string,\n ) => Promise<{\n document: StoredDocumentRecord | null;\n historyRestoreState: DocumentHistoryRestoreState | null;\n }>;\n /** Read every document-store startup row from one database snapshot. */\n loadDocumentStoreState: (\n execSql: ExecSql,\n localId: string,\n ) => Promise<{\n document: StoredDocumentRecord | null;\n historyRestoreState: DocumentHistoryRestoreState | null;\n localAttachments: LocalAttachmentRecord[];\n pendingAttachments: PendingAttachmentRecord[];\n }>;\n /**\n * Atomically replace the exact rejected pull continuation with the durable\n * recovery marker when every supplied sync-identity field still matches.\n * Return the authoritative current record after the compare-and-set, or\n * null when the canonical row no longer exists. A CAS loss returns the\n * winning record so the live store can adopt its newer progress.\n */\n invalidatePullContinuation: (\n execSql: ExecSql,\n input: {\n accessEpoch: number;\n accessStateHash: string | null;\n continuation: DocumentSyncPullContinuation;\n contentKeyBundle: string | null;\n documentId: string;\n documentKekTargets: string | null;\n documentManifestBundle: string | null;\n lastCommitLsn: string | null;\n localId: string;\n },\n ) => Promise<{\n historyRestoreState: DocumentHistoryRestoreState | null;\n record: StoredDocumentRecord;\n } | null>;\n // Read the authoritative container placement for a locally persisted document\n // straight from its projection row. Returns `undefined` when no projection row\n // exists yet (a first create/discovery persist), so a caller can distinguish\n // that from a row that legitimately carries a null container (a document\n // unlinked from every container). Container placement is owned by the\n // link/tombstone/discovery layer, so a content-metadata persist reads this to\n // avoid republishing a stale in-memory container.\n loadDocumentContainer: (\n execSql: ExecSql,\n localId: string,\n ) => Promise<{ containerId: string | null } | undefined>;\n saveDocument: (\n execSql: ExecSql,\n document: StoredDocumentRecord,\n options?: {\n updatedAt?: string;\n },\n ) => Promise<string>;\n /**\n * Durable full-history support (checkpoint + append-only tail) — the ONLY\n * persisted content source. REQUIRED: a persistence without these could\n * report successful writes whose documents reopen empty.\n */\n appendHistoryUpdates: (\n execSql: ExecSql,\n input: {\n localId: string;\n origin: \"local\" | \"remote\";\n updates: readonly string[];\n },\n ) => Promise<void>;\n loadHistoryRestoreState: (\n execSql: ExecSql,\n localId: string,\n ) => Promise<DocumentHistoryRestoreState | null>;\n readHistoryTailSize: (\n execSql: ExecSql,\n localId: string,\n ) => Promise<{\n byteLength: number;\n hasCheckpoint: boolean;\n rowCount: number;\n }>;\n listHistoryTailEntries: (\n execSql: ExecSql,\n localId: string,\n ) => Promise<{ id: string; updateData: string }[]>;\n replaceHistoryCheckpoint: (\n execSql: ExecSql,\n input: {\n coveredTailIds: readonly string[];\n endVersionVector: string;\n force?: boolean;\n localId: string;\n snapshot: string;\n stillCurrent?: () => boolean;\n },\n ) => Promise<void>;\n saveDocumentAndDeletePendingUpdates: (\n execSql: ExecSql,\n document: StoredDocumentRecord,\n pendingUpdateIds: readonly string[],\n options?: {\n updatedAt?: string;\n },\n ) => Promise<string>;\n deleteDocument: (execSql: ExecSql, localId: string) => Promise<void>;\n /**\n * Delete the canonical row and every side row only when its security identity\n * still matches the captured record. The comparison, deletes, and client\n * projection callback must share one write transaction.\n */\n deleteDocumentIfMatches: (\n execSql: ExecSql,\n expectedRecord: StoredDocumentRecord,\n deleteClientProjection: (transactionExecSql: ExecSql) => Promise<void>,\n ) => Promise<boolean>;\n /**\n * Delete orphaned side rows only if the canonical document is still absent.\n * The absence check, row cleanup, and client projection callback must share\n * one write transaction so a concurrent create is preserved.\n */\n deleteDocumentSideRowsIfAbsent: (\n execSql: ExecSql,\n localId: string,\n expectedDocumentId: string | null,\n deleteClientProjection: (transactionExecSql: ExecSql) => Promise<void>,\n ) => Promise<boolean>;\n upsertDiscoveredDocument: (\n execSql: ExecSql,\n input: DiscoveredDocumentInput,\n ) => Promise<DocumentSummary>;\n relinkPersistedDocument: (\n execSql: ExecSql,\n input: RelinkPersistedDocumentInput,\n ) => Promise<DocumentSummary | null>;\n listPendingUpdates: (\n execSql: ExecSql,\n localId: string,\n ) => Promise<PendingUpdateRecord[]>;\n rekeyPendingUpdate: (execSql: ExecSql, id: string) => Promise<string | null>;\n listPendingAttachments: (\n execSql: ExecSql,\n localId: string,\n ) => Promise<PendingAttachmentRecord[]>;\n listLocalAttachments: (\n execSql: ExecSql,\n localId: string,\n ) => Promise<LocalAttachmentRecord[]>;\n /**\n * Durably append the outgoing row and matching local-history tail entry.\n * When expectations are supplied, compare the document identity and recovery\n * generation with the canonical row inside that same mutation and return\n * false on absence or mismatch. Adapters must not insert either row.\n */\n enqueuePendingUpdate: (\n execSql: ExecSql,\n pendingUpdate: PendingUpdateInsert,\n options?: {\n expectedDocumentId: string | null;\n expectedRecoveryGeneration: number;\n },\n ) => Promise<boolean>;\n /** Replace a hydrated copy only if its observed durable slot and live document still match. */\n saveHydratedAttachment: (\n execSql: ExecSql,\n input: {\n attachment: LocalAttachmentRecord;\n expectedStorageKey: string | null;\n expectedSnapshotEndVersion: string | null;\n stillCurrent: () => boolean;\n },\n ) => Promise<boolean>;\n saveLocalAttachment: (\n execSql: ExecSql,\n attachment: LocalAttachmentRecord,\n ) => Promise<void>;\n deleteLocalAttachment: (\n execSql: ExecSql,\n localId: string,\n slotId: string,\n storageKey: string,\n ) => Promise<void>;\n markLocalAttachmentDetached: (\n execSql: ExecSql,\n localId: string,\n slotId: string,\n storageKey: string,\n ) => Promise<void>;\n savePendingAttachment: (\n execSql: ExecSql,\n attachment: PendingAttachmentRecord,\n ) => Promise<void>;\n deletePendingUpdate: (execSql: ExecSql, id: string) => Promise<void>;\n deletePendingUpdates: (execSql: ExecSql, localId: string) => Promise<void>;\n deletePendingAttachment: (\n execSql: ExecSql,\n localId: string,\n slotId: string,\n storageKey: string,\n ) => Promise<void>;\n deletePendingAttachments: (\n execSql: ExecSql,\n localId: string,\n ) => Promise<void>;\n /**\n * Atomically convert a stuck document's local state to the\n * freshly-discovered-share shell (see the SQL implementation's doc\n * comment). The projector registry clears the document-kind client\n * projection in the same transaction — those tables live with the app's\n * projector definitions, so the CALLER'S registry is required (the default\n * registry knows no app kinds, and a discard that silently kept a\n * contact's or card's projected fields would leak the discarded values).\n * The method itself is optional: implementations without the full document\n * schema (container metadata, simple doubles) simply do not offer the\n * discard escape hatch.\n */\n /**\n * `expectedDocumentId` is revalidated against the loaded record inside the\n * serialized mutation: a stale caller (or a relink that raced the request)\n * must never discard a different identity's edits.\n */\n discardDocumentToShell?: (\n execSql: ExecSql,\n localId: string,\n expectedDocumentId: string,\n documentProjectors: DocumentProjectorRegistryInput,\n ) => Promise<DiscardDocumentToShellResult>;\n}\n"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tearleads/client-sdk",
3
- "version": "0.1.23",
3
+ "version": "0.1.24",
4
4
  "description": "React-free Tearleads client runtime: local SQLite persistence, keys, sync, encrypted blobs, and domain workflows.",
5
5
  "license": "UNLICENSED",
6
6
  "repository": {
@@ -1,9 +1,9 @@
1
1
  /** Hydrate only bytes matching the current document's authenticated attachment intent. */
2
2
  import { getDocumentAttachments } from "../../../data/documents/documentContent.js";
3
3
  import { hydrateDocumentAttachmentBlobs } from "../../../workflows/blobs/index.js";
4
- import { reclaimDocumentOrphanBlobs, } from "../../../workflows/documents/index.js";
5
4
  import { createRuntimePrincipalPolicyWarmer } from "../../../workflows/principals/runtimePolicyWarmer.js";
6
5
  import { commitHydratedAttachment } from "./attachmentHydrationCommit.js";
6
+ import { runDocumentOrphanMaintenance } from "./orphanMaintenance.js";
7
7
  import { isDocumentStoreSyncGenerationCurrent as isSyncGenerationCurrent, } from "./syncGeneration.js";
8
8
  export async function hydrateAttachmentBlobs(state, currentDoc, currentRecord, expectedGeneration) {
9
9
  const generationIsCurrent = () => !expectedGeneration || isSyncGenerationCurrent(state, expectedGeneration);
@@ -52,6 +52,6 @@ export async function hydrateAttachmentBlobs(state, currentDoc, currentRecord, e
52
52
  expectedStorageKey: expectedStorageKeys[hydratedBlob.attachment.slotId] ?? null,
53
53
  });
54
54
  }
55
- await reclaimDocumentOrphanBlobs(runtime);
55
+ void runDocumentOrphanMaintenance(state);
56
56
  }
57
57
  //# sourceMappingURL=attachmentHydration.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"attachmentHydration.js","sourceRoot":"","sources":["attachmentHydration.ts"],"names":[],"mappings":"AAAA,0FAA0F;AAE1F,OAAO,EAAE,sBAAsB,EAAE,MAAM,yCAAyC,CAAC;AACjF,OAAO,EAAE,8BAA8B,EAAE,MAAM,0BAA0B,CAAC;AAC1E,OAAO,EAEL,0BAA0B,GAC3B,MAAM,8BAA8B,CAAC;AACtC,OAAO,EAAE,kCAAkC,EAAE,MAAM,mDAAmD,CAAC;AACvG,OAAO,EAAE,wBAAwB,EAAE,MAAM,6BAA6B,CAAC;AAEvE,OAAO,EAEL,oCAAoC,IAAI,uBAAuB,GAChE,MAAM,kBAAkB,CAAC;AAE1B,MAAM,CAAC,KAAK,UAAU,sBAAsB,CAC1C,KAAyB,EACzB,UAAyB,EACzB,aAAoC,EACpC,kBAAgD;IAEhD,MAAM,mBAAmB,GAAG,GAAG,EAAE,CAC/B,CAAC,kBAAkB,IAAI,uBAAuB,CAAC,KAAK,EAAE,kBAAkB,CAAC,CAAC;IAC5E,IAAI,CAAC,mBAAmB,EAAE;QAAE,OAAO;IAEnC,MAAM,OAAO,GAAG,KAAK,CAAC,OAAO,CAAC;IAC9B,MAAM,oBAAoB,GAAG,OAAO,CAAC,MAAM,CAAC,oBAAoB,CAAC;IACjE,IACE,CAAC,oBAAoB;QACrB,CAAC,OAAO,CAAC,IAAI,CAAC,eAAe;QAC7B,CAAC,OAAO,CAAC,KAAK,CAAC,MAAM;QACrB,CAAC,aAAa,EAAE,UAAU,EAC1B,CAAC;QACD,OAAO;IACT,CAAC;IAED,MAAM,WAAW,GAAG,sBAAsB,CAAC,UAAU,CAAC,CAAC;IACvD,IAAI,WAAW,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QAC7B,OAAO;IACT,CAAC;IAED,MAAM,mBAAmB,GAAG,EAAE,GAAG,KAAK,CAAC,4BAA4B,EAAE,CAAC;IACtE,MAAM,aAAa,GAAG,MAAM,8BAA8B,CAAC;QACzD,SAAS,EAAE,OAAO,CAAC,SAAS;QAC5B,WAAW;QACX,UAAU,EAAE,aAAa,CAAC,UAAU;QACpC,OAAO,EAAE,OAAO,CAAC,KAAK,CAAC,OAAO;QAC9B,mBAAmB,EAAE,KAAK,CAAC,wBAAwB;QACnD,uBAAuB,EAAE,KAAK,CAAC,4BAA4B;QAC3D,GAAG,EAAE,OAAO,CAAC,IAAI,CAAC,GAAG;QACrB,sBAAsB,EAAE,KAAK,CAAC,gCAAgC;QAC9D,sBAAsB,EAAE,OAAO,CAAC,IAAI,CAAC,sBAAsB;QAC3D,wBAAwB,EACtB,kBAAkB,EAAE,wBAAwB;YAC5C,KAAK,CAAC,wBAAwB;QAChC,eAAe,EAAE,oBAAoB,CAAC,SAAS;QAC/C,YAAY,EAAE,mBAAmB;QACjC,+BAA+B,EAC7B,kCAAkC,CAAC,OAAO,CAAC;KAC9C,CAAC,CAAC;IACH,IAAI,CAAC,mBAAmB,EAAE;QAAE,OAAO;IACnC,IAAI,CAAC,aAAa,EAAE,CAAC;QACnB,OAAO;IACT,CAAC;IAED,KAAK,MAAM,YAAY,IAAI,aAAa,EAAE,CAAC;QACzC,MAAM,wBAAwB,CAAC;YAC7B,KAAK;YACL,UAAU;YACV,YAAY;YACZ,mBAAmB;YACnB,kBAAkB,EAChB,mBAAmB,CAAC,YAAY,CAAC,UAAU,CAAC,MAAM,CAAC,IAAI,IAAI;SAC9D,CAAC,CAAC;IACL,CAAC;IACD,MAAM,0BAA0B,CAAC,OAAO,CAAC,CAAC;AAC5C,CAAC","sourcesContent":["/** Hydrate only bytes matching the current document's authenticated attachment intent. */\n\nimport { getDocumentAttachments } from \"../../../data/documents/documentContent\";\nimport { hydrateDocumentAttachmentBlobs } from \"../../../workflows/blobs\";\nimport {\n type DocumentRecord,\n reclaimDocumentOrphanBlobs,\n} from \"../../../workflows/documents\";\nimport { createRuntimePrincipalPolicyWarmer } from \"../../../workflows/principals/runtimePolicyWarmer\";\nimport { commitHydratedAttachment } from \"./attachmentHydrationCommit\";\nimport type { DocumentState, DocumentStoreState } from \"./state\";\nimport {\n type DocumentStoreSyncGeneration,\n isDocumentStoreSyncGenerationCurrent as isSyncGenerationCurrent,\n} from \"./syncGeneration\";\n\nexport async function hydrateAttachmentBlobs(\n state: DocumentStoreState,\n currentDoc: DocumentState,\n currentRecord: DocumentRecord | null,\n expectedGeneration?: DocumentStoreSyncGeneration,\n) {\n const generationIsCurrent = () =>\n !expectedGeneration || isSyncGenerationCurrent(state, expectedGeneration);\n if (!generationIsCurrent()) return;\n\n const runtime = state.runtime;\n const encapsulationKeyPair = runtime.crypto.encapsulationKeyPair;\n if (\n !encapsulationKeyPair ||\n !runtime.auth.isAuthenticated ||\n !runtime.state.online ||\n !currentRecord?.documentId\n ) {\n return;\n }\n\n const attachments = getDocumentAttachments(currentDoc);\n if (attachments.length === 0) {\n return;\n }\n\n const expectedStorageKeys = { ...state.attachmentStorageKeyBySlotId };\n const hydratedBlobs = await hydrateDocumentAttachmentBlobs({\n apiClient: runtime.apiClient,\n attachments,\n documentId: currentRecord.documentId,\n execSql: runtime.infra.execSql,\n localBlobIdBySlotId: state.attachmentBlobIdBySlotId,\n localStorageKeyBySlotId: state.attachmentStorageKeyBySlotId,\n log: runtime.util.log,\n rejectedServedBindings: state.rejectedServedAttachmentBindings,\n reportSecurityIncident: runtime.util.reportSecurityIncident,\n resolveProjectionUserKey:\n expectedGeneration?.resolveProjectionUserKey ??\n state.resolveProjectionUserKey,\n targetSecretKey: encapsulationKeyPair.secretKey,\n stillCurrent: generationIsCurrent,\n warmReferencedPrincipalPolicies:\n createRuntimePrincipalPolicyWarmer(runtime),\n });\n if (!generationIsCurrent()) return;\n if (!hydratedBlobs) {\n return;\n }\n\n for (const hydratedBlob of hydratedBlobs) {\n await commitHydratedAttachment({\n state,\n currentDoc,\n hydratedBlob,\n generationIsCurrent,\n expectedStorageKey:\n expectedStorageKeys[hydratedBlob.attachment.slotId] ?? null,\n });\n }\n await reclaimDocumentOrphanBlobs(runtime);\n}\n"]}
1
+ {"version":3,"file":"attachmentHydration.js","sourceRoot":"","sources":["attachmentHydration.ts"],"names":[],"mappings":"AAAA,0FAA0F;AAE1F,OAAO,EAAE,sBAAsB,EAAE,MAAM,yCAAyC,CAAC;AACjF,OAAO,EAAE,8BAA8B,EAAE,MAAM,0BAA0B,CAAC;AAE1E,OAAO,EAAE,kCAAkC,EAAE,MAAM,mDAAmD,CAAC;AACvG,OAAO,EAAE,wBAAwB,EAAE,MAAM,6BAA6B,CAAC;AACvE,OAAO,EAAE,4BAA4B,EAAE,MAAM,qBAAqB,CAAC;AAEnE,OAAO,EAEL,oCAAoC,IAAI,uBAAuB,GAChE,MAAM,kBAAkB,CAAC;AAE1B,MAAM,CAAC,KAAK,UAAU,sBAAsB,CAC1C,KAAyB,EACzB,UAAyB,EACzB,aAAoC,EACpC,kBAAgD;IAEhD,MAAM,mBAAmB,GAAG,GAAG,EAAE,CAC/B,CAAC,kBAAkB,IAAI,uBAAuB,CAAC,KAAK,EAAE,kBAAkB,CAAC,CAAC;IAC5E,IAAI,CAAC,mBAAmB,EAAE;QAAE,OAAO;IAEnC,MAAM,OAAO,GAAG,KAAK,CAAC,OAAO,CAAC;IAC9B,MAAM,oBAAoB,GAAG,OAAO,CAAC,MAAM,CAAC,oBAAoB,CAAC;IACjE,IACE,CAAC,oBAAoB;QACrB,CAAC,OAAO,CAAC,IAAI,CAAC,eAAe;QAC7B,CAAC,OAAO,CAAC,KAAK,CAAC,MAAM;QACrB,CAAC,aAAa,EAAE,UAAU,EAC1B,CAAC;QACD,OAAO;IACT,CAAC;IAED,MAAM,WAAW,GAAG,sBAAsB,CAAC,UAAU,CAAC,CAAC;IACvD,IAAI,WAAW,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QAC7B,OAAO;IACT,CAAC;IAED,MAAM,mBAAmB,GAAG,EAAE,GAAG,KAAK,CAAC,4BAA4B,EAAE,CAAC;IACtE,MAAM,aAAa,GAAG,MAAM,8BAA8B,CAAC;QACzD,SAAS,EAAE,OAAO,CAAC,SAAS;QAC5B,WAAW;QACX,UAAU,EAAE,aAAa,CAAC,UAAU;QACpC,OAAO,EAAE,OAAO,CAAC,KAAK,CAAC,OAAO;QAC9B,mBAAmB,EAAE,KAAK,CAAC,wBAAwB;QACnD,uBAAuB,EAAE,KAAK,CAAC,4BAA4B;QAC3D,GAAG,EAAE,OAAO,CAAC,IAAI,CAAC,GAAG;QACrB,sBAAsB,EAAE,KAAK,CAAC,gCAAgC;QAC9D,sBAAsB,EAAE,OAAO,CAAC,IAAI,CAAC,sBAAsB;QAC3D,wBAAwB,EACtB,kBAAkB,EAAE,wBAAwB;YAC5C,KAAK,CAAC,wBAAwB;QAChC,eAAe,EAAE,oBAAoB,CAAC,SAAS;QAC/C,YAAY,EAAE,mBAAmB;QACjC,+BAA+B,EAC7B,kCAAkC,CAAC,OAAO,CAAC;KAC9C,CAAC,CAAC;IACH,IAAI,CAAC,mBAAmB,EAAE;QAAE,OAAO;IACnC,IAAI,CAAC,aAAa,EAAE,CAAC;QACnB,OAAO;IACT,CAAC;IAED,KAAK,MAAM,YAAY,IAAI,aAAa,EAAE,CAAC;QACzC,MAAM,wBAAwB,CAAC;YAC7B,KAAK;YACL,UAAU;YACV,YAAY;YACZ,mBAAmB;YACnB,kBAAkB,EAChB,mBAAmB,CAAC,YAAY,CAAC,UAAU,CAAC,MAAM,CAAC,IAAI,IAAI;SAC9D,CAAC,CAAC;IACL,CAAC;IACD,KAAK,4BAA4B,CAAC,KAAK,CAAC,CAAC;AAC3C,CAAC","sourcesContent":["/** Hydrate only bytes matching the current document's authenticated attachment intent. */\n\nimport { getDocumentAttachments } from \"../../../data/documents/documentContent\";\nimport { hydrateDocumentAttachmentBlobs } from \"../../../workflows/blobs\";\nimport type { DocumentRecord } from \"../../../workflows/documents\";\nimport { createRuntimePrincipalPolicyWarmer } from \"../../../workflows/principals/runtimePolicyWarmer\";\nimport { commitHydratedAttachment } from \"./attachmentHydrationCommit\";\nimport { runDocumentOrphanMaintenance } from \"./orphanMaintenance\";\nimport type { DocumentState, DocumentStoreState } from \"./state\";\nimport {\n type DocumentStoreSyncGeneration,\n isDocumentStoreSyncGenerationCurrent as isSyncGenerationCurrent,\n} from \"./syncGeneration\";\n\nexport async function hydrateAttachmentBlobs(\n state: DocumentStoreState,\n currentDoc: DocumentState,\n currentRecord: DocumentRecord | null,\n expectedGeneration?: DocumentStoreSyncGeneration,\n) {\n const generationIsCurrent = () =>\n !expectedGeneration || isSyncGenerationCurrent(state, expectedGeneration);\n if (!generationIsCurrent()) return;\n\n const runtime = state.runtime;\n const encapsulationKeyPair = runtime.crypto.encapsulationKeyPair;\n if (\n !encapsulationKeyPair ||\n !runtime.auth.isAuthenticated ||\n !runtime.state.online ||\n !currentRecord?.documentId\n ) {\n return;\n }\n\n const attachments = getDocumentAttachments(currentDoc);\n if (attachments.length === 0) {\n return;\n }\n\n const expectedStorageKeys = { ...state.attachmentStorageKeyBySlotId };\n const hydratedBlobs = await hydrateDocumentAttachmentBlobs({\n apiClient: runtime.apiClient,\n attachments,\n documentId: currentRecord.documentId,\n execSql: runtime.infra.execSql,\n localBlobIdBySlotId: state.attachmentBlobIdBySlotId,\n localStorageKeyBySlotId: state.attachmentStorageKeyBySlotId,\n log: runtime.util.log,\n rejectedServedBindings: state.rejectedServedAttachmentBindings,\n reportSecurityIncident: runtime.util.reportSecurityIncident,\n resolveProjectionUserKey:\n expectedGeneration?.resolveProjectionUserKey ??\n state.resolveProjectionUserKey,\n targetSecretKey: encapsulationKeyPair.secretKey,\n stillCurrent: generationIsCurrent,\n warmReferencedPrincipalPolicies:\n createRuntimePrincipalPolicyWarmer(runtime),\n });\n if (!generationIsCurrent()) return;\n if (!hydratedBlobs) {\n return;\n }\n\n for (const hydratedBlob of hydratedBlobs) {\n await commitHydratedAttachment({\n state,\n currentDoc,\n hydratedBlob,\n generationIsCurrent,\n expectedStorageKey:\n expectedStorageKeys[hydratedBlob.attachment.slotId] ?? null,\n });\n }\n void runDocumentOrphanMaintenance(state);\n}\n"]}
@@ -8,6 +8,7 @@ import { removeLocalAttachmentSlot } from "./attachmentPersistence.js";
8
8
  import { deleteUnreferencedStagedAttachmentBytes, installPendingAttachmentRows, stagePendingAttachments, } from "./attachmentStaging.js";
9
9
  import { captureAttachmentWriteGeneration, isAttachmentWriteGenerationCurrent, shouldAbortAttachmentPersistFollowup, } from "./attachmentWriteGeneration.js";
10
10
  import { ensureDocumentStoreReady } from "./initialization.js";
11
+ import { runDocumentOrphanMaintenance } from "./orphanMaintenance.js";
11
12
  import { advancePendingBaseVersion, pendingDeltaSinceBase, persistDocument, } from "./persistence.js";
12
13
  import { reportDocumentStoreWriteFailure } from "./reportWriteFailure.js";
13
14
  import { canAttachFiles, canWriteDocument, setReadySnapshot, } from "./state.js";
@@ -171,19 +172,20 @@ async function persistSlotAttachmentFile(state, slotId, file, writeGeneration) {
171
172
  state.runtime.util.log(`Queued attachment ${file.name} for slot ${slotId}.`);
172
173
  requestDocumentStoreSync(state);
173
174
  }
174
- async function installCommittedAttachmentRemoval(input) {
175
+ function installCommittedAttachmentRemoval(input) {
175
176
  const { slotId, state, storageKey } = input;
176
177
  if (!storageKey)
177
178
  return;
178
179
  state.pendingAttachments = state.pendingAttachments.filter((attachment) => attachment.slotId !== slotId || attachment.storageKey !== storageKey);
180
+ // The committed removal queued this key. A reset can leave another document
181
+ // uploading from the same copy, even though this document is local-only.
182
+ if (!input.syncedAttachment)
183
+ void runDocumentOrphanMaintenance(state);
179
184
  if (input.syncedAttachment ||
180
185
  state.attachmentStorageKeyBySlotId[slotId] !== storageKey) {
181
186
  return;
182
187
  }
183
188
  removeLocalAttachmentSlot(state, slotId);
184
- await state.runtime.infra.blobStore
185
- .deleteBytes(storageKey)
186
- .catch(() => state.runtime.util.log(`Documents: failed to delete detached attachment bytes for ${slotId}.`));
187
189
  setReadySnapshot(state, input.currentDoc, state.snapshot.syncing, state.snapshot.text, state.snapshot.structuredFields);
188
190
  }
189
191
  async function persistRemovedAttachment(state, slotId, writeGeneration) {
@@ -244,7 +246,7 @@ async function persistRemovedAttachment(state, slotId, writeGeneration) {
244
246
  // See persistAttachedFiles: only durable same-identity work re-arms sync.
245
247
  return;
246
248
  }
247
- await installCommittedAttachmentRemoval({
249
+ installCommittedAttachmentRemoval({
248
250
  currentDoc,
249
251
  slotId,
250
252
  state,
@@ -1 +1 @@
1
- {"version":3,"file":"attachments.js","sourceRoot":"","sources":["attachments.ts"],"names":[],"mappings":"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","sourcesContent":["import {\n encodeVersionVector,\n exportFullHistorySnapshot,\n} from \"@tearleads/loro\";\nimport {\n addDocumentAttachments,\n getDocumentAttachments,\n removeDocumentAttachment,\n} from \"../../../data/documents/documentContent\";\nimport { createPendingUpdateFields } from \"../../../data/documents/documentSync\";\nimport type { PendingAttachmentRecord } from \"../../../workflows/documents\";\nimport { requestDocumentStoreSync } from \"../registry\";\nimport type { DocumentAttachmentUpload } from \"../types\";\nimport {\n buildAttachmentIntent,\n buildPendingAttachments,\n} from \"./attachmentIntents\";\nimport {\n persistStagedAttachmentMutation,\n restoreFailedAttachmentMutation,\n} from \"./attachmentMutationPersistence\";\nimport { removeLocalAttachmentSlot } from \"./attachmentPersistence\";\nimport {\n deleteUnreferencedStagedAttachmentBytes,\n installPendingAttachmentRows,\n stagePendingAttachments,\n} from \"./attachmentStaging\";\nimport {\n type AttachmentWriteGeneration,\n captureAttachmentWriteGeneration,\n isAttachmentWriteGenerationCurrent,\n shouldAbortAttachmentPersistFollowup,\n} from \"./attachmentWriteGeneration\";\nimport { ensureDocumentStoreReady } from \"./initialization\";\nimport {\n advancePendingBaseVersion,\n pendingDeltaSinceBase,\n persistDocument,\n} from \"./persistence\";\nimport { reportDocumentStoreWriteFailure } from \"./reportWriteFailure\";\nimport {\n canAttachFiles,\n canWriteDocument,\n type DocumentStoreState,\n setReadySnapshot,\n} from \"./state\";\nimport { captureDocumentStoreSyncGeneration } from \"./syncGeneration\";\n\n// Fixed literal: a reported message must never carry file names or slot ids.\nconst ATTACHMENT_FAILURE_MESSAGE = \"Documents: attachment mutation failed\";\n\nfunction logAttachedFiles(state: DocumentStoreState, count: number) {\n state.runtime.util.log(\n state.runtime.state.online && state.runtime.auth.isAuthenticated\n ? `Attached ${count} file${count === 1 ? \"\" : \"s\"} to document ${state.localId}.`\n : `Stored ${count} attachment${count === 1 ? \"\" : \"s\"} locally for document ${state.localId}.`,\n );\n}\n\nasync function persistAttachedFiles(\n state: DocumentStoreState,\n files: ReadonlyArray<DocumentAttachmentUpload>,\n writeGeneration: AttachmentWriteGeneration,\n) {\n if (!isAttachmentWriteGenerationCurrent(state, writeGeneration)) return;\n const currentDoc = state.doc;\n\n if (!currentDoc || !canAttachFiles(state)) {\n state.runtime.util.log(\n \"Documents: attachments require a local key package.\",\n );\n return;\n }\n const generation = captureDocumentStoreSyncGeneration(state, currentDoc);\n if (!generation) {\n return;\n }\n\n const { nextAttachments, nextPendingAttachments } =\n await buildPendingAttachments(state.localId, files);\n if (\n !isAttachmentWriteGenerationCurrent(state, writeGeneration) ||\n state.doc !== currentDoc\n )\n return;\n const rollbackSnapshot = exportFullHistorySnapshot(currentDoc).slice();\n addDocumentAttachments(currentDoc, nextAttachments);\n const attachmentUpdate = pendingDeltaSinceBase(state, currentDoc);\n // Captured at delta time: the enqueued row proves coverage up to exactly\n // this version, so it is the frontier the persist below may publish.\n const coveredVersion = encodeVersionVector(currentDoc);\n\n let staged: Awaited<ReturnType<typeof stagePendingAttachments>>;\n try {\n staged = await stagePendingAttachments({\n files,\n generation,\n pendingAttachments: nextPendingAttachments,\n state,\n });\n } catch (error) {\n await restoreFailedAttachmentMutation({\n generation,\n rollbackSnapshot,\n state,\n });\n throw error;\n }\n if (!staged) {\n await restoreFailedAttachmentMutation({\n generation,\n rollbackSnapshot,\n state,\n });\n return;\n }\n // This persist changes attachments, not prose. setReadySnapshot always\n // re-derives attachments from the doc, but preserve the text/structured\n // fields so an attach that overlaps an in-flight keystroke does not republish\n // a stale doc read over the live optimistic editor value.\n const persisted = await persistStagedAttachmentMutation({\n attachmentUpdate,\n coveredVersion,\n currentDoc,\n generation,\n rollbackSnapshot,\n staged,\n state,\n });\n if (\n shouldAbortAttachmentPersistFollowup(\n state,\n writeGeneration,\n generation,\n persisted,\n )\n ) {\n await deleteUnreferencedStagedAttachmentBytes({\n generation,\n pendingAttachments: staged.pendingAttachments,\n state,\n });\n return;\n }\n installPendingAttachmentRows({ ...staged, state });\n advancePendingBaseVersion(state, currentDoc);\n logAttachedFiles(state, files.length);\n requestDocumentStoreSync(state);\n}\n\nasync function persistSlotAttachmentFile(\n state: DocumentStoreState,\n slotId: string,\n file: DocumentAttachmentUpload,\n writeGeneration: AttachmentWriteGeneration,\n) {\n if (!isAttachmentWriteGenerationCurrent(state, writeGeneration)) return;\n const currentDoc = state.doc;\n\n if (!currentDoc || !canAttachFiles(state)) {\n state.runtime.util.log(\n \"Documents: slot attachments require a local key package.\",\n );\n return;\n }\n const generation = captureDocumentStoreSyncGeneration(state, currentDoc);\n if (!generation) {\n return;\n }\n\n const replacementAttachment = await buildAttachmentIntent(slotId, file);\n if (\n !isAttachmentWriteGenerationCurrent(state, writeGeneration) ||\n state.doc !== currentDoc\n )\n return;\n const rollbackSnapshot = exportFullHistorySnapshot(currentDoc).slice();\n addDocumentAttachments(currentDoc, [replacementAttachment]);\n const attachmentUpdate = pendingDeltaSinceBase(state, currentDoc);\n const coveredVersion = encodeVersionVector(currentDoc);\n\n const storageKey = `${state.localId}-${slotId}-${crypto.randomUUID()}`;\n const pendingAttachment: PendingAttachmentRecord = {\n byteLength: replacementAttachment.byteLength,\n contentSha256: replacementAttachment.contentSha256,\n localId: state.localId,\n mimeType: replacementAttachment.mimeType,\n name: replacementAttachment.name,\n slotId,\n storageKey,\n };\n let staged: Awaited<ReturnType<typeof stagePendingAttachments>>;\n try {\n staged = await stagePendingAttachments({\n files: [file],\n generation,\n pendingAttachments: [pendingAttachment],\n state,\n });\n } catch (error) {\n await restoreFailedAttachmentMutation({\n generation,\n rollbackSnapshot,\n state,\n });\n throw error;\n }\n if (!staged) {\n await restoreFailedAttachmentMutation({\n generation,\n rollbackSnapshot,\n state,\n });\n return;\n }\n // Preserve the optimistic text/structured fields (see persistAttachedFiles):\n // a slot replacement overlapping a keystroke must not regress the editor.\n const persisted = await persistStagedAttachmentMutation({\n attachmentUpdate,\n coveredVersion,\n currentDoc,\n generation,\n rollbackSnapshot,\n staged,\n state,\n });\n if (\n shouldAbortAttachmentPersistFollowup(\n state,\n writeGeneration,\n generation,\n persisted,\n )\n ) {\n await deleteUnreferencedStagedAttachmentBytes({\n generation,\n pendingAttachments: staged.pendingAttachments,\n state,\n });\n return;\n }\n installPendingAttachmentRows({ ...staged, state });\n advancePendingBaseVersion(state, currentDoc);\n state.runtime.util.log(`Queued attachment ${file.name} for slot ${slotId}.`);\n requestDocumentStoreSync(state);\n}\n\nasync function installCommittedAttachmentRemoval(input: {\n currentDoc: NonNullable<DocumentStoreState[\"doc\"]>;\n slotId: string;\n state: DocumentStoreState;\n storageKey: string | undefined;\n syncedAttachment: boolean;\n}): Promise<void> {\n const { slotId, state, storageKey } = input;\n if (!storageKey) return;\n state.pendingAttachments = state.pendingAttachments.filter(\n (attachment) =>\n attachment.slotId !== slotId || attachment.storageKey !== storageKey,\n );\n if (\n input.syncedAttachment ||\n state.attachmentStorageKeyBySlotId[slotId] !== storageKey\n ) {\n return;\n }\n removeLocalAttachmentSlot(state, slotId);\n await state.runtime.infra.blobStore\n .deleteBytes(storageKey)\n .catch(() =>\n state.runtime.util.log(\n `Documents: failed to delete detached attachment bytes for ${slotId}.`,\n ),\n );\n setReadySnapshot(\n state,\n input.currentDoc,\n state.snapshot.syncing,\n state.snapshot.text,\n state.snapshot.structuredFields,\n );\n}\n\nasync function persistRemovedAttachment(\n state: DocumentStoreState,\n slotId: string,\n writeGeneration: AttachmentWriteGeneration,\n) {\n if (!isAttachmentWriteGenerationCurrent(state, writeGeneration)) return;\n const currentDoc = state.doc;\n if (!currentDoc) {\n return;\n }\n const generation = captureDocumentStoreSyncGeneration(state, currentDoc);\n if (!generation) {\n return;\n }\n\n const existingAttachment = getDocumentAttachments(currentDoc).find(\n (attachment) => attachment.slotId === slotId,\n );\n if (!existingAttachment) {\n return;\n }\n\n const storageKey = state.attachmentStorageKeyBySlotId[slotId];\n if (\n !isAttachmentWriteGenerationCurrent(state, writeGeneration) ||\n state.doc !== currentDoc\n )\n return;\n const rollbackSnapshot = exportFullHistorySnapshot(currentDoc).slice();\n removeDocumentAttachment(currentDoc, slotId);\n const attachmentUpdate = pendingDeltaSinceBase(state, currentDoc);\n const coveredVersion = encodeVersionVector(currentDoc);\n const pendingUpdate = createPendingUpdateFields(attachmentUpdate);\n const syncedAttachment = Boolean(state.record?.documentId);\n\n // Preserve the optimistic text/structured fields (see persistAttachedFiles):\n // removing an attachment while typing must not regress the editor.\n let persisted: Awaited<ReturnType<typeof persistDocument>>;\n try {\n persisted = await persistDocument(\n state,\n currentDoc,\n { snapshotEndVersion: coveredVersion },\n {\n ...(storageKey\n ? {\n attachmentRemoval: {\n mode: syncedAttachment\n ? (\"detach\" as const)\n : (\"delete\" as const),\n slotId,\n storageKey,\n },\n }\n : {}),\n ...(pendingUpdate ? { pendingUpdate } : {}),\n preserveSnapshotStructuredFields: true,\n preserveSnapshotText: true,\n },\n generation,\n );\n } catch (error) {\n await restoreFailedAttachmentMutation({\n generation,\n rollbackSnapshot,\n state,\n });\n throw error;\n }\n if (\n shouldAbortAttachmentPersistFollowup(\n state,\n writeGeneration,\n generation,\n persisted,\n )\n ) {\n // See persistAttachedFiles: only durable same-identity work re-arms sync.\n return;\n }\n await installCommittedAttachmentRemoval({\n currentDoc,\n slotId,\n state,\n storageKey,\n syncedAttachment,\n });\n advancePendingBaseVersion(state, currentDoc);\n state.runtime.util.log(\n `Removed attachment ${existingAttachment.name} from document ${state.localId}.`,\n );\n requestDocumentStoreSync(state);\n}\n\nexport async function attachFilesToDocumentStore(\n state: DocumentStoreState,\n scheduleSync: () => void,\n files: ReadonlyArray<DocumentAttachmentUpload>,\n) {\n if (files.length === 0) {\n return;\n }\n\n let ready: boolean;\n try {\n ready = await ensureDocumentStoreReady(state, scheduleSync);\n } catch (error) {\n reportDocumentStoreWriteFailure(state, ATTACHMENT_FAILURE_MESSAGE, error);\n return;\n }\n\n if (!ready || !state.doc || !canWriteDocument(state)) {\n return;\n }\n\n const writeGeneration = captureAttachmentWriteGeneration(state);\n if (!writeGeneration) return;\n state.writeChain = state.writeChain\n .catch(() => undefined)\n .then(async () => persistAttachedFiles(state, files, writeGeneration))\n .catch((error: unknown) => {\n reportDocumentStoreWriteFailure(state, ATTACHMENT_FAILURE_MESSAGE, error);\n });\n return state.writeChain;\n}\n\nexport async function removeAttachmentFromDocumentStore(\n state: DocumentStoreState,\n scheduleSync: () => void,\n slotId: string,\n) {\n let ready: boolean;\n try {\n ready = await ensureDocumentStoreReady(state, scheduleSync);\n } catch (error) {\n reportDocumentStoreWriteFailure(state, ATTACHMENT_FAILURE_MESSAGE, error);\n return;\n }\n\n if (!ready || !state.doc || !canWriteDocument(state)) {\n return;\n }\n\n const writeGeneration = captureAttachmentWriteGeneration(state);\n if (!writeGeneration) return;\n state.writeChain = state.writeChain\n .catch(() => undefined)\n .then(async () => persistRemovedAttachment(state, slotId, writeGeneration))\n .catch((error: unknown) => {\n reportDocumentStoreWriteFailure(state, ATTACHMENT_FAILURE_MESSAGE, error);\n });\n return state.writeChain;\n}\n\nexport async function replaceAttachmentInDocumentStore(\n state: DocumentStoreState,\n scheduleSync: () => void,\n slotId: string,\n file: DocumentAttachmentUpload,\n) {\n let ready: boolean;\n try {\n ready = await ensureDocumentStoreReady(state, scheduleSync);\n } catch (error) {\n reportDocumentStoreWriteFailure(state, ATTACHMENT_FAILURE_MESSAGE, error);\n return;\n }\n\n if (!ready || !state.doc || !canWriteDocument(state)) {\n return;\n }\n\n const writeGeneration = captureAttachmentWriteGeneration(state);\n if (!writeGeneration) return;\n state.writeChain = state.writeChain\n .catch(() => undefined)\n .then(async () =>\n persistSlotAttachmentFile(state, slotId, file, writeGeneration),\n )\n .catch((error: unknown) => {\n reportDocumentStoreWriteFailure(state, ATTACHMENT_FAILURE_MESSAGE, error);\n });\n return state.writeChain;\n}\n"]}
1
+ {"version":3,"file":"attachments.js","sourceRoot":"","sources":["attachments.ts"],"names":[],"mappings":"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","sourcesContent":["import {\n encodeVersionVector,\n exportFullHistorySnapshot,\n} from \"@tearleads/loro\";\nimport {\n addDocumentAttachments,\n getDocumentAttachments,\n removeDocumentAttachment,\n} from \"../../../data/documents/documentContent\";\nimport { createPendingUpdateFields } from \"../../../data/documents/documentSync\";\nimport type { PendingAttachmentRecord } from \"../../../workflows/documents\";\nimport { requestDocumentStoreSync } from \"../registry\";\nimport type { DocumentAttachmentUpload } from \"../types\";\nimport {\n buildAttachmentIntent,\n buildPendingAttachments,\n} from \"./attachmentIntents\";\nimport {\n persistStagedAttachmentMutation,\n restoreFailedAttachmentMutation,\n} from \"./attachmentMutationPersistence\";\nimport { removeLocalAttachmentSlot } from \"./attachmentPersistence\";\nimport {\n deleteUnreferencedStagedAttachmentBytes,\n installPendingAttachmentRows,\n stagePendingAttachments,\n} from \"./attachmentStaging\";\nimport {\n type AttachmentWriteGeneration,\n captureAttachmentWriteGeneration,\n isAttachmentWriteGenerationCurrent,\n shouldAbortAttachmentPersistFollowup,\n} from \"./attachmentWriteGeneration\";\nimport { ensureDocumentStoreReady } from \"./initialization\";\nimport { runDocumentOrphanMaintenance } from \"./orphanMaintenance\";\nimport {\n advancePendingBaseVersion,\n pendingDeltaSinceBase,\n persistDocument,\n} from \"./persistence\";\nimport { reportDocumentStoreWriteFailure } from \"./reportWriteFailure\";\nimport {\n canAttachFiles,\n canWriteDocument,\n type DocumentStoreState,\n setReadySnapshot,\n} from \"./state\";\nimport { captureDocumentStoreSyncGeneration } from \"./syncGeneration\";\n\n// Fixed literal: a reported message must never carry file names or slot ids.\nconst ATTACHMENT_FAILURE_MESSAGE = \"Documents: attachment mutation failed\";\n\nfunction logAttachedFiles(state: DocumentStoreState, count: number) {\n state.runtime.util.log(\n state.runtime.state.online && state.runtime.auth.isAuthenticated\n ? `Attached ${count} file${count === 1 ? \"\" : \"s\"} to document ${state.localId}.`\n : `Stored ${count} attachment${count === 1 ? \"\" : \"s\"} locally for document ${state.localId}.`,\n );\n}\n\nasync function persistAttachedFiles(\n state: DocumentStoreState,\n files: ReadonlyArray<DocumentAttachmentUpload>,\n writeGeneration: AttachmentWriteGeneration,\n) {\n if (!isAttachmentWriteGenerationCurrent(state, writeGeneration)) return;\n const currentDoc = state.doc;\n\n if (!currentDoc || !canAttachFiles(state)) {\n state.runtime.util.log(\n \"Documents: attachments require a local key package.\",\n );\n return;\n }\n const generation = captureDocumentStoreSyncGeneration(state, currentDoc);\n if (!generation) {\n return;\n }\n\n const { nextAttachments, nextPendingAttachments } =\n await buildPendingAttachments(state.localId, files);\n if (\n !isAttachmentWriteGenerationCurrent(state, writeGeneration) ||\n state.doc !== currentDoc\n )\n return;\n const rollbackSnapshot = exportFullHistorySnapshot(currentDoc).slice();\n addDocumentAttachments(currentDoc, nextAttachments);\n const attachmentUpdate = pendingDeltaSinceBase(state, currentDoc);\n // Captured at delta time: the enqueued row proves coverage up to exactly\n // this version, so it is the frontier the persist below may publish.\n const coveredVersion = encodeVersionVector(currentDoc);\n\n let staged: Awaited<ReturnType<typeof stagePendingAttachments>>;\n try {\n staged = await stagePendingAttachments({\n files,\n generation,\n pendingAttachments: nextPendingAttachments,\n state,\n });\n } catch (error) {\n await restoreFailedAttachmentMutation({\n generation,\n rollbackSnapshot,\n state,\n });\n throw error;\n }\n if (!staged) {\n await restoreFailedAttachmentMutation({\n generation,\n rollbackSnapshot,\n state,\n });\n return;\n }\n // This persist changes attachments, not prose. setReadySnapshot always\n // re-derives attachments from the doc, but preserve the text/structured\n // fields so an attach that overlaps an in-flight keystroke does not republish\n // a stale doc read over the live optimistic editor value.\n const persisted = await persistStagedAttachmentMutation({\n attachmentUpdate,\n coveredVersion,\n currentDoc,\n generation,\n rollbackSnapshot,\n staged,\n state,\n });\n if (\n shouldAbortAttachmentPersistFollowup(\n state,\n writeGeneration,\n generation,\n persisted,\n )\n ) {\n await deleteUnreferencedStagedAttachmentBytes({\n generation,\n pendingAttachments: staged.pendingAttachments,\n state,\n });\n return;\n }\n installPendingAttachmentRows({ ...staged, state });\n advancePendingBaseVersion(state, currentDoc);\n logAttachedFiles(state, files.length);\n requestDocumentStoreSync(state);\n}\n\nasync function persistSlotAttachmentFile(\n state: DocumentStoreState,\n slotId: string,\n file: DocumentAttachmentUpload,\n writeGeneration: AttachmentWriteGeneration,\n) {\n if (!isAttachmentWriteGenerationCurrent(state, writeGeneration)) return;\n const currentDoc = state.doc;\n\n if (!currentDoc || !canAttachFiles(state)) {\n state.runtime.util.log(\n \"Documents: slot attachments require a local key package.\",\n );\n return;\n }\n const generation = captureDocumentStoreSyncGeneration(state, currentDoc);\n if (!generation) {\n return;\n }\n\n const replacementAttachment = await buildAttachmentIntent(slotId, file);\n if (\n !isAttachmentWriteGenerationCurrent(state, writeGeneration) ||\n state.doc !== currentDoc\n )\n return;\n const rollbackSnapshot = exportFullHistorySnapshot(currentDoc).slice();\n addDocumentAttachments(currentDoc, [replacementAttachment]);\n const attachmentUpdate = pendingDeltaSinceBase(state, currentDoc);\n const coveredVersion = encodeVersionVector(currentDoc);\n\n const storageKey = `${state.localId}-${slotId}-${crypto.randomUUID()}`;\n const pendingAttachment: PendingAttachmentRecord = {\n byteLength: replacementAttachment.byteLength,\n contentSha256: replacementAttachment.contentSha256,\n localId: state.localId,\n mimeType: replacementAttachment.mimeType,\n name: replacementAttachment.name,\n slotId,\n storageKey,\n };\n let staged: Awaited<ReturnType<typeof stagePendingAttachments>>;\n try {\n staged = await stagePendingAttachments({\n files: [file],\n generation,\n pendingAttachments: [pendingAttachment],\n state,\n });\n } catch (error) {\n await restoreFailedAttachmentMutation({\n generation,\n rollbackSnapshot,\n state,\n });\n throw error;\n }\n if (!staged) {\n await restoreFailedAttachmentMutation({\n generation,\n rollbackSnapshot,\n state,\n });\n return;\n }\n // Preserve the optimistic text/structured fields (see persistAttachedFiles):\n // a slot replacement overlapping a keystroke must not regress the editor.\n const persisted = await persistStagedAttachmentMutation({\n attachmentUpdate,\n coveredVersion,\n currentDoc,\n generation,\n rollbackSnapshot,\n staged,\n state,\n });\n if (\n shouldAbortAttachmentPersistFollowup(\n state,\n writeGeneration,\n generation,\n persisted,\n )\n ) {\n await deleteUnreferencedStagedAttachmentBytes({\n generation,\n pendingAttachments: staged.pendingAttachments,\n state,\n });\n return;\n }\n installPendingAttachmentRows({ ...staged, state });\n advancePendingBaseVersion(state, currentDoc);\n state.runtime.util.log(`Queued attachment ${file.name} for slot ${slotId}.`);\n requestDocumentStoreSync(state);\n}\n\nfunction installCommittedAttachmentRemoval(input: {\n currentDoc: NonNullable<DocumentStoreState[\"doc\"]>;\n slotId: string;\n state: DocumentStoreState;\n storageKey: string | undefined;\n syncedAttachment: boolean;\n}): void {\n const { slotId, state, storageKey } = input;\n if (!storageKey) return;\n state.pendingAttachments = state.pendingAttachments.filter(\n (attachment) =>\n attachment.slotId !== slotId || attachment.storageKey !== storageKey,\n );\n // The committed removal queued this key. A reset can leave another document\n // uploading from the same copy, even though this document is local-only.\n if (!input.syncedAttachment) void runDocumentOrphanMaintenance(state);\n if (\n input.syncedAttachment ||\n state.attachmentStorageKeyBySlotId[slotId] !== storageKey\n ) {\n return;\n }\n removeLocalAttachmentSlot(state, slotId);\n setReadySnapshot(\n state,\n input.currentDoc,\n state.snapshot.syncing,\n state.snapshot.text,\n state.snapshot.structuredFields,\n );\n}\n\nasync function persistRemovedAttachment(\n state: DocumentStoreState,\n slotId: string,\n writeGeneration: AttachmentWriteGeneration,\n) {\n if (!isAttachmentWriteGenerationCurrent(state, writeGeneration)) return;\n const currentDoc = state.doc;\n if (!currentDoc) {\n return;\n }\n const generation = captureDocumentStoreSyncGeneration(state, currentDoc);\n if (!generation) {\n return;\n }\n\n const existingAttachment = getDocumentAttachments(currentDoc).find(\n (attachment) => attachment.slotId === slotId,\n );\n if (!existingAttachment) {\n return;\n }\n\n const storageKey = state.attachmentStorageKeyBySlotId[slotId];\n if (\n !isAttachmentWriteGenerationCurrent(state, writeGeneration) ||\n state.doc !== currentDoc\n )\n return;\n const rollbackSnapshot = exportFullHistorySnapshot(currentDoc).slice();\n removeDocumentAttachment(currentDoc, slotId);\n const attachmentUpdate = pendingDeltaSinceBase(state, currentDoc);\n const coveredVersion = encodeVersionVector(currentDoc);\n const pendingUpdate = createPendingUpdateFields(attachmentUpdate);\n const syncedAttachment = Boolean(state.record?.documentId);\n\n // Preserve the optimistic text/structured fields (see persistAttachedFiles):\n // removing an attachment while typing must not regress the editor.\n let persisted: Awaited<ReturnType<typeof persistDocument>>;\n try {\n persisted = await persistDocument(\n state,\n currentDoc,\n { snapshotEndVersion: coveredVersion },\n {\n ...(storageKey\n ? {\n attachmentRemoval: {\n mode: syncedAttachment\n ? (\"detach\" as const)\n : (\"delete\" as const),\n slotId,\n storageKey,\n },\n }\n : {}),\n ...(pendingUpdate ? { pendingUpdate } : {}),\n preserveSnapshotStructuredFields: true,\n preserveSnapshotText: true,\n },\n generation,\n );\n } catch (error) {\n await restoreFailedAttachmentMutation({\n generation,\n rollbackSnapshot,\n state,\n });\n throw error;\n }\n if (\n shouldAbortAttachmentPersistFollowup(\n state,\n writeGeneration,\n generation,\n persisted,\n )\n ) {\n // See persistAttachedFiles: only durable same-identity work re-arms sync.\n return;\n }\n installCommittedAttachmentRemoval({\n currentDoc,\n slotId,\n state,\n storageKey,\n syncedAttachment,\n });\n advancePendingBaseVersion(state, currentDoc);\n state.runtime.util.log(\n `Removed attachment ${existingAttachment.name} from document ${state.localId}.`,\n );\n requestDocumentStoreSync(state);\n}\n\nexport async function attachFilesToDocumentStore(\n state: DocumentStoreState,\n scheduleSync: () => void,\n files: ReadonlyArray<DocumentAttachmentUpload>,\n) {\n if (files.length === 0) {\n return;\n }\n\n let ready: boolean;\n try {\n ready = await ensureDocumentStoreReady(state, scheduleSync);\n } catch (error) {\n reportDocumentStoreWriteFailure(state, ATTACHMENT_FAILURE_MESSAGE, error);\n return;\n }\n\n if (!ready || !state.doc || !canWriteDocument(state)) {\n return;\n }\n\n const writeGeneration = captureAttachmentWriteGeneration(state);\n if (!writeGeneration) return;\n state.writeChain = state.writeChain\n .catch(() => undefined)\n .then(async () => persistAttachedFiles(state, files, writeGeneration))\n .catch((error: unknown) => {\n reportDocumentStoreWriteFailure(state, ATTACHMENT_FAILURE_MESSAGE, error);\n });\n return state.writeChain;\n}\n\nexport async function removeAttachmentFromDocumentStore(\n state: DocumentStoreState,\n scheduleSync: () => void,\n slotId: string,\n) {\n let ready: boolean;\n try {\n ready = await ensureDocumentStoreReady(state, scheduleSync);\n } catch (error) {\n reportDocumentStoreWriteFailure(state, ATTACHMENT_FAILURE_MESSAGE, error);\n return;\n }\n\n if (!ready || !state.doc || !canWriteDocument(state)) {\n return;\n }\n\n const writeGeneration = captureAttachmentWriteGeneration(state);\n if (!writeGeneration) return;\n state.writeChain = state.writeChain\n .catch(() => undefined)\n .then(async () => persistRemovedAttachment(state, slotId, writeGeneration))\n .catch((error: unknown) => {\n reportDocumentStoreWriteFailure(state, ATTACHMENT_FAILURE_MESSAGE, error);\n });\n return state.writeChain;\n}\n\nexport async function replaceAttachmentInDocumentStore(\n state: DocumentStoreState,\n scheduleSync: () => void,\n slotId: string,\n file: DocumentAttachmentUpload,\n) {\n let ready: boolean;\n try {\n ready = await ensureDocumentStoreReady(state, scheduleSync);\n } catch (error) {\n reportDocumentStoreWriteFailure(state, ATTACHMENT_FAILURE_MESSAGE, error);\n return;\n }\n\n if (!ready || !state.doc || !canWriteDocument(state)) {\n return;\n }\n\n const writeGeneration = captureAttachmentWriteGeneration(state);\n if (!writeGeneration) return;\n state.writeChain = state.writeChain\n .catch(() => undefined)\n .then(async () =>\n persistSlotAttachmentFile(state, slotId, file, writeGeneration),\n )\n .catch((error: unknown) => {\n reportDocumentStoreWriteFailure(state, ATTACHMENT_FAILURE_MESSAGE, error);\n });\n return state.writeChain;\n}\n"]}
@@ -1,5 +1,6 @@
1
- import { discardPersistedDocumentToShell, reclaimDocumentOrphanBlobs, } from "../../../workflows/documents/index.js";
1
+ import { discardPersistedDocumentToShell } from "../../../workflows/documents/index.js";
2
2
  import { chainIdentityWrite } from "./identityWriteChain.js";
3
+ import { runDocumentOrphanMaintenance } from "./orphanMaintenance.js";
3
4
  import { resetDocumentStore } from "./state.js";
4
5
  /**
5
6
  * User-initiated escape hatch for a document whose queued local writes can no
@@ -79,7 +80,7 @@ export async function discardDocumentStoreLocalState(state, expectedDocumentId)
79
80
  }
80
81
  // The conversion queued the bytes its dropped rows pointed to; the
81
82
  // reclaim deletes each only once no other row references it.
82
- await reclaimDocumentOrphanBlobs(runtime);
83
+ void runDocumentOrphanMaintenance(state);
83
84
  resetDocumentStore(state);
84
85
  runtime.util.log(`Documents: discarded local edits for document ${state.localId}; re-pulling the server copy`);
85
86
  return true;
@@ -1 +1 @@
1
- {"version":3,"file":"discard.js","sourceRoot":"","sources":["discard.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,+BAA+B,EAC/B,0BAA0B,GAC3B,MAAM,8BAA8B,CAAC;AACtC,OAAO,EAAE,kBAAkB,EAAE,MAAM,sBAAsB,CAAC;AAC1D,OAAO,EAA2B,kBAAkB,EAAE,MAAM,SAAS,CAAC;AAEtE;;;;;;;;;;;;;;;;;;GAkBG;AACH,MAAM,CAAC,KAAK,UAAU,8BAA8B,CAClD,KAAyB,EACzB,kBAA0B;IAE1B,MAAM,OAAO,GAAG,KAAK,CAAC,OAAO,CAAC;IAC9B,IAAI,OAAO,CAAC,KAAK,CAAC,QAAQ,KAAK,OAAO,EAAE,CAAC;QACvC,OAAO,KAAK,CAAC;IACf,CAAC;IAED,yEAAyE;IACzE,0EAA0E;IAC1E,wEAAwE;IACxE,2EAA2E;IAC3E,kDAAkD;IAClD,MAAM,OAAO,GAAG,OAAO,CAAC,KAAK,CAAC,OAAO,CAAC;IACtC,MAAM,MAAM,GAAG,MAAM,KAAK,CAAC,WAAW,CAAC,YAAY,CAAC,OAAO,EAAE,KAAK,CAAC,OAAO,CAAC,CAAC;IAC5E,IACE,CAAC,MAAM,EAAE,UAAU;QACnB,MAAM,CAAC,UAAU,KAAK,kBAAkB;QACxC,CAAC,MAAM,CAAC,WAAW,EACnB,CAAC;QACD,OAAO,KAAK,CAAC;IACf,CAAC;IAED,uEAAuE;IACvE,sEAAsE;IACtE,yEAAyE;IACzE,sEAAsE;IACtE,yBAAyB;IACzB,KAAK,CAAC,GAAG,GAAG,IAAI,CAAC;IACjB,KAAK,CAAC,oBAAoB,IAAI,CAAC,CAAC;IAChC,2EAA2E;IAC3E,mEAAmE;IACnE,mEAAmE;IACnE,0EAA0E;IAC1E,2EAA2E;IAC3E,0EAA0E;IAC1E,+DAA+D;IAC/D,MAAM,KAAK,CAAC,UAAU,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC,SAAS,CAAC,CAAC;IAE9C,OAAO,kBAAkB,CAAC,KAAK,EAAE,KAAK,IAAI,EAAE;QAC1C,IAAI,KAAkE,CAAC;QACvE,IAAI,CAAC;YACH,KAAK,GAAG,MAAM,+BAA+B,CAAC;gBAC5C,kBAAkB,EAAE,OAAO,CAAC,KAAK,CAAC,kBAAkB;gBACpD,OAAO;gBACP,kBAAkB;gBAClB,OAAO,EAAE,KAAK,CAAC,OAAO;gBACtB,WAAW,EAAE,KAAK,CAAC,WAAW;aAC/B,CAAC,CAAC;QACL,CAAC;QAAC,MAAM,CAAC;YACP,wEAAwE;YACxE,qEAAqE;YACrE,gEAAgE;YAChE,kBAAkB,CAAC,KAAK,CAAC,CAAC;YAC1B,OAAO,CAAC,IAAI,CAAC,GAAG,CACd,0CAA0C,KAAK,CAAC,OAAO,yCAAyC,CACjG,CAAC;YACF,OAAO,KAAK,CAAC;QACf,CAAC;QACD,IAAI,CAAC,KAAK,CAAC,SAAS,EAAE,CAAC;YACrB,qEAAqE;YACrE,mEAAmE;YACnE,kBAAkB,CAAC,KAAK,CAAC,CAAC;YAC1B,OAAO,KAAK,CAAC;QACf,CAAC;QAED,mEAAmE;QACnE,6DAA6D;QAC7D,MAAM,0BAA0B,CAAC,OAAO,CAAC,CAAC;QAC1C,kBAAkB,CAAC,KAAK,CAAC,CAAC;QAC1B,OAAO,CAAC,IAAI,CAAC,GAAG,CACd,iDAAiD,KAAK,CAAC,OAAO,8BAA8B,CAC7F,CAAC;QACF,OAAO,IAAI,CAAC;IACd,CAAC,CAAC,CAAC;AACL,CAAC","sourcesContent":["import {\n discardPersistedDocumentToShell,\n reclaimDocumentOrphanBlobs,\n} from \"../../../workflows/documents\";\nimport { chainIdentityWrite } from \"./identityWriteChain\";\nimport { type DocumentStoreState, resetDocumentStore } from \"./state\";\n\n/**\n * User-initiated escape hatch for a document whose queued local writes can no\n * longer sync (e.g. an outgoing queue whose update ids conflict forever):\n * convert the locally persisted document to the freshly-discovered-share\n * shell (documentId kept, empty snapshot, queue/history/failure rows\n * dropped) so the store re-hydrates the server copy. The shell matters twice\n * over: priming's candidate scan reads the documents rows themselves, so a\n * fully deleted record would never be restored after a restart, and\n * re-initialization needs the record to hydrate instead of eagerly creating\n * a blank document. discardPersistedDocumentToShell owns the row-level\n * sequence — including refusing local-only, unlinked, and move-pending\n * documents under its serialized mutation — and converts the record in\n * place, so no step leaves the document row absent.\n *\n * The caller restarts initialization after this settles (success or not);\n * the reset store then hydrates from whatever rows the sequence left. That\n * restart must happen OUTSIDE the identity-chained task — initialization\n * chains identity writes of its own, so starting it there would deadlock.\n */\nexport async function discardDocumentStoreLocalState(\n state: DocumentStoreState,\n expectedDocumentId: string,\n): Promise<boolean> {\n const runtime = state.runtime;\n if (runtime.infra.dbStatus !== \"ready\") {\n return false;\n }\n\n // Cheap eligibility probe before disturbing the live store: an obviously\n // ineligible document (local-only, unlinked, or one whose identity is not\n // the one the caller asked to discard) returns without stopping writes.\n // The workflow re-checks under its serialized mutation, so a status change\n // between here and there is still refused safely.\n const execSql = runtime.infra.execSql;\n const record = await state.persistence.loadDocument(execSql, state.localId);\n if (\n !record?.documentId ||\n record.documentId !== expectedDocumentId ||\n !record.containerId\n ) {\n return false;\n }\n\n // Stop local writes before draining: dropping the live doc rejects new\n // mutations at queue time, and the generation bump invalidates queued\n // write tasks that have not started (and in-flight attachment passes and\n // initializations, which re-check the generation inside their writes'\n // serialized mutations).\n state.doc = null;\n state.localWriteGeneration += 1;\n // Drain OUTSIDE the identity chain. Write-chain tasks can themselves enter\n // the identity chain (the stale-heal recovery installs its rebuilt\n // document that way), so awaiting the write chain while holding an\n // identity-chain slot would deadlock: the drained task would queue behind\n // this one and each would wait on the other forever. Out here the drain is\n // a plain await; whatever the drained tasks chain onto the identity chain\n // runs before the teardown task below and is superseded by it.\n await state.writeChain.catch(() => undefined);\n\n return chainIdentityWrite(state, async () => {\n let shell: Awaited<ReturnType<typeof discardPersistedDocumentToShell>>;\n try {\n shell = await discardPersistedDocumentToShell({\n documentProjectors: runtime.infra.documentProjectors,\n execSql,\n expectedDocumentId,\n localId: state.localId,\n persistence: state.persistence,\n });\n } catch {\n // The conversion is in-place, so an interrupted sequence still leaves a\n // loadable record (old or shell). Reset so re-initialization reloads\n // whatever survived instead of running on the dropped live doc.\n resetDocumentStore(state);\n runtime.util.log(\n `Documents: discard failed for document ${state.localId}; store reset to reload persisted state`,\n );\n return false;\n }\n if (!shell.discarded) {\n // Refused under the lock (a move intent, or an identity change since\n // the probe): rows are untouched; reset so the store reloads them.\n resetDocumentStore(state);\n return false;\n }\n\n // The conversion queued the bytes its dropped rows pointed to; the\n // reclaim deletes each only once no other row references it.\n await reclaimDocumentOrphanBlobs(runtime);\n resetDocumentStore(state);\n runtime.util.log(\n `Documents: discarded local edits for document ${state.localId}; re-pulling the server copy`,\n );\n return true;\n });\n}\n"]}
1
+ {"version":3,"file":"discard.js","sourceRoot":"","sources":["discard.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,+BAA+B,EAAE,MAAM,8BAA8B,CAAC;AAC/E,OAAO,EAAE,kBAAkB,EAAE,MAAM,sBAAsB,CAAC;AAC1D,OAAO,EAAE,4BAA4B,EAAE,MAAM,qBAAqB,CAAC;AACnE,OAAO,EAA2B,kBAAkB,EAAE,MAAM,SAAS,CAAC;AAEtE;;;;;;;;;;;;;;;;;;GAkBG;AACH,MAAM,CAAC,KAAK,UAAU,8BAA8B,CAClD,KAAyB,EACzB,kBAA0B;IAE1B,MAAM,OAAO,GAAG,KAAK,CAAC,OAAO,CAAC;IAC9B,IAAI,OAAO,CAAC,KAAK,CAAC,QAAQ,KAAK,OAAO,EAAE,CAAC;QACvC,OAAO,KAAK,CAAC;IACf,CAAC;IAED,yEAAyE;IACzE,0EAA0E;IAC1E,wEAAwE;IACxE,2EAA2E;IAC3E,kDAAkD;IAClD,MAAM,OAAO,GAAG,OAAO,CAAC,KAAK,CAAC,OAAO,CAAC;IACtC,MAAM,MAAM,GAAG,MAAM,KAAK,CAAC,WAAW,CAAC,YAAY,CAAC,OAAO,EAAE,KAAK,CAAC,OAAO,CAAC,CAAC;IAC5E,IACE,CAAC,MAAM,EAAE,UAAU;QACnB,MAAM,CAAC,UAAU,KAAK,kBAAkB;QACxC,CAAC,MAAM,CAAC,WAAW,EACnB,CAAC;QACD,OAAO,KAAK,CAAC;IACf,CAAC;IAED,uEAAuE;IACvE,sEAAsE;IACtE,yEAAyE;IACzE,sEAAsE;IACtE,yBAAyB;IACzB,KAAK,CAAC,GAAG,GAAG,IAAI,CAAC;IACjB,KAAK,CAAC,oBAAoB,IAAI,CAAC,CAAC;IAChC,2EAA2E;IAC3E,mEAAmE;IACnE,mEAAmE;IACnE,0EAA0E;IAC1E,2EAA2E;IAC3E,0EAA0E;IAC1E,+DAA+D;IAC/D,MAAM,KAAK,CAAC,UAAU,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC,SAAS,CAAC,CAAC;IAE9C,OAAO,kBAAkB,CAAC,KAAK,EAAE,KAAK,IAAI,EAAE;QAC1C,IAAI,KAAkE,CAAC;QACvE,IAAI,CAAC;YACH,KAAK,GAAG,MAAM,+BAA+B,CAAC;gBAC5C,kBAAkB,EAAE,OAAO,CAAC,KAAK,CAAC,kBAAkB;gBACpD,OAAO;gBACP,kBAAkB;gBAClB,OAAO,EAAE,KAAK,CAAC,OAAO;gBACtB,WAAW,EAAE,KAAK,CAAC,WAAW;aAC/B,CAAC,CAAC;QACL,CAAC;QAAC,MAAM,CAAC;YACP,wEAAwE;YACxE,qEAAqE;YACrE,gEAAgE;YAChE,kBAAkB,CAAC,KAAK,CAAC,CAAC;YAC1B,OAAO,CAAC,IAAI,CAAC,GAAG,CACd,0CAA0C,KAAK,CAAC,OAAO,yCAAyC,CACjG,CAAC;YACF,OAAO,KAAK,CAAC;QACf,CAAC;QACD,IAAI,CAAC,KAAK,CAAC,SAAS,EAAE,CAAC;YACrB,qEAAqE;YACrE,mEAAmE;YACnE,kBAAkB,CAAC,KAAK,CAAC,CAAC;YAC1B,OAAO,KAAK,CAAC;QACf,CAAC;QAED,mEAAmE;QACnE,6DAA6D;QAC7D,KAAK,4BAA4B,CAAC,KAAK,CAAC,CAAC;QACzC,kBAAkB,CAAC,KAAK,CAAC,CAAC;QAC1B,OAAO,CAAC,IAAI,CAAC,GAAG,CACd,iDAAiD,KAAK,CAAC,OAAO,8BAA8B,CAC7F,CAAC;QACF,OAAO,IAAI,CAAC;IACd,CAAC,CAAC,CAAC;AACL,CAAC","sourcesContent":["import { discardPersistedDocumentToShell } from \"../../../workflows/documents\";\nimport { chainIdentityWrite } from \"./identityWriteChain\";\nimport { runDocumentOrphanMaintenance } from \"./orphanMaintenance\";\nimport { type DocumentStoreState, resetDocumentStore } from \"./state\";\n\n/**\n * User-initiated escape hatch for a document whose queued local writes can no\n * longer sync (e.g. an outgoing queue whose update ids conflict forever):\n * convert the locally persisted document to the freshly-discovered-share\n * shell (documentId kept, empty snapshot, queue/history/failure rows\n * dropped) so the store re-hydrates the server copy. The shell matters twice\n * over: priming's candidate scan reads the documents rows themselves, so a\n * fully deleted record would never be restored after a restart, and\n * re-initialization needs the record to hydrate instead of eagerly creating\n * a blank document. discardPersistedDocumentToShell owns the row-level\n * sequence — including refusing local-only, unlinked, and move-pending\n * documents under its serialized mutation — and converts the record in\n * place, so no step leaves the document row absent.\n *\n * The caller restarts initialization after this settles (success or not);\n * the reset store then hydrates from whatever rows the sequence left. That\n * restart must happen OUTSIDE the identity-chained task — initialization\n * chains identity writes of its own, so starting it there would deadlock.\n */\nexport async function discardDocumentStoreLocalState(\n state: DocumentStoreState,\n expectedDocumentId: string,\n): Promise<boolean> {\n const runtime = state.runtime;\n if (runtime.infra.dbStatus !== \"ready\") {\n return false;\n }\n\n // Cheap eligibility probe before disturbing the live store: an obviously\n // ineligible document (local-only, unlinked, or one whose identity is not\n // the one the caller asked to discard) returns without stopping writes.\n // The workflow re-checks under its serialized mutation, so a status change\n // between here and there is still refused safely.\n const execSql = runtime.infra.execSql;\n const record = await state.persistence.loadDocument(execSql, state.localId);\n if (\n !record?.documentId ||\n record.documentId !== expectedDocumentId ||\n !record.containerId\n ) {\n return false;\n }\n\n // Stop local writes before draining: dropping the live doc rejects new\n // mutations at queue time, and the generation bump invalidates queued\n // write tasks that have not started (and in-flight attachment passes and\n // initializations, which re-check the generation inside their writes'\n // serialized mutations).\n state.doc = null;\n state.localWriteGeneration += 1;\n // Drain OUTSIDE the identity chain. Write-chain tasks can themselves enter\n // the identity chain (the stale-heal recovery installs its rebuilt\n // document that way), so awaiting the write chain while holding an\n // identity-chain slot would deadlock: the drained task would queue behind\n // this one and each would wait on the other forever. Out here the drain is\n // a plain await; whatever the drained tasks chain onto the identity chain\n // runs before the teardown task below and is superseded by it.\n await state.writeChain.catch(() => undefined);\n\n return chainIdentityWrite(state, async () => {\n let shell: Awaited<ReturnType<typeof discardPersistedDocumentToShell>>;\n try {\n shell = await discardPersistedDocumentToShell({\n documentProjectors: runtime.infra.documentProjectors,\n execSql,\n expectedDocumentId,\n localId: state.localId,\n persistence: state.persistence,\n });\n } catch {\n // The conversion is in-place, so an interrupted sequence still leaves a\n // loadable record (old or shell). Reset so re-initialization reloads\n // whatever survived instead of running on the dropped live doc.\n resetDocumentStore(state);\n runtime.util.log(\n `Documents: discard failed for document ${state.localId}; store reset to reload persisted state`,\n );\n return false;\n }\n if (!shell.discarded) {\n // Refused under the lock (a move intent, or an identity change since\n // the probe): rows are untouched; reset so the store reloads them.\n resetDocumentStore(state);\n return false;\n }\n\n // The conversion queued the bytes its dropped rows pointed to; the\n // reclaim deletes each only once no other row references it.\n void runDocumentOrphanMaintenance(state);\n resetDocumentStore(state);\n runtime.util.log(\n `Documents: discarded local edits for document ${state.localId}; re-pulling the server copy`,\n );\n return true;\n });\n}\n"]}
@@ -1,8 +1,6 @@
1
- import { defaultDocumentsPersistence, reclaimDocumentOrphanBlobs, } from "../../../workflows/documents/index.js";
2
- /** Run SQL-only maintenance without making document initialization wait for it. */
1
+ import { reclaimDocumentOrphanBlobs } from "../../../workflows/documents/index.js";
2
+ /** Reclaim through the adapter that owns the attachment references. */
3
3
  export async function runDocumentOrphanMaintenance(state) {
4
- if (state.persistence === defaultDocumentsPersistence) {
5
- await reclaimDocumentOrphanBlobs(state.runtime);
6
- }
4
+ await reclaimDocumentOrphanBlobs(state.runtime, state.persistence);
7
5
  }
8
6
  //# sourceMappingURL=orphanMaintenance.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"orphanMaintenance.js","sourceRoot":"","sources":["orphanMaintenance.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,2BAA2B,EAC3B,0BAA0B,GAC3B,MAAM,8BAA8B,CAAC;AAGtC,mFAAmF;AACnF,MAAM,CAAC,KAAK,UAAU,4BAA4B,CAChD,KAAyB;IAEzB,IAAI,KAAK,CAAC,WAAW,KAAK,2BAA2B,EAAE,CAAC;QACtD,MAAM,0BAA0B,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC;IAClD,CAAC;AACH,CAAC","sourcesContent":["import {\n defaultDocumentsPersistence,\n reclaimDocumentOrphanBlobs,\n} from \"../../../workflows/documents\";\nimport type { DocumentStoreState } from \"./state\";\n\n/** Run SQL-only maintenance without making document initialization wait for it. */\nexport async function runDocumentOrphanMaintenance(\n state: DocumentStoreState,\n): Promise<void> {\n if (state.persistence === defaultDocumentsPersistence) {\n await reclaimDocumentOrphanBlobs(state.runtime);\n }\n}\n"]}
1
+ {"version":3,"file":"orphanMaintenance.js","sourceRoot":"","sources":["orphanMaintenance.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,0BAA0B,EAAE,MAAM,8BAA8B,CAAC;AAG1E,uEAAuE;AACvE,MAAM,CAAC,KAAK,UAAU,4BAA4B,CAChD,KAAyB;IAEzB,MAAM,0BAA0B,CAAC,KAAK,CAAC,OAAO,EAAE,KAAK,CAAC,WAAW,CAAC,CAAC;AACrE,CAAC","sourcesContent":["import { reclaimDocumentOrphanBlobs } from \"../../../workflows/documents\";\nimport type { DocumentStoreState } from \"./state\";\n\n/** Reclaim through the adapter that owns the attachment references. */\nexport async function runDocumentOrphanMaintenance(\n state: DocumentStoreState,\n): Promise<void> {\n await reclaimDocumentOrphanBlobs(state.runtime, state.persistence);\n}\n"]}
@@ -3,9 +3,10 @@ import { normalizeEffectiveAccessLevel } from "../../../data/accessLevel.js";
3
3
  import { DEFAULT_DOCUMENT_KIND } from "../../../data/documents/documentConstants.js";
4
4
  import { projectStoredDocumentState, } from "../../../data/documents/documentKinds.js";
5
5
  import { errorMessage } from "../../../data/errorMessage.js";
6
- import { defaultDocumentsPersistence, enqueuePendingDocumentUpdate, listPendingDocumentUpdates, persistDocumentState, reclaimDocumentOrphanBlobs, runSerializedSqlMutation, } from "../../../workflows/documents/index.js";
6
+ import { enqueuePendingDocumentUpdate, listPendingDocumentUpdates, persistDocumentState, runSerializedSqlMutation, } from "../../../workflows/documents/index.js";
7
7
  import { maybeCompactDocumentHistory } from "./documentHistoryCompaction.js";
8
8
  import { importDurableDocumentHistory, installDurableDocumentReload, } from "./durableDocumentReload.js";
9
+ import { runDocumentOrphanMaintenance } from "./orphanMaintenance.js";
9
10
  import { markDocumentStoreRemoved, setReadySnapshot, } from "./state.js";
10
11
  import { createFreshPeerStoredDocument } from "./storedDocument.js";
11
12
  import { isDocumentStoreSyncGenerationCurrent as isSyncGenerationCurrent, } from "./syncGeneration.js";
@@ -100,9 +101,7 @@ export async function saveDocumentRecord(state, currentDoc, patch = {}, options
100
101
  if (isCurrent()) {
101
102
  markDocumentStoreRemoved(state);
102
103
  }
103
- if (state.persistence === defaultDocumentsPersistence) {
104
- void reclaimDocumentOrphanBlobs(state.runtime);
105
- }
104
+ void runDocumentOrphanMaintenance(state);
106
105
  return null;
107
106
  }
108
107
  const { record: nextRecord, updatedAt } = persistedDocumentState;