@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/dist/server.d.ts CHANGED
@@ -1,5 +1,5 @@
1
- import { A as UploadQuotaReservation, D as ReservedUploadQuota, _ as FolderPage, a as CreateUploadInput, g as FolderItem, n as AbortedUploadQuota, o as CurrentWorkspace, p as DriveServerClientConfig, s as DriveClient, t as AbortUploadInput, v as GetCurrentWorkspaceInput, y as ListFolderChildrenInput } from "./contract.js";
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): DriveClient;
3
+ declare function createDriveServerClient(config: DriveServerClientConfig): DriveServerClient;
4
4
  //#endregion
5
- export { type AbortUploadInput, type AbortedUploadQuota, type CreateUploadInput, type CurrentWorkspace, type DriveClient, type DriveServerClientConfig, type FolderItem, type FolderPage, type GetCurrentWorkspaceInput, type ListFolderChildrenInput, type ReservedUploadQuota, type UploadQuotaReservation, createDriveServerClient };
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 UPLOAD_QUOTA_EXPIRY_MILLISECONDS = 864e5;
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 isUploadQuotaReservation(value) {
354
- return isExactRecord(value, [
355
- "workspaceId",
380
+ function isUploadSession(value, expectedByteLength) {
381
+ if (!isExactRecord(value, [
382
+ "itemId",
383
+ "versionId",
356
384
  "uploadId",
357
- "parentItemId",
358
- "expectedByteLength",
359
- "state",
360
- "terminalReason",
361
- "createdAtEpochMilliseconds",
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
- "terminalAtEpochMilliseconds"
364
- ]) && typeof value.workspaceId === "string" && WORKSPACE_ID$1.test(value.workspaceId) && typeof value.uploadId === "string" && UPLOAD_ID$1.test(value.uploadId) && typeof value.parentItemId === "string" && ITEM_ID$1.test(value.parentItemId) && typeof value.expectedByteLength === "number" && Number.isSafeInteger(value.expectedByteLength) && value.expectedByteLength >= 0 && value.expectedByteLength <= MAX_UPLOAD_BYTE_LENGTH$1 && (value.state === "reserved" || value.state === "converted" || value.state === "released") && (value.terminalReason === null || value.terminalReason === "completed" || value.terminalReason === "aborted" || value.terminalReason === "failed" || value.terminalReason === "expired") && isSafeInteger$1(value.createdAtEpochMilliseconds) && isSafeInteger$1(value.expiresAtEpochMilliseconds) && value.expiresAtEpochMilliseconds - value.createdAtEpochMilliseconds === UPLOAD_QUOTA_EXPIRY_MILLISECONDS && (value.terminalAtEpochMilliseconds === null || isSafeInteger$1(value.terminalAtEpochMilliseconds) && value.terminalAtEpochMilliseconds >= value.createdAtEpochMilliseconds);
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 validateAbortUploadInput(value) {
612
- const input = captureRecord(value, ["workspaceId", "uploadId"], ["signal"]);
613
- const signal = validateSignal(input.signal);
614
- if (typeof input.workspaceId !== "string" || !WORKSPACE_ID.test(input.workspaceId) || typeof input.uploadId !== "string" || !UPLOAD_ID.test(input.uploadId)) throw new DriveError("invalid_input");
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
- workspaceId: input.workspaceId,
617
- uploadId: input.uploadId,
618
- ...signal === void 0 ? {} : { signal }
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
- const runtime = createClientRuntime(config);
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, 201], (value) => isUploadQuotaReservation(value) && value.workspaceId === validated.workspaceId && value.parentItemId === validated.parentItemId && value.expectedByteLength === validated.expectedByteLength && value.state === "reserved" && value.terminalReason === null && value.terminalAtEpochMilliseconds === null, true);
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
- }), [200], (value) => isUploadQuotaReservation(value) && value.workspaceId === validated.workspaceId && value.uploadId === validated.uploadId && value.state === "released" && value.terminalReason === "aborted" && value.terminalAtEpochMilliseconds !== null);
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 UploadQuotaReservation, C as PermissionGrantee, D as ReservedUploadQuota, E as ReadEffectiveItemPermissionsInput, O as RevokeItemPermissionInput, S as PermissionGrant, T as PermissionPage, _ as FolderPage, a as CreateUploadInput, b as ListItemPermissionsInput, c as DriveClientConfig, d as DriveOperationResult, f as DriveResponseValidators, g as FolderItem, h as EffectivePermissionResponse, i as CreateItemPermissionInput, k as UpdateItemPermissionInput, l as DriveCredentialContext, m as EffectivePermissionAction, n as AbortedUploadQuota, o as CurrentWorkspace, p as DriveServerClientConfig, r as CreateFolderInput, s as DriveClient, t as AbortUploadInput, u as DriveCredentialProvider, v as GetCurrentWorkspaceInput, w as PermissionMutation, x as PermissionAction, y as ListFolderChildrenInput } from "./contract.js";
2
- export { AbortUploadInput, AbortedUploadQuota, CreateFolderInput, CreateItemPermissionInput, CreateUploadInput, CurrentWorkspace, DriveClient, DriveClientConfig, DriveCredentialContext, DriveCredentialProvider, DriveOperationResult, DriveResponseValidators, DriveServerClientConfig, EffectivePermissionAction, EffectivePermissionResponse, FolderItem, FolderPage, GetCurrentWorkspaceInput, ListFolderChildrenInput, ListItemPermissionsInput, PermissionAction, PermissionGrant, PermissionGrantee, PermissionMutation, PermissionPage, ReadEffectiveItemPermissionsInput, ReservedUploadQuota, RevokeItemPermissionInput, UpdateItemPermissionInput, UploadQuotaReservation };
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.2.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
  }