@tearleads/client-sdk 0.1.44 → 0.1.46

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 (133) hide show
  1. package/client/Tearleads.js +3 -0
  2. package/client/Tearleads.js.map +1 -1
  3. package/client/userIdentities.d.ts +11 -1
  4. package/client/userIdentities.js +26 -8
  5. package/client/userIdentities.js.map +1 -1
  6. package/client/workflowRuntime.d.ts +2 -1
  7. package/client/workflowRuntime.js +1 -0
  8. package/client/workflowRuntime.js.map +1 -1
  9. package/data/persistence/container-contents/containerContentsPersistence.js +2 -3
  10. package/data/persistence/container-contents/containerContentsPersistence.js.map +1 -1
  11. package/data/persistence/container-contents/containerCreateIntentSettlement.js +16 -16
  12. package/data/persistence/container-contents/containerCreateIntentSettlement.js.map +1 -1
  13. package/data/persistence/container-contents/containerDeletionPersistence.js +5 -11
  14. package/data/persistence/container-contents/containerDeletionPersistence.js.map +1 -1
  15. package/data/persistence/container-contents/containerDocumentReassignment.js +13 -13
  16. package/data/persistence/container-contents/containerDocumentReassignment.js.map +1 -1
  17. package/data/persistence/container-contents/containerHydrationPersistence.js +7 -4
  18. package/data/persistence/container-contents/containerHydrationPersistence.js.map +1 -1
  19. package/data/persistence/container-contents/containerIntentPersistence.js +43 -42
  20. package/data/persistence/container-contents/containerIntentPersistence.js.map +1 -1
  21. package/data/persistence/container-contents/containerMetadataMutationPersistence.js +15 -20
  22. package/data/persistence/container-contents/containerMetadataMutationPersistence.js.map +1 -1
  23. package/data/persistence/container-contents/containerMoveAbandonPersistence.js +1 -6
  24. package/data/persistence/container-contents/containerMoveAbandonPersistence.js.map +1 -1
  25. package/data/persistence/container-contents/containerMoveIntentRows.js +9 -8
  26. package/data/persistence/container-contents/containerMoveIntentRows.js.map +1 -1
  27. package/data/persistence/container-contents/containerReconcilePersistence.js +4 -8
  28. package/data/persistence/container-contents/containerReconcilePersistence.js.map +1 -1
  29. package/data/persistence/container-contents/containerRecoveryPersistence.js +20 -19
  30. package/data/persistence/container-contents/containerRecoveryPersistence.js.map +1 -1
  31. package/data/persistence/container-contents/containerRemovalWork.js +10 -10
  32. package/data/persistence/container-contents/containerRemovalWork.js.map +1 -1
  33. package/data/persistence/container-contents/containerStructuralRepair.js +21 -44
  34. package/data/persistence/container-contents/containerStructuralRepair.js.map +1 -1
  35. package/data/persistence/container-contents/documentLinkRemovalRepair.js +12 -11
  36. package/data/persistence/container-contents/documentLinkRemovalRepair.js.map +1 -1
  37. package/data/persistence/container-contents/documentMoveIntentPersistence.js +53 -28
  38. package/data/persistence/container-contents/documentMoveIntentPersistence.js.map +1 -1
  39. package/data/persistence/container-contents/documentPlacementIntentEnqueue.js +9 -8
  40. package/data/persistence/container-contents/documentPlacementIntentEnqueue.js.map +1 -1
  41. package/data/persistence/container-contents/documentPurgePlacement.js +6 -6
  42. package/data/persistence/container-contents/documentPurgePlacement.js.map +1 -1
  43. package/data/persistence/container-contents/dormantContainerMetadata.js +1 -5
  44. package/data/persistence/container-contents/dormantContainerMetadata.js.map +1 -1
  45. package/data/persistence/container-contents/pendingContainerCreate.js +5 -5
  46. package/data/persistence/container-contents/pendingContainerCreate.js.map +1 -1
  47. package/data/persistence/containers/containerPersistence.js +10 -27
  48. package/data/persistence/containers/containerPersistence.js.map +1 -1
  49. package/data/persistence/containers/documentContainerProjectionPersistence.js +2 -2
  50. package/data/persistence/containers/documentContainerProjectionPersistence.js.map +1 -1
  51. package/data/persistence/containers/documentPlacement.js +10 -10
  52. package/data/persistence/containers/documentPlacement.js.map +1 -1
  53. package/data/persistence/documents/internal/discardDocument.js +5 -5
  54. package/data/persistence/documents/internal/discardDocument.js.map +1 -1
  55. package/data/persistence/documents/internal/documentDeletionPersistence.js +7 -9
  56. package/data/persistence/documents/internal/documentDeletionPersistence.js.map +1 -1
  57. package/data/persistence/documents/internal/documentMutationPersistence.js +6 -8
  58. package/data/persistence/documents/internal/documentMutationPersistence.js.map +1 -1
  59. package/data/persistence/documents/internal/ensureDocumentsSchema.js +2 -2
  60. package/data/persistence/documents/internal/ensureDocumentsSchema.js.map +1 -1
  61. package/data/persistence/documents/internal/orphanSideRows.js +3 -22
  62. package/data/persistence/documents/internal/orphanSideRows.js.map +1 -1
  63. package/data/persistence/organizations/organizationDataUsagePersistence.js +31 -64
  64. package/data/persistence/organizations/organizationDataUsagePersistence.js.map +1 -1
  65. package/data/persistence/organizations/organizationReadModelPurge.js +1 -5
  66. package/data/persistence/organizations/organizationReadModelPurge.js.map +1 -1
  67. package/data/persistence/principalCurrentAcknowledgementPersistence.js +24 -17
  68. package/data/persistence/principalCurrentAcknowledgementPersistence.js.map +1 -1
  69. package/data/persistence/principalHistoryIncompleteRetention.js +3 -9
  70. package/data/persistence/principalHistoryIncompleteRetention.js.map +1 -1
  71. package/data/persistence/principalHistoryStagePersistence.d.ts +3 -9
  72. package/data/persistence/principalHistoryStagePersistence.js +15 -25
  73. package/data/persistence/principalHistoryStagePersistence.js.map +1 -1
  74. package/data/persistence/principalHistoryStageRetention.js +16 -15
  75. package/data/persistence/principalHistoryStageRetention.js.map +1 -1
  76. package/data/persistence/principalHistoryUnpublishedRetention.js +6 -13
  77. package/data/persistence/principalHistoryUnpublishedRetention.js.map +1 -1
  78. package/data/sqlite/containerSchema.d.ts +84 -180
  79. package/data/sqlite/containerSchema.js +14 -23
  80. package/data/sqlite/containerSchema.js.map +1 -1
  81. package/data/sqlite/documentPlacementIntentSchema.js +3 -50
  82. package/data/sqlite/documentPlacementIntentSchema.js.map +1 -1
  83. package/data/sqlite/documentSyncFailurePersistence.d.ts +16 -6
  84. package/data/sqlite/documentSyncFailurePersistence.js +44 -54
  85. package/data/sqlite/documentSyncFailurePersistence.js.map +1 -1
  86. package/data/sqlite/{documentPlacementIntentSchema.d.ts → localIntentSchema.d.ts} +162 -88
  87. package/data/sqlite/localIntentSchema.js +90 -0
  88. package/data/sqlite/localIntentSchema.js.map +1 -0
  89. package/data/sqlite/organizationDataUsageSchema.js +11 -17
  90. package/data/sqlite/organizationDataUsageSchema.js.map +1 -1
  91. package/data/sqlite/principalHistoryRetentionSchema.js +0 -15
  92. package/data/sqlite/principalHistoryRetentionSchema.js.map +1 -1
  93. package/data/sqlite/principalHistoryStageSchema.d.ts +44 -2
  94. package/data/sqlite/principalHistoryStageSchema.js +14 -2
  95. package/data/sqlite/principalHistoryStageSchema.js.map +1 -1
  96. package/data/sqlite/schema.d.ts +947 -2112
  97. package/data/sqlite/schema.js +26 -134
  98. package/data/sqlite/schema.js.map +1 -1
  99. package/data/trustedUserIdentity/identityLink.d.ts +12 -0
  100. package/data/trustedUserIdentity/identityLink.js +117 -0
  101. package/data/trustedUserIdentity/identityLink.js.map +1 -0
  102. package/data/trustedUserIdentity/index.d.ts +2 -1
  103. package/data/trustedUserIdentity/index.js +1 -0
  104. package/data/trustedUserIdentity/index.js.map +1 -1
  105. package/data/trustedUserIdentity/service.js +48 -2
  106. package/data/trustedUserIdentity/service.js.map +1 -1
  107. package/data/trustedUserIdentity/types.d.ts +3 -0
  108. package/data/trustedUserIdentity/types.js.map +1 -1
  109. package/index.js +1 -1
  110. package/index.js.map +1 -1
  111. package/package.json +1 -1
  112. package/workflows/container-contents/documentPriming.js +6 -10
  113. package/workflows/container-contents/documentPriming.js.map +1 -1
  114. package/workflows/container-contents/documentQueries/sql.js +11 -10
  115. package/workflows/container-contents/documentQueries/sql.js.map +1 -1
  116. package/workflows/container-contents/documentQueries.js +2 -2
  117. package/workflows/container-contents/documentQueries.js.map +1 -1
  118. package/workflows/container-contents/pendingDownloads.js +2 -5
  119. package/workflows/container-contents/pendingDownloads.js.map +1 -1
  120. package/workflows/container-contents/pendingWrites/deferredTails.js +3 -11
  121. package/workflows/container-contents/pendingWrites/deferredTails.js.map +1 -1
  122. package/workflows/container-contents/pendingWrites/sources.js +56 -80
  123. package/workflows/container-contents/pendingWrites/sources.js.map +1 -1
  124. package/workflows/container-contents/pendingWrites.js +6 -4
  125. package/workflows/container-contents/pendingWrites.js.map +1 -1
  126. package/workflows/container-contents/remoteContainerState.js +2 -1
  127. package/workflows/container-contents/remoteContainerState.js.map +1 -1
  128. package/workflows/sync/remoteReset.js +16 -23
  129. package/workflows/sync/remoteReset.js.map +1 -1
  130. package/workflows/sync/remoteResetPrincipalScope.js +1 -2
  131. package/workflows/sync/remoteResetPrincipalScope.js.map +1 -1
  132. package/workflows/sync/remoteResetScope.js +15 -8
  133. package/workflows/sync/remoteResetScope.js.map +1 -1
