@tearleads/client-sdk 0.1.65 → 0.1.67
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/documentPurgePlacement.js +13 -0
- package/data/persistence/container-contents/documentPurgePlacement.js.map +1 -1
- package/index.d.ts +1 -1
- package/index.js.map +1 -1
- package/package.json +1 -1
- package/stores/container-contents/containerContentsStore.js +3 -2
- package/stores/container-contents/containerContentsStore.js.map +1 -1
- package/stores/container-contents/index.d.ts +1 -1
- package/stores/container-contents/index.js.map +1 -1
- package/stores/container-contents/operations/purgeMoveSettlementOperation.js +71 -0
- package/stores/container-contents/operations/purgeMoveSettlementOperation.js.map +1 -0
- package/stores/container-contents/syncAgent/acceptedMoveRetry.js +40 -0
- package/stores/container-contents/syncAgent/acceptedMoveRetry.js.map +1 -0
- package/stores/container-contents/syncAgent/syncLaneIteration.js +9 -2
- package/stores/container-contents/syncAgent/syncLaneIteration.js.map +1 -1
- package/workflows/container-contents/container-state/move/acceptedMoveSettlement.js +59 -0
- package/workflows/container-contents/container-state/move/acceptedMoveSettlement.js.map +1 -0
- package/workflows/container-contents/container-state/{moveIntentSync.js → move/moveIntentSync.js} +77 -39
- package/workflows/container-contents/container-state/move/moveIntentSync.js.map +1 -0
- package/workflows/container-contents/container-state/purge/purgeMoveSettlement.js +119 -0
- package/workflows/container-contents/container-state/purge/purgeMoveSettlement.js.map +1 -0
- package/workflows/container-contents/container-state/purge/purgeProgress.d.ts +8 -0
- package/workflows/container-contents/container-state/purge/purgeProgress.js.map +1 -1
- package/workflows/container-contents/container-state/remote.js +22 -18
- package/workflows/container-contents/container-state/remote.js.map +1 -1
- package/workflows/container-contents/container-state/types.js.map +1 -1
- package/workflows/container-contents/syncLane.d.ts +2 -0
- package/workflows/container-contents/syncLane.js +7 -0
- package/workflows/container-contents/syncLane.js.map +1 -1
- package/workflows/containers/child/move/index.d.ts +24 -2
- package/workflows/containers/child/move/index.js +62 -70
- package/workflows/containers/child/move/index.js.map +1 -1
- package/workflows/containers/child/move/moveRequest.js +47 -0
- package/workflows/containers/child/move/moveRequest.js.map +1 -0
- package/workflows/containers/child/mutationSubmit.js +1 -0
- package/workflows/containers/child/mutationSubmit.js.map +1 -1
- package/workflows/containers/index.d.ts +1 -1
- package/workflows/containers/index.js +1 -1
- package/workflows/containers/index.js.map +1 -1
- package/workflows/container-contents/container-state/moveIntentSync.js.map +0 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"syncLaneIteration.js","sourceRoot":"","sources":["syncLaneIteration.ts"],"names":[],"mappings":"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","sourcesContent":["import { syncPendingContainerCreateIntents } from \"../../../workflows/container-contents/container-state/create/createIntentSync\";\nimport { syncPendingContainerMoveIntents } from \"../../../workflows/container-contents/container-state/moveIntentSync\";\nimport {\n type DocumentMoveIntentSyncHost,\n syncPendingDocumentMoveIntents,\n} from \"../../../workflows/container-contents/documentMove/documentMoveIntentSync\";\nimport { syncContainerMetadataState } from \"../../../workflows/container-contents/metadata\";\nimport type {\n ContainerState,\n RemoteContainerHydrationHost,\n} from \"../../../workflows/container-contents/remoteHydration\";\nimport { createContainerContentsDocumentsRuntime } from \"../../../workflows/container-contents/runtime\";\nimport { openDocumentStore, requestDomainDocumentSync } from \"../../documents\";\nimport { relinkDocumentStoreWithCommitSideEffect } from \"../../documents/documentStore/internalRelink\";\nimport { DocumentRemoteWorkBusyError } from \"../../documents/documentStore/sync/remoteWork\";\nimport { removeMissingSyncedContainerState } from \"../containerState/missingSyncedContainerState\";\nimport { runContainerDocumentWork } from \"../documentWork\";\nimport {\n primeStoreDocuments,\n recoverStoreStaleRoot,\n} from \"../documentWork/documentRecovery\";\nimport {\n bumpMetadataSyncSeq,\n clearMetadataSyncQueueIfUnchanged,\n readMetadataSyncSeq,\n} from \"./metadataSyncSignal\";\nimport type {\n ContainerContentsStoreRuntime,\n ContainerContentsStoreSyncState,\n} from \"./syncAgentTypes\";\n\ntype ContainerContentsStorePrimeDocumentRuntime = ReturnType<\n typeof createContainerContentsDocumentsRuntime\n>;\ntype SyncContainerMetadata = typeof syncContainerMetadataState;\n\nfunction requestContainerContentsStoreSync(\n state: ContainerContentsStoreSyncState,\n) {\n state.syncLane?.requestSync();\n}\n\nfunction createRemoteSyncBlocker(runtime: ContainerContentsStoreRuntime) {\n return (organizationId: string) =>\n runtime.util.isRemoteSyncBlocked?.(organizationId) ?? false;\n}\n\nfunction captureStructuralSyncGeneration(\n state: ContainerContentsStoreSyncState,\n runtime: ContainerContentsStoreRuntime,\n) {\n const activeContainerId = runtime.state.containerId;\n const activeOrganizationId = runtime.auth.organizationId;\n const lifecycleGeneration = state.lifecycleGeneration;\n const structuralGeneration = state.structuralGeneration;\n const domainScope = runtime.state.domainScope;\n const execSql = runtime.infra.execSql;\n const resolveProjectionUserKey = state.resolveProjectionUserKey;\n const syncLane = state.syncLane;\n return () =>\n state.lifecycleGeneration === lifecycleGeneration &&\n state.structuralGeneration === structuralGeneration &&\n state.runtime.auth.organizationId === activeOrganizationId &&\n state.runtime.state.containerId === activeContainerId &&\n state.runtime.state.domainScope === domainScope &&\n state.runtime.infra.execSql === execSql &&\n state.resolveProjectionUserKey === resolveProjectionUserKey &&\n state.syncLane === syncLane &&\n !syncLane?.isDisposed?.();\n}\n\nfunction createContainerContentsStoreDocumentMoveHost(\n state: ContainerContentsStoreSyncState,\n isCurrent: () => boolean,\n): DocumentMoveIntentSyncHost<ContainerContentsStorePrimeDocumentRuntime> {\n const runtime = state.runtime;\n const domainScope = runtime.state.domainScope;\n return {\n documentWorkflowRuntime: (containerId) =>\n createContainerContentsDocumentsRuntime(runtime, containerId),\n openDocumentStore: ({ containerId, documentId, localId }) => {\n const store = openDocumentStore(\n domainScope,\n localId,\n createContainerContentsDocumentsRuntime(runtime, containerId),\n documentId,\n );\n return {\n assertCanRotateContentKey: () =>\n store.assertCanRotateContentKey().catch((error: unknown) => {\n if (error instanceof DocumentRemoteWorkBusyError) {\n void error.whenIdle.then(() => {\n if (isCurrent()) requestContainerContentsStoreSync(state);\n });\n }\n throw error;\n }),\n ensureInitialized: () => store.ensureInitialized(),\n relink: ({ commitSideEffect, ...input }) =>\n commitSideEffect\n ? relinkDocumentStoreWithCommitSideEffect(\n store,\n input,\n commitSideEffect,\n )\n : store.relink(input),\n requestSync: () => store.requestSync(),\n updateRuntime: (nextRuntime) => store.updateRuntime(nextRuntime),\n };\n },\n };\n}\n\nfunction syncPendingStoreDocumentMoves(input: {\n isCreatePending: (containerId: string) => boolean;\n isCurrent: () => boolean;\n isRemoteSyncBlocked: (organizationId: string) => boolean;\n state: ContainerContentsStoreSyncState;\n}) {\n return syncPendingDocumentMoveIntents({\n host: createContainerContentsStoreDocumentMoveHost(\n input.state,\n input.isCurrent,\n ),\n isCreatePending: input.isCreatePending,\n isCurrent: input.isCurrent,\n isRemoteSyncBlocked: input.isRemoteSyncBlocked,\n state: input.state,\n });\n}\n\nasync function syncSingleContainerMetadata(input: {\n host: RemoteContainerHydrationHost;\n isCurrent: () => boolean;\n state: ContainerContentsStoreSyncState;\n containerState: ContainerState;\n encapsulationKeyPair: NonNullable<\n ContainerContentsStoreRuntime[\"crypto\"][\"encapsulationKeyPair\"]\n >;\n syncContainerMetadata?: SyncContainerMetadata | undefined;\n}) {\n const { containerState, encapsulationKeyPair, host, isCurrent, state } =\n input;\n const metadataDocumentId = containerState.record.documentId;\n // Snapshot this signal before the GET so a mid-pass event survives clearing.\n const consumedSeqById = new Map<string, number>();\n if (typeof metadataDocumentId === \"string\") {\n consumedSeqById.set(\n metadataDocumentId,\n readMetadataSyncSeq(state.metadataSyncSignalSeqById, metadataDocumentId),\n );\n }\n const synced = await (\n input.syncContainerMetadata ?? syncContainerMetadataState\n )({\n forceReadSync:\n typeof metadataDocumentId === \"string\" &&\n state.metadataDocumentIdsNeedingSync.has(metadataDocumentId),\n locallyAcceptedUpdateIds: state.locallyAcceptedMetadataUpdateIds,\n isCurrent,\n metadataState: containerState,\n onDurableStateNeedsReload: () => {\n const currentDocumentId = state.containersById.get(\n containerState.container.id,\n )?.record.documentId;\n if (typeof currentDocumentId === \"string\") {\n state.metadataDocumentIdsNeedingSync.add(currentDocumentId);\n bumpMetadataSyncSeq(state.metadataSyncSignalSeqById, currentDocumentId);\n }\n requestContainerContentsStoreSync(state);\n },\n persistence: state.persistence,\n resolveProjectionUserKey: state.resolveProjectionUserKey,\n runtime: state.runtime,\n targetSecretKey: encapsulationKeyPair.secretKey,\n });\n if (!isCurrent() || !synced) {\n return;\n }\n if (\"missing\" in synced) {\n removeMissingSyncedContainerState(\n state,\n containerState,\n host.updateSnapshot,\n );\n return;\n }\n\n for (const id of [metadataDocumentId, synced.record.documentId]) {\n if (typeof id === \"string\") {\n clearMetadataSyncQueueIfUnchanged({\n consumedSeqById,\n id,\n needingSync: state.metadataDocumentIdsNeedingSync,\n seqById: state.metadataSyncSignalSeqById,\n });\n }\n }\n // syncContainerMetadataState settles its detached candidate into this live\n // object while preserving any metadata edit that completed or queued during\n // the remote pass. Do not install its returned detached record again here:\n // doing so would overwrite that preserved live edit.\n host.updateSnapshot();\n\n if (synced.shouldRequestFollowupSync) {\n requestContainerContentsStoreSync(state);\n }\n}\n\n/**\n * A folder whose create has not settled can carry a listed identity that\n * adoption has not verified, or has refused. Folder moves, document moves and\n * metadata edits wait for it; child and document creates wait in their own\n * create paths.\n */\nasync function loadPendingCreatePredicate(\n state: ContainerContentsStoreSyncState,\n): Promise<(containerId: string) => boolean> {\n const pending = await state.persistence.listPendingCreateIntents(\n state.runtime.infra.execSql,\n );\n const pendingIds = new Set(pending.map((intent) => intent.containerId));\n return (containerId) => pendingIds.has(containerId);\n}\n\ninterface ContainerContentsStoreSyncIterationInput {\n host: RemoteContainerHydrationHost;\n reconcileRestoredAccess: (isCurrent: () => boolean) => Promise<void>;\n requestRemoteReconciliation: (parentContainerId: string | null) => void;\n state: ContainerContentsStoreSyncState;\n syncContainerMetadata?: SyncContainerMetadata | undefined;\n}\n\nexport async function runContainerContentsStoreSyncIteration(\n input: ContainerContentsStoreSyncIterationInput,\n) {\n const { host, reconcileRestoredAccess, requestRemoteReconciliation, state } =\n input;\n const runtime = state.runtime;\n const encapsulationKeyPair = runtime.crypto.encapsulationKeyPair;\n if (\n runtime.infra.dbStatus !== \"ready\" ||\n !state.snapshot.ready ||\n !runtime.state.online ||\n !runtime.auth.isAuthenticated ||\n !encapsulationKeyPair\n ) {\n return;\n }\n const domainScope = runtime.state.domainScope;\n const isCurrent = captureStructuralSyncGeneration(state, runtime);\n\n await reconcileRestoredAccess(isCurrent);\n if (!isCurrent()) {\n return;\n }\n const isOrganizationBlocked = createRemoteSyncBlocker(runtime);\n\n const createdContainerCount = await syncPendingContainerCreateIntents({\n host,\n isCurrent,\n isRemoteSyncBlocked: isOrganizationBlocked,\n requestRemoteReconciliation,\n state,\n });\n if (!isCurrent()) {\n return;\n }\n if (createdContainerCount > 0) {\n state.documentStoresNeedPriming = true;\n host.updateSnapshot();\n }\n const isCreatePending = await loadPendingCreatePredicate(state);\n if (!isCurrent()) {\n return;\n }\n\n const movedContainerCount = await syncPendingContainerMoveIntents({\n host,\n isCreatePending,\n isCurrent,\n isRemoteSyncBlocked: isOrganizationBlocked,\n requestRemoteReconciliation,\n state,\n });\n if (!isCurrent()) {\n return;\n }\n if (movedContainerCount > 0) {\n state.documentStoresNeedPriming = true;\n host.updateSnapshot();\n requestDomainDocumentSync(domainScope);\n requestContainerContentsStoreSync(state);\n }\n\n for (const containerState of Array.from(state.containersById.values())) {\n if (isCreatePending(containerState.container.id)) continue;\n await syncSingleContainerMetadata({\n containerState,\n encapsulationKeyPair,\n host,\n isCurrent,\n state,\n syncContainerMetadata: input.syncContainerMetadata,\n });\n if (!isCurrent()) {\n return;\n }\n }\n\n // Root adoption notifies session consumers synchronously. Let container\n // metadata converge first so a recovered system container is never exposed\n // under its placeholder name when the active root changes.\n await runContainerDocumentWork({\n isCurrent,\n onContextChanged: () => {\n if (isCurrent()) {\n requestContainerContentsStoreSync(state);\n }\n },\n onDocumentsMoved: () => {\n if (isCurrent()) {\n requestDomainDocumentSync(domainScope);\n requestContainerContentsStoreSync(state);\n }\n },\n primeDocuments: () => primeStoreDocuments(state, isCurrent),\n recoverStaleRoot: () => recoverStoreStaleRoot(state, isCurrent),\n shouldPrimeDocuments: () => state.documentStoresNeedPriming,\n // Document move intents live in the structural phase because they may\n // target containers created locally in the same session, such as Trash.\n // Root recovery rewrites stale endpoints and returns their intents to\n // pending, so replay follows recovery in this same pass.\n syncPendingDocumentMoves: () =>\n syncPendingStoreDocumentMoves({\n isCreatePending,\n isCurrent,\n isRemoteSyncBlocked: isOrganizationBlocked,\n state,\n }),\n });\n}\n"]}
|
|
1
|
+
{"version":3,"file":"syncLaneIteration.js","sourceRoot":"","sources":["syncLaneIteration.ts"],"names":[],"mappings":"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","sourcesContent":["import { syncPendingContainerCreateIntents } from \"../../../workflows/container-contents/container-state/create/createIntentSync\";\nimport { syncPendingContainerMoveIntents } from \"../../../workflows/container-contents/container-state/move/moveIntentSync\";\nimport {\n type DocumentMoveIntentSyncHost,\n syncPendingDocumentMoveIntents,\n} from \"../../../workflows/container-contents/documentMove/documentMoveIntentSync\";\nimport { syncContainerMetadataState } from \"../../../workflows/container-contents/metadata\";\nimport type {\n ContainerState,\n RemoteContainerHydrationHost,\n} from \"../../../workflows/container-contents/remoteHydration\";\nimport { createContainerContentsDocumentsRuntime } from \"../../../workflows/container-contents/runtime\";\nimport { openDocumentStore, requestDomainDocumentSync } from \"../../documents\";\nimport { relinkDocumentStoreWithCommitSideEffect } from \"../../documents/documentStore/internalRelink\";\nimport { DocumentRemoteWorkBusyError } from \"../../documents/documentStore/sync/remoteWork\";\nimport { removeMissingSyncedContainerState } from \"../containerState/missingSyncedContainerState\";\nimport { runContainerDocumentWork } from \"../documentWork\";\nimport {\n primeStoreDocuments,\n recoverStoreStaleRoot,\n} from \"../documentWork/documentRecovery\";\nimport { createAcceptedMoveRetry } from \"./acceptedMoveRetry\";\nimport {\n bumpMetadataSyncSeq,\n clearMetadataSyncQueueIfUnchanged,\n readMetadataSyncSeq,\n} from \"./metadataSyncSignal\";\nimport type {\n ContainerContentsStoreRuntime,\n ContainerContentsStoreSyncState,\n} from \"./syncAgentTypes\";\n\ntype ContainerContentsStorePrimeDocumentRuntime = ReturnType<\n typeof createContainerContentsDocumentsRuntime\n>;\ntype SyncContainerMetadata = typeof syncContainerMetadataState;\n\nfunction requestContainerContentsStoreSync(\n state: ContainerContentsStoreSyncState,\n) {\n state.syncLane?.requestSync();\n}\n\nfunction createRemoteSyncBlocker(runtime: ContainerContentsStoreRuntime) {\n return (organizationId: string) =>\n runtime.util.isRemoteSyncBlocked?.(organizationId) ?? false;\n}\n\nfunction captureStructuralSyncGeneration(\n state: ContainerContentsStoreSyncState,\n runtime: ContainerContentsStoreRuntime,\n) {\n const activeContainerId = runtime.state.containerId;\n const activeOrganizationId = runtime.auth.organizationId;\n const lifecycleGeneration = state.lifecycleGeneration;\n const structuralGeneration = state.structuralGeneration;\n const domainScope = runtime.state.domainScope;\n const execSql = runtime.infra.execSql;\n const resolveProjectionUserKey = state.resolveProjectionUserKey;\n const syncLane = state.syncLane;\n return () =>\n state.lifecycleGeneration === lifecycleGeneration &&\n state.structuralGeneration === structuralGeneration &&\n state.runtime.auth.organizationId === activeOrganizationId &&\n state.runtime.state.containerId === activeContainerId &&\n state.runtime.state.domainScope === domainScope &&\n state.runtime.infra.execSql === execSql &&\n state.resolveProjectionUserKey === resolveProjectionUserKey &&\n state.syncLane === syncLane &&\n !syncLane?.isDisposed?.();\n}\n\nfunction createContainerContentsStoreDocumentMoveHost(\n state: ContainerContentsStoreSyncState,\n isCurrent: () => boolean,\n): DocumentMoveIntentSyncHost<ContainerContentsStorePrimeDocumentRuntime> {\n const runtime = state.runtime;\n const domainScope = runtime.state.domainScope;\n return {\n documentWorkflowRuntime: (containerId) =>\n createContainerContentsDocumentsRuntime(runtime, containerId),\n openDocumentStore: ({ containerId, documentId, localId }) => {\n const store = openDocumentStore(\n domainScope,\n localId,\n createContainerContentsDocumentsRuntime(runtime, containerId),\n documentId,\n );\n return {\n assertCanRotateContentKey: () =>\n store.assertCanRotateContentKey().catch((error: unknown) => {\n if (error instanceof DocumentRemoteWorkBusyError) {\n void error.whenIdle.then(() => {\n if (isCurrent()) requestContainerContentsStoreSync(state);\n });\n }\n throw error;\n }),\n ensureInitialized: () => store.ensureInitialized(),\n relink: ({ commitSideEffect, ...input }) =>\n commitSideEffect\n ? relinkDocumentStoreWithCommitSideEffect(\n store,\n input,\n commitSideEffect,\n )\n : store.relink(input),\n requestSync: () => store.requestSync(),\n updateRuntime: (nextRuntime) => store.updateRuntime(nextRuntime),\n };\n },\n };\n}\n\nfunction syncPendingStoreDocumentMoves(input: {\n isCreatePending: (containerId: string) => boolean;\n isCurrent: () => boolean;\n isRemoteSyncBlocked: (organizationId: string) => boolean;\n state: ContainerContentsStoreSyncState;\n}) {\n return syncPendingDocumentMoveIntents({\n host: createContainerContentsStoreDocumentMoveHost(\n input.state,\n input.isCurrent,\n ),\n isCreatePending: input.isCreatePending,\n isCurrent: input.isCurrent,\n isRemoteSyncBlocked: input.isRemoteSyncBlocked,\n state: input.state,\n });\n}\n\nfunction syncPendingStoreContainerMoves(\n input: Omit<\n Parameters<typeof syncPendingContainerMoveIntents>[0],\n \"acceptedMoveRetry\" | \"state\"\n > & { state: ContainerContentsStoreSyncState },\n) {\n return syncPendingContainerMoveIntents({\n ...input,\n acceptedMoveRetry: createAcceptedMoveRetry(input.state),\n });\n}\n\nasync function syncSingleContainerMetadata(input: {\n host: RemoteContainerHydrationHost;\n isCurrent: () => boolean;\n state: ContainerContentsStoreSyncState;\n containerState: ContainerState;\n encapsulationKeyPair: NonNullable<\n ContainerContentsStoreRuntime[\"crypto\"][\"encapsulationKeyPair\"]\n >;\n syncContainerMetadata?: SyncContainerMetadata | undefined;\n}) {\n const { containerState, encapsulationKeyPair, host, isCurrent, state } =\n input;\n const metadataDocumentId = containerState.record.documentId;\n // Snapshot this signal before the GET so a mid-pass event survives clearing.\n const consumedSeqById = new Map<string, number>();\n if (typeof metadataDocumentId === \"string\") {\n consumedSeqById.set(\n metadataDocumentId,\n readMetadataSyncSeq(state.metadataSyncSignalSeqById, metadataDocumentId),\n );\n }\n const synced = await (\n input.syncContainerMetadata ?? syncContainerMetadataState\n )({\n forceReadSync:\n typeof metadataDocumentId === \"string\" &&\n state.metadataDocumentIdsNeedingSync.has(metadataDocumentId),\n locallyAcceptedUpdateIds: state.locallyAcceptedMetadataUpdateIds,\n isCurrent,\n metadataState: containerState,\n onDurableStateNeedsReload: () => {\n const currentDocumentId = state.containersById.get(\n containerState.container.id,\n )?.record.documentId;\n if (typeof currentDocumentId === \"string\") {\n state.metadataDocumentIdsNeedingSync.add(currentDocumentId);\n bumpMetadataSyncSeq(state.metadataSyncSignalSeqById, currentDocumentId);\n }\n requestContainerContentsStoreSync(state);\n },\n persistence: state.persistence,\n resolveProjectionUserKey: state.resolveProjectionUserKey,\n runtime: state.runtime,\n targetSecretKey: encapsulationKeyPair.secretKey,\n });\n if (!isCurrent() || !synced) {\n return;\n }\n if (\"missing\" in synced) {\n removeMissingSyncedContainerState(\n state,\n containerState,\n host.updateSnapshot,\n );\n return;\n }\n\n for (const id of [metadataDocumentId, synced.record.documentId]) {\n if (typeof id === \"string\") {\n clearMetadataSyncQueueIfUnchanged({\n consumedSeqById,\n id,\n needingSync: state.metadataDocumentIdsNeedingSync,\n seqById: state.metadataSyncSignalSeqById,\n });\n }\n }\n // syncContainerMetadataState settles its detached candidate into this live\n // object while preserving any metadata edit that completed or queued during\n // the remote pass. Do not install its returned detached record again here:\n // doing so would overwrite that preserved live edit.\n host.updateSnapshot();\n\n if (synced.shouldRequestFollowupSync) {\n requestContainerContentsStoreSync(state);\n }\n}\n\n/**\n * A folder whose create has not settled can carry a listed identity that\n * adoption has not verified, or has refused. Folder moves, document moves and\n * metadata edits wait for it; child and document creates wait in their own\n * create paths.\n */\nasync function loadPendingCreatePredicate(\n state: ContainerContentsStoreSyncState,\n): Promise<(containerId: string) => boolean> {\n const pending = await state.persistence.listPendingCreateIntents(\n state.runtime.infra.execSql,\n );\n const pendingIds = new Set(pending.map((intent) => intent.containerId));\n return (containerId) => pendingIds.has(containerId);\n}\n\ninterface ContainerContentsStoreSyncIterationInput {\n host: RemoteContainerHydrationHost;\n reconcileRestoredAccess: (isCurrent: () => boolean) => Promise<void>;\n requestRemoteReconciliation: (parentContainerId: string | null) => void;\n state: ContainerContentsStoreSyncState;\n syncContainerMetadata?: SyncContainerMetadata | undefined;\n}\n\nexport async function runContainerContentsStoreSyncIteration(\n input: ContainerContentsStoreSyncIterationInput,\n) {\n const { host, reconcileRestoredAccess, requestRemoteReconciliation, state } =\n input;\n const runtime = state.runtime;\n const encapsulationKeyPair = runtime.crypto.encapsulationKeyPair;\n if (\n runtime.infra.dbStatus !== \"ready\" ||\n !state.snapshot.ready ||\n !runtime.state.online ||\n !runtime.auth.isAuthenticated ||\n !encapsulationKeyPair\n ) {\n return;\n }\n const domainScope = runtime.state.domainScope;\n const isCurrent = captureStructuralSyncGeneration(state, runtime);\n\n await reconcileRestoredAccess(isCurrent);\n if (!isCurrent()) {\n return;\n }\n const isOrganizationBlocked = createRemoteSyncBlocker(runtime);\n\n const createdContainerCount = await syncPendingContainerCreateIntents({\n host,\n isCurrent,\n isRemoteSyncBlocked: isOrganizationBlocked,\n requestRemoteReconciliation,\n state,\n });\n if (!isCurrent()) {\n return;\n }\n if (createdContainerCount > 0) {\n state.documentStoresNeedPriming = true;\n host.updateSnapshot();\n }\n const isCreatePending = await loadPendingCreatePredicate(state);\n if (!isCurrent()) {\n return;\n }\n\n const movedContainerCount = await syncPendingStoreContainerMoves({\n host,\n isCreatePending,\n isCurrent,\n isRemoteSyncBlocked: isOrganizationBlocked,\n requestRemoteReconciliation,\n state,\n });\n if (!isCurrent()) {\n return;\n }\n if (movedContainerCount > 0) {\n state.documentStoresNeedPriming = true;\n host.updateSnapshot();\n requestDomainDocumentSync(domainScope);\n requestContainerContentsStoreSync(state);\n }\n\n for (const containerState of Array.from(state.containersById.values())) {\n if (isCreatePending(containerState.container.id)) continue;\n await syncSingleContainerMetadata({\n containerState,\n encapsulationKeyPair,\n host,\n isCurrent,\n state,\n syncContainerMetadata: input.syncContainerMetadata,\n });\n if (!isCurrent()) {\n return;\n }\n }\n\n // Root adoption notifies session consumers synchronously. Let container\n // metadata converge first so a recovered system container is never exposed\n // under its placeholder name when the active root changes.\n await runContainerDocumentWork({\n isCurrent,\n onContextChanged: () => {\n if (isCurrent()) {\n requestContainerContentsStoreSync(state);\n }\n },\n onDocumentsMoved: () => {\n if (isCurrent()) {\n requestDomainDocumentSync(domainScope);\n requestContainerContentsStoreSync(state);\n }\n },\n primeDocuments: () => primeStoreDocuments(state, isCurrent),\n recoverStaleRoot: () => recoverStoreStaleRoot(state, isCurrent),\n shouldPrimeDocuments: () => state.documentStoresNeedPriming,\n // Document move intents live in the structural phase because they may\n // target containers created locally in the same session, such as Trash.\n // Root recovery rewrites stale endpoints and returns their intents to\n // pending, so replay follows recovery in this same pass.\n syncPendingDocumentMoves: () =>\n syncPendingStoreDocumentMoves({\n isCreatePending,\n isCurrent,\n isRemoteSyncBlocked: isOrganizationBlocked,\n state,\n }),\n });\n}\n"]}
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The server holds a move this pass did not settle. Keep the intent queued,
|
|
3
|
+
* report it with a fixed message (the error's call site names the path), and
|
|
4
|
+
* ask for a later pass: its replay finds the move applied and settles it
|
|
5
|
+
* without posting again, so no reload or unrelated write is needed.
|
|
6
|
+
*/
|
|
7
|
+
export function leaveAcceptedMoveQueued(syncInput, message, error) {
|
|
8
|
+
try {
|
|
9
|
+
const report = syncInput.state.runtime.util.logError;
|
|
10
|
+
if (report)
|
|
11
|
+
void Promise.resolve(report(message, error)).catch(() => { });
|
|
12
|
+
}
|
|
13
|
+
catch {
|
|
14
|
+
// A host logger must not change the replay result.
|
|
15
|
+
}
|
|
16
|
+
syncInput.acceptedMoveRetry.arm(syncInput.intent.id);
|
|
17
|
+
}
|
|
18
|
+
export const STALE_ACCEPTED_MOVE = "Container contents: a stale sync pass dropped an accepted container move";
|
|
19
|
+
/** persistAcceptedMoveIntent already asked to reconcile a stale result. */
|
|
20
|
+
export function unpersistedAcceptedMove(syncInput) {
|
|
21
|
+
if (!syncInput.isCurrent()) {
|
|
22
|
+
leaveAcceptedMoveQueued(syncInput, STALE_ACCEPTED_MOVE, new Error("Stale pass dropped a persisting container move"));
|
|
23
|
+
return "abandoned";
|
|
24
|
+
}
|
|
25
|
+
leaveAcceptedMoveQueued(syncInput, "Container contents: an accepted container move did not persist", new Error("Accepted container move did not persist"));
|
|
26
|
+
return "failed";
|
|
27
|
+
}
|
|
28
|
+
/** The server accepted, but this current pass got no result to settle. */
|
|
29
|
+
export function unacknowledgedAcceptedMove(syncInput) {
|
|
30
|
+
leaveAcceptedMoveQueued(syncInput, "Container contents: an accepted container move was not acknowledged", new Error("Accepted container move was not acknowledged"));
|
|
31
|
+
return "failed";
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Settles from the verified head the server already holds. A head carries no
|
|
35
|
+
* server timestamps, so the container keeps the ones this device holds.
|
|
36
|
+
*/
|
|
37
|
+
export function appliedRemoteContainer(containerState, applied) {
|
|
38
|
+
const { container } = containerState;
|
|
39
|
+
const createdAt = container.serverCreatedAt ?? container.createdAt;
|
|
40
|
+
const updatedAt = container.serverUpdatedAt ?? container.updatedAt;
|
|
41
|
+
if (!createdAt || !updatedAt) {
|
|
42
|
+
throw new Error("A synced container must carry its server timestamps");
|
|
43
|
+
}
|
|
44
|
+
const { state } = applied;
|
|
45
|
+
return {
|
|
46
|
+
createdAt,
|
|
47
|
+
effectiveAccessLevel: "admin",
|
|
48
|
+
id: state.containerId,
|
|
49
|
+
metadataAccessEpoch: state.epoch,
|
|
50
|
+
metadataAccessStateHash: applied.manifestHash,
|
|
51
|
+
metadataDocumentId: state.metadataDocumentId,
|
|
52
|
+
// Verifying the head already warmed the policies it references.
|
|
53
|
+
metadataReferencedPrincipals: [],
|
|
54
|
+
organizationId: state.organizationId,
|
|
55
|
+
parentId: state.parentContainerId,
|
|
56
|
+
updatedAt,
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
//# sourceMappingURL=acceptedMoveSettlement.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"acceptedMoveSettlement.js","sourceRoot":"","sources":["acceptedMoveSettlement.ts"],"names":[],"mappings":"AASA;;;;;GAKG;AACH,MAAM,UAAU,uBAAuB,CACrC,SAA4B,EAC5B,OAAe,EACf,KAAY;IAEZ,IAAI,CAAC;QACH,MAAM,MAAM,GAAG,SAAS,CAAC,KAAK,CAAC,OAAO,CAAC,IAAI,CAAC,QAAQ,CAAC;QACrD,IAAI,MAAM;YAAE,KAAK,OAAO,CAAC,OAAO,CAAC,MAAM,CAAC,OAAO,EAAE,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,GAAG,EAAE,GAAE,CAAC,CAAC,CAAC;IAC3E,CAAC;IAAC,MAAM,CAAC;QACP,mDAAmD;IACrD,CAAC;IACD,SAAS,CAAC,iBAAiB,CAAC,GAAG,CAAC,SAAS,CAAC,MAAM,CAAC,EAAE,CAAC,CAAC;AACvD,CAAC;AAED,MAAM,CAAC,MAAM,mBAAmB,GAC9B,0EAA0E,CAAC;AAE7E,2EAA2E;AAC3E,MAAM,UAAU,uBAAuB,CACrC,SAA4B;IAE5B,IAAI,CAAC,SAAS,CAAC,SAAS,EAAE,EAAE,CAAC;QAC3B,uBAAuB,CACrB,SAAS,EACT,mBAAmB,EACnB,IAAI,KAAK,CAAC,gDAAgD,CAAC,CAC5D,CAAC;QACF,OAAO,WAAW,CAAC;IACrB,CAAC;IACD,uBAAuB,CACrB,SAAS,EACT,gEAAgE,EAChE,IAAI,KAAK,CAAC,yCAAyC,CAAC,CACrD,CAAC;IACF,OAAO,QAAQ,CAAC;AAClB,CAAC;AAED,0EAA0E;AAC1E,MAAM,UAAU,0BAA0B,CACxC,SAA4B;IAE5B,uBAAuB,CACrB,SAAS,EACT,qEAAqE,EACrE,IAAI,KAAK,CAAC,8CAA8C,CAAC,CAC1D,CAAC;IACF,OAAO,QAAQ,CAAC;AAClB,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,sBAAsB,CACpC,cAA8B,EAC9B,OAA6B;IAE7B,MAAM,EAAE,SAAS,EAAE,GAAG,cAAc,CAAC;IACrC,MAAM,SAAS,GAAG,SAAS,CAAC,eAAe,IAAI,SAAS,CAAC,SAAS,CAAC;IACnE,MAAM,SAAS,GAAG,SAAS,CAAC,eAAe,IAAI,SAAS,CAAC,SAAS,CAAC;IACnE,IAAI,CAAC,SAAS,IAAI,CAAC,SAAS,EAAE,CAAC;QAC7B,MAAM,IAAI,KAAK,CAAC,qDAAqD,CAAC,CAAC;IACzE,CAAC;IACD,MAAM,EAAE,KAAK,EAAE,GAAG,OAAO,CAAC;IAC1B,OAAO;QACL,SAAS;QACT,oBAAoB,EAAE,OAAO;QAC7B,EAAE,EAAE,KAAK,CAAC,WAAW;QACrB,mBAAmB,EAAE,KAAK,CAAC,KAAK;QAChC,uBAAuB,EAAE,OAAO,CAAC,YAAY;QAC7C,kBAAkB,EAAE,KAAK,CAAC,kBAAkB;QAC5C,gEAAgE;QAChE,4BAA4B,EAAE,EAAE;QAChC,cAAc,EAAE,KAAK,CAAC,cAAc;QACpC,QAAQ,EAAE,KAAK,CAAC,iBAAiB;QACjC,SAAS;KACV,CAAC;AACJ,CAAC","sourcesContent":["import type { AppliedContainerMove } from \"../../../containers\";\nimport type { ContainerState, RemoteContainer } from \"../../remoteHydration\";\nimport type { ContainerMoveIntentSyncInput } from \"../types\";\n\ntype AcceptedMoveInput = Pick<\n ContainerMoveIntentSyncInput,\n \"acceptedMoveRetry\" | \"intent\" | \"isCurrent\" | \"state\"\n>;\n\n/**\n * The server holds a move this pass did not settle. Keep the intent queued,\n * report it with a fixed message (the error's call site names the path), and\n * ask for a later pass: its replay finds the move applied and settles it\n * without posting again, so no reload or unrelated write is needed.\n */\nexport function leaveAcceptedMoveQueued(\n syncInput: AcceptedMoveInput,\n message: string,\n error: Error,\n): void {\n try {\n const report = syncInput.state.runtime.util.logError;\n if (report) void Promise.resolve(report(message, error)).catch(() => {});\n } catch {\n // A host logger must not change the replay result.\n }\n syncInput.acceptedMoveRetry.arm(syncInput.intent.id);\n}\n\nexport const STALE_ACCEPTED_MOVE =\n \"Container contents: a stale sync pass dropped an accepted container move\";\n\n/** persistAcceptedMoveIntent already asked to reconcile a stale result. */\nexport function unpersistedAcceptedMove(\n syncInput: AcceptedMoveInput,\n): \"abandoned\" | \"failed\" {\n if (!syncInput.isCurrent()) {\n leaveAcceptedMoveQueued(\n syncInput,\n STALE_ACCEPTED_MOVE,\n new Error(\"Stale pass dropped a persisting container move\"),\n );\n return \"abandoned\";\n }\n leaveAcceptedMoveQueued(\n syncInput,\n \"Container contents: an accepted container move did not persist\",\n new Error(\"Accepted container move did not persist\"),\n );\n return \"failed\";\n}\n\n/** The server accepted, but this current pass got no result to settle. */\nexport function unacknowledgedAcceptedMove(\n syncInput: AcceptedMoveInput,\n): \"failed\" {\n leaveAcceptedMoveQueued(\n syncInput,\n \"Container contents: an accepted container move was not acknowledged\",\n new Error(\"Accepted container move was not acknowledged\"),\n );\n return \"failed\";\n}\n\n/**\n * Settles from the verified head the server already holds. A head carries no\n * server timestamps, so the container keeps the ones this device holds.\n */\nexport function appliedRemoteContainer(\n containerState: ContainerState,\n applied: AppliedContainerMove,\n): RemoteContainer {\n const { container } = containerState;\n const createdAt = container.serverCreatedAt ?? container.createdAt;\n const updatedAt = container.serverUpdatedAt ?? container.updatedAt;\n if (!createdAt || !updatedAt) {\n throw new Error(\"A synced container must carry its server timestamps\");\n }\n const { state } = applied;\n return {\n createdAt,\n effectiveAccessLevel: \"admin\",\n id: state.containerId,\n metadataAccessEpoch: state.epoch,\n metadataAccessStateHash: applied.manifestHash,\n metadataDocumentId: state.metadataDocumentId,\n // Verifying the head already warmed the policies it references.\n metadataReferencedPrincipals: [],\n organizationId: state.organizationId,\n parentId: state.parentContainerId,\n updatedAt,\n };\n}\n"]}
|
package/workflows/container-contents/container-state/{moveIntentSync.js → move/moveIntentSync.js}
RENAMED
|
@@ -1,10 +1,11 @@
|
|
|
1
|
-
import { ContainerPathTooDeepError } from "
|
|
2
|
-
import { reportAndRethrowKeyingVerificationError } from "
|
|
3
|
-
import { errorMessage } from "
|
|
4
|
-
import { createRuntimePrincipalPolicyWarmer } from "
|
|
5
|
-
import { createDetachedContainerMetadataState, installDetachedContainerMetadataState, } from "
|
|
6
|
-
import { hasRemoteContainerMetadataState } from "
|
|
7
|
-
import { moveRemoteContainer } from "
|
|
1
|
+
import { ContainerPathTooDeepError } from "../../../../data/containers/shared/containerPathLimits.js";
|
|
2
|
+
import { reportAndRethrowKeyingVerificationError } from "../../../../data/keyingProjectionVerification/error.js";
|
|
3
|
+
import { errorMessage } from "../../../../data/shared/errorMessage.js";
|
|
4
|
+
import { createRuntimePrincipalPolicyWarmer } from "../../../principals/runtimePolicyWarmer.js";
|
|
5
|
+
import { createDetachedContainerMetadataState, installDetachedContainerMetadataState, } from "../../metadata/metadataStateIsolation.js";
|
|
6
|
+
import { hasRemoteContainerMetadataState } from "../../remoteHydration/reconciliation.js";
|
|
7
|
+
import { moveRemoteContainer } from "../remote.js";
|
|
8
|
+
import { appliedRemoteContainer, leaveAcceptedMoveQueued, STALE_ACCEPTED_MOVE, unacknowledgedAcceptedMove, unpersistedAcceptedMove, } from "./acceptedMoveSettlement.js";
|
|
8
9
|
function currentMoveResult(isCurrent, result) {
|
|
9
10
|
return isCurrent() ? result : "abandoned";
|
|
10
11
|
}
|
|
@@ -163,30 +164,69 @@ async function abandonTooDeepMove(syncInput) {
|
|
|
163
164
|
state.runtime.util.log(`Container contents: abandoned queued move of ${intent.containerId}; its path would exceed the maximum depth`);
|
|
164
165
|
return "failed";
|
|
165
166
|
}
|
|
167
|
+
/** A replay that threw: refused for depth, tampered, or left unsettled. */
|
|
168
|
+
async function failedMoveReplay(input) {
|
|
169
|
+
const { abandonAppliedMove, error, parentState, remoteMoveApplied, syncInput, } = input;
|
|
170
|
+
const { intent, state } = syncInput;
|
|
171
|
+
if (!syncInput.isCurrent()) {
|
|
172
|
+
return abandonAppliedMove();
|
|
173
|
+
}
|
|
174
|
+
if (error instanceof ContainerPathTooDeepError)
|
|
175
|
+
return abandonTooDeepMove(syncInput);
|
|
176
|
+
// Even a refused acknowledgement leaves the move on the server; its replay
|
|
177
|
+
// verifies the head again before settling.
|
|
178
|
+
if (remoteMoveApplied && error instanceof Error)
|
|
179
|
+
leaveAcceptedMoveQueued(syncInput, "Container contents: an accepted container move failed to settle", error);
|
|
180
|
+
await reportAndRethrowKeyingVerificationError(error, state.runtime.util.reportSecurityIncident, {
|
|
181
|
+
objectId: intent.containerId,
|
|
182
|
+
objectKind: "container",
|
|
183
|
+
operation: "container.move.replay",
|
|
184
|
+
organizationId: parentState.container.organizationId,
|
|
185
|
+
});
|
|
186
|
+
if (!syncInput.isCurrent()) {
|
|
187
|
+
return abandonAppliedMove();
|
|
188
|
+
}
|
|
189
|
+
await recordPendingMoveIntentError({
|
|
190
|
+
containerId: intent.containerId,
|
|
191
|
+
expectedIntentId: intent.id,
|
|
192
|
+
expectedUpdatedAt: intent.updatedAt,
|
|
193
|
+
isCurrent: syncInput.isCurrent,
|
|
194
|
+
message: `Failed to sync container move: ${errorMessage(error)}`,
|
|
195
|
+
state,
|
|
196
|
+
});
|
|
197
|
+
return currentMoveResult(syncInput.isCurrent, "failed");
|
|
198
|
+
}
|
|
166
199
|
async function movePendingRemoteContainer(input) {
|
|
167
|
-
const { parentState, syncInput } = input;
|
|
200
|
+
const { containerState, parentState, syncInput } = input;
|
|
168
201
|
const { host, intent, state } = syncInput;
|
|
169
202
|
let remoteMoveApplied = false;
|
|
170
203
|
let remoteParentId = intent.parentContainerId;
|
|
171
204
|
const abandonAppliedMove = () => {
|
|
172
205
|
if (remoteMoveApplied) {
|
|
206
|
+
leaveAcceptedMoveQueued(syncInput, STALE_ACCEPTED_MOVE, new Error("Stale pass dropped an accepted container move"));
|
|
173
207
|
syncInput.requestRemoteReconciliation(remoteParentId);
|
|
174
208
|
}
|
|
175
209
|
return "abandoned";
|
|
176
210
|
};
|
|
177
211
|
try {
|
|
178
|
-
const
|
|
212
|
+
const replayed = await moveRemoteContainer({
|
|
179
213
|
containerId: intent.containerId,
|
|
214
|
+
onAccepted: () => {
|
|
215
|
+
remoteMoveApplied = true;
|
|
216
|
+
},
|
|
180
217
|
parentContainerId: intent.parentContainerId,
|
|
181
218
|
resolveProjectionUserKey: state.resolveProjectionUserKey,
|
|
182
219
|
runtime: state.runtime,
|
|
183
220
|
stillCurrent: syncInput.isCurrent,
|
|
184
221
|
});
|
|
185
|
-
if (!
|
|
222
|
+
if (!replayed) {
|
|
186
223
|
if (!syncInput.isCurrent()) {
|
|
187
|
-
|
|
188
|
-
|
|
224
|
+
if (!remoteMoveApplied)
|
|
225
|
+
syncInput.requestRemoteReconciliation(intent.parentContainerId);
|
|
226
|
+
return abandonAppliedMove();
|
|
189
227
|
}
|
|
228
|
+
if (remoteMoveApplied)
|
|
229
|
+
return unacknowledgedAcceptedMove(syncInput);
|
|
190
230
|
await recordPendingMoveIntentError({
|
|
191
231
|
containerId: intent.containerId,
|
|
192
232
|
expectedIntentId: intent.id,
|
|
@@ -198,6 +238,9 @@ async function movePendingRemoteContainer(input) {
|
|
|
198
238
|
return currentMoveResult(syncInput.isCurrent, "failed");
|
|
199
239
|
}
|
|
200
240
|
remoteMoveApplied = true;
|
|
241
|
+
const moved = "applied" in replayed
|
|
242
|
+
? appliedRemoteContainer(containerState, replayed.applied)
|
|
243
|
+
: replayed.moved;
|
|
201
244
|
remoteParentId = moved.parentId;
|
|
202
245
|
if (!syncInput.isCurrent())
|
|
203
246
|
return abandonAppliedMove();
|
|
@@ -209,36 +252,21 @@ async function movePendingRemoteContainer(input) {
|
|
|
209
252
|
requestRemoteReconciliation: syncInput.requestRemoteReconciliation,
|
|
210
253
|
state,
|
|
211
254
|
});
|
|
212
|
-
if (!persisted)
|
|
213
|
-
return
|
|
214
|
-
|
|
215
|
-
|
|
255
|
+
if (!persisted)
|
|
256
|
+
return unpersistedAcceptedMove(syncInput);
|
|
257
|
+
state.runtime.util.log("applied" in replayed
|
|
258
|
+
? `Container contents: settled queued container move ${intent.containerId} the server already held`
|
|
259
|
+
: `Container contents: synced queued container move ${intent.containerId}`);
|
|
216
260
|
return "moved";
|
|
217
261
|
}
|
|
218
262
|
catch (error) {
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
objectId: intent.containerId,
|
|
226
|
-
objectKind: "container",
|
|
227
|
-
operation: "container.move.replay",
|
|
228
|
-
organizationId: parentState.container.organizationId,
|
|
229
|
-
});
|
|
230
|
-
if (!syncInput.isCurrent()) {
|
|
231
|
-
return abandonAppliedMove();
|
|
232
|
-
}
|
|
233
|
-
await recordPendingMoveIntentError({
|
|
234
|
-
containerId: intent.containerId,
|
|
235
|
-
expectedIntentId: intent.id,
|
|
236
|
-
expectedUpdatedAt: intent.updatedAt,
|
|
237
|
-
isCurrent: syncInput.isCurrent,
|
|
238
|
-
message: `Failed to sync container move: ${errorMessage(error)}`,
|
|
239
|
-
state,
|
|
263
|
+
return failedMoveReplay({
|
|
264
|
+
abandonAppliedMove,
|
|
265
|
+
error,
|
|
266
|
+
parentState,
|
|
267
|
+
remoteMoveApplied,
|
|
268
|
+
syncInput,
|
|
240
269
|
});
|
|
241
|
-
return currentMoveResult(syncInput.isCurrent, "failed");
|
|
242
270
|
}
|
|
243
271
|
}
|
|
244
272
|
async function trySyncPendingContainerMoveIntent(input) {
|
|
@@ -306,7 +334,11 @@ async function trySyncPendingContainerMoveIntent(input) {
|
|
|
306
334
|
});
|
|
307
335
|
return currentMoveResult(input.isCurrent, "blocked");
|
|
308
336
|
}
|
|
309
|
-
return movePendingRemoteContainer({
|
|
337
|
+
return movePendingRemoteContainer({
|
|
338
|
+
containerState,
|
|
339
|
+
parentState,
|
|
340
|
+
syncInput: input,
|
|
341
|
+
});
|
|
310
342
|
}
|
|
311
343
|
export async function syncPendingContainerMoveIntents(input) {
|
|
312
344
|
if (!input.isCurrent()) {
|
|
@@ -331,6 +363,7 @@ export async function syncPendingContainerMoveIntents(input) {
|
|
|
331
363
|
isRemoteSyncBlocked: input.isRemoteSyncBlocked,
|
|
332
364
|
intent,
|
|
333
365
|
requestRemoteReconciliation: input.requestRemoteReconciliation,
|
|
366
|
+
acceptedMoveRetry: input.acceptedMoveRetry,
|
|
334
367
|
state,
|
|
335
368
|
});
|
|
336
369
|
if (result === "abandoned") {
|
|
@@ -339,6 +372,11 @@ export async function syncPendingContainerMoveIntents(input) {
|
|
|
339
372
|
if (result === "moved") {
|
|
340
373
|
movedCount += 1;
|
|
341
374
|
}
|
|
375
|
+
else {
|
|
376
|
+
// A replay that failed before reaching the server keeps an earlier
|
|
377
|
+
// acceptance's retries going.
|
|
378
|
+
input.acceptedMoveRetry.resume(intent.id);
|
|
379
|
+
}
|
|
342
380
|
}
|
|
343
381
|
return movedCount;
|
|
344
382
|
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"moveIntentSync.js","sourceRoot":"","sources":["moveIntentSync.ts"],"names":[],"mappings":"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","sourcesContent":["import { ContainerPathTooDeepError } from \"../../../../data/containers/shared/containerPathLimits\";\nimport { reportAndRethrowKeyingVerificationError } from \"../../../../data/keyingProjectionVerification/error\";\nimport { errorMessage } from \"../../../../data/shared/errorMessage\";\nimport { createRuntimePrincipalPolicyWarmer } from \"../../../principals/runtimePolicyWarmer\";\nimport {\n createDetachedContainerMetadataState,\n installDetachedContainerMetadataState,\n} from \"../../metadata/metadataStateIsolation\";\nimport type { ContainerState, RemoteContainer } from \"../../remoteHydration\";\nimport { hasRemoteContainerMetadataState } from \"../../remoteHydration/reconciliation\";\nimport { moveRemoteContainer } from \"../remote\";\nimport type {\n AcceptedMoveRetry,\n ContainerMoveIntentSyncHost,\n ContainerMoveIntentSyncInput,\n ContainerMoveIntentSyncState,\n} from \"../types\";\nimport {\n appliedRemoteContainer,\n leaveAcceptedMoveQueued,\n STALE_ACCEPTED_MOVE,\n unacknowledgedAcceptedMove,\n unpersistedAcceptedMove,\n} from \"./acceptedMoveSettlement\";\n\ntype MoveIntentSyncResult = \"abandoned\" | \"blocked\" | \"failed\" | \"moved\";\n\nfunction currentMoveResult<\n Result extends Exclude<MoveIntentSyncResult, \"abandoned\">,\n>(isCurrent: () => boolean, result: Result): \"abandoned\" | Result {\n return isCurrent() ? result : \"abandoned\";\n}\n\nasync function recordPendingMoveIntentError(input: {\n blocked?: boolean | undefined;\n containerId: string;\n expectedIntentId: string;\n expectedUpdatedAt: string;\n isCurrent: () => boolean;\n message: string;\n state: ContainerMoveIntentSyncState;\n}): Promise<boolean> {\n if (!input.isCurrent()) {\n return false;\n }\n const execSql = input.state.runtime.infra.execSql;\n await input.state.persistence.recordMoveIntentError(execSql, {\n blocked: input.blocked,\n containerId: input.containerId,\n expectedIntentId: input.expectedIntentId,\n expectedUpdatedAt: input.expectedUpdatedAt,\n message: input.message,\n stillCurrent: input.isCurrent,\n });\n return input.isCurrent();\n}\n\nasync function resolveMoveIntentLocalUpdatedAt(input: {\n containerId: string;\n isCurrent: () => boolean;\n remoteUpdatedAt: string;\n state: ContainerMoveIntentSyncState;\n}): Promise<string | null> {\n if (!input.isCurrent()) {\n return null;\n }\n const previousLocalUpdatedAt =\n input.state.containersById.get(input.containerId)?.container\n .localUpdatedAt ?? null;\n if (\n !previousLocalUpdatedAt ||\n previousLocalUpdatedAt.localeCompare(input.remoteUpdatedAt) <= 0\n ) {\n return input.remoteUpdatedAt;\n }\n\n const execSql = input.state.runtime.infra.execSql;\n const containersWithPendingMetadataUpdates =\n await input.state.persistence.listContainerIdsWithPendingUpdates(execSql, [\n input.containerId,\n ]);\n if (!input.isCurrent()) {\n return null;\n }\n\n return containersWithPendingMetadataUpdates.includes(input.containerId)\n ? previousLocalUpdatedAt\n : input.remoteUpdatedAt;\n}\n\nexport async function persistAcceptedMoveIntent(input: {\n host: ContainerMoveIntentSyncHost;\n isCurrent: () => boolean;\n intent: ContainerMoveIntentSyncInput[\"intent\"];\n moved: RemoteContainer;\n requestRemoteReconciliation: (parentContainerId: string | null) => void;\n state: ContainerMoveIntentSyncState;\n}): Promise<boolean> {\n const { host, intent, moved, state } = input;\n const abandon = () => {\n input.requestRemoteReconciliation(moved.parentId);\n return false;\n };\n if (!input.isCurrent()) return abandon();\n const containerState = state.containersById.get(intent.containerId);\n if (!containerState) {\n return false;\n }\n\n await createRuntimePrincipalPolicyWarmer(state.runtime)({\n organizationId: moved.organizationId,\n references: moved.metadataReferencedPrincipals,\n stillCurrent: input.isCurrent,\n });\n if (!input.isCurrent()) {\n return abandon();\n }\n const localUpdatedAt = await resolveMoveIntentLocalUpdatedAt({\n containerId: intent.containerId,\n isCurrent: input.isCurrent,\n remoteUpdatedAt: moved.updatedAt,\n state,\n });\n if (!input.isCurrent() || localUpdatedAt === null) {\n return abandon();\n }\n const persistenceCandidate =\n await createDetachedContainerMetadataState(containerState);\n if (!input.isCurrent()) {\n return abandon();\n }\n persistenceCandidate.container = {\n ...persistenceCandidate.container,\n createdAt: moved.createdAt,\n serverCreatedAt: moved.createdAt,\n serverUpdatedAt: moved.updatedAt,\n updatedAt: moved.updatedAt,\n };\n const persistenceResult = await host.persistContainerState(\n persistenceCandidate,\n {\n accessEpoch: moved.metadataAccessEpoch,\n accessStateHash: moved.metadataAccessStateHash,\n documentId: moved.metadataDocumentId,\n metadataDocumentId: moved.metadataDocumentId,\n organizationId: moved.organizationId,\n parentId: moved.parentId,\n systemSlot: moved.systemSlot ?? null,\n },\n false,\n {\n localUpdatedAt,\n serverTimestamps: {\n createdAt: moved.createdAt,\n updatedAt: moved.updatedAt,\n },\n },\n {\n expectedStateWhenMissing: containerState,\n isCurrent: input.isCurrent,\n moveIntentSettlement: {\n containerId: intent.containerId,\n expectedIntentId: intent.id,\n expectedUpdatedAt: intent.updatedAt,\n },\n },\n );\n if (!input.isCurrent() || persistenceResult.status !== \"persisted\") {\n return abandon();\n }\n if (persistenceResult.moveIntentSettled !== true) {\n throw new Error(\n \"Container move persistence must settle the intent atomically\",\n );\n }\n if (!input.isCurrent()) return abandon();\n const { record: nextRecord } = persistenceResult;\n installDetachedContainerMetadataState(containerState, persistenceCandidate, {\n candidateRecord: nextRecord,\n preserveConcurrentMetadataEdit: true,\n });\n containerState.container = {\n ...containerState.container,\n metadataDocumentId: moved.metadataDocumentId,\n organizationId: moved.organizationId,\n parentId: moved.parentId,\n };\n return true;\n}\n\n/**\n * A move refused for path length never succeeds; the queue-time check saw only\n * the descendants this device holds. Drop the queued move and restore the\n * previous parent together: the server row never changed, so no listing would\n * ever put the folder back.\n */\nasync function abandonTooDeepMove(\n syncInput: ContainerMoveIntentSyncInput,\n): Promise<MoveIntentSyncResult> {\n const { host, intent, state } = syncInput;\n const previousParentId = intent.previousParentContainerId;\n if (previousParentId === null) {\n throw new Error(\"A queued container move must name its previous parent\");\n }\n const abandoned = await state.persistence.abandonMoveIntentRevision(\n state.runtime.infra.execSql,\n {\n containerId: intent.containerId,\n expectedIntentId: intent.id,\n expectedUpdatedAt: intent.updatedAt,\n previousParentContainerId: previousParentId,\n stillCurrent: syncInput.isCurrent,\n },\n );\n if (!syncInput.isCurrent()) return \"abandoned\";\n // A newer local move superseded this revision; it replays on its own.\n if (!abandoned) return \"failed\";\n const containerState = state.containersById.get(intent.containerId);\n if (containerState) {\n containerState.container = {\n ...containerState.container,\n parentId: previousParentId,\n };\n }\n host.updateSnapshot();\n state.runtime.util.log(\n `Container contents: abandoned queued move of ${intent.containerId}; its path would exceed the maximum depth`,\n );\n return \"failed\";\n}\n\n/** A replay that threw: refused for depth, tampered, or left unsettled. */\nasync function failedMoveReplay(input: {\n abandonAppliedMove: () => \"abandoned\";\n error: unknown;\n parentState: ContainerState;\n remoteMoveApplied: boolean;\n syncInput: ContainerMoveIntentSyncInput;\n}): Promise<MoveIntentSyncResult> {\n const {\n abandonAppliedMove,\n error,\n parentState,\n remoteMoveApplied,\n syncInput,\n } = input;\n const { intent, state } = syncInput;\n if (!syncInput.isCurrent()) {\n return abandonAppliedMove();\n }\n if (error instanceof ContainerPathTooDeepError)\n return abandonTooDeepMove(syncInput);\n // Even a refused acknowledgement leaves the move on the server; its replay\n // verifies the head again before settling.\n if (remoteMoveApplied && error instanceof Error)\n leaveAcceptedMoveQueued(\n syncInput,\n \"Container contents: an accepted container move failed to settle\",\n error,\n );\n await reportAndRethrowKeyingVerificationError(\n error,\n state.runtime.util.reportSecurityIncident,\n {\n objectId: intent.containerId,\n objectKind: \"container\",\n operation: \"container.move.replay\",\n organizationId: parentState.container.organizationId,\n },\n );\n if (!syncInput.isCurrent()) {\n return abandonAppliedMove();\n }\n await recordPendingMoveIntentError({\n containerId: intent.containerId,\n expectedIntentId: intent.id,\n expectedUpdatedAt: intent.updatedAt,\n isCurrent: syncInput.isCurrent,\n message: `Failed to sync container move: ${errorMessage(error)}`,\n state,\n });\n return currentMoveResult(syncInput.isCurrent, \"failed\");\n}\n\nasync function movePendingRemoteContainer(input: {\n containerState: ContainerState;\n parentState: ContainerState;\n syncInput: ContainerMoveIntentSyncInput;\n}): Promise<MoveIntentSyncResult> {\n const { containerState, parentState, syncInput } = input;\n const { host, intent, state } = syncInput;\n let remoteMoveApplied = false;\n let remoteParentId: string | null = intent.parentContainerId;\n const abandonAppliedMove = () => {\n if (remoteMoveApplied) {\n leaveAcceptedMoveQueued(\n syncInput,\n STALE_ACCEPTED_MOVE,\n new Error(\"Stale pass dropped an accepted container move\"),\n );\n syncInput.requestRemoteReconciliation(remoteParentId);\n }\n return \"abandoned\" as const;\n };\n try {\n const replayed = await moveRemoteContainer({\n containerId: intent.containerId,\n onAccepted: () => {\n remoteMoveApplied = true;\n },\n parentContainerId: intent.parentContainerId,\n resolveProjectionUserKey: state.resolveProjectionUserKey,\n runtime: state.runtime,\n stillCurrent: syncInput.isCurrent,\n });\n if (!replayed) {\n if (!syncInput.isCurrent()) {\n if (!remoteMoveApplied)\n syncInput.requestRemoteReconciliation(intent.parentContainerId);\n return abandonAppliedMove();\n }\n if (remoteMoveApplied) return unacknowledgedAcceptedMove(syncInput);\n await recordPendingMoveIntentError({\n containerId: intent.containerId,\n expectedIntentId: intent.id,\n expectedUpdatedAt: intent.updatedAt,\n isCurrent: syncInput.isCurrent,\n message: \"Remote container move was rejected or unavailable\",\n state,\n });\n return currentMoveResult(syncInput.isCurrent, \"failed\");\n }\n remoteMoveApplied = true;\n const moved =\n \"applied\" in replayed\n ? appliedRemoteContainer(containerState, replayed.applied)\n : replayed.moved;\n remoteParentId = moved.parentId;\n if (!syncInput.isCurrent()) return abandonAppliedMove();\n\n const persisted = await persistAcceptedMoveIntent({\n host,\n isCurrent: syncInput.isCurrent,\n intent,\n moved,\n requestRemoteReconciliation: syncInput.requestRemoteReconciliation,\n state,\n });\n if (!persisted) return unpersistedAcceptedMove(syncInput);\n state.runtime.util.log(\n \"applied\" in replayed\n ? `Container contents: settled queued container move ${intent.containerId} the server already held`\n : `Container contents: synced queued container move ${intent.containerId}`,\n );\n return \"moved\";\n } catch (error: unknown) {\n return failedMoveReplay({\n abandonAppliedMove,\n error,\n parentState,\n remoteMoveApplied,\n syncInput,\n });\n }\n}\n\nasync function trySyncPendingContainerMoveIntent(\n input: ContainerMoveIntentSyncInput,\n): Promise<MoveIntentSyncResult> {\n const { intent, state } = input;\n if (!input.isCurrent()) {\n return \"abandoned\";\n }\n const containerState = state.containersById.get(intent.containerId);\n const parentState = state.containersById.get(intent.parentContainerId);\n\n if (!containerState) {\n await recordPendingMoveIntentError({\n blocked: true,\n containerId: intent.containerId,\n expectedIntentId: intent.id,\n expectedUpdatedAt: intent.updatedAt,\n isCurrent: input.isCurrent,\n message: \"Container move intent references a missing local container\",\n state,\n });\n return currentMoveResult(input.isCurrent, \"failed\");\n }\n if (!parentState) {\n await recordPendingMoveIntentError({\n blocked: true,\n containerId: intent.containerId,\n expectedIntentId: intent.id,\n expectedUpdatedAt: intent.updatedAt,\n isCurrent: input.isCurrent,\n message:\n \"Container move intent references a missing destination parent container\",\n state,\n });\n return currentMoveResult(input.isCurrent, \"failed\");\n }\n if (input.isRemoteSyncBlocked(containerState.container.organizationId)) {\n return \"blocked\";\n }\n if (\n !hasRemoteContainerMetadataState(containerState) ||\n input.isCreatePending(intent.containerId)\n ) {\n // Source not synced yet is TRANSIENT, exactly like the destination case\n // below: the source's own create intent syncs first (createIntentSync runs\n // before moveIntentSync in a pass), but if that create fails on this pass\n // the source still lacks remote metadata here. Keep the intent 'pending'\n // (not 'blocked') so the queue reports a retryable failure rather than a\n // missing-dependency block; either way listUnsyncedMoveIntents replays it\n // once the create lands.\n await recordPendingMoveIntentError({\n containerId: intent.containerId,\n expectedIntentId: intent.id,\n expectedUpdatedAt: intent.updatedAt,\n isCurrent: input.isCurrent,\n message: \"Container move source is not synced yet\",\n state,\n });\n return currentMoveResult(input.isCurrent, \"blocked\");\n }\n if (\n !hasRemoteContainerMetadataState(parentState) ||\n input.isCreatePending(intent.parentContainerId)\n ) {\n await recordPendingMoveIntentError({\n containerId: intent.containerId,\n expectedIntentId: intent.id,\n expectedUpdatedAt: intent.updatedAt,\n isCurrent: input.isCurrent,\n message: \"Container move destination parent is not synced yet\",\n state,\n });\n return currentMoveResult(input.isCurrent, \"blocked\");\n }\n\n return movePendingRemoteContainer({\n containerState,\n parentState,\n syncInput: input,\n });\n}\n\nexport async function syncPendingContainerMoveIntents(input: {\n host: ContainerMoveIntentSyncHost;\n isCreatePending: (containerId: string) => boolean;\n isCurrent: () => boolean;\n isRemoteSyncBlocked: (organizationId: string) => boolean;\n requestRemoteReconciliation: (parentContainerId: string | null) => void;\n acceptedMoveRetry: AcceptedMoveRetry;\n state: ContainerMoveIntentSyncState;\n}): Promise<number> {\n if (!input.isCurrent()) {\n return 0;\n }\n const { host } = input;\n const state = { ...input.state };\n const execSql = state.runtime.infra.execSql;\n const pendingIntents =\n await state.persistence.listUnsyncedMoveIntents(execSql);\n if (!input.isCurrent()) {\n return 0;\n }\n let movedCount = 0;\n\n for (const intent of pendingIntents) {\n if (!input.isCurrent()) {\n return movedCount;\n }\n const result = await trySyncPendingContainerMoveIntent({\n host,\n isCurrent: input.isCurrent,\n isCreatePending: input.isCreatePending,\n isRemoteSyncBlocked: input.isRemoteSyncBlocked,\n intent,\n requestRemoteReconciliation: input.requestRemoteReconciliation,\n acceptedMoveRetry: input.acceptedMoveRetry,\n state,\n });\n if (result === \"abandoned\") {\n return movedCount;\n }\n\n if (result === \"moved\") {\n movedCount += 1;\n } else {\n // A replay that failed before reaching the server keeps an earlier\n // acceptance's retries going.\n input.acceptedMoveRetry.resume(intent.id);\n }\n }\n\n return movedCount;\n}\n"]}
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
import { listDocumentPlacementIntentStates } from "../../../../data/persistence/container-contents/documentPurgePlacement.js";
|
|
2
|
+
import { listDocumentsByContainerIdsOrDocumentIds } from "../../../../data/persistence/documents/documentsPersistence.js";
|
|
3
|
+
import { collectSubtreeLeafFirst } from "./purgeTreeCollection.js";
|
|
4
|
+
// A pass replays pending and blocked moves. A denied placement waits for the
|
|
5
|
+
// next store generation and an unavailable one never replays, so waiting on
|
|
6
|
+
// either would only reach the ceiling.
|
|
7
|
+
const REPLAYED_STATUSES = new Set(["pending", "blocked"]);
|
|
8
|
+
const DEFAULT_TIMING = { pollMs: 200, timeoutMs: 30_000 };
|
|
9
|
+
function delay(ms, signal) {
|
|
10
|
+
return new Promise((resolve) => {
|
|
11
|
+
const timer = setTimeout(done, ms);
|
|
12
|
+
function done() {
|
|
13
|
+
clearTimeout(timer);
|
|
14
|
+
signal?.removeEventListener("abort", done);
|
|
15
|
+
resolve();
|
|
16
|
+
}
|
|
17
|
+
signal?.addEventListener("abort", done, { once: true });
|
|
18
|
+
});
|
|
19
|
+
}
|
|
20
|
+
function createScopeReader(input) {
|
|
21
|
+
const containerIds = new Set(collectSubtreeLeafFirst(input.containersById, input.rootContainerId).map((containerState) => containerState.container.id));
|
|
22
|
+
let documentLocalIds = null;
|
|
23
|
+
// Listing the subtree's documents is the costly read, so it runs only once
|
|
24
|
+
// some document placement is queued anywhere on this device.
|
|
25
|
+
const readDocumentLocalIds = () => {
|
|
26
|
+
documentLocalIds ??= listDocumentsByContainerIdsOrDocumentIds(input.execSql, { containerIds: [...containerIds], documentIds: [] }).then((documents) => new Set(documents.map((document) => document.id)));
|
|
27
|
+
return documentLocalIds;
|
|
28
|
+
};
|
|
29
|
+
return async () => {
|
|
30
|
+
const moves = await input.persistence.listUnsyncedMoveIntents(input.execSql);
|
|
31
|
+
const placements = await listDocumentPlacementIntentStates(input.execSql);
|
|
32
|
+
const scoped = moves.filter((move) => containerIds.has(move.containerId));
|
|
33
|
+
if (placements.length === 0)
|
|
34
|
+
return scoped;
|
|
35
|
+
const localIds = await readDocumentLocalIds();
|
|
36
|
+
return [
|
|
37
|
+
...scoped,
|
|
38
|
+
...placements.filter((placement) => localIds.has(placement.localId)),
|
|
39
|
+
];
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
const isParked = (intent) => !REPLAYED_STATUSES.has(intent.syncStatus);
|
|
43
|
+
/** Why a wait that began at `startedAt` must stop, or null to keep polling. */
|
|
44
|
+
function stopReason(input) {
|
|
45
|
+
const { intents, lane, startedAt } = input;
|
|
46
|
+
if (intents.some(isParked))
|
|
47
|
+
return {
|
|
48
|
+
error: new Error("Purge scope move was parked"),
|
|
49
|
+
message: "Container contents: a move in the purge scope was parked",
|
|
50
|
+
};
|
|
51
|
+
if (startedAt !== null &&
|
|
52
|
+
intents.some(({ lastAttemptedAt }) => lastAttemptedAt !== null && lastAttemptedAt >= startedAt))
|
|
53
|
+
return {
|
|
54
|
+
error: new Error("Purge scope move failed to sync"),
|
|
55
|
+
message: "Container contents: a move in the purge scope failed to sync",
|
|
56
|
+
};
|
|
57
|
+
if (startedAt !== null && lane && !lane.running && !lane.requested)
|
|
58
|
+
return {
|
|
59
|
+
error: new Error("Purge scope move stayed queued"),
|
|
60
|
+
message: "Container contents: a sync pass left a purge scope move queued",
|
|
61
|
+
};
|
|
62
|
+
if (Date.now() >= input.deadline)
|
|
63
|
+
return {
|
|
64
|
+
error: new Error("Purge scope move wait timed out"),
|
|
65
|
+
message: "Container contents: purge timed out waiting for a move",
|
|
66
|
+
};
|
|
67
|
+
return null;
|
|
68
|
+
}
|
|
69
|
+
/**
|
|
70
|
+
* The purge scope refuses a subtree whose moves the server has not accepted,
|
|
71
|
+
* so ask for a pass that replays them and wait for its verdict. The scope's
|
|
72
|
+
* own checks still run afterwards; this only gives the moves time to land.
|
|
73
|
+
* It ends unsettled once a pass requested here has tried and left one queued.
|
|
74
|
+
*/
|
|
75
|
+
export async function settlePurgeScopeMoves(input) {
|
|
76
|
+
const timing = input.timing ?? DEFAULT_TIMING;
|
|
77
|
+
const cancelled = () => input.signal?.aborted === true || !input.stillCurrent();
|
|
78
|
+
const unsettled = (reason) => {
|
|
79
|
+
input.report(reason.message, reason.error);
|
|
80
|
+
input.onMoveSettlement?.("unsettled");
|
|
81
|
+
return "unsettled";
|
|
82
|
+
};
|
|
83
|
+
const readScope = createScopeReader(input);
|
|
84
|
+
let intents = await readScope();
|
|
85
|
+
if (cancelled())
|
|
86
|
+
return "cancelled";
|
|
87
|
+
if (intents.length === 0)
|
|
88
|
+
return "settled";
|
|
89
|
+
if (intents.some(isParked))
|
|
90
|
+
return unsettled({
|
|
91
|
+
error: new Error("Purge scope holds a parked move"),
|
|
92
|
+
message: "Container contents: purge scope holds a parked move",
|
|
93
|
+
});
|
|
94
|
+
input.onMoveSettlement?.("waiting");
|
|
95
|
+
const runCountAtRequest = input.lane.read()?.runCount ?? 0;
|
|
96
|
+
input.lane.request();
|
|
97
|
+
const deadline = Date.now() + timing.timeoutMs;
|
|
98
|
+
let startedAt = null;
|
|
99
|
+
while (true) {
|
|
100
|
+
await delay(timing.pollMs, input.signal);
|
|
101
|
+
if (cancelled())
|
|
102
|
+
return "cancelled";
|
|
103
|
+
// Read the lane first: a row still queued after a pass it shows finished
|
|
104
|
+
// survived that pass, while one read before could have settled since.
|
|
105
|
+
const lane = input.lane.read();
|
|
106
|
+
intents = await readScope();
|
|
107
|
+
if (cancelled())
|
|
108
|
+
return "cancelled";
|
|
109
|
+
if (intents.length === 0)
|
|
110
|
+
return "settled";
|
|
111
|
+
// Only a pass that starts after the request answers it.
|
|
112
|
+
if (lane && lane.runCount > runCountAtRequest)
|
|
113
|
+
startedAt ??= lane.lastStartedAt;
|
|
114
|
+
const reason = stopReason({ deadline, intents, lane, startedAt });
|
|
115
|
+
if (reason)
|
|
116
|
+
return unsettled(reason);
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
//# sourceMappingURL=purgeMoveSettlement.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"purgeMoveSettlement.js","sourceRoot":"","sources":["purgeMoveSettlement.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,iCAAiC,EAAE,MAAM,wEAAwE,CAAC;AAC3H,OAAO,EAAE,wCAAwC,EAAE,MAAM,6DAA6D,CAAC;AAKvH,OAAO,EAAE,uBAAuB,EAAE,MAAM,uBAAuB,CAAC;AAqChE,6EAA6E;AAC7E,4EAA4E;AAC5E,uCAAuC;AACvC,MAAM,iBAAiB,GAAG,IAAI,GAAG,CAAC,CAAC,SAAS,EAAE,SAAS,CAAC,CAAC,CAAC;AAC1D,MAAM,cAAc,GAAG,EAAE,MAAM,EAAE,GAAG,EAAE,SAAS,EAAE,MAAM,EAAE,CAAC;AAE1D,SAAS,KAAK,CAAC,EAAU,EAAE,MAA+B;IACxD,OAAO,IAAI,OAAO,CAAC,CAAC,OAAO,EAAE,EAAE;QAC7B,MAAM,KAAK,GAAG,UAAU,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC;QACnC,SAAS,IAAI;YACX,YAAY,CAAC,KAAK,CAAC,CAAC;YACpB,MAAM,EAAE,mBAAmB,CAAC,OAAO,EAAE,IAAI,CAAC,CAAC;YAC3C,OAAO,EAAE,CAAC;QACZ,CAAC;QACD,MAAM,EAAE,gBAAgB,CAAC,OAAO,EAAE,IAAI,EAAE,EAAE,IAAI,EAAE,IAAI,EAAE,CAAC,CAAC;IAC1D,CAAC,CAAC,CAAC;AACL,CAAC;AAED,SAAS,iBAAiB,CAAC,KAAiC;IAC1D,MAAM,YAAY,GAAG,IAAI,GAAG,CAC1B,uBAAuB,CAAC,KAAK,CAAC,cAAc,EAAE,KAAK,CAAC,eAAe,CAAC,CAAC,GAAG,CACtE,CAAC,cAAc,EAAE,EAAE,CAAC,cAAc,CAAC,SAAS,CAAC,EAAE,CAChD,CACF,CAAC;IACF,IAAI,gBAAgB,GAAgC,IAAI,CAAC;IACzD,2EAA2E;IAC3E,6DAA6D;IAC7D,MAAM,oBAAoB,GAAG,GAAG,EAAE;QAChC,gBAAgB,KAAK,wCAAwC,CAC3D,KAAK,CAAC,OAAO,EACb,EAAE,YAAY,EAAE,CAAC,GAAG,YAAY,CAAC,EAAE,WAAW,EAAE,EAAE,EAAE,CACrD,CAAC,IAAI,CAAC,CAAC,SAAS,EAAE,EAAE,CAAC,IAAI,GAAG,CAAC,SAAS,CAAC,GAAG,CAAC,CAAC,QAAQ,EAAE,EAAE,CAAC,QAAQ,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC;QACzE,OAAO,gBAAgB,CAAC;IAC1B,CAAC,CAAC;IACF,OAAO,KAAK,IAA6B,EAAE;QACzC,MAAM,KAAK,GAAG,MAAM,KAAK,CAAC,WAAW,CAAC,uBAAuB,CAC3D,KAAK,CAAC,OAAO,CACd,CAAC;QACF,MAAM,UAAU,GAAG,MAAM,iCAAiC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC;QAC1E,MAAM,MAAM,GAAmB,KAAK,CAAC,MAAM,CAAC,CAAC,IAAI,EAAE,EAAE,CACnD,YAAY,CAAC,GAAG,CAAC,IAAI,CAAC,WAAW,CAAC,CACnC,CAAC;QACF,IAAI,UAAU,CAAC,MAAM,KAAK,CAAC;YAAE,OAAO,MAAM,CAAC;QAC3C,MAAM,QAAQ,GAAG,MAAM,oBAAoB,EAAE,CAAC;QAC9C,OAAO;YACL,GAAG,MAAM;YACT,GAAG,UAAU,CAAC,MAAM,CAAC,CAAC,SAAS,EAAE,EAAE,CAAC,QAAQ,CAAC,GAAG,CAAC,SAAS,CAAC,OAAO,CAAC,CAAC;SACrE,CAAC;IACJ,CAAC,CAAC;AACJ,CAAC;AAID,MAAM,QAAQ,GAAG,CAAC,MAAoB,EAAE,EAAE,CACxC,CAAC,iBAAiB,CAAC,GAAG,CAAC,MAAM,CAAC,UAAU,CAAC,CAAC;AAE5C,+EAA+E;AAC/E,SAAS,UAAU,CAAC,KAKnB;IACC,MAAM,EAAE,OAAO,EAAE,IAAI,EAAE,SAAS,EAAE,GAAG,KAAK,CAAC;IAC3C,IAAI,OAAO,CAAC,IAAI,CAAC,QAAQ,CAAC;QACxB,OAAO;YACL,KAAK,EAAE,IAAI,KAAK,CAAC,6BAA6B,CAAC;YAC/C,OAAO,EAAE,0DAA0D;SACpE,CAAC;IACJ,IACE,SAAS,KAAK,IAAI;QAClB,OAAO,CAAC,IAAI,CACV,CAAC,EAAE,eAAe,EAAE,EAAE,EAAE,CACtB,eAAe,KAAK,IAAI,IAAI,eAAe,IAAI,SAAS,CAC3D;QAED,OAAO;YACL,KAAK,EAAE,IAAI,KAAK,CAAC,iCAAiC,CAAC;YACnD,OAAO,EAAE,8DAA8D;SACxE,CAAC;IACJ,IAAI,SAAS,KAAK,IAAI,IAAI,IAAI,IAAI,CAAC,IAAI,CAAC,OAAO,IAAI,CAAC,IAAI,CAAC,SAAS;QAChE,OAAO;YACL,KAAK,EAAE,IAAI,KAAK,CAAC,gCAAgC,CAAC;YAClD,OAAO,EAAE,gEAAgE;SAC1E,CAAC;IACJ,IAAI,IAAI,CAAC,GAAG,EAAE,IAAI,KAAK,CAAC,QAAQ;QAC9B,OAAO;YACL,KAAK,EAAE,IAAI,KAAK,CAAC,iCAAiC,CAAC;YACnD,OAAO,EAAE,wDAAwD;SAClE,CAAC;IACJ,OAAO,IAAI,CAAC;AACd,CAAC;AAED;;;;;GAKG;AACH,MAAM,CAAC,KAAK,UAAU,qBAAqB,CACzC,KAAiC;IAEjC,MAAM,MAAM,GAAG,KAAK,CAAC,MAAM,IAAI,cAAc,CAAC;IAC9C,MAAM,SAAS,GAAG,GAAG,EAAE,CACrB,KAAK,CAAC,MAAM,EAAE,OAAO,KAAK,IAAI,IAAI,CAAC,KAAK,CAAC,YAAY,EAAE,CAAC;IAC1D,MAAM,SAAS,GAAG,CAAC,MAAyC,EAAE,EAAE;QAC9D,KAAK,CAAC,MAAM,CAAC,MAAM,CAAC,OAAO,EAAE,MAAM,CAAC,KAAK,CAAC,CAAC;QAC3C,KAAK,CAAC,gBAAgB,EAAE,CAAC,WAAW,CAAC,CAAC;QACtC,OAAO,WAAoB,CAAC;IAC9B,CAAC,CAAC;IACF,MAAM,SAAS,GAAG,iBAAiB,CAAC,KAAK,CAAC,CAAC;IAC3C,IAAI,OAAO,GAAG,MAAM,SAAS,EAAE,CAAC;IAChC,IAAI,SAAS,EAAE;QAAE,OAAO,WAAW,CAAC;IACpC,IAAI,OAAO,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,SAAS,CAAC;IAC3C,IAAI,OAAO,CAAC,IAAI,CAAC,QAAQ,CAAC;QACxB,OAAO,SAAS,CAAC;YACf,KAAK,EAAE,IAAI,KAAK,CAAC,iCAAiC,CAAC;YACnD,OAAO,EAAE,qDAAqD;SAC/D,CAAC,CAAC;IAEL,KAAK,CAAC,gBAAgB,EAAE,CAAC,SAAS,CAAC,CAAC;IACpC,MAAM,iBAAiB,GAAG,KAAK,CAAC,IAAI,CAAC,IAAI,EAAE,EAAE,QAAQ,IAAI,CAAC,CAAC;IAC3D,KAAK,CAAC,IAAI,CAAC,OAAO,EAAE,CAAC;IACrB,MAAM,QAAQ,GAAG,IAAI,CAAC,GAAG,EAAE,GAAG,MAAM,CAAC,SAAS,CAAC;IAC/C,IAAI,SAAS,GAAkB,IAAI,CAAC;IACpC,OAAO,IAAI,EAAE,CAAC;QACZ,MAAM,KAAK,CAAC,MAAM,CAAC,MAAM,EAAE,KAAK,CAAC,MAAM,CAAC,CAAC;QACzC,IAAI,SAAS,EAAE;YAAE,OAAO,WAAW,CAAC;QACpC,yEAAyE;QACzE,sEAAsE;QACtE,MAAM,IAAI,GAAG,KAAK,CAAC,IAAI,CAAC,IAAI,EAAE,CAAC;QAC/B,OAAO,GAAG,MAAM,SAAS,EAAE,CAAC;QAC5B,IAAI,SAAS,EAAE;YAAE,OAAO,WAAW,CAAC;QACpC,IAAI,OAAO,CAAC,MAAM,KAAK,CAAC;YAAE,OAAO,SAAS,CAAC;QAC3C,wDAAwD;QACxD,IAAI,IAAI,IAAI,IAAI,CAAC,QAAQ,GAAG,iBAAiB;YAC3C,SAAS,KAAK,IAAI,CAAC,aAAa,CAAC;QACnC,MAAM,MAAM,GAAG,UAAU,CAAC,EAAE,QAAQ,EAAE,OAAO,EAAE,IAAI,EAAE,SAAS,EAAE,CAAC,CAAC;QAClE,IAAI,MAAM;YAAE,OAAO,SAAS,CAAC,MAAM,CAAC,CAAC;IACvC,CAAC;AACH,CAAC","sourcesContent":["import { listDocumentPlacementIntentStates } from \"../../../../data/persistence/container-contents/documentPurgePlacement\";\nimport { listDocumentsByContainerIdsOrDocumentIds } from \"../../../../data/persistence/documents/documentsPersistence\";\nimport type { ExecSql } from \"../../../../data/sqlite/sqlSchema\";\nimport type { ContainerContentsPersistence } from \"../../containerPersistence\";\nimport type { ContainerState } from \"../../remoteHydration\";\nimport type { PurgeMoveSettlement } from \"./purgeProgress\";\nimport { collectSubtreeLeafFirst } from \"./purgeTreeCollection\";\n\n/** The container-contents lane replays queued container and document moves. */\nexport interface PurgeMoveSyncLane {\n readonly read: () => {\n readonly lastStartedAt: string | null;\n readonly requested: boolean;\n readonly runCount: number;\n readonly running: boolean;\n } | null;\n readonly request: () => void;\n}\n\ninterface ScopedIntent {\n readonly lastAttemptedAt: string | null;\n readonly syncStatus: string;\n}\n\ninterface SettlePurgeScopeMovesInput {\n readonly containersById: ReadonlyMap<string, ContainerState>;\n readonly execSql: ExecSql;\n readonly lane: PurgeMoveSyncLane;\n readonly onMoveSettlement?:\n | ((settlement: PurgeMoveSettlement) => void)\n | undefined;\n readonly persistence: Pick<\n ContainerContentsPersistence,\n \"listUnsyncedMoveIntents\"\n >;\n /** Fixed messages only: the error's call site names the outcome. */\n readonly report: (message: string, error: Error) => void;\n readonly rootContainerId: string;\n readonly signal?: AbortSignal | undefined;\n readonly stillCurrent: () => boolean;\n readonly timing?: { pollMs: number; timeoutMs: number } | undefined;\n}\n\n// A pass replays pending and blocked moves. A denied placement waits for the\n// next store generation and an unavailable one never replays, so waiting on\n// either would only reach the ceiling.\nconst REPLAYED_STATUSES = new Set([\"pending\", \"blocked\"]);\nconst DEFAULT_TIMING = { pollMs: 200, timeoutMs: 30_000 };\n\nfunction delay(ms: number, signal: AbortSignal | undefined): Promise<void> {\n return new Promise((resolve) => {\n const timer = setTimeout(done, ms);\n function done() {\n clearTimeout(timer);\n signal?.removeEventListener(\"abort\", done);\n resolve();\n }\n signal?.addEventListener(\"abort\", done, { once: true });\n });\n}\n\nfunction createScopeReader(input: SettlePurgeScopeMovesInput) {\n const containerIds = new Set(\n collectSubtreeLeafFirst(input.containersById, input.rootContainerId).map(\n (containerState) => containerState.container.id,\n ),\n );\n let documentLocalIds: Promise<Set<string>> | null = null;\n // Listing the subtree's documents is the costly read, so it runs only once\n // some document placement is queued anywhere on this device.\n const readDocumentLocalIds = () => {\n documentLocalIds ??= listDocumentsByContainerIdsOrDocumentIds(\n input.execSql,\n { containerIds: [...containerIds], documentIds: [] },\n ).then((documents) => new Set(documents.map((document) => document.id)));\n return documentLocalIds;\n };\n return async (): Promise<ScopedIntent[]> => {\n const moves = await input.persistence.listUnsyncedMoveIntents(\n input.execSql,\n );\n const placements = await listDocumentPlacementIntentStates(input.execSql);\n const scoped: ScopedIntent[] = moves.filter((move) =>\n containerIds.has(move.containerId),\n );\n if (placements.length === 0) return scoped;\n const localIds = await readDocumentLocalIds();\n return [\n ...scoped,\n ...placements.filter((placement) => localIds.has(placement.localId)),\n ];\n };\n}\n\ntype LaneRun = ReturnType<PurgeMoveSyncLane[\"read\"]>;\n\nconst isParked = (intent: ScopedIntent) =>\n !REPLAYED_STATUSES.has(intent.syncStatus);\n\n/** Why a wait that began at `startedAt` must stop, or null to keep polling. */\nfunction stopReason(input: {\n deadline: number;\n intents: readonly ScopedIntent[];\n lane: LaneRun;\n startedAt: string | null;\n}): { error: Error; message: string } | null {\n const { intents, lane, startedAt } = input;\n if (intents.some(isParked))\n return {\n error: new Error(\"Purge scope move was parked\"),\n message: \"Container contents: a move in the purge scope was parked\",\n };\n if (\n startedAt !== null &&\n intents.some(\n ({ lastAttemptedAt }) =>\n lastAttemptedAt !== null && lastAttemptedAt >= startedAt,\n )\n )\n return {\n error: new Error(\"Purge scope move failed to sync\"),\n message: \"Container contents: a move in the purge scope failed to sync\",\n };\n if (startedAt !== null && lane && !lane.running && !lane.requested)\n return {\n error: new Error(\"Purge scope move stayed queued\"),\n message: \"Container contents: a sync pass left a purge scope move queued\",\n };\n if (Date.now() >= input.deadline)\n return {\n error: new Error(\"Purge scope move wait timed out\"),\n message: \"Container contents: purge timed out waiting for a move\",\n };\n return null;\n}\n\n/**\n * The purge scope refuses a subtree whose moves the server has not accepted,\n * so ask for a pass that replays them and wait for its verdict. The scope's\n * own checks still run afterwards; this only gives the moves time to land.\n * It ends unsettled once a pass requested here has tried and left one queued.\n */\nexport async function settlePurgeScopeMoves(\n input: SettlePurgeScopeMovesInput,\n): Promise<\"cancelled\" | \"settled\" | \"unsettled\"> {\n const timing = input.timing ?? DEFAULT_TIMING;\n const cancelled = () =>\n input.signal?.aborted === true || !input.stillCurrent();\n const unsettled = (reason: { error: Error; message: string }) => {\n input.report(reason.message, reason.error);\n input.onMoveSettlement?.(\"unsettled\");\n return \"unsettled\" as const;\n };\n const readScope = createScopeReader(input);\n let intents = await readScope();\n if (cancelled()) return \"cancelled\";\n if (intents.length === 0) return \"settled\";\n if (intents.some(isParked))\n return unsettled({\n error: new Error(\"Purge scope holds a parked move\"),\n message: \"Container contents: purge scope holds a parked move\",\n });\n\n input.onMoveSettlement?.(\"waiting\");\n const runCountAtRequest = input.lane.read()?.runCount ?? 0;\n input.lane.request();\n const deadline = Date.now() + timing.timeoutMs;\n let startedAt: string | null = null;\n while (true) {\n await delay(timing.pollMs, input.signal);\n if (cancelled()) return \"cancelled\";\n // Read the lane first: a row still queued after a pass it shows finished\n // survived that pass, while one read before could have settled since.\n const lane = input.lane.read();\n intents = await readScope();\n if (cancelled()) return \"cancelled\";\n if (intents.length === 0) return \"settled\";\n // Only a pass that starts after the request answers it.\n if (lane && lane.runCount > runCountAtRequest)\n startedAt ??= lane.lastStartedAt;\n const reason = stopReason({ deadline, intents, lane, startedAt });\n if (reason) return unsettled(reason);\n }\n}\n"]}
|
|
@@ -3,7 +3,15 @@ export interface PurgeProgress {
|
|
|
3
3
|
readonly failedCount: number;
|
|
4
4
|
readonly totalCount: number;
|
|
5
5
|
}
|
|
6
|
+
/**
|
|
7
|
+
* A purge first waits for moves this device queued inside its scope: until the
|
|
8
|
+
* server holds them, the scope cannot prove what it would destroy. "waiting"
|
|
9
|
+
* reports the wait; "unsettled" means it ended before every move reached the
|
|
10
|
+
* server, and nothing was deleted.
|
|
11
|
+
*/
|
|
12
|
+
export type PurgeMoveSettlement = "waiting" | "unsettled";
|
|
6
13
|
export interface PurgeOptions {
|
|
14
|
+
onMoveSettlement?: ((settlement: PurgeMoveSettlement) => void) | undefined;
|
|
7
15
|
onProgress?: ((progress: PurgeProgress) => void) | undefined;
|
|
8
16
|
signal?: AbortSignal | undefined;
|
|
9
17
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"purgeProgress.js","sourceRoot":"","sources":["purgeProgress.ts"],"names":[],"mappings":"","sourcesContent":["// Progress + cancellation contract shared by the recursive purge engine and its\n// store wrappers (\"Delete Forever\" and \"Empty Trash\"). onProgress reports\n// ABSOLUTE counts (not deltas) so a determinate progress bar can render\n// completed/total directly; a fresh object is emitted each tick so a\n// referential-equality state setter never drops an update. signal cancels at the\n// next unit boundary — between whole documents / whole containers, never\n// mid-remote-call — so a cancelled run always stops on a consistent prefix that\n// re-running can safely resume.\nexport interface PurgeProgress {\n readonly completedCount: number;\n readonly failedCount: number;\n readonly totalCount: number;\n}\n\nexport interface PurgeOptions {\n onProgress?: ((progress: PurgeProgress) => void) | undefined;\n signal?: AbortSignal | undefined;\n}\n"]}
|
|
1
|
+
{"version":3,"file":"purgeProgress.js","sourceRoot":"","sources":["purgeProgress.ts"],"names":[],"mappings":"","sourcesContent":["// Progress + cancellation contract shared by the recursive purge engine and its\n// store wrappers (\"Delete Forever\" and \"Empty Trash\"). onProgress reports\n// ABSOLUTE counts (not deltas) so a determinate progress bar can render\n// completed/total directly; a fresh object is emitted each tick so a\n// referential-equality state setter never drops an update. signal cancels at the\n// next unit boundary — between whole documents / whole containers, never\n// mid-remote-call — so a cancelled run always stops on a consistent prefix that\n// re-running can safely resume.\nexport interface PurgeProgress {\n readonly completedCount: number;\n readonly failedCount: number;\n readonly totalCount: number;\n}\n\n/**\n * A purge first waits for moves this device queued inside its scope: until the\n * server holds them, the scope cannot prove what it would destroy. \"waiting\"\n * reports the wait; \"unsettled\" means it ended before every move reached the\n * server, and nothing was deleted.\n */\nexport type PurgeMoveSettlement = \"waiting\" | \"unsettled\";\n\nexport interface PurgeOptions {\n onMoveSettlement?: ((settlement: PurgeMoveSettlement) => void) | undefined;\n onProgress?: ((progress: PurgeProgress) => void) | undefined;\n signal?: AbortSignal | undefined;\n}\n"]}
|