@hot-updater/aws 1.2.0-rc.7 → 1.2.0-rc.9

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
  let node_crypto = require("node:crypto");
11
10
  //#region src/cloudFrontInvalidation.ts
12
11
  const DEFAULT_POLL_INTERVAL_MS = 2e3;
@@ -315,10 +314,30 @@ async apply() {
315
314
  };
316
315
  };
317
316
  //#endregion
317
+ //#region src/lazyCredentials.ts
318
+ /**
319
+ * `credentials` as the AWS SDK takes them: access keys with a lazy field
320
+ * become a provider, which the SDK calls once, on its first request.
321
+ */
322
+ const toSdkCredentials = (credentials) => {
323
+ if (credentials === void 0 || typeof credentials === "function") return credentials;
324
+ const { accessKeyId, secretAccessKey, sessionToken, ...rest } = credentials;
325
+ if (typeof accessKeyId !== "function" && typeof secretAccessKey !== "function" && typeof sessionToken !== "function") return credentials;
326
+ return async () => ({
327
+ ...rest,
328
+ accessKeyId: await (0, _hot_updater_plugin_core.resolveLazy)(accessKeyId),
329
+ secretAccessKey: await (0, _hot_updater_plugin_core.resolveLazy)(secretAccessKey),
330
+ ...sessionToken === void 0 ? {} : { sessionToken: await (0, _hot_updater_plugin_core.resolveLazy)(sessionToken) }
331
+ });
332
+ };
333
+ //#endregion
318
334
  //#region src/dynamoDB.ts
319
335
  /** The DynamoDB client ignores the CloudFront and batching settings. */
