@opengeni/storage 0.2.126-canary.0 → 0.2.126

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/index.d.ts CHANGED
@@ -14,7 +14,6 @@ export type ObjectHead = {
14
14
  /** Opaque provider generation/etag used only for conditional internal reads. */
15
15
  VersionToken?: string;
16
16
  };
17
- export { uploadWorkspaceArchiveSpool, downloadWorkspaceArchiveSpool, WorkspaceArchiveStorageError, } from "./workspace-archive-spool.js";
18
17
  export type ObjectStorage = {
19
18
  bucket: string;
20
19
  backend: "s3-compatible" | "aws-s3" | "azure-blob" | "gcs";
@@ -86,19 +85,6 @@ export type ObjectStorage = {
86
85
  body: Uint8Array;
87
86
  sha256?: string | null;
88
87
  }) => Promise<void>;
89
- /**
90
- * Unconditional authenticated upload from a bounded byte stream. May overwrite
91
- * an existing key; this is NOT an atomic create-only operation. Callers needing
92
- * write isolation must supply a fresh unique key and verify stored content.
93
- */
94
- putObjectStream?: (args: {
95
- key: string;
96
- contentType: string;
97
- chunks: AsyncIterable<Uint8Array>;
98
- byteSize: number;
99
- sha256?: string;
100
- signal?: AbortSignal;
101
- }) => Promise<void>;
102
88
  /** Atomic create-only raw PUT. Returns false when the key already exists. */