@@ -1 +1 @@
1
- {"version":3,"file":"containerCreateIntentSettlement.js","sourceRoot":"","sources":["containerCreateIntentSettlement.ts"],"names":[],"mappings":"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","sourcesContent":["import { and, eq } from \"drizzle-orm\";\nimport {\n containerCreateIntents,\n containerMoveIntents,\n} from \"../../sqlite/schema\";\nimport type { ClientSQLiteTransactionScope } from \"../../sqlite/sqlitePersistenceRuntime\";\nimport {\n CONTAINER_CREATE_INTENT_TYPE,\n CONTAINER_MOVE_INTENT_TYPE,\n type ContainerMoveIntentInput,\n} from \"./containerContentsPersistenceTypes\";\nimport { saveContainerMoveIntent } from \"./containerMoveIntentRows\";\n\nasync function markContainerCreateIntentRevisionSynced(input: {\n containerId: string;\n expectedIntentId: string;\n expectedUpdatedAt: string;\n remoteContainerId: string;\n remoteMetadataAccessStateHash: string;\n remoteMetadataDocumentId: string;\n tx: ClientSQLiteTransactionScope;\n}): Promise<boolean> {\n const updated = await input.tx\n .update(containerCreateIntents)\n .set({\n syncStatus: \"synced\",\n remoteContainerId: input.remoteContainerId,\n remoteMetadataDocumentId: input.remoteMetadataDocumentId,\n remoteMetadataAccessStateHash: input.remoteMetadataAccessStateHash,\n lastError: null,\n updatedAt: new Date().toISOString(),\n })\n .where(\n and(\n eq(containerCreateIntents.containerId, input.containerId),\n eq(containerCreateIntents.intentType, CONTAINER_CREATE_INTENT_TYPE),\n eq(containerCreateIntents.syncStatus, \"pending\"),\n eq(containerCreateIntents.id, input.expectedIntentId),\n eq(containerCreateIntents.updatedAt, input.expectedUpdatedAt),\n ),\n )\n .returning({ containerId: containerCreateIntents.containerId });\n return updated.length > 0;\n}\n\n/**\n * The move an adopted create still owes: to the desired parent, citing where\n * the folder is now. A desired parent equal to the created one means the user\n * never moved it away, so a later remote move by another writer stands.\n */\nfunction owedCreateMove(input: {\n readonly createdParentContainerId?: string | undefined;\n readonly desiredParentContainerId: string;\n readonly supersededMovePreviousParentId: string;\n}): ContainerMoveIntentInput | null {\n const desired = input.desiredParentContainerId;\n if (\n desired === input.supersededMovePreviousParentId ||\n desired === input.createdParentContainerId\n ) {\n return null;\n }\n return {\n parentContainerId: desired,\n previousParentContainerId: input.supersededMovePreviousParentId,\n };\n}\n\n/**\n * A local move of a folder the listing already carried queues its own move\n * intent, which is newer than the create's parent: it keeps its destination,\n * and now cites where the folder sits remotely rather than the parent it was\n * queued from, which never committed. One whose destination is already that\n * parent is done and is dropped.\n */\nasync function rebaseQueuedMove(input: {\n readonly containerId: string;\n readonly supersededMovePreviousParentId: string;\n readonly tx: ClientSQLiteTransactionScope;\n}): Promise<\"converted-to-move\" | \"none\" | \"synced\"> {\n const queued = and(\n eq(containerMoveIntents.containerId, input.containerId),\n eq(containerMoveIntents.intentType, CONTAINER_MOVE_INTENT_TYPE),\n );\n const arrived = await input.tx\n .delete(containerMoveIntents)\n .where(\n and(\n queued,\n eq(\n containerMoveIntents.parentContainerId,\n input.supersededMovePreviousParentId,\n ),\n ),\n )\n .returning({ containerId: containerMoveIntents.containerId });\n if (arrived.length > 0) return \"synced\";\n const rebased = await input.tx\n .update(containerMoveIntents)\n .set({ previousParentContainerId: input.supersededMovePreviousParentId })\n .where(queued)\n .returning({ containerId: containerMoveIntents.containerId });\n return rebased.length > 0 ? \"converted-to-move\" : \"none\";\n}\n\nasync function queueOwedCreateMove(input: {\n readonly containerId: string;\n readonly createdParentContainerId?: string | undefined;\n readonly desiredParentContainerId: string;\n readonly supersededMovePreviousParentId: string;\n readonly tx: ClientSQLiteTransactionScope;\n}): Promise<\"converted-to-move\" | \"synced\"> {\n const queued = await rebaseQueuedMove(input);\n if (queued !== \"none\") return queued;\n const moveIntent = owedCreateMove(input);\n if (!moveIntent) return \"synced\";\n await saveContainerMoveIntent({\n containerId: input.containerId,\n moveIntent,\n tx: input.tx,\n updatedAt: new Date().toISOString(),\n });\n return \"converted-to-move\";\n}\n\nexport async function settleContainerCreateIntentRevision(input: {\n containerId: string;\n expectedIntentId: string;\n expectedUpdatedAt: string;\n remoteContainerId: string;\n remoteMetadataAccessStateHash: string;\n remoteMetadataDocumentId: string;\n supersededMovePreviousParentId?: string | undefined;\n createdParentContainerId?: string | undefined;\n desiredParentContainerId?: string | undefined;\n tx: ClientSQLiteTransactionScope;\n}): Promise<\"converted-to-move\" | \"superseded\" | \"synced\"> {\n const previousParentId = input.supersededMovePreviousParentId;\n if (await markContainerCreateIntentRevisionSynced(input)) {\n // An adopted create may still owe a move: the user can move the folder\n // after its create committed.\n if (\n previousParentId === undefined ||\n input.desiredParentContainerId === undefined\n ) {\n return \"synced\";\n }\n return queueOwedCreateMove({\n ...input,\n desiredParentContainerId: input.desiredParentContainerId,\n supersededMovePreviousParentId: previousParentId,\n });\n }\n if (previousParentId === undefined) {\n return \"superseded\";\n }\n\n const [currentIntent] = await input.tx\n .select({\n id: containerCreateIntents.id,\n parentContainerId: containerCreateIntents.parentContainerId,\n updatedAt: containerCreateIntents.updatedAt,\n })\n .from(containerCreateIntents)\n .where(\n and(\n eq(containerCreateIntents.containerId, input.containerId),\n eq(containerCreateIntents.intentType, CONTAINER_CREATE_INTENT_TYPE),\n eq(containerCreateIntents.syncStatus, \"pending\"),\n ),\n )\n .limit(1);\n if (!currentIntent?.id) return \"superseded\";\n\n const adopted = await markContainerCreateIntentRevisionSynced({\n ...input,\n expectedIntentId: currentIntent.id,\n expectedUpdatedAt: currentIntent.updatedAt,\n });\n if (!adopted) return \"superseded\";\n return queueOwedCreateMove({\n ...input,\n desiredParentContainerId: currentIntent.parentContainerId,\n supersededMovePreviousParentId: previousParentId,\n });\n}\n"]}
1
+ {"version":3,"file":"containerCreateIntentSettlement.js","sourceRoot":"","sources":["containerCreateIntentSettlement.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,GAAG,EAAE,EAAE,EAAE,MAAM,aAAa,CAAC;AACtC,OAAO,EACL,iBAAiB,EACjB,kBAAkB,EAClB,YAAY,GACb,MAAM,qBAAqB,CAAC;AAE7B,OAAO,EACL,4BAA4B,EAC5B,0BAA0B,GAE3B,MAAM,qCAAqC,CAAC;AAC7C,OAAO,EAAE,uBAAuB,EAAE,MAAM,2BAA2B,CAAC;AAEpE,KAAK,UAAU,uCAAuC,CAAC,KAQtD;IACC,MAAM,OAAO,GAAG,MAAM,KAAK,CAAC,EAAE;SAC3B,MAAM,CAAC,YAAY,CAAC;SACpB,GAAG,CAAC;QACH,UAAU,EAAE,QAAQ;QACpB,iBAAiB,EAAE,KAAK,CAAC,iBAAiB;QAC1C,wBAAwB,EAAE,KAAK,CAAC,wBAAwB;QACxD,6BAA6B,EAAE,KAAK,CAAC,6BAA6B;QAClE,SAAS,EAAE,IAAI;QACf,SAAS,EAAE,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE;KACpC,CAAC;SACD,KAAK,CACJ,GAAG,CACD,iBAAiB,CAAC,kBAAkB,CAAC,eAAe,CAAC,EACrD,EAAE,CAAC,YAAY,CAAC,SAAS,EAAE,KAAK,CAAC,WAAW,CAAC,EAC7C,EAAE,CAAC,YAAY,CAAC,UAAU,EAAE,4BAA4B,CAAC,EACzD,EAAE,CAAC,YAAY,CAAC,UAAU,EAAE,SAAS,CAAC,EACtC,EAAE,CAAC,YAAY,CAAC,EAAE,EAAE,KAAK,CAAC,gBAAgB,CAAC,EAC3C,EAAE,CAAC,YAAY,CAAC,SAAS,EAAE,KAAK,CAAC,iBAAiB,CAAC,CACpD,CACF;SACA,SAAS,CAAC,EAAE,WAAW,EAAE,YAAY,CAAC,SAAS,EAAE,CAAC,CAAC;IACtD,OAAO,OAAO,CAAC,MAAM,GAAG,CAAC,CAAC;AAC5B,CAAC;AAED;;;;GAIG;AACH,SAAS,cAAc,CAAC,KAIvB;IACC,MAAM,OAAO,GAAG,KAAK,CAAC,wBAAwB,CAAC;IAC/C,IACE,OAAO,KAAK,KAAK,CAAC,8BAA8B;QAChD,OAAO,KAAK,KAAK,CAAC,wBAAwB,EAC1C,CAAC;QACD,OAAO,IAAI,CAAC;IACd,CAAC;IACD,OAAO;QACL,iBAAiB,EAAE,OAAO;QAC1B,yBAAyB,EAAE,KAAK,CAAC,8BAA8B;KAChE,CAAC;AACJ,CAAC;AAED;;;;;;GAMG;AACH,KAAK,UAAU,gBAAgB,CAAC,KAI/B;IACC,MAAM,MAAM,GAAG,GAAG,CAChB,iBAAiB,CAAC,kBAAkB,CAAC,aAAa,CAAC,EACnD,EAAE,CAAC,YAAY,CAAC,SAAS,EAAE,KAAK,CAAC,WAAW,CAAC,EAC7C,EAAE,CAAC,YAAY,CAAC,UAAU,EAAE,0BAA0B,CAAC,CACxD,CAAC;IACF,MAAM,OAAO,GAAG,MAAM,KAAK,CAAC,EAAE;SAC3B,MAAM,CAAC,YAAY,CAAC;SACpB,KAAK,CACJ,GAAG,CACD,MAAM,EACN,EAAE,CACA,YAAY,CAAC,iBAAiB,EAC9B,KAAK,CAAC,8BAA8B,CACrC,CACF,CACF;SACA,SAAS,CAAC,EAAE,WAAW,EAAE,YAAY,CAAC,SAAS,EAAE,CAAC,CAAC;IACtD,IAAI,OAAO,CAAC,MAAM,GAAG,CAAC;QAAE,OAAO,QAAQ,CAAC;IACxC,MAAM,OAAO,GAAG,MAAM,KAAK,CAAC,EAAE;SAC3B,MAAM,CAAC,YAAY,CAAC;SACpB,GAAG,CAAC,EAAE,iBAAiB,EAAE,KAAK,CAAC,8BAA8B,EAAE,CAAC;SAChE,KAAK,CAAC,MAAM,CAAC;SACb,SAAS,CAAC,EAAE,WAAW,EAAE,YAAY,CAAC,SAAS,EAAE,CAAC,CAAC;IACtD,OAAO,OAAO,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,mBAAmB,CAAC,CAAC,CAAC,MAAM,CAAC;AAC3D,CAAC;AAED,KAAK,UAAU,mBAAmB,CAAC,KAMlC;IACC,MAAM,MAAM,GAAG,MAAM,gBAAgB,CAAC,KAAK,CAAC,CAAC;IAC7C,IAAI,MAAM,KAAK,MAAM;QAAE,OAAO,MAAM,CAAC;IACrC,MAAM,UAAU,GAAG,cAAc,CAAC,KAAK,CAAC,CAAC;IACzC,IAAI,CAAC,UAAU;QAAE,OAAO,QAAQ,CAAC;IACjC,MAAM,uBAAuB,CAAC;QAC5B,WAAW,EAAE,KAAK,CAAC,WAAW;QAC9B,UAAU;QACV,EAAE,EAAE,KAAK,CAAC,EAAE;QACZ,SAAS,EAAE,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE;KACpC,CAAC,CAAC;IACH,OAAO,mBAAmB,CAAC;AAC7B,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,mCAAmC,CAAC,KAWzD;IACC,MAAM,gBAAgB,GAAG,KAAK,CAAC,8BAA8B,CAAC;IAC9D,IAAI,MAAM,uCAAuC,CAAC,KAAK,CAAC,EAAE,CAAC;QACzD,uEAAuE;QACvE,8BAA8B;QAC9B,IACE,gBAAgB,KAAK,SAAS;YAC9B,KAAK,CAAC,wBAAwB,KAAK,SAAS,EAC5C,CAAC;YACD,OAAO,QAAQ,CAAC;QAClB,CAAC;QACD,OAAO,mBAAmB,CAAC;YACzB,GAAG,KAAK;YACR,wBAAwB,EAAE,KAAK,CAAC,wBAAwB;YACxD,8BAA8B,EAAE,gBAAgB;SACjD,CAAC,CAAC;IACL,CAAC;IACD,IAAI,gBAAgB,KAAK,SAAS,EAAE,CAAC;QACnC,OAAO,YAAY,CAAC;IACtB,CAAC;IAED,MAAM,CAAC,aAAa,CAAC,GAAG,MAAM,KAAK,CAAC,EAAE;SACnC,MAAM,CAAC;QACN,EAAE,EAAE,YAAY,CAAC,EAAE;QACnB,iBAAiB,EAAE,YAAY,CAAC,iBAAiB;QACjD,SAAS,EAAE,YAAY,CAAC,SAAS;KAClC,CAAC;SACD,IAAI,CAAC,YAAY,CAAC;SAClB,KAAK,CACJ,GAAG,CACD,iBAAiB,CAAC,kBAAkB,CAAC,eAAe,CAAC,EACrD,EAAE,CAAC,YAAY,CAAC,SAAS,EAAE,KAAK,CAAC,WAAW,CAAC,EAC7C,EAAE,CAAC,YAAY,CAAC,UAAU,EAAE,4BAA4B,CAAC,EACzD,EAAE,CAAC,YAAY,CAAC,UAAU,EAAE,SAAS,CAAC,CACvC,CACF;SACA,KAAK,CAAC,CAAC,CAAC,CAAC;IACZ,IAAI,CAAC,aAAa,EAAE,EAAE;QAAE,OAAO,YAAY,CAAC;IAE5C,MAAM,OAAO,GAAG,MAAM,uCAAuC,CAAC;QAC5D,GAAG,KAAK;QACR,gBAAgB,EAAE,aAAa,CAAC,EAAE;QAClC,iBAAiB,EAAE,aAAa,CAAC,SAAS;KAC3C,CAAC,CAAC;IACH,IAAI,CAAC,OAAO;QAAE,OAAO,YAAY,CAAC;IAClC,OAAO,mBAAmB,CAAC;QACzB,GAAG,KAAK;QACR,wBAAwB,EAAE,aAAa,CAAC,iBAAiB;QACzD,8BAA8B,EAAE,gBAAgB;KACjD,CAAC,CAAC;AACL,CAAC","sourcesContent":["import { and, eq } from \"drizzle-orm\";\nimport {\n inLocalIntentLane,\n LOCAL_INTENT_LANES,\n localIntents,\n} from \"../../sqlite/schema\";\nimport type { ClientSQLiteTransactionScope } from \"../../sqlite/sqlitePersistenceRuntime\";\nimport {\n CONTAINER_CREATE_INTENT_TYPE,\n CONTAINER_MOVE_INTENT_TYPE,\n type ContainerMoveIntentInput,\n} from \"./containerContentsPersistenceTypes\";\nimport { saveContainerMoveIntent } from \"./containerMoveIntentRows\";\n\nasync function markContainerCreateIntentRevisionSynced(input: {\n containerId: string;\n expectedIntentId: string;\n expectedUpdatedAt: string;\n remoteContainerId: string;\n remoteMetadataAccessStateHash: string;\n remoteMetadataDocumentId: string;\n tx: ClientSQLiteTransactionScope;\n}): Promise<boolean> {\n const updated = await input.tx\n .update(localIntents)\n .set({\n syncStatus: \"synced\",\n remoteContainerId: input.remoteContainerId,\n remoteMetadataDocumentId: input.remoteMetadataDocumentId,\n remoteMetadataAccessStateHash: input.remoteMetadataAccessStateHash,\n lastError: null,\n updatedAt: new Date().toISOString(),\n })\n .where(\n and(\n inLocalIntentLane(LOCAL_INTENT_LANES.containerCreate),\n eq(localIntents.subjectId, input.containerId),\n eq(localIntents.intentType, CONTAINER_CREATE_INTENT_TYPE),\n eq(localIntents.syncStatus, \"pending\"),\n eq(localIntents.id, input.expectedIntentId),\n eq(localIntents.updatedAt, input.expectedUpdatedAt),\n ),\n )\n .returning({ containerId: localIntents.subjectId });\n return updated.length > 0;\n}\n\n/**\n * The move an adopted create still owes: to the desired parent, citing where\n * the folder is now. A desired parent equal to the created one means the user\n * never moved it away, so a later remote move by another writer stands.\n */\nfunction owedCreateMove(input: {\n readonly createdParentContainerId?: string | undefined;\n readonly desiredParentContainerId: string;\n readonly supersededMovePreviousParentId: string;\n}): ContainerMoveIntentInput | null {\n const desired = input.desiredParentContainerId;\n if (\n desired === input.supersededMovePreviousParentId ||\n desired === input.createdParentContainerId\n ) {\n return null;\n }\n return {\n parentContainerId: desired,\n previousParentContainerId: input.supersededMovePreviousParentId,\n };\n}\n\n/**\n * A local move of a folder the listing already carried queues its own move\n * intent, which is newer than the create's parent: it keeps its destination,\n * and now cites where the folder sits remotely rather than the parent it was\n * queued from, which never committed. One whose destination is already that\n * parent is done and is dropped.\n */\nasync function rebaseQueuedMove(input: {\n readonly containerId: string;\n readonly supersededMovePreviousParentId: string;\n readonly tx: ClientSQLiteTransactionScope;\n}): Promise<\"converted-to-move\" | \"none\" | \"synced\"> {\n const queued = and(\n inLocalIntentLane(LOCAL_INTENT_LANES.containerMove),\n eq(localIntents.subjectId, input.containerId),\n eq(localIntents.intentType, CONTAINER_MOVE_INTENT_TYPE),\n );\n const arrived = await input.tx\n .delete(localIntents)\n .where(\n and(\n queued,\n eq(\n localIntents.targetContainerId,\n input.supersededMovePreviousParentId,\n ),\n ),\n )\n .returning({ containerId: localIntents.subjectId });\n if (arrived.length > 0) return \"synced\";\n const rebased = await input.tx\n .update(localIntents)\n .set({ sourceContainerId: input.supersededMovePreviousParentId })\n .where(queued)\n .returning({ containerId: localIntents.subjectId });\n return rebased.length > 0 ? \"converted-to-move\" : \"none\";\n}\n\nasync function queueOwedCreateMove(input: {\n readonly containerId: string;\n readonly createdParentContainerId?: string | undefined;\n readonly desiredParentContainerId: string;\n readonly supersededMovePreviousParentId: string;\n readonly tx: ClientSQLiteTransactionScope;\n}): Promise<\"converted-to-move\" | \"synced\"> {\n const queued = await rebaseQueuedMove(input);\n if (queued !== \"none\") return queued;\n const moveIntent = owedCreateMove(input);\n if (!moveIntent) return \"synced\";\n await saveContainerMoveIntent({\n containerId: input.containerId,\n moveIntent,\n tx: input.tx,\n updatedAt: new Date().toISOString(),\n });\n return \"converted-to-move\";\n}\n\nexport async function settleContainerCreateIntentRevision(input: {\n containerId: string;\n expectedIntentId: string;\n expectedUpdatedAt: string;\n remoteContainerId: string;\n remoteMetadataAccessStateHash: string;\n remoteMetadataDocumentId: string;\n supersededMovePreviousParentId?: string | undefined;\n createdParentContainerId?: string | undefined;\n desiredParentContainerId?: string | undefined;\n tx: ClientSQLiteTransactionScope;\n}): Promise<\"converted-to-move\" | \"superseded\" | \"synced\"> {\n const previousParentId = input.supersededMovePreviousParentId;\n if (await markContainerCreateIntentRevisionSynced(input)) {\n // An adopted create may still owe a move: the user can move the folder\n // after its create committed.\n if (\n previousParentId === undefined ||\n input.desiredParentContainerId === undefined\n ) {\n return \"synced\";\n }\n return queueOwedCreateMove({\n ...input,\n desiredParentContainerId: input.desiredParentContainerId,\n supersededMovePreviousParentId: previousParentId,\n });\n }\n if (previousParentId === undefined) {\n return \"superseded\";\n }\n\n const [currentIntent] = await input.tx\n .select({\n id: localIntents.id,\n parentContainerId: localIntents.targetContainerId,\n updatedAt: localIntents.updatedAt,\n })\n .from(localIntents)\n .where(\n and(\n inLocalIntentLane(LOCAL_INTENT_LANES.containerCreate),\n eq(localIntents.subjectId, input.containerId),\n eq(localIntents.intentType, CONTAINER_CREATE_INTENT_TYPE),\n eq(localIntents.syncStatus, \"pending\"),\n ),\n )\n .limit(1);\n if (!currentIntent?.id) return \"superseded\";\n\n const adopted = await markContainerCreateIntentRevisionSynced({\n ...input,\n expectedIntentId: currentIntent.id,\n expectedUpdatedAt: currentIntent.updatedAt,\n });\n if (!adopted) return \"superseded\";\n return queueOwedCreateMove({\n ...input,\n desiredParentContainerId: currentIntent.parentContainerId,\n supersededMovePreviousParentId: previousParentId,\n });\n}\n"]}
@@ -1,6 +1,6 @@
1
- import { inArray } from "drizzle-orm";
1
+ import { and, inArray } from "drizzle-orm";
2
2
  import { ensureDocumentProjectionTables } from "../../sqlite/documentPersistence.js";
3
- import { containerCreateIntents, containerCreateIntentTables, containerMoveIntents, containerMoveIntentTables, containers, documentContainerProjectionTables, documentMoveIntentTables, } from "../../sqlite/schema.js";
3
+ import { containers, documentContainerProjectionTables, documentPlacementIntentTables, inContainerIntentLanes, localIntents, } from "../../sqlite/schema.js";
4
4
  import { getClientSQLitePersistenceRuntime, } from "../../sqlite/sqlitePersistenceRuntime.js";
5
5
  import { ensureSqlTables, runSerializedSqlMutation, } from "../../sqlite/sqlSchema.js";
6
6
  import { deleteContainerRowsInTransaction, ensureContainerTables, } from "../containers/containerPersistence.js";
@@ -111,12 +111,8 @@ async function applyContainerRemovals(input) {
111
111
  else {
112
112
  await repairLinkIntentsForRemovedContainers({ containerIds, tx: input.tx });
113
113
  await input.tx
114
- .delete(containerCreateIntents)
115
- .where(inArray(containerCreateIntents.containerId, containerIds))
116
- .run();
117
- await input.tx
118
- .delete(containerMoveIntents)
119
- .where(inArray(containerMoveIntents.containerId, containerIds))
114
+ .delete(localIntents)
115
+ .where(and(inContainerIntentLanes(), inArray(localIntents.subjectId, containerIds)))
120
116
  .run();
121
117
  }
122
118
  await deleteContainerRowsInTransaction(input.tx, containerIds);
@@ -130,10 +126,8 @@ export async function deleteStoredContainers(execSql, removals, options) {
130
126
  return [];
131
127
  return runSerializedSqlMutation(execSql, async (lockedExecSql) => {
132
128
  await ensureSqlTables(lockedExecSql, documentContainerProjectionTables);
133
- await ensureSqlTables(lockedExecSql, documentMoveIntentTables);
129
+ await ensureSqlTables(lockedExecSql, documentPlacementIntentTables);
134
130
  await ensureContainerTables(lockedExecSql);
135
- await ensureSqlTables(lockedExecSql, containerCreateIntentTables);
136
- await ensureSqlTables(lockedExecSql, containerMoveIntentTables);
137
131
  await ensureDocumentProjectionTables(lockedExecSql);
138
132
  await sqlContainerSyncWatermarkPersistence.ensureSchema(lockedExecSql);
139
133
  const runtime = getClientSQLitePersistenceRuntime(lockedExecSql);
@@ -1 +1 @@
1
- {"version":3,"file":"containerDeletionPersistence.js","sourceRoot":"","sources":["containerDeletionPersistence.ts"],"names":[],"mappings":"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","sourcesContent":["import { inArray } from \"drizzle-orm\";\nimport { ensureDocumentProjectionTables } from \"../../sqlite/documentPersistence\";\nimport {\n containerCreateIntents,\n containerCreateIntentTables,\n containerMoveIntents,\n containerMoveIntentTables,\n containers,\n documentContainerProjectionTables,\n documentMoveIntentTables,\n} from \"../../sqlite/schema\";\nimport {\n type ClientSQLiteTransactionScope,\n getClientSQLitePersistenceRuntime,\n} from \"../../sqlite/sqlitePersistenceRuntime\";\nimport {\n ensureSqlTables,\n runSerializedSqlMutation,\n} from \"../../sqlite/sqlSchema\";\nimport {\n deleteContainerRowsInTransaction,\n ensureContainerTables,\n} from \"../containers/containerPersistence\";\nimport {\n deleteContainerWatermarksInTransaction,\n sqlContainerSyncWatermarkPersistence,\n} from \"../containers/containerSyncWatermarkPersistence\";\nimport type {\n ContainerContentsPersistence,\n ContainerDeletionGuard,\n ContainerRemoval,\n} from \"./containerContentsPersistenceTypes\";\nimport { recordContainerHydrationTombstones } from \"./containerHydrationPersistence\";\nimport { containerIdsWithRemovalWork } from \"./containerRemovalWork\";\nimport { repairDocumentsForRemovedContainersInTransaction } from \"./containerStructuralRepair\";\nimport {\n refreshPlacementIntentsAfterRemoval,\n repairLinkIntentsForRemovedContainers,\n} from \"./documentLinkRemovalRepair\";\nimport {\n deleteContainerMetadataDocumentRowsInTransaction,\n retainDormantContainerMetadataInTransaction,\n} from \"./dormantContainerMetadata\";\n\ntype DeleteContainerOptions = NonNullable<\n Parameters<ContainerContentsPersistence[\"deleteContainers\"]>[2]\n>;\n\nfunction normalizeContainerRemovals(\n removals: ReadonlyArray<ContainerRemoval>,\n): ContainerRemoval[] {\n const removalByContainerId = new Map<string, ContainerRemoval>();\n for (const removal of removals) {\n const current = removalByContainerId.get(removal.containerId);\n if (\n !current ||\n (current.reason === \"access_revoked\" && removal.reason === \"deleted\") ||\n (current.reason === removal.reason &&\n current.updatedAt < removal.updatedAt)\n ) {\n removalByContainerId.set(removal.containerId, removal);\n }\n }\n return Array.from(removalByContainerId.values());\n}\n\nfunction loadGuardedContainerRows(\n tx: ClientSQLiteTransactionScope,\n containerIds: ReadonlyArray<string>,\n) {\n return tx\n .select({\n effectiveAccessLevel: containers.effectiveAccessLevel,\n id: containers.id,\n metadataDocumentId: containers.metadataDocumentId,\n organizationId: containers.organizationId,\n parentId: containers.parentId,\n serverCreatedAt: containers.serverCreatedAt,\n serverUpdatedAt: containers.serverUpdatedAt,\n systemSlot: containers.systemSlot,\n })\n .from(containers)\n .where(inArray(containers.id, [...containerIds]));\n}\n\ntype GuardedContainerRow = Awaited<\n ReturnType<typeof loadGuardedContainerRows>\n>[number];\n\nfunction sameGuardedContainerRow(\n row: GuardedContainerRow | undefined,\n guard: ContainerDeletionGuard,\n): boolean {\n const expected = guard.expectedContainer;\n if (!row || !expected) return row === undefined && expected === null;\n return (\n row.id === expected.id &&\n row.organizationId === expected.organizationId &&\n row.parentId === expected.parentId &&\n row.metadataDocumentId === expected.metadataDocumentId &&\n row.systemSlot === (expected.systemSlot ?? null) &&\n row.effectiveAccessLevel === (expected.effectiveAccessLevel ?? \"read\") &&\n row.serverCreatedAt === (expected.serverCreatedAt ?? null) &&\n row.serverUpdatedAt === (expected.serverUpdatedAt ?? null)\n );\n}\n\nasync function canApplyContainerRemovals(input: {\n expectedContainers: DeleteContainerOptions[\"expectedContainers\"];\n removals: ReadonlyArray<ContainerRemoval>;\n tx: ClientSQLiteTransactionScope;\n}): Promise<boolean> {\n const currentRows = await loadGuardedContainerRows(\n input.tx,\n input.removals.map((removal) => removal.containerId),\n );\n const currentRowById = new Map(\n currentRows.flatMap((row) => (row.id ? ([[row.id, row]] as const) : [])),\n );\n const guardById = new Map(\n input.expectedContainers?.map((guard) => [guard.containerId, guard]) ?? [],\n );\n return input.removals.every((removal) => {\n const currentRow = currentRowById.get(removal.containerId);\n const guard = guardById.get(removal.containerId);\n return (\n (!guard || sameGuardedContainerRow(currentRow, guard)) &&\n (currentRow?.serverUpdatedAt == null ||\n currentRow.serverUpdatedAt <= removal.updatedAt)\n );\n });\n}\n\nasync function retainRemovedContainerMetadata(input: {\n containerIds: ReadonlyArray<string>;\n retainedAt: string;\n tx: ClientSQLiteTransactionScope;\n}): Promise<void> {\n if (input.containerIds.length === 0) return;\n const retainedContainers = await input.tx\n .select({\n containerId: containers.id,\n organizationId: containers.organizationId,\n })\n .from(containers)\n .where(inArray(containers.id, [...input.containerIds]));\n await retainDormantContainerMetadataInTransaction(\n input.tx,\n retainedContainers.flatMap((container) =>\n container.containerId\n ? [\n {\n containerId: container.containerId,\n organizationId: container.organizationId,\n retainedAt: input.retainedAt,\n },\n ]\n : [],\n ),\n );\n}\n\nasync function applyContainerRemovals(input: {\n discoveryOnly: DeleteContainerOptions[\"discoveryOnly\"];\n metadataDeleteIds: ReadonlyArray<string>;\n removals: ReadonlyArray<ContainerRemoval>;\n retainedMetadataIds: ReadonlyArray<string>;\n tx: ClientSQLiteTransactionScope;\n}): Promise<ReadonlyArray<string>> {\n const containerIds = input.removals.map((removal) => removal.containerId);\n await recordContainerHydrationTombstones({\n removals: input.removals,\n tx: input.tx,\n });\n await retainRemovedContainerMetadata({\n containerIds: input.retainedMetadataIds,\n retainedAt: new Date().toISOString(),\n tx: input.tx,\n });\n // Listing clocks are untrusted; they cannot advance local projection clocks.\n const localObservedAt = new Date().toISOString();\n await repairDocumentsForRemovedContainersInTransaction({\n removals: input.discoveryOnly\n ? input.removals.map((removal) => ({\n ...removal,\n updatedAt: localObservedAt,\n }))\n : input.removals,\n tx: input.tx,\n });\n if (input.discoveryOnly) {\n await refreshPlacementIntentsAfterRemoval({ containerIds, tx: input.tx });\n } else {\n await repairLinkIntentsForRemovedContainers({ containerIds, tx: input.tx });\n await input.tx\n .delete(containerCreateIntents)\n .where(inArray(containerCreateIntents.containerId, containerIds))\n .run();\n await input.tx\n .delete(containerMoveIntents)\n .where(inArray(containerMoveIntents.containerId, containerIds))\n .run();\n }\n await deleteContainerRowsInTransaction(input.tx, containerIds);\n await deleteContainerMetadataDocumentRowsInTransaction(\n input.tx,\n input.metadataDeleteIds,\n );\n await deleteContainerWatermarksInTransaction(input.tx, containerIds);\n return containerIds;\n}\n\nexport async function deleteStoredContainers(\n execSql: Parameters<ContainerContentsPersistence[\"deleteContainers\"]>[0],\n removals: Parameters<ContainerContentsPersistence[\"deleteContainers\"]>[1],\n options?: Parameters<ContainerContentsPersistence[\"deleteContainers\"]>[2],\n): Promise<ReadonlyArray<string>> {\n const uniqueRemovals = normalizeContainerRemovals(removals);\n if (uniqueRemovals.length === 0) return [];\n return runSerializedSqlMutation(execSql, async (lockedExecSql) => {\n await ensureSqlTables(lockedExecSql, documentContainerProjectionTables);\n await ensureSqlTables(lockedExecSql, documentMoveIntentTables);\n await ensureContainerTables(lockedExecSql);\n await ensureSqlTables(lockedExecSql, containerCreateIntentTables);\n await ensureSqlTables(lockedExecSql, containerMoveIntentTables);\n await ensureDocumentProjectionTables(lockedExecSql);\n await sqlContainerSyncWatermarkPersistence.ensureSchema(lockedExecSql);\n const runtime = getClientSQLitePersistenceRuntime(lockedExecSql);\n const remove = async (tx: ClientSQLiteTransactionScope) => {\n if (\n options?.beforeDeleteInTransaction &&\n !(await options.beforeDeleteInTransaction(lockedExecSql))\n )\n return [];\n if (\n !(await canApplyContainerRemovals({\n expectedContainers: options?.expectedContainers,\n removals: uniqueRemovals,\n tx,\n }))\n ) {\n return [];\n }\n const retainedMetadataIds = options?.discoveryOnly\n ? await containerIdsWithRemovalWork(\n tx,\n uniqueRemovals.map((removal) => removal.containerId),\n )\n : [];\n return applyContainerRemovals({\n discoveryOnly: options?.discoveryOnly,\n metadataDeleteIds: uniqueRemovals.flatMap((removal) =>\n retainedMetadataIds.includes(removal.containerId)\n ? []\n : [removal.containerId],\n ),\n removals: uniqueRemovals,\n retainedMetadataIds,\n tx,\n });\n };\n if (!options?.stillCurrent) {\n return runtime.transaction(remove, { behavior: \"immediate\" });\n }\n const outcome = await runtime.guardedTransaction(\n remove,\n options.stillCurrent,\n { behavior: \"immediate\" },\n );\n return outcome.result ?? [];\n });\n}\n"]}
1
+ {"version":3,"file":"containerDeletionPersistence.js","sourceRoot":"","sources":["containerDeletionPersistence.ts"],"names":[],"mappings":"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","sourcesContent":["import { and, inArray } from \"drizzle-orm\";\nimport { ensureDocumentProjectionTables } from \"../../sqlite/documentPersistence\";\nimport {\n containers,\n documentContainerProjectionTables,\n documentPlacementIntentTables,\n inContainerIntentLanes,\n localIntents,\n} from \"../../sqlite/schema\";\nimport {\n type ClientSQLiteTransactionScope,\n getClientSQLitePersistenceRuntime,\n} from \"../../sqlite/sqlitePersistenceRuntime\";\nimport {\n ensureSqlTables,\n runSerializedSqlMutation,\n} from \"../../sqlite/sqlSchema\";\nimport {\n deleteContainerRowsInTransaction,\n ensureContainerTables,\n} from \"../containers/containerPersistence\";\nimport {\n deleteContainerWatermarksInTransaction,\n sqlContainerSyncWatermarkPersistence,\n} from \"../containers/containerSyncWatermarkPersistence\";\nimport type {\n ContainerContentsPersistence,\n ContainerDeletionGuard,\n ContainerRemoval,\n} from \"./containerContentsPersistenceTypes\";\nimport { recordContainerHydrationTombstones } from \"./containerHydrationPersistence\";\nimport { containerIdsWithRemovalWork } from \"./containerRemovalWork\";\nimport { repairDocumentsForRemovedContainersInTransaction } from \"./containerStructuralRepair\";\nimport {\n refreshPlacementIntentsAfterRemoval,\n repairLinkIntentsForRemovedContainers,\n} from \"./documentLinkRemovalRepair\";\nimport {\n deleteContainerMetadataDocumentRowsInTransaction,\n retainDormantContainerMetadataInTransaction,\n} from \"./dormantContainerMetadata\";\n\ntype DeleteContainerOptions = NonNullable<\n Parameters<ContainerContentsPersistence[\"deleteContainers\"]>[2]\n>;\n\nfunction normalizeContainerRemovals(\n removals: ReadonlyArray<ContainerRemoval>,\n): ContainerRemoval[] {\n const removalByContainerId = new Map<string, ContainerRemoval>();\n for (const removal of removals) {\n const current = removalByContainerId.get(removal.containerId);\n if (\n !current ||\n (current.reason === \"access_revoked\" && removal.reason === \"deleted\") ||\n (current.reason === removal.reason &&\n current.updatedAt < removal.updatedAt)\n ) {\n removalByContainerId.set(removal.containerId, removal);\n }\n }\n return Array.from(removalByContainerId.values());\n}\n\nfunction loadGuardedContainerRows(\n tx: ClientSQLiteTransactionScope,\n containerIds: ReadonlyArray<string>,\n) {\n return tx\n .select({\n effectiveAccessLevel: containers.effectiveAccessLevel,\n id: containers.id,\n metadataDocumentId: containers.metadataDocumentId,\n organizationId: containers.organizationId,\n parentId: containers.parentId,\n serverCreatedAt: containers.serverCreatedAt,\n serverUpdatedAt: containers.serverUpdatedAt,\n systemSlot: containers.systemSlot,\n })\n .from(containers)\n .where(inArray(containers.id, [...containerIds]));\n}\n\ntype GuardedContainerRow = Awaited<\n ReturnType<typeof loadGuardedContainerRows>\n>[number];\n\nfunction sameGuardedContainerRow(\n row: GuardedContainerRow | undefined,\n guard: ContainerDeletionGuard,\n): boolean {\n const expected = guard.expectedContainer;\n if (!row || !expected) return row === undefined && expected === null;\n return (\n row.id === expected.id &&\n row.organizationId === expected.organizationId &&\n row.parentId === expected.parentId &&\n row.metadataDocumentId === expected.metadataDocumentId &&\n row.systemSlot === (expected.systemSlot ?? null) &&\n row.effectiveAccessLevel === (expected.effectiveAccessLevel ?? \"read\") &&\n row.serverCreatedAt === (expected.serverCreatedAt ?? null) &&\n row.serverUpdatedAt === (expected.serverUpdatedAt ?? null)\n );\n}\n\nasync function canApplyContainerRemovals(input: {\n expectedContainers: DeleteContainerOptions[\"expectedContainers\"];\n removals: ReadonlyArray<ContainerRemoval>;\n tx: ClientSQLiteTransactionScope;\n}): Promise<boolean> {\n const currentRows = await loadGuardedContainerRows(\n input.tx,\n input.removals.map((removal) => removal.containerId),\n );\n const currentRowById = new Map(\n currentRows.flatMap((row) => (row.id ? ([[row.id, row]] as const) : [])),\n );\n const guardById = new Map(\n input.expectedContainers?.map((guard) => [guard.containerId, guard]) ?? [],\n );\n return input.removals.every((removal) => {\n const currentRow = currentRowById.get(removal.containerId);\n const guard = guardById.get(removal.containerId);\n return (\n (!guard || sameGuardedContainerRow(currentRow, guard)) &&\n (currentRow?.serverUpdatedAt == null ||\n currentRow.serverUpdatedAt <= removal.updatedAt)\n );\n });\n}\n\nasync function retainRemovedContainerMetadata(input: {\n containerIds: ReadonlyArray<string>;\n retainedAt: string;\n tx: ClientSQLiteTransactionScope;\n}): Promise<void> {\n if (input.containerIds.length === 0) return;\n const retainedContainers = await input.tx\n .select({\n containerId: containers.id,\n organizationId: containers.organizationId,\n })\n .from(containers)\n .where(inArray(containers.id, [...input.containerIds]));\n await retainDormantContainerMetadataInTransaction(\n input.tx,\n retainedContainers.flatMap((container) =>\n container.containerId\n ? [\n {\n containerId: container.containerId,\n organizationId: container.organizationId,\n retainedAt: input.retainedAt,\n },\n ]\n : [],\n ),\n );\n}\n\nasync function applyContainerRemovals(input: {\n discoveryOnly: DeleteContainerOptions[\"discoveryOnly\"];\n metadataDeleteIds: ReadonlyArray<string>;\n removals: ReadonlyArray<ContainerRemoval>;\n retainedMetadataIds: ReadonlyArray<string>;\n tx: ClientSQLiteTransactionScope;\n}): Promise<ReadonlyArray<string>> {\n const containerIds = input.removals.map((removal) => removal.containerId);\n await recordContainerHydrationTombstones({\n removals: input.removals,\n tx: input.tx,\n });\n await retainRemovedContainerMetadata({\n containerIds: input.retainedMetadataIds,\n retainedAt: new Date().toISOString(),\n tx: input.tx,\n });\n // Listing clocks are untrusted; they cannot advance local projection clocks.\n const localObservedAt = new Date().toISOString();\n await repairDocumentsForRemovedContainersInTransaction({\n removals: input.discoveryOnly\n ? input.removals.map((removal) => ({\n ...removal,\n updatedAt: localObservedAt,\n }))\n : input.removals,\n tx: input.tx,\n });\n if (input.discoveryOnly) {\n await refreshPlacementIntentsAfterRemoval({ containerIds, tx: input.tx });\n } else {\n await repairLinkIntentsForRemovedContainers({ containerIds, tx: input.tx });\n await input.tx\n .delete(localIntents)\n .where(\n and(\n inContainerIntentLanes(),\n inArray(localIntents.subjectId, containerIds),\n ),\n )\n .run();\n }\n await deleteContainerRowsInTransaction(input.tx, containerIds);\n await deleteContainerMetadataDocumentRowsInTransaction(\n input.tx,\n input.metadataDeleteIds,\n );\n await deleteContainerWatermarksInTransaction(input.tx, containerIds);\n return containerIds;\n}\n\nexport async function deleteStoredContainers(\n execSql: Parameters<ContainerContentsPersistence[\"deleteContainers\"]>[0],\n removals: Parameters<ContainerContentsPersistence[\"deleteContainers\"]>[1],\n options?: Parameters<ContainerContentsPersistence[\"deleteContainers\"]>[2],\n): Promise<ReadonlyArray<string>> {\n const uniqueRemovals = normalizeContainerRemovals(removals);\n if (uniqueRemovals.length === 0) return [];\n return runSerializedSqlMutation(execSql, async (lockedExecSql) => {\n await ensureSqlTables(lockedExecSql, documentContainerProjectionTables);\n await ensureSqlTables(lockedExecSql, documentPlacementIntentTables);\n await ensureContainerTables(lockedExecSql);\n await ensureDocumentProjectionTables(lockedExecSql);\n await sqlContainerSyncWatermarkPersistence.ensureSchema(lockedExecSql);\n const runtime = getClientSQLitePersistenceRuntime(lockedExecSql);\n const remove = async (tx: ClientSQLiteTransactionScope) => {\n if (\n options?.beforeDeleteInTransaction &&\n !(await options.beforeDeleteInTransaction(lockedExecSql))\n )\n return [];\n if (\n !(await canApplyContainerRemovals({\n expectedContainers: options?.expectedContainers,\n removals: uniqueRemovals,\n tx,\n }))\n ) {\n return [];\n }\n const retainedMetadataIds = options?.discoveryOnly\n ? await containerIdsWithRemovalWork(\n tx,\n uniqueRemovals.map((removal) => removal.containerId),\n )\n : [];\n return applyContainerRemovals({\n discoveryOnly: options?.discoveryOnly,\n metadataDeleteIds: uniqueRemovals.flatMap((removal) =>\n retainedMetadataIds.includes(removal.containerId)\n ? []\n : [removal.containerId],\n ),\n removals: uniqueRemovals,\n retainedMetadataIds,\n tx,\n });\n };\n if (!options?.stillCurrent) {\n return runtime.transaction(remove, { behavior: \"immediate\" });\n }\n const outcome = await runtime.guardedTransaction(\n remove,\n options.stillCurrent,\n { behavior: \"immediate\" },\n );\n return outcome.result ?? [];\n });\n}\n"]}
@@ -1,7 +1,7 @@
1
1
  import { and, eq, inArray, or, sql } from "drizzle-orm";
2
2
  import { uniqueSortedStrings } from "../../documents/shared/readers.js";
3
3
  import { documentIntentLinkTargets } from "../../sqlite/documentPlacementIntentSchema.js";
4
- import { documentContainerProjection, documentMoveIntents, documentProjection, } from "../../sqlite/schema.js";
4
+ import { documentContainerProjection, documentProjection, inLocalIntentLane, LOCAL_INTENT_LANES, localIntents, } from "../../sqlite/schema.js";
5
5
  import { deleteContainerDocumentTombstoneHoldRowsForLinks, deleteContainerDocumentTombstoneHoldsForContainers, } from "../documents/containerDocumentTombstoneHoldsPersistence.js";
6
6
  import { DOCUMENT_LINK_INTENT_TYPE, DOCUMENT_MOVE_INTENT_TYPE, } from "./documentMoveIntentPersistence.js";
7
7
  async function reassignDocumentLinksForContainer(input) {
@@ -61,14 +61,14 @@ async function reassignDocumentMoveIntentsForContainer(input) {
61
61
  const { fromContainerId, toContainerId, tx, updatedAt } = input;
62
62
  const affectedIntents = await tx
63
63
  .select({
64
- documentId: documentMoveIntents.documentId,
65
- id: documentMoveIntents.id,
64
+ documentId: localIntents.subjectId,
65
+ id: localIntents.id,
66
66
  })
67
- .from(documentMoveIntents)
68
- .where(and(inArray(documentMoveIntents.intentType, [
67
+ .from(localIntents)
68
+ .where(and(inLocalIntentLane(LOCAL_INTENT_LANES.documentPlacement), inArray(localIntents.intentType, [
69
69
  DOCUMENT_MOVE_INTENT_TYPE,
70
70
  DOCUMENT_LINK_INTENT_TYPE,
71
- ]), or(eq(documentMoveIntents.sourceContainerId, fromContainerId), eq(documentMoveIntents.targetContainerId, fromContainerId), inArray(documentMoveIntents.id, tx
71
+ ]), or(eq(localIntents.sourceContainerId, fromContainerId), eq(localIntents.targetContainerId, fromContainerId), inArray(localIntents.id, tx
72
72
  .select({ id: documentIntentLinkTargets.intentId })
73
73
  .from(documentIntentLinkTargets)
74
74
  .where(and(eq(documentIntentLinkTargets.containerId, fromContainerId), eq(documentIntentLinkTargets.operation, "link")))))));
@@ -97,25 +97,25 @@ async function reassignDocumentMoveIntentsForContainer(input) {
97
97
  })))
98
98
  .run();
99
99
  await tx
100
- .update(documentMoveIntents)
100
+ .update(localIntents)
101
101
  .set({
102
102
  id,
103
103
  lastAttemptedAt: null,
104
104
  lastError: null,
105
105
  sourceContainerId: sql `CASE
106
- WHEN ${documentMoveIntents.sourceContainerId} = ${fromContainerId}
106
+ WHEN ${localIntents.sourceContainerId} = ${fromContainerId}
107
107
  THEN ${toContainerId}
108
- ELSE ${documentMoveIntents.sourceContainerId}
108
+ ELSE ${localIntents.sourceContainerId}
109
109
  END`,
110
110
  syncStatus: "pending",
111
111
  targetContainerId: sql `CASE
112
- WHEN ${documentMoveIntents.targetContainerId} = ${fromContainerId}
112
+ WHEN ${localIntents.targetContainerId} = ${fromContainerId}
113
113
  THEN ${toContainerId}
114
- ELSE ${documentMoveIntents.targetContainerId}
114
+ ELSE ${localIntents.targetContainerId}
115
115
  END`,
116
- updatedAt: sql `max(${documentMoveIntents.updatedAt}, ${updatedAt})`,
116
+ updatedAt: sql `max(${localIntents.updatedAt}, ${updatedAt})`,
117
117
  })
118
- .where(eq(documentMoveIntents.documentId, intent.documentId))
118
+ .where(and(inLocalIntentLane(LOCAL_INTENT_LANES.documentPlacement), eq(localIntents.subjectId, intent.documentId)))
119
119
  .run();
120
120
  }
121
121
  }
@@ -1 +1 @@
1
- {"version":3,"file":"containerDocumentReassignment.js","sourceRoot":"","sources":["containerDocumentReassignment.ts"],"names":[],"mappings":"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","sourcesContent":["import { and, eq, inArray, or, sql } from \"drizzle-orm\";\nimport { uniqueSortedStrings } from \"../../documents/shared/readers\";\nimport { documentIntentLinkTargets } from \"../../sqlite/documentPlacementIntentSchema\";\nimport {\n documentContainerProjection,\n documentMoveIntents,\n documentProjection,\n} from \"../../sqlite/schema\";\nimport type { ClientSQLiteTransactionScope } from \"../../sqlite/sqlitePersistenceRuntime\";\nimport {\n deleteContainerDocumentTombstoneHoldRowsForLinks,\n deleteContainerDocumentTombstoneHoldsForContainers,\n} from \"../documents/containerDocumentTombstoneHoldsPersistence\";\nimport {\n DOCUMENT_LINK_INTENT_TYPE,\n DOCUMENT_MOVE_INTENT_TYPE,\n} from \"./documentMoveIntentPersistence\";\n\ninterface ContainerDocumentReassignmentInput {\n readonly fromContainerId: string;\n readonly toContainerId: string;\n readonly tx: ClientSQLiteTransactionScope;\n readonly updatedAt: string;\n}\n\nasync function reassignDocumentLinksForContainer(\n input: ContainerDocumentReassignmentInput,\n): Promise<void> {\n const { fromContainerId, toContainerId, tx, updatedAt } = input;\n await tx.run(sql`\n INSERT INTO ${documentContainerProjection} (\n ${sql.raw(\"document_id\")},\n ${sql.raw(\"container_id\")},\n ${sql.raw(\"updated_at\")}\n )\n SELECT\n ${documentContainerProjection.documentId},\n ${toContainerId},\n max(${documentContainerProjection.updatedAt}, ${updatedAt})\n FROM ${documentContainerProjection}\n WHERE ${documentContainerProjection.containerId} = ${fromContainerId}\n ON CONFLICT (\n ${sql.raw(\"document_id\")},\n ${sql.raw(\"container_id\")}\n ) DO UPDATE SET\n ${sql.raw(\"updated_at\")} = max(\n ${sql.raw(\"updated_at\")},\n excluded.${sql.raw(\"updated_at\")}\n )\n `);\n\n // The reassigned placements are asserted locally: a stale hold on the\n // destination would hide them, and holds on the vacated container have\n // nothing left to hide.\n const movedDocumentIds = (\n await tx\n .select({ documentId: documentContainerProjection.documentId })\n .from(documentContainerProjection)\n .where(eq(documentContainerProjection.containerId, fromContainerId))\n ).map((row) => row.documentId);\n await deleteContainerDocumentTombstoneHoldRowsForLinks(\n tx,\n movedDocumentIds.map((documentId) => ({\n containerIds: [toContainerId],\n documentId,\n })),\n );\n await deleteContainerDocumentTombstoneHoldsForContainers(tx, [\n fromContainerId,\n ]);\n await tx\n .delete(documentContainerProjection)\n .where(eq(documentContainerProjection.containerId, fromContainerId))\n .run();\n}\n\nasync function reassignPrimaryDocumentsForContainer(\n input: ContainerDocumentReassignmentInput,\n): Promise<void> {\n const { fromContainerId, toContainerId, tx, updatedAt } = input;\n await tx\n .update(documentProjection)\n .set({\n containerId: toContainerId,\n updatedAt: sql`max(${documentProjection.updatedAt}, ${updatedAt})`,\n })\n .where(eq(documentProjection.containerId, fromContainerId))\n .run();\n}\n\nasync function reassignDocumentMoveIntentsForContainer(\n input: ContainerDocumentReassignmentInput,\n): Promise<void> {\n const { fromContainerId, toContainerId, tx, updatedAt } = input;\n const affectedIntents = await tx\n .select({\n documentId: documentMoveIntents.documentId,\n id: documentMoveIntents.id,\n })\n .from(documentMoveIntents)\n .where(\n and(\n inArray(documentMoveIntents.intentType, [\n DOCUMENT_MOVE_INTENT_TYPE,\n DOCUMENT_LINK_INTENT_TYPE,\n ]),\n or(\n eq(documentMoveIntents.sourceContainerId, fromContainerId),\n eq(documentMoveIntents.targetContainerId, fromContainerId),\n inArray(\n documentMoveIntents.id,\n tx\n .select({ id: documentIntentLinkTargets.intentId })\n .from(documentIntentLinkTargets)\n .where(\n and(\n eq(documentIntentLinkTargets.containerId, fromContainerId),\n eq(documentIntentLinkTargets.operation, \"link\"),\n ),\n ),\n ),\n ),\n ),\n );\n for (const intent of affectedIntents) {\n const id = crypto.randomUUID();\n const targets = await tx\n .select()\n .from(documentIntentLinkTargets)\n .where(eq(documentIntentLinkTargets.intentId, intent.id ?? \"\"));\n await tx\n .delete(documentIntentLinkTargets)\n .where(eq(documentIntentLinkTargets.intentId, intent.id ?? \"\"))\n .run();\n const retained = new Map(\n targets.map((target) => [target.containerId, target.operation]),\n );\n if (retained.get(fromContainerId) === \"link\") {\n retained.delete(fromContainerId);\n retained.set(toContainerId, \"link\");\n }\n if (retained.size)\n await tx\n .insert(documentIntentLinkTargets)\n .values(\n uniqueSortedStrings([...retained.keys()]).map((containerId) => ({\n intentId: id,\n containerId,\n operation: retained.get(containerId) ?? \"link\",\n })),\n )\n .run();\n await tx\n .update(documentMoveIntents)\n .set({\n id,\n lastAttemptedAt: null,\n lastError: null,\n sourceContainerId: sql`CASE\n WHEN ${documentMoveIntents.sourceContainerId} = ${fromContainerId}\n THEN ${toContainerId}\n ELSE ${documentMoveIntents.sourceContainerId}\n END`,\n syncStatus: \"pending\",\n targetContainerId: sql`CASE\n WHEN ${documentMoveIntents.targetContainerId} = ${fromContainerId}\n THEN ${toContainerId}\n ELSE ${documentMoveIntents.targetContainerId}\n END`,\n updatedAt: sql`max(${documentMoveIntents.updatedAt}, ${updatedAt})`,\n })\n .where(eq(documentMoveIntents.documentId, intent.documentId))\n .run();\n }\n}\n\nexport async function reassignContainerDocumentsInTransaction(\n input: ContainerDocumentReassignmentInput,\n): Promise<void> {\n await reassignDocumentLinksForContainer(input);\n await reassignPrimaryDocumentsForContainer(input);\n await reassignDocumentMoveIntentsForContainer(input);\n}\n"]}
1
+ {"version":3,"file":"containerDocumentReassignment.js","sourceRoot":"","sources":["containerDocumentReassignment.ts"],"names":[],"mappings":"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","sourcesContent":["import { and, eq, inArray, or, sql } from \"drizzle-orm\";\nimport { uniqueSortedStrings } from \"../../documents/shared/readers\";\nimport { documentIntentLinkTargets } from \"../../sqlite/documentPlacementIntentSchema\";\nimport {\n documentContainerProjection,\n documentProjection,\n inLocalIntentLane,\n LOCAL_INTENT_LANES,\n localIntents,\n} from \"../../sqlite/schema\";\nimport type { ClientSQLiteTransactionScope } from \"../../sqlite/sqlitePersistenceRuntime\";\nimport {\n deleteContainerDocumentTombstoneHoldRowsForLinks,\n deleteContainerDocumentTombstoneHoldsForContainers,\n} from \"../documents/containerDocumentTombstoneHoldsPersistence\";\nimport {\n DOCUMENT_LINK_INTENT_TYPE,\n DOCUMENT_MOVE_INTENT_TYPE,\n} from \"./documentMoveIntentPersistence\";\n\ninterface ContainerDocumentReassignmentInput {\n readonly fromContainerId: string;\n readonly toContainerId: string;\n readonly tx: ClientSQLiteTransactionScope;\n readonly updatedAt: string;\n}\n\nasync function reassignDocumentLinksForContainer(\n input: ContainerDocumentReassignmentInput,\n): Promise<void> {\n const { fromContainerId, toContainerId, tx, updatedAt } = input;\n await tx.run(sql`\n INSERT INTO ${documentContainerProjection} (\n ${sql.raw(\"document_id\")},\n ${sql.raw(\"container_id\")},\n ${sql.raw(\"updated_at\")}\n )\n SELECT\n ${documentContainerProjection.documentId},\n ${toContainerId},\n max(${documentContainerProjection.updatedAt}, ${updatedAt})\n FROM ${documentContainerProjection}\n WHERE ${documentContainerProjection.containerId} = ${fromContainerId}\n ON CONFLICT (\n ${sql.raw(\"document_id\")},\n ${sql.raw(\"container_id\")}\n ) DO UPDATE SET\n ${sql.raw(\"updated_at\")} = max(\n ${sql.raw(\"updated_at\")},\n excluded.${sql.raw(\"updated_at\")}\n )\n `);\n\n // The reassigned placements are asserted locally: a stale hold on the\n // destination would hide them, and holds on the vacated container have\n // nothing left to hide.\n const movedDocumentIds = (\n await tx\n .select({ documentId: documentContainerProjection.documentId })\n .from(documentContainerProjection)\n .where(eq(documentContainerProjection.containerId, fromContainerId))\n ).map((row) => row.documentId);\n await deleteContainerDocumentTombstoneHoldRowsForLinks(\n tx,\n movedDocumentIds.map((documentId) => ({\n containerIds: [toContainerId],\n documentId,\n })),\n );\n await deleteContainerDocumentTombstoneHoldsForContainers(tx, [\n fromContainerId,\n ]);\n await tx\n .delete(documentContainerProjection)\n .where(eq(documentContainerProjection.containerId, fromContainerId))\n .run();\n}\n\nasync function reassignPrimaryDocumentsForContainer(\n input: ContainerDocumentReassignmentInput,\n): Promise<void> {\n const { fromContainerId, toContainerId, tx, updatedAt } = input;\n await tx\n .update(documentProjection)\n .set({\n containerId: toContainerId,\n updatedAt: sql`max(${documentProjection.updatedAt}, ${updatedAt})`,\n })\n .where(eq(documentProjection.containerId, fromContainerId))\n .run();\n}\n\nasync function reassignDocumentMoveIntentsForContainer(\n input: ContainerDocumentReassignmentInput,\n): Promise<void> {\n const { fromContainerId, toContainerId, tx, updatedAt } = input;\n const affectedIntents = await tx\n .select({\n documentId: localIntents.subjectId,\n id: localIntents.id,\n })\n .from(localIntents)\n .where(\n and(\n inLocalIntentLane(LOCAL_INTENT_LANES.documentPlacement),\n inArray(localIntents.intentType, [\n DOCUMENT_MOVE_INTENT_TYPE,\n DOCUMENT_LINK_INTENT_TYPE,\n ]),\n or(\n eq(localIntents.sourceContainerId, fromContainerId),\n eq(localIntents.targetContainerId, fromContainerId),\n inArray(\n localIntents.id,\n tx\n .select({ id: documentIntentLinkTargets.intentId })\n .from(documentIntentLinkTargets)\n .where(\n and(\n eq(documentIntentLinkTargets.containerId, fromContainerId),\n eq(documentIntentLinkTargets.operation, \"link\"),\n ),\n ),\n ),\n ),\n ),\n );\n for (const intent of affectedIntents) {\n const id = crypto.randomUUID();\n const targets = await tx\n .select()\n .from(documentIntentLinkTargets)\n .where(eq(documentIntentLinkTargets.intentId, intent.id ?? \"\"));\n await tx\n .delete(documentIntentLinkTargets)\n .where(eq(documentIntentLinkTargets.intentId, intent.id ?? \"\"))\n .run();\n const retained = new Map(\n targets.map((target) => [target.containerId, target.operation]),\n );\n if (retained.get(fromContainerId) === \"link\") {\n retained.delete(fromContainerId);\n retained.set(toContainerId, \"link\");\n }\n if (retained.size)\n await tx\n .insert(documentIntentLinkTargets)\n .values(\n uniqueSortedStrings([...retained.keys()]).map((containerId) => ({\n intentId: id,\n containerId,\n operation: retained.get(containerId) ?? \"link\",\n })),\n )\n .run();\n await tx\n .update(localIntents)\n .set({\n id,\n lastAttemptedAt: null,\n lastError: null,\n sourceContainerId: sql`CASE\n WHEN ${localIntents.sourceContainerId} = ${fromContainerId}\n THEN ${toContainerId}\n ELSE ${localIntents.sourceContainerId}\n END`,\n syncStatus: \"pending\",\n targetContainerId: sql`CASE\n WHEN ${localIntents.targetContainerId} = ${fromContainerId}\n THEN ${toContainerId}\n ELSE ${localIntents.targetContainerId}\n END`,\n updatedAt: sql`max(${localIntents.updatedAt}, ${updatedAt})`,\n })\n .where(\n and(\n inLocalIntentLane(LOCAL_INTENT_LANES.documentPlacement),\n eq(localIntents.subjectId, intent.documentId),\n ),\n )\n .run();\n }\n}\n\nexport async function reassignContainerDocumentsInTransaction(\n input: ContainerDocumentReassignmentInput,\n): Promise<void> {\n await reassignDocumentLinksForContainer(input);\n await reassignPrimaryDocumentsForContainer(input);\n await reassignDocumentMoveIntentsForContainer(input);\n}\n"]}
@@ -2,7 +2,7 @@ import { KeyingVerificationError } from "../../../internal/crypto/index.js";
2
2
  import { and, eq, inArray, sql } from "drizzle-orm";
3
3
  import { assertHeldContainerBinding, heldContainerBinding, } from "../../containers/containerBinding.js";
4
4
  import { documentSyncPullContinuationsEqual } from "../../documents/shared/pullContinuation.js";
5
- import { containerHydrationTombstones, containerMoveIntents, containers, dormantContainerMetadata, } from "../../sqlite/schema.js";
5
+ import { containerHydrationTombstones, containers, dormantContainerMetadata, inLocalIntentLane, LOCAL_INTENT_LANES, localIntents, } from "../../sqlite/schema.js";
6
6
  import { getClientSQLitePersistenceRuntime, } from "../../sqlite/sqlitePersistenceRuntime.js";
7
7
  import { runSerializedSqlMutation } from "../../sqlite/sqlSchema.js";
8
8
  import { loadContainerById } from "../containers/containerPersistence.js";
@@ -70,9 +70,12 @@ async function placementCheckpointMatches(tx, input) {
70
70
  /** A retained local move continues to own placement after verified reattachment. */
71
71
  async function saveRecoveredContainer(tx, input) {
72
72
  const [move] = await tx
73
- .select()
74
- .from(containerMoveIntents)
75
- .where(and(eq(containerMoveIntents.containerId, input.container.id), inArray(containerMoveIntents.syncStatus, ["pending", "blocked"])))
73
+ .select({
74
+ parentContainerId: localIntents.targetContainerId,
75
+ updatedAt: localIntents.updatedAt,
76
+ })
77
+ .from(localIntents)
78
+ .where(and(inLocalIntentLane(LOCAL_INTENT_LANES.containerMove), eq(localIntents.subjectId, input.container.id), inArray(localIntents.syncStatus, ["pending", "blocked"])))
76
79
  .limit(1);
77
80
  return saveContainerContentsContainerRows({
78
81
  container: move
@@ -1 +1 @@
1
- {"version":3,"file":"containerHydrationPersistence.js","sourceRoot":"","sources":["containerHydrationPersistence.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,uBAAuB,EAAE,MAAM,mBAAmB,CAAC;AAC5D,OAAO,EAAE,GAAG,EAAE,EAAE,EAAE,OAAO,EAAE,GAAG,EAAE,MAAM,aAAa,CAAC;AACpD,OAAO,EACL,0BAA0B,EAE1B,oBAAoB,GACrB,MAAM,mCAAmC,CAAC;AAC3C,OAAO,EAAE,kCAAkC,EAAE,MAAM,yCAAyC,CAAC;AAC7F,OAAO,EACL,4BAA4B,EAC5B,oBAAoB,EACpB,UAAU,EACV,wBAAwB,GACzB,MAAM,qBAAqB,CAAC;AAC7B,OAAO,EAEL,iCAAiC,GAClC,MAAM,uCAAuC,CAAC;AAC/C,OAAO,EAAgB,wBAAwB,EAAE,MAAM,wBAAwB,CAAC;AAChF,OAAO,EAAE,iBAAiB,EAAE,MAAM,oCAAoC,CAAC;AACvE,OAAO,EAAE,kCAAkC,EAAE,MAAM,gCAAgC,CAAC;AAMpF,OAAO,EACL,kCAAkC,EAClC,6BAA6B,GAC9B,MAAM,yBAAyB,CAAC;AACjC,OAAO,EAAE,4BAA4B,EAAE,MAAM,+BAA+B,CAAC;AAE7E,SAAS,iBAAiB,CACxB,IAA+B,EAC/B,KAAgC;IAEhC,OAAO,CAAC,IAAI,IAAI,IAAI,CAAC,KAAK,CAAC,KAAK,IAAI,IAAI,CAAC,CAAC;AAC5C,CAAC;AAED,SAAS,kBAAkB,CACzB,OAAuC,EACvC,QAAwC;IAExC,IAAI,CAAC,OAAO,IAAI,CAAC,QAAQ;QAAE,OAAO,OAAO,KAAK,QAAQ,CAAC;IACvD,OAAO,CACL,OAAO,CAAC,EAAE,KAAK,QAAQ,CAAC,EAAE;QAC1B,OAAO,CAAC,UAAU,KAAK,QAAQ,CAAC,UAAU;QAC1C,OAAO,CAAC,WAAW,KAAK,QAAQ,CAAC,WAAW;QAC5C,iBAAiB,CAAC,OAAO,CAAC,eAAe,EAAE,QAAQ,CAAC,eAAe,CAAC;QACpE,iBAAiB,CAAC,OAAO,CAAC,aAAa,EAAE,QAAQ,CAAC,aAAa,CAAC;QAChE,iBAAiB,CAAC,OAAO,CAAC,gBAAgB,EAAE,QAAQ,CAAC,gBAAgB,CAAC;QACtE,iBAAiB,CACf,OAAO,CAAC,kBAAkB,EAC1B,QAAQ,CAAC,kBAAkB,CAC5B;QACD,iBAAiB,CACf,OAAO,CAAC,sBAAsB,EAC9B,QAAQ,CAAC,sBAAsB,CAChC;QACD,OAAO,CAAC,eAAe,KAAK,QAAQ,CAAC,eAAe;QACpD,OAAO,CAAC,kBAAkB,KAAK,QAAQ,CAAC,kBAAkB;QAC1D,kCAAkC,CAChC,OAAO,CAAC,gBAAgB,EACxB,QAAQ,CAAC,gBAAgB,CAC1B;QACD,OAAO,CAAC,OAAO,CAAC,gCAAgC,CAAC;YAC/C,OAAO,CAAC,QAAQ,CAAC,gCAAgC,CAAC,CACrD,CAAC;AACJ,CAAC;AAED,SAAS,sBAAsB,CAC7B,OAEa,EACb,QAAwD;IAExD,OAAO,CACL,OAAO,KAAK,SAAS;QACrB,QAAQ,IAAI,IAAI;QAChB,OAAO,CAAC,UAAU,KAAK,QAAQ,CAAC,UAAU;QAC1C,OAAO,CAAC,MAAM,KAAK,QAAQ,CAAC,MAAM;QAClC,OAAO,CAAC,SAAS,KAAK,QAAQ,CAAC,SAAS,CACzC,CAAC;AACJ,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,kCAAkC,CAAC,KAOxD;IACC,IAAI,KAAK,CAAC,QAAQ,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO;IACxC,MAAM,KAAK,CAAC,EAAE;SACX,MAAM,CAAC,4BAA4B,CAAC;SACpC,MAAM,CAAC,CAAC,GAAG,KAAK,CAAC,QAAQ,CAAC,CAAC;SAC3B,kBAAkB,CAAC;QAClB,MAAM,EAAE,4BAA4B,CAAC,WAAW;QAChD,GAAG,EAAE;YACH,UAAU,EAAE,GAAG,CAAA,GAAG,4BAA4B,CAAC,UAAU,MAAM;YAC/D,OAAO,EAAE,KAAK;YACd,MAAM,EAAE,GAAG,CAAA;iBACF,4BAA4B,CAAC,MAAM;;YAExC;YACJ,SAAS,EAAE,GAAG,CAAA,OAAO,4BAA4B,CAAC,SAAS,wBAAwB;SACpF;KACF,CAAC;SACD,GAAG,EAAE,CAAC;AACX,CAAC;AAED,KAAK,UAAU,0BAA0B,CACvC,EAAgC,EAChC,KAA6E;IAE7E,MAAM,QAAQ,GAAG,KAAK,CAAC,2BAA2B,CAAC;IACnD,IAAI,CAAC,QAAQ;QAAE,OAAO,IAAI,CAAC;IAC3B,MAAM,OAAO,GAAG,MAAM,kCAAkC,CAAC,EAAE,EAAE,QAAQ,CAAC,CAAC;IACvE,OAAO,CACL,QAAQ,CAAC,UAAU,KAAK,WAAW;QACnC,QAAQ,CAAC,QAAQ,KAAK,KAAK,CAAC,SAAS,CAAC,EAAE;QACxC,QAAQ,CAAC,cAAc,KAAK,KAAK,CAAC,SAAS,CAAC,cAAc;QAC1D,OAAO,EAAE,KAAK,KAAK,QAAQ,CAAC,KAAK;QACjC,OAAO,CAAC,YAAY,KAAK,QAAQ,CAAC,YAAY,CAC/C,CAAC;AACJ,CAAC;AAED,oFAAoF;AACpF,KAAK,UAAU,sBAAsB,CACnC,EAAgC,EAChC,KAA6E;IAE7E,MAAM,CAAC,IAAI,CAAC,GAAG,MAAM,EAAE;SACpB,MAAM,EAAE;SACR,IAAI,CAAC,oBAAoB,CAAC;SAC1B,KAAK,CACJ,GAAG,CACD,EAAE,CAAC,oBAAoB,CAAC,WAAW,EAAE,KAAK,CAAC,SAAS,CAAC,EAAE,CAAC,EACxD,OAAO,CAAC,oBAAoB,CAAC,UAAU,EAAE,CAAC,SAAS,EAAE,SAAS,CAAC,CAAC,CACjE,CACF;SACA,KAAK,CAAC,CAAC,CAAC,CAAC;IACZ,OAAO,kCAAkC,CAAC;QACxC,SAAS,EAAE,IAAI;YACb,CAAC,CAAC,EAAE,GAAG,KAAK,CAAC,SAAS,EAAE,QAAQ,EAAE,IAAI,CAAC,iBAAiB,EAAE;YAC1D,CAAC,CAAC,KAAK,CAAC,SAAS;QACnB,cAAc,EACZ,IAAI,EAAE,SAAS;YACf,KAAK,CAAC,WAAW,CAAC,cAAc;YAChC,KAAK,CAAC,eAAe;QACvB,MAAM,EAAE,KAAK,CAAC,MAAM;QACpB,gBAAgB,EAAE,KAAK,CAAC,WAAW,CAAC,gBAAgB;QACpD,EAAE;KACH,CAAC,CAAC;AACL,CAAC;AAED,KAAK,UAAU,yBAAyB,CACtC,OAAgB,EAChB,WAAmB;IAEnB,MAAM,CAAC,GAAG,CAAC,GAAG,MAAM,iCAAiC,CAAC,OAAO,CAAC;SAC3D,EAAE,CAAC,MAAM,CAAC,EAAE,cAAc,EAAE,wBAAwB,CAAC,cAAc,EAAE,CAAC;SACtE,IAAI,CAAC,wBAAwB,CAAC;SAC9B,KAAK,CAAC,EAAE,CAAC,wBAAwB,CAAC,WAAW,EAAE,WAAW,CAAC,CAAC;SAC5D,KAAK,CAAC,CAAC,CAAC,CAAC;IACZ,OAAO,GAAG,EAAE,cAAc,IAAI,IAAI,CAAC;AACrC,CAAC;AAED;;;;GAIG;AACH,MAAM,CAAC,KAAK,UAAU,8BAA8B,CAClD,OAAgB,EAChB,WAAmB;IAEnB,MAAM,SAAS,GAAG,MAAM,iBAAiB,CAAC,OAAO,EAAE,WAAW,CAAC,CAAC;IAChE,MAAM,MAAM,GAAG,MAAM,6BAA6B,CAAC,OAAO,EAAE,WAAW,CAAC,CAAC;IACzE,IAAI,SAAS;QAAE,OAAO,oBAAoB,CAAC,EAAE,SAAS,EAAE,MAAM,EAAE,CAAC,CAAC;IAClE,IAAI,CAAC,MAAM;QAAE,OAAO,IAAI,CAAC;IACzB,4EAA4E;IAC5E,uDAAuD;IACvD,MAAM,qBAAqB,GAAG,MAAM,yBAAyB,CAC3D,OAAO,EACP,WAAW,CACZ,CAAC;IACF,OAAO;QACL,cAAc,EAAE,qBAAqB,IAAI,EAAE;QAC3C,kBAAkB,EAAE,MAAM,CAAC,UAAU;KACtC,CAAC;AACJ,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,6BAA6B,CACjD,OAEI,EACJ,KAA6E;IAE7E,OAAO,wBAAwB,CAAC,OAAO,EAAE,KAAK,EAAE,aAAa,EAAE,EAAE;QAC/D,MAAM,OAAO,GAAG,iCAAiC,CAAC,aAAa,CAAC,CAAC;QACjE,MAAM,MAAM,GAAG,KAAK,EAAE,EAAgC,EAAE,EAAE;YACxD,MAAM,kBAAkB,GAAG,MAAM,EAAE;iBAChC,MAAM,CAAC,EAAE,EAAE,EAAE,UAAU,CAAC,EAAE,EAAE,CAAC;iBAC7B,IAAI,CAAC,UAAU,CAAC;iBAChB,KAAK,CAAC,EAAE,CAAC,UAAU,CAAC,EAAE,EAAE,KAAK,CAAC,SAAS,CAAC,EAAE,CAAC,CAAC;iBAC5C,KAAK,CAAC,CAAC,CAAC,CAAC;YACZ,IAAI,kBAAkB,CAAC,MAAM,GAAG,CAAC;gBAAE,OAAO,EAAE,SAAS,EAAE,KAAc,EAAE,CAAC;YACxE,IAAI,CAAC,CAAC,MAAM,0BAA0B,CAAC,EAAE,EAAE,KAAK,CAAC,CAAC;gBAChD,OAAO,EAAE,SAAS,EAAE,KAAc,EAAE,CAAC;YAEvC,MAAM,MAAM,GAAG,MAAM,EAAE;iBACpB,MAAM,CAAC;gBACN,UAAU,EAAE,4BAA4B,CAAC,UAAU;gBACnD,MAAM,EAAE,4BAA4B,CAAC,MAAM;gBAC3C,SAAS,EAAE,4BAA4B,CAAC,SAAS;aAClD,CAAC;iBACD,IAAI,CAAC,4BAA4B,CAAC;iBAClC,KAAK,CACJ,GAAG,CACD,EAAE,CAAC,4BAA4B,CAAC,WAAW,EAAE,KAAK,CAAC,SAAS,CAAC,EAAE,CAAC,EAChE,EAAE,CAAC,4BAA4B,CAAC,OAAO,EAAE,KAAK,CAAC,CAChD,CACF;iBACA,KAAK,CAAC,CAAC,CAAC,CAAC;YACZ,MAAM,KAAK,GAAG,MAAM,CAAC,CAAC,CAAC,CAAC;YACxB,MAAM,oBAAoB,GAAG,MAAM,6BAA6B,CAC9D,aAAa,EACb,KAAK,CAAC,SAAS,CAAC,EAAE,CACnB,CAAC;YACF,IACE,KAAK;gBACL,CAAC,sBAAsB,CAAC,KAAK,EAAE,KAAK,CAAC,0BAA0B,CAAC,EAChE,CAAC;gBACD,OAAO,EAAE,SAAS,EAAE,KAAc,EAAE,CAAC;YACvC,CAAC;YACD,IACE,CAAC,kBAAkB,CAAC,oBAAoB,EAAE,KAAK,CAAC,qBAAqB,CAAC,EACtE,CAAC;gBACD,OAAO,EAAE,SAAS,EAAE,KAAc,EAAE,CAAC;YACvC,CAAC;YACD,IACE,MAAM,4BAA4B,CAAC,aAAa,EAAE;gBAChD,WAAW,EAAE,KAAK,CAAC,SAAS,CAAC,EAAE;gBAC/B,cAAc,EAAE,KAAK,CAAC,SAAS,CAAC,cAAc;aAC/C,CAAC,EACF,CAAC;gBACD,MAAM,IAAI,uBAAuB,CAC/B,iBAAiB,EACjB,oDAAoD,CACrD,CAAC;YACJ,CAAC;YACD,wEAAwE;YACxE,wEAAwE;YACxE,MAAM,qBAAqB,GAAG,MAAM,yBAAyB,CAC3D,aAAa,EACb,KAAK,CAAC,SAAS,CAAC,EAAE,CACnB,CAAC;YACF,0BAA0B,CACxB;gBACE,cAAc,EAAE,qBAAqB,IAAI,EAAE;gBAC3C,kBAAkB,EAAE,oBAAoB,EAAE,UAAU,IAAI,IAAI;aAC7D,EACD;gBACE,cAAc,EAAE,KAAK,CAAC,SAAS,CAAC,cAAc;gBAC9C,mBAAmB,EAAE;oBACnB,KAAK,CAAC,SAAS,CAAC,kBAAkB;oBAClC,KAAK,CAAC,MAAM,CAAC,UAAU;iBACxB;aACF,CACF,CAAC;YAEF,MAAM,SAAS,GAAG,MAAM,sBAAsB,CAAC,EAAE,EAAE,KAAK,CAAC,CAAC;YAC1D,MAAM,EAAE;iBACL,MAAM,CAAC,4BAA4B,CAAC;iBACpC,GAAG,CAAC,EAAE,OAAO,EAAE,IAAI,EAAE,CAAC;iBACtB,KAAK,CAAC,EAAE,CAAC,4BAA4B,CAAC,WAAW,EAAE,KAAK,CAAC,SAAS,CAAC,EAAE,CAAC,CAAC;iBACvE,GAAG,EAAE,CAAC;YACT,OAAO,EAAE,SAAS,EAAE,IAAa,EAAE,SAAS,EAAE,CAAC;QACjD,CAAC,CAAC;QACF,IAAI,CAAC,KAAK,CAAC,YAAY,EAAE,CAAC;YACxB,OAAO,OAAO,CAAC,WAAW,CAAC,MAAM,EAAE,EAAE,QAAQ,EAAE,WAAW,EAAE,CAAC,CAAC;QAChE,CAAC;QACD,MAAM,OAAO,GAAG,MAAM,OAAO,CAAC,kBAAkB,CAC9C,MAAM,EACN,KAAK,CAAC,YAAY,EAClB,EAAE,QAAQ,EAAE,WAAW,EAAE,CAC1B,CAAC;QACF,OAAO,OAAO,CAAC,MAAM,IAAI,EAAE,SAAS,EAAE,KAAc,EAAE,CAAC;IACzD,CAAC,CAAC,CAAC;AACL,CAAC","sourcesContent":["import { KeyingVerificationError } from \"@tearleads/crypto\";\nimport { and, eq, inArray, sql } from \"drizzle-orm\";\nimport {\n assertHeldContainerBinding,\n type HeldContainerBinding,\n heldContainerBinding,\n} from \"../../containers/containerBinding\";\nimport { documentSyncPullContinuationsEqual } from \"../../documents/shared/pullContinuation\";\nimport {\n containerHydrationTombstones,\n containerMoveIntents,\n containers,\n dormantContainerMetadata,\n} from \"../../sqlite/schema\";\nimport {\n type ClientSQLiteTransactionScope,\n getClientSQLitePersistenceRuntime,\n} from \"../../sqlite/sqlitePersistenceRuntime\";\nimport { type ExecSql, runSerializedSqlMutation } from \"../../sqlite/sqlSchema\";\nimport { loadContainerById } from \"../containers/containerPersistence\";\nimport { loadStoredAccessManifestCheckpoint } from \"../keyingCheckpointPersistence\";\nimport type {\n ContainerContentsPersistence,\n ContainerHydrationTombstone,\n ContainerMetadataRecord,\n} from \"./containerContentsPersistenceTypes\";\nimport {\n saveContainerContentsContainerRows,\n selectContainerMetadataRecord,\n} from \"./containerMetadataRows\";\nimport { isSupersededContainerBinding } from \"./supersededContainerBindings\";\n\nfunction sameNullableValue(\n left: string | null | undefined,\n right: string | null | undefined,\n): boolean {\n return (left ?? null) === (right ?? null);\n}\n\nfunction sameMetadataRecord(\n current: ContainerMetadataRecord | null,\n expected: ContainerMetadataRecord | null,\n): boolean {\n if (!current || !expected) return current === expected;\n return (\n current.id === expected.id &&\n current.documentId === expected.documentId &&\n current.accessEpoch === expected.accessEpoch &&\n sameNullableValue(current.accessStateHash, expected.accessStateHash) &&\n sameNullableValue(current.lastCommitLsn, expected.lastCommitLsn) &&\n sameNullableValue(current.contentKeyBundle, expected.contentKeyBundle) &&\n sameNullableValue(\n current.documentKekTargets,\n expected.documentKekTargets,\n ) &&\n sameNullableValue(\n current.documentManifestBundle,\n expected.documentManifestBundle,\n ) &&\n current.metadataUpdates === expected.metadataUpdates &&\n current.snapshotEndVersion === expected.snapshotEndVersion &&\n documentSyncPullContinuationsEqual(\n current.pullContinuation,\n expected.pullContinuation,\n ) &&\n Boolean(current.pullContinuationRecoveryRequired) ===\n Boolean(expected.pullContinuationRecoveryRequired)\n );\n}\n\nfunction sameHydrationTombstone(\n current:\n | { generation: number; reason: string; updatedAt: string }\n | undefined,\n expected: ContainerHydrationTombstone | null | undefined,\n): boolean {\n return (\n current !== undefined &&\n expected != null &&\n current.generation === expected.generation &&\n current.reason === expected.reason &&\n current.updatedAt === expected.updatedAt\n );\n}\n\nexport async function recordContainerHydrationTombstones(input: {\n removals: ReadonlyArray<{\n containerId: string;\n reason: \"access_revoked\" | \"deleted\";\n updatedAt: string;\n }>;\n tx: ClientSQLiteTransactionScope;\n}): Promise<void> {\n if (input.removals.length === 0) return;\n await input.tx\n .insert(containerHydrationTombstones)\n .values([...input.removals])\n .onConflictDoUpdate({\n target: containerHydrationTombstones.containerId,\n set: {\n generation: sql`${containerHydrationTombstones.generation} + 1`,\n cleared: false,\n reason: sql`CASE\n WHEN ${containerHydrationTombstones.reason} = 'deleted' THEN 'deleted'\n ELSE excluded.reason\n END`,\n updatedAt: sql`MAX(${containerHydrationTombstones.updatedAt}, excluded.updated_at)`,\n },\n })\n .run();\n}\n\nasync function placementCheckpointMatches(\n tx: ClientSQLiteTransactionScope,\n input: Parameters<ContainerContentsPersistence[\"commitHydratedContainer\"]>[1],\n): Promise<boolean> {\n const expected = input.expectedPlacementCheckpoint;\n if (!expected) return true;\n const current = await loadStoredAccessManifestCheckpoint(tx, expected);\n return (\n expected.objectKind === \"container\" &&\n expected.objectId === input.container.id &&\n expected.organizationId === input.container.organizationId &&\n current?.epoch === expected.epoch &&\n current.manifestHash === expected.manifestHash\n );\n}\n\n/** A retained local move continues to own placement after verified reattachment. */\nasync function saveRecoveredContainer(\n tx: ClientSQLiteTransactionScope,\n input: Parameters<ContainerContentsPersistence[\"commitHydratedContainer\"]>[1],\n) {\n const [move] = await tx\n .select()\n .from(containerMoveIntents)\n .where(\n and(\n eq(containerMoveIntents.containerId, input.container.id),\n inArray(containerMoveIntents.syncStatus, [\"pending\", \"blocked\"]),\n ),\n )\n .limit(1);\n return saveContainerContentsContainerRows({\n container: move\n ? { ...input.container, parentId: move.parentContainerId }\n : input.container,\n localUpdatedAt:\n move?.updatedAt ??\n input.saveOptions.localUpdatedAt ??\n input.remoteUpdatedAt,\n record: input.record,\n serverTimestamps: input.saveOptions.serverTimestamps,\n tx,\n });\n}\n\nasync function loadDormantOrganizationId(\n execSql: ExecSql,\n containerId: string,\n): Promise<string | null> {\n const [row] = await getClientSQLitePersistenceRuntime(execSql)\n .db.select({ organizationId: dormantContainerMetadata.organizationId })\n .from(dormantContainerMetadata)\n .where(eq(dormantContainerMetadata.containerId, containerId))\n .limit(1);\n return row?.organizationId ?? null;\n}\n\n/**\n * The binding this device holds for a container id: its live row, or the\n * organization and metadata target retained after access loss. Null when the\n * device has never held the container.\n */\nexport async function loadStoredHeldContainerBinding(\n execSql: ExecSql,\n containerId: string,\n): Promise<HeldContainerBinding | null> {\n const container = await loadContainerById(execSql, containerId);\n const record = await selectContainerMetadataRecord(execSql, containerId);\n if (container) return heldContainerBinding({ container, record });\n if (!record) return null;\n // Retention records the organization with each retained record; without one\n // the retained metadata target still pins the binding.\n const dormantOrganizationId = await loadDormantOrganizationId(\n execSql,\n containerId,\n );\n return {\n organizationId: dormantOrganizationId ?? \"\",\n metadataDocumentId: record.documentId,\n };\n}\n\nexport async function commitStoredHydratedContainer(\n execSql: Parameters<\n ContainerContentsPersistence[\"commitHydratedContainer\"]\n >[0],\n input: Parameters<ContainerContentsPersistence[\"commitHydratedContainer\"]>[1],\n) {\n return runSerializedSqlMutation(execSql, async (lockedExecSql) => {\n const runtime = getClientSQLitePersistenceRuntime(lockedExecSql);\n const commit = async (tx: ClientSQLiteTransactionScope) => {\n const existingContainers = await tx\n .select({ id: containers.id })\n .from(containers)\n .where(eq(containers.id, input.container.id))\n .limit(1);\n if (existingContainers.length > 0) return { committed: false as const };\n if (!(await placementCheckpointMatches(tx, input)))\n return { committed: false as const };\n\n const fences = await tx\n .select({\n generation: containerHydrationTombstones.generation,\n reason: containerHydrationTombstones.reason,\n updatedAt: containerHydrationTombstones.updatedAt,\n })\n .from(containerHydrationTombstones)\n .where(\n and(\n eq(containerHydrationTombstones.containerId, input.container.id),\n eq(containerHydrationTombstones.cleared, false),\n ),\n )\n .limit(1);\n const fence = fences[0];\n const currentDormantRecord = await selectContainerMetadataRecord(\n lockedExecSql,\n input.container.id,\n );\n if (\n fence &&\n !sameHydrationTombstone(fence, input.expectedHydrationTombstone)\n ) {\n return { committed: false as const };\n }\n if (\n !sameMetadataRecord(currentDormantRecord, input.expectedDormantRecord)\n ) {\n return { committed: false as const };\n }\n if (\n await isSupersededContainerBinding(lockedExecSql, {\n containerId: input.container.id,\n organizationId: input.container.organizationId,\n })\n ) {\n throw new KeyingVerificationError(\n \"object_mismatch\",\n \"container was re-homed away from this organization\",\n );\n }\n // Retained metadata keeps its organization and target; a relisting that\n // names others rolls back rather than replacing retained private edits.\n const dormantOrganizationId = await loadDormantOrganizationId(\n lockedExecSql,\n input.container.id,\n );\n assertHeldContainerBinding(\n {\n organizationId: dormantOrganizationId ?? \"\",\n metadataDocumentId: currentDormantRecord?.documentId ?? null,\n },\n {\n organizationId: input.container.organizationId,\n metadataDocumentIds: [\n input.container.metadataDocumentId,\n input.record.documentId,\n ],\n },\n );\n\n const container = await saveRecoveredContainer(tx, input);\n await tx\n .update(containerHydrationTombstones)\n .set({ cleared: true })\n .where(eq(containerHydrationTombstones.containerId, input.container.id))\n .run();\n return { committed: true as const, container };\n };\n if (!input.stillCurrent) {\n return runtime.transaction(commit, { behavior: \"immediate\" });\n }\n const outcome = await runtime.guardedTransaction(\n commit,\n input.stillCurrent,\n { behavior: \"immediate\" },\n );\n return outcome.result ?? { committed: false as const };\n });\n}\n"]}
1
+ {"version":3,"file":"containerHydrationPersistence.js","sourceRoot":"","sources":["containerHydrationPersistence.ts"],"names":[],"mappings":"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","sourcesContent":["import { KeyingVerificationError } from \"@tearleads/crypto\";\nimport { and, eq, inArray, sql } from \"drizzle-orm\";\nimport {\n assertHeldContainerBinding,\n type HeldContainerBinding,\n heldContainerBinding,\n} from \"../../containers/containerBinding\";\nimport { documentSyncPullContinuationsEqual } from \"../../documents/shared/pullContinuation\";\nimport {\n containerHydrationTombstones,\n containers,\n dormantContainerMetadata,\n inLocalIntentLane,\n LOCAL_INTENT_LANES,\n localIntents,\n} from \"../../sqlite/schema\";\nimport {\n type ClientSQLiteTransactionScope,\n getClientSQLitePersistenceRuntime,\n} from \"../../sqlite/sqlitePersistenceRuntime\";\nimport { type ExecSql, runSerializedSqlMutation } from \"../../sqlite/sqlSchema\";\nimport { loadContainerById } from \"../containers/containerPersistence\";\nimport { loadStoredAccessManifestCheckpoint } from \"../keyingCheckpointPersistence\";\nimport type {\n ContainerContentsPersistence,\n ContainerHydrationTombstone,\n ContainerMetadataRecord,\n} from \"./containerContentsPersistenceTypes\";\nimport {\n saveContainerContentsContainerRows,\n selectContainerMetadataRecord,\n} from \"./containerMetadataRows\";\nimport { isSupersededContainerBinding } from \"./supersededContainerBindings\";\n\nfunction sameNullableValue(\n left: string | null | undefined,\n right: string | null | undefined,\n): boolean {\n return (left ?? null) === (right ?? null);\n}\n\nfunction sameMetadataRecord(\n current: ContainerMetadataRecord | null,\n expected: ContainerMetadataRecord | null,\n): boolean {\n if (!current || !expected) return current === expected;\n return (\n current.id === expected.id &&\n current.documentId === expected.documentId &&\n current.accessEpoch === expected.accessEpoch &&\n sameNullableValue(current.accessStateHash, expected.accessStateHash) &&\n sameNullableValue(current.lastCommitLsn, expected.lastCommitLsn) &&\n sameNullableValue(current.contentKeyBundle, expected.contentKeyBundle) &&\n sameNullableValue(\n current.documentKekTargets,\n expected.documentKekTargets,\n ) &&\n sameNullableValue(\n current.documentManifestBundle,\n expected.documentManifestBundle,\n ) &&\n current.metadataUpdates === expected.metadataUpdates &&\n current.snapshotEndVersion === expected.snapshotEndVersion &&\n documentSyncPullContinuationsEqual(\n current.pullContinuation,\n expected.pullContinuation,\n ) &&\n Boolean(current.pullContinuationRecoveryRequired) ===\n Boolean(expected.pullContinuationRecoveryRequired)\n );\n}\n\nfunction sameHydrationTombstone(\n current:\n | { generation: number; reason: string; updatedAt: string }\n | undefined,\n expected: ContainerHydrationTombstone | null | undefined,\n): boolean {\n return (\n current !== undefined &&\n expected != null &&\n current.generation === expected.generation &&\n current.reason === expected.reason &&\n current.updatedAt === expected.updatedAt\n );\n}\n\nexport async function recordContainerHydrationTombstones(input: {\n removals: ReadonlyArray<{\n containerId: string;\n reason: \"access_revoked\" | \"deleted\";\n updatedAt: string;\n }>;\n tx: ClientSQLiteTransactionScope;\n}): Promise<void> {\n if (input.removals.length === 0) return;\n await input.tx\n .insert(containerHydrationTombstones)\n .values([...input.removals])\n .onConflictDoUpdate({\n target: containerHydrationTombstones.containerId,\n set: {\n generation: sql`${containerHydrationTombstones.generation} + 1`,\n cleared: false,\n reason: sql`CASE\n WHEN ${containerHydrationTombstones.reason} = 'deleted' THEN 'deleted'\n ELSE excluded.reason\n END`,\n updatedAt: sql`MAX(${containerHydrationTombstones.updatedAt}, excluded.updated_at)`,\n },\n })\n .run();\n}\n\nasync function placementCheckpointMatches(\n tx: ClientSQLiteTransactionScope,\n input: Parameters<ContainerContentsPersistence[\"commitHydratedContainer\"]>[1],\n): Promise<boolean> {\n const expected = input.expectedPlacementCheckpoint;\n if (!expected) return true;\n const current = await loadStoredAccessManifestCheckpoint(tx, expected);\n return (\n expected.objectKind === \"container\" &&\n expected.objectId === input.container.id &&\n expected.organizationId === input.container.organizationId &&\n current?.epoch === expected.epoch &&\n current.manifestHash === expected.manifestHash\n );\n}\n\n/** A retained local move continues to own placement after verified reattachment. */\nasync function saveRecoveredContainer(\n tx: ClientSQLiteTransactionScope,\n input: Parameters<ContainerContentsPersistence[\"commitHydratedContainer\"]>[1],\n) {\n const [move] = await tx\n .select({\n parentContainerId: localIntents.targetContainerId,\n updatedAt: localIntents.updatedAt,\n })\n .from(localIntents)\n .where(\n and(\n inLocalIntentLane(LOCAL_INTENT_LANES.containerMove),\n eq(localIntents.subjectId, input.container.id),\n inArray(localIntents.syncStatus, [\"pending\", \"blocked\"]),\n ),\n )\n .limit(1);\n return saveContainerContentsContainerRows({\n container: move\n ? { ...input.container, parentId: move.parentContainerId }\n : input.container,\n localUpdatedAt:\n move?.updatedAt ??\n input.saveOptions.localUpdatedAt ??\n input.remoteUpdatedAt,\n record: input.record,\n serverTimestamps: input.saveOptions.serverTimestamps,\n tx,\n });\n}\n\nasync function loadDormantOrganizationId(\n execSql: ExecSql,\n containerId: string,\n): Promise<string | null> {\n const [row] = await getClientSQLitePersistenceRuntime(execSql)\n .db.select({ organizationId: dormantContainerMetadata.organizationId })\n .from(dormantContainerMetadata)\n .where(eq(dormantContainerMetadata.containerId, containerId))\n .limit(1);\n return row?.organizationId ?? null;\n}\n\n/**\n * The binding this device holds for a container id: its live row, or the\n * organization and metadata target retained after access loss. Null when the\n * device has never held the container.\n */\nexport async function loadStoredHeldContainerBinding(\n execSql: ExecSql,\n containerId: string,\n): Promise<HeldContainerBinding | null> {\n const container = await loadContainerById(execSql, containerId);\n const record = await selectContainerMetadataRecord(execSql, containerId);\n if (container) return heldContainerBinding({ container, record });\n if (!record) return null;\n // Retention records the organization with each retained record; without one\n // the retained metadata target still pins the binding.\n const dormantOrganizationId = await loadDormantOrganizationId(\n execSql,\n containerId,\n );\n return {\n organizationId: dormantOrganizationId ?? \"\",\n metadataDocumentId: record.documentId,\n };\n}\n\nexport async function commitStoredHydratedContainer(\n execSql: Parameters<\n ContainerContentsPersistence[\"commitHydratedContainer\"]\n >[0],\n input: Parameters<ContainerContentsPersistence[\"commitHydratedContainer\"]>[1],\n) {\n return runSerializedSqlMutation(execSql, async (lockedExecSql) => {\n const runtime = getClientSQLitePersistenceRuntime(lockedExecSql);\n const commit = async (tx: ClientSQLiteTransactionScope) => {\n const existingContainers = await tx\n .select({ id: containers.id })\n .from(containers)\n .where(eq(containers.id, input.container.id))\n .limit(1);\n if (existingContainers.length > 0) return { committed: false as const };\n if (!(await placementCheckpointMatches(tx, input)))\n return { committed: false as const };\n\n const fences = await tx\n .select({\n generation: containerHydrationTombstones.generation,\n reason: containerHydrationTombstones.reason,\n updatedAt: containerHydrationTombstones.updatedAt,\n })\n .from(containerHydrationTombstones)\n .where(\n and(\n eq(containerHydrationTombstones.containerId, input.container.id),\n eq(containerHydrationTombstones.cleared, false),\n ),\n )\n .limit(1);\n const fence = fences[0];\n const currentDormantRecord = await selectContainerMetadataRecord(\n lockedExecSql,\n input.container.id,\n );\n if (\n fence &&\n !sameHydrationTombstone(fence, input.expectedHydrationTombstone)\n ) {\n return { committed: false as const };\n }\n if (\n !sameMetadataRecord(currentDormantRecord, input.expectedDormantRecord)\n ) {\n return { committed: false as const };\n }\n if (\n await isSupersededContainerBinding(lockedExecSql, {\n containerId: input.container.id,\n organizationId: input.container.organizationId,\n })\n ) {\n throw new KeyingVerificationError(\n \"object_mismatch\",\n \"container was re-homed away from this organization\",\n );\n }\n // Retained metadata keeps its organization and target; a relisting that\n // names others rolls back rather than replacing retained private edits.\n const dormantOrganizationId = await loadDormantOrganizationId(\n lockedExecSql,\n input.container.id,\n );\n assertHeldContainerBinding(\n {\n organizationId: dormantOrganizationId ?? \"\",\n metadataDocumentId: currentDormantRecord?.documentId ?? null,\n },\n {\n organizationId: input.container.organizationId,\n metadataDocumentIds: [\n input.container.metadataDocumentId,\n input.record.documentId,\n ],\n },\n );\n\n const container = await saveRecoveredContainer(tx, input);\n await tx\n .update(containerHydrationTombstones)\n .set({ cleared: true })\n .where(eq(containerHydrationTombstones.containerId, input.container.id))\n .run();\n return { committed: true as const, container };\n };\n if (!input.stillCurrent) {\n return runtime.transaction(commit, { behavior: \"immediate\" });\n }\n const outcome = await runtime.guardedTransaction(\n commit,\n input.stillCurrent,\n { behavior: \"immediate\" },\n );\n return outcome.result ?? { committed: false as const };\n });\n}\n"]}
@@ -1,5 +1,5 @@
1
1
  import { and, asc, eq, inArray } from "drizzle-orm";
2
- import { containerCreateIntents, containerMoveIntents, } from "../../sqlite/schema.js";
2
+ import { inLocalIntentLane, LOCAL_INTENT_LANES, LOCAL_INTENT_SUBJECT, localIntents, } from "../../sqlite/schema.js";
3
3
  import { getClientSQLitePersistenceRuntime, } from "../../sqlite/sqlitePersistenceRuntime.js";
4
4
  import { CONTAINER_CREATE_INTENT_TYPE, CONTAINER_MOVE_INTENT_TYPE, } from "./containerContentsPersistenceTypes.js";
5
5
  import { settleContainerCreateIntentRevision } from "./containerCreateIntentSettlement.js";
@@ -43,11 +43,12 @@ export async function saveContainerCreateIntent(input) {
43
43
  const { containerId, createIntent, tx, updatedAt } = input;
44
44
  const id = createIntent.id ?? crypto.randomUUID();
45
45
  await tx
46
- .insert(containerCreateIntents)
46
+ .insert(localIntents)
47
47
  .values({
48
48
  id,
49
- containerId,
50
- parentContainerId: createIntent.parentContainerId,
49
+ lane: LOCAL_INTENT_LANES.containerCreate,
50
+ subjectId: containerId,
51
+ targetContainerId: createIntent.parentContainerId,
51
52
  intentType: CONTAINER_CREATE_INTENT_TYPE,
52
53
  syncStatus: "pending",
53
54
  remoteContainerId: null,
@@ -59,12 +60,12 @@ export async function saveContainerCreateIntent(input) {
59
60
  updatedAt,
60
61
  })
61
62
  .onConflictDoUpdate({
62
- target: containerCreateIntents.containerId,
63
+ target: LOCAL_INTENT_SUBJECT,
63
64
  set: {
64
65
  // Timestamps can collide when a create is re-queued in one clock tick,
65
66
  // so every enqueue owns a fresh revision token too.
66
67
  id,
67
- parentContainerId: createIntent.parentContainerId,
68
+ targetContainerId: createIntent.parentContainerId,
68
69
  intentType: CONTAINER_CREATE_INTENT_TYPE,
69
70
  syncStatus: "pending",
70
71
  remoteContainerId: null,
@@ -84,9 +85,9 @@ export class ContainerCreateIntentSupersededError extends Error {
84
85
  }
85
86
  export async function deleteContainerMoveIntentRevision(input) {
86
87
  const deleted = await input.tx
87
- .delete(containerMoveIntents)
88
- .where(and(eq(containerMoveIntents.containerId, input.containerId), eq(containerMoveIntents.intentType, CONTAINER_MOVE_INTENT_TYPE), eq(containerMoveIntents.id, input.expectedIntentId), eq(containerMoveIntents.updatedAt, input.expectedUpdatedAt)))
89
- .returning({ containerId: containerMoveIntents.containerId });
88
+ .delete(localIntents)
89
+ .where(and(inLocalIntentLane(LOCAL_INTENT_LANES.containerMove), eq(localIntents.subjectId, input.containerId), eq(localIntents.intentType, CONTAINER_MOVE_INTENT_TYPE), eq(localIntents.id, input.expectedIntentId), eq(localIntents.updatedAt, input.expectedUpdatedAt)))
90
+ .returning({ containerId: localIntents.subjectId });
90
91
  return deleted.length > 0;
91
92
  }
92
93
  async function recordCreateIntentErrorAttempt(execSql, input) {
@@ -97,13 +98,13 @@ async function recordCreateIntentErrorAttempt(execSql, input) {
97
98
  const record = async (tx) => {
98
99
  const updatedAt = new Date().toISOString();
99
100
  await tx
100
- .update(containerCreateIntents)
101
+ .update(localIntents)
101
102
  .set({
102
103
  lastAttemptedAt: updatedAt,
103
104
  lastError: message,
104
105
  updatedAt,
105
106
  })
106
- .where(and(eq(containerCreateIntents.containerId, containerId), eq(containerCreateIntents.syncStatus, "pending"), eq(containerCreateIntents.intentType, CONTAINER_CREATE_INTENT_TYPE), eq(containerCreateIntents.id, expectedIntentId), eq(containerCreateIntents.updatedAt, expectedUpdatedAt)))
107
+ .where(and(inLocalIntentLane(LOCAL_INTENT_LANES.containerCreate), eq(localIntents.subjectId, containerId), eq(localIntents.syncStatus, "pending"), eq(localIntents.intentType, CONTAINER_CREATE_INTENT_TYPE), eq(localIntents.id, expectedIntentId), eq(localIntents.updatedAt, expectedUpdatedAt)))
107
108
  .run();
108
109
  };
109
110
  if (stillCurrent) {
@@ -129,21 +130,21 @@ export const containerIntentPersistence = {
129
130
  const { db } = getClientSQLitePersistenceRuntime(execSql);
130
131
  const rows = await db
131
132
  .select({
132
- id: containerCreateIntents.id,
133
- containerId: containerCreateIntents.containerId,
134
- parentContainerId: containerCreateIntents.parentContainerId,
135
- syncStatus: containerCreateIntents.syncStatus,
136
- remoteContainerId: containerCreateIntents.remoteContainerId,
137
- remoteMetadataDocumentId: containerCreateIntents.remoteMetadataDocumentId,
138
- remoteMetadataAccessStateHash: containerCreateIntents.remoteMetadataAccessStateHash,
139
- lastError: containerCreateIntents.lastError,
140
- lastAttemptedAt: containerCreateIntents.lastAttemptedAt,
141
- createdAt: containerCreateIntents.createdAt,
142
- updatedAt: containerCreateIntents.updatedAt,
133
+ id: localIntents.id,
134
+ containerId: localIntents.subjectId,
135
+ parentContainerId: localIntents.targetContainerId,
136
+ syncStatus: localIntents.syncStatus,
137
+ remoteContainerId: localIntents.remoteContainerId,
138
+ remoteMetadataDocumentId: localIntents.remoteMetadataDocumentId,
139
+ remoteMetadataAccessStateHash: localIntents.remoteMetadataAccessStateHash,
140
+ lastError: localIntents.lastError,
141
+ lastAttemptedAt: localIntents.lastAttemptedAt,
142
+ createdAt: localIntents.createdAt,
143
+ updatedAt: localIntents.updatedAt,
143
144
  })
144
- .from(containerCreateIntents)
145
- .where(and(eq(containerCreateIntents.syncStatus, "pending"), eq(containerCreateIntents.intentType, CONTAINER_CREATE_INTENT_TYPE)))
146
- .orderBy(asc(containerCreateIntents.createdAt));
145
+ .from(localIntents)
146
+ .where(and(inLocalIntentLane(LOCAL_INTENT_LANES.containerCreate), eq(localIntents.syncStatus, "pending"), eq(localIntents.intentType, CONTAINER_CREATE_INTENT_TYPE)))
147
+ .orderBy(asc(localIntents.createdAt));
147
148
  return rows.map((row) => mapContainerCreateIntentRecord(row));
148
149
  },
149
150
  async listUnsyncedMoveIntents(execSql) {
@@ -155,19 +156,19 @@ export const containerIntentPersistence = {
155
156
  // the move completes — and hydration must still not revert them.
156
157
  const rows = await db
157
158
  .select({
158
- id: containerMoveIntents.id,
159
- containerId: containerMoveIntents.containerId,
160
- parentContainerId: containerMoveIntents.parentContainerId,
161
- previousParentContainerId: containerMoveIntents.previousParentContainerId,
162
- syncStatus: containerMoveIntents.syncStatus,
163
- lastError: containerMoveIntents.lastError,
164
- lastAttemptedAt: containerMoveIntents.lastAttemptedAt,
165
- createdAt: containerMoveIntents.createdAt,
166
- updatedAt: containerMoveIntents.updatedAt,
159
+ id: localIntents.id,
160
+ containerId: localIntents.subjectId,
161
+ parentContainerId: localIntents.targetContainerId,
162
+ previousParentContainerId: localIntents.sourceContainerId,
163
+ syncStatus: localIntents.syncStatus,
164
+ lastError: localIntents.lastError,
165
+ lastAttemptedAt: localIntents.lastAttemptedAt,
166
+ createdAt: localIntents.createdAt,
167
+ updatedAt: localIntents.updatedAt,
167
168
  })
168
- .from(containerMoveIntents)
169
- .where(eq(containerMoveIntents.intentType, CONTAINER_MOVE_INTENT_TYPE))
170
- .orderBy(asc(containerMoveIntents.createdAt));
169
+ .from(localIntents)
170
+ .where(and(inLocalIntentLane(LOCAL_INTENT_LANES.containerMove), eq(localIntents.intentType, CONTAINER_MOVE_INTENT_TYPE)))
171
+ .orderBy(asc(localIntents.createdAt));
171
172
  return rows.map((row) => mapContainerMoveIntentRecord(row));
172
173
  },
173
174
  recordCreateIntentRevisionError: (execSql, input) => recordCreateIntentErrorAttempt(execSql, input),
@@ -176,20 +177,20 @@ export const containerIntentPersistence = {
176
177
  const record = async (tx) => {
177
178
  const updatedAt = new Date().toISOString();
178
179
  await tx
179
- .update(containerMoveIntents)
180
+ .update(localIntents)
180
181
  .set({
181
182
  lastAttemptedAt: updatedAt,
182
183
  lastError: input.message,
183
184
  syncStatus: input.blocked ? "blocked" : "pending",
184
185
  updatedAt,
185
186
  })
186
- .where(and(eq(containerMoveIntents.containerId, input.containerId),
187
+ .where(and(inLocalIntentLane(LOCAL_INTENT_LANES.containerMove), eq(localIntents.subjectId, input.containerId),
187
188
  // Blocked rows stay updatable so a retried intent records its
188
189
  // fresh outcome and a transient failure unblocks it.
189
- inArray(containerMoveIntents.syncStatus, ["pending", "blocked"]), eq(containerMoveIntents.intentType, CONTAINER_MOVE_INTENT_TYPE), ...(input.expectedIntentId
190
- ? [eq(containerMoveIntents.id, input.expectedIntentId)]
190
+ inArray(localIntents.syncStatus, ["pending", "blocked"]), eq(localIntents.intentType, CONTAINER_MOVE_INTENT_TYPE), ...(input.expectedIntentId
191
+ ? [eq(localIntents.id, input.expectedIntentId)]
191
192
  : []), ...(input.expectedUpdatedAt
192
- ? [eq(containerMoveIntents.updatedAt, input.expectedUpdatedAt)]
193
+ ? [eq(localIntents.updatedAt, input.expectedUpdatedAt)]
193
194
  : [])))
194
195
  .run();
195
196
  };