@tearleads/client-sdk 0.1.114 → 0.1.116
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/containerContents/documentDiscovery.js +5 -3
- package/client/containerContents/documentDiscovery.js.map +1 -1
- 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/internal/api-client/ApiClient.d.ts +118 -0
- package/internal/api-client/ApiClient.js +4 -0
- package/internal/api-client/ApiClient.js.map +1 -1
- package/internal/api-client/index.d.ts +1 -1
- package/internal/api-client/index.js.map +1 -1
- package/internal/api-client/principalPolicyPages.d.ts +15 -1
- package/internal/api-client/principalPolicyPages.js +48 -2
- package/internal/api-client/principalPolicyPages.js.map +1 -1
- package/internal/api-client/principalPolicyRequests.d.ts +118 -1
- package/internal/api-client/principalPolicyRequests.js +4 -1
- package/internal/api-client/principalPolicyRequests.js.map +1 -1
- package/internal/validators/operation/principalPolicyPageQuery.d.ts +9 -0
- package/internal/validators/operation/principalPolicyPageQuery.js +10 -0
- package/internal/validators/operation/principalPolicyPageQuery.js.map +1 -1
- package/internal/validators/operation/principals.d.ts +37 -4
- package/internal/validators/operation/principals.js +5 -3
- package/internal/validators/operation/principals.js.map +1 -1
- package/internal/validators/response/index.d.ts +1 -1
- package/internal/validators/response/index.js +1 -1
- package/internal/validators/response/index.js.map +1 -1
- package/internal/validators/response/principalPolicyPage.d.ts +497 -1
- package/internal/validators/response/principalPolicyPage.js +18 -0
- package/internal/validators/response/principalPolicyPage.js.map +1 -1
- package/package.json +1 -1
- package/workflows/container-contents/documentLinks/index.d.ts +4 -0
- package/workflows/container-contents/documentLinks/index.js +10 -3
- package/workflows/container-contents/documentLinks/index.js.map +1 -1
- package/workflows/container-contents/remoteHydration/remoteContainers/remoteContainerPageHydration.js +7 -2
- package/workflows/container-contents/remoteHydration/remoteContainers/remoteContainerPageHydration.js.map +1 -1
- package/workflows/container-contents/remoteReset/containerRemoteReset.js +12 -7
- package/workflows/container-contents/remoteReset/containerRemoteReset.js.map +1 -1
- package/workflows/container-contents/tombstones/documentTombstoneEvidence.js +6 -5
- package/workflows/container-contents/tombstones/documentTombstoneEvidence.js.map +1 -1
- package/workflows/documents/remoteReset/documentRemoteReset.js +31 -18
- package/workflows/documents/remoteReset/documentRemoteReset.js.map +1 -1
- package/workflows/principals/principalPolicyHistoryReader.d.ts +6 -1
- package/workflows/principals/principalPolicyHistoryReader.js +4 -1
- package/workflows/principals/principalPolicyHistoryReader.js.map +1 -1
- package/workflows/principals/recoveryOperation/principalRecoveryDirectory.js +108 -59
- package/workflows/principals/recoveryOperation/principalRecoveryDirectory.js.map +1 -1
- package/workflows/principals/runtimePolicyRecovery.js +26 -14
- package/workflows/principals/runtimePolicyRecovery.js.map +1 -1
- package/workflows/principals/runtimePolicyWarmer.d.ts +26 -0
- package/workflows/principals/runtimePolicyWarmer.js +8 -5
- package/workflows/principals/runtimePolicyWarmer.js.map +1 -1
|
@@ -9,11 +9,11 @@ import { createContainerDocumentQueriesFromRuntime } from "../../workflows/conta
|
|
|
9
9
|
import { createContainerContentsWorkflowRuntime, } from "../../workflows/container-contents/runtime.js";
|
|
10
10
|
import { guardDocumentDiscoveryWrites } from "../../workflows/container-contents/syncPause/discoveryGuard.js";
|
|
11
11
|
import { createContainerDocumentTombstoneVerifier, createDocumentHeadLinkSetLoader, } from "../../workflows/container-contents/tombstones/documentTombstoneEvidence.js";
|
|
12
|
-
import { createRuntimePrincipalPolicyWarmer } from "../../workflows/principals/runtimePolicyWarmer.js";
|
|
12
|
+
import { createPrincipalPolicyOperation, createRuntimePrincipalPolicyWarmer, } from "../../workflows/principals/runtimePolicyWarmer.js";
|
|
13
13
|
function createDiscoveryLane(input) {
|
|
14
14
|
const { containerOrganizationId, execSql, request, runtime } = input;
|
|
15
|
-
const warmReferencedPrincipalPolicies = createRuntimePrincipalPolicyWarmer(runtime);
|
|
16
|
-
const loadHead = createDocumentHeadLinkSetLoader(runtime);
|
|
15
|
+
const warmReferencedPrincipalPolicies = createRuntimePrincipalPolicyWarmer(runtime, { operation: input.policyOperation });
|
|
16
|
+
const loadHead = createDocumentHeadLinkSetLoader(runtime, undefined, input.policyOperation);
|
|
17
17
|
const loadEpoch = (documentId) => loadLocalDocumentAccessEpoch(execSql, documentId);
|
|
18
18
|
return {
|
|
19
19
|
...input.queries,
|
|
@@ -63,6 +63,7 @@ export async function discoverContainerDocumentLanesForRuntime({ onOutcome, requ
|
|
|
63
63
|
const syncPauses = getOrCreateOrganizationSyncPauses(input.state.domainScope);
|
|
64
64
|
await syncPauses.load(execSql);
|
|
65
65
|
const lanes = [];
|
|
66
|
+
const policyOperation = createPrincipalPolicyOperation();
|
|
66
67
|
for (const request of requests) {
|
|
67
68
|
const stored = await sqlContainerContentsPersistence.loadContainerMetadataState(execSql, request.containerId);
|
|
68
69
|
if (!stored)
|
|
@@ -76,6 +77,7 @@ export async function discoverContainerDocumentLanesForRuntime({ onOutcome, requ
|
|
|
76
77
|
isContainerPaused,
|
|
77
78
|
evidenceStore,
|
|
78
79
|
execSql,
|
|
80
|
+
policyOperation,
|
|
79
81
|
queries,
|
|
80
82
|
request,
|
|
81
83
|
runtime,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"documentDiscovery.js","sourceRoot":"","sources":["documentDiscovery.ts"],"names":[],"mappings":"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","sourcesContent":["import type { DocumentSummary } from \"../../data/documents/documentSummary\";\nimport { sqlContainerContentsPersistence } from \"../../data/persistence/container-contents/containerContentsPersistence\";\nimport {\n holdContainerDocumentTombstones,\n listKnownContainerDocumentPlacements,\n listRetryableHeldContainerDocumentTombstones,\n loadLocalDocumentAccessEpoch,\n refuteContainerDocumentTombstoneHolds,\n} from \"../../data/persistence/documents/containerDocumentTombstoneHoldsPersistence\";\nimport { createDocumentDiscoveryEvidenceStore } from \"../../data/persistence/documents/documentDiscoveryEvidencePersistence\";\nimport { getOrCreateOrganizationSyncPauses } from \"../../data/sync/organizationSyncPauses\";\nimport { listContainerDocumentLanesFromApi } from \"../../workflows/container-contents/documentDiscovery/containerDocumentListing\";\nimport { createDiscoveredDocumentVerifier } from \"../../workflows/container-contents/documentDiscovery/documentDiscoveryEvidence\";\nimport {\n type ContainerDocumentDiscoveryLane,\n type ContainerDocumentDiscoveryOutcome,\n type ContainerDocumentDiscoveryOutcomeHandler,\n discoverContainerDocumentLanes,\n} from \"../../workflows/container-contents/documentDiscovery/documentDiscoveryLanes\";\nimport { createContainerDocumentQueriesFromRuntime } from \"../../workflows/container-contents/documentQueries\";\nimport {\n type ContainerContentsWorkflowRuntime,\n createContainerContentsWorkflowRuntime,\n} from \"../../workflows/container-contents/runtime\";\nimport { guardDocumentDiscoveryWrites } from \"../../workflows/container-contents/syncPause/discoveryGuard\";\nimport {\n createContainerDocumentTombstoneVerifier,\n createDocumentHeadLinkSetLoader,\n} from \"../../workflows/container-contents/tombstones/documentTombstoneEvidence\";\nimport { createRuntimePrincipalPolicyWarmer } from \"../../workflows/principals/runtimePolicyWarmer\";\nimport type { InternalRuntime } from \"../runtime/workflowRuntime\";\n\nexport type { ContainerDocumentDiscoveryOutcome } from \"../../workflows/container-contents/documentDiscovery/documentDiscoveryLanes\";\n\n/** One container to discover, with the reconciler's per-lane callbacks. */\nexport interface ContainerDocumentDiscoveryRequest {\n readonly containerId: string;\n readonly onFullListing?:\n | ((documentIds: ReadonlyArray<string>) => void)\n | undefined;\n readonly onPendingDiscovery?: ((delayMs: number) => void) | undefined;\n}\n\ntype ExecSql = ReturnType<InternalRuntime[\"workflowInput\"]>[\"infra\"][\"execSql\"];\n\nfunction createDiscoveryLane(input: {\n readonly containerOrganizationId: string | undefined;\n readonly isContainerPaused: () => boolean;\n readonly evidenceStore: ReturnType<\n typeof createDocumentDiscoveryEvidenceStore\n >;\n readonly execSql: ExecSql;\n readonly queries: ReturnType<\n typeof createContainerDocumentQueriesFromRuntime\n >;\n readonly request: ContainerDocumentDiscoveryRequest;\n readonly runtime: ContainerContentsWorkflowRuntime;\n}): ContainerDocumentDiscoveryLane {\n const { containerOrganizationId, execSql, request, runtime } = input;\n const warmReferencedPrincipalPolicies =\n createRuntimePrincipalPolicyWarmer(runtime);\n const loadHead = createDocumentHeadLinkSetLoader(runtime);\n const loadEpoch = (documentId: string) =>\n loadLocalDocumentAccessEpoch(execSql, documentId);\n return {\n ...input.queries,\n cacheReferencedPrincipalPolicies: (references) =>\n containerOrganizationId\n ? warmReferencedPrincipalPolicies({\n organizationId: containerOrganizationId,\n references,\n })\n : Promise.resolve(),\n containerId: request.containerId,\n holdContainerDocumentTombstones: (tombstones) =>\n holdContainerDocumentTombstones(execSql, tombstones),\n listHeldContainerDocumentTombstones: (containerIds) =>\n listRetryableHeldContainerDocumentTombstones(execSql, containerIds),\n listKnownContainerDocumentPlacements: (placements) =>\n listKnownContainerDocumentPlacements(execSql, placements),\n onFullListing: request.onFullListing,\n refuteContainerDocumentTombstoneHolds: (placements) =>\n refuteContainerDocumentTombstoneHolds(execSql, placements),\n verifyContainerDocumentTombstones: createContainerDocumentTombstoneVerifier(\n containerOrganizationId,\n loadHead,\n loadEpoch,\n ),\n ...guardDocumentDiscoveryWrites({\n execSql,\n isPaused: input.isContainerPaused,\n verifyDiscoveredDocuments: createDiscoveredDocumentVerifier(\n containerOrganizationId,\n loadHead,\n loadEpoch,\n input.evidenceStore,\n request.onPendingDiscovery,\n ),\n }),\n };\n}\n\n/**\n * Internal adapter shared by explicit discovery and background reconciliation:\n * one batched listing for every requested container, then per-container\n * verification scoped to each container's durable organization.\n */\nexport async function discoverContainerDocumentLanesForRuntime({\n onOutcome,\n requests,\n runtimeService,\n}: {\n readonly onOutcome?: ContainerDocumentDiscoveryOutcomeHandler | undefined;\n readonly requests: ReadonlyArray<ContainerDocumentDiscoveryRequest>;\n readonly runtimeService: InternalRuntime;\n}): Promise<ReadonlyArray<ContainerDocumentDiscoveryOutcome>> {\n const unlisted = (\n containerId: string,\n ): ContainerDocumentDiscoveryOutcome => ({\n containerId,\n status: \"fulfilled\",\n value: null,\n });\n const input = runtimeService.workflowInput();\n if (input.infra.dbStatus !== \"ready\") {\n return requests.map(({ containerId }) => unlisted(containerId));\n }\n const runtime = createContainerContentsWorkflowRuntime(input);\n const execSql = input.infra.execSql;\n const evidenceStore = createDocumentDiscoveryEvidenceStore(execSql);\n const queries = createContainerDocumentQueriesFromRuntime(runtime);\n // Capture the reset fence before reading any lane's scope, so a reset\n // between these reads and the listing cannot publish old-organization\n // evidence into its replacement.\n const generation = await evidenceStore.begin();\n const syncPauses = getOrCreateOrganizationSyncPauses(input.state.domainScope);\n await syncPauses.load(execSql);\n const lanes: ContainerDocumentDiscoveryLane[] = [];\n for (const request of requests) {\n const stored =\n await sqlContainerContentsPersistence.loadContainerMetadataState(\n execSql,\n request.containerId,\n );\n if (!stored) continue;\n const isContainerPaused = () =>\n syncPauses.isContainerPaused(\n stored.container,\n runtimeService.workflowInput().auth.organizationId ?? null,\n );\n // A paused organization's container lists like one that is not stored,\n // and a pause landing mid-listing stops its write.\n if (!isContainerPaused())\n lanes.push(\n createDiscoveryLane({\n containerOrganizationId: stored.container.organizationId,\n isContainerPaused,\n evidenceStore,\n execSql,\n queries,\n request,\n runtime,\n }),\n );\n }\n const outcomes = await discoverContainerDocumentLanes({\n generation,\n lanes,\n listContainerDocumentLanes: (listed) =>\n listContainerDocumentLanesFromApi(runtime.apiClient, listed),\n loadContainerDocumentWatermark: queries.loadContainerDocumentWatermark,\n onOutcome,\n });\n const outcomeByContainerId = new Map(\n outcomes.map((outcome) => [outcome.containerId, outcome]),\n );\n return requests.map(\n ({ containerId }) =>\n outcomeByContainerId.get(containerId) ?? unlisted(containerId),\n );\n}\n\n/** Discovers one container; a rejected lane rethrows its failure. */\nexport async function discoverContainerDocumentsForRuntime({\n runtimeService,\n ...request\n}: ContainerDocumentDiscoveryRequest & {\n readonly runtimeService: InternalRuntime;\n}): Promise<ReadonlyArray<DocumentSummary> | null> {\n const [outcome] = await discoverContainerDocumentLanesForRuntime({\n requests: [request],\n runtimeService,\n });\n if (outcome?.status === \"rejected\") throw outcome.reason;\n return outcome?.value ?? null;\n}\n"]}
|
|
1
|
+
{"version":3,"file":"documentDiscovery.js","sourceRoot":"","sources":["documentDiscovery.ts"],"names":[],"mappings":"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","sourcesContent":["import type { DocumentSummary } from \"../../data/documents/documentSummary\";\nimport { sqlContainerContentsPersistence } from \"../../data/persistence/container-contents/containerContentsPersistence\";\nimport {\n holdContainerDocumentTombstones,\n listKnownContainerDocumentPlacements,\n listRetryableHeldContainerDocumentTombstones,\n loadLocalDocumentAccessEpoch,\n refuteContainerDocumentTombstoneHolds,\n} from \"../../data/persistence/documents/containerDocumentTombstoneHoldsPersistence\";\nimport { createDocumentDiscoveryEvidenceStore } from \"../../data/persistence/documents/documentDiscoveryEvidencePersistence\";\nimport { getOrCreateOrganizationSyncPauses } from \"../../data/sync/organizationSyncPauses\";\nimport { listContainerDocumentLanesFromApi } from \"../../workflows/container-contents/documentDiscovery/containerDocumentListing\";\nimport { createDiscoveredDocumentVerifier } from \"../../workflows/container-contents/documentDiscovery/documentDiscoveryEvidence\";\nimport {\n type ContainerDocumentDiscoveryLane,\n type ContainerDocumentDiscoveryOutcome,\n type ContainerDocumentDiscoveryOutcomeHandler,\n discoverContainerDocumentLanes,\n} from \"../../workflows/container-contents/documentDiscovery/documentDiscoveryLanes\";\nimport { createContainerDocumentQueriesFromRuntime } from \"../../workflows/container-contents/documentQueries\";\nimport {\n type ContainerContentsWorkflowRuntime,\n createContainerContentsWorkflowRuntime,\n} from \"../../workflows/container-contents/runtime\";\nimport { guardDocumentDiscoveryWrites } from \"../../workflows/container-contents/syncPause/discoveryGuard\";\nimport {\n createContainerDocumentTombstoneVerifier,\n createDocumentHeadLinkSetLoader,\n} from \"../../workflows/container-contents/tombstones/documentTombstoneEvidence\";\nimport {\n createPrincipalPolicyOperation,\n createRuntimePrincipalPolicyWarmer,\n type PrincipalPolicyOperation,\n} from \"../../workflows/principals/runtimePolicyWarmer\";\nimport type { InternalRuntime } from \"../runtime/workflowRuntime\";\n\nexport type { ContainerDocumentDiscoveryOutcome } from \"../../workflows/container-contents/documentDiscovery/documentDiscoveryLanes\";\n\n/** One container to discover, with the reconciler's per-lane callbacks. */\nexport interface ContainerDocumentDiscoveryRequest {\n readonly containerId: string;\n readonly onFullListing?:\n | ((documentIds: ReadonlyArray<string>) => void)\n | undefined;\n readonly onPendingDiscovery?: ((delayMs: number) => void) | undefined;\n}\n\ntype ExecSql = ReturnType<InternalRuntime[\"workflowInput\"]>[\"infra\"][\"execSql\"];\n\nfunction createDiscoveryLane(input: {\n readonly containerOrganizationId: string | undefined;\n readonly isContainerPaused: () => boolean;\n readonly evidenceStore: ReturnType<\n typeof createDocumentDiscoveryEvidenceStore\n >;\n readonly execSql: ExecSql;\n /** The discovery pass's token: its lanes share one directory discovery. */\n readonly policyOperation: PrincipalPolicyOperation;\n readonly queries: ReturnType<\n typeof createContainerDocumentQueriesFromRuntime\n >;\n readonly request: ContainerDocumentDiscoveryRequest;\n readonly runtime: ContainerContentsWorkflowRuntime;\n}): ContainerDocumentDiscoveryLane {\n const { containerOrganizationId, execSql, request, runtime } = input;\n const warmReferencedPrincipalPolicies = createRuntimePrincipalPolicyWarmer(\n runtime,\n { operation: input.policyOperation },\n );\n const loadHead = createDocumentHeadLinkSetLoader(\n runtime,\n undefined,\n input.policyOperation,\n );\n const loadEpoch = (documentId: string) =>\n loadLocalDocumentAccessEpoch(execSql, documentId);\n return {\n ...input.queries,\n cacheReferencedPrincipalPolicies: (references) =>\n containerOrganizationId\n ? warmReferencedPrincipalPolicies({\n organizationId: containerOrganizationId,\n references,\n })\n : Promise.resolve(),\n containerId: request.containerId,\n holdContainerDocumentTombstones: (tombstones) =>\n holdContainerDocumentTombstones(execSql, tombstones),\n listHeldContainerDocumentTombstones: (containerIds) =>\n listRetryableHeldContainerDocumentTombstones(execSql, containerIds),\n listKnownContainerDocumentPlacements: (placements) =>\n listKnownContainerDocumentPlacements(execSql, placements),\n onFullListing: request.onFullListing,\n refuteContainerDocumentTombstoneHolds: (placements) =>\n refuteContainerDocumentTombstoneHolds(execSql, placements),\n verifyContainerDocumentTombstones: createContainerDocumentTombstoneVerifier(\n containerOrganizationId,\n loadHead,\n loadEpoch,\n ),\n ...guardDocumentDiscoveryWrites({\n execSql,\n isPaused: input.isContainerPaused,\n verifyDiscoveredDocuments: createDiscoveredDocumentVerifier(\n containerOrganizationId,\n loadHead,\n loadEpoch,\n input.evidenceStore,\n request.onPendingDiscovery,\n ),\n }),\n };\n}\n\n/**\n * Internal adapter shared by explicit discovery and background reconciliation:\n * one batched listing for every requested container, then per-container\n * verification scoped to each container's durable organization.\n */\nexport async function discoverContainerDocumentLanesForRuntime({\n onOutcome,\n requests,\n runtimeService,\n}: {\n readonly onOutcome?: ContainerDocumentDiscoveryOutcomeHandler | undefined;\n readonly requests: ReadonlyArray<ContainerDocumentDiscoveryRequest>;\n readonly runtimeService: InternalRuntime;\n}): Promise<ReadonlyArray<ContainerDocumentDiscoveryOutcome>> {\n const unlisted = (\n containerId: string,\n ): ContainerDocumentDiscoveryOutcome => ({\n containerId,\n status: \"fulfilled\",\n value: null,\n });\n const input = runtimeService.workflowInput();\n if (input.infra.dbStatus !== \"ready\") {\n return requests.map(({ containerId }) => unlisted(containerId));\n }\n const runtime = createContainerContentsWorkflowRuntime(input);\n const execSql = input.infra.execSql;\n const evidenceStore = createDocumentDiscoveryEvidenceStore(execSql);\n const queries = createContainerDocumentQueriesFromRuntime(runtime);\n // Capture the reset fence before reading any lane's scope, so a reset\n // between these reads and the listing cannot publish old-organization\n // evidence into its replacement.\n const generation = await evidenceStore.begin();\n const syncPauses = getOrCreateOrganizationSyncPauses(input.state.domainScope);\n await syncPauses.load(execSql);\n const lanes: ContainerDocumentDiscoveryLane[] = [];\n const policyOperation = createPrincipalPolicyOperation();\n for (const request of requests) {\n const stored =\n await sqlContainerContentsPersistence.loadContainerMetadataState(\n execSql,\n request.containerId,\n );\n if (!stored) continue;\n const isContainerPaused = () =>\n syncPauses.isContainerPaused(\n stored.container,\n runtimeService.workflowInput().auth.organizationId ?? null,\n );\n // A paused organization's container lists like one that is not stored,\n // and a pause landing mid-listing stops its write.\n if (!isContainerPaused())\n lanes.push(\n createDiscoveryLane({\n containerOrganizationId: stored.container.organizationId,\n isContainerPaused,\n evidenceStore,\n execSql,\n policyOperation,\n queries,\n request,\n runtime,\n }),\n );\n }\n const outcomes = await discoverContainerDocumentLanes({\n generation,\n lanes,\n listContainerDocumentLanes: (listed) =>\n listContainerDocumentLanesFromApi(runtime.apiClient, listed),\n loadContainerDocumentWatermark: queries.loadContainerDocumentWatermark,\n onOutcome,\n });\n const outcomeByContainerId = new Map(\n outcomes.map((outcome) => [outcome.containerId, outcome]),\n );\n return requests.map(\n ({ containerId }) =>\n outcomeByContainerId.get(containerId) ?? unlisted(containerId),\n );\n}\n\n/** Discovers one container; a rejected lane rethrows its failure. */\nexport async function discoverContainerDocumentsForRuntime({\n runtimeService,\n ...request\n}: ContainerDocumentDiscoveryRequest & {\n readonly runtimeService: InternalRuntime;\n}): Promise<ReadonlyArray<DocumentSummary> | null> {\n const [outcome] = await discoverContainerDocumentLanesForRuntime({\n requests: [request],\n runtimeService,\n });\n if (outcome?.status === \"rejected\") throw outcome.reason;\n return outcome?.value ?? null;\n}\n"]}
|
|
@@ -8,8 +8,32 @@ import { recordContainerHydrationTombstones } from "./containerHydrationPersiste
|
|
|
8
8
|
import { repairDocumentsForRemovedContainersInTransaction } from "./containerStructuralRepair.js";
|
|
9
9
|
import { discardLinkIntentsForRemovedContainers } from "./documentLinkRemovalRepair.js";
|
|
10
10
|
import { deleteContainerMetadataDocumentRowsInTransaction } from "./dormantContainerMetadata.js";
|
|
11
|
+
const RECOVERY_METADATA_TABLES = [
|
|
12
|
+
[
|
|
13
|
+
"documents",
|
|
14
|
+
"document_id, recovery_document_id, recovery_generation, snapshot_end_version, pending_base_version",
|
|
15
|
+
],
|
|
16
|
+
["document_history_checkpoints", "snapshot, end_version_vector"],
|
|
17
|
+
[
|
|
18
|
+
"document_history_updates",
|
|
19
|
+
"id, update_data, origin, update_id, partial_start_version_vector, partial_end_version_vector, source_version_vector",
|
|
20
|
+
],
|
|
21
|
+
];
|
|
22
|
+
const CREATE_COLUMNS = "id, subject_id, target_container_id, intent_type, remote_container_id, remote_metadata_document_id, remote_metadata_access_state_hash";
|
|
23
|
+
const MOVE_COLUMNS = "id, subject_id, target_container_id, source_container_id, intent_type";
|
|
24
|
+
const LINK_COLUMNS = "id, local_id, subject_id, target_container_id, source_container_id, replace_linked_containers, intent_type";
|
|
25
|
+
const TARGET_COLUMNS = "intent_id, operation, container_id";
|
|
11
26
|
// The token covers all local work the user is agreeing to discard, including
|
|
12
27
|
// shared link intents. A second pane's edit invalidates the confirmation.
|
|
28
|
+
async function summarizeRecoverySnapshot(input) {
|
|
29
|
+
const { creates, dormant, links, metadata, moves, targets } = input;
|
|
30
|
+
const revision = await toFingerprint(new TextEncoder().encode(JSON.stringify([dormant, metadata, creates, moves, links, targets])));
|
|
31
|
+
return {
|
|
32
|
+
revision,
|
|
33
|
+
pendingUpdateCount: (metadata[2] ?? []).filter((row) => Reflect.get(row, "update_id") !== null).length,
|
|
34
|
+
hasStructuralIntent: creates.length + moves.length + links.length > 0,
|
|
35
|
+
};
|
|
36
|
+
}
|
|
13
37
|
async function recoverySnapshot(execSql, containerId, organizationId) {
|
|
14
38
|
const [dormant] = await execSql(`SELECT container_id, organization_id FROM dormant_container_metadata
|
|
15
39
|
WHERE container_id = ? AND organization_id = ?
|
|
@@ -17,21 +41,11 @@ async function recoverySnapshot(execSql, containerId, organizationId) {
|
|
|
17
41
|
if (!dormant)
|
|
18
42
|
return null;
|
|
19
43
|
const metadata = [];
|
|
20
|
-
for (const [table, columns] of
|
|
21
|
-
[
|
|
22
|
-
"documents",
|
|
23
|
-
"document_id, recovery_document_id, recovery_generation, snapshot_end_version, pending_base_version",
|
|
24
|
-
],
|
|
25
|
-
["document_history_checkpoints", "snapshot, end_version_vector"],
|
|
26
|
-
[
|
|
27
|
-
"document_history_updates",
|
|
28
|
-
"id, update_data, origin, update_id, partial_start_version_vector, partial_end_version_vector, source_version_vector",
|
|
29
|
-
],
|
|
30
|
-
]) {
|
|
44
|
+
for (const [table, columns] of RECOVERY_METADATA_TABLES) {
|
|
31
45
|
metadata.push(await execSql(`SELECT ${columns} FROM ${table} WHERE app_kind = 'container-metadata' AND local_id = ? ORDER BY rowid`, [containerId]));
|
|
32
46
|
}
|
|
33
|
-
const creates = await execSql(
|
|
34
|
-
const moves = await execSql(
|
|
47
|
+
const creates = await execSql(`SELECT ${CREATE_COLUMNS} FROM local_intents WHERE lane = ? AND subject_id = ? AND sync_status = 'pending' ORDER BY id`, [LOCAL_INTENT_LANES.containerCreate, containerId]);
|
|
48
|
+
const moves = await execSql(`SELECT ${MOVE_COLUMNS} FROM local_intents WHERE lane = ? AND subject_id = ? ORDER BY id`, [LOCAL_INTENT_LANES.containerMove, containerId]);
|
|
35
49
|
const placementWhere = `lane = ? AND (target_container_id = ? OR id IN
|
|
36
50
|
(SELECT intent_id FROM document_intent_link_targets WHERE container_id = ?))`;
|
|
37
51
|
const placementBind = [
|
|
@@ -39,40 +53,140 @@ async function recoverySnapshot(execSql, containerId, organizationId) {
|
|
|
39
53
|
containerId,
|
|
40
54
|
containerId,
|
|
41
55
|
];
|
|
42
|
-
const links = await execSql(`SELECT
|
|
43
|
-
const targets = await execSql(`SELECT
|
|
56
|
+
const links = await execSql(`SELECT ${LINK_COLUMNS} FROM local_intents WHERE ${placementWhere} ORDER BY id`, placementBind);
|
|
57
|
+
const targets = await execSql(`SELECT ${TARGET_COLUMNS} FROM document_intent_link_targets WHERE intent_id IN
|
|
44
58
|
(SELECT id FROM local_intents WHERE ${placementWhere}) ORDER BY intent_id, container_id, operation`, placementBind);
|
|
45
|
-
|
|
59
|
+
return summarizeRecoverySnapshot({
|
|
60
|
+
creates,
|
|
61
|
+
dormant,
|
|
62
|
+
links,
|
|
63
|
+
metadata,
|
|
64
|
+
moves,
|
|
65
|
+
targets,
|
|
66
|
+
});
|
|
67
|
+
}
|
|
68
|
+
/** Rows grouped by one column's value, dropping that column when asked. */
|
|
69
|
+
function groupRows(rows, column, drop) {
|
|
70
|
+
const groups = new Map();
|
|
71
|
+
for (const row of rows) {
|
|
72
|
+
const key = String(Reflect.get(row, column));
|
|
73
|
+
const kept = drop
|
|
74
|
+
? Object.fromEntries(Object.entries(row).filter(([name]) => name !== column))
|
|
75
|
+
: row;
|
|
76
|
+
const group = groups.get(key);
|
|
77
|
+
if (group)
|
|
78
|
+
group.push(kept);
|
|
79
|
+
else
|
|
80
|
+
groups.set(key, [kept]);
|
|
81
|
+
}
|
|
82
|
+
return groups;
|
|
83
|
+
}
|
|
84
|
+
/** Keeps SQL `IN` lists, bound twice by the placement reads, under the budget. */
|
|
85
|
+
const RECOVERY_CONTAINER_BATCH_SIZE = 400;
|
|
86
|
+
/** Every row the recovery snapshots of a batch of folders read. */
|
|
87
|
+
async function readRecoveryBatchRows(execSql, ids) {
|
|
88
|
+
const list = ids.map(() => "?").join(", ");
|
|
89
|
+
const metadata = [];
|
|
90
|
+
for (const [table, columns] of RECOVERY_METADATA_TABLES) {
|
|
91
|
+
metadata.push(groupRows(await execSql(`SELECT local_id, ${columns} FROM ${table} WHERE app_kind = 'container-metadata' AND local_id IN (${list}) ORDER BY local_id, rowid`, ids), "local_id", true));
|
|
92
|
+
}
|
|
93
|
+
const creates = groupRows(await execSql(`SELECT ${CREATE_COLUMNS} FROM local_intents WHERE lane = ? AND subject_id IN (${list}) AND sync_status = 'pending' ORDER BY subject_id, id`, [LOCAL_INTENT_LANES.containerCreate, ...ids]), "subject_id", false);
|
|
94
|
+
const moves = groupRows(await execSql(`SELECT ${MOVE_COLUMNS} FROM local_intents WHERE lane = ? AND subject_id IN (${list}) ORDER BY subject_id, id`, [LOCAL_INTENT_LANES.containerMove, ...ids]), "subject_id", false);
|
|
95
|
+
const placementWhere = `lane = ? AND (target_container_id IN (${list}) OR id IN
|
|
96
|
+
(SELECT intent_id FROM document_intent_link_targets WHERE container_id IN (${list})))`;
|
|
97
|
+
const placementBind = [LOCAL_INTENT_LANES.documentPlacement, ...ids, ...ids];
|
|
98
|
+
// Every placement intent any folder in the batch reaches, and ALL link
|
|
99
|
+
// targets of those intents: a folder's share is then the intents that
|
|
100
|
+
// target it or carry a link target in it, as the per-folder read selects.
|
|
101
|
+
const links = await execSql(`SELECT ${LINK_COLUMNS} FROM local_intents WHERE ${placementWhere} ORDER BY id`, placementBind);
|
|
102
|
+
const targets = await execSql(`SELECT ${TARGET_COLUMNS} FROM document_intent_link_targets WHERE intent_id IN
|
|
103
|
+
(SELECT id FROM local_intents WHERE ${placementWhere}) ORDER BY intent_id, container_id, operation`, placementBind);
|
|
104
|
+
return { creates, links, metadata, moves, targets };
|
|
105
|
+
}
|
|
106
|
+
/**
|
|
107
|
+
* Each folder's share of a batch's placement rows, indexed in one pass: the
|
|
108
|
+
* intents that target the folder or carry a link target in it (in intent id
|
|
109
|
+
* order), and all link targets of those intents (in the per-folder order).
|
|
110
|
+
*/
|
|
111
|
+
function indexPlacementRows(rows) {
|
|
112
|
+
const targetsByIntent = groupRows(rows.targets, "intent_id", false);
|
|
113
|
+
const linksByFolder = new Map();
|
|
114
|
+
for (const link of rows.links) {
|
|
115
|
+
const intentId = String(Reflect.get(link, "id"));
|
|
116
|
+
const folders = new Set([
|
|
117
|
+
String(Reflect.get(link, "target_container_id")),
|
|
118
|
+
...(targetsByIntent.get(intentId) ?? []).map((target) => String(Reflect.get(target, "container_id"))),
|
|
119
|
+
]);
|
|
120
|
+
for (const folder of folders) {
|
|
121
|
+
const folderLinks = linksByFolder.get(folder);
|
|
122
|
+
if (folderLinks)
|
|
123
|
+
folderLinks.push(link);
|
|
124
|
+
else
|
|
125
|
+
linksByFolder.set(folder, [link]);
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
return { linksByFolder, targetsByIntent };
|
|
129
|
+
}
|
|
130
|
+
/** One folder's snapshot from its batch's rows, in the per-folder shape. */
|
|
131
|
+
async function folderRecoverySnapshot(rows, placements, containerId, dormant) {
|
|
132
|
+
const links = placements.linksByFolder.get(containerId) ?? [];
|
|
133
|
+
// Links are in intent id order and targets in (intent id, container,
|
|
134
|
+
// operation) order, so concatenating per link keeps the per-folder order.
|
|
135
|
+
const targets = links.flatMap((link) => placements.targetsByIntent.get(String(Reflect.get(link, "id"))) ?? []);
|
|
136
|
+
const metadata = rows.metadata.map((byFolder) => byFolder.get(containerId) ?? []);
|
|
137
|
+
// The metadata record's updates are its checkpoint snapshot, read above; a
|
|
138
|
+
// folder with no record has none.
|
|
139
|
+
const snapshotRow = (metadata[1] ?? [])[0];
|
|
46
140
|
return {
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
141
|
+
containerId,
|
|
142
|
+
metadataUpdates: (metadata[0] ?? []).length > 0 && snapshotRow
|
|
143
|
+
? String(Reflect.get(snapshotRow, "snapshot") ?? "")
|
|
144
|
+
: "",
|
|
145
|
+
snapshot: await summarizeRecoverySnapshot({
|
|
146
|
+
creates: rows.creates.get(containerId) ?? [],
|
|
147
|
+
dormant,
|
|
148
|
+
links,
|
|
149
|
+
metadata,
|
|
150
|
+
moves: rows.moves.get(containerId) ?? [],
|
|
151
|
+
targets,
|
|
152
|
+
}),
|
|
50
153
|
};
|
|
51
154
|
}
|
|
155
|
+
/**
|
|
156
|
+
* `recoverySnapshot` for every retained folder of an organization, one read
|
|
157
|
+
* per table for each batch of folders. Each folder's rows are grouped in the
|
|
158
|
+
* per-folder order (`local_id, rowid` for metadata; `subject_id, id` for
|
|
159
|
+
* folder intents), so its revision is byte-identical to `recoverySnapshot`'s,
|
|
160
|
+
* which a later discard compares.
|
|
161
|
+
*/
|
|
162
|
+
async function recoverySnapshots(execSql, organizationId) {
|
|
163
|
+
const dormantRows = await execSql(`SELECT container_id, organization_id FROM dormant_container_metadata d
|
|
164
|
+
WHERE organization_id = ?
|
|
165
|
+
AND NOT EXISTS (SELECT 1 FROM containers WHERE id = d.container_id)
|
|
166
|
+
ORDER BY container_id`, [organizationId]);
|
|
167
|
+
const result = [];
|
|
168
|
+
for (let offset = 0; offset < dormantRows.length; offset += RECOVERY_CONTAINER_BATCH_SIZE) {
|
|
169
|
+
const batch = dormantRows.slice(offset, offset + RECOVERY_CONTAINER_BATCH_SIZE);
|
|
170
|
+
const ids = batch.map((row) => String(Reflect.get(row, "container_id")));
|
|
171
|
+
const rows = await readRecoveryBatchRows(execSql, ids);
|
|
172
|
+
const placements = indexPlacementRows(rows);
|
|
173
|
+
for (const [index, dormant] of batch.entries()) {
|
|
174
|
+
result.push(await folderRecoverySnapshot(rows, placements, ids[index] ?? "", dormant));
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
return result;
|
|
178
|
+
}
|
|
52
179
|
export async function listRetainedContainerMetadata(execSql, organizationId) {
|
|
53
180
|
if (!organizationId)
|
|
54
181
|
return [];
|
|
55
182
|
await sqlContainerContentsPersistence.ensureSchema(execSql);
|
|
56
183
|
await ensureSqlTables(execSql, documentPlacementIntentTables);
|
|
57
|
-
return runSerializedSqlMutation(execSql, async (locked) => getClientSQLitePersistenceRuntime(locked).transaction(async () => {
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
const snapshot = await recoverySnapshot(locked, containerId, organizationId);
|
|
64
|
-
if (!snapshot)
|
|
65
|
-
continue;
|
|
66
|
-
const record = await sqlContainerContentsPersistence.loadContainerMetadataRecord(locked, containerId);
|
|
67
|
-
result.push({
|
|
68
|
-
containerId,
|
|
69
|
-
organizationId,
|
|
70
|
-
...snapshot,
|
|
71
|
-
metadataUpdates: record?.metadataUpdates ?? "",
|
|
72
|
-
});
|
|
73
|
-
}
|
|
74
|
-
return result;
|
|
75
|
-
}));
|
|
184
|
+
return runSerializedSqlMutation(execSql, async (locked) => getClientSQLitePersistenceRuntime(locked).transaction(async () => (await recoverySnapshots(locked, organizationId)).map(({ containerId, metadataUpdates, snapshot }) => ({
|
|
185
|
+
containerId,
|
|
186
|
+
organizationId,
|
|
187
|
+
...snapshot,
|
|
188
|
+
metadataUpdates,
|
|
189
|
+
}))));
|
|
76
190
|
}
|
|
77
191
|
/** Explicit local discard, never called by remote discovery. */
|
|
78
192
|
export async function discardRetainedContainerMetadata(execSql, input) {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"containerRecoveryPersistence.js","sourceRoot":"","sources":["containerRecoveryPersistence.ts"],"names":[],"mappings":"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","sourcesContent":["import { toFingerprint } from \"@tearleads/crypto\";\nimport { and, eq } from \"drizzle-orm\";\nimport {\n documentPlacementIntentTables,\n inContainerIntentLanes,\n LOCAL_INTENT_LANES,\n localIntents,\n} from \"../../sqlite/schema\";\nimport { getClientSQLitePersistenceRuntime } from \"../../sqlite/sqlitePersistenceRuntime\";\nimport {\n type ExecSql,\n ensureSqlTables,\n runSerializedSqlMutation,\n} from \"../../sqlite/sqlSchema\";\nimport { sqlContainerContentsPersistence } from \"./containerContentsPersistence\";\nimport { recordContainerHydrationTombstones } from \"./containerHydrationPersistence\";\nimport { repairDocumentsForRemovedContainersInTransaction } from \"./containerStructuralRepair\";\nimport { discardLinkIntentsForRemovedContainers } from \"./documentLinkRemovalRepair\";\nimport { deleteContainerMetadataDocumentRowsInTransaction } from \"./dormantContainerMetadata\";\n\n// The token covers all local work the user is agreeing to discard, including\n// shared link intents. A second pane's edit invalidates the confirmation.\nasync function recoverySnapshot(\n execSql: ExecSql,\n containerId: string,\n organizationId: string,\n) {\n const [dormant] = await execSql(\n `SELECT container_id, organization_id FROM dormant_container_metadata\n WHERE container_id = ? AND organization_id = ?\n AND NOT EXISTS (SELECT 1 FROM containers WHERE id = ?)`,\n [containerId, organizationId, containerId],\n );\n if (!dormant) return null;\n const metadata = [];\n for (const [table, columns] of [\n [\n \"documents\",\n \"document_id, recovery_document_id, recovery_generation, snapshot_end_version, pending_base_version\",\n ],\n [\"document_history_checkpoints\", \"snapshot, end_version_vector\"],\n [\n \"document_history_updates\",\n \"id, update_data, origin, update_id, partial_start_version_vector, partial_end_version_vector, source_version_vector\",\n ],\n ]) {\n metadata.push(\n await execSql(\n `SELECT ${columns} FROM ${table} WHERE app_kind = 'container-metadata' AND local_id = ? ORDER BY rowid`,\n [containerId],\n ),\n );\n }\n const creates = await execSql(\n \"SELECT id, subject_id, target_container_id, intent_type, remote_container_id, remote_metadata_document_id, remote_metadata_access_state_hash FROM local_intents WHERE lane = ? AND subject_id = ? AND sync_status = 'pending' ORDER BY id\",\n [LOCAL_INTENT_LANES.containerCreate, containerId],\n );\n const moves = await execSql(\n \"SELECT id, subject_id, target_container_id, source_container_id, intent_type FROM local_intents WHERE lane = ? AND subject_id = ? ORDER BY id\",\n [LOCAL_INTENT_LANES.containerMove, containerId],\n );\n const placementWhere = `lane = ? AND (target_container_id = ? OR id IN\n (SELECT intent_id FROM document_intent_link_targets WHERE container_id = ?))`;\n const placementBind = [\n LOCAL_INTENT_LANES.documentPlacement,\n containerId,\n containerId,\n ];\n const links = await execSql(\n `SELECT id, local_id, subject_id, target_container_id, source_container_id, replace_linked_containers, intent_type FROM local_intents WHERE ${placementWhere} ORDER BY id`,\n placementBind,\n );\n const targets = await execSql(\n `SELECT intent_id, operation, container_id FROM document_intent_link_targets WHERE intent_id IN\n (SELECT id FROM local_intents WHERE ${placementWhere}) ORDER BY intent_id, container_id, operation`,\n placementBind,\n );\n const revision = await toFingerprint(\n new TextEncoder().encode(\n JSON.stringify([dormant, metadata, creates, moves, links, targets]),\n ),\n );\n return {\n revision,\n pendingUpdateCount: (metadata[2] ?? []).filter(\n (row) => Reflect.get(row, \"update_id\") !== null,\n ).length,\n hasStructuralIntent: creates.length + moves.length + links.length > 0,\n };\n}\n\nexport async function listRetainedContainerMetadata(\n execSql: ExecSql,\n organizationId: string | null,\n) {\n if (!organizationId) return [];\n await sqlContainerContentsPersistence.ensureSchema(execSql);\n await ensureSqlTables(execSql, documentPlacementIntentTables);\n return runSerializedSqlMutation(execSql, async (locked) =>\n getClientSQLitePersistenceRuntime(locked).transaction(async () => {\n const rows = await locked(\n `SELECT container_id FROM dormant_container_metadata\n WHERE organization_id = ? ORDER BY container_id`,\n [organizationId],\n );\n const result = [];\n for (const row of rows) {\n const containerId = String(Reflect.get(row, \"container_id\"));\n const snapshot = await recoverySnapshot(\n locked,\n containerId,\n organizationId,\n );\n if (!snapshot) continue;\n const record =\n await sqlContainerContentsPersistence.loadContainerMetadataRecord(\n locked,\n containerId,\n );\n result.push({\n containerId,\n organizationId,\n ...snapshot,\n metadataUpdates: record?.metadataUpdates ?? \"\",\n });\n }\n return result;\n }),\n );\n}\n\n/** Explicit local discard, never called by remote discovery. */\nexport async function discardRetainedContainerMetadata(\n execSql: ExecSql,\n input: {\n containerId: string;\n organizationId: string;\n revision: string;\n },\n): Promise<boolean> {\n await sqlContainerContentsPersistence.ensureSchema(execSql);\n await ensureSqlTables(execSql, documentPlacementIntentTables);\n return runSerializedSqlMutation(execSql, async (locked) =>\n getClientSQLitePersistenceRuntime(locked).transaction(\n async (tx) => {\n const current = await recoverySnapshot(\n locked,\n input.containerId,\n input.organizationId,\n );\n if (!current || current.revision !== input.revision) return false;\n await recordContainerHydrationTombstones({\n tx,\n removals: [\n {\n containerId: input.containerId,\n reason: \"deleted\",\n updatedAt: new Date().toISOString(),\n },\n ],\n });\n await repairDocumentsForRemovedContainersInTransaction({\n tx,\n removals: [\n {\n containerId: input.containerId,\n reason: \"deleted\",\n updatedAt: new Date().toISOString(),\n },\n ],\n });\n await discardLinkIntentsForRemovedContainers({\n tx,\n containerIds: [input.containerId],\n });\n await tx\n .delete(localIntents)\n .where(\n and(\n inContainerIntentLanes(),\n eq(localIntents.subjectId, input.containerId),\n ),\n )\n .run();\n await deleteContainerMetadataDocumentRowsInTransaction(tx, [\n input.containerId,\n ]);\n return true;\n },\n { behavior: \"immediate\" },\n ),\n );\n}\n\n/** Retained metadata of an organization's absent folders; binds the org. */\nexport const RETAINED_CONTAINER_METADATA_SQL = `SELECT 1 FROM dormant_container_metadata d\n WHERE d.organization_id = ? AND NOT EXISTS (SELECT 1 FROM containers c WHERE c.id = d.container_id)`;\n\n/**\n * An organization's local folders whose queued move points at an unavailable\n * parent; binds the move lane, then the organization.\n */\nexport const RECOVERY_FOLDER_MOVES_SQL = `SELECT c.id FROM containers c JOIN local_intents i\n ON i.lane = ? AND i.subject_id = c.id AND i.target_container_id = c.parent_id\n WHERE c.organization_id = ? AND i.sync_status IN ('pending', 'blocked')\n AND NOT EXISTS (SELECT 1 FROM containers p WHERE p.id = c.parent_id)`;\n\n/** Existing local folders whose queued move points at an unavailable parent. */\nexport async function listRecoveryFolderMoveIds(\n execSql: ExecSql,\n organizationId: string | null,\n): Promise<string[]> {\n if (!organizationId) return [];\n await sqlContainerContentsPersistence.ensureSchema(execSql);\n const rows = await execSql(`${RECOVERY_FOLDER_MOVES_SQL} ORDER BY c.id`, [\n LOCAL_INTENT_LANES.containerMove,\n organizationId,\n ]);\n return rows.map((row) => String(Reflect.get(row, \"id\")));\n}\n"]}
|
|
1
|
+
{"version":3,"file":"containerRecoveryPersistence.js","sourceRoot":"","sources":["containerRecoveryPersistence.ts"],"names":[],"mappings":"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","sourcesContent":["import { toFingerprint } from \"@tearleads/crypto\";\nimport { and, eq } from \"drizzle-orm\";\nimport {\n documentPlacementIntentTables,\n inContainerIntentLanes,\n LOCAL_INTENT_LANES,\n localIntents,\n} from \"../../sqlite/schema\";\nimport { getClientSQLitePersistenceRuntime } from \"../../sqlite/sqlitePersistenceRuntime\";\nimport {\n type ExecSql,\n ensureSqlTables,\n runSerializedSqlMutation,\n} from \"../../sqlite/sqlSchema\";\nimport { sqlContainerContentsPersistence } from \"./containerContentsPersistence\";\nimport { recordContainerHydrationTombstones } from \"./containerHydrationPersistence\";\nimport { repairDocumentsForRemovedContainersInTransaction } from \"./containerStructuralRepair\";\nimport { discardLinkIntentsForRemovedContainers } from \"./documentLinkRemovalRepair\";\nimport { deleteContainerMetadataDocumentRowsInTransaction } from \"./dormantContainerMetadata\";\n\nconst RECOVERY_METADATA_TABLES = [\n [\n \"documents\",\n \"document_id, recovery_document_id, recovery_generation, snapshot_end_version, pending_base_version\",\n ],\n [\"document_history_checkpoints\", \"snapshot, end_version_vector\"],\n [\n \"document_history_updates\",\n \"id, update_data, origin, update_id, partial_start_version_vector, partial_end_version_vector, source_version_vector\",\n ],\n] as const;\nconst CREATE_COLUMNS =\n \"id, subject_id, target_container_id, intent_type, remote_container_id, remote_metadata_document_id, remote_metadata_access_state_hash\";\nconst MOVE_COLUMNS =\n \"id, subject_id, target_container_id, source_container_id, intent_type\";\nconst LINK_COLUMNS =\n \"id, local_id, subject_id, target_container_id, source_container_id, replace_linked_containers, intent_type\";\nconst TARGET_COLUMNS = \"intent_id, operation, container_id\";\n\ntype RecoveryRows = Awaited<ReturnType<ExecSql>>;\n\n// The token covers all local work the user is agreeing to discard, including\n// shared link intents. A second pane's edit invalidates the confirmation.\nasync function summarizeRecoverySnapshot(input: {\n creates: RecoveryRows;\n dormant: RecoveryRows[number];\n links: RecoveryRows;\n metadata: RecoveryRows[];\n moves: RecoveryRows;\n targets: RecoveryRows;\n}) {\n const { creates, dormant, links, metadata, moves, targets } = input;\n const revision = await toFingerprint(\n new TextEncoder().encode(\n JSON.stringify([dormant, metadata, creates, moves, links, targets]),\n ),\n );\n return {\n revision,\n pendingUpdateCount: (metadata[2] ?? []).filter(\n (row) => Reflect.get(row, \"update_id\") !== null,\n ).length,\n hasStructuralIntent: creates.length + moves.length + links.length > 0,\n };\n}\n\nasync function recoverySnapshot(\n execSql: ExecSql,\n containerId: string,\n organizationId: string,\n) {\n const [dormant] = await execSql(\n `SELECT container_id, organization_id FROM dormant_container_metadata\n WHERE container_id = ? AND organization_id = ?\n AND NOT EXISTS (SELECT 1 FROM containers WHERE id = ?)`,\n [containerId, organizationId, containerId],\n );\n if (!dormant) return null;\n const metadata = [];\n for (const [table, columns] of RECOVERY_METADATA_TABLES) {\n metadata.push(\n await execSql(\n `SELECT ${columns} FROM ${table} WHERE app_kind = 'container-metadata' AND local_id = ? ORDER BY rowid`,\n [containerId],\n ),\n );\n }\n const creates = await execSql(\n `SELECT ${CREATE_COLUMNS} FROM local_intents WHERE lane = ? AND subject_id = ? AND sync_status = 'pending' ORDER BY id`,\n [LOCAL_INTENT_LANES.containerCreate, containerId],\n );\n const moves = await execSql(\n `SELECT ${MOVE_COLUMNS} FROM local_intents WHERE lane = ? AND subject_id = ? ORDER BY id`,\n [LOCAL_INTENT_LANES.containerMove, containerId],\n );\n const placementWhere = `lane = ? AND (target_container_id = ? OR id IN\n (SELECT intent_id FROM document_intent_link_targets WHERE container_id = ?))`;\n const placementBind = [\n LOCAL_INTENT_LANES.documentPlacement,\n containerId,\n containerId,\n ];\n const links = await execSql(\n `SELECT ${LINK_COLUMNS} FROM local_intents WHERE ${placementWhere} ORDER BY id`,\n placementBind,\n );\n const targets = await execSql(\n `SELECT ${TARGET_COLUMNS} FROM document_intent_link_targets WHERE intent_id IN\n (SELECT id FROM local_intents WHERE ${placementWhere}) ORDER BY intent_id, container_id, operation`,\n placementBind,\n );\n return summarizeRecoverySnapshot({\n creates,\n dormant,\n links,\n metadata,\n moves,\n targets,\n });\n}\n\n/** Rows grouped by one column's value, dropping that column when asked. */\nfunction groupRows(\n rows: RecoveryRows,\n column: string,\n drop: boolean,\n): Map<string, RecoveryRows> {\n const groups = new Map<string, RecoveryRows>();\n for (const row of rows) {\n const key = String(Reflect.get(row, column));\n const kept = drop\n ? Object.fromEntries(\n Object.entries(row).filter(([name]) => name !== column),\n )\n : row;\n const group = groups.get(key);\n if (group) group.push(kept);\n else groups.set(key, [kept]);\n }\n return groups;\n}\n\n/** Keeps SQL `IN` lists, bound twice by the placement reads, under the budget. */\nconst RECOVERY_CONTAINER_BATCH_SIZE = 400;\n\n/** Every row the recovery snapshots of a batch of folders read. */\nasync function readRecoveryBatchRows(execSql: ExecSql, ids: string[]) {\n const list = ids.map(() => \"?\").join(\", \");\n const metadata = [];\n for (const [table, columns] of RECOVERY_METADATA_TABLES) {\n metadata.push(\n groupRows(\n await execSql(\n `SELECT local_id, ${columns} FROM ${table} WHERE app_kind = 'container-metadata' AND local_id IN (${list}) ORDER BY local_id, rowid`,\n ids,\n ),\n \"local_id\",\n true,\n ),\n );\n }\n const creates = groupRows(\n await execSql(\n `SELECT ${CREATE_COLUMNS} FROM local_intents WHERE lane = ? AND subject_id IN (${list}) AND sync_status = 'pending' ORDER BY subject_id, id`,\n [LOCAL_INTENT_LANES.containerCreate, ...ids],\n ),\n \"subject_id\",\n false,\n );\n const moves = groupRows(\n await execSql(\n `SELECT ${MOVE_COLUMNS} FROM local_intents WHERE lane = ? AND subject_id IN (${list}) ORDER BY subject_id, id`,\n [LOCAL_INTENT_LANES.containerMove, ...ids],\n ),\n \"subject_id\",\n false,\n );\n const placementWhere = `lane = ? AND (target_container_id IN (${list}) OR id IN\n (SELECT intent_id FROM document_intent_link_targets WHERE container_id IN (${list})))`;\n const placementBind = [LOCAL_INTENT_LANES.documentPlacement, ...ids, ...ids];\n // Every placement intent any folder in the batch reaches, and ALL link\n // targets of those intents: a folder's share is then the intents that\n // target it or carry a link target in it, as the per-folder read selects.\n const links = await execSql(\n `SELECT ${LINK_COLUMNS} FROM local_intents WHERE ${placementWhere} ORDER BY id`,\n placementBind,\n );\n const targets = await execSql(\n `SELECT ${TARGET_COLUMNS} FROM document_intent_link_targets WHERE intent_id IN\n (SELECT id FROM local_intents WHERE ${placementWhere}) ORDER BY intent_id, container_id, operation`,\n placementBind,\n );\n return { creates, links, metadata, moves, targets };\n}\n\n/**\n * Each folder's share of a batch's placement rows, indexed in one pass: the\n * intents that target the folder or carry a link target in it (in intent id\n * order), and all link targets of those intents (in the per-folder order).\n */\nfunction indexPlacementRows(\n rows: Awaited<ReturnType<typeof readRecoveryBatchRows>>,\n) {\n const targetsByIntent = groupRows(rows.targets, \"intent_id\", false);\n const linksByFolder = new Map<string, RecoveryRows>();\n for (const link of rows.links) {\n const intentId = String(Reflect.get(link, \"id\"));\n const folders = new Set([\n String(Reflect.get(link, \"target_container_id\")),\n ...(targetsByIntent.get(intentId) ?? []).map((target) =>\n String(Reflect.get(target, \"container_id\")),\n ),\n ]);\n for (const folder of folders) {\n const folderLinks = linksByFolder.get(folder);\n if (folderLinks) folderLinks.push(link);\n else linksByFolder.set(folder, [link]);\n }\n }\n return { linksByFolder, targetsByIntent };\n}\n\n/** One folder's snapshot from its batch's rows, in the per-folder shape. */\nasync function folderRecoverySnapshot(\n rows: Awaited<ReturnType<typeof readRecoveryBatchRows>>,\n placements: ReturnType<typeof indexPlacementRows>,\n containerId: string,\n dormant: RecoveryRows[number],\n) {\n const links = placements.linksByFolder.get(containerId) ?? [];\n // Links are in intent id order and targets in (intent id, container,\n // operation) order, so concatenating per link keeps the per-folder order.\n const targets = links.flatMap(\n (link) =>\n placements.targetsByIntent.get(String(Reflect.get(link, \"id\"))) ?? [],\n );\n const metadata = rows.metadata.map(\n (byFolder) => byFolder.get(containerId) ?? [],\n );\n // The metadata record's updates are its checkpoint snapshot, read above; a\n // folder with no record has none.\n const snapshotRow = (metadata[1] ?? [])[0];\n return {\n containerId,\n metadataUpdates:\n (metadata[0] ?? []).length > 0 && snapshotRow\n ? String(Reflect.get(snapshotRow, \"snapshot\") ?? \"\")\n : \"\",\n snapshot: await summarizeRecoverySnapshot({\n creates: rows.creates.get(containerId) ?? [],\n dormant,\n links,\n metadata,\n moves: rows.moves.get(containerId) ?? [],\n targets,\n }),\n };\n}\n\n/**\n * `recoverySnapshot` for every retained folder of an organization, one read\n * per table for each batch of folders. Each folder's rows are grouped in the\n * per-folder order (`local_id, rowid` for metadata; `subject_id, id` for\n * folder intents), so its revision is byte-identical to `recoverySnapshot`'s,\n * which a later discard compares.\n */\nasync function recoverySnapshots(execSql: ExecSql, organizationId: string) {\n const dormantRows = await execSql(\n `SELECT container_id, organization_id FROM dormant_container_metadata d\n WHERE organization_id = ?\n AND NOT EXISTS (SELECT 1 FROM containers WHERE id = d.container_id)\n ORDER BY container_id`,\n [organizationId],\n );\n const result = [];\n for (\n let offset = 0;\n offset < dormantRows.length;\n offset += RECOVERY_CONTAINER_BATCH_SIZE\n ) {\n const batch = dormantRows.slice(\n offset,\n offset + RECOVERY_CONTAINER_BATCH_SIZE,\n );\n const ids = batch.map((row) => String(Reflect.get(row, \"container_id\")));\n const rows = await readRecoveryBatchRows(execSql, ids);\n const placements = indexPlacementRows(rows);\n for (const [index, dormant] of batch.entries()) {\n result.push(\n await folderRecoverySnapshot(\n rows,\n placements,\n ids[index] ?? \"\",\n dormant,\n ),\n );\n }\n }\n return result;\n}\n\nexport async function listRetainedContainerMetadata(\n execSql: ExecSql,\n organizationId: string | null,\n) {\n if (!organizationId) return [];\n await sqlContainerContentsPersistence.ensureSchema(execSql);\n await ensureSqlTables(execSql, documentPlacementIntentTables);\n return runSerializedSqlMutation(execSql, async (locked) =>\n getClientSQLitePersistenceRuntime(locked).transaction(async () =>\n (await recoverySnapshots(locked, organizationId)).map(\n ({ containerId, metadataUpdates, snapshot }) => ({\n containerId,\n organizationId,\n ...snapshot,\n metadataUpdates,\n }),\n ),\n ),\n );\n}\n\n/** Explicit local discard, never called by remote discovery. */\nexport async function discardRetainedContainerMetadata(\n execSql: ExecSql,\n input: {\n containerId: string;\n organizationId: string;\n revision: string;\n },\n): Promise<boolean> {\n await sqlContainerContentsPersistence.ensureSchema(execSql);\n await ensureSqlTables(execSql, documentPlacementIntentTables);\n return runSerializedSqlMutation(execSql, async (locked) =>\n getClientSQLitePersistenceRuntime(locked).transaction(\n async (tx) => {\n const current = await recoverySnapshot(\n locked,\n input.containerId,\n input.organizationId,\n );\n if (!current || current.revision !== input.revision) return false;\n await recordContainerHydrationTombstones({\n tx,\n removals: [\n {\n containerId: input.containerId,\n reason: \"deleted\",\n updatedAt: new Date().toISOString(),\n },\n ],\n });\n await repairDocumentsForRemovedContainersInTransaction({\n tx,\n removals: [\n {\n containerId: input.containerId,\n reason: \"deleted\",\n updatedAt: new Date().toISOString(),\n },\n ],\n });\n await discardLinkIntentsForRemovedContainers({\n tx,\n containerIds: [input.containerId],\n });\n await tx\n .delete(localIntents)\n .where(\n and(\n inContainerIntentLanes(),\n eq(localIntents.subjectId, input.containerId),\n ),\n )\n .run();\n await deleteContainerMetadataDocumentRowsInTransaction(tx, [\n input.containerId,\n ]);\n return true;\n },\n { behavior: \"immediate\" },\n ),\n );\n}\n\n/** Retained metadata of an organization's absent folders; binds the org. */\nexport const RETAINED_CONTAINER_METADATA_SQL = `SELECT 1 FROM dormant_container_metadata d\n WHERE d.organization_id = ? AND NOT EXISTS (SELECT 1 FROM containers c WHERE c.id = d.container_id)`;\n\n/**\n * An organization's local folders whose queued move points at an unavailable\n * parent; binds the move lane, then the organization.\n */\nexport const RECOVERY_FOLDER_MOVES_SQL = `SELECT c.id FROM containers c JOIN local_intents i\n ON i.lane = ? AND i.subject_id = c.id AND i.target_container_id = c.parent_id\n WHERE c.organization_id = ? AND i.sync_status IN ('pending', 'blocked')\n AND NOT EXISTS (SELECT 1 FROM containers p WHERE p.id = c.parent_id)`;\n\n/** Existing local folders whose queued move points at an unavailable parent. */\nexport async function listRecoveryFolderMoveIds(\n execSql: ExecSql,\n organizationId: string | null,\n): Promise<string[]> {\n if (!organizationId) return [];\n await sqlContainerContentsPersistence.ensureSchema(execSql);\n const rows = await execSql(`${RECOVERY_FOLDER_MOVES_SQL} ORDER BY c.id`, [\n LOCAL_INTENT_LANES.containerMove,\n organizationId,\n ]);\n return rows.map((row) => String(Reflect.get(row, \"id\")));\n}\n"]}
|
|
@@ -1,10 +1,10 @@
|
|
|
1
|
-
import { and, asc, eq, inArray, notInArray } from "drizzle-orm";
|
|
1
|
+
import { and, asc, eq, inArray, notInArray, or } from "drizzle-orm";
|
|
2
2
|
import { documentContainerProjection, documentContainerProjectionTables, documentPlacementIntentTables, documentTables, inLocalIntentLane, LOCAL_INTENT_LANES, localIntents, } from "../../sqlite/schema/index.js";
|
|
3
3
|
import { documentIntentLinkTargets } from "../../sqlite/schema/documentPlacementIntentSchema.js";
|
|
4
|
-
import { sqliteInsertBatchSize } from "../../sqlite/sqliteBindBudget.js";
|
|
4
|
+
import { REMOTE_RESET_SQL_BATCH_SIZE, remoteResetBatches, SQLITE_BOUND_VARIABLE_BUDGET, sqliteInsertBatchSize, } from "../../sqlite/sqliteBindBudget.js";
|
|
5
5
|
import { getClientSQLitePersistenceRuntime, } from "../../sqlite/sqlitePersistenceRuntime.js";
|
|
6
6
|
import { ensureSqlTables } from "../../sqlite/sqlSchema.js";
|
|
7
|
-
import { deleteContainerDocumentTombstoneHoldRowsForLinks, deleteContainerDocumentTombstoneHoldsForContainers, deleteContainerDocumentTombstoneHoldsForDocuments, } from "../documents/containerDocumentTombstoneHoldsPersistence.js";
|
|
7
|
+
import { deleteContainerDocumentTombstoneHoldRowsForLinks, deleteContainerDocumentTombstoneHoldsForContainers, deleteContainerDocumentTombstoneHoldsForDocuments, placementPairsMatch, } from "../documents/containerDocumentTombstoneHoldsPersistence.js";
|
|
8
8
|
import { filterWritableDocumentPlacements, } from "./documentPlacement.js";
|
|
9
9
|
const LINK_INSERT_BATCH_SIZE = sqliteInsertBatchSize(documentContainerProjection);
|
|
10
10
|
/** Link rows and their tombstone holds leave together on a remote reset. */
|
|
@@ -22,23 +22,52 @@ export async function deleteDocumentPlacementRowsByDocumentIds(tx, documentIds)
|
|
|
22
22
|
.run();
|
|
23
23
|
await deleteContainerDocumentTombstoneHoldsForDocuments(tx, documentIds);
|
|
24
24
|
}
|
|
25
|
-
/** Removes
|
|
26
|
-
export async function
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
25
|
+
/** Removes the listed (document, container) placements, 50 pairs per statement. */
|
|
26
|
+
export async function deleteDocumentPlacementRowsInTransaction(tx, placements) {
|
|
27
|
+
for (const batch of remoteResetBatches(placements)) {
|
|
28
|
+
await tx
|
|
29
|
+
.delete(documentContainerProjection)
|
|
30
|
+
.where(placementPairsMatch(batch, documentContainerProjection.documentId, documentContainerProjection.containerId))
|
|
31
|
+
.run();
|
|
32
|
+
}
|
|
31
33
|
}
|
|
32
|
-
/**
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
34
|
+
/**
|
|
35
|
+
* Removes each document's placements outside the containers its head links,
|
|
36
|
+
* batching documents so one statement stays inside SQLite's bind budget.
|
|
37
|
+
*/
|
|
38
|
+
export async function deleteUnlinkedDocumentPlacementRowsInTransaction(tx, linkedContainerIdsByDocumentId) {
|
|
39
|
+
let batch = [];
|
|
40
|
+
let binds = 0;
|
|
41
|
+
const flush = async () => {
|
|
42
|
+
if (batch.length === 0)
|
|
43
|
+
return;
|
|
44
|
+
await tx
|
|
45
|
+
.delete(documentContainerProjection)
|
|
46
|
+
.where(or(...batch))
|
|
47
|
+
.run();
|
|
48
|
+
batch = [];
|
|
49
|
+
binds = 0;
|
|
50
|
+
};
|
|
51
|
+
for (const [documentId, linked] of linkedContainerIdsByDocumentId) {
|
|
52
|
+
// The document id plus one bind per linked container.
|
|
53
|
+
const documentBinds = 1 + linked.size;
|
|
54
|
+
if (batch.length >= REMOTE_RESET_SQL_BATCH_SIZE ||
|
|
55
|
+
binds + documentBinds > SQLITE_BOUND_VARIABLE_BUDGET) {
|
|
56
|
+
await flush();
|
|
57
|
+
}
|
|
58
|
+
batch.push(unlinkedDocumentPlacementRows(documentId, linked));
|
|
59
|
+
binds += documentBinds;
|
|
60
|
+
}
|
|
61
|
+
await flush();
|
|
62
|
+
}
|
|
63
|
+
/** A document's placements outside the containers its head links. */
|
|
64
|
+
function unlinkedDocumentPlacementRows(documentId, linkedContainerIds) {
|
|
65
|
+
const ofDocument = eq(documentContainerProjection.documentId, documentId);
|
|
66
|
+
return linkedContainerIds.size === 0
|
|
67
|
+
? ofDocument
|
|
68
|
+
: (and(ofDocument, notInArray(documentContainerProjection.containerId, [
|
|
39
69
|
...linkedContainerIds,
|
|
40
|
-
])))
|
|
41
|
-
.run();
|
|
70
|
+
])) ?? ofDocument);
|
|
42
71
|
}
|
|
43
72
|
/** Removes a deleted document's queued placement intents and link targets. */
|
|
44
73
|
export async function deleteDocumentPlacementIntentsInTransaction(tx, documentId) {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"documentContainerProjectionPersistence.js","sourceRoot":"","sources":["documentContainerProjectionPersistence.ts"],"names":[],"mappings":"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","sourcesContent":["import { and, asc, eq, inArray, notInArray } from \"drizzle-orm\";\nimport {\n documentContainerProjection,\n documentContainerProjectionTables,\n documentPlacementIntentTables,\n documentTables,\n inLocalIntentLane,\n LOCAL_INTENT_LANES,\n localIntents,\n} from \"../../sqlite/schema\";\nimport { documentIntentLinkTargets } from \"../../sqlite/schema/documentPlacementIntentSchema\";\nimport { sqliteInsertBatchSize } from \"../../sqlite/sqliteBindBudget\";\nimport {\n type ClientSQLiteTransactionScope,\n getClientSQLitePersistenceRuntime,\n} from \"../../sqlite/sqlitePersistenceRuntime\";\nimport { type ExecSql, ensureSqlTables } from \"../../sqlite/sqlSchema\";\nimport {\n deleteContainerDocumentTombstoneHoldRowsForLinks,\n deleteContainerDocumentTombstoneHoldsForContainers,\n deleteContainerDocumentTombstoneHoldsForDocuments,\n} from \"../documents/containerDocumentTombstoneHoldsPersistence\";\n\nimport {\n type DocumentPlacementInput,\n type DocumentPlacementWriteOptions,\n filterWritableDocumentPlacements,\n} from \"./documentPlacement\";\n\nconst LINK_INSERT_BATCH_SIZE = sqliteInsertBatchSize(\n documentContainerProjection,\n);\n\ninterface DocumentContainerProjectionPersistence {\n ensureSchema: (execSql: ExecSql) => Promise<void>;\n listLinkedContainerIds: (\n execSql: ExecSql,\n documentId: string,\n ) => Promise<ReadonlyArray<string>>;\n listLinkedContainerIdsByDocumentIds: (\n execSql: ExecSql,\n documentIds: ReadonlyArray<string>,\n ) => Promise<ReadonlyMap<string, ReadonlyArray<string>>>;\n listDocumentIdsByContainerIds: (\n execSql: ExecSql,\n containerIds: ReadonlyArray<string>,\n ) => Promise<ReadonlyArray<string>>;\n replaceDocumentLinks: (\n execSql: ExecSql,\n documentId: string,\n containerIds: ReadonlyArray<string>,\n options?: DocumentPlacementWriteOptions,\n ) => Promise<void>;\n replaceDocumentLinksBatch: (\n execSql: ExecSql,\n inputs: ReadonlyArray<DocumentPlacementInput>,\n options?: DocumentPlacementWriteOptions,\n ) => Promise<void>;\n}\n\n/** Link rows and their tombstone holds leave together on a remote reset. */\nexport async function deleteDocumentPlacementRowsByContainerIds(\n tx: ClientSQLiteTransactionScope,\n containerIds: ReadonlyArray<string>,\n): Promise<void> {\n await tx\n .delete(documentContainerProjection)\n .where(inArray(documentContainerProjection.containerId, containerIds))\n .run();\n await deleteContainerDocumentTombstoneHoldsForContainers(tx, containerIds);\n}\n\nexport async function deleteDocumentPlacementRowsByDocumentIds(\n tx: ClientSQLiteTransactionScope,\n documentIds: ReadonlyArray<string>,\n): Promise<void> {\n await tx\n .delete(documentContainerProjection)\n .where(inArray(documentContainerProjection.documentId, documentIds))\n .run();\n await deleteContainerDocumentTombstoneHoldsForDocuments(tx, documentIds);\n}\n\n/** Removes one placement a verified tombstone retired. */\nexport async function deleteDocumentPlacementRowInTransaction(\n tx: ClientSQLiteTransactionScope,\n placement: { readonly containerId: string; readonly documentId: string },\n): Promise<void> {\n await tx\n .delete(documentContainerProjection)\n .where(\n and(\n eq(documentContainerProjection.documentId, placement.documentId),\n eq(documentContainerProjection.containerId, placement.containerId),\n ),\n )\n .run();\n}\n\n/** Removes a document's placements outside the containers its head links. */\nexport async function deleteUnlinkedDocumentPlacementRowsInTransaction(\n tx: ClientSQLiteTransactionScope,\n documentId: string,\n linkedContainerIds: ReadonlySet<string>,\n): Promise<void> {\n await tx\n .delete(documentContainerProjection)\n .where(\n linkedContainerIds.size === 0\n ? eq(documentContainerProjection.documentId, documentId)\n : and(\n eq(documentContainerProjection.documentId, documentId),\n notInArray(documentContainerProjection.containerId, [\n ...linkedContainerIds,\n ]),\n ),\n )\n .run();\n}\n\n/** Removes a deleted document's queued placement intents and link targets. */\nexport async function deleteDocumentPlacementIntentsInTransaction(\n tx: ClientSQLiteTransactionScope,\n documentId: string,\n): Promise<void> {\n const placementIntent = and(\n inLocalIntentLane(LOCAL_INTENT_LANES.documentPlacement),\n eq(localIntents.subjectId, documentId),\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(placementIntent),\n ),\n )\n .run();\n await tx.delete(localIntents).where(placementIntent).run();\n}\n\nexport const sqlDocumentContainerProjectionPersistence: DocumentContainerProjectionPersistence =\n {\n async ensureSchema(execSql) {\n await ensureSqlTables(execSql, documentContainerProjectionTables);\n },\n async listLinkedContainerIds(execSql, documentId) {\n await ensureSqlTables(execSql, documentContainerProjectionTables);\n const { db } = getClientSQLitePersistenceRuntime(execSql);\n const rows = await db\n .select({ containerId: documentContainerProjection.containerId })\n .from(documentContainerProjection)\n .where(eq(documentContainerProjection.documentId, documentId))\n .orderBy(asc(documentContainerProjection.containerId));\n\n return rows.map((row) => row.containerId);\n },\n async listLinkedContainerIdsByDocumentIds(execSql, documentIds) {\n await ensureSqlTables(execSql, documentContainerProjectionTables);\n const uniqueDocumentIds = Array.from(new Set(documentIds));\n if (uniqueDocumentIds.length === 0) {\n return new Map();\n }\n\n const { db } = getClientSQLitePersistenceRuntime(execSql);\n const rows = await db\n .select({\n documentId: documentContainerProjection.documentId,\n containerId: documentContainerProjection.containerId,\n })\n .from(documentContainerProjection)\n .where(\n inArray(documentContainerProjection.documentId, uniqueDocumentIds),\n )\n .orderBy(\n asc(documentContainerProjection.documentId),\n asc(documentContainerProjection.containerId),\n );\n\n const linkedContainerIdsByDocumentId = new Map<string, string[]>();\n for (const documentId of uniqueDocumentIds) {\n linkedContainerIdsByDocumentId.set(documentId, []);\n }\n\n for (const row of rows) {\n const linkedContainerIds =\n linkedContainerIdsByDocumentId.get(row.documentId) ?? [];\n linkedContainerIds.push(row.containerId);\n linkedContainerIdsByDocumentId.set(row.documentId, linkedContainerIds);\n }\n\n return linkedContainerIdsByDocumentId;\n },\n async listDocumentIdsByContainerIds(execSql, containerIds) {\n await ensureSqlTables(execSql, documentContainerProjectionTables);\n const uniqueContainerIds = Array.from(new Set(containerIds));\n if (uniqueContainerIds.length === 0) {\n return [];\n }\n\n const { db } = getClientSQLitePersistenceRuntime(execSql);\n const rows = await db\n .selectDistinct({ documentId: documentContainerProjection.documentId })\n .from(documentContainerProjection)\n .where(\n inArray(documentContainerProjection.containerId, uniqueContainerIds),\n )\n .orderBy(asc(documentContainerProjection.documentId));\n\n return rows.map((row) => row.documentId);\n },\n async replaceDocumentLinks(execSql, documentId, containerIds, options) {\n await sqlDocumentContainerProjectionPersistence.replaceDocumentLinksBatch(\n execSql,\n [{ documentId, containerIds }],\n options,\n );\n },\n async replaceDocumentLinksBatch(execSql, inputs, options) {\n await ensureSqlTables(execSql, [\n ...documentContainerProjectionTables,\n ...documentPlacementIntentTables,\n ...documentTables,\n ]);\n const latest = new Map(inputs.map((input) => [input.documentId, input]));\n if (latest.size === 0) return;\n const runtime = getClientSQLitePersistenceRuntime(execSql);\n const replace = async (tx: ClientSQLiteTransactionScope) => {\n const writable = await filterWritableDocumentPlacements(\n tx,\n [...latest.values()],\n options,\n );\n if (writable.length === 0) return;\n // A placement the writer re-asserts is no longer tombstoned: a\n // listing that links it again, or a local move that owns it, releases\n // the hold so the document is not hidden until the next retry.\n if (options?.moveIntentId) {\n await deleteContainerDocumentTombstoneHoldsForDocuments(\n tx,\n writable.map((input) => input.documentId),\n );\n } else {\n await deleteContainerDocumentTombstoneHoldRowsForLinks(tx, writable);\n }\n await tx\n .delete(documentContainerProjection)\n .where(\n inArray(\n documentContainerProjection.documentId,\n writable.map((input) => input.documentId),\n ),\n )\n .run();\n const updatedAt = new Date().toISOString();\n const rows = writable.flatMap((input) =>\n Array.from(new Set(input.containerIds))\n .sort()\n .map((containerId) => ({\n documentId: input.documentId,\n containerId,\n updatedAt,\n })),\n );\n // A lane page of multi-link documents can carry more link rows than\n // one statement may bind.\n for (\n let index = 0;\n index < rows.length;\n index += LINK_INSERT_BATCH_SIZE\n ) {\n await tx\n .insert(documentContainerProjection)\n .values(rows.slice(index, index + LINK_INSERT_BATCH_SIZE))\n .run();\n }\n };\n if (options?.stillCurrent) {\n await runtime.guardedTransaction(replace, options.stillCurrent);\n return;\n }\n await runtime.transaction(replace);\n },\n };\n"]}
|
|
1
|
+
{"version":3,"file":"documentContainerProjectionPersistence.js","sourceRoot":"","sources":["documentContainerProjectionPersistence.ts"],"names":[],"mappings":"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","sourcesContent":["import { and, asc, eq, inArray, notInArray, or, type SQL } from \"drizzle-orm\";\nimport {\n documentContainerProjection,\n documentContainerProjectionTables,\n documentPlacementIntentTables,\n documentTables,\n inLocalIntentLane,\n LOCAL_INTENT_LANES,\n localIntents,\n} from \"../../sqlite/schema\";\nimport { documentIntentLinkTargets } from \"../../sqlite/schema/documentPlacementIntentSchema\";\nimport {\n REMOTE_RESET_SQL_BATCH_SIZE,\n remoteResetBatches,\n SQLITE_BOUND_VARIABLE_BUDGET,\n sqliteInsertBatchSize,\n} from \"../../sqlite/sqliteBindBudget\";\nimport {\n type ClientSQLiteTransactionScope,\n getClientSQLitePersistenceRuntime,\n} from \"../../sqlite/sqlitePersistenceRuntime\";\nimport { type ExecSql, ensureSqlTables } from \"../../sqlite/sqlSchema\";\nimport {\n deleteContainerDocumentTombstoneHoldRowsForLinks,\n deleteContainerDocumentTombstoneHoldsForContainers,\n deleteContainerDocumentTombstoneHoldsForDocuments,\n placementPairsMatch,\n} from \"../documents/containerDocumentTombstoneHoldsPersistence\";\n\nimport {\n type DocumentPlacementInput,\n type DocumentPlacementWriteOptions,\n filterWritableDocumentPlacements,\n} from \"./documentPlacement\";\n\nconst LINK_INSERT_BATCH_SIZE = sqliteInsertBatchSize(\n documentContainerProjection,\n);\n\ninterface DocumentContainerProjectionPersistence {\n ensureSchema: (execSql: ExecSql) => Promise<void>;\n listLinkedContainerIds: (\n execSql: ExecSql,\n documentId: string,\n ) => Promise<ReadonlyArray<string>>;\n listLinkedContainerIdsByDocumentIds: (\n execSql: ExecSql,\n documentIds: ReadonlyArray<string>,\n ) => Promise<ReadonlyMap<string, ReadonlyArray<string>>>;\n listDocumentIdsByContainerIds: (\n execSql: ExecSql,\n containerIds: ReadonlyArray<string>,\n ) => Promise<ReadonlyArray<string>>;\n replaceDocumentLinks: (\n execSql: ExecSql,\n documentId: string,\n containerIds: ReadonlyArray<string>,\n options?: DocumentPlacementWriteOptions,\n ) => Promise<void>;\n replaceDocumentLinksBatch: (\n execSql: ExecSql,\n inputs: ReadonlyArray<DocumentPlacementInput>,\n options?: DocumentPlacementWriteOptions,\n ) => Promise<void>;\n}\n\n/** Link rows and their tombstone holds leave together on a remote reset. */\nexport async function deleteDocumentPlacementRowsByContainerIds(\n tx: ClientSQLiteTransactionScope,\n containerIds: ReadonlyArray<string>,\n): Promise<void> {\n await tx\n .delete(documentContainerProjection)\n .where(inArray(documentContainerProjection.containerId, containerIds))\n .run();\n await deleteContainerDocumentTombstoneHoldsForContainers(tx, containerIds);\n}\n\nexport async function deleteDocumentPlacementRowsByDocumentIds(\n tx: ClientSQLiteTransactionScope,\n documentIds: ReadonlyArray<string>,\n): Promise<void> {\n await tx\n .delete(documentContainerProjection)\n .where(inArray(documentContainerProjection.documentId, documentIds))\n .run();\n await deleteContainerDocumentTombstoneHoldsForDocuments(tx, documentIds);\n}\n\n/** Removes the listed (document, container) placements, 50 pairs per statement. */\nexport async function deleteDocumentPlacementRowsInTransaction(\n tx: ClientSQLiteTransactionScope,\n placements: ReadonlyArray<{\n readonly containerId: string;\n readonly documentId: string;\n }>,\n): Promise<void> {\n for (const batch of remoteResetBatches(placements)) {\n await tx\n .delete(documentContainerProjection)\n .where(\n placementPairsMatch(\n batch,\n documentContainerProjection.documentId,\n documentContainerProjection.containerId,\n ),\n )\n .run();\n }\n}\n\n/**\n * Removes each document's placements outside the containers its head links,\n * batching documents so one statement stays inside SQLite's bind budget.\n */\nexport async function deleteUnlinkedDocumentPlacementRowsInTransaction(\n tx: ClientSQLiteTransactionScope,\n linkedContainerIdsByDocumentId: ReadonlyMap<string, ReadonlySet<string>>,\n): Promise<void> {\n let batch: SQL[] = [];\n let binds = 0;\n const flush = async () => {\n if (batch.length === 0) return;\n await tx\n .delete(documentContainerProjection)\n .where(or(...batch))\n .run();\n batch = [];\n binds = 0;\n };\n for (const [documentId, linked] of linkedContainerIdsByDocumentId) {\n // The document id plus one bind per linked container.\n const documentBinds = 1 + linked.size;\n if (\n batch.length >= REMOTE_RESET_SQL_BATCH_SIZE ||\n binds + documentBinds > SQLITE_BOUND_VARIABLE_BUDGET\n ) {\n await flush();\n }\n batch.push(unlinkedDocumentPlacementRows(documentId, linked));\n binds += documentBinds;\n }\n await flush();\n}\n\n/** A document's placements outside the containers its head links. */\nfunction unlinkedDocumentPlacementRows(\n documentId: string,\n linkedContainerIds: ReadonlySet<string>,\n): SQL {\n const ofDocument = eq(documentContainerProjection.documentId, documentId);\n return linkedContainerIds.size === 0\n ? ofDocument\n : (and(\n ofDocument,\n notInArray(documentContainerProjection.containerId, [\n ...linkedContainerIds,\n ]),\n ) ?? ofDocument);\n}\n\n/** Removes a deleted document's queued placement intents and link targets. */\nexport async function deleteDocumentPlacementIntentsInTransaction(\n tx: ClientSQLiteTransactionScope,\n documentId: string,\n): Promise<void> {\n const placementIntent = and(\n inLocalIntentLane(LOCAL_INTENT_LANES.documentPlacement),\n eq(localIntents.subjectId, documentId),\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(placementIntent),\n ),\n )\n .run();\n await tx.delete(localIntents).where(placementIntent).run();\n}\n\nexport const sqlDocumentContainerProjectionPersistence: DocumentContainerProjectionPersistence =\n {\n async ensureSchema(execSql) {\n await ensureSqlTables(execSql, documentContainerProjectionTables);\n },\n async listLinkedContainerIds(execSql, documentId) {\n await ensureSqlTables(execSql, documentContainerProjectionTables);\n const { db } = getClientSQLitePersistenceRuntime(execSql);\n const rows = await db\n .select({ containerId: documentContainerProjection.containerId })\n .from(documentContainerProjection)\n .where(eq(documentContainerProjection.documentId, documentId))\n .orderBy(asc(documentContainerProjection.containerId));\n\n return rows.map((row) => row.containerId);\n },\n async listLinkedContainerIdsByDocumentIds(execSql, documentIds) {\n await ensureSqlTables(execSql, documentContainerProjectionTables);\n const uniqueDocumentIds = Array.from(new Set(documentIds));\n if (uniqueDocumentIds.length === 0) {\n return new Map();\n }\n\n const { db } = getClientSQLitePersistenceRuntime(execSql);\n const rows = await db\n .select({\n documentId: documentContainerProjection.documentId,\n containerId: documentContainerProjection.containerId,\n })\n .from(documentContainerProjection)\n .where(\n inArray(documentContainerProjection.documentId, uniqueDocumentIds),\n )\n .orderBy(\n asc(documentContainerProjection.documentId),\n asc(documentContainerProjection.containerId),\n );\n\n const linkedContainerIdsByDocumentId = new Map<string, string[]>();\n for (const documentId of uniqueDocumentIds) {\n linkedContainerIdsByDocumentId.set(documentId, []);\n }\n\n for (const row of rows) {\n const linkedContainerIds =\n linkedContainerIdsByDocumentId.get(row.documentId) ?? [];\n linkedContainerIds.push(row.containerId);\n linkedContainerIdsByDocumentId.set(row.documentId, linkedContainerIds);\n }\n\n return linkedContainerIdsByDocumentId;\n },\n async listDocumentIdsByContainerIds(execSql, containerIds) {\n await ensureSqlTables(execSql, documentContainerProjectionTables);\n const uniqueContainerIds = Array.from(new Set(containerIds));\n if (uniqueContainerIds.length === 0) {\n return [];\n }\n\n const { db } = getClientSQLitePersistenceRuntime(execSql);\n const rows = await db\n .selectDistinct({ documentId: documentContainerProjection.documentId })\n .from(documentContainerProjection)\n .where(\n inArray(documentContainerProjection.containerId, uniqueContainerIds),\n )\n .orderBy(asc(documentContainerProjection.documentId));\n\n return rows.map((row) => row.documentId);\n },\n async replaceDocumentLinks(execSql, documentId, containerIds, options) {\n await sqlDocumentContainerProjectionPersistence.replaceDocumentLinksBatch(\n execSql,\n [{ documentId, containerIds }],\n options,\n );\n },\n async replaceDocumentLinksBatch(execSql, inputs, options) {\n await ensureSqlTables(execSql, [\n ...documentContainerProjectionTables,\n ...documentPlacementIntentTables,\n ...documentTables,\n ]);\n const latest = new Map(inputs.map((input) => [input.documentId, input]));\n if (latest.size === 0) return;\n const runtime = getClientSQLitePersistenceRuntime(execSql);\n const replace = async (tx: ClientSQLiteTransactionScope) => {\n const writable = await filterWritableDocumentPlacements(\n tx,\n [...latest.values()],\n options,\n );\n if (writable.length === 0) return;\n // A placement the writer re-asserts is no longer tombstoned: a\n // listing that links it again, or a local move that owns it, releases\n // the hold so the document is not hidden until the next retry.\n if (options?.moveIntentId) {\n await deleteContainerDocumentTombstoneHoldsForDocuments(\n tx,\n writable.map((input) => input.documentId),\n );\n } else {\n await deleteContainerDocumentTombstoneHoldRowsForLinks(tx, writable);\n }\n await tx\n .delete(documentContainerProjection)\n .where(\n inArray(\n documentContainerProjection.documentId,\n writable.map((input) => input.documentId),\n ),\n )\n .run();\n const updatedAt = new Date().toISOString();\n const rows = writable.flatMap((input) =>\n Array.from(new Set(input.containerIds))\n .sort()\n .map((containerId) => ({\n documentId: input.documentId,\n containerId,\n updatedAt,\n })),\n );\n // A lane page of multi-link documents can carry more link rows than\n // one statement may bind.\n for (\n let index = 0;\n index < rows.length;\n index += LINK_INSERT_BATCH_SIZE\n ) {\n await tx\n .insert(documentContainerProjection)\n .values(rows.slice(index, index + LINK_INSERT_BATCH_SIZE))\n .run();\n }\n };\n if (options?.stillCurrent) {\n await runtime.guardedTransaction(replace, options.stillCurrent);\n return;\n }\n await runtime.transaction(replace);\n },\n };\n"]}
|