@tearleads/client-sdk 0.1.32 → 0.1.34
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/blobs/transferPool.js +53 -0
- package/data/blobs/transferPool.js.map +1 -0
- package/data/documents/blob/shared/types.d.ts +2 -2
- package/data/documents/blob/shared/types.js.map +1 -1
- package/data/sync/coordinatorLaneRun.js +154 -0
- package/data/sync/coordinatorLaneRun.js.map +1 -0
- package/data/sync/coordinatorPump.js +72 -169
- package/data/sync/coordinatorPump.js.map +1 -1
- package/data/sync/coordinatorState.d.ts +6 -0
- package/data/sync/coordinatorState.js +3 -1
- package/data/sync/coordinatorState.js.map +1 -1
- package/data/sync/syncCoordinator.js +4 -3
- package/data/sync/syncCoordinator.js.map +1 -1
- package/data/sync/syncLaneConfig.d.ts +2 -0
- package/data/sync/syncLaneConfig.js.map +1 -1
- package/internal/api-client/ApiClient.d.ts +2 -2
- package/internal/api-client/ApiClient.js +6 -30
- package/internal/api-client/ApiClient.js.map +1 -1
- package/internal/api-client/routes/blobs/get.d.ts +2 -2
- package/internal/api-client/routes/blobs/get.js +24 -21
- package/internal/api-client/routes/blobs/get.js.map +1 -1
- package/internal/api-client/routes/blobs/multipartUpload.js +146 -0
- package/internal/api-client/routes/blobs/multipartUpload.js.map +1 -0
- package/internal/api-client/routes/blobs/storageFetchOptions.js +11 -0
- package/internal/api-client/routes/blobs/storageFetchOptions.js.map +1 -0
- package/internal/api-client/types.d.ts +1 -1
- package/internal/api-client/types.js.map +1 -1
- package/internal/validators/operation/blobDownloads.js +3 -9
- package/internal/validators/operation/blobDownloads.js.map +1 -1
- package/internal/validators/operation/blobTransferSchemas.js +11 -0
- package/internal/validators/operation/blobTransferSchemas.js.map +1 -0
- package/internal/validators/operation/index.d.ts +1 -0
- package/internal/validators/operation/index.js +1 -0
- package/internal/validators/operation/index.js.map +1 -1
- package/internal/validators/operation/multipartPartUploads.d.ts +179 -0
- package/internal/validators/operation/multipartPartUploads.js +47 -0
- package/internal/validators/operation/multipartPartUploads.js.map +1 -0
- package/internal/validators/operation/registry.d.ts +2 -0
- package/internal/validators/operation/registry.js +2 -0
- package/internal/validators/operation/registry.js.map +1 -1
- package/internal/validators/util/blob.d.ts +2 -4
- package/internal/validators/util/blob.js +2 -4
- package/internal/validators/util/blob.js.map +1 -1
- package/package.json +1 -1
- package/stores/documents/documentStore/attachmentHydration.js +33 -0
- package/stores/documents/documentStore/attachmentHydration.js.map +1 -1
- package/stores/documents/documentStore/remoteWork.js +1 -1
- package/stores/documents/documentStore/remoteWork.js.map +1 -1
- package/stores/documents/documentStore/sync.js +3 -0
- package/stores/documents/documentStore/sync.js.map +1 -1
- package/sync/reconciliation/documentContentPull.js +7 -3
- package/sync/reconciliation/documentContentPull.js.map +1 -1
- package/sync/reconciliation/idleBackfill.js +14 -2
- package/sync/reconciliation/idleBackfill.js.map +1 -1
- package/sync/reconciliation/knownContainerSweep.js +5 -2
- package/sync/reconciliation/knownContainerSweep.js.map +1 -1
- package/sync/reconciliation/reconcileContainer.js +3 -3
- package/sync/reconciliation/reconcileContainer.js.map +1 -1
- package/sync/reconciliation/service.js +9 -4
- package/sync/reconciliation/service.js.map +1 -1
- package/sync/reconciliation/serviceTypes.d.ts +5 -3
- package/sync/reconciliation/serviceTypes.js.map +1 -1
- package/sync/reconciliation/triggers.d.ts +2 -1
- package/sync/reconciliation/triggers.js +5 -2
- package/sync/reconciliation/triggers.js.map +1 -1
- package/workflows/blobs/hydrate.d.ts +4 -2
- package/workflows/blobs/hydrate.js +21 -7
- package/workflows/blobs/hydrate.js.map +1 -1
- package/workflows/blobs/multipartStageFailures.js +1 -1
- package/workflows/blobs/multipartStageFailures.js.map +1 -1
- package/workflows/blobs/multipartUpload.js +69 -39
- package/workflows/blobs/multipartUpload.js.map +1 -1
- package/workflows/blobs/readAttachmentTransfer.js +42 -0
- package/workflows/blobs/readAttachmentTransfer.js.map +1 -0
- package/workflows/documents/syncLane.d.ts +1 -0
- package/workflows/documents/syncLane.js +1 -0
- package/workflows/documents/syncLane.js.map +1 -1
- package/workflows/registration/persistRegistrationBootstrap.js +3 -0
- package/workflows/registration/persistRegistrationBootstrap.js.map +1 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"knownContainerSweep.js","sourceRoot":"","sources":["knownContainerSweep.ts"],"names":[],"mappings":"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","sourcesContent":["import {\n acknowledgeContainerForce,\n type IdleBackfillState,\n} from \"./idleBackfill\";\nimport {\n type FailedForcedContainer,\n rearmFailedSweepContainers,\n} from \"./laneFailure\";\nimport { reconcileOneContainer } from \"./reconcileContainer\";\nimport type { ReconciliationHost } from \"./serviceTypes\";\n\ninterface KnownContainerSweepState extends IdleBackfillState {\n active: boolean;\n lane: {\n requestSync: () => void;\n requestSyncAfter: (delayMs: number) => void;\n } | null;\n lifecycleGeneration: number;\n}\n\nconst pendingRetries = new WeakMap<\n KnownContainerSweepState,\n Map<string, { due: number; timer: ReturnType<typeof setTimeout> }>\n>();\n\nexport function clearPendingDiscoveryRetries(\n state: KnownContainerSweepState,\n): void {\n for (const { timer } of pendingRetries.get(state)?.values() ?? [])\n clearTimeout(timer);\n pendingRetries.delete(state);\n}\n\nfunction schedulePendingDiscovery(\n state: KnownContainerSweepState,\n containerId: string,\n delayMs: number,\n): void {\n if (!state.active) return;\n const retries = pendingRetries.get(state) ?? new Map();\n pendingRetries.set(state, retries);\n const due = Date.now() + delayMs;\n const existing = retries.get(containerId);\n if (existing && existing.due <= due) return;\n if (existing) clearTimeout(existing.timer);\n const generation = state.lifecycleGeneration;\n const timer = setTimeout(\n () => {\n retries.delete(containerId);\n if (!state.active || state.lifecycleGeneration !== generation) return;\n state.discoveredContainerIds.delete(containerId);\n state.queue.enqueue(\n containerId,\n state.activeContainerId === containerId ? \"active\" : \"idle\",\n );\n state.lane?.requestSync();\n },\n Math.max(1, delayMs),\n );\n retries.set(containerId, { due, timer });\n}\n\nexport function isCurrentReconciliationLifecycle(\n state: KnownContainerSweepState,\n generation: number,\n): boolean {\n return (\n state.lifecycleGeneration === generation &&\n (state.active || generation === 0)\n );\n}\n\n// Reconcile a container whose discovered mark is already set, rolling the mark\n// back when the container is skipped or fails so a transient error (or a\n// container that later becomes eligible) can be retried.\nexport async function reconcileMarkedContainer(\n host: ReconciliationHost,\n state: KnownContainerSweepState,\n containerId: string,\n forceDocumentContentPull: boolean,\n): Promise<boolean> {\n try {\n const reconciled = await reconcileOneContainer(host, containerId, {\n forceDocumentContentPull,\n onFullListing: state.initialDocumentProbe.captureListing(containerId),\n onPendingDiscovery: (delayMs) =>\n schedulePendingDiscovery(state, containerId, delayMs),\n });\n if (!reconciled) {\n state.discoveredContainerIds.delete(containerId);\n }\n return reconciled;\n } catch (error) {\n state.discoveredContainerIds.delete(containerId);\n throw error;\n }\n}\n\ntype SweepContainerResult =\n | { status: \"failed\"; error: unknown; forceGeneration: number | undefined }\n | { status: \"stale\" }\n | { status: \"succeeded\" };\n\nasync function sweepContainer(input: {\n containerId: string;\n forceAllDocumentContentPulls: boolean;\n host: ReconciliationHost;\n lifecycleGeneration: number;\n state: KnownContainerSweepState;\n}): Promise<SweepContainerResult> {\n const { containerId, host, lifecycleGeneration, state } = input;\n if (!isCurrentReconciliationLifecycle(state, lifecycleGeneration)) {\n return { status: \"stale\" };\n }\n const forceGeneration = state.forcedContainerGenerations.get(containerId);\n try {\n const reconciled = await reconcileMarkedContainer(\n host,\n state,\n containerId,\n input.forceAllDocumentContentPulls || forceGeneration !== undefined,\n );\n if (!isCurrentReconciliationLifecycle(state, lifecycleGeneration)) {\n return { status: \"stale\" };\n }\n if (reconciled) {\n acknowledgeContainerForce(state, containerId, forceGeneration);\n }\n return { status: \"succeeded\" };\n } catch (error) {\n if (!isCurrentReconciliationLifecycle(state, lifecycleGeneration)) {\n return { status: \"stale\" };\n }\n return { error, forceGeneration, status: \"failed\" };\n }\n}\n\nexport async function sweepKnownContainers(input: {\n forceAllDocumentContentPulls: boolean;\n host: ReconciliationHost;\n knownIds: ReadonlyArray<string>;\n lifecycleGeneration: number;\n state: KnownContainerSweepState;\n}): Promise<void> {\n const {\n forceAllDocumentContentPulls,\n host,\n knownIds,\n lifecycleGeneration,\n state,\n } = input;\n if (!isCurrentReconciliationLifecycle(state, lifecycleGeneration)) {\n return;\n }\n const containerIds = forceAllDocumentContentPulls\n ? knownIds\n : knownIds.filter(\n (containerId) =>\n state.forcedContainerGenerations.has(containerId) ||\n !state.discoveredContainerIds.has(containerId),\n );\n // Mark only the containers this sweep will fetch. Automatic root hints are\n // discovery signals, so already-reconciled containers stay settled unless a\n // targeted event forced them; explicit full refreshes still fetch every id.\n for (const containerId of containerIds) {\n state.discoveredContainerIds.add(containerId);\n }\n // Reconcile every container independently: one failing container must not\n // block refreshing the rest. Surface the first real error after the sweep.\n let firstError: unknown;\n const failedForces: FailedForcedContainer[] = [];\n for (const containerId of containerIds) {\n const result = await sweepContainer({\n containerId,\n forceAllDocumentContentPulls,\n host,\n lifecycleGeneration,\n state,\n });\n if (result.status === \"stale\") {\n return;\n }\n if (result.status === \"failed\") {\n if (result.forceGeneration !== undefined) {\n failedForces.push({\n containerId,\n forceGeneration: result.forceGeneration,\n });\n }\n if (!host.isIgnorableError(result.error) && firstError === undefined) {\n firstError = result.error;\n }\n }\n }\n rearmFailedSweepContainers(state, failedForces, lifecycleGeneration);\n if (firstError !== undefined) {\n throw firstError;\n }\n}\n"]}
|
|
1
|
+
{"version":3,"file":"knownContainerSweep.js","sourceRoot":"","sources":["knownContainerSweep.ts"],"names":[],"mappings":"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","sourcesContent":["import {\n acknowledgeContainerForce,\n type IdleBackfillState,\n} from \"./idleBackfill\";\nimport {\n type FailedForcedContainer,\n rearmFailedSweepContainers,\n} from \"./laneFailure\";\nimport { reconcileOneContainer } from \"./reconcileContainer\";\nimport type { ReconciliationHost } from \"./serviceTypes\";\n\ninterface KnownContainerSweepState extends IdleBackfillState {\n active: boolean;\n lane: {\n requestSync: () => void;\n requestSyncAfter: (delayMs: number) => void;\n } | null;\n lifecycleGeneration: number;\n}\n\nconst pendingRetries = new WeakMap<\n KnownContainerSweepState,\n Map<string, { due: number; timer: ReturnType<typeof setTimeout> }>\n>();\n\nexport function clearPendingDiscoveryRetries(\n state: KnownContainerSweepState,\n): void {\n for (const { timer } of pendingRetries.get(state)?.values() ?? [])\n clearTimeout(timer);\n pendingRetries.delete(state);\n}\n\nfunction schedulePendingDiscovery(\n state: KnownContainerSweepState,\n containerId: string,\n delayMs: number,\n): void {\n if (!state.active) return;\n const retries = pendingRetries.get(state) ?? new Map();\n pendingRetries.set(state, retries);\n const due = Date.now() + delayMs;\n const existing = retries.get(containerId);\n if (existing && existing.due <= due) return;\n if (existing) clearTimeout(existing.timer);\n const generation = state.lifecycleGeneration;\n const timer = setTimeout(\n () => {\n retries.delete(containerId);\n if (!state.active || state.lifecycleGeneration !== generation) return;\n state.discoveredContainerIds.delete(containerId);\n state.queue.enqueue(\n containerId,\n state.activeContainerId === containerId ? \"active\" : \"idle\",\n );\n state.lane?.requestSync();\n },\n Math.max(1, delayMs),\n );\n retries.set(containerId, { due, timer });\n}\n\nexport function isCurrentReconciliationLifecycle(\n state: KnownContainerSweepState,\n generation: number,\n): boolean {\n return (\n state.lifecycleGeneration === generation &&\n (state.active || generation === 0)\n );\n}\n\n// Reconcile a container whose discovered mark is already set, rolling the mark\n// back when the container is skipped or fails so a transient error (or a\n// container that later becomes eligible) can be retried.\nexport async function reconcileMarkedContainer(\n host: ReconciliationHost,\n state: KnownContainerSweepState,\n containerId: string,\n forceDocumentContentPull: boolean,\n fullRevalidation = false,\n): Promise<boolean> {\n try {\n const reconciled = await reconcileOneContainer(host, containerId, {\n forceDocumentContentPull,\n documentIds: fullRevalidation\n ? undefined\n : state.forcedDocumentIds.get(containerId),\n onFullListing: state.initialDocumentProbe.captureListing(containerId),\n onPendingDiscovery: (delayMs) =>\n schedulePendingDiscovery(state, containerId, delayMs),\n });\n if (!reconciled) {\n state.discoveredContainerIds.delete(containerId);\n }\n return reconciled;\n } catch (error) {\n state.discoveredContainerIds.delete(containerId);\n throw error;\n }\n}\n\ntype SweepContainerResult =\n | { status: \"failed\"; error: unknown; forceGeneration: number | undefined }\n | { status: \"stale\" }\n | { status: \"succeeded\" };\n\nasync function sweepContainer(input: {\n containerId: string;\n forceAllDocumentContentPulls: boolean;\n host: ReconciliationHost;\n lifecycleGeneration: number;\n state: KnownContainerSweepState;\n}): Promise<SweepContainerResult> {\n const { containerId, host, lifecycleGeneration, state } = input;\n if (!isCurrentReconciliationLifecycle(state, lifecycleGeneration)) {\n return { status: \"stale\" };\n }\n const forceGeneration = state.forcedContainerGenerations.get(containerId);\n try {\n const reconciled = await reconcileMarkedContainer(\n host,\n state,\n containerId,\n input.forceAllDocumentContentPulls || forceGeneration !== undefined,\n input.forceAllDocumentContentPulls,\n );\n if (!isCurrentReconciliationLifecycle(state, lifecycleGeneration)) {\n return { status: \"stale\" };\n }\n if (reconciled) {\n acknowledgeContainerForce(state, containerId, forceGeneration);\n }\n return { status: \"succeeded\" };\n } catch (error) {\n if (!isCurrentReconciliationLifecycle(state, lifecycleGeneration)) {\n return { status: \"stale\" };\n }\n return { error, forceGeneration, status: \"failed\" };\n }\n}\n\nexport async function sweepKnownContainers(input: {\n forceAllDocumentContentPulls: boolean;\n host: ReconciliationHost;\n knownIds: ReadonlyArray<string>;\n lifecycleGeneration: number;\n state: KnownContainerSweepState;\n}): Promise<void> {\n const {\n forceAllDocumentContentPulls,\n host,\n knownIds,\n lifecycleGeneration,\n state,\n } = input;\n if (!isCurrentReconciliationLifecycle(state, lifecycleGeneration)) {\n return;\n }\n const containerIds = forceAllDocumentContentPulls\n ? knownIds\n : knownIds.filter(\n (containerId) =>\n state.forcedContainerGenerations.has(containerId) ||\n !state.discoveredContainerIds.has(containerId),\n );\n // Mark only the containers this sweep will fetch. Automatic root hints are\n // discovery signals, so already-reconciled containers stay settled unless a\n // targeted event forced them; explicit full refreshes still fetch every id.\n for (const containerId of containerIds) {\n state.discoveredContainerIds.add(containerId);\n }\n // Reconcile every container independently: one failing container must not\n // block refreshing the rest. Surface the first real error after the sweep.\n let firstError: unknown;\n const failedForces: FailedForcedContainer[] = [];\n for (const containerId of containerIds) {\n const result = await sweepContainer({\n containerId,\n forceAllDocumentContentPulls,\n host,\n lifecycleGeneration,\n state,\n });\n if (result.status === \"stale\") {\n return;\n }\n if (result.status === \"failed\") {\n if (result.forceGeneration !== undefined) {\n failedForces.push({\n containerId,\n forceGeneration: result.forceGeneration,\n });\n }\n if (!host.isIgnorableError(result.error) && firstError === undefined) {\n firstError = result.error;\n }\n }\n }\n rearmFailedSweepContainers(state, failedForces, lifecycleGeneration);\n if (firstError !== undefined) {\n throw firstError;\n }\n}\n"]}
|
|
@@ -14,9 +14,9 @@ export async function reconcileOneContainer(host, containerId, options = {}) {
|
|
|
14
14
|
}
|
|
15
15
|
const delta = await host.loadContainerDelta(containerId);
|
|
16
16
|
host.applyReconciled(delta);
|
|
17
|
-
//
|
|
18
|
-
//
|
|
19
|
-
host.requestDocumentContentPull(containerId, delta.documentSummaries, options.forceDocumentContentPull ?? false);
|
|
17
|
+
// Hint forces target named documents. Full refreshes revalidate every
|
|
18
|
+
// registered store; passive pulls only open system projections.
|
|
19
|
+
host.requestDocumentContentPull(containerId, delta.documentSummaries, options.forceDocumentContentPull ?? false, options.documentIds);
|
|
20
20
|
// A durable, scheduled retry is settled for this sweep and force request.
|
|
21
21
|
// Its own timer will reopen the lane when evidence is due.
|
|
22
22
|
return true;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"reconcileContainer.js","sourceRoot":"","sources":["reconcileContainer.ts"],"names":[],"mappings":"AAEA,MAAM,CAAC,KAAK,UAAU,qBAAqB,CACzC,IAAwB,EACxB,WAAmB,EACnB,
|
|
1
|
+
{"version":3,"file":"reconcileContainer.js","sourceRoot":"","sources":["reconcileContainer.ts"],"names":[],"mappings":"AAEA,MAAM,CAAC,KAAK,UAAU,qBAAqB,CACzC,IAAwB,EACxB,WAAmB,EACnB,UAKI,EAAE;IAEN,4EAA4E;IAC5E,IAAI,CAAC,IAAI,CAAC,6BAA6B,CAAC,WAAW,CAAC,EAAE,CAAC;QACrD,OAAO,KAAK,CAAC;IACf,CAAC;IAED,IAAI,CAAC;QACH,IAAI,OAAO,GAAG,KAAK,CAAC;QACpB,MAAM,UAAU,GAAG,MAAM,IAAI,CAAC,0BAA0B,CACtD,WAAW,EACX,OAAO,CAAC,aAAa,EACrB,CAAC,OAAO,EAAE,EAAE;YACV,OAAO,GAAG,IAAI,CAAC;YACf,OAAO,CAAC,kBAAkB,EAAE,CAAC,OAAO,CAAC,CAAC;QACxC,CAAC,CACF,CAAC;QACF,IAAI,UAAU,KAAK,IAAI,IAAI,CAAC,OAAO,EAAE,CAAC;YACpC,OAAO,KAAK,CAAC;QACf,CAAC;QACD,MAAM,KAAK,GAAG,MAAM,IAAI,CAAC,kBAAkB,CAAC,WAAW,CAAC,CAAC;QACzD,IAAI,CAAC,eAAe,CAAC,KAAK,CAAC,CAAC;QAC5B,sEAAsE;QACtE,gEAAgE;QAChE,IAAI,CAAC,0BAA0B,CAC7B,WAAW,EACX,KAAK,CAAC,iBAAiB,EACvB,OAAO,CAAC,wBAAwB,IAAI,KAAK,EACzC,OAAO,CAAC,WAAW,CACpB,CAAC;QACF,0EAA0E;QAC1E,2DAA2D;QAC3D,OAAO,IAAI,CAAC;IACd,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,IAAI,IAAI,CAAC,gBAAgB,CAAC,KAAK,CAAC,EAAE,CAAC;YACjC,OAAO,KAAK,CAAC;QACf,CAAC;QACD,MAAM,KAAK,CAAC;IACd,CAAC;AACH,CAAC","sourcesContent":["import type { ReconciliationHost } from \"./serviceTypes\";\n\nexport async function reconcileOneContainer(\n host: ReconciliationHost,\n containerId: string,\n options: {\n forceDocumentContentPull?: boolean;\n documentIds?: ReadonlySet<string> | undefined;\n onFullListing?: ((documentIds: ReadonlyArray<string>) => void) | undefined;\n onPendingDiscovery?: ((delayMs: number) => void) | undefined;\n } = {},\n): Promise<boolean> {\n // A queued local root/system id can become stale before the document phase.\n if (!host.canDiscoverContainerDocuments(containerId)) {\n return false;\n }\n\n try {\n let pending = false;\n const discovered = await host.discoverContainerDocuments(\n containerId,\n options.onFullListing,\n (delayMs) => {\n pending = true;\n options.onPendingDiscovery?.(delayMs);\n },\n );\n if (discovered === null && !pending) {\n return false;\n }\n const delta = await host.loadContainerDelta(containerId);\n host.applyReconciled(delta);\n // Hint forces target named documents. Full refreshes revalidate every\n // registered store; passive pulls only open system projections.\n host.requestDocumentContentPull(\n containerId,\n delta.documentSummaries,\n options.forceDocumentContentPull ?? false,\n options.documentIds,\n );\n // A durable, scheduled retry is settled for this sweep and force request.\n // Its own timer will reopen the lane when evidence is due.\n return true;\n } catch (error) {\n if (host.isIgnorableError(error)) {\n return false;\n }\n throw error;\n }\n}\n"]}
|
|
@@ -166,6 +166,7 @@ function startReconciliationLane(host, state) {
|
|
|
166
166
|
// Document discovery runs in the document phase, after structural
|
|
167
167
|
// container hydration/metadata sync settles for the scope.
|
|
168
168
|
phase: "document",
|
|
169
|
+
priority: () => (state.activeContainerId ? -1 : 0),
|
|
169
170
|
onUnexpectedError: (error) => {
|
|
170
171
|
console.error("Device-first reconciliation failed:", error);
|
|
171
172
|
},
|
|
@@ -185,6 +186,7 @@ function createReconciliationState(host) {
|
|
|
185
186
|
automaticRetryGenerations: new Map(),
|
|
186
187
|
discoveredContainerIds: new Set(),
|
|
187
188
|
forcedContainerGenerations: new Map(),
|
|
189
|
+
forcedDocumentIds: new Map(),
|
|
188
190
|
initialDocumentProbe: createInitialDocumentProbe(host),
|
|
189
191
|
lane: null,
|
|
190
192
|
lifecycleGeneration: 0,
|
|
@@ -202,6 +204,7 @@ function stopReconciliationService(host, state) {
|
|
|
202
204
|
state.queue.clear();
|
|
203
205
|
state.automaticRetryGenerations.clear();
|
|
204
206
|
state.forcedContainerGenerations.clear();
|
|
207
|
+
state.forcedDocumentIds.clear();
|
|
205
208
|
state.pendingRefresh = null;
|
|
206
209
|
state.initialDocumentProbe.resetPending();
|
|
207
210
|
// Drop the per-session discovered suppression cache too: a stopped
|
|
@@ -226,18 +229,20 @@ export function createReconciliationService(host) {
|
|
|
226
229
|
state.probeContinuationCancel = null;
|
|
227
230
|
state.lane?.requestSync();
|
|
228
231
|
};
|
|
229
|
-
const enqueueContainer = (containerId, priority, force = false) => {
|
|
232
|
+
const enqueueContainer = (containerId, priority, force = false, documentId) => {
|
|
230
233
|
if (!containerId) {
|
|
231
234
|
return;
|
|
232
235
|
}
|
|
233
236
|
// Skip containers already reconciled this session unless forced. Events
|
|
234
237
|
// force re-discovery; passive active/backfill scheduling does not, so
|
|
235
238
|
// opening Explorer does not re-fetch every container on each navigation.
|
|
236
|
-
if (!force &&
|
|
239
|
+
if (!force &&
|
|
240
|
+
!state.forcedContainerGenerations.has(containerId) &&
|
|
241
|
+
state.discoveredContainerIds.has(containerId)) {
|
|
237
242
|
return;
|
|
238
243
|
}
|
|
239
244
|
if (force) {
|
|
240
|
-
markContainerForced(state, containerId);
|
|
245
|
+
markContainerForced(state, containerId, documentId);
|
|
241
246
|
}
|
|
242
247
|
state.queue.enqueue(containerId, priority);
|
|
243
248
|
scheduleDrain();
|
|
@@ -255,7 +260,7 @@ export function createReconciliationService(host) {
|
|
|
255
260
|
scheduleDrain();
|
|
256
261
|
},
|
|
257
262
|
// Reconcile the active container first; siblings remain lazy until visited.
|
|
258
|
-
setActiveContainer: (containerId) => activateContainer(state, containerId, (activeId
|
|
263
|
+
setActiveContainer: (containerId) => activateContainer(state, containerId, (activeId) => enqueueContainer(activeId, "active")),
|
|
259
264
|
enqueueContainer,
|
|
260
265
|
enqueueIdleBackfill,
|
|
261
266
|
resetDiscovered: () => {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"service.js","sourceRoot":"","sources":["service.ts"],"names":[],"mappings":"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","sourcesContent":["import {\n getOrCreateDomainSyncCoordinator,\n type SyncLane,\n} from \"../../data/sync/syncCoordinator\";\nimport {\n acknowledgeContainerForce,\n activateContainer,\n enqueueKnownContainersForIdleBackfill,\n type IdleBackfillState,\n markContainerForced,\n} from \"./idleBackfill\";\nimport { createInitialDocumentProbe } from \"./initialDocumentProbe\";\nimport {\n clearPendingDiscoveryRetries,\n isCurrentReconciliationLifecycle,\n reconcileMarkedContainer,\n sweepKnownContainers,\n} from \"./knownContainerSweep\";\nimport { rearmFailedContainer } from \"./laneFailure\";\nimport { clearOriginatedDocuments } from \"./originatedDocuments\";\nimport { createReconcileQueue, type ReconcilePriority } from \"./queue\";\nimport type {\n ReconciliationHost,\n ReconciliationRuntimeStatus,\n ReconciliationService,\n} from \"./serviceTypes\";\n\nfunction canReconcile(status: ReconciliationRuntimeStatus): boolean {\n return status.dbStatus === \"ready\" && status.isAuthenticated && status.online;\n}\n\ntype RefreshScope = \"root\" | \"full\";\n\ninterface ReconciliationState extends IdleBackfillState {\n active: boolean;\n lane: SyncLane | null;\n lifecycleGeneration: number;\n probeContinuationCancel: (() => void) | null;\n pendingRefresh: {\n readonly promise: Promise<void>;\n readonly scope: RefreshScope;\n } | null;\n}\n\nasync function runReconcileLane(\n host: ReconciliationHost,\n state: ReconciliationState,\n): Promise<void> {\n if (!state.active || !canReconcile(host.getRuntimeStatus())) {\n return;\n }\n const lifecycleGeneration = state.lifecycleGeneration;\n\n const containerId = state.queue.dequeue();\n if (!containerId) {\n await state.initialDocumentProbe.run();\n scheduleProbeContinuation(host, state);\n return;\n }\n // Double-check at run time: a container may have been discovered (by an\n // explicit refresh or an earlier lane pass) after it was queued. Mark it\n // discovered up front to collapse concurrent re-enqueues into one fetch, but\n // roll the mark back on failure so a transient error can be retried later.\n const forceGeneration = state.forcedContainerGenerations.get(containerId);\n const shouldForce = forceGeneration !== undefined;\n if (shouldForce || !state.discoveredContainerIds.has(containerId)) {\n state.discoveredContainerIds.add(containerId);\n try {\n const reconciled = await reconcileMarkedContainer(\n host,\n state,\n containerId,\n shouldForce,\n );\n if (reconciled) {\n acknowledgeContainerForce(state, containerId, forceGeneration);\n }\n } catch (error) {\n rearmFailedContainer(\n state,\n containerId,\n forceGeneration,\n lifecycleGeneration,\n );\n throw error;\n }\n }\n\n // Keep draining: schedule another pass while the queue holds work.\n if (state.queue.size > 0) {\n state.lane?.requestSync();\n } else {\n scheduleProbeContinuation(host, state);\n }\n}\n\nfunction scheduleProbeContinuation(\n host: ReconciliationHost,\n state: ReconciliationState,\n): void {\n state.probeContinuationCancel?.();\n const canContinue = () =>\n state.active &&\n state.queue.size === 0 &&\n canReconcile(host.getRuntimeStatus()) &&\n state.initialDocumentProbe.hasPendingWork();\n if (!canContinue()) {\n state.probeContinuationCancel = null;\n return;\n }\n const timeout = setTimeout(() => {\n if (canContinue()) {\n state.probeContinuationCancel = null;\n state.lane?.requestSync();\n }\n }, state.initialDocumentProbe.continuationDelayMs());\n state.probeContinuationCancel = () => clearTimeout(timeout);\n}\n\nfunction forgetIneligibleDiscoveredContainers(\n host: ReconciliationHost,\n state: ReconciliationState,\n): void {\n for (const containerId of state.discoveredContainerIds) {\n if (!host.canDiscoverContainerDocuments(containerId)) {\n state.discoveredContainerIds.delete(containerId);\n }\n }\n}\n\nasync function refreshAndReconcileContainers(input: {\n forceAllDocumentContentPulls: boolean;\n host: ReconciliationHost;\n listContainerIds: () => ReadonlyArray<string>;\n refreshTree: () => Promise<void>;\n state: ReconciliationState;\n}): Promise<void> {\n const { host, listContainerIds, refreshTree, state } = input;\n if (!canReconcile(host.getRuntimeStatus())) {\n return;\n }\n const lifecycleGeneration = state.lifecycleGeneration;\n\n if (input.forceAllDocumentContentPulls) {\n state.queue.clear();\n }\n try {\n // A resync_required structural refresh runs independently of this service.\n // If it already removed a container after the forced document lane won its\n // race against the old tree, forget the stale discovered bit before this\n // refresh can re-add the same id.\n forgetIneligibleDiscoveredContainers(host, state);\n await refreshTree();\n if (!isCurrentReconciliationLifecycle(state, lifecycleGeneration)) {\n return;\n }\n // Structural hydration can revoke and remove a container after a targeted\n // force already reconciled it against the old tree. Forget every id that\n // is no longer remotely listable so a later share of the same container id\n // is treated as newly surfaced instead of being suppressed for the rest of\n // the session.\n forgetIneligibleDiscoveredContainers(host, state);\n await sweepKnownContainers({\n forceAllDocumentContentPulls: input.forceAllDocumentContentPulls,\n host,\n knownIds: listContainerIds(),\n lifecycleGeneration,\n state,\n });\n if (!isCurrentReconciliationLifecycle(state, lifecycleGeneration)) {\n return;\n }\n } catch (error) {\n if (!isCurrentReconciliationLifecycle(state, lifecycleGeneration)) {\n return;\n }\n if (!host.isIgnorableError(error)) {\n throw error;\n }\n }\n}\n\n// Sweeps share the queue and discovered set, so they must run serially.\n// Root requests join either kind of sweep; a full request after a root sweep\n// must wait for it and then refresh the whole tree.\nfunction requestContainerRefresh(input: {\n host: ReconciliationHost;\n state: ReconciliationState;\n refreshTree: () => Promise<void>;\n listContainerIds: () => ReadonlyArray<string>;\n scope: RefreshScope;\n}): Promise<void> {\n const { state, scope } = input;\n const pending = state.pendingRefresh;\n if (pending && (scope === \"root\" || pending.scope === \"full\")) {\n return pending.promise;\n }\n\n const promise = (pending?.promise ?? Promise.resolve())\n // A failed root sweep must not prevent the queued full refresh.\n .catch(() => undefined)\n .then(() =>\n refreshAndReconcileContainers({\n forceAllDocumentContentPulls: scope === \"full\",\n host: input.host,\n listContainerIds: input.listContainerIds,\n refreshTree: input.refreshTree,\n state,\n }),\n )\n .finally(() => {\n if (state.pendingRefresh?.promise === promise) {\n state.pendingRefresh = null;\n }\n });\n state.pendingRefresh = { promise, scope };\n return promise;\n}\n\nfunction listFullRefreshContainerIds(\n host: ReconciliationHost,\n state: ReconciliationState,\n): ReadonlyArray<string> {\n const knownIds = host.listKnownContainerIds();\n const activeContainerId = state.activeContainerId;\n if (\n !activeContainerId ||\n knownIds.includes(activeContainerId) ||\n !host.canDiscoverContainerDocuments(activeContainerId)\n ) {\n return knownIds;\n }\n // Explicit refresh also retries an open foreign system container that the\n // background discovery list excludes.\n return [...knownIds, activeContainerId];\n}\n\nfunction startReconciliationLane(\n host: ReconciliationHost,\n state: ReconciliationState,\n): void {\n if (state.active) {\n return;\n }\n state.lifecycleGeneration += 1;\n state.active = true;\n state.lane = getOrCreateDomainSyncCoordinator(host.domainScope).registerLane(\n \"reconciliation:documents\",\n {\n label: \"Device-first document reconciliation\",\n // Document discovery runs in the document phase, after structural\n // container hydration/metadata sync settles for the scope.\n phase: \"document\",\n onUnexpectedError: (error) => {\n console.error(\"Device-first reconciliation failed:\", error);\n },\n // Fixed literal: no container or document id may reach a report. The\n // mapped stack identifies which lane body threw. Returning the host's\n // result hands a rejection to the lane reporter's wrapper; discarding it\n // would surface as an unhandled rejection instead.\n reportUnexpectedError: (error) =>\n host.logError?.(\"Reconciliation: sync lane failed\", error),\n run: () => runReconcileLane(host, state),\n shouldIgnoreError: host.isIgnorableError,\n },\n );\n}\n\nfunction createReconciliationState(\n host: ReconciliationHost,\n): ReconciliationState {\n return {\n active: false,\n activeContainerId: null,\n automaticRetryGenerations: new Map(),\n discoveredContainerIds: new Set(),\n forcedContainerGenerations: new Map(),\n initialDocumentProbe: createInitialDocumentProbe(host),\n lane: null,\n lifecycleGeneration: 0,\n nextForceGeneration: 0,\n probeContinuationCancel: null,\n queue: createReconcileQueue(),\n pendingRefresh: null,\n };\n}\n\nfunction stopReconciliationService(\n host: ReconciliationHost,\n state: ReconciliationState,\n): void {\n clearPendingDiscoveryRetries(state);\n state.active = false;\n state.probeContinuationCancel?.();\n state.probeContinuationCancel = null;\n state.queue.clear();\n state.automaticRetryGenerations.clear();\n state.forcedContainerGenerations.clear();\n state.pendingRefresh = null;\n state.initialDocumentProbe.resetPending();\n // Drop the per-session discovered suppression cache too: a stopped\n // reconciler is being torn down (scope/identity change) or paused across\n // a prerequisite loss, after which every container must be re-validated.\n state.discoveredContainerIds.clear();\n // Drop pending self-echo originations too — they are session-scoped and a\n // teardown invalidates them.\n clearOriginatedDocuments(host.domainScope);\n}\n\nexport function createReconciliationService(\n host: ReconciliationHost,\n): ReconciliationService {\n const state = createReconciliationState(host);\n\n const scheduleDrain = () => {\n if (\n !state.active ||\n (state.queue.size === 0 && !state.initialDocumentProbe.canRun())\n ) {\n return;\n }\n if (!canReconcile(host.getRuntimeStatus())) {\n return;\n }\n state.probeContinuationCancel?.();\n state.probeContinuationCancel = null;\n state.lane?.requestSync();\n };\n\n const enqueueContainer = (\n containerId: string,\n priority: ReconcilePriority,\n force = false,\n ) => {\n if (!containerId) {\n return;\n }\n // Skip containers already reconciled this session unless forced. Events\n // force re-discovery; passive active/backfill scheduling does not, so\n // opening Explorer does not re-fetch every container on each navigation.\n if (!force && state.discoveredContainerIds.has(containerId)) {\n return;\n }\n if (force) {\n markContainerForced(state, containerId);\n }\n state.queue.enqueue(containerId, priority);\n scheduleDrain();\n };\n\n const enqueueIdleBackfill = () => {\n enqueueKnownContainersForIdleBackfill({\n host,\n scheduleDrain,\n state,\n });\n };\n\n return {\n start: () => {\n startReconciliationLane(host, state);\n scheduleDrain();\n },\n // Reconcile the active container first; siblings remain lazy until visited.\n setActiveContainer: (containerId) =>\n activateContainer(state, containerId, (activeId, force) =>\n enqueueContainer(activeId, \"active\", force),\n ),\n enqueueContainer,\n enqueueIdleBackfill,\n resetDiscovered: () => {\n state.discoveredContainerIds.clear();\n state.initialDocumentProbe.resetSkippedListings();\n },\n reconcileRootContainersNow: () =>\n requestContainerRefresh({\n host,\n state,\n refreshTree: host.refreshRootTree,\n listContainerIds: host.listAutomaticRootCatchupContainerIds,\n scope: \"root\",\n }),\n reconcileNow: () =>\n requestContainerRefresh({\n host,\n state,\n refreshTree: host.refreshTree,\n listContainerIds: () => listFullRefreshContainerIds(host, state),\n scope: \"full\",\n }),\n stop: () => stopReconciliationService(host, state),\n };\n}\n"]}
|
|
1
|
+
{"version":3,"file":"service.js","sourceRoot":"","sources":["service.ts"],"names":[],"mappings":"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","sourcesContent":["import {\n getOrCreateDomainSyncCoordinator,\n type SyncLane,\n} from \"../../data/sync/syncCoordinator\";\nimport {\n acknowledgeContainerForce,\n activateContainer,\n enqueueKnownContainersForIdleBackfill,\n type IdleBackfillState,\n markContainerForced,\n} from \"./idleBackfill\";\nimport { createInitialDocumentProbe } from \"./initialDocumentProbe\";\nimport {\n clearPendingDiscoveryRetries,\n isCurrentReconciliationLifecycle,\n reconcileMarkedContainer,\n sweepKnownContainers,\n} from \"./knownContainerSweep\";\nimport { rearmFailedContainer } from \"./laneFailure\";\nimport { clearOriginatedDocuments } from \"./originatedDocuments\";\nimport { createReconcileQueue, type ReconcilePriority } from \"./queue\";\nimport type {\n ReconciliationHost,\n ReconciliationRuntimeStatus,\n ReconciliationService,\n} from \"./serviceTypes\";\n\nfunction canReconcile(status: ReconciliationRuntimeStatus): boolean {\n return status.dbStatus === \"ready\" && status.isAuthenticated && status.online;\n}\n\ntype RefreshScope = \"root\" | \"full\";\n\ninterface ReconciliationState extends IdleBackfillState {\n active: boolean;\n lane: SyncLane | null;\n lifecycleGeneration: number;\n probeContinuationCancel: (() => void) | null;\n pendingRefresh: {\n readonly promise: Promise<void>;\n readonly scope: RefreshScope;\n } | null;\n}\n\nasync function runReconcileLane(\n host: ReconciliationHost,\n state: ReconciliationState,\n): Promise<void> {\n if (!state.active || !canReconcile(host.getRuntimeStatus())) {\n return;\n }\n const lifecycleGeneration = state.lifecycleGeneration;\n\n const containerId = state.queue.dequeue();\n if (!containerId) {\n await state.initialDocumentProbe.run();\n scheduleProbeContinuation(host, state);\n return;\n }\n // Double-check at run time: a container may have been discovered (by an\n // explicit refresh or an earlier lane pass) after it was queued. Mark it\n // discovered up front to collapse concurrent re-enqueues into one fetch, but\n // roll the mark back on failure so a transient error can be retried later.\n const forceGeneration = state.forcedContainerGenerations.get(containerId);\n const shouldForce = forceGeneration !== undefined;\n if (shouldForce || !state.discoveredContainerIds.has(containerId)) {\n state.discoveredContainerIds.add(containerId);\n try {\n const reconciled = await reconcileMarkedContainer(\n host,\n state,\n containerId,\n shouldForce,\n );\n if (reconciled) {\n acknowledgeContainerForce(state, containerId, forceGeneration);\n }\n } catch (error) {\n rearmFailedContainer(\n state,\n containerId,\n forceGeneration,\n lifecycleGeneration,\n );\n throw error;\n }\n }\n\n // Keep draining: schedule another pass while the queue holds work.\n if (state.queue.size > 0) {\n state.lane?.requestSync();\n } else {\n scheduleProbeContinuation(host, state);\n }\n}\n\nfunction scheduleProbeContinuation(\n host: ReconciliationHost,\n state: ReconciliationState,\n): void {\n state.probeContinuationCancel?.();\n const canContinue = () =>\n state.active &&\n state.queue.size === 0 &&\n canReconcile(host.getRuntimeStatus()) &&\n state.initialDocumentProbe.hasPendingWork();\n if (!canContinue()) {\n state.probeContinuationCancel = null;\n return;\n }\n const timeout = setTimeout(() => {\n if (canContinue()) {\n state.probeContinuationCancel = null;\n state.lane?.requestSync();\n }\n }, state.initialDocumentProbe.continuationDelayMs());\n state.probeContinuationCancel = () => clearTimeout(timeout);\n}\n\nfunction forgetIneligibleDiscoveredContainers(\n host: ReconciliationHost,\n state: ReconciliationState,\n): void {\n for (const containerId of state.discoveredContainerIds) {\n if (!host.canDiscoverContainerDocuments(containerId)) {\n state.discoveredContainerIds.delete(containerId);\n }\n }\n}\n\nasync function refreshAndReconcileContainers(input: {\n forceAllDocumentContentPulls: boolean;\n host: ReconciliationHost;\n listContainerIds: () => ReadonlyArray<string>;\n refreshTree: () => Promise<void>;\n state: ReconciliationState;\n}): Promise<void> {\n const { host, listContainerIds, refreshTree, state } = input;\n if (!canReconcile(host.getRuntimeStatus())) {\n return;\n }\n const lifecycleGeneration = state.lifecycleGeneration;\n\n if (input.forceAllDocumentContentPulls) {\n state.queue.clear();\n }\n try {\n // A resync_required structural refresh runs independently of this service.\n // If it already removed a container after the forced document lane won its\n // race against the old tree, forget the stale discovered bit before this\n // refresh can re-add the same id.\n forgetIneligibleDiscoveredContainers(host, state);\n await refreshTree();\n if (!isCurrentReconciliationLifecycle(state, lifecycleGeneration)) {\n return;\n }\n // Structural hydration can revoke and remove a container after a targeted\n // force already reconciled it against the old tree. Forget every id that\n // is no longer remotely listable so a later share of the same container id\n // is treated as newly surfaced instead of being suppressed for the rest of\n // the session.\n forgetIneligibleDiscoveredContainers(host, state);\n await sweepKnownContainers({\n forceAllDocumentContentPulls: input.forceAllDocumentContentPulls,\n host,\n knownIds: listContainerIds(),\n lifecycleGeneration,\n state,\n });\n if (!isCurrentReconciliationLifecycle(state, lifecycleGeneration)) {\n return;\n }\n } catch (error) {\n if (!isCurrentReconciliationLifecycle(state, lifecycleGeneration)) {\n return;\n }\n if (!host.isIgnorableError(error)) {\n throw error;\n }\n }\n}\n\n// Sweeps share the queue and discovered set, so they must run serially.\n// Root requests join either kind of sweep; a full request after a root sweep\n// must wait for it and then refresh the whole tree.\nfunction requestContainerRefresh(input: {\n host: ReconciliationHost;\n state: ReconciliationState;\n refreshTree: () => Promise<void>;\n listContainerIds: () => ReadonlyArray<string>;\n scope: RefreshScope;\n}): Promise<void> {\n const { state, scope } = input;\n const pending = state.pendingRefresh;\n if (pending && (scope === \"root\" || pending.scope === \"full\")) {\n return pending.promise;\n }\n\n const promise = (pending?.promise ?? Promise.resolve())\n // A failed root sweep must not prevent the queued full refresh.\n .catch(() => undefined)\n .then(() =>\n refreshAndReconcileContainers({\n forceAllDocumentContentPulls: scope === \"full\",\n host: input.host,\n listContainerIds: input.listContainerIds,\n refreshTree: input.refreshTree,\n state,\n }),\n )\n .finally(() => {\n if (state.pendingRefresh?.promise === promise) {\n state.pendingRefresh = null;\n }\n });\n state.pendingRefresh = { promise, scope };\n return promise;\n}\n\nfunction listFullRefreshContainerIds(\n host: ReconciliationHost,\n state: ReconciliationState,\n): ReadonlyArray<string> {\n const knownIds = host.listKnownContainerIds();\n const activeContainerId = state.activeContainerId;\n if (\n !activeContainerId ||\n knownIds.includes(activeContainerId) ||\n !host.canDiscoverContainerDocuments(activeContainerId)\n ) {\n return knownIds;\n }\n // Explicit refresh also retries an open foreign system container that the\n // background discovery list excludes.\n return [...knownIds, activeContainerId];\n}\n\nfunction startReconciliationLane(\n host: ReconciliationHost,\n state: ReconciliationState,\n): void {\n if (state.active) {\n return;\n }\n state.lifecycleGeneration += 1;\n state.active = true;\n state.lane = getOrCreateDomainSyncCoordinator(host.domainScope).registerLane(\n \"reconciliation:documents\",\n {\n label: \"Device-first document reconciliation\",\n // Document discovery runs in the document phase, after structural\n // container hydration/metadata sync settles for the scope.\n phase: \"document\",\n priority: () => (state.activeContainerId ? -1 : 0),\n onUnexpectedError: (error) => {\n console.error(\"Device-first reconciliation failed:\", error);\n },\n // Fixed literal: no container or document id may reach a report. The\n // mapped stack identifies which lane body threw. Returning the host's\n // result hands a rejection to the lane reporter's wrapper; discarding it\n // would surface as an unhandled rejection instead.\n reportUnexpectedError: (error) =>\n host.logError?.(\"Reconciliation: sync lane failed\", error),\n run: () => runReconcileLane(host, state),\n shouldIgnoreError: host.isIgnorableError,\n },\n );\n}\n\nfunction createReconciliationState(\n host: ReconciliationHost,\n): ReconciliationState {\n return {\n active: false,\n activeContainerId: null,\n automaticRetryGenerations: new Map(),\n discoveredContainerIds: new Set(),\n forcedContainerGenerations: new Map(),\n forcedDocumentIds: new Map(),\n initialDocumentProbe: createInitialDocumentProbe(host),\n lane: null,\n lifecycleGeneration: 0,\n nextForceGeneration: 0,\n probeContinuationCancel: null,\n queue: createReconcileQueue(),\n pendingRefresh: null,\n };\n}\n\nfunction stopReconciliationService(\n host: ReconciliationHost,\n state: ReconciliationState,\n): void {\n clearPendingDiscoveryRetries(state);\n state.active = false;\n state.probeContinuationCancel?.();\n state.probeContinuationCancel = null;\n state.queue.clear();\n state.automaticRetryGenerations.clear();\n state.forcedContainerGenerations.clear();\n state.forcedDocumentIds.clear();\n state.pendingRefresh = null;\n state.initialDocumentProbe.resetPending();\n // Drop the per-session discovered suppression cache too: a stopped\n // reconciler is being torn down (scope/identity change) or paused across\n // a prerequisite loss, after which every container must be re-validated.\n state.discoveredContainerIds.clear();\n // Drop pending self-echo originations too — they are session-scoped and a\n // teardown invalidates them.\n clearOriginatedDocuments(host.domainScope);\n}\n\nexport function createReconciliationService(\n host: ReconciliationHost,\n): ReconciliationService {\n const state = createReconciliationState(host);\n\n const scheduleDrain = () => {\n if (\n !state.active ||\n (state.queue.size === 0 && !state.initialDocumentProbe.canRun())\n ) {\n return;\n }\n if (!canReconcile(host.getRuntimeStatus())) {\n return;\n }\n state.probeContinuationCancel?.();\n state.probeContinuationCancel = null;\n state.lane?.requestSync();\n };\n\n const enqueueContainer = (\n containerId: string,\n priority: ReconcilePriority,\n force = false,\n documentId?: string,\n ) => {\n if (!containerId) {\n return;\n }\n // Skip containers already reconciled this session unless forced. Events\n // force re-discovery; passive active/backfill scheduling does not, so\n // opening Explorer does not re-fetch every container on each navigation.\n if (\n !force &&\n !state.forcedContainerGenerations.has(containerId) &&\n state.discoveredContainerIds.has(containerId)\n ) {\n return;\n }\n if (force) {\n markContainerForced(state, containerId, documentId);\n }\n state.queue.enqueue(containerId, priority);\n scheduleDrain();\n };\n\n const enqueueIdleBackfill = () => {\n enqueueKnownContainersForIdleBackfill({\n host,\n scheduleDrain,\n state,\n });\n };\n\n return {\n start: () => {\n startReconciliationLane(host, state);\n scheduleDrain();\n },\n // Reconcile the active container first; siblings remain lazy until visited.\n setActiveContainer: (containerId) =>\n activateContainer(state, containerId, (activeId) =>\n enqueueContainer(activeId, \"active\"),\n ),\n enqueueContainer,\n enqueueIdleBackfill,\n resetDiscovered: () => {\n state.discoveredContainerIds.clear();\n state.initialDocumentProbe.resetSkippedListings();\n },\n reconcileRootContainersNow: () =>\n requestContainerRefresh({\n host,\n state,\n refreshTree: host.refreshRootTree,\n listContainerIds: host.listAutomaticRootCatchupContainerIds,\n scope: \"root\",\n }),\n reconcileNow: () =>\n requestContainerRefresh({\n host,\n state,\n refreshTree: host.refreshTree,\n listContainerIds: () => listFullRefreshContainerIds(host, state),\n scope: \"full\",\n }),\n stop: () => stopReconciliationService(host, state),\n };\n}\n"]}
|
|
@@ -33,9 +33,10 @@ export interface ReconciliationHost extends InitialDocumentProbeHost {
|
|
|
33
33
|
* refresh) revalidates registered ordinary documents and retries system
|
|
34
34
|
* documents; otherwise only unopened system documents are synced, so a
|
|
35
35
|
* background pass can materialize projection-owned content without eagerly
|
|
36
|
-
* opening every ordinary document.
|
|
36
|
+
* opening every ordinary document. A documentIds set limits forced pulls to
|
|
37
|
+
* hint-named documents while still discovering new system content.
|
|
37
38
|
*/
|
|
38
|
-
requestDocumentContentPull: (containerId: string, documents: ReadonlyArray<DocumentSummary>, force: boolean) => void;
|
|
39
|
+
requestDocumentContentPull: (containerId: string, documents: ReadonlyArray<DocumentSummary>, force: boolean, documentIds?: ReadonlySet<string>) => void;
|
|
39
40
|
/** Refresh the container tree from the server (explicit refresh). */
|
|
40
41
|
refreshTree: () => Promise<void>;
|
|
41
42
|
/** Refresh only the top-level root lane from the server. */
|
|
@@ -48,7 +49,8 @@ export interface ReconciliationHost extends InitialDocumentProbeHost {
|
|
|
48
49
|
export interface ReconciliationService {
|
|
49
50
|
start: () => void;
|
|
50
51
|
setActiveContainer: (containerId: string | null) => void;
|
|
51
|
-
|
|
52
|
+
/** Force a listing; documentId limits content revalidation to that hint. */
|
|
53
|
+
enqueueContainer: (containerId: string, priority: ReconcilePriority, force?: boolean, documentId?: string) => void;
|
|
52
54
|
/** Queue known containers at idle priority. */
|
|
53
55
|
enqueueIdleBackfill: () => void;
|
|
54
56
|
/**
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"serviceTypes.js","sourceRoot":"","sources":["serviceTypes.ts"],"names":[],"mappings":"","sourcesContent":["import type { DocumentSummary } from \"../../data/documents/documentSummary\";\nimport type { DomainScope } from \"../../data/domainScope\";\nimport type { LocalProjectionReconciledDelta } from \"../../stores/local-projection\";\nimport type { InitialDocumentProbeHost } from \"./initialDocumentProbe\";\nimport type { ReconcilePriority } from \"./queue\";\n\nexport interface ReconciliationRuntimeStatus {\n dbStatus: string;\n isAuthenticated: boolean;\n online: boolean;\n}\n\n/**\n * Host contract injected by the device-first facade. Keeps the reconciler\n * React-free and decoupled from runtime/persistence construction — it only\n * orchestrates *when* these run and routes their results into Layer A.\n */\nexport interface ReconciliationHost extends InitialDocumentProbeHost {\n domainScope: DomainScope;\n getRuntimeStatus: () => ReconciliationRuntimeStatus;\n /** True when this container has enough remote state to list documents. */\n canDiscoverContainerDocuments: (containerId: string) => boolean;\n /** Known container ids eligible for idle document reconciliation. */\n listKnownContainerIds: () => ReadonlyArray<string>;\n /** Known ids eligible after an automatic root-lane discovery hint. */\n listAutomaticRootCatchupContainerIds: () => ReadonlyArray<string>;\n /** Discover + persist documents; `null` means the lane was not fully listed. */\n discoverContainerDocuments: (\n containerId: string,\n onFullListing?: ((documentIds: ReadonlyArray<string>) => void) | undefined,\n onPendingDiscovery?: ((delayMs: number) => void) | undefined,\n ) => Promise<ReadonlyArray<DocumentSummary> | null>;\n /** Read a container's freshly-persisted summaries+links from SQLite. */\n loadContainerDelta: (\n containerId: string,\n ) => Promise<LocalProjectionReconciledDelta>;\n /** Push a reconciled delta into the local projection store. */\n applyReconciled: (delta: LocalProjectionReconciledDelta) => void;\n /**\n * Sync document bodies for a reconciled container. `force` (an explicit\n * refresh) revalidates registered ordinary documents and retries system\n * documents; otherwise only unopened system documents are synced, so a\n * background pass can materialize projection-owned content without eagerly\n * opening every ordinary document.\n */\n requestDocumentContentPull: (\n containerId: string,\n documents: ReadonlyArray<DocumentSummary>,\n force: boolean,\n ) => void;\n /** Refresh the container tree from the server (explicit refresh). */\n refreshTree: () => Promise<void>;\n /** Refresh only the top-level root lane from the server. */\n refreshRootTree: () => Promise<void>;\n /** True if the destroyed-db error should be swallowed rather than surfaced. */\n isIgnorableError: (error: unknown) => boolean;\n /** Host diagnostics sink; bare runtimes do not structure errors. */\n logError?: ((message: string, error: unknown) => void) | undefined;\n}\n\nexport interface ReconciliationService {\n start: () => void;\n setActiveContainer: (containerId: string | null) => void;\n enqueueContainer: (\n containerId: string,\n priority: ReconcilePriority,\n force?: boolean,\n ) => void;\n /** Queue known containers at idle priority. */\n enqueueIdleBackfill: () => void;\n /**\n * Forget which containers were reconciled this session so the next enqueue of\n * each re-validates against the server exactly once. Call on the\n * cannot-reconcile → can-reconcile edge (relogin/reconnect): the discovered\n * set is a per-session suppression cache, a previously-visited container may\n * have changed remotely, and initial-probe listings that exhausted their\n * retries need a fresh opportunity once the server is reachable again.\n */\n resetDiscovered: () => void;\n reconcileRootContainersNow: () => Promise<void>;\n reconcileNow: () => Promise<void>;\n stop: () => void;\n}\n"]}
|
|
1
|
+
{"version":3,"file":"serviceTypes.js","sourceRoot":"","sources":["serviceTypes.ts"],"names":[],"mappings":"","sourcesContent":["import type { DocumentSummary } from \"../../data/documents/documentSummary\";\nimport type { DomainScope } from \"../../data/domainScope\";\nimport type { LocalProjectionReconciledDelta } from \"../../stores/local-projection\";\nimport type { InitialDocumentProbeHost } from \"./initialDocumentProbe\";\nimport type { ReconcilePriority } from \"./queue\";\n\nexport interface ReconciliationRuntimeStatus {\n dbStatus: string;\n isAuthenticated: boolean;\n online: boolean;\n}\n\n/**\n * Host contract injected by the device-first facade. Keeps the reconciler\n * React-free and decoupled from runtime/persistence construction — it only\n * orchestrates *when* these run and routes their results into Layer A.\n */\nexport interface ReconciliationHost extends InitialDocumentProbeHost {\n domainScope: DomainScope;\n getRuntimeStatus: () => ReconciliationRuntimeStatus;\n /** True when this container has enough remote state to list documents. */\n canDiscoverContainerDocuments: (containerId: string) => boolean;\n /** Known container ids eligible for idle document reconciliation. */\n listKnownContainerIds: () => ReadonlyArray<string>;\n /** Known ids eligible after an automatic root-lane discovery hint. */\n listAutomaticRootCatchupContainerIds: () => ReadonlyArray<string>;\n /** Discover + persist documents; `null` means the lane was not fully listed. */\n discoverContainerDocuments: (\n containerId: string,\n onFullListing?: ((documentIds: ReadonlyArray<string>) => void) | undefined,\n onPendingDiscovery?: ((delayMs: number) => void) | undefined,\n ) => Promise<ReadonlyArray<DocumentSummary> | null>;\n /** Read a container's freshly-persisted summaries+links from SQLite. */\n loadContainerDelta: (\n containerId: string,\n ) => Promise<LocalProjectionReconciledDelta>;\n /** Push a reconciled delta into the local projection store. */\n applyReconciled: (delta: LocalProjectionReconciledDelta) => void;\n /**\n * Sync document bodies for a reconciled container. `force` (an explicit\n * refresh) revalidates registered ordinary documents and retries system\n * documents; otherwise only unopened system documents are synced, so a\n * background pass can materialize projection-owned content without eagerly\n * opening every ordinary document. A documentIds set limits forced pulls to\n * hint-named documents while still discovering new system content.\n */\n requestDocumentContentPull: (\n containerId: string,\n documents: ReadonlyArray<DocumentSummary>,\n force: boolean,\n documentIds?: ReadonlySet<string>,\n ) => void;\n /** Refresh the container tree from the server (explicit refresh). */\n refreshTree: () => Promise<void>;\n /** Refresh only the top-level root lane from the server. */\n refreshRootTree: () => Promise<void>;\n /** True if the destroyed-db error should be swallowed rather than surfaced. */\n isIgnorableError: (error: unknown) => boolean;\n /** Host diagnostics sink; bare runtimes do not structure errors. */\n logError?: ((message: string, error: unknown) => void) | undefined;\n}\n\nexport interface ReconciliationService {\n start: () => void;\n setActiveContainer: (containerId: string | null) => void;\n /** Force a listing; documentId limits content revalidation to that hint. */\n enqueueContainer: (\n containerId: string,\n priority: ReconcilePriority,\n force?: boolean,\n documentId?: string,\n ) => void;\n /** Queue known containers at idle priority. */\n enqueueIdleBackfill: () => void;\n /**\n * Forget which containers were reconciled this session so the next enqueue of\n * each re-validates against the server exactly once. Call on the\n * cannot-reconcile → can-reconcile edge (relogin/reconnect): the discovered\n * set is a per-session suppression cache, a previously-visited container may\n * have changed remotely, and initial-probe listings that exhausted their\n * retries need a fresh opportunity once the server is reachable again.\n */\n resetDiscovered: () => void;\n reconcileRootContainersNow: () => Promise<void>;\n reconcileNow: () => Promise<void>;\n stop: () => void;\n}\n"]}
|
|
@@ -13,7 +13,8 @@ export declare function connectReconciliationTriggers(input: {
|
|
|
13
13
|
/**
|
|
14
14
|
* Translate a batch of server events into reconciler work. Containers named by
|
|
15
15
|
* document content or structural mutation events are reconciled at active
|
|
16
|
-
* priority
|
|
16
|
+
* priority; content pulls target only the event's document. Document updates
|
|
17
|
+
* must carry the current container-scoped hint shape.
|
|
17
18
|
*
|
|
18
19
|
* `domainScope`, when provided, suppresses self-echoes: a `document_update_created`
|
|
19
20
|
* whose documentId this client just originated (created/uploaded) is dropped
|
|
@@ -3,7 +3,7 @@ import { consumeOriginatedDocument } from "./originatedDocuments.js";
|
|
|
3
3
|
function enqueueKnownEventContainers(input) {
|
|
4
4
|
for (const containerId of input.containerIds) {
|
|
5
5
|
if (input.knownContainerIds.has(containerId)) {
|
|
6
|
-
input.service.enqueueContainer(containerId, "active", true);
|
|
6
|
+
input.service.enqueueContainer(containerId, "active", true, input.documentId);
|
|
7
7
|
}
|
|
8
8
|
}
|
|
9
9
|
}
|
|
@@ -49,7 +49,8 @@ export function connectReconciliationTriggers(input) {
|
|
|
49
49
|
/**
|
|
50
50
|
* Translate a batch of server events into reconciler work. Containers named by
|
|
51
51
|
* document content or structural mutation events are reconciled at active
|
|
52
|
-
* priority
|
|
52
|
+
* priority; content pulls target only the event's document. Document updates
|
|
53
|
+
* must carry the current container-scoped hint shape.
|
|
53
54
|
*
|
|
54
55
|
* `domainScope`, when provided, suppresses self-echoes: a `document_update_created`
|
|
55
56
|
* whose documentId this client just originated (created/uploaded) is dropped
|
|
@@ -67,6 +68,7 @@ export function enqueueReconciliationForEvents(input) {
|
|
|
67
68
|
// while another session for the same identity must reconcile both sides.
|
|
68
69
|
enqueueKnownEventContainers({
|
|
69
70
|
containerIds: event.containerIds,
|
|
71
|
+
documentId: event.documentId,
|
|
70
72
|
knownContainerIds: knownContainerIdSet,
|
|
71
73
|
service,
|
|
72
74
|
});
|
|
@@ -85,6 +87,7 @@ export function enqueueReconciliationForEvents(input) {
|
|
|
85
87
|
// container was already reconciled this session.
|
|
86
88
|
enqueueKnownEventContainers({
|
|
87
89
|
containerIds: event.containerIds,
|
|
90
|
+
documentId: event.documentId,
|
|
88
91
|
knownContainerIds: knownContainerIdSet,
|
|
89
92
|
service,
|
|
90
93
|
});
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"triggers.js","sourceRoot":"","sources":["triggers.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,8BAA8B,EAC9B,4BAA4B,GAC7B,MAAM,mCAAmC,CAAC;AAG3C,OAAO,EAAE,yBAAyB,EAAE,MAAM,uBAAuB,CAAC;AAGlE,SAAS,2BAA2B,CAAC,
|
|
1
|
+
{"version":3,"file":"triggers.js","sourceRoot":"","sources":["triggers.ts"],"names":[],"mappings":"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","sourcesContent":["import {\n isDocumentMutationCreatedEvent,\n isDocumentUpdateCreatedEvent,\n} from \"../../data/documents/documentSync\";\nimport type { DomainScope } from \"../../data/domainScope\";\nimport type { LocalProjectionStore } from \"../../stores/local-projection\";\nimport { consumeOriginatedDocument } from \"./originatedDocuments\";\nimport type { ReconciliationService } from \"./serviceTypes\";\n\nfunction enqueueKnownEventContainers(input: {\n readonly containerIds: ReadonlyArray<string>;\n readonly documentId: string;\n readonly knownContainerIds: ReadonlySet<string>;\n readonly service: ReconciliationService;\n}): void {\n for (const containerId of input.containerIds) {\n if (input.knownContainerIds.has(containerId)) {\n input.service.enqueueContainer(\n containerId,\n \"active\",\n true,\n input.documentId,\n );\n }\n }\n}\n\n/**\n * Wire the local projection store's reconcile signals into the background\n * reconciler. Returns an unsubscribe handle. This is the only place Layer A and\n * Layer B are connected: signals in, scheduling out — no React involved.\n */\nexport function connectReconciliationTriggers(input: {\n store: LocalProjectionStore;\n service: ReconciliationService;\n}): () => void {\n const { service, store } = input;\n\n return store.onReconcileSignal((signal) => {\n switch (signal.reason) {\n case \"active-changed\":\n service.setActiveContainer(signal.activeContainerId);\n break;\n case \"hydrated\":\n if (signal.activeContainerId) {\n service.enqueueContainer(signal.activeContainerId, \"active\");\n }\n break;\n case \"remote-containers-added\":\n // A cold-cache tree crawl can finish after the auth-edge backfill was\n // queued. Pick up the newly materialized ids without resetting the\n // discovered suppression set for containers already reconciled.\n service.enqueueIdleBackfill();\n break;\n case \"prerequisites-regained\":\n // Auth/connectivity was just regained (relogin or reconnect). Forget the\n // per-session discovered set first: containers visited before the gap\n // may have changed remotely while this client was offline, and the\n // enqueue calls below are otherwise suppressed for already-discovered\n // containers. Resetting makes the active container and every backfill\n // target re-validate against the server exactly once.\n service.resetDiscovered();\n if (signal.activeContainerId) {\n service.enqueueContainer(signal.activeContainerId, \"active\");\n }\n service.enqueueIdleBackfill();\n break;\n }\n });\n}\n\n/**\n * Translate a batch of server events into reconciler work. Containers named by\n * document content or structural mutation events are reconciled at active\n * priority; content pulls target only the event's document. Document updates\n * must carry the current container-scoped hint shape.\n *\n * `domainScope`, when provided, suppresses self-echoes: a `document_update_created`\n * whose documentId this client just originated (created/uploaded) is dropped\n * once, since reconciling it would only re-fetch a delta the client already has.\n * See {@link consumeOriginatedDocument}.\n */\nexport function enqueueReconciliationForEvents(input: {\n domainScope?: DomainScope;\n events: ReadonlyArray<unknown>;\n knownContainerIds: ReadonlyArray<string>;\n service: ReconciliationService;\n}): void {\n const { domainScope, events, knownContainerIds, service } = input;\n const knownContainerIdSet = new Set(knownContainerIds);\n\n for (const event of events) {\n if (isDocumentMutationCreatedEvent(event)) {\n // A link-set mutation changes membership even when the document is\n // already present locally. Never apply content-origination self-echo\n // suppression here: the server excludes only the exact authoring session,\n // while another session for the same identity must reconcile both sides.\n enqueueKnownEventContainers({\n containerIds: event.containerIds,\n documentId: event.documentId,\n knownContainerIds: knownContainerIdSet,\n service,\n });\n continue;\n }\n if (!isDocumentUpdateCreatedEvent(event)) {\n continue;\n }\n // Self-echo of this client's own create/upload: consume the origination and\n // skip. Single-use, so a genuine later remote change still reconciles.\n if (\n domainScope !== undefined &&\n consumeOriginatedDocument(domainScope, event.documentId)\n ) {\n continue;\n }\n // Events signal fresh remote data, so force re-discovery even if this\n // container was already reconciled this session.\n enqueueKnownEventContainers({\n containerIds: event.containerIds,\n documentId: event.documentId,\n knownContainerIds: knownContainerIdSet,\n service,\n });\n }\n}\n"]}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { BlobBytesResponse } from "../../internal/api-client/index.js";
|
|
1
|
+
import type { BlobBytesResponse, RequestResultOptions } from "../../internal/api-client/index.js";
|
|
2
2
|
import type { BlobAttachmentSummary, DocumentWriterProjectionResponse, ListDocumentAttachmentsResponse } from "../../internal/validators/response/index.js";
|
|
3
3
|
import type { BlobBytes } from "../../data/blobContracts.js";
|
|
4
4
|
import type { DocumentAttachment } from "../../data/documents/documentContent.js";
|
|
@@ -7,7 +7,7 @@ import type { SecurityIncidentReporter } from "../../data/securityIncidents.js";
|
|
|
7
7
|
import type { ExecSql } from "../../data/sqlite/sqlSchema.js";
|
|
8
8
|
interface DocumentAttachmentHydrationApi {
|
|
9
9
|
evictDocumentWriterProjection?(documentId: string): void;
|
|
10
|
-
getBlobBytes(blobId: string): Promise<BlobBytesResponse | null>;
|
|
10
|
+
getBlobBytes(blobId: string, options?: RequestResultOptions): Promise<BlobBytesResponse | null>;
|
|
11
11
|
getDocumentWriterProjection(documentId: string): Promise<DocumentWriterProjectionResponse | null>;
|
|
12
12
|
listDocumentAttachments(documentId: string): Promise<ListDocumentAttachmentsResponse | null>;
|
|
13
13
|
}
|
|
@@ -50,5 +50,7 @@ interface DocumentAttachmentHydrationContext {
|
|
|
50
50
|
}
|
|
51
51
|
export declare function hydrateDocumentAttachmentBlobs(input: DocumentAttachmentHydrationContext & {
|
|
52
52
|
attachments: ReadonlyArray<DocumentAttachment>;
|
|
53
|
+
/** No binding target remains unchecked or unavailable in this pass. */
|
|
54
|
+
onBindingsChecked?: () => void;
|
|
53
55
|
}): Promise<HydratedDocumentAttachmentBlob[] | null>;
|
|
54
56
|
export {};
|
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
import { bytesToHex } from "../../internal/crypto/index.js";
|
|
2
|
+
import { runBlobTransfer } from "../../data/blobs/transferPool.js";
|
|
2
3
|
import { attachmentContentSha256 } from "../../data/documents/attachmentContentIdentity.js";
|
|
3
4
|
import { errorMessage } from "../../data/errorMessage.js";
|
|
4
5
|
import { createAttachmentDecryptor } from "./attachmentDecryptor.js";
|
|
5
6
|
import { collectHydrationResults } from "./hydrationResults.js";
|
|
7
|
+
import { readAttachmentTransfer } from "./readAttachmentTransfer.js";
|
|
6
8
|
function servedBindingRejectionKey(attachment, binding) {
|
|
7
9
|
return JSON.stringify([
|
|
8
10
|
attachment.slotId,
|
|
@@ -10,9 +12,6 @@ function servedBindingRejectionKey(attachment, binding) {
|
|
|
10
12
|
attachment.contentSha256,
|
|
11
13
|
]);
|
|
12
14
|
}
|
|
13
|
-
async function readBlobStreamBytes(blob) {
|
|
14
|
-
return new Uint8Array(await new Response(blob.encryptedBytes).arrayBuffer());
|
|
15
|
-
}
|
|
16
15
|
async function hasExpectedBlobSha256(blob, encryptedBytes) {
|
|
17
16
|
const blobDigest = new Uint8Array(await crypto.subtle.digest("SHA-256", encryptedBytes));
|
|
18
17
|
return bytesToHex(blobDigest) === blob.sha256;
|
|
@@ -34,13 +33,18 @@ function shouldHydrateAttachment(input) {
|
|
|
34
33
|
async function loadDocumentAttachmentBlob(input) {
|
|
35
34
|
const { apiClient, binding, log } = input;
|
|
36
35
|
const logPrefix = input.logPrefix ?? "Documents";
|
|
37
|
-
|
|
36
|
+
if (input.stillCurrent?.() === false)
|
|
37
|
+
return null;
|
|
38
|
+
input.signal.throwIfAborted();
|
|
39
|
+
const blob = await apiClient.getBlobBytes(binding.blobId, {
|
|
40
|
+
signal: input.signal,
|
|
41
|
+
});
|
|
38
42
|
if (!blob) {
|
|
39
43
|
return null;
|
|
40
44
|
}
|
|
41
45
|
let encryptedBytes;
|
|
42
46
|
try {
|
|
43
|
-
encryptedBytes = await
|
|
47
|
+
encryptedBytes = await readAttachmentTransfer(blob, input.signal, input.progress);
|
|
44
48
|
}
|
|
45
49
|
catch (error) {
|
|
46
50
|
log?.(`${logPrefix}: blob ${binding.blobId} failed to stream during hydration: ${errorMessage(error)}`);
|
|
@@ -54,6 +58,7 @@ async function loadDocumentAttachmentBlob(input) {
|
|
|
54
58
|
log?.(`${logPrefix}: blob ${binding.blobId} sha256 mismatch during hydration.`);
|
|
55
59
|
return null;
|
|
56
60
|
}
|
|
61
|
+
input.signal.throwIfAborted();
|
|
57
62
|
return {
|
|
58
63
|
blobId: binding.blobId,
|
|
59
64
|
encryptedBytes,
|
|
@@ -70,7 +75,12 @@ async function loadUniqueDocumentAttachmentBlobs(input) {
|
|
|
70
75
|
...input,
|
|
71
76
|
tasks: Array.from(targetsByBlobId.values()).map((target) => ({
|
|
72
77
|
blobId: target.binding.blobId,
|
|
73
|
-
run: () =>
|
|
78
|
+
run: () => runBlobTransfer(input.apiClient, (signal, progress) => loadDocumentAttachmentBlob({
|
|
79
|
+
...input,
|
|
80
|
+
binding: target.binding,
|
|
81
|
+
signal,
|
|
82
|
+
progress,
|
|
83
|
+
})),
|
|
74
84
|
})),
|
|
75
85
|
});
|
|
76
86
|
return new Map(loadedBlobs.flatMap((loaded) => loaded ? [[loaded.blobId, loaded]] : []));
|
|
@@ -143,6 +153,7 @@ export async function hydrateDocumentAttachmentBlobs(input) {
|
|
|
143
153
|
: [];
|
|
144
154
|
});
|
|
145
155
|
if (hydrationTargets.length === 0) {
|
|
156
|
+
input.onBindingsChecked?.();
|
|
146
157
|
return [];
|
|
147
158
|
}
|
|
148
159
|
const loadedBlobs = applyLoadedBlobBytesToHydrationTargets(hydrationTargets, await loadUniqueDocumentAttachmentBlobs({
|
|
@@ -171,6 +182,9 @@ export async function hydrateDocumentAttachmentBlobs(input) {
|
|
|
171
182
|
}),
|
|
172
183
|
})),
|
|
173
184
|
});
|
|
174
|
-
|
|
185
|
+
const complete = hydrated.filter((item) => item !== null);
|
|
186
|
+
if (complete.length === hydrationTargets.length)
|
|
187
|
+
input.onBindingsChecked?.();
|
|
188
|
+
return complete;
|
|
175
189
|
}
|
|
176
190
|
//# sourceMappingURL=hydrate.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"hydrate.js","sourceRoot":"","sources":["hydrate.ts"],"names":[],"mappings":"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","sourcesContent":["import type { BlobBytesResponse } from \"@tearleads/api-client\";\nimport { bytesToHex } from \"@tearleads/crypto\";\nimport type {\n BlobAttachmentSummary,\n DocumentWriterProjectionResponse,\n ListDocumentAttachmentsResponse,\n} from \"@tearleads/validators/response\";\nimport type { BlobBytes } from \"../../data/blobContracts\";\nimport { attachmentContentSha256 } from \"../../data/documents/attachmentContentIdentity\";\nimport type { DocumentAttachment } from \"../../data/documents/documentContent\";\nimport { errorMessage } from \"../../data/errorMessage\";\nimport type {\n ProjectionUserKeyResolver,\n ReferencedPrincipalPolicyWarmer,\n} from \"../../data/keyingProjectionVerification\";\nimport type { SecurityIncidentReporter } from \"../../data/securityIncidents\";\nimport type { ExecSql } from \"../../data/sqlite/sqlSchema\";\nimport { createAttachmentDecryptor } from \"./attachmentDecryptor\";\nimport { collectHydrationResults } from \"./hydrationResults\";\n\ninterface DocumentAttachmentHydrationApi {\n evictDocumentWriterProjection?(documentId: string): void;\n getBlobBytes(blobId: string): Promise<BlobBytesResponse | null>;\n getDocumentWriterProjection(\n documentId: string,\n ): Promise<DocumentWriterProjectionResponse | null>;\n listDocumentAttachments(\n documentId: string,\n ): Promise<ListDocumentAttachmentsResponse | null>;\n}\n\ninterface HydratedDocumentAttachmentBlob {\n attachment: DocumentAttachment;\n binding: BlobAttachmentSummary;\n bytes: BlobBytes;\n /** Plaintext digest of `bytes`. */\n contentSha256: string;\n /**\n * The served binding is validly signed but its plaintext digest differs\n * from the digest the document content records for the slot. Set only when\n * the slot held no local bytes: the bind and the content update carrying\n * its digest are two server writes, and an uploader lost between them must\n * not leave the attachment permanently invisible. Never a security incident;\n * an honest crash window is indistinguishable from a served rollback.\n */\n intentMismatch: boolean;\n storageKey: string;\n}\n\ninterface DocumentAttachmentHydrationContext {\n apiClient: DocumentAttachmentHydrationApi;\n documentId: string;\n execSql: ExecSql;\n localBlobIdBySlotId?:\n | Readonly<Record<string, string | null | undefined>>\n | undefined;\n localStorageKeyBySlotId?: Readonly<Record<string, string | undefined>>;\n log?: ((message: string) => void) | undefined;\n logPrefix?: string | undefined;\n /**\n * Served bindings already downloaded and refused for a held copy, keyed per\n * (slot, binding, intent). Populated here; a later pass skips the download\n * until the binding or the document intent changes.\n */\n rejectedServedBindings?: Set<string> | undefined;\n resolveProjectionUserKey: ProjectionUserKeyResolver;\n reportSecurityIncident?: SecurityIncidentReporter | undefined;\n targetSecretKey: Uint8Array;\n stillCurrent?: (() => boolean) | undefined;\n warmReferencedPrincipalPolicies?: ReferencedPrincipalPolicyWarmer | undefined;\n}\n\nfunction servedBindingRejectionKey(\n attachment: DocumentAttachment,\n binding: BlobAttachmentSummary,\n): string {\n return JSON.stringify([\n attachment.slotId,\n binding.bindingId,\n attachment.contentSha256,\n ]);\n}\n\ninterface DocumentAttachmentHydrationTarget {\n attachment: DocumentAttachment;\n binding: BlobAttachmentSummary;\n}\n\ninterface LoadedDocumentAttachmentBlob\n extends DocumentAttachmentHydrationTarget {\n encryptedBytes: Uint8Array<ArrayBuffer>;\n}\n\ninterface LoadedDocumentAttachmentBlobBytes {\n blobId: string;\n encryptedBytes: Uint8Array<ArrayBuffer>;\n}\n\nasync function readBlobStreamBytes(\n blob: BlobBytesResponse,\n): Promise<Uint8Array<ArrayBuffer>> {\n return new Uint8Array(await new Response(blob.encryptedBytes).arrayBuffer());\n}\n\nasync function hasExpectedBlobSha256(\n blob: BlobBytesResponse,\n encryptedBytes: Uint8Array<ArrayBuffer>,\n): Promise<boolean> {\n const blobDigest = new Uint8Array(\n await crypto.subtle.digest(\"SHA-256\", encryptedBytes),\n );\n\n return bytesToHex(blobDigest) === blob.sha256;\n}\n\nfunction shouldHydrateAttachment(input: {\n attachment: DocumentAttachment;\n binding: BlobAttachmentSummary;\n localBlobIdBySlotId:\n | Readonly<Record<string, string | null | undefined>>\n | undefined;\n localStorageKeyBySlotId:\n | Readonly<Record<string, string | undefined>>\n | undefined;\n rejectedServedBindings: ReadonlySet<string> | undefined;\n}): boolean {\n const slotId = input.attachment.slotId;\n const localStorageKey = input.localStorageKeyBySlotId?.[slotId];\n // An empty slot always hydrates: a rejection only ever protected a held\n // copy, so once that copy is gone the served binding is shown (flagged).\n if (!localStorageKey) {\n return true;\n }\n if (\n input.rejectedServedBindings?.has(\n servedBindingRejectionKey(input.attachment, input.binding),\n )\n ) {\n return false;\n }\n\n const localBlobId = input.localBlobIdBySlotId?.[slotId];\n return localBlobId !== null && localBlobId !== input.binding.blobId;\n}\n\nasync function loadDocumentAttachmentBlob(\n input: DocumentAttachmentHydrationContext & {\n binding: BlobAttachmentSummary;\n },\n): Promise<LoadedDocumentAttachmentBlobBytes | null> {\n const { apiClient, binding, log } = input;\n const logPrefix = input.logPrefix ?? \"Documents\";\n const blob = await apiClient.getBlobBytes(binding.blobId);\n if (!blob) {\n return null;\n }\n\n let encryptedBytes: Uint8Array<ArrayBuffer>;\n try {\n encryptedBytes = await readBlobStreamBytes(blob);\n } catch (error) {\n log?.(\n `${logPrefix}: blob ${binding.blobId} failed to stream during hydration: ${errorMessage(\n error,\n )}`,\n );\n return null;\n }\n\n if (encryptedBytes.byteLength !== blob.byteLength) {\n log?.(\n `${logPrefix}: blob ${binding.blobId} byte length mismatch during hydration.`,\n );\n return null;\n }\n\n if (!(await hasExpectedBlobSha256(blob, encryptedBytes))) {\n log?.(\n `${logPrefix}: blob ${binding.blobId} sha256 mismatch during hydration.`,\n );\n return null;\n }\n\n return {\n blobId: binding.blobId,\n encryptedBytes,\n };\n}\n\nasync function loadUniqueDocumentAttachmentBlobs(\n input: DocumentAttachmentHydrationContext & {\n hydrationTargets: ReadonlyArray<DocumentAttachmentHydrationTarget>;\n },\n): Promise<ReadonlyMap<string, LoadedDocumentAttachmentBlobBytes>> {\n const targetsByBlobId = new Map<string, DocumentAttachmentHydrationTarget>();\n for (const target of input.hydrationTargets) {\n if (!targetsByBlobId.has(target.binding.blobId)) {\n targetsByBlobId.set(target.binding.blobId, target);\n }\n }\n\n const loadedBlobs = await collectHydrationResults({\n ...input,\n tasks: Array.from(targetsByBlobId.values()).map((target) => ({\n blobId: target.binding.blobId,\n run: () =>\n loadDocumentAttachmentBlob({ ...input, binding: target.binding }),\n })),\n });\n\n return new Map(\n loadedBlobs.flatMap((loaded) =>\n loaded ? [[loaded.blobId, loaded] as const] : [],\n ),\n );\n}\n\nfunction applyLoadedBlobBytesToHydrationTargets(\n hydrationTargets: ReadonlyArray<DocumentAttachmentHydrationTarget>,\n loadedBlobsByBlobId: ReadonlyMap<string, LoadedDocumentAttachmentBlobBytes>,\n): LoadedDocumentAttachmentBlob[] {\n return hydrationTargets.flatMap((target) => {\n const loaded = loadedBlobsByBlobId.get(target.binding.blobId);\n return loaded\n ? [\n {\n ...target,\n encryptedBytes: loaded.encryptedBytes,\n },\n ]\n : [];\n });\n}\n\nasync function decryptLoadedDocumentAttachmentBlob(\n input: DocumentAttachmentHydrationContext & {\n loaded: LoadedDocumentAttachmentBlob;\n decryptAttachment: ReturnType<typeof createAttachmentDecryptor>;\n writerProjection: DocumentWriterProjectionResponse;\n },\n): Promise<HydratedDocumentAttachmentBlob | null> {\n const bytes = await input.decryptAttachment({\n binding: input.loaded.binding,\n encryptedBytes: input.loaded.encryptedBytes,\n expectedDocumentId: input.documentId,\n expectedSlotId: input.loaded.attachment.slotId,\n execSql: input.execSql,\n resolveProjectionUserKey: input.resolveProjectionUserKey,\n targetSecretKey: input.targetSecretKey,\n stillCurrent: input.stillCurrent,\n warmReferencedPrincipalPolicies: input.warmReferencedPrincipalPolicies,\n writerProjection: input.writerProjection,\n });\n\n const { attachment, binding } = input.loaded;\n const contentSha256 = await attachmentContentSha256(bytes);\n const intentMismatch = contentSha256 !== attachment.contentSha256;\n const logPrefix = input.logPrefix ?? \"Documents\";\n if (intentMismatch && input.localStorageKeyBySlotId?.[attachment.slotId]) {\n // Never replace a held copy with bytes the document does not record.\n input.rejectedServedBindings?.add(\n servedBindingRejectionKey(attachment, binding),\n );\n input.log?.(\n `${logPrefix}: served attachment bytes differ from the current document content; keeping the held copy.`,\n );\n return null;\n }\n if (intentMismatch) {\n input.log?.(\n `${logPrefix}: served attachment bytes differ from the current document content; showing them flagged.`,\n );\n }\n return {\n attachment,\n binding,\n bytes,\n contentSha256,\n intentMismatch,\n storageKey: `blob-${binding.blobId}`,\n };\n}\n\nexport async function hydrateDocumentAttachmentBlobs(\n input: DocumentAttachmentHydrationContext & {\n attachments: ReadonlyArray<DocumentAttachment>;\n },\n): Promise<HydratedDocumentAttachmentBlob[] | null> {\n const attachmentBindings = await input.apiClient.listDocumentAttachments(\n input.documentId,\n );\n if (!attachmentBindings) {\n return null;\n }\n\n const bindingBySlotId = new Map(\n attachmentBindings.map((binding) => [binding.slotId, binding]),\n );\n const hydrationTargets: DocumentAttachmentHydrationTarget[] =\n input.attachments.flatMap((attachment) => {\n const binding = bindingBySlotId.get(attachment.slotId);\n return binding &&\n shouldHydrateAttachment({\n attachment,\n binding,\n localBlobIdBySlotId: input.localBlobIdBySlotId,\n localStorageKeyBySlotId: input.localStorageKeyBySlotId,\n rejectedServedBindings: input.rejectedServedBindings,\n })\n ? [{ attachment, binding }]\n : [];\n });\n if (hydrationTargets.length === 0) {\n return [];\n }\n\n const loadedBlobs = applyLoadedBlobBytesToHydrationTargets(\n hydrationTargets,\n await loadUniqueDocumentAttachmentBlobs({\n ...input,\n hydrationTargets,\n }),\n );\n if (loadedBlobs.length === 0) {\n return [];\n }\n\n const writerProjection = await input.apiClient.getDocumentWriterProjection(\n input.documentId,\n );\n if (!writerProjection) {\n const logPrefix = input.logPrefix ?? \"Documents\";\n input.log?.(\n `${logPrefix}: cannot hydrate attachments for document ${input.documentId} without a writer projection.`,\n );\n return [];\n }\n\n const decryptAttachment = createAttachmentDecryptor(\n input.apiClient,\n input.documentId,\n );\n const hydrated = await collectHydrationResults({\n ...input,\n tasks: loadedBlobs.map((loaded) => ({\n blobId: loaded.binding.blobId,\n run: () =>\n decryptLoadedDocumentAttachmentBlob({\n ...input,\n decryptAttachment,\n loaded,\n writerProjection,\n }),\n })),\n });\n return hydrated.filter(\n (item): item is HydratedDocumentAttachmentBlob => item !== null,\n );\n}\n"]}
|
|
1
|
+
{"version":3,"file":"hydrate.js","sourceRoot":"","sources":["hydrate.ts"],"names":[],"mappings":"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","sourcesContent":["import type {\n BlobBytesResponse,\n RequestResultOptions,\n} from \"@tearleads/api-client\";\nimport { bytesToHex } from \"@tearleads/crypto\";\nimport type {\n BlobAttachmentSummary,\n DocumentWriterProjectionResponse,\n ListDocumentAttachmentsResponse,\n} from \"@tearleads/validators/response\";\nimport type { BlobBytes } from \"../../data/blobContracts\";\nimport { runBlobTransfer } from \"../../data/blobs/transferPool\";\nimport { attachmentContentSha256 } from \"../../data/documents/attachmentContentIdentity\";\nimport type { DocumentAttachment } from \"../../data/documents/documentContent\";\nimport { errorMessage } from \"../../data/errorMessage\";\nimport type {\n ProjectionUserKeyResolver,\n ReferencedPrincipalPolicyWarmer,\n} from \"../../data/keyingProjectionVerification\";\nimport type { SecurityIncidentReporter } from \"../../data/securityIncidents\";\nimport type { ExecSql } from \"../../data/sqlite/sqlSchema\";\nimport { createAttachmentDecryptor } from \"./attachmentDecryptor\";\nimport { collectHydrationResults } from \"./hydrationResults\";\nimport { readAttachmentTransfer } from \"./readAttachmentTransfer\";\n\ninterface DocumentAttachmentHydrationApi {\n evictDocumentWriterProjection?(documentId: string): void;\n getBlobBytes(\n blobId: string,\n options?: RequestResultOptions,\n ): Promise<BlobBytesResponse | null>;\n getDocumentWriterProjection(\n documentId: string,\n ): Promise<DocumentWriterProjectionResponse | null>;\n listDocumentAttachments(\n documentId: string,\n ): Promise<ListDocumentAttachmentsResponse | null>;\n}\n\ninterface HydratedDocumentAttachmentBlob {\n attachment: DocumentAttachment;\n binding: BlobAttachmentSummary;\n bytes: BlobBytes;\n /** Plaintext digest of `bytes`. */\n contentSha256: string;\n /**\n * The served binding is validly signed but its plaintext digest differs\n * from the digest the document content records for the slot. Set only when\n * the slot held no local bytes: the bind and the content update carrying\n * its digest are two server writes, and an uploader lost between them must\n * not leave the attachment permanently invisible. Never a security incident;\n * an honest crash window is indistinguishable from a served rollback.\n */\n intentMismatch: boolean;\n storageKey: string;\n}\n\ninterface DocumentAttachmentHydrationContext {\n apiClient: DocumentAttachmentHydrationApi;\n documentId: string;\n execSql: ExecSql;\n localBlobIdBySlotId?:\n | Readonly<Record<string, string | null | undefined>>\n | undefined;\n localStorageKeyBySlotId?: Readonly<Record<string, string | undefined>>;\n log?: ((message: string) => void) | undefined;\n logPrefix?: string | undefined;\n /**\n * Served bindings already downloaded and refused for a held copy, keyed per\n * (slot, binding, intent). Populated here; a later pass skips the download\n * until the binding or the document intent changes.\n */\n rejectedServedBindings?: Set<string> | undefined;\n resolveProjectionUserKey: ProjectionUserKeyResolver;\n reportSecurityIncident?: SecurityIncidentReporter | undefined;\n targetSecretKey: Uint8Array;\n stillCurrent?: (() => boolean) | undefined;\n warmReferencedPrincipalPolicies?: ReferencedPrincipalPolicyWarmer | undefined;\n}\n\nfunction servedBindingRejectionKey(\n attachment: DocumentAttachment,\n binding: BlobAttachmentSummary,\n): string {\n return JSON.stringify([\n attachment.slotId,\n binding.bindingId,\n attachment.contentSha256,\n ]);\n}\n\ninterface DocumentAttachmentHydrationTarget {\n attachment: DocumentAttachment;\n binding: BlobAttachmentSummary;\n}\n\ninterface LoadedDocumentAttachmentBlob\n extends DocumentAttachmentHydrationTarget {\n encryptedBytes: Uint8Array<ArrayBuffer>;\n}\n\ninterface LoadedDocumentAttachmentBlobBytes {\n blobId: string;\n encryptedBytes: Uint8Array<ArrayBuffer>;\n}\n\nasync function hasExpectedBlobSha256(\n blob: BlobBytesResponse,\n encryptedBytes: Uint8Array<ArrayBuffer>,\n): Promise<boolean> {\n const blobDigest = new Uint8Array(\n await crypto.subtle.digest(\"SHA-256\", encryptedBytes),\n );\n\n return bytesToHex(blobDigest) === blob.sha256;\n}\n\nfunction shouldHydrateAttachment(input: {\n attachment: DocumentAttachment;\n binding: BlobAttachmentSummary;\n localBlobIdBySlotId:\n | Readonly<Record<string, string | null | undefined>>\n | undefined;\n localStorageKeyBySlotId:\n | Readonly<Record<string, string | undefined>>\n | undefined;\n rejectedServedBindings: ReadonlySet<string> | undefined;\n}): boolean {\n const slotId = input.attachment.slotId;\n const localStorageKey = input.localStorageKeyBySlotId?.[slotId];\n // An empty slot always hydrates: a rejection only ever protected a held\n // copy, so once that copy is gone the served binding is shown (flagged).\n if (!localStorageKey) {\n return true;\n }\n if (\n input.rejectedServedBindings?.has(\n servedBindingRejectionKey(input.attachment, input.binding),\n )\n ) {\n return false;\n }\n\n const localBlobId = input.localBlobIdBySlotId?.[slotId];\n return localBlobId !== null && localBlobId !== input.binding.blobId;\n}\n\nasync function loadDocumentAttachmentBlob(\n input: DocumentAttachmentHydrationContext & {\n binding: BlobAttachmentSummary;\n signal: AbortSignal;\n progress: () => void;\n },\n): Promise<LoadedDocumentAttachmentBlobBytes | null> {\n const { apiClient, binding, log } = input;\n const logPrefix = input.logPrefix ?? \"Documents\";\n if (input.stillCurrent?.() === false) return null;\n input.signal.throwIfAborted();\n const blob = await apiClient.getBlobBytes(binding.blobId, {\n signal: input.signal,\n });\n if (!blob) {\n return null;\n }\n\n let encryptedBytes: Uint8Array<ArrayBuffer>;\n try {\n encryptedBytes = await readAttachmentTransfer(\n blob,\n input.signal,\n input.progress,\n );\n } catch (error) {\n log?.(\n `${logPrefix}: blob ${binding.blobId} failed to stream during hydration: ${errorMessage(\n error,\n )}`,\n );\n return null;\n }\n\n if (encryptedBytes.byteLength !== blob.byteLength) {\n log?.(\n `${logPrefix}: blob ${binding.blobId} byte length mismatch during hydration.`,\n );\n return null;\n }\n\n if (!(await hasExpectedBlobSha256(blob, encryptedBytes))) {\n log?.(\n `${logPrefix}: blob ${binding.blobId} sha256 mismatch during hydration.`,\n );\n return null;\n }\n\n input.signal.throwIfAborted();\n return {\n blobId: binding.blobId,\n encryptedBytes,\n };\n}\n\nasync function loadUniqueDocumentAttachmentBlobs(\n input: DocumentAttachmentHydrationContext & {\n hydrationTargets: ReadonlyArray<DocumentAttachmentHydrationTarget>;\n },\n): Promise<ReadonlyMap<string, LoadedDocumentAttachmentBlobBytes>> {\n const targetsByBlobId = new Map<string, DocumentAttachmentHydrationTarget>();\n for (const target of input.hydrationTargets) {\n if (!targetsByBlobId.has(target.binding.blobId)) {\n targetsByBlobId.set(target.binding.blobId, target);\n }\n }\n\n const loadedBlobs = await collectHydrationResults({\n ...input,\n tasks: Array.from(targetsByBlobId.values()).map((target) => ({\n blobId: target.binding.blobId,\n run: () =>\n runBlobTransfer(input.apiClient, (signal, progress) =>\n loadDocumentAttachmentBlob({\n ...input,\n binding: target.binding,\n signal,\n progress,\n }),\n ),\n })),\n });\n\n return new Map(\n loadedBlobs.flatMap((loaded) =>\n loaded ? [[loaded.blobId, loaded] as const] : [],\n ),\n );\n}\n\nfunction applyLoadedBlobBytesToHydrationTargets(\n hydrationTargets: ReadonlyArray<DocumentAttachmentHydrationTarget>,\n loadedBlobsByBlobId: ReadonlyMap<string, LoadedDocumentAttachmentBlobBytes>,\n): LoadedDocumentAttachmentBlob[] {\n return hydrationTargets.flatMap((target) => {\n const loaded = loadedBlobsByBlobId.get(target.binding.blobId);\n return loaded\n ? [\n {\n ...target,\n encryptedBytes: loaded.encryptedBytes,\n },\n ]\n : [];\n });\n}\n\nasync function decryptLoadedDocumentAttachmentBlob(\n input: DocumentAttachmentHydrationContext & {\n loaded: LoadedDocumentAttachmentBlob;\n decryptAttachment: ReturnType<typeof createAttachmentDecryptor>;\n writerProjection: DocumentWriterProjectionResponse;\n },\n): Promise<HydratedDocumentAttachmentBlob | null> {\n const bytes = await input.decryptAttachment({\n binding: input.loaded.binding,\n encryptedBytes: input.loaded.encryptedBytes,\n expectedDocumentId: input.documentId,\n expectedSlotId: input.loaded.attachment.slotId,\n execSql: input.execSql,\n resolveProjectionUserKey: input.resolveProjectionUserKey,\n targetSecretKey: input.targetSecretKey,\n stillCurrent: input.stillCurrent,\n warmReferencedPrincipalPolicies: input.warmReferencedPrincipalPolicies,\n writerProjection: input.writerProjection,\n });\n\n const { attachment, binding } = input.loaded;\n const contentSha256 = await attachmentContentSha256(bytes);\n const intentMismatch = contentSha256 !== attachment.contentSha256;\n const logPrefix = input.logPrefix ?? \"Documents\";\n if (intentMismatch && input.localStorageKeyBySlotId?.[attachment.slotId]) {\n // Never replace a held copy with bytes the document does not record.\n input.rejectedServedBindings?.add(\n servedBindingRejectionKey(attachment, binding),\n );\n input.log?.(\n `${logPrefix}: served attachment bytes differ from the current document content; keeping the held copy.`,\n );\n return null;\n }\n if (intentMismatch) {\n input.log?.(\n `${logPrefix}: served attachment bytes differ from the current document content; showing them flagged.`,\n );\n }\n return {\n attachment,\n binding,\n bytes,\n contentSha256,\n intentMismatch,\n storageKey: `blob-${binding.blobId}`,\n };\n}\n\nexport async function hydrateDocumentAttachmentBlobs(\n input: DocumentAttachmentHydrationContext & {\n attachments: ReadonlyArray<DocumentAttachment>;\n /** No binding target remains unchecked or unavailable in this pass. */\n onBindingsChecked?: () => void;\n },\n): Promise<HydratedDocumentAttachmentBlob[] | null> {\n const attachmentBindings = await input.apiClient.listDocumentAttachments(\n input.documentId,\n );\n if (!attachmentBindings) {\n return null;\n }\n\n const bindingBySlotId = new Map(\n attachmentBindings.map((binding) => [binding.slotId, binding]),\n );\n const hydrationTargets: DocumentAttachmentHydrationTarget[] =\n input.attachments.flatMap((attachment) => {\n const binding = bindingBySlotId.get(attachment.slotId);\n return binding &&\n shouldHydrateAttachment({\n attachment,\n binding,\n localBlobIdBySlotId: input.localBlobIdBySlotId,\n localStorageKeyBySlotId: input.localStorageKeyBySlotId,\n rejectedServedBindings: input.rejectedServedBindings,\n })\n ? [{ attachment, binding }]\n : [];\n });\n if (hydrationTargets.length === 0) {\n input.onBindingsChecked?.();\n return [];\n }\n\n const loadedBlobs = applyLoadedBlobBytesToHydrationTargets(\n hydrationTargets,\n await loadUniqueDocumentAttachmentBlobs({\n ...input,\n hydrationTargets,\n }),\n );\n if (loadedBlobs.length === 0) {\n return [];\n }\n\n const writerProjection = await input.apiClient.getDocumentWriterProjection(\n input.documentId,\n );\n if (!writerProjection) {\n const logPrefix = input.logPrefix ?? \"Documents\";\n input.log?.(\n `${logPrefix}: cannot hydrate attachments for document ${input.documentId} without a writer projection.`,\n );\n return [];\n }\n\n const decryptAttachment = createAttachmentDecryptor(\n input.apiClient,\n input.documentId,\n );\n const hydrated = await collectHydrationResults({\n ...input,\n tasks: loadedBlobs.map((loaded) => ({\n blobId: loaded.binding.blobId,\n run: () =>\n decryptLoadedDocumentAttachmentBlob({\n ...input,\n decryptAttachment,\n loaded,\n writerProjection,\n }),\n })),\n });\n const complete = hydrated.filter(\n (item): item is HydratedDocumentAttachmentBlob => item !== null,\n );\n if (complete.length === hydrationTargets.length) input.onBindingsChecked?.();\n return complete;\n}\n"]}
|
|
@@ -5,7 +5,7 @@ export function multipartStagePath(stageId) {
|
|
|
5
5
|
export function multipartApiFailureMessage(input) {
|
|
6
6
|
const failure = input.apiClient.getRequestFailure?.(input.request)?.message;
|
|
7
7
|
return failure
|
|
8
|
-
? `${input.fallback} Last
|
|
8
|
+
? `${input.fallback} Last request failure: ${failure}`
|
|
9
9
|
: input.fallback;
|
|
10
10
|
}
|
|
11
11
|
function provesMultipartStageReplacement(failure) {
|