103
89
  putObjectIfAbsent?: (args: {
104
90
  key: string;
package/dist/index.js CHANGED
@@ -629,263 +629,6 @@ function throwIfAborted4(signal) {
629
629
  if (signal?.aborted) throw new Error("Object storage operation was cancelled");
630
630
  }
631
631
 
632
- // src/workspace-archive-spool.ts
633
- import { createHash as createHash3 } from "crypto";
634
- import { createReadStream } from "fs";
635
- import { chmod as chmod2, mkdtemp as mkdtemp2, open as open2, rm as rm2 } from "fs/promises";
636
- import { tmpdir as tmpdir2 } from "os";
637
- import { join as join2 } from "path";
638
- var CHUNK_BYTES = DEFAULT_BOUNDED_OBJECT_CHUNK_BYTES;
639
- var WorkspaceArchiveStorageError = class extends Error {
640
- constructor(code, message, retryable, options) {
641
- super(message, options);
642
- this.code = code;
643
- this.retryable = retryable;
644
- this.name = "WorkspaceArchiveStorageError";
645
- }
646
- };
647
- async function uploadWorkspaceArchiveSpool(storage, key, spool) {
648
- requireBoundedReads(storage);
649
- if (!storage.putObjectStream) {
650
- throw new WorkspaceArchiveStorageError(
651
- "archive_hydration_failed",
652
- "Workspace archive storage unsupported: unconditional streaming upload required",
653
- false
654
- );
655
- }
656
- const expected = { bytes: spool.byteSize, sha256: spool.sha256 };
657
- validateExpected(expected);
658
- let validated = false;
659
- let streamFailed = false;
660
- let streamFailure;
661
- const chunks = async function* () {
662
- const digest = createHash3("sha256");
663
- let bytes = 0;
664
- try {
665
- for await (const chunk of spool.open()) {
666
- if (!(chunk instanceof Uint8Array) || chunk.byteLength > expected.bytes - bytes) {
667
- throw new WorkspaceArchiveStorageError(
668
- "archive_hash_mismatch",
669
- "Workspace archive upload stream has invalid size",
670
- false
671
- );
672
- }
673
- bytes += chunk.byteLength;
674
- digest.update(chunk);
675
- yield chunk;
676
- }
677
- if (bytes !== expected.bytes || digest.digest("hex") !== expected.sha256) {
678
- throw new WorkspaceArchiveStorageError(
679
- "archive_hash_mismatch",
680
- "Workspace archive upload stream digest or size mismatch",
681
- false
682
- );
683
- }
684
- validated = true;
685
- } catch (error) {
686
- streamFailed = true;
687
- streamFailure = error;
688
- throw error;
689
- }
690
- }();
691
- try {
692
- await storage.putObjectStream({
693
- key,
694
- contentType: "application/x-tar",
695
- chunks,
696
- byteSize: expected.bytes,
697
- sha256: expected.sha256
698
- });
699
- if (!validated) {
700
- if (streamFailed) throw streamFailure;
701
- throw new WorkspaceArchiveStorageError(
702
- "archive_hydration_failed",
703
- "Workspace archive upload provider did not completely consume and validate the stream",
704
- false
705
- );
706
- }
707
- await verifyRanges(storage, key, expected);
708
- } catch (error) {
709
- const failure = streamFailed ? streamFailure : error;
710
- if (failure instanceof WorkspaceArchiveStorageError) throw failure;
711
- throw new WorkspaceArchiveStorageError(
712
- "archive_hydration_failed",
713
- "Workspace archive upload or readback failed",
714
- true,
715
- { cause: failure }
716
- );
717
- } finally {
718
- await chunks.return(void 0);
719
- }
720
- }
721
- async function downloadWorkspaceArchiveSpool(storage, key, inputExpected) {
722
- const expected = { bytes: inputExpected.bytes, sha256: inputExpected.sha256 };
723
- requireBoundedReads(storage);
724
- validateExpected(expected);
725
- const directory = await mkdtemp2(join2(tmpdir2(), "opengeni-workspace-archive-"));
726
- const path = join2(directory, "archive.tar");
727
- let handle;
728
- try {
729
- await chmod2(directory, 448);
730
- handle = await open2(path, "wx", 384);
731
- await verifyRanges(storage, key, expected, async (chunk) => {
732
- let offset = 0;
733
- while (offset < chunk.byteLength) {
734
- const { bytesWritten } = await handle.write(chunk, offset, chunk.byteLength - offset);
735
- if (bytesWritten <= 0)
736
- throw new WorkspaceArchiveStorageError(
737
- "archive_hydration_failed",
738
- "Workspace archive spool write made no progress",
739
- true
740
- );
741
- offset += bytesWritten;
742
- }
743
- });
744
- await handle.close();
745
- handle = void 0;
746
- let disposed = false;
747
- return {
748
- path,
749
- byteSize: expected.bytes,
750
- sha256: expected.sha256,
751
- async *open() {
752
- if (disposed) throw new Error("Workspace archive spool is disposed");
753
- const stream = createReadStream(path, { highWaterMark: CHUNK_BYTES });
754
- try {
755
- for await (const chunk of stream) yield chunk;
756
- } finally {
757
- stream.destroy();
758
- }
759
- },
760
- async dispose() {
761
- disposed = true;
762
- await rm2(directory, { recursive: true, force: true });
763
- }
764
- };
765
- } catch (error) {
766
- await handle?.close().catch(() => void 0);
767
- await rm2(directory, { recursive: true, force: true });
768
- if (error instanceof WorkspaceArchiveStorageError) throw error;
769
- throw new WorkspaceArchiveStorageError(
770
- "archive_hydration_failed",
771
- "Workspace archive download failed",
772
- true,
773
- { cause: error }
774
- );
775
- }
776
- }
777
- function requireBoundedReads(storage) {
778
- if (!storage.headObject || !storage.getObjectRange) {
779
- throw new WorkspaceArchiveStorageError(
780
- "archive_hydration_failed",
781
- "Workspace archive storage unsupported: versioned head/range reads required",
782
- false
783
- );
784
- }
785
- }
786
- function validateExpected(expected) {
787
- if (!Number.isSafeInteger(expected.bytes) || expected.bytes < 0 || !/^[0-9a-f]{64}$/u.test(expected.sha256)) {
788
- throw new WorkspaceArchiveStorageError(
789
- "archive_hydration_failed",
790
- "Workspace archive expected size or SHA-256 is invalid",
791
- false
792
- );
793
- }
794
- }
795
- async function verifyRanges(storage, key, expected, consume) {
796
- const head = await storage.headObject(key);
797
- if (!head)
798
- throw new WorkspaceArchiveStorageError(
799
- "archive_base64_invalid",
800
- "Workspace archive object is missing",
801
- false
802
- );
803
- if (head.ContentLength !== expected.bytes)
804
- throw new WorkspaceArchiveStorageError(
805
- "archive_hash_mismatch",
806
- "Workspace archive object size mismatch",
807
- false
808
- );
809
- const version = head.VersionToken;
810
- if (typeof version !== "string" || version.length === 0 || version.length > 2048) {
811
- throw new WorkspaceArchiveStorageError(
812
- "archive_hydration_failed",
813
- "Workspace archive storage unsupported: valid object version token required",
814
- false
815
- );
816
- }
817
- const digest = createHash3("sha256");
818
- let bytes = 0;
819
- while (bytes < expected.bytes) {
820
- const length = Math.min(CHUNK_BYTES, expected.bytes - bytes);
821
- const result = await storage.getObjectRange({
822
- key,
823
- start: bytes,
824
- endInclusive: bytes + length - 1,
825
- expectedVersionToken: version
826
- });
827
- if (!result) {
828
- const current = await storage.headObject(key);
829
- if (!current) {
830
- throw new WorkspaceArchiveStorageError(
831
- "archive_base64_invalid",
832
- "Workspace archive object is missing during range read",
833
- false
834
- );
835
- }
836
- throw new WorkspaceArchiveStorageError(
837
- "archive_hydration_failed",
838
- current.VersionToken !== version ? "Workspace archive object version changed" : "Workspace archive pinned range is temporarily unavailable",
839
- true
840
- );
841
- }
842
- if (result.versionToken !== version)
843
- throw new WorkspaceArchiveStorageError(
844
- "archive_hydration_failed",
845
- "Workspace archive object version changed",
846
- true
847
- );
848
- if (!(result.bytes instanceof Uint8Array) || result.bytes.byteLength !== length) {
849
- throw new WorkspaceArchiveStorageError(
850
- "archive_hash_mismatch",
851
- "Workspace archive object range is truncated or has invalid size",
852
- false
853
- );
854
- }
855
- digest.update(result.bytes);
856
- await consume?.(result.bytes);
857
- bytes += result.bytes.byteLength;
858
- }
859
- const finalHead = await storage.headObject(key);
860
- if (!finalHead)
861
- throw new WorkspaceArchiveStorageError(
862
- "archive_base64_invalid",
863
- "Workspace archive object is missing after range read",
864
- false
865
- );
866
- if (finalHead.VersionToken !== version) {
867
- throw new WorkspaceArchiveStorageError(
868
- "archive_hydration_failed",
869
- "Workspace archive object version changed",
870
- true
871
- );
872
- }
873
- if (finalHead.ContentLength !== expected.bytes) {
874
- throw new WorkspaceArchiveStorageError(
875
- "archive_hash_mismatch",
876
- "Workspace archive object size changed",
877
- false
878
- );
879
- }
880
- if (bytes !== expected.bytes || digest.digest("hex") !== expected.sha256) {
881
- throw new WorkspaceArchiveStorageError(
882
- "archive_hash_mismatch",
883
- "Workspace archive object digest or size mismatch",
884
- false
885
- );
886
- }
887
- }
888
-
889
632
  // src/index.ts
890
633
  var MAX_SINGLE_PUT_SIZE_BYTES = 5e9;
891
634
  var UPLOAD_URL_TTL_SECONDS = 15 * 60;
@@ -998,46 +741,6 @@ function createS3CompatibleObjectStorage(settings) {
998
741
  throw error;
999
742
  }
1000
743
  },
1001
- async putObjectStream(args) {
1002
- const body = Readable.from(args.chunks, {
1003
- objectMode: false,
1004
- highWaterMark: INTERNAL_STREAM_BUFFER_BYTES
1005
- });
1006
- const request = new AbortController();
1007
- let producerFailed = false;
1008
- let producerError;
1009
- const onBodyError = (error) => {
1010
- producerFailed = true;
1011
- producerError = error;
1012
- request.abort(error);
1013
- };
1014
- const onCallerAbort = () => request.abort(args.signal?.reason);
1015
- body.once("error", onBodyError);
1016
- const detachBodyError = () => body.off("error", onBodyError);
1017
- body.once("close", detachBodyError);
1018
- args.signal?.addEventListener("abort", onCallerAbort, { once: true });
1019
- if (args.signal?.aborted) onCallerAbort();
1020
- try {
1021
- await requestClient.send(
1022
- new PutObjectCommand({
1023
- Bucket: settings.objectStorageBucket,
1024
- Key: args.key,
1025
- ContentType: args.contentType,
1026
- ContentLength: args.byteSize,
1027
- Body: body,
1028
- Metadata: args.sha256 ? { sha256: args.sha256 } : void 0
1029
- }),
1030
- { abortSignal: request.signal }
1031
- );
1032
- if (producerFailed) throw producerError;
1033
- } catch (error) {
1034
- if (producerFailed) throw producerError;
1035
- throw error;
1036
- } finally {
1037
- args.signal?.removeEventListener("abort", onCallerAbort);
1038
- body.destroy();
1039
- }
1040
- },
1041
744
  async putObjectStreamIfAbsent(args) {
1042
745
  try {
1043
746
  await requestClient.send(
@@ -1283,23 +986,6 @@ function createGcsObjectStorage(settings) {
1283
986
  throw error;
1284
987
  }
1285
988
  },
1286
- async putObjectStream(args) {
1287
- const destination = bucket.file(args.key).createWriteStream({
1288
- resumable: false,
1289
- contentType: args.contentType,
1290
- highWaterMark: INTERNAL_STREAM_BUFFER_BYTES,
1291
- ...args.sha256 ? { metadata: { metadata: { sha256: args.sha256 } } } : {}
1292
- });
1293
- const source = Readable.from(args.chunks, {
1294
- objectMode: false,
1295
- highWaterMark: INTERNAL_STREAM_BUFFER_BYTES
1296
- });
1297
- if (args.signal) {
1298
- await pipeline(source, destination, { signal: args.signal });
1299
- } else {
1300
- await pipeline(source, destination);
1301
- }
1302
- },
1303
989
  async putObjectStreamIfAbsent(args) {
1304
990
  const destination = bucket.file(args.key).createWriteStream({
1305
991
  resumable: false,
@@ -1479,27 +1165,6 @@ function createAzureBlobObjectStorage(settings) {
1479
1165
  throw error;
1480
1166
  }
1481
1167
  },
1482
- async putObjectStream(args) {
1483
- const blobClient = requestContainerClient.getBlockBlobClient(args.key);
1484
- const source = Readable.from(args.chunks, {
1485
- objectMode: false,
1486
- highWaterMark: INTERNAL_STREAM_BUFFER_BYTES
1487
- });
1488
- try {
1489
- await blobClient.uploadStream(
1490
- source,
1491
- INTERNAL_STREAM_BUFFER_BYTES,
1492
- INTERNAL_STREAM_CONCURRENCY,
1493
- {
1494
- blobHTTPHeaders: { blobContentType: args.contentType },
1495
- ...args.sha256 ? { metadata: { sha256: args.sha256 } } : {},
1496
- ...args.signal ? { abortSignal: args.signal } : {}
1497
- }
1498
- );
1499
- } finally {
1500
- source.destroy();
1501
- }
1502
- },
1503
1168
  async putObjectStreamIfAbsent(args) {
1504
1169
  const blobClient = requestContainerClient.getBlockBlobClient(args.key);
1505
1170
  try {
@@ -1764,14 +1429,11 @@ export {
1764
1429
  MAX_SINGLE_PUT_SIZE_BYTES,
1765
1430
  OBJECT_VISIBILITY_RETRY_DELAYS_MS,
1766
1431
  UPLOAD_URL_TTL_SECONDS,
1767
- WorkspaceArchiveStorageError,
1768
1432
  bytesToDataUrl,
1769
1433
  createBoundedImmutableObjectWritePort,
1770
1434
  createBoundedObjectReadPort,
1771
1435
  createObjectStorage,
1772
1436
  createObjectStorageBoundedPorts,
1773
- downloadWorkspaceArchiveSpool,
1774
- retryWhileMissing,
1775
- uploadWorkspaceArchiveSpool
1437
+ retryWhileMissing
1776
1438
  };
1777
1439
  //# sourceMappingURL=index.js.map