@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.
- package/dist/iac/index.cjs +180 -124
- package/dist/iac/index.d.cts +15 -8
- package/dist/iac/index.d.mts +15 -8
- package/dist/iac/index.mjs +185 -129
- package/dist/index.cjs +92 -49
- package/dist/index.d.cts +120 -29
- package/dist/index.d.mts +120 -29
- package/dist/index.mjs +95 -52
- package/dist/lambda/index.cjs +11299 -9548
- package/package.json +6 -6
package/dist/iac/index.cjs
CHANGED
|
@@ -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
|
|
6350
|
-
|
|
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 =
|
|
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
|
|
6457
|
+
const RequestItems = { [tableName]: {
|
|
6445
6458
|
Keys: pending,
|
|
6446
6459
|
ConsistentRead: true
|
|
6447
|
-
} }
|
|
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
|
-
|
|
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
|
-
...
|
|
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
|
-
/**
|
|
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.
|
|
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
|
-
|
|
6516
|
-
AttributeName:
|
|
6517
|
-
|
|
6518
|
-
}
|
|
6519
|
-
|
|
6520
|
-
|
|
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
|
|
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
|
|
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
|
|
6555
|
-
*
|
|
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 =
|
|
6603
|
+
const migrateDynamoDB = (config, plugins = []) => {
|
|
6558
6604
|
const { client, adapter } = adapterOf(config);
|
|
6559
|
-
|
|
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
|
-
|
|
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
|
|
7209
|
+
});
|
|
7210
|
+
const buildReleaseCatalogCachePolicyConfig = (clientHeaders) => ({
|
|
7163
7211
|
Name: "HotUpdaterReleaseCatalogV1",
|
|
7164
|
-
Comment: "Cache Release catalogs by canonical path,
|
|
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
|
|
7223
|
+
});
|
|
7224
|
+
const buildOriginRequestPolicyConfig = (clientHeaders) => ({
|
|
7183
7225
|
Name: "HotUpdaterManagedApiOriginRequestV2",
|
|
7184
|
-
Comment: "Forward managed API bodies, query strings, and the
|
|
7226
|
+
Comment: "Forward managed API bodies, query strings, and the client credential",
|
|
7185
7227
|
HeadersConfig: {
|
|
7186
7228
|
HeaderBehavior: "whitelist",
|
|
7187
7229
|
Headers: {
|
|
7188
|
-
Quantity:
|
|
7189
|
-
Items: ["content-type",
|
|
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 ===
|
|
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:
|
|
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,
|
|
7554
|
-
this.getOrCreateCachePolicy(cloudfrontClient,
|
|
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
|
|
7765
|
-
*
|
|
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.
|
|
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
|
|
7808
|
-
await this.client.
|
|
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
|
-
/**
|
|
7814
|
-
|
|
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
|
|
8582
|
+
let credential;
|
|
8526
8583
|
try {
|
|
8527
|
-
|
|
8528
|
-
|
|
8529
|
-
|
|
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)({
|
|
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
|
-
|
|
8577
|
-
|
|
8578
|
-
|
|
8579
|
-
|
|
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;
|
package/dist/iac/index.d.cts
CHANGED
|
@@ -21,8 +21,9 @@ declare const buildDynamoDBCreateTableInput: (tableName: string) => {
|
|
|
21
21
|
TableName: string;
|
|
22
22
|
};
|
|
23
23
|
/**
|
|
24
|
-
* The schema settings items
|
|
25
|
-
*
|
|
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,
|
|
124
|
+
export { LAMBDA_EDGE_TRUST_POLICY, buildDynamoDBBackupInput, buildDynamoDBCreateTableInput, buildDynamoDBPolicy, buildDynamoDBSchemaSettingsInput, buildDynamoDBTimeToLiveInput, buildS3Policy, buildSsmPolicy, prepareDynamoDBDeployment, runInit };
|
package/dist/iac/index.d.mts
CHANGED
|
@@ -21,8 +21,9 @@ declare const buildDynamoDBCreateTableInput: (tableName: string) => {
|
|
|
21
21
|
TableName: string;
|
|
22
22
|
};
|
|
23
23
|
/**
|
|
24
|
-
* The schema settings items
|
|
25
|
-
*
|
|
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,
|
|
124
|
+
export { LAMBDA_EDGE_TRUST_POLICY, buildDynamoDBBackupInput, buildDynamoDBCreateTableInput, buildDynamoDBPolicy, buildDynamoDBSchemaSettingsInput, buildDynamoDBTimeToLiveInput, buildS3Policy, buildSsmPolicy, prepareDynamoDBDeployment, runInit };
|