@tearleads/client-sdk 0.1.43 → 0.1.45

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (189) hide show
  1. package/client/containerDocumentDiscovery.js +71 -29
  2. package/client/containerDocumentDiscovery.js.map +1 -1
  3. package/client/deviceFirst.js +4 -5
  4. package/client/deviceFirst.js.map +1 -1
  5. package/data/persistence/container-contents/containerContentsPersistence.js +2 -3
  6. package/data/persistence/container-contents/containerContentsPersistence.js.map +1 -1
  7. package/data/persistence/container-contents/containerCreateIntentSettlement.js +16 -16
  8. package/data/persistence/container-contents/containerCreateIntentSettlement.js.map +1 -1
  9. package/data/persistence/container-contents/containerDeletionPersistence.js +5 -11
  10. package/data/persistence/container-contents/containerDeletionPersistence.js.map +1 -1
  11. package/data/persistence/container-contents/containerDocumentReassignment.js +13 -13
  12. package/data/persistence/container-contents/containerDocumentReassignment.js.map +1 -1
  13. package/data/persistence/container-contents/containerHydrationPersistence.js +7 -4
  14. package/data/persistence/container-contents/containerHydrationPersistence.js.map +1 -1
  15. package/data/persistence/container-contents/containerIntentPersistence.js +43 -42
  16. package/data/persistence/container-contents/containerIntentPersistence.js.map +1 -1
  17. package/data/persistence/container-contents/containerMetadataMutationPersistence.js +15 -20
  18. package/data/persistence/container-contents/containerMetadataMutationPersistence.js.map +1 -1
  19. package/data/persistence/container-contents/containerMoveAbandonPersistence.js +1 -6
  20. package/data/persistence/container-contents/containerMoveAbandonPersistence.js.map +1 -1
  21. package/data/persistence/container-contents/containerMoveIntentRows.js +9 -8
  22. package/data/persistence/container-contents/containerMoveIntentRows.js.map +1 -1
  23. package/data/persistence/container-contents/containerReconcilePersistence.js +4 -8
  24. package/data/persistence/container-contents/containerReconcilePersistence.js.map +1 -1
  25. package/data/persistence/container-contents/containerRecoveryPersistence.js +20 -19
  26. package/data/persistence/container-contents/containerRecoveryPersistence.js.map +1 -1
  27. package/data/persistence/container-contents/containerRemovalWork.js +10 -10
  28. package/data/persistence/container-contents/containerRemovalWork.js.map +1 -1
  29. package/data/persistence/container-contents/containerStructuralRepair.js +21 -44
  30. package/data/persistence/container-contents/containerStructuralRepair.js.map +1 -1
  31. package/data/persistence/container-contents/documentLinkRemovalRepair.js +12 -11
  32. package/data/persistence/container-contents/documentLinkRemovalRepair.js.map +1 -1
  33. package/data/persistence/container-contents/documentMoveIntentPersistence.js +53 -28
  34. package/data/persistence/container-contents/documentMoveIntentPersistence.js.map +1 -1
  35. package/data/persistence/container-contents/documentPlacementIntentEnqueue.js +9 -8
  36. package/data/persistence/container-contents/documentPlacementIntentEnqueue.js.map +1 -1
  37. package/data/persistence/container-contents/documentPurgePlacement.js +6 -6
  38. package/data/persistence/container-contents/documentPurgePlacement.js.map +1 -1
  39. package/data/persistence/container-contents/dormantContainerMetadata.js +1 -5
  40. package/data/persistence/container-contents/dormantContainerMetadata.js.map +1 -1
  41. package/data/persistence/container-contents/pendingContainerCreate.js +5 -5
  42. package/data/persistence/container-contents/pendingContainerCreate.js.map +1 -1
  43. package/data/persistence/containers/containerPersistence.js +10 -27
  44. package/data/persistence/containers/containerPersistence.js.map +1 -1
  45. package/data/persistence/containers/containerSyncWatermarkPersistence.d.ts +11 -3
  46. package/data/persistence/containers/containerSyncWatermarkPersistence.js +46 -58
  47. package/data/persistence/containers/containerSyncWatermarkPersistence.js.map +1 -1
  48. package/data/persistence/containers/documentContainerProjectionPersistence.js +2 -2
  49. package/data/persistence/containers/documentContainerProjectionPersistence.js.map +1 -1
  50. package/data/persistence/containers/documentPlacement.js +10 -10
  51. package/data/persistence/containers/documentPlacement.js.map +1 -1
  52. package/data/persistence/documents/internal/discardDocument.js +5 -5
  53. package/data/persistence/documents/internal/discardDocument.js.map +1 -1
  54. package/data/persistence/documents/internal/documentDeletionPersistence.js +7 -9
  55. package/data/persistence/documents/internal/documentDeletionPersistence.js.map +1 -1
  56. package/data/persistence/documents/internal/documentMutationPersistence.js +6 -8
  57. package/data/persistence/documents/internal/documentMutationPersistence.js.map +1 -1
  58. package/data/persistence/documents/internal/ensureDocumentsSchema.js +2 -2
  59. package/data/persistence/documents/internal/ensureDocumentsSchema.js.map +1 -1
  60. package/data/persistence/documents/internal/orphanSideRows.js +3 -22
  61. package/data/persistence/documents/internal/orphanSideRows.js.map +1 -1
  62. package/data/persistence/organizations/organizationDataUsagePersistence.js +31 -64
  63. package/data/persistence/organizations/organizationDataUsagePersistence.js.map +1 -1
  64. package/data/persistence/organizations/organizationReadModelPurge.js +1 -5
  65. package/data/persistence/organizations/organizationReadModelPurge.js.map +1 -1
  66. package/data/persistence/principalCurrentAcknowledgementPersistence.js +24 -17
  67. package/data/persistence/principalCurrentAcknowledgementPersistence.js.map +1 -1
  68. package/data/persistence/principalHistoryIncompleteRetention.js +3 -9
  69. package/data/persistence/principalHistoryIncompleteRetention.js.map +1 -1
  70. package/data/persistence/principalHistoryStagePersistence.d.ts +3 -9
  71. package/data/persistence/principalHistoryStagePersistence.js +15 -25
  72. package/data/persistence/principalHistoryStagePersistence.js.map +1 -1
  73. package/data/persistence/principalHistoryStageRetention.js +16 -15
  74. package/data/persistence/principalHistoryStageRetention.js.map +1 -1
  75. package/data/persistence/principalHistoryUnpublishedRetention.js +6 -13
  76. package/data/persistence/principalHistoryUnpublishedRetention.js.map +1 -1
  77. package/data/sqlite/containerSchema.d.ts +84 -180
  78. package/data/sqlite/containerSchema.js +14 -23
  79. package/data/sqlite/containerSchema.js.map +1 -1
  80. package/data/sqlite/documentPlacementIntentSchema.js +3 -50
  81. package/data/sqlite/documentPlacementIntentSchema.js.map +1 -1
  82. package/data/sqlite/documentSyncFailurePersistence.d.ts +16 -6
  83. package/data/sqlite/documentSyncFailurePersistence.js +44 -54
  84. package/data/sqlite/documentSyncFailurePersistence.js.map +1 -1
  85. package/data/sqlite/{documentPlacementIntentSchema.d.ts → localIntentSchema.d.ts} +162 -88
  86. package/data/sqlite/localIntentSchema.js +90 -0
  87. package/data/sqlite/localIntentSchema.js.map +1 -0
  88. package/data/sqlite/organizationDataUsageSchema.js +11 -17
  89. package/data/sqlite/organizationDataUsageSchema.js.map +1 -1
  90. package/data/sqlite/principalHistoryRetentionSchema.js +0 -15
  91. package/data/sqlite/principalHistoryRetentionSchema.js.map +1 -1
  92. package/data/sqlite/principalHistoryStageSchema.d.ts +44 -2
  93. package/data/sqlite/principalHistoryStageSchema.js +14 -2
  94. package/data/sqlite/principalHistoryStageSchema.js.map +1 -1
  95. package/data/sqlite/schema.d.ts +1019 -2284
  96. package/data/sqlite/schema.js +48 -169
  97. package/data/sqlite/schema.js.map +1 -1
  98. package/internal/api-client/ApiClient.d.ts +6 -34
  99. package/internal/api-client/ApiClient.js +8 -10
  100. package/internal/api-client/ApiClient.js.map +1 -1
  101. package/internal/api-client/apiRequestRuntime.d.ts +2 -0
  102. package/internal/api-client/apiRequestRuntime.js +4 -0
  103. package/internal/api-client/apiRequestRuntime.js.map +1 -1
  104. package/internal/api-client/requestInternals.js +7 -7
  105. package/internal/api-client/requestInternals.js.map +1 -1
  106. package/internal/api-client/routes/containers/reads.d.ts +13 -7
  107. package/internal/api-client/routes/containers/reads.js +36 -15
  108. package/internal/api-client/routes/containers/reads.js.map +1 -1
  109. package/internal/api-client/types.d.ts +1 -5
  110. package/internal/api-client/types.js.map +1 -1
  111. package/internal/validators/containerReadRefinements.js +15 -5
  112. package/internal/validators/containerReadRefinements.js.map +1 -1
  113. package/internal/validators/containerReadSchemas.d.ts +36 -0
  114. package/internal/validators/containerReadSchemas.js +64 -1
  115. package/internal/validators/containerReadSchemas.js.map +1 -1
  116. package/internal/validators/operation/containerReads.d.ts +118 -182
  117. package/internal/validators/operation/containerReads.js +20 -54
  118. package/internal/validators/operation/containerReads.js.map +1 -1
  119. package/internal/validators/operation/index.d.ts +1 -1
  120. package/internal/validators/operation/index.js +1 -1
  121. package/internal/validators/operation/index.js.map +1 -1
  122. package/internal/validators/operation/registry.d.ts +2 -2
  123. package/internal/validators/operation/registry.js +2 -2
  124. package/internal/validators/operation/registry.js.map +1 -1
  125. package/internal/validators/request/containerDocumentLanes.d.ts +23 -0
  126. package/internal/validators/request/containerDocumentLanes.js +23 -0
  127. package/internal/validators/request/containerDocumentLanes.js.map +1 -0
  128. package/internal/validators/request/containerParentLanes.js +5 -35
  129. package/internal/validators/request/containerParentLanes.js.map +1 -1
  130. package/internal/validators/request/index.d.ts +1 -0
  131. package/internal/validators/request/index.js +1 -0
  132. package/internal/validators/request/index.js.map +1 -1
  133. package/internal/validators/response/containerDocumentLanes.d.ts +82 -0
  134. package/internal/validators/response/containerDocumentLanes.js +35 -0
  135. package/internal/validators/response/containerDocumentLanes.js.map +1 -0
  136. package/internal/validators/response/containerParentLanes.js +3 -15
  137. package/internal/validators/response/containerParentLanes.js.map +1 -1
  138. package/internal/validators/response/index.d.ts +2 -1
  139. package/internal/validators/response/index.js +1 -1
  140. package/internal/validators/response/index.js.map +1 -1
  141. package/package.json +1 -1
  142. package/sync/reconciliation/knownContainerSweep.js +57 -54
  143. package/sync/reconciliation/knownContainerSweep.js.map +1 -1
  144. package/sync/reconciliation/reconcileContainer.js +49 -17
  145. package/sync/reconciliation/reconcileContainer.js.map +1 -1
  146. package/sync/reconciliation/service.js +44 -16
  147. package/sync/reconciliation/service.js.map +1 -1
  148. package/sync/reconciliation/serviceTypes.d.ts +23 -2
  149. package/sync/reconciliation/serviceTypes.js.map +1 -1
  150. package/workflows/container-contents/containerDocumentListing.js +92 -38
  151. package/workflows/container-contents/containerDocumentListing.js.map +1 -1
  152. package/workflows/container-contents/containerPersistence.d.ts +8 -2
  153. package/workflows/container-contents/containerPersistence.js +6 -7
  154. package/workflows/container-contents/containerPersistence.js.map +1 -1
  155. package/workflows/container-contents/documentDiscovery.d.ts +3 -3
  156. package/workflows/container-contents/documentDiscovery.js +24 -96
  157. package/workflows/container-contents/documentDiscovery.js.map +1 -1
  158. package/workflows/container-contents/documentDiscoveryApply.js +65 -0
  159. package/workflows/container-contents/documentDiscoveryApply.js.map +1 -0
  160. package/workflows/container-contents/documentDiscoveryLanes.js +53 -0
  161. package/workflows/container-contents/documentDiscoveryLanes.js.map +1 -0
  162. package/workflows/container-contents/documentDiscoveryTypes.d.ts +25 -19
  163. package/workflows/container-contents/documentDiscoveryTypes.js.map +1 -1
  164. package/workflows/container-contents/documentPriming.js +6 -10
  165. package/workflows/container-contents/documentPriming.js.map +1 -1
  166. package/workflows/container-contents/documentQueries/sql.js +11 -10
  167. package/workflows/container-contents/documentQueries/sql.js.map +1 -1
  168. package/workflows/container-contents/documentQueries.js +2 -2
  169. package/workflows/container-contents/documentQueries.js.map +1 -1
  170. package/workflows/container-contents/pendingDownloads.js +2 -5
  171. package/workflows/container-contents/pendingDownloads.js.map +1 -1
  172. package/workflows/container-contents/pendingWrites/deferredTails.js +3 -11
  173. package/workflows/container-contents/pendingWrites/deferredTails.js.map +1 -1
  174. package/workflows/container-contents/pendingWrites/sources.js +56 -80
  175. package/workflows/container-contents/pendingWrites/sources.js.map +1 -1
  176. package/workflows/container-contents/pendingWrites.js +6 -4
  177. package/workflows/container-contents/pendingWrites.js.map +1 -1
  178. package/workflows/container-contents/remoteContainerState.js +2 -1
  179. package/workflows/container-contents/remoteContainerState.js.map +1 -1
  180. package/workflows/container-contents/remoteHydration/laneFetchMarkers.js +11 -17
  181. package/workflows/container-contents/remoteHydration/laneFetchMarkers.js.map +1 -1
  182. package/workflows/sync/remoteReset.js +16 -23
  183. package/workflows/sync/remoteReset.js.map +1 -1
  184. package/workflows/sync/remoteResetCursorScope.js +10 -18
  185. package/workflows/sync/remoteResetCursorScope.js.map +1 -1
  186. package/workflows/sync/remoteResetPrincipalScope.js +1 -2
  187. package/workflows/sync/remoteResetPrincipalScope.js.map +1 -1
  188. package/workflows/sync/remoteResetScope.js +18 -18
  189. package/workflows/sync/remoteResetScope.js.map +1 -1
