@tearleads/client-sdk 0.1.114 → 0.1.115
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/data/persistence/container-contents/containerRecoveryPersistence.js +152 -38
- package/data/persistence/container-contents/containerRecoveryPersistence.js.map +1 -1
- package/data/persistence/container-contents/documentContainerProjectionPersistence.js +47 -18
- package/data/persistence/container-contents/documentContainerProjectionPersistence.js.map +1 -1
- package/data/persistence/container-contents/documentPlacement.js +24 -5
- package/data/persistence/container-contents/documentPlacement.js.map +1 -1
- package/data/persistence/documents/containerDocumentTombstoneHoldsPersistence.js +1 -1
- package/data/persistence/documents/containerDocumentTombstoneHoldsPersistence.js.map +1 -1
- package/data/persistence/documents/documentDiscoveryEvidencePersistence.js +43 -35
- package/data/persistence/documents/documentDiscoveryEvidencePersistence.js.map +1 -1
- package/data/persistence/documents/documentsPersistence.js +4 -58
- package/data/persistence/documents/documentsPersistence.js.map +1 -1
- package/data/persistence/documents/internal/containerDocumentTombstoneHolds.js +15 -11
- package/data/persistence/documents/internal/containerDocumentTombstoneHolds.js.map +1 -1
- package/data/persistence/documents/internal/containerDocumentTombstones.js +87 -35
- package/data/persistence/documents/internal/containerDocumentTombstones.js.map +1 -1
- package/data/persistence/documents/internal/discoveredDocumentPlacement.js +5 -2
- package/data/persistence/documents/internal/discoveredDocumentPlacement.js.map +1 -1
- package/data/persistence/documents/internal/discoveredDocumentUpsert.js +174 -0
- package/data/persistence/documents/internal/discoveredDocumentUpsert.js.map +1 -0
- package/data/persistence/documents/internal/documentRows.js +96 -1
- package/data/persistence/documents/internal/documentRows.js.map +1 -1
- package/data/persistence/documents/internal/rowQueryPersistence.js +38 -20
- package/data/persistence/documents/internal/rowQueryPersistence.js.map +1 -1
- package/data/persistence/documents/records/documentRecordPersistence.d.ts +5 -0
- package/data/persistence/documents/records/documentRecordPersistence.js +41 -6
- package/data/persistence/documents/records/documentRecordPersistence.js.map +1 -1
- package/data/persistence/documents/records/index.d.ts +1 -1
- package/data/persistence/documents/records/index.js +1 -1
- package/data/persistence/documents/records/index.js.map +1 -1
- package/data/persistence/keyingCheckpoints/documentPurgeCheckpointPersistence.d.ts +2 -0
- package/data/persistence/keyingCheckpoints/documentPurgeCheckpointPersistence.js +17 -1
- package/data/persistence/keyingCheckpoints/documentPurgeCheckpointPersistence.js.map +1 -1
- package/package.json +1 -1
- package/workflows/container-contents/remoteReset/containerRemoteReset.js +12 -7
- package/workflows/container-contents/remoteReset/containerRemoteReset.js.map +1 -1
- package/workflows/documents/remoteReset/documentRemoteReset.js +31 -18
- package/workflows/documents/remoteReset/documentRemoteReset.js.map +1 -1
package/package.json
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { and, eq, inArray } from "drizzle-orm";
|
|
1
|
+
import { and, eq, inArray, sql } from "drizzle-orm";
|
|
2
2
|
import { deleteDocumentPlacementRowsByContainerIds, deleteDocumentPlacementRowsByDocumentIds, } from "../../../data/persistence/container-contents/documentContainerProjectionPersistence.js";
|
|
3
3
|
import { recordSupersededContainerBindingsInTransaction } from "../../../data/persistence/container-contents/supersededContainerBindings.js";
|
|
4
4
|
import { containerHydrationTombstones, containerSyncWatermarks, containers, documentContainerProjection, dormantContainerMetadata, dormantMetadataSweepRequests, inContainerIntentLanes, inLocalIntentLane, LOCAL_INTENT_LANES, localIntents, } from "../../../data/sqlite/schema/index.js";
|
|
@@ -124,21 +124,26 @@ export async function resetContainerServerIdentity(input) {
|
|
|
124
124
|
supersededAt: now,
|
|
125
125
|
});
|
|
126
126
|
}
|
|
127
|
-
|
|
127
|
+
// One update per batch: SQLite evaluates every SET value against the old
|
|
128
|
+
// row, and no earlier reset step writes `parent_id`, so COALESCE re-homes
|
|
129
|
+
// exactly the root folders the snapshot read with a null parent.
|
|
130
|
+
for (const rowBatch of remoteResetBatches(input.containerRows)) {
|
|
128
131
|
await tx
|
|
129
132
|
.update(containers)
|
|
130
133
|
.set({
|
|
131
134
|
metadataDocumentId: null,
|
|
132
135
|
organizationId: reset.replacement?.organizationId ?? reset.organizationId,
|
|
133
|
-
|
|
134
|
-
?
|
|
135
|
-
|
|
136
|
+
...(reset.replacement
|
|
137
|
+
? {
|
|
138
|
+
parentId: sql `coalesce(${containers.parentId}, ${reset.replacement.rootContainerId})`,
|
|
139
|
+
systemSlot: null,
|
|
140
|
+
}
|
|
141
|
+
: {}),
|
|
136
142
|
serverCreatedAt: null,
|
|
137
143
|
serverUpdatedAt: null,
|
|
138
|
-
...(reset.replacement ? { systemSlot: null } : {}),
|
|
139
144
|
localUpdatedAt: now,
|
|
140
145
|
})
|
|
141
|
-
.where(
|
|
146
|
+
.where(inArray(containers.id, rowBatch.map((row) => row.id)))
|
|
142
147
|
.run();
|
|
143
148
|
}
|
|
144
149
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"containerRemoteReset.js","sourceRoot":"","sources":["containerRemoteReset.ts"],"names":[],"mappings":"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","sourcesContent":["import { and, eq, inArray } from \"drizzle-orm\";\nimport {\n deleteDocumentPlacementRowsByContainerIds,\n deleteDocumentPlacementRowsByDocumentIds,\n} from \"../../../data/persistence/container-contents/documentContainerProjectionPersistence\";\nimport { recordSupersededContainerBindingsInTransaction } from \"../../../data/persistence/container-contents/supersededContainerBindings\";\nimport {\n containerHydrationTombstones,\n containerSyncWatermarks,\n containers,\n documentContainerProjection,\n dormantContainerMetadata,\n dormantMetadataSweepRequests,\n inContainerIntentLanes,\n inLocalIntentLane,\n LOCAL_INTENT_LANES,\n localIntents,\n} from \"../../../data/sqlite/schema\";\nimport { documentIntentLinkTargets } from \"../../../data/sqlite/schema/documentPlacementIntentSchema\";\nimport { remoteResetBatches } from \"../../../data/sqlite/sqliteBindBudget\";\nimport type { ClientSQLiteTransactionScope } from \"../../../data/sqlite/sqlitePersistenceRuntime\";\nimport { isRemoteResetSyncCursor } from \"./remoteResetCursorScope\";\n\n// Container Contents' part of a remote reset. The sync remote reset calls these\n// in one transaction: it forgets the organization's server state, strips server\n// identity from the folders it keeps, and queues them to be created again.\nexport { clearRemoteResetSyncCursors } from \"./remoteResetCursorScope\";\n\nconst CONTAINER_CREATE_INTENT_TYPE = \"container.create\";\n\nexport interface ContainerRemoteReset {\n readonly organizationId: string;\n readonly replacement?:\n | {\n readonly organizationId: string;\n readonly rootContainerId: string;\n }\n | undefined;\n}\n\nexport interface ResetContainerRow {\n readonly id: string;\n readonly parentId: string | null;\n}\n\n/** The organization's live folders, plus the ids of its dormant ones. */\nexport async function loadContainerResetScope(\n tx: ClientSQLiteTransactionScope,\n organizationId: string,\n): Promise<{\n containerIds: string[];\n containerRows: ResetContainerRow[];\n}> {\n const liveContainerRows = (\n await tx\n .select({ id: containers.id, parentId: containers.parentId })\n .from(containers)\n .where(eq(containers.organizationId, organizationId))\n ).flatMap((row) => (row.id ? [{ id: row.id, parentId: row.parentId }] : []));\n const dormantContainerRows = await tx\n .select({ id: dormantContainerMetadata.containerId })\n .from(dormantContainerMetadata)\n .where(eq(dormantContainerMetadata.organizationId, organizationId));\n const containerRows = liveContainerRows;\n return {\n containerIds: [\n ...new Set([\n ...containerRows.map((row) => row.id),\n ...dormantContainerRows.map((row) => row.id),\n ]),\n ],\n containerRows,\n };\n}\n\n/** Every document placement, which decides the documents a reset covers. */\nexport function listDocumentPlacementRows(tx: ClientSQLiteTransactionScope) {\n return tx.select().from(documentContainerProjection);\n}\n\n/** Counts the queued folder and document move intents a reset clears. */\nexport async function countContainerResetIntents(input: {\n containerIds: readonly string[];\n documentLocalIds: readonly string[];\n tx: ClientSQLiteTransactionScope;\n}): Promise<{\n clearedContainerCreateIntentCount: number;\n clearedContainerMoveIntentCount: number;\n clearedDocumentMoveIntentCount: number;\n}> {\n const containerIdSet = new Set(input.containerIds);\n const documentLocalIdSet = new Set(input.documentLocalIds);\n const intents = await input.tx\n .select({\n lane: localIntents.lane,\n localId: localIntents.localId,\n subjectId: localIntents.subjectId,\n })\n .from(localIntents);\n const containerIntentCount = (lane: string) =>\n intents.filter(\n (row) => row.lane === lane && containerIdSet.has(row.subjectId),\n ).length;\n return {\n clearedContainerCreateIntentCount: containerIntentCount(\n LOCAL_INTENT_LANES.containerCreate,\n ),\n clearedContainerMoveIntentCount: containerIntentCount(\n LOCAL_INTENT_LANES.containerMove,\n ),\n clearedDocumentMoveIntentCount: intents.filter(\n (row) =>\n row.lane === LOCAL_INTENT_LANES.documentPlacement &&\n row.localId !== null &&\n documentLocalIdSet.has(row.localId),\n ).length,\n };\n}\n\n/** Counts the sync cursors `clearRemoteResetSyncCursors` deletes. */\nexport async function countRemoteResetSyncCursors(input: {\n containerIds: readonly string[];\n tx: ClientSQLiteTransactionScope;\n}): Promise<number> {\n const scopedContainerIds = new Set(input.containerIds);\n const syncLanes = await input.tx.select().from(containerSyncWatermarks);\n return syncLanes.filter((row) =>\n isRemoteResetSyncCursor({\n containerIds: scopedContainerIds,\n row,\n }),\n ).length;\n}\n\nexport async function clearDormantContainerMetadataForRemoteReset(\n tx: ClientSQLiteTransactionScope,\n organizationId: string,\n): Promise<void> {\n for (const table of [\n dormantContainerMetadata,\n dormantMetadataSweepRequests,\n ] as const) {\n await tx\n .delete(table)\n .where(eq(table.organizationId, organizationId))\n .run();\n }\n}\n\n/** Clears the folders' placements, queued intents, and hydration tombstones. */\nexport async function clearContainerRowsForRemoteReset(input: {\n containerIds: readonly string[];\n tx: ClientSQLiteTransactionScope;\n}): Promise<void> {\n for (const containerBatch of remoteResetBatches(input.containerIds)) {\n await deleteDocumentPlacementRowsByContainerIds(input.tx, containerBatch);\n await input.tx\n .delete(localIntents)\n .where(\n and(\n inContainerIntentLanes(),\n inArray(localIntents.subjectId, containerBatch),\n ),\n )\n .run();\n await input.tx\n .delete(containerHydrationTombstones)\n .where(inArray(containerHydrationTombstones.containerId, containerBatch))\n .run();\n }\n}\n\n/** Clears the documents' placements and their queued placement intents. */\nexport async function clearDocumentPlacementsForRemoteReset(input: {\n documentLocalIds: readonly string[];\n oldDocumentIds: readonly string[];\n tx: ClientSQLiteTransactionScope;\n}): Promise<void> {\n const { tx } = input;\n for (const documentIdBatch of remoteResetBatches(input.oldDocumentIds)) {\n await deleteDocumentPlacementRowsByDocumentIds(tx, documentIdBatch);\n }\n for (const localIdBatch of remoteResetBatches(input.documentLocalIds)) {\n const placementIntents = and(\n inLocalIntentLane(LOCAL_INTENT_LANES.documentPlacement),\n inArray(localIntents.localId, localIdBatch),\n );\n await tx\n .delete(documentIntentLinkTargets)\n .where(\n inArray(\n documentIntentLinkTargets.intentId,\n tx\n .select({ id: localIntents.id })\n .from(localIntents)\n .where(placementIntents),\n ),\n )\n .run();\n await tx.delete(localIntents).where(placementIntents).run();\n }\n}\n\n/**\n * Drops the folders' server timestamps and metadata document, and moves them\n * to the replacement organization when there is one.\n */\nexport async function resetContainerServerIdentity(input: {\n containerIds: readonly string[];\n containerRows: readonly ResetContainerRow[];\n now: string;\n reset: ContainerRemoteReset;\n tx: ClientSQLiteTransactionScope;\n}): Promise<void> {\n const { now, reset, tx } = input;\n // Replacement re-homes folders under their ids; the purged organization\n // can never bind them again on this device.\n for (const containerIds of reset.replacement\n ? remoteResetBatches(input.containerIds)\n : []) {\n await recordSupersededContainerBindingsInTransaction(tx, {\n containerIds,\n organizationId: reset.organizationId,\n supersededAt: now,\n });\n }\n for (const row of input.containerRows) {\n await tx\n .update(containers)\n .set({\n metadataDocumentId: null,\n organizationId:\n reset.replacement?.organizationId ?? reset.organizationId,\n parentId:\n reset.replacement && row.parentId === null\n ? reset.replacement.rootContainerId\n : row.parentId,\n serverCreatedAt: null,\n serverUpdatedAt: null,\n ...(reset.replacement ? { systemSlot: null } : {}),\n localUpdatedAt: now,\n })\n .where(eq(containers.id, row.id))\n .run();\n }\n}\n\n/** Queues a create for every kept folder that has a parent to create it in. */\nexport async function queueContainerCreatesForRemoteReset(input: {\n containerRows: readonly ResetContainerRow[];\n now: string;\n reset: ContainerRemoteReset;\n tx: ClientSQLiteTransactionScope;\n}): Promise<number> {\n const rows = input.containerRows.flatMap((row) => {\n const parentId =\n row.parentId ?? input.reset.replacement?.rootContainerId ?? null;\n return parentId ? [{ id: row.id, parentId }] : [];\n });\n if (rows.length === 0) return 0;\n for (const rowBatch of remoteResetBatches(rows)) {\n await input.tx\n .insert(localIntents)\n .values(\n rowBatch.map((row) => ({\n id: crypto.randomUUID(),\n lane: LOCAL_INTENT_LANES.containerCreate,\n subjectId: row.id,\n targetContainerId: row.parentId,\n intentType: CONTAINER_CREATE_INTENT_TYPE,\n syncStatus: \"pending\" as const,\n remoteContainerId: null,\n remoteMetadataAccessStateHash: null,\n remoteMetadataDocumentId: null,\n lastError: null,\n createdAt: input.now,\n updatedAt: input.now,\n })),\n )\n .run();\n }\n return rows.length;\n}\n"]}
|
|
1
|
+
{"version":3,"file":"containerRemoteReset.js","sourceRoot":"","sources":["containerRemoteReset.ts"],"names":[],"mappings":"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","sourcesContent":["import { and, eq, inArray, sql } from \"drizzle-orm\";\nimport {\n deleteDocumentPlacementRowsByContainerIds,\n deleteDocumentPlacementRowsByDocumentIds,\n} from \"../../../data/persistence/container-contents/documentContainerProjectionPersistence\";\nimport { recordSupersededContainerBindingsInTransaction } from \"../../../data/persistence/container-contents/supersededContainerBindings\";\nimport {\n containerHydrationTombstones,\n containerSyncWatermarks,\n containers,\n documentContainerProjection,\n dormantContainerMetadata,\n dormantMetadataSweepRequests,\n inContainerIntentLanes,\n inLocalIntentLane,\n LOCAL_INTENT_LANES,\n localIntents,\n} from \"../../../data/sqlite/schema\";\nimport { documentIntentLinkTargets } from \"../../../data/sqlite/schema/documentPlacementIntentSchema\";\nimport { remoteResetBatches } from \"../../../data/sqlite/sqliteBindBudget\";\nimport type { ClientSQLiteTransactionScope } from \"../../../data/sqlite/sqlitePersistenceRuntime\";\nimport { isRemoteResetSyncCursor } from \"./remoteResetCursorScope\";\n\n// Container Contents' part of a remote reset. The sync remote reset calls these\n// in one transaction: it forgets the organization's server state, strips server\n// identity from the folders it keeps, and queues them to be created again.\nexport { clearRemoteResetSyncCursors } from \"./remoteResetCursorScope\";\n\nconst CONTAINER_CREATE_INTENT_TYPE = \"container.create\";\n\nexport interface ContainerRemoteReset {\n readonly organizationId: string;\n readonly replacement?:\n | {\n readonly organizationId: string;\n readonly rootContainerId: string;\n }\n | undefined;\n}\n\nexport interface ResetContainerRow {\n readonly id: string;\n readonly parentId: string | null;\n}\n\n/** The organization's live folders, plus the ids of its dormant ones. */\nexport async function loadContainerResetScope(\n tx: ClientSQLiteTransactionScope,\n organizationId: string,\n): Promise<{\n containerIds: string[];\n containerRows: ResetContainerRow[];\n}> {\n const liveContainerRows = (\n await tx\n .select({ id: containers.id, parentId: containers.parentId })\n .from(containers)\n .where(eq(containers.organizationId, organizationId))\n ).flatMap((row) => (row.id ? [{ id: row.id, parentId: row.parentId }] : []));\n const dormantContainerRows = await tx\n .select({ id: dormantContainerMetadata.containerId })\n .from(dormantContainerMetadata)\n .where(eq(dormantContainerMetadata.organizationId, organizationId));\n const containerRows = liveContainerRows;\n return {\n containerIds: [\n ...new Set([\n ...containerRows.map((row) => row.id),\n ...dormantContainerRows.map((row) => row.id),\n ]),\n ],\n containerRows,\n };\n}\n\n/** Every document placement, which decides the documents a reset covers. */\nexport function listDocumentPlacementRows(tx: ClientSQLiteTransactionScope) {\n return tx.select().from(documentContainerProjection);\n}\n\n/** Counts the queued folder and document move intents a reset clears. */\nexport async function countContainerResetIntents(input: {\n containerIds: readonly string[];\n documentLocalIds: readonly string[];\n tx: ClientSQLiteTransactionScope;\n}): Promise<{\n clearedContainerCreateIntentCount: number;\n clearedContainerMoveIntentCount: number;\n clearedDocumentMoveIntentCount: number;\n}> {\n const containerIdSet = new Set(input.containerIds);\n const documentLocalIdSet = new Set(input.documentLocalIds);\n const intents = await input.tx\n .select({\n lane: localIntents.lane,\n localId: localIntents.localId,\n subjectId: localIntents.subjectId,\n })\n .from(localIntents);\n const containerIntentCount = (lane: string) =>\n intents.filter(\n (row) => row.lane === lane && containerIdSet.has(row.subjectId),\n ).length;\n return {\n clearedContainerCreateIntentCount: containerIntentCount(\n LOCAL_INTENT_LANES.containerCreate,\n ),\n clearedContainerMoveIntentCount: containerIntentCount(\n LOCAL_INTENT_LANES.containerMove,\n ),\n clearedDocumentMoveIntentCount: intents.filter(\n (row) =>\n row.lane === LOCAL_INTENT_LANES.documentPlacement &&\n row.localId !== null &&\n documentLocalIdSet.has(row.localId),\n ).length,\n };\n}\n\n/** Counts the sync cursors `clearRemoteResetSyncCursors` deletes. */\nexport async function countRemoteResetSyncCursors(input: {\n containerIds: readonly string[];\n tx: ClientSQLiteTransactionScope;\n}): Promise<number> {\n const scopedContainerIds = new Set(input.containerIds);\n const syncLanes = await input.tx.select().from(containerSyncWatermarks);\n return syncLanes.filter((row) =>\n isRemoteResetSyncCursor({\n containerIds: scopedContainerIds,\n row,\n }),\n ).length;\n}\n\nexport async function clearDormantContainerMetadataForRemoteReset(\n tx: ClientSQLiteTransactionScope,\n organizationId: string,\n): Promise<void> {\n for (const table of [\n dormantContainerMetadata,\n dormantMetadataSweepRequests,\n ] as const) {\n await tx\n .delete(table)\n .where(eq(table.organizationId, organizationId))\n .run();\n }\n}\n\n/** Clears the folders' placements, queued intents, and hydration tombstones. */\nexport async function clearContainerRowsForRemoteReset(input: {\n containerIds: readonly string[];\n tx: ClientSQLiteTransactionScope;\n}): Promise<void> {\n for (const containerBatch of remoteResetBatches(input.containerIds)) {\n await deleteDocumentPlacementRowsByContainerIds(input.tx, containerBatch);\n await input.tx\n .delete(localIntents)\n .where(\n and(\n inContainerIntentLanes(),\n inArray(localIntents.subjectId, containerBatch),\n ),\n )\n .run();\n await input.tx\n .delete(containerHydrationTombstones)\n .where(inArray(containerHydrationTombstones.containerId, containerBatch))\n .run();\n }\n}\n\n/** Clears the documents' placements and their queued placement intents. */\nexport async function clearDocumentPlacementsForRemoteReset(input: {\n documentLocalIds: readonly string[];\n oldDocumentIds: readonly string[];\n tx: ClientSQLiteTransactionScope;\n}): Promise<void> {\n const { tx } = input;\n for (const documentIdBatch of remoteResetBatches(input.oldDocumentIds)) {\n await deleteDocumentPlacementRowsByDocumentIds(tx, documentIdBatch);\n }\n for (const localIdBatch of remoteResetBatches(input.documentLocalIds)) {\n const placementIntents = and(\n inLocalIntentLane(LOCAL_INTENT_LANES.documentPlacement),\n inArray(localIntents.localId, localIdBatch),\n );\n await tx\n .delete(documentIntentLinkTargets)\n .where(\n inArray(\n documentIntentLinkTargets.intentId,\n tx\n .select({ id: localIntents.id })\n .from(localIntents)\n .where(placementIntents),\n ),\n )\n .run();\n await tx.delete(localIntents).where(placementIntents).run();\n }\n}\n\n/**\n * Drops the folders' server timestamps and metadata document, and moves them\n * to the replacement organization when there is one.\n */\nexport async function resetContainerServerIdentity(input: {\n containerIds: readonly string[];\n containerRows: readonly ResetContainerRow[];\n now: string;\n reset: ContainerRemoteReset;\n tx: ClientSQLiteTransactionScope;\n}): Promise<void> {\n const { now, reset, tx } = input;\n // Replacement re-homes folders under their ids; the purged organization\n // can never bind them again on this device.\n for (const containerIds of reset.replacement\n ? remoteResetBatches(input.containerIds)\n : []) {\n await recordSupersededContainerBindingsInTransaction(tx, {\n containerIds,\n organizationId: reset.organizationId,\n supersededAt: now,\n });\n }\n // One update per batch: SQLite evaluates every SET value against the old\n // row, and no earlier reset step writes `parent_id`, so COALESCE re-homes\n // exactly the root folders the snapshot read with a null parent.\n for (const rowBatch of remoteResetBatches(input.containerRows)) {\n await tx\n .update(containers)\n .set({\n metadataDocumentId: null,\n organizationId:\n reset.replacement?.organizationId ?? reset.organizationId,\n ...(reset.replacement\n ? {\n parentId: sql`coalesce(${containers.parentId}, ${reset.replacement.rootContainerId})`,\n systemSlot: null,\n }\n : {}),\n serverCreatedAt: null,\n serverUpdatedAt: null,\n localUpdatedAt: now,\n })\n .where(\n inArray(\n containers.id,\n rowBatch.map((row) => row.id),\n ),\n )\n .run();\n }\n}\n\n/** Queues a create for every kept folder that has a parent to create it in. */\nexport async function queueContainerCreatesForRemoteReset(input: {\n containerRows: readonly ResetContainerRow[];\n now: string;\n reset: ContainerRemoteReset;\n tx: ClientSQLiteTransactionScope;\n}): Promise<number> {\n const rows = input.containerRows.flatMap((row) => {\n const parentId =\n row.parentId ?? input.reset.replacement?.rootContainerId ?? null;\n return parentId ? [{ id: row.id, parentId }] : [];\n });\n if (rows.length === 0) return 0;\n for (const rowBatch of remoteResetBatches(rows)) {\n await input.tx\n .insert(localIntents)\n .values(\n rowBatch.map((row) => ({\n id: crypto.randomUUID(),\n lane: LOCAL_INTENT_LANES.containerCreate,\n subjectId: row.id,\n targetContainerId: row.parentId,\n intentType: CONTAINER_CREATE_INTENT_TYPE,\n syncStatus: \"pending\" as const,\n remoteContainerId: null,\n remoteMetadataAccessStateHash: null,\n remoteMetadataDocumentId: null,\n lastError: null,\n createdAt: input.now,\n updatedAt: input.now,\n })),\n )\n .run();\n }\n return rows.length;\n}\n"]}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { and, eq, inArray, or } from "drizzle-orm";
|
|
1
|
+
import { and, eq, inArray, or, sql } from "drizzle-orm";
|
|
2
2
|
import { queuedUpdateRow, unqueueDocumentScopesInTransaction, } from "../../../data/persistence/documents/records/documentPendingUpdatePersistence.js";
|
|
3
3
|
import { CLEARED_DOCUMENT_SYNC_FAILURE } from "../../../data/persistence/documents/records/documentSyncFailurePersistence.js";
|
|
4
4
|
import { documentAttachmentBlobProjection, documentHistoryUpdates, documentPendingAttachments, documentProjection, documents, } from "../../../data/sqlite/schema/index.js";
|
|
@@ -71,24 +71,37 @@ export async function clearDocumentRowsForRemoteReset(input) {
|
|
|
71
71
|
*/
|
|
72
72
|
export async function resetDocumentServerIdentity(input) {
|
|
73
73
|
const { now, tx } = input;
|
|
74
|
+
const localIdsByAppKind = new Map();
|
|
74
75
|
for (const row of input.documentRows) {
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
.
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
76
|
+
const localIds = localIdsByAppKind.get(row.appKind);
|
|
77
|
+
if (localIds)
|
|
78
|
+
localIds.push(row.localId);
|
|
79
|
+
else
|
|
80
|
+
localIdsByAppKind.set(row.appKind, [row.localId]);
|
|
81
|
+
}
|
|
82
|
+
// One update per batch: SQLite evaluates every SET value against the old
|
|
83
|
+
// row, so COALESCE keeps the id being cleared (or the earlier recovery id)
|
|
84
|
+
// per row; no earlier reset step writes either column.
|
|
85
|
+
for (const [appKind, localIds] of localIdsByAppKind) {
|
|
86
|
+
for (const localIdBatch of remoteResetBatches(localIds)) {
|
|
87
|
+
await tx
|
|
88
|
+
.update(documents)
|
|
89
|
+
.set({
|
|
90
|
+
accessEpoch: 1,
|
|
91
|
+
accessStateHash: null,
|
|
92
|
+
contentKeyBundle: null,
|
|
93
|
+
documentId: null,
|
|
94
|
+
documentKekTargets: null,
|
|
95
|
+
documentManifestBundle: null,
|
|
96
|
+
lastCommitLsn: null,
|
|
97
|
+
pullContinuation: null,
|
|
98
|
+
recoveryDocumentId: sql `coalesce(${documents.documentId}, ${documents.recoveryDocumentId})`,
|
|
99
|
+
...CLEARED_DOCUMENT_SYNC_FAILURE,
|
|
100
|
+
updatedAt: now,
|
|
101
|
+
})
|
|
102
|
+
.where(and(eq(documents.appKind, appKind), inArray(documents.localId, localIdBatch)))
|
|
103
|
+
.run();
|
|
104
|
+
}
|
|
92
105
|
}
|
|
93
106
|
for (const localIdBatch of remoteResetBatches(input.documentLocalIds)) {
|
|
94
107
|
await tx
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"documentRemoteReset.js","sourceRoot":"","sources":["documentRemoteReset.ts"],"names":[],"mappings":"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","sourcesContent":["import { and, eq, inArray, or } from \"drizzle-orm\";\nimport {\n queuedUpdateRow,\n unqueueDocumentScopesInTransaction,\n} from \"../../../data/persistence/documents/records/documentPendingUpdatePersistence\";\nimport { CLEARED_DOCUMENT_SYNC_FAILURE } from \"../../../data/persistence/documents/records/documentSyncFailurePersistence\";\nimport {\n documentAttachmentBlobProjection,\n documentHistoryUpdates,\n documentPendingAttachments,\n documentProjection,\n documents,\n} from \"../../../data/sqlite/schema\";\nimport { remoteResetBatches } from \"../../../data/sqlite/sqliteBindBudget\";\nimport type { ClientSQLiteTransactionScope } from \"../../../data/sqlite/sqlitePersistenceRuntime\";\nimport { clearResetPendingAttachments } from \"./remoteResetAttachments\";\nimport type {\n ResetAttachmentUpload,\n ResetDocumentScope,\n ResetDocumentUpdate,\n} from \"./remoteResetPlans\";\n\n// Document Store's part of a remote reset. The sync remote reset calls these in\n// one transaction: it forgets what the server held for the organization's\n// documents, strips their server identity, and queues their local history and\n// attachments to be published again.\nexport { clearDocumentDiscoveryEvidenceOnRemoteReset } from \"../../../data/persistence/documents/documentDiscoveryReset\";\nexport {\n buildResetPlans,\n type ResetAttachmentUpload,\n type ResetDocumentUpdate,\n} from \"./remoteResetPlans\";\n\nexport interface ResetDocumentRow extends ResetDocumentScope {\n readonly documentId: string | null;\n readonly recoveryDocumentId: string | null;\n}\n\ninterface DocumentPlacementRow {\n readonly containerId: string;\n readonly documentId: string;\n}\n\n/**\n * The documents a reset covers: metadata documents of its folders, documents\n * projected into the organization or its folders, and documents placed in them.\n */\nexport async function loadDocumentResetScope(input: {\n containerIds: readonly string[];\n organizationId: string;\n placementRows: readonly DocumentPlacementRow[];\n tx: ClientSQLiteTransactionScope;\n}): Promise<{\n documentLocalIds: string[];\n documentRows: ResetDocumentRow[];\n oldDocumentIds: string[];\n}> {\n const { organizationId, tx } = input;\n const containerIdSet = new Set(input.containerIds);\n const [allDocuments, projectionRows] = await Promise.all([\n tx\n .select({\n appKind: documents.appKind,\n documentId: documents.documentId,\n localId: documents.localId,\n recoveryDocumentId: documents.recoveryDocumentId,\n })\n .from(documents),\n tx.select().from(documentProjection),\n ]);\n const linkedDocumentIds = new Set(\n input.placementRows\n .filter((row) => containerIdSet.has(row.containerId))\n .map((row) => row.documentId),\n );\n const projectedLocalIds = new Set(\n projectionRows\n .filter(\n (row) =>\n (row.containerId !== null && containerIdSet.has(row.containerId)) ||\n row.organizationId === organizationId ||\n (row.documentId !== null && linkedDocumentIds.has(row.documentId)),\n )\n .flatMap((row) => (row.localId ? [row.localId] : [])),\n );\n const documentRows = allDocuments.filter(\n (row) =>\n (row.appKind === \"container-metadata\" &&\n containerIdSet.has(row.localId)) ||\n (row.appKind === \"documents\" && projectedLocalIds.has(row.localId)) ||\n (row.documentId !== null && linkedDocumentIds.has(row.documentId)),\n );\n return {\n documentLocalIds: [\n ...new Set(\n documentRows\n .filter((row) => row.appKind === \"documents\")\n .map((row) => row.localId),\n ),\n ],\n documentRows,\n oldDocumentIds: [\n ...new Set(\n documentRows.flatMap((row) => (row.documentId ? [row.documentId] : [])),\n ),\n ],\n };\n}\n\n/** Clears pending attachments (keeping re-queued bytes) and queued updates. */\nexport async function clearDocumentRowsForRemoteReset(input: {\n attachmentUploads: readonly ResetAttachmentUpload[];\n documentLocalIds: readonly string[];\n documentRows: readonly ResetDocumentRow[];\n tx: ClientSQLiteTransactionScope;\n}): Promise<void> {\n const { tx } = input;\n await clearResetPendingAttachments({\n attachmentUploads: input.attachmentUploads,\n documentLocalIds: input.documentLocalIds,\n tx,\n });\n // Sync failures clear with the document columns in resetDocumentServerIdentity.\n for (const documentBatch of remoteResetBatches(input.documentRows)) {\n await unqueueDocumentScopesInTransaction(tx, documentBatch);\n }\n}\n\n/**\n * Drops each document's server id, keys, manifest, and sync position, keeping\n * the old id for recovery, and moves its projection to `organizationId`.\n */\nexport async function resetDocumentServerIdentity(input: {\n documentLocalIds: readonly string[];\n documentRows: readonly ResetDocumentRow[];\n now: string;\n organizationId: string;\n tx: ClientSQLiteTransactionScope;\n}): Promise<void> {\n const { now, tx } = input;\n for (const row of input.documentRows) {\n await tx\n .update(documents)\n .set({\n accessEpoch: 1,\n accessStateHash: null,\n contentKeyBundle: null,\n documentId: null,\n documentKekTargets: null,\n documentManifestBundle: null,\n lastCommitLsn: null,\n pullContinuation: null,\n recoveryDocumentId: row.documentId ?? row.recoveryDocumentId,\n ...CLEARED_DOCUMENT_SYNC_FAILURE,\n updatedAt: now,\n })\n .where(\n and(\n eq(documents.appKind, row.appKind),\n eq(documents.localId, row.localId),\n ),\n )\n .run();\n }\n for (const localIdBatch of remoteResetBatches(input.documentLocalIds)) {\n await tx\n .update(documentProjection)\n .set({\n documentId: null,\n organizationId: input.organizationId,\n })\n .where(inArray(documentProjection.localId, localIdBatch))\n .run();\n }\n}\n\n/** Queues each document's rebuilt local history as one outbox update. */\nexport async function queueDocumentRepublishForRemoteReset(input: {\n documentUpdates: readonly ResetDocumentUpdate[];\n now: string;\n tx: ClientSQLiteTransactionScope;\n}): Promise<void> {\n for (const updateBatch of remoteResetBatches(input.documentUpdates)) {\n await input.tx\n .insert(documentHistoryUpdates)\n .values(\n updateBatch.map((update) =>\n queuedUpdateRow({\n createdAt: input.now,\n origin: \"outbox\",\n pendingUpdate: update,\n scope: update,\n updateId: crypto.randomUUID(),\n }),\n ),\n )\n .run();\n }\n}\n\n/** Queues the attachments the documents still advertise for upload. */\nexport async function queueAttachmentReuploadsForRemoteReset(input: {\n attachmentUploads: readonly ResetAttachmentUpload[];\n now: string;\n tx: ClientSQLiteTransactionScope;\n}): Promise<void> {\n for (const attachmentBatch of remoteResetBatches(input.attachmentUploads)) {\n await input.tx\n .insert(documentPendingAttachments)\n .values(\n attachmentBatch.map((attachment) => ({\n ...attachment,\n createdAt: input.now,\n })),\n )\n .run();\n await input.tx\n .delete(documentAttachmentBlobProjection)\n .where(\n or(\n ...attachmentBatch.map((attachment) =>\n and(\n eq(documentAttachmentBlobProjection.localId, attachment.localId),\n eq(documentAttachmentBlobProjection.slotId, attachment.slotId),\n ),\n ),\n ),\n )\n .run();\n }\n}\n"]}
|
|
1
|
+
{"version":3,"file":"documentRemoteReset.js","sourceRoot":"","sources":["documentRemoteReset.ts"],"names":[],"mappings":"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","sourcesContent":["import { and, eq, inArray, or, sql } from \"drizzle-orm\";\nimport {\n queuedUpdateRow,\n unqueueDocumentScopesInTransaction,\n} from \"../../../data/persistence/documents/records/documentPendingUpdatePersistence\";\nimport { CLEARED_DOCUMENT_SYNC_FAILURE } from \"../../../data/persistence/documents/records/documentSyncFailurePersistence\";\nimport {\n documentAttachmentBlobProjection,\n documentHistoryUpdates,\n documentPendingAttachments,\n documentProjection,\n documents,\n} from \"../../../data/sqlite/schema\";\nimport { remoteResetBatches } from \"../../../data/sqlite/sqliteBindBudget\";\nimport type { ClientSQLiteTransactionScope } from \"../../../data/sqlite/sqlitePersistenceRuntime\";\nimport { clearResetPendingAttachments } from \"./remoteResetAttachments\";\nimport type {\n ResetAttachmentUpload,\n ResetDocumentScope,\n ResetDocumentUpdate,\n} from \"./remoteResetPlans\";\n\n// Document Store's part of a remote reset. The sync remote reset calls these in\n// one transaction: it forgets what the server held for the organization's\n// documents, strips their server identity, and queues their local history and\n// attachments to be published again.\nexport { clearDocumentDiscoveryEvidenceOnRemoteReset } from \"../../../data/persistence/documents/documentDiscoveryReset\";\nexport {\n buildResetPlans,\n type ResetAttachmentUpload,\n type ResetDocumentUpdate,\n} from \"./remoteResetPlans\";\n\nexport interface ResetDocumentRow extends ResetDocumentScope {\n readonly documentId: string | null;\n readonly recoveryDocumentId: string | null;\n}\n\ninterface DocumentPlacementRow {\n readonly containerId: string;\n readonly documentId: string;\n}\n\n/**\n * The documents a reset covers: metadata documents of its folders, documents\n * projected into the organization or its folders, and documents placed in them.\n */\nexport async function loadDocumentResetScope(input: {\n containerIds: readonly string[];\n organizationId: string;\n placementRows: readonly DocumentPlacementRow[];\n tx: ClientSQLiteTransactionScope;\n}): Promise<{\n documentLocalIds: string[];\n documentRows: ResetDocumentRow[];\n oldDocumentIds: string[];\n}> {\n const { organizationId, tx } = input;\n const containerIdSet = new Set(input.containerIds);\n const [allDocuments, projectionRows] = await Promise.all([\n tx\n .select({\n appKind: documents.appKind,\n documentId: documents.documentId,\n localId: documents.localId,\n recoveryDocumentId: documents.recoveryDocumentId,\n })\n .from(documents),\n tx.select().from(documentProjection),\n ]);\n const linkedDocumentIds = new Set(\n input.placementRows\n .filter((row) => containerIdSet.has(row.containerId))\n .map((row) => row.documentId),\n );\n const projectedLocalIds = new Set(\n projectionRows\n .filter(\n (row) =>\n (row.containerId !== null && containerIdSet.has(row.containerId)) ||\n row.organizationId === organizationId ||\n (row.documentId !== null && linkedDocumentIds.has(row.documentId)),\n )\n .flatMap((row) => (row.localId ? [row.localId] : [])),\n );\n const documentRows = allDocuments.filter(\n (row) =>\n (row.appKind === \"container-metadata\" &&\n containerIdSet.has(row.localId)) ||\n (row.appKind === \"documents\" && projectedLocalIds.has(row.localId)) ||\n (row.documentId !== null && linkedDocumentIds.has(row.documentId)),\n );\n return {\n documentLocalIds: [\n ...new Set(\n documentRows\n .filter((row) => row.appKind === \"documents\")\n .map((row) => row.localId),\n ),\n ],\n documentRows,\n oldDocumentIds: [\n ...new Set(\n documentRows.flatMap((row) => (row.documentId ? [row.documentId] : [])),\n ),\n ],\n };\n}\n\n/** Clears pending attachments (keeping re-queued bytes) and queued updates. */\nexport async function clearDocumentRowsForRemoteReset(input: {\n attachmentUploads: readonly ResetAttachmentUpload[];\n documentLocalIds: readonly string[];\n documentRows: readonly ResetDocumentRow[];\n tx: ClientSQLiteTransactionScope;\n}): Promise<void> {\n const { tx } = input;\n await clearResetPendingAttachments({\n attachmentUploads: input.attachmentUploads,\n documentLocalIds: input.documentLocalIds,\n tx,\n });\n // Sync failures clear with the document columns in resetDocumentServerIdentity.\n for (const documentBatch of remoteResetBatches(input.documentRows)) {\n await unqueueDocumentScopesInTransaction(tx, documentBatch);\n }\n}\n\n/**\n * Drops each document's server id, keys, manifest, and sync position, keeping\n * the old id for recovery, and moves its projection to `organizationId`.\n */\nexport async function resetDocumentServerIdentity(input: {\n documentLocalIds: readonly string[];\n documentRows: readonly ResetDocumentRow[];\n now: string;\n organizationId: string;\n tx: ClientSQLiteTransactionScope;\n}): Promise<void> {\n const { now, tx } = input;\n const localIdsByAppKind = new Map<string, string[]>();\n for (const row of input.documentRows) {\n const localIds = localIdsByAppKind.get(row.appKind);\n if (localIds) localIds.push(row.localId);\n else localIdsByAppKind.set(row.appKind, [row.localId]);\n }\n // One update per batch: SQLite evaluates every SET value against the old\n // row, so COALESCE keeps the id being cleared (or the earlier recovery id)\n // per row; no earlier reset step writes either column.\n for (const [appKind, localIds] of localIdsByAppKind) {\n for (const localIdBatch of remoteResetBatches(localIds)) {\n await tx\n .update(documents)\n .set({\n accessEpoch: 1,\n accessStateHash: null,\n contentKeyBundle: null,\n documentId: null,\n documentKekTargets: null,\n documentManifestBundle: null,\n lastCommitLsn: null,\n pullContinuation: null,\n recoveryDocumentId: sql`coalesce(${documents.documentId}, ${documents.recoveryDocumentId})`,\n ...CLEARED_DOCUMENT_SYNC_FAILURE,\n updatedAt: now,\n })\n .where(\n and(\n eq(documents.appKind, appKind),\n inArray(documents.localId, localIdBatch),\n ),\n )\n .run();\n }\n }\n for (const localIdBatch of remoteResetBatches(input.documentLocalIds)) {\n await tx\n .update(documentProjection)\n .set({\n documentId: null,\n organizationId: input.organizationId,\n })\n .where(inArray(documentProjection.localId, localIdBatch))\n .run();\n }\n}\n\n/** Queues each document's rebuilt local history as one outbox update. */\nexport async function queueDocumentRepublishForRemoteReset(input: {\n documentUpdates: readonly ResetDocumentUpdate[];\n now: string;\n tx: ClientSQLiteTransactionScope;\n}): Promise<void> {\n for (const updateBatch of remoteResetBatches(input.documentUpdates)) {\n await input.tx\n .insert(documentHistoryUpdates)\n .values(\n updateBatch.map((update) =>\n queuedUpdateRow({\n createdAt: input.now,\n origin: \"outbox\",\n pendingUpdate: update,\n scope: update,\n updateId: crypto.randomUUID(),\n }),\n ),\n )\n .run();\n }\n}\n\n/** Queues the attachments the documents still advertise for upload. */\nexport async function queueAttachmentReuploadsForRemoteReset(input: {\n attachmentUploads: readonly ResetAttachmentUpload[];\n now: string;\n tx: ClientSQLiteTransactionScope;\n}): Promise<void> {\n for (const attachmentBatch of remoteResetBatches(input.attachmentUploads)) {\n await input.tx\n .insert(documentPendingAttachments)\n .values(\n attachmentBatch.map((attachment) => ({\n ...attachment,\n createdAt: input.now,\n })),\n )\n .run();\n await input.tx\n .delete(documentAttachmentBlobProjection)\n .where(\n or(\n ...attachmentBatch.map((attachment) =>\n and(\n eq(documentAttachmentBlobProjection.localId, attachment.localId),\n eq(documentAttachmentBlobProjection.slotId, attachment.slotId),\n ),\n ),\n ),\n )\n .run();\n }\n}\n"]}
|