@tearleads/client-sdk 0.1.44 → 0.1.45

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 (113) hide show
  1. package/data/persistence/container-contents/containerContentsPersistence.js +2 -3
  2. package/data/persistence/container-contents/containerContentsPersistence.js.map +1 -1
  3. package/data/persistence/container-contents/containerCreateIntentSettlement.js +16 -16
  4. package/data/persistence/container-contents/containerCreateIntentSettlement.js.map +1 -1
  5. package/data/persistence/container-contents/containerDeletionPersistence.js +5 -11
  6. package/data/persistence/container-contents/containerDeletionPersistence.js.map +1 -1
  7. package/data/persistence/container-contents/containerDocumentReassignment.js +13 -13
  8. package/data/persistence/container-contents/containerDocumentReassignment.js.map +1 -1
  9. package/data/persistence/container-contents/containerHydrationPersistence.js +7 -4
  10. package/data/persistence/container-contents/containerHydrationPersistence.js.map +1 -1
  11. package/data/persistence/container-contents/containerIntentPersistence.js +43 -42
  12. package/data/persistence/container-contents/containerIntentPersistence.js.map +1 -1
  13. package/data/persistence/container-contents/containerMetadataMutationPersistence.js +15 -20
  14. package/data/persistence/container-contents/containerMetadataMutationPersistence.js.map +1 -1
  15. package/data/persistence/container-contents/containerMoveAbandonPersistence.js +1 -6
  16. package/data/persistence/container-contents/containerMoveAbandonPersistence.js.map +1 -1
  17. package/data/persistence/container-contents/containerMoveIntentRows.js +9 -8
  18. package/data/persistence/container-contents/containerMoveIntentRows.js.map +1 -1
  19. package/data/persistence/container-contents/containerReconcilePersistence.js +4 -8
  20. package/data/persistence/container-contents/containerReconcilePersistence.js.map +1 -1
  21. package/data/persistence/container-contents/containerRecoveryPersistence.js +20 -19
  22. package/data/persistence/container-contents/containerRecoveryPersistence.js.map +1 -1
  23. package/data/persistence/container-contents/containerRemovalWork.js +10 -10
  24. package/data/persistence/container-contents/containerRemovalWork.js.map +1 -1
  25. package/data/persistence/container-contents/containerStructuralRepair.js +21 -44
  26. package/data/persistence/container-contents/containerStructuralRepair.js.map +1 -1
  27. package/data/persistence/container-contents/documentLinkRemovalRepair.js +12 -11
  28. package/data/persistence/container-contents/documentLinkRemovalRepair.js.map +1 -1
  29. package/data/persistence/container-contents/documentMoveIntentPersistence.js +53 -28
  30. package/data/persistence/container-contents/documentMoveIntentPersistence.js.map +1 -1
  31. package/data/persistence/container-contents/documentPlacementIntentEnqueue.js +9 -8
  32. package/data/persistence/container-contents/documentPlacementIntentEnqueue.js.map +1 -1
  33. package/data/persistence/container-contents/documentPurgePlacement.js +6 -6
  34. package/data/persistence/container-contents/documentPurgePlacement.js.map +1 -1
  35. package/data/persistence/container-contents/dormantContainerMetadata.js +1 -5
  36. package/data/persistence/container-contents/dormantContainerMetadata.js.map +1 -1
  37. package/data/persistence/container-contents/pendingContainerCreate.js +5 -5
  38. package/data/persistence/container-contents/pendingContainerCreate.js.map +1 -1
  39. package/data/persistence/containers/containerPersistence.js +10 -27
  40. package/data/persistence/containers/containerPersistence.js.map +1 -1
  41. package/data/persistence/containers/documentContainerProjectionPersistence.js +2 -2
  42. package/data/persistence/containers/documentContainerProjectionPersistence.js.map +1 -1
  43. package/data/persistence/containers/documentPlacement.js +10 -10
  44. package/data/persistence/containers/documentPlacement.js.map +1 -1
  45. package/data/persistence/documents/internal/discardDocument.js +5 -5
  46. package/data/persistence/documents/internal/discardDocument.js.map +1 -1
  47. package/data/persistence/documents/internal/documentDeletionPersistence.js +7 -9
  48. package/data/persistence/documents/internal/documentDeletionPersistence.js.map +1 -1
  49. package/data/persistence/documents/internal/documentMutationPersistence.js +6 -8
  50. package/data/persistence/documents/internal/documentMutationPersistence.js.map +1 -1
  51. package/data/persistence/documents/internal/ensureDocumentsSchema.js +2 -2
  52. package/data/persistence/documents/internal/ensureDocumentsSchema.js.map +1 -1
  53. package/data/persistence/documents/internal/orphanSideRows.js +3 -22
  54. package/data/persistence/documents/internal/orphanSideRows.js.map +1 -1
  55. package/data/persistence/organizations/organizationDataUsagePersistence.js +31 -64
  56. package/data/persistence/organizations/organizationDataUsagePersistence.js.map +1 -1
  57. package/data/persistence/organizations/organizationReadModelPurge.js +1 -5
  58. package/data/persistence/organizations/organizationReadModelPurge.js.map +1 -1
  59. package/data/persistence/principalCurrentAcknowledgementPersistence.js +24 -17
  60. package/data/persistence/principalCurrentAcknowledgementPersistence.js.map +1 -1
  61. package/data/persistence/principalHistoryIncompleteRetention.js +3 -9
  62. package/data/persistence/principalHistoryIncompleteRetention.js.map +1 -1
  63. package/data/persistence/principalHistoryStagePersistence.d.ts +3 -9
  64. package/data/persistence/principalHistoryStagePersistence.js +15 -25
  65. package/data/persistence/principalHistoryStagePersistence.js.map +1 -1
  66. package/data/persistence/principalHistoryStageRetention.js +16 -15
  67. package/data/persistence/principalHistoryStageRetention.js.map +1 -1
  68. package/data/persistence/principalHistoryUnpublishedRetention.js +6 -13
  69. package/data/persistence/principalHistoryUnpublishedRetention.js.map +1 -1
  70. package/data/sqlite/containerSchema.d.ts +84 -180
  71. package/data/sqlite/containerSchema.js +14 -23
  72. package/data/sqlite/containerSchema.js.map +1 -1
  73. package/data/sqlite/documentPlacementIntentSchema.js +3 -50
  74. package/data/sqlite/documentPlacementIntentSchema.js.map +1 -1
  75. package/data/sqlite/documentSyncFailurePersistence.d.ts +16 -6
  76. package/data/sqlite/documentSyncFailurePersistence.js +44 -54
  77. package/data/sqlite/documentSyncFailurePersistence.js.map +1 -1
  78. package/data/sqlite/{documentPlacementIntentSchema.d.ts → localIntentSchema.d.ts} +162 -88
  79. package/data/sqlite/localIntentSchema.js +90 -0
  80. package/data/sqlite/localIntentSchema.js.map +1 -0
  81. package/data/sqlite/organizationDataUsageSchema.js +11 -17
  82. package/data/sqlite/organizationDataUsageSchema.js.map +1 -1
  83. package/data/sqlite/principalHistoryRetentionSchema.js +0 -15
  84. package/data/sqlite/principalHistoryRetentionSchema.js.map +1 -1
  85. package/data/sqlite/principalHistoryStageSchema.d.ts +44 -2
  86. package/data/sqlite/principalHistoryStageSchema.js +14 -2
  87. package/data/sqlite/principalHistoryStageSchema.js.map +1 -1
  88. package/data/sqlite/schema.d.ts +947 -2112
  89. package/data/sqlite/schema.js +26 -134
  90. package/data/sqlite/schema.js.map +1 -1
  91. package/package.json +1 -1
  92. package/workflows/container-contents/documentPriming.js +6 -10
  93. package/workflows/container-contents/documentPriming.js.map +1 -1
  94. package/workflows/container-contents/documentQueries/sql.js +11 -10
  95. package/workflows/container-contents/documentQueries/sql.js.map +1 -1
  96. package/workflows/container-contents/documentQueries.js +2 -2
  97. package/workflows/container-contents/documentQueries.js.map +1 -1
  98. package/workflows/container-contents/pendingDownloads.js +2 -5
  99. package/workflows/container-contents/pendingDownloads.js.map +1 -1
  100. package/workflows/container-contents/pendingWrites/deferredTails.js +3 -11
  101. package/workflows/container-contents/pendingWrites/deferredTails.js.map +1 -1
  102. package/workflows/container-contents/pendingWrites/sources.js +56 -80
  103. package/workflows/container-contents/pendingWrites/sources.js.map +1 -1
  104. package/workflows/container-contents/pendingWrites.js +6 -4
  105. package/workflows/container-contents/pendingWrites.js.map +1 -1
  106. package/workflows/container-contents/remoteContainerState.js +2 -1
  107. package/workflows/container-contents/remoteContainerState.js.map +1 -1
  108. package/workflows/sync/remoteReset.js +16 -23
  109. package/workflows/sync/remoteReset.js.map +1 -1
  110. package/workflows/sync/remoteResetPrincipalScope.js +1 -2
  111. package/workflows/sync/remoteResetPrincipalScope.js.map +1 -1
  112. package/workflows/sync/remoteResetScope.js +15 -8
  113. package/workflows/sync/remoteResetScope.js.map +1 -1
