@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.
- package/client/containerDocumentDiscovery.js +71 -29
- package/client/containerDocumentDiscovery.js.map +1 -1
- package/client/deviceFirst.js +4 -5
- package/client/deviceFirst.js.map +1 -1
- package/data/persistence/container-contents/containerContentsPersistence.js +2 -3
- package/data/persistence/container-contents/containerContentsPersistence.js.map +1 -1
- package/data/persistence/container-contents/containerCreateIntentSettlement.js +16 -16
- package/data/persistence/container-contents/containerCreateIntentSettlement.js.map +1 -1
- package/data/persistence/container-contents/containerDeletionPersistence.js +5 -11
- package/data/persistence/container-contents/containerDeletionPersistence.js.map +1 -1
- package/data/persistence/container-contents/containerDocumentReassignment.js +13 -13
- package/data/persistence/container-contents/containerDocumentReassignment.js.map +1 -1
- package/data/persistence/container-contents/containerHydrationPersistence.js +7 -4
- package/data/persistence/container-contents/containerHydrationPersistence.js.map +1 -1
- package/data/persistence/container-contents/containerIntentPersistence.js +43 -42
- package/data/persistence/container-contents/containerIntentPersistence.js.map +1 -1
- package/data/persistence/container-contents/containerMetadataMutationPersistence.js +15 -20
- package/data/persistence/container-contents/containerMetadataMutationPersistence.js.map +1 -1
- package/data/persistence/container-contents/containerMoveAbandonPersistence.js +1 -6
- package/data/persistence/container-contents/containerMoveAbandonPersistence.js.map +1 -1
- package/data/persistence/container-contents/containerMoveIntentRows.js +9 -8
- package/data/persistence/container-contents/containerMoveIntentRows.js.map +1 -1
- package/data/persistence/container-contents/containerReconcilePersistence.js +4 -8
- package/data/persistence/container-contents/containerReconcilePersistence.js.map +1 -1
- package/data/persistence/container-contents/containerRecoveryPersistence.js +20 -19
- package/data/persistence/container-contents/containerRecoveryPersistence.js.map +1 -1
- package/data/persistence/container-contents/containerRemovalWork.js +10 -10
- package/data/persistence/container-contents/containerRemovalWork.js.map +1 -1
- package/data/persistence/container-contents/containerStructuralRepair.js +21 -44
- package/data/persistence/container-contents/containerStructuralRepair.js.map +1 -1
- package/data/persistence/container-contents/documentLinkRemovalRepair.js +12 -11
- package/data/persistence/container-contents/documentLinkRemovalRepair.js.map +1 -1
- package/data/persistence/container-contents/documentMoveIntentPersistence.js +53 -28
- package/data/persistence/container-contents/documentMoveIntentPersistence.js.map +1 -1
- package/data/persistence/container-contents/documentPlacementIntentEnqueue.js +9 -8
- package/data/persistence/container-contents/documentPlacementIntentEnqueue.js.map +1 -1
- package/data/persistence/container-contents/documentPurgePlacement.js +6 -6
- package/data/persistence/container-contents/documentPurgePlacement.js.map +1 -1
- package/data/persistence/container-contents/dormantContainerMetadata.js +1 -5
- package/data/persistence/container-contents/dormantContainerMetadata.js.map +1 -1
- package/data/persistence/container-contents/pendingContainerCreate.js +5 -5
- package/data/persistence/container-contents/pendingContainerCreate.js.map +1 -1
- package/data/persistence/containers/containerPersistence.js +10 -27
- package/data/persistence/containers/containerPersistence.js.map +1 -1
- package/data/persistence/containers/containerSyncWatermarkPersistence.d.ts +11 -3
- package/data/persistence/containers/containerSyncWatermarkPersistence.js +46 -58
- package/data/persistence/containers/containerSyncWatermarkPersistence.js.map +1 -1
- package/data/persistence/containers/documentContainerProjectionPersistence.js +2 -2
- package/data/persistence/containers/documentContainerProjectionPersistence.js.map +1 -1
- package/data/persistence/containers/documentPlacement.js +10 -10
- package/data/persistence/containers/documentPlacement.js.map +1 -1
- package/data/persistence/documents/internal/discardDocument.js +5 -5
- package/data/persistence/documents/internal/discardDocument.js.map +1 -1
- package/data/persistence/documents/internal/documentDeletionPersistence.js +7 -9
- package/data/persistence/documents/internal/documentDeletionPersistence.js.map +1 -1
- package/data/persistence/documents/internal/documentMutationPersistence.js +6 -8
- package/data/persistence/documents/internal/documentMutationPersistence.js.map +1 -1
- package/data/persistence/documents/internal/ensureDocumentsSchema.js +2 -2
- package/data/persistence/documents/internal/ensureDocumentsSchema.js.map +1 -1
- package/data/persistence/documents/internal/orphanSideRows.js +3 -22
- package/data/persistence/documents/internal/orphanSideRows.js.map +1 -1
- package/data/persistence/organizations/organizationDataUsagePersistence.js +31 -64
- package/data/persistence/organizations/organizationDataUsagePersistence.js.map +1 -1
- package/data/persistence/organizations/organizationReadModelPurge.js +1 -5
- package/data/persistence/organizations/organizationReadModelPurge.js.map +1 -1
- package/data/persistence/principalCurrentAcknowledgementPersistence.js +24 -17
- package/data/persistence/principalCurrentAcknowledgementPersistence.js.map +1 -1
- package/data/persistence/principalHistoryIncompleteRetention.js +3 -9
- package/data/persistence/principalHistoryIncompleteRetention.js.map +1 -1
- package/data/persistence/principalHistoryStagePersistence.d.ts +3 -9
- package/data/persistence/principalHistoryStagePersistence.js +15 -25
- package/data/persistence/principalHistoryStagePersistence.js.map +1 -1
- package/data/persistence/principalHistoryStageRetention.js +16 -15
- package/data/persistence/principalHistoryStageRetention.js.map +1 -1
- package/data/persistence/principalHistoryUnpublishedRetention.js +6 -13
- package/data/persistence/principalHistoryUnpublishedRetention.js.map +1 -1
- package/data/sqlite/containerSchema.d.ts +84 -180
- package/data/sqlite/containerSchema.js +14 -23
- package/data/sqlite/containerSchema.js.map +1 -1
- package/data/sqlite/documentPlacementIntentSchema.js +3 -50
- package/data/sqlite/documentPlacementIntentSchema.js.map +1 -1
- package/data/sqlite/documentSyncFailurePersistence.d.ts +16 -6
- package/data/sqlite/documentSyncFailurePersistence.js +44 -54
- package/data/sqlite/documentSyncFailurePersistence.js.map +1 -1
- package/data/sqlite/{documentPlacementIntentSchema.d.ts → localIntentSchema.d.ts} +162 -88
- package/data/sqlite/localIntentSchema.js +90 -0
- package/data/sqlite/localIntentSchema.js.map +1 -0
- package/data/sqlite/organizationDataUsageSchema.js +11 -17
- package/data/sqlite/organizationDataUsageSchema.js.map +1 -1
- package/data/sqlite/principalHistoryRetentionSchema.js +0 -15
- package/data/sqlite/principalHistoryRetentionSchema.js.map +1 -1
- package/data/sqlite/principalHistoryStageSchema.d.ts +44 -2
- package/data/sqlite/principalHistoryStageSchema.js +14 -2
- package/data/sqlite/principalHistoryStageSchema.js.map +1 -1
- package/data/sqlite/schema.d.ts +1019 -2284
- package/data/sqlite/schema.js +48 -169
- package/data/sqlite/schema.js.map +1 -1
- package/internal/api-client/ApiClient.d.ts +6 -34
- package/internal/api-client/ApiClient.js +8 -10
- package/internal/api-client/ApiClient.js.map +1 -1
- package/internal/api-client/apiRequestRuntime.d.ts +2 -0
- package/internal/api-client/apiRequestRuntime.js +4 -0
- package/internal/api-client/apiRequestRuntime.js.map +1 -1
- package/internal/api-client/requestInternals.js +7 -7
- package/internal/api-client/requestInternals.js.map +1 -1
- package/internal/api-client/routes/containers/reads.d.ts +13 -7
- package/internal/api-client/routes/containers/reads.js +36 -15
- package/internal/api-client/routes/containers/reads.js.map +1 -1
- package/internal/api-client/types.d.ts +1 -5
- package/internal/api-client/types.js.map +1 -1
- package/internal/validators/containerReadRefinements.js +15 -5
- package/internal/validators/containerReadRefinements.js.map +1 -1
- package/internal/validators/containerReadSchemas.d.ts +36 -0
- package/internal/validators/containerReadSchemas.js +64 -1
- package/internal/validators/containerReadSchemas.js.map +1 -1
- package/internal/validators/operation/containerReads.d.ts +118 -182
- package/internal/validators/operation/containerReads.js +20 -54
- package/internal/validators/operation/containerReads.js.map +1 -1
- package/internal/validators/operation/index.d.ts +1 -1
- package/internal/validators/operation/index.js +1 -1
- package/internal/validators/operation/index.js.map +1 -1
- package/internal/validators/operation/registry.d.ts +2 -2
- package/internal/validators/operation/registry.js +2 -2
- package/internal/validators/operation/registry.js.map +1 -1
- package/internal/validators/request/containerDocumentLanes.d.ts +23 -0
- package/internal/validators/request/containerDocumentLanes.js +23 -0
- package/internal/validators/request/containerDocumentLanes.js.map +1 -0
- package/internal/validators/request/containerParentLanes.js +5 -35
- package/internal/validators/request/containerParentLanes.js.map +1 -1
- package/internal/validators/request/index.d.ts +1 -0
- package/internal/validators/request/index.js +1 -0
- package/internal/validators/request/index.js.map +1 -1
- package/internal/validators/response/containerDocumentLanes.d.ts +82 -0
- package/internal/validators/response/containerDocumentLanes.js +35 -0
- package/internal/validators/response/containerDocumentLanes.js.map +1 -0
- package/internal/validators/response/containerParentLanes.js +3 -15
- package/internal/validators/response/containerParentLanes.js.map +1 -1
- package/internal/validators/response/index.d.ts +2 -1
- package/internal/validators/response/index.js +1 -1
- package/internal/validators/response/index.js.map +1 -1
- package/package.json +1 -1
- package/sync/reconciliation/knownContainerSweep.js +57 -54
- package/sync/reconciliation/knownContainerSweep.js.map +1 -1
- package/sync/reconciliation/reconcileContainer.js +49 -17
- package/sync/reconciliation/reconcileContainer.js.map +1 -1
- package/sync/reconciliation/service.js +44 -16
- package/sync/reconciliation/service.js.map +1 -1
- package/sync/reconciliation/serviceTypes.d.ts +23 -2
- package/sync/reconciliation/serviceTypes.js.map +1 -1
- package/workflows/container-contents/containerDocumentListing.js +92 -38
- package/workflows/container-contents/containerDocumentListing.js.map +1 -1
- package/workflows/container-contents/containerPersistence.d.ts +8 -2
- package/workflows/container-contents/containerPersistence.js +6 -7
- package/workflows/container-contents/containerPersistence.js.map +1 -1
- package/workflows/container-contents/documentDiscovery.d.ts +3 -3
- package/workflows/container-contents/documentDiscovery.js +24 -96
- package/workflows/container-contents/documentDiscovery.js.map +1 -1
- package/workflows/container-contents/documentDiscoveryApply.js +65 -0
- package/workflows/container-contents/documentDiscoveryApply.js.map +1 -0
- package/workflows/container-contents/documentDiscoveryLanes.js +53 -0
- package/workflows/container-contents/documentDiscoveryLanes.js.map +1 -0
- package/workflows/container-contents/documentDiscoveryTypes.d.ts +25 -19
- package/workflows/container-contents/documentDiscoveryTypes.js.map +1 -1
- package/workflows/container-contents/documentPriming.js +6 -10
- package/workflows/container-contents/documentPriming.js.map +1 -1
- package/workflows/container-contents/documentQueries/sql.js +11 -10
- package/workflows/container-contents/documentQueries/sql.js.map +1 -1
- package/workflows/container-contents/documentQueries.js +2 -2
- package/workflows/container-contents/documentQueries.js.map +1 -1
- package/workflows/container-contents/pendingDownloads.js +2 -5
- package/workflows/container-contents/pendingDownloads.js.map +1 -1
- package/workflows/container-contents/pendingWrites/deferredTails.js +3 -11
- package/workflows/container-contents/pendingWrites/deferredTails.js.map +1 -1
- package/workflows/container-contents/pendingWrites/sources.js +56 -80
- package/workflows/container-contents/pendingWrites/sources.js.map +1 -1
- package/workflows/container-contents/pendingWrites.js +6 -4
- package/workflows/container-contents/pendingWrites.js.map +1 -1
- package/workflows/container-contents/remoteContainerState.js +2 -1
- package/workflows/container-contents/remoteContainerState.js.map +1 -1
- package/workflows/container-contents/remoteHydration/laneFetchMarkers.js +11 -17
- package/workflows/container-contents/remoteHydration/laneFetchMarkers.js.map +1 -1
- package/workflows/sync/remoteReset.js +16 -23
- package/workflows/sync/remoteReset.js.map +1 -1
- package/workflows/sync/remoteResetCursorScope.js +10 -18
- package/workflows/sync/remoteResetCursorScope.js.map +1 -1
- package/workflows/sync/remoteResetPrincipalScope.js +1 -2
- package/workflows/sync/remoteResetPrincipalScope.js.map +1 -1
- package/workflows/sync/remoteResetScope.js +18 -18
- package/workflows/sync/remoteResetScope.js.map +1 -1
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { asc, eq, inArray } from "drizzle-orm";
|
|
2
|
-
import {
|
|
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(
|
|
132
|
+
.update(localIntents)
|
|
138
133
|
.set({
|
|
139
134
|
id: crypto.randomUUID(),
|
|
140
|
-
|
|
135
|
+
targetContainerId: reparent.parentContainerId,
|
|
141
136
|
syncStatus: "pending",
|
|
142
137
|
updatedAt,
|
|
143
138
|
})
|
|
144
|
-
.where(eq(
|
|
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
|
-
|
|
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:
|
|
174
|
-
.from(
|
|
175
|
-
.where(eq(
|
|
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(
|
|
163
|
+
.update(localIntents)
|
|
180
164
|
.set({
|
|
181
165
|
id: crypto.randomUUID(),
|
|
182
|
-
|
|
166
|
+
targetContainerId: toContainerId,
|
|
183
167
|
updatedAt,
|
|
184
168
|
})
|
|
185
|
-
.where(eq(
|
|
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:
|
|
190
|
-
.from(
|
|
191
|
-
.where(eq(
|
|
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(
|
|
180
|
+
.update(localIntents)
|
|
196
181
|
.set({
|
|
197
182
|
id: crypto.randomUUID(),
|
|
198
|
-
|
|
183
|
+
targetContainerId: toContainerId,
|
|
199
184
|
syncStatus: "pending",
|
|
200
185
|
updatedAt,
|
|
201
186
|
})
|
|
202
|
-
.where(eq(
|
|
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(
|
|
210
|
-
.where(eq(
|
|
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":"AAAA,OAAO,EAAE,GAAG,EAAE,GAAG,EAAE,EAAE,EAAE,OAAO,EAAE,MAAM,aAAa,CAAC;AACpD,OAAO,EACL,UAAU,EACV,2BAA2B,EAC3B,kBAAkB,EAClB,sBAAsB,EACtB,iBAAiB,EACjB,kBAAkB,EAClB,YAAY,GACb,MAAM,qBAAqB,CAAC;AAE7B,OAAO,EAAE,sCAAsC,EAAE,MAAM,iDAAiD,CAAC;AACzG,OAAO,EAAE,kDAAkD,EAAE,MAAM,yDAAyD,CAAC;AAC7H,OAAO,EAAE,kBAAkB,EAAE,MAAM,oBAAoB,CAAC;AAKxD,OAAO,EAAE,yBAAyB,EAAE,MAAM,8BAA8B,CAAC;AACzE,OAAO,EAAE,gDAAgD,EAAE,MAAM,4BAA4B,CAAC;AAE9F,8EAA8E;AAC9E,6EAA6E;AAC7E,8EAA8E;AAC9E,8CAA8C;AAC9C,KAAK,UAAU,gCAAgC,CAC7C,EAAgC,EAChC,YAAmC;IAEnC,MAAM,IAAI,GAAG,MAAM,EAAE;SAClB,MAAM,CAAC;QACN,EAAE,EAAE,UAAU,CAAC,EAAE;QACjB,cAAc,EAAE,UAAU,CAAC,cAAc;KAC1C,CAAC;SACD,IAAI,CAAC,UAAU,CAAC;SAChB,KAAK,CAAC,OAAO,CAAC,UAAU,CAAC,EAAE,EAAE,YAAY,CAAC,CAAC,CAAC;IAC/C,MAAM,2BAA2B,GAAG,IAAI,GAAG,EAAkB,CAAC;IAC9D,KAAK,MAAM,SAAS,IAAI,IAAI,EAAE,CAAC;QAC7B,IAAI,SAAS,CAAC,EAAE,IAAI,SAAS,CAAC,cAAc,EAAE,CAAC;YAC7C,2BAA2B,CAAC,GAAG,CAAC,SAAS,CAAC,EAAE,EAAE,SAAS,CAAC,cAAc,CAAC,CAAC;QAC1E,CAAC;IACH,CAAC;IACD,OAAO,2BAA2B,CAAC;AACrC,CAAC;AAED,8EAA8E;AAC9E,6EAA6E;AAC7E,+BAA+B;AAC/B,KAAK,UAAU,yCAAyC,CACtD,EAAgC,EAChC,WAAkC;IAElC,MAAM,qCAAqC,GAAG,IAAI,GAAG,EAAkB,CAAC;IACxE,IAAI,WAAW,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QAC7B,OAAO,qCAAqC,CAAC;IAC/C,CAAC;IACD,MAAM,cAAc,GAAG,MAAM,EAAE;SAC5B,MAAM,CAAC;QACN,WAAW,EAAE,2BAA2B,CAAC,WAAW;QACpD,UAAU,EAAE,2BAA2B,CAAC,UAAU;KACnD,CAAC;SACD,IAAI,CAAC,2BAA2B,CAAC;SACjC,KAAK,CAAC,OAAO,CAAC,2BAA2B,CAAC,UAAU,EAAE,WAAW,CAAC,CAAC;SACnE,OAAO,CACN,GAAG,CAAC,2BAA2B,CAAC,UAAU,CAAC,EAC3C,GAAG,CAAC,2BAA2B,CAAC,WAAW,CAAC,CAC7C,CAAC;IACJ,KAAK,MAAM,IAAI,IAAI,cAAc,EAAE,CAAC;QAClC,IAAI,CAAC,qCAAqC,CAAC,GAAG,CAAC,IAAI,CAAC,UAAU,CAAC,EAAE,CAAC;YAChE,qCAAqC,CAAC,GAAG,CACvC,IAAI,CAAC,UAAU,EACf,IAAI,CAAC,WAAW,CACjB,CAAC;QACJ,CAAC;IACH,CAAC;IACD,OAAO,qCAAqC,CAAC;AAC/C,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,gDAAgD,CAAC,KAGtE;IACC,MAAM,EAAE,EAAE,EAAE,GAAG,KAAK,CAAC;IACrB,MAAM,sBAAsB,GAAG,IAAI,GAAG,CACpC,KAAK,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC,CAAC,OAAO,CAAC,WAAW,EAAE,OAAO,CAAC,SAAS,CAAC,CAAC,CAC1E,CAAC;IACF,MAAM,YAAY,GAAG,KAAK,CAAC,IAAI,CAAC,sBAAsB,CAAC,IAAI,EAAE,CAAC,CAAC;IAC/D,IAAI,YAAY,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QAC9B,OAAO;IACT,CAAC;IAED,CAAC;QACC,MAAM,YAAY,GAAG,MAAM,EAAE;aAC1B,MAAM,CAAC;YACN,WAAW,EAAE,kBAAkB,CAAC,WAAW;YAC3C,UAAU,EAAE,kBAAkB,CAAC,UAAU;YACzC,OAAO,EAAE,kBAAkB,CAAC,OAAO;YACnC,SAAS,EAAE,kBAAkB,CAAC,SAAS;SACxC,CAAC;aACD,IAAI,CAAC,kBAAkB,CAAC;aACxB,KAAK,CAAC,OAAO,CAAC,kBAAkB,CAAC,WAAW,EAAE,YAAY,CAAC,CAAC,CAAC;QAEhE,MAAM,2BAA2B,GAAG,MAAM,gCAAgC,CACxE,EAAE,EACF,YAAY,CACb,CAAC;QAEF,MAAM,EAAE;aACL,MAAM,CAAC,2BAA2B,CAAC;aACnC,KAAK,CAAC,OAAO,CAAC,2BAA2B,CAAC,WAAW,EAAE,YAAY,CAAC,CAAC;aACrE,GAAG,EAAE,CAAC;QACT,MAAM,kDAAkD,CAAC,EAAE,EAAE,YAAY,CAAC,CAAC;QAE3E,MAAM,WAAW,GAAG,KAAK,CAAC,IAAI,CAC5B,IAAI,GAAG,CACL,YAAY,CAAC,OAAO,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC,CAAC,GAAG,CAAC,UAAU,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,UAAU,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CACxE,CACF,CAAC;QACF,MAAM,qCAAqC,GACzC,MAAM,yCAAyC,CAAC,EAAE,EAAE,WAAW,CAAC,CAAC;QAEnE,KAAK,MAAM,GAAG,IAAI,YAAY,EAAE,CAAC;YAC/B,IAAI,CAAC,GAAG,CAAC,OAAO,EAAE,CAAC;gBACjB,SAAS;YACX,CAAC;YAED,MAAM,qBAAqB,GAAG,GAAG,CAAC,WAAW;gBAC3C,CAAC,CAAC,2BAA2B,CAAC,GAAG,CAAC,GAAG,CAAC,WAAW,CAAC;gBAClD,CAAC,CAAC,SAAS,CAAC;YACd,MAAM,EAAE;iBACL,MAAM,CAAC,kBAAkB,CAAC;iBAC1B,GAAG,CAAC;gBACH,WAAW,EAAE,GAAG,CAAC,UAAU;oBACzB,CAAC,CAAC,CAAC,qCAAqC,CAAC,GAAG,CAAC,GAAG,CAAC,UAAU,CAAC;wBAC1D,IAAI,CAAC;oBACP,CAAC,CAAC,IAAI;gBACR,GAAG,CAAC,qBAAqB;oBACvB,CAAC,CAAC,EAAE,cAAc,EAAE,qBAAqB,EAAE;oBAC3C,CAAC,CAAC,EAAE,CAAC;gBACP,SAAS,EAAE,kBAAkB,CAC3B,GAAG,CAAC,SAAS,EACb,GAAG,CAAC,WAAW;oBACb,CAAC,CAAC,CAAC,sBAAsB,CAAC,GAAG,CAAC,GAAG,CAAC,WAAW,CAAC,IAAI,GAAG,CAAC,SAAS,CAAC;oBAChE,CAAC,CAAC,GAAG,CAAC,SAAS,CAClB;aACF,CAAC;iBACD,KAAK,CAAC,EAAE,CAAC,kBAAkB,CAAC,OAAO,EAAE,GAAG,CAAC,OAAO,CAAC,CAAC;iBAClD,GAAG,EAAE,CAAC;QACX,CAAC;IACH,CAAC;AACH,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,oCAAoC,CAAC,KAK1D;IACC,MAAM,EAAE,mBAAmB,EAAE,oBAAoB,EAAE,EAAE,EAAE,SAAS,EAAE,GAAG,KAAK,CAAC;IAC3E,MAAM,aAAa,GAAG,KAAK,CAAC,IAAI,CAC9B,IAAI,GAAG,CAAC,mBAAmB,CAAC,GAAG,CAAC,CAAC,QAAQ,EAAE,EAAE,CAAC,QAAQ,CAAC,WAAW,CAAC,CAAC,CACrE,CAAC;IACF,IAAI,aAAa,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QAC/B,OAAO;IACT,CAAC;IAED,MAAM,EAAE;SACL,MAAM,CAAC,UAAU,CAAC;SAClB,GAAG,CAAC;QACH,cAAc,EAAE,oBAAoB;QACpC,cAAc,EAAE,SAAS;KAC1B,CAAC;SACD,KAAK,CAAC,OAAO,CAAC,UAAU,CAAC,EAAE,EAAE,aAAa,CAAC,CAAC;SAC5C,GAAG,EAAE,CAAC;IAET,KAAK,MAAM,QAAQ,IAAI,mBAAmB,EAAE,CAAC;QAC3C,MAAM,EAAE;aACL,MAAM,CAAC,UAAU,CAAC;aAClB,GAAG,CAAC,EAAE,QAAQ,EAAE,QAAQ,CAAC,iBAAiB,EAAE,CAAC;aAC7C,KAAK,CAAC,EAAE,CAAC,UAAU,CAAC,EAAE,EAAE,QAAQ,CAAC,WAAW,CAAC,CAAC;aAC9C,GAAG,EAAE,CAAC;QAET,IAAI,QAAQ,CAAC,kBAAkB,IAAI,QAAQ,CAAC,iBAAiB,EAAE,CAAC;YAC9D,MAAM,yBAAyB,CAAC;gBAC9B,WAAW,EAAE,QAAQ,CAAC,WAAW;gBACjC,YAAY,EAAE,EAAE,iBAAiB,EAAE,QAAQ,CAAC,iBAAiB,EAAE;gBAC/D,EAAE;gBACF,SAAS;aACV,CAAC,CAAC;QACL,CAAC;QAED,IAAI,QAAQ,CAAC,iBAAiB,EAAE,CAAC;YAC/B,MAAM,EAAE;iBACL,MAAM,CAAC,YAAY,CAAC;iBACpB,GAAG,CAAC;gBACH,EAAE,EAAE,MAAM,CAAC,UAAU,EAAE;gBACvB,iBAAiB,EAAE,QAAQ,CAAC,iBAAiB;gBAC7C,UAAU,EAAE,SAAS;gBACrB,SAAS;aACV,CAAC;iBACD,KAAK,CACJ,GAAG,CACD,iBAAiB,CAAC,kBAAkB,CAAC,aAAa,CAAC,EACnD,EAAE,CAAC,YAAY,CAAC,SAAS,EAAE,QAAQ,CAAC,WAAW,CAAC,CACjD,CACF;iBACA,GAAG,EAAE,CAAC;QACX,CAAC;IACH,CAAC;AACH,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,8BAA8B,CAAC,KAMpD;IACC,MAAM,EACJ,eAAe,EACf,oBAAoB,EACpB,aAAa,EACb,EAAE,EACF,SAAS,GACV,GAAG,KAAK,CAAC;IACV,MAAM,EAAE;SACL,MAAM,CAAC,UAAU,CAAC;SAClB,GAAG,CAAC;QACH,cAAc,EAAE,oBAAoB;QACpC,QAAQ,EAAE,aAAa;QACvB,cAAc,EAAE,SAAS;KAC1B,CAAC;SACD,KAAK,CAAC,EAAE,CAAC,UAAU,CAAC,QAAQ,EAAE,eAAe,CAAC,CAAC;SAC/C,GAAG,EAAE,CAAC;IACT,MAAM,UAAU,GAAG,iBAAiB,CAAC,kBAAkB,CAAC,eAAe,CAAC,CAAC;IACzE,MAAM,gBAAgB,GAAG,MAAM,EAAE;SAC9B,MAAM,CAAC,EAAE,WAAW,EAAE,YAAY,CAAC,SAAS,EAAE,CAAC;SAC/C,IAAI,CAAC,YAAY,CAAC;SAClB,KAAK,CAAC,GAAG,CAAC,UAAU,EAAE,EAAE,CAAC,YAAY,CAAC,iBAAiB,EAAE,eAAe,CAAC,CAAC,CAAC;SAC3E,GAAG,EAAE,CAAC;IACT,KAAK,MAAM,MAAM,IAAI,gBAAgB,EAAE,CAAC;QACtC,MAAM,EAAE;aACL,MAAM,CAAC,YAAY,CAAC;aACpB,GAAG,CAAC;YACH,EAAE,EAAE,MAAM,CAAC,UAAU,EAAE;YACvB,iBAAiB,EAAE,aAAa;YAChC,SAAS;SACV,CAAC;aACD,KAAK,CAAC,GAAG,CAAC,UAAU,EAAE,EAAE,CAAC,YAAY,CAAC,SAAS,EAAE,MAAM,CAAC,WAAW,CAAC,CAAC,CAAC;aACtE,GAAG,EAAE,CAAC;IACX,CAAC;IACD,MAAM,QAAQ,GAAG,iBAAiB,CAAC,kBAAkB,CAAC,aAAa,CAAC,CAAC;IACrE,MAAM,cAAc,GAAG,MAAM,EAAE;SAC5B,MAAM,CAAC,EAAE,WAAW,EAAE,YAAY,CAAC,SAAS,EAAE,CAAC;SAC/C,IAAI,CAAC,YAAY,CAAC;SAClB,KAAK,CAAC,GAAG,CAAC,QAAQ,EAAE,EAAE,CAAC,YAAY,CAAC,iBAAiB,EAAE,eAAe,CAAC,CAAC,CAAC;SACzE,GAAG,EAAE,CAAC;IACT,KAAK,MAAM,MAAM,IAAI,cAAc,EAAE,CAAC;QACpC,MAAM,EAAE;aACL,MAAM,CAAC,YAAY,CAAC;aACpB,GAAG,CAAC;YACH,EAAE,EAAE,MAAM,CAAC,UAAU,EAAE;YACvB,iBAAiB,EAAE,aAAa;YAChC,UAAU,EAAE,SAAS;YACrB,SAAS;SACV,CAAC;aACD,KAAK,CAAC,GAAG,CAAC,QAAQ,EAAE,EAAE,CAAC,YAAY,CAAC,SAAS,EAAE,MAAM,CAAC,WAAW,CAAC,CAAC,CAAC;aACpE,GAAG,EAAE,CAAC;IACX,CAAC;AACH,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,wBAAwB,CAAC,KAG9C;IACC,MAAM,EAAE,WAAW,EAAE,EAAE,EAAE,GAAG,KAAK,CAAC;IAClC,MAAM,EAAE;SACL,MAAM,CAAC,YAAY,CAAC;SACpB,KAAK,CACJ,GAAG,CAAC,sBAAsB,EAAE,EAAE,EAAE,CAAC,YAAY,CAAC,SAAS,EAAE,WAAW,CAAC,CAAC,CACvE;SACA,GAAG,EAAE,CAAC;IACT,MAAM,EAAE,CAAC,MAAM,CAAC,UAAU,CAAC,CAAC,KAAK,CAAC,EAAE,CAAC,UAAU,CAAC,EAAE,EAAE,WAAW,CAAC,CAAC,CAAC,GAAG,EAAE,CAAC;IACxE,MAAM,gDAAgD,CAAC,EAAE,EAAE,CAAC,WAAW,CAAC,CAAC,CAAC;IAC1E,MAAM,sCAAsC,CAAC,EAAE,EAAE,CAAC,WAAW,CAAC,CAAC,CAAC;AAClE,CAAC","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,
|
|
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(
|
|
9
|
-
.where(and(inArray(
|
|
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(
|
|
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.
|
|
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(
|
|
60
|
-
.where(eq(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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,
|
|
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
|
-
|
|
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,
|
|
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,
|
|
74
|
+
await ensureSqlTables(lockedExecSql, documentPlacementIntentTables);
|
|
73
75
|
return getClientSQLitePersistenceRuntime(lockedExecSql).transaction(async (tx) => {
|
|
74
76
|
const rows = await tx
|
|
75
|
-
.select(
|
|
76
|
-
.
|
|
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(
|
|
86
|
-
.orderBy(asc(
|
|
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
|
|
109
|
-
WHERE
|
|
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(
|
|
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(
|
|
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(
|
|
160
|
+
inArray(localIntents.syncStatus, [
|
|
144
161
|
"pending",
|
|
145
162
|
"blocked",
|
|
146
163
|
"denied",
|
|
147
|
-
]), inArray(
|
|
148
|
-
? [eq(
|
|
164
|
+
]), inArray(localIntents.intentType, DOCUMENT_PLACEMENT_INTENT_TYPES), ...(input.expectedIntentId
|
|
165
|
+
? [eq(localIntents.id, input.expectedIntentId)]
|
|
149
166
|
: []), ...(input.expectedUpdatedAt
|
|
150
|
-
? [eq(
|
|
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.
|
|
166
|
-
FROM
|
|
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.
|
|
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`,
|
|
172
|
-
|
|
173
|
-
|
|
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
|
|
212
|
+
await lockedExecSql(`UPDATE local_intents
|
|
193
213
|
SET sync_status = 'pending', updated_at = ?
|
|
194
|
-
WHERE
|
|
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
|
|
220
|
+
SELECT intent.local_id FROM local_intents intent
|
|
200
221
|
${DENIED_INTENT_ORGANIZATION_JOINS_SQL}
|
|
201
|
-
WHERE
|
|
202
|
-
|
|
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
|
|
231
|
+
...(input?.organizationId
|
|
232
|
+
? [LOCAL_INTENT_LANES.documentPlacement, input.organizationId]
|
|
233
|
+
: []),
|
|
209
234
|
]);
|
|
210
235
|
});
|
|
211
236
|
},
|