lean-s3 0.7.0 → 0.7.2

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
@@ -67,6 +67,27 @@ interface S3FilePresignOptions {
67
67
  acl?: Acl;
68
68
  /** `Content-Type` of the file. */
69
69
  type?: string;
70
+ /**
71
+ * Headers to set on the response of the S3 service.
72
+ */
73
+ response?: {
74
+ /**
75
+ * Used to set the file name that browsers display when downloading the file.
76
+ *
77
+ * @example
78
+ * ```js
79
+ * client.presign("foo.jpg", {
80
+ * response: {
81
+ * contentDisposition: {
82
+ * type: "attachment",
83
+ * filename: "download.jpg",
84
+ * },
85
+ * },
86
+ * });
87
+ * ```
88
+ */
89
+ contentDisposition?: ContentDisposition;
90
+ };
70
91
  }
71
92
  type ListObjectsOptions = {
72
93
  bucket?: string;
@@ -311,9 +332,22 @@ declare class S3Client {
311
332
  * expiresIn: 3600 // 1 hour
312
333
  * });
313
334
  * ```
335
+ *
336
+ * @example
337
+ * ```js
338
+ * client.presign("foo.jpg", {
339
+ * expiresIn: 3600 // 1 hour
340
+ * response: {
341
+ * contentDisposition: {
342
+ * type: "attachment",
343
+ * filename: "download.jpg",
344
+ * },
345
+ * },
346
+ * });
347
+ * ```
314
348
  */
315
349
  presign(path: string, { method, expiresIn, // TODO: Maybe rename this to expiresInSeconds
316
- storageClass, contentLength, type, acl, region: regionOverride, bucket: bucketOverride, endpoint: endpointOverride, }?: S3FilePresignOptions & OverridableS3ClientOptions): string;
350
+ storageClass, contentLength, type, acl, region: regionOverride, bucket: bucketOverride, endpoint: endpointOverride, response, }?: S3FilePresignOptions & OverridableS3ClientOptions): string;
317
351
  createMultipartUpload(key: string, options?: CreateMultipartUploadOptions): Promise<CreateMultipartUploadResult>;
318
352
  /**
319
353
  * @remarks Uses [`ListMultipartUploads`](https://docs.aws.amazon.com/AmazonS3/latest/API/API_ListMultipartUploads.html).
@@ -538,5 +572,16 @@ type BucketInfo = {
538
572
  dataRedundancy?: string;
539
573
  type?: string;
540
574
  };
575
+ /**
576
+ * Represents valid values for the [`Content-Disposition`](https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Headers/Content-Disposition) header.
577
+ */
578
+ type ContentDisposition = AttachmentContentDisposition | InlineContentDisposition;
579
+ type InlineContentDisposition = {
580
+ type: "inline";
581
+ };
582
+ type AttachmentContentDisposition = {
583
+ type: "attachment";
584
+ filename?: string;
585
+ };
541
586
 
542
- export { type AbortMultipartUploadOptions, type Acl, type BucketCorsRule, type BucketCorsRules, type BucketCreationOptions, type BucketDeletionOptions, type BucketExistsOptions, type BucketInfo, type BucketLocationInfo, type ByteSource, type ChecksumAlgorithm, type ChecksumType, type CompleteMultipartUploadOptions, type CompleteMultipartUploadResult, type CreateFileInstanceOptions, type CreateMultipartUploadOptions, type CreateMultipartUploadResult, type DeleteBucketCorsOptions, type DeleteObjectsError, type DeleteObjectsOptions, type DeleteObjectsResult, type GetBucketCorsOptions, type GetBucketCorsResult, type HttpMethod, type ListMultipartUploadsOptions, type ListMultipartUploadsResult, type ListObjectsIteratingOptions, type ListObjectsOptions, type ListObjectsResult, type ListPartsOptions, type ListPartsResult, type MultipartUpload, type MultipartUploadPart, type OverridableS3ClientOptions, type PresignableHttpMethod, type PutBucketCorsOptions, S3BucketEntry, S3Client, type S3ClientOptions, S3Error, type S3ErrorOptions, S3File, type S3FileDeleteOptions, type S3FileExistsOptions, type S3FilePresignOptions, type S3FileWriteOptions, S3Stat, type S3StatOptions, type StorageClass, type UndiciBodyInit, type UploadPartOptions, type UploadPartResult };
587
+ export { type AbortMultipartUploadOptions, type Acl, type AttachmentContentDisposition, type BucketCorsRule, type BucketCorsRules, type BucketCreationOptions, type BucketDeletionOptions, type BucketExistsOptions, type BucketInfo, type BucketLocationInfo, type ByteSource, type ChecksumAlgorithm, type ChecksumType, type CompleteMultipartUploadOptions, type CompleteMultipartUploadResult, type ContentDisposition, type CreateFileInstanceOptions, type CreateMultipartUploadOptions, type CreateMultipartUploadResult, type DeleteBucketCorsOptions, type DeleteObjectsError, type DeleteObjectsOptions, type DeleteObjectsResult, type GetBucketCorsOptions, type GetBucketCorsResult, type HttpMethod, type InlineContentDisposition, type ListMultipartUploadsOptions, type ListMultipartUploadsResult, type ListObjectsIteratingOptions, type ListObjectsOptions, type ListObjectsResult, type ListPartsOptions, type ListPartsResult, type MultipartUpload, type MultipartUploadPart, type OverridableS3ClientOptions, type PresignableHttpMethod, type PutBucketCorsOptions, S3BucketEntry, S3Client, type S3ClientOptions, S3Error, type S3ErrorOptions, S3File, type S3FileDeleteOptions, type S3FileExistsOptions, type S3FilePresignOptions, type S3FileWriteOptions, S3Stat, type S3StatOptions, type StorageClass, type UndiciBodyInit, type UploadPartOptions, type UploadPartResult };
package/dist/index.js CHANGED
@@ -202,7 +202,7 @@ function buildRequestUrl(endpoint, bucket, region, path) {
202
202
  const result = new URL(endpointWithBucketAndRegion);
203
203
  const pathPrefix = result.pathname.endsWith("/") ? result.pathname : `${result.pathname}/`;
204
204
  const pathSuffix = replacedBucket ? normalizePath(path) : `${normalizedBucket}/${normalizePath(path)}`;
205
- result.pathname = pathPrefix + pathSuffix.replaceAll(":", "%3A").replaceAll("+", "%2B").replaceAll("(", "%28").replaceAll(")", "%29").replaceAll(",", "%2C");
205
+ result.pathname = pathPrefix + pathSuffix.replaceAll(":", "%3A").replaceAll("+", "%2B").replaceAll("(", "%28").replaceAll(")", "%29").replaceAll(",", "%2C").replaceAll("'", "%27").replaceAll("*", "%2A");
206
206
  return result;
207
207
  }
208
208
  function replaceDomainPlaceholders(endpoint, bucket, region) {
@@ -346,6 +346,32 @@ function ensureValidPath(path) {
346
346
  return path;
347
347
  }
348
348
 
349
+ // src/assertNever.ts
350
+ function assertNever(v) {
351
+ throw new TypeError(`Expected value not to have type ${typeof v}`);
352
+ }
353
+
354
+ // src/encode.ts
355
+ function getContentDispositionHeader(value) {
356
+ switch (value.type) {
357
+ case "inline":
358
+ return "inline";
359
+ case "attachment": {
360
+ const { filename } = value;
361
+ if (typeof filename === "undefined") {
362
+ return "attachment";
363
+ }
364
+ const encoded = encodeURIComponent(filename);
365
+ return `attachment;filename="${encoded}";filename*=UTF-8''${encoded}`;
366
+ }
367
+ default:
368
+ assertNever(value);
369
+ }
370
+ }
371
+ function encodeURIComponentExtended(value) {
372
+ return encodeURIComponent(value).replaceAll(":", "%3A").replaceAll("+", "%2B").replaceAll("(", "%28").replaceAll(")", "%29").replaceAll(",", "%2C").replaceAll("'", "%27").replaceAll("*", "%2A");
373
+ }
374
+
349
375
  // src/S3Client.ts
350
376
  var write = Symbol("write");
351
377
  var stream = Symbol("stream");
@@ -471,6 +497,19 @@ var S3Client = class {
471
497
  * expiresIn: 3600 // 1 hour
472
498
  * });
473
499
  * ```