320
- const adapterOf = ({ tableName, ...clientConfig }) => {
321
- const client = new _aws_sdk_client_dynamodb.DynamoDBClient(clientConfig);
336
+ const adapterOf = ({ tableName, credentials, ...clientConfig }) => {
337
+ const client = new _aws_sdk_client_dynamodb.DynamoDBClient({
338
+ ...clientConfig,
339
+ credentials: toSdkCredentials(credentials)
340
+ });
322
341
  const store = createDynamoDBStore({
323
342
  client,
324
343
  tableName
@@ -353,7 +372,10 @@ const marked = (target) => ({ [DATABASE_TARGET]: target });
353
372
  */
354
373
  const dynamoDB = (config) => {
355
374
  const { client, adapter } = adapterOf(config);
356
- const cloudFront = createUpdateRouteInvalidation(config);
375
+ const cloudFront = createUpdateRouteInvalidation({
376
+ ...config,
377
+ credentials: toSdkCredentials(config.credentials)
378
+ });
357
379
  return {
358
380
  ...(0, _hot_updater_plugin_core.createEngineDatabase)({
359
381
  name: "dynamoDB",
@@ -496,153 +518,17 @@ const cloudFrontDownloadUrl = (config) => {
496
518
  };
497
519
  //#endregion
498
520
  //#region src/s3Storage.ts
499
- /** Devices download right after the update check that returns the URL. */
500
- const PRESIGNED_URL_EXPIRES_IN_SECONDS = 3600;
501
- const isObjectNotFoundError = (error) => error instanceof Error && (error.name === "NotFound" || error.name === "NoSuchKey");
502
521
  function s3Storage(config) {
503
- const { bucketName, basePath, getDownloadUrl, ...s3Config } = config;
504
- const client = new _aws_sdk_client_s3.S3Client(applyS3RuntimeAwsConfig(s3Config));
505
- const normalizedBasePath = basePath?.replace(/^\/+|\/+$/g, "") ?? "";
506
- const getStorageKey = (0, _hot_updater_plugin_core.createStorageKeyBuilder)(normalizedBasePath);
507
- const getListPrefix = (prefix = "") => {
508
- const normalizedPrefix = prefix.replace(/^\/+|\/+$/g, "");
509
- const value = [normalizedBasePath, normalizedPrefix].filter(Boolean).join("/");
510
- return value ? `${value}/` : "";
511
- };
512
- /**
513
- * The URI `put` returns for the key, or null for a key no `put` writes,
514
- * such as a folder marker that ends in "/".
515
- */
516
- const getListedStorageUri = (key) => {
517
- try {
518
- return (0, _hot_updater_plugin_core.createStorageUri)({
519
- bucket: bucketName,
520
- key,
521
- protocol: "s3"
522
- });
523
- } catch {
524
- return null;
525
- }
526
- };
527
- const getRelativeKey = (key) => {
528
- if (!normalizedBasePath) return key;
529
- const basePrefix = `${normalizedBasePath}/`;
530
- return key.startsWith(basePrefix) ? key.slice(basePrefix.length) : key;
531
- };
532
- const parseAndValidate = (storageUri) => {
533
- const parsed = (0, _hot_updater_plugin_core.parseStorageUri)(storageUri, "s3");
534
- if (parsed.bucket !== bucketName) throw new Error(`Bucket name mismatch: expected "${bucketName}", but found "${parsed.bucket}".`);
535
- return parsed;
536
- };
537
- return (0, _hot_updater_plugin_core.createStorageAdapter)({
522
+ const { bucketName, basePath, getDownloadUrl, credentials, ...s3Config } = config;
523
+ return (0, _hot_updater_storage_s3.createS3Storage)({
538
524
  name: "s3Storage",
539
- protocol: "s3",
540
- async listObjects(prefix) {
541
- const objects = [];
542
- let continuationToken;
543
- do {
544
- const response = await client.send(new _aws_sdk_client_s3.ListObjectsV2Command({
545
- Bucket: bucketName,
546
- ContinuationToken: continuationToken,
547
- Prefix: getListPrefix(prefix)
548
- }));
549
- for (const object of response.Contents ?? []) {
550
- if (!object.Key) continue;
551
- const storageUri = getListedStorageUri(object.Key);
552
- if (storageUri === null) continue;
553
- objects.push({
554
- key: getRelativeKey(object.Key),
555
- lastModifiedAt: object.LastModified,
556
- size: object.Size ?? 0,
557
- storageUri
558
- });
559
- }
560
- continuationToken = response.NextContinuationToken;
561
- } while (continuationToken);
562
- return objects;
563
- },
564
- async deleteObjects(relativeKeys) {
565
- const keys = relativeKeys.map((key) => getStorageKey(key));
566
- for (let offset = 0; offset < keys.length; offset += 1e3) {
567
- const batch = keys.slice(offset, offset + 1e3);
568
- if (batch.length === 0) continue;
569
- const response = await client.send(new _aws_sdk_client_s3.DeleteObjectsCommand({
570
- Bucket: bucketName,
571
- Delete: {
572
- Objects: batch.map((Key) => ({ Key })),
573
- Quiet: true
574
- }
575
- }));
576
- if (response.Errors && response.Errors.length > 0) throw new Error(`Failed to delete ${response.Errors.length} S3 object(s).`);
577
- }
578
- },
579
- async put({ key, body, contentLength, contentType }) {
580
- const storageKey = getStorageKey(key);
581
- const response = await new _aws_sdk_lib_storage.Upload({
582
- client,
583
- params: {
584
- Body: body,
585
- Bucket: bucketName,
586
- CacheControl: "max-age=31536000",
587
- ...contentLength === void 0 ? {} : { ContentLength: contentLength },
588
- ContentType: contentType,
589
- Key: storageKey
590
- }
591
- }).done();
592
- if (!response.Bucket || !response.Key) throw new Error("Upload failed");
593
- return { storageUri: (0, _hot_updater_plugin_core.createStorageUri)({
594
- bucket: bucketName,
595
- key: storageKey,
596
- protocol: "s3"
597
- }) };
598
- },
599
- async get({ storageUri }) {
600
- const { key } = parseAndValidate(storageUri);
601
- try {
602
- const response = await client.send(new _aws_sdk_client_s3.GetObjectCommand({
603
- Bucket: bucketName,
604
- Key: key
605
- }));
606
- if (!response.Body) return { response: null };
607
- const headers = new Headers();
608
- if (response.ContentType) headers.set("content-type", response.ContentType);
609
- if (response.ContentLength !== void 0) headers.set("content-length", String(response.ContentLength));
610
- return { response: new Response(response.Body.transformToWebStream(), { headers }) };
611
- } catch (error) {
612
- if (isObjectNotFoundError(error)) return { response: null };
613
- throw error;
614
- }
615
- },
616
- async getDownloadUrl({ storageUri }) {
617
- const { key } = parseAndValidate(storageUri);
618
- if (getDownloadUrl) return getDownloadUrl({ storageUri });
619
- const { getSignedUrl } = await import("@aws-sdk/s3-request-presigner");
620
- return { url: await getSignedUrl(client, new _aws_sdk_client_s3.GetObjectCommand({
621
- Bucket: bucketName,
622
- Key: key
623
- }), { expiresIn: PRESIGNED_URL_EXPIRES_IN_SECONDS }) };
624
- },
625
- async exists({ storageUri }) {
626
- const { key } = parseAndValidate(storageUri);
627
- try {
628
- await client.send(new _aws_sdk_client_s3.HeadObjectCommand({
629
- Bucket: bucketName,
630
- Key: key
631
- }));
632
- return { exists: true };
633
- } catch (error) {
634
- if (isObjectNotFoundError(error)) return { exists: false };
635
- throw error;
636
- }
637
- },
638
- async delete({ storageUri }) {
639
- const { key } = parseAndValidate(storageUri);
640
- await client.send(new _aws_sdk_client_s3.DeleteObjectCommand({
641
- Bucket: bucketName,
642
- Key: key
643
- }));
644
- return { deleted: true };
645
- }
525
+ protocol: "s3"
526
+ }, {
527
+ ...applyS3RuntimeAwsConfig(s3Config),
528
+ credentials,
529
+ bucketName,
530
+ basePath,
531
+ getDownloadUrl
646
532
  });
647
533
  }
648
534
  //#endregion
@@ -667,7 +553,22 @@ const EXPIRED_CREDENTIAL_ERRORS = /* @__PURE__ */ new Set([
667
553
  ]);
668
554
  /** Errors of the SDK finding no credentials, or an expired SSO session. */
669
555
  const NO_CREDENTIAL_ERRORS = /* @__PURE__ */ new Set(["CredentialsProviderError", "TokenProviderError"]);
670
- const SIGN_IN_HINT = "In CI, set them with aws-actions/configure-aws-credentials; on your machine, run aws sso login --profile <profile> and set AWS_PROFILE=<profile>";
556
+ const SIGN_IN_HINT = ["In CI, set them with aws-actions/configure-aws-credentials.", "On your machine: aws sso login --profile <profile>, then set AWS_PROFILE."];
557
+ /** The widest line a message has: the CLI prints no wider. */
558
+ const LINE_WIDTH = 76;
559
+ /** `text`, such as AWS's own message, in lines that fit, broken at spaces. */
560
+ const wrap = (text) => {
561
+ const lines = [];
562
+ let line = "";
563
+ for (const word of text.split(/\s+/u).filter(Boolean)) if (line && line.length + 1 + word.length > LINE_WIDTH) {
564
+ lines.push(line);
565
+ line = word;
566
+ } else line = line ? `${line} ${word}` : word;
567
+ if (line) lines.push(line);
568
+ return lines;
569
+ };
570
+ /** An error of one line each, for a terminal. */
571
+ const failure = (lines, cause) => new Error(lines.join("\n"), cause === void 0 ? void 0 : { cause });
671
572
  const errorName = (error) => error instanceof Error ? error.name : "";
672
573
  const errorMessage = (error) => (error instanceof Error ? error.message : String(error)).replace(/\.+$/u, "");
673
574
  const resolveEndpoint = (endpoint) => {
@@ -682,7 +583,11 @@ const resolveEndpoint = (endpoint) => {
682
583
  "127.0.0.1",
683
584
  "[::1]"
684
585
  ])).has(url.hostname);
685
- if (url.protocol !== "https:" && !(url.protocol === "http:" && loopback) || url.username || url.password || url.search || url.hash) throw new Error(`${NAME} endpoint must use HTTPS, or HTTP on a loopback address, without credentials, a query, or a fragment.`);
586
+ if (url.protocol !== "https:" && !(url.protocol === "http:" && loopback) || url.username || url.password || url.search || url.hash) throw failure([
587
+ `${NAME} can't use its endpoint.`,
588
+ "Use HTTPS, or HTTP on a loopback address.",
589
+ "Leave out credentials, a query, and a fragment."
590
+ ]);
686
591
  return url.toString();
687
592
  };
688
593
  /** Masks a value in the CLI's output, which loads `@hot-updater/cli-tools`. */
@@ -709,20 +614,20 @@ const checkCredentials = async (credentials) => {
709
614
  */
710
615
  const secretsManager = ({ credentials, endpoint, prefix = "hot-updater/", region }) => {
711
616
  const checkSettings = () => {
712
- if (typeof region !== "string" || !REGION_PATTERN.test(region)) throw new Error(`${NAME} needs an AWS region, such as "us-east-1", and ${JSON.stringify(region)} is not one.`);
617
+ if (typeof region !== "string" || !REGION_PATTERN.test(region)) throw failure([`${NAME} can't use the region ${JSON.stringify(region)}.`, "Set an AWS region, such as \"us-east-1\"."]);
713
618
  if (prefix !== "" && !SECRET_ID_PATTERN.test(prefix)) throw new Error(`${NAME}'s prefix takes letters, digits, and /_+=.@- only.`);
714
619
  };
715
620
  const secretId = (name) => {
716
621
  checkSettings();
717
622
  const id = `${prefix}${name}`;
718
- if (!SECRET_ID_PATTERN.test(name) || id.length > MAX_SECRET_ID_LENGTH) throw new Error(`${NAME} cannot name a secret ${JSON.stringify(id)}: AWS takes up to ${MAX_SECRET_ID_LENGTH} letters, digits, and /_+=.@- characters.`);
623
+ if (!SECRET_ID_PATTERN.test(name) || id.length > MAX_SECRET_ID_LENGTH) throw failure([`${NAME} can't name a secret ${JSON.stringify(id)}.`, `AWS takes up to ${MAX_SECRET_ID_LENGTH} letters, digits, and /_+=.@- characters.`]);
719
624
  return id;
720
625
  };
721
626
  const resolveCredentials = () => {
722
627
  if (credentials !== void 0) return async () => checkCredentials(typeof credentials === "function" ? await credentials() : credentials);
723
628
  const accessKeyId = process.env["AWS_ACCESS_KEY_ID"];
724
629
  const secretAccessKey = process.env["AWS_SECRET_ACCESS_KEY"];
725
- if (Boolean(accessKeyId) !== Boolean(secretAccessKey)) throw new Error(`${NAME} found ${accessKeyId ? "AWS_ACCESS_KEY_ID" : "AWS_SECRET_ACCESS_KEY"} without ${accessKeyId ? "AWS_SECRET_ACCESS_KEY" : "AWS_ACCESS_KEY_ID"} in the environment: set both, or neither to use a profile.`);
630
+ if (Boolean(accessKeyId) !== Boolean(secretAccessKey)) throw failure([`${NAME} found ${accessKeyId ? "AWS_ACCESS_KEY_ID" : "AWS_SECRET_ACCESS_KEY"} without ${accessKeyId ? "AWS_SECRET_ACCESS_KEY" : "AWS_ACCESS_KEY_ID"}.`, "Set both, or neither to use a profile."]);
726
631
  };
727
632
  let opened;
728
633
  const open = () => {
@@ -745,12 +650,40 @@ const secretsManager = ({ credentials, endpoint, prefix = "hot-updater/", region
745
650
  const describeFailure = (verb, id, error, permissions) => {
746
651
  const name = errorName(error);
747
652
  const status = error?.$metadata?.httpStatusCode;
748
- const detail = `${name || "Error"}${status ? ` (${status})` : ""}: ${errorMessage(error)}`;
749
- if (NO_CREDENTIAL_ERRORS.has(name)) return new Error(`${NAME} found no usable AWS credentials to ${verb} ${JSON.stringify(id)} (${errorMessage(error)}). ${SIGN_IN_HINT}.`, { cause: error });
750
- if (EXPIRED_CREDENTIAL_ERRORS.has(name)) return new Error(`AWS Secrets Manager refused the credentials to ${verb} ${JSON.stringify(id)}, ${detail}. They are expired or invalid: sign in again. ${SIGN_IN_HINT}.`, { cause: error });
751
- if (name === "AccessDeniedException") return new Error(`AWS Secrets Manager refused to ${verb} ${JSON.stringify(id)}, ${detail}. The AWS identity needs ${permissions} on it, and on a secret encrypted with a customer managed KMS key, kms:Decrypt to read and kms:GenerateDataKey to store.`, { cause: error });
752
- if (/marked for deletion|scheduled for deletion/iu.test(errorMessage(error))) return new Error(`AWS Secrets Manager cannot ${verb} ${JSON.stringify(id)}: it is scheduled for deletion. Restore it with aws secretsmanager restore-secret --secret-id ${id}, or wait until it is deleted.`, { cause: error });
753
- return new Error(`AWS Secrets Manager failed to ${verb} ${JSON.stringify(id)}, ${detail}.`, { cause: error });
653
+ const detail = wrap(`${name || "Error"}${status ? ` (${status})` : ""}: ${errorMessage(error)}.`);
654
+ const secret = `Secret: ${id}`;
655
+ if (NO_CREDENTIAL_ERRORS.has(name)) return failure([
656
+ `${NAME} found no AWS credentials to ${verb} a secret.`,
657
+ secret,
658
+ ...wrap(`${errorMessage(error)}.`),
659
+ ...SIGN_IN_HINT
660
+ ], error);
661
+ if (EXPIRED_CREDENTIAL_ERRORS.has(name)) return failure([
662
+ `AWS Secrets Manager refused the sign-in to ${verb} a secret.`,
663
+ secret,
664
+ ...detail,
665
+ "They are expired or invalid: sign in again.",
666
+ ...SIGN_IN_HINT
667
+ ], error);
668
+ if (name === "AccessDeniedException") return failure([
669
+ `AWS Secrets Manager refused to ${verb} a secret.`,
670
+ secret,
671
+ ...detail,
672
+ `The AWS identity needs ${permissions} on it.`,
673
+ `With a customer managed KMS key, it needs ${verb === "read" ? "kms:Decrypt" : "kms:GenerateDataKey"} too.`
674
+ ], error);
675
+ if (/marked for deletion|scheduled for deletion/iu.test(errorMessage(error))) return failure([
676
+ `AWS Secrets Manager can't ${verb} a secret.`,
677
+ secret,
678
+ "It is scheduled for deletion. Restore it, or wait until it is gone:",
679
+ "aws secretsmanager restore-secret \\",
680
+ ` --secret-id ${id}`
681
+ ], error);
682
+ return failure([
683
+ `AWS Secrets Manager failed to ${verb} a secret.`,
684
+ secret,
685
+ ...detail
686
+ ], error);
754
687
  };
755
688
  const read = async ({ sdk, client }, id) => {
756
689
  let output;
@@ -780,8 +713,8 @@ const secretsManager = ({ credentials, endpoint, prefix = "hot-updater/", region
780
713
  },
781
714
  set: async (name, value) => {
782
715
  const id = secretId(name);
783
- if (typeof value !== "string" || value === "") throw new Error(`${NAME} cannot store an empty ${id}.`);
784
- if (Buffer.byteLength(value, "utf8") > MAX_SECRET_BYTES) throw new Error(`${NAME} cannot store ${id}: AWS takes up to ${MAX_SECRET_BYTES} bytes per secret.`);
716
+ if (typeof value !== "string" || value === "") throw new Error(`${NAME} can't store an empty ${id}.`);
717
+ if (Buffer.byteLength(value, "utf8") > MAX_SECRET_BYTES) throw failure([`${NAME} can't store ${id}.`, `AWS takes up to ${MAX_SECRET_BYTES} bytes per secret.`]);
785
718
  await mask([value]);
786
719
  const { sdk, client } = await open();
787
720
  const token = (0, node_crypto.randomUUID)();
package/dist/index.d.cts CHANGED
@@ -1,8 +1,25 @@
1
1
  import { DynamoDBClientConfig } from "@aws-sdk/client-dynamodb";
2
- import { EngineDatabase, PluginTables, SecretsAdapter, StorageAdapter, StorageAdapterWith } from "@hot-updater/plugin-core";
2
+ import { EngineDatabase, Lazy, PluginTables, SecretsAdapter, StorageAdapter, StorageAdapterWith } from "@hot-updater/plugin-core";
3
3
  import { S3ClientConfig } from "@aws-sdk/client-s3";
4
+ import { S3StorageOperations } from "@hot-updater/storage-s3";
5
+ //#region src/lazyCredentials.d.ts
6
+ type SdkCredentials = S3ClientConfig["credentials"];
7
+ /**
8
+ * Access keys whose fields may each be a function that returns them, such
9
+ * as `secret("<name>")` in `hot-updater.config.ts`, called on first use.
10
+ */
11
+ interface LazyAwsCredentials {
12
+ readonly accessKeyId: Lazy<string>;
13
+ readonly secretAccessKey: Lazy<string>;
14
+ readonly sessionToken?: Lazy<string>;
15
+ }
16
+ /** The credentials the AWS SDK takes, or access keys with lazy fields. */
17
+ type AwsCredentialsOption = SdkCredentials | LazyAwsCredentials;
18
+ //#endregion
4
19
  //#region src/dynamoDB.d.ts
5
- interface DynamoDBConfig extends DynamoDBClientConfig {
20
+ interface DynamoDBConfig extends Omit<DynamoDBClientConfig, "credentials"> {
21
+ /** The SDK's credentials, or access keys whose fields are lazy. */
22
+ readonly credentials?: AwsCredentialsOption;
6
23
  readonly apiBasePath?: string;
7
24
  readonly cloudfrontDistributionId?: string;
8
25
  readonly shouldWaitForInvalidation?: boolean;
@@ -59,7 +76,9 @@ declare const cloudFrontDownloadUrl: (config: CloudFrontDownloadUrlOptions) => (
59
76
  }>;
60
77
  //#endregion
61
78
  //#region src/s3Storage.d.ts
62
- interface S3StorageConfig extends S3ClientConfig {
79
+ interface S3StorageConfig extends Omit<S3ClientConfig, "credentials"> {
80
+ /** The SDK's credentials, or access keys whose fields are lazy. */
81
+ credentials?: AwsCredentialsOption;
63
82
  bucketName: string;
64
83
  /** Base path where bundles will be stored in the bucket. */
65
84
  basePath?: string;
@@ -70,7 +89,6 @@ interface S3StorageConfig extends S3ClientConfig {
70
89
  */
71
90
  getDownloadUrl?: StorageAdapter["getDownloadUrl"];
72
91
  }
73
- type S3StorageOperations = "put" | "get" | "getDownloadUrl" | "exists" | "delete" | "listObjects" | "deleteObjects";
74
92
  declare function s3Storage(config: S3StorageConfig): StorageAdapterWith<S3StorageOperations>;
75
93
  //#endregion
76
94
  //#region src/secretsManager.d.ts
@@ -85,7 +103,7 @@ interface SecretsManagerOptions {
85
103
  /** The AWS region the secrets are in, such as `"us-east-1"`. */
86
104
  readonly region: string;
87
105
  /**
88
- * Put before every name to make its secret's name, so `secret:asc-api-key`
106
+ * Put before every name to make its secret's name, so `secret("asc-api-key")`
89
107
  * reads the secret `hot-updater/asc-api-key` and one IAM resource,
90
108
  * `secret:hot-updater/*`, covers them all. Letters, digits, and
91
109
  * `/_+=.@-`.
package/dist/index.d.mts CHANGED
@@ -1,8 +1,25 @@
1
1
  import { DynamoDBClientConfig } from "@aws-sdk/client-dynamodb";
2
- import { EngineDatabase, PluginTables, SecretsAdapter, StorageAdapter, StorageAdapterWith } from "@hot-updater/plugin-core";
2
+ import { EngineDatabase, Lazy, PluginTables, SecretsAdapter, StorageAdapter, StorageAdapterWith } from "@hot-updater/plugin-core";
3
+ import { S3StorageOperations } from "@hot-updater/storage-s3";
3
4
  import { S3ClientConfig } from "@aws-sdk/client-s3";
5
+ //#region src/lazyCredentials.d.ts
6
+ type SdkCredentials = S3ClientConfig["credentials"];
7
+ /**
8
+ * Access keys whose fields may each be a function that returns them, such
9
+ * as `secret("<name>")` in `hot-updater.config.ts`, called on first use.
10
+ */
11
+ interface LazyAwsCredentials {
12
+ readonly accessKeyId: Lazy<string>;
13
+ readonly secretAccessKey: Lazy<string>;
14
+ readonly sessionToken?: Lazy<string>;
15
+ }
16
+ /** The credentials the AWS SDK takes, or access keys with lazy fields. */
17
+ type AwsCredentialsOption = SdkCredentials | LazyAwsCredentials;
18
+ //#endregion
4
19
  //#region src/dynamoDB.d.ts
5
- interface DynamoDBConfig extends DynamoDBClientConfig {
20
+ interface DynamoDBConfig extends Omit<DynamoDBClientConfig, "credentials"> {
21
+ /** The SDK's credentials, or access keys whose fields are lazy. */
22
+ readonly credentials?: AwsCredentialsOption;
6
23
  readonly apiBasePath?: string;
7
24
  readonly cloudfrontDistributionId?: string;
8
25
  readonly shouldWaitForInvalidation?: boolean;
@@ -59,7 +76,9 @@ declare const cloudFrontDownloadUrl: (config: CloudFrontDownloadUrlOptions) => (
59
76
  }>;
60
77
  //#endregion
61
78
  //#region src/s3Storage.d.ts
62
- interface S3StorageConfig extends S3ClientConfig {
79
+ interface S3StorageConfig extends Omit<S3ClientConfig, "credentials"> {
80
+ /** The SDK's credentials, or access keys whose fields are lazy. */
81
+ credentials?: AwsCredentialsOption;
63
82
  bucketName: string;
64
83
  /** Base path where bundles will be stored in the bucket. */
65
84
  basePath?: string;
@@ -70,7 +89,6 @@ interface S3StorageConfig extends S3ClientConfig {
70
89
  */
71
90
  getDownloadUrl?: StorageAdapter["getDownloadUrl"];
72
91
  }
73
- type S3StorageOperations = "put" | "get" | "getDownloadUrl" | "exists" | "delete" | "listObjects" | "deleteObjects";
74
92
  declare function s3Storage(config: S3StorageConfig): StorageAdapterWith<S3StorageOperations>;
75
93
  //#endregion
76
94
  //#region src/secretsManager.d.ts
@@ -85,7 +103,7 @@ interface SecretsManagerOptions {
85
103
  /** The AWS region the secrets are in, such as `"us-east-1"`. */
86
104
  readonly region: string;
87
105
  /**
88
- * Put before every name to make its secret's name, so `secret:asc-api-key`
106
+ * Put before every name to make its secret's name, so `secret("asc-api-key")`
89
107
  * reads the secret `hot-updater/asc-api-key` and one IAM resource,
90
108
  * `secret:hot-updater/*`, covers them all. Letters, digits, and
91
109
  * `/_+=.@-`.