@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":"containerRecoveryPersistence.js","sourceRoot":"","sources":["containerRecoveryPersistence.ts"],"names":[],"mappings":"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","sourcesContent":["import { toFingerprint } from \"@tearleads/crypto\";\nimport { eq } from \"drizzle-orm\";\nimport {\n containerCreateIntents,\n containerMoveIntents,\n documentMoveIntentTables,\n} from \"../../sqlite/schema\";\nimport { getClientSQLitePersistenceRuntime } from \"../../sqlite/sqlitePersistenceRuntime\";\nimport {\n type ExecSql,\n ensureSqlTables,\n runSerializedSqlMutation,\n} from \"../../sqlite/sqlSchema\";\nimport { sqlContainerContentsPersistence } from \"./containerContentsPersistence\";\nimport { recordContainerHydrationTombstones } from \"./containerHydrationPersistence\";\nimport { repairDocumentsForRemovedContainersInTransaction } from \"./containerStructuralRepair\";\nimport { discardLinkIntentsForRemovedContainers } from \"./documentLinkRemovalRepair\";\nimport { deleteContainerMetadataDocumentRowsInTransaction } from \"./dormantContainerMetadata\";\n\n// The token covers all local work the user is agreeing to discard, including\n// shared link intents. A second pane's edit invalidates the confirmation.\nasync function recoverySnapshot(\n execSql: ExecSql,\n containerId: string,\n organizationId: string,\n) {\n const [dormant] = await execSql(\n `SELECT container_id, organization_id FROM dormant_container_metadata\n WHERE container_id = ? AND organization_id = ?\n AND NOT EXISTS (SELECT 1 FROM containers WHERE id = ?)`,\n [containerId, organizationId, containerId],\n );\n if (!dormant) return null;\n const metadata = [];\n for (const [table, columns] of [\n [\n \"documents\",\n \"document_id, recovery_document_id, recovery_generation, snapshot_end_version, pending_base_version\",\n ],\n [\"document_history_checkpoints\", \"snapshot, end_version_vector\"],\n [\n \"document_history_updates\",\n \"id, update_data, origin, update_id, partial_start_version_vector, partial_end_version_vector, source_version_vector\",\n ],\n ]) {\n metadata.push(\n await execSql(\n `SELECT ${columns} FROM ${table} WHERE app_kind = 'container-metadata' AND local_id = ? ORDER BY rowid`,\n [containerId],\n ),\n );\n }\n const creates = await execSql(\n \"SELECT id, container_id, parent_container_id, intent_type, remote_container_id, remote_metadata_document_id, remote_metadata_access_state_hash FROM container_create_intents WHERE container_id = ? AND sync_status = 'pending' ORDER BY id\",\n [containerId],\n );\n const moves = await execSql(\n \"SELECT id, container_id, parent_container_id, previous_parent_container_id, intent_type FROM container_move_intents WHERE container_id = ? ORDER BY id\",\n [containerId],\n );\n const links = await execSql(\n `SELECT id, local_id, document_id, target_container_id, source_container_id, replace_linked_containers, intent_type FROM document_move_intents WHERE target_container_id = ? OR id IN\n (SELECT intent_id FROM document_intent_link_targets WHERE container_id = ?) ORDER BY id`,\n [containerId, containerId],\n );\n const targets = await execSql(\n `SELECT intent_id, operation, container_id FROM document_intent_link_targets WHERE intent_id IN\n (SELECT id FROM document_move_intents WHERE target_container_id = ? OR id IN\n (SELECT intent_id FROM document_intent_link_targets WHERE container_id = ?)) ORDER BY intent_id, container_id, operation`,\n [containerId, containerId],\n );\n const revision = await toFingerprint(\n new TextEncoder().encode(\n JSON.stringify([dormant, metadata, creates, moves, links, targets]),\n ),\n );\n return {\n revision,\n pendingUpdateCount: (metadata[2] ?? []).filter(\n (row) => Reflect.get(row, \"update_id\") !== null,\n ).length,\n hasStructuralIntent: creates.length + moves.length + links.length > 0,\n };\n}\n\nexport async function listRetainedContainerMetadata(\n execSql: ExecSql,\n organizationId: string | null,\n) {\n if (!organizationId) return [];\n await sqlContainerContentsPersistence.ensureSchema(execSql);\n await ensureSqlTables(execSql, documentMoveIntentTables);\n return runSerializedSqlMutation(execSql, async (locked) =>\n getClientSQLitePersistenceRuntime(locked).transaction(async () => {\n const rows = await locked(\n `SELECT container_id FROM dormant_container_metadata\n WHERE organization_id = ? ORDER BY container_id`,\n [organizationId],\n );\n const result = [];\n for (const row of rows) {\n const containerId = String(Reflect.get(row, \"container_id\"));\n const snapshot = await recoverySnapshot(\n locked,\n containerId,\n organizationId,\n );\n if (!snapshot) continue;\n const record =\n await sqlContainerContentsPersistence.loadContainerMetadataRecord(\n locked,\n containerId,\n );\n result.push({\n containerId,\n organizationId,\n ...snapshot,\n metadataUpdates: record?.metadataUpdates ?? \"\",\n });\n }\n return result;\n }),\n );\n}\n\n/** Explicit local discard, never called by remote discovery. */\nexport async function discardRetainedContainerMetadata(\n execSql: ExecSql,\n input: {\n containerId: string;\n organizationId: string;\n revision: string;\n },\n): Promise<boolean> {\n await sqlContainerContentsPersistence.ensureSchema(execSql);\n await ensureSqlTables(execSql, documentMoveIntentTables);\n return runSerializedSqlMutation(execSql, async (locked) =>\n getClientSQLitePersistenceRuntime(locked).transaction(\n async (tx) => {\n const current = await recoverySnapshot(\n locked,\n input.containerId,\n input.organizationId,\n );\n if (!current || current.revision !== input.revision) return false;\n await recordContainerHydrationTombstones({\n tx,\n removals: [\n {\n containerId: input.containerId,\n reason: \"deleted\",\n updatedAt: new Date().toISOString(),\n },\n ],\n });\n await repairDocumentsForRemovedContainersInTransaction({\n tx,\n removals: [\n {\n containerId: input.containerId,\n reason: \"deleted\",\n updatedAt: new Date().toISOString(),\n },\n ],\n });\n await discardLinkIntentsForRemovedContainers({\n tx,\n containerIds: [input.containerId],\n });\n await tx\n .delete(containerCreateIntents)\n .where(eq(containerCreateIntents.containerId, input.containerId))\n .run();\n await tx\n .delete(containerMoveIntents)\n .where(eq(containerMoveIntents.containerId, input.containerId))\n .run();\n await deleteContainerMetadataDocumentRowsInTransaction(tx, [\n input.containerId,\n ]);\n return true;\n },\n { behavior: \"immediate\" },\n ),\n );\n}\n\nexport async function hasRetainedContainerMetadata(\n execSql: ExecSql,\n organizationId: string | null,\n): Promise<boolean> {\n if (!organizationId) return false;\n await sqlContainerContentsPersistence.ensureSchema(execSql);\n const rows = await execSql(\n `SELECT 1 FROM dormant_container_metadata d\n WHERE d.organization_id = ? AND NOT EXISTS (SELECT 1 FROM containers c WHERE c.id = d.container_id) LIMIT 1`,\n [organizationId],\n );\n return rows.length > 0;\n}\n\n/** Existing local folders whose queued move points at an unavailable parent. */\nexport async function listRecoveryFolderMoveIds(\n execSql: ExecSql,\n organizationId: string | null,\n): Promise<string[]> {\n if (!organizationId) return [];\n await sqlContainerContentsPersistence.ensureSchema(execSql);\n const rows = await execSql(\n `SELECT c.id FROM containers c JOIN container_move_intents i\n ON i.container_id = c.id AND i.parent_container_id = c.parent_id\n WHERE c.organization_id = ? AND i.sync_status IN ('pending', 'blocked')\n AND NOT EXISTS (SELECT 1 FROM containers p WHERE p.id = c.parent_id)\n ORDER BY c.id`,\n [organizationId],\n );\n return rows.map((row) => String(Reflect.get(row, \"id\")));\n}\n"]}
1
+ {"version":3,"file":"containerRecoveryPersistence.js","sourceRoot":"","sources":["containerRecoveryPersistence.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,MAAM,mBAAmB,CAAC;AAClD,OAAO,EAAE,GAAG,EAAE,EAAE,EAAE,MAAM,aAAa,CAAC;AACtC,OAAO,EACL,6BAA6B,EAC7B,sBAAsB,EACtB,kBAAkB,EAClB,YAAY,GACb,MAAM,qBAAqB,CAAC;AAC7B,OAAO,EAAE,iCAAiC,EAAE,MAAM,uCAAuC,CAAC;AAC1F,OAAO,EAEL,eAAe,EACf,wBAAwB,GACzB,MAAM,wBAAwB,CAAC;AAChC,OAAO,EAAE,+BAA+B,EAAE,MAAM,gCAAgC,CAAC;AACjF,OAAO,EAAE,kCAAkC,EAAE,MAAM,iCAAiC,CAAC;AACrF,OAAO,EAAE,gDAAgD,EAAE,MAAM,6BAA6B,CAAC;AAC/F,OAAO,EAAE,sCAAsC,EAAE,MAAM,6BAA6B,CAAC;AACrF,OAAO,EAAE,gDAAgD,EAAE,MAAM,4BAA4B,CAAC;AAE9F,6EAA6E;AAC7E,0EAA0E;AAC1E,KAAK,UAAU,gBAAgB,CAC7B,OAAgB,EAChB,WAAmB,EACnB,cAAsB;IAEtB,MAAM,CAAC,OAAO,CAAC,GAAG,MAAM,OAAO,CAC7B;;2DAEuD,EACvD,CAAC,WAAW,EAAE,cAAc,EAAE,WAAW,CAAC,CAC3C,CAAC;IACF,IAAI,CAAC,OAAO;QAAE,OAAO,IAAI,CAAC;IAC1B,MAAM,QAAQ,GAAG,EAAE,CAAC;IACpB,KAAK,MAAM,CAAC,KAAK,EAAE,OAAO,CAAC,IAAI;QAC7B;YACE,WAAW;YACX,oGAAoG;SACrG;QACD,CAAC,8BAA8B,EAAE,8BAA8B,CAAC;QAChE;YACE,0BAA0B;YAC1B,qHAAqH;SACtH;KACF,EAAE,CAAC;QACF,QAAQ,CAAC,IAAI,CACX,MAAM,OAAO,CACX,UAAU,OAAO,SAAS,KAAK,wEAAwE,EACvG,CAAC,WAAW,CAAC,CACd,CACF,CAAC;IACJ,CAAC;IACD,MAAM,OAAO,GAAG,MAAM,OAAO,CAC3B,2OAA2O,EAC3O,CAAC,kBAAkB,CAAC,eAAe,EAAE,WAAW,CAAC,CAClD,CAAC;IACF,MAAM,KAAK,GAAG,MAAM,OAAO,CACzB,+IAA+I,EAC/I,CAAC,kBAAkB,CAAC,aAAa,EAAE,WAAW,CAAC,CAChD,CAAC;IACF,MAAM,cAAc,GAAG;iFACwD,CAAC;IAChF,MAAM,aAAa,GAAG;QACpB,kBAAkB,CAAC,iBAAiB;QACpC,WAAW;QACX,WAAW;KACZ,CAAC;IACF,MAAM,KAAK,GAAG,MAAM,OAAO,CACzB,8IAA8I,cAAc,cAAc,EAC1K,aAAa,CACd,CAAC;IACF,MAAM,OAAO,GAAG,MAAM,OAAO,CAC3B;0CACsC,cAAc,+CAA+C,EACnG,aAAa,CACd,CAAC;IACF,MAAM,QAAQ,GAAG,MAAM,aAAa,CAClC,IAAI,WAAW,EAAE,CAAC,MAAM,CACtB,IAAI,CAAC,SAAS,CAAC,CAAC,OAAO,EAAE,QAAQ,EAAE,OAAO,EAAE,KAAK,EAAE,KAAK,EAAE,OAAO,CAAC,CAAC,CACpE,CACF,CAAC;IACF,OAAO;QACL,QAAQ;QACR,kBAAkB,EAAE,CAAC,QAAQ,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC,MAAM,CAC5C,CAAC,GAAG,EAAE,EAAE,CAAC,OAAO,CAAC,GAAG,CAAC,GAAG,EAAE,WAAW,CAAC,KAAK,IAAI,CAChD,CAAC,MAAM;QACR,mBAAmB,EAAE,OAAO,CAAC,MAAM,GAAG,KAAK,CAAC,MAAM,GAAG,KAAK,CAAC,MAAM,GAAG,CAAC;KACtE,CAAC;AACJ,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,6BAA6B,CACjD,OAAgB,EAChB,cAA6B;IAE7B,IAAI,CAAC,cAAc;QAAE,OAAO,EAAE,CAAC;IAC/B,MAAM,+BAA+B,CAAC,YAAY,CAAC,OAAO,CAAC,CAAC;IAC5D,MAAM,eAAe,CAAC,OAAO,EAAE,6BAA6B,CAAC,CAAC;IAC9D,OAAO,wBAAwB,CAAC,OAAO,EAAE,KAAK,EAAE,MAAM,EAAE,EAAE,CACxD,iCAAiC,CAAC,MAAM,CAAC,CAAC,WAAW,CAAC,KAAK,IAAI,EAAE;QAC/D,MAAM,IAAI,GAAG,MAAM,MAAM,CACvB;sDAC8C,EAC9C,CAAC,cAAc,CAAC,CACjB,CAAC;QACF,MAAM,MAAM,GAAG,EAAE,CAAC;QAClB,KAAK,MAAM,GAAG,IAAI,IAAI,EAAE,CAAC;YACvB,MAAM,WAAW,GAAG,MAAM,CAAC,OAAO,CAAC,GAAG,CAAC,GAAG,EAAE,cAAc,CAAC,CAAC,CAAC;YAC7D,MAAM,QAAQ,GAAG,MAAM,gBAAgB,CACrC,MAAM,EACN,WAAW,EACX,cAAc,CACf,CAAC;YACF,IAAI,CAAC,QAAQ;gBAAE,SAAS;YACxB,MAAM,MAAM,GACV,MAAM,+BAA+B,CAAC,2BAA2B,CAC/D,MAAM,EACN,WAAW,CACZ,CAAC;YACJ,MAAM,CAAC,IAAI,CAAC;gBACV,WAAW;gBACX,cAAc;gBACd,GAAG,QAAQ;gBACX,eAAe,EAAE,MAAM,EAAE,eAAe,IAAI,EAAE;aAC/C,CAAC,CAAC;QACL,CAAC;QACD,OAAO,MAAM,CAAC;IAChB,CAAC,CAAC,CACH,CAAC;AACJ,CAAC;AAED,gEAAgE;AAChE,MAAM,CAAC,KAAK,UAAU,gCAAgC,CACpD,OAAgB,EAChB,KAIC;IAED,MAAM,+BAA+B,CAAC,YAAY,CAAC,OAAO,CAAC,CAAC;IAC5D,MAAM,eAAe,CAAC,OAAO,EAAE,6BAA6B,CAAC,CAAC;IAC9D,OAAO,wBAAwB,CAAC,OAAO,EAAE,KAAK,EAAE,MAAM,EAAE,EAAE,CACxD,iCAAiC,CAAC,MAAM,CAAC,CAAC,WAAW,CACnD,KAAK,EAAE,EAAE,EAAE,EAAE;QACX,MAAM,OAAO,GAAG,MAAM,gBAAgB,CACpC,MAAM,EACN,KAAK,CAAC,WAAW,EACjB,KAAK,CAAC,cAAc,CACrB,CAAC;QACF,IAAI,CAAC,OAAO,IAAI,OAAO,CAAC,QAAQ,KAAK,KAAK,CAAC,QAAQ;YAAE,OAAO,KAAK,CAAC;QAClE,MAAM,kCAAkC,CAAC;YACvC,EAAE;YACF,QAAQ,EAAE;gBACR;oBACE,WAAW,EAAE,KAAK,CAAC,WAAW;oBAC9B,MAAM,EAAE,SAAS;oBACjB,SAAS,EAAE,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE;iBACpC;aACF;SACF,CAAC,CAAC;QACH,MAAM,gDAAgD,CAAC;YACrD,EAAE;YACF,QAAQ,EAAE;gBACR;oBACE,WAAW,EAAE,KAAK,CAAC,WAAW;oBAC9B,MAAM,EAAE,SAAS;oBACjB,SAAS,EAAE,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE;iBACpC;aACF;SACF,CAAC,CAAC;QACH,MAAM,sCAAsC,CAAC;YAC3C,EAAE;YACF,YAAY,EAAE,CAAC,KAAK,CAAC,WAAW,CAAC;SAClC,CAAC,CAAC;QACH,MAAM,EAAE;aACL,MAAM,CAAC,YAAY,CAAC;aACpB,KAAK,CACJ,GAAG,CACD,sBAAsB,EAAE,EACxB,EAAE,CAAC,YAAY,CAAC,SAAS,EAAE,KAAK,CAAC,WAAW,CAAC,CAC9C,CACF;aACA,GAAG,EAAE,CAAC;QACT,MAAM,gDAAgD,CAAC,EAAE,EAAE;YACzD,KAAK,CAAC,WAAW;SAClB,CAAC,CAAC;QACH,OAAO,IAAI,CAAC;IACd,CAAC,EACD,EAAE,QAAQ,EAAE,WAAW,EAAE,CAC1B,CACF,CAAC;AACJ,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,4BAA4B,CAChD,OAAgB,EAChB,cAA6B;IAE7B,IAAI,CAAC,cAAc;QAAE,OAAO,KAAK,CAAC;IAClC,MAAM,+BAA+B,CAAC,YAAY,CAAC,OAAO,CAAC,CAAC;IAC5D,MAAM,IAAI,GAAG,MAAM,OAAO,CACxB;gHAC4G,EAC5G,CAAC,cAAc,CAAC,CACjB,CAAC;IACF,OAAO,IAAI,CAAC,MAAM,GAAG,CAAC,CAAC;AACzB,CAAC;AAED,gFAAgF;AAChF,MAAM,CAAC,KAAK,UAAU,yBAAyB,CAC7C,OAAgB,EAChB,cAA6B;IAE7B,IAAI,CAAC,cAAc;QAAE,OAAO,EAAE,CAAC;IAC/B,MAAM,+BAA+B,CAAC,YAAY,CAAC,OAAO,CAAC,CAAC;IAC5D,MAAM,IAAI,GAAG,MAAM,OAAO,CACxB;;;;kBAIc,EACd,CAAC,kBAAkB,CAAC,aAAa,EAAE,cAAc,CAAC,CACnD,CAAC;IACF,OAAO,IAAI,CAAC,GAAG,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC,MAAM,CAAC,OAAO,CAAC,GAAG,CAAC,GAAG,EAAE,IAAI,CAAC,CAAC,CAAC,CAAC;AAC3D,CAAC","sourcesContent":["import { toFingerprint } from \"@tearleads/crypto\";\nimport { and, eq } from \"drizzle-orm\";\nimport {\n documentPlacementIntentTables,\n inContainerIntentLanes,\n LOCAL_INTENT_LANES,\n localIntents,\n} from \"../../sqlite/schema\";\nimport { getClientSQLitePersistenceRuntime } from \"../../sqlite/sqlitePersistenceRuntime\";\nimport {\n type ExecSql,\n ensureSqlTables,\n runSerializedSqlMutation,\n} from \"../../sqlite/sqlSchema\";\nimport { sqlContainerContentsPersistence } from \"./containerContentsPersistence\";\nimport { recordContainerHydrationTombstones } from \"./containerHydrationPersistence\";\nimport { repairDocumentsForRemovedContainersInTransaction } from \"./containerStructuralRepair\";\nimport { discardLinkIntentsForRemovedContainers } from \"./documentLinkRemovalRepair\";\nimport { deleteContainerMetadataDocumentRowsInTransaction } from \"./dormantContainerMetadata\";\n\n// The token covers all local work the user is agreeing to discard, including\n// shared link intents. A second pane's edit invalidates the confirmation.\nasync function recoverySnapshot(\n execSql: ExecSql,\n containerId: string,\n organizationId: string,\n) {\n const [dormant] = await execSql(\n `SELECT container_id, organization_id FROM dormant_container_metadata\n WHERE container_id = ? AND organization_id = ?\n AND NOT EXISTS (SELECT 1 FROM containers WHERE id = ?)`,\n [containerId, organizationId, containerId],\n );\n if (!dormant) return null;\n const metadata = [];\n for (const [table, columns] of [\n [\n \"documents\",\n \"document_id, recovery_document_id, recovery_generation, snapshot_end_version, pending_base_version\",\n ],\n [\"document_history_checkpoints\", \"snapshot, end_version_vector\"],\n [\n \"document_history_updates\",\n \"id, update_data, origin, update_id, partial_start_version_vector, partial_end_version_vector, source_version_vector\",\n ],\n ]) {\n metadata.push(\n await execSql(\n `SELECT ${columns} FROM ${table} WHERE app_kind = 'container-metadata' AND local_id = ? ORDER BY rowid`,\n [containerId],\n ),\n );\n }\n const creates = await execSql(\n \"SELECT id, subject_id, target_container_id, intent_type, remote_container_id, remote_metadata_document_id, remote_metadata_access_state_hash FROM local_intents WHERE lane = ? AND subject_id = ? AND sync_status = 'pending' ORDER BY id\",\n [LOCAL_INTENT_LANES.containerCreate, containerId],\n );\n const moves = await execSql(\n \"SELECT id, subject_id, target_container_id, source_container_id, intent_type FROM local_intents WHERE lane = ? AND subject_id = ? ORDER BY id\",\n [LOCAL_INTENT_LANES.containerMove, containerId],\n );\n const placementWhere = `lane = ? AND (target_container_id = ? OR id IN\n (SELECT intent_id FROM document_intent_link_targets WHERE container_id = ?))`;\n const placementBind = [\n LOCAL_INTENT_LANES.documentPlacement,\n containerId,\n containerId,\n ];\n const links = await execSql(\n `SELECT id, local_id, subject_id, target_container_id, source_container_id, replace_linked_containers, intent_type FROM local_intents WHERE ${placementWhere} ORDER BY id`,\n placementBind,\n );\n const targets = await execSql(\n `SELECT intent_id, operation, container_id FROM document_intent_link_targets WHERE intent_id IN\n (SELECT id FROM local_intents WHERE ${placementWhere}) ORDER BY intent_id, container_id, operation`,\n placementBind,\n );\n const revision = await toFingerprint(\n new TextEncoder().encode(\n JSON.stringify([dormant, metadata, creates, moves, links, targets]),\n ),\n );\n return {\n revision,\n pendingUpdateCount: (metadata[2] ?? []).filter(\n (row) => Reflect.get(row, \"update_id\") !== null,\n ).length,\n hasStructuralIntent: creates.length + moves.length + links.length > 0,\n };\n}\n\nexport async function listRetainedContainerMetadata(\n execSql: ExecSql,\n organizationId: string | null,\n) {\n if (!organizationId) return [];\n await sqlContainerContentsPersistence.ensureSchema(execSql);\n await ensureSqlTables(execSql, documentPlacementIntentTables);\n return runSerializedSqlMutation(execSql, async (locked) =>\n getClientSQLitePersistenceRuntime(locked).transaction(async () => {\n const rows = await locked(\n `SELECT container_id FROM dormant_container_metadata\n WHERE organization_id = ? ORDER BY container_id`,\n [organizationId],\n );\n const result = [];\n for (const row of rows) {\n const containerId = String(Reflect.get(row, \"container_id\"));\n const snapshot = await recoverySnapshot(\n locked,\n containerId,\n organizationId,\n );\n if (!snapshot) continue;\n const record =\n await sqlContainerContentsPersistence.loadContainerMetadataRecord(\n locked,\n containerId,\n );\n result.push({\n containerId,\n organizationId,\n ...snapshot,\n metadataUpdates: record?.metadataUpdates ?? \"\",\n });\n }\n return result;\n }),\n );\n}\n\n/** Explicit local discard, never called by remote discovery. */\nexport async function discardRetainedContainerMetadata(\n execSql: ExecSql,\n input: {\n containerId: string;\n organizationId: string;\n revision: string;\n },\n): Promise<boolean> {\n await sqlContainerContentsPersistence.ensureSchema(execSql);\n await ensureSqlTables(execSql, documentPlacementIntentTables);\n return runSerializedSqlMutation(execSql, async (locked) =>\n getClientSQLitePersistenceRuntime(locked).transaction(\n async (tx) => {\n const current = await recoverySnapshot(\n locked,\n input.containerId,\n input.organizationId,\n );\n if (!current || current.revision !== input.revision) return false;\n await recordContainerHydrationTombstones({\n tx,\n removals: [\n {\n containerId: input.containerId,\n reason: \"deleted\",\n updatedAt: new Date().toISOString(),\n },\n ],\n });\n await repairDocumentsForRemovedContainersInTransaction({\n tx,\n removals: [\n {\n containerId: input.containerId,\n reason: \"deleted\",\n updatedAt: new Date().toISOString(),\n },\n ],\n });\n await discardLinkIntentsForRemovedContainers({\n tx,\n containerIds: [input.containerId],\n });\n await tx\n .delete(localIntents)\n .where(\n and(\n inContainerIntentLanes(),\n eq(localIntents.subjectId, input.containerId),\n ),\n )\n .run();\n await deleteContainerMetadataDocumentRowsInTransaction(tx, [\n input.containerId,\n ]);\n return true;\n },\n { behavior: \"immediate\" },\n ),\n );\n}\n\nexport async function hasRetainedContainerMetadata(\n execSql: ExecSql,\n organizationId: string | null,\n): Promise<boolean> {\n if (!organizationId) return false;\n await sqlContainerContentsPersistence.ensureSchema(execSql);\n const rows = await execSql(\n `SELECT 1 FROM dormant_container_metadata d\n WHERE d.organization_id = ? AND NOT EXISTS (SELECT 1 FROM containers c WHERE c.id = d.container_id) LIMIT 1`,\n [organizationId],\n );\n return rows.length > 0;\n}\n\n/** Existing local folders whose queued move points at an unavailable parent. */\nexport async function listRecoveryFolderMoveIds(\n execSql: ExecSql,\n organizationId: string | null,\n): Promise<string[]> {\n if (!organizationId) return [];\n await sqlContainerContentsPersistence.ensureSchema(execSql);\n const rows = await execSql(\n `SELECT c.id FROM containers c JOIN local_intents i\n ON i.lane = ? AND i.subject_id = c.id AND i.target_container_id = c.parent_id\n WHERE c.organization_id = ? AND i.sync_status IN ('pending', 'blocked')\n AND NOT EXISTS (SELECT 1 FROM containers p WHERE p.id = c.parent_id)\n ORDER BY c.id`,\n [LOCAL_INTENT_LANES.containerMove, organizationId],\n );\n return rows.map((row) => String(Reflect.get(row, \"id\")));\n}\n"]}
@@ -1,22 +1,22 @@
1
1
  import { and, eq, inArray } from "drizzle-orm";
2
2
  import { listLocalIdsWithQueuedUpdates } from "../../sqlite/documentPendingUpdatePersistence.js";
3
3
  import { documentIntentLinkTargets } from "../../sqlite/documentPlacementIntentSchema.js";
4
- import { containerCreateIntents, containerMoveIntents, documentMoveIntents, } from "../../sqlite/schema.js";
4
+ import { inLocalIntentLane, LOCAL_INTENT_LANES, localIntents, } from "../../sqlite/schema.js";
5
5
  /** Only local work makes removed metadata a recovery item; clean cache is disposable. */
6
6
  export async function containerIdsWithRemovalWork(tx, ids) {
7
7
  const pending = (await listLocalIdsWithQueuedUpdates(tx, "container-metadata", ids)).map((id) => ({ id }));
8
8
  const creates = await tx
9
- .select({ id: containerCreateIntents.containerId })
10
- .from(containerCreateIntents)
11
- .where(and(eq(containerCreateIntents.syncStatus, "pending"), inArray(containerCreateIntents.containerId, ids)));
9
+ .select({ id: localIntents.subjectId })
10
+ .from(localIntents)
11
+ .where(and(inLocalIntentLane(LOCAL_INTENT_LANES.containerCreate), eq(localIntents.syncStatus, "pending"), inArray(localIntents.subjectId, ids)));
12
12
  const moves = await tx
13
- .select({ id: containerMoveIntents.containerId })
14
- .from(containerMoveIntents)
15
- .where(and(inArray(containerMoveIntents.syncStatus, ["pending", "blocked"]), inArray(containerMoveIntents.containerId, ids)));
13
+ .select({ id: localIntents.subjectId })
14
+ .from(localIntents)
15
+ .where(and(inLocalIntentLane(LOCAL_INTENT_LANES.containerMove), inArray(localIntents.syncStatus, ["pending", "blocked"]), inArray(localIntents.subjectId, ids)));
16
16
  const placements = await tx
17
- .select({ id: documentMoveIntents.targetContainerId })
18
- .from(documentMoveIntents)
19
- .where(inArray(documentMoveIntents.targetContainerId, ids));
17
+ .select({ id: localIntents.targetContainerId })
18
+ .from(localIntents)
19
+ .where(and(inLocalIntentLane(LOCAL_INTENT_LANES.documentPlacement), inArray(localIntents.targetContainerId, ids)));
20
20
  const links = await tx
21
21
  .select({ id: documentIntentLinkTargets.containerId })
22
22
  .from(documentIntentLinkTargets)
@@ -1 +1 @@
1
- {"version":3,"file":"containerRemovalWork.js","sourceRoot":"","sources":["containerRemovalWork.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,GAAG,EAAE,EAAE,EAAE,OAAO,EAAE,MAAM,aAAa,CAAC;AAC/C,OAAO,EAAE,6BAA6B,EAAE,MAAM,+CAA+C,CAAC;AAC9F,OAAO,EAAE,yBAAyB,EAAE,MAAM,4CAA4C,CAAC;AACvF,OAAO,EACL,sBAAsB,EACtB,oBAAoB,EACpB,mBAAmB,GACpB,MAAM,qBAAqB,CAAC;AAG7B,yFAAyF;AACzF,MAAM,CAAC,KAAK,UAAU,2BAA2B,CAC/C,EAAgC,EAChC,GAAa;IAEb,MAAM,OAAO,GAAG,CACd,MAAM,6BAA6B,CAAC,EAAE,EAAE,oBAAoB,EAAE,GAAG,CAAC,CACnE,CAAC,GAAG,CAAC,CAAC,EAAE,EAAE,EAAE,CAAC,CAAC,EAAE,EAAE,EAAE,CAAC,CAAC,CAAC;IACxB,MAAM,OAAO,GAAG,MAAM,EAAE;SACrB,MAAM,CAAC,EAAE,EAAE,EAAE,sBAAsB,CAAC,WAAW,EAAE,CAAC;SAClD,IAAI,CAAC,sBAAsB,CAAC;SAC5B,KAAK,CACJ,GAAG,CACD,EAAE,CAAC,sBAAsB,CAAC,UAAU,EAAE,SAAS,CAAC,EAChD,OAAO,CAAC,sBAAsB,CAAC,WAAW,EAAE,GAAG,CAAC,CACjD,CACF,CAAC;IACJ,MAAM,KAAK,GAAG,MAAM,EAAE;SACnB,MAAM,CAAC,EAAE,EAAE,EAAE,oBAAoB,CAAC,WAAW,EAAE,CAAC;SAChD,IAAI,CAAC,oBAAoB,CAAC;SAC1B,KAAK,CACJ,GAAG,CACD,OAAO,CAAC,oBAAoB,CAAC,UAAU,EAAE,CAAC,SAAS,EAAE,SAAS,CAAC,CAAC,EAChE,OAAO,CAAC,oBAAoB,CAAC,WAAW,EAAE,GAAG,CAAC,CAC/C,CACF,CAAC;IACJ,MAAM,UAAU,GAAG,MAAM,EAAE;SACxB,MAAM,CAAC,EAAE,EAAE,EAAE,mBAAmB,CAAC,iBAAiB,EAAE,CAAC;SACrD,IAAI,CAAC,mBAAmB,CAAC;SACzB,KAAK,CAAC,OAAO,CAAC,mBAAmB,CAAC,iBAAiB,EAAE,GAAG,CAAC,CAAC,CAAC;IAC9D,MAAM,KAAK,GAAG,MAAM,EAAE;SACnB,MAAM,CAAC,EAAE,EAAE,EAAE,yBAAyB,CAAC,WAAW,EAAE,CAAC;SACrD,IAAI,CAAC,yBAAyB,CAAC;SAC/B,KAAK,CAAC,OAAO,CAAC,yBAAyB,CAAC,WAAW,EAAE,GAAG,CAAC,CAAC,CAAC;IAC9D,OAAO;QACL,GAAG,IAAI,GAAG,CACR,CAAC,GAAG,OAAO,EAAE,GAAG,OAAO,EAAE,GAAG,KAAK,EAAE,GAAG,UAAU,EAAE,GAAG,KAAK,CAAC,CAAC,GAAG,CAC7D,CAAC,GAAG,EAAE,EAAE,CAAC,GAAG,CAAC,EAAE,CAChB,CACF;KACF,CAAC;AACJ,CAAC","sourcesContent":["import { and, eq, inArray } from \"drizzle-orm\";\nimport { listLocalIdsWithQueuedUpdates } from \"../../sqlite/documentPendingUpdatePersistence\";\nimport { documentIntentLinkTargets } from \"../../sqlite/documentPlacementIntentSchema\";\nimport {\n containerCreateIntents,\n containerMoveIntents,\n documentMoveIntents,\n} from \"../../sqlite/schema\";\nimport type { ClientSQLiteTransactionScope } from \"../../sqlite/sqlitePersistenceRuntime\";\n\n/** Only local work makes removed metadata a recovery item; clean cache is disposable. */\nexport async function containerIdsWithRemovalWork(\n tx: ClientSQLiteTransactionScope,\n ids: string[],\n): Promise<string[]> {\n const pending = (\n await listLocalIdsWithQueuedUpdates(tx, \"container-metadata\", ids)\n ).map((id) => ({ id }));\n const creates = await tx\n .select({ id: containerCreateIntents.containerId })\n .from(containerCreateIntents)\n .where(\n and(\n eq(containerCreateIntents.syncStatus, \"pending\"),\n inArray(containerCreateIntents.containerId, ids),\n ),\n );\n const moves = await tx\n .select({ id: containerMoveIntents.containerId })\n .from(containerMoveIntents)\n .where(\n and(\n inArray(containerMoveIntents.syncStatus, [\"pending\", \"blocked\"]),\n inArray(containerMoveIntents.containerId, ids),\n ),\n );\n const placements = await tx\n .select({ id: documentMoveIntents.targetContainerId })\n .from(documentMoveIntents)\n .where(inArray(documentMoveIntents.targetContainerId, ids));\n const links = await tx\n .select({ id: documentIntentLinkTargets.containerId })\n .from(documentIntentLinkTargets)\n .where(inArray(documentIntentLinkTargets.containerId, ids));\n return [\n ...new Set(\n [...pending, ...creates, ...moves, ...placements, ...links].map(\n (row) => row.id,\n ),\n ),\n ];\n}\n"]}
1
+ {"version":3,"file":"containerRemovalWork.js","sourceRoot":"","sources":["containerRemovalWork.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,GAAG,EAAE,EAAE,EAAE,OAAO,EAAE,MAAM,aAAa,CAAC;AAC/C,OAAO,EAAE,6BAA6B,EAAE,MAAM,+CAA+C,CAAC;AAC9F,OAAO,EAAE,yBAAyB,EAAE,MAAM,4CAA4C,CAAC;AACvF,OAAO,EACL,iBAAiB,EACjB,kBAAkB,EAClB,YAAY,GACb,MAAM,qBAAqB,CAAC;AAG7B,yFAAyF;AACzF,MAAM,CAAC,KAAK,UAAU,2BAA2B,CAC/C,EAAgC,EAChC,GAAa;IAEb,MAAM,OAAO,GAAG,CACd,MAAM,6BAA6B,CAAC,EAAE,EAAE,oBAAoB,EAAE,GAAG,CAAC,CACnE,CAAC,GAAG,CAAC,CAAC,EAAE,EAAE,EAAE,CAAC,CAAC,EAAE,EAAE,EAAE,CAAC,CAAC,CAAC;IACxB,MAAM,OAAO,GAAG,MAAM,EAAE;SACrB,MAAM,CAAC,EAAE,EAAE,EAAE,YAAY,CAAC,SAAS,EAAE,CAAC;SACtC,IAAI,CAAC,YAAY,CAAC;SAClB,KAAK,CACJ,GAAG,CACD,iBAAiB,CAAC,kBAAkB,CAAC,eAAe,CAAC,EACrD,EAAE,CAAC,YAAY,CAAC,UAAU,EAAE,SAAS,CAAC,EACtC,OAAO,CAAC,YAAY,CAAC,SAAS,EAAE,GAAG,CAAC,CACrC,CACF,CAAC;IACJ,MAAM,KAAK,GAAG,MAAM,EAAE;SACnB,MAAM,CAAC,EAAE,EAAE,EAAE,YAAY,CAAC,SAAS,EAAE,CAAC;SACtC,IAAI,CAAC,YAAY,CAAC;SAClB,KAAK,CACJ,GAAG,CACD,iBAAiB,CAAC,kBAAkB,CAAC,aAAa,CAAC,EACnD,OAAO,CAAC,YAAY,CAAC,UAAU,EAAE,CAAC,SAAS,EAAE,SAAS,CAAC,CAAC,EACxD,OAAO,CAAC,YAAY,CAAC,SAAS,EAAE,GAAG,CAAC,CACrC,CACF,CAAC;IACJ,MAAM,UAAU,GAAG,MAAM,EAAE;SACxB,MAAM,CAAC,EAAE,EAAE,EAAE,YAAY,CAAC,iBAAiB,EAAE,CAAC;SAC9C,IAAI,CAAC,YAAY,CAAC;SAClB,KAAK,CACJ,GAAG,CACD,iBAAiB,CAAC,kBAAkB,CAAC,iBAAiB,CAAC,EACvD,OAAO,CAAC,YAAY,CAAC,iBAAiB,EAAE,GAAG,CAAC,CAC7C,CACF,CAAC;IACJ,MAAM,KAAK,GAAG,MAAM,EAAE;SACnB,MAAM,CAAC,EAAE,EAAE,EAAE,yBAAyB,CAAC,WAAW,EAAE,CAAC;SACrD,IAAI,CAAC,yBAAyB,CAAC;SAC/B,KAAK,CAAC,OAAO,CAAC,yBAAyB,CAAC,WAAW,EAAE,GAAG,CAAC,CAAC,CAAC;IAC9D,OAAO;QACL,GAAG,IAAI,GAAG,CACR,CAAC,GAAG,OAAO,EAAE,GAAG,OAAO,EAAE,GAAG,KAAK,EAAE,GAAG,UAAU,EAAE,GAAG,KAAK,CAAC,CAAC,GAAG,CAC7D,CAAC,GAAG,EAAE,EAAE,CAAC,GAAG,CAAC,EAAE,CAChB,CACF;KACF,CAAC;AACJ,CAAC","sourcesContent":["import { and, eq, inArray } from \"drizzle-orm\";\nimport { listLocalIdsWithQueuedUpdates } from \"../../sqlite/documentPendingUpdatePersistence\";\nimport { documentIntentLinkTargets } from \"../../sqlite/documentPlacementIntentSchema\";\nimport {\n inLocalIntentLane,\n LOCAL_INTENT_LANES,\n localIntents,\n} from \"../../sqlite/schema\";\nimport type { ClientSQLiteTransactionScope } from \"../../sqlite/sqlitePersistenceRuntime\";\n\n/** Only local work makes removed metadata a recovery item; clean cache is disposable. */\nexport async function containerIdsWithRemovalWork(\n tx: ClientSQLiteTransactionScope,\n ids: string[],\n): Promise<string[]> {\n const pending = (\n await listLocalIdsWithQueuedUpdates(tx, \"container-metadata\", ids)\n ).map((id) => ({ id }));\n const creates = await tx\n .select({ id: localIntents.subjectId })\n .from(localIntents)\n .where(\n and(\n inLocalIntentLane(LOCAL_INTENT_LANES.containerCreate),\n eq(localIntents.syncStatus, \"pending\"),\n inArray(localIntents.subjectId, ids),\n ),\n );\n const moves = await tx\n .select({ id: localIntents.subjectId })\n .from(localIntents)\n .where(\n and(\n inLocalIntentLane(LOCAL_INTENT_LANES.containerMove),\n inArray(localIntents.syncStatus, [\"pending\", \"blocked\"]),\n inArray(localIntents.subjectId, ids),\n ),\n );\n const placements = await tx\n .select({ id: localIntents.targetContainerId })\n .from(localIntents)\n .where(\n and(\n inLocalIntentLane(LOCAL_INTENT_LANES.documentPlacement),\n inArray(localIntents.targetContainerId, ids),\n ),\n );\n const links = await tx\n .select({ id: documentIntentLinkTargets.containerId })\n .from(documentIntentLinkTargets)\n .where(inArray(documentIntentLinkTargets.containerId, ids));\n return [\n ...new Set(\n [...pending, ...creates, ...moves, ...placements, ...links].map(\n (row) => row.id,\n ),\n ),\n ];\n}\n"]}
@@ -1,5 +1,5 @@
1
- import { asc, eq, inArray } from "drizzle-orm";
2
- import { containerCreateIntents, containerMoveIntents, containerProjection, containers, documentContainerProjection, documentProjection, } from "../../sqlite/schema.js";
1
+ import { and, asc, eq, inArray } from "drizzle-orm";
2
+ import { containers, documentContainerProjection, documentProjection, inContainerIntentLanes, inLocalIntentLane, LOCAL_INTENT_LANES, localIntents, } from "../../sqlite/schema.js";
3
3
  import { deleteContainerWatermarksInTransaction } from "../containers/containerSyncWatermarkPersistence.js";
4
4
  import { deleteContainerDocumentTombstoneHoldsForContainers } from "../documents/containerDocumentTombstoneHoldsPersistence.js";
5
5
  import { getLatestTimestamp } from "../latestTimestamp.js";
@@ -113,11 +113,6 @@ export async function updateReparentedDescendantContainers(input) {
113
113
  })
114
114
  .where(inArray(containers.id, descendantIds))
115
115
  .run();
116
- await tx
117
- .update(containerProjection)
118
- .set({ updatedAt })
119
- .where(inArray(containerProjection.containerId, descendantIds))
120
- .run();
121
116
  for (const reparent of descendantReparents) {
122
117
  await tx
123
118
  .update(containers)
@@ -134,25 +129,20 @@ export async function updateReparentedDescendantContainers(input) {
134
129
  }
135
130
  if (reparent.parentContainerId) {
136
131
  await tx
137
- .update(containerMoveIntents)
132
+ .update(localIntents)
138
133
  .set({
139
134
  id: crypto.randomUUID(),
140
- parentContainerId: reparent.parentContainerId,
135
+ targetContainerId: reparent.parentContainerId,
141
136
  syncStatus: "pending",
142
137
  updatedAt,
143
138
  })
144
- .where(eq(containerMoveIntents.containerId, reparent.containerId))
139
+ .where(and(inLocalIntentLane(LOCAL_INTENT_LANES.containerMove), eq(localIntents.subjectId, reparent.containerId)))
145
140
  .run();
146
141
  }
147
142
  }
148
143
  }
149
144
  export async function reparentLocalContainerChildren(input) {
150
145
  const { fromContainerId, remoteOrganizationId, toContainerId, tx, updatedAt, } = input;
151
- const childRows = await tx
152
- .select({ id: containers.id })
153
- .from(containers)
154
- .where(eq(containers.parentId, fromContainerId));
155
- const childContainerIds = childRows.flatMap((row) => row.id ? [row.id] : []);
156
146
  await tx
157
147
  .update(containers)
158
148
  .set({
@@ -162,60 +152,47 @@ export async function reparentLocalContainerChildren(input) {
162
152
  })
163
153
  .where(eq(containers.parentId, fromContainerId))
164
154
  .run();
165
- if (childContainerIds.length > 0) {
166
- await tx
167
- .update(containerProjection)
168
- .set({ updatedAt })
169
- .where(inArray(containerProjection.containerId, childContainerIds))
170
- .run();
171
- }
155
+ const createLane = inLocalIntentLane(LOCAL_INTENT_LANES.containerCreate);
172
156
  const createIntentRows = await tx
173
- .select({ containerId: containerCreateIntents.containerId })
174
- .from(containerCreateIntents)
175
- .where(eq(containerCreateIntents.parentContainerId, fromContainerId))
157
+ .select({ containerId: localIntents.subjectId })
158
+ .from(localIntents)
159
+ .where(and(createLane, eq(localIntents.targetContainerId, fromContainerId)))
176
160
  .all();
177
161
  for (const intent of createIntentRows) {
178
162
  await tx
179
- .update(containerCreateIntents)
163
+ .update(localIntents)
180
164
  .set({
181
165
  id: crypto.randomUUID(),
182
- parentContainerId: toContainerId,
166
+ targetContainerId: toContainerId,
183
167
  updatedAt,
184
168
  })
185
- .where(eq(containerCreateIntents.containerId, intent.containerId))
169
+ .where(and(createLane, eq(localIntents.subjectId, intent.containerId)))
186
170
  .run();
187
171
  }
172
+ const moveLane = inLocalIntentLane(LOCAL_INTENT_LANES.containerMove);
188
173
  const moveIntentRows = await tx
189
- .select({ containerId: containerMoveIntents.containerId })
190
- .from(containerMoveIntents)
191
- .where(eq(containerMoveIntents.parentContainerId, fromContainerId))
174
+ .select({ containerId: localIntents.subjectId })
175
+ .from(localIntents)
176
+ .where(and(moveLane, eq(localIntents.targetContainerId, fromContainerId)))
192
177
  .all();
193
178
  for (const intent of moveIntentRows) {
194
179
  await tx
195
- .update(containerMoveIntents)
180
+ .update(localIntents)
196
181
  .set({
197
182
  id: crypto.randomUUID(),
198
- parentContainerId: toContainerId,
183
+ targetContainerId: toContainerId,
199
184
  syncStatus: "pending",
200
185
  updatedAt,
201
186
  })
202
- .where(eq(containerMoveIntents.containerId, intent.containerId))
187
+ .where(and(moveLane, eq(localIntents.subjectId, intent.containerId)))
203
188
  .run();
204
189
  }
205
190
  }
206
191
  export async function deleteLocalContainerRows(input) {
207
192
  const { containerId, tx } = input;
208
193
  await tx
209
- .delete(containerCreateIntents)
210
- .where(eq(containerCreateIntents.containerId, containerId))
211
- .run();
212
- await tx
213
- .delete(containerMoveIntents)
214
- .where(eq(containerMoveIntents.containerId, containerId))
215
- .run();
216
- await tx
217
- .delete(containerProjection)
218
- .where(eq(containerProjection.containerId, containerId))
194
+ .delete(localIntents)
195
+ .where(and(inContainerIntentLanes(), eq(localIntents.subjectId, containerId)))
219
196
  .run();
220
197
  await tx.delete(containers).where(eq(containers.id, containerId)).run();
221
198
  await deleteContainerMetadataDocumentRowsInTransaction(tx, [containerId]);
@@ -1 +1 @@
1
- {"version":3,"file":"containerStructuralRepair.js","sourceRoot":"","sources":["containerStructuralRepair.ts"],"names":[],"mappings":"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","sourcesContent":["import { asc, eq, inArray } from \"drizzle-orm\";\nimport {\n containerCreateIntents,\n containerMoveIntents,\n containerProjection,\n containers,\n documentContainerProjection,\n documentProjection,\n} from \"../../sqlite/schema\";\nimport type { ClientSQLiteTransactionScope } from \"../../sqlite/sqlitePersistenceRuntime\";\nimport { deleteContainerWatermarksInTransaction } from \"../containers/containerSyncWatermarkPersistence\";\nimport { deleteContainerDocumentTombstoneHoldsForContainers } from \"../documents/containerDocumentTombstoneHoldsPersistence\";\nimport { getLatestTimestamp } from \"../latestTimestamp\";\nimport type {\n ContainerRemoval,\n LocalRootDescendantReparentInput,\n} from \"./containerContentsPersistenceTypes\";\nimport { saveContainerCreateIntent } from \"./containerIntentPersistence\";\nimport { deleteContainerMetadataDocumentRowsInTransaction } from \"./dormantContainerMetadata\";\n\n// The containers being removed still exist when repair runs (deletion follows\n// it). Capture their org attribution so a detached document's pending writes\n// keep a resolvable organization id after the join source is gone — e.g. when\n// access to a shared org is revoked mid-sync.\nasync function loadOrganizationIdsForContainers(\n tx: ClientSQLiteTransactionScope,\n containerIds: ReadonlyArray<string>,\n): Promise<Map<string, string>> {\n const rows = await tx\n .select({\n id: containers.id,\n organizationId: containers.organizationId,\n })\n .from(containers)\n .where(inArray(containers.id, containerIds));\n const organizationIdByContainerId = new Map<string, string>();\n for (const container of rows) {\n if (container.id && container.organizationId) {\n organizationIdByContainerId.set(container.id, container.organizationId);\n }\n }\n return organizationIdByContainerId;\n}\n\n// A document unlinked from a removed container may keep other live links; its\n// projection then re-homes to the first remaining link (deterministic order)\n// instead of dropping to null.\nasync function loadFirstRemainingContainerIdByDocumentId(\n tx: ClientSQLiteTransactionScope,\n documentIds: ReadonlyArray<string>,\n): Promise<Map<string, string>> {\n const firstRemainingContainerIdByDocumentId = new Map<string, string>();\n if (documentIds.length === 0) {\n return firstRemainingContainerIdByDocumentId;\n }\n const remainingLinks = await tx\n .select({\n containerId: documentContainerProjection.containerId,\n documentId: documentContainerProjection.documentId,\n })\n .from(documentContainerProjection)\n .where(inArray(documentContainerProjection.documentId, documentIds))\n .orderBy(\n asc(documentContainerProjection.documentId),\n asc(documentContainerProjection.containerId),\n );\n for (const link of remainingLinks) {\n if (!firstRemainingContainerIdByDocumentId.has(link.documentId)) {\n firstRemainingContainerIdByDocumentId.set(\n link.documentId,\n link.containerId,\n );\n }\n }\n return firstRemainingContainerIdByDocumentId;\n}\n\nexport async function repairDocumentsForRemovedContainersInTransaction(input: {\n removals: ReadonlyArray<ContainerRemoval>;\n tx: ClientSQLiteTransactionScope;\n}): Promise<void> {\n const { tx } = input;\n const updatedAtByContainerId = new Map(\n input.removals.map((removal) => [removal.containerId, removal.updatedAt]),\n );\n const containerIds = Array.from(updatedAtByContainerId.keys());\n if (containerIds.length === 0) {\n return;\n }\n\n {\n const selectedRows = await tx\n .select({\n containerId: documentProjection.containerId,\n documentId: documentProjection.documentId,\n localId: documentProjection.localId,\n updatedAt: documentProjection.updatedAt,\n })\n .from(documentProjection)\n .where(inArray(documentProjection.containerId, containerIds));\n\n const organizationIdByContainerId = await loadOrganizationIdsForContainers(\n tx,\n containerIds,\n );\n\n await tx\n .delete(documentContainerProjection)\n .where(inArray(documentContainerProjection.containerId, containerIds))\n .run();\n await deleteContainerDocumentTombstoneHoldsForContainers(tx, containerIds);\n\n const documentIds = Array.from(\n new Set(\n selectedRows.flatMap((row) => (row.documentId ? [row.documentId] : [])),\n ),\n );\n const firstRemainingContainerIdByDocumentId =\n await loadFirstRemainingContainerIdByDocumentId(tx, documentIds);\n\n for (const row of selectedRows) {\n if (!row.localId) {\n continue;\n }\n\n const removedOrganizationId = row.containerId\n ? organizationIdByContainerId.get(row.containerId)\n : undefined;\n await tx\n .update(documentProjection)\n .set({\n containerId: row.documentId\n ? (firstRemainingContainerIdByDocumentId.get(row.documentId) ??\n null)\n : null,\n ...(removedOrganizationId\n ? { organizationId: removedOrganizationId }\n : {}),\n updatedAt: getLatestTimestamp(\n row.updatedAt,\n row.containerId\n ? (updatedAtByContainerId.get(row.containerId) ?? row.updatedAt)\n : row.updatedAt,\n ),\n })\n .where(eq(documentProjection.localId, row.localId))\n .run();\n }\n }\n}\n\nexport async function updateReparentedDescendantContainers(input: {\n descendantReparents: ReadonlyArray<LocalRootDescendantReparentInput>;\n remoteOrganizationId: string;\n tx: ClientSQLiteTransactionScope;\n updatedAt: string;\n}): Promise<void> {\n const { descendantReparents, remoteOrganizationId, tx, updatedAt } = input;\n const descendantIds = Array.from(\n new Set(descendantReparents.map((reparent) => reparent.containerId)),\n );\n if (descendantIds.length === 0) {\n return;\n }\n\n await tx\n .update(containers)\n .set({\n organizationId: remoteOrganizationId,\n localUpdatedAt: updatedAt,\n })\n .where(inArray(containers.id, descendantIds))\n .run();\n await tx\n .update(containerProjection)\n .set({ updatedAt })\n .where(inArray(containerProjection.containerId, descendantIds))\n .run();\n\n for (const reparent of descendantReparents) {\n await tx\n .update(containers)\n .set({ parentId: reparent.parentContainerId })\n .where(eq(containers.id, reparent.containerId))\n .run();\n\n if (reparent.updateCreateIntent && reparent.parentContainerId) {\n await saveContainerCreateIntent({\n containerId: reparent.containerId,\n createIntent: { parentContainerId: reparent.parentContainerId },\n tx,\n updatedAt,\n });\n }\n\n if (reparent.parentContainerId) {\n await tx\n .update(containerMoveIntents)\n .set({\n id: crypto.randomUUID(),\n parentContainerId: reparent.parentContainerId,\n syncStatus: \"pending\",\n updatedAt,\n })\n .where(eq(containerMoveIntents.containerId, reparent.containerId))\n .run();\n }\n }\n}\n\nexport async function reparentLocalContainerChildren(input: {\n fromContainerId: string;\n remoteOrganizationId: string;\n toContainerId: string;\n tx: ClientSQLiteTransactionScope;\n updatedAt: string;\n}): Promise<void> {\n const {\n fromContainerId,\n remoteOrganizationId,\n toContainerId,\n tx,\n updatedAt,\n } = input;\n const childRows = await tx\n .select({ id: containers.id })\n .from(containers)\n .where(eq(containers.parentId, fromContainerId));\n const childContainerIds = childRows.flatMap((row) =>\n row.id ? [row.id] : [],\n );\n\n await tx\n .update(containers)\n .set({\n organizationId: remoteOrganizationId,\n parentId: toContainerId,\n localUpdatedAt: updatedAt,\n })\n .where(eq(containers.parentId, fromContainerId))\n .run();\n if (childContainerIds.length > 0) {\n await tx\n .update(containerProjection)\n .set({ updatedAt })\n .where(inArray(containerProjection.containerId, childContainerIds))\n .run();\n }\n const createIntentRows = await tx\n .select({ containerId: containerCreateIntents.containerId })\n .from(containerCreateIntents)\n .where(eq(containerCreateIntents.parentContainerId, fromContainerId))\n .all();\n for (const intent of createIntentRows) {\n await tx\n .update(containerCreateIntents)\n .set({\n id: crypto.randomUUID(),\n parentContainerId: toContainerId,\n updatedAt,\n })\n .where(eq(containerCreateIntents.containerId, intent.containerId))\n .run();\n }\n const moveIntentRows = await tx\n .select({ containerId: containerMoveIntents.containerId })\n .from(containerMoveIntents)\n .where(eq(containerMoveIntents.parentContainerId, fromContainerId))\n .all();\n for (const intent of moveIntentRows) {\n await tx\n .update(containerMoveIntents)\n .set({\n id: crypto.randomUUID(),\n parentContainerId: toContainerId,\n syncStatus: \"pending\",\n updatedAt,\n })\n .where(eq(containerMoveIntents.containerId, intent.containerId))\n .run();\n }\n}\n\nexport async function deleteLocalContainerRows(input: {\n containerId: string;\n tx: ClientSQLiteTransactionScope;\n}): Promise<void> {\n const { containerId, tx } = input;\n await tx\n .delete(containerCreateIntents)\n .where(eq(containerCreateIntents.containerId, containerId))\n .run();\n await tx\n .delete(containerMoveIntents)\n .where(eq(containerMoveIntents.containerId, containerId))\n .run();\n await tx\n .delete(containerProjection)\n .where(eq(containerProjection.containerId, containerId))\n .run();\n await tx.delete(containers).where(eq(containers.id, containerId)).run();\n await deleteContainerMetadataDocumentRowsInTransaction(tx, [containerId]);\n await deleteContainerWatermarksInTransaction(tx, [containerId]);\n}\n"]}
1
+ {"version":3,"file":"containerStructuralRepair.js","sourceRoot":"","sources":["containerStructuralRepair.ts"],"names":[],"mappings":"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","sourcesContent":["import { and, asc, eq, inArray } from \"drizzle-orm\";\nimport {\n containers,\n documentContainerProjection,\n documentProjection,\n inContainerIntentLanes,\n inLocalIntentLane,\n LOCAL_INTENT_LANES,\n localIntents,\n} from \"../../sqlite/schema\";\nimport type { ClientSQLiteTransactionScope } from \"../../sqlite/sqlitePersistenceRuntime\";\nimport { deleteContainerWatermarksInTransaction } from \"../containers/containerSyncWatermarkPersistence\";\nimport { deleteContainerDocumentTombstoneHoldsForContainers } from \"../documents/containerDocumentTombstoneHoldsPersistence\";\nimport { getLatestTimestamp } from \"../latestTimestamp\";\nimport type {\n ContainerRemoval,\n LocalRootDescendantReparentInput,\n} from \"./containerContentsPersistenceTypes\";\nimport { saveContainerCreateIntent } from \"./containerIntentPersistence\";\nimport { deleteContainerMetadataDocumentRowsInTransaction } from \"./dormantContainerMetadata\";\n\n// The containers being removed still exist when repair runs (deletion follows\n// it). Capture their org attribution so a detached document's pending writes\n// keep a resolvable organization id after the join source is gone — e.g. when\n// access to a shared org is revoked mid-sync.\nasync function loadOrganizationIdsForContainers(\n tx: ClientSQLiteTransactionScope,\n containerIds: ReadonlyArray<string>,\n): Promise<Map<string, string>> {\n const rows = await tx\n .select({\n id: containers.id,\n organizationId: containers.organizationId,\n })\n .from(containers)\n .where(inArray(containers.id, containerIds));\n const organizationIdByContainerId = new Map<string, string>();\n for (const container of rows) {\n if (container.id && container.organizationId) {\n organizationIdByContainerId.set(container.id, container.organizationId);\n }\n }\n return organizationIdByContainerId;\n}\n\n// A document unlinked from a removed container may keep other live links; its\n// projection then re-homes to the first remaining link (deterministic order)\n// instead of dropping to null.\nasync function loadFirstRemainingContainerIdByDocumentId(\n tx: ClientSQLiteTransactionScope,\n documentIds: ReadonlyArray<string>,\n): Promise<Map<string, string>> {\n const firstRemainingContainerIdByDocumentId = new Map<string, string>();\n if (documentIds.length === 0) {\n return firstRemainingContainerIdByDocumentId;\n }\n const remainingLinks = await tx\n .select({\n containerId: documentContainerProjection.containerId,\n documentId: documentContainerProjection.documentId,\n })\n .from(documentContainerProjection)\n .where(inArray(documentContainerProjection.documentId, documentIds))\n .orderBy(\n asc(documentContainerProjection.documentId),\n asc(documentContainerProjection.containerId),\n );\n for (const link of remainingLinks) {\n if (!firstRemainingContainerIdByDocumentId.has(link.documentId)) {\n firstRemainingContainerIdByDocumentId.set(\n link.documentId,\n link.containerId,\n );\n }\n }\n return firstRemainingContainerIdByDocumentId;\n}\n\nexport async function repairDocumentsForRemovedContainersInTransaction(input: {\n removals: ReadonlyArray<ContainerRemoval>;\n tx: ClientSQLiteTransactionScope;\n}): Promise<void> {\n const { tx } = input;\n const updatedAtByContainerId = new Map(\n input.removals.map((removal) => [removal.containerId, removal.updatedAt]),\n );\n const containerIds = Array.from(updatedAtByContainerId.keys());\n if (containerIds.length === 0) {\n return;\n }\n\n {\n const selectedRows = await tx\n .select({\n containerId: documentProjection.containerId,\n documentId: documentProjection.documentId,\n localId: documentProjection.localId,\n updatedAt: documentProjection.updatedAt,\n })\n .from(documentProjection)\n .where(inArray(documentProjection.containerId, containerIds));\n\n const organizationIdByContainerId = await loadOrganizationIdsForContainers(\n tx,\n containerIds,\n );\n\n await tx\n .delete(documentContainerProjection)\n .where(inArray(documentContainerProjection.containerId, containerIds))\n .run();\n await deleteContainerDocumentTombstoneHoldsForContainers(tx, containerIds);\n\n const documentIds = Array.from(\n new Set(\n selectedRows.flatMap((row) => (row.documentId ? [row.documentId] : [])),\n ),\n );\n const firstRemainingContainerIdByDocumentId =\n await loadFirstRemainingContainerIdByDocumentId(tx, documentIds);\n\n for (const row of selectedRows) {\n if (!row.localId) {\n continue;\n }\n\n const removedOrganizationId = row.containerId\n ? organizationIdByContainerId.get(row.containerId)\n : undefined;\n await tx\n .update(documentProjection)\n .set({\n containerId: row.documentId\n ? (firstRemainingContainerIdByDocumentId.get(row.documentId) ??\n null)\n : null,\n ...(removedOrganizationId\n ? { organizationId: removedOrganizationId }\n : {}),\n updatedAt: getLatestTimestamp(\n row.updatedAt,\n row.containerId\n ? (updatedAtByContainerId.get(row.containerId) ?? row.updatedAt)\n : row.updatedAt,\n ),\n })\n .where(eq(documentProjection.localId, row.localId))\n .run();\n }\n }\n}\n\nexport async function updateReparentedDescendantContainers(input: {\n descendantReparents: ReadonlyArray<LocalRootDescendantReparentInput>;\n remoteOrganizationId: string;\n tx: ClientSQLiteTransactionScope;\n updatedAt: string;\n}): Promise<void> {\n const { descendantReparents, remoteOrganizationId, tx, updatedAt } = input;\n const descendantIds = Array.from(\n new Set(descendantReparents.map((reparent) => reparent.containerId)),\n );\n if (descendantIds.length === 0) {\n return;\n }\n\n await tx\n .update(containers)\n .set({\n organizationId: remoteOrganizationId,\n localUpdatedAt: updatedAt,\n })\n .where(inArray(containers.id, descendantIds))\n .run();\n\n for (const reparent of descendantReparents) {\n await tx\n .update(containers)\n .set({ parentId: reparent.parentContainerId })\n .where(eq(containers.id, reparent.containerId))\n .run();\n\n if (reparent.updateCreateIntent && reparent.parentContainerId) {\n await saveContainerCreateIntent({\n containerId: reparent.containerId,\n createIntent: { parentContainerId: reparent.parentContainerId },\n tx,\n updatedAt,\n });\n }\n\n if (reparent.parentContainerId) {\n await tx\n .update(localIntents)\n .set({\n id: crypto.randomUUID(),\n targetContainerId: reparent.parentContainerId,\n syncStatus: \"pending\",\n updatedAt,\n })\n .where(\n and(\n inLocalIntentLane(LOCAL_INTENT_LANES.containerMove),\n eq(localIntents.subjectId, reparent.containerId),\n ),\n )\n .run();\n }\n }\n}\n\nexport async function reparentLocalContainerChildren(input: {\n fromContainerId: string;\n remoteOrganizationId: string;\n toContainerId: string;\n tx: ClientSQLiteTransactionScope;\n updatedAt: string;\n}): Promise<void> {\n const {\n fromContainerId,\n remoteOrganizationId,\n toContainerId,\n tx,\n updatedAt,\n } = input;\n await tx\n .update(containers)\n .set({\n organizationId: remoteOrganizationId,\n parentId: toContainerId,\n localUpdatedAt: updatedAt,\n })\n .where(eq(containers.parentId, fromContainerId))\n .run();\n const createLane = inLocalIntentLane(LOCAL_INTENT_LANES.containerCreate);\n const createIntentRows = await tx\n .select({ containerId: localIntents.subjectId })\n .from(localIntents)\n .where(and(createLane, eq(localIntents.targetContainerId, fromContainerId)))\n .all();\n for (const intent of createIntentRows) {\n await tx\n .update(localIntents)\n .set({\n id: crypto.randomUUID(),\n targetContainerId: toContainerId,\n updatedAt,\n })\n .where(and(createLane, eq(localIntents.subjectId, intent.containerId)))\n .run();\n }\n const moveLane = inLocalIntentLane(LOCAL_INTENT_LANES.containerMove);\n const moveIntentRows = await tx\n .select({ containerId: localIntents.subjectId })\n .from(localIntents)\n .where(and(moveLane, eq(localIntents.targetContainerId, fromContainerId)))\n .all();\n for (const intent of moveIntentRows) {\n await tx\n .update(localIntents)\n .set({\n id: crypto.randomUUID(),\n targetContainerId: toContainerId,\n syncStatus: \"pending\",\n updatedAt,\n })\n .where(and(moveLane, eq(localIntents.subjectId, intent.containerId)))\n .run();\n }\n}\n\nexport async function deleteLocalContainerRows(input: {\n containerId: string;\n tx: ClientSQLiteTransactionScope;\n}): Promise<void> {\n const { containerId, tx } = input;\n await tx\n .delete(localIntents)\n .where(\n and(inContainerIntentLanes(), eq(localIntents.subjectId, containerId)),\n )\n .run();\n await tx.delete(containers).where(eq(containers.id, containerId)).run();\n await deleteContainerMetadataDocumentRowsInTransaction(tx, [containerId]);\n await deleteContainerWatermarksInTransaction(tx, [containerId]);\n}\n"]}
@@ -1,17 +1,18 @@
1
1
  import { and, asc, eq, inArray, or } from "drizzle-orm";
2
2
  import { documentIntentLinkTargets } from "../../sqlite/documentPlacementIntentSchema.js";
3
- import { documentContainerProjection, documentMoveIntents, } from "../../sqlite/schema.js";
3
+ import { documentContainerProjection, inLocalIntentLane, LOCAL_INTENT_LANES, localIntents, } from "../../sqlite/schema.js";
4
4
  import { DOCUMENT_LINK_INTENT_TYPE, DOCUMENT_MOVE_INTENT_TYPE, } from "./documentMoveIntentPersistence.js";
5
+ const placementLane = inLocalIntentLane(LOCAL_INTENT_LANES.documentPlacement);
5
6
  function loadAffectedIntents(tx, containerIds, includePrimaryMoves) {
6
7
  return tx
7
8
  .select()
8
- .from(documentMoveIntents)
9
- .where(and(inArray(documentMoveIntents.intentType, [
9
+ .from(localIntents)
10
+ .where(and(placementLane, inArray(localIntents.intentType, [
10
11
  DOCUMENT_LINK_INTENT_TYPE,
11
12
  DOCUMENT_MOVE_INTENT_TYPE,
12
13
  ]), or(and(includePrimaryMoves
13
14
  ? undefined
14
- : eq(documentMoveIntents.intentType, DOCUMENT_LINK_INTENT_TYPE), inArray(documentMoveIntents.targetContainerId, containerIds)), inArray(documentMoveIntents.id, tx
15
+ : eq(localIntents.intentType, DOCUMENT_LINK_INTENT_TYPE), inArray(localIntents.targetContainerId, containerIds)), inArray(localIntents.id, tx
15
16
  .select({ id: documentIntentLinkTargets.intentId })
16
17
  .from(documentIntentLinkTargets)
17
18
  .where(inArray(documentIntentLinkTargets.containerId, containerIds))))));
@@ -24,7 +25,7 @@ async function survivingIntentTarget(input) {
24
25
  const [remaining] = await tx
25
26
  .select()
26
27
  .from(documentContainerProjection)
27
- .where(eq(documentContainerProjection.documentId, intent.documentId))
28
+ .where(eq(documentContainerProjection.documentId, intent.subjectId))
28
29
  .orderBy(asc(documentContainerProjection.containerId))
29
30
  .limit(1);
30
31
  return (remaining?.containerId ??
@@ -56,8 +57,8 @@ async function repairRemovedLinks(input) {
56
57
  .run();
57
58
  if (intentType === DOCUMENT_LINK_INTENT_TYPE && targets.length === 0) {
58
59
  await tx
59
- .delete(documentMoveIntents)
60
- .where(eq(documentMoveIntents.documentId, intent.documentId))
60
+ .delete(localIntents)
61
+ .where(and(placementLane, eq(localIntents.subjectId, intent.subjectId)))
61
62
  .run();
62
63
  continue;
63
64
  }
@@ -79,7 +80,7 @@ async function repairRemovedLinks(input) {
79
80
  .run();
80
81
  const unavailable = removed.has(targetContainerId);
81
82
  await tx
82
- .update(documentMoveIntents)
83
+ .update(localIntents)
83
84
  .set({
84
85
  id,
85
86
  intentType,
@@ -97,7 +98,7 @@ async function repairRemovedLinks(input) {
97
98
  syncStatus: unavailable ? "unavailable" : "pending",
98
99
  updatedAt: new Date().toISOString(),
99
100
  })
100
- .where(eq(documentMoveIntents.documentId, intent.documentId))
101
+ .where(and(placementLane, eq(localIntents.subjectId, intent.subjectId)))
101
102
  .run();
102
103
  }
103
104
  }
@@ -120,7 +121,7 @@ export async function refreshPlacementIntentsAfterRemoval(input) {
120
121
  .where(eq(documentIntentLinkTargets.intentId, intent.id ?? ""))
121
122
  .run();
122
123
  await tx
123
- .update(documentMoveIntents)
124
+ .update(localIntents)
124
125
  .set({
125
126
  id,
126
127
  syncStatus: intent.syncStatus === "unavailable" ? "pending" : intent.syncStatus,
@@ -128,7 +129,7 @@ export async function refreshPlacementIntentsAfterRemoval(input) {
128
129
  lastAttemptedAt: intent.syncStatus === "unavailable" ? null : intent.lastAttemptedAt,
129
130
  updatedAt: new Date().toISOString(),
130
131
  })
131
- .where(eq(documentMoveIntents.id, intent.id ?? ""))
132
+ .where(eq(localIntents.id, intent.id ?? ""))
132
133
  .run();
133
134
  }
134
135
  }
@@ -1 +1 @@
1
- {"version":3,"file":"documentLinkRemovalRepair.js","sourceRoot":"","sources":["documentLinkRemovalRepair.ts"],"names":[],"mappings":"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","sourcesContent":["import { and, asc, eq, inArray, or } from \"drizzle-orm\";\nimport { documentIntentLinkTargets } from \"../../sqlite/documentPlacementIntentSchema\";\nimport {\n documentContainerProjection,\n documentMoveIntents,\n} from \"../../sqlite/schema\";\nimport type { ClientSQLiteTransactionScope } from \"../../sqlite/sqlitePersistenceRuntime\";\nimport {\n DOCUMENT_LINK_INTENT_TYPE,\n DOCUMENT_MOVE_INTENT_TYPE,\n} from \"./documentMoveIntentPersistence\";\n\nfunction loadAffectedIntents(\n tx: ClientSQLiteTransactionScope,\n containerIds: ReadonlyArray<string>,\n includePrimaryMoves: boolean,\n) {\n return tx\n .select()\n .from(documentMoveIntents)\n .where(\n and(\n inArray(documentMoveIntents.intentType, [\n DOCUMENT_LINK_INTENT_TYPE,\n DOCUMENT_MOVE_INTENT_TYPE,\n ]),\n or(\n and(\n includePrimaryMoves\n ? undefined\n : eq(documentMoveIntents.intentType, DOCUMENT_LINK_INTENT_TYPE),\n inArray(documentMoveIntents.targetContainerId, containerIds),\n ),\n inArray(\n documentMoveIntents.id,\n tx\n .select({ id: documentIntentLinkTargets.intentId })\n .from(documentIntentLinkTargets)\n .where(\n inArray(documentIntentLinkTargets.containerId, containerIds),\n ),\n ),\n ),\n ),\n );\n}\n\nasync function survivingIntentTarget(input: {\n tx: ClientSQLiteTransactionScope;\n intent: typeof documentMoveIntents.$inferSelect;\n intentType: string;\n removed: ReadonlySet<string>;\n targets: ReadonlyArray<typeof documentIntentLinkTargets.$inferSelect>;\n}): Promise<string> {\n const { tx, intent, intentType, removed, targets } = input;\n if (\n intentType !== DOCUMENT_LINK_INTENT_TYPE ||\n !removed.has(intent.targetContainerId)\n )\n return intent.targetContainerId;\n const [remaining] = await tx\n .select()\n .from(documentContainerProjection)\n .where(eq(documentContainerProjection.documentId, intent.documentId))\n .orderBy(asc(documentContainerProjection.containerId))\n .limit(1);\n return (\n remaining?.containerId ??\n targets.find((target) => target.operation === \"link\")?.containerId ??\n intent.targetContainerId\n );\n}\n\n/** Runs after removed containers have been dropped from local link projections. */\nasync function repairRemovedLinks(input: {\n containerIds: ReadonlyArray<string>;\n discardPrimaryMoves: boolean;\n tx: ClientSQLiteTransactionScope;\n}): Promise<void> {\n const { containerIds, tx } = input;\n const removed = new Set(containerIds);\n const affected = await loadAffectedIntents(\n tx,\n containerIds,\n input.discardPrimaryMoves,\n );\n for (const intent of affected) {\n const discardedMove =\n input.discardPrimaryMoves &&\n intent.intentType === DOCUMENT_MOVE_INTENT_TYPE &&\n removed.has(intent.targetContainerId);\n const intentType = discardedMove\n ? DOCUMENT_LINK_INTENT_TYPE\n : intent.intentType;\n // Cancel both halves of a discarded move; unrelated link edits survive.\n const targets = (\n await tx\n .select()\n .from(documentIntentLinkTargets)\n .where(eq(documentIntentLinkTargets.intentId, intent.id ?? \"\"))\n ).filter((target) =>\n target.operation === \"unlink\"\n ? !(discardedMove && target.containerId === intent.sourceContainerId)\n : !removed.has(target.containerId),\n );\n await tx\n .delete(documentIntentLinkTargets)\n .where(eq(documentIntentLinkTargets.intentId, intent.id ?? \"\"))\n .run();\n if (intentType === DOCUMENT_LINK_INTENT_TYPE && targets.length === 0) {\n await tx\n .delete(documentMoveIntents)\n .where(eq(documentMoveIntents.documentId, intent.documentId))\n .run();\n continue;\n }\n const targetContainerId = await survivingIntentTarget({\n tx,\n intent,\n intentType,\n removed,\n targets,\n });\n // A removed preferred link is only a routing hint for an additive intent.\n // Keep explicit operations for surviving containers, and invalidate any\n // replay that captured the old target set before this transaction.\n const id = crypto.randomUUID();\n if (targets.length)\n await tx\n .insert(documentIntentLinkTargets)\n .values(targets.map((target) => ({ ...target, intentId: id })))\n .run();\n const unavailable = removed.has(targetContainerId);\n await tx\n .update(documentMoveIntents)\n .set({\n id,\n intentType,\n replaceLinkedContainers: discardedMove\n ? false\n : intent.replaceLinkedContainers,\n targetContainerId,\n // The source records what the user moved. Clearing it would make\n // replay fall back to the active link and remove that link instead.\n sourceContainerId: intent.sourceContainerId,\n lastAttemptedAt: null,\n lastError: unavailable\n ? \"Document placement has no surviving destination container\"\n : null,\n syncStatus: unavailable ? \"unavailable\" : \"pending\",\n updatedAt: new Date().toISOString(),\n })\n .where(eq(documentMoveIntents.documentId, intent.documentId))\n .run();\n }\n}\n\nexport function repairLinkIntentsForRemovedContainers(input: {\n containerIds: ReadonlyArray<string>;\n tx: ClientSQLiteTransactionScope;\n}): Promise<void> {\n return repairRemovedLinks({ ...input, discardPrimaryMoves: false });\n}\n\n/** Explicit discard cancels placement into these folders, retaining unrelated operations. */\nexport function discardLinkIntentsForRemovedContainers(input: {\n containerIds: ReadonlyArray<string>;\n tx: ClientSQLiteTransactionScope;\n}): Promise<void> {\n return repairRemovedLinks({ ...input, discardPrimaryMoves: true });\n}\n\n/** An unsigned absence hint must not let an old 404 strand queued placement. */\nexport async function refreshPlacementIntentsAfterRemoval(input: {\n containerIds: ReadonlyArray<string>;\n tx: ClientSQLiteTransactionScope;\n}): Promise<void> {\n const { tx } = input;\n for (const intent of await loadAffectedIntents(\n tx,\n input.containerIds,\n true,\n )) {\n // Rotate the revision to refuse completion from a pass that parked the old intent.\n const id = crypto.randomUUID();\n await tx\n .update(documentIntentLinkTargets)\n .set({ intentId: id })\n .where(eq(documentIntentLinkTargets.intentId, intent.id ?? \"\"))\n .run();\n await tx\n .update(documentMoveIntents)\n .set({\n id,\n syncStatus:\n intent.syncStatus === \"unavailable\" ? \"pending\" : intent.syncStatus,\n lastError:\n intent.syncStatus === \"unavailable\" ? null : intent.lastError,\n lastAttemptedAt:\n intent.syncStatus === \"unavailable\" ? null : intent.lastAttemptedAt,\n updatedAt: new Date().toISOString(),\n })\n .where(eq(documentMoveIntents.id, intent.id ?? \"\"))\n .run();\n }\n}\n"]}
1
+ {"version":3,"file":"documentLinkRemovalRepair.js","sourceRoot":"","sources":["documentLinkRemovalRepair.ts"],"names":[],"mappings":"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","sourcesContent":["import { and, asc, eq, inArray, or } from \"drizzle-orm\";\nimport { documentIntentLinkTargets } from \"../../sqlite/documentPlacementIntentSchema\";\nimport {\n documentContainerProjection,\n inLocalIntentLane,\n LOCAL_INTENT_LANES,\n localIntents,\n} from \"../../sqlite/schema\";\nimport type { ClientSQLiteTransactionScope } from \"../../sqlite/sqlitePersistenceRuntime\";\nimport {\n DOCUMENT_LINK_INTENT_TYPE,\n DOCUMENT_MOVE_INTENT_TYPE,\n} from \"./documentMoveIntentPersistence\";\n\nconst placementLane = inLocalIntentLane(LOCAL_INTENT_LANES.documentPlacement);\n\nfunction loadAffectedIntents(\n tx: ClientSQLiteTransactionScope,\n containerIds: ReadonlyArray<string>,\n includePrimaryMoves: boolean,\n) {\n return tx\n .select()\n .from(localIntents)\n .where(\n and(\n placementLane,\n inArray(localIntents.intentType, [\n DOCUMENT_LINK_INTENT_TYPE,\n DOCUMENT_MOVE_INTENT_TYPE,\n ]),\n or(\n and(\n includePrimaryMoves\n ? undefined\n : eq(localIntents.intentType, DOCUMENT_LINK_INTENT_TYPE),\n inArray(localIntents.targetContainerId, containerIds),\n ),\n inArray(\n localIntents.id,\n tx\n .select({ id: documentIntentLinkTargets.intentId })\n .from(documentIntentLinkTargets)\n .where(\n inArray(documentIntentLinkTargets.containerId, containerIds),\n ),\n ),\n ),\n ),\n );\n}\n\nasync function survivingIntentTarget(input: {\n tx: ClientSQLiteTransactionScope;\n intent: typeof localIntents.$inferSelect;\n intentType: string;\n removed: ReadonlySet<string>;\n targets: ReadonlyArray<typeof documentIntentLinkTargets.$inferSelect>;\n}): Promise<string> {\n const { tx, intent, intentType, removed, targets } = input;\n if (\n intentType !== DOCUMENT_LINK_INTENT_TYPE ||\n !removed.has(intent.targetContainerId)\n )\n return intent.targetContainerId;\n const [remaining] = await tx\n .select()\n .from(documentContainerProjection)\n .where(eq(documentContainerProjection.documentId, intent.subjectId))\n .orderBy(asc(documentContainerProjection.containerId))\n .limit(1);\n return (\n remaining?.containerId ??\n targets.find((target) => target.operation === \"link\")?.containerId ??\n intent.targetContainerId\n );\n}\n\n/** Runs after removed containers have been dropped from local link projections. */\nasync function repairRemovedLinks(input: {\n containerIds: ReadonlyArray<string>;\n discardPrimaryMoves: boolean;\n tx: ClientSQLiteTransactionScope;\n}): Promise<void> {\n const { containerIds, tx } = input;\n const removed = new Set(containerIds);\n const affected = await loadAffectedIntents(\n tx,\n containerIds,\n input.discardPrimaryMoves,\n );\n for (const intent of affected) {\n const discardedMove =\n input.discardPrimaryMoves &&\n intent.intentType === DOCUMENT_MOVE_INTENT_TYPE &&\n removed.has(intent.targetContainerId);\n const intentType = discardedMove\n ? DOCUMENT_LINK_INTENT_TYPE\n : intent.intentType;\n // Cancel both halves of a discarded move; unrelated link edits survive.\n const targets = (\n await tx\n .select()\n .from(documentIntentLinkTargets)\n .where(eq(documentIntentLinkTargets.intentId, intent.id ?? \"\"))\n ).filter((target) =>\n target.operation === \"unlink\"\n ? !(discardedMove && target.containerId === intent.sourceContainerId)\n : !removed.has(target.containerId),\n );\n await tx\n .delete(documentIntentLinkTargets)\n .where(eq(documentIntentLinkTargets.intentId, intent.id ?? \"\"))\n .run();\n if (intentType === DOCUMENT_LINK_INTENT_TYPE && targets.length === 0) {\n await tx\n .delete(localIntents)\n .where(and(placementLane, eq(localIntents.subjectId, intent.subjectId)))\n .run();\n continue;\n }\n const targetContainerId = await survivingIntentTarget({\n tx,\n intent,\n intentType,\n removed,\n targets,\n });\n // A removed preferred link is only a routing hint for an additive intent.\n // Keep explicit operations for surviving containers, and invalidate any\n // replay that captured the old target set before this transaction.\n const id = crypto.randomUUID();\n if (targets.length)\n await tx\n .insert(documentIntentLinkTargets)\n .values(targets.map((target) => ({ ...target, intentId: id })))\n .run();\n const unavailable = removed.has(targetContainerId);\n await tx\n .update(localIntents)\n .set({\n id,\n intentType,\n replaceLinkedContainers: discardedMove\n ? false\n : intent.replaceLinkedContainers,\n targetContainerId,\n // The source records what the user moved. Clearing it would make\n // replay fall back to the active link and remove that link instead.\n sourceContainerId: intent.sourceContainerId,\n lastAttemptedAt: null,\n lastError: unavailable\n ? \"Document placement has no surviving destination container\"\n : null,\n syncStatus: unavailable ? \"unavailable\" : \"pending\",\n updatedAt: new Date().toISOString(),\n })\n .where(and(placementLane, eq(localIntents.subjectId, intent.subjectId)))\n .run();\n }\n}\n\nexport function repairLinkIntentsForRemovedContainers(input: {\n containerIds: ReadonlyArray<string>;\n tx: ClientSQLiteTransactionScope;\n}): Promise<void> {\n return repairRemovedLinks({ ...input, discardPrimaryMoves: false });\n}\n\n/** Explicit discard cancels placement into these folders, retaining unrelated operations. */\nexport function discardLinkIntentsForRemovedContainers(input: {\n containerIds: ReadonlyArray<string>;\n tx: ClientSQLiteTransactionScope;\n}): Promise<void> {\n return repairRemovedLinks({ ...input, discardPrimaryMoves: true });\n}\n\n/** An unsigned absence hint must not let an old 404 strand queued placement. */\nexport async function refreshPlacementIntentsAfterRemoval(input: {\n containerIds: ReadonlyArray<string>;\n tx: ClientSQLiteTransactionScope;\n}): Promise<void> {\n const { tx } = input;\n for (const intent of await loadAffectedIntents(\n tx,\n input.containerIds,\n true,\n )) {\n // Rotate the revision to refuse completion from a pass that parked the old intent.\n const id = crypto.randomUUID();\n await tx\n .update(documentIntentLinkTargets)\n .set({ intentId: id })\n .where(eq(documentIntentLinkTargets.intentId, intent.id ?? \"\"))\n .run();\n await tx\n .update(localIntents)\n .set({\n id,\n syncStatus:\n intent.syncStatus === \"unavailable\" ? \"pending\" : intent.syncStatus,\n lastError:\n intent.syncStatus === \"unavailable\" ? null : intent.lastError,\n lastAttemptedAt:\n intent.syncStatus === \"unavailable\" ? null : intent.lastAttemptedAt,\n updatedAt: new Date().toISOString(),\n })\n .where(eq(localIntents.id, intent.id ?? \"\"))\n .run();\n }\n}\n"]}