500
+ *
501
+ * @example
502
+ * ```js
503
+ * client.presign("foo.jpg", {
504
+ * expiresIn: 3600 // 1 hour
505
+ * response: {
506
+ * contentDisposition: {
507
+ * type: "attachment",
508
+ * filename: "download.jpg",
509
+ * },
510
+ * },
511
+ * });
512
+ * ```
474
513
  */
475
514
  presign(path, {
476
515
  method = "GET",
@@ -482,7 +521,8 @@ var S3Client = class {
482
521
  acl,
483
522
  region: regionOverride,
484
523
  bucket: bucketOverride,
485
- endpoint: endpointOverride
524
+ endpoint: endpointOverride,
525
+ response
486
526
  } = {}) {
487
527
  if (typeof contentLength === "number") {
488
528
  if (contentLength < 0) {
@@ -495,6 +535,7 @@ var S3Client = class {
495
535
  const region = regionOverride ?? options.region;
496
536
  const bucket = bucketOverride ?? options.bucket;
497
537
  const endpoint = endpointOverride ?? options.endpoint;
538
+ const responseContentDisposition = response?.contentDisposition ? getContentDispositionHeader(response?.contentDisposition) : void 0;
498
539
  const res = buildRequestUrl(endpoint, bucket, region, path);
499
540
  const query = buildSearchParams(
500
541
  `${options.accessKeyId}/${date.date}/${region}/s3/aws4_request`,
@@ -504,7 +545,8 @@ var S3Client = class {
504
545
  unsignedPayload,
505
546
  storageClass,
506
547
  options.sessionToken,
507
- acl
548
+ acl,
549
+ responseContentDisposition
508
550
  );
509
551
  const dataDigest = typeof contentLength === "number" || typeof type === "string" ? createCanonicalDataDigest(
510
552
  method,
@@ -1409,7 +1451,7 @@ var S3Client = class {
1409
1451
  });
1410
1452
  }
1411
1453
  };
1412
- function buildSearchParams(amzCredential, date, expiresIn, headerList, contentHashStr, storageClass, sessionToken, acl) {
1454
+ function buildSearchParams(amzCredential, date, expiresIn, headerList, contentHashStr, storageClass, sessionToken, acl, responseContentDisposition) {
1413
1455
  let res = "";
1414
1456
  if (acl) {
1415
1457
  res += `X-Amz-Acl=${encodeURIComponent(acl)}&`;
@@ -1418,7 +1460,7 @@ function buildSearchParams(amzCredential, date, expiresIn, headerList, contentHa
1418
1460
  if (contentHashStr) {
1419
1461
  res += `&X-Amz-Content-Sha256=${contentHashStr}`;
1420
1462
  }
1421
- res += `&X-Amz-Credential=${encodeURIComponent(amzCredential)}`;
1463
+ res += `&X-Amz-Credential=${encodeURIComponentExtended(amzCredential)}`;
1422
1464
  res += `&X-Amz-Date=${date.dateTime}`;
1423
1465
  res += `&X-Amz-Expires=${expiresIn}`;
1424
1466
  if (sessionToken) {
@@ -1428,6 +1470,9 @@ function buildSearchParams(amzCredential, date, expiresIn, headerList, contentHa
1428
1470
  if (storageClass) {
1429
1471
  res += `&X-Amz-Storage-Class=${storageClass}`;
1430
1472
  }
1473
+ if (responseContentDisposition) {
1474
+ res += `&response-content-disposition=${encodeURIComponentExtended(responseContentDisposition)}`;
1475
+ }
1431
1476
  return res;
1432
1477
  }
1433
1478
  function ensureParsedXml(text) {
@@ -1447,11 +1492,6 @@ function ensureParsedXml(text) {
1447
1492
  }
1448
1493
  }
1449
1494
 
1450
- // src/assertNever.ts
1451
- function assertNever(v) {
1452
- throw new TypeError(`Expected value not to have type ${typeof v}`);
1453
- }
1454
-
1455
1495
  // src/S3File.ts
1456
1496
  var S3File = class _S3File {
1457
1497
  #client;
@@ -1600,7 +1640,9 @@ var S3File = class _S3File {
1600
1640
  return new Response(this.stream()).text();
1601
1641
  }
1602
1642
  blob() {
1603
- return new Response(this.stream()).blob();
1643
+ return new Response(this.stream(), {
1644
+ headers: { "Content-Type": this.#contentType }
1645
+ }).blob();
1604
1646
  }
1605
1647
  /** @returns {ReadableStream<Uint8Array>} */
1606
1648
  stream() {
package/package.json CHANGED
@@ -2,7 +2,7 @@
2
2
  "name": "lean-s3",
3
3
  "author": "Niklas Mollenhauer",
4
4
  "license": "MIT",
5
- "version": "0.7.0",
5
+ "version": "0.7.2",
6
6
  "description": "A server-side S3 API for the regular user.",
7
7
  "keywords": [
8
8
  "s3",