@tearleads/client-sdk 0.1.33 → 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 +4 -4
- 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 +16 -9
- package/internal/api-client/routes/blobs/multipartUpload.js.map +1 -1
- package/internal/api-client/routes/blobs/storageFetchOptions.js +11 -0
- package/internal/api-client/routes/blobs/storageFetchOptions.js.map +1 -0
- 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/multipartUpload.js +57 -30
- 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":"get.js","sourceRoot":"","sources":["get.ts"],"names":[],"mappings":"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","sourcesContent":["import {\n blobWireHeaderKeys,\n getBlobBytesOperation,\n getBlobDownloadOperation,\n} from \"@tearleads/validators/operation\";\nimport type { BinaryResponseOperationTransport } from \"../../binaryResponseOperationTransport\";\nimport type { JsonOperationTransport } from \"../../operationTransport\";\nimport type { OperationResponseRequestFn } from \"../../types\";\n\nexport interface BlobBytesResponse {\n readonly blobId: string;\n readonly byteLength: number;\n readonly encryptedBytes: ReadableStream<Uint8Array>;\n readonly sha256: string;\n}\n\nexport interface UploadMultipartBlobPartBytesRequest {\n readonly byteLength: number;\n readonly encryptedBytes: Blob | BufferSource;\n readonly sha256: string;\n readonly uploadId: string;\n}\n\ntype BlobDownloadTransport = Pick<JsonOperationTransport, \"requestResult\"> &\n Pick<BinaryResponseOperationTransport, \"requestBinaryResponse\"> &\n Pick<OperationResponseRequestFn, \"reportFailure\">;\n\ntype DownloadFetch = (url: string, init: RequestInit) => Promise<Response>;\n\nexport async function getBlobBytes(\n transport: BlobDownloadTransport,\n blobId: string,\n downloadFetch: DownloadFetch = (url, init) => fetch(url, init),\n): Promise<BlobBytesResponse | null> {\n // Refresh at most once. R2 expiration errors can appear as CORS/network\n // failures in browsers, so both rejected fetches and 403s need a fresh URL.\n for (let attempt = 0; attempt < 2; attempt += 1) {\n const result = await transport.requestResult(\n getBlobDownloadOperation,\n { params: { blobId } },\n { reportErrors: false },\n );\n if (!result.ok) {\n if (result.kind === \"http\" && result.status === 404) {\n return getBlobStream(transport, blobId);\n }\n result.report();\n return null;\n }\n const descriptor = result.data;\n if (descriptor.blobId !== blobId) {\n transport.reportFailure({\n kind: \"shape\",\n method: \"GET\",\n path: `/blobs/${blobId}/download`,\n status: 200,\n statusText: \"\",\n message: `Blob ${blobId} download descriptor has the wrong identity`,\n });\n return null;\n }\n if (descriptor.source.kind === \"api\") {\n return getBlobStream(transport, blobId);\n }\n try {\n // Never send the application's session or cookies to the object store.\n // Reject redirects and keep bearer URLs out of referrers and errors.\n const response = await downloadFetch(descriptor.source.url, {\n cache: \"no-store\",\n credentials: \"omit\",\n redirect: \"error\",\n referrerPolicy: \"no-referrer\",\n });\n if (response.status !== 200) {\n await response.body?.cancel().catch(() => {});\n if (response.status === 403) {\n continue;\n }\n break;\n }\n return {\n blobId: descriptor.blobId,\n byteLength: descriptor.byteLength,\n sha256: descriptor.sha256,\n encryptedBytes: response.body\n ? signedDownloadBodyStream(response.body)\n : bufferedResponseBodyStream(await response.arrayBuffer()),\n };\n } catch {\n // The downstream SDK still verifies length, hash and authenticated\n // encryption. An error after streaming begins propagates to that reader.\n }\n }\n // Preserve downloads when storage is unreachable or a native HTTP bridge\n // cannot use the signed URL. This endpoint checks current access again.\n // A storage failure alone must not signal that the API is offline.\n return getBlobStream(transport, blobId);\n}\n\nfunction signedDownloadBodyStream(\n stream: ReadableStream<Uint8Array>,\n): ReadableStream<Uint8Array> {\n const reader = stream.getReader();\n // Native network errors may contain the bearer URL. Keep failures generic\n // before downstream readers log them, without buffering or eager reads.\n return new ReadableStream<Uint8Array>(\n {\n async pull(controller) {\n try {\n const { done, value } = await reader.read();\n if (done) {\n controller.close();\n reader.releaseLock();\n } else {\n controller.enqueue(value);\n }\n } catch {\n controller.error(new Error(\"Blob download interrupted\"));\n reader.releaseLock();\n }\n },\n async cancel(reason) {\n try {\n await reader.cancel(reason);\n } catch {\n throw new Error(\"Blob download cancellation failed\");\n } finally {\n reader.releaseLock();\n }\n },\n },\n { highWaterMark: 0 },\n );\n}\n\nasync function getBlobStream(\n transport: Pick<BinaryResponseOperationTransport, \"requestBinaryResponse\">,\n blobId: string,\n): Promise<BlobBytesResponse | null> {\n const result = await transport.requestBinaryResponse(getBlobBytesOperation, {\n params: { blobId },\n });\n if (!result) {\n return null;\n }\n\n const { headers, response } = result;\n const byteLength = Number(headers[blobWireHeaderKeys.blobByteLength]);\n\n // On the browser / WKWebView-fetch path the body is streamed, so\n // `response.body` is a live ReadableStream. Under a native HTTP bridge\n // (CapacitorHttp — which the Capacitor iOS/Android builds enable so cross-origin\n // API requests reach the backend at all; the WKWebView's own fetch fails from\n // the app's https://localhost origin) the response is buffered natively and\n // `response.body` is null even for a valid 200. So take the bytes either way:\n // the stream when present, otherwise buffer the response and re-wrap it as a\n // one-shot ReadableStream so callers keep a single streaming interface. This\n // does not weaken validation — a truncated or corrupted body is still caught\n // downstream by the X-Tearleads-Blob-Sha256 integrity check; it only stops a\n // buffered-but-valid response from being rejected here as malformed.\n const encryptedBytes =\n response.body ?? bufferedResponseBodyStream(await response.arrayBuffer());\n\n return {\n blobId: headers[blobWireHeaderKeys.blobId],\n byteLength,\n encryptedBytes,\n sha256: headers[blobWireHeaderKeys.blobSha256],\n };\n}\n\n// Wraps a fully-buffered response body as a single-chunk ReadableStream, so a\n// native HTTP bridge that only exposes the buffered bytes (no WHATWG\n// `response.body` stream) can still satisfy the streaming\n// `BlobBytesResponse.encryptedBytes` contract. An empty buffer yields an\n// immediately-closed, zero-chunk stream.\nfunction bufferedResponseBodyStream(\n buffer: ArrayBuffer,\n): ReadableStream<Uint8Array> {\n const bytes = new Uint8Array(buffer);\n return new ReadableStream<Uint8Array>({\n start(controller) {\n if (bytes.byteLength > 0) {\n controller.enqueue(bytes);\n }\n controller.close();\n },\n });\n}\n"]}
|
|
1
|
+
{"version":3,"file":"get.js","sourceRoot":"","sources":["get.ts"],"names":[],"mappings":"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","sourcesContent":["import {\n blobWireHeaderKeys,\n getBlobBytesOperation,\n getBlobDownloadOperation,\n} from \"@tearleads/validators/operation\";\nimport type { BinaryResponseOperationTransport } from \"../../binaryResponseOperationTransport\";\nimport type { JsonOperationTransport } from \"../../operationTransport\";\nimport type {\n OperationResponseRequestFn,\n RequestResultOptions,\n} from \"../../types\";\nimport { storageFetchOptions } from \"./storageFetchOptions\";\n\nexport interface BlobBytesResponse {\n readonly blobId: string;\n readonly byteLength: number;\n readonly encryptedBytes: ReadableStream<Uint8Array>;\n readonly sha256: string;\n}\n\nexport interface UploadMultipartBlobPartBytesRequest {\n readonly byteLength: number;\n readonly encryptedBytes: Blob | BufferSource;\n readonly sha256: string;\n readonly uploadId: string;\n}\n\ntype BlobDownloadTransport = Pick<JsonOperationTransport, \"requestResult\"> &\n Pick<BinaryResponseOperationTransport, \"requestBinaryResponse\"> &\n Pick<OperationResponseRequestFn, \"reportFailure\">;\n\ntype DownloadFetch = (url: string, init: RequestInit) => Promise<Response>;\n\nexport async function getBlobBytes(\n transport: BlobDownloadTransport,\n blobId: string,\n downloadFetch: DownloadFetch = (url, init) => fetch(url, init),\n options: RequestResultOptions = {},\n): Promise<BlobBytesResponse | null> {\n // Refresh at most once. R2 expiration errors can appear as CORS/network\n // failures in browsers, so both rejected fetches and 403s need a fresh URL.\n for (let attempt = 0; attempt < 2; attempt += 1) {\n if (options.signal?.aborted) return null;\n const result = await transport.requestResult(\n getBlobDownloadOperation,\n { params: { blobId } },\n { ...options, reportErrors: false },\n );\n if (!result.ok) {\n if (result.kind === \"http\" && result.status === 404) {\n return getBlobStream(transport, blobId, options);\n }\n result.report();\n return null;\n }\n const descriptor = result.data;\n if (descriptor.blobId !== blobId) {\n transport.reportFailure({\n kind: \"shape\",\n method: \"GET\",\n path: `/blobs/${blobId}/download`,\n status: 200,\n statusText: \"\",\n message: `Blob ${blobId} download descriptor has the wrong identity`,\n });\n return null;\n }\n if (descriptor.source.kind === \"api\") {\n return getBlobStream(transport, blobId, options);\n }\n try {\n // Never send the application's session or cookies to the object store.\n // Reject redirects and keep bearer URLs out of referrers and errors.\n const response = await downloadFetch(\n descriptor.source.url,\n storageFetchOptions(options),\n );\n if (response.status !== 200) {\n await response.body?.cancel().catch(() => {});\n if (response.status === 403) {\n continue;\n }\n break;\n }\n return await signedBlobResponse(descriptor, response);\n } catch {\n // The downstream SDK still verifies length, hash and authenticated\n // encryption. An error after streaming begins propagates to that reader.\n }\n }\n // Preserve downloads when storage is unreachable or a native HTTP bridge\n // cannot use the signed URL. This endpoint checks current access again.\n // A storage failure alone must not signal that the API is offline.\n return getBlobStream(transport, blobId, options);\n}\n\nasync function signedBlobResponse(\n descriptor: Pick<BlobBytesResponse, \"blobId\" | \"byteLength\" | \"sha256\">,\n response: Response,\n): Promise<BlobBytesResponse> {\n return {\n blobId: descriptor.blobId,\n byteLength: descriptor.byteLength,\n sha256: descriptor.sha256,\n encryptedBytes: response.body\n ? signedDownloadBodyStream(response.body)\n : bufferedResponseBodyStream(await response.arrayBuffer()),\n };\n}\n\nfunction signedDownloadBodyStream(\n stream: ReadableStream<Uint8Array>,\n): ReadableStream<Uint8Array> {\n const reader = stream.getReader();\n // Native network errors may contain the bearer URL. Keep failures generic\n // before downstream readers log them, without buffering or eager reads.\n return new ReadableStream<Uint8Array>(\n {\n async pull(controller) {\n try {\n const { done, value } = await reader.read();\n if (done) {\n controller.close();\n reader.releaseLock();\n } else {\n controller.enqueue(value);\n }\n } catch {\n controller.error(new Error(\"Blob download interrupted\"));\n reader.releaseLock();\n }\n },\n async cancel(reason) {\n try {\n await reader.cancel(reason);\n } catch {\n throw new Error(\"Blob download cancellation failed\");\n } finally {\n reader.releaseLock();\n }\n },\n },\n { highWaterMark: 0 },\n );\n}\n\nasync function getBlobStream(\n transport: Pick<BinaryResponseOperationTransport, \"requestBinaryResponse\">,\n blobId: string,\n options: RequestResultOptions,\n): Promise<BlobBytesResponse | null> {\n if (options.signal?.aborted) return null;\n const result = await transport.requestBinaryResponse(\n getBlobBytesOperation,\n {\n params: { blobId },\n },\n options,\n );\n if (!result) {\n return null;\n }\n\n const { headers, response } = result;\n const byteLength = Number(headers[blobWireHeaderKeys.blobByteLength]);\n\n // On the browser / WKWebView-fetch path the body is streamed, so\n // `response.body` is a live ReadableStream. Under a native HTTP bridge\n // (CapacitorHttp — which the Capacitor iOS/Android builds enable so cross-origin\n // API requests reach the backend at all; the WKWebView's own fetch fails from\n // the app's https://localhost origin) the response is buffered natively and\n // `response.body` is null even for a valid 200. So take the bytes either way:\n // the stream when present, otherwise buffer the response and re-wrap it as a\n // one-shot ReadableStream so callers keep a single streaming interface. This\n // does not weaken validation — a truncated or corrupted body is still caught\n // downstream by the X-Tearleads-Blob-Sha256 integrity check; it only stops a\n // buffered-but-valid response from being rejected here as malformed.\n const encryptedBytes =\n response.body ?? bufferedResponseBodyStream(await response.arrayBuffer());\n\n return {\n blobId: headers[blobWireHeaderKeys.blobId],\n byteLength,\n encryptedBytes,\n sha256: headers[blobWireHeaderKeys.blobSha256],\n };\n}\n\n// Wraps a fully-buffered response body as a single-chunk ReadableStream, so a\n// native HTTP bridge that only exposes the buffered bytes (no WHATWG\n// `response.body` stream) can still satisfy the streaming\n// `BlobBytesResponse.encryptedBytes` contract. An empty buffer yields an\n// immediately-closed, zero-chunk stream.\nfunction bufferedResponseBodyStream(\n buffer: ArrayBuffer,\n): ReadableStream<Uint8Array> {\n const bytes = new Uint8Array(buffer);\n return new ReadableStream<Uint8Array>({\n start(controller) {\n if (bytes.byteLength > 0) {\n controller.enqueue(bytes);\n }\n controller.close();\n },\n });\n}\n"]}
|
|
@@ -2,6 +2,7 @@ import { md5 } from "@noble/hashes/legacy.js";
|
|
|
2
2
|
import { bytesToHex } from "../../../crypto/index.js";
|
|
3
3
|
import { authorizeMultipartBlobPartUploadOperation, blobWireHeaderKeys, operationRequestPath, uploadMultipartBlobPartBytesOperation, } from "../../../validators/operation/index.js";
|
|
4
4
|
import { MAX_MULTIPART_BLOB_PART_BYTES } from "../../../validators/util/index.js";
|
|
5
|
+
import { storageFetchOptions } from "./storageFetchOptions.js";
|
|
5
6
|
function invalidPart(transport, path) {
|
|
6
7
|
transport.reportFailure({
|
|
7
8
|
kind: "shape",
|
|
@@ -44,7 +45,9 @@ async function preparePart(input) {
|
|
|
44
45
|
const contentMd5 = btoa(String.fromCharCode(...md5(new Uint8Array(bytes))));
|
|
45
46
|
return { file, contentMd5 };
|
|
46
47
|
}
|
|
47
|
-
export async function uploadMultipartBlobPartBytes(transport, stageId, partNumber, input, uploadFetch = (url, init) => fetch(url, init)) {
|
|
48
|
+
export async function uploadMultipartBlobPartBytes(transport, stageId, partNumber, input, uploadFetch = (url, init) => fetch(url, init), options = {}) {
|
|
49
|
+
if (options.signal?.aborted)
|
|
50
|
+
return null;
|
|
48
51
|
const operation = authorizeMultipartBlobPartUploadOperation;
|
|
49
52
|
const params = { stageId, partNumber };
|
|
50
53
|
const path = operationRequestPath(operation, params);
|
|
@@ -62,7 +65,9 @@ export async function uploadMultipartBlobPartBytes(transport, stageId, partNumbe
|
|
|
62
65
|
// failure may be an expired URL hidden by R2 CORS, or a lost success response;
|
|
63
66
|
// reauthorize once and send the same immutable bytes to the same part number.
|
|
64
67
|
for (let attempt = 0; attempt < 2; attempt += 1) {
|
|
65
|
-
|
|
68
|
+
if (options.signal?.aborted)
|
|
69
|
+
return null;
|
|
70
|
+
const result = await transport.requestResult(operation, { params, body }, options);
|
|
66
71
|
if (!result.ok)
|
|
67
72
|
return null;
|
|
68
73
|
const descriptor = result.data;
|
|
@@ -91,7 +96,7 @@ export async function uploadMultipartBlobPartBytes(transport, stageId, partNumbe
|
|
|
91
96
|
[blobWireHeaderKeys.partSha256]: input.sha256,
|
|
92
97
|
[blobWireHeaderKeys.partUploadId]: input.uploadId,
|
|
93
98
|
},
|
|
94
|
-
}, { encodeBody: () => file });
|
|
99
|
+
}, { ...options, encodeBody: () => file });
|
|
95
100
|
}
|
|
96
101
|
try {
|
|
97
102
|
const response = await uploadFetch(descriptor.source.url, {
|
|
@@ -101,10 +106,7 @@ export async function uploadMultipartBlobPartBytes(transport, stageId, partNumbe
|
|
|
101
106
|
"Content-Type": "application/octet-stream",
|
|
102
107
|
"Content-MD5": contentMd5,
|
|
103
108
|
},
|
|
104
|
-
|
|
105
|
-
credentials: "omit",
|
|
106
|
-
redirect: "error",
|
|
107
|
-
referrerPolicy: "no-referrer",
|
|
109
|
+
...storageFetchOptions(options),
|
|
108
110
|
});
|
|
109
111
|
status = response.status;
|
|
110
112
|
const etag = response.headers.get("ETag")?.trim();
|
|
@@ -124,9 +126,15 @@ export async function uploadMultipartBlobPartBytes(transport, stageId, partNumbe
|
|
|
124
126
|
status = null;
|
|
125
127
|
}
|
|
126
128
|
}
|
|
129
|
+
if (options.signal?.aborted)
|
|
130
|
+
return null;
|
|
131
|
+
reportStorageFailure(transport, path, partNumber, status);
|
|
132
|
+
return null;
|
|
133
|
+
}
|
|
134
|
+
function reportStorageFailure(transport, path, partNumber, status) {
|
|
127
135
|
transport.reportFailure({
|
|
128
136
|
kind: "storage",
|
|
129
|
-
method:
|
|
137
|
+
method: authorizeMultipartBlobPartUploadOperation.method,
|
|
130
138
|
path,
|
|
131
139
|
status,
|
|
132
140
|
statusText: "",
|
|
@@ -134,6 +142,5 @@ export async function uploadMultipartBlobPartBytes(transport, stageId, partNumbe
|
|
|
134
142
|
? `Multipart part ${partNumber} storage response is missing ETag; check storage CORS exposure`
|
|
135
143
|
: `Multipart part ${partNumber} storage upload failed${status === null ? "" : ` (HTTP ${status})`}`,
|
|
136
144
|
});
|
|
137
|
-
return null;
|
|
138
145
|
}
|
|
139
146
|
//# sourceMappingURL=multipartUpload.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"multipartUpload.js","sourceRoot":"","sources":["multipartUpload.ts"],"names":[],"mappings":"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","sourcesContent":["import { md5 } from \"@noble/hashes/legacy.js\";\nimport { bytesToHex } from \"@tearleads/crypto\";\nimport {\n authorizeMultipartBlobPartUploadOperation,\n blobWireHeaderKeys,\n operationRequestPath,\n uploadMultipartBlobPartBytesOperation,\n} from \"@tearleads/validators/operation\";\nimport type { UploadMultipartBlobPartResponse } from \"@tearleads/validators/response\";\nimport { MAX_MULTIPART_BLOB_PART_BYTES } from \"@tearleads/validators/util\";\nimport type { OperationTransport } from \"../../operationTransportFactory\";\nimport type { UploadMultipartBlobPartBytesRequest } from \"./get\";\n\ntype UploadFetch = (url: string, init: RequestInit) => Promise<Response>;\n\nfunction invalidPart(transport: OperationTransport, path: string) {\n transport.reportFailure({\n kind: \"shape\",\n method: authorizeMultipartBlobPartUploadOperation.method,\n path,\n status: null,\n statusText: \"\",\n message: \"Multipart part bytes do not match the declared length or SHA-256\",\n });\n return null;\n}\n\nasync function retryStorageStatus(status: number, attempt: number) {\n if (status === 403) return true;\n const transient = status === 429 || (status >= 500 && status <= 599);\n if (transient && attempt === 0) {\n await new Promise<void>((resolve) => setTimeout(resolve, 250));\n }\n return transient;\n}\n\nasync function preparePart(input: UploadMultipartBlobPartBytesRequest) {\n // File snapshots mutable buffers and preserves ciphertext across Capacitor's\n // native HTTP bridge. Uint8Array is UTF-8 decoded there; a plain Blob becomes\n // JSON. File's base64 bridge path is byte-for-byte, including non-UTF-8 data.\n const file =\n input.encryptedBytes instanceof File\n ? input.encryptedBytes\n : new File([input.encryptedBytes], \"encrypted-blob-part\", {\n type: \"application/octet-stream\",\n });\n if (\n file.size !== input.byteLength ||\n file.size < 1 ||\n file.size > MAX_MULTIPART_BLOB_PART_BYTES\n ) {\n throw new TypeError(\"Invalid multipart part byte length\");\n }\n const bytes = await file.arrayBuffer();\n if (\n bytesToHex(new Uint8Array(await crypto.subtle.digest(\"SHA-256\", bytes))) !==\n input.sha256\n ) {\n throw new TypeError(\"Multipart part SHA-256 does not match its bytes\");\n }\n const contentMd5 = btoa(String.fromCharCode(...md5(new Uint8Array(bytes))));\n return { file, contentMd5 };\n}\n\nexport async function uploadMultipartBlobPartBytes(\n transport: OperationTransport,\n stageId: string,\n partNumber: number,\n input: UploadMultipartBlobPartBytesRequest,\n uploadFetch: UploadFetch = (url, init) => fetch(url, init),\n): Promise<UploadMultipartBlobPartResponse | null> {\n const operation = authorizeMultipartBlobPartUploadOperation;\n const params = { stageId, partNumber };\n const path = operationRequestPath(operation, params);\n const prepared = await preparePart(input).catch(() =>\n invalidPart(transport, path),\n );\n if (!prepared) return null;\n const { file, contentMd5 } = prepared;\n const body = {\n byteLength: input.byteLength,\n contentMd5,\n uploadId: input.uploadId,\n };\n let status: number | null = null;\n // Sign just in time for each part, not once for the entire file. A network\n // failure may be an expired URL hidden by R2 CORS, or a lost success response;\n // reauthorize once and send the same immutable bytes to the same part number.\n for (let attempt = 0; attempt < 2; attempt += 1) {\n const result = await transport.requestResult(operation, { params, body });\n if (!result.ok) return null;\n const descriptor = result.data;\n if (\n descriptor.stageId !== stageId ||\n descriptor.uploadId !== input.uploadId ||\n descriptor.partNumber !== partNumber ||\n descriptor.byteLength !== input.byteLength ||\n descriptor.contentMd5 !== contentMd5\n ) {\n transport.reportFailure({\n kind: \"shape\",\n method: operation.method,\n path,\n status: 200,\n statusText: \"\",\n message: \"Multipart upload descriptor does not match its part\",\n });\n return null;\n }\n if (descriptor.source.kind === \"api\") {\n // A capability path for memory/custom stores; S3/R2 never proxy parts.\n return transport.requestBinaryRequest(\n uploadMultipartBlobPartBytesOperation,\n {\n params,\n body: input.encryptedBytes,\n headers: {\n [blobWireHeaderKeys.partByteLength]: input.byteLength.toString(),\n [blobWireHeaderKeys.partSha256]: input.sha256,\n [blobWireHeaderKeys.partUploadId]: input.uploadId,\n },\n },\n { encodeBody: () => file },\n );\n }\n try {\n const response = await uploadFetch(descriptor.source.url, {\n method: \"PUT\",\n body: file,\n headers: {\n \"Content-Type\": \"application/octet-stream\",\n \"Content-MD5\": contentMd5,\n },\n cache: \"no-store\",\n credentials: \"omit\",\n redirect: \"error\",\n referrerPolicy: \"no-referrer\",\n });\n status = response.status;\n const etag = response.headers.get(\"ETag\")?.trim();\n await response.body?.cancel().catch(() => {});\n if (status === 200 && etag) {\n return {\n stageId,\n uploadId: input.uploadId,\n part: { byteLength: input.byteLength, partNumber, etag },\n };\n }\n if (!(await retryStorageStatus(status, attempt))) break;\n } catch {\n // Never expose a provider exception or body: it may contain the bearer URL.\n status = null;\n }\n }\n transport.reportFailure({\n kind: \"storage\",\n method: operation.method,\n path,\n status,\n statusText: \"\",\n message:\n status === 200\n ? `Multipart part ${partNumber} storage response is missing ETag; check storage CORS exposure`\n : `Multipart part ${partNumber} storage upload failed${status === null ? \"\" : ` (HTTP ${status})`}`,\n });\n return null;\n}\n"]}
|
|
1
|
+
{"version":3,"file":"multipartUpload.js","sourceRoot":"","sources":["multipartUpload.ts"],"names":[],"mappings":"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","sourcesContent":["import { md5 } from \"@noble/hashes/legacy.js\";\nimport { bytesToHex } from \"@tearleads/crypto\";\nimport {\n authorizeMultipartBlobPartUploadOperation,\n blobWireHeaderKeys,\n operationRequestPath,\n uploadMultipartBlobPartBytesOperation,\n} from \"@tearleads/validators/operation\";\nimport type { UploadMultipartBlobPartResponse } from \"@tearleads/validators/response\";\nimport { MAX_MULTIPART_BLOB_PART_BYTES } from \"@tearleads/validators/util\";\nimport type { OperationTransport } from \"../../operationTransportFactory\";\nimport type { RequestResultOptions } from \"../../types\";\nimport type { UploadMultipartBlobPartBytesRequest } from \"./get\";\nimport { storageFetchOptions } from \"./storageFetchOptions\";\n\ntype UploadFetch = (url: string, init: RequestInit) => Promise<Response>;\n\nfunction invalidPart(transport: OperationTransport, path: string) {\n transport.reportFailure({\n kind: \"shape\",\n method: authorizeMultipartBlobPartUploadOperation.method,\n path,\n status: null,\n statusText: \"\",\n message: \"Multipart part bytes do not match the declared length or SHA-256\",\n });\n return null;\n}\n\nasync function retryStorageStatus(status: number, attempt: number) {\n if (status === 403) return true;\n const transient = status === 429 || (status >= 500 && status <= 599);\n if (transient && attempt === 0) {\n await new Promise<void>((resolve) => setTimeout(resolve, 250));\n }\n return transient;\n}\n\nasync function preparePart(input: UploadMultipartBlobPartBytesRequest) {\n // File snapshots mutable buffers and preserves ciphertext across Capacitor's\n // native HTTP bridge. Uint8Array is UTF-8 decoded there; a plain Blob becomes\n // JSON. File's base64 bridge path is byte-for-byte, including non-UTF-8 data.\n const file =\n input.encryptedBytes instanceof File\n ? input.encryptedBytes\n : new File([input.encryptedBytes], \"encrypted-blob-part\", {\n type: \"application/octet-stream\",\n });\n if (\n file.size !== input.byteLength ||\n file.size < 1 ||\n file.size > MAX_MULTIPART_BLOB_PART_BYTES\n ) {\n throw new TypeError(\"Invalid multipart part byte length\");\n }\n const bytes = await file.arrayBuffer();\n if (\n bytesToHex(new Uint8Array(await crypto.subtle.digest(\"SHA-256\", bytes))) !==\n input.sha256\n ) {\n throw new TypeError(\"Multipart part SHA-256 does not match its bytes\");\n }\n const contentMd5 = btoa(String.fromCharCode(...md5(new Uint8Array(bytes))));\n return { file, contentMd5 };\n}\n\nexport async function uploadMultipartBlobPartBytes(\n transport: OperationTransport,\n stageId: string,\n partNumber: number,\n input: UploadMultipartBlobPartBytesRequest,\n uploadFetch: UploadFetch = (url, init) => fetch(url, init),\n options: RequestResultOptions = {},\n): Promise<UploadMultipartBlobPartResponse | null> {\n if (options.signal?.aborted) return null;\n const operation = authorizeMultipartBlobPartUploadOperation;\n const params = { stageId, partNumber };\n const path = operationRequestPath(operation, params);\n const prepared = await preparePart(input).catch(() =>\n invalidPart(transport, path),\n );\n if (!prepared) return null;\n const { file, contentMd5 } = prepared;\n const body = {\n byteLength: input.byteLength,\n contentMd5,\n uploadId: input.uploadId,\n };\n let status: number | null = null;\n // Sign just in time for each part, not once for the entire file. A network\n // failure may be an expired URL hidden by R2 CORS, or a lost success response;\n // reauthorize once and send the same immutable bytes to the same part number.\n for (let attempt = 0; attempt < 2; attempt += 1) {\n if (options.signal?.aborted) return null;\n const result = await transport.requestResult(\n operation,\n { params, body },\n options,\n );\n if (!result.ok) return null;\n const descriptor = result.data;\n if (\n descriptor.stageId !== stageId ||\n descriptor.uploadId !== input.uploadId ||\n descriptor.partNumber !== partNumber ||\n descriptor.byteLength !== input.byteLength ||\n descriptor.contentMd5 !== contentMd5\n ) {\n transport.reportFailure({\n kind: \"shape\",\n method: operation.method,\n path,\n status: 200,\n statusText: \"\",\n message: \"Multipart upload descriptor does not match its part\",\n });\n return null;\n }\n if (descriptor.source.kind === \"api\") {\n // A capability path for memory/custom stores; S3/R2 never proxy parts.\n return transport.requestBinaryRequest(\n uploadMultipartBlobPartBytesOperation,\n {\n params,\n body: input.encryptedBytes,\n headers: {\n [blobWireHeaderKeys.partByteLength]: input.byteLength.toString(),\n [blobWireHeaderKeys.partSha256]: input.sha256,\n [blobWireHeaderKeys.partUploadId]: input.uploadId,\n },\n },\n { ...options, encodeBody: () => file },\n );\n }\n try {\n const response = await uploadFetch(descriptor.source.url, {\n method: \"PUT\",\n body: file,\n headers: {\n \"Content-Type\": \"application/octet-stream\",\n \"Content-MD5\": contentMd5,\n },\n ...storageFetchOptions(options),\n });\n status = response.status;\n const etag = response.headers.get(\"ETag\")?.trim();\n await response.body?.cancel().catch(() => {});\n if (status === 200 && etag) {\n return {\n stageId,\n uploadId: input.uploadId,\n part: { byteLength: input.byteLength, partNumber, etag },\n };\n }\n if (!(await retryStorageStatus(status, attempt))) break;\n } catch {\n // Never expose a provider exception or body: it may contain the bearer URL.\n status = null;\n }\n }\n if (options.signal?.aborted) return null;\n reportStorageFailure(transport, path, partNumber, status);\n return null;\n}\n\nfunction reportStorageFailure(\n transport: OperationTransport,\n path: string,\n partNumber: number,\n status: number | null,\n) {\n transport.reportFailure({\n kind: \"storage\",\n method: authorizeMultipartBlobPartUploadOperation.method,\n path,\n status,\n statusText: \"\",\n message:\n status === 200\n ? `Multipart part ${partNumber} storage response is missing ETag; check storage CORS exposure`\n : `Multipart part ${partNumber} storage upload failed${status === null ? \"\" : ` (HTTP ${status})`}`,\n });\n}\n"]}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/** Keep bearer URLs and application credentials out of object-store requests. */
|
|
2
|
+
export function storageFetchOptions(options) {
|
|
3
|
+
return {
|
|
4
|
+
cache: "no-store",
|
|
5
|
+
credentials: "omit",
|
|
6
|
+
redirect: "error",
|
|
7
|
+
referrerPolicy: "no-referrer",
|
|
8
|
+
...(options.signal ? { signal: options.signal } : {}),
|
|
9
|
+
};
|
|
10
|
+
}
|
|
11
|
+
//# sourceMappingURL=storageFetchOptions.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"storageFetchOptions.js","sourceRoot":"","sources":["storageFetchOptions.ts"],"names":[],"mappings":"AAEA,iFAAiF;AACjF,MAAM,UAAU,mBAAmB,CACjC,OAA6B;IAE7B,OAAO;QACL,KAAK,EAAE,UAAU;QACjB,WAAW,EAAE,MAAM;QACnB,QAAQ,EAAE,OAAO;QACjB,cAAc,EAAE,aAAa;QAC7B,GAAG,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,MAAM,EAAE,OAAO,CAAC,MAAM,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;KACtD,CAAC;AACJ,CAAC","sourcesContent":["import type { RequestResultOptions } from \"../../types\";\n\n/** Keep bearer URLs and application credentials out of object-store requests. */\nexport function storageFetchOptions(\n options: RequestResultOptions,\n): RequestInit {\n return {\n cache: \"no-store\",\n credentials: \"omit\",\n redirect: \"error\",\n referrerPolicy: \"no-referrer\",\n ...(options.signal ? { signal: options.signal } : {}),\n };\n}\n"]}
|
package/package.json
CHANGED
|
@@ -5,6 +5,10 @@ import { createRuntimePrincipalPolicyWarmer } from "../../../workflows/principal
|
|
|
5
5
|
import { commitHydratedAttachment } from "./attachmentHydrationCommit.js";
|
|
6
6
|
import { runDocumentOrphanMaintenance } from "./orphanMaintenance.js";
|
|
7
7
|
import { isDocumentStoreSyncGenerationCurrent as isSyncGenerationCurrent, } from "./syncGeneration.js";
|
|
8
|
+
// A remote probe is evidence that bindings may have moved independently of
|
|
9
|
+
// content. Capture its sequence before I/O so a hint arriving during hydration
|
|
10
|
+
// survives for the trailing pass. Missing or changed local bytes always retry.
|
|
11
|
+
const checkedSignals = new WeakMap();
|
|
8
12
|
export async function hydrateAttachmentBlobs(state, currentDoc, currentRecord, expectedGeneration) {
|
|
9
13
|
const generationIsCurrent = () => !expectedGeneration || isSyncGenerationCurrent(state, expectedGeneration);
|
|
10
14
|
if (!generationIsCurrent())
|
|
@@ -21,9 +25,16 @@ export async function hydrateAttachmentBlobs(state, currentDoc, currentRecord, e
|
|
|
21
25
|
if (attachments.length === 0) {
|
|
22
26
|
return;
|
|
23
27
|
}
|
|
28
|
+
const signalSequence = state.remoteUpdateSignalSeq;
|
|
29
|
+
if (bindingsAlreadyChecked(state, currentDoc, currentRecord.documentId, attachments, signalSequence))
|
|
30
|
+
return;
|
|
24
31
|
const expectedStorageKeys = { ...state.attachmentStorageKeyBySlotId };
|
|
32
|
+
let bindingsChecked = false;
|
|
25
33
|
const hydratedBlobs = await hydrateDocumentAttachmentBlobs({
|
|
26
34
|
apiClient: runtime.apiClient,
|
|
35
|
+
onBindingsChecked: () => {
|
|
36
|
+
bindingsChecked = true;
|
|
37
|
+
},
|
|
27
38
|
attachments,
|
|
28
39
|
documentId: currentRecord.documentId,
|
|
29
40
|
execSql: runtime.infra.execSql,
|
|
@@ -52,6 +63,28 @@ export async function hydrateAttachmentBlobs(state, currentDoc, currentRecord, e
|
|
|
52
63
|
expectedStorageKey: expectedStorageKeys[hydratedBlob.attachment.slotId] ?? null,
|
|
53
64
|
});
|
|
54
65
|
}
|
|
66
|
+
if (bindingsChecked &&
|
|
67
|
+
generationIsCurrent() &&
|
|
68
|
+
hydratedBlobs.every((blob) => state.attachmentBlobIdBySlotId[blob.attachment.slotId] ===
|
|
69
|
+
blob.binding.blobId)) {
|
|
70
|
+
checkedSignals.set(state, {
|
|
71
|
+
documentId: currentRecord.documentId,
|
|
72
|
+
document: new WeakRef(currentDoc),
|
|
73
|
+
sequence: signalSequence,
|
|
74
|
+
});
|
|
75
|
+
}
|
|
55
76
|
void runDocumentOrphanMaintenance(state);
|
|
56
77
|
}
|
|
78
|
+
function bindingsAlreadyChecked(state, currentDoc, documentId, attachments, signalSequence) {
|
|
79
|
+
const checked = checkedSignals.get(state);
|
|
80
|
+
const needsBindingProbe = signalSequence >
|
|
81
|
+
(checked?.documentId === documentId &&
|
|
82
|
+
checked.document.deref() === currentDoc
|
|
83
|
+
? checked.sequence
|
|
84
|
+
: 0);
|
|
85
|
+
return (!needsBindingProbe &&
|
|
86
|
+
attachments.every((attachment) => state.attachmentStorageKeyBySlotId[attachment.slotId] &&
|
|
87
|
+
state.attachmentContentSha256BySlotId[attachment.slotId] ===
|
|
88
|
+
attachment.contentSha256));
|
|
89
|
+
}
|
|
57
90
|
//# sourceMappingURL=attachmentHydration.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"attachmentHydration.js","sourceRoot":"","sources":["attachmentHydration.ts"],"names":[],"mappings":"AAAA,0FAA0F;AAE1F,OAAO,EAAE,sBAAsB,EAAE,MAAM,yCAAyC,CAAC;AACjF,OAAO,EAAE,8BAA8B,EAAE,MAAM,0BAA0B,CAAC;AAE1E,OAAO,EAAE,kCAAkC,EAAE,MAAM,mDAAmD,CAAC;AACvG,OAAO,EAAE,wBAAwB,EAAE,MAAM,6BAA6B,CAAC;AACvE,OAAO,EAAE,4BAA4B,EAAE,MAAM,qBAAqB,CAAC;AAEnE,OAAO,EAEL,oCAAoC,IAAI,uBAAuB,GAChE,MAAM,kBAAkB,CAAC;AAE1B,MAAM,CAAC,KAAK,UAAU,sBAAsB,CAC1C,KAAyB,EACzB,UAAyB,EACzB,aAAoC,EACpC,kBAAgD;IAEhD,MAAM,mBAAmB,GAAG,GAAG,EAAE,CAC/B,CAAC,kBAAkB,IAAI,uBAAuB,CAAC,KAAK,EAAE,kBAAkB,CAAC,CAAC;IAC5E,IAAI,CAAC,mBAAmB,EAAE;QAAE,OAAO;IAEnC,MAAM,OAAO,GAAG,KAAK,CAAC,OAAO,CAAC;IAC9B,MAAM,oBAAoB,GAAG,OAAO,CAAC,MAAM,CAAC,oBAAoB,CAAC;IACjE,IACE,CAAC,oBAAoB;QACrB,CAAC,OAAO,CAAC,IAAI,CAAC,eAAe;QAC7B,CAAC,OAAO,CAAC,KAAK,CAAC,MAAM;QACrB,CAAC,aAAa,EAAE,UAAU,EAC1B,CAAC;QACD,OAAO;IACT,CAAC;IAED,MAAM,WAAW,GAAG,sBAAsB,CAAC,UAAU,CAAC,CAAC;IACvD,IAAI,WAAW,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QAC7B,OAAO;IACT,CAAC;IAED,MAAM,mBAAmB,GAAG,EAAE,GAAG,KAAK,CAAC,4BAA4B,EAAE,CAAC;IACtE,MAAM,aAAa,GAAG,MAAM,8BAA8B,CAAC;QACzD,SAAS,EAAE,OAAO,CAAC,SAAS;QAC5B,WAAW;QACX,UAAU,EAAE,aAAa,CAAC,UAAU;QACpC,OAAO,EAAE,OAAO,CAAC,KAAK,CAAC,OAAO;QAC9B,mBAAmB,EAAE,KAAK,CAAC,wBAAwB;QACnD,uBAAuB,EAAE,KAAK,CAAC,4BAA4B;QAC3D,GAAG,EAAE,OAAO,CAAC,IAAI,CAAC,GAAG;QACrB,sBAAsB,EAAE,KAAK,CAAC,gCAAgC;QAC9D,sBAAsB,EAAE,OAAO,CAAC,IAAI,CAAC,sBAAsB;QAC3D,wBAAwB,EACtB,kBAAkB,EAAE,wBAAwB;YAC5C,KAAK,CAAC,wBAAwB;QAChC,eAAe,EAAE,oBAAoB,CAAC,SAAS;QAC/C,YAAY,EAAE,mBAAmB;QACjC,+BAA+B,EAC7B,kCAAkC,CAAC,OAAO,CAAC;KAC9C,CAAC,CAAC;IACH,IAAI,CAAC,mBAAmB,EAAE;QAAE,OAAO;IACnC,IAAI,CAAC,aAAa,EAAE,CAAC;QACnB,OAAO;IACT,CAAC;IAED,KAAK,MAAM,YAAY,IAAI,aAAa,EAAE,CAAC;QACzC,MAAM,wBAAwB,CAAC;YAC7B,KAAK;YACL,UAAU;YACV,YAAY;YACZ,mBAAmB;YACnB,kBAAkB,EAChB,mBAAmB,CAAC,YAAY,CAAC,UAAU,CAAC,MAAM,CAAC,IAAI,IAAI;SAC9D,CAAC,CAAC;IACL,CAAC;IACD,KAAK,4BAA4B,CAAC,KAAK,CAAC,CAAC;AAC3C,CAAC","sourcesContent":["/** Hydrate only bytes matching the current document's authenticated attachment intent. */\n\nimport { getDocumentAttachments } from \"../../../data/documents/documentContent\";\nimport { hydrateDocumentAttachmentBlobs } from \"../../../workflows/blobs\";\nimport type { DocumentRecord } from \"../../../workflows/documents\";\nimport { createRuntimePrincipalPolicyWarmer } from \"../../../workflows/principals/runtimePolicyWarmer\";\nimport { commitHydratedAttachment } from \"./attachmentHydrationCommit\";\nimport { runDocumentOrphanMaintenance } from \"./orphanMaintenance\";\nimport type { DocumentState, DocumentStoreState } from \"./state\";\nimport {\n type DocumentStoreSyncGeneration,\n isDocumentStoreSyncGenerationCurrent as isSyncGenerationCurrent,\n} from \"./syncGeneration\";\n\nexport async function hydrateAttachmentBlobs(\n state: DocumentStoreState,\n currentDoc: DocumentState,\n currentRecord: DocumentRecord | null,\n expectedGeneration?: DocumentStoreSyncGeneration,\n) {\n const generationIsCurrent = () =>\n !expectedGeneration || isSyncGenerationCurrent(state, expectedGeneration);\n if (!generationIsCurrent()) return;\n\n const runtime = state.runtime;\n const encapsulationKeyPair = runtime.crypto.encapsulationKeyPair;\n if (\n !encapsulationKeyPair ||\n !runtime.auth.isAuthenticated ||\n !runtime.state.online ||\n !currentRecord?.documentId\n ) {\n return;\n }\n\n const attachments = getDocumentAttachments(currentDoc);\n if (attachments.length === 0) {\n return;\n }\n\n const expectedStorageKeys = { ...state.attachmentStorageKeyBySlotId };\n const hydratedBlobs = await hydrateDocumentAttachmentBlobs({\n apiClient: runtime.apiClient,\n attachments,\n documentId: currentRecord.documentId,\n execSql: runtime.infra.execSql,\n localBlobIdBySlotId: state.attachmentBlobIdBySlotId,\n localStorageKeyBySlotId: state.attachmentStorageKeyBySlotId,\n log: runtime.util.log,\n rejectedServedBindings: state.rejectedServedAttachmentBindings,\n reportSecurityIncident: runtime.util.reportSecurityIncident,\n resolveProjectionUserKey:\n expectedGeneration?.resolveProjectionUserKey ??\n state.resolveProjectionUserKey,\n targetSecretKey: encapsulationKeyPair.secretKey,\n stillCurrent: generationIsCurrent,\n warmReferencedPrincipalPolicies:\n createRuntimePrincipalPolicyWarmer(runtime),\n });\n if (!generationIsCurrent()) return;\n if (!hydratedBlobs) {\n return;\n }\n\n for (const hydratedBlob of hydratedBlobs) {\n await commitHydratedAttachment({\n state,\n currentDoc,\n hydratedBlob,\n generationIsCurrent,\n expectedStorageKey:\n expectedStorageKeys[hydratedBlob.attachment.slotId] ?? null,\n });\n }\n void runDocumentOrphanMaintenance(state);\n}\n"]}
|
|
1
|
+
{"version":3,"file":"attachmentHydration.js","sourceRoot":"","sources":["attachmentHydration.ts"],"names":[],"mappings":"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","sourcesContent":["/** Hydrate only bytes matching the current document's authenticated attachment intent. */\n\nimport { getDocumentAttachments } from \"../../../data/documents/documentContent\";\nimport { hydrateDocumentAttachmentBlobs } from \"../../../workflows/blobs\";\nimport type { DocumentRecord } from \"../../../workflows/documents\";\nimport { createRuntimePrincipalPolicyWarmer } from \"../../../workflows/principals/runtimePolicyWarmer\";\nimport { commitHydratedAttachment } from \"./attachmentHydrationCommit\";\nimport { runDocumentOrphanMaintenance } from \"./orphanMaintenance\";\nimport type { DocumentState, DocumentStoreState } from \"./state\";\nimport {\n type DocumentStoreSyncGeneration,\n isDocumentStoreSyncGenerationCurrent as isSyncGenerationCurrent,\n} from \"./syncGeneration\";\n\n// A remote probe is evidence that bindings may have moved independently of\n// content. Capture its sequence before I/O so a hint arriving during hydration\n// survives for the trailing pass. Missing or changed local bytes always retry.\nconst checkedSignals = new WeakMap<\n DocumentStoreState,\n { documentId: string; document: WeakRef<DocumentState>; sequence: number }\n>();\n\nexport async function hydrateAttachmentBlobs(\n state: DocumentStoreState,\n currentDoc: DocumentState,\n currentRecord: DocumentRecord | null,\n expectedGeneration?: DocumentStoreSyncGeneration,\n) {\n const generationIsCurrent = () =>\n !expectedGeneration || isSyncGenerationCurrent(state, expectedGeneration);\n if (!generationIsCurrent()) return;\n\n const runtime = state.runtime;\n const encapsulationKeyPair = runtime.crypto.encapsulationKeyPair;\n if (\n !encapsulationKeyPair ||\n !runtime.auth.isAuthenticated ||\n !runtime.state.online ||\n !currentRecord?.documentId\n ) {\n return;\n }\n\n const attachments = getDocumentAttachments(currentDoc);\n if (attachments.length === 0) {\n return;\n }\n\n const signalSequence = state.remoteUpdateSignalSeq;\n if (\n bindingsAlreadyChecked(\n state,\n currentDoc,\n currentRecord.documentId,\n attachments,\n signalSequence,\n )\n )\n return;\n\n const expectedStorageKeys = { ...state.attachmentStorageKeyBySlotId };\n let bindingsChecked = false;\n const hydratedBlobs = await hydrateDocumentAttachmentBlobs({\n apiClient: runtime.apiClient,\n onBindingsChecked: () => {\n bindingsChecked = true;\n },\n attachments,\n documentId: currentRecord.documentId,\n execSql: runtime.infra.execSql,\n localBlobIdBySlotId: state.attachmentBlobIdBySlotId,\n localStorageKeyBySlotId: state.attachmentStorageKeyBySlotId,\n log: runtime.util.log,\n rejectedServedBindings: state.rejectedServedAttachmentBindings,\n reportSecurityIncident: runtime.util.reportSecurityIncident,\n resolveProjectionUserKey:\n expectedGeneration?.resolveProjectionUserKey ??\n state.resolveProjectionUserKey,\n targetSecretKey: encapsulationKeyPair.secretKey,\n stillCurrent: generationIsCurrent,\n warmReferencedPrincipalPolicies:\n createRuntimePrincipalPolicyWarmer(runtime),\n });\n if (!generationIsCurrent()) return;\n if (!hydratedBlobs) {\n return;\n }\n\n for (const hydratedBlob of hydratedBlobs) {\n await commitHydratedAttachment({\n state,\n currentDoc,\n hydratedBlob,\n generationIsCurrent,\n expectedStorageKey:\n expectedStorageKeys[hydratedBlob.attachment.slotId] ?? null,\n });\n }\n if (\n bindingsChecked &&\n generationIsCurrent() &&\n hydratedBlobs.every(\n (blob) =>\n state.attachmentBlobIdBySlotId[blob.attachment.slotId] ===\n blob.binding.blobId,\n )\n ) {\n checkedSignals.set(state, {\n documentId: currentRecord.documentId,\n document: new WeakRef(currentDoc),\n sequence: signalSequence,\n });\n }\n void runDocumentOrphanMaintenance(state);\n}\n\nfunction bindingsAlreadyChecked(\n state: DocumentStoreState,\n currentDoc: DocumentState,\n documentId: string,\n attachments: ReturnType<typeof getDocumentAttachments>,\n signalSequence: number,\n): boolean {\n const checked = checkedSignals.get(state);\n const needsBindingProbe =\n signalSequence >\n (checked?.documentId === documentId &&\n checked.document.deref() === currentDoc\n ? checked.sequence\n : 0);\n return (\n !needsBindingProbe &&\n attachments.every(\n (attachment) =>\n state.attachmentStorageKeyBySlotId[attachment.slotId] &&\n state.attachmentContentSha256BySlotId[attachment.slotId] ===\n attachment.contentSha256,\n )\n );\n}\n"]}
|
|
@@ -15,7 +15,7 @@ export function getActiveDocumentRemoteWork(state) {
|
|
|
15
15
|
? active.tail
|
|
16
16
|
: null;
|
|
17
17
|
}
|
|
18
|
-
/**
|
|
18
|
+
/** A document lane retries instead of waiting on overlapping remote work. */
|
|
19
19
|
export function runDocumentRemoteWork(state, task) {
|
|
20
20
|
const pending = getActiveDocumentRemoteWork(state);
|
|
21
21
|
if (pending) {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"remoteWork.js","sourceRoot":"","sources":["remoteWork.ts"],"names":[],"mappings":"AACA,OAAO,EACL,sCAAsC,EACtC,sCAAsC,EAEtC,wCAAwC,GACzC,MAAM,kBAAkB,CAAC;AAE1B,MAAM,iBAAiB,GAAG,IAAI,OAAO,EAOlC,CAAC;AAEJ,MAAM,OAAO,2BAA4B,SAAQ,KAAK;IAC/B;IAArB,YAAqB,QAAuB;QAC1C,KAAK,CAAC,4DAA4D,CAAC,CAAC;QADjD,aAAQ,GAAR,QAAQ,CAAe;QAE1C,IAAI,CAAC,IAAI,GAAG,6BAA6B,CAAC;IAC5C,CAAC;CACF;AAED,MAAM,UAAU,2BAA2B,CACzC,KAAyB;IAEzB,MAAM,MAAM,GAAG,iBAAiB,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC;IAC5C,OAAO,MAAM;QACX,wCAAwC,CAAC,KAAK,EAAE,MAAM,CAAC,UAAU,CAAC;QAClE,CAAC,CAAC,MAAM,CAAC,IAAI;QACb,CAAC,CAAC,IAAI,CAAC;AACX,CAAC;AAED,
|
|
1
|
+
{"version":3,"file":"remoteWork.js","sourceRoot":"","sources":["remoteWork.ts"],"names":[],"mappings":"AACA,OAAO,EACL,sCAAsC,EACtC,sCAAsC,EAEtC,wCAAwC,GACzC,MAAM,kBAAkB,CAAC;AAE1B,MAAM,iBAAiB,GAAG,IAAI,OAAO,EAOlC,CAAC;AAEJ,MAAM,OAAO,2BAA4B,SAAQ,KAAK;IAC/B;IAArB,YAAqB,QAAuB;QAC1C,KAAK,CAAC,4DAA4D,CAAC,CAAC;QADjD,aAAQ,GAAR,QAAQ,CAAe;QAE1C,IAAI,CAAC,IAAI,GAAG,6BAA6B,CAAC;IAC5C,CAAC;CACF;AAED,MAAM,UAAU,2BAA2B,CACzC,KAAyB;IAEzB,MAAM,MAAM,GAAG,iBAAiB,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC;IAC5C,OAAO,MAAM;QACX,wCAAwC,CAAC,KAAK,EAAE,MAAM,CAAC,UAAU,CAAC;QAClE,CAAC,CAAC,MAAM,CAAC,IAAI;QACb,CAAC,CAAC,IAAI,CAAC;AACX,CAAC;AAED,6EAA6E;AAC7E,MAAM,UAAU,qBAAqB,CACnC,KAAyB,EACzB,IAAsB;IAEtB,MAAM,OAAO,GAAG,2BAA2B,CAAC,KAAK,CAAC,CAAC;IACnD,IAAI,OAAO,EAAE,CAAC;QACZ,OAAO,OAAO,CAAC,MAAM,CAAC,IAAI,2BAA2B,CAAC,OAAO,CAAC,CAAC,CAAC;IAClE,CAAC;IACD,gFAAgF;IAChF,MAAM,UAAU,GAAG;QACjB,GAAG,sCAAsC,CAAC,KAAK,CAAC;QAChD,GAAG,sCAAsC,CAAC,KAAK,CAAC;KACjD,CAAC;IACF,MAAM,IAAI,GAAG,OAAO,CAAC,OAAO,EAAE;SAC3B,IAAI,CAAC,IAAI,CAAC;SACV,OAAO,CAAC,GAAG,EAAE;QACZ,IAAI,iBAAiB,CAAC,GAAG,CAAC,KAAK,CAAC,KAAK,MAAM,EAAE,CAAC;YAC5C,iBAAiB,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;QAClC,CAAC;IACH,CAAC,CAAC,CAAC;IACL,MAAM,MAAM,GAAG;QACb,UAAU;QACV,IAAI,EAAE,IAAI,CAAC,IAAI,CACb,GAAG,EAAE,CAAC,SAAS,EACf,GAAG,EAAE,CAAC,SAAS,CAChB;KACF,CAAC;IACF,iBAAiB,CAAC,GAAG,CAAC,KAAK,EAAE,MAAM,CAAC,CAAC;IACrC,OAAO,IAAI,CAAC;AACd,CAAC","sourcesContent":["import type { DocumentStoreState } from \"./state\";\nimport {\n captureDocumentStoreGenerationIdentity,\n captureDocumentStoreSyncLaneGeneration,\n type DocumentStoreSyncLaneGeneration,\n isDocumentStoreGenerationIdentityCurrent,\n} from \"./syncGeneration\";\n\nconst remoteWorkByState = new WeakMap<\n DocumentStoreState,\n {\n generation: DocumentStoreSyncLaneGeneration &\n ReturnType<typeof captureDocumentStoreGenerationIdentity>;\n tail: Promise<void>;\n }\n>();\n\nexport class DocumentRemoteWorkBusyError extends Error {\n constructor(readonly whenIdle: Promise<void>) {\n super(\"Document remote work is still running; retry the operation\");\n this.name = \"DocumentRemoteWorkBusyError\";\n }\n}\n\nexport function getActiveDocumentRemoteWork(\n state: DocumentStoreState,\n): Promise<void> | null {\n const active = remoteWorkByState.get(state);\n return active &&\n isDocumentStoreGenerationIdentityCurrent(state, active.generation)\n ? active.tail\n : null;\n}\n\n/** A document lane retries instead of waiting on overlapping remote work. */\nexport function runDocumentRemoteWork<T>(\n state: DocumentStoreState,\n task: () => Promise<T>,\n): Promise<T> {\n const pending = getActiveDocumentRemoteWork(state);\n if (pending) {\n return Promise.reject(new DocumentRemoteWorkBusyError(pending));\n }\n // Database/runtime replacement abandons ownership; ordinary local edits do not.\n const generation = {\n ...captureDocumentStoreGenerationIdentity(state),\n ...captureDocumentStoreSyncLaneGeneration(state),\n };\n const work = Promise.resolve()\n .then(task)\n .finally(() => {\n if (remoteWorkByState.get(state) === active) {\n remoteWorkByState.delete(state);\n }\n });\n const active = {\n generation,\n tail: work.then(\n () => undefined,\n () => undefined,\n ),\n };\n remoteWorkByState.set(state, active);\n return work;\n}\n"]}
|
|
@@ -260,6 +260,9 @@ export function registerDocumentStoreSyncLane(state, getSyncLaneGeneration) {
|
|
|
260
260
|
return registerDocumentSyncLane({
|
|
261
261
|
domainScope: state.runtime.state.domainScope,
|
|
262
262
|
localId: state.localId,
|
|
263
|
+
priority: () => state.remoteUpdatePending || state.record?.snapshotEndVersion === ""
|
|
264
|
+
? -1
|
|
265
|
+
: 0,
|
|
263
266
|
logError: (message, error) => {
|
|
264
267
|
// Read the runtime lazily: a store's runtime is replaced in place, and a
|
|
265
268
|
// reference captured here would report into a torn-down host.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"sync.js","sourceRoot":"","sources":["sync.ts"],"names":[],"mappings":"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","sourcesContent":["import { reportAndRethrowKeyingVerificationError } from \"../../../data/keyingProjectionVerification/error\";\nimport {\n type DocumentRecord,\n type DocumentSyncLane,\n isDatabaseUnavailableError,\n type PendingUpdateRecord,\n registerDocumentSyncLane,\n resolveDocumentCreateAuthor,\n} from \"../../../workflows/documents\";\nimport { requestDocumentStoreSync } from \"../registry\";\nimport { awaitInitializationForSync } from \"./initialization\";\nimport { listPendingUpdates } from \"./persistence\";\nimport { logRevalidationUnavailable as logUnavailable } from \"./remoteRevalidationTelemetry\";\nimport {\n getActiveDocumentRemoteWork,\n runDocumentRemoteWork,\n} from \"./remoteWork\";\nimport {\n type DocumentState,\n type DocumentStoreState,\n type DocumentSyncAttempt,\n type EncapsulationKeyPair,\n setDocumentSyncing,\n} from \"./state\";\nimport {\n cleanupPreRegisteredUpdateIdsOnFailure,\n discardPreRegisteredUpdateIds,\n preRegisterMaterializedDocumentSyncUpdateIds,\n} from \"./syncAcceptedUpdateIds\";\nimport { syncPendingAttachments } from \"./syncAttachments\";\nimport { syncDetachedAttachmentBindings } from \"./syncDetachedAttachments\";\nimport { finalizeDocumentSync } from \"./syncFinalize\";\nimport {\n captureDocumentStoreAttachmentSyncGeneration,\n captureDocumentStoreRemoteSyncGeneration,\n captureDocumentStoreSyncGeneration,\n type DocumentStoreSyncGeneration,\n type DocumentStoreSyncLaneGeneration,\n isDocumentStoreRemoteSyncBlocked,\n isDocumentStoreSyncGenerationCurrent,\n isDocumentStoreSyncLaneGenerationCurrent,\n} from \"./syncGeneration\";\nimport { prepareDocumentOutgoingCoverage } from \"./syncOutgoingCoverage\";\nimport { requestRemoteDocumentSync } from \"./syncRequest\";\nimport {\n ensureRemoteDocument,\n isContainerAwaitingRemoteCreate,\n shouldSkipCleanScheduledDocumentSync,\n} from \"./syncShared\";\n\nfunction canRunScheduledSync(state: DocumentStoreState): boolean {\n return (\n state.doc !== null &&\n state.snapshot.ready &&\n state.runtime.state.online &&\n state.runtime.auth.isAuthenticated &&\n state.runtime.crypto.encapsulationKeyPair !== null &&\n resolveDocumentCreateAuthor(state.runtime) !== null\n );\n}\n\nasync function requestOutgoingDocumentBatch(input: {\n readonly currentDoc: DocumentState;\n readonly encapsulationKeyPair: EncapsulationKeyPair;\n readonly generation: DocumentStoreSyncGeneration;\n readonly pendingUpdates: PendingUpdateRecord[];\n readonly record: DocumentRecord;\n readonly state: DocumentStoreState;\n}): Promise<{\n sentUpdateIds: string[];\n syncAttempt: DocumentSyncAttempt | null;\n}> {\n const { planningUpdates, queuedUpdateCount } = prepareDocumentStoreSyncQueue(\n input.pendingUpdates,\n );\n const sentUpdateIds: string[] = [];\n const syncAttempt = await cleanupPreRegisteredUpdateIdsOnFailure(\n input.state,\n sentUpdateIds,\n () =>\n requestRemoteDocumentSync({\n currentDoc: input.currentDoc,\n currentRecord: input.record,\n encapsulationKeyPair: input.encapsulationKeyPair,\n generation: input.generation,\n onOutgoingUpdatesMaterialized: (updateIds) =>\n preRegisterMaterializedDocumentSyncUpdateIds(\n input.state,\n sentUpdateIds,\n updateIds,\n ),\n pendingUpdates: planningUpdates,\n queuedUpdateCount,\n state: input.state,\n unavailableWriterLogMessage:\n \"Documents: skipped sync because the writer context is unavailable.\",\n }),\n );\n return { sentUpdateIds, syncAttempt };\n}\n\n/**\n * Keep the full durable queue visible to stale-heal/checkpoint classification.\n * The workflow planner owns the final count and byte bounds, and the exact\n * materialized batch is pre-registered immediately before each submit.\n */\nexport function prepareDocumentStoreSyncQueue(\n pendingUpdates: PendingUpdateRecord[],\n): {\n planningUpdates: PendingUpdateRecord[];\n queuedUpdateCount: number;\n} {\n return {\n planningUpdates: pendingUpdates,\n queuedUpdateCount: pendingUpdates.length,\n };\n}\n\nexport function canSyncDetachedAttachmentBindings(\n record: DocumentRecord,\n pendingUpdateCount: number,\n): boolean {\n return (\n pendingUpdateCount === 0 &&\n (record.pullContinuation ?? null) === null &&\n !record.pullContinuationRecoveryRequired\n );\n}\n\nasync function syncDocumentState(\n state: DocumentStoreState,\n currentDoc: DocumentState,\n nextRecord: DocumentRecord,\n encapsulationKeyPair: EncapsulationKeyPair,\n generation: DocumentStoreSyncGeneration,\n): Promise<DocumentRecord> {\n // Snapshot the remote-update signal sequence before any await. The GET below\n // fetches server state as of its own snapshot; any remote event delivered\n // after this point describes an update that may not be in that response, so\n // finalizeDocumentSync must not clear the signal if this sequence has moved.\n const consumedRemoteUpdateSignalSeq = state.remoteUpdateSignalSeq;\n const wasRemoteProbe = state.remoteUpdatePending;\n let pendingUpdates = await listPendingUpdates(state);\n if (!isDocumentStoreSyncGenerationCurrent(state, generation)) {\n requestDocumentStoreSync(state);\n return state.record ?? nextRecord;\n }\n if (pendingUpdates.length === 0 && isDocumentStoreRemoteSyncBlocked(state)) {\n return nextRecord;\n }\n // Create the remote document even when nothing is queued to send: a note\n // created and never edited enqueues no updates, but its local row is still a\n // pending create the write queue reports — it must flush, not sit. Defer\n // while the container itself still awaits its remote create — with OR\n // without queued edits, since the create cannot succeed before its parent\n // exists and attempting it just records a spurious failure. The structural\n // lane primes this store again as soon as the container create lands.\n if (!nextRecord.documentId) {\n const containerAwaitsCreate = await isContainerAwaitingRemoteCreate(state);\n if (!isDocumentStoreSyncGenerationCurrent(state, generation)) {\n requestDocumentStoreSync(state);\n return state.record ?? nextRecord;\n }\n if (containerAwaitsCreate) return nextRecord;\n }\n let nextRemoteRecord = await ensureRemoteDocument(\n state,\n currentDoc,\n nextRecord,\n encapsulationKeyPair,\n generation,\n );\n if (!isDocumentStoreSyncGenerationCurrent(state, generation)) {\n requestDocumentStoreSync(state);\n return state.record ?? nextRecord;\n }\n if (!nextRemoteRecord?.documentId) return nextRecord;\n\n const preparedCoverage = await prepareDocumentOutgoingCoverage({\n currentDoc,\n generation,\n pendingUpdates,\n state,\n });\n if (!preparedCoverage) {\n requestDocumentStoreSync(state);\n return state.record ?? nextRemoteRecord;\n }\n nextRemoteRecord = preparedCoverage.record;\n pendingUpdates = preparedCoverage.pendingUpdates;\n\n if (\n shouldSkipCleanScheduledDocumentSync({\n currentRecord: nextRemoteRecord,\n pendingUpdates,\n state,\n })\n ) {\n return nextRemoteRecord;\n }\n\n // Pre-register the IDs we are about to send so the author's OWN\n // `document_update_created` echo (fanned back over redis) is classified as\n // self-authored and never re-arms a redundant sync. Registering BEFORE the\n // network await closes the race where the echo lands before\n // finalizeDocumentSync records the accepted IDs — the gap that turned every\n // fast keystroke into an extra self-triggered sync.\n const { sentUpdateIds, syncAttempt } = await requestOutgoingDocumentBatch({\n currentDoc,\n encapsulationKeyPair,\n generation,\n pendingUpdates,\n record: nextRemoteRecord,\n state,\n });\n if (!isDocumentStoreSyncGenerationCurrent(state, generation)) {\n discardPreRegisteredUpdateIds(state, sentUpdateIds);\n requestDocumentStoreSync(state);\n return state.record ?? nextRemoteRecord;\n }\n if (!syncAttempt) {\n discardPreRegisteredUpdateIds(state, sentUpdateIds);\n logUnavailable(state, wasRemoteProbe);\n return nextRemoteRecord;\n }\n\n return cleanupPreRegisteredUpdateIdsOnFailure(state, sentUpdateIds, () =>\n finalizeDocumentSync(\n state,\n currentDoc,\n nextRemoteRecord,\n syncAttempt,\n consumedRemoteUpdateSignalSeq,\n generation,\n sentUpdateIds,\n wasRemoteProbe,\n ),\n );\n}\n\nasync function revalidateRemoteDocumentBeforeAttachments(\n state: DocumentStoreState,\n currentDoc: DocumentState,\n nextRecord: DocumentRecord,\n encapsulationKeyPair: EncapsulationKeyPair,\n generation: DocumentStoreSyncGeneration,\n): Promise<{ canSyncAttachments: boolean; nextRecord: DocumentRecord }> {\n if (state.pendingAttachments.length === 0 || !nextRecord.documentId) {\n return { canSyncAttachments: true, nextRecord };\n }\n\n const consumedRemoteUpdateSignalSeq = state.remoteUpdateSignalSeq;\n const wasRemoteProbe = state.remoteUpdatePending;\n const syncAttempt = await requestRemoteDocumentSync({\n state,\n currentDoc,\n currentRecord: nextRecord,\n encapsulationKeyPair,\n generation,\n pendingUpdates: [],\n unavailableWriterLogMessage:\n \"Documents: skipped pre-attachment revalidation because the writer context is unavailable.\",\n });\n if (!isDocumentStoreSyncGenerationCurrent(state, generation)) {\n requestDocumentStoreSync(state);\n return { canSyncAttachments: false, nextRecord };\n }\n if (!syncAttempt) {\n logUnavailable(state, wasRemoteProbe);\n return { canSyncAttachments: false, nextRecord };\n }\n\n const refreshedRecord = await finalizeDocumentSync(\n state,\n currentDoc,\n nextRecord,\n syncAttempt,\n consumedRemoteUpdateSignalSeq,\n generation,\n [],\n wasRemoteProbe,\n );\n return {\n canSyncAttachments:\n !syncAttempt.synced.hasIncompletePull &&\n refreshedRecord.pullContinuation == null &&\n refreshedRecord.pullContinuationRecoveryRequired !== true &&\n isDocumentStoreSyncGenerationCurrent(state, generation),\n nextRecord: refreshedRecord,\n };\n}\n\nasync function runDocumentSyncPass(\n state: DocumentStoreState,\n syncLaneGeneration: DocumentStoreSyncLaneGeneration,\n) {\n const currentDoc = state.doc;\n const encapsulationKeyPair = state.runtime.crypto.encapsulationKeyPair;\n let nextRecord = state.record;\n\n if (!currentDoc || !nextRecord || !encapsulationKeyPair) {\n return;\n }\n\n const generation = captureDocumentStoreRemoteSyncGeneration(\n state,\n currentDoc,\n syncLaneGeneration,\n );\n const attachmentGeneration = captureDocumentStoreAttachmentSyncGeneration(\n state,\n currentDoc,\n syncLaneGeneration,\n );\n if (!generation || !attachmentGeneration) {\n requestDocumentStoreSync(state);\n return;\n }\n\n const revalidation = await revalidateRemoteDocumentBeforeAttachments(\n state,\n currentDoc,\n nextRecord,\n encapsulationKeyPair,\n generation,\n );\n nextRecord = revalidation.nextRecord;\n if (!revalidation.canSyncAttachments) {\n return;\n }\n\n const attachmentResult = await syncPendingAttachments(\n state,\n nextRecord,\n encapsulationKeyPair,\n attachmentGeneration,\n );\n nextRecord = attachmentResult.nextRecord;\n if (state.pendingAttachments.length > 0) {\n return;\n }\n // A settled bind and the content update carrying its digest are two server\n // writes. Push the content in this same pass rather than re-arming a later\n // one, so a device lost between them is the only remaining window: peers\n // otherwise see the old digest against the new binding.\n\n nextRecord = await syncDocumentState(\n state,\n currentDoc,\n nextRecord,\n encapsulationKeyPair,\n generation,\n );\n\n if (!isDocumentStoreSyncGenerationCurrent(state, generation)) {\n requestDocumentStoreSync(state);\n return;\n }\n\n if (!state.doc || !state.record) return;\n\n const pendingUpdates = await listPendingUpdates(state);\n if (!isDocumentStoreSyncGenerationCurrent(state, generation)) {\n requestDocumentStoreSync(state);\n return;\n }\n if (!canSyncDetachedAttachmentBindings(nextRecord, pendingUpdates.length)) {\n return;\n }\n\n await syncDetachedAttachmentBindings(state, nextRecord, generation);\n if (!isDocumentStoreSyncGenerationCurrent(state, generation)) {\n requestDocumentStoreSync(state);\n }\n}\n\nasync function runScheduledSyncIteration(\n state: DocumentStoreState,\n syncLaneGeneration: DocumentStoreSyncLaneGeneration,\n) {\n if (!(await awaitInitializationForSync(state))) {\n return false;\n }\n if (!isDocumentStoreSyncLaneGenerationCurrent(state, syncLaneGeneration)) {\n return true;\n }\n\n if (!canRunScheduledSync(state)) {\n return true;\n }\n\n try {\n await runDocumentSyncPass(state, syncLaneGeneration);\n return true;\n } catch (error) {\n if (isDatabaseUnavailableError(error)) {\n return false;\n }\n\n await reportAndRethrowKeyingVerificationError(\n error,\n state.runtime.util.reportSecurityIncident,\n {\n objectId: state.record?.documentId ?? state.localId,\n objectKind: \"document\",\n operation: \"document.sync\",\n },\n );\n\n throw error;\n }\n}\n\nasync function runScheduledSyncLoop(\n state: DocumentStoreState,\n syncLaneGeneration: DocumentStoreSyncLaneGeneration,\n) {\n const syncingGeneration = captureDocumentStoreSyncGeneration(\n state,\n state.doc,\n );\n setDocumentSyncing(state, true);\n\n try {\n await runScheduledSyncIteration(state, syncLaneGeneration);\n } finally {\n if (\n syncingGeneration &&\n isDocumentStoreSyncGenerationCurrent(state, syncingGeneration) &&\n isDocumentStoreSyncLaneGenerationCurrent(state, syncLaneGeneration)\n ) {\n setDocumentSyncing(state, false);\n }\n }\n}\n\nexport function registerDocumentStoreSyncLane(\n state: DocumentStoreState,\n getSyncLaneGeneration: () => DocumentStoreSyncLaneGeneration | null,\n): DocumentSyncLane {\n return registerDocumentSyncLane({\n domainScope: state.runtime.state.domainScope,\n localId: state.localId,\n logError: (message, error) => {\n // Read the runtime lazily: a store's runtime is replaced in place, and a\n // reference captured here would report into a torn-down host.\n const { log, logError } = state.runtime.util;\n // Return the host's result: both callbacks are declared `=> void` but may\n // be async, and the lane reporter only catches a rejection it is handed.\n return logError ? logError(message, error) : log(message);\n },\n run: () => {\n const syncLaneGeneration = getSyncLaneGeneration();\n if (!syncLaneGeneration) return Promise.resolve();\n const activeWork = getActiveDocumentRemoteWork(state);\n if (activeWork) {\n // Re-arm on settlement, not immediately: immediate re-requests would\n // spin this lane and starve other work while the HTTP request is held.\n void activeWork.then(() => {\n if (\n isDocumentStoreSyncLaneGenerationCurrent(state, syncLaneGeneration)\n ) {\n requestDocumentStoreSync(state);\n }\n });\n return Promise.resolve();\n }\n return runDocumentRemoteWork(state, () =>\n isDocumentStoreSyncLaneGenerationCurrent(state, syncLaneGeneration)\n ? runScheduledSyncLoop(state, syncLaneGeneration)\n : Promise.resolve(),\n );\n },\n });\n}\n"]}
|
|
1
|
+
{"version":3,"file":"sync.js","sourceRoot":"","sources":["sync.ts"],"names":[],"mappings":"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","sourcesContent":["import { reportAndRethrowKeyingVerificationError } from \"../../../data/keyingProjectionVerification/error\";\nimport {\n type DocumentRecord,\n type DocumentSyncLane,\n isDatabaseUnavailableError,\n type PendingUpdateRecord,\n registerDocumentSyncLane,\n resolveDocumentCreateAuthor,\n} from \"../../../workflows/documents\";\nimport { requestDocumentStoreSync } from \"../registry\";\nimport { awaitInitializationForSync } from \"./initialization\";\nimport { listPendingUpdates } from \"./persistence\";\nimport { logRevalidationUnavailable as logUnavailable } from \"./remoteRevalidationTelemetry\";\nimport {\n getActiveDocumentRemoteWork,\n runDocumentRemoteWork,\n} from \"./remoteWork\";\nimport {\n type DocumentState,\n type DocumentStoreState,\n type DocumentSyncAttempt,\n type EncapsulationKeyPair,\n setDocumentSyncing,\n} from \"./state\";\nimport {\n cleanupPreRegisteredUpdateIdsOnFailure,\n discardPreRegisteredUpdateIds,\n preRegisterMaterializedDocumentSyncUpdateIds,\n} from \"./syncAcceptedUpdateIds\";\nimport { syncPendingAttachments } from \"./syncAttachments\";\nimport { syncDetachedAttachmentBindings } from \"./syncDetachedAttachments\";\nimport { finalizeDocumentSync } from \"./syncFinalize\";\nimport {\n captureDocumentStoreAttachmentSyncGeneration,\n captureDocumentStoreRemoteSyncGeneration,\n captureDocumentStoreSyncGeneration,\n type DocumentStoreSyncGeneration,\n type DocumentStoreSyncLaneGeneration,\n isDocumentStoreRemoteSyncBlocked,\n isDocumentStoreSyncGenerationCurrent,\n isDocumentStoreSyncLaneGenerationCurrent,\n} from \"./syncGeneration\";\nimport { prepareDocumentOutgoingCoverage } from \"./syncOutgoingCoverage\";\nimport { requestRemoteDocumentSync } from \"./syncRequest\";\nimport {\n ensureRemoteDocument,\n isContainerAwaitingRemoteCreate,\n shouldSkipCleanScheduledDocumentSync,\n} from \"./syncShared\";\n\nfunction canRunScheduledSync(state: DocumentStoreState): boolean {\n return (\n state.doc !== null &&\n state.snapshot.ready &&\n state.runtime.state.online &&\n state.runtime.auth.isAuthenticated &&\n state.runtime.crypto.encapsulationKeyPair !== null &&\n resolveDocumentCreateAuthor(state.runtime) !== null\n );\n}\n\nasync function requestOutgoingDocumentBatch(input: {\n readonly currentDoc: DocumentState;\n readonly encapsulationKeyPair: EncapsulationKeyPair;\n readonly generation: DocumentStoreSyncGeneration;\n readonly pendingUpdates: PendingUpdateRecord[];\n readonly record: DocumentRecord;\n readonly state: DocumentStoreState;\n}): Promise<{\n sentUpdateIds: string[];\n syncAttempt: DocumentSyncAttempt | null;\n}> {\n const { planningUpdates, queuedUpdateCount } = prepareDocumentStoreSyncQueue(\n input.pendingUpdates,\n );\n const sentUpdateIds: string[] = [];\n const syncAttempt = await cleanupPreRegisteredUpdateIdsOnFailure(\n input.state,\n sentUpdateIds,\n () =>\n requestRemoteDocumentSync({\n currentDoc: input.currentDoc,\n currentRecord: input.record,\n encapsulationKeyPair: input.encapsulationKeyPair,\n generation: input.generation,\n onOutgoingUpdatesMaterialized: (updateIds) =>\n preRegisterMaterializedDocumentSyncUpdateIds(\n input.state,\n sentUpdateIds,\n updateIds,\n ),\n pendingUpdates: planningUpdates,\n queuedUpdateCount,\n state: input.state,\n unavailableWriterLogMessage:\n \"Documents: skipped sync because the writer context is unavailable.\",\n }),\n );\n return { sentUpdateIds, syncAttempt };\n}\n\n/**\n * Keep the full durable queue visible to stale-heal/checkpoint classification.\n * The workflow planner owns the final count and byte bounds, and the exact\n * materialized batch is pre-registered immediately before each submit.\n */\nexport function prepareDocumentStoreSyncQueue(\n pendingUpdates: PendingUpdateRecord[],\n): {\n planningUpdates: PendingUpdateRecord[];\n queuedUpdateCount: number;\n} {\n return {\n planningUpdates: pendingUpdates,\n queuedUpdateCount: pendingUpdates.length,\n };\n}\n\nexport function canSyncDetachedAttachmentBindings(\n record: DocumentRecord,\n pendingUpdateCount: number,\n): boolean {\n return (\n pendingUpdateCount === 0 &&\n (record.pullContinuation ?? null) === null &&\n !record.pullContinuationRecoveryRequired\n );\n}\n\nasync function syncDocumentState(\n state: DocumentStoreState,\n currentDoc: DocumentState,\n nextRecord: DocumentRecord,\n encapsulationKeyPair: EncapsulationKeyPair,\n generation: DocumentStoreSyncGeneration,\n): Promise<DocumentRecord> {\n // Snapshot the remote-update signal sequence before any await. The GET below\n // fetches server state as of its own snapshot; any remote event delivered\n // after this point describes an update that may not be in that response, so\n // finalizeDocumentSync must not clear the signal if this sequence has moved.\n const consumedRemoteUpdateSignalSeq = state.remoteUpdateSignalSeq;\n const wasRemoteProbe = state.remoteUpdatePending;\n let pendingUpdates = await listPendingUpdates(state);\n if (!isDocumentStoreSyncGenerationCurrent(state, generation)) {\n requestDocumentStoreSync(state);\n return state.record ?? nextRecord;\n }\n if (pendingUpdates.length === 0 && isDocumentStoreRemoteSyncBlocked(state)) {\n return nextRecord;\n }\n // Create the remote document even when nothing is queued to send: a note\n // created and never edited enqueues no updates, but its local row is still a\n // pending create the write queue reports — it must flush, not sit. Defer\n // while the container itself still awaits its remote create — with OR\n // without queued edits, since the create cannot succeed before its parent\n // exists and attempting it just records a spurious failure. The structural\n // lane primes this store again as soon as the container create lands.\n if (!nextRecord.documentId) {\n const containerAwaitsCreate = await isContainerAwaitingRemoteCreate(state);\n if (!isDocumentStoreSyncGenerationCurrent(state, generation)) {\n requestDocumentStoreSync(state);\n return state.record ?? nextRecord;\n }\n if (containerAwaitsCreate) return nextRecord;\n }\n let nextRemoteRecord = await ensureRemoteDocument(\n state,\n currentDoc,\n nextRecord,\n encapsulationKeyPair,\n generation,\n );\n if (!isDocumentStoreSyncGenerationCurrent(state, generation)) {\n requestDocumentStoreSync(state);\n return state.record ?? nextRecord;\n }\n if (!nextRemoteRecord?.documentId) return nextRecord;\n\n const preparedCoverage = await prepareDocumentOutgoingCoverage({\n currentDoc,\n generation,\n pendingUpdates,\n state,\n });\n if (!preparedCoverage) {\n requestDocumentStoreSync(state);\n return state.record ?? nextRemoteRecord;\n }\n nextRemoteRecord = preparedCoverage.record;\n pendingUpdates = preparedCoverage.pendingUpdates;\n\n if (\n shouldSkipCleanScheduledDocumentSync({\n currentRecord: nextRemoteRecord,\n pendingUpdates,\n state,\n })\n ) {\n return nextRemoteRecord;\n }\n\n // Pre-register the IDs we are about to send so the author's OWN\n // `document_update_created` echo (fanned back over redis) is classified as\n // self-authored and never re-arms a redundant sync. Registering BEFORE the\n // network await closes the race where the echo lands before\n // finalizeDocumentSync records the accepted IDs — the gap that turned every\n // fast keystroke into an extra self-triggered sync.\n const { sentUpdateIds, syncAttempt } = await requestOutgoingDocumentBatch({\n currentDoc,\n encapsulationKeyPair,\n generation,\n pendingUpdates,\n record: nextRemoteRecord,\n state,\n });\n if (!isDocumentStoreSyncGenerationCurrent(state, generation)) {\n discardPreRegisteredUpdateIds(state, sentUpdateIds);\n requestDocumentStoreSync(state);\n return state.record ?? nextRemoteRecord;\n }\n if (!syncAttempt) {\n discardPreRegisteredUpdateIds(state, sentUpdateIds);\n logUnavailable(state, wasRemoteProbe);\n return nextRemoteRecord;\n }\n\n return cleanupPreRegisteredUpdateIdsOnFailure(state, sentUpdateIds, () =>\n finalizeDocumentSync(\n state,\n currentDoc,\n nextRemoteRecord,\n syncAttempt,\n consumedRemoteUpdateSignalSeq,\n generation,\n sentUpdateIds,\n wasRemoteProbe,\n ),\n );\n}\n\nasync function revalidateRemoteDocumentBeforeAttachments(\n state: DocumentStoreState,\n currentDoc: DocumentState,\n nextRecord: DocumentRecord,\n encapsulationKeyPair: EncapsulationKeyPair,\n generation: DocumentStoreSyncGeneration,\n): Promise<{ canSyncAttachments: boolean; nextRecord: DocumentRecord }> {\n if (state.pendingAttachments.length === 0 || !nextRecord.documentId) {\n return { canSyncAttachments: true, nextRecord };\n }\n\n const consumedRemoteUpdateSignalSeq = state.remoteUpdateSignalSeq;\n const wasRemoteProbe = state.remoteUpdatePending;\n const syncAttempt = await requestRemoteDocumentSync({\n state,\n currentDoc,\n currentRecord: nextRecord,\n encapsulationKeyPair,\n generation,\n pendingUpdates: [],\n unavailableWriterLogMessage:\n \"Documents: skipped pre-attachment revalidation because the writer context is unavailable.\",\n });\n if (!isDocumentStoreSyncGenerationCurrent(state, generation)) {\n requestDocumentStoreSync(state);\n return { canSyncAttachments: false, nextRecord };\n }\n if (!syncAttempt) {\n logUnavailable(state, wasRemoteProbe);\n return { canSyncAttachments: false, nextRecord };\n }\n\n const refreshedRecord = await finalizeDocumentSync(\n state,\n currentDoc,\n nextRecord,\n syncAttempt,\n consumedRemoteUpdateSignalSeq,\n generation,\n [],\n wasRemoteProbe,\n );\n return {\n canSyncAttachments:\n !syncAttempt.synced.hasIncompletePull &&\n refreshedRecord.pullContinuation == null &&\n refreshedRecord.pullContinuationRecoveryRequired !== true &&\n isDocumentStoreSyncGenerationCurrent(state, generation),\n nextRecord: refreshedRecord,\n };\n}\n\nasync function runDocumentSyncPass(\n state: DocumentStoreState,\n syncLaneGeneration: DocumentStoreSyncLaneGeneration,\n) {\n const currentDoc = state.doc;\n const encapsulationKeyPair = state.runtime.crypto.encapsulationKeyPair;\n let nextRecord = state.record;\n\n if (!currentDoc || !nextRecord || !encapsulationKeyPair) {\n return;\n }\n\n const generation = captureDocumentStoreRemoteSyncGeneration(\n state,\n currentDoc,\n syncLaneGeneration,\n );\n const attachmentGeneration = captureDocumentStoreAttachmentSyncGeneration(\n state,\n currentDoc,\n syncLaneGeneration,\n );\n if (!generation || !attachmentGeneration) {\n requestDocumentStoreSync(state);\n return;\n }\n\n const revalidation = await revalidateRemoteDocumentBeforeAttachments(\n state,\n currentDoc,\n nextRecord,\n encapsulationKeyPair,\n generation,\n );\n nextRecord = revalidation.nextRecord;\n if (!revalidation.canSyncAttachments) {\n return;\n }\n\n const attachmentResult = await syncPendingAttachments(\n state,\n nextRecord,\n encapsulationKeyPair,\n attachmentGeneration,\n );\n nextRecord = attachmentResult.nextRecord;\n if (state.pendingAttachments.length > 0) {\n return;\n }\n // A settled bind and the content update carrying its digest are two server\n // writes. Push the content in this same pass rather than re-arming a later\n // one, so a device lost between them is the only remaining window: peers\n // otherwise see the old digest against the new binding.\n\n nextRecord = await syncDocumentState(\n state,\n currentDoc,\n nextRecord,\n encapsulationKeyPair,\n generation,\n );\n\n if (!isDocumentStoreSyncGenerationCurrent(state, generation)) {\n requestDocumentStoreSync(state);\n return;\n }\n\n if (!state.doc || !state.record) return;\n\n const pendingUpdates = await listPendingUpdates(state);\n if (!isDocumentStoreSyncGenerationCurrent(state, generation)) {\n requestDocumentStoreSync(state);\n return;\n }\n if (!canSyncDetachedAttachmentBindings(nextRecord, pendingUpdates.length)) {\n return;\n }\n\n await syncDetachedAttachmentBindings(state, nextRecord, generation);\n if (!isDocumentStoreSyncGenerationCurrent(state, generation)) {\n requestDocumentStoreSync(state);\n }\n}\n\nasync function runScheduledSyncIteration(\n state: DocumentStoreState,\n syncLaneGeneration: DocumentStoreSyncLaneGeneration,\n) {\n if (!(await awaitInitializationForSync(state))) {\n return false;\n }\n if (!isDocumentStoreSyncLaneGenerationCurrent(state, syncLaneGeneration)) {\n return true;\n }\n\n if (!canRunScheduledSync(state)) {\n return true;\n }\n\n try {\n await runDocumentSyncPass(state, syncLaneGeneration);\n return true;\n } catch (error) {\n if (isDatabaseUnavailableError(error)) {\n return false;\n }\n\n await reportAndRethrowKeyingVerificationError(\n error,\n state.runtime.util.reportSecurityIncident,\n {\n objectId: state.record?.documentId ?? state.localId,\n objectKind: \"document\",\n operation: \"document.sync\",\n },\n );\n\n throw error;\n }\n}\n\nasync function runScheduledSyncLoop(\n state: DocumentStoreState,\n syncLaneGeneration: DocumentStoreSyncLaneGeneration,\n) {\n const syncingGeneration = captureDocumentStoreSyncGeneration(\n state,\n state.doc,\n );\n setDocumentSyncing(state, true);\n\n try {\n await runScheduledSyncIteration(state, syncLaneGeneration);\n } finally {\n if (\n syncingGeneration &&\n isDocumentStoreSyncGenerationCurrent(state, syncingGeneration) &&\n isDocumentStoreSyncLaneGenerationCurrent(state, syncLaneGeneration)\n ) {\n setDocumentSyncing(state, false);\n }\n }\n}\n\nexport function registerDocumentStoreSyncLane(\n state: DocumentStoreState,\n getSyncLaneGeneration: () => DocumentStoreSyncLaneGeneration | null,\n): DocumentSyncLane {\n return registerDocumentSyncLane({\n domainScope: state.runtime.state.domainScope,\n localId: state.localId,\n priority: () =>\n state.remoteUpdatePending || state.record?.snapshotEndVersion === \"\"\n ? -1\n : 0,\n logError: (message, error) => {\n // Read the runtime lazily: a store's runtime is replaced in place, and a\n // reference captured here would report into a torn-down host.\n const { log, logError } = state.runtime.util;\n // Return the host's result: both callbacks are declared `=> void` but may\n // be async, and the lane reporter only catches a rejection it is handed.\n return logError ? logError(message, error) : log(message);\n },\n run: () => {\n const syncLaneGeneration = getSyncLaneGeneration();\n if (!syncLaneGeneration) return Promise.resolve();\n const activeWork = getActiveDocumentRemoteWork(state);\n if (activeWork) {\n // Re-arm on settlement, not immediately: immediate re-requests would\n // spin this lane and starve other work while the HTTP request is held.\n void activeWork.then(() => {\n if (\n isDocumentStoreSyncLaneGenerationCurrent(state, syncLaneGeneration)\n ) {\n requestDocumentStoreSync(state);\n }\n });\n return Promise.resolve();\n }\n return runDocumentRemoteWork(state, () =>\n isDocumentStoreSyncLaneGenerationCurrent(state, syncLaneGeneration)\n ? runScheduledSyncLoop(state, syncLaneGeneration)\n : Promise.resolve(),\n );\n },\n });\n}\n"]}
|
|
@@ -1,4 +1,7 @@
|
|
|
1
1
|
import { isSystemContainerNode } from "../../stores/container-contents/index.js";
|
|
2
|
+
function shouldForceDocument(force, documentIds, documentId) {
|
|
3
|
+
return force && (!documentIds || documentIds.has(documentId));
|
|
4
|
+
}
|
|
2
5
|
/**
|
|
3
6
|
* Routes reconciled summaries into the appropriate content-sync policy.
|
|
4
7
|
*
|
|
@@ -12,12 +15,13 @@ export function createReconciledDocumentContentPuller(host) {
|
|
|
12
15
|
// Keeping the version in the key would grow this cache for every historical
|
|
13
16
|
// updatedAt value over the reconciler's lifetime.
|
|
14
17
|
const pulledSystemDocumentVersions = new Map();
|
|
15
|
-
return (containerId, documents, force) => {
|
|
18
|
+
return (containerId, documents, force, documentIds) => {
|
|
16
19
|
const systemContainer = isSystemContainerNode(host.getContainer(containerId) ?? { systemSlot: null });
|
|
17
20
|
for (const document of documents) {
|
|
18
21
|
if (!document.documentId) {
|
|
19
22
|
continue;
|
|
20
23
|
}
|
|
24
|
+
const forceDocument = shouldForceDocument(force, documentIds, document.documentId);
|
|
21
25
|
if (systemContainer) {
|
|
22
26
|
// Container scope is significant: the same document can be linked into
|
|
23
27
|
// multiple system containers whose keys establish different decryption
|
|
@@ -31,7 +35,7 @@ export function createReconciledDocumentContentPuller(host) {
|
|
|
31
35
|
});
|
|
32
36
|
pulledSystemDocumentVersions.set(documentKey, document.updatedAt);
|
|
33
37
|
}
|
|
34
|
-
else if (
|
|
38
|
+
else if (forceDocument) {
|
|
35
39
|
// The first eager pull registers a document store before scheduling
|
|
36
40
|
// its async sync. If that sync failed, a later explicit Refresh must
|
|
37
41
|
// be able to retry the same server version without defeating normal
|
|
@@ -40,7 +44,7 @@ export function createReconciledDocumentContentPuller(host) {
|
|
|
40
44
|
}
|
|
41
45
|
continue;
|
|
42
46
|
}
|
|
43
|
-
if (
|
|
47
|
+
if (forceDocument) {
|
|
44
48
|
host.requestRegisteredDocumentRemoteSync(document.id, document.documentId);
|
|
45
49
|
}
|
|
46
50
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"documentContentPull.js","sourceRoot":"","sources":["documentContentPull.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,qBAAqB,EAAE,MAAM,iCAAiC,CAAC;AAgBxE;;;;;;;GAOG;AACH,MAAM,UAAU,qCAAqC,CACnD,IAAuC;IAEvC,4EAA4E;IAC5E,4EAA4E;IAC5E,kDAAkD;IAClD,MAAM,4BAA4B,GAAG,IAAI,GAAG,EAAkB,CAAC;IAE/D,OAAO,CAAC,WAAW,EAAE,SAAS,EAAE,KAAK,EAAE,EAAE;
|
|
1
|
+
{"version":3,"file":"documentContentPull.js","sourceRoot":"","sources":["documentContentPull.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,qBAAqB,EAAE,MAAM,iCAAiC,CAAC;AAgBxE,SAAS,mBAAmB,CAC1B,KAAc,EACd,WAA4C,EAC5C,UAAkB;IAElB,OAAO,KAAK,IAAI,CAAC,CAAC,WAAW,IAAI,WAAW,CAAC,GAAG,CAAC,UAAU,CAAC,CAAC,CAAC;AAChE,CAAC;AAED;;;;;;;GAOG;AACH,MAAM,UAAU,qCAAqC,CACnD,IAAuC;IAEvC,4EAA4E;IAC5E,4EAA4E;IAC5E,kDAAkD;IAClD,MAAM,4BAA4B,GAAG,IAAI,GAAG,EAAkB,CAAC;IAE/D,OAAO,CAAC,WAAW,EAAE,SAAS,EAAE,KAAK,EAAE,WAAW,EAAE,EAAE;QACpD,MAAM,eAAe,GAAG,qBAAqB,CAC3C,IAAI,CAAC,YAAY,CAAC,WAAW,CAAC,IAAI,EAAE,UAAU,EAAE,IAAI,EAAE,CACvD,CAAC;QAEF,KAAK,MAAM,QAAQ,IAAI,SAAS,EAAE,CAAC;YACjC,IAAI,CAAC,QAAQ,CAAC,UAAU,EAAE,CAAC;gBACzB,SAAS;YACX,CAAC;YAED,MAAM,aAAa,GAAG,mBAAmB,CACvC,KAAK,EACL,WAAW,EACX,QAAQ,CAAC,UAAU,CACpB,CAAC;YAEF,IAAI,eAAe,EAAE,CAAC;gBACpB,uEAAuE;gBACvE,uEAAuE;gBACvE,oCAAoC;gBACpC,MAAM,WAAW,GAAG,GAAG,WAAW,IAAI,QAAQ,CAAC,UAAU,EAAE,CAAC;gBAC5D,IACE,4BAA4B,CAAC,GAAG,CAAC,WAAW,CAAC,KAAK,QAAQ,CAAC,SAAS,EACpE,CAAC;oBACD,IAAI,CAAC,mBAAmB,CAAC;wBACvB,WAAW;wBACX,UAAU,EAAE,QAAQ,CAAC,UAAU;wBAC/B,OAAO,EAAE,QAAQ,CAAC,EAAE;qBACrB,CAAC,CAAC;oBACH,4BAA4B,CAAC,GAAG,CAAC,WAAW,EAAE,QAAQ,CAAC,SAAS,CAAC,CAAC;gBACpE,CAAC;qBAAM,IAAI,aAAa,EAAE,CAAC;oBACzB,oEAAoE;oBACpE,qEAAqE;oBACrE,oEAAoE;oBACpE,qBAAqB;oBACrB,IAAI,CAAC,mCAAmC,CACtC,QAAQ,CAAC,EAAE,EACX,QAAQ,CAAC,UAAU,CACpB,CAAC;gBACJ,CAAC;gBACD,SAAS;YACX,CAAC;YAED,IAAI,aAAa,EAAE,CAAC;gBAClB,IAAI,CAAC,mCAAmC,CACtC,QAAQ,CAAC,EAAE,EACX,QAAQ,CAAC,UAAU,CACpB,CAAC;YACJ,CAAC;QACH,CAAC;IACH,CAAC,CAAC;AACJ,CAAC","sourcesContent":["import type { ContainerNode } from \"../../stores/container-contents\";\nimport { isSystemContainerNode } from \"../../stores/container-contents\";\nimport type { ReconciliationHost } from \"./serviceTypes\";\n\ninterface ReconciledDocumentContentPullHost {\n readonly getContainer: (containerId: string) => ContainerNode | null;\n readonly pullDocumentContent: (input: {\n readonly containerId: string;\n readonly documentId: string;\n readonly localId: string;\n }) => void;\n readonly requestRegisteredDocumentRemoteSync: (\n localId: string,\n documentId: string,\n ) => void;\n}\n\nfunction shouldForceDocument(\n force: boolean,\n documentIds: ReadonlySet<string> | undefined,\n documentId: string,\n): boolean {\n return force && (!documentIds || documentIds.has(documentId));\n}\n\n/**\n * Routes reconciled summaries into the appropriate content-sync policy.\n *\n * System-container contents cannot rely on a mini-app opening them on this\n * device, especially during identity recovery. Pull each remote version once.\n * Ordinary documents stay lazy unless an explicit refresh asks an already-open\n * store to revalidate.\n */\nexport function createReconciledDocumentContentPuller(\n host: ReconciledDocumentContentPullHost,\n): ReconciliationHost[\"requestDocumentContentPull\"] {\n // Retain only the last observed version for each system-container document.\n // Keeping the version in the key would grow this cache for every historical\n // updatedAt value over the reconciler's lifetime.\n const pulledSystemDocumentVersions = new Map<string, string>();\n\n return (containerId, documents, force, documentIds) => {\n const systemContainer = isSystemContainerNode(\n host.getContainer(containerId) ?? { systemSlot: null },\n );\n\n for (const document of documents) {\n if (!document.documentId) {\n continue;\n }\n\n const forceDocument = shouldForceDocument(\n force,\n documentIds,\n document.documentId,\n );\n\n if (systemContainer) {\n // Container scope is significant: the same document can be linked into\n // multiple system containers whose keys establish different decryption\n // contexts for the transient store.\n const documentKey = `${containerId}:${document.documentId}`;\n if (\n pulledSystemDocumentVersions.get(documentKey) !== document.updatedAt\n ) {\n host.pullDocumentContent({\n containerId,\n documentId: document.documentId,\n localId: document.id,\n });\n pulledSystemDocumentVersions.set(documentKey, document.updatedAt);\n } else if (forceDocument) {\n // The first eager pull registers a document store before scheduling\n // its async sync. If that sync failed, a later explicit Refresh must\n // be able to retry the same server version without defeating normal\n // background dedupe.\n host.requestRegisteredDocumentRemoteSync(\n document.id,\n document.documentId,\n );\n }\n continue;\n }\n\n if (forceDocument) {\n host.requestRegisteredDocumentRemoteSync(\n document.id,\n document.documentId,\n );\n }\n }\n };\n}\n"]}
|
|
@@ -1,4 +1,13 @@
|
|
|
1
|
-
export function markContainerForced(state, containerId) {
|
|
1
|
+
export function markContainerForced(state, containerId, documentId) {
|
|
2
|
+
const previous = state.forcedDocumentIds.get(containerId);
|
|
3
|
+
if (documentId &&
|
|
4
|
+
(!state.forcedContainerGenerations.has(containerId) || previous)) {
|
|
5
|
+
state.forcedDocumentIds.set(containerId, new Set([...(previous ?? []), documentId]));
|
|
6
|
+
}
|
|
7
|
+
else {
|
|
8
|
+
// A full revalidation dominates any pending targeted hints.
|
|
9
|
+
state.forcedDocumentIds.delete(containerId);
|
|
10
|
+
}
|
|
2
11
|
state.nextForceGeneration += 1;
|
|
3
12
|
state.automaticRetryGenerations.delete(containerId);
|
|
4
13
|
state.forcedContainerGenerations.set(containerId, state.nextForceGeneration);
|
|
@@ -6,13 +15,16 @@ export function markContainerForced(state, containerId) {
|
|
|
6
15
|
export function acknowledgeContainerForce(state, containerId, generation) {
|
|
7
16
|
if (state.forcedContainerGenerations.get(containerId) === generation) {
|
|
8
17
|
state.forcedContainerGenerations.delete(containerId);
|
|
18
|
+
state.forcedDocumentIds.delete(containerId);
|
|
9
19
|
state.automaticRetryGenerations.delete(containerId);
|
|
10
20
|
}
|
|
11
21
|
}
|
|
12
22
|
export function activateContainer(state, containerId, enqueue) {
|
|
13
23
|
state.activeContainerId = containerId;
|
|
14
24
|
if (containerId) {
|
|
15
|
-
|
|
25
|
+
// Navigation preserves pending targets and their bounded retry allowance.
|
|
26
|
+
// A new hint or explicit refresh, rather than navigation, rearms that budget.
|
|
27
|
+
enqueue(containerId);
|
|
16
28
|
}
|
|
17
29
|
}
|
|
18
30
|
export function enqueueKnownContainersForIdleBackfill(input) {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"idleBackfill.js","sourceRoot":"","sources":["idleBackfill.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"idleBackfill.js","sourceRoot":"","sources":["idleBackfill.ts"],"names":[],"mappings":"AAeA,MAAM,UAAU,mBAAmB,CACjC,KAAwB,EACxB,WAAmB,EACnB,UAAmB;IAEnB,MAAM,QAAQ,GAAG,KAAK,CAAC,iBAAiB,CAAC,GAAG,CAAC,WAAW,CAAC,CAAC;IAC1D,IACE,UAAU;QACV,CAAC,CAAC,KAAK,CAAC,0BAA0B,CAAC,GAAG,CAAC,WAAW,CAAC,IAAI,QAAQ,CAAC,EAChE,CAAC;QACD,KAAK,CAAC,iBAAiB,CAAC,GAAG,CACzB,WAAW,EACX,IAAI,GAAG,CAAC,CAAC,GAAG,CAAC,QAAQ,IAAI,EAAE,CAAC,EAAE,UAAU,CAAC,CAAC,CAC3C,CAAC;IACJ,CAAC;SAAM,CAAC;QACN,4DAA4D;QAC5D,KAAK,CAAC,iBAAiB,CAAC,MAAM,CAAC,WAAW,CAAC,CAAC;IAC9C,CAAC;IACD,KAAK,CAAC,mBAAmB,IAAI,CAAC,CAAC;IAC/B,KAAK,CAAC,yBAAyB,CAAC,MAAM,CAAC,WAAW,CAAC,CAAC;IACpD,KAAK,CAAC,0BAA0B,CAAC,GAAG,CAAC,WAAW,EAAE,KAAK,CAAC,mBAAmB,CAAC,CAAC;AAC/E,CAAC;AAED,MAAM,UAAU,yBAAyB,CACvC,KAAwB,EACxB,WAAmB,EACnB,UAA8B;IAE9B,IAAI,KAAK,CAAC,0BAA0B,CAAC,GAAG,CAAC,WAAW,CAAC,KAAK,UAAU,EAAE,CAAC;QACrE,KAAK,CAAC,0BAA0B,CAAC,MAAM,CAAC,WAAW,CAAC,CAAC;QACrD,KAAK,CAAC,iBAAiB,CAAC,MAAM,CAAC,WAAW,CAAC,CAAC;QAC5C,KAAK,CAAC,yBAAyB,CAAC,MAAM,CAAC,WAAW,CAAC,CAAC;IACtD,CAAC;AACH,CAAC;AAED,MAAM,UAAU,iBAAiB,CAC/B,KAAwB,EACxB,WAA0B,EAC1B,OAAsC;IAEtC,KAAK,CAAC,iBAAiB,GAAG,WAAW,CAAC;IACtC,IAAI,WAAW,EAAE,CAAC;QAChB,0EAA0E;QAC1E,8EAA8E;QAC9E,OAAO,CAAC,WAAW,CAAC,CAAC;IACvB,CAAC;AACH,CAAC;AAED,MAAM,UAAU,qCAAqC,CAAC,KAIrD;IACC,MAAM,EAAE,IAAI,EAAE,aAAa,EAAE,KAAK,EAAE,GAAG,KAAK,CAAC;IAC7C,MAAM,iBAAiB,GAAG,IAAI,CAAC,qBAAqB,EAAE,CAAC;IACvD,MAAM,iBAAiB,GAAG,KAAK,CAAC,iBAAiB,CAAC;IAClD,MAAM,oBAAoB,GACxB,iBAAiB,KAAK,IAAI;QAC1B,CAAC,iBAAiB,CAAC,QAAQ,CAAC,iBAAiB,CAAC;QAC9C,IAAI,CAAC,6BAA6B,CAAC,iBAAiB,CAAC;QACnD,CAAC,CAAC,CAAC,GAAG,iBAAiB,EAAE,iBAAiB,CAAC;QAC3C,CAAC,CAAC,iBAAiB,CAAC;IACxB,KAAK,CAAC,oBAAoB,CAAC,GAAG,CAC5B,oBAAoB,CAAC,MAAM,CAAC,CAAC,EAAE,EAAE,EAAE,CAAC,IAAI,CAAC,6BAA6B,CAAC,EAAE,CAAC,CAAC,CAC5E,CAAC;IACF,KAAK,MAAM,WAAW,IAAI,oBAAoB,EAAE,CAAC;QAC/C,MAAM,WAAW,GAAG,KAAK,CAAC,0BAA0B,CAAC,GAAG,CAAC,WAAW,CAAC,CAAC;QACtE,IAAI,CAAC,WAAW,IAAI,KAAK,CAAC,sBAAsB,CAAC,GAAG,CAAC,WAAW,CAAC,EAAE,CAAC;YAClE,SAAS;QACX,CAAC;QACD,KAAK,CAAC,KAAK,CAAC,OAAO,CAAC,WAAW,EAAE,MAAM,CAAC,CAAC;IAC3C,CAAC;IACD,aAAa,EAAE,CAAC;AAClB,CAAC","sourcesContent":["import type { InitialDocumentProbe } from \"./initialDocumentProbe\";\nimport type { ReconcileQueue } from \"./queue\";\nimport type { ReconciliationHost } from \"./serviceTypes\";\n\nexport interface IdleBackfillState {\n activeContainerId: string | null;\n automaticRetryGenerations: Map<string, number>;\n discoveredContainerIds: Set<string>;\n forcedContainerGenerations: Map<string, number>;\n forcedDocumentIds: Map<string, ReadonlySet<string>>;\n initialDocumentProbe: InitialDocumentProbe;\n nextForceGeneration: number;\n queue: ReconcileQueue;\n}\n\nexport function markContainerForced(\n state: IdleBackfillState,\n containerId: string,\n documentId?: string,\n): void {\n const previous = state.forcedDocumentIds.get(containerId);\n if (\n documentId &&\n (!state.forcedContainerGenerations.has(containerId) || previous)\n ) {\n state.forcedDocumentIds.set(\n containerId,\n new Set([...(previous ?? []), documentId]),\n );\n } else {\n // A full revalidation dominates any pending targeted hints.\n state.forcedDocumentIds.delete(containerId);\n }\n state.nextForceGeneration += 1;\n state.automaticRetryGenerations.delete(containerId);\n state.forcedContainerGenerations.set(containerId, state.nextForceGeneration);\n}\n\nexport function acknowledgeContainerForce(\n state: IdleBackfillState,\n containerId: string,\n generation: number | undefined,\n): void {\n if (state.forcedContainerGenerations.get(containerId) === generation) {\n state.forcedContainerGenerations.delete(containerId);\n state.forcedDocumentIds.delete(containerId);\n state.automaticRetryGenerations.delete(containerId);\n }\n}\n\nexport function activateContainer(\n state: IdleBackfillState,\n containerId: string | null,\n enqueue: (containerId: string) => void,\n): void {\n state.activeContainerId = containerId;\n if (containerId) {\n // Navigation preserves pending targets and their bounded retry allowance.\n // A new hint or explicit refresh, rather than navigation, rearms that budget.\n enqueue(containerId);\n }\n}\n\nexport function enqueueKnownContainersForIdleBackfill(input: {\n host: ReconciliationHost;\n scheduleDrain: () => void;\n state: IdleBackfillState;\n}): void {\n const { host, scheduleDrain, state } = input;\n const knownContainerIds = host.listKnownContainerIds();\n const activeContainerId = state.activeContainerId;\n const backfillContainerIds =\n activeContainerId !== null &&\n !knownContainerIds.includes(activeContainerId) &&\n host.canDiscoverContainerDocuments(activeContainerId)\n ? [...knownContainerIds, activeContainerId]\n : knownContainerIds;\n state.initialDocumentProbe.arm(\n backfillContainerIds.filter((id) => host.canDiscoverContainerDocuments(id)),\n );\n for (const containerId of backfillContainerIds) {\n const shouldForce = state.forcedContainerGenerations.has(containerId);\n if (!shouldForce && state.discoveredContainerIds.has(containerId)) {\n continue;\n }\n state.queue.enqueue(containerId, \"idle\");\n }\n scheduleDrain();\n}\n"]}
|