@hot-updater/aws 1.1.4 → 1.1.5

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
@@ -5,8 +5,7 @@ let _aws_sdk_client_cloudfront = require("@aws-sdk/client-cloudfront");
5
5
  let _aws_sdk_lib_dynamodb = require("@aws-sdk/lib-dynamodb");
6
6
  let _aws_sdk_client_ssm = require("@aws-sdk/client-ssm");
7
7
  let _aws_sdk_cloudfront_signer = require("@aws-sdk/cloudfront-signer");
8
- let _aws_sdk_client_s3 = require("@aws-sdk/client-s3");
9
- let _aws_sdk_lib_storage = require("@aws-sdk/lib-storage");
8
+ let _hot_updater_storage_s3 = require("@hot-updater/storage-s3");
10
9
  //#region src/cloudFrontInvalidation.ts
11
10
  const DEFAULT_POLL_INTERVAL_MS = 2e3;
12
11
  const DEFAULT_TIMEOUT_MS = 3e5;
@@ -486,153 +485,16 @@ const cloudFrontDownloadUrl = (config) => {
486
485
  };
487
486
  //#endregion
488
487
  //#region src/s3Storage.ts
489
- /** Devices download right after the update check that returns the URL. */
490
- const PRESIGNED_URL_EXPIRES_IN_SECONDS = 3600;
491
- const isObjectNotFoundError = (error) => error instanceof Error && (error.name === "NotFound" || error.name === "NoSuchKey");
492
488
  function s3Storage(config) {
493
489
  const { bucketName, basePath, getDownloadUrl, ...s3Config } = config;
494
- const client = new _aws_sdk_client_s3.S3Client(applyS3RuntimeAwsConfig(s3Config));
495
- const normalizedBasePath = basePath?.replace(/^\/+|\/+$/g, "") ?? "";
496
- const getStorageKey = (0, _hot_updater_plugin_core.createStorageKeyBuilder)(normalizedBasePath);
497
- const getListPrefix = (prefix = "") => {
498
- const normalizedPrefix = prefix.replace(/^\/+|\/+$/g, "");
499
- const value = [normalizedBasePath, normalizedPrefix].filter(Boolean).join("/");
500
- return value ? `${value}/` : "";
501
- };
502
- /**
503
- * The URI `put` returns for the key, or null for a key no `put` writes,
504
- * such as a folder marker that ends in "/".
505
- */
506
- const getListedStorageUri = (key) => {
507
- try {
508
- return (0, _hot_updater_plugin_core.createStorageUri)({
509
- bucket: bucketName,
510
- key,
511
- protocol: "s3"
512
- });
513
- } catch {
514
- return null;
515
- }
516
- };
517
- const getRelativeKey = (key) => {
518
- if (!normalizedBasePath) return key;
519
- const basePrefix = `${normalizedBasePath}/`;
520
- return key.startsWith(basePrefix) ? key.slice(basePrefix.length) : key;
521
- };
522
- const parseAndValidate = (storageUri) => {
523
- const parsed = (0, _hot_updater_plugin_core.parseStorageUri)(storageUri, "s3");
524
- if (parsed.bucket !== bucketName) throw new Error(`Bucket name mismatch: expected "${bucketName}", but found "${parsed.bucket}".`);
525
- return parsed;
526
- };
527
- return (0, _hot_updater_plugin_core.createStorageAdapter)({
490
+ return (0, _hot_updater_storage_s3.createS3Storage)({
528
491
  name: "s3Storage",
529
- protocol: "s3",
530
- async listObjects(prefix) {
531
- const objects = [];
532
- let continuationToken;
533
- do {
534
- const response = await client.send(new _aws_sdk_client_s3.ListObjectsV2Command({
535
- Bucket: bucketName,
536
- ContinuationToken: continuationToken,
537
- Prefix: getListPrefix(prefix)
538
- }));
539
- for (const object of response.Contents ?? []) {
540
- if (!object.Key) continue;
541
- const storageUri = getListedStorageUri(object.Key);
542
- if (storageUri === null) continue;
543
- objects.push({
544
- key: getRelativeKey(object.Key),
545
- lastModifiedAt: object.LastModified,
546
- size: object.Size ?? 0,
547
- storageUri
548
- });
549
- }
550
- continuationToken = response.NextContinuationToken;
551
- } while (continuationToken);
552
- return objects;
553
- },
554
- async deleteObjects(relativeKeys) {
555
- const keys = relativeKeys.map((key) => getStorageKey(key));
556
- for (let offset = 0; offset < keys.length; offset += 1e3) {
557
- const batch = keys.slice(offset, offset + 1e3);
558
- if (batch.length === 0) continue;
559
- const response = await client.send(new _aws_sdk_client_s3.DeleteObjectsCommand({
560
- Bucket: bucketName,
561
- Delete: {
562
- Objects: batch.map((Key) => ({ Key })),
563
- Quiet: true
564
- }
565
- }));
566
- if (response.Errors && response.Errors.length > 0) throw new Error(`Failed to delete ${response.Errors.length} S3 object(s).`);
567
- }
568
- },
569
- async put({ key, body, contentLength, contentType }) {
570
- const storageKey = getStorageKey(key);
571
- const response = await new _aws_sdk_lib_storage.Upload({
572
- client,
573
- params: {
574
- Body: body,
575
- Bucket: bucketName,
576
- CacheControl: "max-age=31536000",
577
- ...contentLength === void 0 ? {} : { ContentLength: contentLength },
578
- ContentType: contentType,
579
- Key: storageKey
580
- }
581
- }).done();
582
- if (!response.Bucket || !response.Key) throw new Error("Upload failed");
583
- return { storageUri: (0, _hot_updater_plugin_core.createStorageUri)({
584
- bucket: bucketName,
585
- key: storageKey,
586
- protocol: "s3"
587
- }) };
588
- },
589
- async get({ storageUri }) {
590
- const { key } = parseAndValidate(storageUri);
591
- try {
592
- const response = await client.send(new _aws_sdk_client_s3.GetObjectCommand({
593
- Bucket: bucketName,
594
- Key: key
595
- }));
596
- if (!response.Body) return { response: null };
597
- const headers = new Headers();
598
- if (response.ContentType) headers.set("content-type", response.ContentType);
599
- if (response.ContentLength !== void 0) headers.set("content-length", String(response.ContentLength));
600
- return { response: new Response(response.Body.transformToWebStream(), { headers }) };
601
- } catch (error) {
602
- if (isObjectNotFoundError(error)) return { response: null };
603
- throw error;
604
- }
605
- },
606
- async getDownloadUrl({ storageUri }) {
607
- const { key } = parseAndValidate(storageUri);
608
- if (getDownloadUrl) return getDownloadUrl({ storageUri });
609
- const { getSignedUrl } = await import("@aws-sdk/s3-request-presigner");
610
- return { url: await getSignedUrl(client, new _aws_sdk_client_s3.GetObjectCommand({
611
- Bucket: bucketName,
612
- Key: key
613
- }), { expiresIn: PRESIGNED_URL_EXPIRES_IN_SECONDS }) };
614
- },
615
- async exists({ storageUri }) {
616
- const { key } = parseAndValidate(storageUri);
617
- try {
618
- await client.send(new _aws_sdk_client_s3.HeadObjectCommand({
619
- Bucket: bucketName,
620
- Key: key
621
- }));
622
- return { exists: true };
623
- } catch (error) {
624
- if (isObjectNotFoundError(error)) return { exists: false };
625
- throw error;
626
- }
627
- },
628
- async delete({ storageUri }) {
629
- const { key } = parseAndValidate(storageUri);
630
- await client.send(new _aws_sdk_client_s3.DeleteObjectCommand({
631
- Bucket: bucketName,
632
- Key: key
633
- }));
634
- return { deleted: true };
635
- }
492
+ protocol: "s3"
493
+ }, {
494
+ ...applyS3RuntimeAwsConfig(s3Config),
495
+ bucketName,
496
+ basePath,
497
+ getDownloadUrl
636
498
  });
637
499
  }
638
500
  //#endregion
package/dist/index.d.cts CHANGED
@@ -1,6 +1,7 @@
1
1
  import { DynamoDBClientConfig } from "@aws-sdk/client-dynamodb";
2
2
  import { EngineDatabase, PluginTables, StorageAdapter, StorageAdapterWith } from "@hot-updater/plugin-core";
3
3
  import { S3ClientConfig } from "@aws-sdk/client-s3";
4
+ import { S3StorageOperations } from "@hot-updater/storage-s3";
4
5
  //#region src/dynamoDB.d.ts
5
6
  interface DynamoDBConfig extends DynamoDBClientConfig {
6
7
  readonly apiBasePath?: string;
@@ -70,7 +71,6 @@ interface S3StorageConfig extends S3ClientConfig {
70
71
  */
71
72
  getDownloadUrl?: StorageAdapter["getDownloadUrl"];
72
73
  }
73
- type S3StorageOperations = "put" | "get" | "getDownloadUrl" | "exists" | "delete" | "listObjects" | "deleteObjects";
74
74
  declare function s3Storage(config: S3StorageConfig): StorageAdapterWith<S3StorageOperations>;
75
75
  //#endregion
76
76
  export { CloudFrontDownloadUrlOptions, type DynamoDBConfig, S3StorageConfig, cloudFrontDownloadUrl, dynamoDB, migrateDynamoDB, s3Storage };
package/dist/index.d.mts CHANGED
@@ -1,5 +1,6 @@
1
1
  import { DynamoDBClientConfig } from "@aws-sdk/client-dynamodb";
2
2
  import { EngineDatabase, PluginTables, StorageAdapter, StorageAdapterWith } from "@hot-updater/plugin-core";
3
+ import { S3StorageOperations } from "@hot-updater/storage-s3";
3
4
  import { S3ClientConfig } from "@aws-sdk/client-s3";
4
5
  //#region src/dynamoDB.d.ts
5
6
  interface DynamoDBConfig extends DynamoDBClientConfig {
@@ -70,7 +71,6 @@ interface S3StorageConfig extends S3ClientConfig {
70
71
  */
71
72
  getDownloadUrl?: StorageAdapter["getDownloadUrl"];
72
73
  }
73
- type S3StorageOperations = "put" | "get" | "getDownloadUrl" | "exists" | "delete" | "listObjects" | "deleteObjects";
74
74
  declare function s3Storage(config: S3StorageConfig): StorageAdapterWith<S3StorageOperations>;
75
75
  //#endregion
76
76
  export { CloudFrontDownloadUrlOptions, type DynamoDBConfig, S3StorageConfig, cloudFrontDownloadUrl, dynamoDB, migrateDynamoDB, s3Storage };
package/dist/index.mjs CHANGED
@@ -1,11 +1,10 @@
1
1
  import { CreateTableCommand, DescribeTableCommand, DescribeTimeToLiveCommand, DynamoDBClient, UpdateTimeToLiveCommand, waitUntilTableExists } from "@aws-sdk/client-dynamodb";
2
- import { BUNDLE_STORAGE_PREFIX, CONTENT_ADDRESSED_ASSET_PREFIX, createEngineDatabase, createKvAdapter, createStorageAdapter, createStorageKeyBuilder, createStorageUri, migrateCoreSchema, parseStorageUri } from "@hot-updater/plugin-core";
2
+ import { BUNDLE_STORAGE_PREFIX, CONTENT_ADDRESSED_ASSET_PREFIX, createEngineDatabase, createKvAdapter, migrateCoreSchema } from "@hot-updater/plugin-core";
3
3
  import { CloudFrontClient, CreateInvalidationCommand, GetInvalidationCommand } from "@aws-sdk/client-cloudfront";
4
4
  import { BatchGetCommand, BatchWriteCommand, DynamoDBDocumentClient, QueryCommand, TransactWriteCommand } from "@aws-sdk/lib-dynamodb";
5
5
  import { SSM } from "@aws-sdk/client-ssm";
6
6
  import { getSignedUrl } from "@aws-sdk/cloudfront-signer";
7
- import { DeleteObjectCommand, DeleteObjectsCommand, GetObjectCommand, HeadObjectCommand, ListObjectsV2Command, S3Client } from "@aws-sdk/client-s3";
8
- import { Upload } from "@aws-sdk/lib-storage";
7
+ import { createS3Storage } from "@hot-updater/storage-s3";
9
8
  //#region src/cloudFrontInvalidation.ts
10
9
  const DEFAULT_POLL_INTERVAL_MS = 2e3;
11
10
  const DEFAULT_TIMEOUT_MS = 3e5;
@@ -485,153 +484,16 @@ const cloudFrontDownloadUrl = (config) => {
485
484
  };
486
485
  //#endregion
487
486
  //#region src/s3Storage.ts
488
- /** Devices download right after the update check that returns the URL. */
489
- const PRESIGNED_URL_EXPIRES_IN_SECONDS = 3600;
490
- const isObjectNotFoundError = (error) => error instanceof Error && (error.name === "NotFound" || error.name === "NoSuchKey");
491
487
  function s3Storage(config) {
492
488
  const { bucketName, basePath, getDownloadUrl, ...s3Config } = config;
493
- const client = new S3Client(applyS3RuntimeAwsConfig(s3Config));
494
- const normalizedBasePath = basePath?.replace(/^\/+|\/+$/g, "") ?? "";
495
- const getStorageKey = createStorageKeyBuilder(normalizedBasePath);
496
- const getListPrefix = (prefix = "") => {
497
- const normalizedPrefix = prefix.replace(/^\/+|\/+$/g, "");
498
- const value = [normalizedBasePath, normalizedPrefix].filter(Boolean).join("/");
499
- return value ? `${value}/` : "";
500
- };
501
- /**
502
- * The URI `put` returns for the key, or null for a key no `put` writes,
503
- * such as a folder marker that ends in "/".
504
- */
505
- const getListedStorageUri = (key) => {
506
- try {
507
- return createStorageUri({
508
- bucket: bucketName,
509
- key,
510
- protocol: "s3"
511
- });
512
- } catch {
513
- return null;
514
- }
515
- };
516
- const getRelativeKey = (key) => {
517
- if (!normalizedBasePath) return key;
518
- const basePrefix = `${normalizedBasePath}/`;
519
- return key.startsWith(basePrefix) ? key.slice(basePrefix.length) : key;
520
- };
521
- const parseAndValidate = (storageUri) => {
522
- const parsed = parseStorageUri(storageUri, "s3");
523
- if (parsed.bucket !== bucketName) throw new Error(`Bucket name mismatch: expected "${bucketName}", but found "${parsed.bucket}".`);
524
- return parsed;
525
- };
526
- return createStorageAdapter({
489
+ return createS3Storage({
527
490
  name: "s3Storage",
528
- protocol: "s3",
529
- async listObjects(prefix) {
530
- const objects = [];
531
- let continuationToken;
532
- do {
533
- const response = await client.send(new ListObjectsV2Command({
534
- Bucket: bucketName,
535
- ContinuationToken: continuationToken,
536
- Prefix: getListPrefix(prefix)
537
- }));
538
- for (const object of response.Contents ?? []) {
539
- if (!object.Key) continue;
540
- const storageUri = getListedStorageUri(object.Key);
541
- if (storageUri === null) continue;
542
- objects.push({
543
- key: getRelativeKey(object.Key),
544
- lastModifiedAt: object.LastModified,
545
- size: object.Size ?? 0,
546
- storageUri
547
- });
548
- }
549
- continuationToken = response.NextContinuationToken;
550
- } while (continuationToken);
551
- return objects;
552
- },
553
- async deleteObjects(relativeKeys) {
554
- const keys = relativeKeys.map((key) => getStorageKey(key));
555
- for (let offset = 0; offset < keys.length; offset += 1e3) {
556
- const batch = keys.slice(offset, offset + 1e3);
557
- if (batch.length === 0) continue;
558
- const response = await client.send(new DeleteObjectsCommand({
559
- Bucket: bucketName,
560
- Delete: {
561
- Objects: batch.map((Key) => ({ Key })),
562
- Quiet: true
563
- }
564
- }));
565
- if (response.Errors && response.Errors.length > 0) throw new Error(`Failed to delete ${response.Errors.length} S3 object(s).`);
566
- }
567
- },
568
- async put({ key, body, contentLength, contentType }) {
569
- const storageKey = getStorageKey(key);
570
- const response = await new Upload({
571
- client,
572
- params: {
573
- Body: body,
574
- Bucket: bucketName,
575
- CacheControl: "max-age=31536000",
576
- ...contentLength === void 0 ? {} : { ContentLength: contentLength },
577
- ContentType: contentType,
578
- Key: storageKey
579
- }
580
- }).done();
581
- if (!response.Bucket || !response.Key) throw new Error("Upload failed");
582
- return { storageUri: createStorageUri({
583
- bucket: bucketName,
584
- key: storageKey,
585
- protocol: "s3"
586
- }) };
587
- },
588
- async get({ storageUri }) {
589
- const { key } = parseAndValidate(storageUri);
590
- try {
591
- const response = await client.send(new GetObjectCommand({
592
- Bucket: bucketName,
593
- Key: key
594
- }));
595
- if (!response.Body) return { response: null };
596
- const headers = new Headers();
597
- if (response.ContentType) headers.set("content-type", response.ContentType);
598
- if (response.ContentLength !== void 0) headers.set("content-length", String(response.ContentLength));
599
- return { response: new Response(response.Body.transformToWebStream(), { headers }) };
600
- } catch (error) {
601
- if (isObjectNotFoundError(error)) return { response: null };
602
- throw error;
603
- }
604
- },
605
- async getDownloadUrl({ storageUri }) {
606
- const { key } = parseAndValidate(storageUri);
607
- if (getDownloadUrl) return getDownloadUrl({ storageUri });
608
- const { getSignedUrl } = await import("@aws-sdk/s3-request-presigner");
609
- return { url: await getSignedUrl(client, new GetObjectCommand({
610
- Bucket: bucketName,
611
- Key: key
612
- }), { expiresIn: PRESIGNED_URL_EXPIRES_IN_SECONDS }) };
613
- },
614
- async exists({ storageUri }) {
615
- const { key } = parseAndValidate(storageUri);
616
- try {
617
- await client.send(new HeadObjectCommand({
618
- Bucket: bucketName,
619
- Key: key
620
- }));
621
- return { exists: true };
622
- } catch (error) {
623
- if (isObjectNotFoundError(error)) return { exists: false };
624
- throw error;
625
- }
626
- },
627
- async delete({ storageUri }) {
628
- const { key } = parseAndValidate(storageUri);
629
- await client.send(new DeleteObjectCommand({
630
- Bucket: bucketName,
631
- Key: key
632
- }));
633
- return { deleted: true };
634
- }
491
+ protocol: "s3"
492
+ }, {
493
+ ...applyS3RuntimeAwsConfig(s3Config),
494
+ bucketName,
495
+ basePath,
496
+ getDownloadUrl
635
497
  });
636
498
  }
637
499
  //#endregion
@@ -30,6 +30,7 @@ let _aws_sdk_client_cloudfront = require("@aws-sdk/client-cloudfront");
30
30
  let _aws_sdk_lib_dynamodb = require("@aws-sdk/lib-dynamodb");
31
31
  let _aws_sdk_client_s3 = require("@aws-sdk/client-s3");
32
32
  let _aws_sdk_lib_storage = require("@aws-sdk/lib-storage");
33
+ let _aws_sdk_s3_request_presigner = require("@aws-sdk/s3-request-presigner");
33
34
  const SHA256_FILE_HASH_RE = /^[0-9a-f]{64}$/;
34
35
  const isContentAddressedAssetFileHash = (value) => {
35
36
  return typeof value === "string" && SHA256_FILE_HASH_RE.test(value);
@@ -4549,7 +4550,7 @@ const createKvAdapter = ({ store, tablePrefix = "" }) => {
4549
4550
  };
4550
4551
  //#endregion
4551
4552
  //#region ../../packages/server/dist/version.mjs
4552
- const HOT_UPDATER_SERVER_VERSION = "1.1.4";
4553
+ const HOT_UPDATER_SERVER_VERSION = "1.1.5";
4553
4554
  //#endregion
4554
4555
  //#region ../../packages/server/dist/handlerVersionRoutes.mjs
4555
4556
  /**
@@ -13829,18 +13830,31 @@ const dynamoDB = (config) => {
13829
13830
  };
13830
13831
  };
13831
13832
  //#endregion
13832
- //#region src/s3Storage.ts
13833
+ //#region ../storage-s3/dist/index.mjs
13833
13834
  /** Devices download right after the update check that returns the URL. */
13834
- const PRESIGNED_URL_EXPIRES_IN_SECONDS = 3600;
13835
- const isObjectNotFoundError = (error) => error instanceof Error && (error.name === "NotFound" || error.name === "NoSuchKey");
13836
- function s3Storage(config) {
13837
- const { bucketName, basePath, getDownloadUrl, ...s3Config } = config;
13838
- const client = new _aws_sdk_client_s3.S3Client(applyS3RuntimeAwsConfig(s3Config));
13839
- const normalizedBasePath = basePath?.replace(/^\/+|\/+$/g, "") ?? "";
13835
+ const DEFAULT_SIGNED_LIFETIME_SECONDS = 3600;
13836
+ /** SigV4's limit on a presigned URL. */
13837
+ const MAX_SIGNED_LIFETIME_SECONDS = 604800;
13838
+ /** DeleteObjects takes at most 1000 keys a request. */
13839
+ const DELETE_BATCH_SIZE = 1e3;
13840
+ const isObjectNotFoundError = (error) => {
13841
+ if (error instanceof Error && (error.name === "NotFound" || error.name === "NoSuchKey")) return true;
13842
+ return typeof error === "object" && error !== null && "$metadata" in error && error.$metadata?.httpStatusCode === 404;
13843
+ };
13844
+ const trimSlashes = (value) => value.replace(/^\/+|\/+$/g, "");
13845
+ /**
13846
+ * A storage adapter over a bucket's S3 API, which AWS S3, Cloudflare R2, and
13847
+ * Neon Object Storage all serve. Each provider package passes its own name,
13848
+ * URI protocol, and client defaults.
13849
+ */
13850
+ const createS3Storage = ({ name, protocol }, config) => {
13851
+ const { bucketName, basePath, getDownloadUrl, signedUrlExpiresIn = DEFAULT_SIGNED_LIFETIME_SECONDS, ...clientConfig } = config;
13852
+ if (!Number.isFinite(signedUrlExpiresIn) || signedUrlExpiresIn <= 0 || signedUrlExpiresIn > MAX_SIGNED_LIFETIME_SECONDS) throw new Error(`${name} signedUrlExpiresIn must be between 1 and ${MAX_SIGNED_LIFETIME_SECONDS} seconds (seven days).`);
13853
+ const client = new _aws_sdk_client_s3.S3Client(clientConfig);
13854
+ const normalizedBasePath = trimSlashes(basePath ?? "");
13840
13855
  const getStorageKey = createStorageKeyBuilder(normalizedBasePath);
13841
13856
  const getListPrefix = (prefix = "") => {
13842
- const normalizedPrefix = prefix.replace(/^\/+|\/+$/g, "");
13843
- const value = [normalizedBasePath, normalizedPrefix].filter(Boolean).join("/");
13857
+ const value = [normalizedBasePath, trimSlashes(prefix)].filter(Boolean).join("/");
13844
13858
  return value ? `${value}/` : "";
13845
13859
  };
13846
13860
  /**
@@ -13852,7 +13866,7 @@ function s3Storage(config) {
13852
13866
  return createStorageUri({
13853
13867
  bucket: bucketName,
13854
13868
  key,
13855
- protocol: "s3"
13869
+ protocol
13856
13870
  });
13857
13871
  } catch {
13858
13872
  return null;
@@ -13864,55 +13878,16 @@ function s3Storage(config) {
13864
13878
  return key.startsWith(basePrefix) ? key.slice(basePrefix.length) : key;
13865
13879
  };
13866
13880
  const parseAndValidate = (storageUri) => {
13867
- const parsed = parseStorageUri(storageUri, "s3");
13881
+ const parsed = parseStorageUri(storageUri, protocol);
13868
13882
  if (parsed.bucket !== bucketName) throw new Error(`Bucket name mismatch: expected "${bucketName}", but found "${parsed.bucket}".`);
13869
13883
  return parsed;
13870
13884
  };
13871
13885
  return createStorageAdapter({
13872
- name: "s3Storage",
13873
- protocol: "s3",
13874
- async listObjects(prefix) {
13875
- const objects = [];
13876
- let continuationToken;
13877
- do {
13878
- const response = await client.send(new _aws_sdk_client_s3.ListObjectsV2Command({
13879
- Bucket: bucketName,
13880
- ContinuationToken: continuationToken,
13881
- Prefix: getListPrefix(prefix)
13882
- }));
13883
- for (const object of response.Contents ?? []) {
13884
- if (!object.Key) continue;
13885
- const storageUri = getListedStorageUri(object.Key);
13886
- if (storageUri === null) continue;
13887
- objects.push({
13888
- key: getRelativeKey(object.Key),
13889
- lastModifiedAt: object.LastModified,
13890
- size: object.Size ?? 0,
13891
- storageUri
13892
- });
13893
- }
13894
- continuationToken = response.NextContinuationToken;
13895
- } while (continuationToken);
13896
- return objects;
13897
- },
13898
- async deleteObjects(relativeKeys) {
13899
- const keys = relativeKeys.map((key) => getStorageKey(key));
13900
- for (let offset = 0; offset < keys.length; offset += 1e3) {
13901
- const batch = keys.slice(offset, offset + 1e3);
13902
- if (batch.length === 0) continue;
13903
- const response = await client.send(new _aws_sdk_client_s3.DeleteObjectsCommand({
13904
- Bucket: bucketName,
13905
- Delete: {
13906
- Objects: batch.map((Key) => ({ Key })),
13907
- Quiet: true
13908
- }
13909
- }));
13910
- if (response.Errors && response.Errors.length > 0) throw new Error(`Failed to delete ${response.Errors.length} S3 object(s).`);
13911
- }
13912
- },
13886
+ name,
13887
+ protocol,
13913
13888
  async put({ key, body, contentLength, contentType }) {
13914
13889
  const storageKey = getStorageKey(key);
13915
- const response = await new _aws_sdk_lib_storage.Upload({
13890
+ await new _aws_sdk_lib_storage.Upload({
13916
13891
  client,
13917
13892
  params: {
13918
13893
  Body: body,
@@ -13923,11 +13898,10 @@ function s3Storage(config) {
13923
13898
  Key: storageKey
13924
13899
  }
13925
13900
  }).done();
13926
- if (!response.Bucket || !response.Key) throw new Error("Upload failed");
13927
13901
  return { storageUri: createStorageUri({
13928
13902
  bucket: bucketName,
13929
13903
  key: storageKey,
13930
- protocol: "s3"
13904
+ protocol
13931
13905
  }) };
13932
13906
  },
13933
13907
  async get({ storageUri }) {
@@ -13950,11 +13924,10 @@ function s3Storage(config) {
13950
13924
  async getDownloadUrl({ storageUri }) {
13951
13925
  const { key } = parseAndValidate(storageUri);
13952
13926
  if (getDownloadUrl) return getDownloadUrl({ storageUri });
13953
- const { getSignedUrl } = await import("@aws-sdk/s3-request-presigner");
13954
- return { url: await getSignedUrl(client, new _aws_sdk_client_s3.GetObjectCommand({
13927
+ return { url: await (0, _aws_sdk_s3_request_presigner.getSignedUrl)(client, new _aws_sdk_client_s3.GetObjectCommand({
13955
13928
  Bucket: bucketName,
13956
13929
  Key: key
13957
- }), { expiresIn: PRESIGNED_URL_EXPIRES_IN_SECONDS }) };
13930
+ }), { expiresIn: signedUrlExpiresIn }) };
13958
13931
  },
13959
13932
  async exists({ storageUri }) {
13960
13933
  const { key } = parseAndValidate(storageUri);
@@ -13976,8 +13949,60 @@ function s3Storage(config) {
13976
13949
  Key: key
13977
13950
  }));
13978
13951
  return { deleted: true };
13952
+ },
13953
+ async listObjects(prefix) {
13954
+ const objects = [];
13955
+ let continuationToken;
13956
+ do {
13957
+ const response = await client.send(new _aws_sdk_client_s3.ListObjectsV2Command({
13958
+ Bucket: bucketName,
13959
+ ContinuationToken: continuationToken,
13960
+ Prefix: getListPrefix(prefix)
13961
+ }));
13962
+ for (const object of response.Contents ?? []) {
13963
+ if (!object.Key) continue;
13964
+ const storageUri = getListedStorageUri(object.Key);
13965
+ if (storageUri === null) continue;
13966
+ objects.push({
13967
+ key: getRelativeKey(object.Key),
13968
+ lastModifiedAt: object.LastModified,
13969
+ size: object.Size ?? 0,
13970
+ storageUri
13971
+ });
13972
+ }
13973
+ continuationToken = response.NextContinuationToken;
13974
+ } while (continuationToken);
13975
+ return objects;
13976
+ },
13977
+ async deleteObjects(relativeKeys) {
13978
+ const keys = relativeKeys.map((key) => getStorageKey(key));
13979
+ for (let offset = 0; offset < keys.length; offset += DELETE_BATCH_SIZE) {
13980
+ const batch = keys.slice(offset, offset + DELETE_BATCH_SIZE);
13981
+ const response = await client.send(new _aws_sdk_client_s3.DeleteObjectsCommand({
13982
+ Bucket: bucketName,
13983
+ Delete: {
13984
+ Objects: batch.map((Key) => ({ Key })),
13985
+ Quiet: true
13986
+ }
13987
+ }));
13988
+ if (response.Errors && response.Errors.length > 0) throw new Error(`Failed to delete ${response.Errors.length} S3 object(s) from bucket "${bucketName}".`);
13989
+ }
13979
13990
  }
13980
13991
  });
13992
+ };
13993
+ //#endregion
13994
+ //#region src/s3Storage.ts
13995
+ function s3Storage(config) {
13996
+ const { bucketName, basePath, getDownloadUrl, ...s3Config } = config;
13997
+ return createS3Storage({
13998
+ name: "s3Storage",
13999
+ protocol: "s3"
14000
+ }, {
14001
+ ...applyS3RuntimeAwsConfig(s3Config),
14002
+ bucketName,
14003
+ basePath,
14004
+ getDownloadUrl
14005
+ });
13981
14006
  }
13982
14007
  //#endregion
13983
14008
  //#region lambda/index.ts
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@hot-updater/aws",
3
3
  "type": "module",
4
- "version": "1.1.4",
4
+ "version": "1.1.5",
5
5
  "description": "React Native OTA solution for self-hosted",
6
6
  "main": "dist/index.cjs",
7
7
  "module": "dist/index.mjs",
@@ -45,9 +45,9 @@
45
45
  "es-toolkit": "^1.32.0",
46
46
  "execa": "9.5.2",
47
47
  "mime": "^4.0.4",
48
- "@hot-updater/protocol": "1.1.4",
49
- "@hot-updater/mock": "1.1.4",
50
- "@hot-updater/test-utils": "1.1.4"
48
+ "@hot-updater/protocol": "1.1.5",
49
+ "@hot-updater/mock": "1.1.5",
50
+ "@hot-updater/test-utils": "1.1.5"
51
51
  },
52
52
  "dependencies": {
53
53
  "@aws-sdk/client-cloudfront": "3.1066.0",
@@ -63,9 +63,10 @@
63
63
  "@aws-sdk/lib-dynamodb": "3.1066.0",
64
64
  "@aws-sdk/s3-request-presigner": "3.1066.0",
65
65
  "hono": "4.12.34",
66
- "@hot-updater/cli-tools": "1.1.4",
67
- "@hot-updater/plugin-core": "1.1.4",
68
- "@hot-updater/server": "1.1.4"
66
+ "@hot-updater/cli-tools": "1.1.5",
67
+ "@hot-updater/server": "1.1.5",
68
+ "@hot-updater/plugin-core": "1.1.5",
69
+ "@hot-updater/storage-s3": "1.1.5"
69
70
  },
70
71
  "scripts": {
71
72
  "build": "tsdown",