@@ -1 +1 @@
1
- {"version":3,"file":"documentMutationPersistence.js","sourceRoot":"","sources":["documentMutationPersistence.ts"],"names":[],"mappings":"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","sourcesContent":["import { base64ToBytes } from \"@tearleads/encoding\";\nimport {\n createDocument,\n importSnapshot,\n updateMatchesDocumentHistory,\n} from \"@tearleads/loro\";\nimport { and, eq } from \"drizzle-orm\";\nimport { documentSyncPullContinuationsEqual } from \"../../../documents/shared/pullContinuation\";\nimport {\n appendDocumentHistoryUpdates,\n replaceDocumentHistoryCheckpoint,\n} from \"../../../sqlite/documentHistoryPersistence\";\nimport {\n insertDocumentPendingUpdateWithHistoryInTransaction,\n settleQueuedDocumentUpdatesInTransaction,\n} from \"../../../sqlite/documentPendingUpdatePersistence\";\nimport {\n documentHistoryUpdates,\n documentSyncFailures,\n} from \"../../../sqlite/schema\";\nimport {\n type ClientSQLiteTransactionScope,\n getClientSQLitePersistenceRuntime,\n} from \"../../../sqlite/sqlitePersistenceRuntime\";\nimport { runSerializedSqlMutation } from \"../../../sqlite/sqlSchema\";\nimport type { DocumentsPersistence, StoredDocumentRecord } from \"../types\";\nimport {\n applyStoredAttachmentRemoval,\n upsertStoredAttachmentStagingRows,\n} from \"./attachmentStagingPersistence\";\nimport { DOCUMENTS_APP_KIND } from \"./constants\";\nimport {\n sameDocumentSecurityIdentity,\n sameNullableDocumentValue,\n} from \"./documentRecordIdentity\";\nimport { resolveDocumentSaveTimestamp, saveDocumentRows } from \"./documentRows\";\n\nfunction sameRecoveryGeneration(\n current: StoredDocumentRecord,\n expected: StoredDocumentRecord,\n): boolean {\n return (\n (current.recoveryGeneration ?? 0) === (expected.recoveryGeneration ?? 0)\n );\n}\n\nfunction sameExpectedDocumentRecord(\n current: StoredDocumentRecord,\n expected: StoredDocumentRecord,\n): boolean {\n return (\n sameDocumentSecurityIdentity(current, expected) &&\n sameRecoveryGeneration(current, expected) &&\n sameNullableDocumentValue(current.lastCommitLsn, expected.lastCommitLsn) &&\n current.snapshotEndVersion === expected.snapshotEndVersion &&\n sameNullableDocumentValue(\n current.pendingBaseVersion,\n expected.pendingBaseVersion,\n ) &&\n documentSyncPullContinuationsEqual(\n current.pullContinuation,\n expected.pullContinuation,\n ) &&\n Boolean(current.pullContinuationRecoveryRequired) ===\n Boolean(expected.pullContinuationRecoveryRequired) &&\n (current.documentKind ?? null) === (expected.documentKind ?? null) &&\n current.text === expected.text &&\n (current.title ?? null) === (expected.title ?? null)\n );\n}\n\nasync function deleteAcceptedPendingUpdates(\n tx: ClientSQLiteTransactionScope,\n localId: string,\n ids: readonly string[],\n): Promise<void> {\n await settleQueuedDocumentUpdatesInTransaction(\n tx,\n { appKind: DOCUMENTS_APP_KIND, localId },\n ids,\n );\n}\n\nasync function clearSyncFailure(\n tx: ClientSQLiteTransactionScope,\n localId: string,\n): Promise<void> {\n await tx\n .delete(documentSyncFailures)\n .where(\n and(\n eq(documentSyncFailures.appKind, DOCUMENTS_APP_KIND),\n eq(documentSyncFailures.localId, localId),\n ),\n )\n .run();\n}\n\nasync function insertPendingUpdate(\n tx: ClientSQLiteTransactionScope,\n documentId: string,\n pendingUpdate: Parameters<\n DocumentsPersistence[\"commitDocumentMutation\"]\n >[1][\"pendingUpdate\"],\n): Promise<void> {\n if (!pendingUpdate) return;\n await insertDocumentPendingUpdateWithHistoryInTransaction({\n createdAt: new Date().toISOString(),\n pendingUpdate,\n scope: { appKind: DOCUMENTS_APP_KIND, localId: documentId },\n tx,\n });\n}\n\nasync function settleMutationConflict(input: {\n currentRecord: StoredDocumentRecord | null;\n mutation: Parameters<DocumentsPersistence[\"commitDocumentMutation\"]>[1];\n tx: ClientSQLiteTransactionScope;\n}): Promise<void> {\n if (\n input.currentRecord &&\n input.mutation.settleAcceptedPendingOnConflict &&\n sameDocumentSecurityIdentity(\n input.currentRecord,\n input.mutation.expectedRecord,\n ) &&\n sameRecoveryGeneration(input.currentRecord, input.mutation.expectedRecord)\n ) {\n await deleteAcceptedPendingUpdates(\n input.tx,\n input.mutation.document.id,\n input.mutation.acceptedPendingUpdateIds,\n );\n }\n}\n\nasync function appendMutationHistory(\n execSql: Parameters<DocumentsPersistence[\"commitDocumentMutation\"]>[0],\n input: Parameters<DocumentsPersistence[\"commitDocumentMutation\"]>[1],\n tx: ClientSQLiteTransactionScope,\n): Promise<void> {\n if (input.historyCheckpoint) {\n const coveredLocalState = input.historyCheckpoint.pruneCoveredLocalState\n ? await findCoveredRecoveryLocalState(\n tx,\n input.document.id,\n input.historyCheckpoint.snapshot,\n )\n : {\n checkpointIds: [],\n hasOrdinaryPendingUpdates: false,\n hasUnprunablePendingUpdates: false,\n hasUnverifiedHistoryTail: false,\n tailIds: [],\n };\n if (\n input.historyCheckpoint.pruneCoveredLocalState &&\n (coveredLocalState.hasOrdinaryPendingUpdates ||\n coveredLocalState.hasUnprunablePendingUpdates)\n ) {\n throw new Error(\n \"Document recovery found unproven pending updates before installation\",\n );\n }\n if (\n input.historyCheckpoint.pruneCoveredLocalState &&\n coveredLocalState.hasUnverifiedHistoryTail\n ) {\n throw new Error(\n \"Document recovery found unverified history tail before installation\",\n );\n }\n // A queued rotation baseline the recovery snapshot covers is unqueued\n // first so the replacement below can delete it with the covered tail.\n await deleteAcceptedPendingUpdates(\n tx,\n input.document.id,\n coveredLocalState.checkpointIds,\n );\n const checkpointReplaced = await replaceDocumentHistoryCheckpoint(\n execSql,\n { appKind: DOCUMENTS_APP_KIND, localId: input.document.id },\n {\n ...input.historyCheckpoint,\n coveredTailIds: input.historyCheckpoint.pruneCoveredLocalState\n ? coveredLocalState.tailIds\n : input.historyCheckpoint.coveredTailIds,\n },\n );\n if (input.historyCheckpoint.pruneCoveredLocalState && !checkpointReplaced) {\n throw new Error(\n \"Document recovery checkpoint was superseded before installation\",\n );\n }\n }\n if (input.historyUpdates && input.historyUpdates.length > 0) {\n await appendDocumentHistoryUpdates(\n execSql,\n { appKind: DOCUMENTS_APP_KIND, localId: input.document.id },\n input.historyUpdates,\n input.historyUpdateOrigin ?? \"local\",\n );\n }\n}\n\nasync function findCoveredRecoveryLocalState(\n tx: ClientSQLiteTransactionScope,\n localId: string,\n recoverySnapshot: string,\n): Promise<{\n checkpointIds: string[];\n hasOrdinaryPendingUpdates: boolean;\n hasUnprunablePendingUpdates: boolean;\n hasUnverifiedHistoryTail: boolean;\n tailIds: string[];\n}> {\n const recoveredDocument = await createDocument(\n `recovery-tail-verification:${localId}`,\n );\n try {\n importSnapshot(recoveredDocument, base64ToBytes(recoverySnapshot));\n const rows = await tx\n .select({\n id: documentHistoryUpdates.id,\n origin: documentHistoryUpdates.origin,\n sourceVersionVector: documentHistoryUpdates.sourceVersionVector,\n updateData: documentHistoryUpdates.updateData,\n updateId: documentHistoryUpdates.updateId,\n })\n .from(documentHistoryUpdates)\n .where(\n and(\n eq(documentHistoryUpdates.appKind, DOCUMENTS_APP_KIND),\n eq(documentHistoryUpdates.localId, localId),\n ),\n );\n const pendingUpdates = rows.filter((row) => row.updateId !== null);\n const tail = rows.filter((row) => row.origin !== \"outbox\");\n const checkpointUpdateData = new Set(\n pendingUpdates.flatMap((row) =>\n row.sourceVersionVector === null ? [] : [row.updateData],\n ),\n );\n let hasUnverifiedHistoryTail = false;\n const tailIds = tail.flatMap((row) => {\n if (row.id === null) {\n // An unverifiable row identity cannot participate in the guarded\n // delete, so accepting it would leave a stale recovery redirect.\n hasUnverifiedHistoryTail = true;\n return [];\n }\n if (checkpointUpdateData.has(row.updateData)) return [row.id];\n try {\n if (\n updateMatchesDocumentHistory(\n recoveredDocument,\n base64ToBytes(row.updateData),\n )\n ) {\n return [row.id];\n }\n } catch {\n // Fall through: malformed history is not proven by the rebuild.\n }\n hasUnverifiedHistoryTail = true;\n return [];\n });\n return {\n checkpointIds: pendingUpdates.flatMap((row) =>\n row.updateId !== null && row.sourceVersionVector !== null\n ? [row.updateId]\n : [],\n ),\n hasOrdinaryPendingUpdates: pendingUpdates.some(\n (row) => row.sourceVersionVector === null,\n ),\n hasUnprunablePendingUpdates: pendingUpdates.some(\n (row) => row.id === null,\n ),\n hasUnverifiedHistoryTail,\n tailIds,\n };\n } finally {\n recoveredDocument.free();\n }\n}\n\ntype CommitDocumentMutationInput = Parameters<\n DocumentsPersistence[\"commitDocumentMutation\"]\n>[1];\ntype SaveClientProjection = Parameters<\n DocumentsPersistence[\"commitDocumentMutation\"]\n>[2];\n\nasync function applyStoredDocumentMutation(input: {\n lockedExecSql: Parameters<DocumentsPersistence[\"commitDocumentMutation\"]>[0];\n mutation: CommitDocumentMutationInput;\n saveClientProjection: SaveClientProjection;\n tx: ClientSQLiteTransactionScope;\n loadDocument: DocumentsPersistence[\"loadDocument\"];\n}) {\n const { lockedExecSql, mutation, tx } = input;\n const currentRecord = await input.loadDocument(\n lockedExecSql,\n mutation.document.id,\n );\n if (mutation.stillCurrent && !mutation.stillCurrent()) {\n return { committed: false as const, currentRecord };\n }\n if (\n !currentRecord ||\n !sameExpectedDocumentRecord(currentRecord, mutation.expectedRecord)\n ) {\n await settleMutationConflict({ currentRecord, mutation, tx });\n return { committed: false as const, currentRecord };\n }\n\n if (mutation.attachmentRemoval) {\n await applyStoredAttachmentRemoval({\n localId: mutation.document.id,\n removal: mutation.attachmentRemoval,\n tx,\n });\n }\n if (mutation.attachmentStaging) {\n await upsertStoredAttachmentStagingRows({\n createdAt: new Date().toISOString(),\n localId: mutation.document.id,\n staging: mutation.attachmentStaging,\n tx,\n });\n }\n await appendMutationHistory(lockedExecSql, mutation, tx);\n await insertPendingUpdate(tx, mutation.document.id, mutation.pendingUpdate);\n await deleteAcceptedPendingUpdates(\n tx,\n mutation.document.id,\n mutation.acceptedPendingUpdateIds,\n );\n if (mutation.clearSyncFailure) {\n await clearSyncFailure(tx, mutation.document.id);\n }\n const updatedAt = await resolveDocumentSaveTimestamp({\n document: mutation.document,\n options:\n mutation.updatedAt === undefined\n ? undefined\n : { updatedAt: mutation.updatedAt },\n tx,\n });\n if (\n !(await saveDocumentRows({ document: mutation.document, tx, updatedAt }))\n ) {\n throw new Error(\n \"Document recovery generation changed before mutation save\",\n );\n }\n await input.saveClientProjection(lockedExecSql, updatedAt);\n return { committed: true as const, updatedAt };\n}\n\nasync function runStoredDocumentMutationTransaction(input: {\n lockedExecSql: Parameters<DocumentsPersistence[\"commitDocumentMutation\"]>[0];\n mutation: CommitDocumentMutationInput;\n saveClientProjection: SaveClientProjection;\n loadDocument: DocumentsPersistence[\"loadDocument\"];\n}) {\n const outcome = await getClientSQLitePersistenceRuntime(\n input.lockedExecSql,\n ).guardedTransaction(\n (tx) => applyStoredDocumentMutation({ ...input, tx }),\n () => !input.mutation.stillCurrent || input.mutation.stillCurrent(),\n { behavior: \"immediate\" },\n );\n if (outcome.committed && outcome.result) {\n return outcome.result;\n }\n return {\n committed: false as const,\n currentRecord: await input.loadDocument(\n input.lockedExecSql,\n input.mutation.document.id,\n ),\n };\n}\n\nexport async function commitStoredDocumentMutation(\n execSql: Parameters<DocumentsPersistence[\"commitDocumentMutation\"]>[0],\n input: CommitDocumentMutationInput,\n saveClientProjection: SaveClientProjection,\n loadDocument: DocumentsPersistence[\"loadDocument\"],\n) {\n return runSerializedSqlMutation(execSql, (lockedExecSql) =>\n runStoredDocumentMutationTransaction({\n lockedExecSql,\n loadDocument,\n mutation: input,\n saveClientProjection,\n }),\n );\n}\n\nexport async function settleStoredDocumentPendingUpdates(\n execSql: Parameters<DocumentsPersistence[\"settleAcceptedPendingUpdates\"]>[0],\n input: Parameters<DocumentsPersistence[\"settleAcceptedPendingUpdates\"]>[1],\n loadDocument: DocumentsPersistence[\"loadDocument\"],\n) {\n return runSerializedSqlMutation(execSql, async (lockedExecSql) =>\n getClientSQLitePersistenceRuntime(lockedExecSql).transaction(\n async (tx) => {\n const currentRecord = await loadDocument(\n lockedExecSql,\n input.expectedRecord.id,\n );\n if (\n currentRecord &&\n sameDocumentSecurityIdentity(currentRecord, input.expectedRecord) &&\n sameRecoveryGeneration(currentRecord, input.expectedRecord)\n ) {\n await deleteAcceptedPendingUpdates(\n tx,\n input.expectedRecord.id,\n input.pendingUpdateIds,\n );\n }\n return currentRecord;\n },\n { behavior: \"immediate\" },\n ),\n );\n}\n"]}
1
+ {"version":3,"file":"documentMutationPersistence.js","sourceRoot":"","sources":["documentMutationPersistence.ts"],"names":[],"mappings":"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","sourcesContent":["import { base64ToBytes } from \"@tearleads/encoding\";\nimport {\n createDocument,\n importSnapshot,\n updateMatchesDocumentHistory,\n} from \"@tearleads/loro\";\nimport { and, eq } from \"drizzle-orm\";\nimport { documentSyncPullContinuationsEqual } from \"../../../documents/shared/pullContinuation\";\nimport {\n appendDocumentHistoryUpdates,\n replaceDocumentHistoryCheckpoint,\n} from \"../../../sqlite/documentHistoryPersistence\";\nimport {\n insertDocumentPendingUpdateWithHistoryInTransaction,\n settleQueuedDocumentUpdatesInTransaction,\n} from \"../../../sqlite/documentPendingUpdatePersistence\";\nimport { clearDocumentSyncFailureInTransaction } from \"../../../sqlite/documentSyncFailurePersistence\";\nimport { documentHistoryUpdates } from \"../../../sqlite/schema\";\nimport {\n type ClientSQLiteTransactionScope,\n getClientSQLitePersistenceRuntime,\n} from \"../../../sqlite/sqlitePersistenceRuntime\";\nimport { runSerializedSqlMutation } from \"../../../sqlite/sqlSchema\";\nimport type { DocumentsPersistence, StoredDocumentRecord } from \"../types\";\nimport {\n applyStoredAttachmentRemoval,\n upsertStoredAttachmentStagingRows,\n} from \"./attachmentStagingPersistence\";\nimport { DOCUMENTS_APP_KIND } from \"./constants\";\nimport {\n sameDocumentSecurityIdentity,\n sameNullableDocumentValue,\n} from \"./documentRecordIdentity\";\nimport { resolveDocumentSaveTimestamp, saveDocumentRows } from \"./documentRows\";\n\nfunction sameRecoveryGeneration(\n current: StoredDocumentRecord,\n expected: StoredDocumentRecord,\n): boolean {\n return (\n (current.recoveryGeneration ?? 0) === (expected.recoveryGeneration ?? 0)\n );\n}\n\nfunction sameExpectedDocumentRecord(\n current: StoredDocumentRecord,\n expected: StoredDocumentRecord,\n): boolean {\n return (\n sameDocumentSecurityIdentity(current, expected) &&\n sameRecoveryGeneration(current, expected) &&\n sameNullableDocumentValue(current.lastCommitLsn, expected.lastCommitLsn) &&\n current.snapshotEndVersion === expected.snapshotEndVersion &&\n sameNullableDocumentValue(\n current.pendingBaseVersion,\n expected.pendingBaseVersion,\n ) &&\n documentSyncPullContinuationsEqual(\n current.pullContinuation,\n expected.pullContinuation,\n ) &&\n Boolean(current.pullContinuationRecoveryRequired) ===\n Boolean(expected.pullContinuationRecoveryRequired) &&\n (current.documentKind ?? null) === (expected.documentKind ?? null) &&\n current.text === expected.text &&\n (current.title ?? null) === (expected.title ?? null)\n );\n}\n\nasync function deleteAcceptedPendingUpdates(\n tx: ClientSQLiteTransactionScope,\n localId: string,\n ids: readonly string[],\n): Promise<void> {\n await settleQueuedDocumentUpdatesInTransaction(\n tx,\n { appKind: DOCUMENTS_APP_KIND, localId },\n ids,\n );\n}\n\nasync function insertPendingUpdate(\n tx: ClientSQLiteTransactionScope,\n documentId: string,\n pendingUpdate: Parameters<\n DocumentsPersistence[\"commitDocumentMutation\"]\n >[1][\"pendingUpdate\"],\n): Promise<void> {\n if (!pendingUpdate) return;\n await insertDocumentPendingUpdateWithHistoryInTransaction({\n createdAt: new Date().toISOString(),\n pendingUpdate,\n scope: { appKind: DOCUMENTS_APP_KIND, localId: documentId },\n tx,\n });\n}\n\nasync function settleMutationConflict(input: {\n currentRecord: StoredDocumentRecord | null;\n mutation: Parameters<DocumentsPersistence[\"commitDocumentMutation\"]>[1];\n tx: ClientSQLiteTransactionScope;\n}): Promise<void> {\n if (\n input.currentRecord &&\n input.mutation.settleAcceptedPendingOnConflict &&\n sameDocumentSecurityIdentity(\n input.currentRecord,\n input.mutation.expectedRecord,\n ) &&\n sameRecoveryGeneration(input.currentRecord, input.mutation.expectedRecord)\n ) {\n await deleteAcceptedPendingUpdates(\n input.tx,\n input.mutation.document.id,\n input.mutation.acceptedPendingUpdateIds,\n );\n }\n}\n\nasync function appendMutationHistory(\n execSql: Parameters<DocumentsPersistence[\"commitDocumentMutation\"]>[0],\n input: Parameters<DocumentsPersistence[\"commitDocumentMutation\"]>[1],\n tx: ClientSQLiteTransactionScope,\n): Promise<void> {\n if (input.historyCheckpoint) {\n const coveredLocalState = input.historyCheckpoint.pruneCoveredLocalState\n ? await findCoveredRecoveryLocalState(\n tx,\n input.document.id,\n input.historyCheckpoint.snapshot,\n )\n : {\n checkpointIds: [],\n hasOrdinaryPendingUpdates: false,\n hasUnprunablePendingUpdates: false,\n hasUnverifiedHistoryTail: false,\n tailIds: [],\n };\n if (\n input.historyCheckpoint.pruneCoveredLocalState &&\n (coveredLocalState.hasOrdinaryPendingUpdates ||\n coveredLocalState.hasUnprunablePendingUpdates)\n ) {\n throw new Error(\n \"Document recovery found unproven pending updates before installation\",\n );\n }\n if (\n input.historyCheckpoint.pruneCoveredLocalState &&\n coveredLocalState.hasUnverifiedHistoryTail\n ) {\n throw new Error(\n \"Document recovery found unverified history tail before installation\",\n );\n }\n // A queued rotation baseline the recovery snapshot covers is unqueued\n // first so the replacement below can delete it with the covered tail.\n await deleteAcceptedPendingUpdates(\n tx,\n input.document.id,\n coveredLocalState.checkpointIds,\n );\n const checkpointReplaced = await replaceDocumentHistoryCheckpoint(\n execSql,\n { appKind: DOCUMENTS_APP_KIND, localId: input.document.id },\n {\n ...input.historyCheckpoint,\n coveredTailIds: input.historyCheckpoint.pruneCoveredLocalState\n ? coveredLocalState.tailIds\n : input.historyCheckpoint.coveredTailIds,\n },\n );\n if (input.historyCheckpoint.pruneCoveredLocalState && !checkpointReplaced) {\n throw new Error(\n \"Document recovery checkpoint was superseded before installation\",\n );\n }\n }\n if (input.historyUpdates && input.historyUpdates.length > 0) {\n await appendDocumentHistoryUpdates(\n execSql,\n { appKind: DOCUMENTS_APP_KIND, localId: input.document.id },\n input.historyUpdates,\n input.historyUpdateOrigin ?? \"local\",\n );\n }\n}\n\nasync function findCoveredRecoveryLocalState(\n tx: ClientSQLiteTransactionScope,\n localId: string,\n recoverySnapshot: string,\n): Promise<{\n checkpointIds: string[];\n hasOrdinaryPendingUpdates: boolean;\n hasUnprunablePendingUpdates: boolean;\n hasUnverifiedHistoryTail: boolean;\n tailIds: string[];\n}> {\n const recoveredDocument = await createDocument(\n `recovery-tail-verification:${localId}`,\n );\n try {\n importSnapshot(recoveredDocument, base64ToBytes(recoverySnapshot));\n const rows = await tx\n .select({\n id: documentHistoryUpdates.id,\n origin: documentHistoryUpdates.origin,\n sourceVersionVector: documentHistoryUpdates.sourceVersionVector,\n updateData: documentHistoryUpdates.updateData,\n updateId: documentHistoryUpdates.updateId,\n })\n .from(documentHistoryUpdates)\n .where(\n and(\n eq(documentHistoryUpdates.appKind, DOCUMENTS_APP_KIND),\n eq(documentHistoryUpdates.localId, localId),\n ),\n );\n const pendingUpdates = rows.filter((row) => row.updateId !== null);\n const tail = rows.filter((row) => row.origin !== \"outbox\");\n const checkpointUpdateData = new Set(\n pendingUpdates.flatMap((row) =>\n row.sourceVersionVector === null ? [] : [row.updateData],\n ),\n );\n let hasUnverifiedHistoryTail = false;\n const tailIds = tail.flatMap((row) => {\n if (row.id === null) {\n // An unverifiable row identity cannot participate in the guarded\n // delete, so accepting it would leave a stale recovery redirect.\n hasUnverifiedHistoryTail = true;\n return [];\n }\n if (checkpointUpdateData.has(row.updateData)) return [row.id];\n try {\n if (\n updateMatchesDocumentHistory(\n recoveredDocument,\n base64ToBytes(row.updateData),\n )\n ) {\n return [row.id];\n }\n } catch {\n // Fall through: malformed history is not proven by the rebuild.\n }\n hasUnverifiedHistoryTail = true;\n return [];\n });\n return {\n checkpointIds: pendingUpdates.flatMap((row) =>\n row.updateId !== null && row.sourceVersionVector !== null\n ? [row.updateId]\n : [],\n ),\n hasOrdinaryPendingUpdates: pendingUpdates.some(\n (row) => row.sourceVersionVector === null,\n ),\n hasUnprunablePendingUpdates: pendingUpdates.some(\n (row) => row.id === null,\n ),\n hasUnverifiedHistoryTail,\n tailIds,\n };\n } finally {\n recoveredDocument.free();\n }\n}\n\ntype CommitDocumentMutationInput = Parameters<\n DocumentsPersistence[\"commitDocumentMutation\"]\n>[1];\ntype SaveClientProjection = Parameters<\n DocumentsPersistence[\"commitDocumentMutation\"]\n>[2];\n\nasync function applyStoredDocumentMutation(input: {\n lockedExecSql: Parameters<DocumentsPersistence[\"commitDocumentMutation\"]>[0];\n mutation: CommitDocumentMutationInput;\n saveClientProjection: SaveClientProjection;\n tx: ClientSQLiteTransactionScope;\n loadDocument: DocumentsPersistence[\"loadDocument\"];\n}) {\n const { lockedExecSql, mutation, tx } = input;\n const currentRecord = await input.loadDocument(\n lockedExecSql,\n mutation.document.id,\n );\n if (mutation.stillCurrent && !mutation.stillCurrent()) {\n return { committed: false as const, currentRecord };\n }\n if (\n !currentRecord ||\n !sameExpectedDocumentRecord(currentRecord, mutation.expectedRecord)\n ) {\n await settleMutationConflict({ currentRecord, mutation, tx });\n return { committed: false as const, currentRecord };\n }\n\n if (mutation.attachmentRemoval) {\n await applyStoredAttachmentRemoval({\n localId: mutation.document.id,\n removal: mutation.attachmentRemoval,\n tx,\n });\n }\n if (mutation.attachmentStaging) {\n await upsertStoredAttachmentStagingRows({\n createdAt: new Date().toISOString(),\n localId: mutation.document.id,\n staging: mutation.attachmentStaging,\n tx,\n });\n }\n await appendMutationHistory(lockedExecSql, mutation, tx);\n await insertPendingUpdate(tx, mutation.document.id, mutation.pendingUpdate);\n await deleteAcceptedPendingUpdates(\n tx,\n mutation.document.id,\n mutation.acceptedPendingUpdateIds,\n );\n if (mutation.clearSyncFailure) {\n await clearDocumentSyncFailureInTransaction(tx, {\n appKind: DOCUMENTS_APP_KIND,\n localId: mutation.document.id,\n });\n }\n const updatedAt = await resolveDocumentSaveTimestamp({\n document: mutation.document,\n options:\n mutation.updatedAt === undefined\n ? undefined\n : { updatedAt: mutation.updatedAt },\n tx,\n });\n if (\n !(await saveDocumentRows({ document: mutation.document, tx, updatedAt }))\n ) {\n throw new Error(\n \"Document recovery generation changed before mutation save\",\n );\n }\n await input.saveClientProjection(lockedExecSql, updatedAt);\n return { committed: true as const, updatedAt };\n}\n\nasync function runStoredDocumentMutationTransaction(input: {\n lockedExecSql: Parameters<DocumentsPersistence[\"commitDocumentMutation\"]>[0];\n mutation: CommitDocumentMutationInput;\n saveClientProjection: SaveClientProjection;\n loadDocument: DocumentsPersistence[\"loadDocument\"];\n}) {\n const outcome = await getClientSQLitePersistenceRuntime(\n input.lockedExecSql,\n ).guardedTransaction(\n (tx) => applyStoredDocumentMutation({ ...input, tx }),\n () => !input.mutation.stillCurrent || input.mutation.stillCurrent(),\n { behavior: \"immediate\" },\n );\n if (outcome.committed && outcome.result) {\n return outcome.result;\n }\n return {\n committed: false as const,\n currentRecord: await input.loadDocument(\n input.lockedExecSql,\n input.mutation.document.id,\n ),\n };\n}\n\nexport async function commitStoredDocumentMutation(\n execSql: Parameters<DocumentsPersistence[\"commitDocumentMutation\"]>[0],\n input: CommitDocumentMutationInput,\n saveClientProjection: SaveClientProjection,\n loadDocument: DocumentsPersistence[\"loadDocument\"],\n) {\n return runSerializedSqlMutation(execSql, (lockedExecSql) =>\n runStoredDocumentMutationTransaction({\n lockedExecSql,\n loadDocument,\n mutation: input,\n saveClientProjection,\n }),\n );\n}\n\nexport async function settleStoredDocumentPendingUpdates(\n execSql: Parameters<DocumentsPersistence[\"settleAcceptedPendingUpdates\"]>[0],\n input: Parameters<DocumentsPersistence[\"settleAcceptedPendingUpdates\"]>[1],\n loadDocument: DocumentsPersistence[\"loadDocument\"],\n) {\n return runSerializedSqlMutation(execSql, async (lockedExecSql) =>\n getClientSQLitePersistenceRuntime(lockedExecSql).transaction(\n async (tx) => {\n const currentRecord = await loadDocument(\n lockedExecSql,\n input.expectedRecord.id,\n );\n if (\n currentRecord &&\n sameDocumentSecurityIdentity(currentRecord, input.expectedRecord) &&\n sameRecoveryGeneration(currentRecord, input.expectedRecord)\n ) {\n await deleteAcceptedPendingUpdates(\n tx,\n input.expectedRecord.id,\n input.pendingUpdateIds,\n );\n }\n return currentRecord;\n },\n { behavior: \"immediate\" },\n ),\n );\n}\n"]}
@@ -1,5 +1,5 @@
1
1
  import { ensureDocumentProjectionTables, ensureDocumentTables, } from "../../../sqlite/documentPersistence.js";
