@hot-updater/aws 1.0.0-rc.16 → 1.0.0-rc.18

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.
@@ -6341,17 +6341,27 @@ const DYNAMODB_LIMITS = {
6341
6341
  sk: 1024
6342
6342
  }
6343
6343
  };
6344
- /** BatchGetItem takes 100 keys a call. */
6344
+ /** BatchGetItem takes 100 keys a call, and BatchWriteItem 25 requests. */
6345
6345
  const GET_BATCH = 100;
6346
+ const WRITE_BATCH = 25;
6347
+ /**
6348
+ * The table's TTL attribute: epoch seconds on every item of a row that
6349
+ * expires, which DynamoDB deletes for free. No column is named like it.
6350
+ */
6351
+ const DYNAMODB_TTL_ATTRIBUTE = "_ttl";
6346
6352
  /** A column named like a key attribute is stored with a leading dot, which no column name has. */
6347
6353
  const attribute = (column) => column === "pk" || column === "sk" ? `.${column}` : column;
6348
6354
  const renamed = (record, rename) => Object.fromEntries(Object.entries(record).map(([name, value]) => [rename(name), value]));
6349
- const toItem = (key, row) => ({
6350
- ...renamed(row, attribute),
6355
+ const ttl = (expiresAt) => expiresAt === void 0 ? {} : { _ttl: Math.ceil(expiresAt / 1e3) };
6356
+ const toItem = (key, row, expiresAt) => ({
6357
+ ...renamed({
6358
+ ...row,
6359
+ ...ttl(expiresAt)
6360
+ }, attribute),
6351
6361
  pk: key.pk,
6352
6362
  sk: key.sk
6353
6363
  });
6354
- const toRow = ({ pk: _pk, sk: _sk, ...item }) => renamed(item, (name) => name.replace(/^\./, ""));
6364
+ const toRow = ({ pk: _pk, sk: _sk, _ttl, ...item }) => renamed(item, (name) => name.replace(/^\./, ""));
6355
6365
  const idOf = ({ pk, sk }) => JSON.stringify([pk, sk]);
6356
6366
  const nameOf = (error) => error.name;
6357
6367
  const sleep = (milliseconds) => new Promise((resolve) => setTimeout(resolve, milliseconds));
@@ -6383,14 +6393,17 @@ const toTransactItem = (tableName, op) => {
6383
6393
  sk: op.key.sk
6384
6394
  };
6385
6395
  switch (op.type) {
6386
- case "put": return { Put: item({ Item: toItem(op.key, op.value) }) };
6396
+ case "put": return { Put: item({ Item: toItem(op.key, op.value, op.expiresAt) }) };
6387
6397
  case "delete": return { Delete: item({ Key }) };
6388
6398
  case "check": return { ConditionCheck: item({
6389
6399
  Key,
6390
6400
  ConditionExpression: guard
6391
6401
  }) };
6392
6402
  case "add": {
6393
- const init = op.init ?? {};
6403
+ const init = {
6404
+ ...op.init,
6405
+ ...ttl(op.expiresAt)
6406
+ };
6394
6407
  return { Update: item({
6395
6408
  Key,
6396
6409
  UpdateExpression: `SET ${Object.keys({
@@ -6441,19 +6454,36 @@ const createDynamoDBStore = ({ client, tableName, nativePageSize }) => {
6441
6454
  }));
6442
6455
  for (let round = 0; pending.length > 0; round += 1) {
6443
6456
  if (round > 0) await sleep(2 ** round * 10);
6444
- const result = await documents.send(new _aws_sdk_lib_dynamodb.BatchGetCommand({ RequestItems: { [tableName]: {
6457
+ const RequestItems = { [tableName]: {
6445
6458
  Keys: pending,
6446
6459
  ConsistentRead: true
6447
- } } })).catch(missingTable);
6460
+ } };
6461
+ const result = await documents.send(new _aws_sdk_lib_dynamodb.BatchGetCommand({ RequestItems })).catch(missingTable);
6448
6462
  for (const item of result.Responses?.[tableName] ?? []) found.set(idOf(item), toRow(item));
6449
6463
  pending = result.UnprocessedKeys?.[tableName]?.Keys ?? [];
6450
6464
  }
6451
6465
  }
6452
6466
  return keys.map((key) => found.get(idOf(key)) ?? null);
6453
6467
  },
6468
+ /** Plain BatchWriteItem deletes: 1 write unit per KB each, not a transaction's 2. */
6469
+ async deleteConsumed(keys) {
6470
+ for (let at = 0; at < keys.length; at += WRITE_BATCH) {
6471
+ let pending = keys.slice(at, at + WRITE_BATCH).map(({ pk, sk }) => ({ DeleteRequest: { Key: {
6472
+ pk,
6473
+ sk
6474
+ } } }));
6475
+ for (let round = 0; pending.length > 0; round += 1) {
6476
+ if (round > 0) await sleep(2 ** round * 10);
6477
+ const RequestItems = { [tableName]: pending };
6478
+ const { UnprocessedItems } = await documents.send(new _aws_sdk_lib_dynamodb.BatchWriteCommand({ RequestItems })).catch(missingTable);
6479
+ pending = UnprocessedItems?.[tableName] ?? [];
6480
+ }
6481
+ }
6482
+ },
6454
6483
  async query({ pk, gte, lt, lte, order, limit, after }) {
6455
6484
  let condition = "pk = :pk";
6456
- if (gte !== void 0 && lt !== void 0) condition += " AND sk BETWEEN :gte AND :lte";
6485
+ const between = gte !== void 0 && lt !== void 0;
6486
+ if (between) condition += " AND sk BETWEEN :gte AND :lte";
6457
6487
  else if (gte !== void 0) condition += " AND sk >= :gte";
6458
6488
  else if (lt !== void 0) condition += " AND sk < :lt";
6459
6489
  const result = await documents.send(new _aws_sdk_lib_dynamodb.QueryCommand({
@@ -6462,7 +6492,7 @@ const createDynamoDBStore = ({ client, tableName, nativePageSize }) => {
6462
6492
  ExpressionAttributeValues: {
6463
6493
  ":pk": pk,
6464
6494
  ":gte": gte,
6465
- ...gte !== void 0 && lt !== void 0 ? { ":lte": lte ?? lt } : { ":lt": lt }
6495
+ ...between ? { ":lte": lte ?? lt } : { ":lt": lt }
6466
6496
  },
6467
6497
  ScanIndexForward: order === "asc",
6468
6498
  ConsistentRead: true,
@@ -6502,65 +6532,77 @@ const createDynamoDBStore = ({ client, tableName, nativePageSize }) => {
6502
6532
  }
6503
6533
  },
6504
6534
  migrations: {
6505
- /** Creates the table for local and test runs; `hot-updater init` creates it in AWS. */
6535
+ /**
6536
+ * Creates the table for local and test runs, and turns on its TTL,
6537
+ * which every table the store runs on needs; `hot-updater init`
6538
+ * creates it in AWS.
6539
+ */
6506
6540
  async apply() {
6507
6541
  try {
6508
6542
  await client.send(new _aws_sdk_client_dynamodb.DescribeTableCommand({ TableName: tableName }));
6509
- return;
6510
6543
  } catch (error) {
6511
6544
  if (nameOf(error) !== "ResourceNotFoundException") throw error;
6545
+ await client.send(new _aws_sdk_client_dynamodb.CreateTableCommand({
6546
+ TableName: tableName,
6547
+ AttributeDefinitions: [{
6548
+ AttributeName: "pk",
6549
+ AttributeType: "S"
6550
+ }, {
6551
+ AttributeName: "sk",
6552
+ AttributeType: "S"
6553
+ }],
6554
+ KeySchema: [{
6555
+ AttributeName: "pk",
6556
+ KeyType: "HASH"
6557
+ }, {
6558
+ AttributeName: "sk",
6559
+ KeyType: "RANGE"
6560
+ }],
6561
+ BillingMode: "PAY_PER_REQUEST"
6562
+ }));
6563
+ await (0, _aws_sdk_client_dynamodb.waitUntilTableExists)({
6564
+ client,
6565
+ maxWaitTime: 120,
6566
+ minDelay: 1,
6567
+ maxDelay: 1
6568
+ }, { TableName: tableName });
6512
6569
  }
6513
- await client.send(new _aws_sdk_client_dynamodb.CreateTableCommand({
6570
+ const { TimeToLiveDescription: ttl } = await client.send(new _aws_sdk_client_dynamodb.DescribeTimeToLiveCommand({ TableName: tableName }));
6571
+ if (ttl?.TimeToLiveStatus?.startsWith("ENABL")) return;
6572
+ const input = {
6514
6573
  TableName: tableName,
6515
- AttributeDefinitions: [{
6516
- AttributeName: "pk",
6517
- AttributeType: "S"
6518
- }, {
6519
- AttributeName: "sk",
6520
- AttributeType: "S"
6521
- }],
6522
- KeySchema: [{
6523
- AttributeName: "pk",
6524
- KeyType: "HASH"
6525
- }, {
6526
- AttributeName: "sk",
6527
- KeyType: "RANGE"
6528
- }],
6529
- BillingMode: "PAY_PER_REQUEST"
6530
- }));
6531
- await (0, _aws_sdk_client_dynamodb.waitUntilTableExists)({
6532
- client,
6533
- maxWaitTime: 120,
6534
- minDelay: 1,
6535
- maxDelay: 1
6536
- }, { TableName: tableName });
6574
+ TimeToLiveSpecification: {
6575
+ AttributeName: DYNAMODB_TTL_ATTRIBUTE,
6576
+ Enabled: true
6577
+ }
6578
+ };
6579
+ await client.send(new _aws_sdk_client_dynamodb.UpdateTimeToLiveCommand(input));
6537
6580
  } }
6538
6581
  };
6539
6582
  };
6540
6583
  //#endregion
6541
6584
  //#region src/dynamoDB.ts
6542
- /** The DynamoDB client ignores the CloudFront settings in its config. */
6585
+ /** The DynamoDB client ignores the CloudFront and batching settings. */
6543
6586
  const adapterOf = ({ tableName, ...clientConfig }) => {
6544
6587
  const client = new _aws_sdk_client_dynamodb.DynamoDBClient(clientConfig);
6588
+ const store = createDynamoDBStore({
6589
+ client,
6590
+ tableName
6591
+ });
6545
6592
  return {
6546
6593
  client,
6547
- adapter: (0, _hot_updater_server_database.createKvAdapter)({ store: createDynamoDBStore({
6548
- client,
6549
- tableName
6550
- }) })
6594
+ adapter: (0, _hot_updater_server_database.createKvAdapter)({ store })
6551
6595
  };
6552
6596
  };
6553
6597
  /**
6554
- * Creates the table when it is missing and writes the schema settings the
6555
- * database checks before its first read. `hot-updater init` runs it in AWS.
6598
+ * Creates the table when it is missing and writes the schema settings of
6599
+ * core and `plugins`, the plugins the server runs, which the database checks
6600
+ * before its first read. `hot-updater init` runs it in AWS with the managed
6601
+ * server's plugins.
6556
6602
  */
6557
- const migrateDynamoDB = async (config) => {
6603
+ const migrateDynamoDB = (config, plugins = []) => {
6558
6604
  const { client, adapter } = adapterOf(config);
6559
- try {
6560
- await (0, _hot_updater_server_database.migrateBuiltInSchema)(adapter, "dynamoDB");
6561
- } finally {
6562
- client.destroy();
6563
- }
6605
+ return (0, _hot_updater_server_database.migrateCoreSchema)(adapter, "dynamoDB", plugins).finally(() => client.destroy());
6564
6606
  };
6565
6607
  /**
6566
6608
  * Hot Updater's database on one DynamoDB table, through the storage engine.
@@ -6574,7 +6616,8 @@ const dynamoDB = (config) => {
6574
6616
  ...(0, _hot_updater_server_database.createEngineDatabase)({
6575
6617
  name: "dynamoDB",
6576
6618
  adapter,
6577
- onCachedRoutesChange: cloudFront?.invalidate
6619
+ onCachedRoutesChange: cloudFront?.invalidate,
6620
+ aggregateBatching: config.aggregateBatching ?? {}
6578
6621
  }),
6579
6622
  async dispose() {
6580
6623
  await adapter.dispose?.();
@@ -7139,7 +7182,18 @@ function delay(ms, { signal } = {}) {
7139
7182
  }
7140
7183
  //#endregion
7141
7184
  //#region iac/cloudfrontDistributionConfig.ts
7142
- const HOT_UPDATER_SHARED_CACHE_POLICY_CONFIG = {
7185
+ /**
7186
+ * The request headers a cache key holds: those the server's client-route
7187
+ * policy reads, so responses to different credentials never share an entry.
7188
+ */
7189
+ const headersConfig = (clientHeaders) => clientHeaders.length === 0 ? { HeaderBehavior: "none" } : {
7190
+ HeaderBehavior: "whitelist",
7191
+ Headers: {
7192
+ Quantity: clientHeaders.length,
7193
+ Items: [...clientHeaders]
7194
+ }
7195
+ };
7196
+ const buildSharedCachePolicyConfig = (clientHeaders) => ({
7143
7197
  Name: "HotUpdaterOriginCacheControlV2",
7144
7198
  Comment: "Honor origin Cache-Control without forwarding viewer Host/cookies/query strings",
7145
7199
  DefaultTTL: 0,
@@ -7148,50 +7202,38 @@ const HOT_UPDATER_SHARED_CACHE_POLICY_CONFIG = {
7148
7202
  ParametersInCacheKeyAndForwardedToOrigin: {
7149
7203
  EnableAcceptEncodingBrotli: true,
7150
7204
  EnableAcceptEncodingGzip: true,
7151
- HeadersConfig: {
7152
- HeaderBehavior: "whitelist",
7153
- Headers: {
7154
- Quantity: 1,
7155
- Items: ["x-api-key"]
7156
- }
7157
- },
7205
+ HeadersConfig: headersConfig(clientHeaders),
7158
7206
  CookiesConfig: { CookieBehavior: "none" },
7159
7207
  QueryStringsConfig: { QueryStringBehavior: "none" }
7160
7208
  }
7161
- };
7162
- const HOT_UPDATER_RELEASE_CATALOG_CACHE_POLICY_CONFIG = {
7209
+ });
7210
+ const buildReleaseCatalogCachePolicyConfig = (clientHeaders) => ({
7163
7211
  Name: "HotUpdaterReleaseCatalogV1",
7164
- Comment: "Cache Release catalogs by canonical path, API key, and encoding",
7212
+ Comment: "Cache Release catalogs by canonical path, client credential, and encoding",
7165
7213
  DefaultTTL: 0,
7166
7214
  MaxTTL: 5,
7167
7215
  MinTTL: 0,
7168
7216
  ParametersInCacheKeyAndForwardedToOrigin: {
7169
7217
  EnableAcceptEncodingBrotli: true,
7170
7218
  EnableAcceptEncodingGzip: true,
7171
- HeadersConfig: {
7172
- HeaderBehavior: "whitelist",
7173
- Headers: {
7174
- Quantity: 1,
7175
- Items: ["x-api-key"]
7176
- }
7177
- },
7219
+ HeadersConfig: headersConfig(clientHeaders),
7178
7220
  CookiesConfig: { CookieBehavior: "none" },
7179
7221
  QueryStringsConfig: { QueryStringBehavior: "none" }
7180
7222
  }
7181
- };
7182
- const HOT_UPDATER_ORIGIN_REQUEST_POLICY_CONFIG = {
7223
+ });
7224
+ const buildOriginRequestPolicyConfig = (clientHeaders) => ({
7183
7225
  Name: "HotUpdaterManagedApiOriginRequestV2",
7184
- Comment: "Forward managed API bodies, query strings, and the API key",
7226
+ Comment: "Forward managed API bodies, query strings, and the client credential",
7185
7227
  HeadersConfig: {
7186
7228
  HeaderBehavior: "whitelist",
7187
7229
  Headers: {
7188
- Quantity: 2,
7189
- Items: ["content-type", "x-api-key"]
7230
+ Quantity: clientHeaders.length + 1,
7231
+ Items: ["content-type", ...clientHeaders]
7190
7232
  }
7191
7233
  },
7192
7234
  CookiesConfig: { CookieBehavior: "none" },
7193
7235
  QueryStringsConfig: { QueryStringBehavior: "all" }
7194
- };
7236
+ });
7195
7237
  const READ_ONLY_METHODS = {
7196
7238
  Quantity: 2,
7197
7239
  Items: ["HEAD", "GET"],
@@ -7459,7 +7501,7 @@ var CloudFrontManager = class {
7459
7501
  if (!cachePolicyId) throw new Error("Failed to create shared cache policy");
7460
7502
  return cachePolicyId;
7461
7503
  }
7462
- async getOrCreateOriginRequestPolicy(cloudfrontClient) {
7504
+ async getOrCreateOriginRequestPolicy(cloudfrontClient, config) {
7463
7505
  const existingPolicyId = (await findInPaginatedCloudFrontList({
7464
7506
  listPage: async (marker) => {
7465
7507
  const response = await cloudfrontClient.listOriginRequestPolicies({
@@ -7471,10 +7513,10 @@ var CloudFrontManager = class {
7471
7513
  nextMarker: response.OriginRequestPolicyList?.NextMarker
7472
7514
  };
7473
7515
  },
7474
- matches: (policy) => policy.OriginRequestPolicy?.OriginRequestPolicyConfig?.Name === HOT_UPDATER_ORIGIN_REQUEST_POLICY_CONFIG.Name
7516
+ matches: (policy) => policy.OriginRequestPolicy?.OriginRequestPolicyConfig?.Name === config.Name
7475
7517
  }))?.OriginRequestPolicy?.Id;
7476
7518
  if (existingPolicyId) return existingPolicyId;
7477
- const policyId = (await cloudfrontClient.createOriginRequestPolicy({ OriginRequestPolicyConfig: HOT_UPDATER_ORIGIN_REQUEST_POLICY_CONFIG })).OriginRequestPolicy?.Id;
7519
+ const policyId = (await cloudfrontClient.createOriginRequestPolicy({ OriginRequestPolicyConfig: config })).OriginRequestPolicy?.Id;
7478
7520
  if (!policyId) throw new Error("Failed to create origin request policy");
7479
7521
  return policyId;
7480
7522
  }
@@ -7550,9 +7592,9 @@ var CloudFrontManager = class {
7550
7592
  let originRequestPolicyId;
7551
7593
  try {
7552
7594
  [sharedCachePolicyId, releaseCatalogCachePolicyId, originRequestPolicyId] = await Promise.all([
7553
- this.getOrCreateCachePolicy(cloudfrontClient, HOT_UPDATER_SHARED_CACHE_POLICY_CONFIG),
7554
- this.getOrCreateCachePolicy(cloudfrontClient, HOT_UPDATER_RELEASE_CATALOG_CACHE_POLICY_CONFIG),
7555
- this.getOrCreateOriginRequestPolicy(cloudfrontClient)
7595
+ this.getOrCreateCachePolicy(cloudfrontClient, buildSharedCachePolicyConfig(options.clientHeaders)),
7596
+ this.getOrCreateCachePolicy(cloudfrontClient, buildReleaseCatalogCachePolicyConfig(options.clientHeaders)),
7597
+ this.getOrCreateOriginRequestPolicy(cloudfrontClient, buildOriginRequestPolicyConfig(options.clientHeaders))
7556
7598
  ]);
7557
7599
  } catch (error) {
7558
7600
  throw new Error(`Failed to get or create CloudFront request policies: ${error instanceof Error ? error.message : String(error)}`);
@@ -7711,6 +7753,17 @@ var DynamoDBTableSchemaError = class extends Error {
7711
7753
  this.tableName = tableName;
7712
7754
  }
7713
7755
  };
7756
+ /** TTL is on for another attribute; a table has one, and the store writes `_ttl`. */
7757
+ var DynamoDBTimeToLiveError = class extends Error {
7758
+ tableName;
7759
+ attributeName;
7760
+ name = "DynamoDBTimeToLiveError";
7761
+ constructor(tableName, attributeName) {
7762
+ super(`DynamoDB table "${tableName}" has TTL on "${attributeName}", but Hot Updater expires rows by "${DYNAMODB_TTL_ATTRIBUTE}". Turn TTL off on the table (aws dynamodb update-time-to-live --table-name ${tableName} --time-to-live-specification Enabled=false,AttributeName=${attributeName}), then rerun init.`);
7763
+ this.tableName = tableName;
7764
+ this.attributeName = attributeName;
7765
+ }
7766
+ };
7714
7767
  var DynamoDBPermissionError = class extends _hot_updater_cli_tools.InitError {
7715
7768
  tableName;
7716
7769
  region;
@@ -7761,10 +7814,11 @@ const buildDynamoDBCreateTableInput = (tableName) => ({
7761
7814
  TableName: tableName
7762
7815
  });
7763
7816
  /**
7764
- * The schema settings items the plugin checks before its first read, as one
7765
- * BatchWriteItem request: what `migrateDynamoDB` writes to a new table.
7817
+ * The schema settings items of core and the managed server's plugins, which
7818
+ * the database checks before its first read, as one BatchWriteItem request:
7819
+ * what `migrateDynamoDB` writes to a new table for those plugins.
7766
7820
  */
7767
- const buildDynamoDBSchemaSettingsInput = (tableName) => ({ RequestItems: { [tableName]: Object.entries(_hot_updater_server_database.builtInSettings).map(([key, value]) => ({ PutRequest: { Item: {
7821
+ const buildDynamoDBSchemaSettingsInput = (tableName) => ({ RequestItems: { [tableName]: Object.entries((0, _hot_updater_server_database.toolingTargetOf)(plugins).settings).map(([key, value]) => ({ PutRequest: { Item: {
7768
7822
  pk: { S: _hot_updater_server_database.SETTINGS_TABLE.name },
7769
7823
  sk: { S: (0, _hot_updater_server_database.encodeKvKey)([key]) },
7770
7824
  key: { S: key },
@@ -7775,6 +7829,17 @@ const buildDynamoDBBackupInput = (tableName) => ({
7775
7829
  PointInTimeRecoverySpecification: { PointInTimeRecoveryEnabled: true },
7776
7830
  TableName: tableName
7777
7831
  });
7832
+ /**
7833
+ * Turns on the table's TTL, which deletes expired items for free: the store
7834
+ * writes `_ttl`, in epoch seconds, on every item of a row with retention.
7835
+ */
7836
+ const buildDynamoDBTimeToLiveInput = (tableName) => ({
7837
+ TableName: tableName,
7838
+ TimeToLiveSpecification: {
7839
+ AttributeName: DYNAMODB_TTL_ATTRIBUTE,
7840
+ Enabled: true
7841
+ }
7842
+ });
7778
7843
  var DynamoDBManager = class {
7779
7844
  region;
7780
7845
  client;
@@ -7802,16 +7867,31 @@ var DynamoDBManager = class {
7802
7867
  }, { TableName: tableName });
7803
7868
  await this.ensureLifecycle(tableName);
7804
7869
  }
7870
+ /** Point-in-time recovery and TTL, each turned on only when it is off. */
7805
7871
  async ensureLifecycle(tableName) {
7806
7872
  const { ContinuousBackupsDescription } = await this.client.describeContinuousBackups({ TableName: tableName });
7807
- if (ContinuousBackupsDescription?.PointInTimeRecoveryDescription?.PointInTimeRecoveryStatus === "ENABLED") return;
7808
- await this.client.updateContinuousBackups(buildDynamoDBBackupInput(tableName));
7873
+ if (ContinuousBackupsDescription?.PointInTimeRecoveryDescription?.PointInTimeRecoveryStatus !== "ENABLED") await this.client.updateContinuousBackups(buildDynamoDBBackupInput(tableName));
7874
+ const { TimeToLiveDescription: ttl } = await this.client.describeTimeToLive({ TableName: tableName });
7875
+ const status = ttl?.TimeToLiveStatus;
7876
+ if (status === "ENABLED" || status === "ENABLING") {
7877
+ if (ttl?.AttributeName !== "_ttl") throw new DynamoDBTimeToLiveError(tableName, ttl?.AttributeName);
7878
+ return;
7879
+ }
7880
+ await this.client.updateTimeToLive(buildDynamoDBTimeToLiveInput(tableName));
7809
7881
  }
7810
7882
  };
7811
7883
  //#endregion
7812
7884
  //#region iac/iam.ts
7813
- /** The partitions the plugin's items use: each table's rows, and its index items after `#`. */
7814
- const dynamoDBLeadingKeys = () => [..._hot_updater_server_database.builtInSchema.tables, _hot_updater_server_database.SETTINGS_TABLE].flatMap(({ name }) => [name, `${name}#*`]);
7885
+ /**
7886
+ * The partitions the managed server's items use: each table's rows of core
7887
+ * and its plugins, and its index items after `#`, the log and lease tables
7888
+ * of batched aggregates included.
7889
+ */
7890
+ const dynamoDBLeadingKeys = () => [
7891
+ ...(0, _hot_updater_server_database.toolingTargetOf)(plugins).schema.tables,
7892
+ ...(0, _hot_updater_server_database.resolveSchema)([_hot_updater_server_database.aggregateBatchingModule]).tables,
7893
+ _hot_updater_server_database.SETTINGS_TABLE
7894
+ ].flatMap(({ name }) => [name, `${name}#*`]);
7815
7895
  const buildDynamoDBPolicy = (region, accountId, tableName) => {
7816
7896
  const tableArn = `arn:aws:dynamodb:${region}:${accountId}:table/${tableName}`;
7817
7897
  return {
@@ -7819,6 +7899,7 @@ const buildDynamoDBPolicy = (region, accountId, tableName) => {
7819
7899
  Statement: [{
7820
7900
  Action: [
7821
7901
  "dynamodb:BatchGetItem",
7902
+ "dynamodb:BatchWriteItem",
7822
7903
  "dynamodb:ConditionCheckItem",
7823
7904
  "dynamodb:DeleteItem",
7824
7905
  "dynamodb:GetItem",
@@ -8325,23 +8406,6 @@ const awsOptions = {
8325
8406
  }
8326
8407
  return (0, _hot_updater_cli_tools.createHotUpdaterConfigScaffoldFromBuilder)(builder, { helperStatements });
8327
8408
  };
8328
- const SOURCE_TEMPLATE = `// Add this to your App.tsx
8329
- import { HotUpdater } from "@hot-updater/react-native";
8330
-
8331
- function App() {
8332
- return null; // Replace with your app root.
8333
- }
8334
-
8335
- HotUpdater.init({
8336
- baseURL: %%source%%,
8337
- requestHeaders: {
8338
- "x-api-key": %%apiKey%%,
8339
- },
8340
- });
8341
-
8342
- // Call HotUpdater.checkForUpdate({ updateStrategy: "appVersion" })
8343
- // when your app is ready to check.
8344
- export default App;`;
8345
8409
  //#endregion
8346
8410
  //#region iac/index.ts
8347
8411
  const checkIfAwsCliInstalled = async () => {
@@ -8357,14 +8421,7 @@ const isAwsRegion = (value) => {
8357
8421
  };
8358
8422
  const prepareDynamoDBDeployment = async (input) => {
8359
8423
  await new DynamoDBManager(input.region, input.credentials).ensureTable(input.tableName);
8360
- await migrateDynamoDB(input);
8361
- };
8362
- /** The app's client key, through the managed server's apiKeys() plugin, on the table it reads. */
8363
- const prepareDynamoDBApiKey = async (input) => {
8364
- return (await (0, _hot_updater_server_db.createDatabasePluginApis)(input.database, plugins).apiKeys.provision({
8365
- existingApiKey: input.existingApiKey,
8366
- name: "AWS init"
8367
- })).apiKey;
8424
+ await migrateDynamoDB(input, plugins);
8368
8425
  };
8369
8426
  const runInit = async ({ build, envFile }) => {
8370
8427
  const nonInteractive = envFile !== void 0;
@@ -8522,13 +8579,13 @@ const runInit = async ({ build, envFile }) => {
8522
8579
  region: bucketRegion,
8523
8580
  tableName: resolvedDynamoDBTableName
8524
8581
  });
8525
- let apiKey;
8582
+ let credential;
8526
8583
  try {
8527
- apiKey = await prepareDynamoDBApiKey({
8528
- database: databasePlugin,
8529
- existingApiKey: providerEnv.HOT_UPDATER_API_KEY
8584
+ credential = await (0, _hot_updater_server_db.provisionClientCredential)(databasePlugin, plugins, {
8585
+ env: providerEnv,
8586
+ name: "AWS init"
8530
8587
  });
8531
- await (0, _hot_updater_cli_tools.makeEnv)({ HOT_UPDATER_API_KEY: apiKey });
8588
+ if (credential !== void 0) await (0, _hot_updater_cli_tools.makeEnv)({ [credential.env]: credential.value });
8532
8589
  } finally {
8533
8590
  await databasePlugin.dispose?.();
8534
8591
  }
@@ -8554,6 +8611,7 @@ const runInit = async ({ build, envFile }) => {
8554
8611
  const { distributionDomain, distributionId } = await cloudFrontManager.createOrUpdateDistribution({
8555
8612
  keyGroupId,
8556
8613
  bucketName,
8614
+ clientHeaders: (0, _hot_updater_server_db.clientAuthOf)(plugins)?.varyHeaders ?? [],
8557
8615
  distribution: selectedDistribution,
8558
8616
  functionArn
8559
8617
  });
@@ -8573,13 +8631,11 @@ const runInit = async ({ build, envFile }) => {
8573
8631
  else if (configWriteResult.status === "merged") _hot_updater_cli_tools.p.log.success("Updated 'hot-updater.config.ts' file with AWS settings.");
8574
8632
  else _hot_updater_cli_tools.p.log.warn(`Kept existing 'hot-updater.config.ts' unchanged: ${configWriteResult.reason}`);
8575
8633
  await (0, _hot_updater_cli_tools.generateHotUpdaterPlugins)("@hot-updater/aws");
8576
- const sourceUrl = `https://${distributionDomain}`;
8577
- _hot_updater_cli_tools.p.note((0, _hot_updater_cli_tools.transformTemplate)(SOURCE_TEMPLATE, {
8578
- apiKey: JSON.stringify(apiKey),
8579
- source: JSON.stringify(sourceUrl)
8580
- }));
8581
- _hot_updater_cli_tools.p.note((0, _hot_updater_cli_tools.formatApiKeyNote)(apiKey), "API Key");
8582
- _hot_updater_cli_tools.p.log.message("Store this API key separately in a secure place.");
8634
+ (0, _hot_updater_cli_tools.printAppSetup)({
8635
+ baseURL: `https://${distributionDomain}`,
8636
+ ...credential === void 0 ? {} : { credential },
8637
+ clientPlugins: (0, _hot_updater_server_db.clientPluginsOf)(plugins)
8638
+ });
8583
8639
  _hot_updater_cli_tools.p.log.message(`Next step: ${(0, _hot_updater_cli_tools.link)("https://hot-updater.dev/docs/managed/aws#step-4-changeenv-file-optional")}`);
8584
8640
  _hot_updater_cli_tools.p.log.success("Done! 🎉");
8585
8641
  };
@@ -8589,8 +8645,8 @@ exports.buildDynamoDBBackupInput = buildDynamoDBBackupInput;
8589
8645
  exports.buildDynamoDBCreateTableInput = buildDynamoDBCreateTableInput;
8590
8646
  exports.buildDynamoDBPolicy = buildDynamoDBPolicy;
8591
8647
  exports.buildDynamoDBSchemaSettingsInput = buildDynamoDBSchemaSettingsInput;
8648
+ exports.buildDynamoDBTimeToLiveInput = buildDynamoDBTimeToLiveInput;
8592
8649
  exports.buildS3Policy = buildS3Policy;
8593
8650
  exports.buildSsmPolicy = buildSsmPolicy;
8594
- exports.prepareDynamoDBApiKey = prepareDynamoDBApiKey;
8595
8651
  exports.prepareDynamoDBDeployment = prepareDynamoDBDeployment;
8596
8652
  exports.runInit = runInit;
@@ -21,8 +21,9 @@ declare const buildDynamoDBCreateTableInput: (tableName: string) => {
21
21
  TableName: string;
22
22
  };
23
23
  /**
24
- * The schema settings items the plugin checks before its first read, as one
25
- * BatchWriteItem request: what `migrateDynamoDB` writes to a new table.
24
+ * The schema settings items of core and the managed server's plugins, which
25
+ * the database checks before its first read, as one BatchWriteItem request:
26
+ * what `migrateDynamoDB` writes to a new table for those plugins.
26
27
  */
27
28
  declare const buildDynamoDBSchemaSettingsInput: (tableName: string) => {
28
29
  RequestItems: {
@@ -55,6 +56,17 @@ declare const buildDynamoDBBackupInput: (tableName: string) => {
55
56
  };
56
57
  TableName: string;
57
58
  };
59
+ /**
60
+ * Turns on the table's TTL, which deletes expired items for free: the store
61
+ * writes `_ttl`, in epoch seconds, on every item of a row with retention.
62
+ */
63
+ declare const buildDynamoDBTimeToLiveInput: (tableName: string) => {
64
+ TableName: string;
65
+ TimeToLiveSpecification: {
66
+ AttributeName: string;
67
+ Enabled: true;
68
+ };
69
+ };
58
70
  //#endregion
59
71
  //#region iac/iam.d.ts
60
72
  declare const buildDynamoDBPolicy: (region: string, accountId: string, tableName: string) => {
@@ -107,11 +119,6 @@ declare const prepareDynamoDBDeployment: (input: {
107
119
  readonly region: string;
108
120
  readonly tableName: string;
109
121
  }) => Promise<void>;
110
- /** The app's client key, through the managed server's apiKeys() plugin, on the table it reads. */
111
- declare const prepareDynamoDBApiKey: (input: {
112
- readonly database: unknown;
113
- readonly existingApiKey?: string;
114
- }) => Promise<string>;
115
122
  declare const runInit: ({ build, envFile }: RunInitOptions) => Promise<void>;
116
123
  //#endregion
117
- export { LAMBDA_EDGE_TRUST_POLICY, buildDynamoDBBackupInput, buildDynamoDBCreateTableInput, buildDynamoDBPolicy, buildDynamoDBSchemaSettingsInput, buildS3Policy, buildSsmPolicy, prepareDynamoDBApiKey, prepareDynamoDBDeployment, runInit };
124
+ export { LAMBDA_EDGE_TRUST_POLICY, buildDynamoDBBackupInput, buildDynamoDBCreateTableInput, buildDynamoDBPolicy, buildDynamoDBSchemaSettingsInput, buildDynamoDBTimeToLiveInput, buildS3Policy, buildSsmPolicy, prepareDynamoDBDeployment, runInit };
@@ -21,8 +21,9 @@ declare const buildDynamoDBCreateTableInput: (tableName: string) => {
21
21
  TableName: string;
22
22
  };
23
23
  /**
24
- * The schema settings items the plugin checks before its first read, as one
25
- * BatchWriteItem request: what `migrateDynamoDB` writes to a new table.
24
+ * The schema settings items of core and the managed server's plugins, which
25
+ * the database checks before its first read, as one BatchWriteItem request:
26
+ * what `migrateDynamoDB` writes to a new table for those plugins.
26
27
  */
27
28
  declare const buildDynamoDBSchemaSettingsInput: (tableName: string) => {
28
29
  RequestItems: {
@@ -55,6 +56,17 @@ declare const buildDynamoDBBackupInput: (tableName: string) => {
55
56
  };
56
57
  TableName: string;
57
58
  };
59
+ /**
60
+ * Turns on the table's TTL, which deletes expired items for free: the store
61
+ * writes `_ttl`, in epoch seconds, on every item of a row with retention.
62
+ */
63
+ declare const buildDynamoDBTimeToLiveInput: (tableName: string) => {
64
+ TableName: string;
65
+ TimeToLiveSpecification: {
66
+ AttributeName: string;
67
+ Enabled: true;
68
+ };
69
+ };
58
70
  //#endregion
59
71
  //#region iac/iam.d.ts
60
72
  declare const buildDynamoDBPolicy: (region: string, accountId: string, tableName: string) => {
@@ -107,11 +119,6 @@ declare const prepareDynamoDBDeployment: (input: {
107
119
  readonly region: string;
108
120
  readonly tableName: string;
109
121
  }) => Promise<void>;
110
- /** The app's client key, through the managed server's apiKeys() plugin, on the table it reads. */
111
- declare const prepareDynamoDBApiKey: (input: {
112
- readonly database: unknown;
113
- readonly existingApiKey?: string;
114
- }) => Promise<string>;
115
122
  declare const runInit: ({ build, envFile }: RunInitOptions) => Promise<void>;
116
123
  //#endregion
117
- export { LAMBDA_EDGE_TRUST_POLICY, buildDynamoDBBackupInput, buildDynamoDBCreateTableInput, buildDynamoDBPolicy, buildDynamoDBSchemaSettingsInput, buildS3Policy, buildSsmPolicy, prepareDynamoDBApiKey, prepareDynamoDBDeployment, runInit };
124
+ export { LAMBDA_EDGE_TRUST_POLICY, buildDynamoDBBackupInput, buildDynamoDBCreateTableInput, buildDynamoDBPolicy, buildDynamoDBSchemaSettingsInput, buildDynamoDBTimeToLiveInput, buildS3Policy, buildSsmPolicy, prepareDynamoDBDeployment, runInit };