@meistrari/vault-sdk 1.6.3 → 1.8.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/index.mjs CHANGED
@@ -180,7 +180,7 @@ async function getFileHash(blob) {
180
180
  }
181
181
  async function detectFileMimeType(blob) {
182
182
  if (blob instanceof Blob && blob.type) {
183
- return blob.type;
183
+ return blob.type.split(";")[0].trim();
184
184
  }
185
185
  if ("name" in blob && typeof blob.name === "string") {
186
186
  const extension = blob.name.split(".").pop()?.toLowerCase();
@@ -217,9 +217,20 @@ async function detectFileMimeType(blob) {
217
217
  }
218
218
  return void 0;
219
219
  }
220
+ function basename(name, separator = "/") {
221
+ if (!name)
222
+ return void 0;
223
+ return name.substring(name.lastIndexOf(separator) + 1);
224
+ }
225
+ function getFileName(content) {
226
+ if ("name" in content && typeof content.name === "string") {
227
+ return basename(content.name);
228
+ }
229
+ return void 0;
230
+ }
220
231
 
221
232
  const name = "@meistrari/vault-sdk";
222
- const version = "1.6.3";
233
+ const version = "1.8.0";
223
234
  const license = "UNLICENSED";
224
235
  const repository = {
225
236
  type: "git",
@@ -397,6 +408,7 @@ class VaultFile {
397
408
  * @param metadata - The metadata for creating a file
398
409
  * @param metadata.size - The size of the file
399
410
  * @param metadata.mimeType - The mime type of the file
411
+ * @param metadata.parentId - The ID of the parent file for hierarchical file relationships
400
412
  * @param options - The options for the request
401
413
  * @param options.signal - The signal to abort the request
402
414
  *
@@ -409,7 +421,9 @@ class VaultFile {
409
421
  method: "POST",
410
422
  path: `files`,
411
423
  body: JSON.stringify({
412
- ...metadata,
424
+ size: metadata.size,
425
+ mimeType: metadata.mimeType,
426
+ parentId: metadata.parentId,
413
427
  fileName: this.name,
414
428
  sha256sum: this.id ?? this.metadata?.id ?? (this.content ? await getFileHash(this.content) : void 0)
415
429
  }),
@@ -497,7 +511,9 @@ class VaultFile {
497
511
  * @param params.name - The name of the file
498
512
  * @param params.content - The content of the file
499
513
  * @param params.config - The configuration for the VaultFile
514
+ * @param params.mimeType - The MIME type of the file (optional)
500
515
  * @param params.upload - Whether to upload the file (default: false)
516
+ * @param params.parentId - The ID of the parent file for hierarchical file relationships
501
517
  * @param options - The options for the request
502
518
  * @param options.signal - The signal to abort the request
503
519
  *
@@ -532,13 +548,30 @@ class VaultFile {
532
548
  * upload: true
533
549
  * })
534
550
  * ```
551
+ *
552
+ * @example
553
+ * ```ts
554
+ * // Create a child file with a parent
555
+ * const file = new File(['content'], 'child.txt')
556
+ * const vaultFile = await VaultFile.fromContent({
557
+ * name: 'child.txt',
558
+ * content: file,
559
+ * parentId: 'parent-file-id',
560
+ * config: {
561
+ * vaultUrl,
562
+ * authStrategy,
563
+ * },
564
+ * upload: true
565
+ * })
566
+ * ```
535
567
  */
536
568
  static async fromContent(params, options) {
537
- const { name, content, config: vaultConfig, upload = false } = params;
569
+ const { content, config: vaultConfig, upload = false, parentId } = params;
570
+ const name = basename(params.name) ?? getFileName(content);
538
571
  const config = resolveConfig(vaultConfig);
539
572
  const { vaultUrl, authStrategy } = config;
540
573
  const sha256sum = await getFileHash(content);
541
- const mimeType = await detectFileMimeType(content);
574
+ const mimeType = params.mimeType ?? await detectFileMimeType(content);
542
575
  const size = content.size;
543
576
  const file = new VaultFile({
544
577
  content,
@@ -551,7 +584,8 @@ class VaultFile {
551
584
  });
552
585
  const createdFile = await file._createFile({
553
586
  size,
554
- mimeType
587
+ mimeType,
588
+ parentId
555
589
  }, { signal: options?.signal });
556
590
  if (upload) {
557
591
  await file.upload(file.content, createdFile.uploadUrl, { signal: options?.signal });
@@ -567,6 +601,7 @@ class VaultFile {
567
601
  * @param params.contentLength - The size of the content in bytes
568
602
  * @param params.config - The configuration for the VaultFile
569
603
  * @param params.contentType - The MIME type of the content (optional)
604
+ * @param params.parentId - The ID of the parent file for hierarchical file relationships
570
605
  * @param options - The options for the request
571
606
  * @param options.signal - The signal to abort the request
572
607
  *
@@ -589,25 +624,66 @@ class VaultFile {
589
624
  * contentType: file.type
590
625
  * })
591
626
  * ```
627
+ *
628
+ * @example
629
+ * ```ts
630
+ * // Create a child file with streaming
631
+ * const vaultFile = await VaultFile.fromStream({
632
+ * name: 'child-video.mp4',
633
+ * contentLength: 50 * 1024 * 1024,
634
+ * contentType: 'video/mp4',
635
+ * parentId: 'parent-file-id',
636
+ * config: { vaultUrl, authStrategy }
637
+ * })
638
+ * ```
592
639
  */
593
640
  static async fromStream(params, options) {
594
- const { name, contentLength, config: vaultConfig, contentType } = params;
641
+ const { contentLength, config: vaultConfig, contentType, parentId } = params;
642
+ const name = basename(params.name);
595
643
  const config = resolveConfig(vaultConfig);
596
644
  const file = new VaultFile({ config, name });
597
645
  await file._createFile({
598
646
  size: contentLength,
599
- mimeType: contentType || "application/octet-stream"
647
+ mimeType: contentType || "application/octet-stream",
648
+ parentId
600
649
  }, { signal: options?.signal });
601
650
  return file;
602
651
  }
603
652
  /**
604
- * Populates the metadata of the file instance.
605
- * @param options - The options for the request
606
- * @param options.signal - The signal to abort the request
653
+ * Fetches and populates the metadata fields for this VaultFile instance.
654
+ *
655
+ * This method retrieves the file's metadata from the vault server and updates the instance's
656
+ * metadata, name, and id properties. Useful when you have a VaultFile instance that was created
657
+ * without full metadata or when you need to refresh the metadata.
607
658
  *
608
- * @returns The file instance
659
+ * @param options - Additional options for the request
660
+ * @param options.signal - AbortSignal to cancel the request
661
+ *
662
+ * @returns The VaultFile instance with populated metadata (for method chaining)
609
663
  * @throws {Error} If the file ID is not set
610
664
  * @throws {FetchError} If the metadata fetch fails
665
+ *
666
+ * @example
667
+ * ```ts
668
+ * const vaultFile = await VaultFile.fromVaultReference({
669
+ * reference: 'vault://file-id',
670
+ * config: { vaultUrl, authStrategy }
671
+ * })
672
+ * await vaultFile.populateMetadata()
673
+ * console.log('File size:', vaultFile.metadata?.size)
674
+ * console.log('MIME type:', vaultFile.metadata?.mimeType)
675
+ * ```
676
+ *
677
+ * @example
678
+ * ```ts
679
+ * // Chain with other operations
680
+ * const file = await VaultFile.fromVaultReference({
681
+ * reference: 'vault://file-id',
682
+ * config: { vaultUrl, authStrategy }
683
+ * })
684
+ * await file.populateMetadata()
685
+ * const children = await file.getChildren()
686
+ * ```
611
687
  */
612
688
  async populateMetadata(options) {
613
689
  try {
@@ -620,10 +696,38 @@ class VaultFile {
620
696
  }
621
697
  }
622
698
  /**
623
- * Gets the vault reference for this file.
699
+ * Returns the vault URI reference for this file.
624
700
  *
625
- * @returns The vault reference in the format `vault://{fileId}`
701
+ * The vault reference is a URI in the format `vault://{fileId}` that can be used to
702
+ * uniquely identify and retrieve this file from the vault. This reference can be stored
703
+ * in databases, passed to other services, or used with {@link VaultFile.fromVaultReference}
704
+ * to reconstruct a VaultFile instance.
705
+ *
706
+ * @returns The vault reference string in the format `vault://{fileId}`
626
707
  * @throws {Error} If the file ID is not set
708
+ *
709
+ * @example
710
+ * ```ts
711
+ * const file = await VaultFile.fromContent({
712
+ * name: 'document.txt',
713
+ * content: new Blob(['content']),
714
+ * config: { vaultUrl, authStrategy },
715
+ * upload: true
716
+ * })
717
+ * const reference = file.getVaultReference()
718
+ * // Returns: "vault://abc123..."
719
+ * // Store this reference in your database for later retrieval
720
+ * ```
721
+ *
722
+ * @example
723
+ * ```ts
724
+ * // Retrieve a file later using the reference
725
+ * const storedReference = database.get('file_reference')
726
+ * const file = await VaultFile.fromVaultReference({
727
+ * reference: storedReference,
728
+ * config: { vaultUrl, authStrategy }
729
+ * })
730
+ * ```
627
731
  */
628
732
  getVaultReference() {
629
733
  if (!this.id) {
@@ -632,13 +736,37 @@ class VaultFile {
632
736
  return `vault://${this.id}`;
633
737
  }
634
738
  /**
635
- * Fetches the metadata of the file.
636
- * @param options - The options for the request
637
- * @param options.signal - The signal to abort the request
739
+ * Fetches the complete metadata for this file from the vault server.
638
740
  *
639
- * @returns The metadata of the file
741
+ * Retrieves detailed information about the file including size, MIME type, creation date,
742
+ * workspace ID, and other metadata fields. Unlike {@link populateMetadata}, this method
743
+ * returns the metadata without modifying the VaultFile instance.
744
+ *
745
+ * @param options - Additional options for the request
746
+ * @param options.signal - AbortSignal to cancel the request
747
+ *
748
+ * @returns A Promise that resolves to the FileMetadata object
640
749
  * @throws {Error} If the file ID is not set
641
750
  * @throws {FetchError} If the metadata fetch fails
751
+ *
752
+ * @example
753
+ * ```ts
754
+ * const vaultFile = await VaultFile.fromVaultReference({
755
+ * reference: 'vault://file-id',
756
+ * config: { vaultUrl, authStrategy }
757
+ * })
758
+ * const metadata = await vaultFile.getFileMetadata()
759
+ * console.log('File size:', metadata.size)
760
+ * console.log('Created at:', metadata.createdAt)
761
+ * console.log('Workspace:', metadata.workspaceId)
762
+ * ```
763
+ *
764
+ * @example
765
+ * ```ts
766
+ * // Use with abort signal
767
+ * const controller = new AbortController()
768
+ * const metadata = await vaultFile.getFileMetadata({ signal: controller.signal })
769
+ * ```
642
770
  */
643
771
  async getFileMetadata(options) {
644
772
  if (!this.id) {
@@ -652,14 +780,41 @@ class VaultFile {
652
780
  return response;
653
781
  }
654
782
  /**
655
- * Fetches a upload URL for the file.
656
- * @param options - The options for the request
657
- * @param options.signal - The signal to abort the request
658
- * @param options.expiresIn - The number of seconds the upload URL will be valid for
783
+ * Retrieves a presigned upload URL for uploading file content to the vault.
659
784
  *
660
- * @returns The upload URL for the file
785
+ * This method returns a temporary URL that can be used to upload the file content directly
786
+ * to cloud storage (e.g., S3). The URL is cached and reused if still valid. If the file
787
+ * doesn't exist yet, it will be created automatically.
788
+ *
789
+ * @param options - Additional options for the request
790
+ * @param options.signal - AbortSignal to cancel the request
791
+ * @param options.expiresIn - Number of seconds the upload URL should be valid for (default: server-defined)
792
+ *
793
+ * @returns A Promise that resolves to a URL object for uploading the file
661
794
  * @throws {Error} If the vault service returns an invalid response
662
795
  * @throws {FetchError} If the upload URL fetch fails
796
+ *
797
+ * @example
798
+ * ```ts
799
+ * const vaultFile = await VaultFile.fromContent({
800
+ * name: 'document.txt',
801
+ * content: new Blob(['content']),
802
+ * config: { vaultUrl, authStrategy }
803
+ * })
804
+ * const uploadUrl = await vaultFile.getUploadUrl()
805
+ * // Use the URL for custom upload logic
806
+ * await fetch(uploadUrl, {
807
+ * method: 'PUT',
808
+ * body: file,
809
+ * headers: { 'Content-Type': 'text/plain' }
810
+ * })
811
+ * ```
812
+ *
813
+ * @example
814
+ * ```ts
815
+ * // Request a longer-lived upload URL
816
+ * const uploadUrl = await vaultFile.getUploadUrl({ expiresIn: 7200 }) // 2 hours
817
+ * ```
663
818
  */
664
819
  async getUploadUrl(options) {
665
820
  if (this.lastUploadUrl && this.lastUploadUrl.expiresAt > /* @__PURE__ */ new Date()) {
@@ -686,15 +841,38 @@ class VaultFile {
686
841
  return this.lastUploadUrl.url;
687
842
  }
688
843
  /**
689
- * Fetches a download URL for the file.
690
- * @param options - The options for the request
691
- * @param options.signal - The signal to abort the request
692
- * @param options.expiresIn - The number of seconds the download URL will be valid for
844
+ * Retrieves a presigned download URL for accessing the file content.
693
845
  *
694
- * @returns The download URL for the file
846
+ * This method returns a temporary URL that can be used to download the file content directly
847
+ * from cloud storage (e.g., S3). The URL is cached and reused if still valid. This is useful
848
+ * for generating shareable links or implementing custom download logic.
849
+ *
850
+ * @param options - Additional options for the request
851
+ * @param options.signal - AbortSignal to cancel the request
852
+ * @param options.expiresIn - Number of seconds the download URL should be valid for (default: server-defined)
853
+ *
854
+ * @returns A Promise that resolves to a URL object for downloading the file
695
855
  * @throws {Error} If the vault service returns an invalid response
696
- * @throws {Error} If not file ID, name or content is set
856
+ * @throws {Error} If no file ID, name, or content is set
697
857
  * @throws {FetchError} If the download URL fetch fails
858
+ *
859
+ * @example
860
+ * ```ts
861
+ * const vaultFile = await VaultFile.fromVaultReference({
862
+ * reference: 'vault://file-id',
863
+ * config: { vaultUrl, authStrategy }
864
+ * })
865
+ * const downloadUrl = await vaultFile.getDownloadUrl()
866
+ * // Use the URL to download the file
867
+ * window.location.href = downloadUrl.toString()
868
+ * ```
869
+ *
870
+ * @example
871
+ * ```ts
872
+ * // Request a longer-lived download URL
873
+ * const downloadUrl = await vaultFile.getDownloadUrl({ expiresIn: 3600 }) // 1 hour
874
+ * // Share this URL with others
875
+ * ```
698
876
  */
699
877
  async getDownloadUrl(options) {
700
878
  if (this.lastDownloadUrl && this.lastDownloadUrl.expiresAt > /* @__PURE__ */ new Date()) {
@@ -714,30 +892,51 @@ class VaultFile {
714
892
  return this.lastDownloadUrl.url;
715
893
  }
716
894
  /**
717
- * Uploads a file to the vault.
895
+ * Uploads file content to the vault using a presigned URL.
896
+ *
897
+ * This method handles the actual file upload to cloud storage. If no file is provided,
898
+ * it uses the content from the VaultFile instance. If no upload URL is provided, one
899
+ * will be fetched automatically. The MIME type is detected automatically if not already set.
900
+ *
901
+ * @param file - The Blob or File to upload. If not provided, uses the instance's content property
902
+ * @param url - A presigned upload URL. If not provided, will be fetched via {@link getUploadUrl}
903
+ * @param options - Additional options for the request
904
+ * @param options.signal - AbortSignal to cancel the upload
905
+ *
906
+ * @throws {Error} If no file content is available (neither provided nor in the instance)
907
+ * @throws {FetchError} If the upload request fails
908
+ * @returns A Promise that resolves when the upload completes successfully
718
909
  *
719
910
  * @example
720
911
  * ```ts
912
+ * // Simple upload using instance content
721
913
  * const file = new File(['content'], 'document.txt')
722
914
  * const vaultFile = await VaultFile.fromContent({
723
915
  * name: 'document.txt',
724
916
  * content: file,
725
- * config: {
726
- * vaultUrl,
727
- * authStrategy,
728
- * },
917
+ * config: { vaultUrl, authStrategy }
729
918
  * })
730
- * await vaultFile.upload(file)
919
+ * await vaultFile.upload()
731
920
  * ```
732
921
  *
733
- * @param file - The file to upload to the vault. If not provided, the file content will be taken from the `content` property.
734
- * @param url - The URL to upload the file to. If not provided, the upload URL will be fetched from the vault.
735
- * @param options - The options for the request
736
- * @param options.signal - The signal to abort the request
922
+ * @example
923
+ * ```ts
924
+ * // Upload different content than what's in the instance
925
+ * const vaultFile = await VaultFile.fromContent({
926
+ * name: 'document.txt',
927
+ * content: new Blob(['original']),
928
+ * config: { vaultUrl, authStrategy }
929
+ * })
930
+ * const newContent = new Blob(['updated content'])
931
+ * await vaultFile.upload(newContent)
932
+ * ```
737
933
  *
738
- * @throws {FetchError} If the upload fails
739
- * @throws {Error} If the file content is not set and no file is provided
740
- * @returns Promise that resolves when upload is complete
934
+ * @example
935
+ * ```ts
936
+ * // Upload with custom URL (advanced usage)
937
+ * const uploadUrl = await vaultFile.getUploadUrl({ expiresIn: 3600 })
938
+ * await vaultFile.upload(file, uploadUrl.toString())
939
+ * ```
741
940
  */
742
941
  async upload(file, url, options) {
743
942
  const content = file ?? this.content;
@@ -768,31 +967,60 @@ class VaultFile {
768
967
  return await blobToBase64(blob);
769
968
  }
770
969
  /**
771
- * Downloads a file from the vault as a stream for memory-efficient processing.
970
+ * Downloads the file as a ReadableStream for memory-efficient processing of large files.
772
971
  *
773
- * @param options - The options for the request
774
- * @param options.signal - The signal to abort the request
972
+ * This method is ideal for handling large files without loading the entire content into memory.
973
+ * The stream can be processed chunk-by-chunk, piped to other streams, or saved incrementally.
974
+ * This is the recommended approach for files larger than a few megabytes.
975
+ *
976
+ * @param options - Additional options for the request
977
+ * @param options.signal - AbortSignal to cancel the download
775
978
  *
776
- * @returns A ReadableStream that yields chunks of the file data
979
+ * @returns A Promise resolving to a ReadableStream that yields Uint8Array chunks
980
+ * @throws {Error} If the response body is not readable
981
+ * @throws {FetchError} If the download fails
777
982
  *
778
983
  * @example
779
984
  * ```ts
780
- * const vaultFile = await VaultFile.fromVaultReference('vault://1234567890', { vaultUrl, authStrategy })
985
+ * // Process large file chunk by chunk
986
+ * const vaultFile = await VaultFile.fromVaultReference({
987
+ * reference: 'vault://large-video-id',
988
+ * config: { vaultUrl, authStrategy }
989
+ * })
781
990
  * const stream = await vaultFile.downloadStream()
782
991
  *
783
- * // Process the stream chunk by chunk
784
992
  * const reader = stream.getReader()
993
+ * let bytesReceived = 0
785
994
  * try {
786
995
  * while (true) {
787
996
  * const { done, value } = await reader.read()
788
997
  * if (done) break
789
- * // Process the chunk (Uint8Array)
790
- * console.log('Received chunk of size:', value.length)
998
+ * bytesReceived += value.length
999
+ * console.log(`Downloaded ${bytesReceived} bytes...`)
1000
+ * // Process chunk (value is Uint8Array)
791
1001
  * }
792
1002
  * } finally {
793
1003
  * reader.releaseLock()
794
1004
  * }
795
1005
  * ```
1006
+ *
1007
+ * @example
1008
+ * ```ts
1009
+ * // Pipe stream to a writable destination
1010
+ * const stream = await vaultFile.downloadStream()
1011
+ * const fileHandle = await navigator.storage.getDirectory()
1012
+ * .then(dir => dir.getFileHandle('output.dat', { create: true }))
1013
+ * const writable = await fileHandle.createWritable()
1014
+ * await stream.pipeTo(writable)
1015
+ * ```
1016
+ *
1017
+ * @example
1018
+ * ```ts
1019
+ * // Use with abort signal
1020
+ * const controller = new AbortController()
1021
+ * const stream = await vaultFile.downloadStream({ signal: controller.signal })
1022
+ * // Later: controller.abort()
1023
+ * ```
796
1024
  */
797
1025
  async downloadStream(options) {
798
1026
  const downloadUrl = await this.getDownloadUrl({ signal: options?.signal });
@@ -806,32 +1034,64 @@ class VaultFile {
806
1034
  return response.body;
807
1035
  }
808
1036
  /**
809
- * Uploads a file to the vault using a stream for memory-efficient processing.
1037
+ * Uploads file content using a ReadableStream for memory-efficient processing of large files.
810
1038
  *
811
- * @param stream - The readable stream of file data to upload
812
- * @param options - The options for the request
813
- * @param options.signal - The signal to abort the request
814
- * @param options.contentLength - The total size of the content (required for S3 uploads)
815
- * @param options.contentType - The MIME type of the content (will be detected if not provided)
1039
+ * This method is ideal for uploading large files without loading the entire content into memory.
1040
+ * The stream is sent directly to cloud storage, making it perfect for files larger than a few
1041
+ * megabytes. Note: When using Bun, the stream is buffered due to implementation limitations.
816
1042
  *
817
- * @throws {Error} If contentLength is not provided
1043
+ * @param stream - A ReadableStream of Uint8Array chunks containing the file data
1044
+ * @param options - Required options for the upload
1045
+ * @param options.contentLength - The total size of the content in bytes (required for S3 uploads)
1046
+ * @param options.contentType - The MIME type of the content (auto-detected from filename if not provided)
1047
+ * @param options.signal - AbortSignal to cancel the upload
1048
+ *
1049
+ * @throws {Error} If contentLength is not provided or is negative
818
1050
  * @throws {FetchError} If the upload fails
819
- * @returns Promise that resolves when upload is complete
1051
+ * @returns A Promise that resolves when the upload completes successfully
820
1052
  *
821
1053
  * @example
822
1054
  * ```ts
823
- * const file = new File(['content'], 'document.txt')
824
- * const vaultFile = await VaultFile.fromStream('document.txt', file.size, {
1055
+ * // Upload a large file using streaming
1056
+ * const file = new File(['large content'], 'video.mp4')
1057
+ * const vaultFile = await VaultFile.fromStream({
1058
+ * name: 'video.mp4',
1059
+ * contentLength: file.size,
825
1060
  * contentType: file.type,
826
1061
  * config: { vaultUrl, authStrategy }
827
1062
  * })
828
1063
  *
829
- * // Upload using the stream directly
830
1064
  * const stream = file.stream()
831
1065
  * await vaultFile.uploadStream(stream, {
832
1066
  * contentLength: file.size,
833
1067
  * contentType: file.type
834
1068
  * })
1069
+ * console.log('Upload complete!')
1070
+ * ```
1071
+ *
1072
+ * @example
1073
+ * ```ts
1074
+ * // Upload with progress tracking
1075
+ * const vaultFile = await VaultFile.fromStream({
1076
+ * name: 'document.pdf',
1077
+ * contentLength: fileSize,
1078
+ * config: { vaultUrl, authStrategy }
1079
+ * })
1080
+ *
1081
+ * // Create a transform stream to track progress
1082
+ * let uploaded = 0
1083
+ * const progressStream = new TransformStream({
1084
+ * transform(chunk, controller) {
1085
+ * uploaded += chunk.length
1086
+ * console.log(`Uploaded ${uploaded}/${fileSize} bytes`)
1087
+ * controller.enqueue(chunk)
1088
+ * }
1089
+ * })
1090
+ *
1091
+ * await vaultFile.uploadStream(
1092
+ * originalStream.pipeThrough(progressStream),
1093
+ * { contentLength: fileSize }
1094
+ * )
835
1095
  * ```
836
1096
  */
837
1097
  async uploadStream(stream, options) {
@@ -844,18 +1104,76 @@ class VaultFile {
844
1104
  const headers = new Headers();
845
1105
  headers.set("Content-Type", mimeType);
846
1106
  headers.set("Content-Length", contentLength.toString());
1107
+ let content = stream;
1108
+ if (stream instanceof ReadableStream && typeof Bun !== "undefined") {
1109
+ console.warn(
1110
+ "[Vault SDK - WARNING] Buffering file upload due to Bun fetch implementation. Large files may cause memory issues. Consider using Node.js for streaming uploads.",
1111
+ { fileName: this.name, fileSize: contentLength }
1112
+ );
1113
+ const chunks = [];
1114
+ const reader = stream.getReader();
1115
+ try {
1116
+ while (true) {
1117
+ const { done, value } = await reader.read();
1118
+ if (done)
1119
+ break;
1120
+ chunks.push(value);
1121
+ }
1122
+ } finally {
1123
+ reader.releaseLock();
1124
+ }
1125
+ content = new Blob(chunks, { type: mimeType });
1126
+ }
847
1127
  await wrappedFetch(uploadUrl, {
848
1128
  method: "PUT",
849
- body: stream,
1129
+ body: content,
850
1130
  headers,
851
1131
  signal
852
1132
  });
853
1133
  }
854
1134
  /**
855
- * Deletes the file from the vault.
856
- * @param options - The options for the request
857
- * @param options.signal - The signal to abort the request
1135
+ * Permanently deletes this file from the vault.
1136
+ *
1137
+ * This operation removes the file metadata and content from the vault. This action cannot be
1138
+ * undone. Any vault references or permalinks to this file will become invalid after deletion.
1139
+ * Note: This does not automatically delete child files in hierarchical relationships.
1140
+ *
1141
+ * @param options - Additional options for the request
1142
+ * @param options.signal - AbortSignal to cancel the request
1143
+ *
1144
+ * @throws {Error} If the file ID is not set
1145
+ * @throws {FetchError} If the deletion request fails
1146
+ * @returns A Promise that resolves when the deletion completes successfully
1147
+ *
1148
+ * @example
1149
+ * ```ts
1150
+ * // Delete a file
1151
+ * const vaultFile = await VaultFile.fromVaultReference({
1152
+ * reference: 'vault://file-id',
1153
+ * config: { vaultUrl, authStrategy }
1154
+ * })
1155
+ * await vaultFile.delete()
1156
+ * console.log('File deleted successfully')
1157
+ * ```
1158
+ *
1159
+ * @example
1160
+ * ```ts
1161
+ * // Delete with confirmation
1162
+ * const vaultFile = await VaultFile.fromVaultReference({
1163
+ * reference: 'vault://file-id',
1164
+ * config: { vaultUrl, authStrategy }
1165
+ * })
1166
+ * if (confirm('Are you sure you want to delete this file?')) {
1167
+ * await vaultFile.delete()
1168
+ * }
1169
+ * ```
858
1170
  *
1171
+ * @example
1172
+ * ```ts
1173
+ * // Delete with abort signal
1174
+ * const controller = new AbortController()
1175
+ * await vaultFile.delete({ signal: controller.signal })
1176
+ * ```
859
1177
  */
860
1178
  async delete(options) {
861
1179
  if (!this.id) {
@@ -868,13 +1186,51 @@ class VaultFile {
868
1186
  });
869
1187
  }
870
1188
  /**
871
- * Creates a permalink for the file.
872
- * @param params - The parameters for the permalink
873
- * @param params.expiresIn - The number of seconds the permalink will be valid for
874
- * @param options - The options for the request
875
- * @param options.signal - The signal to abort the request
1189
+ * Creates a new shareable permalink for this file.
1190
+ *
1191
+ * A permalink is a public URL that allows anyone with the link to access the file without
1192
+ * authentication. The permalink can optionally expire after a specified duration. This is
1193
+ * useful for sharing files with external users or embedding files in public content.
1194
+ *
1195
+ * @param params - Optional parameters for permalink creation
1196
+ * @param params.expiresIn - Number of seconds until the permalink expires (optional, defaults to server setting)
1197
+ * @param options - Additional options for the request
1198
+ * @param options.signal - AbortSignal to cancel the request
876
1199
  *
877
- * @returns The permalink for the file
1200
+ * @throws {Error} If the file ID is not set
1201
+ * @throws {Error} If the workspace ID is not available (call {@link populateMetadata} first)
1202
+ * @throws {FetchError} If the permalink creation fails
1203
+ * @returns A Promise that resolves to a Permalink instance
1204
+ *
1205
+ * @example
1206
+ * ```ts
1207
+ * // Create a permanent permalink
1208
+ * const vaultFile = await VaultFile.fromVaultReference({
1209
+ * reference: 'vault://file-id',
1210
+ * config: { vaultUrl, authStrategy }
1211
+ * })
1212
+ * await vaultFile.populateMetadata() // Required to get workspace ID
1213
+ * const permalink = await vaultFile.createPermalink()
1214
+ * console.log('Share this link:', permalink.url)
1215
+ * ```
1216
+ *
1217
+ * @example
1218
+ * ```ts
1219
+ * // Create a permalink that expires in 24 hours
1220
+ * await vaultFile.populateMetadata()
1221
+ * const permalink = await vaultFile.createPermalink({
1222
+ * expiresIn: 24 * 60 * 60 // 24 hours in seconds
1223
+ * })
1224
+ * console.log('Link expires at:', permalink.expiresAt)
1225
+ * ```
1226
+ *
1227
+ * @example
1228
+ * ```ts
1229
+ * // Create a short-lived sharing link (1 hour)
1230
+ * await vaultFile.populateMetadata()
1231
+ * const permalink = await vaultFile.createPermalink({ expiresIn: 3600 })
1232
+ * // Send permalink.url to user
1233
+ * ```
878
1234
  */
879
1235
  async createPermalink(params = {}, options) {
880
1236
  if (!this.id) {
@@ -890,11 +1246,51 @@ class VaultFile {
890
1246
  }, options);
891
1247
  }
892
1248
  /**
893
- * Gets a list of the valid permalinks for the file.
1249
+ * Retrieves all active permalinks associated with this file.
1250
+ *
1251
+ * This method fetches all permalinks that have been created for this file and are still
1252
+ * valid (not expired or deleted). Each permalink is returned as a Permalink instance
1253
+ * with full details including URL, expiration date, and other metadata.
1254
+ *
894
1255
  * @param options - Additional options for the request
895
- * @param options.signal - Abort signal
1256
+ * @param options.signal - AbortSignal to cancel the request
896
1257
  *
897
- * @returns The permalinks for the file
1258
+ * @throws {Error} If the file ID is not set
1259
+ * @throws {FetchError} If the request to fetch permalinks fails
1260
+ * @returns A Promise that resolves to an array of Permalink instances
1261
+ *
1262
+ * @example
1263
+ * ```ts
1264
+ * // List all permalinks for a file
1265
+ * const vaultFile = await VaultFile.fromVaultReference({
1266
+ * reference: 'vault://file-id',
1267
+ * config: { vaultUrl, authStrategy }
1268
+ * })
1269
+ * const permalinks = await vaultFile.getPermalinks()
1270
+ * permalinks.forEach(permalink => {
1271
+ * console.log('URL:', permalink.url)
1272
+ * console.log('Expires:', permalink.expiresAt || 'Never')
1273
+ * })
1274
+ * ```
1275
+ *
1276
+ * @example
1277
+ * ```ts
1278
+ * // Check if any permalinks exist
1279
+ * const permalinks = await vaultFile.getPermalinks()
1280
+ * if (permalinks.length > 0) {
1281
+ * console.log(`This file has ${permalinks.length} active permalink(s)`)
1282
+ * } else {
1283
+ * console.log('No active permalinks')
1284
+ * }
1285
+ * ```
1286
+ *
1287
+ * @example
1288
+ * ```ts
1289
+ * // Find non-expiring permalinks
1290
+ * const permalinks = await vaultFile.getPermalinks()
1291
+ * const permanent = permalinks.filter(p => !p.expiresAt)
1292
+ * console.log(`Found ${permanent.length} permanent links`)
1293
+ * ```
898
1294
  */
899
1295
  async getPermalinks(options) {
900
1296
  if (!this.id) {
@@ -907,6 +1303,273 @@ class VaultFile {
907
1303
  });
908
1304
  return permalinks.map((data) => new Permalink(this.config, data));
909
1305
  }
1306
+ /**
1307
+ * Retrieves all child files in the hierarchical file relationship.
1308
+ *
1309
+ * This method queries files that have this file set as their parent (via parentId).
1310
+ * Returns an empty array if the file has no children or if the file doesn't exist.
1311
+ * The returned children are VaultFile instances with populated metadata but no content.
1312
+ *
1313
+ * @param params - Optional parameters for filtering and request control
1314
+ * @param params.mimeType - Filter children by MIME type (e.g., 'image/png', 'application/pdf')
1315
+ * @param params.includeDeleted - Whether to include deleted child files (default: false)
1316
+ * @param params.limit - Maximum number of children to return (default: 100)
1317
+ * @param params.offset - Number of children to skip (default: 0)
1318
+ * @param options - Additional options for the request
1319
+ * @param options.signal - AbortSignal to cancel the request
1320
+ *
1321
+ * @returns An array of VaultFile instances representing the child files
1322
+ * @throws {Error} If the file ID is not set
1323
+ * @throws {FetchError} If the request to fetch children fails
1324
+ *
1325
+ * @example
1326
+ * ```ts
1327
+ * // Get all children of a file
1328
+ * const parent = await VaultFile.fromVaultReference({
1329
+ * reference: 'vault://parent-id',
1330
+ * config: { vaultUrl, authStrategy }
1331
+ * })
1332
+ * const children = await parent.getChildren()
1333
+ * console.log(`Found ${children.length} child files`)
1334
+ * ```
1335
+ *
1336
+ * @example
1337
+ * ```ts
1338
+ * // Get only image children
1339
+ * const parent = await VaultFile.fromVaultReference({
1340
+ * reference: 'vault://parent-id',
1341
+ * config: { vaultUrl, authStrategy }
1342
+ * })
1343
+ * const imageChildren = await parent.getChildren({ mimeType: 'image/png' })
1344
+ * ```
1345
+ *
1346
+ * @example
1347
+ * ```ts
1348
+ * // Get children with pagination
1349
+ * const parent = await VaultFile.fromVaultReference({
1350
+ * reference: 'vault://parent-id',
1351
+ * config: { vaultUrl, authStrategy }
1352
+ * })
1353
+ * const firstPage = await parent.getChildren({ limit: 10, offset: 0 })
1354
+ * const secondPage = await parent.getChildren({ limit: 10, offset: 10 })
1355
+ * ```
1356
+ *
1357
+ * @example
1358
+ * ```ts
1359
+ * // Use with abort signal for cancellable requests
1360
+ * const controller = new AbortController()
1361
+ * const children = await parent.getChildren({}, { signal: controller.signal })
1362
+ * // Later: controller.abort()
1363
+ * ```
1364
+ */
1365
+ async getChildren(params = {}, options) {
1366
+ if (!this.id) {
1367
+ throw new Error("File ID is not set");
1368
+ }
1369
+ const query = {};
1370
+ if (params.mimeType) {
1371
+ query.mimeType = params.mimeType;
1372
+ }
1373
+ if (params.includeDeleted) {
1374
+ query.includeDeleted = params.includeDeleted ? "true" : "false";
1375
+ }
1376
+ if (params.limit !== void 0) {
1377
+ query.limit = params.limit.toString();
1378
+ }
1379
+ if (params.offset !== void 0) {
1380
+ query.offset = params.offset.toString();
1381
+ }
1382
+ const response = await this._fetch({
1383
+ method: "GET",
1384
+ path: `files/${this.id}/children`,
1385
+ signal: options?.signal,
1386
+ query
1387
+ });
1388
+ return response.files.map((data) => new VaultFile({
1389
+ config: this.config,
1390
+ id: data.id,
1391
+ name: data.originalFileName ?? void 0,
1392
+ metadata: data
1393
+ }));
1394
+ }
1395
+ /**
1396
+ * Iterates through all child files with automatic pagination.
1397
+ *
1398
+ * This method returns an async iterator that automatically fetches pages of children
1399
+ * as you iterate through them. This is useful when you have a large number of children
1400
+ * and want to process them without loading all of them into memory at once.
1401
+ *
1402
+ * @param params - Optional parameters for filtering and pagination
1403
+ * @param params.pageSize - Number of children to fetch per page (default: 100)
1404
+ * @param params.mimeType - Filter children by MIME type (e.g., 'image/png', 'application/pdf')
1405
+ * @param params.includeDeleted - Whether to include deleted child files (default: false)
1406
+ * @param options - Additional options for the request
1407
+ * @param options.signal - AbortSignal to cancel the iteration
1408
+ *
1409
+ * @returns An async iterator that yields VaultFile instances
1410
+ * @throws {Error} If the file ID is not set
1411
+ * @throws {FetchError} If the request to fetch children fails
1412
+ *
1413
+ * @example
1414
+ * ```ts
1415
+ * // Iterate through all children
1416
+ * const parent = await VaultFile.fromVaultReference({
1417
+ * reference: 'vault://parent-id',
1418
+ * config: { vaultUrl, authStrategy }
1419
+ * })
1420
+ *
1421
+ * for await (const child of parent.iterateChildren()) {
1422
+ * console.log(`Processing child: ${child.name}`)
1423
+ * }
1424
+ * ```
1425
+ *
1426
+ * @example
1427
+ * ```ts
1428
+ * // Iterate with filters and custom page size
1429
+ * for await (const child of parent.iterateChildren({
1430
+ * pageSize: 50,
1431
+ * mimeType: 'image/png'
1432
+ * })) {
1433
+ * console.log(`Processing image: ${child.name}`)
1434
+ * }
1435
+ * ```
1436
+ *
1437
+ * @example
1438
+ * ```ts
1439
+ * // Use with abort signal
1440
+ * const controller = new AbortController()
1441
+ * try {
1442
+ * for await (const child of parent.iterateChildren({}, { signal: controller.signal })) {
1443
+ * console.log(child.name)
1444
+ * // Can abort iteration at any time
1445
+ * if (someCondition) {
1446
+ * controller.abort()
1447
+ * }
1448
+ * }
1449
+ * } catch (error) {
1450
+ * if (error.name === 'AbortError') {
1451
+ * console.log('Iteration cancelled')
1452
+ * }
1453
+ * }
1454
+ * ```
1455
+ */
1456
+ async *iterateChildren(params = {}, options) {
1457
+ if (!this.id) {
1458
+ throw new Error("File ID is not set");
1459
+ }
1460
+ const pageSize = params.pageSize ?? 100;
1461
+ let offset = 0;
1462
+ let hasMore = true;
1463
+ while (hasMore) {
1464
+ const children = await this.getChildren({
1465
+ limit: pageSize,
1466
+ offset,
1467
+ mimeType: params.mimeType,
1468
+ includeDeleted: params.includeDeleted
1469
+ }, options);
1470
+ for (const child of children) {
1471
+ yield child;
1472
+ }
1473
+ hasMore = children.length === pageSize;
1474
+ offset += children.length;
1475
+ if (children.length === 0) {
1476
+ hasMore = false;
1477
+ }
1478
+ }
1479
+ }
1480
+ /**
1481
+ * Creates a child file from content with this file as the parent.
1482
+ *
1483
+ * This is a convenience method that wraps {@link VaultFile.fromContent} and automatically
1484
+ * sets the current file as the parent. All parameters and behavior are identical to
1485
+ * {@link VaultFile.fromContent}, except parentId is automatically provided.
1486
+ *
1487
+ * @param params - Parameters for creating the child file (same as fromContent, minus parentId)
1488
+ * @param options - Additional options for the request
1489
+ *
1490
+ * @returns A new VaultFile instance representing the child file
1491
+ * @throws {Error} If the parent file ID is not set
1492
+ * @throws {FetchError} If the child file creation fails
1493
+ *
1494
+ * @example
1495
+ * ```ts
1496
+ * // Create a child file
1497
+ * const parent = await VaultFile.fromContent({
1498
+ * name: 'parent.txt',
1499
+ * content: new Blob(['parent content']),
1500
+ * config: { vaultUrl, authStrategy },
1501
+ * upload: true
1502
+ * })
1503
+ * const child = await parent.createChildFromContent({
1504
+ * name: 'child.txt',
1505
+ * content: new Blob(['child content']),
1506
+ * upload: true
1507
+ * })
1508
+ * console.log('Child created with parent:', child.metadata?.parentId)
1509
+ * ```
1510
+ */
1511
+ async createChildFromContent(params, options) {
1512
+ if (!this.id) {
1513
+ throw new Error("Parent file ID is not set");
1514
+ }
1515
+ return VaultFile.fromContent(
1516
+ {
1517
+ ...params,
1518
+ config: this.config,
1519
+ parentId: this.id
1520
+ },
1521
+ options
1522
+ );
1523
+ }
1524
+ /**
1525
+ * Creates a child file from a stream with this file as the parent.
1526
+ *
1527
+ * This is a convenience method that wraps {@link VaultFile.fromStream} and automatically
1528
+ * sets the current file as the parent. All parameters and behavior are identical to
1529
+ * {@link VaultFile.fromStream}, except parentId is automatically provided.
1530
+ *
1531
+ * @param params - Parameters for creating the child file (same as fromStream, minus parentId)
1532
+ * @param options - Additional options for the request
1533
+ *
1534
+ * @returns A new VaultFile instance ready for streaming upload as a child
1535
+ * @throws {Error} If the parent file ID is not set
1536
+ * @throws {FetchError} If the child file creation fails
1537
+ *
1538
+ * @example
1539
+ * ```ts
1540
+ * // Create a child file for streaming
1541
+ * const parent = await VaultFile.fromContent({
1542
+ * name: 'parent.txt',
1543
+ * content: new Blob(['parent']),
1544
+ * config: { vaultUrl, authStrategy },
1545
+ * upload: true
1546
+ * })
1547
+ * const child = await parent.createChildFromStream({
1548
+ * name: 'large-child.mp4',
1549
+ * contentLength: 100 * 1024 * 1024,
1550
+ * contentType: 'video/mp4'
1551
+ * })
1552
+ * // Upload the stream
1553
+ * const stream = file.stream()
1554
+ * await child.uploadStream(stream, {
1555
+ * contentLength: file.size,
1556
+ * contentType: file.type
1557
+ * })
1558
+ * ```
1559
+ */
1560
+ async createChildFromStream(params, options) {
1561
+ if (!this.id) {
1562
+ throw new Error("Parent file ID is not set");
1563
+ }
1564
+ return VaultFile.fromStream(
1565
+ {
1566
+ ...params,
1567
+ config: this.config,
1568
+ parentId: this.id
1569
+ },
1570
+ options
1571
+ );
1572
+ }
910
1573
  }
911
1574
 
912
1575
  function isS3UrlExpired(url) {
@@ -992,12 +1655,18 @@ function vaultClient(vaultConfig) {
992
1655
  const name2 = nameOrContent;
993
1656
  const content2 = contentOrNameOrOptions;
994
1657
  const signal2 = options?.signal;
995
- return VaultFile.fromContent({ content: content2, name: name2, config }, { signal: signal2 });
1658
+ const parentId2 = options?.parentId;
1659
+ const mimeType2 = options?.mimeType;
1660
+ const upload2 = options?.upload;
1661
+ return VaultFile.fromContent({ content: content2, name: name2, config, parentId: parentId2, mimeType: mimeType2, upload: upload2 }, { signal: signal2 });
996
1662
  }
997
1663
  const content = nameOrContent;
998
1664
  const name = typeof contentOrNameOrOptions === "string" ? contentOrNameOrOptions : void 0;
999
1665
  const signal = isOptionsObject(contentOrNameOrOptions) ? contentOrNameOrOptions.signal : options?.signal;
1000
- return VaultFile.fromContent({ content, name, config }, { signal });
1666
+ const parentId = isOptionsObject(contentOrNameOrOptions) ? contentOrNameOrOptions.parentId : options?.parentId;
1667
+ const mimeType = isOptionsObject(contentOrNameOrOptions) ? contentOrNameOrOptions.mimeType : options?.mimeType;
1668
+ const upload = isOptionsObject(contentOrNameOrOptions) ? contentOrNameOrOptions.upload : options?.upload;
1669
+ return VaultFile.fromContent({ content, name, config, parentId, mimeType, upload }, { signal });
1001
1670
  }
1002
1671
  function createFromReference(reference, options) {
1003
1672
  return VaultFile.fromVaultReference({
@@ -1010,7 +1679,8 @@ function vaultClient(vaultConfig) {
1010
1679
  name,
1011
1680
  contentLength,
1012
1681
  config,
1013
- contentType: options?.contentType
1682
+ contentType: options?.contentType,
1683
+ parentId: options?.parentId
1014
1684
  }, { signal: options?.signal });
1015
1685
  }
1016
1686
  return { createFromContent, createFromReference, createFromStream };