2
- import { documentContainerProjectionTables, documentMoveIntentTables, keyingCheckpointTables, } from "../../../sqlite/schema.js";
2
+ import { documentContainerProjectionTables, documentPlacementIntentTables, keyingCheckpointTables, } from "../../../sqlite/schema.js";
3
3
  import { ensureSqlTables, runOncePerConnection, runSerializedSqlMutation, } from "../../../sqlite/sqlSchema.js";
4
4
  export async function ensureDocumentsSchema(execSql) {
5
5
  await runOncePerConnection(execSql, "ensure:documents", () => runSerializedSqlMutation(execSql, async (lockedExecSql) => {
@@ -7,7 +7,7 @@ export async function ensureDocumentsSchema(execSql) {
7
7
  await ensureDocumentProjectionTables(lockedExecSql);
8
8
  await ensureSqlTables(lockedExecSql, [
9
9
  ...documentContainerProjectionTables,
10
- ...documentMoveIntentTables,
10
+ ...documentPlacementIntentTables,
11
11
  ...keyingCheckpointTables,
12
12
  ]);
13
13
  }));
@@ -1 +1 @@
1
- {"version":3,"file":"ensureDocumentsSchema.js","sourceRoot":"","sources":["ensureDocumentsSchema.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,8BAA8B,EAC9B,oBAAoB,GACrB,MAAM,qCAAqC,CAAC;AAC7C,OAAO,EACL,iCAAiC,EACjC,wBAAwB,EACxB,sBAAsB,GACvB,MAAM,wBAAwB,CAAC;AAChC,OAAO,EAEL,eAAe,EACf,oBAAoB,EACpB,wBAAwB,GACzB,MAAM,2BAA2B,CAAC;AAEnC,MAAM,CAAC,KAAK,UAAU,qBAAqB,CAAC,OAAgB;IAC1D,MAAM,oBAAoB,CAAC,OAAO,EAAE,kBAAkB,EAAE,GAAG,EAAE,CAC3D,wBAAwB,CAAC,OAAO,EAAE,KAAK,EAAE,aAAa,EAAE,EAAE;QACxD,MAAM,oBAAoB,CAAC,aAAa,CAAC,CAAC;QAC1C,MAAM,8BAA8B,CAAC,aAAa,CAAC,CAAC;QACpD,MAAM,eAAe,CAAC,aAAa,EAAE;YACnC,GAAG,iCAAiC;YACpC,GAAG,wBAAwB;YAC3B,GAAG,sBAAsB;SAC1B,CAAC,CAAC;IACL,CAAC,CAAC,CACH,CAAC;AACJ,CAAC","sourcesContent":["import {\n ensureDocumentProjectionTables,\n ensureDocumentTables,\n} from \"../../../sqlite/documentPersistence\";\nimport {\n documentContainerProjectionTables,\n documentMoveIntentTables,\n keyingCheckpointTables,\n} from \"../../../sqlite/schema\";\nimport {\n type ExecSql,\n ensureSqlTables,\n runOncePerConnection,\n runSerializedSqlMutation,\n} from \"../../../sqlite/sqlSchema\";\n\nexport async function ensureDocumentsSchema(execSql: ExecSql): Promise<void> {\n await runOncePerConnection(execSql, \"ensure:documents\", () =>\n runSerializedSqlMutation(execSql, async (lockedExecSql) => {\n await ensureDocumentTables(lockedExecSql);\n await ensureDocumentProjectionTables(lockedExecSql);\n await ensureSqlTables(lockedExecSql, [\n ...documentContainerProjectionTables,\n ...documentMoveIntentTables,\n ...keyingCheckpointTables,\n ]);\n }),\n );\n}\n"]}
1
+ {"version":3,"file":"ensureDocumentsSchema.js","sourceRoot":"","sources":["ensureDocumentsSchema.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,8BAA8B,EAC9B,oBAAoB,GACrB,MAAM,qCAAqC,CAAC;AAC7C,OAAO,EACL,iCAAiC,EACjC,6BAA6B,EAC7B,sBAAsB,GACvB,MAAM,wBAAwB,CAAC;AAChC,OAAO,EAEL,eAAe,EACf,oBAAoB,EACpB,wBAAwB,GACzB,MAAM,2BAA2B,CAAC;AAEnC,MAAM,CAAC,KAAK,UAAU,qBAAqB,CAAC,OAAgB;IAC1D,MAAM,oBAAoB,CAAC,OAAO,EAAE,kBAAkB,EAAE,GAAG,EAAE,CAC3D,wBAAwB,CAAC,OAAO,EAAE,KAAK,EAAE,aAAa,EAAE,EAAE;QACxD,MAAM,oBAAoB,CAAC,aAAa,CAAC,CAAC;QAC1C,MAAM,8BAA8B,CAAC,aAAa,CAAC,CAAC;QACpD,MAAM,eAAe,CAAC,aAAa,EAAE;YACnC,GAAG,iCAAiC;YACpC,GAAG,6BAA6B;YAChC,GAAG,sBAAsB;SAC1B,CAAC,CAAC;IACL,CAAC,CAAC,CACH,CAAC;AACJ,CAAC","sourcesContent":["import {\n ensureDocumentProjectionTables,\n ensureDocumentTables,\n} from \"../../../sqlite/documentPersistence\";\nimport {\n documentContainerProjectionTables,\n documentPlacementIntentTables,\n keyingCheckpointTables,\n} from \"../../../sqlite/schema\";\nimport {\n type ExecSql,\n ensureSqlTables,\n runOncePerConnection,\n runSerializedSqlMutation,\n} from \"../../../sqlite/sqlSchema\";\n\nexport async function ensureDocumentsSchema(execSql: ExecSql): Promise<void> {\n await runOncePerConnection(execSql, \"ensure:documents\", () =>\n runSerializedSqlMutation(execSql, async (lockedExecSql) => {\n await ensureDocumentTables(lockedExecSql);\n await ensureDocumentProjectionTables(lockedExecSql);\n await ensureSqlTables(lockedExecSql, [\n ...documentContainerProjectionTables,\n ...documentPlacementIntentTables,\n ...keyingCheckpointTables,\n ]);\n }),\n );\n}\n"]}
@@ -1,6 +1,6 @@
1
1
  import { and, asc, eq, inArray, isNotNull, lt, notExists, or, } from "drizzle-orm";
2
2
  import { documentOrphanBlobReclaims } from "../../../sqlite/documentOrphanBlobReclaims.js";
3
- import { documentAttachmentBlobProjection, documentHistoryCheckpoints, documentHistoryUpdates, documentPendingAttachments, documentSyncFailures, documents, } from "../../../sqlite/schema.js";
3
+ import { documentAttachmentBlobProjection, documentHistoryCheckpoints, documentHistoryUpdates, documentPendingAttachments, documents, } from "../../../sqlite/schema.js";
4
4
  import { getClientSQLitePersistenceRuntime, } from "../../../sqlite/sqlitePersistenceRuntime.js";
5
5
  import { runSerializedSqlMutation, } from "../../../sqlite/sqlSchema.js";
6
6
  import { DOCUMENTS_APP_KIND } from "./constants.js";
@@ -67,14 +67,6 @@ async function selectHistorySweepCandidates(tx, olderThan) {
67
67
  .limit(DOCUMENT_ORPHAN_SWEEP_BATCH_SIZE);
68
68
  return { checkpoints, updates };
69
69
  }
70
- async function selectWriteSweepCandidates(tx, olderThan) {
71
- const syncFailures = await tx
72
- .select({ localId: documentSyncFailures.localId })
73
- .from(documentSyncFailures)
74
- .where(and(eq(documentSyncFailures.appKind, DOCUMENTS_APP_KIND), lt(documentSyncFailures.attemptedAt, olderThan), hasNoDocument(tx, documentSyncFailures.localId)))
75
- .limit(DOCUMENT_ORPHAN_SWEEP_BATCH_SIZE);
76
- return { syncFailures };
77
- }
78
70
  async function selectAttachmentSweepCandidates(tx, olderThan) {
79
71
  const pending = await tx
80
72
  .select({
@@ -110,14 +102,6 @@ async function deleteHistorySweepCandidates(tx, candidates) {
110
102
  .run();
111
103
  }
112
104
  }
113
- async function deleteWriteSweepCandidates(tx, candidates) {
114
- if (candidates.syncFailures.length > 0) {
115
- await tx
116
- .delete(documentSyncFailures)
117
- .where(and(eq(documentSyncFailures.appKind, DOCUMENTS_APP_KIND), inArray(documentSyncFailures.localId, candidates.syncFailures.map((row) => row.localId))))
118
- .run();
119
- }
120
- }
121
105
  async function deleteAttachmentSweepCandidates(tx, candidates) {
122
106
  await queueDocumentAttachmentStorageKeys(tx, [
123
107
  ...new Set([...candidates.pending, ...candidates.local].map((row) => row.storageKey)),
@@ -135,11 +119,10 @@ async function deleteAttachmentSweepCandidates(tx, candidates) {
135
119
  .run();
136
120
  }
137
121
  }
138
- function hasFullSweepBatch(history, writes, attachments) {
122
+ function hasFullSweepBatch(history, attachments) {
139
123
  return [
140
124
  history.checkpoints,
141
125
  history.updates,
142
- writes.syncFailures,
143
126
  attachments.pending,
144
127
  attachments.local,
145
128
  ].some((rows) => rows.length === DOCUMENT_ORPHAN_SWEEP_BATCH_SIZE);
@@ -149,12 +132,10 @@ export async function deleteOrphanedDocumentSideRows(execSql, options = {}) {
149
132
  const olderThan = new Date((options.now?.getTime() ?? Date.now()) - DOCUMENT_ORPHAN_SWEEP_MIN_AGE_MS).toISOString();
150
133
  return runSerializedSqlMutation(execSql, async (lockedExecSql) => getClientSQLitePersistenceRuntime(lockedExecSql).transaction(async (tx) => {
151
134
  const history = await selectHistorySweepCandidates(tx, olderThan);
152
- const writes = await selectWriteSweepCandidates(tx, olderThan);
153
135
  const attachments = await selectAttachmentSweepCandidates(tx, olderThan);
154
136
  await deleteHistorySweepCandidates(tx, history);
155
- await deleteWriteSweepCandidates(tx, writes);
156
137
  await deleteAttachmentSweepCandidates(tx, attachments);
157
- return hasFullSweepBatch(history, writes, attachments);
138
+ return hasFullSweepBatch(history, attachments);
158
139
  }));
159
140
  }
160
141
  export async function listDocumentOrphanBlobReclaims(execSql, limit) {
@@ -1 +1 @@
1
- {"version":3,"file":"orphanSideRows.js","sourceRoot":"","sources":["orphanSideRows.ts"],"names":[],"mappings":"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","sourcesContent":["import {\n and,\n asc,\n eq,\n inArray,\n isNotNull,\n lt,\n notExists,\n or,\n type SQL,\n} from \"drizzle-orm\";\nimport { documentOrphanBlobReclaims } from \"../../../sqlite/documentOrphanBlobReclaims\";\nimport {\n documentAttachmentBlobProjection,\n documentHistoryCheckpoints,\n documentHistoryUpdates,\n documentPendingAttachments,\n documentSyncFailures,\n documents,\n} from \"../../../sqlite/schema\";\nimport {\n type ClientSQLiteTransactionScope,\n getClientSQLitePersistenceRuntime,\n} from \"../../../sqlite/sqlitePersistenceRuntime\";\nimport {\n type ExecSql,\n runSerializedSqlMutation,\n} from \"../../../sqlite/sqlSchema\";\nimport { DOCUMENTS_APP_KIND } from \"./constants\";\n\n// Crash residue gets a full day to outlive slow devices and wall-clock skew;\n// canonical-row absence is necessary but never sufficient for fresh writes.\nconst DOCUMENT_ORPHAN_SWEEP_MIN_AGE_MS = 24 * 60 * 60 * 1000;\nconst DOCUMENT_ORPHAN_SWEEP_BATCH_SIZE = 32;\n\ntype DocumentSideLocalIdColumn =\n | typeof documentAttachmentBlobProjection.localId\n | typeof documentHistoryCheckpoints.localId\n | typeof documentHistoryUpdates.localId\n | typeof documentPendingAttachments.localId\n | typeof documentSyncFailures.localId;\n\nfunction requirePredicate(predicate: SQL | undefined): SQL {\n if (!predicate) {\n throw new Error(\"Document orphan cleanup requires a SQL predicate\");\n }\n return predicate;\n}\n\nfunction hasNoDocument(\n tx: ClientSQLiteTransactionScope,\n localId: DocumentSideLocalIdColumn,\n appKind: string | null = DOCUMENTS_APP_KIND,\n): SQL {\n return notExists(\n tx\n .select({ localId: documents.localId })\n .from(documents)\n .where(\n appKind === null\n ? eq(documents.localId, localId)\n : and(eq(documents.appKind, appKind), eq(documents.localId, localId)),\n ),\n );\n}\n\nexport async function queueDocumentAttachmentBlobReclaims(input: {\n localWhere: SQL;\n pendingWhere: SQL;\n tx: ClientSQLiteTransactionScope;\n}): Promise<void> {\n const { localWhere, pendingWhere, tx } = input;\n const [pendingAttachmentRows, localAttachmentRows] = await Promise.all([\n tx\n .select({ storageKey: documentPendingAttachments.storageKey })\n .from(documentPendingAttachments)\n .where(pendingWhere),\n tx\n .select({ storageKey: documentAttachmentBlobProjection.storageKey })\n .from(documentAttachmentBlobProjection)\n .where(localWhere),\n ]);\n const storageKeys = [\n ...new Set(\n [...pendingAttachmentRows, ...localAttachmentRows].map(\n (row) => row.storageKey,\n ),\n ),\n ];\n await queueDocumentAttachmentStorageKeys(tx, storageKeys);\n}\n\nexport async function queueDocumentAttachmentStorageKeys(\n tx: ClientSQLiteTransactionScope,\n storageKeys: ReadonlyArray<string>,\n): Promise<void> {\n if (storageKeys.length > 0) {\n await tx\n .insert(documentOrphanBlobReclaims)\n .values(storageKeys.map((storageKey) => ({ storageKey })))\n .onConflictDoNothing()\n .run();\n }\n}\n\ninterface AttachmentSweepCandidate {\n localId: string;\n slotId: string;\n storageKey: string;\n}\n\nfunction pendingAttachmentCandidatesWhere(\n candidates: ReadonlyArray<AttachmentSweepCandidate>,\n): SQL {\n return requirePredicate(\n or(\n ...candidates.map((candidate) =>\n and(\n eq(documentPendingAttachments.localId, candidate.localId),\n eq(documentPendingAttachments.slotId, candidate.slotId),\n ),\n ),\n ),\n );\n}\n\nfunction localAttachmentCandidatesWhere(\n candidates: ReadonlyArray<AttachmentSweepCandidate>,\n): SQL {\n return requirePredicate(\n or(\n ...candidates.map((candidate) =>\n and(\n eq(documentAttachmentBlobProjection.localId, candidate.localId),\n eq(documentAttachmentBlobProjection.slotId, candidate.slotId),\n ),\n ),\n ),\n );\n}\n\ninterface HistorySweepCandidates {\n checkpoints: ReadonlyArray<{ localId: string }>;\n updates: ReadonlyArray<{ id: string | null }>;\n}\n\ninterface WriteSweepCandidates {\n syncFailures: ReadonlyArray<{ localId: string }>;\n}\n\ninterface AttachmentSweepCandidates {\n local: ReadonlyArray<AttachmentSweepCandidate>;\n pending: ReadonlyArray<AttachmentSweepCandidate>;\n}\n\nasync function selectHistorySweepCandidates(\n tx: ClientSQLiteTransactionScope,\n olderThan: string,\n): Promise<HistorySweepCandidates> {\n const checkpoints = await tx\n .select({ localId: documentHistoryCheckpoints.localId })\n .from(documentHistoryCheckpoints)\n .where(\n and(\n eq(documentHistoryCheckpoints.appKind, DOCUMENTS_APP_KIND),\n lt(documentHistoryCheckpoints.updatedAt, olderThan),\n hasNoDocument(tx, documentHistoryCheckpoints.localId),\n ),\n )\n .limit(DOCUMENT_ORPHAN_SWEEP_BATCH_SIZE);\n const updates = await tx\n .select({ id: documentHistoryUpdates.id })\n .from(documentHistoryUpdates)\n .where(\n and(\n eq(documentHistoryUpdates.appKind, DOCUMENTS_APP_KIND),\n isNotNull(documentHistoryUpdates.id),\n lt(documentHistoryUpdates.createdAt, olderThan),\n hasNoDocument(tx, documentHistoryUpdates.localId),\n ),\n )\n .limit(DOCUMENT_ORPHAN_SWEEP_BATCH_SIZE);\n return { checkpoints, updates };\n}\n\nasync function selectWriteSweepCandidates(\n tx: ClientSQLiteTransactionScope,\n olderThan: string,\n): Promise<WriteSweepCandidates> {\n const syncFailures = await tx\n .select({ localId: documentSyncFailures.localId })\n .from(documentSyncFailures)\n .where(\n and(\n eq(documentSyncFailures.appKind, DOCUMENTS_APP_KIND),\n lt(documentSyncFailures.attemptedAt, olderThan),\n hasNoDocument(tx, documentSyncFailures.localId),\n ),\n )\n .limit(DOCUMENT_ORPHAN_SWEEP_BATCH_SIZE);\n return { syncFailures };\n}\n\nasync function selectAttachmentSweepCandidates(\n tx: ClientSQLiteTransactionScope,\n olderThan: string,\n): Promise<AttachmentSweepCandidates> {\n const pending = await tx\n .select({\n localId: documentPendingAttachments.localId,\n slotId: documentPendingAttachments.slotId,\n storageKey: documentPendingAttachments.storageKey,\n })\n .from(documentPendingAttachments)\n .where(\n and(\n lt(documentPendingAttachments.createdAt, olderThan),\n hasNoDocument(tx, documentPendingAttachments.localId, null),\n ),\n )\n .limit(DOCUMENT_ORPHAN_SWEEP_BATCH_SIZE);\n const local = await tx\n .select({\n localId: documentAttachmentBlobProjection.localId,\n slotId: documentAttachmentBlobProjection.slotId,\n storageKey: documentAttachmentBlobProjection.storageKey,\n })\n .from(documentAttachmentBlobProjection)\n .where(\n and(\n lt(documentAttachmentBlobProjection.updatedAt, olderThan),\n hasNoDocument(tx, documentAttachmentBlobProjection.localId, null),\n ),\n )\n .limit(DOCUMENT_ORPHAN_SWEEP_BATCH_SIZE);\n return { local, pending };\n}\n\nasync function deleteHistorySweepCandidates(\n tx: ClientSQLiteTransactionScope,\n candidates: HistorySweepCandidates,\n): Promise<void> {\n if (candidates.checkpoints.length > 0) {\n await tx\n .delete(documentHistoryCheckpoints)\n .where(\n and(\n eq(documentHistoryCheckpoints.appKind, DOCUMENTS_APP_KIND),\n inArray(\n documentHistoryCheckpoints.localId,\n candidates.checkpoints.map((row) => row.localId),\n ),\n ),\n )\n .run();\n }\n if (candidates.updates.length > 0) {\n await tx\n .delete(documentHistoryUpdates)\n .where(\n inArray(\n documentHistoryUpdates.id,\n candidates.updates.flatMap((row) =>\n row.id === null ? [] : [row.id],\n ),\n ),\n )\n .run();\n }\n}\n\nasync function deleteWriteSweepCandidates(\n tx: ClientSQLiteTransactionScope,\n candidates: WriteSweepCandidates,\n): Promise<void> {\n if (candidates.syncFailures.length > 0) {\n await tx\n .delete(documentSyncFailures)\n .where(\n and(\n eq(documentSyncFailures.appKind, DOCUMENTS_APP_KIND),\n inArray(\n documentSyncFailures.localId,\n candidates.syncFailures.map((row) => row.localId),\n ),\n ),\n )\n .run();\n }\n}\n\nasync function deleteAttachmentSweepCandidates(\n tx: ClientSQLiteTransactionScope,\n candidates: AttachmentSweepCandidates,\n): Promise<void> {\n await queueDocumentAttachmentStorageKeys(tx, [\n ...new Set(\n [...candidates.pending, ...candidates.local].map((row) => row.storageKey),\n ),\n ]);\n if (candidates.pending.length > 0) {\n await tx\n .delete(documentPendingAttachments)\n .where(pendingAttachmentCandidatesWhere(candidates.pending))\n .run();\n }\n if (candidates.local.length > 0) {\n await tx\n .delete(documentAttachmentBlobProjection)\n .where(localAttachmentCandidatesWhere(candidates.local))\n .run();\n }\n}\n\nfunction hasFullSweepBatch(\n history: HistorySweepCandidates,\n writes: WriteSweepCandidates,\n attachments: AttachmentSweepCandidates,\n): boolean {\n return [\n history.checkpoints,\n history.updates,\n writes.syncFailures,\n attachments.pending,\n attachments.local,\n ].some((rows) => rows.length === DOCUMENT_ORPHAN_SWEEP_BATCH_SIZE);\n}\n\n/** Remove one bounded batch of aged side writes without a canonical row. */\nexport async function deleteOrphanedDocumentSideRows(\n execSql: ExecSql,\n options: { now?: Date | undefined } = {},\n): Promise<boolean> {\n const olderThan = new Date(\n (options.now?.getTime() ?? Date.now()) - DOCUMENT_ORPHAN_SWEEP_MIN_AGE_MS,\n ).toISOString();\n return runSerializedSqlMutation(execSql, async (lockedExecSql) =>\n getClientSQLitePersistenceRuntime(lockedExecSql).transaction(async (tx) => {\n const history = await selectHistorySweepCandidates(tx, olderThan);\n const writes = await selectWriteSweepCandidates(tx, olderThan);\n const attachments = await selectAttachmentSweepCandidates(tx, olderThan);\n await deleteHistorySweepCandidates(tx, history);\n await deleteWriteSweepCandidates(tx, writes);\n await deleteAttachmentSweepCandidates(tx, attachments);\n return hasFullSweepBatch(history, writes, attachments);\n }),\n );\n}\n\nexport async function listDocumentOrphanBlobReclaims(\n execSql: ExecSql,\n limit: number,\n): Promise<ReadonlyArray<string>> {\n const { db } = getClientSQLitePersistenceRuntime(execSql);\n const rows = await db\n .select({ storageKey: documentOrphanBlobReclaims.storageKey })\n .from(documentOrphanBlobReclaims)\n .orderBy(asc(documentOrphanBlobReclaims.storageKey))\n .limit(limit);\n return rows.map((row) => row.storageKey);\n}\n\nexport async function isDocumentBlobStorageKeyReferenced(\n execSql: ExecSql,\n storageKey: string,\n): Promise<boolean> {\n const { db } = getClientSQLitePersistenceRuntime(execSql);\n const [pendingRows, localRows] = await Promise.all([\n db\n .select({ storageKey: documentPendingAttachments.storageKey })\n .from(documentPendingAttachments)\n .where(eq(documentPendingAttachments.storageKey, storageKey))\n .limit(1),\n db\n .select({ storageKey: documentAttachmentBlobProjection.storageKey })\n .from(documentAttachmentBlobProjection)\n .where(eq(documentAttachmentBlobProjection.storageKey, storageKey))\n .limit(1),\n ]);\n return pendingRows.length > 0 || localRows.length > 0;\n}\n\nexport async function acknowledgeDocumentOrphanBlobReclaim(\n execSql: ExecSql,\n storageKey: string,\n): Promise<void> {\n const { db } = getClientSQLitePersistenceRuntime(execSql);\n await db\n .delete(documentOrphanBlobReclaims)\n .where(eq(documentOrphanBlobReclaims.storageKey, storageKey))\n .run();\n}\n"]}
1
+ {"version":3,"file":"orphanSideRows.js","sourceRoot":"","sources":["orphanSideRows.ts"],"names":[],"mappings":"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","sourcesContent":["import {\n and,\n asc,\n eq,\n inArray,\n isNotNull,\n lt,\n notExists,\n or,\n type SQL,\n} from \"drizzle-orm\";\nimport { documentOrphanBlobReclaims } from \"../../../sqlite/documentOrphanBlobReclaims\";\nimport {\n documentAttachmentBlobProjection,\n documentHistoryCheckpoints,\n documentHistoryUpdates,\n documentPendingAttachments,\n documents,\n} from \"../../../sqlite/schema\";\nimport {\n type ClientSQLiteTransactionScope,\n getClientSQLitePersistenceRuntime,\n} from \"../../../sqlite/sqlitePersistenceRuntime\";\nimport {\n type ExecSql,\n runSerializedSqlMutation,\n} from \"../../../sqlite/sqlSchema\";\nimport { DOCUMENTS_APP_KIND } from \"./constants\";\n\n// Crash residue gets a full day to outlive slow devices and wall-clock skew;\n// canonical-row absence is necessary but never sufficient for fresh writes.\nconst DOCUMENT_ORPHAN_SWEEP_MIN_AGE_MS = 24 * 60 * 60 * 1000;\nconst DOCUMENT_ORPHAN_SWEEP_BATCH_SIZE = 32;\n\ntype DocumentSideLocalIdColumn =\n | typeof documentAttachmentBlobProjection.localId\n | typeof documentHistoryCheckpoints.localId\n | typeof documentHistoryUpdates.localId\n | typeof documentPendingAttachments.localId;\n\nfunction requirePredicate(predicate: SQL | undefined): SQL {\n if (!predicate) {\n throw new Error(\"Document orphan cleanup requires a SQL predicate\");\n }\n return predicate;\n}\n\nfunction hasNoDocument(\n tx: ClientSQLiteTransactionScope,\n localId: DocumentSideLocalIdColumn,\n appKind: string | null = DOCUMENTS_APP_KIND,\n): SQL {\n return notExists(\n tx\n .select({ localId: documents.localId })\n .from(documents)\n .where(\n appKind === null\n ? eq(documents.localId, localId)\n : and(eq(documents.appKind, appKind), eq(documents.localId, localId)),\n ),\n );\n}\n\nexport async function queueDocumentAttachmentBlobReclaims(input: {\n localWhere: SQL;\n pendingWhere: SQL;\n tx: ClientSQLiteTransactionScope;\n}): Promise<void> {\n const { localWhere, pendingWhere, tx } = input;\n const [pendingAttachmentRows, localAttachmentRows] = await Promise.all([\n tx\n .select({ storageKey: documentPendingAttachments.storageKey })\n .from(documentPendingAttachments)\n .where(pendingWhere),\n tx\n .select({ storageKey: documentAttachmentBlobProjection.storageKey })\n .from(documentAttachmentBlobProjection)\n .where(localWhere),\n ]);\n const storageKeys = [\n ...new Set(\n [...pendingAttachmentRows, ...localAttachmentRows].map(\n (row) => row.storageKey,\n ),\n ),\n ];\n await queueDocumentAttachmentStorageKeys(tx, storageKeys);\n}\n\nexport async function queueDocumentAttachmentStorageKeys(\n tx: ClientSQLiteTransactionScope,\n storageKeys: ReadonlyArray<string>,\n): Promise<void> {\n if (storageKeys.length > 0) {\n await tx\n .insert(documentOrphanBlobReclaims)\n .values(storageKeys.map((storageKey) => ({ storageKey })))\n .onConflictDoNothing()\n .run();\n }\n}\n\ninterface AttachmentSweepCandidate {\n localId: string;\n slotId: string;\n storageKey: string;\n}\n\nfunction pendingAttachmentCandidatesWhere(\n candidates: ReadonlyArray<AttachmentSweepCandidate>,\n): SQL {\n return requirePredicate(\n or(\n ...candidates.map((candidate) =>\n and(\n eq(documentPendingAttachments.localId, candidate.localId),\n eq(documentPendingAttachments.slotId, candidate.slotId),\n ),\n ),\n ),\n );\n}\n\nfunction localAttachmentCandidatesWhere(\n candidates: ReadonlyArray<AttachmentSweepCandidate>,\n): SQL {\n return requirePredicate(\n or(\n ...candidates.map((candidate) =>\n and(\n eq(documentAttachmentBlobProjection.localId, candidate.localId),\n eq(documentAttachmentBlobProjection.slotId, candidate.slotId),\n ),\n ),\n ),\n );\n}\n\ninterface HistorySweepCandidates {\n checkpoints: ReadonlyArray<{ localId: string }>;\n updates: ReadonlyArray<{ id: string | null }>;\n}\n\ninterface AttachmentSweepCandidates {\n local: ReadonlyArray<AttachmentSweepCandidate>;\n pending: ReadonlyArray<AttachmentSweepCandidate>;\n}\n\nasync function selectHistorySweepCandidates(\n tx: ClientSQLiteTransactionScope,\n olderThan: string,\n): Promise<HistorySweepCandidates> {\n const checkpoints = await tx\n .select({ localId: documentHistoryCheckpoints.localId })\n .from(documentHistoryCheckpoints)\n .where(\n and(\n eq(documentHistoryCheckpoints.appKind, DOCUMENTS_APP_KIND),\n lt(documentHistoryCheckpoints.updatedAt, olderThan),\n hasNoDocument(tx, documentHistoryCheckpoints.localId),\n ),\n )\n .limit(DOCUMENT_ORPHAN_SWEEP_BATCH_SIZE);\n const updates = await tx\n .select({ id: documentHistoryUpdates.id })\n .from(documentHistoryUpdates)\n .where(\n and(\n eq(documentHistoryUpdates.appKind, DOCUMENTS_APP_KIND),\n isNotNull(documentHistoryUpdates.id),\n lt(documentHistoryUpdates.createdAt, olderThan),\n hasNoDocument(tx, documentHistoryUpdates.localId),\n ),\n )\n .limit(DOCUMENT_ORPHAN_SWEEP_BATCH_SIZE);\n return { checkpoints, updates };\n}\n\nasync function selectAttachmentSweepCandidates(\n tx: ClientSQLiteTransactionScope,\n olderThan: string,\n): Promise<AttachmentSweepCandidates> {\n const pending = await tx\n .select({\n localId: documentPendingAttachments.localId,\n slotId: documentPendingAttachments.slotId,\n storageKey: documentPendingAttachments.storageKey,\n })\n .from(documentPendingAttachments)\n .where(\n and(\n lt(documentPendingAttachments.createdAt, olderThan),\n hasNoDocument(tx, documentPendingAttachments.localId, null),\n ),\n )\n .limit(DOCUMENT_ORPHAN_SWEEP_BATCH_SIZE);\n const local = await tx\n .select({\n localId: documentAttachmentBlobProjection.localId,\n slotId: documentAttachmentBlobProjection.slotId,\n storageKey: documentAttachmentBlobProjection.storageKey,\n })\n .from(documentAttachmentBlobProjection)\n .where(\n and(\n lt(documentAttachmentBlobProjection.updatedAt, olderThan),\n hasNoDocument(tx, documentAttachmentBlobProjection.localId, null),\n ),\n )\n .limit(DOCUMENT_ORPHAN_SWEEP_BATCH_SIZE);\n return { local, pending };\n}\n\nasync function deleteHistorySweepCandidates(\n tx: ClientSQLiteTransactionScope,\n candidates: HistorySweepCandidates,\n): Promise<void> {\n if (candidates.checkpoints.length > 0) {\n await tx\n .delete(documentHistoryCheckpoints)\n .where(\n and(\n eq(documentHistoryCheckpoints.appKind, DOCUMENTS_APP_KIND),\n inArray(\n documentHistoryCheckpoints.localId,\n candidates.checkpoints.map((row) => row.localId),\n ),\n ),\n )\n .run();\n }\n if (candidates.updates.length > 0) {\n await tx\n .delete(documentHistoryUpdates)\n .where(\n inArray(\n documentHistoryUpdates.id,\n candidates.updates.flatMap((row) =>\n row.id === null ? [] : [row.id],\n ),\n ),\n )\n .run();\n }\n}\n\nasync function deleteAttachmentSweepCandidates(\n tx: ClientSQLiteTransactionScope,\n candidates: AttachmentSweepCandidates,\n): Promise<void> {\n await queueDocumentAttachmentStorageKeys(tx, [\n ...new Set(\n [...candidates.pending, ...candidates.local].map((row) => row.storageKey),\n ),\n ]);\n if (candidates.pending.length > 0) {\n await tx\n .delete(documentPendingAttachments)\n .where(pendingAttachmentCandidatesWhere(candidates.pending))\n .run();\n }\n if (candidates.local.length > 0) {\n await tx\n .delete(documentAttachmentBlobProjection)\n .where(localAttachmentCandidatesWhere(candidates.local))\n .run();\n }\n}\n\nfunction hasFullSweepBatch(\n history: HistorySweepCandidates,\n attachments: AttachmentSweepCandidates,\n): boolean {\n return [\n history.checkpoints,\n history.updates,\n attachments.pending,\n attachments.local,\n ].some((rows) => rows.length === DOCUMENT_ORPHAN_SWEEP_BATCH_SIZE);\n}\n\n/** Remove one bounded batch of aged side writes without a canonical row. */\nexport async function deleteOrphanedDocumentSideRows(\n execSql: ExecSql,\n options: { now?: Date | undefined } = {},\n): Promise<boolean> {\n const olderThan = new Date(\n (options.now?.getTime() ?? Date.now()) - DOCUMENT_ORPHAN_SWEEP_MIN_AGE_MS,\n ).toISOString();\n return runSerializedSqlMutation(execSql, async (lockedExecSql) =>\n getClientSQLitePersistenceRuntime(lockedExecSql).transaction(async (tx) => {\n const history = await selectHistorySweepCandidates(tx, olderThan);\n const attachments = await selectAttachmentSweepCandidates(tx, olderThan);\n await deleteHistorySweepCandidates(tx, history);\n await deleteAttachmentSweepCandidates(tx, attachments);\n return hasFullSweepBatch(history, attachments);\n }),\n );\n}\n\nexport async function listDocumentOrphanBlobReclaims(\n execSql: ExecSql,\n limit: number,\n): Promise<ReadonlyArray<string>> {\n const { db } = getClientSQLitePersistenceRuntime(execSql);\n const rows = await db\n .select({ storageKey: documentOrphanBlobReclaims.storageKey })\n .from(documentOrphanBlobReclaims)\n .orderBy(asc(documentOrphanBlobReclaims.storageKey))\n .limit(limit);\n return rows.map((row) => row.storageKey);\n}\n\nexport async function isDocumentBlobStorageKeyReferenced(\n execSql: ExecSql,\n storageKey: string,\n): Promise<boolean> {\n const { db } = getClientSQLitePersistenceRuntime(execSql);\n const [pendingRows, localRows] = await Promise.all([\n db\n .select({ storageKey: documentPendingAttachments.storageKey })\n .from(documentPendingAttachments)\n .where(eq(documentPendingAttachments.storageKey, storageKey))\n .limit(1),\n db\n .select({ storageKey: documentAttachmentBlobProjection.storageKey })\n .from(documentAttachmentBlobProjection)\n .where(eq(documentAttachmentBlobProjection.storageKey, storageKey))\n .limit(1),\n ]);\n return pendingRows.length > 0 || localRows.length > 0;\n}\n\nexport async function acknowledgeDocumentOrphanBlobReclaim(\n execSql: ExecSql,\n storageKey: string,\n): Promise<void> {\n const { db } = getClientSQLitePersistenceRuntime(execSql);\n await db\n .delete(documentOrphanBlobReclaims)\n .where(eq(documentOrphanBlobReclaims.storageKey, storageKey))\n .run();\n}\n"]}
@@ -1,6 +1,6 @@
1
1
  import { ORGANIZATION_DOCUMENT_USAGE_CATEGORIES, OrganizationDataUsageResponseSchema, } from "../../../internal/validators/response/index.js";
2
2
  import { and, eq } from "drizzle-orm";
3
- import { ORGANIZATION_DATA_USAGE_PROJECTION_VERSION, organizationDataUsageCategories, organizationDataUsageSnapshots, organizationDataUsageTables, } from "../../sqlite/organizationDataUsageSchema.js";
3
+ import { ORGANIZATION_DATA_USAGE_PROJECTION_VERSION, organizationDataUsageSnapshots, organizationDataUsageTables, } from "../../sqlite/organizationDataUsageSchema.js";
4
4
  import { organizationReadModelTables } from "../../sqlite/organizationReadModelTableRegistry.js";
5
5
  import { getClientSQLitePersistenceRuntime, } from "../../sqlite/sqlitePersistenceRuntime.js";
6
6
  import { ensureSqlTables } from "../../sqlite/sqlTableSchema.js";
@@ -8,9 +8,6 @@ import { clearOrganizationPresentationDenialInTransaction } from "./organization
8
8
  function isNonNegativeSafeInteger(value) {
9
9
  return Number.isSafeInteger(value) && value >= 0;
10
10
  }
11
- function isDocumentUsageCategory(value) {
12
- return ORGANIZATION_DOCUMENT_USAGE_CATEGORIES.some((category) => category === value);
13
- }
14
11
  function isIsoTimestamp(value) {
15
12
  try {
16
13
  return value.length > 0 && new Date(value).toISOString() === value;
@@ -34,10 +31,6 @@ function parseResponse(response) {
34
31
  }
35
32
  export async function purgeOrganizationDataUsageProjectionInTransaction(input) {
36
33
  requireScope(input.organizationId, input.requesterUserId);
37
- const condition = input.requesterUserId
38
- ? and(eq(organizationDataUsageCategories.organizationId, input.organizationId), eq(organizationDataUsageCategories.requesterUserId, input.requesterUserId))
39
- : eq(organizationDataUsageCategories.organizationId, input.organizationId);
40
- await input.tx.delete(organizationDataUsageCategories).where(condition).run();
41
34
  const snapshotCondition = input.requesterUserId
42
35
  ? and(eq(organizationDataUsageSnapshots.organizationId, input.organizationId), eq(organizationDataUsageSnapshots.requesterUserId, input.requesterUserId))
43
36
  : eq(organizationDataUsageSnapshots.organizationId, input.organizationId);
@@ -58,53 +51,49 @@ function isStoredSnapshotValid(snapshot, organizationId, requesterUserId) {
58
51
  isNonNegativeSafeInteger(snapshot.totalByteLength) &&
59
52
  isIsoTimestamp(snapshot.refreshedAt));
60
53
  }
61
- function storedResponse(snapshot, categories) {
62
- if (categories.length !== ORGANIZATION_DOCUMENT_USAGE_CATEGORIES.length) {
63
- return null;
64
- }
65
- const byCategory = new Map();
66
- for (const row of categories) {
67
- const category = row.category;
68
- if (row.organizationId !== snapshot.organizationId ||
69
- row.requesterUserId !== snapshot.requesterUserId ||
70
- !isDocumentUsageCategory(category) ||
71
- byCategory.has(category) ||
72
- !isNonNegativeSafeInteger(row.byteLength) ||
73
- !isNonNegativeSafeInteger(row.documentCount) ||
74
- !isNonNegativeSafeInteger(row.updateCount)) {
75
- return null;
76
- }
77
- byCategory.set(category, {
78
- byteLength: row.byteLength,
79
- category,
80
- documentCount: row.documentCount,
81
- updateCount: row.updateCount,
82
- });
54
+ function parseStoredBreakdown(json) {
55
+ try {
56
+ return JSON.parse(json);
83
57
  }
84
- const breakdown = ORGANIZATION_DOCUMENT_USAGE_CATEGORIES.flatMap((category) => {
85
- const entry = byCategory.get(category);
86
- return entry ? [entry] : [];
87
- });
88
- if (breakdown.length !== ORGANIZATION_DOCUMENT_USAGE_CATEGORIES.length) {
58
+ catch {
89
59
  return null;
90
60
  }
91
- const response = {
61
+ }
62
+ // The stored breakdown is untrusted local JSON: the strict response schema
63
+ // re-checks every entry (known category, each exactly once, safe non-negative
64
+ // counts that sum to the stored totals) before it is served.
65
+ function storedResponse(snapshot) {
66
+ const parsed = OrganizationDataUsageResponseSchema.safeParse({
92
67
  organizationId: snapshot.organizationId,
93
68
  blobs: {
94
69
  blobCount: snapshot.blobCount,
95
70
  byteLength: snapshot.blobByteLength,
96
71
  },
97
72
  documents: {
98
- breakdown,
73
+ breakdown: parseStoredBreakdown(snapshot.categoryBreakdownJson),
99
74
  byteLength: snapshot.documentByteLength,
100
75
  documentCount: snapshot.documentCount,
101
76
  updateCount: snapshot.documentUpdateCount,
102
77
  },
103
78
  totalByteLength: snapshot.totalByteLength,
104
- };
105
- const parsed = OrganizationDataUsageResponseSchema.safeParse(response);
79
+ });
106
80
  return parsed.success ? parsed.data : null;
107
81
  }
82
+ function canonicalBreakdownJson(response) {
83
+ const order = new Map(ORGANIZATION_DOCUMENT_USAGE_CATEGORIES.map((category, index) => [
84
+ category,
85
+ index,
86
+ ]));
87
+ const breakdown = [...response.documents.breakdown]
88
+ .sort((left, right) => (order.get(left.category) ?? 0) - (order.get(right.category) ?? 0))
89
+ .map((entry) => ({
90
+ byteLength: entry.byteLength,
91
+ category: entry.category,
92
+ documentCount: entry.documentCount,
93
+ updateCount: entry.updateCount,
94
+ }));
95
+ return JSON.stringify(breakdown);
96
+ }
108
97
  export async function loadOrganizationDataUsageProjection(execSql, organizationId, requesterUserId) {
109
98
  requireScope(organizationId, requesterUserId);
110
99
  await ensureSqlTables(execSql, organizationDataUsageTables);
@@ -115,22 +104,11 @@ export async function loadOrganizationDataUsageProjection(execSql, organizationI
115
104
  .from(organizationDataUsageSnapshots)
116
105
  .where(scope)
117
106
  .limit(1);
118
- const categories = await tx
119
- .select()
120
- .from(organizationDataUsageCategories)
121
- .where(and(eq(organizationDataUsageCategories.organizationId, organizationId), eq(organizationDataUsageCategories.requesterUserId, requesterUserId)));
122
107
  if (!snapshot) {
123
- if (categories.length > 0) {
124
- await purgeOrganizationDataUsageProjectionInTransaction({
125
- organizationId,
126
- requesterUserId,
127
- tx,
128
- });
129
- }
130
108
  return null;
131
109
  }
132
110
  const response = isStoredSnapshotValid(snapshot, organizationId, requesterUserId)
133
- ? storedResponse(snapshot, categories)
111
+ ? storedResponse(snapshot)
134
112
  : null;
135
113
  if (!response) {
136
114
  await purgeOrganizationDataUsageProjectionInTransaction({
@@ -158,8 +136,6 @@ export async function saveOrganizationDataUsageProjection(input) {
158
136
  requesterUserId: input.requesterUserId,
159
137
  tx,
160
138
  });
161
- const scope = and(eq(organizationDataUsageCategories.organizationId, response.organizationId), eq(organizationDataUsageCategories.requesterUserId, input.requesterUserId));
162
- await tx.delete(organizationDataUsageCategories).where(scope).run();
163
139
  const snapshot = {
164
140
  organizationId: response.organizationId,
165
141
  requesterUserId: input.requesterUserId,
@@ -170,6 +146,7 @@ export async function saveOrganizationDataUsageProjection(input) {
170
146
  documentCount: response.documents.documentCount,
171
147
  documentUpdateCount: response.documents.updateCount,
172
148
  totalByteLength: response.totalByteLength,
149
+ categoryBreakdownJson: canonicalBreakdownJson(response),
173
150
  refreshedAt: new Date().toISOString(),
174
151
  };
175
152
  await tx
@@ -188,21 +165,11 @@ export async function saveOrganizationDataUsageProjection(input) {
188
165
  documentCount: snapshot.documentCount,
189
166
  documentUpdateCount: snapshot.documentUpdateCount,
190
167
  totalByteLength: snapshot.totalByteLength,
168
+ categoryBreakdownJson: snapshot.categoryBreakdownJson,
191
169
  refreshedAt: snapshot.refreshedAt,
192
170
  },
193
171
  })
194
172
  .run();
195
- await tx
196
- .insert(organizationDataUsageCategories)
197
- .values(response.documents.breakdown.map((entry) => ({
198
- organizationId: response.organizationId,
199
- requesterUserId: input.requesterUserId,
200
- category: entry.category,
201
- byteLength: entry.byteLength,
202
- documentCount: entry.documentCount,
203
- updateCount: entry.updateCount,
204
- })))
205
- .run();
206
173
  });
207
174
  }
208
175
  export async function purgeOrganizationDataUsageProjection(execSql, organizationId, requesterUserId) {
@@ -1 +1 @@
1
- {"version":3,"file":"organizationDataUsagePersistence.js","sourceRoot":"","sources":["organizationDataUsagePersistence.ts"],"names":[],"mappings":"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","sourcesContent":["import {\n ORGANIZATION_DOCUMENT_USAGE_CATEGORIES,\n type OrganizationDataUsageResponse,\n OrganizationDataUsageResponseSchema,\n type OrganizationDocumentUsageCategory,\n type OrganizationDocumentUsageCategoryBreakdown,\n} from \"@tearleads/validators/response\";\nimport { and, eq } from \"drizzle-orm\";\nimport {\n ORGANIZATION_DATA_USAGE_PROJECTION_VERSION,\n organizationDataUsageCategories,\n organizationDataUsageSnapshots,\n organizationDataUsageTables,\n} from \"../../sqlite/organizationDataUsageSchema\";\nimport { organizationReadModelTables } from \"../../sqlite/organizationReadModelTableRegistry\";\nimport {\n type ClientSQLiteTransactionScope,\n getClientSQLitePersistenceRuntime,\n} from \"../../sqlite/sqlitePersistenceRuntime\";\nimport type { ExecSql } from \"../../sqlite/sqlSchema\";\nimport { ensureSqlTables } from \"../../sqlite/sqlTableSchema\";\nimport { clearOrganizationPresentationDenialInTransaction } from \"./organizationPresentationDenialPersistence\";\n\ntype StoredCategory = typeof organizationDataUsageCategories.$inferSelect;\ntype StoredSnapshot = typeof organizationDataUsageSnapshots.$inferSelect;\n\nfunction isNonNegativeSafeInteger(value: number): boolean {\n return Number.isSafeInteger(value) && value >= 0;\n}\n\nfunction isDocumentUsageCategory(\n value: string,\n): value is OrganizationDocumentUsageCategory {\n return ORGANIZATION_DOCUMENT_USAGE_CATEGORIES.some(\n (category) => category === value,\n );\n}\n\nfunction isIsoTimestamp(value: string): boolean {\n try {\n return value.length > 0 && new Date(value).toISOString() === value;\n } catch {\n return false;\n }\n}\n\nfunction requireScope(organizationId: string, requesterUserId?: string): void {\n if (\n organizationId.length === 0 ||\n (requesterUserId !== undefined && requesterUserId.length === 0)\n ) {\n throw new Error(\"Organization data usage scope is invalid\");\n }\n}\n\nfunction parseResponse(\n response: OrganizationDataUsageResponse,\n): OrganizationDataUsageResponse {\n const parsed = OrganizationDataUsageResponseSchema.safeParse(response);\n if (!parsed.success) {\n throw new Error(\"Organization data usage response is invalid\");\n }\n return parsed.data;\n}\n\nexport async function purgeOrganizationDataUsageProjectionInTransaction(input: {\n readonly organizationId: string;\n readonly requesterUserId?: string | undefined;\n readonly tx: ClientSQLiteTransactionScope;\n}): Promise<void> {\n requireScope(input.organizationId, input.requesterUserId);\n const condition = input.requesterUserId\n ? and(\n eq(\n organizationDataUsageCategories.organizationId,\n input.organizationId,\n ),\n eq(\n organizationDataUsageCategories.requesterUserId,\n input.requesterUserId,\n ),\n )\n : eq(organizationDataUsageCategories.organizationId, input.organizationId);\n await input.tx.delete(organizationDataUsageCategories).where(condition).run();\n\n const snapshotCondition = input.requesterUserId\n ? and(\n eq(organizationDataUsageSnapshots.organizationId, input.organizationId),\n eq(\n organizationDataUsageSnapshots.requesterUserId,\n input.requesterUserId,\n ),\n )\n : eq(organizationDataUsageSnapshots.organizationId, input.organizationId);\n await input.tx\n .delete(organizationDataUsageSnapshots)\n .where(snapshotCondition)\n .run();\n}\n\nfunction isStoredSnapshotValid(\n snapshot: StoredSnapshot,\n organizationId: string,\n requesterUserId: string,\n): boolean {\n return (\n snapshot.organizationId === organizationId &&\n snapshot.requesterUserId === requesterUserId &&\n snapshot.projectionVersion === ORGANIZATION_DATA_USAGE_PROJECTION_VERSION &&\n isNonNegativeSafeInteger(snapshot.blobCount) &&\n isNonNegativeSafeInteger(snapshot.blobByteLength) &&\n isNonNegativeSafeInteger(snapshot.documentByteLength) &&\n isNonNegativeSafeInteger(snapshot.documentCount) &&\n isNonNegativeSafeInteger(snapshot.documentUpdateCount) &&\n isNonNegativeSafeInteger(snapshot.totalByteLength) &&\n isIsoTimestamp(snapshot.refreshedAt)\n );\n}\n\nfunction storedResponse(\n snapshot: StoredSnapshot,\n categories: readonly StoredCategory[],\n): OrganizationDataUsageResponse | null {\n if (categories.length !== ORGANIZATION_DOCUMENT_USAGE_CATEGORIES.length) {\n return null;\n }\n const byCategory = new Map<\n OrganizationDocumentUsageCategory,\n OrganizationDocumentUsageCategoryBreakdown\n >();\n for (const row of categories) {\n const category = row.category;\n if (\n row.organizationId !== snapshot.organizationId ||\n row.requesterUserId !== snapshot.requesterUserId ||\n !isDocumentUsageCategory(category) ||\n byCategory.has(category) ||\n !isNonNegativeSafeInteger(row.byteLength) ||\n !isNonNegativeSafeInteger(row.documentCount) ||\n !isNonNegativeSafeInteger(row.updateCount)\n ) {\n return null;\n }\n byCategory.set(category, {\n byteLength: row.byteLength,\n category,\n documentCount: row.documentCount,\n updateCount: row.updateCount,\n });\n }\n const breakdown = ORGANIZATION_DOCUMENT_USAGE_CATEGORIES.flatMap(\n (category) => {\n const entry = byCategory.get(category);\n return entry ? [entry] : [];\n },\n );\n if (breakdown.length !== ORGANIZATION_DOCUMENT_USAGE_CATEGORIES.length) {\n return null;\n }\n\n const response: OrganizationDataUsageResponse = {\n organizationId: snapshot.organizationId,\n blobs: {\n blobCount: snapshot.blobCount,\n byteLength: snapshot.blobByteLength,\n },\n documents: {\n breakdown,\n byteLength: snapshot.documentByteLength,\n documentCount: snapshot.documentCount,\n updateCount: snapshot.documentUpdateCount,\n },\n totalByteLength: snapshot.totalByteLength,\n };\n const parsed = OrganizationDataUsageResponseSchema.safeParse(response);\n return parsed.success ? parsed.data : null;\n}\n\nexport async function loadOrganizationDataUsageProjection(\n execSql: ExecSql,\n organizationId: string,\n requesterUserId: string,\n): Promise<OrganizationDataUsageResponse | null> {\n requireScope(organizationId, requesterUserId);\n await ensureSqlTables(execSql, organizationDataUsageTables);\n return getClientSQLitePersistenceRuntime(execSql).transaction(async (tx) => {\n const scope = and(\n eq(organizationDataUsageSnapshots.organizationId, organizationId),\n eq(organizationDataUsageSnapshots.requesterUserId, requesterUserId),\n );\n const [snapshot] = await tx\n .select()\n .from(organizationDataUsageSnapshots)\n .where(scope)\n .limit(1);\n const categories = await tx\n .select()\n .from(organizationDataUsageCategories)\n .where(\n and(\n eq(organizationDataUsageCategories.organizationId, organizationId),\n eq(organizationDataUsageCategories.requesterUserId, requesterUserId),\n ),\n );\n if (!snapshot) {\n if (categories.length > 0) {\n await purgeOrganizationDataUsageProjectionInTransaction({\n organizationId,\n requesterUserId,\n tx,\n });\n }\n return null;\n }\n const response = isStoredSnapshotValid(\n snapshot,\n organizationId,\n requesterUserId,\n )\n ? storedResponse(snapshot, categories)\n : null;\n if (!response) {\n await purgeOrganizationDataUsageProjectionInTransaction({\n organizationId,\n requesterUserId,\n tx,\n });\n }\n return response;\n });\n}\n\nexport async function saveOrganizationDataUsageProjection(input: {\n readonly execSql: ExecSql;\n readonly requesterUserId: string;\n readonly response: OrganizationDataUsageResponse;\n}): Promise<void> {\n const response = parseResponse(input.response);\n requireScope(response.organizationId, input.requesterUserId);\n await ensureSqlTables(input.execSql, [\n ...organizationDataUsageTables,\n ...organizationReadModelTables,\n ]);\n await getClientSQLitePersistenceRuntime(input.execSql).transaction(\n async (tx) => {\n // Storing an authoritative usage response proves access: retire any\n // durable denial marker for this scope in the same transaction.\n await clearOrganizationPresentationDenialInTransaction({\n organizationId: response.organizationId,\n projection: \"usage\",\n requesterUserId: input.requesterUserId,\n tx,\n });\n const scope = and(\n eq(\n organizationDataUsageCategories.organizationId,\n response.organizationId,\n ),\n eq(\n organizationDataUsageCategories.requesterUserId,\n input.requesterUserId,\n ),\n );\n await tx.delete(organizationDataUsageCategories).where(scope).run();\n\n const snapshot = {\n organizationId: response.organizationId,\n requesterUserId: input.requesterUserId,\n projectionVersion: ORGANIZATION_DATA_USAGE_PROJECTION_VERSION,\n blobCount: response.blobs.blobCount,\n blobByteLength: response.blobs.byteLength,\n documentByteLength: response.documents.byteLength,\n documentCount: response.documents.documentCount,\n documentUpdateCount: response.documents.updateCount,\n totalByteLength: response.totalByteLength,\n refreshedAt: new Date().toISOString(),\n };\n await tx\n .insert(organizationDataUsageSnapshots)\n .values(snapshot)\n .onConflictDoUpdate({\n target: [\n organizationDataUsageSnapshots.organizationId,\n organizationDataUsageSnapshots.requesterUserId,\n ],\n set: {\n projectionVersion: snapshot.projectionVersion,\n blobCount: snapshot.blobCount,\n blobByteLength: snapshot.blobByteLength,\n documentByteLength: snapshot.documentByteLength,\n documentCount: snapshot.documentCount,\n documentUpdateCount: snapshot.documentUpdateCount,\n totalByteLength: snapshot.totalByteLength,\n refreshedAt: snapshot.refreshedAt,\n },\n })\n .run();\n await tx\n .insert(organizationDataUsageCategories)\n .values(\n response.documents.breakdown.map((entry) => ({\n organizationId: response.organizationId,\n requesterUserId: input.requesterUserId,\n category: entry.category,\n byteLength: entry.byteLength,\n documentCount: entry.documentCount,\n updateCount: entry.updateCount,\n })),\n )\n .run();\n },\n );\n}\n\nexport async function purgeOrganizationDataUsageProjection(\n execSql: ExecSql,\n organizationId: string,\n requesterUserId?: string,\n): Promise<void> {\n requireScope(organizationId, requesterUserId);\n await ensureSqlTables(execSql, organizationDataUsageTables);\n await getClientSQLitePersistenceRuntime(execSql).transaction(async (tx) => {\n await purgeOrganizationDataUsageProjectionInTransaction({\n organizationId,\n requesterUserId,\n tx,\n });\n });\n}\n"]}
1
+ {"version":3,"file":"organizationDataUsagePersistence.js","sourceRoot":"","sources":["organizationDataUsagePersistence.ts"],"names":[],"mappings":"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","sourcesContent":["import {\n ORGANIZATION_DOCUMENT_USAGE_CATEGORIES,\n type OrganizationDataUsageResponse,\n OrganizationDataUsageResponseSchema,\n} from \"@tearleads/validators/response\";\nimport { and, eq } from \"drizzle-orm\";\nimport {\n ORGANIZATION_DATA_USAGE_PROJECTION_VERSION,\n organizationDataUsageSnapshots,\n organizationDataUsageTables,\n} from \"../../sqlite/organizationDataUsageSchema\";\nimport { organizationReadModelTables } from \"../../sqlite/organizationReadModelTableRegistry\";\nimport {\n type ClientSQLiteTransactionScope,\n getClientSQLitePersistenceRuntime,\n} from \"../../sqlite/sqlitePersistenceRuntime\";\nimport type { ExecSql } from \"../../sqlite/sqlSchema\";\nimport { ensureSqlTables } from \"../../sqlite/sqlTableSchema\";\nimport { clearOrganizationPresentationDenialInTransaction } from \"./organizationPresentationDenialPersistence\";\n\ntype StoredSnapshot = typeof organizationDataUsageSnapshots.$inferSelect;\n\nfunction isNonNegativeSafeInteger(value: number): boolean {\n return Number.isSafeInteger(value) && value >= 0;\n}\n\nfunction isIsoTimestamp(value: string): boolean {\n try {\n return value.length > 0 && new Date(value).toISOString() === value;\n } catch {\n return false;\n }\n}\n\nfunction requireScope(organizationId: string, requesterUserId?: string): void {\n if (\n organizationId.length === 0 ||\n (requesterUserId !== undefined && requesterUserId.length === 0)\n ) {\n throw new Error(\"Organization data usage scope is invalid\");\n }\n}\n\nfunction parseResponse(\n response: OrganizationDataUsageResponse,\n): OrganizationDataUsageResponse {\n const parsed = OrganizationDataUsageResponseSchema.safeParse(response);\n if (!parsed.success) {\n throw new Error(\"Organization data usage response is invalid\");\n }\n return parsed.data;\n}\n\nexport async function purgeOrganizationDataUsageProjectionInTransaction(input: {\n readonly organizationId: string;\n readonly requesterUserId?: string | undefined;\n readonly tx: ClientSQLiteTransactionScope;\n}): Promise<void> {\n requireScope(input.organizationId, input.requesterUserId);\n const snapshotCondition = input.requesterUserId\n ? and(\n eq(organizationDataUsageSnapshots.organizationId, input.organizationId),\n eq(\n organizationDataUsageSnapshots.requesterUserId,\n input.requesterUserId,\n ),\n )\n : eq(organizationDataUsageSnapshots.organizationId, input.organizationId);\n await input.tx\n .delete(organizationDataUsageSnapshots)\n .where(snapshotCondition)\n .run();\n}\n\nfunction isStoredSnapshotValid(\n snapshot: StoredSnapshot,\n organizationId: string,\n requesterUserId: string,\n): boolean {\n return (\n snapshot.organizationId === organizationId &&\n snapshot.requesterUserId === requesterUserId &&\n snapshot.projectionVersion === ORGANIZATION_DATA_USAGE_PROJECTION_VERSION &&\n isNonNegativeSafeInteger(snapshot.blobCount) &&\n isNonNegativeSafeInteger(snapshot.blobByteLength) &&\n isNonNegativeSafeInteger(snapshot.documentByteLength) &&\n isNonNegativeSafeInteger(snapshot.documentCount) &&\n isNonNegativeSafeInteger(snapshot.documentUpdateCount) &&\n isNonNegativeSafeInteger(snapshot.totalByteLength) &&\n isIsoTimestamp(snapshot.refreshedAt)\n );\n}\n\nfunction parseStoredBreakdown(json: string): unknown {\n try {\n return JSON.parse(json);\n } catch {\n return null;\n }\n}\n\n// The stored breakdown is untrusted local JSON: the strict response schema\n// re-checks every entry (known category, each exactly once, safe non-negative\n// counts that sum to the stored totals) before it is served.\nfunction storedResponse(\n snapshot: StoredSnapshot,\n): OrganizationDataUsageResponse | null {\n const parsed = OrganizationDataUsageResponseSchema.safeParse({\n organizationId: snapshot.organizationId,\n blobs: {\n blobCount: snapshot.blobCount,\n byteLength: snapshot.blobByteLength,\n },\n documents: {\n breakdown: parseStoredBreakdown(snapshot.categoryBreakdownJson),\n byteLength: snapshot.documentByteLength,\n documentCount: snapshot.documentCount,\n updateCount: snapshot.documentUpdateCount,\n },\n totalByteLength: snapshot.totalByteLength,\n });\n return parsed.success ? parsed.data : null;\n}\n\nfunction canonicalBreakdownJson(\n response: OrganizationDataUsageResponse,\n): string {\n const order = new Map<string, number>(\n ORGANIZATION_DOCUMENT_USAGE_CATEGORIES.map((category, index) => [\n category,\n index,\n ]),\n );\n const breakdown = [...response.documents.breakdown]\n .sort(\n (left, right) =>\n (order.get(left.category) ?? 0) - (order.get(right.category) ?? 0),\n )\n .map((entry) => ({\n byteLength: entry.byteLength,\n category: entry.category,\n documentCount: entry.documentCount,\n updateCount: entry.updateCount,\n }));\n return JSON.stringify(breakdown);\n}\n\nexport async function loadOrganizationDataUsageProjection(\n execSql: ExecSql,\n organizationId: string,\n requesterUserId: string,\n): Promise<OrganizationDataUsageResponse | null> {\n requireScope(organizationId, requesterUserId);\n await ensureSqlTables(execSql, organizationDataUsageTables);\n return getClientSQLitePersistenceRuntime(execSql).transaction(async (tx) => {\n const scope = and(\n eq(organizationDataUsageSnapshots.organizationId, organizationId),\n eq(organizationDataUsageSnapshots.requesterUserId, requesterUserId),\n );\n const [snapshot] = await tx\n .select()\n .from(organizationDataUsageSnapshots)\n .where(scope)\n .limit(1);\n if (!snapshot) {\n return null;\n }\n const response = isStoredSnapshotValid(\n snapshot,\n organizationId,\n requesterUserId,\n )\n ? storedResponse(snapshot)\n : null;\n if (!response) {\n await purgeOrganizationDataUsageProjectionInTransaction({\n organizationId,\n requesterUserId,\n tx,\n });\n }\n return response;\n });\n}\n\nexport async function saveOrganizationDataUsageProjection(input: {\n readonly execSql: ExecSql;\n readonly requesterUserId: string;\n readonly response: OrganizationDataUsageResponse;\n}): Promise<void> {\n const response = parseResponse(input.response);\n requireScope(response.organizationId, input.requesterUserId);\n await ensureSqlTables(input.execSql, [\n ...organizationDataUsageTables,\n ...organizationReadModelTables,\n ]);\n await getClientSQLitePersistenceRuntime(input.execSql).transaction(\n async (tx) => {\n // Storing an authoritative usage response proves access: retire any\n // durable denial marker for this scope in the same transaction.\n await clearOrganizationPresentationDenialInTransaction({\n organizationId: response.organizationId,\n projection: \"usage\",\n requesterUserId: input.requesterUserId,\n tx,\n });\n const snapshot = {\n organizationId: response.organizationId,\n requesterUserId: input.requesterUserId,\n projectionVersion: ORGANIZATION_DATA_USAGE_PROJECTION_VERSION,\n blobCount: response.blobs.blobCount,\n blobByteLength: response.blobs.byteLength,\n documentByteLength: response.documents.byteLength,\n documentCount: response.documents.documentCount,\n documentUpdateCount: response.documents.updateCount,\n totalByteLength: response.totalByteLength,\n categoryBreakdownJson: canonicalBreakdownJson(response),\n refreshedAt: new Date().toISOString(),\n };\n await tx\n .insert(organizationDataUsageSnapshots)\n .values(snapshot)\n .onConflictDoUpdate({\n target: [\n organizationDataUsageSnapshots.organizationId,\n organizationDataUsageSnapshots.requesterUserId,\n ],\n set: {\n projectionVersion: snapshot.projectionVersion,\n blobCount: snapshot.blobCount,\n blobByteLength: snapshot.blobByteLength,\n documentByteLength: snapshot.documentByteLength,\n documentCount: snapshot.documentCount,\n documentUpdateCount: snapshot.documentUpdateCount,\n totalByteLength: snapshot.totalByteLength,\n categoryBreakdownJson: snapshot.categoryBreakdownJson,\n refreshedAt: snapshot.refreshedAt,\n },\n })\n .run();\n },\n );\n}\n\nexport async function purgeOrganizationDataUsageProjection(\n execSql: ExecSql,\n organizationId: string,\n requesterUserId?: string,\n): Promise<void> {\n requireScope(organizationId, requesterUserId);\n await ensureSqlTables(execSql, organizationDataUsageTables);\n await getClientSQLitePersistenceRuntime(execSql).transaction(async (tx) => {\n await purgeOrganizationDataUsageProjectionInTransaction({\n organizationId,\n requesterUserId,\n tx,\n });\n });\n}\n"]}
@@ -1,5 +1,5 @@
1
1
  import { and, eq, ne, notExists } from "drizzle-orm";
2
- import { organizationDataUsageCategories, organizationDataUsageSnapshots, } from "../../sqlite/organizationDataUsageSchema.js";
2
+ import { organizationDataUsageSnapshots } from "../../sqlite/organizationDataUsageSchema.js";
3
3
  import { organizationReadModelContainerGrants, organizationReadModelDirectoryUsers, organizationReadModelGroupMembers, organizationReadModelGroups, organizationReadModelRequesters, organizationReadModelState, } from "../../sqlite/organizationReadModelSchema.js";
4
4
  import { organizationReadModelTables } from "../../sqlite/schema.js";
5
5
  import { getClientSQLitePersistenceRuntime, } from "../../sqlite/sqlitePersistenceRuntime.js";
@@ -36,10 +36,6 @@ export async function pruneInactiveOrganizationRequestersInTransaction(input) {
36
36
  .delete(organizationDataUsageSnapshots)
37
37
  .where(inactiveUserScope(input, organizationDataUsageSnapshots.organizationId, organizationDataUsageSnapshots.requesterUserId))
38
38
  .run();
39
- await input.tx
40
- .delete(organizationDataUsageCategories)
41
- .where(inactiveUserScope(input, organizationDataUsageCategories.organizationId, organizationDataUsageCategories.requesterUserId))
42
- .run();
43
39
  }
44
40
  /**
45
41
  * Delete the shared projection rows for one organization. By default every