@sequenceholdings/storage 0.1.0 → 0.1.1
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/README.md +16 -3
- package/dist/client.d.ts +76 -0
- package/dist/client.js +24 -0
- package/dist/contracts.d.ts +118 -0
- package/dist/contracts.js +47 -1
- package/dist/transfer.js +4 -5
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -16,6 +16,15 @@ const result = await workspace.uploads.put({
|
|
|
16
16
|
```
|
|
17
17
|
|
|
18
18
|
`uploads.start` returns an upload ID before transfer. Keep that ID for
|
|
19
|
+
recovery. Replaying an aborted session returns `409 upload_aborted` after
|
|
20
|
+
authorization and request-identity checks. For a new-file upload, that terminal
|
|
21
|
+
signal permits a new deterministic attempt key **and a new path** for the same
|
|
22
|
+
content (the aborted attempt's node/history remains reserved). Replacements
|
|
23
|
+
still require the file's current revision; the signal does not bypass that CAS.
|
|
24
|
+
Generic `409 conflict`, expired
|
|
25
|
+
but unreconciled sessions, permission failures and lost responses do **not**.
|
|
26
|
+
Completed sessions replay their original file/version even after expiry. Keep
|
|
27
|
+
the original source for
|
|
19
28
|
`uploads.resume(id, originalSource)`. Source can be a Blob, Web ReadableStream or
|
|
20
29
|
Node async iterable. The client retains fixed-size chunks, probes GCS offsets and
|
|
21
30
|
retries transient failures. Supply `size` when known (up to 50 GiB); a stream without
|
|
@@ -26,7 +35,11 @@ to keep it and `resume` later. `put` generates an `idempotencyKey` unless you pa
|
|
|
26
35
|
one and reuses it whenever it resends `start`, which `start` itself does after a
|
|
27
36
|
timeout, a lost reply or a 5xx: the server replays a start that committed, so it
|
|
28
37
|
never leaves an unnamed session holding one of your 20 pending uploads per
|
|
29
|
-
workspace.
|
|
38
|
+
workspace (100 per workspace). `uploads.startBatch(items)` reserves up to 20 of
|
|
39
|
+
those sessions under one lock. `uploads.list()` returns the caller's live pending
|
|
40
|
+
sessions without bearer URLs, and `uploads.reconcile({ abortIncomplete })`
|
|
41
|
+
publishes sessions whose bytes already match or aborts the ones that never arrived.
|
|
42
|
+
A `429 uploads_busy` names how many sessions are live and the cap. Small Blobs can use
|
|
30
43
|
`{ method: 'simple' }`; sequential resumable is the default. Parallel multipart is
|
|
31
44
|
not included. Do not log the session URL: it is a bearer credential.
|
|
32
45
|
|
|
@@ -83,8 +96,8 @@ const file = await storage.workspace(workspaceId).getNode(fileId)
|
|
|
83
96
|
User mode is the default. `{ mode: 'service' }` explicitly selects the manifest's
|
|
84
97
|
attached platform service account, which needs its own storage grants. Declare
|
|
85
98
|
`service_account`, `capabilities.storage` and hosted network egress to your Atlas
|
|
86
|
-
host and `storage.googleapis.com`. Delegation expires after
|
|
87
|
-
fresh invocation when necessary. Signed download leases expire after 60 seconds.
|
|
99
|
+
host and `storage.googleapis.com`. Delegation expires shortly after the function's
|
|
100
|
+
`timeout_seconds` (never sooner than 15 minutes); resume in a fresh invocation when necessary. Signed download leases expire after 60 seconds.
|
|
88
101
|
|
|
89
102
|
For local integration use the normal local Atlas environment, `seq-studio storage`
|
|
90
103
|
and `functions deploy -e local`. File references in ORM remain application-owned;
|
package/dist/client.d.ts
CHANGED
|
@@ -36,6 +36,15 @@ export declare function storageClient(transport: StorageTransport, { permissions
|
|
|
36
36
|
capabilities: () => Promise<{
|
|
37
37
|
canProvision: boolean;
|
|
38
38
|
uploadMethods: string[];
|
|
39
|
+
pendingUploads: {
|
|
40
|
+
perPrincipal: number;
|
|
41
|
+
perWorkspace: number;
|
|
42
|
+
};
|
|
43
|
+
uploadBatchLimit: number;
|
|
44
|
+
importConcurrency: {
|
|
45
|
+
default: number;
|
|
46
|
+
max: number;
|
|
47
|
+
};
|
|
39
48
|
}>;
|
|
40
49
|
list: (options?: StorageListOptions) => Promise<{
|
|
41
50
|
items: {
|
|
@@ -405,6 +414,73 @@ export declare function storageClient(transport: StorageTransport, { permissions
|
|
|
405
414
|
versionId: string | null;
|
|
406
415
|
maxBytes: number;
|
|
407
416
|
}>;
|
|
417
|
+
startBatch: (items: StartStorageUpload[]) => Promise<{
|
|
418
|
+
items: {
|
|
419
|
+
id: string;
|
|
420
|
+
fileId: string;
|
|
421
|
+
uploadUrl: string | null;
|
|
422
|
+
expiresAt: string;
|
|
423
|
+
state: "pending" | "complete" | "aborted";
|
|
424
|
+
versionId: string | null;
|
|
425
|
+
maxBytes: number;
|
|
426
|
+
}[];
|
|
427
|
+
}>;
|
|
428
|
+
list: () => Promise<{
|
|
429
|
+
items: {
|
|
430
|
+
id: string;
|
|
431
|
+
fileId: string;
|
|
432
|
+
name: string;
|
|
433
|
+
state: "pending" | "complete" | "aborted";
|
|
434
|
+
expiresAt: string;
|
|
435
|
+
maxBytes: number;
|
|
436
|
+
expectedSize: number | null;
|
|
437
|
+
versionId: string | null;
|
|
438
|
+
}[];
|
|
439
|
+
limits: {
|
|
440
|
+
perPrincipal: number;
|
|
441
|
+
perWorkspace: number;
|
|
442
|
+
};
|
|
443
|
+
}>;
|
|
444
|
+
reconcile: (input?: {
|
|
445
|
+
abortIncomplete?: boolean;
|
|
446
|
+
}) => Promise<{
|
|
447
|
+
completed: {
|
|
448
|
+
id: string;
|
|
449
|
+
fileId: string;
|
|
450
|
+
name: string;
|
|
451
|
+
state: "pending" | "complete" | "aborted";
|
|
452
|
+
expiresAt: string;
|
|
453
|
+
maxBytes: number;
|
|
454
|
+
expectedSize: number | null;
|
|
455
|
+
versionId: string | null;
|
|
456
|
+
code: string;
|
|
457
|
+
message: string;
|
|
458
|
+
}[];
|
|
459
|
+
aborted: {
|
|
460
|
+
id: string;
|
|
461
|
+
fileId: string;
|
|
462
|
+
name: string;
|
|
463
|
+
state: "pending" | "complete" | "aborted";
|
|
464
|
+
expiresAt: string;
|
|
465
|
+
maxBytes: number;
|
|
466
|
+
expectedSize: number | null;
|
|
467
|
+
versionId: string | null;
|
|
468
|
+
code: string;
|
|
469
|
+
message: string;
|
|
470
|
+
}[];
|
|
471
|
+
pending: {
|
|
472
|
+
id: string;
|
|
473
|
+
fileId: string;
|
|
474
|
+
name: string;
|
|
475
|
+
state: "pending" | "complete" | "aborted";
|
|
476
|
+
expiresAt: string;
|
|
477
|
+
maxBytes: number;
|
|
478
|
+
expectedSize: number | null;
|
|
479
|
+
versionId: string | null;
|
|
480
|
+
code: string;
|
|
481
|
+
message: string;
|
|
482
|
+
}[];
|
|
483
|
+
}>;
|
|
408
484
|
resume(id: string, source: StorageUploadSource, options?: StorageTransferOptions & {
|
|
409
485
|
size?: number;
|
|
410
486
|
}): Promise<{
|
package/dist/client.js
CHANGED
|
@@ -133,6 +133,15 @@ function storageClient(transport, { permissionsRetry = {}, } = {}) {
|
|
|
133
133
|
capabilities: () => replayable('GET', '/capabilities', zod_1.z.object({
|
|
134
134
|
canProvision: zod_1.z.boolean(),
|
|
135
135
|
uploadMethods: zod_1.z.array(zod_1.z.string()),
|
|
136
|
+
pendingUploads: zod_1.z.object({
|
|
137
|
+
perPrincipal: zod_1.z.number().int(),
|
|
138
|
+
perWorkspace: zod_1.z.number().int(),
|
|
139
|
+
}),
|
|
140
|
+
uploadBatchLimit: zod_1.z.number().int(),
|
|
141
|
+
importConcurrency: zod_1.z.object({
|
|
142
|
+
default: zod_1.z.number().int(),
|
|
143
|
+
max: zod_1.z.number().int(),
|
|
144
|
+
}),
|
|
136
145
|
})),
|
|
137
146
|
list: (options = {}) => replayable('GET', `/workspaces${query({ ...options })}`, (0, contracts_js_1.storagePage)(contracts_js_1.workspaceSchema)),
|
|
138
147
|
shared: (options = {}) => replayable('GET', `/shared${query({ ...options })}`, (0, contracts_js_1.storagePage)(contracts_js_1.nodeSchema)),
|
|
@@ -161,6 +170,21 @@ function storageClient(transport, { permissionsRetry = {}, } = {}) {
|
|
|
161
170
|
// Abort is idempotent: an aborted session stays aborted, and a completed
|
|
162
171
|
// one deterministically rejects, so a replay cannot change the outcome.
|
|
163
172
|
abort: (id) => replayable('DELETE', session(id), contracts_js_1.uploadSessionSchema),
|
|
173
|
+
startBatch: (items) => send({
|
|
174
|
+
method: 'POST',
|
|
175
|
+
path: `${root}/uploads/batch`,
|
|
176
|
+
schema: zod_1.z.object({ items: zod_1.z.array(contracts_js_1.uploadSessionSchema) }),
|
|
177
|
+
body: contracts_js_1.startUploadBatchSchema.parse({ items }),
|
|
178
|
+
retryable: (error) => isRetryableConflict(error) || isUnacknowledged(error),
|
|
179
|
+
}),
|
|
180
|
+
list: () => replayable('GET', `${root}/uploads`, contracts_js_1.uploadListSchema),
|
|
181
|
+
reconcile: (input = {}) => send({
|
|
182
|
+
method: 'POST',
|
|
183
|
+
path: `${root}/uploads/reconcile`,
|
|
184
|
+
schema: contracts_js_1.reconcileUploadsResultSchema,
|
|
185
|
+
body: contracts_js_1.reconcileUploadsSchema.parse(input),
|
|
186
|
+
retryable: isRetryableConflict,
|
|
187
|
+
}),
|
|
164
188
|
async resume(id, source, options = {}) {
|
|
165
189
|
const current = await upload.get(id);
|
|
166
190
|
if (current.state === 'complete')
|
package/dist/contracts.d.ts
CHANGED
|
@@ -149,6 +149,17 @@ export declare const updateNodeSchema: z.ZodObject<{
|
|
|
149
149
|
parentId: z.ZodOptional<z.ZodNullable<z.ZodUUID>>;
|
|
150
150
|
metadata: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodString>>;
|
|
151
151
|
}, z.core.$strict>;
|
|
152
|
+
/**
|
|
153
|
+
* Live pending upload sessions. Published on the capabilities and upload-list
|
|
154
|
+
* responses so callers can size a batch instead of discovering the cap from a
|
|
155
|
+
* 429. A batch registers at most one principal's cap in a single request.
|
|
156
|
+
*/
|
|
157
|
+
export declare const STORAGE_PENDING_UPLOADS_PER_PRINCIPAL = 20;
|
|
158
|
+
export declare const STORAGE_PENDING_UPLOADS_PER_WORKSPACE = 100;
|
|
159
|
+
export declare const STORAGE_UPLOAD_BATCH_LIMIT = 20;
|
|
160
|
+
/** Parallel byte transfers `seq-studio storage import` opens. */
|
|
161
|
+
export declare const STORAGE_IMPORT_CONCURRENCY_DEFAULT = 8;
|
|
162
|
+
export declare const STORAGE_IMPORT_CONCURRENCY_MAX = 16;
|
|
152
163
|
/** CRC32C in GCS's encoding: base64 of the big-endian uint32. */
|
|
153
164
|
export declare const storageChecksum: z.ZodString;
|
|
154
165
|
export declare const startUploadSchema: z.ZodObject<{
|
|
@@ -166,6 +177,19 @@ export declare const completeUploadSchema: z.ZodObject<{
|
|
|
166
177
|
crc32c: z.ZodOptional<z.ZodString>;
|
|
167
178
|
}, z.core.$strict>;
|
|
168
179
|
export type CompleteStorageUpload = z.infer<typeof completeUploadSchema>;
|
|
180
|
+
export declare const startUploadBatchSchema: z.ZodObject<{
|
|
181
|
+
items: z.ZodArray<z.ZodObject<{
|
|
182
|
+
parentId: z.ZodDefault<z.ZodNullable<z.ZodUUID>>;
|
|
183
|
+
path: z.ZodPipe<z.ZodString, z.ZodTransform<string, string>>;
|
|
184
|
+
fileId: z.ZodOptional<z.ZodUUID>;
|
|
185
|
+
expectedRevision: z.ZodOptional<z.ZodNumber>;
|
|
186
|
+
size: z.ZodOptional<z.ZodNumber>;
|
|
187
|
+
contentType: z.ZodDefault<z.ZodString>;
|
|
188
|
+
checksum: z.ZodOptional<z.ZodString>;
|
|
189
|
+
idempotencyKey: z.ZodString;
|
|
190
|
+
}, z.core.$strict>>;
|
|
191
|
+
}, z.core.$strict>;
|
|
192
|
+
export type StartStorageUploadBatch = z.infer<typeof startUploadBatchSchema>;
|
|
169
193
|
export declare const uploadSessionSchema: z.ZodObject<{
|
|
170
194
|
id: z.ZodUUID;
|
|
171
195
|
fileId: z.ZodUUID;
|
|
@@ -180,6 +204,100 @@ export declare const uploadSessionSchema: z.ZodObject<{
|
|
|
180
204
|
maxBytes: z.ZodNumber;
|
|
181
205
|
}, z.core.$strip>;
|
|
182
206
|
export type StorageUploadSession = z.infer<typeof uploadSessionSchema>;
|
|
207
|
+
/** A pending session without its bearer upload URL. */
|
|
208
|
+
export declare const uploadListItemSchema: z.ZodObject<{
|
|
209
|
+
id: z.ZodUUID;
|
|
210
|
+
fileId: z.ZodUUID;
|
|
211
|
+
name: z.ZodString;
|
|
212
|
+
state: z.ZodEnum<{
|
|
213
|
+
pending: "pending";
|
|
214
|
+
complete: "complete";
|
|
215
|
+
aborted: "aborted";
|
|
216
|
+
}>;
|
|
217
|
+
expiresAt: z.ZodString;
|
|
218
|
+
maxBytes: z.ZodNumber;
|
|
219
|
+
expectedSize: z.ZodNullable<z.ZodNumber>;
|
|
220
|
+
versionId: z.ZodNullable<z.ZodUUID>;
|
|
221
|
+
}, z.core.$strip>;
|
|
222
|
+
export type StorageUploadListItem = z.infer<typeof uploadListItemSchema>;
|
|
223
|
+
export declare const uploadLimitsSchema: z.ZodObject<{
|
|
224
|
+
perPrincipal: z.ZodNumber;
|
|
225
|
+
perWorkspace: z.ZodNumber;
|
|
226
|
+
}, z.core.$strip>;
|
|
227
|
+
export declare const uploadListSchema: z.ZodObject<{
|
|
228
|
+
items: z.ZodArray<z.ZodObject<{
|
|
229
|
+
id: z.ZodUUID;
|
|
230
|
+
fileId: z.ZodUUID;
|
|
231
|
+
name: z.ZodString;
|
|
232
|
+
state: z.ZodEnum<{
|
|
233
|
+
pending: "pending";
|
|
234
|
+
complete: "complete";
|
|
235
|
+
aborted: "aborted";
|
|
236
|
+
}>;
|
|
237
|
+
expiresAt: z.ZodString;
|
|
238
|
+
maxBytes: z.ZodNumber;
|
|
239
|
+
expectedSize: z.ZodNullable<z.ZodNumber>;
|
|
240
|
+
versionId: z.ZodNullable<z.ZodUUID>;
|
|
241
|
+
}, z.core.$strip>>;
|
|
242
|
+
limits: z.ZodObject<{
|
|
243
|
+
perPrincipal: z.ZodNumber;
|
|
244
|
+
perWorkspace: z.ZodNumber;
|
|
245
|
+
}, z.core.$strip>;
|
|
246
|
+
}, z.core.$strip>;
|
|
247
|
+
export declare const reconcileUploadsSchema: z.ZodObject<{
|
|
248
|
+
abortIncomplete: z.ZodDefault<z.ZodBoolean>;
|
|
249
|
+
}, z.core.$strict>;
|
|
250
|
+
export declare const reconcileUploadsResultSchema: z.ZodObject<{
|
|
251
|
+
completed: z.ZodArray<z.ZodObject<{
|
|
252
|
+
id: z.ZodUUID;
|
|
253
|
+
fileId: z.ZodUUID;
|
|
254
|
+
name: z.ZodString;
|
|
255
|
+
state: z.ZodEnum<{
|
|
256
|
+
pending: "pending";
|
|
257
|
+
complete: "complete";
|
|
258
|
+
aborted: "aborted";
|
|
259
|
+
}>;
|
|
260
|
+
expiresAt: z.ZodString;
|
|
261
|
+
maxBytes: z.ZodNumber;
|
|
262
|
+
expectedSize: z.ZodNullable<z.ZodNumber>;
|
|
263
|
+
versionId: z.ZodNullable<z.ZodUUID>;
|
|
264
|
+
code: z.ZodString;
|
|
265
|
+
message: z.ZodString;
|
|
266
|
+
}, z.core.$strip>>;
|
|
267
|
+
aborted: z.ZodArray<z.ZodObject<{
|
|
268
|
+
id: z.ZodUUID;
|
|
269
|
+
fileId: z.ZodUUID;
|
|
270
|
+
name: z.ZodString;
|
|
271
|
+
state: z.ZodEnum<{
|
|
272
|
+
pending: "pending";
|
|
273
|
+
complete: "complete";
|
|
274
|
+
aborted: "aborted";
|
|
275
|
+
}>;
|
|
276
|
+
expiresAt: z.ZodString;
|
|
277
|
+
maxBytes: z.ZodNumber;
|
|
278
|
+
expectedSize: z.ZodNullable<z.ZodNumber>;
|
|
279
|
+
versionId: z.ZodNullable<z.ZodUUID>;
|
|
280
|
+
code: z.ZodString;
|
|
281
|
+
message: z.ZodString;
|
|
282
|
+
}, z.core.$strip>>;
|
|
283
|
+
pending: z.ZodArray<z.ZodObject<{
|
|
284
|
+
id: z.ZodUUID;
|
|
285
|
+
fileId: z.ZodUUID;
|
|
286
|
+
name: z.ZodString;
|
|
287
|
+
state: z.ZodEnum<{
|
|
288
|
+
pending: "pending";
|
|
289
|
+
complete: "complete";
|
|
290
|
+
aborted: "aborted";
|
|
291
|
+
}>;
|
|
292
|
+
expiresAt: z.ZodString;
|
|
293
|
+
maxBytes: z.ZodNumber;
|
|
294
|
+
expectedSize: z.ZodNullable<z.ZodNumber>;
|
|
295
|
+
versionId: z.ZodNullable<z.ZodUUID>;
|
|
296
|
+
code: z.ZodString;
|
|
297
|
+
message: z.ZodString;
|
|
298
|
+
}, z.core.$strip>>;
|
|
299
|
+
}, z.core.$strip>;
|
|
300
|
+
export type ReconcileStorageUploads = z.infer<typeof reconcileUploadsSchema>;
|
|
183
301
|
export declare const storagePrincipalSchema: z.ZodObject<{
|
|
184
302
|
type: z.ZodEnum<{
|
|
185
303
|
user: "user";
|
package/dist/contracts.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.storageErrorSchema = exports.storageAccessSchema = exports.updateStorageAccessSchema = exports.storagePrincipalSchema = exports.uploadSessionSchema = exports.completeUploadSchema = exports.startUploadSchema = exports.storageChecksum = exports.updateNodeSchema = exports.createFolderSchema = exports.listStorageSchema = exports.storagePage = exports.listStorageOperationsSchema = exports.storageOperationScope = exports.operationSchema = exports.versionSchema = exports.nodeSchema = exports.workspaceSchema = exports.workspaceManifestSchema = exports.storageMetadata = exports.storagePath = exports.storageName = exports.storageLifecycle = exports.storageRole = exports.storageId = void 0;
|
|
3
|
+
exports.storageErrorSchema = exports.storageAccessSchema = exports.updateStorageAccessSchema = exports.storagePrincipalSchema = exports.reconcileUploadsResultSchema = exports.reconcileUploadsSchema = exports.uploadListSchema = exports.uploadLimitsSchema = exports.uploadListItemSchema = exports.uploadSessionSchema = exports.startUploadBatchSchema = exports.completeUploadSchema = exports.startUploadSchema = exports.storageChecksum = exports.STORAGE_IMPORT_CONCURRENCY_MAX = exports.STORAGE_IMPORT_CONCURRENCY_DEFAULT = exports.STORAGE_UPLOAD_BATCH_LIMIT = exports.STORAGE_PENDING_UPLOADS_PER_WORKSPACE = exports.STORAGE_PENDING_UPLOADS_PER_PRINCIPAL = exports.updateNodeSchema = exports.createFolderSchema = exports.listStorageSchema = exports.storagePage = exports.listStorageOperationsSchema = exports.storageOperationScope = exports.operationSchema = exports.versionSchema = exports.nodeSchema = exports.workspaceSchema = exports.workspaceManifestSchema = exports.storageMetadata = exports.storagePath = exports.storageName = exports.storageLifecycle = exports.storageRole = exports.storageId = void 0;
|
|
4
4
|
const zod_1 = require("zod");
|
|
5
5
|
const paths_js_1 = require("./paths.js");
|
|
6
6
|
exports.storageId = zod_1.z.uuid();
|
|
@@ -124,6 +124,17 @@ exports.updateNodeSchema = zod_1.z.strictObject({
|
|
|
124
124
|
parentId: exports.storageId.nullable().optional(),
|
|
125
125
|
metadata: exports.storageMetadata.optional(),
|
|
126
126
|
});
|
|
127
|
+
/**
|
|
128
|
+
* Live pending upload sessions. Published on the capabilities and upload-list
|
|
129
|
+
* responses so callers can size a batch instead of discovering the cap from a
|
|
130
|
+
* 429. A batch registers at most one principal's cap in a single request.
|
|
131
|
+
*/
|
|
132
|
+
exports.STORAGE_PENDING_UPLOADS_PER_PRINCIPAL = 20;
|
|
133
|
+
exports.STORAGE_PENDING_UPLOADS_PER_WORKSPACE = 100;
|
|
134
|
+
exports.STORAGE_UPLOAD_BATCH_LIMIT = exports.STORAGE_PENDING_UPLOADS_PER_PRINCIPAL;
|
|
135
|
+
/** Parallel byte transfers `seq-studio storage import` opens. */
|
|
136
|
+
exports.STORAGE_IMPORT_CONCURRENCY_DEFAULT = 8;
|
|
137
|
+
exports.STORAGE_IMPORT_CONCURRENCY_MAX = 16;
|
|
127
138
|
/** CRC32C in GCS's encoding: base64 of the big-endian uint32. */
|
|
128
139
|
exports.storageChecksum = zod_1.z.string().regex(/^[A-Za-z0-9+/]{6}==$/);
|
|
129
140
|
exports.startUploadSchema = zod_1.z
|
|
@@ -153,6 +164,9 @@ exports.completeUploadSchema = zod_1.z.strictObject({
|
|
|
153
164
|
*/
|
|
154
165
|
crc32c: exports.storageChecksum.optional(),
|
|
155
166
|
});
|
|
167
|
+
exports.startUploadBatchSchema = zod_1.z.strictObject({
|
|
168
|
+
items: zod_1.z.array(exports.startUploadSchema).min(1).max(exports.STORAGE_UPLOAD_BATCH_LIMIT),
|
|
169
|
+
});
|
|
156
170
|
exports.uploadSessionSchema = zod_1.z.object({
|
|
157
171
|
id: exports.storageId,
|
|
158
172
|
fileId: exports.storageId,
|
|
@@ -162,6 +176,38 @@ exports.uploadSessionSchema = zod_1.z.object({
|
|
|
162
176
|
versionId: exports.storageId.nullable(),
|
|
163
177
|
maxBytes: zod_1.z.number().int(),
|
|
164
178
|
});
|
|
179
|
+
/** A pending session without its bearer upload URL. */
|
|
180
|
+
exports.uploadListItemSchema = zod_1.z.object({
|
|
181
|
+
id: exports.storageId,
|
|
182
|
+
fileId: exports.storageId,
|
|
183
|
+
name: zod_1.z.string(),
|
|
184
|
+
state: zod_1.z.enum(['pending', 'complete', 'aborted']),
|
|
185
|
+
expiresAt: zod_1.z.string(),
|
|
186
|
+
maxBytes: zod_1.z.number().int(),
|
|
187
|
+
expectedSize: zod_1.z.number().int().nullable(),
|
|
188
|
+
versionId: exports.storageId.nullable(),
|
|
189
|
+
});
|
|
190
|
+
exports.uploadLimitsSchema = zod_1.z.object({
|
|
191
|
+
perPrincipal: zod_1.z.number().int(),
|
|
192
|
+
perWorkspace: zod_1.z.number().int(),
|
|
193
|
+
});
|
|
194
|
+
exports.uploadListSchema = zod_1.z.object({
|
|
195
|
+
items: zod_1.z.array(exports.uploadListItemSchema),
|
|
196
|
+
limits: exports.uploadLimitsSchema,
|
|
197
|
+
});
|
|
198
|
+
exports.reconcileUploadsSchema = zod_1.z.strictObject({
|
|
199
|
+
/** Abort sessions whose bytes never arrived. Terminal mismatches are already released. */
|
|
200
|
+
abortIncomplete: zod_1.z.boolean().default(false),
|
|
201
|
+
});
|
|
202
|
+
const reconcileEntrySchema = exports.uploadListItemSchema.extend({
|
|
203
|
+
code: zod_1.z.string(),
|
|
204
|
+
message: zod_1.z.string(),
|
|
205
|
+
});
|
|
206
|
+
exports.reconcileUploadsResultSchema = zod_1.z.object({
|
|
207
|
+
completed: zod_1.z.array(reconcileEntrySchema),
|
|
208
|
+
aborted: zod_1.z.array(reconcileEntrySchema),
|
|
209
|
+
pending: zod_1.z.array(reconcileEntrySchema),
|
|
210
|
+
});
|
|
165
211
|
exports.storagePrincipalSchema = zod_1.z.strictObject({
|
|
166
212
|
type: zod_1.z.enum(['user', 'group', 'organization', 'service_account']),
|
|
167
213
|
id: zod_1.z.string().min(1).max(255),
|
package/dist/transfer.js
CHANGED
|
@@ -128,9 +128,8 @@ async function transferStorageUpload({ uploadUrl, source, size, maxBytes, ...opt
|
|
|
128
128
|
const url = checkedUploadUrl(uploadUrl);
|
|
129
129
|
const fetcher = options.fetch ?? globalThis.fetch;
|
|
130
130
|
const total = source instanceof Blob ? source.size : size;
|
|
131
|
-
if (total !== undefined &&
|
|
132
|
-
(
|
|
133
|
-
throw new StorageTransferError('Upload exceeds its reserved size');
|
|
131
|
+
if (total !== undefined && (!Number.isSafeInteger(total) || total < 0 || total > maxBytes))
|
|
132
|
+
throw new StorageTransferError(`Upload source is ${total} bytes, above the ${maxBytes}-byte reservation`);
|
|
134
133
|
let chunkBytes = options.chunkBytes ?? 8 * 1024 * 1024;
|
|
135
134
|
if (chunkBytes < 262144 ||
|
|
136
135
|
chunkBytes > 32 * 1024 * 1024 ||
|
|
@@ -181,7 +180,7 @@ async function transferStorageUpload({ uploadUrl, source, size, maxBytes, ...opt
|
|
|
181
180
|
}
|
|
182
181
|
let offset = initial.offset;
|
|
183
182
|
if (offset > maxBytes || (total !== undefined && offset > total))
|
|
184
|
-
throw new StorageTransferError(
|
|
183
|
+
throw new StorageTransferError(`Upload session offset is ${offset} bytes, past the source${total === undefined ? '' : ` of ${total} bytes`} and the ${maxBytes}-byte reservation`);
|
|
185
184
|
options.onProgress?.(offset);
|
|
186
185
|
const hash = new crc32c_js_1.Crc32c();
|
|
187
186
|
const iterable = chunks(source, chunkBytes, offset, hash, options.signal);
|
|
@@ -197,7 +196,7 @@ async function transferStorageUpload({ uploadUrl, source, size, maxBytes, ...opt
|
|
|
197
196
|
throw new StorageTransferError(`Upload stream exceeds its ${maxBytes}-byte reservation; declare \`size\` when starting the upload to send a larger file`);
|
|
198
197
|
if (end > maxBytes ||
|
|
199
198
|
(total !== undefined && (end > total || (last && end !== total))))
|
|
200
|
-
throw new StorageTransferError(
|
|
199
|
+
throw new StorageTransferError(`Upload source does not match its reserved size: session offset ${offset} bytes, read ${end} bytes, reservation ${maxBytes} bytes${total === undefined ? '' : `, declared ${total} bytes`}`);
|
|
201
200
|
const length = total ?? (last ? end : '*');
|
|
202
201
|
let acknowledged = offset;
|
|
203
202
|
for (let attempt = 0;; attempt++) {
|