@tearleads/client-sdk 0.1.42 → 0.1.44
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/client/containerDocumentDiscovery.js +71 -29
- package/client/containerDocumentDiscovery.js.map +1 -1
- package/client/deviceFirst.js +4 -5
- package/client/deviceFirst.js.map +1 -1
- package/data/blobs/transferPool.js +35 -8
- package/data/blobs/transferPool.js.map +1 -1
- package/data/containers/shared/projection.js +12 -0
- package/data/containers/shared/projection.js.map +1 -1
- package/data/documents/blob/shared/types.d.ts +2 -3
- package/data/documents/blob/shared/types.js.map +1 -1
- package/data/documents/shared/events.js +0 -1
- package/data/documents/shared/events.js.map +1 -1
- package/data/persistence/container-contents/containerContentsPersistence.js +6 -22
- package/data/persistence/container-contents/containerContentsPersistence.js.map +1 -1
- package/data/persistence/container-contents/containerMetadataMutationPersistence.js +5 -15
- package/data/persistence/container-contents/containerMetadataMutationPersistence.js.map +1 -1
- package/data/persistence/container-contents/containerPendingUpdatePersistence.js +6 -15
- package/data/persistence/container-contents/containerPendingUpdatePersistence.js.map +1 -1
- package/data/persistence/container-contents/containerRecoveryPersistence.js +3 -4
- package/data/persistence/container-contents/containerRecoveryPersistence.js.map +1 -1
- package/data/persistence/container-contents/containerRemovalWork.js +3 -5
- package/data/persistence/container-contents/containerRemovalWork.js.map +1 -1
- package/data/persistence/container-contents/dormantContainerMetadata.js +1 -5
- package/data/persistence/container-contents/dormantContainerMetadata.js.map +1 -1
- package/data/persistence/containers/containerSyncWatermarkPersistence.d.ts +11 -3
- package/data/persistence/containers/containerSyncWatermarkPersistence.js +46 -58
- package/data/persistence/containers/containerSyncWatermarkPersistence.js.map +1 -1
- package/data/persistence/documents/documentsPersistence.js +4 -9
- package/data/persistence/documents/documentsPersistence.js.map +1 -1
- package/data/persistence/documents/internal/discardDocument.js +1 -2
- package/data/persistence/documents/internal/discardDocument.js.map +1 -1
- package/data/persistence/documents/internal/documentDeletionPersistence.js +1 -2
- package/data/persistence/documents/internal/documentDeletionPersistence.js.map +1 -1
- package/data/persistence/documents/internal/documentMutationPersistence.js +17 -25
- package/data/persistence/documents/internal/documentMutationPersistence.js.map +1 -1
- package/data/persistence/documents/internal/orphanSideRows.js +2 -14
- package/data/persistence/documents/internal/orphanSideRows.js.map +1 -1
- package/data/persistence/documents/internal/syncQueuePersistence.js +3 -3
- package/data/persistence/documents/internal/syncQueuePersistence.js.map +1 -1
- package/data/sqlite/documentHistoryPersistence.d.ts +9 -3
- package/data/sqlite/documentHistoryPersistence.js +31 -8
- package/data/sqlite/documentHistoryPersistence.js.map +1 -1
- package/data/sqlite/documentPendingUpdatePersistence.d.ts +58 -11
- package/data/sqlite/documentPendingUpdatePersistence.js +137 -63
- package/data/sqlite/documentPendingUpdatePersistence.js.map +1 -1
- package/data/sqlite/documentPersistence.d.ts +1 -1
- package/data/sqlite/documentPersistence.js +1 -1
- package/data/sqlite/documentPersistence.js.map +1 -1
- package/data/sqlite/documentPersistenceTypes.d.ts +2 -2
- package/data/sqlite/documentPersistenceTypes.js.map +1 -1
- package/data/sqlite/documentRecordPersistence.js +5 -4
- package/data/sqlite/documentRecordPersistence.js.map +1 -1
- package/data/sqlite/schema.d.ts +167 -517
- package/data/sqlite/schema.js +68 -82
- package/data/sqlite/schema.js.map +1 -1
- package/internal/api-client/ApiClient.d.ts +18 -41
- package/internal/api-client/ApiClient.js +9 -14
- package/internal/api-client/ApiClient.js.map +1 -1
- package/internal/api-client/apiRequestRuntime.d.ts +2 -0
- package/internal/api-client/apiRequestRuntime.js +4 -0
- package/internal/api-client/apiRequestRuntime.js.map +1 -1
- package/internal/api-client/binaryRequestOperationTransport.d.ts +18 -0
- package/internal/api-client/index.d.ts +1 -0
- package/internal/api-client/index.js +1 -0
- package/internal/api-client/index.js.map +1 -1
- package/internal/api-client/operationTransportFactory.d.ts +6 -0
- package/internal/api-client/requestInternals.js +7 -7
- package/internal/api-client/requestInternals.js.map +1 -1
- package/internal/api-client/routes/blobs/get.d.ts +7 -0
- package/internal/api-client/routes/blobs/get.js.map +1 -1
- package/internal/api-client/routes/blobs/multipart.js +1 -11
- package/internal/api-client/routes/blobs/multipart.js.map +1 -1
- package/internal/api-client/routes/blobs/multipartUpload.d.ts +14 -0
- package/internal/api-client/routes/blobs/multipartUpload.js +94 -43
- package/internal/api-client/routes/blobs/multipartUpload.js.map +1 -1
- package/internal/api-client/routes/containers/reads.d.ts +13 -7
- package/internal/api-client/routes/containers/reads.js +36 -15
- package/internal/api-client/routes/containers/reads.js.map +1 -1
- package/internal/api-client/types.d.ts +1 -5
- package/internal/api-client/types.js.map +1 -1
- package/internal/validators/containerReadRefinements.js +15 -5
- package/internal/validators/containerReadRefinements.js.map +1 -1
- package/internal/validators/containerReadSchemas.d.ts +36 -0
- package/internal/validators/containerReadSchemas.js +64 -1
- package/internal/validators/containerReadSchemas.js.map +1 -1
- package/internal/validators/operation/attachments.d.ts +80 -160
- package/internal/validators/operation/auth.d.ts +760 -1664
- package/internal/validators/operation/blobDownloads.js +1 -1
- package/internal/validators/operation/blobDownloads.js.map +1 -1
- package/internal/validators/operation/containerMutations.d.ts +416 -856
- package/internal/validators/operation/containerReads.d.ts +118 -182
- package/internal/validators/operation/containerReads.js +20 -54
- package/internal/validators/operation/containerReads.js.map +1 -1
- package/internal/validators/operation/documentMutations.d.ts +120 -264
- package/internal/validators/operation/documentSync.d.ts +40 -80
- package/internal/validators/operation/index.d.ts +2 -2
- package/internal/validators/operation/index.js +2 -2
- package/internal/validators/operation/index.js.map +1 -1
- package/internal/validators/operation/multipartBlobs.d.ts +70 -190
- package/internal/validators/operation/multipartBlobs.js +2 -24
- package/internal/validators/operation/multipartBlobs.js.map +1 -1
- package/internal/validators/operation/multipartPartUploads.d.ts +5 -5
- package/internal/validators/operation/multipartPartUploads.js +6 -20
- package/internal/validators/operation/multipartPartUploads.js.map +1 -1
- package/internal/validators/operation/organizations.d.ts +882 -1906
- package/internal/validators/operation/principals.d.ts +120 -240
- package/internal/validators/operation/registry.d.ts +3 -4
- package/internal/validators/operation/registry.js +3 -4
- package/internal/validators/operation/registry.js.map +1 -1
- package/internal/validators/request/blob.d.ts +100 -201
- package/internal/validators/request/blob.js +6 -13
- package/internal/validators/request/blob.js.map +1 -1
- package/internal/validators/request/container.d.ts +136 -272
- package/internal/validators/request/container.js +16 -10
- package/internal/validators/request/container.js.map +1 -1
- package/internal/validators/request/containerDocumentLanes.d.ts +23 -0
- package/internal/validators/request/containerDocumentLanes.js +23 -0
- package/internal/validators/request/containerDocumentLanes.js.map +1 -0
- package/internal/validators/request/containerManifestRef.d.ts +54 -0
- package/internal/validators/request/containerManifestRef.js +28 -0
- package/internal/validators/request/containerManifestRef.js.map +1 -0
- package/internal/validators/request/containerMetadata.d.ts +160 -368
- package/internal/validators/request/containerParentLanes.js +5 -35
- package/internal/validators/request/containerParentLanes.js.map +1 -1
- package/internal/validators/request/document.d.ts +100 -245
- package/internal/validators/request/document.js +1 -2
- package/internal/validators/request/document.js.map +1 -1
- package/internal/validators/request/documentSyncSchema.d.ts +42 -109
- package/internal/validators/request/documentSyncSchema.js +8 -25
- package/internal/validators/request/documentSyncSchema.js.map +1 -1
- package/internal/validators/request/index.d.ts +2 -1
- package/internal/validators/request/index.js +2 -1
- package/internal/validators/request/index.js.map +1 -1
- package/internal/validators/request/organization.d.ts +160 -320
- package/internal/validators/request/organizationProvisioning.d.ts +2424 -5312
- package/internal/validators/request/principal.d.ts +160 -320
- package/internal/validators/request/registration.d.ts +760 -1664
- package/internal/validators/response/blob.d.ts +48 -27
- package/internal/validators/response/blob.js +3 -10
- package/internal/validators/response/blob.js.map +1 -1
- package/internal/validators/response/containerDocumentLanes.d.ts +82 -0
- package/internal/validators/response/containerDocumentLanes.js +35 -0
- package/internal/validators/response/containerDocumentLanes.js.map +1 -0
- package/internal/validators/response/containerParentLanes.js +3 -15
- package/internal/validators/response/containerParentLanes.js.map +1 -1
- package/internal/validators/response/index.d.ts +3 -2
- package/internal/validators/response/index.js +2 -2
- package/internal/validators/response/index.js.map +1 -1
- package/internal/validators/util/blob.d.ts +7 -0
- package/internal/validators/util/blob.js +7 -0
- package/internal/validators/util/blob.js.map +1 -1
- package/internal/validators/util/blobTransferSchemas.d.ts +41 -0
- package/internal/validators/util/blobTransferSchemas.js +39 -0
- package/internal/validators/util/blobTransferSchemas.js.map +1 -0
- package/internal/validators/util/containerLimits.d.ts +7 -0
- package/internal/validators/util/containerLimits.js +7 -0
- package/internal/validators/util/containerLimits.js.map +1 -1
- package/internal/validators/util/index.d.ts +2 -1
- package/internal/validators/util/index.js +2 -1
- package/internal/validators/util/index.js.map +1 -1
- package/package.json +1 -1
- package/sync/reconciliation/knownContainerSweep.js +57 -54
- package/sync/reconciliation/knownContainerSweep.js.map +1 -1
- package/sync/reconciliation/reconcileContainer.js +49 -17
- package/sync/reconciliation/reconcileContainer.js.map +1 -1
- package/sync/reconciliation/service.js +44 -16
- package/sync/reconciliation/service.js.map +1 -1
- package/sync/reconciliation/serviceTypes.d.ts +23 -2
- package/sync/reconciliation/serviceTypes.js.map +1 -1
- package/workflows/blobs/multipartFirstParts.js +82 -0
- package/workflows/blobs/multipartFirstParts.js.map +1 -0
- package/workflows/blobs/multipartStageResolution.js +83 -0
- package/workflows/blobs/multipartStageResolution.js.map +1 -0
- package/workflows/blobs/multipartUpload.js +45 -89
- package/workflows/blobs/multipartUpload.js.map +1 -1
- package/workflows/container-contents/containerDocumentListing.js +92 -38
- package/workflows/container-contents/containerDocumentListing.js.map +1 -1
- package/workflows/container-contents/containerPersistence.d.ts +8 -2
- package/workflows/container-contents/containerPersistence.js +6 -7
- package/workflows/container-contents/containerPersistence.js.map +1 -1
- package/workflows/container-contents/documentDiscovery.d.ts +3 -3
- package/workflows/container-contents/documentDiscovery.js +24 -96
- package/workflows/container-contents/documentDiscovery.js.map +1 -1
- package/workflows/container-contents/documentDiscoveryApply.js +65 -0
- package/workflows/container-contents/documentDiscoveryApply.js.map +1 -0
- package/workflows/container-contents/documentDiscoveryLanes.js +53 -0
- package/workflows/container-contents/documentDiscoveryLanes.js.map +1 -0
- package/workflows/container-contents/documentDiscoveryTypes.d.ts +25 -19
- package/workflows/container-contents/documentDiscoveryTypes.js.map +1 -1
- package/workflows/container-contents/documentPriming.js +2 -1
- package/workflows/container-contents/documentPriming.js.map +1 -1
- package/workflows/container-contents/documentQueries/sql.js +4 -4
- package/workflows/container-contents/documentQueries/sql.js.map +1 -1
- package/workflows/container-contents/pendingWrites/deferredTails.js +6 -3
- package/workflows/container-contents/pendingWrites/deferredTails.js.map +1 -1
- package/workflows/container-contents/pendingWrites/sources.js +2 -1
- package/workflows/container-contents/pendingWrites/sources.js.map +1 -1
- package/workflows/container-contents/pendingWrites.js +2 -1
- package/workflows/container-contents/pendingWrites.js.map +1 -1
- package/workflows/container-contents/remoteHydration/laneFetchMarkers.js +11 -17
- package/workflows/container-contents/remoteHydration/laneFetchMarkers.js.map +1 -1
- package/workflows/containers/child/create.js +3 -3
- package/workflows/containers/child/create.js.map +1 -1
- package/workflows/containers/child/move.js +2 -2
- package/workflows/containers/child/move.js.map +1 -1
- package/workflows/containers/child/mutationRequestCore.js +5 -4
- package/workflows/containers/child/mutationRequestCore.js.map +1 -1
- package/workflows/containers/child/recitePlan.js +3 -2
- package/workflows/containers/child/recitePlan.js.map +1 -1
- package/workflows/containers/child/sharePlanCore.js +2 -2
- package/workflows/containers/child/sharePlanCore.js.map +1 -1
- package/workflows/containers/root/create.js +2 -2
- package/workflows/containers/root/create.js.map +1 -1
- package/workflows/registration/provisioningBootstrapBuilders.d.ts +20 -46
- package/workflows/sync/remoteReset.js +9 -9
- package/workflows/sync/remoteReset.js.map +1 -1
- package/workflows/sync/remoteResetCursorScope.js +10 -18
- package/workflows/sync/remoteResetCursorScope.js.map +1 -1
- package/workflows/sync/remoteResetPlans.js +6 -3
- package/workflows/sync/remoteResetPlans.js.map +1 -1
- package/workflows/sync/remoteResetScope.js +4 -11
- package/workflows/sync/remoteResetScope.js.map +1 -1
- package/internal/validators/operation/blobTransferSchemas.js +0 -11
- package/internal/validators/operation/blobTransferSchemas.js.map +0 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"multipartUpload.js","sourceRoot":"","sources":["multipartUpload.ts"],"names":[],"mappings":"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","sourcesContent":["import { bytesToHex, MAX_BLOB_CHUNK_COUNT } from \"@tearleads/crypto\";\nimport type {\n InitiateMultipartBlobStageResponse,\n MultipartBlobStagePart,\n MultipartBlobStageStatusResponse,\n} from \"@tearleads/validators/response\";\nimport { MAX_MULTIPART_BLOB_PART_BYTES } from \"@tearleads/validators/util\";\nimport {\n BLOB_TRANSFER_CONCURRENCY,\n runBlobTransfer,\n} from \"../../data/blobs/transferPool\";\nimport type { BlobEncryptionPlan } from \"../../data/documents/blob/shared/crypto\";\nimport type {\n BlobAttachmentApi,\n MultipartStageResolvedListener,\n MultipartUploadProgressListener,\n UploadDocumentAttachmentInput,\n} from \"../../data/documents/blob/shared/types\";\nimport {\n assertMultipartStageReplaceable,\n multipartApiFailureMessage,\n multipartStagePath,\n} from \"./multipartStageFailures\";\n\nconst DEFAULT_MULTIPART_UPLOAD_CONCURRENCY = 4;\nconst MAX_MULTIPART_UPLOAD_CONCURRENCY = 4;\n// Fetch exposes no upload progress on every host. Allow a slow shared uplink\n// (8 KiB/s total, shared by every admitted part), plus setup/encryption time.\nconst MULTIPART_TRANSFER_BYTES_PER_SECOND = 8 * 1024;\n\ninterface MultipartPartCommit {\n readonly etag: string;\n readonly partNumber: number;\n}\n\ninterface MultipartPartUploadTask {\n readonly byteLength: number;\n readonly partIndex: number;\n readonly partNumber: number;\n}\n\nfunction pathSegment(value: number | string): string {\n return encodeURIComponent(String(value));\n}\n\nfunction multipartPartRequest(input: {\n readonly apiClient: BlobAttachmentApi;\n readonly partNumber: number;\n readonly stageId: string;\n}) {\n const basePath = `${multipartStagePath(input.stageId)}/parts/${pathSegment(input.partNumber)}`;\n const authorization = { method: \"POST\" as const, path: `${basePath}/upload` };\n // A successful authorization clears its previous failure. If a memory-store\n // byte request then fails, its separate transport diagnostic is authoritative.\n return input.apiClient.getRequestFailure?.(authorization)\n ? authorization\n : { method: \"PUT\" as const, path: `${basePath}/bytes` };\n}\n\nasync function sha256BytesHex(bytes: Uint8Array<ArrayBuffer>): Promise<string> {\n return bytesToHex(\n new Uint8Array(await crypto.subtle.digest(\"SHA-256\", bytes)),\n );\n}\n\nfunction normalizeMultipartUploadConcurrency(\n value: number | undefined,\n): number {\n if (value === undefined) {\n return DEFAULT_MULTIPART_UPLOAD_CONCURRENCY;\n }\n if (\n !Number.isInteger(value) ||\n value <= 0 ||\n value > MAX_MULTIPART_UPLOAD_CONCURRENCY\n ) {\n throw new Error(\n `Multipart blob upload concurrency must be between 1 and ${MAX_MULTIPART_UPLOAD_CONCURRENCY}`,\n );\n }\n\n return value;\n}\n\nfunction isMultipartPartCommit(\n part: MultipartPartCommit | undefined,\n): part is MultipartPartCommit {\n return part !== undefined;\n}\n\nasync function uploadMultipartPartTasks(input: {\n readonly apiClient: BlobAttachmentApi;\n readonly completeParts: (MultipartPartCommit | undefined)[];\n readonly concurrency: number;\n readonly encryption: BlobEncryptionPlan;\n readonly onPartUploaded?: (task: MultipartPartUploadTask) => void;\n readonly stageId: string;\n readonly tasks: readonly MultipartPartUploadTask[];\n readonly uploadId: string;\n}): Promise<void> {\n let failed = false;\n let firstFailure: { readonly error: unknown } | undefined;\n let nextTaskIndex = 0;\n const recordFailure = (error: unknown) => {\n failed = true;\n firstFailure ??= { error };\n };\n\n async function worker(): Promise<void> {\n while (!failed) {\n const task = input.tasks[nextTaskIndex];\n nextTaskIndex += 1;\n if (!task) {\n return;\n }\n\n try {\n await runBlobTransfer(\n input.apiClient,\n async (signal) => {\n if (failed) return;\n const aborted = () => recordFailure(signal.reason);\n signal.addEventListener(\"abort\", aborted, { once: true });\n try {\n const uploaded = await uploadMultipartPartBytes({\n apiClient: input.apiClient,\n encryption: input.encryption,\n stageId: input.stageId,\n task,\n uploadId: input.uploadId,\n signal,\n });\n signal.throwIfAborted();\n if (!uploaded) {\n failed = true;\n throw new Error(\n multipartApiFailureMessage({\n apiClient: input.apiClient,\n fallback: `Multipart blob part ${task.partNumber} upload failed for stage ${input.stageId}.`,\n request: multipartPartRequest({\n apiClient: input.apiClient,\n partNumber: task.partNumber,\n stageId: input.stageId,\n }),\n }),\n );\n }\n\n input.completeParts[task.partIndex] = {\n etag: uploaded.part.etag,\n partNumber: task.partNumber,\n };\n input.onPartUploaded?.(task);\n } catch (error) {\n // Publish failure before releasing the permit to a queued sibling.\n // Already-started parts still record success and drain through the join.\n recordFailure(error);\n } finally {\n signal.removeEventListener(\"abort\", aborted);\n }\n },\n 120_000 +\n Math.ceil(\n (task.byteLength * BLOB_TRANSFER_CONCURRENCY * 1000) /\n MULTIPART_TRANSFER_BYTES_PER_SECOND,\n ),\n );\n } catch (error) {\n recordFailure(error);\n }\n }\n }\n\n const workerCount = Math.min(input.concurrency, input.tasks.length);\n await Promise.all(Array.from({ length: workerCount }, () => worker()));\n if (firstFailure) {\n throw firstFailure.error;\n }\n}\n\nasync function uploadMultipartPartBytes(input: {\n readonly apiClient: BlobAttachmentApi;\n readonly encryption: BlobEncryptionPlan;\n readonly stageId: string;\n readonly task: MultipartPartUploadTask;\n readonly uploadId: string;\n readonly signal: AbortSignal;\n}): ReturnType<BlobAttachmentApi[\"uploadMultipartBlobPartBytes\"]> {\n input.signal.throwIfAborted();\n const encryptedPartBytes = await input.encryption.encryptPart(\n input.task.partIndex,\n );\n if (encryptedPartBytes.byteLength !== input.task.byteLength) {\n throw new Error(\n `Encrypted blob part ${input.task.partNumber} byte length mismatch: expected ${input.task.byteLength.toLocaleString()}, received ${encryptedPartBytes.byteLength.toLocaleString()}.`,\n );\n }\n\n const sha256 = await sha256BytesHex(encryptedPartBytes);\n input.signal.throwIfAborted();\n const uploaded = await input.apiClient.uploadMultipartBlobPartBytes(\n input.stageId,\n input.task.partNumber,\n {\n byteLength: encryptedPartBytes.byteLength,\n encryptedBytes: encryptedPartBytes,\n sha256,\n uploadId: input.uploadId,\n },\n { signal: input.signal },\n );\n input.signal.throwIfAborted();\n if (\n uploaded &&\n (uploaded.stageId !== input.stageId ||\n uploaded.uploadId !== input.uploadId ||\n uploaded.part.partNumber !== input.task.partNumber ||\n uploaded.part.byteLength !== input.task.byteLength)\n ) {\n throw new Error(\n `Multipart blob part ${input.task.partNumber} response does not match its request.`,\n );\n }\n return uploaded;\n}\n\nfunction uploadedPartsByPartNumber(\n parts: readonly MultipartBlobStagePart[],\n): ReadonlyMap<number, MultipartBlobStagePart> {\n return new Map(parts.map((part) => [part.partNumber, part]));\n}\n\nfunction isMultipartStageStatus(\n stage: InitiateMultipartBlobStageResponse | MultipartBlobStageStatusResponse,\n): stage is MultipartBlobStageStatusResponse {\n return \"completed\" in stage && typeof stage.completed === \"boolean\";\n}\n\ninterface MultipartUploadProgressState {\n bytesTotal: number;\n bytesUploaded: number;\n partsCompleted: number;\n partsTotal: number;\n}\n\ninterface MultipartPartPlan {\n completeParts: (MultipartPartCommit | undefined)[];\n progress: MultipartUploadProgressState;\n uploadTasks: MultipartPartUploadTask[];\n}\n\n/**\n * Plans encrypted part indices without materializing their bytes, reusing any\n * already-staged parts and seeding cumulative progress for resumed uploads.\n */\nfunction planMultipartParts(input: {\n encryption: BlobEncryptionPlan;\n uploadedParts: ReadonlyMap<number, MultipartBlobStagePart>;\n}): MultipartPartPlan {\n const { encryption, uploadedParts } = input;\n const completeParts: (MultipartPartCommit | undefined)[] = new Array(\n encryption.partCount,\n );\n const uploadTasks: MultipartPartUploadTask[] = [];\n const progress: MultipartUploadProgressState = {\n bytesTotal: 0,\n bytesUploaded: 0,\n partsCompleted: 0,\n partsTotal: encryption.partCount,\n };\n\n for (let partIndex = 0; partIndex < encryption.partCount; partIndex += 1) {\n const partNumber = partIndex + 1;\n const byteLength = encryption.getPartByteLength(partIndex);\n progress.bytesTotal += byteLength;\n const uploadedPart = uploadedParts.get(partNumber);\n if (uploadedPart?.byteLength === byteLength) {\n completeParts[partIndex] = {\n etag: uploadedPart.etag,\n partNumber,\n };\n progress.partsCompleted += 1;\n progress.bytesUploaded += byteLength;\n continue;\n }\n\n uploadTasks.push({\n byteLength,\n partIndex,\n partNumber,\n });\n }\n\n return { completeParts, progress, uploadTasks };\n}\n\nasync function resolveMultipartStageStatus(input: {\n readonly apiClient: BlobAttachmentApi;\n readonly byteLength: number;\n readonly multipart: NonNullable<UploadDocumentAttachmentInput[\"multipart\"]>;\n readonly organizationId: string;\n readonly onStageUnavailable?:\n | ((stageId: string) => Promise<void>)\n | undefined;\n readonly sha256: string;\n}): Promise<MultipartBlobStageStatusResponse> {\n const resumeStageId = input.multipart.resumeStageId;\n if (resumeStageId) {\n const resumed = await input.apiClient.getMultipartBlobStage(resumeStageId);\n if (!resumed) {\n await assertMultipartStageReplaceable({\n apiClient: input.apiClient,\n onStageUnavailable: input.onStageUnavailable,\n stageId: resumeStageId,\n });\n }\n if (resumed && resumed.stageId !== resumeStageId) {\n throw new Error(\n `Multipart blob resume response does not match stage ${resumeStageId}.`,\n );\n }\n if (resumed) {\n // A successful lookup is authoritative: a metadata mismatch indicates\n // inconsistent persisted state or a bad server response, not proof that\n // the existing stage is safe to abandon.\n if (\n resumed.organizationId !== input.organizationId ||\n resumed.byteLength !== input.byteLength ||\n resumed.sha256 !== input.sha256\n ) {\n throw new Error(\n `Multipart blob resume stage ${resumeStageId} does not match the requested organization or bytes.`,\n );\n }\n return resumed;\n }\n }\n\n const stage = await input.apiClient.initiateMultipartBlobStage({\n organizationId: input.organizationId,\n byteLength: input.byteLength,\n sha256: input.sha256,\n });\n if (!stage) {\n throw new Error(\n multipartApiFailureMessage({\n apiClient: input.apiClient,\n fallback: `Multipart blob stage initiation failed for ${input.byteLength.toLocaleString()} bytes.`,\n request: { method: \"POST\", path: \"/blobs/stages/multipart\" },\n }),\n );\n }\n if (\n stage.organizationId !== input.organizationId ||\n stage.byteLength !== input.byteLength ||\n stage.sha256 !== input.sha256\n ) {\n throw new Error(\n \"Multipart blob stage initiation response does not match its request.\",\n );\n }\n\n return isMultipartStageStatus(stage)\n ? stage\n : {\n ...stage,\n completed: false,\n };\n}\n\nfunction assertMultipartPlanLimits(encryption: BlobEncryptionPlan): void {\n if (encryption.partCount > MAX_BLOB_CHUNK_COUNT) {\n throw new Error(\n `Multipart blob upload would require ${encryption.partCount.toLocaleString()} parts; the maximum is ${MAX_BLOB_CHUNK_COUNT.toLocaleString()} parts.`,\n );\n }\n for (let index = 0; index < encryption.partCount; index += 1) {\n if (encryption.getPartByteLength(index) > MAX_MULTIPART_BLOB_PART_BYTES) {\n throw new Error(\n `Encrypted blob part ${index + 1} exceeds the 32 MiB multipart part limit.`,\n );\n }\n }\n}\n\nexport async function stageMultipartBlobAttachment(input: {\n readonly apiClient: BlobAttachmentApi;\n readonly encryption: BlobEncryptionPlan;\n readonly multipart: NonNullable<UploadDocumentAttachmentInput[\"multipart\"]>;\n readonly organizationId: string;\n readonly onMultipartProgress?: MultipartUploadProgressListener | undefined;\n readonly onStageResolved?: MultipartStageResolvedListener | undefined;\n readonly onStageUnavailable?:\n | ((stageId: string) => Promise<void>)\n | undefined;\n}): Promise<string | null> {\n if (input.multipart.partSize !== input.encryption.chunkSize) {\n throw new Error(\n \"Multipart part size must match the encryption chunk size exactly.\",\n );\n }\n assertMultipartPlanLimits(input.encryption);\n const status = await resolveMultipartStageStatus({\n apiClient: input.apiClient,\n byteLength: input.encryption.byteLength,\n multipart: input.multipart,\n organizationId: input.organizationId,\n onStageUnavailable: input.onStageUnavailable,\n sha256: input.encryption.sha256,\n });\n // Surface the stage id before any parts upload so the caller can persist it;\n // an upload interrupted mid-flight can then resume this same stage.\n await input.onStageResolved?.({\n partSize: input.encryption.chunkSize,\n stageId: status.stageId,\n });\n if (status.completed) {\n input.onMultipartProgress?.({\n bytesTotal: input.encryption.byteLength,\n bytesUploaded: input.encryption.byteLength,\n partsCompleted: input.encryption.partCount,\n partsTotal: input.encryption.partCount,\n });\n return status.stageId;\n }\n\n const { completeParts, progress, uploadTasks } = planMultipartParts({\n encryption: input.encryption,\n uploadedParts: uploadedPartsByPartNumber(status.uploadedParts),\n });\n\n const reportMultipartProgress = () => {\n input.onMultipartProgress?.({\n bytesTotal: progress.bytesTotal,\n bytesUploaded: progress.bytesUploaded,\n partsCompleted: progress.partsCompleted,\n partsTotal: progress.partsTotal,\n });\n };\n reportMultipartProgress();\n\n await uploadMultipartPartTasks({\n apiClient: input.apiClient,\n completeParts,\n concurrency: normalizeMultipartUploadConcurrency(\n input.multipart.uploadConcurrency,\n ),\n encryption: input.encryption,\n onPartUploaded: (task) => {\n progress.partsCompleted += 1;\n progress.bytesUploaded += task.byteLength;\n reportMultipartProgress();\n },\n stageId: status.stageId,\n tasks: uploadTasks,\n uploadId: status.uploadId,\n });\n const committedParts = completeParts.filter(isMultipartPartCommit);\n if (committedParts.length !== input.encryption.partCount) {\n throw new Error(\n `Multipart blob upload incomplete for stage ${status.stageId}: committed ${committedParts.length.toLocaleString()} of ${input.encryption.partCount.toLocaleString()} parts.`,\n );\n }\n\n const completed = await input.apiClient.completeMultipartBlobStage(\n status.stageId,\n {\n parts: committedParts,\n uploadId: status.uploadId,\n },\n );\n if (!completed) {\n throw new Error(\n multipartApiFailureMessage({\n apiClient: input.apiClient,\n fallback: `Multipart blob stage completion failed for stage ${status.stageId} with ${committedParts.length.toLocaleString()} committed parts.`,\n request: {\n method: \"POST\",\n path: `${multipartStagePath(status.stageId)}/complete`,\n },\n }),\n );\n }\n if (\n completed.organizationId !== input.organizationId ||\n completed.stageId !== status.stageId ||\n completed.byteLength !== input.encryption.byteLength ||\n completed.sha256 !== input.encryption.sha256\n ) {\n throw new Error(\n `Multipart blob stage ${status.stageId} completion response does not match its request.`,\n );\n }\n\n return completed.stageId;\n}\n"]}
|
|
1
|
+
{"version":3,"file":"multipartUpload.js","sourceRoot":"","sources":["multipartUpload.ts"],"names":[],"mappings":"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","sourcesContent":["import { MAX_BLOB_CHUNK_COUNT } from \"@tearleads/crypto\";\nimport type { MultipartBlobStagePart } from \"@tearleads/validators/response\";\nimport { MAX_MULTIPART_BLOB_PART_BYTES } from \"@tearleads/validators/util\";\nimport {\n BLOB_TRANSFER_CONCURRENCY,\n runBlobTransfer,\n} from \"../../data/blobs/transferPool\";\nimport type { BlobEncryptionPlan } from \"../../data/documents/blob/shared/crypto\";\nimport type {\n BlobAttachmentApi,\n MultipartStageResolvedListener,\n MultipartUploadProgressListener,\n UploadDocumentAttachmentInput,\n} from \"../../data/documents/blob/shared/types\";\nimport {\n type MultipartPartSources,\n type PreparedFirstPart,\n type PreparedMultipartPart,\n releasePreparedParts,\n sha256BytesHex,\n takePartSource,\n} from \"./multipartFirstParts\";\nimport {\n multipartApiFailureMessage,\n multipartStagePath,\n} from \"./multipartStageFailures\";\nimport { resolveMultipartStage } from \"./multipartStageResolution\";\n\nconst DEFAULT_MULTIPART_UPLOAD_CONCURRENCY = 4;\nconst MAX_MULTIPART_UPLOAD_CONCURRENCY = 4;\n// Fetch exposes no upload progress on every host. Allow a slow shared uplink\n// (8 KiB/s total, shared by every admitted part), plus setup/encryption time.\nconst MULTIPART_TRANSFER_BYTES_PER_SECOND = 8 * 1024;\n\ninterface MultipartPartCommit {\n readonly etag: string;\n readonly partNumber: number;\n}\n\ninterface MultipartPartUploadTask {\n readonly byteLength: number;\n readonly partIndex: number;\n readonly partNumber: number;\n}\n\nfunction pathSegment(value: number | string): string {\n return encodeURIComponent(String(value));\n}\n\nfunction multipartPartRequest(input: {\n readonly apiClient: BlobAttachmentApi;\n readonly partNumber: number;\n readonly stageId: string;\n}) {\n const basePath = `${multipartStagePath(input.stageId)}/parts/${pathSegment(input.partNumber)}`;\n const authorization = { method: \"POST\" as const, path: `${basePath}/upload` };\n // A successful authorization clears its previous failure. If a memory-store\n // byte request then fails, its separate transport diagnostic is authoritative.\n return input.apiClient.getRequestFailure?.(authorization)\n ? authorization\n : { method: \"PUT\" as const, path: `${basePath}/bytes` };\n}\n\nfunction normalizeMultipartUploadConcurrency(\n value: number | undefined,\n): number {\n if (value === undefined) {\n return DEFAULT_MULTIPART_UPLOAD_CONCURRENCY;\n }\n if (\n !Number.isInteger(value) ||\n value <= 0 ||\n value > MAX_MULTIPART_UPLOAD_CONCURRENCY\n ) {\n throw new Error(\n `Multipart blob upload concurrency must be between 1 and ${MAX_MULTIPART_UPLOAD_CONCURRENCY}`,\n );\n }\n\n return value;\n}\n\nfunction isMultipartPartCommit(\n part: MultipartPartCommit | undefined,\n): part is MultipartPartCommit {\n return part !== undefined;\n}\n\nasync function uploadMultipartPartTasks(input: {\n readonly apiClient: BlobAttachmentApi;\n readonly completeParts: (MultipartPartCommit | undefined)[];\n readonly concurrency: number;\n readonly encryption: BlobEncryptionPlan;\n readonly onPartUploaded?: (task: MultipartPartUploadTask) => void;\n readonly prepared: Map<number, PreparedFirstPart>;\n readonly sources: MultipartPartSources;\n readonly stageId: string;\n readonly tasks: readonly MultipartPartUploadTask[];\n readonly uploadId: string;\n}): Promise<void> {\n let failed = false;\n let firstFailure: { readonly error: unknown } | undefined;\n let nextTaskIndex = 0;\n const recordFailure = (error: unknown) => {\n failed = true;\n firstFailure ??= { error };\n };\n\n async function worker(): Promise<void> {\n while (!failed) {\n const task = input.tasks[nextTaskIndex];\n nextTaskIndex += 1;\n if (!task) {\n return;\n }\n\n try {\n // A first part already holds the pool slot its prepared bytes use.\n const heldPermit = input.prepared.get(task.partIndex)?.permit;\n await runBlobTransfer(\n input.apiClient,\n async (signal) => {\n if (failed) return;\n const aborted = () => recordFailure(signal.reason);\n signal.addEventListener(\"abort\", aborted, { once: true });\n try {\n const uploaded = await uploadMultipartPartBytes({\n apiClient: input.apiClient,\n encryption: input.encryption,\n prepared: input.prepared,\n sources: input.sources,\n stageId: input.stageId,\n task,\n uploadId: input.uploadId,\n signal,\n });\n signal.throwIfAborted();\n if (!uploaded) {\n failed = true;\n throw new Error(\n multipartApiFailureMessage({\n apiClient: input.apiClient,\n fallback: `Multipart blob part ${task.partNumber} upload failed for stage ${input.stageId}.`,\n request: multipartPartRequest({\n apiClient: input.apiClient,\n partNumber: task.partNumber,\n stageId: input.stageId,\n }),\n }),\n );\n }\n\n input.completeParts[task.partIndex] = {\n etag: uploaded.part.etag,\n partNumber: task.partNumber,\n };\n input.onPartUploaded?.(task);\n } catch (error) {\n // Publish failure before releasing the permit to a queued sibling.\n // Already-started parts still record success and drain through the join.\n recordFailure(error);\n } finally {\n signal.removeEventListener(\"abort\", aborted);\n }\n },\n 120_000 +\n Math.ceil(\n (task.byteLength * BLOB_TRANSFER_CONCURRENCY * 1000) /\n MULTIPART_TRANSFER_BYTES_PER_SECOND,\n ),\n heldPermit,\n );\n } catch (error) {\n recordFailure(error);\n }\n }\n }\n\n const workerCount = Math.min(input.concurrency, input.tasks.length);\n await Promise.all(Array.from({ length: workerCount }, () => worker()));\n if (firstFailure) {\n throw firstFailure.error;\n }\n}\n\n// A first part was encrypted once before the stage existed; hand its bytes\n// over (and release them) instead of encrypting it again.\nasync function encryptedPart(input: {\n readonly encryption: BlobEncryptionPlan;\n readonly prepared: Map<number, PreparedFirstPart>;\n readonly task: MultipartPartUploadTask;\n}): Promise<PreparedMultipartPart> {\n const prepared = input.prepared.get(input.task.partIndex);\n if (prepared) {\n input.prepared.delete(input.task.partIndex);\n return prepared;\n }\n const bytes = await input.encryption.encryptPart(input.task.partIndex);\n if (bytes.byteLength !== input.task.byteLength) {\n throw new Error(\n `Encrypted blob part ${input.task.partNumber} byte length mismatch: expected ${input.task.byteLength.toLocaleString()}, received ${bytes.byteLength.toLocaleString()}.`,\n );\n }\n return { bytes, sha256: await sha256BytesHex(bytes) };\n}\n\nasync function uploadMultipartPartBytes(input: {\n readonly apiClient: BlobAttachmentApi;\n readonly encryption: BlobEncryptionPlan;\n readonly prepared: Map<number, PreparedFirstPart>;\n readonly sources: MultipartPartSources;\n readonly stageId: string;\n readonly task: MultipartPartUploadTask;\n readonly uploadId: string;\n readonly signal: AbortSignal;\n}): ReturnType<BlobAttachmentApi[\"uploadMultipartBlobPartBytes\"]> {\n input.signal.throwIfAborted();\n const { bytes: encryptedPartBytes, sha256 } = await encryptedPart(input);\n input.signal.throwIfAborted();\n const source = takePartSource(input.sources, input.task.partNumber);\n const uploaded = await input.apiClient.uploadMultipartBlobPartBytes(\n input.stageId,\n input.task.partNumber,\n {\n byteLength: encryptedPartBytes.byteLength,\n encryptedBytes: encryptedPartBytes,\n sha256,\n ...(source === undefined ? {} : { source }),\n uploadId: input.uploadId,\n },\n { signal: input.signal },\n );\n input.signal.throwIfAborted();\n if (\n uploaded &&\n (uploaded.stageId !== input.stageId ||\n uploaded.uploadId !== input.uploadId ||\n uploaded.part.partNumber !== input.task.partNumber ||\n uploaded.part.byteLength !== input.task.byteLength)\n ) {\n throw new Error(\n `Multipart blob part ${input.task.partNumber} response does not match its request.`,\n );\n }\n return uploaded;\n}\n\nfunction uploadedPartsByPartNumber(\n parts: readonly MultipartBlobStagePart[],\n): ReadonlyMap<number, MultipartBlobStagePart> {\n return new Map(parts.map((part) => [part.partNumber, part]));\n}\n\ninterface MultipartUploadProgressState {\n bytesTotal: number;\n bytesUploaded: number;\n partsCompleted: number;\n partsTotal: number;\n}\n\ninterface MultipartPartPlan {\n completeParts: (MultipartPartCommit | undefined)[];\n progress: MultipartUploadProgressState;\n uploadTasks: MultipartPartUploadTask[];\n}\n\n/**\n * Plans encrypted part indices without materializing their bytes, reusing any\n * already-staged parts and seeding cumulative progress for resumed uploads.\n */\nfunction planMultipartParts(input: {\n encryption: BlobEncryptionPlan;\n uploadedParts: ReadonlyMap<number, MultipartBlobStagePart>;\n}): MultipartPartPlan {\n const { encryption, uploadedParts } = input;\n const completeParts: (MultipartPartCommit | undefined)[] = new Array(\n encryption.partCount,\n );\n const uploadTasks: MultipartPartUploadTask[] = [];\n const progress: MultipartUploadProgressState = {\n bytesTotal: 0,\n bytesUploaded: 0,\n partsCompleted: 0,\n partsTotal: encryption.partCount,\n };\n\n for (let partIndex = 0; partIndex < encryption.partCount; partIndex += 1) {\n const partNumber = partIndex + 1;\n const byteLength = encryption.getPartByteLength(partIndex);\n progress.bytesTotal += byteLength;\n const uploadedPart = uploadedParts.get(partNumber);\n if (uploadedPart?.byteLength === byteLength) {\n completeParts[partIndex] = {\n etag: uploadedPart.etag,\n partNumber,\n };\n progress.partsCompleted += 1;\n progress.bytesUploaded += byteLength;\n continue;\n }\n\n uploadTasks.push({\n byteLength,\n partIndex,\n partNumber,\n });\n }\n\n return { completeParts, progress, uploadTasks };\n}\n\nfunction assertMultipartPlanLimits(encryption: BlobEncryptionPlan): void {\n if (encryption.partCount > MAX_BLOB_CHUNK_COUNT) {\n throw new Error(\n `Multipart blob upload would require ${encryption.partCount.toLocaleString()} parts; the maximum is ${MAX_BLOB_CHUNK_COUNT.toLocaleString()} parts.`,\n );\n }\n for (let index = 0; index < encryption.partCount; index += 1) {\n if (encryption.getPartByteLength(index) > MAX_MULTIPART_BLOB_PART_BYTES) {\n throw new Error(\n `Encrypted blob part ${index + 1} exceeds the 32 MiB multipart part limit.`,\n );\n }\n }\n}\n\ntype StageMultipartBlobAttachmentInput = {\n readonly apiClient: BlobAttachmentApi;\n readonly encryption: BlobEncryptionPlan;\n readonly multipart: NonNullable<UploadDocumentAttachmentInput[\"multipart\"]>;\n readonly organizationId: string;\n readonly onMultipartProgress?: MultipartUploadProgressListener | undefined;\n readonly onStageResolved?: MultipartStageResolvedListener | undefined;\n readonly onStageUnavailable?:\n | ((stageId: string) => Promise<void>)\n | undefined;\n};\n\nexport async function stageMultipartBlobAttachment(\n input: StageMultipartBlobAttachmentInput,\n): Promise<string | null> {\n if (input.multipart.partSize !== input.encryption.chunkSize) {\n throw new Error(\n \"Multipart part size must match the encryption chunk size exactly.\",\n );\n }\n assertMultipartPlanLimits(input.encryption);\n const concurrency = normalizeMultipartUploadConcurrency(\n input.multipart.uploadConcurrency,\n );\n const resolved = await resolveMultipartStage({\n apiClient: input.apiClient,\n concurrency,\n encryption: input.encryption,\n multipart: input.multipart,\n organizationId: input.organizationId,\n onStageUnavailable: input.onStageUnavailable,\n });\n try {\n return await uploadResolvedMultipartStage(input, concurrency, resolved);\n } finally {\n // First parts no worker consumed (an early failure) return their slots.\n releasePreparedParts(resolved.prepared);\n }\n}\n\nasync function uploadResolvedMultipartStage(\n input: StageMultipartBlobAttachmentInput,\n concurrency: number,\n {\n prepared,\n sources,\n status,\n }: Awaited<ReturnType<typeof resolveMultipartStage>>,\n): Promise<string> {\n // Surface the stage id before any parts upload so the caller can persist it;\n // an upload interrupted mid-flight can then resume this same stage.\n await input.onStageResolved?.({\n partSize: input.encryption.chunkSize,\n stageId: status.stageId,\n });\n if (status.completed) {\n input.onMultipartProgress?.({\n bytesTotal: input.encryption.byteLength,\n bytesUploaded: input.encryption.byteLength,\n partsCompleted: input.encryption.partCount,\n partsTotal: input.encryption.partCount,\n });\n return status.stageId;\n }\n\n const { completeParts, progress, uploadTasks } = planMultipartParts({\n encryption: input.encryption,\n uploadedParts: uploadedPartsByPartNumber(status.uploadedParts),\n });\n\n const reportMultipartProgress = () => {\n input.onMultipartProgress?.({\n bytesTotal: progress.bytesTotal,\n bytesUploaded: progress.bytesUploaded,\n partsCompleted: progress.partsCompleted,\n partsTotal: progress.partsTotal,\n });\n };\n reportMultipartProgress();\n\n await uploadMultipartPartTasks({\n apiClient: input.apiClient,\n completeParts,\n concurrency,\n encryption: input.encryption,\n onPartUploaded: (task) => {\n progress.partsCompleted += 1;\n progress.bytesUploaded += task.byteLength;\n reportMultipartProgress();\n },\n prepared,\n sources,\n stageId: status.stageId,\n tasks: uploadTasks,\n uploadId: status.uploadId,\n });\n const committedParts = completeParts.filter(isMultipartPartCommit);\n if (committedParts.length !== input.encryption.partCount) {\n throw new Error(\n `Multipart blob upload incomplete for stage ${status.stageId}: committed ${committedParts.length.toLocaleString()} of ${input.encryption.partCount.toLocaleString()} parts.`,\n );\n }\n\n // Binding the attachment completes the stage: the API assembles these parts\n // and verifies the object's length and SHA-256 before promoting it.\n return status.stageId;\n}\n"]}
|
|
@@ -1,52 +1,106 @@
|
|
|
1
|
-
import {
|
|
2
|
-
|
|
3
|
-
|
|
1
|
+
import { DEFAULT_CONTAINER_DOCUMENT_LANE_PAGE_LIMIT, MAX_CONTAINER_DOCUMENT_LANE_PAGE_TOTAL, MAX_CONTAINER_DOCUMENT_LANES, } from "../../internal/validators/request/index.js";
|
|
2
|
+
/**
|
|
3
|
+
* Rows each lane may return so a full batch stays within the single-page
|
|
4
|
+
* maximum; a lone lane keeps the ordinary page size.
|
|
5
|
+
*/
|
|
6
|
+
function laneLimit(laneCount) {
|
|
7
|
+
return Math.max(1, Math.min(DEFAULT_CONTAINER_DOCUMENT_LANE_PAGE_LIMIT, Math.floor(MAX_CONTAINER_DOCUMENT_LANE_PAGE_TOTAL / laneCount)));
|
|
4
8
|
}
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
9
|
+
/**
|
|
10
|
+
* Lists one page per requested lane in a single batched request. A lane the
|
|
11
|
+
* server rejected yields `null` for that lane only; a failed request yields
|
|
12
|
+
* `null` for the batch.
|
|
13
|
+
*/
|
|
14
|
+
export async function listContainerDocumentLanesFromApi(apiClient, lanes, reportErrors = true) {
|
|
15
|
+
const limit = laneLimit(lanes.length);
|
|
16
|
+
const requested = lanes.map((lane, index) => ({
|
|
17
|
+
containerId: lane.containerId,
|
|
18
|
+
laneId: `lane-${index}`,
|
|
19
|
+
limit,
|
|
20
|
+
watermark: lane.watermark,
|
|
21
|
+
}));
|
|
22
|
+
const response = await apiClient.listContainerDocumentLanes({ lanes: requested }, { reportErrors });
|
|
23
|
+
if (!response)
|
|
14
24
|
return null;
|
|
15
|
-
|
|
16
|
-
return
|
|
25
|
+
const resultsByLaneId = new Map(response.results.map((result) => [result.laneId, result]));
|
|
26
|
+
return requested.map(({ laneId }) => {
|
|
27
|
+
const result = resultsByLaneId.get(laneId);
|
|
28
|
+
return result && "page" in result ? result.page : null;
|
|
29
|
+
});
|
|
17
30
|
}
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
31
|
+
/**
|
|
32
|
+
* Pages every lane to exhaustion, batching first pages and continuations
|
|
33
|
+
* alike. Each container keeps its own watermark, so one lane's failure or
|
|
34
|
+
* continuation never changes another lane's result.
|
|
35
|
+
*/
|
|
36
|
+
export async function listAllContainerDocumentLanes(input) {
|
|
37
|
+
const listed = new Map();
|
|
38
|
+
const pending = [];
|
|
39
|
+
const queued = new Set();
|
|
40
|
+
for (const { containerId, watermark } of input.lanes) {
|
|
41
|
+
if (queued.has(containerId))
|
|
42
|
+
continue;
|
|
43
|
+
queued.add(containerId);
|
|
44
|
+
pending.push({
|
|
45
|
+
containerId,
|
|
46
|
+
isFullListing: watermark === null,
|
|
47
|
+
items: [],
|
|
48
|
+
tombstones: [],
|
|
25
49
|
watermark,
|
|
26
50
|
});
|
|
27
|
-
if (!response) {
|
|
28
|
-
return null;
|
|
29
|
-
}
|
|
30
|
-
items.push(...response.items);
|
|
31
|
-
tombstones.push(...response.tombstones);
|
|
32
|
-
watermark = response.nextWatermark;
|
|
33
|
-
if (!response.hasMore) {
|
|
34
|
-
return { isFullListing, items, nextWatermark: watermark, tombstones };
|
|
35
|
-
}
|
|
36
|
-
if (!watermark) {
|
|
37
|
-
return null;
|
|
38
|
-
}
|
|
39
51
|
}
|
|
52
|
+
while (pending.length > 0) {
|
|
53
|
+
const batch = pending.splice(0, MAX_CONTAINER_DOCUMENT_LANES);
|
|
54
|
+
const pages = await input.listContainerDocumentLanes(batch.map(({ containerId, watermark }) => ({ containerId, watermark })));
|
|
55
|
+
batch.forEach((lane, index) => {
|
|
56
|
+
const page = pages?.[index] ?? null;
|
|
57
|
+
if (!page) {
|
|
58
|
+
listed.set(lane.containerId, null);
|
|
59
|
+
return;
|
|
60
|
+
}
|
|
61
|
+
lane.items.push(...page.items);
|
|
62
|
+
lane.tombstones.push(...page.tombstones);
|
|
63
|
+
lane.watermark = page.nextWatermark;
|
|
64
|
+
if (!page.hasMore) {
|
|
65
|
+
listed.set(lane.containerId, {
|
|
66
|
+
isFullListing: lane.isFullListing,
|
|
67
|
+
items: lane.items,
|
|
68
|
+
nextWatermark: lane.watermark,
|
|
69
|
+
tombstones: lane.tombstones,
|
|
70
|
+
});
|
|
71
|
+
}
|
|
72
|
+
else if (lane.watermark) {
|
|
73
|
+
pending.push(lane);
|
|
74
|
+
}
|
|
75
|
+
else {
|
|
76
|
+
listed.set(lane.containerId, null);
|
|
77
|
+
}
|
|
78
|
+
});
|
|
79
|
+
}
|
|
80
|
+
return listed;
|
|
40
81
|
}
|
|
41
82
|
/** Lists the authoritative current ids by deliberately starting at no watermark. */
|
|
42
83
|
export async function listAllContainerDocumentIdsFromApi(input) {
|
|
43
|
-
const listed = await
|
|
44
|
-
containerId: input.containerId,
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
});
|
|
84
|
+
const listed = (await listAllContainerDocumentLanes({
|
|
85
|
+
lanes: [{ containerId: input.containerId, watermark: null }],
|
|
86
|
+
listContainerDocumentLanes: (lanes) => listContainerDocumentLanesFromApi(input.apiClient, lanes, input.reportErrors),
|
|
87
|
+
})).get(input.containerId);
|
|
48
88
|
return listed
|
|
49
89
|
? Array.from(new Set(listed.items.map((document) => document.id)))
|
|
50
90
|
: null;
|
|
51
91
|
}
|
|
92
|
+
/** Loads each lane's persisted watermark, then lists every lane in batches. */
|
|
93
|
+
export async function listContainerDocumentsFromWatermarks(input) {
|
|
94
|
+
const lanes = [];
|
|
95
|
+
for (const containerId of new Set(input.containerIds)) {
|
|
96
|
+
lanes.push({
|
|
97
|
+
containerId,
|
|
98
|
+
watermark: await input.loadContainerDocumentWatermark(containerId),
|
|
99
|
+
});
|
|
100
|
+
}
|
|
101
|
+
return listAllContainerDocumentLanes({
|
|
102
|
+
lanes,
|
|
103
|
+
listContainerDocumentLanes: input.listContainerDocumentLanes,
|
|
104
|
+
});
|
|
105
|
+
}
|
|
52
106
|
//# sourceMappingURL=containerDocumentListing.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"containerDocumentListing.js","sourceRoot":"","sources":["containerDocumentListing.ts"],"names":[],"mappings":"AAAA,OAAO,
|
|
1
|
+
{"version":3,"file":"containerDocumentListing.js","sourceRoot":"","sources":["containerDocumentListing.ts"],"names":[],"mappings":"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","sourcesContent":["import {\n DEFAULT_CONTAINER_DOCUMENT_LANE_PAGE_LIMIT,\n MAX_CONTAINER_DOCUMENT_LANE_PAGE_TOTAL,\n MAX_CONTAINER_DOCUMENT_LANES,\n} from \"@tearleads/validators/request\";\nimport type { SyncWatermark } from \"@tearleads/validators/response\";\nimport type {\n ContainerDocumentDiscoveryApi,\n ContainerDocumentLaneLister,\n ContainerDocumentTombstone,\n ListedContainerDocument,\n ListedContainerDocuments,\n} from \"./documentDiscoveryTypes\";\n\ntype ContainerDocumentLaneApi = Pick<\n ContainerDocumentDiscoveryApi,\n \"listContainerDocumentLanes\"\n>;\n\n/**\n * Rows each lane may return so a full batch stays within the single-page\n * maximum; a lone lane keeps the ordinary page size.\n */\nfunction laneLimit(laneCount: number): number {\n return Math.max(\n 1,\n Math.min(\n DEFAULT_CONTAINER_DOCUMENT_LANE_PAGE_LIMIT,\n Math.floor(MAX_CONTAINER_DOCUMENT_LANE_PAGE_TOTAL / laneCount),\n ),\n );\n}\n\n/**\n * Lists one page per requested lane in a single batched request. A lane the\n * server rejected yields `null` for that lane only; a failed request yields\n * `null` for the batch.\n */\nexport async function listContainerDocumentLanesFromApi(\n apiClient: ContainerDocumentLaneApi,\n lanes: Parameters<ContainerDocumentLaneLister>[0],\n reportErrors = true,\n): ReturnType<ContainerDocumentLaneLister> {\n const limit = laneLimit(lanes.length);\n const requested = lanes.map((lane, index) => ({\n containerId: lane.containerId,\n laneId: `lane-${index}`,\n limit,\n watermark: lane.watermark,\n }));\n const response = await apiClient.listContainerDocumentLanes(\n { lanes: requested },\n { reportErrors },\n );\n if (!response) return null;\n const resultsByLaneId = new Map(\n response.results.map((result) => [result.laneId, result]),\n );\n return requested.map(({ laneId }) => {\n const result = resultsByLaneId.get(laneId);\n return result && \"page\" in result ? result.page : null;\n });\n}\n\ninterface PendingContainerDocumentLane {\n readonly containerId: string;\n readonly isFullListing: boolean;\n readonly items: ListedContainerDocument[];\n readonly tombstones: ContainerDocumentTombstone[];\n watermark: SyncWatermark | null;\n}\n\n/**\n * Pages every lane to exhaustion, batching first pages and continuations\n * alike. Each container keeps its own watermark, so one lane's failure or\n * continuation never changes another lane's result.\n */\nexport async function listAllContainerDocumentLanes(input: {\n readonly lanes: ReadonlyArray<{\n readonly containerId: string;\n readonly watermark: SyncWatermark | null;\n }>;\n readonly listContainerDocumentLanes: ContainerDocumentLaneLister;\n}): Promise<ReadonlyMap<string, ListedContainerDocuments | null>> {\n const listed = new Map<string, ListedContainerDocuments | null>();\n const pending: PendingContainerDocumentLane[] = [];\n const queued = new Set<string>();\n for (const { containerId, watermark } of input.lanes) {\n if (queued.has(containerId)) continue;\n queued.add(containerId);\n pending.push({\n containerId,\n isFullListing: watermark === null,\n items: [],\n tombstones: [],\n watermark,\n });\n }\n\n while (pending.length > 0) {\n const batch = pending.splice(0, MAX_CONTAINER_DOCUMENT_LANES);\n const pages = await input.listContainerDocumentLanes(\n batch.map(({ containerId, watermark }) => ({ containerId, watermark })),\n );\n batch.forEach((lane, index) => {\n const page = pages?.[index] ?? null;\n if (!page) {\n listed.set(lane.containerId, null);\n return;\n }\n lane.items.push(...page.items);\n lane.tombstones.push(...page.tombstones);\n lane.watermark = page.nextWatermark;\n if (!page.hasMore) {\n listed.set(lane.containerId, {\n isFullListing: lane.isFullListing,\n items: lane.items,\n nextWatermark: lane.watermark,\n tombstones: lane.tombstones,\n });\n } else if (lane.watermark) {\n pending.push(lane);\n } else {\n listed.set(lane.containerId, null);\n }\n });\n }\n return listed;\n}\n\n/** Lists the authoritative current ids by deliberately starting at no watermark. */\nexport async function listAllContainerDocumentIdsFromApi(input: {\n readonly apiClient: ContainerDocumentLaneApi;\n readonly containerId: string;\n readonly reportErrors?: boolean;\n}): Promise<ReadonlyArray<string> | null> {\n const listed = (\n await listAllContainerDocumentLanes({\n lanes: [{ containerId: input.containerId, watermark: null }],\n listContainerDocumentLanes: (lanes) =>\n listContainerDocumentLanesFromApi(\n input.apiClient,\n lanes,\n input.reportErrors,\n ),\n })\n ).get(input.containerId);\n return listed\n ? Array.from(new Set(listed.items.map((document) => document.id)))\n : null;\n}\n\n/** Loads each lane's persisted watermark, then lists every lane in batches. */\nexport async function listContainerDocumentsFromWatermarks(input: {\n readonly containerIds: ReadonlyArray<string>;\n readonly listContainerDocumentLanes: ContainerDocumentLaneLister;\n readonly loadContainerDocumentWatermark: (\n containerId: string,\n ) => Promise<SyncWatermark | null>;\n}): Promise<ReadonlyMap<string, ListedContainerDocuments | null>> {\n const lanes: Array<{ containerId: string; watermark: SyncWatermark | null }> =\n [];\n for (const containerId of new Set(input.containerIds)) {\n lanes.push({\n containerId,\n watermark: await input.loadContainerDocumentWatermark(containerId),\n });\n }\n return listAllContainerDocumentLanes({\n lanes,\n listContainerDocumentLanes: input.listContainerDocumentLanes,\n });\n}\n"]}
|
|
@@ -15,7 +15,13 @@ export declare function createContainerParentSyncLane(parentId: string | null):
|
|
|
15
15
|
export declare function createContainerContentsSyncLane(containerId: string): ContainerSyncWatermarkLane;
|
|
16
16
|
export declare function loadContainerSyncWatermark(execSql: ExecSql, syncLane: ContainerSyncWatermarkLane): Promise<SyncWatermark | null>;
|
|
17
17
|
export declare function saveContainerSyncWatermark(execSql: ExecSql, syncLane: ContainerSyncWatermarkLane, watermark: SyncWatermark): Promise<void>;
|
|
18
|
-
export declare function saveContainerSyncWatermarkIfCurrent(execSql: ExecSql, syncLane: ContainerSyncWatermarkLane, watermark: SyncWatermark, stillCurrent?: (() => boolean) | undefined): Promise<boolean>;
|
|
19
18
|
export declare function loadContainerSyncLaneCheckRecords(execSql: ExecSql, syncLanes: ReadonlyArray<ContainerSyncWatermarkLane>): Promise<Array<ContainerSyncLaneCheckRecord | null>>;
|
|
20
19
|
export declare function markContainerSyncLaneChecked(execSql: ExecSql, syncLane: ContainerSyncWatermarkLane): Promise<void>;
|
|
21
|
-
|
|
20
|
+
/**
|
|
21
|
+
* Records one lane fetch in a single guarded upsert: the cursor when the page
|
|
22
|
+
* carried one and the freshness marker when the lane is fully drained.
|
|
23
|
+
*/
|
|
24
|
+
export declare function recordContainerSyncLaneFetchIfCurrent(execSql: ExecSql, syncLane: ContainerSyncWatermarkLane, fetch: {
|
|
25
|
+
readonly checked: boolean;
|
|
26
|
+
readonly watermark: SyncWatermark | null;
|
|
27
|
+
}, stillCurrent?: (() => boolean) | undefined): Promise<boolean>;
|
|
@@ -23,18 +23,17 @@ export async function loadContainerSyncWatermark(execSql, syncLane) {
|
|
|
23
23
|
export async function saveContainerSyncWatermark(execSql, syncLane, watermark) {
|
|
24
24
|
await sqlContainerSyncWatermarkPersistence.saveWatermark(execSql, syncLane, watermark);
|
|
25
25
|
}
|
|
26
|
-
export async function saveContainerSyncWatermarkIfCurrent(execSql, syncLane, watermark, stillCurrent) {
|
|
27
|
-
return sqlContainerSyncWatermarkPersistence.saveWatermark(execSql, syncLane, watermark, { stillCurrent });
|
|
28
|
-
}
|
|
29
26
|
export async function loadContainerSyncLaneCheckRecords(execSql, syncLanes) {
|
|
30
27
|
return sqlContainerSyncWatermarkPersistence.loadCheckRecords(execSql, syncLanes);
|
|
31
28
|
}
|
|
32
29
|
export async function markContainerSyncLaneChecked(execSql, syncLane) {
|
|
33
30
|
await sqlContainerSyncWatermarkPersistence.markChecked(execSql, syncLane);
|
|
34
31
|
}
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
32
|
+
/**
|
|
33
|
+
* Records one lane fetch in a single guarded upsert: the cursor when the page
|
|
34
|
+
* carried one and the freshness marker when the lane is fully drained.
|
|
35
|
+
*/
|
|
36
|
+
export async function recordContainerSyncLaneFetchIfCurrent(execSql, syncLane, fetch, stillCurrent) {
|
|
37
|
+
return sqlContainerSyncWatermarkPersistence.recordLaneFetch(execSql, syncLane, fetch, { stillCurrent });
|
|
39
38
|
}
|
|
40
39
|
//# sourceMappingURL=containerPersistence.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"containerPersistence.js","sourceRoot":"","sources":["containerPersistence.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,yBAAyB,EAAE,MAAM,mCAAmC,CAAC;AAE9E,OAAO,EAGL,yBAAyB,EACzB,uBAAuB,EACvB,oCAAoC,GACrC,MAAM,qEAAqE,CAAC;AAa7E,OAAO,EAAE,+BAA+B,IAAI,mCAAmC,EAAE,MAAM,wEAAwE,CAAC;AAIhK,MAAM,CAAC,KAAK,UAAU,6BAA6B,CACjD,OAAgB,EAChB,WAAyC,EACzC,MAIC;IAED,MAAM,mBAAmB,GAAG,yBAAyB,CACnD,MAAM,CAAC,MAAM,EACb,MAAM,CAAC,mBAAmB,CAC3B,CAAC;IACF,IAAI,CAAC,mBAAmB,EAAE,CAAC;QACzB,OAAO,IAAI,CAAC;IACd,CAAC;IAED,OAAO,WAAW,CAAC,oBAAoB,CAAC,OAAO,EAAE;QAC/C,WAAW,EAAE,MAAM,CAAC,WAAW;QAC/B,GAAG,mBAAmB;KACvB,CAAC,CAAC;AACL,CAAC;AAED,MAAM,UAAU,6BAA6B,CAC3C,QAAuB;IAEvB,OAAO,uBAAuB,CAAC,QAAQ,CAAC,CAAC;AAC3C,CAAC;AAED,MAAM,UAAU,+BAA+B,CAC7C,WAAmB;IAEnB,OAAO,yBAAyB,CAAC,WAAW,CAAC,CAAC;AAChD,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,0BAA0B,CAC9C,OAAgB,EAChB,QAAoC;IAEpC,OAAO,oCAAoC,CAAC,aAAa,CAAC,OAAO,EAAE,QAAQ,CAAC,CAAC;AAC/E,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,0BAA0B,CAC9C,OAAgB,EAChB,QAAoC,EACpC,SAAwB;IAExB,MAAM,oCAAoC,CAAC,aAAa,CACtD,OAAO,EACP,QAAQ,EACR,SAAS,CACV,CAAC;AACJ,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,
|
|
1
|
+
{"version":3,"file":"containerPersistence.js","sourceRoot":"","sources":["containerPersistence.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,yBAAyB,EAAE,MAAM,mCAAmC,CAAC;AAE9E,OAAO,EAGL,yBAAyB,EACzB,uBAAuB,EACvB,oCAAoC,GACrC,MAAM,qEAAqE,CAAC;AAa7E,OAAO,EAAE,+BAA+B,IAAI,mCAAmC,EAAE,MAAM,wEAAwE,CAAC;AAIhK,MAAM,CAAC,KAAK,UAAU,6BAA6B,CACjD,OAAgB,EAChB,WAAyC,EACzC,MAIC;IAED,MAAM,mBAAmB,GAAG,yBAAyB,CACnD,MAAM,CAAC,MAAM,EACb,MAAM,CAAC,mBAAmB,CAC3B,CAAC;IACF,IAAI,CAAC,mBAAmB,EAAE,CAAC;QACzB,OAAO,IAAI,CAAC;IACd,CAAC;IAED,OAAO,WAAW,CAAC,oBAAoB,CAAC,OAAO,EAAE;QAC/C,WAAW,EAAE,MAAM,CAAC,WAAW;QAC/B,GAAG,mBAAmB;KACvB,CAAC,CAAC;AACL,CAAC;AAED,MAAM,UAAU,6BAA6B,CAC3C,QAAuB;IAEvB,OAAO,uBAAuB,CAAC,QAAQ,CAAC,CAAC;AAC3C,CAAC;AAED,MAAM,UAAU,+BAA+B,CAC7C,WAAmB;IAEnB,OAAO,yBAAyB,CAAC,WAAW,CAAC,CAAC;AAChD,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,0BAA0B,CAC9C,OAAgB,EAChB,QAAoC;IAEpC,OAAO,oCAAoC,CAAC,aAAa,CAAC,OAAO,EAAE,QAAQ,CAAC,CAAC;AAC/E,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,0BAA0B,CAC9C,OAAgB,EAChB,QAAoC,EACpC,SAAwB;IAExB,MAAM,oCAAoC,CAAC,aAAa,CACtD,OAAO,EACP,QAAQ,EACR,SAAS,CACV,CAAC;AACJ,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,iCAAiC,CACrD,OAAgB,EAChB,SAAoD;IAEpD,OAAO,oCAAoC,CAAC,gBAAgB,CAC1D,OAAO,EACP,SAAS,CACV,CAAC;AACJ,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,4BAA4B,CAChD,OAAgB,EAChB,QAAoC;IAEpC,MAAM,oCAAoC,CAAC,WAAW,CAAC,OAAO,EAAE,QAAQ,CAAC,CAAC;AAC5E,CAAC;AAED;;;GAGG;AACH,MAAM,CAAC,KAAK,UAAU,qCAAqC,CACzD,OAAgB,EAChB,QAAoC,EACpC,KAGC,EACD,YAA0C;IAE1C,OAAO,oCAAoC,CAAC,eAAe,CACzD,OAAO,EACP,QAAQ,EACR,KAAK,EACL,EAAE,YAAY,EAAE,CACjB,CAAC;AACJ,CAAC","sourcesContent":["import type { SyncWatermark } from \"@tearleads/validators/response\";\nimport { createPendingUpdateFields } from \"../../data/documents/documentSync\";\nimport type { ContainerContentsPersistence } from \"../../data/persistence/container-contents/containerContentsPersistence\";\nimport {\n type ContainerSyncLaneCheckRecord,\n type ContainerSyncWatermarkLane,\n containerContentsSyncLane,\n containerParentSyncLane,\n sqlContainerSyncWatermarkPersistence,\n} from \"../../data/persistence/containers/containerSyncWatermarkPersistence\";\nimport type { ExecSql } from \"../../data/sqlite/sqlSchema\";\n\nexport type {\n ContainerContentsPersistence,\n ContainerCreateIntentRecord,\n ContainerHydrationTombstone,\n ContainerMetadataRecord,\n ContainerMetadataRecord as ContainerDocumentRecord,\n ContainerMoveIntentRecord,\n LocalRootDescendantReparentInput,\n StoredContainerState,\n} from \"../../data/persistence/container-contents/containerContentsPersistence\";\nexport { sqlContainerContentsPersistence as defaultContainerContentsPersistence } from \"../../data/persistence/container-contents/containerContentsPersistence\";\nexport type { ContainerRecord } from \"../../data/persistence/containers/containerPersistence\";\nexport type { ContainerSyncWatermarkLane } from \"../../data/persistence/containers/containerSyncWatermarkPersistence\";\n\nexport async function enqueuePendingContainerUpdate(\n execSql: ExecSql,\n persistence: ContainerContentsPersistence,\n params: {\n containerId: string;\n sourceVersionVector?: string | null | undefined;\n update: Uint8Array;\n },\n): Promise<string | null> {\n const pendingUpdateFields = createPendingUpdateFields(\n params.update,\n params.sourceVersionVector,\n );\n if (!pendingUpdateFields) {\n return null;\n }\n\n return persistence.enqueuePendingUpdate(execSql, {\n containerId: params.containerId,\n ...pendingUpdateFields,\n });\n}\n\nexport function createContainerParentSyncLane(\n parentId: string | null,\n): ContainerSyncWatermarkLane {\n return containerParentSyncLane(parentId);\n}\n\nexport function createContainerContentsSyncLane(\n containerId: string,\n): ContainerSyncWatermarkLane {\n return containerContentsSyncLane(containerId);\n}\n\nexport async function loadContainerSyncWatermark(\n execSql: ExecSql,\n syncLane: ContainerSyncWatermarkLane,\n): Promise<SyncWatermark | null> {\n return sqlContainerSyncWatermarkPersistence.loadWatermark(execSql, syncLane);\n}\n\nexport async function saveContainerSyncWatermark(\n execSql: ExecSql,\n syncLane: ContainerSyncWatermarkLane,\n watermark: SyncWatermark,\n): Promise<void> {\n await sqlContainerSyncWatermarkPersistence.saveWatermark(\n execSql,\n syncLane,\n watermark,\n );\n}\n\nexport async function loadContainerSyncLaneCheckRecords(\n execSql: ExecSql,\n syncLanes: ReadonlyArray<ContainerSyncWatermarkLane>,\n): Promise<Array<ContainerSyncLaneCheckRecord | null>> {\n return sqlContainerSyncWatermarkPersistence.loadCheckRecords(\n execSql,\n syncLanes,\n );\n}\n\nexport async function markContainerSyncLaneChecked(\n execSql: ExecSql,\n syncLane: ContainerSyncWatermarkLane,\n): Promise<void> {\n await sqlContainerSyncWatermarkPersistence.markChecked(execSql, syncLane);\n}\n\n/**\n * Records one lane fetch in a single guarded upsert: the cursor when the page\n * carried one and the freshness marker when the lane is fully drained.\n */\nexport async function recordContainerSyncLaneFetchIfCurrent(\n execSql: ExecSql,\n syncLane: ContainerSyncWatermarkLane,\n fetch: {\n readonly checked: boolean;\n readonly watermark: SyncWatermark | null;\n },\n stillCurrent?: (() => boolean) | undefined,\n): Promise<boolean> {\n return sqlContainerSyncWatermarkPersistence.recordLaneFetch(\n execSql,\n syncLane,\n fetch,\n { stillCurrent },\n );\n}\n"]}
|
|
@@ -4,9 +4,9 @@ export type { RefreshAllContainerDocumentsFromApiOptions } from "./documentDisco
|
|
|
4
4
|
export declare function hasUndiscoveredDocumentUpdateEvent(events: ReadonlyArray<unknown>, knownDocumentIds: ReadonlySet<string>): boolean;
|
|
5
5
|
export declare function listAllRemoteContainerIds(listContainerParentLanes: ContainerDocumentDiscoveryApi["listContainerParentLanes"]): Promise<ReadonlyArray<string> | null>;
|
|
6
6
|
export declare function listAllRemoteContainerIdsFromApi(apiClient: Pick<ContainerDocumentDiscoveryApi, "listContainerParentLanes">): Promise<ReadonlyArray<string> | null>;
|
|
7
|
-
export declare function discoverContainerDocuments(
|
|
8
|
-
export declare function discoverContainerDocumentsFromApi({ apiClient, ...input }: Omit<DiscoverContainerDocumentsOptions, "
|
|
9
|
-
readonly apiClient: Pick<ContainerDocumentDiscoveryApi, "
|
|
7
|
+
export declare function discoverContainerDocuments({ beginDocumentDiscovery, listContainerDocumentLanes, loadContainerDocumentWatermark, ...lane }: DiscoverContainerDocumentsOptions): Promise<ReadonlyArray<DocumentSummary> | null>;
|
|
8
|
+
export declare function discoverContainerDocumentsFromApi({ apiClient, ...input }: Omit<DiscoverContainerDocumentsOptions, "listContainerDocumentLanes"> & {
|
|
9
|
+
readonly apiClient: Pick<ContainerDocumentDiscoveryApi, "listContainerDocumentLanes">;
|
|
10
10
|
}): Promise<ReadonlyArray<DocumentSummary> | null>;
|
|
11
11
|
export declare function discoverAllContainerDocuments(options: DiscoverAllContainerDocumentsOptions): Promise<ReadonlyArray<DocumentSummary> | null>;
|
|
12
12
|
export declare function refreshAllContainerDocumentsFromApi({ apiClient, ...input }: RefreshAllContainerDocumentsFromApiOptions): Promise<ReadonlyArray<DocumentSummary> | null>;
|
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
import { isDocumentUpdateCreatedEvent } from "../../data/documents/documentSync.js";
|
|
2
2
|
import { dedupeReferencedPrincipalStates } from "../../data/keyingProjectionVerification/principalPolicyCache.js";
|
|
3
|
-
import {
|
|
3
|
+
import { listContainerDocumentLanesFromApi, listContainerDocumentsFromWatermarks, } from "./containerDocumentListing.js";
|
|
4
4
|
import { withoutDeferredDocumentLinks } from "./deferredDocumentLinks.js";
|
|
5
|
-
import {
|
|
5
|
+
import { saveAppliedContainerDocumentWatermark } from "./documentDiscoveryApply.js";
|
|
6
|
+
import { collectApplicableDocumentTombstones, collectDiscoveredDocumentInputs, } from "./documentDiscoveryInputs.js";
|
|
7
|
+
import { discoverContainerDocumentLanes } from "./documentDiscoveryLanes.js";
|
|
6
8
|
import { settleContainerDocumentTombstones } from "./documentTombstoneGate.js";
|
|
7
9
|
const CONTAINER_PARENT_DISCOVERY_BATCH_SIZE = 4;
|
|
8
|
-
const CONTAINER_DOCUMENT_DISCOVERY_CONCURRENCY = 4;
|
|
9
10
|
async function listContainerParentLaneBatch(listContainerParentLanes, requestedLanes) {
|
|
10
11
|
const response = await listContainerParentLanes({
|
|
11
12
|
lanes: requestedLanes.map(({ request }) => request),
|
|
@@ -36,40 +37,6 @@ function collectRemoteContainerIds(items, seenContainerIds, containerIds) {
|
|
|
36
37
|
}
|
|
37
38
|
return items.map(({ id }) => id);
|
|
38
39
|
}
|
|
39
|
-
async function saveAppliedContainerDocumentWatermark(input) {
|
|
40
|
-
const { containerId, listedDocuments, saveContainerDocumentWatermark } = input;
|
|
41
|
-
if (!listedDocuments.nextWatermark) {
|
|
42
|
-
return;
|
|
43
|
-
}
|
|
44
|
-
await saveContainerDocumentWatermark(containerId, listedDocuments.nextWatermark);
|
|
45
|
-
}
|
|
46
|
-
async function listContainerDocumentLanes(input) {
|
|
47
|
-
const { containerIds, loadContainerDocumentWatermark, listContainerDocuments, } = input;
|
|
48
|
-
const listedDocumentsByContainer = [];
|
|
49
|
-
let nextContainerIndex = 0;
|
|
50
|
-
async function worker() {
|
|
51
|
-
while (nextContainerIndex < containerIds.length) {
|
|
52
|
-
const containerIndex = nextContainerIndex;
|
|
53
|
-
nextContainerIndex += 1;
|
|
54
|
-
const containerId = containerIds[containerIndex];
|
|
55
|
-
if (!containerId) {
|
|
56
|
-
throw new Error("Container document discovery received an empty lane");
|
|
57
|
-
}
|
|
58
|
-
listedDocumentsByContainer[containerIndex] = {
|
|
59
|
-
containerId,
|
|
60
|
-
listedDocuments: await listAllContainerDocuments({
|
|
61
|
-
containerId,
|
|
62
|
-
loadContainerDocumentWatermark,
|
|
63
|
-
listContainerDocuments,
|
|
64
|
-
}),
|
|
65
|
-
};
|
|
66
|
-
}
|
|
67
|
-
}
|
|
68
|
-
await Promise.all(Array.from({
|
|
69
|
-
length: Math.min(CONTAINER_DOCUMENT_DISCOVERY_CONCURRENCY, containerIds.length),
|
|
70
|
-
}, () => worker()));
|
|
71
|
-
return listedDocumentsByContainer;
|
|
72
|
-
}
|
|
73
40
|
export function hasUndiscoveredDocumentUpdateEvent(events, knownDocumentIds) {
|
|
74
41
|
return events.some((event) => isDocumentUpdateCreatedEvent(event) &&
|
|
75
42
|
!knownDocumentIds.has(event.documentId));
|
|
@@ -127,77 +94,38 @@ export async function listAllRemoteContainerIds(listContainerParentLanes) {
|
|
|
127
94
|
export function listAllRemoteContainerIdsFromApi(apiClient) {
|
|
128
95
|
return listAllRemoteContainerIds((input) => apiClient.listContainerParentLanes(input));
|
|
129
96
|
}
|
|
130
|
-
export async function discoverContainerDocuments(
|
|
131
|
-
const
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
97
|
+
export async function discoverContainerDocuments({ beginDocumentDiscovery, listContainerDocumentLanes, loadContainerDocumentWatermark, ...lane }) {
|
|
98
|
+
const [outcome] = await discoverContainerDocumentLanes({
|
|
99
|
+
generation: await beginDocumentDiscovery(),
|
|
100
|
+
lanes: [lane],
|
|
101
|
+
listContainerDocumentLanes,
|
|
135
102
|
loadContainerDocumentWatermark,
|
|
136
|
-
listContainerDocuments,
|
|
137
103
|
});
|
|
138
|
-
if (
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
await cacheReferencedPrincipalPolicies?.(dedupeReferencedPrincipalStates(listedDocuments.items.flatMap((document) => document.referencedPrincipals)));
|
|
142
|
-
const verification = await options.verifyDiscoveredDocuments(listedDocuments.items.map((document) => ({
|
|
143
|
-
accessEpoch: document.currentAccessEpoch,
|
|
144
|
-
accessStateHash: document.currentAccessStateHash,
|
|
145
|
-
containerId,
|
|
146
|
-
listedContainerIds: [containerId],
|
|
147
|
-
createdAt: document.createdAt,
|
|
148
|
-
documentId: document.id,
|
|
149
|
-
effectiveAccessLevel: document.effectiveAccessLevel,
|
|
150
|
-
linkedContainerIds: document.linkedContainerIds,
|
|
151
|
-
})), [containerId], generation, getApplicableDocumentTombstones(listedDocuments));
|
|
152
|
-
if (!(await verification.isCurrent()))
|
|
153
|
-
return null;
|
|
154
|
-
const verifiedInputs = verification.inputs;
|
|
155
|
-
const discoveredDocuments = verifiedInputs.length
|
|
156
|
-
? await upsertDiscoveredDocuments(verifiedInputs)
|
|
157
|
-
: [];
|
|
158
|
-
if (verifiedInputs.length)
|
|
159
|
-
await replaceDocumentLinksBatch(await withoutDeferredDocumentLinks(verifiedInputs.map((document) => ({
|
|
160
|
-
documentId: document.documentId,
|
|
161
|
-
accessEpoch: document.accessEpoch,
|
|
162
|
-
containerIds: document.linkedContainerIds,
|
|
163
|
-
})), discoveredDocuments));
|
|
164
|
-
const tombstoneDocumentSummaries = await settleContainerDocumentTombstones({
|
|
165
|
-
containerIds: [containerId],
|
|
166
|
-
store: options,
|
|
167
|
-
tombstones: getApplicableDocumentTombstones(listedDocuments),
|
|
168
|
-
});
|
|
169
|
-
const complete = await verification.commit();
|
|
170
|
-
if (!(await verification.isCurrent()))
|
|
171
|
-
return null;
|
|
172
|
-
await saveAppliedContainerDocumentWatermark({
|
|
173
|
-
containerId,
|
|
174
|
-
listedDocuments,
|
|
175
|
-
saveContainerDocumentWatermark,
|
|
176
|
-
});
|
|
177
|
-
if (listedDocuments.isFullListing) {
|
|
178
|
-
// Initial probes consume ids as discovery hints and verify their own heads.
|
|
179
|
-
onFullListing?.(listedDocuments.items.map((document) => document.id));
|
|
180
|
-
}
|
|
181
|
-
return !complete &&
|
|
182
|
-
discoveredDocuments.length === 0 &&
|
|
183
|
-
tombstoneDocumentSummaries.length === 0
|
|
184
|
-
? null
|
|
185
|
-
: [...discoveredDocuments, ...tombstoneDocumentSummaries];
|
|
104
|
+
if (outcome?.status === "rejected")
|
|
105
|
+
throw outcome.reason;
|
|
106
|
+
return outcome?.value ?? null;
|
|
186
107
|
}
|
|
187
108
|
export function discoverContainerDocumentsFromApi({ apiClient, ...input }) {
|
|
188
109
|
return discoverContainerDocuments({
|
|
189
110
|
...input,
|
|
190
|
-
|
|
111
|
+
listContainerDocumentLanes: (lanes) => listContainerDocumentLanesFromApi(apiClient, lanes),
|
|
191
112
|
});
|
|
192
113
|
}
|
|
114
|
+
async function listDiscoveryLanes(input) {
|
|
115
|
+
const listed = await listContainerDocumentsFromWatermarks(input);
|
|
116
|
+
return input.containerIds.map((containerId) => ({
|
|
117
|
+
containerId,
|
|
118
|
+
listedDocuments: listed.get(containerId) ?? null,
|
|
119
|
+
}));
|
|
120
|
+
}
|
|
193
121
|
export async function discoverAllContainerDocuments(options) {
|
|
194
|
-
const { cacheReferencedPrincipalPolicies, containerIds, loadContainerDocumentWatermark,
|
|
122
|
+
const { cacheReferencedPrincipalPolicies, containerIds, loadContainerDocumentWatermark, listContainerDocumentLanes, replaceDocumentLinksBatch, saveContainerDocumentWatermark, upsertDiscoveredDocuments, } = options;
|
|
195
123
|
const uniqueContainerIds = Array.from(new Set(containerIds)).filter((containerId) => typeof containerId === "string" && containerId.length > 0);
|
|
196
124
|
const generation = await options.beginDocumentDiscovery();
|
|
197
|
-
const listedDocumentsByContainer = await
|
|
125
|
+
const listedDocumentsByContainer = await listDiscoveryLanes({
|
|
198
126
|
containerIds: uniqueContainerIds,
|
|
127
|
+
listContainerDocumentLanes,
|
|
199
128
|
loadContainerDocumentWatermark,
|
|
200
|
-
listContainerDocuments,
|
|
201
129
|
});
|
|
202
130
|
await cacheReferencedPrincipalPolicies?.(dedupeReferencedPrincipalStates(listedDocumentsByContainer.flatMap(({ listedDocuments }) => listedDocuments?.items.flatMap((document) => document.referencedPrincipals) ?? [])));
|
|
203
131
|
const verification = await options.verifyDiscoveredDocuments(collectDiscoveredDocumentInputs(listedDocumentsByContainer), uniqueContainerIds, generation, collectApplicableDocumentTombstones(listedDocumentsByContainer));
|
|
@@ -248,7 +176,7 @@ async function refreshAllContainerDocuments({ listContainerParentLanes, ...input
|
|
|
248
176
|
export function refreshAllContainerDocumentsFromApi({ apiClient, ...input }) {
|
|
249
177
|
return refreshAllContainerDocuments({
|
|
250
178
|
...input,
|
|
251
|
-
|
|
179
|
+
listContainerDocumentLanes: (lanes) => listContainerDocumentLanesFromApi(apiClient, lanes),
|
|
252
180
|
listContainerParentLanes: (input) => apiClient.listContainerParentLanes(input),
|
|
253
181
|
});
|
|
254
182
|
}
|