@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,7 +1,7 @@
|
|
|
1
1
|
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
|
-
import { MAX_MULTIPART_BLOB_PART_BYTES } from "../../../validators/util/index.js";
|
|
4
|
+
import { MAX_MULTIPART_BLOB_PART_BYTES, } from "../../../validators/util/index.js";
|
|
5
5
|
import { storageFetchOptions } from "./storageFetchOptions.js";
|
|
6
6
|
function invalidPart(transport, path) {
|
|
7
7
|
transport.reportFailure({
|
|
@@ -23,6 +23,21 @@ async function retryStorageStatus(status, attempt) {
|
|
|
23
23
|
}
|
|
24
24
|
return transient;
|
|
25
25
|
}
|
|
26
|
+
function contentMd5Base64(bytes) {
|
|
27
|
+
return btoa(String.fromCharCode(...md5(bytes)));
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* Describe an encrypted part the way a stage initiation lists the parts it
|
|
31
|
+
* will send first: number, exact length, and the Content-MD5 a signed upload
|
|
32
|
+
* URL binds.
|
|
33
|
+
*/
|
|
34
|
+
export function describeMultipartBlobPart(partNumber, encryptedBytes) {
|
|
35
|
+
return {
|
|
36
|
+
byteLength: encryptedBytes.byteLength,
|
|
37
|
+
contentMd5: contentMd5Base64(encryptedBytes),
|
|
38
|
+
partNumber,
|
|
39
|
+
};
|
|
40
|
+
}
|
|
26
41
|
async function preparePart(input) {
|
|
27
42
|
// File snapshots mutable buffers and preserves ciphertext across Capacitor's
|
|
28
43
|
// native HTTP bridge. Uint8Array is UTF-8 decoded there; a plain Blob becomes
|
|
@@ -42,64 +57,100 @@ async function preparePart(input) {
|
|
|
42
57
|
input.sha256) {
|
|
43
58
|
throw new TypeError("Multipart part SHA-256 does not match its bytes");
|
|
44
59
|
}
|
|
45
|
-
|
|
46
|
-
|
|
60
|
+
return { file, contentMd5: contentMd5Base64(new Uint8Array(bytes)) };
|
|
61
|
+
}
|
|
62
|
+
// Sign just in time for this part. The returned descriptor must name exactly
|
|
63
|
+
// the part, upload, length, and checksum that were requested.
|
|
64
|
+
async function authorizePart(context) {
|
|
65
|
+
const operation = authorizeMultipartBlobPartUploadOperation;
|
|
66
|
+
const { input, partNumber, stageId } = context;
|
|
67
|
+
const result = await context.transport.requestResult(operation, {
|
|
68
|
+
params: { stageId, partNumber },
|
|
69
|
+
body: {
|
|
70
|
+
byteLength: input.byteLength,
|
|
71
|
+
contentMd5: context.contentMd5,
|
|
72
|
+
uploadId: input.uploadId,
|
|
73
|
+
},
|
|
74
|
+
}, context.options);
|
|
75
|
+
if (!result.ok)
|
|
76
|
+
return null;
|
|
77
|
+
const descriptor = result.data;
|
|
78
|
+
if (descriptor.stageId !== stageId ||
|
|
79
|
+
descriptor.uploadId !== input.uploadId ||
|
|
80
|
+
descriptor.partNumber !== partNumber ||
|
|
81
|
+
descriptor.byteLength !== input.byteLength ||
|
|
82
|
+
descriptor.contentMd5 !== context.contentMd5) {
|
|
83
|
+
context.transport.reportFailure({
|
|
84
|
+
kind: "shape",
|
|
85
|
+
method: operation.method,
|
|
86
|
+
path: context.path,
|
|
87
|
+
status: 200,
|
|
88
|
+
statusText: "",
|
|
89
|
+
message: "Multipart upload descriptor does not match its part",
|
|
90
|
+
});
|
|
91
|
+
return null;
|
|
92
|
+
}
|
|
93
|
+
return descriptor.source;
|
|
94
|
+
}
|
|
95
|
+
// Parts can wait behind other transfers in the shared pool. An issued URL that
|
|
96
|
+
// is about to lapse would only spend this part's retry on a certain 403, so
|
|
97
|
+
// authorize afresh instead; a skewed clock errs toward authorizing.
|
|
98
|
+
const ISSUED_URL_MIN_REMAINING_MS = 30_000;
|
|
99
|
+
function isUsableIssuedSource(source) {
|
|
100
|
+
return (source !== undefined &&
|
|
101
|
+
(source.kind === "api" ||
|
|
102
|
+
Date.parse(source.expiresAt) - Date.now() > ISSUED_URL_MIN_REMAINING_MS));
|
|
103
|
+
}
|
|
104
|
+
// A capability path for memory/custom stores; S3/R2 never proxy parts.
|
|
105
|
+
function uploadThroughApi(context, file) {
|
|
106
|
+
const { input } = context;
|
|
107
|
+
return context.transport.requestBinaryRequest(uploadMultipartBlobPartBytesOperation, {
|
|
108
|
+
params: { stageId: context.stageId, partNumber: context.partNumber },
|
|
109
|
+
body: input.encryptedBytes,
|
|
110
|
+
headers: {
|
|
111
|
+
[blobWireHeaderKeys.partByteLength]: input.byteLength.toString(),
|
|
112
|
+
[blobWireHeaderKeys.partSha256]: input.sha256,
|
|
113
|
+
[blobWireHeaderKeys.partUploadId]: input.uploadId,
|
|
114
|
+
},
|
|
115
|
+
}, { ...context.options, encodeBody: () => file });
|
|
47
116
|
}
|
|
48
117
|
export async function uploadMultipartBlobPartBytes(transport, stageId, partNumber, input, uploadFetch = (url, init) => fetch(url, init), options = {}) {
|
|
49
118
|
if (options.signal?.aborted)
|
|
50
119
|
return null;
|
|
51
|
-
const
|
|
52
|
-
|
|
53
|
-
|
|
120
|
+
const path = operationRequestPath(authorizeMultipartBlobPartUploadOperation, {
|
|
121
|
+
stageId,
|
|
122
|
+
partNumber,
|
|
123
|
+
});
|
|
54
124
|
const prepared = await preparePart(input).catch(() => invalidPart(transport, path));
|
|
55
125
|
if (!prepared)
|
|
56
126
|
return null;
|
|
57
127
|
const { file, contentMd5 } = prepared;
|
|
58
|
-
const
|
|
59
|
-
byteLength: input.byteLength,
|
|
128
|
+
const context = {
|
|
60
129
|
contentMd5,
|
|
61
|
-
|
|
130
|
+
input,
|
|
131
|
+
options,
|
|
132
|
+
partNumber,
|
|
133
|
+
path,
|
|
134
|
+
stageId,
|
|
135
|
+
transport,
|
|
62
136
|
};
|
|
63
137
|
let status = null;
|
|
64
|
-
//
|
|
65
|
-
// failure may be an expired URL hidden by R2 CORS, or a lost success
|
|
66
|
-
// reauthorize once and send the same immutable bytes to the same
|
|
138
|
+
// The first attempt uses the source the stage initiation issued, if any. A
|
|
139
|
+
// network failure may be an expired URL hidden by R2 CORS, or a lost success
|
|
140
|
+
// response; reauthorize once and send the same immutable bytes to the same
|
|
141
|
+
// part number.
|
|
67
142
|
for (let attempt = 0; attempt < 2; attempt += 1) {
|
|
68
143
|
if (options.signal?.aborted)
|
|
69
144
|
return null;
|
|
70
|
-
const
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
if (descriptor.stageId !== stageId ||
|
|
75
|
-
descriptor.uploadId !== input.uploadId ||
|
|
76
|
-
descriptor.partNumber !== partNumber ||
|
|
77
|
-
descriptor.byteLength !== input.byteLength ||
|
|
78
|
-
descriptor.contentMd5 !== contentMd5) {
|
|
79
|
-
transport.reportFailure({
|
|
80
|
-
kind: "shape",
|
|
81
|
-
method: operation.method,
|
|
82
|
-
path,
|
|
83
|
-
status: 200,
|
|
84
|
-
statusText: "",
|
|
85
|
-
message: "Multipart upload descriptor does not match its part",
|
|
86
|
-
});
|
|
145
|
+
const source = attempt === 0 && isUsableIssuedSource(input.source)
|
|
146
|
+
? input.source
|
|
147
|
+
: await authorizePart(context);
|
|
148
|
+
if (!source)
|
|
87
149
|
return null;
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
// A capability path for memory/custom stores; S3/R2 never proxy parts.
|
|
91
|
-
return transport.requestBinaryRequest(uploadMultipartBlobPartBytesOperation, {
|
|
92
|
-
params,
|
|
93
|
-
body: input.encryptedBytes,
|
|
94
|
-
headers: {
|
|
95
|
-
[blobWireHeaderKeys.partByteLength]: input.byteLength.toString(),
|
|
96
|
-
[blobWireHeaderKeys.partSha256]: input.sha256,
|
|
97
|
-
[blobWireHeaderKeys.partUploadId]: input.uploadId,
|
|
98
|
-
},
|
|
99
|
-
}, { ...options, encodeBody: () => file });
|
|
100
|
-
}
|
|
150
|
+
if (source.kind === "api")
|
|
151
|
+
return uploadThroughApi(context, file);
|
|
101
152
|
try {
|
|
102
|
-
const response = await uploadFetch(
|
|
153
|
+
const response = await uploadFetch(source.url, {
|
|
103
154
|
method: "PUT",
|
|
104
155
|
body: file,
|
|
105
156
|
headers: {
|
|
@@ -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 { 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"]}
|
|
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 {\n MAX_MULTIPART_BLOB_PART_BYTES,\n type MultipartBlobPartDescriptor,\n type MultipartBlobPartUploadSource,\n} 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\nfunction contentMd5Base64(bytes: Uint8Array): string {\n return btoa(String.fromCharCode(...md5(bytes)));\n}\n\n/**\n * Describe an encrypted part the way a stage initiation lists the parts it\n * will send first: number, exact length, and the Content-MD5 a signed upload\n * URL binds.\n */\nexport function describeMultipartBlobPart(\n partNumber: number,\n encryptedBytes: Uint8Array,\n): MultipartBlobPartDescriptor {\n return {\n byteLength: encryptedBytes.byteLength,\n contentMd5: contentMd5Base64(encryptedBytes),\n partNumber,\n };\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 return { file, contentMd5: contentMd5Base64(new Uint8Array(bytes)) };\n}\n\ninterface PartUploadContext {\n readonly contentMd5: string;\n readonly input: UploadMultipartBlobPartBytesRequest;\n readonly options: RequestResultOptions;\n readonly partNumber: number;\n readonly path: string;\n readonly stageId: string;\n readonly transport: OperationTransport;\n}\n\n// Sign just in time for this part. The returned descriptor must name exactly\n// the part, upload, length, and checksum that were requested.\nasync function authorizePart(\n context: PartUploadContext,\n): Promise<MultipartBlobPartUploadSource | null> {\n const operation = authorizeMultipartBlobPartUploadOperation;\n const { input, partNumber, stageId } = context;\n const result = await context.transport.requestResult(\n operation,\n {\n params: { stageId, partNumber },\n body: {\n byteLength: input.byteLength,\n contentMd5: context.contentMd5,\n uploadId: input.uploadId,\n },\n },\n context.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 !== context.contentMd5\n ) {\n context.transport.reportFailure({\n kind: \"shape\",\n method: operation.method,\n path: context.path,\n status: 200,\n statusText: \"\",\n message: \"Multipart upload descriptor does not match its part\",\n });\n return null;\n }\n return descriptor.source;\n}\n\n// Parts can wait behind other transfers in the shared pool. An issued URL that\n// is about to lapse would only spend this part's retry on a certain 403, so\n// authorize afresh instead; a skewed clock errs toward authorizing.\nconst ISSUED_URL_MIN_REMAINING_MS = 30_000;\n\nfunction isUsableIssuedSource(\n source: MultipartBlobPartUploadSource | undefined,\n): source is MultipartBlobPartUploadSource {\n return (\n source !== undefined &&\n (source.kind === \"api\" ||\n Date.parse(source.expiresAt) - Date.now() > ISSUED_URL_MIN_REMAINING_MS)\n );\n}\n\n// A capability path for memory/custom stores; S3/R2 never proxy parts.\nfunction uploadThroughApi(context: PartUploadContext, file: File) {\n const { input } = context;\n return context.transport.requestBinaryRequest(\n uploadMultipartBlobPartBytesOperation,\n {\n params: { stageId: context.stageId, partNumber: context.partNumber },\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 { ...context.options, encodeBody: () => file },\n );\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 path = operationRequestPath(authorizeMultipartBlobPartUploadOperation, {\n stageId,\n partNumber,\n });\n const prepared = await preparePart(input).catch(() =>\n invalidPart(transport, path),\n );\n if (!prepared) return null;\n const { file, contentMd5 } = prepared;\n const context: PartUploadContext = {\n contentMd5,\n input,\n options,\n partNumber,\n path,\n stageId,\n transport,\n };\n let status: number | null = null;\n // The first attempt uses the source the stage initiation issued, if any. A\n // network failure may be an expired URL hidden by R2 CORS, or a lost success\n // response; reauthorize once and send the same immutable bytes to the same\n // part number.\n for (let attempt = 0; attempt < 2; attempt += 1) {\n if (options.signal?.aborted) return null;\n const source =\n attempt === 0 && isUsableIssuedSource(input.source)\n ? input.source\n : await authorizePart(context);\n if (!source) return null;\n if (source.kind === \"api\") return uploadThroughApi(context, file);\n try {\n const response = await uploadFetch(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"]}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import type { ListContainerParentLanesRequest } from "../../../validators/request/index.js";
|
|
2
|
-
import type
|
|
3
|
-
import type {
|
|
1
|
+
import type { ListContainerDocumentLanesRequest, ListContainerParentLanesRequest } from "../../../validators/request/index.js";
|
|
2
|
+
import { type ListContainerDocumentLanesResponse, type ListContainerParentLanesResponse } from "../../../validators/response/index.js";
|
|
3
|
+
import type { RequestResult } from "../../types.js";
|
|
4
4
|
export interface ContainerKekLogOptions {
|
|
5
5
|
readonly afterKeyEpoch?: number;
|
|
6
6
|
readonly keyringForEpoch?: number;
|
|
@@ -10,10 +10,16 @@ export declare const containerKekLog: {
|
|
|
10
10
|
readonly method: "GET";
|
|
11
11
|
readonly path: (containerId: string, options?: ContainerKekLogOptions) => string;
|
|
12
12
|
};
|
|
13
|
-
export declare const
|
|
14
|
-
readonly
|
|
15
|
-
readonly method: "
|
|
16
|
-
readonly path:
|
|
13
|
+
export declare const containerDocumentLanes: {
|
|
14
|
+
readonly isResponseForRequest: (input: ListContainerDocumentLanesRequest, value: unknown) => value is ListContainerDocumentLanesResponse;
|
|
15
|
+
readonly method: "POST";
|
|
16
|
+
readonly path: string;
|
|
17
|
+
/**
|
|
18
|
+
* Identical concurrent batches share one silent fetch; each caller then
|
|
19
|
+
* applies its own reporting policy. A lane rejected for anything but a
|
|
20
|
+
* positive absence proof is reported like the failed request it replaces.
|
|
21
|
+
*/
|
|
22
|
+
readonly settle: (result: RequestResult<ListContainerDocumentLanesResponse> | null, reportErrors: boolean, reportError: (message: string) => void) => ListContainerDocumentLanesResponse | null;
|
|
17
23
|
};
|
|
18
24
|
export declare const containerParentLanes: {
|
|
19
25
|
readonly isResponseForRequest: (input: ListContainerParentLanesRequest, value: unknown) => value is ListContainerParentLanesResponse;
|
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import { getContainerKekLogOperation, isGetContainerKekLogOperationResponse,
|
|
1
|
+
import { getContainerKekLogOperation, isGetContainerKekLogOperationResponse, isListContainerDocumentLanesOperationResponse, isListContainerParentLanesOperationResponse, listContainerDocumentLanesOperation, listContainerParentLanesOperation, operationRequestPath, operationRequestPathWithQuery, } from "../../../validators/operation/index.js";
|
|
2
|
+
import { CONTAINER_NOT_FOUND_ERROR_CODE, } from "../../../validators/response/index.js";
|
|
2
3
|
export const containerKekLog = {
|
|
3
4
|
isResponse: isGetContainerKekLogOperationResponse,
|
|
4
5
|
method: getContainerKekLogOperation.method,
|
|
@@ -7,23 +8,43 @@ export const containerKekLog = {
|
|
|
7
8
|
afterKeyEpoch: options.afterKeyEpoch,
|
|
8
9
|
}),
|
|
9
10
|
};
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
11
|
+
function resultsMatchLanes(lanes, results) {
|
|
12
|
+
const expectedLaneIds = new Set(lanes.map((lane) => lane.laneId));
|
|
13
|
+
return (results.length === lanes.length &&
|
|
14
|
+
results.every((result) => expectedLaneIds.has(result.laneId)));
|
|
15
|
+
}
|
|
16
|
+
export const containerDocumentLanes = {
|
|
17
|
+
isResponseForRequest: (input, value) => isListContainerDocumentLanesOperationResponse(value) &&
|
|
18
|
+
resultsMatchLanes(input.lanes, value.results),
|
|
19
|
+
method: listContainerDocumentLanesOperation.method,
|
|
20
|
+
path: operationRequestPath(listContainerDocumentLanesOperation, {}),
|
|
21
|
+
/**
|
|
22
|
+
* Identical concurrent batches share one silent fetch; each caller then
|
|
23
|
+
* applies its own reporting policy. A lane rejected for anything but a
|
|
24
|
+
* positive absence proof is reported like the failed request it replaces.
|
|
25
|
+
*/
|
|
26
|
+
settle: (result, reportErrors, reportError) => {
|
|
27
|
+
if (result?.ok !== true) {
|
|
28
|
+
if (reportErrors)
|
|
29
|
+
result?.report();
|
|
30
|
+
return null;
|
|
31
|
+
}
|
|
32
|
+
if (reportErrors) {
|
|
33
|
+
for (const lane of result.data.results) {
|
|
34
|
+
// Only the exact 404 + absence-code pair proves the container is gone.
|
|
35
|
+
if ("failure" in lane &&
|
|
36
|
+
!(lane.failure.status === 404 &&
|
|
37
|
+
lane.failure.code === CONTAINER_NOT_FOUND_ERROR_CODE))
|
|
38
|
+
reportError(`${listContainerDocumentLanesOperation.method} ${listContainerDocumentLanesOperation.path}: ${lane.failure.status} ${lane.failure.error}`);
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
return result.data;
|
|
42
|
+
},
|
|
18
43
|
};
|
|
19
44
|
export const containerParentLanes = {
|
|
20
45
|
isResponseForRequest: (input, value) => {
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
}
|
|
24
|
-
const expectedLaneIds = new Set(input.lanes.map((lane) => lane.laneId));
|
|
25
|
-
return (value.results.length === input.lanes.length &&
|
|
26
|
-
value.results.every((result) => expectedLaneIds.has(result.laneId)));
|
|
46
|
+
return (isListContainerParentLanesOperationResponse(value) &&
|
|
47
|
+
resultsMatchLanes(input.lanes, value.results));
|
|
27
48
|
},
|
|
28
49
|
method: listContainerParentLanesOperation.method,
|
|
29
50
|
path: operationRequestPath(listContainerParentLanesOperation, {}),
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"reads.js","sourceRoot":"","sources":["reads.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,2BAA2B,EAC3B,qCAAqC,EACrC,
|
|
1
|
+
{"version":3,"file":"reads.js","sourceRoot":"","sources":["reads.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,2BAA2B,EAC3B,qCAAqC,EACrC,6CAA6C,EAC7C,2CAA2C,EAC3C,mCAAmC,EACnC,iCAAiC,EACjC,oBAAoB,EACpB,6BAA6B,GAC9B,MAAM,iCAAiC,CAAC;AAKzC,OAAO,EACL,8BAA8B,GAG/B,MAAM,gCAAgC,CAAC;AAQxC,MAAM,CAAC,MAAM,eAAe,GAAG;IAC7B,UAAU,EAAE,qCAAqC;IACjD,MAAM,EAAE,2BAA2B,CAAC,MAAM;IAC1C,IAAI,EAAE,CAAC,WAAmB,EAAE,UAAkC,EAAE,EAAE,EAAE,CAClE,6BAA6B,CAC3B,2BAA2B,EAC3B,EAAE,WAAW,EAAE,EACf;QACE,eAAe,EAAE,OAAO,CAAC,eAAe;QACxC,aAAa,EAAE,OAAO,CAAC,aAAa;KACrC,CACF;CACK,CAAC;AAEX,SAAS,iBAAiB,CACxB,KAAiD,EACjD,OAAmD;IAEnD,MAAM,eAAe,GAAG,IAAI,GAAG,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC,CAAC;IAClE,OAAO,CACL,OAAO,CAAC,MAAM,KAAK,KAAK,CAAC,MAAM;QAC/B,OAAO,CAAC,KAAK,CAAC,CAAC,MAAM,EAAE,EAAE,CAAC,eAAe,CAAC,GAAG,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC,CAC9D,CAAC;AACJ,CAAC;AAED,MAAM,CAAC,MAAM,sBAAsB,GAAG;IACpC,oBAAoB,EAAE,CACpB,KAAwC,EACxC,KAAc,EAC+B,EAAE,CAC/C,6CAA6C,CAAC,KAAK,CAAC;QACpD,iBAAiB,CAAC,KAAK,CAAC,KAAK,EAAE,KAAK,CAAC,OAAO,CAAC;IAC/C,MAAM,EAAE,mCAAmC,CAAC,MAAM;IAClD,IAAI,EAAE,oBAAoB,CAAC,mCAAmC,EAAE,EAAE,CAAC;IACnE;;;;OAIG;IACH,MAAM,EAAE,CACN,MAAgE,EAChE,YAAqB,EACrB,WAAsC,EACK,EAAE;QAC7C,IAAI,MAAM,EAAE,EAAE,KAAK,IAAI,EAAE,CAAC;YACxB,IAAI,YAAY;gBAAE,MAAM,EAAE,MAAM,EAAE,CAAC;YACnC,OAAO,IAAI,CAAC;QACd,CAAC;QACD,IAAI,YAAY,EAAE,CAAC;YACjB,KAAK,MAAM,IAAI,IAAI,MAAM,CAAC,IAAI,CAAC,OAAO,EAAE,CAAC;gBACvC,uEAAuE;gBACvE,IACE,SAAS,IAAI,IAAI;oBACjB,CAAC,CACC,IAAI,CAAC,OAAO,CAAC,MAAM,KAAK,GAAG;wBAC3B,IAAI,CAAC,OAAO,CAAC,IAAI,KAAK,8BAA8B,CACrD;oBAED,WAAW,CACT,GAAG,mCAAmC,CAAC,MAAM,IAAI,mCAAmC,CAAC,IAAI,KAAK,IAAI,CAAC,OAAO,CAAC,MAAM,IAAI,IAAI,CAAC,OAAO,CAAC,KAAK,EAAE,CAC1I,CAAC;YACN,CAAC;QACH,CAAC;QACD,OAAO,MAAM,CAAC,IAAI,CAAC;IACrB,CAAC;CACO,CAAC;AAEX,MAAM,CAAC,MAAM,oBAAoB,GAAG;IAClC,oBAAoB,EAAE,CACpB,KAAsC,EACtC,KAAc,EAC6B,EAAE;QAC7C,OAAO,CACL,2CAA2C,CAAC,KAAK,CAAC;YAClD,iBAAiB,CAAC,KAAK,CAAC,KAAK,EAAE,KAAK,CAAC,OAAO,CAAC,CAC9C,CAAC;IACJ,CAAC;IACD,MAAM,EAAE,iCAAiC,CAAC,MAAM;IAChD,IAAI,EAAE,oBAAoB,CAAC,iCAAiC,EAAE,EAAE,CAAC;CACzD,CAAC","sourcesContent":["import {\n getContainerKekLogOperation,\n isGetContainerKekLogOperationResponse,\n isListContainerDocumentLanesOperationResponse,\n isListContainerParentLanesOperationResponse,\n listContainerDocumentLanesOperation,\n listContainerParentLanesOperation,\n operationRequestPath,\n operationRequestPathWithQuery,\n} from \"@tearleads/validators/operation\";\nimport type {\n ListContainerDocumentLanesRequest,\n ListContainerParentLanesRequest,\n} from \"@tearleads/validators/request\";\nimport {\n CONTAINER_NOT_FOUND_ERROR_CODE,\n type ListContainerDocumentLanesResponse,\n type ListContainerParentLanesResponse,\n} from \"@tearleads/validators/response\";\nimport type { RequestResult } from \"../../types\";\n\nexport interface ContainerKekLogOptions {\n readonly afterKeyEpoch?: number;\n readonly keyringForEpoch?: number;\n}\n\nexport const containerKekLog = {\n isResponse: isGetContainerKekLogOperationResponse,\n method: getContainerKekLogOperation.method,\n path: (containerId: string, options: ContainerKekLogOptions = {}) =>\n operationRequestPathWithQuery(\n getContainerKekLogOperation,\n { containerId },\n {\n keyringForEpoch: options.keyringForEpoch,\n afterKeyEpoch: options.afterKeyEpoch,\n },\n ),\n} as const;\n\nfunction resultsMatchLanes(\n lanes: ReadonlyArray<{ readonly laneId: string }>,\n results: ReadonlyArray<{ readonly laneId: string }>,\n): boolean {\n const expectedLaneIds = new Set(lanes.map((lane) => lane.laneId));\n return (\n results.length === lanes.length &&\n results.every((result) => expectedLaneIds.has(result.laneId))\n );\n}\n\nexport const containerDocumentLanes = {\n isResponseForRequest: (\n input: ListContainerDocumentLanesRequest,\n value: unknown,\n ): value is ListContainerDocumentLanesResponse =>\n isListContainerDocumentLanesOperationResponse(value) &&\n resultsMatchLanes(input.lanes, value.results),\n method: listContainerDocumentLanesOperation.method,\n path: operationRequestPath(listContainerDocumentLanesOperation, {}),\n /**\n * Identical concurrent batches share one silent fetch; each caller then\n * applies its own reporting policy. A lane rejected for anything but a\n * positive absence proof is reported like the failed request it replaces.\n */\n settle: (\n result: RequestResult<ListContainerDocumentLanesResponse> | null,\n reportErrors: boolean,\n reportError: (message: string) => void,\n ): ListContainerDocumentLanesResponse | null => {\n if (result?.ok !== true) {\n if (reportErrors) result?.report();\n return null;\n }\n if (reportErrors) {\n for (const lane of result.data.results) {\n // Only the exact 404 + absence-code pair proves the container is gone.\n if (\n \"failure\" in lane &&\n !(\n lane.failure.status === 404 &&\n lane.failure.code === CONTAINER_NOT_FOUND_ERROR_CODE\n )\n )\n reportError(\n `${listContainerDocumentLanesOperation.method} ${listContainerDocumentLanesOperation.path}: ${lane.failure.status} ${lane.failure.error}`,\n );\n }\n }\n return result.data;\n },\n} as const;\n\nexport const containerParentLanes = {\n isResponseForRequest: (\n input: ListContainerParentLanesRequest,\n value: unknown,\n ): value is ListContainerParentLanesResponse => {\n return (\n isListContainerParentLanesOperationResponse(value) &&\n resultsMatchLanes(input.lanes, value.results)\n );\n },\n method: listContainerParentLanesOperation.method,\n path: operationRequestPath(listContainerParentLanesOperation, {}),\n} as const;\n"]}
|
|
@@ -1,11 +1,7 @@
|
|
|
1
1
|
import type { DocumentAttributionRangesQuery, HttpOperation, HttpOperationMethod } from "../validators/operation/index.js";
|
|
2
|
-
import type { ReferencedPrincipalStateResponse
|
|
2
|
+
import type { ReferencedPrincipalStateResponse } from "../validators/response/index.js";
|
|
3
3
|
export type HttpMethod = HttpOperationMethod;
|
|
4
4
|
export type RequestBody = BodyInit;
|
|
5
|
-
export interface ListContainerDocumentsOptions {
|
|
6
|
-
limit?: number;
|
|
7
|
-
watermark?: SyncWatermark | null;
|
|
8
|
-
}
|
|
9
5
|
export type ListDocumentEditAttributionRangesOptions = Omit<DocumentAttributionRangesQuery, "cursor"> & {
|
|
10
6
|
cursor?: DocumentAttributionRangesQuery["cursor"] | null;
|
|
11
7
|
};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.js","sourceRoot":"","sources":["types.ts"],"names":[],"mappings":"","sourcesContent":["import type {\n DocumentAttributionRangesQuery,\n HttpOperation,\n HttpOperationMethod,\n} from \"@tearleads/validators/operation\";\nimport type {
|
|
1
|
+
{"version":3,"file":"types.js","sourceRoot":"","sources":["types.ts"],"names":[],"mappings":"","sourcesContent":["import type {\n DocumentAttributionRangesQuery,\n HttpOperation,\n HttpOperationMethod,\n} from \"@tearleads/validators/operation\";\nimport type { ReferencedPrincipalStateResponse } from \"@tearleads/validators/response\";\n\nexport type HttpMethod = HttpOperationMethod;\nexport type RequestBody = BodyInit;\n\nexport type ListDocumentEditAttributionRangesOptions = Omit<\n DocumentAttributionRangesQuery,\n \"cursor\"\n> & {\n cursor?: DocumentAttributionRangesQuery[\"cursor\"] | null;\n};\n\nexport interface RequestResultOptions {\n readonly signal?: AbortSignal | undefined;\n /** Expected target for a declared 402; mismatched response identities fail closed. */\n readonly expectedPaymentRequiredOrganizationId?: string | undefined;\n readonly headers?: Record<string, string> | undefined;\n readonly reportErrors?: boolean | undefined;\n /** Called after this request or a concurrent request renews the same session. */\n readonly onSessionRenewed?: (() => void) | undefined;\n readonly retryOnSessionExpired?: boolean | \"renew-only\" | undefined;\n}\n\nexport type OperationRequestFn = <T>(\n path: string,\n validator: (value: unknown) => value is T,\n method: HttpMethod,\n body: RequestBody | undefined,\n options: RequestResultOptions | undefined,\n failureOperation: HttpOperation,\n) => Promise<T | null>;\n\nexport type OperationRequestResultFn = <T>(\n path: string,\n validator: (value: unknown) => value is T,\n method: HttpMethod,\n body: RequestBody | undefined,\n options: RequestResultOptions | undefined,\n failureOperation: HttpOperation,\n) => Promise<RequestResult<T>>;\n\nexport type RequestFailureKind =\n | \"storage\"\n | \"http\"\n | \"network\"\n | \"json\"\n | \"shape\"\n | \"cancelled\"\n | \"outcome-unknown\";\n\nexport interface RequestFailure {\n readonly code?: string | undefined;\n readonly kind: RequestFailureKind;\n readonly message: string;\n readonly method: HttpMethod;\n readonly ok: false;\n readonly path: string;\n readonly report: () => void;\n readonly status: number | null;\n readonly statusText: string;\n readonly stalePrincipalHeads?: ReferencedPrincipalStateResponse[] | undefined;\n /**\n * With `container_descendant_rekeys_required`, the full owed set, parent-first;\n * with `container_descendant_rekeys_inaccessible`, its unwritable subset.\n * A hint, never authority; verify each container before signing for it.\n */\n readonly requiredContainerIds?: readonly string[] | undefined;\n}\n\nexport interface RequestSuccess<T> {\n readonly data: T;\n readonly ok: true;\n}\n\nexport type RequestResult<T> = RequestFailure | RequestSuccess<T>;\n\nexport interface ResponseRequestValidationFailureInput {\n readonly code?: string | undefined;\n readonly kind: RequestFailureKind;\n readonly message: string;\n readonly method: HttpMethod;\n readonly options?: RequestResultOptions | undefined;\n readonly path: string;\n readonly status: number | null;\n readonly statusText: string;\n}\n\nexport interface OperationResponseRequestFn {\n (\n path: string,\n method: HttpMethod,\n body: RequestBody | undefined,\n options: RequestResultOptions | undefined,\n additionalSuccessStatuses: readonly number[],\n failureOperation: HttpOperation,\n ): Promise<RequestResult<Response>>;\n readonly reportFailure: (\n input: ResponseRequestValidationFailureInput,\n ) => RequestFailure;\n}\n"]}
|
|
@@ -2,9 +2,17 @@ export const containerKekLogSingleKeyringRefinement = {
|
|
|
2
2
|
description: "a container KEK log page contains at most one sealed keyring",
|
|
3
3
|
id: "response.container-kek-log-single-keyring",
|
|
4
4
|
};
|
|
5
|
-
const
|
|
6
|
-
description: "container-
|
|
7
|
-
id: "request.container-
|
|
5
|
+
export const containerDocumentLaneRequestUniqueIdsRefinement = {
|
|
6
|
+
description: "container document-lane request lane ids must be unique",
|
|
7
|
+
id: "request.container-document-lane-unique-ids",
|
|
8
|
+
};
|
|
9
|
+
export const containerDocumentLaneRequestPageTotalRefinement = {
|
|
10
|
+
description: "container document-lane request page limits must total at most 500",
|
|
11
|
+
id: "request.container-document-lane-page-total",
|
|
12
|
+
};
|
|
13
|
+
export const containerDocumentLaneResponseUniqueIdsRefinement = {
|
|
14
|
+
description: "container document-lane response lane ids must be unique",
|
|
15
|
+
id: "response.container-document-lane-unique-ids",
|
|
8
16
|
};
|
|
9
17
|
export const containerParentLaneRequestUniqueIdsRefinement = {
|
|
10
18
|
description: "container parent-lane request lane ids must be unique",
|
|
@@ -21,8 +29,10 @@ export const containerParentLaneResponseUniqueIdsRefinement = {
|
|
|
21
29
|
export const containerKekLogResponseRuntimeRefinements = [
|
|
22
30
|
containerKekLogSingleKeyringRefinement,
|
|
23
31
|
];
|
|
24
|
-
export const
|
|
25
|
-
|
|
32
|
+
export const containerDocumentLaneRuntimeRefinements = [
|
|
33
|
+
containerDocumentLaneRequestUniqueIdsRefinement,
|
|
34
|
+
containerDocumentLaneRequestPageTotalRefinement,
|
|
35
|
+
containerDocumentLaneResponseUniqueIdsRefinement,
|
|
26
36
|
];
|
|
27
37
|
export const containerParentLaneRuntimeRefinements = [
|
|
28
38
|
containerParentLaneRequestUniqueIdsRefinement,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"containerReadRefinements.js","sourceRoot":"","sources":["containerReadRefinements.ts"],"names":[],"mappings":"AAAA,MAAM,CAAC,MAAM,sCAAsC,GAAG;IACpD,WAAW,EAAE,8DAA8D;IAC3E,EAAE,EAAE,2CAA2C;CACvC,CAAC;AAEX,MAAM,
|
|
1
|
+
{"version":3,"file":"containerReadRefinements.js","sourceRoot":"","sources":["containerReadRefinements.ts"],"names":[],"mappings":"AAAA,MAAM,CAAC,MAAM,sCAAsC,GAAG;IACpD,WAAW,EAAE,8DAA8D;IAC3E,EAAE,EAAE,2CAA2C;CACvC,CAAC;AAEX,MAAM,CAAC,MAAM,+CAA+C,GAAG;IAC7D,WAAW,EAAE,yDAAyD;IACtE,EAAE,EAAE,4CAA4C;CACxC,CAAC;AAEX,MAAM,CAAC,MAAM,+CAA+C,GAAG;IAC7D,WAAW,EACT,oEAAoE;IACtE,EAAE,EAAE,4CAA4C;CACxC,CAAC;AAEX,MAAM,CAAC,MAAM,gDAAgD,GAAG;IAC9D,WAAW,EAAE,0DAA0D;IACvE,EAAE,EAAE,6CAA6C;CACzC,CAAC;AAEX,MAAM,CAAC,MAAM,6CAA6C,GAAG;IAC3D,WAAW,EAAE,uDAAuD;IACpE,EAAE,EAAE,0CAA0C;CACtC,CAAC;AAEX,MAAM,CAAC,MAAM,6CAA6C,GAAG;IAC3D,WAAW,EACT,kEAAkE;IACpE,EAAE,EAAE,0CAA0C;CACtC,CAAC;AAEX,MAAM,CAAC,MAAM,8CAA8C,GAAG;IAC5D,WAAW,EAAE,wDAAwD;IACrE,EAAE,EAAE,2CAA2C;CACvC,CAAC;AAEX,MAAM,CAAC,MAAM,yCAAyC,GAAG;IACvD,sCAAsC;CAC9B,CAAC;AAEX,MAAM,CAAC,MAAM,uCAAuC,GAAG;IACrD,+CAA+C;IAC/C,+CAA+C;IAC/C,gDAAgD;CACxC,CAAC;AAEX,MAAM,CAAC,MAAM,qCAAqC,GAAG;IACnD,6CAA6C;IAC7C,6CAA6C;IAC7C,8CAA8C;CACtC,CAAC","sourcesContent":["export const containerKekLogSingleKeyringRefinement = {\n description: \"a container KEK log page contains at most one sealed keyring\",\n id: \"response.container-kek-log-single-keyring\",\n} as const;\n\nexport const containerDocumentLaneRequestUniqueIdsRefinement = {\n description: \"container document-lane request lane ids must be unique\",\n id: \"request.container-document-lane-unique-ids\",\n} as const;\n\nexport const containerDocumentLaneRequestPageTotalRefinement = {\n description:\n \"container document-lane request page limits must total at most 500\",\n id: \"request.container-document-lane-page-total\",\n} as const;\n\nexport const containerDocumentLaneResponseUniqueIdsRefinement = {\n description: \"container document-lane response lane ids must be unique\",\n id: \"response.container-document-lane-unique-ids\",\n} as const;\n\nexport const containerParentLaneRequestUniqueIdsRefinement = {\n description: \"container parent-lane request lane ids must be unique\",\n id: \"request.container-parent-lane-unique-ids\",\n} as const;\n\nexport const containerParentLaneRequestPageTotalRefinement = {\n description:\n \"container parent-lane request page limits must total at most 500\",\n id: \"request.container-parent-lane-page-total\",\n} as const;\n\nexport const containerParentLaneResponseUniqueIdsRefinement = {\n description: \"container parent-lane response lane ids must be unique\",\n id: \"response.container-parent-lane-unique-ids\",\n} as const;\n\nexport const containerKekLogResponseRuntimeRefinements = [\n containerKekLogSingleKeyringRefinement,\n] as const;\n\nexport const containerDocumentLaneRuntimeRefinements = [\n containerDocumentLaneRequestUniqueIdsRefinement,\n containerDocumentLaneRequestPageTotalRefinement,\n containerDocumentLaneResponseUniqueIdsRefinement,\n] as const;\n\nexport const containerParentLaneRuntimeRefinements = [\n containerParentLaneRequestUniqueIdsRefinement,\n containerParentLaneRequestPageTotalRefinement,\n containerParentLaneResponseUniqueIdsRefinement,\n] as const;\n"]}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
/** Caller-chosen id that pairs one batched lane request with its result. */
|
|
3
|
+
export declare const ContainerReadLaneIdSchema: z.ZodCustom<string, string>;
|
|
4
|
+
/** Opaque `(updatedAt, id)` cursor returned by an earlier page of the lane. */
|
|
5
|
+
export declare const ContainerReadLaneWatermarkSchema: z.ZodObject<{
|
|
6
|
+
id: z.ZodString;
|
|
7
|
+
updatedAt: z.ZodString;
|
|
8
|
+
}, z.core.$strict>;
|
|
9
|
+
/**
|
|
10
|
+
* Containers one document-listing request may name. Each lane runs the same
|
|
11
|
+
* readable-container gate and access resolver as a single listing, so this
|
|
12
|
+
* bounds per-request server work; the page total bounds returned rows.
|
|
13
|
+
*/
|
|
14
|
+
export declare const MAX_CONTAINER_DOCUMENT_LANES = 32;
|
|
15
|
+
/** Rows one batched lane request may ask for when it omits `limit`. */
|
|
16
|
+
export declare const DEFAULT_CONTAINER_READ_LANE_PAGE_LIMIT = 100;
|
|
17
|
+
/** Bound on the sum of every lane's page limit, the single-page maximum. */
|
|
18
|
+
export declare const MAX_CONTAINER_READ_LANE_BATCH_PAGE_TOTAL = 500;
|
|
19
|
+
interface ContainerReadLaneRequestShape {
|
|
20
|
+
readonly laneId: string;
|
|
21
|
+
readonly limit?: number | undefined;
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* Batched container reads pair results to requests by lane id and bound the
|
|
25
|
+
* rows one request may return to the single-page maximum.
|
|
26
|
+
*/
|
|
27
|
+
export declare function refineContainerReadLaneRequests(label: string): ({ lanes }: {
|
|
28
|
+
readonly lanes: ReadonlyArray<ContainerReadLaneRequestShape>;
|
|
29
|
+
}, context: z.RefinementCtx) => void;
|
|
30
|
+
/** Each requested lane id appears at most once among the results. */
|
|
31
|
+
export declare function refineContainerReadLaneResults(label: string): ({ results, }: {
|
|
32
|
+
readonly results: ReadonlyArray<{
|
|
33
|
+
readonly laneId: string;
|
|
34
|
+
}>;
|
|
35
|
+
}, context: z.RefinementCtx) => void;
|
|
36
|
+
export {};
|
|
@@ -1,4 +1,67 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
2
|
import { registerJsonSchemaFragment } from "./jsonSchema.js";
|
|
3
|
-
|
|
3
|
+
/** Caller-chosen id that pairs one batched lane request with its result. */
|
|
4
|
+
export const ContainerReadLaneIdSchema = registerJsonSchemaFragment(z.custom((value) => typeof value === "string" && value.length > 0 && value.length <= 64), { maxLength: 64, minLength: 1, type: "string" });
|
|
5
|
+
/** Opaque `(updatedAt, id)` cursor returned by an earlier page of the lane. */
|
|
6
|
+
export const ContainerReadLaneWatermarkSchema = z.strictObject({
|
|
7
|
+
id: z.string().min(1),
|
|
8
|
+
updatedAt: registerJsonSchemaFragment(z
|
|
9
|
+
.string()
|
|
10
|
+
.refine((value) => !Number.isNaN(new Date(value).getTime()), "Invalid watermark date"), { minLength: 1, type: "string" }),
|
|
11
|
+
});
|
|
12
|
+
/**
|
|
13
|
+
* Containers one document-listing request may name. Each lane runs the same
|
|
14
|
+
* readable-container gate and access resolver as a single listing, so this
|
|
15
|
+
* bounds per-request server work; the page total bounds returned rows.
|
|
16
|
+
*/
|
|
17
|
+
export const MAX_CONTAINER_DOCUMENT_LANES = 32;
|
|
18
|
+
/** Rows one batched lane request may ask for when it omits `limit`. */
|
|
19
|
+
export const DEFAULT_CONTAINER_READ_LANE_PAGE_LIMIT = 100;
|
|
20
|
+
/** Bound on the sum of every lane's page limit, the single-page maximum. */
|
|
21
|
+
export const MAX_CONTAINER_READ_LANE_BATCH_PAGE_TOTAL = 500;
|
|
22
|
+
/**
|
|
23
|
+
* Batched container reads pair results to requests by lane id and bound the
|
|
24
|
+
* rows one request may return to the single-page maximum.
|
|
25
|
+
*/
|
|
26
|
+
export function refineContainerReadLaneRequests(label) {
|
|
27
|
+
return ({ lanes }, context) => {
|
|
28
|
+
const laneIds = new Set();
|
|
29
|
+
let requestedPageTotal = 0;
|
|
30
|
+
lanes.forEach((lane, index) => {
|
|
31
|
+
if (laneIds.has(lane.laneId)) {
|
|
32
|
+
context.addIssue({
|
|
33
|
+
code: "custom",
|
|
34
|
+
message: `${label} id is duplicated`,
|
|
35
|
+
path: ["lanes", index, "laneId"],
|
|
36
|
+
});
|
|
37
|
+
}
|
|
38
|
+
laneIds.add(lane.laneId);
|
|
39
|
+
requestedPageTotal +=
|
|
40
|
+
lane.limit ?? DEFAULT_CONTAINER_READ_LANE_PAGE_LIMIT;
|
|
41
|
+
});
|
|
42
|
+
if (requestedPageTotal > MAX_CONTAINER_READ_LANE_BATCH_PAGE_TOTAL) {
|
|
43
|
+
context.addIssue({
|
|
44
|
+
code: "custom",
|
|
45
|
+
message: `${label} batch page total exceeds ${MAX_CONTAINER_READ_LANE_BATCH_PAGE_TOTAL}`,
|
|
46
|
+
path: ["lanes"],
|
|
47
|
+
});
|
|
48
|
+
}
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
/** Each requested lane id appears at most once among the results. */
|
|
52
|
+
export function refineContainerReadLaneResults(label) {
|
|
53
|
+
return ({ results, }, context) => {
|
|
54
|
+
const laneIds = new Set();
|
|
55
|
+
results.forEach((result, index) => {
|
|
56
|
+
if (laneIds.has(result.laneId)) {
|
|
57
|
+
context.addIssue({
|
|
58
|
+
code: "custom",
|
|
59
|
+
message: `${label} result id is duplicated`,
|
|
60
|
+
path: ["results", index, "laneId"],
|
|
61
|
+
});
|
|
62
|
+
}
|
|
63
|
+
laneIds.add(result.laneId);
|
|
64
|
+
});
|
|
65
|
+
};
|
|
66
|
+
}
|
|
4
67
|
//# sourceMappingURL=containerReadSchemas.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"containerReadSchemas.js","sourceRoot":"","sources":["containerReadSchemas.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AACxB,OAAO,EAAE,0BAA0B,EAAE,MAAM,cAAc,CAAC;AAE1D,MAAM,CAAC,MAAM,
|
|
1
|
+
{"version":3,"file":"containerReadSchemas.js","sourceRoot":"","sources":["containerReadSchemas.ts"],"names":[],"mappings":"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","sourcesContent":["import { z } from \"zod\";\nimport { registerJsonSchemaFragment } from \"./jsonSchema\";\n\n/** Caller-chosen id that pairs one batched lane request with its result. */\nexport const ContainerReadLaneIdSchema = registerJsonSchemaFragment(\n z.custom<string>(\n (value) =>\n typeof value === \"string\" && value.length > 0 && value.length <= 64,\n ),\n { maxLength: 64, minLength: 1, type: \"string\" },\n);\n\n/** Opaque `(updatedAt, id)` cursor returned by an earlier page of the lane. */\nexport const ContainerReadLaneWatermarkSchema = z.strictObject({\n id: z.string().min(1),\n updatedAt: registerJsonSchemaFragment(\n z\n .string()\n .refine(\n (value) => !Number.isNaN(new Date(value).getTime()),\n \"Invalid watermark date\",\n ),\n { minLength: 1, type: \"string\" },\n ),\n});\n\n/**\n * Containers one document-listing request may name. Each lane runs the same\n * readable-container gate and access resolver as a single listing, so this\n * bounds per-request server work; the page total bounds returned rows.\n */\nexport const MAX_CONTAINER_DOCUMENT_LANES = 32;\n\n/** Rows one batched lane request may ask for when it omits `limit`. */\nexport const DEFAULT_CONTAINER_READ_LANE_PAGE_LIMIT = 100;\n/** Bound on the sum of every lane's page limit, the single-page maximum. */\nexport const MAX_CONTAINER_READ_LANE_BATCH_PAGE_TOTAL = 500;\n\ninterface ContainerReadLaneRequestShape {\n readonly laneId: string;\n readonly limit?: number | undefined;\n}\n\n/**\n * Batched container reads pair results to requests by lane id and bound the\n * rows one request may return to the single-page maximum.\n */\nexport function refineContainerReadLaneRequests(label: string) {\n return (\n { lanes }: { readonly lanes: ReadonlyArray<ContainerReadLaneRequestShape> },\n context: z.RefinementCtx,\n ): void => {\n const laneIds = new Set<string>();\n let requestedPageTotal = 0;\n\n lanes.forEach((lane, index) => {\n if (laneIds.has(lane.laneId)) {\n context.addIssue({\n code: \"custom\",\n message: `${label} id is duplicated`,\n path: [\"lanes\", index, \"laneId\"],\n });\n }\n laneIds.add(lane.laneId);\n requestedPageTotal +=\n lane.limit ?? DEFAULT_CONTAINER_READ_LANE_PAGE_LIMIT;\n });\n\n if (requestedPageTotal > MAX_CONTAINER_READ_LANE_BATCH_PAGE_TOTAL) {\n context.addIssue({\n code: \"custom\",\n message: `${label} batch page total exceeds ${MAX_CONTAINER_READ_LANE_BATCH_PAGE_TOTAL}`,\n path: [\"lanes\"],\n });\n }\n };\n}\n\n/** Each requested lane id appears at most once among the results. */\nexport function refineContainerReadLaneResults(label: string) {\n return (\n {\n results,\n }: { readonly results: ReadonlyArray<{ readonly laneId: string }> },\n context: z.RefinementCtx,\n ): void => {\n const laneIds = new Set<string>();\n results.forEach((result, index) => {\n if (laneIds.has(result.laneId)) {\n context.addIssue({\n code: \"custom\",\n message: `${label} result id is duplicated`,\n path: [\"results\", index, \"laneId\"],\n });\n }\n laneIds.add(result.laneId);\n });\n };\n}\n"]}
|