@meistrari/vault-sdk 1.5.0 → 1.6.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.cjs CHANGED
@@ -98,7 +98,7 @@ class Permalink {
98
98
  /**
99
99
  * Get a new permalink instance from its ID.
100
100
  *
101
- * @param config - The vault config.
101
+ * @param vaultConfig - The vault config.
102
102
  * @param id - The permalink ID.
103
103
  * @returns The permalink.
104
104
  */
@@ -116,7 +116,7 @@ class Permalink {
116
116
  /**
117
117
  * Create a new permalink.
118
118
  *
119
- * @param config - The vault config.
119
+ * @param vaultConfig - The vault config.
120
120
  * @param params - The parameters for the permalink.
121
121
  * @param params.expiresIn - Time, in seconds, the permalink will be valid for.
122
122
  * @param params.fileId - The ID of the file to create a permalink for.
@@ -197,11 +197,31 @@ async function detectFileMimeType(blob) {
197
197
  if (result?.mime) {
198
198
  return result.mime;
199
199
  }
200
+ const text = await blob.text();
201
+ const trimmedText = text.trim();
202
+ if (trimmedText.startsWith("{") && trimmedText.endsWith("}") || trimmedText.startsWith("[") && trimmedText.endsWith("]")) {
203
+ try {
204
+ JSON.parse(trimmedText);
205
+ return "application/json";
206
+ } catch {
207
+ }
208
+ }
209
+ const lines = text.split("\n").slice(0, 5).filter((line) => line.trim() !== "");
210
+ if (lines.length > 1) {
211
+ const commaCounts = lines.map((line) => (line.match(/,/g) || []).length);
212
+ const allSame = commaCounts.every((count) => count === commaCounts[0]);
213
+ if (allSame && commaCounts[0] > 0) {
214
+ return "text/csv";
215
+ }
216
+ }
217
+ if (!text.includes("\0") && /^[\s\S]{1,1000}$/.test(text.slice(0, 1e3))) {
218
+ return "text/plain";
219
+ }
200
220
  return void 0;
201
221
  }
202
222
 
203
223
  const name = "@meistrari/vault-sdk";
204
- const version = "1.5.0";
224
+ const version = "1.6.0";
205
225
  const license = "UNLICENSED";
206
226
  const repository = {
207
227
  type: "git",
@@ -273,8 +293,22 @@ const compatibilityDate = "2025-05-19";
273
293
  function removeVaultPrefix(url) {
274
294
  return url.replace("vault://", "");
275
295
  }
276
- async function wrappedFetch(...params) {
277
- const request = new Request(...params);
296
+ function detectMimeTypeFromFilename(filename) {
297
+ const extension = filename.split(".").pop()?.toLowerCase();
298
+ if (extension) {
299
+ const mimeType = mimeTypes.lookup(`.${extension}`);
300
+ if (mimeType) {
301
+ return mimeType;
302
+ }
303
+ }
304
+ return void 0;
305
+ }
306
+ async function wrappedFetch(url, requestInit) {
307
+ const options = {
308
+ ...requestInit,
309
+ duplex: requestInit.body instanceof ReadableStream ? "half" : void 0
310
+ };
311
+ const request = new Request(url, options);
278
312
  request.headers.set("User-Agent", `vault-js-sdk:${packageJson.version}`);
279
313
  const response = await fetch(request);
280
314
  if (!response.ok) {
@@ -284,9 +318,11 @@ async function wrappedFetch(...params) {
284
318
  }
285
319
  class VaultFile {
286
320
  /**
287
- * Constructs a new VaultFile instance. Direct usage of the constructor is not recommended,
288
- * instead use the static methods {@link VaultFile.fromVaultReference} when dealing with an existing file in the vault,
289
- * or {@link VaultFile.fromContent} when preparing a new file for upload.
321
+ * Constructs a new VaultFile instance. Direct usage of the constructor is not recommended.
322
+ *
323
+ * Use the static methods {@link VaultFile.fromVaultReference} when dealing with an existing file in the vault,
324
+ * {@link VaultFile.fromContent} when preparing a new file for upload,
325
+ * or {@link VaultFile.fromStream} when preparing a new file for streaming upload.
290
326
  *
291
327
  * @param params - The parameters for the VaultFile constructor
292
328
  * @param params.config - The configuration for the VaultFile
@@ -342,12 +378,16 @@ class VaultFile {
342
378
  url.searchParams.set(key, value);
343
379
  });
344
380
  }
345
- const response = await wrappedFetch(url, {
381
+ const requestInit = {
346
382
  method,
347
383
  body,
348
384
  headers,
349
385
  signal
350
- });
386
+ };
387
+ if (body && body instanceof ReadableStream) {
388
+ requestInit.duplex = "half";
389
+ }
390
+ const response = await wrappedFetch(url, requestInit);
351
391
  if (response.status === 204 || response.headers.get("content-length") === "0") {
352
392
  return null;
353
393
  }
@@ -520,6 +560,48 @@ class VaultFile {
520
560
  }
521
561
  return file;
522
562
  }
563
+ /**
564
+ * Creates a new VaultFile instance for streaming upload workflows.
565
+ * This method creates a VaultFile with placeholder content that's optimized for streaming uploads.
566
+ *
567
+ * @param params - The parameters for creating a VaultFile for streaming
568
+ * @param params.name - The name of the file
569
+ * @param params.contentLength - The size of the content in bytes
570
+ * @param params.config - The configuration for the VaultFile
571
+ * @param params.contentType - The MIME type of the content (optional)
572
+ * @param options - The options for the request
573
+ * @param options.signal - The signal to abort the request
574
+ *
575
+ * @returns A new VaultFile instance ready for streaming upload
576
+ *
577
+ * @example
578
+ * ```ts
579
+ * // Create VaultFile for streaming
580
+ * const vaultFile = await VaultFile.fromStream({
581
+ * name: 'large-video.mp4',
582
+ * contentLength: 100 * 1024 * 1024, // 100MB
583
+ * contentType: 'video/mp4',
584
+ * config: { vaultUrl, authStrategy }
585
+ * })
586
+ *
587
+ * // Upload using a stream
588
+ * const fileStream = file.stream()
589
+ * await vaultFile.uploadStream(fileStream, {
590
+ * contentLength: file.size,
591
+ * contentType: file.type
592
+ * })
593
+ * ```
594
+ */
595
+ static async fromStream(params, options) {
596
+ const { name, contentLength, config: vaultConfig, contentType } = params;
597
+ const config = resolveConfig(vaultConfig);
598
+ const file = new VaultFile({ config, name });
599
+ await file._createFile({
600
+ size: contentLength,
601
+ mimeType: contentType || "application/octet-stream"
602
+ }, { signal: options?.signal });
603
+ return file;
604
+ }
523
605
  /**
524
606
  * Populates the metadata of the file instance.
525
607
  * @param options - The options for the request
@@ -687,6 +769,90 @@ class VaultFile {
687
769
  return blob;
688
770
  return await blobToBase64(blob);
689
771
  }
772
+ /**
773
+ * Downloads a file from the vault as a stream for memory-efficient processing.
774
+ *
775
+ * @param options - The options for the request
776
+ * @param options.signal - The signal to abort the request
777
+ *
778
+ * @returns A ReadableStream that yields chunks of the file data
779
+ *
780
+ * @example
781
+ * ```ts
782
+ * const vaultFile = await VaultFile.fromVaultReference('vault://1234567890', { vaultUrl, authStrategy })
783
+ * const stream = await vaultFile.downloadStream()
784
+ *
785
+ * // Process the stream chunk by chunk
786
+ * const reader = stream.getReader()
787
+ * try {
788
+ * while (true) {
789
+ * const { done, value } = await reader.read()
790
+ * if (done) break
791
+ * // Process the chunk (Uint8Array)
792
+ * console.log('Received chunk of size:', value.length)
793
+ * }
794
+ * } finally {
795
+ * reader.releaseLock()
796
+ * }
797
+ * ```
798
+ */
799
+ async downloadStream(options) {
800
+ const downloadUrl = await this.getDownloadUrl({ signal: options?.signal });
801
+ const response = await wrappedFetch(downloadUrl, {
802
+ method: "GET",
803
+ signal: options?.signal
804
+ });
805
+ if (!response.body) {
806
+ throw new Error("Response body is not readable");
807
+ }
808
+ return response.body;
809
+ }
810
+ /**
811
+ * Uploads a file to the vault using a stream for memory-efficient processing.
812
+ *
813
+ * @param stream - The readable stream of file data to upload
814
+ * @param options - The options for the request
815
+ * @param options.signal - The signal to abort the request
816
+ * @param options.contentLength - The total size of the content (required for S3 uploads)
817
+ * @param options.contentType - The MIME type of the content (will be detected if not provided)
818
+ *
819
+ * @throws {Error} If contentLength is not provided
820
+ * @throws {FetchError} If the upload fails
821
+ * @returns Promise that resolves when upload is complete
822
+ *
823
+ * @example
824
+ * ```ts
825
+ * const file = new File(['content'], 'document.txt')
826
+ * const vaultFile = await VaultFile.fromStream('document.txt', file.size, {
827
+ * contentType: file.type,
828
+ * config: { vaultUrl, authStrategy }
829
+ * })
830
+ *
831
+ * // Upload using the stream directly
832
+ * const stream = file.stream()
833
+ * await vaultFile.uploadStream(stream, {
834
+ * contentLength: file.size,
835
+ * contentType: file.type
836
+ * })
837
+ * ```
838
+ */
839
+ async uploadStream(stream, options) {
840
+ const { contentLength, contentType, signal } = options;
841
+ if (contentLength === void 0 || contentLength < 0) {
842
+ throw new Error("contentLength must be provided and non-negative for streaming uploads");
843
+ }
844
+ const uploadUrl = await this.getUploadUrl({ signal });
845
+ const mimeType = contentType ?? this.metadata?.mimeType ?? (this.name ? detectMimeTypeFromFilename(this.name) : void 0) ?? "application/octet-stream";
846
+ const headers = new Headers();
847
+ headers.set("Content-Type", mimeType);
848
+ headers.set("Content-Length", contentLength.toString());
849
+ await wrappedFetch(uploadUrl, {
850
+ method: "PUT",
851
+ body: stream,
852
+ headers,
853
+ signal
854
+ });
855
+ }
690
856
  /**
691
857
  * Deletes the file from the vault.
692
858
  * @param options - The options for the request
@@ -833,7 +999,15 @@ function vaultClient(vaultConfig) {
833
999
  config
834
1000
  }, { signal: options?.signal });
835
1001
  }
836
- return { createFromContent, createFromReference };
1002
+ async function createFromStream(name, contentLength, options) {
1003
+ return VaultFile.fromStream({
1004
+ name,
1005
+ contentLength,
1006
+ config,
1007
+ contentType: options?.contentType
1008
+ }, { signal: options?.signal });
1009
+ }
1010
+ return { createFromContent, createFromReference, createFromStream };
837
1011
  }
838
1012
 
839
1013
  exports.APIKeyAuthStrategy = APIKeyAuthStrategy;
package/dist/index.d.cts CHANGED
@@ -45,7 +45,7 @@ declare class Permalink {
45
45
  /**
46
46
  * Get a new permalink instance from its ID.
47
47
  *
48
- * @param config - The vault config.
48
+ * @param vaultConfig - The vault config.
49
49
  * @param id - The permalink ID.
50
50
  * @returns The permalink.
51
51
  */
@@ -53,7 +53,7 @@ declare class Permalink {
53
53
  /**
54
54
  * Create a new permalink.
55
55
  *
56
- * @param config - The vault config.
56
+ * @param vaultConfig - The vault config.
57
57
  * @param params - The parameters for the permalink.
58
58
  * @param params.expiresIn - Time, in seconds, the permalink will be valid for.
59
59
  * @param params.fileId - The ID of the file to create a permalink for.
@@ -114,9 +114,11 @@ declare class VaultFile {
114
114
  } | undefined;
115
115
  private readonly baseUrl;
116
116
  /**
117
- * Constructs a new VaultFile instance. Direct usage of the constructor is not recommended,
118
- * instead use the static methods {@link VaultFile.fromVaultReference} when dealing with an existing file in the vault,
119
- * or {@link VaultFile.fromContent} when preparing a new file for upload.
117
+ * Constructs a new VaultFile instance. Direct usage of the constructor is not recommended.
118
+ *
119
+ * Use the static methods {@link VaultFile.fromVaultReference} when dealing with an existing file in the vault,
120
+ * {@link VaultFile.fromContent} when preparing a new file for upload,
121
+ * or {@link VaultFile.fromStream} when preparing a new file for streaming upload.
120
122
  *
121
123
  * @param params - The parameters for the VaultFile constructor
122
124
  * @param params.config - The configuration for the VaultFile
@@ -254,6 +256,46 @@ declare class VaultFile {
254
256
  }, options?: {
255
257
  signal?: AbortSignal;
256
258
  }): Promise<VaultFile>;
259
+ /**
260
+ * Creates a new VaultFile instance for streaming upload workflows.
261
+ * This method creates a VaultFile with placeholder content that's optimized for streaming uploads.
262
+ *
263
+ * @param params - The parameters for creating a VaultFile for streaming
264
+ * @param params.name - The name of the file
265
+ * @param params.contentLength - The size of the content in bytes
266
+ * @param params.config - The configuration for the VaultFile
267
+ * @param params.contentType - The MIME type of the content (optional)
268
+ * @param options - The options for the request
269
+ * @param options.signal - The signal to abort the request
270
+ *
271
+ * @returns A new VaultFile instance ready for streaming upload
272
+ *
273
+ * @example
274
+ * ```ts
275
+ * // Create VaultFile for streaming
276
+ * const vaultFile = await VaultFile.fromStream({
277
+ * name: 'large-video.mp4',
278
+ * contentLength: 100 * 1024 * 1024, // 100MB
279
+ * contentType: 'video/mp4',
280
+ * config: { vaultUrl, authStrategy }
281
+ * })
282
+ *
283
+ * // Upload using a stream
284
+ * const fileStream = file.stream()
285
+ * await vaultFile.uploadStream(fileStream, {
286
+ * contentLength: file.size,
287
+ * contentType: file.type
288
+ * })
289
+ * ```
290
+ */
291
+ static fromStream(params: {
292
+ name: string;
293
+ contentLength: number;
294
+ config: VaultConfig;
295
+ contentType?: string;
296
+ }, options?: {
297
+ signal?: AbortSignal;
298
+ }): Promise<VaultFile>;
257
299
  /**
258
300
  * Populates the metadata of the file instance.
259
301
  * @param options - The options for the request
@@ -364,6 +406,70 @@ declare class VaultFile {
364
406
  download(responseType: 'base64', options?: {
365
407
  signal?: AbortSignal;
366
408
  }): Promise<string>;
409
+ /**
410
+ * Downloads a file from the vault as a stream for memory-efficient processing.
411
+ *
412
+ * @param options - The options for the request
413
+ * @param options.signal - The signal to abort the request
414
+ *
415
+ * @returns A ReadableStream that yields chunks of the file data
416
+ *
417
+ * @example
418
+ * ```ts
419
+ * const vaultFile = await VaultFile.fromVaultReference('vault://1234567890', { vaultUrl, authStrategy })
420
+ * const stream = await vaultFile.downloadStream()
421
+ *
422
+ * // Process the stream chunk by chunk
423
+ * const reader = stream.getReader()
424
+ * try {
425
+ * while (true) {
426
+ * const { done, value } = await reader.read()
427
+ * if (done) break
428
+ * // Process the chunk (Uint8Array)
429
+ * console.log('Received chunk of size:', value.length)
430
+ * }
431
+ * } finally {
432
+ * reader.releaseLock()
433
+ * }
434
+ * ```
435
+ */
436
+ downloadStream(options?: {
437
+ signal?: AbortSignal;
438
+ }): Promise<ReadableStream<Uint8Array>>;
439
+ /**
440
+ * Uploads a file to the vault using a stream for memory-efficient processing.
441
+ *
442
+ * @param stream - The readable stream of file data to upload
443
+ * @param options - The options for the request
444
+ * @param options.signal - The signal to abort the request
445
+ * @param options.contentLength - The total size of the content (required for S3 uploads)
446
+ * @param options.contentType - The MIME type of the content (will be detected if not provided)
447
+ *
448
+ * @throws {Error} If contentLength is not provided
449
+ * @throws {FetchError} If the upload fails
450
+ * @returns Promise that resolves when upload is complete
451
+ *
452
+ * @example
453
+ * ```ts
454
+ * const file = new File(['content'], 'document.txt')
455
+ * const vaultFile = await VaultFile.fromStream('document.txt', file.size, {
456
+ * contentType: file.type,
457
+ * config: { vaultUrl, authStrategy }
458
+ * })
459
+ *
460
+ * // Upload using the stream directly
461
+ * const stream = file.stream()
462
+ * await vaultFile.uploadStream(stream, {
463
+ * contentLength: file.size,
464
+ * contentType: file.type
465
+ * })
466
+ * ```
467
+ */
468
+ uploadStream(stream: ReadableStream<Uint8Array>, options: {
469
+ signal?: AbortSignal;
470
+ contentLength: number;
471
+ contentType?: string;
472
+ }): Promise<void>;
367
473
  /**
368
474
  * Deletes the file from the vault.
369
475
  * @param options - The options for the request
@@ -436,6 +542,10 @@ declare function vaultClient(vaultConfig: VaultConfig): {
436
542
  createFromReference: (reference: string, options?: {
437
543
  signal?: AbortSignal;
438
544
  }) => Promise<VaultFile>;
545
+ createFromStream: (name: string, contentLength: number, options?: {
546
+ contentType?: string;
547
+ signal?: AbortSignal;
548
+ }) => Promise<VaultFile>;
439
549
  };
440
550
 
441
551
  export { APIKeyAuthStrategy, DataTokenAuthStrategy, FetchError, VaultFile, convertS3UrlToVaultReference, extractVaultFileIdFromS3Url, isS3UrlExpired, isVaultFileS3Url, vaultClient };
package/dist/index.d.mts CHANGED
@@ -45,7 +45,7 @@ declare class Permalink {
45
45
  /**
46
46
  * Get a new permalink instance from its ID.
47
47
  *
48
- * @param config - The vault config.
48
+ * @param vaultConfig - The vault config.
49
49
  * @param id - The permalink ID.
50
50
  * @returns The permalink.
51
51
  */
@@ -53,7 +53,7 @@ declare class Permalink {
53
53
  /**
54
54
  * Create a new permalink.
55
55
  *
56
- * @param config - The vault config.
56
+ * @param vaultConfig - The vault config.
57
57
  * @param params - The parameters for the permalink.
58
58
  * @param params.expiresIn - Time, in seconds, the permalink will be valid for.
59
59
  * @param params.fileId - The ID of the file to create a permalink for.
@@ -114,9 +114,11 @@ declare class VaultFile {
114
114
  } | undefined;
115
115
  private readonly baseUrl;
116
116
  /**
117
- * Constructs a new VaultFile instance. Direct usage of the constructor is not recommended,
118
- * instead use the static methods {@link VaultFile.fromVaultReference} when dealing with an existing file in the vault,
119
- * or {@link VaultFile.fromContent} when preparing a new file for upload.
117
+ * Constructs a new VaultFile instance. Direct usage of the constructor is not recommended.
118
+ *
119
+ * Use the static methods {@link VaultFile.fromVaultReference} when dealing with an existing file in the vault,
120
+ * {@link VaultFile.fromContent} when preparing a new file for upload,
121
+ * or {@link VaultFile.fromStream} when preparing a new file for streaming upload.
120
122
  *
121
123
  * @param params - The parameters for the VaultFile constructor
122
124
  * @param params.config - The configuration for the VaultFile
@@ -254,6 +256,46 @@ declare class VaultFile {
254
256
  }, options?: {
255
257
  signal?: AbortSignal;
256
258
  }): Promise<VaultFile>;
259
+ /**
260
+ * Creates a new VaultFile instance for streaming upload workflows.
261
+ * This method creates a VaultFile with placeholder content that's optimized for streaming uploads.
262
+ *
263
+ * @param params - The parameters for creating a VaultFile for streaming
264
+ * @param params.name - The name of the file
265
+ * @param params.contentLength - The size of the content in bytes
266
+ * @param params.config - The configuration for the VaultFile
267
+ * @param params.contentType - The MIME type of the content (optional)
268
+ * @param options - The options for the request
269
+ * @param options.signal - The signal to abort the request
270
+ *
271
+ * @returns A new VaultFile instance ready for streaming upload
272
+ *
273
+ * @example
274
+ * ```ts
275
+ * // Create VaultFile for streaming
276
+ * const vaultFile = await VaultFile.fromStream({
277
+ * name: 'large-video.mp4',
278
+ * contentLength: 100 * 1024 * 1024, // 100MB
279
+ * contentType: 'video/mp4',
280
+ * config: { vaultUrl, authStrategy }
281
+ * })
282
+ *
283
+ * // Upload using a stream
284
+ * const fileStream = file.stream()
285
+ * await vaultFile.uploadStream(fileStream, {
286
+ * contentLength: file.size,
287
+ * contentType: file.type
288
+ * })
289
+ * ```
290
+ */
291
+ static fromStream(params: {
292
+ name: string;
293
+ contentLength: number;
294
+ config: VaultConfig;
295
+ contentType?: string;
296
+ }, options?: {
297
+ signal?: AbortSignal;
298
+ }): Promise<VaultFile>;
257
299
  /**
258
300
  * Populates the metadata of the file instance.
259
301
  * @param options - The options for the request
@@ -364,6 +406,70 @@ declare class VaultFile {
364
406
  download(responseType: 'base64', options?: {
365
407
  signal?: AbortSignal;
366
408
  }): Promise<string>;
409
+ /**
410
+ * Downloads a file from the vault as a stream for memory-efficient processing.
411
+ *
412
+ * @param options - The options for the request
413
+ * @param options.signal - The signal to abort the request
414
+ *
415
+ * @returns A ReadableStream that yields chunks of the file data
416
+ *
417
+ * @example
418
+ * ```ts
419
+ * const vaultFile = await VaultFile.fromVaultReference('vault://1234567890', { vaultUrl, authStrategy })
420
+ * const stream = await vaultFile.downloadStream()
421
+ *
422
+ * // Process the stream chunk by chunk
423
+ * const reader = stream.getReader()
424
+ * try {
425
+ * while (true) {
426
+ * const { done, value } = await reader.read()
427
+ * if (done) break
428
+ * // Process the chunk (Uint8Array)
429
+ * console.log('Received chunk of size:', value.length)
430
+ * }
431
+ * } finally {
432
+ * reader.releaseLock()
433
+ * }
434
+ * ```
435
+ */
436
+ downloadStream(options?: {
437
+ signal?: AbortSignal;
438
+ }): Promise<ReadableStream<Uint8Array>>;
439
+ /**
440
+ * Uploads a file to the vault using a stream for memory-efficient processing.
441
+ *
442
+ * @param stream - The readable stream of file data to upload
443
+ * @param options - The options for the request
444
+ * @param options.signal - The signal to abort the request
445
+ * @param options.contentLength - The total size of the content (required for S3 uploads)
446
+ * @param options.contentType - The MIME type of the content (will be detected if not provided)
447
+ *
448
+ * @throws {Error} If contentLength is not provided
449
+ * @throws {FetchError} If the upload fails
450
+ * @returns Promise that resolves when upload is complete
451
+ *
452
+ * @example
453
+ * ```ts
454
+ * const file = new File(['content'], 'document.txt')
455
+ * const vaultFile = await VaultFile.fromStream('document.txt', file.size, {
456
+ * contentType: file.type,
457
+ * config: { vaultUrl, authStrategy }
458
+ * })
459
+ *
460
+ * // Upload using the stream directly
461
+ * const stream = file.stream()
462
+ * await vaultFile.uploadStream(stream, {
463
+ * contentLength: file.size,
464
+ * contentType: file.type
465
+ * })
466
+ * ```
467
+ */
468
+ uploadStream(stream: ReadableStream<Uint8Array>, options: {
469
+ signal?: AbortSignal;
470
+ contentLength: number;
471
+ contentType?: string;
472
+ }): Promise<void>;
367
473
  /**
368
474
  * Deletes the file from the vault.
369
475
  * @param options - The options for the request
@@ -436,6 +542,10 @@ declare function vaultClient(vaultConfig: VaultConfig): {
436
542
  createFromReference: (reference: string, options?: {
437
543
  signal?: AbortSignal;
438
544
  }) => Promise<VaultFile>;
545
+ createFromStream: (name: string, contentLength: number, options?: {
546
+ contentType?: string;
547
+ signal?: AbortSignal;
548
+ }) => Promise<VaultFile>;
439
549
  };
440
550
 
441
551
  export { APIKeyAuthStrategy, DataTokenAuthStrategy, FetchError, VaultFile, convertS3UrlToVaultReference, extractVaultFileIdFromS3Url, isS3UrlExpired, isVaultFileS3Url, vaultClient };
package/dist/index.d.ts CHANGED
@@ -45,7 +45,7 @@ declare class Permalink {
45
45
  /**
46
46
  * Get a new permalink instance from its ID.
47
47
  *
48
- * @param config - The vault config.
48
+ * @param vaultConfig - The vault config.
49
49
  * @param id - The permalink ID.
50
50
  * @returns The permalink.
51
51
  */
@@ -53,7 +53,7 @@ declare class Permalink {
53
53
  /**
54
54
  * Create a new permalink.
55
55
  *
56
- * @param config - The vault config.
56
+ * @param vaultConfig - The vault config.
57
57
  * @param params - The parameters for the permalink.
58
58
  * @param params.expiresIn - Time, in seconds, the permalink will be valid for.
59
59
  * @param params.fileId - The ID of the file to create a permalink for.
@@ -114,9 +114,11 @@ declare class VaultFile {
114
114
  } | undefined;
115
115
  private readonly baseUrl;
116
116
  /**
117
- * Constructs a new VaultFile instance. Direct usage of the constructor is not recommended,
118
- * instead use the static methods {@link VaultFile.fromVaultReference} when dealing with an existing file in the vault,
119
- * or {@link VaultFile.fromContent} when preparing a new file for upload.
117
+ * Constructs a new VaultFile instance. Direct usage of the constructor is not recommended.
118
+ *
119
+ * Use the static methods {@link VaultFile.fromVaultReference} when dealing with an existing file in the vault,
120
+ * {@link VaultFile.fromContent} when preparing a new file for upload,
121
+ * or {@link VaultFile.fromStream} when preparing a new file for streaming upload.
120
122
  *
121
123
  * @param params - The parameters for the VaultFile constructor
122
124
  * @param params.config - The configuration for the VaultFile
@@ -254,6 +256,46 @@ declare class VaultFile {
254
256
  }, options?: {
255
257
  signal?: AbortSignal;
256
258
  }): Promise<VaultFile>;
259
+ /**
260
+ * Creates a new VaultFile instance for streaming upload workflows.
261
+ * This method creates a VaultFile with placeholder content that's optimized for streaming uploads.
262
+ *
263
+ * @param params - The parameters for creating a VaultFile for streaming
264
+ * @param params.name - The name of the file
265
+ * @param params.contentLength - The size of the content in bytes
266
+ * @param params.config - The configuration for the VaultFile
267
+ * @param params.contentType - The MIME type of the content (optional)
268
+ * @param options - The options for the request
269
+ * @param options.signal - The signal to abort the request
270
+ *
271
+ * @returns A new VaultFile instance ready for streaming upload
272
+ *
273
+ * @example
274
+ * ```ts
275
+ * // Create VaultFile for streaming
276
+ * const vaultFile = await VaultFile.fromStream({
277
+ * name: 'large-video.mp4',
278
+ * contentLength: 100 * 1024 * 1024, // 100MB
279
+ * contentType: 'video/mp4',
280
+ * config: { vaultUrl, authStrategy }
281
+ * })
282
+ *
283
+ * // Upload using a stream
284
+ * const fileStream = file.stream()
285
+ * await vaultFile.uploadStream(fileStream, {
286
+ * contentLength: file.size,
287
+ * contentType: file.type
288
+ * })
289
+ * ```
290
+ */
291
+ static fromStream(params: {
292
+ name: string;
293
+ contentLength: number;
294
+ config: VaultConfig;
295
+ contentType?: string;
296
+ }, options?: {
297
+ signal?: AbortSignal;
298
+ }): Promise<VaultFile>;
257
299
  /**
258
300
  * Populates the metadata of the file instance.
259
301
  * @param options - The options for the request
@@ -364,6 +406,70 @@ declare class VaultFile {
364
406
  download(responseType: 'base64', options?: {
365
407
  signal?: AbortSignal;
366
408
  }): Promise<string>;
409
+ /**
410
+ * Downloads a file from the vault as a stream for memory-efficient processing.
411
+ *
412
+ * @param options - The options for the request
413
+ * @param options.signal - The signal to abort the request
414
+ *
415
+ * @returns A ReadableStream that yields chunks of the file data
416
+ *
417
+ * @example
418
+ * ```ts
419
+ * const vaultFile = await VaultFile.fromVaultReference('vault://1234567890', { vaultUrl, authStrategy })
420
+ * const stream = await vaultFile.downloadStream()
421
+ *
422
+ * // Process the stream chunk by chunk
423
+ * const reader = stream.getReader()
424
+ * try {
425
+ * while (true) {
426
+ * const { done, value } = await reader.read()
427
+ * if (done) break
428
+ * // Process the chunk (Uint8Array)
429
+ * console.log('Received chunk of size:', value.length)
430
+ * }
431
+ * } finally {
432
+ * reader.releaseLock()
433
+ * }
434
+ * ```
435
+ */
436
+ downloadStream(options?: {
437
+ signal?: AbortSignal;
438
+ }): Promise<ReadableStream<Uint8Array>>;
439
+ /**
440
+ * Uploads a file to the vault using a stream for memory-efficient processing.
441
+ *
442
+ * @param stream - The readable stream of file data to upload
443
+ * @param options - The options for the request
444
+ * @param options.signal - The signal to abort the request
445
+ * @param options.contentLength - The total size of the content (required for S3 uploads)
446
+ * @param options.contentType - The MIME type of the content (will be detected if not provided)
447
+ *
448
+ * @throws {Error} If contentLength is not provided
449
+ * @throws {FetchError} If the upload fails
450
+ * @returns Promise that resolves when upload is complete
451
+ *
452
+ * @example
453
+ * ```ts
454
+ * const file = new File(['content'], 'document.txt')
455
+ * const vaultFile = await VaultFile.fromStream('document.txt', file.size, {
456
+ * contentType: file.type,
457
+ * config: { vaultUrl, authStrategy }
458
+ * })
459
+ *
460
+ * // Upload using the stream directly
461
+ * const stream = file.stream()
462
+ * await vaultFile.uploadStream(stream, {
463
+ * contentLength: file.size,
464
+ * contentType: file.type
465
+ * })
466
+ * ```
467
+ */
468
+ uploadStream(stream: ReadableStream<Uint8Array>, options: {
469
+ signal?: AbortSignal;
470
+ contentLength: number;
471
+ contentType?: string;
472
+ }): Promise<void>;
367
473
  /**
368
474
  * Deletes the file from the vault.
369
475
  * @param options - The options for the request
@@ -436,6 +542,10 @@ declare function vaultClient(vaultConfig: VaultConfig): {
436
542
  createFromReference: (reference: string, options?: {
437
543
  signal?: AbortSignal;
438
544
  }) => Promise<VaultFile>;
545
+ createFromStream: (name: string, contentLength: number, options?: {
546
+ contentType?: string;
547
+ signal?: AbortSignal;
548
+ }) => Promise<VaultFile>;
439
549
  };
440
550
 
441
551
  export { APIKeyAuthStrategy, DataTokenAuthStrategy, FetchError, VaultFile, convertS3UrlToVaultReference, extractVaultFileIdFromS3Url, isS3UrlExpired, isVaultFileS3Url, vaultClient };
package/dist/index.mjs CHANGED
@@ -96,7 +96,7 @@ class Permalink {
96
96
  /**
97
97
  * Get a new permalink instance from its ID.
98
98
  *
99
- * @param config - The vault config.
99
+ * @param vaultConfig - The vault config.
100
100
  * @param id - The permalink ID.
101
101
  * @returns The permalink.
102
102
  */
@@ -114,7 +114,7 @@ class Permalink {
114
114
  /**
115
115
  * Create a new permalink.
116
116
  *
117
- * @param config - The vault config.
117
+ * @param vaultConfig - The vault config.
118
118
  * @param params - The parameters for the permalink.
119
119
  * @param params.expiresIn - Time, in seconds, the permalink will be valid for.
120
120
  * @param params.fileId - The ID of the file to create a permalink for.
@@ -195,11 +195,31 @@ async function detectFileMimeType(blob) {
195
195
  if (result?.mime) {
196
196
  return result.mime;
197
197
  }
198
+ const text = await blob.text();
199
+ const trimmedText = text.trim();
200
+ if (trimmedText.startsWith("{") && trimmedText.endsWith("}") || trimmedText.startsWith("[") && trimmedText.endsWith("]")) {
201
+ try {
202
+ JSON.parse(trimmedText);
203
+ return "application/json";
204
+ } catch {
205
+ }
206
+ }
207
+ const lines = text.split("\n").slice(0, 5).filter((line) => line.trim() !== "");
208
+ if (lines.length > 1) {
209
+ const commaCounts = lines.map((line) => (line.match(/,/g) || []).length);
210
+ const allSame = commaCounts.every((count) => count === commaCounts[0]);
211
+ if (allSame && commaCounts[0] > 0) {
212
+ return "text/csv";
213
+ }
214
+ }
215
+ if (!text.includes("\0") && /^[\s\S]{1,1000}$/.test(text.slice(0, 1e3))) {
216
+ return "text/plain";
217
+ }
198
218
  return void 0;
199
219
  }
200
220
 
201
221
  const name = "@meistrari/vault-sdk";
202
- const version = "1.5.0";
222
+ const version = "1.6.0";
203
223
  const license = "UNLICENSED";
204
224
  const repository = {
205
225
  type: "git",
@@ -271,8 +291,22 @@ const compatibilityDate = "2025-05-19";
271
291
  function removeVaultPrefix(url) {
272
292
  return url.replace("vault://", "");
273
293
  }
274
- async function wrappedFetch(...params) {
275
- const request = new Request(...params);
294
+ function detectMimeTypeFromFilename(filename) {
295
+ const extension = filename.split(".").pop()?.toLowerCase();
296
+ if (extension) {
297
+ const mimeType = lookup(`.${extension}`);
298
+ if (mimeType) {
299
+ return mimeType;
300
+ }
301
+ }
302
+ return void 0;
303
+ }
304
+ async function wrappedFetch(url, requestInit) {
305
+ const options = {
306
+ ...requestInit,
307
+ duplex: requestInit.body instanceof ReadableStream ? "half" : void 0
308
+ };
309
+ const request = new Request(url, options);
276
310
  request.headers.set("User-Agent", `vault-js-sdk:${packageJson.version}`);
277
311
  const response = await fetch(request);
278
312
  if (!response.ok) {
@@ -282,9 +316,11 @@ async function wrappedFetch(...params) {
282
316
  }
283
317
  class VaultFile {
284
318
  /**
285
- * Constructs a new VaultFile instance. Direct usage of the constructor is not recommended,
286
- * instead use the static methods {@link VaultFile.fromVaultReference} when dealing with an existing file in the vault,
287
- * or {@link VaultFile.fromContent} when preparing a new file for upload.
319
+ * Constructs a new VaultFile instance. Direct usage of the constructor is not recommended.
320
+ *
321
+ * Use the static methods {@link VaultFile.fromVaultReference} when dealing with an existing file in the vault,
322
+ * {@link VaultFile.fromContent} when preparing a new file for upload,
323
+ * or {@link VaultFile.fromStream} when preparing a new file for streaming upload.
288
324
  *
289
325
  * @param params - The parameters for the VaultFile constructor
290
326
  * @param params.config - The configuration for the VaultFile
@@ -340,12 +376,16 @@ class VaultFile {
340
376
  url.searchParams.set(key, value);
341
377
  });
342
378
  }
343
- const response = await wrappedFetch(url, {
379
+ const requestInit = {
344
380
  method,
345
381
  body,
346
382
  headers,
347
383
  signal
348
- });
384
+ };
385
+ if (body && body instanceof ReadableStream) {
386
+ requestInit.duplex = "half";
387
+ }
388
+ const response = await wrappedFetch(url, requestInit);
349
389
  if (response.status === 204 || response.headers.get("content-length") === "0") {
350
390
  return null;
351
391
  }
@@ -518,6 +558,48 @@ class VaultFile {
518
558
  }
519
559
  return file;
520
560
  }
561
+ /**
562
+ * Creates a new VaultFile instance for streaming upload workflows.
563
+ * This method creates a VaultFile with placeholder content that's optimized for streaming uploads.
564
+ *
565
+ * @param params - The parameters for creating a VaultFile for streaming
566
+ * @param params.name - The name of the file
567
+ * @param params.contentLength - The size of the content in bytes
568
+ * @param params.config - The configuration for the VaultFile
569
+ * @param params.contentType - The MIME type of the content (optional)
570
+ * @param options - The options for the request
571
+ * @param options.signal - The signal to abort the request
572
+ *
573
+ * @returns A new VaultFile instance ready for streaming upload
574
+ *
575
+ * @example
576
+ * ```ts
577
+ * // Create VaultFile for streaming
578
+ * const vaultFile = await VaultFile.fromStream({
579
+ * name: 'large-video.mp4',
580
+ * contentLength: 100 * 1024 * 1024, // 100MB
581
+ * contentType: 'video/mp4',
582
+ * config: { vaultUrl, authStrategy }
583
+ * })
584
+ *
585
+ * // Upload using a stream
586
+ * const fileStream = file.stream()
587
+ * await vaultFile.uploadStream(fileStream, {
588
+ * contentLength: file.size,
589
+ * contentType: file.type
590
+ * })
591
+ * ```
592
+ */
593
+ static async fromStream(params, options) {
594
+ const { name, contentLength, config: vaultConfig, contentType } = params;
595
+ const config = resolveConfig(vaultConfig);
596
+ const file = new VaultFile({ config, name });
597
+ await file._createFile({
598
+ size: contentLength,
599
+ mimeType: contentType || "application/octet-stream"
600
+ }, { signal: options?.signal });
601
+ return file;
602
+ }
521
603
  /**
522
604
  * Populates the metadata of the file instance.
523
605
  * @param options - The options for the request
@@ -685,6 +767,90 @@ class VaultFile {
685
767
  return blob;
686
768
  return await blobToBase64(blob);
687
769
  }
770
+ /**
771
+ * Downloads a file from the vault as a stream for memory-efficient processing.
772
+ *
773
+ * @param options - The options for the request
774
+ * @param options.signal - The signal to abort the request
775
+ *
776
+ * @returns A ReadableStream that yields chunks of the file data
777
+ *
778
+ * @example
779
+ * ```ts
780
+ * const vaultFile = await VaultFile.fromVaultReference('vault://1234567890', { vaultUrl, authStrategy })
781
+ * const stream = await vaultFile.downloadStream()
782
+ *
783
+ * // Process the stream chunk by chunk
784
+ * const reader = stream.getReader()
785
+ * try {
786
+ * while (true) {
787
+ * const { done, value } = await reader.read()
788
+ * if (done) break
789
+ * // Process the chunk (Uint8Array)
790
+ * console.log('Received chunk of size:', value.length)
791
+ * }
792
+ * } finally {
793
+ * reader.releaseLock()
794
+ * }
795
+ * ```
796
+ */
797
+ async downloadStream(options) {
798
+ const downloadUrl = await this.getDownloadUrl({ signal: options?.signal });
799
+ const response = await wrappedFetch(downloadUrl, {
800
+ method: "GET",
801
+ signal: options?.signal
802
+ });
803
+ if (!response.body) {
804
+ throw new Error("Response body is not readable");
805
+ }
806
+ return response.body;
807
+ }
808
+ /**
809
+ * Uploads a file to the vault using a stream for memory-efficient processing.
810
+ *
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)
816
+ *
817
+ * @throws {Error} If contentLength is not provided
818
+ * @throws {FetchError} If the upload fails
819
+ * @returns Promise that resolves when upload is complete
820
+ *
821
+ * @example
822
+ * ```ts
823
+ * const file = new File(['content'], 'document.txt')
824
+ * const vaultFile = await VaultFile.fromStream('document.txt', file.size, {
825
+ * contentType: file.type,
826
+ * config: { vaultUrl, authStrategy }
827
+ * })
828
+ *
829
+ * // Upload using the stream directly
830
+ * const stream = file.stream()
831
+ * await vaultFile.uploadStream(stream, {
832
+ * contentLength: file.size,
833
+ * contentType: file.type
834
+ * })
835
+ * ```
836
+ */
837
+ async uploadStream(stream, options) {
838
+ const { contentLength, contentType, signal } = options;
839
+ if (contentLength === void 0 || contentLength < 0) {
840
+ throw new Error("contentLength must be provided and non-negative for streaming uploads");
841
+ }
842
+ const uploadUrl = await this.getUploadUrl({ signal });
843
+ const mimeType = contentType ?? this.metadata?.mimeType ?? (this.name ? detectMimeTypeFromFilename(this.name) : void 0) ?? "application/octet-stream";
844
+ const headers = new Headers();
845
+ headers.set("Content-Type", mimeType);
846
+ headers.set("Content-Length", contentLength.toString());
847
+ await wrappedFetch(uploadUrl, {
848
+ method: "PUT",
849
+ body: stream,
850
+ headers,
851
+ signal
852
+ });
853
+ }
688
854
  /**
689
855
  * Deletes the file from the vault.
690
856
  * @param options - The options for the request
@@ -831,7 +997,15 @@ function vaultClient(vaultConfig) {
831
997
  config
832
998
  }, { signal: options?.signal });
833
999
  }
834
- return { createFromContent, createFromReference };
1000
+ async function createFromStream(name, contentLength, options) {
1001
+ return VaultFile.fromStream({
1002
+ name,
1003
+ contentLength,
1004
+ config,
1005
+ contentType: options?.contentType
1006
+ }, { signal: options?.signal });
1007
+ }
1008
+ return { createFromContent, createFromReference, createFromStream };
835
1009
  }
836
1010
 
837
1011
  export { APIKeyAuthStrategy, DataTokenAuthStrategy, FetchError, VaultFile, convertS3UrlToVaultReference, extractVaultFileIdFromS3Url, isS3UrlExpired, isVaultFileS3Url, vaultClient };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@meistrari/vault-sdk",
3
- "version": "1.5.0",
3
+ "version": "1.6.0",
4
4
  "license": "UNLICENSED",
5
5
  "repository": {
6
6
  "type": "git",