@@ -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"]}
@@ -1,10 +1,11 @@
1
1
  import { and, asc, eq, inArray } from "drizzle-orm";
2
2
  import { documentIntentLinkTargets } from "../../sqlite/documentPlacementIntentSchema.js";
3
- import { containerTables, documentMoveIntents, documentMoveIntentTables, documentProjectionTables, } from "../../sqlite/schema.js";
3
+ import { containerTables, documentPlacementIntentTables, documentProjectionTables, inLocalIntentLane, LOCAL_INTENT_LANES, 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 { enqueueDocumentLinkIntent, enqueueDocumentMoveIntent, enqueueDocumentUnlinkIntent, loadDocumentIntentLinkTargets, } from "./documentPlacementIntentEnqueue.js";
7
7
  export const DOCUMENT_MOVE_INTENT_TYPE = "document.move";
8
+ const placementLane = inLocalIntentLane(LOCAL_INTENT_LANES.documentPlacement);
8
9
  export const DOCUMENT_LINK_INTENT_TYPE = "document.link";
9
10
  const DOCUMENT_PLACEMENT_INTENT_TYPES = [
10
11
  DOCUMENT_MOVE_INTENT_TYPE,
@@ -51,7 +52,8 @@ function mapDocumentMoveIntentRecord(row) {
51
52
  : DOCUMENT_MOVE_INTENT_TYPE,
52
53
  lastAttemptedAt: row.lastAttemptedAt,
53
54
  lastError: row.lastError,
54
- localId: row.localId,
55
+ // Placement intents always carry their local document row.
56
+ localId: row.localId ?? "",
55
57
  replaceLinkedContainers: Boolean(row.replaceLinkedContainers),
56
58
  sourceContainerId: row.sourceContainerId,
57
59
  syncStatus: parseDocumentMoveIntentSyncStatus(row.syncStatus),
@@ -62,18 +64,31 @@ function mapDocumentMoveIntentRecord(row) {
62
64
  }
63
65
  export const sqlDocumentMoveIntentPersistence = {
64
66
  async ensureSchema(execSql) {
65
- await ensureSqlTables(execSql, documentMoveIntentTables);
67
+ await ensureSqlTables(execSql, documentPlacementIntentTables);
66
68
  },
67
69
  enqueueMoveIntent: enqueueDocumentMoveIntent,
68
70
  enqueueLinkIntent: enqueueDocumentLinkIntent,
69
71
  enqueueUnlinkIntent: enqueueDocumentUnlinkIntent,
70
72
  async listPendingMoveIntents(execSql) {
71
73
  return runSerializedSqlMutation(execSql, async (lockedExecSql) => {
72
- await ensureSqlTables(lockedExecSql, documentMoveIntentTables);
74
+ await ensureSqlTables(lockedExecSql, documentPlacementIntentTables);
73
75
  return getClientSQLitePersistenceRuntime(lockedExecSql).transaction(async (tx) => {
74
76
  const rows = await tx
75
- .select()
76
- .from(documentMoveIntents)
77
+ .select({
78
+ id: localIntents.id,
79
+ documentId: localIntents.subjectId,
80
+ intentType: localIntents.intentType,
81
+ lastAttemptedAt: localIntents.lastAttemptedAt,
82
+ lastError: localIntents.lastError,
83
+ localId: localIntents.localId,
84
+ replaceLinkedContainers: localIntents.replaceLinkedContainers,
85
+ sourceContainerId: localIntents.sourceContainerId,
86
+ syncStatus: localIntents.syncStatus,
87
+ targetContainerId: localIntents.targetContainerId,
88
+ createdAt: localIntents.createdAt,
89
+ updatedAt: localIntents.updatedAt,
90
+ })
91
+ .from(localIntents)
77
92
  // Blocked intents replay too: "blocked" names the reason the last
78
93
  // attempt could not proceed (missing local doc / destination), not a
79
94
  // terminal verdict. The blocking condition can heal after hydration or
@@ -82,8 +97,8 @@ export const sqlDocumentMoveIntentPersistence = {
82
97
  // Unavailable intents never replay: the server proved a cited container
83
98
  // is gone, so every replay would re-issue the same doomed requests
84
99
  // (#2278 #4). Only the tombstone cascade or a re-enqueue revives them.
85
- .where(and(inArray(documentMoveIntents.syncStatus, ["pending", "blocked"]), inArray(documentMoveIntents.intentType, DOCUMENT_PLACEMENT_INTENT_TYPES)))
86
- .orderBy(asc(documentMoveIntents.createdAt));
100
+ .where(and(placementLane, inArray(localIntents.syncStatus, ["pending", "blocked"]), inArray(localIntents.intentType, DOCUMENT_PLACEMENT_INTENT_TYPES)))
101
+ .orderBy(asc(localIntents.createdAt));
87
102
  return Promise.all(rows.map(async (row) => {
88
103
  const targets = await loadDocumentIntentLinkTargets(lockedExecSql, row.id ?? "");
89
104
  const added = targets
@@ -105,12 +120,14 @@ export const sqlDocumentMoveIntentPersistence = {
105
120
  },
106
121
  async markMoveIntentSynced(execSql, input) {
107
122
  return runSerializedSqlMutation(execSql, async (lockedExecSql) => {
108
- const deleted = await lockedExecSql(`DELETE FROM document_move_intents
109
- WHERE document_id = ?
123
+ const deleted = await lockedExecSql(`DELETE FROM local_intents
124
+ WHERE lane = ?
125
+ AND subject_id = ?
110
126
  AND intent_type IN (?, ?)
111
127
  AND updated_at = ?
112
128
  ${input.expectedIntentId ? "AND id = ?" : ""}
113
129
  RETURNING id`, [
130
+ LOCAL_INTENT_LANES.documentPlacement,
114
131
  input.documentId,
115
132
  ...DOCUMENT_PLACEMENT_INTENT_TYPES,
116
133
  input.expectedUpdatedAt,
@@ -129,25 +146,25 @@ export const sqlDocumentMoveIntentPersistence = {
129
146
  await getClientSQLitePersistenceRuntime(execSql).guardedTransaction(async (db) => {
130
147
  const updatedAt = new Date().toISOString();
131
148
  await db
132
- .update(documentMoveIntents)
149
+ .update(localIntents)
133
150
  .set({
134
151
  lastAttemptedAt: updatedAt,
135
152
  lastError: input.message,
136
153
  syncStatus: resolveRecordedSyncStatus(input),
137
154
  updatedAt,
138
155
  })
139
- .where(and(eq(documentMoveIntents.documentId, input.documentId),
156
+ .where(and(placementLane, eq(localIntents.subjectId, input.documentId),
140
157
  // Blocked/denied rows must stay updatable: a retried intent
141
158
  // records its fresh outcome, and a transient failure flips it
142
159
  // back to pending instead of freezing it forever.
143
- inArray(documentMoveIntents.syncStatus, [
160
+ inArray(localIntents.syncStatus, [
144
161
  "pending",
145
162
  "blocked",
146
163
  "denied",
147
- ]), inArray(documentMoveIntents.intentType, DOCUMENT_PLACEMENT_INTENT_TYPES), ...(input.expectedIntentId
148
- ? [eq(documentMoveIntents.id, input.expectedIntentId)]
164
+ ]), inArray(localIntents.intentType, DOCUMENT_PLACEMENT_INTENT_TYPES), ...(input.expectedIntentId
165
+ ? [eq(localIntents.id, input.expectedIntentId)]
149
166
  : []), ...(input.expectedUpdatedAt
150
- ? [eq(documentMoveIntents.updatedAt, input.expectedUpdatedAt)]
167
+ ? [eq(localIntents.updatedAt, input.expectedUpdatedAt)]
151
168
  : [])))
152
169
  .run();
153
170
  }, input.stillCurrent ?? (() => true), { behavior: "immediate" });
@@ -162,15 +179,18 @@ export const sqlDocumentMoveIntentPersistence = {
162
179
  ...documentProjectionTables,
163
180
  ...containerTables,
164
181
  ]);
165
- const rows = await execSql(`SELECT intent.document_id AS document_id
166
- FROM document_move_intents intent
182
+ const rows = await execSql(`SELECT intent.subject_id AS document_id
183
+ FROM local_intents intent
167
184
  ${input?.organizationId ? DENIED_INTENT_ORGANIZATION_JOINS_SQL : ""}
168
- WHERE intent.sync_status = 'denied'
185
+ WHERE intent.lane = ?
186
+ AND intent.sync_status = 'denied'
169
187
  AND intent.intent_type IN (?, ?)
170
188
  ${input?.organizationId ? `AND (${DENIED_INTENT_ORGANIZATION_SQL} = ? OR ${DENIED_INTENT_ORGANIZATION_SQL} IS NULL)` : ""}
171
- LIMIT 1`, input?.organizationId
172
- ? [...DOCUMENT_PLACEMENT_INTENT_TYPES, input.organizationId]
173
- : DOCUMENT_PLACEMENT_INTENT_TYPES);
189
+ LIMIT 1`, [
190
+ LOCAL_INTENT_LANES.documentPlacement,
191
+ ...DOCUMENT_PLACEMENT_INTENT_TYPES,
192
+ ...(input?.organizationId ? [input.organizationId] : []),
193
+ ]);
174
194
  return rows.length > 0;
175
195
  },
176
196
  /**
@@ -189,23 +209,28 @@ export const sqlDocumentMoveIntentPersistence = {
189
209
  // container joins above: restoring one organization's access must not
190
210
  // un-park moves that another organization still denies. Rows with no
191
211
  // resolvable organization are included (device-first shapes).
192
- await lockedExecSql(`UPDATE document_move_intents
212
+ await lockedExecSql(`UPDATE local_intents
193
213
  SET sync_status = 'pending', updated_at = ?
194
- WHERE sync_status = 'denied'
214
+ WHERE lane = ?
215
+ AND sync_status = 'denied'
195
216
  AND intent_type IN (?, ?)
196
217
  ${input?.localId ? "AND local_id = ?" : ""}
197
218
  ${input?.organizationId
198
219
  ? `AND local_id IN (
199
- SELECT intent.local_id FROM document_move_intents intent
220
+ SELECT intent.local_id FROM local_intents intent
200
221
  ${DENIED_INTENT_ORGANIZATION_JOINS_SQL}
201
- WHERE ${DENIED_INTENT_ORGANIZATION_SQL} = ?
202
- OR ${DENIED_INTENT_ORGANIZATION_SQL} IS NULL
222
+ WHERE intent.lane = ?
223
+ AND (${DENIED_INTENT_ORGANIZATION_SQL} = ?
224
+ OR ${DENIED_INTENT_ORGANIZATION_SQL} IS NULL)
203
225
  )`
204
226
  : ""}`, [
205
227
  new Date().toISOString(),
228
+ LOCAL_INTENT_LANES.documentPlacement,
206
229
  ...DOCUMENT_PLACEMENT_INTENT_TYPES,
207
230
  ...(input?.localId ? [input.localId] : []),
208
- ...(input?.organizationId ? [input.organizationId] : []),
231
+ ...(input?.organizationId
232
+ ? [LOCAL_INTENT_LANES.documentPlacement, input.organizationId]
233
+ : []),
209
234
  ]);
210
235
  });
211
236
  },