@finueva/drive 0.2.0 → 0.3.0
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/CHANGELOG.md +6 -0
- package/README.md +40 -8
- package/THIRD_PARTY_NOTICES.md +26 -0
- package/dist/client.d.ts +2 -2
- package/dist/client.js +1071 -22
- package/dist/contract.d.ts +792 -65
- package/dist/core.d.ts +2 -1
- package/dist/core.js +2 -0
- package/dist/index.d.ts +2 -2
- package/dist/index.js +1071 -22
- package/dist/server.d.ts +3 -3
- package/dist/server.js +329 -24
- package/dist/types.d.ts +2 -2
- package/package.json +6 -2
package/dist/server.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { A as
|
|
1
|
+
import { A as ProtectionClassification, C as ListFolderChildrenInput, H as UploadPartResult, I as UploadConfirmedPart, M as ReadyUpload, N as RecordUploadPartInput, S as GetUploadStatusInput, U as UploadSession, V as UploadPartDescriptor, W as UploadStatus, a as CreateUploadInput, b as FolderPage, g as DriveServerClientConfig, h as DriveServerClient, n as CompleteUploadInput, o as CreateUploadPartDescriptorInput, s as CurrentWorkspace, t as AbortUploadInput, x as GetCurrentWorkspaceInput, y as FolderItem } from "./contract.js";
|
|
2
2
|
//#region src/server.d.ts
|
|
3
|
-
declare function createDriveServerClient(config: DriveServerClientConfig):
|
|
3
|
+
declare function createDriveServerClient(config: DriveServerClientConfig): DriveServerClient;
|
|
4
4
|
//#endregion
|
|
5
|
-
export { type AbortUploadInput, type
|
|
5
|
+
export { type AbortUploadInput, type CompleteUploadInput, type CreateUploadInput, type CreateUploadPartDescriptorInput, type CurrentWorkspace, type DriveServerClient, type DriveServerClientConfig, type FolderItem, type FolderPage, type GetCurrentWorkspaceInput, type GetUploadStatusInput, type ListFolderChildrenInput, type ProtectionClassification, type ReadyUpload, type RecordUploadPartInput, type UploadConfirmedPart, type UploadPartDescriptor, type UploadPartResult, type UploadSession, type UploadStatus, createDriveServerClient };
|
package/dist/server.js
CHANGED
|
@@ -10,6 +10,7 @@ const DRIVE_API_ERROR_CODES = Object.freeze([
|
|
|
10
10
|
"precondition_failed",
|
|
11
11
|
"idempotency_conflict",
|
|
12
12
|
"quota_exceeded",
|
|
13
|
+
"upload_recovery_required",
|
|
13
14
|
"service_unavailable",
|
|
14
15
|
"internal_error"
|
|
15
16
|
]);
|
|
@@ -24,6 +25,7 @@ const DRIVE_API_ERROR_MESSAGES = Object.freeze({
|
|
|
24
25
|
precondition_failed: "The request precondition failed.",
|
|
25
26
|
idempotency_conflict: "The idempotency key conflicts with an earlier request.",
|
|
26
27
|
quota_exceeded: "The workspace storage quota would be exceeded.",
|
|
28
|
+
upload_recovery_required: "The upload requires recovery before it can continue.",
|
|
27
29
|
service_unavailable: "The service is temporarily unavailable.",
|
|
28
30
|
internal_error: "The request could not be completed."
|
|
29
31
|
});
|
|
@@ -79,6 +81,28 @@ async function parseResponse(response, operationSignal, preconditionStatus, succ
|
|
|
79
81
|
validators: readValidators(response.headers)
|
|
80
82
|
});
|
|
81
83
|
}
|
|
84
|
+
async function parseNoContentResponse(response, operationSignal, preconditionStatus, successStatuses, allowedErrorCodes) {
|
|
85
|
+
const requestId = safeRequestId(response.headers.get("x-request-id"));
|
|
86
|
+
if (requestId === null) {
|
|
87
|
+
cancelResponse(response);
|
|
88
|
+
throw new DriveError("invalid_response", { status: response.status });
|
|
89
|
+
}
|
|
90
|
+
if (!successStatuses.includes(response.status)) throw await normalizeError(response, requestId, operationSignal, preconditionStatus, false, allowedErrorCodes);
|
|
91
|
+
const contentLength = response.headers.get("content-length");
|
|
92
|
+
if (response.body !== null || contentLength !== null && contentLength !== "0" || response.headers.has("content-type")) {
|
|
93
|
+
cancelResponse(response);
|
|
94
|
+
throw new DriveError("invalid_response", {
|
|
95
|
+
status: response.status,
|
|
96
|
+
requestId
|
|
97
|
+
});
|
|
98
|
+
}
|
|
99
|
+
return Object.freeze({
|
|
100
|
+
data: void 0,
|
|
101
|
+
status: response.status,
|
|
102
|
+
requestId,
|
|
103
|
+
validators: Object.freeze({})
|
|
104
|
+
});
|
|
105
|
+
}
|
|
82
106
|
function parseCollectionEtag(value) {
|
|
83
107
|
const match = COLLECTION_ETAG.exec(value);
|
|
84
108
|
if (!match) return void 0;
|
|
@@ -137,6 +161,7 @@ function errorStatusMatches(code, status, preconditionStatus, allowsQuotaExceede
|
|
|
137
161
|
precondition_failed: preconditionStatus === null ? [] : [preconditionStatus],
|
|
138
162
|
idempotency_conflict: [409],
|
|
139
163
|
quota_exceeded: [409],
|
|
164
|
+
upload_recovery_required: [409],
|
|
140
165
|
service_unavailable: [503],
|
|
141
166
|
internal_error: [500]
|
|
142
167
|
}[code].includes(status);
|
|
@@ -247,6 +272,7 @@ const WORKSPACE_ID$1 = /^drive\.workspace\.[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-
|
|
|
247
272
|
const VOLUME_ID = /^drive\.volume\.[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/;
|
|
248
273
|
const GRANT_ID$1 = /^drive\.grant\.[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/;
|
|
249
274
|
const UPLOAD_ID$1 = /^drive\.upload\.[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/;
|
|
275
|
+
const VERSION_ID = /^drive\.version\.[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/;
|
|
250
276
|
const CURSOR$1 = /^drive\.cursor\.v1\.[A-Za-z0-9_-]+$/;
|
|
251
277
|
const AUTH_ID$1 = /^[A-Za-z0-9_-]{1,128}$/;
|
|
252
278
|
const GRANT_ETAG = /^"drive\.permission-grant\.v1\.(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)"$/;
|
|
@@ -269,7 +295,8 @@ const EFFECTIVE_ACTIONS = Object.freeze([
|
|
|
269
295
|
"items:update"
|
|
270
296
|
]);
|
|
271
297
|
const MAX_UPLOAD_BYTE_LENGTH$1 = 33554432e4;
|
|
272
|
-
const
|
|
298
|
+
const UPLOAD_PART_BYTE_LENGTH$1 = 32 * 1024 * 1024;
|
|
299
|
+
const CONTENT_DIGEST$1 = /^sha256:[0-9a-f]{64}$/;
|
|
273
300
|
function isCurrentWorkspace(value) {
|
|
274
301
|
if (!isExactRecord(value, [
|
|
275
302
|
"workspaceId",
|
|
@@ -350,18 +377,93 @@ function isEffectivePermissionResponse(value, targetItemId) {
|
|
|
350
377
|
}
|
|
351
378
|
return true;
|
|
352
379
|
}
|
|
353
|
-
function
|
|
354
|
-
|
|
355
|
-
"
|
|
380
|
+
function isUploadSession(value, expectedByteLength) {
|
|
381
|
+
if (!isExactRecord(value, [
|
|
382
|
+
"itemId",
|
|
383
|
+
"versionId",
|
|
356
384
|
"uploadId",
|
|
357
|
-
"
|
|
358
|
-
"
|
|
359
|
-
"
|
|
360
|
-
"
|
|
361
|
-
"
|
|
385
|
+
"mode",
|
|
386
|
+
"partByteLength",
|
|
387
|
+
"partCount",
|
|
388
|
+
"revision",
|
|
389
|
+
"expiresAtEpochMilliseconds"
|
|
390
|
+
]) || typeof value.itemId !== "string" || !ITEM_ID$1.test(value.itemId) || typeof value.versionId !== "string" || !VERSION_ID.test(value.versionId) || typeof value.uploadId !== "string" || !UPLOAD_ID$1.test(value.uploadId) || value.mode !== "presigned-multipart" && value.mode !== "server-mediated-zero-byte" || value.partByteLength !== UPLOAD_PART_BYTE_LENGTH$1 || !isSafeInteger$1(value.partCount) || value.partCount > 1e4 || value.partCount !== Math.ceil(expectedByteLength / UPLOAD_PART_BYTE_LENGTH$1) || (expectedByteLength === 0 ? value.mode !== "server-mediated-zero-byte" : value.mode !== "presigned-multipart") || !isSafeInteger$1(value.revision) || !isSafeInteger$1(value.expiresAtEpochMilliseconds) || value.expiresAtEpochMilliseconds <= Date.now()) return false;
|
|
391
|
+
Object.freeze(value);
|
|
392
|
+
return true;
|
|
393
|
+
}
|
|
394
|
+
function isUploadStatus(value, afterPartNumber, limit) {
|
|
395
|
+
if (!isExactRecord(value, [
|
|
396
|
+
"itemId",
|
|
397
|
+
"versionId",
|
|
398
|
+
"uploadId",
|
|
399
|
+
"mode",
|
|
400
|
+
"lifecycle",
|
|
401
|
+
"revision",
|
|
402
|
+
"partPolicy",
|
|
403
|
+
"confirmedParts",
|
|
404
|
+
"nextAfterPartNumber",
|
|
362
405
|
"expiresAtEpochMilliseconds",
|
|
363
|
-
"
|
|
364
|
-
])
|
|
406
|
+
"ready"
|
|
407
|
+
]) || typeof value.itemId !== "string" || !ITEM_ID$1.test(value.itemId) || typeof value.versionId !== "string" || !VERSION_ID.test(value.versionId) || typeof value.uploadId !== "string" || !UPLOAD_ID$1.test(value.uploadId) || value.mode !== "presigned-multipart" && value.mode !== "server-mediated-zero-byte" || ![
|
|
408
|
+
"initiating",
|
|
409
|
+
"recovery-required",
|
|
410
|
+
"uploading",
|
|
411
|
+
"completing",
|
|
412
|
+
"provider-completed",
|
|
413
|
+
"ready",
|
|
414
|
+
"aborting",
|
|
415
|
+
"aborted"
|
|
416
|
+
].includes(String(value.lifecycle)) || !isSafeInteger$1(value.revision) || !isExactRecord(value.partPolicy, [
|
|
417
|
+
"partByteLength",
|
|
418
|
+
"partCount",
|
|
419
|
+
"maxParts"
|
|
420
|
+
]) || value.partPolicy.partByteLength !== UPLOAD_PART_BYTE_LENGTH$1 || !isSafeInteger$1(value.partPolicy.partCount) || value.partPolicy.partCount > 1e4 || value.partPolicy.maxParts !== 1e4 || (value.mode === "server-mediated-zero-byte" ? value.partPolicy.partCount !== 0 : value.partPolicy.partCount < 1) || !Array.isArray(value.confirmedParts) || value.confirmedParts.length > limit || !isSafeInteger$1(value.expiresAtEpochMilliseconds)) return false;
|
|
421
|
+
let previousPartNumber = afterPartNumber;
|
|
422
|
+
for (const part of value.confirmedParts) {
|
|
423
|
+
if (!isExactRecord(part, ["partNumber", "checksumSha256"]) || !isSafeInteger$1(part.partNumber) || part.partNumber <= previousPartNumber || part.partNumber > value.partPolicy.partCount || !isPartChecksum$1(part.checksumSha256)) return false;
|
|
424
|
+
previousPartNumber = part.partNumber;
|
|
425
|
+
}
|
|
426
|
+
if (!(value.nextAfterPartNumber === null || isSafeInteger$1(value.nextAfterPartNumber) && value.confirmedParts.length === limit && value.nextAfterPartNumber === previousPartNumber) || value.mode === "server-mediated-zero-byte" && (value.confirmedParts.length !== 0 || value.nextAfterPartNumber !== null) || value.lifecycle === "ready" !== (value.ready !== null) || value.ready !== null && (!isReadyUpload(value.ready, { uploadId: value.uploadId }) || value.ready.itemId !== value.itemId || value.ready.versionId !== value.versionId)) return false;
|
|
427
|
+
Object.freeze(value.partPolicy);
|
|
428
|
+
for (const part of value.confirmedParts) Object.freeze(part);
|
|
429
|
+
Object.freeze(value.confirmedParts);
|
|
430
|
+
Object.freeze(value);
|
|
431
|
+
return true;
|
|
432
|
+
}
|
|
433
|
+
function isUploadPartDescriptor(value, expectedPartNumber, checksumSha256, allowInsecureHttp) {
|
|
434
|
+
if (!isExactRecord(value, [
|
|
435
|
+
"url",
|
|
436
|
+
"method",
|
|
437
|
+
"headers",
|
|
438
|
+
"partNumber",
|
|
439
|
+
"expectedByteLength",
|
|
440
|
+
"expiresAtEpochMilliseconds"
|
|
441
|
+
]) || !isSignedTransferUrl(value.url, allowInsecureHttp) || value.method !== "PUT" || !isExactRecord(value.headers, ["x-amz-checksum-sha256"]) || value.headers["x-amz-checksum-sha256"] !== checksumSha256 || value.partNumber !== expectedPartNumber || !isSafeInteger$1(value.expectedByteLength) || value.expectedByteLength < 1 || value.expectedByteLength > UPLOAD_PART_BYTE_LENGTH$1 || !isSafeInteger$1(value.expiresAtEpochMilliseconds) || value.expiresAtEpochMilliseconds <= Date.now()) return false;
|
|
442
|
+
Object.freeze(value.headers);
|
|
443
|
+
Object.freeze(value);
|
|
444
|
+
return true;
|
|
445
|
+
}
|
|
446
|
+
function isUploadPartResult(value, expectedPartNumber, expectedUploadRevision) {
|
|
447
|
+
if (!isExactRecord(value, ["partNumber", "revision"]) || value.partNumber !== expectedPartNumber || value.revision !== expectedUploadRevision + 1 || !isSafeInteger$1(value.revision)) return false;
|
|
448
|
+
Object.freeze(value);
|
|
449
|
+
return true;
|
|
450
|
+
}
|
|
451
|
+
function isReadyUpload(value, expected) {
|
|
452
|
+
if (!isExactRecord(value, [
|
|
453
|
+
"itemId",
|
|
454
|
+
"versionId",
|
|
455
|
+
"uploadId",
|
|
456
|
+
"byteLength",
|
|
457
|
+
"callerDeclaredContentDigest",
|
|
458
|
+
"displayFilename",
|
|
459
|
+
"mediaType",
|
|
460
|
+
"state"
|
|
461
|
+
]) || typeof value.itemId !== "string" || !ITEM_ID$1.test(value.itemId) || typeof value.versionId !== "string" || !VERSION_ID.test(value.versionId) || value.uploadId !== expected.uploadId || !isSafeInteger$1(value.byteLength) || value.byteLength < 0 || value.byteLength > MAX_UPLOAD_BYTE_LENGTH$1 || typeof value.callerDeclaredContentDigest !== "string" || !CONTENT_DIGEST$1.test(value.callerDeclaredContentDigest) || !isSafeFilename$1(value.displayFilename) || !isMediaType$1(value.mediaType) || value.state !== "ready" || expected.byteLength !== void 0 && value.byteLength !== expected.byteLength || expected.callerDeclaredContentDigest !== void 0 && value.callerDeclaredContentDigest !== expected.callerDeclaredContentDigest || expected.displayFilename !== void 0 && value.displayFilename !== expected.displayFilename || expected.mediaType !== void 0 && value.mediaType !== expected.mediaType) return false;
|
|
462
|
+
Object.freeze(value);
|
|
463
|
+
return true;
|
|
464
|
+
}
|
|
465
|
+
function isPartChecksum$1(value) {
|
|
466
|
+
return typeof value === "string" && /^[A-Za-z0-9+/]{42}[AEIMQUYcgkosw048]=$/.test(value);
|
|
365
467
|
}
|
|
366
468
|
function mutationValidatorsAreCoherent(headers) {
|
|
367
469
|
const collection = headers.get("drive-permission-collection-etag");
|
|
@@ -418,6 +520,19 @@ function isSafeFilename$1(value) {
|
|
|
418
520
|
}
|
|
419
521
|
return true;
|
|
420
522
|
}
|
|
523
|
+
function isMediaType$1(value) {
|
|
524
|
+
return typeof value === "string" && value.length <= 255 && /^[a-z0-9](?:[a-z0-9!#$&^_.+-]{0,125}[a-z0-9])?\/[a-z0-9](?:[a-z0-9!#$&^_.+-]{0,125}[a-z0-9])?$/.test(value);
|
|
525
|
+
}
|
|
526
|
+
function isSignedTransferUrl(value, allowInsecureHttp) {
|
|
527
|
+
if (typeof value !== "string" || value.length < 1 || value.length > 8192 || value !== value.trim()) return false;
|
|
528
|
+
try {
|
|
529
|
+
const url = new URL(value);
|
|
530
|
+
const loopbackHost = url.hostname === "localhost" || url.hostname === "127.0.0.1" || url.hostname === "[::1]";
|
|
531
|
+
return (url.protocol === "https:" || url.protocol === "http:" && allowInsecureHttp && loopbackHost) && url.username === "" && url.password === "" && url.hash === "" && url.host !== "";
|
|
532
|
+
} catch {
|
|
533
|
+
return false;
|
|
534
|
+
}
|
|
535
|
+
}
|
|
421
536
|
function filenameIsReserved$1(value) {
|
|
422
537
|
const base = value.split(".", 1)[0].toUpperCase();
|
|
423
538
|
return value === "." || value === ".." || [
|
|
@@ -457,7 +572,10 @@ const PERMISSION_ACTIONS = Object.freeze([
|
|
|
457
572
|
"items:update"
|
|
458
573
|
]);
|
|
459
574
|
const IDEMPOTENCY_KEY = /^[A-Za-z0-9_-]{16,128}$/;
|
|
575
|
+
const CONTENT_DIGEST = /^sha256:[0-9a-f]{64}$/;
|
|
576
|
+
const PART_CHECKSUM = /^[A-Za-z0-9+/]{42}[AEIMQUYcgkosw048]=$/;
|
|
460
577
|
const MAX_UPLOAD_BYTE_LENGTH = 33554432e4;
|
|
578
|
+
const UPLOAD_PART_BYTE_LENGTH = 32 * 1024 * 1024;
|
|
461
579
|
function validateCurrentWorkspaceInput(value) {
|
|
462
580
|
const signal = validateSignal(captureRecord(value === void 0 ? {} : value, [], ["signal"]).signal);
|
|
463
581
|
return signal === void 0 ? {} : { signal };
|
|
@@ -596,28 +714,93 @@ function validateCreateUploadInput(value) {
|
|
|
596
714
|
"workspaceId",
|
|
597
715
|
"parentItemId",
|
|
598
716
|
"expectedByteLength",
|
|
717
|
+
"displayFilename",
|
|
718
|
+
"mediaType",
|
|
719
|
+
"callerDeclaredContentDigest",
|
|
720
|
+
"protectionClassification",
|
|
599
721
|
"idempotencyKey"
|
|
600
722
|
], ["signal"]);
|
|
601
723
|
const signal = validateSignal(input.signal);
|
|
602
|
-
if (typeof input.workspaceId !== "string" || !WORKSPACE_ID.test(input.workspaceId) || typeof input.parentItemId !== "string" || !ITEM_ID.test(input.parentItemId) || typeof input.expectedByteLength !== "number" || !Number.isSafeInteger(input.expectedByteLength) || input.expectedByteLength < 0 || input.expectedByteLength > MAX_UPLOAD_BYTE_LENGTH || !isIdempotencyKey(input.idempotencyKey)) throw new DriveError("invalid_input");
|
|
724
|
+
if (typeof input.workspaceId !== "string" || !WORKSPACE_ID.test(input.workspaceId) || typeof input.parentItemId !== "string" || !ITEM_ID.test(input.parentItemId) || typeof input.expectedByteLength !== "number" || !Number.isSafeInteger(input.expectedByteLength) || input.expectedByteLength < 0 || input.expectedByteLength > MAX_UPLOAD_BYTE_LENGTH || !isSafeFilename(input.displayFilename) || !isMediaType(input.mediaType) || typeof input.callerDeclaredContentDigest !== "string" || !CONTENT_DIGEST.test(input.callerDeclaredContentDigest) || input.protectionClassification !== "ordinary" && input.protectionClassification !== "opaque-encrypted" || !isIdempotencyKey(input.idempotencyKey)) throw new DriveError("invalid_input");
|
|
603
725
|
return {
|
|
604
726
|
workspaceId: input.workspaceId,
|
|
605
727
|
parentItemId: input.parentItemId,
|
|
606
728
|
expectedByteLength: input.expectedByteLength,
|
|
729
|
+
displayFilename: input.displayFilename,
|
|
730
|
+
mediaType: input.mediaType,
|
|
731
|
+
callerDeclaredContentDigest: input.callerDeclaredContentDigest,
|
|
732
|
+
protectionClassification: input.protectionClassification,
|
|
607
733
|
idempotencyKey: input.idempotencyKey,
|
|
608
734
|
...signal === void 0 ? {} : { signal }
|
|
609
735
|
};
|
|
610
736
|
}
|
|
611
|
-
function
|
|
612
|
-
const input = captureRecord(value, ["workspaceId", "uploadId"], [
|
|
613
|
-
|
|
614
|
-
|
|
737
|
+
function validateGetUploadStatusInput(value) {
|
|
738
|
+
const input = captureRecord(value, ["workspaceId", "uploadId"], [
|
|
739
|
+
"afterPartNumber",
|
|
740
|
+
"limit",
|
|
741
|
+
"signal"
|
|
742
|
+
]);
|
|
743
|
+
const upload = validateUploadPath(input.workspaceId, input.uploadId, input.signal);
|
|
744
|
+
if (input.afterPartNumber !== void 0 && (!isSafeInteger(input.afterPartNumber) || input.afterPartNumber > 1e4) || input.limit !== void 0 && (!isSafeInteger(input.limit) || input.limit < 1 || input.limit > 100)) throw new DriveError("invalid_input");
|
|
615
745
|
return {
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
...
|
|
746
|
+
...upload,
|
|
747
|
+
...input.afterPartNumber === void 0 ? {} : { afterPartNumber: input.afterPartNumber },
|
|
748
|
+
...input.limit === void 0 ? {} : { limit: input.limit }
|
|
749
|
+
};
|
|
750
|
+
}
|
|
751
|
+
function validateCreateUploadPartDescriptorInput(value) {
|
|
752
|
+
const input = captureRecord(value, [
|
|
753
|
+
"workspaceId",
|
|
754
|
+
"uploadId",
|
|
755
|
+
"partNumber",
|
|
756
|
+
"checksumSha256"
|
|
757
|
+
], ["signal"]);
|
|
758
|
+
const upload = validateUploadPath(input.workspaceId, input.uploadId, input.signal);
|
|
759
|
+
if (!isPartNumber(input.partNumber) || !isPartChecksum(input.checksumSha256)) throw new DriveError("invalid_input");
|
|
760
|
+
return {
|
|
761
|
+
...upload,
|
|
762
|
+
partNumber: input.partNumber,
|
|
763
|
+
checksumSha256: input.checksumSha256
|
|
619
764
|
};
|
|
620
765
|
}
|
|
766
|
+
function validateRecordUploadPartInput(value) {
|
|
767
|
+
const input = captureRecord(value, [
|
|
768
|
+
"workspaceId",
|
|
769
|
+
"uploadId",
|
|
770
|
+
"partNumber",
|
|
771
|
+
"expectedUploadRevision",
|
|
772
|
+
"byteLength",
|
|
773
|
+
"etag",
|
|
774
|
+
"checksumSha256"
|
|
775
|
+
], ["signal"]);
|
|
776
|
+
const upload = validateUploadPath(input.workspaceId, input.uploadId, input.signal);
|
|
777
|
+
if (!isPartNumber(input.partNumber) || !isSafeInteger(input.expectedUploadRevision) || input.expectedUploadRevision >= Number.MAX_SAFE_INTEGER || !isSafeInteger(input.byteLength) || input.byteLength < 1 || input.byteLength > UPLOAD_PART_BYTE_LENGTH || !isEtag(input.etag) || !isPartChecksum(input.checksumSha256)) throw new DriveError("invalid_input");
|
|
778
|
+
return {
|
|
779
|
+
...upload,
|
|
780
|
+
partNumber: input.partNumber,
|
|
781
|
+
expectedUploadRevision: input.expectedUploadRevision,
|
|
782
|
+
byteLength: input.byteLength,
|
|
783
|
+
etag: input.etag,
|
|
784
|
+
checksumSha256: input.checksumSha256
|
|
785
|
+
};
|
|
786
|
+
}
|
|
787
|
+
function validateCompleteUploadInput(value) {
|
|
788
|
+
const input = captureRecord(value, [
|
|
789
|
+
"workspaceId",
|
|
790
|
+
"uploadId",
|
|
791
|
+
"idempotencyKey"
|
|
792
|
+
], ["signal"]);
|
|
793
|
+
const upload = validateUploadPath(input.workspaceId, input.uploadId, input.signal);
|
|
794
|
+
if (!isIdempotencyKey(input.idempotencyKey)) throw new DriveError("invalid_input");
|
|
795
|
+
return {
|
|
796
|
+
...upload,
|
|
797
|
+
idempotencyKey: input.idempotencyKey
|
|
798
|
+
};
|
|
799
|
+
}
|
|
800
|
+
function validateAbortUploadInput(value) {
|
|
801
|
+
const input = captureRecord(value, ["workspaceId", "uploadId"], ["signal"]);
|
|
802
|
+
return validateUploadPath(input.workspaceId, input.uploadId, input.signal);
|
|
803
|
+
}
|
|
621
804
|
function resourcePath(input) {
|
|
622
805
|
return {
|
|
623
806
|
workspaceId: input.workspaceId,
|
|
@@ -702,6 +885,27 @@ function captureArray(value) {
|
|
|
702
885
|
function isIdempotencyKey(value) {
|
|
703
886
|
return typeof value === "string" && IDEMPOTENCY_KEY.test(value);
|
|
704
887
|
}
|
|
888
|
+
function validateUploadPath(workspaceId, uploadId, signalValue) {
|
|
889
|
+
const signal = validateSignal(signalValue);
|
|
890
|
+
if (typeof workspaceId !== "string" || !WORKSPACE_ID.test(workspaceId) || typeof uploadId !== "string" || !UPLOAD_ID.test(uploadId)) throw new DriveError("invalid_input");
|
|
891
|
+
return {
|
|
892
|
+
workspaceId,
|
|
893
|
+
uploadId,
|
|
894
|
+
...signal === void 0 ? {} : { signal }
|
|
895
|
+
};
|
|
896
|
+
}
|
|
897
|
+
function isPartNumber(value) {
|
|
898
|
+
return typeof value === "number" && Number.isSafeInteger(value) && value >= 1 && value <= 1e4;
|
|
899
|
+
}
|
|
900
|
+
function isPartChecksum(value) {
|
|
901
|
+
return typeof value === "string" && PART_CHECKSUM.test(value);
|
|
902
|
+
}
|
|
903
|
+
function isEtag(value) {
|
|
904
|
+
return typeof value === "string" && value.length >= 1 && value.length <= 1024 && /^[\x20-\x7e]+$/.test(value);
|
|
905
|
+
}
|
|
906
|
+
function isMediaType(value) {
|
|
907
|
+
return typeof value === "string" && value.length <= 255 && /^[a-z0-9](?:[a-z0-9!#$&^_.+-]{0,125}[a-z0-9])?\/[a-z0-9](?:[a-z0-9!#$&^_.+-]{0,125}[a-z0-9])?$/.test(value);
|
|
908
|
+
}
|
|
705
909
|
function isExpiry(value) {
|
|
706
910
|
return value === null || isSafeInteger(value);
|
|
707
911
|
}
|
|
@@ -1188,7 +1392,7 @@ async function execute(runtime, signal, preconditionStatus, send, successStatuse
|
|
|
1188
1392
|
signal: operationSignal
|
|
1189
1393
|
});
|
|
1190
1394
|
if (!capturedResponse) throw new DriveError("network_error");
|
|
1191
|
-
return await parseResponse(capturedResponse, operationSignal, preconditionStatus, successStatuses, guard, allowsQuotaExceeded, allowedErrorCodes);
|
|
1395
|
+
return guard === null ? await parseNoContentResponse(capturedResponse, operationSignal, preconditionStatus, successStatuses, allowedErrorCodes) : await parseResponse(capturedResponse, operationSignal, preconditionStatus, successStatuses, guard, allowsQuotaExceeded, allowedErrorCodes);
|
|
1192
1396
|
} catch (error) {
|
|
1193
1397
|
if (operation.didTimeout()) throw new DriveError("timeout");
|
|
1194
1398
|
if (error instanceof DriveError) throw error;
|
|
@@ -1276,8 +1480,35 @@ const CURRENT_WORKSPACE_ERROR_CODES = Object.freeze([
|
|
|
1276
1480
|
"invalid_request",
|
|
1277
1481
|
"service_unavailable"
|
|
1278
1482
|
]);
|
|
1483
|
+
const TRANSFER_ERROR_CODES = Object.freeze([
|
|
1484
|
+
"authentication_required",
|
|
1485
|
+
"identity_authority_unavailable",
|
|
1486
|
+
"permission_denied",
|
|
1487
|
+
"resource_not_found",
|
|
1488
|
+
"invalid_request",
|
|
1489
|
+
"precondition_failed",
|
|
1490
|
+
"upload_recovery_required",
|
|
1491
|
+
"service_unavailable"
|
|
1492
|
+
]);
|
|
1493
|
+
const STATUS_ERROR_CODES = Object.freeze([
|
|
1494
|
+
"authentication_required",
|
|
1495
|
+
"identity_authority_unavailable",
|
|
1496
|
+
"permission_denied",
|
|
1497
|
+
"resource_not_found",
|
|
1498
|
+
"invalid_request",
|
|
1499
|
+
"service_unavailable"
|
|
1500
|
+
]);
|
|
1501
|
+
const CREATE_UPLOAD_ERROR_CODES = Object.freeze([
|
|
1502
|
+
...TRANSFER_ERROR_CODES,
|
|
1503
|
+
"invalid_idempotency_key",
|
|
1504
|
+
"idempotency_conflict",
|
|
1505
|
+
"quota_exceeded"
|
|
1506
|
+
]);
|
|
1507
|
+
const COMPLETE_UPLOAD_ERROR_CODES = Object.freeze([...TRANSFER_ERROR_CODES, "invalid_idempotency_key"]);
|
|
1279
1508
|
function createDriveClientInternal(config) {
|
|
1280
|
-
|
|
1509
|
+
return createDriveOperations(createClientRuntime(config));
|
|
1510
|
+
}
|
|
1511
|
+
function createDriveOperations(runtime) {
|
|
1281
1512
|
return Object.freeze({
|
|
1282
1513
|
getCurrentWorkspace: (input) => getCurrentWorkspace(runtime, input),
|
|
1283
1514
|
listFolderChildren: (input) => listFolderChildren(runtime, input),
|
|
@@ -1288,6 +1519,10 @@ function createDriveClientInternal(config) {
|
|
|
1288
1519
|
updateItemPermission: (input) => updateItemPermission(runtime, input),
|
|
1289
1520
|
revokeItemPermission: (input) => revokeItemPermission(runtime, input),
|
|
1290
1521
|
createUpload: (input) => createUpload(runtime, input),
|
|
1522
|
+
getUploadStatus: (input) => getUploadStatus(runtime, input),
|
|
1523
|
+
createUploadPartDescriptor: (input) => createUploadPartDescriptor(runtime, input),
|
|
1524
|
+
recordUploadPart: (input) => recordUploadPart(runtime, input),
|
|
1525
|
+
completeUpload: (input) => completeUpload(runtime, input),
|
|
1291
1526
|
abortUpload: (input) => abortUpload(runtime, input)
|
|
1292
1527
|
});
|
|
1293
1528
|
}
|
|
@@ -1405,11 +1640,81 @@ async function createUpload(runtime, input) {
|
|
|
1405
1640
|
},
|
|
1406
1641
|
body: {
|
|
1407
1642
|
parentItemId: validated.parentItemId,
|
|
1408
|
-
expectedByteLength: validated.expectedByteLength
|
|
1643
|
+
expectedByteLength: validated.expectedByteLength,
|
|
1644
|
+
displayFilename: validated.displayFilename,
|
|
1645
|
+
mediaType: validated.mediaType,
|
|
1646
|
+
callerDeclaredContentDigest: validated.callerDeclaredContentDigest,
|
|
1647
|
+
protectionClassification: validated.protectionClassification
|
|
1648
|
+
}
|
|
1649
|
+
}),
|
|
1650
|
+
...requestOptions
|
|
1651
|
+
}), [200, 201], (value) => isUploadSession(value, validated.expectedByteLength), true, CREATE_UPLOAD_ERROR_CODES);
|
|
1652
|
+
}
|
|
1653
|
+
async function getUploadStatus(runtime, input) {
|
|
1654
|
+
const validated = validateGetUploadStatusInput(input);
|
|
1655
|
+
const afterPartNumber = validated.afterPartNumber ?? 0;
|
|
1656
|
+
const limit = validated.limit ?? 100;
|
|
1657
|
+
return execute(runtime, validated.signal, null, (api, requestOptions) => api.GET("/api/v1/workspaces/{workspaceId}/uploads/{uploadId}", {
|
|
1658
|
+
...operationOptions("getUploadStatus", { params: {
|
|
1659
|
+
path: {
|
|
1660
|
+
workspaceId: validated.workspaceId,
|
|
1661
|
+
uploadId: validated.uploadId
|
|
1662
|
+
},
|
|
1663
|
+
query: {
|
|
1664
|
+
...validated.afterPartNumber === void 0 ? {} : { afterPartNumber: String(validated.afterPartNumber) },
|
|
1665
|
+
...validated.limit === void 0 ? {} : { limit: String(validated.limit) }
|
|
1666
|
+
}
|
|
1667
|
+
} }),
|
|
1668
|
+
...requestOptions
|
|
1669
|
+
}), [200], (value) => isUploadStatus(value, afterPartNumber, limit), false, STATUS_ERROR_CODES);
|
|
1670
|
+
}
|
|
1671
|
+
async function createUploadPartDescriptor(runtime, input) {
|
|
1672
|
+
const validated = validateCreateUploadPartDescriptorInput(input);
|
|
1673
|
+
return execute(runtime, validated.signal, 409, (api, requestOptions) => api.POST("/api/v1/workspaces/{workspaceId}/uploads/{uploadId}/parts", {
|
|
1674
|
+
...operationOptions("createUploadPartDescriptor", {
|
|
1675
|
+
params: { path: {
|
|
1676
|
+
workspaceId: validated.workspaceId,
|
|
1677
|
+
uploadId: validated.uploadId
|
|
1678
|
+
} },
|
|
1679
|
+
body: {
|
|
1680
|
+
partNumber: validated.partNumber,
|
|
1681
|
+
checksumSha256: validated.checksumSha256
|
|
1682
|
+
}
|
|
1683
|
+
}),
|
|
1684
|
+
...requestOptions
|
|
1685
|
+
}), [200], (value) => isUploadPartDescriptor(value, validated.partNumber, validated.checksumSha256, new URL(runtime.baseUrl).protocol === "http:"), false, TRANSFER_ERROR_CODES);
|
|
1686
|
+
}
|
|
1687
|
+
async function recordUploadPart(runtime, input) {
|
|
1688
|
+
const validated = validateRecordUploadPartInput(input);
|
|
1689
|
+
return execute(runtime, validated.signal, 409, (api, requestOptions) => api.POST("/api/v1/workspaces/{workspaceId}/uploads/{uploadId}/parts/{partNumber}", {
|
|
1690
|
+
...operationOptions("recordUploadPart", {
|
|
1691
|
+
params: { path: {
|
|
1692
|
+
workspaceId: validated.workspaceId,
|
|
1693
|
+
uploadId: validated.uploadId,
|
|
1694
|
+
partNumber: String(validated.partNumber)
|
|
1695
|
+
} },
|
|
1696
|
+
body: {
|
|
1697
|
+
expectedUploadRevision: validated.expectedUploadRevision,
|
|
1698
|
+
byteLength: validated.byteLength,
|
|
1699
|
+
etag: validated.etag,
|
|
1700
|
+
checksumSha256: validated.checksumSha256
|
|
1409
1701
|
}
|
|
1410
1702
|
}),
|
|
1411
1703
|
...requestOptions
|
|
1412
|
-
}), [200
|
|
1704
|
+
}), [200], (value) => isUploadPartResult(value, validated.partNumber, validated.expectedUploadRevision), false, TRANSFER_ERROR_CODES);
|
|
1705
|
+
}
|
|
1706
|
+
async function completeUpload(runtime, input) {
|
|
1707
|
+
const validated = validateCompleteUploadInput(input);
|
|
1708
|
+
return execute(runtime, validated.signal, 409, (api, requestOptions) => api.POST("/api/v1/workspaces/{workspaceId}/uploads/{uploadId}/complete", {
|
|
1709
|
+
...operationOptions("completeUpload", { params: {
|
|
1710
|
+
path: {
|
|
1711
|
+
workspaceId: validated.workspaceId,
|
|
1712
|
+
uploadId: validated.uploadId
|
|
1713
|
+
},
|
|
1714
|
+
header: { "Idempotency-Key": validated.idempotencyKey }
|
|
1715
|
+
} }),
|
|
1716
|
+
...requestOptions
|
|
1717
|
+
}), [200], (value) => isReadyUpload(value, { uploadId: validated.uploadId }), false, COMPLETE_UPLOAD_ERROR_CODES);
|
|
1413
1718
|
}
|
|
1414
1719
|
async function abortUpload(runtime, input) {
|
|
1415
1720
|
const validated = validateAbortUploadInput(input);
|
|
@@ -1419,7 +1724,7 @@ async function abortUpload(runtime, input) {
|
|
|
1419
1724
|
uploadId: validated.uploadId
|
|
1420
1725
|
} } }),
|
|
1421
1726
|
...requestOptions
|
|
1422
|
-
}), [
|
|
1727
|
+
}), [204], null, false, TRANSFER_ERROR_CODES);
|
|
1423
1728
|
}
|
|
1424
1729
|
//#endregion
|
|
1425
1730
|
//#region src/server.ts
|
package/dist/types.d.ts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import { A as
|
|
2
|
-
export { AbortUploadInput,
|
|
1
|
+
import { A as ProtectionClassification, B as UploadFileProgressCallback, C as ListFolderChildrenInput, D as PermissionGrantee, E as PermissionGrant, F as UpdateItemPermissionInput, H as UploadPartResult, I as UploadConfirmedPart, L as UploadFileInput, M as ReadyUpload, N as RecordUploadPartInput, O as PermissionMutation, P as RevokeItemPermissionInput, R as UploadFilePhase, S as GetUploadStatusInput, T as PermissionAction, U as UploadSession, V as UploadPartDescriptor, W as UploadStatus, _ as EffectivePermissionAction, a as CreateUploadInput, b as FolderPage, c as DriveClient, d as DriveCredentialProvider, f as DriveOperationResult, g as DriveServerClientConfig, h as DriveServerClient, i as CreateItemPermissionInput, j as ReadEffectiveItemPermissionsInput, k as PermissionPage, l as DriveClientConfig, m as DriveResponseValidators, n as CompleteUploadInput, o as CreateUploadPartDescriptorInput, p as DriveOperations, r as CreateFolderInput, s as CurrentWorkspace, t as AbortUploadInput, u as DriveCredentialContext, v as EffectivePermissionResponse, w as ListItemPermissionsInput, x as GetCurrentWorkspaceInput, y as FolderItem, z as UploadFileProgress } from "./contract.js";
|
|
2
|
+
export { AbortUploadInput, CompleteUploadInput, CreateFolderInput, CreateItemPermissionInput, CreateUploadInput, CreateUploadPartDescriptorInput, CurrentWorkspace, DriveClient, DriveClientConfig, DriveCredentialContext, DriveCredentialProvider, DriveOperationResult, DriveOperations, DriveResponseValidators, DriveServerClient, DriveServerClientConfig, EffectivePermissionAction, EffectivePermissionResponse, FolderItem, FolderPage, GetCurrentWorkspaceInput, GetUploadStatusInput, ListFolderChildrenInput, ListItemPermissionsInput, PermissionAction, PermissionGrant, PermissionGrantee, PermissionMutation, PermissionPage, ProtectionClassification, ReadEffectiveItemPermissionsInput, ReadyUpload, RecordUploadPartInput, RevokeItemPermissionInput, UpdateItemPermissionInput, UploadConfirmedPart, UploadFileInput, UploadFilePhase, UploadFileProgress, UploadFileProgressCallback, UploadPartDescriptor, UploadPartResult, UploadSession, UploadStatus };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@finueva/drive",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.0",
|
|
4
4
|
"description": "Public browser, Worker, and Node client for Finueva Drive",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"types": "./dist/index.d.ts",
|
|
@@ -52,9 +52,10 @@
|
|
|
52
52
|
"generate": "node scripts/openapi/generate.mjs",
|
|
53
53
|
"check:generated": "node scripts/openapi/generate.mjs verify",
|
|
54
54
|
"prepack": "pnpm build",
|
|
55
|
-
"test": "node scripts/quality/source.mjs && pnpm check:generated && pnpm test:package",
|
|
55
|
+
"test": "node scripts/quality/source.mjs && pnpm test:unit && pnpm check:generated && pnpm test:package",
|
|
56
56
|
"test:artifact": "node scripts/package/artifact.mjs",
|
|
57
57
|
"test:package": "node scripts/package/qualify.mjs",
|
|
58
|
+
"test:unit": "node scripts/quality/unit.mjs",
|
|
58
59
|
"typecheck": "tsc --noEmit"
|
|
59
60
|
},
|
|
60
61
|
"devDependencies": {
|
|
@@ -68,5 +69,8 @@
|
|
|
68
69
|
"rolldown-plugin-dts": "0.27.14",
|
|
69
70
|
"typescript": "5.9.3",
|
|
70
71
|
"ufo": "1.6.4"
|
|
72
|
+
},
|
|
73
|
+
"dependencies": {
|
|
74
|
+
"@noble/hashes": "2.3.0"
|
|
71
75
|
}
|
|
72
76
|
}
|