@hot-updater/aws 1.0.0-rc.2 → 1.0.0-rc.4

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.
@@ -7839,10 +7839,10 @@ const hasValidBundleEventShape = (value) => {
7839
7839
  const type = field(value, "type");
7840
7840
  const fromBundleId = field(value, "from_bundle_id");
7841
7841
  const updateStrategy = field(value, "update_strategy");
7842
- return (type === "UPDATE_APPLIED" || type === "RECOVERED" || type === "RELEASE_ADOPTED") && typeof fromBundleId === "string" && (updateStrategy === "fingerprint" || updateStrategy === "appVersion") || type === "UNCHANGED" && fromBundleId === null && updateStrategy === null;
7842
+ return (type === "UPDATE_APPLIED" || type === "RECOVERED") && typeof fromBundleId === "string" && (updateStrategy === "fingerprint" || updateStrategy === "appVersion") || type === "UNCHANGED" && fromBundleId === null && updateStrategy === null;
7843
7843
  };
7844
7844
  const isBundleEventRow = (value) => typeof value === "object" && value !== null && typeof field(value, "id") === "string" && typeof field(value, "install_id") === "string" && isNullableString(field(value, "from_release_id")) && isNullableString(field(value, "to_release_id")) && typeof field(value, "to_bundle_id") === "string" && (field(value, "platform") === "ios" || field(value, "platform") === "android") && typeof field(value, "received_at_ms") === "number" && hasValidBundleEventShape(value);
7845
- const isInsightsInstallationRow = (value) => typeof value === "object" && value !== null && typeof field(value, "id") === "string" && typeof field(value, "install_id") === "string" && isNullableString(field(value, "user_id")) && isNullableString(field(value, "username")) && typeof field(value, "to_bundle_id") === "string" && (field(value, "type") === "UPDATE_APPLIED" || field(value, "type") === "RECOVERED" || field(value, "type") === "RELEASE_ADOPTED" || field(value, "type") === "UNCHANGED") && (field(value, "platform") === "ios" || field(value, "platform") === "android") && typeof field(value, "app_version") === "string" && typeof field(value, "channel") === "string" && typeof field(value, "cohort") === "string" && typeof field(value, "received_at_ms") === "number";
7845
+ const isInsightsInstallationRow = (value) => typeof value === "object" && value !== null && typeof field(value, "id") === "string" && typeof field(value, "install_id") === "string" && isNullableString(field(value, "user_id")) && isNullableString(field(value, "username")) && typeof field(value, "to_bundle_id") === "string" && (field(value, "type") === "UPDATE_APPLIED" || field(value, "type") === "RECOVERED" || field(value, "type") === "UNCHANGED") && (field(value, "platform") === "ios" || field(value, "platform") === "android") && typeof field(value, "app_version") === "string" && typeof field(value, "channel") === "string" && typeof field(value, "cohort") === "string" && typeof field(value, "received_at_ms") === "number";
7846
7846
  const isApiKeyRow = (value) => typeof value === "object" && value !== null && typeof field(value, "id") === "string" && typeof field(value, "hash") === "string" && typeof field(value, "name") === "string" && typeof field(value, "prefix") === "string" && field(value, "role") === "client" && typeof field(value, "created_at_ms") === "number" && (field(value, "revoked_at_ms") === null || typeof field(value, "revoked_at_ms") === "number");
7847
7847
  const parseOfficialRowItem = (value, partition, isRow) => {
7848
7848
  if (value.pk !== partition || typeof value.sk !== "string" || typeof value.version !== "number" || !isRow(value.row)) throw new DynamoDBStoredItemError();
@@ -7864,7 +7864,7 @@ const insightsBundlePartition = (filter) => {
7864
7864
  ]);
7865
7865
  return `${DYNAMODB_INSIGHTS_BUNDLE_PREFIX}${(0, node_crypto.createHash)("sha256").update(scope, "utf8").digest("hex")}`;
7866
7866
  };
7867
- const toInsightsBundleItem = (row) => row.type === "UNCHANGED" ? null : boundedDynamoDBMetadataItem({
7867
+ const toInsightsBundleItem = (row) => boundedDynamoDBMetadataItem({
7868
7868
  pk: insightsBundlePartition({
7869
7869
  platform: row.platform,
7870
7870
  channel: row.channel,
@@ -7950,10 +7950,10 @@ const recordDynamoDBInsightsEvent = async (store, { event: row, installation: ne
7950
7950
  TableName: store.tableName,
7951
7951
  Item: eventItem
7952
7952
  } },
7953
- ...bundleItem === null ? [] : [{ Put: {
7953
+ { Put: {
7954
7954
  TableName: store.tableName,
7955
7955
  Item: bundleItem
7956
- } }]
7956
+ } }
7957
7957
  ];
7958
7958
  if (current === null || advancesInsightsInstallation(next, current.row)) {
7959
7959
  actions.push({ Put: {
@@ -9437,6 +9437,41 @@ const hasExpectedSchema = (table) => {
9437
9437
  return keyAttributes.every((attributeName) => table?.AttributeDefinitions?.some(({ AttributeName, AttributeType }) => AttributeName === attributeName && AttributeType === "S")) && table?.BillingModeSummary?.BillingMode === "PAY_PER_REQUEST" && table.OnDemandThroughput?.MaxReadRequestUnits === onDemandThroughput.MaxReadRequestUnits && table.OnDemandThroughput?.MaxWriteRequestUnits === onDemandThroughput.MaxWriteRequestUnits && hasKeySchema(table?.KeySchema, primaryKeySchema) && hasKeySchema(updateIndex?.KeySchema, updateIndexKeySchema) && updateIndex?.OnDemandThroughput?.MaxReadRequestUnits === onDemandThroughput.MaxReadRequestUnits && updateIndex?.OnDemandThroughput?.MaxWriteRequestUnits === onDemandThroughput.MaxWriteRequestUnits && updateIndex?.Projection?.ProjectionType === "ALL";
9438
9438
  };
9439
9439
  const isResourceNotFound = (error) => typeof error === "object" && error !== null && Reflect.get(error, "name") === "ResourceNotFoundException";
9440
+ const buildDynamoDBCreateTableInput = (tableName) => ({
9441
+ AttributeDefinitions: [
9442
+ {
9443
+ AttributeName: "pk",
9444
+ AttributeType: "S"
9445
+ },
9446
+ {
9447
+ AttributeName: "sk",
9448
+ AttributeType: "S"
9449
+ },
9450
+ {
9451
+ AttributeName: "gsi1pk",
9452
+ AttributeType: "S"
9453
+ },
9454
+ {
9455
+ AttributeName: "gsi1sk",
9456
+ AttributeType: "S"
9457
+ }
9458
+ ],
9459
+ BillingMode: "PAY_PER_REQUEST",
9460
+ DeletionProtectionEnabled: true,
9461
+ GlobalSecondaryIndexes: [{
9462
+ IndexName: DYNAMODB_UPDATE_INDEX_NAME,
9463
+ KeySchema: [...updateIndexKeySchema],
9464
+ Projection: { ProjectionType: "ALL" },
9465
+ OnDemandThroughput: onDemandThroughput
9466
+ }],
9467
+ KeySchema: [...primaryKeySchema],
9468
+ OnDemandThroughput: onDemandThroughput,
9469
+ TableName: tableName
9470
+ });
9471
+ const buildDynamoDBBackupInput = (tableName) => ({
9472
+ PointInTimeRecoverySpecification: { PointInTimeRecoveryEnabled: true },
9473
+ TableName: tableName
9474
+ });
9440
9475
  var DynamoDBManager = class {
9441
9476
  region;
9442
9477
  client;
@@ -9457,37 +9492,7 @@ var DynamoDBManager = class {
9457
9492
  if (error instanceof Error && error.name === "AccessDeniedException" && error.message.includes(DYNAMODB_DESCRIBE_TABLE_ACTION)) throw new DynamoDBPermissionError(tableName, this.region, error);
9458
9493
  if (!isResourceNotFound(error)) throw error;
9459
9494
  }
9460
- await this.client.createTable({
9461
- AttributeDefinitions: [
9462
- {
9463
- AttributeName: "pk",
9464
- AttributeType: "S"
9465
- },
9466
- {
9467
- AttributeName: "sk",
9468
- AttributeType: "S"
9469
- },
9470
- {
9471
- AttributeName: "gsi1pk",
9472
- AttributeType: "S"
9473
- },
9474
- {
9475
- AttributeName: "gsi1sk",
9476
- AttributeType: "S"
9477
- }
9478
- ],
9479
- BillingMode: "PAY_PER_REQUEST",
9480
- DeletionProtectionEnabled: true,
9481
- GlobalSecondaryIndexes: [{
9482
- IndexName: DYNAMODB_UPDATE_INDEX_NAME,
9483
- KeySchema: [...updateIndexKeySchema],
9484
- Projection: { ProjectionType: "ALL" },
9485
- OnDemandThroughput: onDemandThroughput
9486
- }],
9487
- KeySchema: [...primaryKeySchema],
9488
- OnDemandThroughput: onDemandThroughput,
9489
- TableName: tableName
9490
- });
9495
+ await this.client.createTable(buildDynamoDBCreateTableInput(tableName));
9491
9496
  await (0, _aws_sdk_client_dynamodb.waitUntilTableExists)({
9492
9497
  client: this.client,
9493
9498
  maxWaitTime: 120
@@ -9497,14 +9502,80 @@ var DynamoDBManager = class {
9497
9502
  async ensureLifecycle(tableName) {
9498
9503
  const { ContinuousBackupsDescription } = await this.client.describeContinuousBackups({ TableName: tableName });
9499
9504
  if (ContinuousBackupsDescription?.PointInTimeRecoveryDescription?.PointInTimeRecoveryStatus === "ENABLED") return;
9500
- await this.client.updateContinuousBackups({
9501
- PointInTimeRecoverySpecification: { PointInTimeRecoveryEnabled: true },
9502
- TableName: tableName
9503
- });
9505
+ await this.client.updateContinuousBackups(buildDynamoDBBackupInput(tableName));
9504
9506
  }
9505
9507
  };
9506
9508
  //#endregion
9507
9509
  //#region iac/iam.ts
9510
+ const buildDynamoDBPolicy = (region, accountId, tableName) => {
9511
+ const tableArn = `arn:aws:dynamodb:${region}:${accountId}:table/${tableName}`;
9512
+ return {
9513
+ Version: "2012-10-17",
9514
+ Statement: [{
9515
+ Action: ["dynamodb:Query"],
9516
+ Effect: "Allow",
9517
+ Resource: [`${tableArn}/index/${DYNAMODB_UPDATE_INDEX_NAME}`]
9518
+ }, {
9519
+ Action: [
9520
+ "dynamodb:BatchGetItem",
9521
+ "dynamodb:DeleteItem",
9522
+ "dynamodb:GetItem",
9523
+ "dynamodb:PutItem",
9524
+ "dynamodb:Query",
9525
+ "dynamodb:TransactWriteItems",
9526
+ "dynamodb:UpdateItem"
9527
+ ],
9528
+ Condition: { "ForAllValues:StringLike": { "dynamodb:LeadingKeys": [
9529
+ "_hot-updater",
9530
+ "bundles",
9531
+ "bundle_patches",
9532
+ DYNAMODB_CHANNEL_PARTITION,
9533
+ DYNAMODB_CHANNEL_NAME_PARTITION,
9534
+ DYNAMODB_INSIGHTS_PARTITION,
9535
+ DYNAMODB_INSIGHTS_INSTALLATIONS_PARTITION,
9536
+ DYNAMODB_INSIGHTS_EVENT_IDS_PARTITION,
9537
+ `${DYNAMODB_INSIGHTS_BUNDLE_PREFIX}*`,
9538
+ "_hot-updater#insights-user#*",
9539
+ DYNAMODB_API_KEY_PARTITION,
9540
+ DYNAMODB_API_KEY_HASH_PARTITION
9541
+ ] } },
9542
+ Effect: "Allow",
9543
+ Resource: [tableArn]
9544
+ }]
9545
+ };
9546
+ };
9547
+ const buildS3Policy = (bucketName) => {
9548
+ return {
9549
+ Version: "2012-10-17",
9550
+ Statement: [{
9551
+ Action: ["s3:ListBucket"],
9552
+ Effect: "Allow",
9553
+ Resource: [`arn:aws:s3:::${bucketName}`]
9554
+ }, {
9555
+ Action: ["s3:GetObject"],
9556
+ Effect: "Allow",
9557
+ Resource: [`arn:aws:s3:::${bucketName}/*`]
9558
+ }]
9559
+ };
9560
+ };
9561
+ const buildSsmPolicy = (region, accountId, parameterName) => {
9562
+ return {
9563
+ Version: "2012-10-17",
9564
+ Statement: [{
9565
+ Effect: "Allow",
9566
+ Action: ["ssm:GetParameter"],
9567
+ Resource: `arn:aws:ssm:${region}:${accountId}:parameter/${parameterName.replace(/^\/+/, "")}`
9568
+ }]
9569
+ };
9570
+ };
9571
+ const LAMBDA_EDGE_TRUST_POLICY = {
9572
+ Version: "2012-10-17",
9573
+ Statement: [{
9574
+ Effect: "Allow",
9575
+ Principal: { Service: ["lambda.amazonaws.com", "edgelambda.amazonaws.com"] },
9576
+ Action: "sts:AssumeRole"
9577
+ }]
9578
+ };
9508
9579
  var IAMManager = class {
9509
9580
  region;
9510
9581
  credentials;
@@ -9521,75 +9592,22 @@ var IAMManager = class {
9521
9592
  });
9522
9593
  }
9523
9594
  async ensureDynamoDBPolicy(iamClient, roleName, accountId, tableName) {
9524
- const tableArn = `arn:aws:dynamodb:${this.region}:${accountId}:table/${tableName}`;
9525
9595
  await iamClient.putRolePolicy({
9526
- PolicyDocument: JSON.stringify({
9527
- Version: "2012-10-17",
9528
- Statement: [{
9529
- Action: ["dynamodb:Query"],
9530
- Effect: "Allow",
9531
- Resource: [`${tableArn}/index/${DYNAMODB_UPDATE_INDEX_NAME}`]
9532
- }, {
9533
- Action: [
9534
- "dynamodb:BatchGetItem",
9535
- "dynamodb:DeleteItem",
9536
- "dynamodb:GetItem",
9537
- "dynamodb:PutItem",
9538
- "dynamodb:Query",
9539
- "dynamodb:TransactWriteItems",
9540
- "dynamodb:UpdateItem"
9541
- ],
9542
- Condition: { "ForAllValues:StringLike": { "dynamodb:LeadingKeys": [
9543
- "_hot-updater",
9544
- "bundles",
9545
- "bundle_patches",
9546
- DYNAMODB_CHANNEL_PARTITION,
9547
- DYNAMODB_CHANNEL_NAME_PARTITION,
9548
- DYNAMODB_INSIGHTS_PARTITION,
9549
- DYNAMODB_INSIGHTS_INSTALLATIONS_PARTITION,
9550
- DYNAMODB_INSIGHTS_EVENT_IDS_PARTITION,
9551
- `${DYNAMODB_INSIGHTS_BUNDLE_PREFIX}*`,
9552
- "_hot-updater#insights-user#*",
9553
- DYNAMODB_API_KEY_PARTITION,
9554
- DYNAMODB_API_KEY_HASH_PARTITION
9555
- ] } },
9556
- Effect: "Allow",
9557
- Resource: [tableArn]
9558
- }]
9559
- }),
9596
+ PolicyDocument: JSON.stringify(buildDynamoDBPolicy(this.region, accountId, tableName)),
9560
9597
  PolicyName: "HotUpdaterDynamoDBReadAccess",
9561
9598
  RoleName: roleName
9562
9599
  });
9563
9600
  }
9564
9601
  async ensureS3Policy(iamClient, roleName, bucketName) {
9565
9602
  await iamClient.putRolePolicy({
9566
- PolicyDocument: JSON.stringify({
9567
- Version: "2012-10-17",
9568
- Statement: [{
9569
- Action: ["s3:ListBucket"],
9570
- Effect: "Allow",
9571
- Resource: [`arn:aws:s3:::${bucketName}`]
9572
- }, {
9573
- Action: ["s3:GetObject"],
9574
- Effect: "Allow",
9575
- Resource: [`arn:aws:s3:::${bucketName}/*`]
9576
- }]
9577
- }),
9603
+ PolicyDocument: JSON.stringify(buildS3Policy(bucketName)),
9578
9604
  PolicyName: "HotUpdaterS3ReadAccess",
9579
9605
  RoleName: roleName
9580
9606
  });
9581
9607
  }
9582
9608
  async ensureSsmPolicy(iamClient, roleName, accountId, parameterName) {
9583
- const parameterPath = parameterName.replace(/^\/+/, "");
9584
9609
  await iamClient.putRolePolicy({
9585
- PolicyDocument: JSON.stringify({
9586
- Version: "2012-10-17",
9587
- Statement: [{
9588
- Effect: "Allow",
9589
- Action: ["ssm:GetParameter"],
9590
- Resource: `arn:aws:ssm:${this.region}:${accountId}:parameter/${parameterPath}`
9591
- }]
9592
- }),
9610
+ PolicyDocument: JSON.stringify(buildSsmPolicy(this.region, accountId, parameterName)),
9593
9611
  PolicyName: "HotUpdaterSSMAccess",
9594
9612
  RoleName: roleName
9595
9613
  });
@@ -9604,14 +9622,7 @@ var IAMManager = class {
9604
9622
  credentials: this.credentials
9605
9623
  }).getCallerIdentity({})).Account;
9606
9624
  if (!accountId) throw new Error("Failed to get AWS account ID");
9607
- const assumeRolePolicyDocument = JSON.stringify({
9608
- Version: "2012-10-17",
9609
- Statement: [{
9610
- Effect: "Allow",
9611
- Principal: { Service: ["lambda.amazonaws.com", "edgelambda.amazonaws.com"] },
9612
- Action: "sts:AssumeRole"
9613
- }]
9614
- });
9625
+ const assumeRolePolicyDocument = JSON.stringify(LAMBDA_EDGE_TRUST_POLICY);
9615
9626
  const roleName = `hot-updater-edge-${(0, node_crypto.createHash)("sha256").update(options.lambdaName).digest("hex").slice(0, 16)}`;
9616
9627
  try {
9617
9628
  const { Role: existingRole } = await iamClient.getRole({ RoleName: roleName });
@@ -10282,6 +10293,12 @@ const runInit = async ({ build, envFile }) => {
10282
10293
  _hot_updater_cli_tools.p.log.success("Done! 🎉");
10283
10294
  };
10284
10295
  //#endregion
10296
+ exports.LAMBDA_EDGE_TRUST_POLICY = LAMBDA_EDGE_TRUST_POLICY;
10297
+ exports.buildDynamoDBBackupInput = buildDynamoDBBackupInput;
10298
+ exports.buildDynamoDBCreateTableInput = buildDynamoDBCreateTableInput;
10299
+ exports.buildDynamoDBPolicy = buildDynamoDBPolicy;
10300
+ exports.buildS3Policy = buildS3Policy;
10301
+ exports.buildSsmPolicy = buildSsmPolicy;
10285
10302
  exports.prepareDynamoDBApiKey = prepareDynamoDBApiKey;
10286
10303
  exports.prepareDynamoDBDeployment = prepareDynamoDBDeployment;
10287
10304
  exports.runInit = runInit;
@@ -1,5 +1,96 @@
1
1
  import { RunInitOptions } from "@hot-updater/cli-tools";
2
2
  import { ApiKeyModel } from "@hot-updater/plugin-core";
3
+ //#region iac/dynamodb.d.ts
4
+ declare const buildDynamoDBCreateTableInput: (tableName: string) => {
5
+ AttributeDefinitions: {
6
+ AttributeName: string;
7
+ AttributeType: "S";
8
+ }[];
9
+ BillingMode: "PAY_PER_REQUEST";
10
+ DeletionProtectionEnabled: true;
11
+ GlobalSecondaryIndexes: {
12
+ IndexName: string;
13
+ KeySchema: ({
14
+ readonly AttributeName: "gsi1pk";
15
+ readonly KeyType: "HASH";
16
+ } | {
17
+ readonly AttributeName: "gsi1sk";
18
+ readonly KeyType: "RANGE";
19
+ })[];
20
+ Projection: {
21
+ ProjectionType: "ALL";
22
+ };
23
+ OnDemandThroughput: {
24
+ readonly MaxReadRequestUnits: 4000;
25
+ readonly MaxWriteRequestUnits: 100;
26
+ };
27
+ }[];
28
+ KeySchema: ({
29
+ readonly AttributeName: "pk";
30
+ readonly KeyType: "HASH";
31
+ } | {
32
+ readonly AttributeName: "sk";
33
+ readonly KeyType: "RANGE";
34
+ })[];
35
+ OnDemandThroughput: {
36
+ readonly MaxReadRequestUnits: 4000;
37
+ readonly MaxWriteRequestUnits: 100;
38
+ };
39
+ TableName: string;
40
+ };
41
+ declare const buildDynamoDBBackupInput: (tableName: string) => {
42
+ PointInTimeRecoverySpecification: {
43
+ PointInTimeRecoveryEnabled: true;
44
+ };
45
+ TableName: string;
46
+ };
47
+ //#endregion
48
+ //#region iac/iam.d.ts
49
+ declare const buildDynamoDBPolicy: (region: string, accountId: string, tableName: string) => {
50
+ Version: string;
51
+ Statement: ({
52
+ Action: string[];
53
+ Effect: string;
54
+ Resource: string[];
55
+ Condition?: undefined;
56
+ } | {
57
+ Action: string[];
58
+ Condition: {
59
+ "ForAllValues:StringLike": {
60
+ "dynamodb:LeadingKeys": string[];
61
+ };
62
+ };
63
+ Effect: string;
64
+ Resource: string[];
65
+ })[];
66
+ };
67
+ declare const buildS3Policy: (bucketName: string) => {
68
+ Version: string;
69
+ Statement: {
70
+ Action: string[];
71
+ Effect: string;
72
+ Resource: string[];
73
+ }[];
74
+ };
75
+ declare const buildSsmPolicy: (region: string, accountId: string, parameterName: string) => {
76
+ Version: string;
77
+ Statement: {
78
+ Effect: string;
79
+ Action: string[];
80
+ Resource: string;
81
+ }[];
82
+ };
83
+ declare const LAMBDA_EDGE_TRUST_POLICY: {
84
+ Version: string;
85
+ Statement: {
86
+ Effect: string;
87
+ Principal: {
88
+ Service: string[];
89
+ };
90
+ Action: string;
91
+ }[];
92
+ };
93
+ //#endregion
3
94
  //#region iac/index.d.ts
4
95
  declare const prepareDynamoDBDeployment: (input: {
5
96
  readonly credentials: {
@@ -16,4 +107,4 @@ declare const prepareDynamoDBApiKey: (input: {
16
107
  }) => Promise<string>;
17
108
  declare const runInit: ({ build, envFile }: RunInitOptions) => Promise<void>;
18
109
  //#endregion
19
- export { prepareDynamoDBApiKey, prepareDynamoDBDeployment, runInit };
110
+ export { LAMBDA_EDGE_TRUST_POLICY, buildDynamoDBBackupInput, buildDynamoDBCreateTableInput, buildDynamoDBPolicy, buildS3Policy, buildSsmPolicy, prepareDynamoDBApiKey, prepareDynamoDBDeployment, runInit };
@@ -1,5 +1,96 @@
1
- import { RunInitOptions } from "@hot-updater/cli-tools";
1
+ import { InitError, RunInitOptions } from "@hot-updater/cli-tools";
2
2
  import { ApiKeyModel } from "@hot-updater/plugin-core";
3
+ //#region iac/dynamodb.d.ts
4
+ declare const buildDynamoDBCreateTableInput: (tableName: string) => {
5
+ AttributeDefinitions: {
6
+ AttributeName: string;
7
+ AttributeType: "S";
8
+ }[];
9
+ BillingMode: "PAY_PER_REQUEST";
10
+ DeletionProtectionEnabled: true;
11
+ GlobalSecondaryIndexes: {
12
+ IndexName: string;
13
+ KeySchema: ({
14
+ readonly AttributeName: "gsi1pk";
15
+ readonly KeyType: "HASH";
16
+ } | {
17
+ readonly AttributeName: "gsi1sk";
18
+ readonly KeyType: "RANGE";
19
+ })[];
20
+ Projection: {
21
+ ProjectionType: "ALL";
22
+ };
23
+ OnDemandThroughput: {
24
+ readonly MaxReadRequestUnits: 4000;
25
+ readonly MaxWriteRequestUnits: 100;
26
+ };
27
+ }[];
28
+ KeySchema: ({
29
+ readonly AttributeName: "pk";
30
+ readonly KeyType: "HASH";
31
+ } | {
32
+ readonly AttributeName: "sk";
33
+ readonly KeyType: "RANGE";
34
+ })[];
35
+ OnDemandThroughput: {
36
+ readonly MaxReadRequestUnits: 4000;
37
+ readonly MaxWriteRequestUnits: 100;
38
+ };
39
+ TableName: string;
40
+ };
41
+ declare const buildDynamoDBBackupInput: (tableName: string) => {
42
+ PointInTimeRecoverySpecification: {
43
+ PointInTimeRecoveryEnabled: true;
44
+ };
45
+ TableName: string;
46
+ };
47
+ //#endregion
48
+ //#region iac/iam.d.ts
49
+ declare const buildDynamoDBPolicy: (region: string, accountId: string, tableName: string) => {
50
+ Version: string;
51
+ Statement: ({
52
+ Action: string[];
53
+ Effect: string;
54
+ Resource: string[];
55
+ Condition?: undefined;
56
+ } | {
57
+ Action: string[];
58
+ Condition: {
59
+ "ForAllValues:StringLike": {
60
+ "dynamodb:LeadingKeys": string[];
61
+ };
62
+ };
63
+ Effect: string;
64
+ Resource: string[];
65
+ })[];
66
+ };
67
+ declare const buildS3Policy: (bucketName: string) => {
68
+ Version: string;
69
+ Statement: {
70
+ Action: string[];
71
+ Effect: string;
72
+ Resource: string[];
73
+ }[];
74
+ };
75
+ declare const buildSsmPolicy: (region: string, accountId: string, parameterName: string) => {
76
+ Version: string;
77
+ Statement: {
78
+ Effect: string;
79
+ Action: string[];
80
+ Resource: string;
81
+ }[];
82
+ };
83
+ declare const LAMBDA_EDGE_TRUST_POLICY: {
84
+ Version: string;
85
+ Statement: {
86
+ Effect: string;
87
+ Principal: {
88
+ Service: string[];
89
+ };
90
+ Action: string;
91
+ }[];
92
+ };
93
+ //#endregion
3
94
  //#region iac/index.d.ts
4
95
  declare const prepareDynamoDBDeployment: (input: {
5
96
  readonly credentials: {
@@ -16,4 +107,4 @@ declare const prepareDynamoDBApiKey: (input: {
16
107
  }) => Promise<string>;
17
108
  declare const runInit: ({ build, envFile }: RunInitOptions) => Promise<void>;
18
109
  //#endregion
19
- export { prepareDynamoDBApiKey, prepareDynamoDBDeployment, runInit };
110
+ export { LAMBDA_EDGE_TRUST_POLICY, buildDynamoDBBackupInput, buildDynamoDBCreateTableInput, buildDynamoDBPolicy, buildS3Policy, buildSsmPolicy, prepareDynamoDBApiKey, prepareDynamoDBDeployment, runInit };
@@ -7834,10 +7834,10 @@ const hasValidBundleEventShape = (value) => {
7834
7834
  const type = field(value, "type");
7835
7835
  const fromBundleId = field(value, "from_bundle_id");
7836
7836
  const updateStrategy = field(value, "update_strategy");
7837
- return (type === "UPDATE_APPLIED" || type === "RECOVERED" || type === "RELEASE_ADOPTED") && typeof fromBundleId === "string" && (updateStrategy === "fingerprint" || updateStrategy === "appVersion") || type === "UNCHANGED" && fromBundleId === null && updateStrategy === null;
7837
+ return (type === "UPDATE_APPLIED" || type === "RECOVERED") && typeof fromBundleId === "string" && (updateStrategy === "fingerprint" || updateStrategy === "appVersion") || type === "UNCHANGED" && fromBundleId === null && updateStrategy === null;
7838
7838
  };
7839
7839
  const isBundleEventRow = (value) => typeof value === "object" && value !== null && typeof field(value, "id") === "string" && typeof field(value, "install_id") === "string" && isNullableString(field(value, "from_release_id")) && isNullableString(field(value, "to_release_id")) && typeof field(value, "to_bundle_id") === "string" && (field(value, "platform") === "ios" || field(value, "platform") === "android") && typeof field(value, "received_at_ms") === "number" && hasValidBundleEventShape(value);
7840
- const isInsightsInstallationRow = (value) => typeof value === "object" && value !== null && typeof field(value, "id") === "string" && typeof field(value, "install_id") === "string" && isNullableString(field(value, "user_id")) && isNullableString(field(value, "username")) && typeof field(value, "to_bundle_id") === "string" && (field(value, "type") === "UPDATE_APPLIED" || field(value, "type") === "RECOVERED" || field(value, "type") === "RELEASE_ADOPTED" || field(value, "type") === "UNCHANGED") && (field(value, "platform") === "ios" || field(value, "platform") === "android") && typeof field(value, "app_version") === "string" && typeof field(value, "channel") === "string" && typeof field(value, "cohort") === "string" && typeof field(value, "received_at_ms") === "number";
7840
+ const isInsightsInstallationRow = (value) => typeof value === "object" && value !== null && typeof field(value, "id") === "string" && typeof field(value, "install_id") === "string" && isNullableString(field(value, "user_id")) && isNullableString(field(value, "username")) && typeof field(value, "to_bundle_id") === "string" && (field(value, "type") === "UPDATE_APPLIED" || field(value, "type") === "RECOVERED" || field(value, "type") === "UNCHANGED") && (field(value, "platform") === "ios" || field(value, "platform") === "android") && typeof field(value, "app_version") === "string" && typeof field(value, "channel") === "string" && typeof field(value, "cohort") === "string" && typeof field(value, "received_at_ms") === "number";
7841
7841
  const isApiKeyRow = (value) => typeof value === "object" && value !== null && typeof field(value, "id") === "string" && typeof field(value, "hash") === "string" && typeof field(value, "name") === "string" && typeof field(value, "prefix") === "string" && field(value, "role") === "client" && typeof field(value, "created_at_ms") === "number" && (field(value, "revoked_at_ms") === null || typeof field(value, "revoked_at_ms") === "number");
7842
7842
  const parseOfficialRowItem = (value, partition, isRow) => {
7843
7843
  if (value.pk !== partition || typeof value.sk !== "string" || typeof value.version !== "number" || !isRow(value.row)) throw new DynamoDBStoredItemError();
@@ -7859,7 +7859,7 @@ const insightsBundlePartition = (filter) => {
7859
7859
  ]);
7860
7860
  return `${DYNAMODB_INSIGHTS_BUNDLE_PREFIX}${createHash("sha256").update(scope, "utf8").digest("hex")}`;
7861
7861
  };
7862
- const toInsightsBundleItem = (row) => row.type === "UNCHANGED" ? null : boundedDynamoDBMetadataItem({
7862
+ const toInsightsBundleItem = (row) => boundedDynamoDBMetadataItem({
7863
7863
  pk: insightsBundlePartition({
7864
7864
  platform: row.platform,
7865
7865
  channel: row.channel,
@@ -7945,10 +7945,10 @@ const recordDynamoDBInsightsEvent = async (store, { event: row, installation: ne
7945
7945
  TableName: store.tableName,
7946
7946
  Item: eventItem
7947
7947
  } },
7948
- ...bundleItem === null ? [] : [{ Put: {
7948
+ { Put: {
7949
7949
  TableName: store.tableName,
7950
7950
  Item: bundleItem
7951
- } }]
7951
+ } }
7952
7952
  ];
7953
7953
  if (current === null || advancesInsightsInstallation(next, current.row)) {
7954
7954
  actions.push({ Put: {
@@ -9432,6 +9432,41 @@ const hasExpectedSchema = (table) => {
9432
9432
  return keyAttributes.every((attributeName) => table?.AttributeDefinitions?.some(({ AttributeName, AttributeType }) => AttributeName === attributeName && AttributeType === "S")) && table?.BillingModeSummary?.BillingMode === "PAY_PER_REQUEST" && table.OnDemandThroughput?.MaxReadRequestUnits === onDemandThroughput.MaxReadRequestUnits && table.OnDemandThroughput?.MaxWriteRequestUnits === onDemandThroughput.MaxWriteRequestUnits && hasKeySchema(table?.KeySchema, primaryKeySchema) && hasKeySchema(updateIndex?.KeySchema, updateIndexKeySchema) && updateIndex?.OnDemandThroughput?.MaxReadRequestUnits === onDemandThroughput.MaxReadRequestUnits && updateIndex?.OnDemandThroughput?.MaxWriteRequestUnits === onDemandThroughput.MaxWriteRequestUnits && updateIndex?.Projection?.ProjectionType === "ALL";
9433
9433
  };
9434
9434
  const isResourceNotFound = (error) => typeof error === "object" && error !== null && Reflect.get(error, "name") === "ResourceNotFoundException";
9435
+ const buildDynamoDBCreateTableInput = (tableName) => ({
9436
+ AttributeDefinitions: [
9437
+ {
9438
+ AttributeName: "pk",
9439
+ AttributeType: "S"
9440
+ },
9441
+ {
9442
+ AttributeName: "sk",
9443
+ AttributeType: "S"
9444
+ },
9445
+ {
9446
+ AttributeName: "gsi1pk",
9447
+ AttributeType: "S"
9448
+ },
9449
+ {
9450
+ AttributeName: "gsi1sk",
9451
+ AttributeType: "S"
9452
+ }
9453
+ ],
9454
+ BillingMode: "PAY_PER_REQUEST",
9455
+ DeletionProtectionEnabled: true,
9456
+ GlobalSecondaryIndexes: [{
9457
+ IndexName: DYNAMODB_UPDATE_INDEX_NAME,
9458
+ KeySchema: [...updateIndexKeySchema],
9459
+ Projection: { ProjectionType: "ALL" },
9460
+ OnDemandThroughput: onDemandThroughput
9461
+ }],
9462
+ KeySchema: [...primaryKeySchema],
9463
+ OnDemandThroughput: onDemandThroughput,
9464
+ TableName: tableName
9465
+ });
9466
+ const buildDynamoDBBackupInput = (tableName) => ({
9467
+ PointInTimeRecoverySpecification: { PointInTimeRecoveryEnabled: true },
9468
+ TableName: tableName
9469
+ });
9435
9470
  var DynamoDBManager = class {
9436
9471
  region;
9437
9472
  client;
@@ -9452,37 +9487,7 @@ var DynamoDBManager = class {
9452
9487
  if (error instanceof Error && error.name === "AccessDeniedException" && error.message.includes(DYNAMODB_DESCRIBE_TABLE_ACTION)) throw new DynamoDBPermissionError(tableName, this.region, error);
9453
9488
  if (!isResourceNotFound(error)) throw error;
9454
9489
  }
9455
- await this.client.createTable({
9456
- AttributeDefinitions: [
9457
- {
9458
- AttributeName: "pk",
9459
- AttributeType: "S"
9460
- },
9461
- {
9462
- AttributeName: "sk",
9463
- AttributeType: "S"
9464
- },
9465
- {
9466
- AttributeName: "gsi1pk",
9467
- AttributeType: "S"
9468
- },
9469
- {
9470
- AttributeName: "gsi1sk",
9471
- AttributeType: "S"
9472
- }
9473
- ],
9474
- BillingMode: "PAY_PER_REQUEST",
9475
- DeletionProtectionEnabled: true,
9476
- GlobalSecondaryIndexes: [{
9477
- IndexName: DYNAMODB_UPDATE_INDEX_NAME,
9478
- KeySchema: [...updateIndexKeySchema],
9479
- Projection: { ProjectionType: "ALL" },
9480
- OnDemandThroughput: onDemandThroughput
9481
- }],
9482
- KeySchema: [...primaryKeySchema],
9483
- OnDemandThroughput: onDemandThroughput,
9484
- TableName: tableName
9485
- });
9490
+ await this.client.createTable(buildDynamoDBCreateTableInput(tableName));
9486
9491
  await waitUntilTableExists({
9487
9492
  client: this.client,
9488
9493
  maxWaitTime: 120
@@ -9492,14 +9497,80 @@ var DynamoDBManager = class {
9492
9497
  async ensureLifecycle(tableName) {
9493
9498
  const { ContinuousBackupsDescription } = await this.client.describeContinuousBackups({ TableName: tableName });
9494
9499
  if (ContinuousBackupsDescription?.PointInTimeRecoveryDescription?.PointInTimeRecoveryStatus === "ENABLED") return;
9495
- await this.client.updateContinuousBackups({
9496
- PointInTimeRecoverySpecification: { PointInTimeRecoveryEnabled: true },
9497
- TableName: tableName
9498
- });
9500
+ await this.client.updateContinuousBackups(buildDynamoDBBackupInput(tableName));
9499
9501
  }
9500
9502
  };
9501
9503
  //#endregion
9502
9504
  //#region iac/iam.ts
9505
+ const buildDynamoDBPolicy = (region, accountId, tableName) => {
9506
+ const tableArn = `arn:aws:dynamodb:${region}:${accountId}:table/${tableName}`;
9507
+ return {
9508
+ Version: "2012-10-17",
9509
+ Statement: [{
9510
+ Action: ["dynamodb:Query"],
9511
+ Effect: "Allow",
9512
+ Resource: [`${tableArn}/index/${DYNAMODB_UPDATE_INDEX_NAME}`]
9513
+ }, {
9514
+ Action: [
9515
+ "dynamodb:BatchGetItem",
9516
+ "dynamodb:DeleteItem",
9517
+ "dynamodb:GetItem",
9518
+ "dynamodb:PutItem",
9519
+ "dynamodb:Query",
9520
+ "dynamodb:TransactWriteItems",
9521
+ "dynamodb:UpdateItem"
9522
+ ],
9523
+ Condition: { "ForAllValues:StringLike": { "dynamodb:LeadingKeys": [
9524
+ "_hot-updater",
9525
+ "bundles",
9526
+ "bundle_patches",
9527
+ DYNAMODB_CHANNEL_PARTITION,
9528
+ DYNAMODB_CHANNEL_NAME_PARTITION,
9529
+ DYNAMODB_INSIGHTS_PARTITION,
9530
+ DYNAMODB_INSIGHTS_INSTALLATIONS_PARTITION,
9531
+ DYNAMODB_INSIGHTS_EVENT_IDS_PARTITION,
9532
+ `${DYNAMODB_INSIGHTS_BUNDLE_PREFIX}*`,
9533
+ "_hot-updater#insights-user#*",
9534
+ DYNAMODB_API_KEY_PARTITION,
9535
+ DYNAMODB_API_KEY_HASH_PARTITION
9536
+ ] } },
9537
+ Effect: "Allow",
9538
+ Resource: [tableArn]
9539
+ }]
9540
+ };
9541
+ };
9542
+ const buildS3Policy = (bucketName) => {
9543
+ return {
9544
+ Version: "2012-10-17",
9545
+ Statement: [{
9546
+ Action: ["s3:ListBucket"],
9547
+ Effect: "Allow",
9548
+ Resource: [`arn:aws:s3:::${bucketName}`]
9549
+ }, {
9550
+ Action: ["s3:GetObject"],
9551
+ Effect: "Allow",
9552
+ Resource: [`arn:aws:s3:::${bucketName}/*`]
9553
+ }]
9554
+ };
9555
+ };
9556
+ const buildSsmPolicy = (region, accountId, parameterName) => {
9557
+ return {
9558
+ Version: "2012-10-17",
9559
+ Statement: [{
9560
+ Effect: "Allow",
9561
+ Action: ["ssm:GetParameter"],
9562
+ Resource: `arn:aws:ssm:${region}:${accountId}:parameter/${parameterName.replace(/^\/+/, "")}`
9563
+ }]
9564
+ };
9565
+ };
9566
+ const LAMBDA_EDGE_TRUST_POLICY = {
9567
+ Version: "2012-10-17",
9568
+ Statement: [{
9569
+ Effect: "Allow",
9570
+ Principal: { Service: ["lambda.amazonaws.com", "edgelambda.amazonaws.com"] },
9571
+ Action: "sts:AssumeRole"
9572
+ }]
9573
+ };
9503
9574
  var IAMManager = class {
9504
9575
  region;
9505
9576
  credentials;
@@ -9516,75 +9587,22 @@ var IAMManager = class {
9516
9587
  });
9517
9588
  }
9518
9589
  async ensureDynamoDBPolicy(iamClient, roleName, accountId, tableName) {
9519
- const tableArn = `arn:aws:dynamodb:${this.region}:${accountId}:table/${tableName}`;
9520
9590
  await iamClient.putRolePolicy({
9521
- PolicyDocument: JSON.stringify({
9522
- Version: "2012-10-17",
9523
- Statement: [{
9524
- Action: ["dynamodb:Query"],
9525
- Effect: "Allow",
9526
- Resource: [`${tableArn}/index/${DYNAMODB_UPDATE_INDEX_NAME}`]
9527
- }, {
9528
- Action: [
9529
- "dynamodb:BatchGetItem",
9530
- "dynamodb:DeleteItem",
9531
- "dynamodb:GetItem",
9532
- "dynamodb:PutItem",
9533
- "dynamodb:Query",
9534
- "dynamodb:TransactWriteItems",
9535
- "dynamodb:UpdateItem"
9536
- ],
9537
- Condition: { "ForAllValues:StringLike": { "dynamodb:LeadingKeys": [
9538
- "_hot-updater",
9539
- "bundles",
9540
- "bundle_patches",
9541
- DYNAMODB_CHANNEL_PARTITION,
9542
- DYNAMODB_CHANNEL_NAME_PARTITION,
9543
- DYNAMODB_INSIGHTS_PARTITION,
9544
- DYNAMODB_INSIGHTS_INSTALLATIONS_PARTITION,
9545
- DYNAMODB_INSIGHTS_EVENT_IDS_PARTITION,
9546
- `${DYNAMODB_INSIGHTS_BUNDLE_PREFIX}*`,
9547
- "_hot-updater#insights-user#*",
9548
- DYNAMODB_API_KEY_PARTITION,
9549
- DYNAMODB_API_KEY_HASH_PARTITION
9550
- ] } },
9551
- Effect: "Allow",
9552
- Resource: [tableArn]
9553
- }]
9554
- }),
9591
+ PolicyDocument: JSON.stringify(buildDynamoDBPolicy(this.region, accountId, tableName)),
9555
9592
  PolicyName: "HotUpdaterDynamoDBReadAccess",
9556
9593
  RoleName: roleName
9557
9594
  });
9558
9595
  }
9559
9596
  async ensureS3Policy(iamClient, roleName, bucketName) {
9560
9597
  await iamClient.putRolePolicy({
9561
- PolicyDocument: JSON.stringify({
9562
- Version: "2012-10-17",
9563
- Statement: [{
9564
- Action: ["s3:ListBucket"],
9565
- Effect: "Allow",
9566
- Resource: [`arn:aws:s3:::${bucketName}`]
9567
- }, {
9568
- Action: ["s3:GetObject"],
9569
- Effect: "Allow",
9570
- Resource: [`arn:aws:s3:::${bucketName}/*`]
9571
- }]
9572
- }),
9598
+ PolicyDocument: JSON.stringify(buildS3Policy(bucketName)),
9573
9599
  PolicyName: "HotUpdaterS3ReadAccess",
9574
9600
  RoleName: roleName
9575
9601
  });
9576
9602
  }
9577
9603
  async ensureSsmPolicy(iamClient, roleName, accountId, parameterName) {
9578
- const parameterPath = parameterName.replace(/^\/+/, "");
9579
9604
  await iamClient.putRolePolicy({
9580
- PolicyDocument: JSON.stringify({
9581
- Version: "2012-10-17",
9582
- Statement: [{
9583
- Effect: "Allow",
9584
- Action: ["ssm:GetParameter"],
9585
- Resource: `arn:aws:ssm:${this.region}:${accountId}:parameter/${parameterPath}`
9586
- }]
9587
- }),
9605
+ PolicyDocument: JSON.stringify(buildSsmPolicy(this.region, accountId, parameterName)),
9588
9606
  PolicyName: "HotUpdaterSSMAccess",
9589
9607
  RoleName: roleName
9590
9608
  });
@@ -9599,14 +9617,7 @@ var IAMManager = class {
9599
9617
  credentials: this.credentials
9600
9618
  }).getCallerIdentity({})).Account;
9601
9619
  if (!accountId) throw new Error("Failed to get AWS account ID");
9602
- const assumeRolePolicyDocument = JSON.stringify({
9603
- Version: "2012-10-17",
9604
- Statement: [{
9605
- Effect: "Allow",
9606
- Principal: { Service: ["lambda.amazonaws.com", "edgelambda.amazonaws.com"] },
9607
- Action: "sts:AssumeRole"
9608
- }]
9609
- });
9620
+ const assumeRolePolicyDocument = JSON.stringify(LAMBDA_EDGE_TRUST_POLICY);
9610
9621
  const roleName = `hot-updater-edge-${createHash("sha256").update(options.lambdaName).digest("hex").slice(0, 16)}`;
9611
9622
  try {
9612
9623
  const { Role: existingRole } = await iamClient.getRole({ RoleName: roleName });
@@ -10277,4 +10288,4 @@ const runInit = async ({ build, envFile }) => {
10277
10288
  p.log.success("Done! 🎉");
10278
10289
  };
10279
10290
  //#endregion
10280
- export { prepareDynamoDBApiKey, prepareDynamoDBDeployment, runInit };
10291
+ export { LAMBDA_EDGE_TRUST_POLICY, buildDynamoDBBackupInput, buildDynamoDBCreateTableInput, buildDynamoDBPolicy, buildS3Policy, buildSsmPolicy, prepareDynamoDBApiKey, prepareDynamoDBDeployment, runInit };
package/dist/index.cjs CHANGED
@@ -1575,10 +1575,10 @@ const hasValidBundleEventShape = (value) => {
1575
1575
  const type = field(value, "type");
1576
1576
  const fromBundleId = field(value, "from_bundle_id");
1577
1577
  const updateStrategy = field(value, "update_strategy");
1578
- return (type === "UPDATE_APPLIED" || type === "RECOVERED" || type === "RELEASE_ADOPTED") && typeof fromBundleId === "string" && (updateStrategy === "fingerprint" || updateStrategy === "appVersion") || type === "UNCHANGED" && fromBundleId === null && updateStrategy === null;
1578
+ return (type === "UPDATE_APPLIED" || type === "RECOVERED") && typeof fromBundleId === "string" && (updateStrategy === "fingerprint" || updateStrategy === "appVersion") || type === "UNCHANGED" && fromBundleId === null && updateStrategy === null;
1579
1579
  };
1580
1580
  const isBundleEventRow = (value) => typeof value === "object" && value !== null && typeof field(value, "id") === "string" && typeof field(value, "install_id") === "string" && isNullableString(field(value, "from_release_id")) && isNullableString(field(value, "to_release_id")) && typeof field(value, "to_bundle_id") === "string" && (field(value, "platform") === "ios" || field(value, "platform") === "android") && typeof field(value, "received_at_ms") === "number" && hasValidBundleEventShape(value);
1581
- const isInsightsInstallationRow = (value) => typeof value === "object" && value !== null && typeof field(value, "id") === "string" && typeof field(value, "install_id") === "string" && isNullableString(field(value, "user_id")) && isNullableString(field(value, "username")) && typeof field(value, "to_bundle_id") === "string" && (field(value, "type") === "UPDATE_APPLIED" || field(value, "type") === "RECOVERED" || field(value, "type") === "RELEASE_ADOPTED" || field(value, "type") === "UNCHANGED") && (field(value, "platform") === "ios" || field(value, "platform") === "android") && typeof field(value, "app_version") === "string" && typeof field(value, "channel") === "string" && typeof field(value, "cohort") === "string" && typeof field(value, "received_at_ms") === "number";
1581
+ const isInsightsInstallationRow = (value) => typeof value === "object" && value !== null && typeof field(value, "id") === "string" && typeof field(value, "install_id") === "string" && isNullableString(field(value, "user_id")) && isNullableString(field(value, "username")) && typeof field(value, "to_bundle_id") === "string" && (field(value, "type") === "UPDATE_APPLIED" || field(value, "type") === "RECOVERED" || field(value, "type") === "UNCHANGED") && (field(value, "platform") === "ios" || field(value, "platform") === "android") && typeof field(value, "app_version") === "string" && typeof field(value, "channel") === "string" && typeof field(value, "cohort") === "string" && typeof field(value, "received_at_ms") === "number";
1582
1582
  const isApiKeyRow = (value) => typeof value === "object" && value !== null && typeof field(value, "id") === "string" && typeof field(value, "hash") === "string" && typeof field(value, "name") === "string" && typeof field(value, "prefix") === "string" && field(value, "role") === "client" && typeof field(value, "created_at_ms") === "number" && (field(value, "revoked_at_ms") === null || typeof field(value, "revoked_at_ms") === "number");
1583
1583
  const parseOfficialRowItem = (value, partition, isRow) => {
1584
1584
  if (value.pk !== partition || typeof value.sk !== "string" || typeof value.version !== "number" || !isRow(value.row)) throw new DynamoDBStoredItemError();
@@ -1600,7 +1600,7 @@ const insightsBundlePartition = (filter) => {
1600
1600
  ]);
1601
1601
  return `${DYNAMODB_INSIGHTS_BUNDLE_PREFIX}${(0, node_crypto.createHash)("sha256").update(scope, "utf8").digest("hex")}`;
1602
1602
  };
1603
- const toInsightsBundleItem = (row) => row.type === "UNCHANGED" ? null : boundedDynamoDBMetadataItem({
1603
+ const toInsightsBundleItem = (row) => boundedDynamoDBMetadataItem({
1604
1604
  pk: insightsBundlePartition({
1605
1605
  platform: row.platform,
1606
1606
  channel: row.channel,
@@ -1686,10 +1686,10 @@ const recordDynamoDBInsightsEvent = async (store, { event: row, installation: ne
1686
1686
  TableName: store.tableName,
1687
1687
  Item: eventItem
1688
1688
  } },
1689
- ...bundleItem === null ? [] : [{ Put: {
1689
+ { Put: {
1690
1690
  TableName: store.tableName,
1691
1691
  Item: bundleItem
1692
- } }]
1692
+ } }
1693
1693
  ];
1694
1694
  if (current === null || advancesInsightsInstallation(next, current.row)) {
1695
1695
  actions.push({ Put: {
package/dist/index.mjs CHANGED
@@ -1574,10 +1574,10 @@ const hasValidBundleEventShape = (value) => {
1574
1574
  const type = field(value, "type");
1575
1575
  const fromBundleId = field(value, "from_bundle_id");
1576
1576
  const updateStrategy = field(value, "update_strategy");
1577
- return (type === "UPDATE_APPLIED" || type === "RECOVERED" || type === "RELEASE_ADOPTED") && typeof fromBundleId === "string" && (updateStrategy === "fingerprint" || updateStrategy === "appVersion") || type === "UNCHANGED" && fromBundleId === null && updateStrategy === null;
1577
+ return (type === "UPDATE_APPLIED" || type === "RECOVERED") && typeof fromBundleId === "string" && (updateStrategy === "fingerprint" || updateStrategy === "appVersion") || type === "UNCHANGED" && fromBundleId === null && updateStrategy === null;
1578
1578
  };
1579
1579
  const isBundleEventRow = (value) => typeof value === "object" && value !== null && typeof field(value, "id") === "string" && typeof field(value, "install_id") === "string" && isNullableString(field(value, "from_release_id")) && isNullableString(field(value, "to_release_id")) && typeof field(value, "to_bundle_id") === "string" && (field(value, "platform") === "ios" || field(value, "platform") === "android") && typeof field(value, "received_at_ms") === "number" && hasValidBundleEventShape(value);
1580
- const isInsightsInstallationRow = (value) => typeof value === "object" && value !== null && typeof field(value, "id") === "string" && typeof field(value, "install_id") === "string" && isNullableString(field(value, "user_id")) && isNullableString(field(value, "username")) && typeof field(value, "to_bundle_id") === "string" && (field(value, "type") === "UPDATE_APPLIED" || field(value, "type") === "RECOVERED" || field(value, "type") === "RELEASE_ADOPTED" || field(value, "type") === "UNCHANGED") && (field(value, "platform") === "ios" || field(value, "platform") === "android") && typeof field(value, "app_version") === "string" && typeof field(value, "channel") === "string" && typeof field(value, "cohort") === "string" && typeof field(value, "received_at_ms") === "number";
1580
+ const isInsightsInstallationRow = (value) => typeof value === "object" && value !== null && typeof field(value, "id") === "string" && typeof field(value, "install_id") === "string" && isNullableString(field(value, "user_id")) && isNullableString(field(value, "username")) && typeof field(value, "to_bundle_id") === "string" && (field(value, "type") === "UPDATE_APPLIED" || field(value, "type") === "RECOVERED" || field(value, "type") === "UNCHANGED") && (field(value, "platform") === "ios" || field(value, "platform") === "android") && typeof field(value, "app_version") === "string" && typeof field(value, "channel") === "string" && typeof field(value, "cohort") === "string" && typeof field(value, "received_at_ms") === "number";
1581
1581
  const isApiKeyRow = (value) => typeof value === "object" && value !== null && typeof field(value, "id") === "string" && typeof field(value, "hash") === "string" && typeof field(value, "name") === "string" && typeof field(value, "prefix") === "string" && field(value, "role") === "client" && typeof field(value, "created_at_ms") === "number" && (field(value, "revoked_at_ms") === null || typeof field(value, "revoked_at_ms") === "number");
1582
1582
  const parseOfficialRowItem = (value, partition, isRow) => {
1583
1583
  if (value.pk !== partition || typeof value.sk !== "string" || typeof value.version !== "number" || !isRow(value.row)) throw new DynamoDBStoredItemError();
@@ -1599,7 +1599,7 @@ const insightsBundlePartition = (filter) => {
1599
1599
  ]);
1600
1600
  return `${DYNAMODB_INSIGHTS_BUNDLE_PREFIX}${createHash("sha256").update(scope, "utf8").digest("hex")}`;
1601
1601
  };
1602
- const toInsightsBundleItem = (row) => row.type === "UNCHANGED" ? null : boundedDynamoDBMetadataItem({
1602
+ const toInsightsBundleItem = (row) => boundedDynamoDBMetadataItem({
1603
1603
  pk: insightsBundlePartition({
1604
1604
  platform: row.platform,
1605
1605
  channel: row.channel,
@@ -1685,10 +1685,10 @@ const recordDynamoDBInsightsEvent = async (store, { event: row, installation: ne
1685
1685
  TableName: store.tableName,
1686
1686
  Item: eventItem
1687
1687
  } },
1688
- ...bundleItem === null ? [] : [{ Put: {
1688
+ { Put: {
1689
1689
  TableName: store.tableName,
1690
1690
  Item: bundleItem
1691
- } }]
1691
+ } }
1692
1692
  ];
1693
1693
  if (current === null || advancesInsightsInstallation(next, current.row)) {
1694
1694
  actions.push({ Put: {
@@ -32,7 +32,7 @@ let _aws_sdk_client_s3 = require("@aws-sdk/client-s3");
32
32
  let _aws_sdk_lib_storage = require("@aws-sdk/lib-storage");
33
33
  //#endregion
34
34
  //#region ../../packages/server/dist/version.mjs
35
- const HOT_UPDATER_SERVER_VERSION = "1.0.0-rc.2";
35
+ const HOT_UPDATER_SERVER_VERSION = "1.0.0-rc.3";
36
36
  //#endregion
37
37
  //#region ../../packages/server/dist/handlerVersionRoutes.mjs
38
38
  const createVersionRouteHandlers = () => ({ version: async () => Response.json({
@@ -835,7 +835,7 @@ const modelValidators = {
835
835
  },
836
836
  bundle_events: {
837
837
  id: (value) => typeof value === "string",
838
- type: (value) => value === "UPDATE_APPLIED" || value === "RECOVERED" || value === "RELEASE_ADOPTED" || value === "UNCHANGED",
838
+ type: (value) => value === "UPDATE_APPLIED" || value === "RECOVERED" || value === "UNCHANGED",
839
839
  install_id: isInsightsIdentityText,
840
840
  user_id: isNullableInsightsIdentityText,
841
841
  username: (value) => value === null || typeof value === "string",
@@ -858,7 +858,7 @@ const modelValidators = {
858
858
  user_id: isNullableInsightsIdentityText,
859
859
  username: (value) => value === null || typeof value === "string",
860
860
  to_bundle_id: (value) => typeof value === "string",
861
- type: (value) => value === "UPDATE_APPLIED" || value === "RECOVERED" || value === "RELEASE_ADOPTED" || value === "UNCHANGED",
861
+ type: (value) => value === "UPDATE_APPLIED" || value === "RECOVERED" || value === "UNCHANGED",
862
862
  platform: (value) => value === "ios" || value === "android",
863
863
  app_version: (value) => typeof value === "string",
864
864
  channel: (value) => typeof value === "string",
@@ -1061,7 +1061,7 @@ const hasValidReleaseInvariants = (data) => {
1061
1061
  const fingerprintHash = data.fingerprint_hash;
1062
1062
  return (kind === "BUNDLE" && typeof bundleId === "string" || kind === "EMBEDDED" && bundleId === null) && (strategy === "APP_VERSION" && typeof targetAppVersion === "string" && fingerprintHash === null || strategy === "FINGERPRINT" && targetAppVersion === null && typeof fingerprintHash === "string");
1063
1063
  };
1064
- const hasValidBundleEventInvariants = (data) => (data.type === "UPDATE_APPLIED" || data.type === "RECOVERED" || data.type === "RELEASE_ADOPTED") && typeof data.from_bundle_id === "string" && (data.update_strategy === "fingerprint" || data.update_strategy === "appVersion") || data.type === "UNCHANGED" && data.from_bundle_id === null && data.update_strategy === null;
1064
+ const hasValidBundleEventInvariants = (data) => (data.type === "UPDATE_APPLIED" || data.type === "RECOVERED") && typeof data.from_bundle_id === "string" && (data.update_strategy === "fingerprint" || data.update_strategy === "appVersion") || data.type === "UNCHANGED" && data.from_bundle_id === null && data.update_strategy === null;
1065
1065
  const validateCreateData = (model, data) => {
1066
1066
  if (!isRecord$3(data)) throw new DatabasePluginInputError("invalid-data");
1067
1067
  validateFields(model, Object.keys(data));
@@ -1131,7 +1131,7 @@ const isBundleFilter = (value, withKind = false) => {
1131
1131
  "type",
1132
1132
  ...withKind ? ["kind"] : []
1133
1133
  ];
1134
- return value.type === "RECOVERED" ? isText(value.fromBundleId) && hasOnlyKeys$1(value, [...keys, "fromBundleId"]) : (value.type === "UPDATE_APPLIED" || value.type === "RELEASE_ADOPTED") && isText(value.toBundleId) && hasOnlyKeys$1(value, [...keys, "toBundleId"]);
1134
+ return value.type === "RECOVERED" ? isText(value.fromBundleId) && hasOnlyKeys$1(value, [...keys, "fromBundleId"]) : (value.type === "UPDATE_APPLIED" || value.type === "UNCHANGED") && isText(value.toBundleId) && hasOnlyKeys$1(value, [...keys, "toBundleId"]);
1135
1135
  };
1136
1136
  const isEventFilter = (value) => {
1137
1137
  if (!isRecord$3(value)) return false;
@@ -1165,7 +1165,7 @@ const toInsightsInstallationRow = (event) => {
1165
1165
  received_at_ms: event.received_at_ms
1166
1166
  };
1167
1167
  };
1168
- /** Release adoption and unchanged lifecycle reports are not bundle movements. */
1168
+ /** Unchanged reports are not bundle movements. */
1169
1169
  const isInsightsMovementEvent = (event) => event.type === "UPDATE_APPLIED" || event.type === "RECOVERED";
1170
1170
  const matchesInsightsEventFilter = (event, filter) => {
1171
1171
  if (filter.kind === "all") return true;
@@ -4219,8 +4219,7 @@ function requireEvent(payload) {
4219
4219
  const type = requireStringField(payload, "type");
4220
4220
  switch (type) {
4221
4221
  case "UPDATE_APPLIED":
4222
- case "RECOVERED":
4223
- case "RELEASE_ADOPTED": {
4222
+ case "RECOVERED": {
4224
4223
  const updateStrategy = requireStringField(payload, "updateStrategy");
4225
4224
  if (updateStrategy !== "fingerprint" && updateStrategy !== "appVersion") throw new InsightsBadRequestError("Invalid event field: updateStrategy");
4226
4225
  return {
@@ -4264,8 +4263,7 @@ function createBundleEventRow(input) {
4264
4263
  };
4265
4264
  switch (input.type) {
4266
4265
  case "UPDATE_APPLIED":
4267
- case "RECOVERED":
4268
- case "RELEASE_ADOPTED": return {
4266
+ case "RECOVERED": return {
4269
4267
  ...base,
4270
4268
  from_bundle_id: input.fromBundleId,
4271
4269
  type: input.type,
@@ -4332,7 +4330,7 @@ const parseEventPageInput = (request) => {
4332
4330
  let bundle;
4333
4331
  if (bundleFields.some((key) => url.searchParams.has(key))) {
4334
4332
  const outcome = readSingle(url, "outcome");
4335
- if (outcome !== "applied" && outcome !== "recovered" && outcome !== "adopted") throw new InsightsBadRequestError("Invalid 'outcome' query parameter.");
4333
+ if (outcome !== "applied" && outcome !== "recovered" && outcome !== "unchanged") throw new InsightsBadRequestError("Invalid 'outcome' query parameter.");
4336
4334
  bundle = {
4337
4335
  ...readScope$1(url),
4338
4336
  bundleId: readId(url, "bundleId", 1024),
@@ -4653,9 +4651,9 @@ const bundleFilter = (input) => {
4653
4651
  type: "RECOVERED",
4654
4652
  fromBundleId: bundleId
4655
4653
  };
4656
- case "adopted": return {
4654
+ case "unchanged": return {
4657
4655
  ...scope,
4658
- type: "RELEASE_ADOPTED",
4656
+ type: "UNCHANGED",
4659
4657
  toBundleId: bundleId
4660
4658
  };
4661
4659
  default: throw new InsightsBadRequestError("Invalid Insights outcome.");
@@ -4862,7 +4860,7 @@ const createInsightsProvider = (model) => Object.freeze({
4862
4860
  sinceMs,
4863
4861
  beforeReceivedAtMs
4864
4862
  }));
4865
- const [reportingInstallations, bundleInstallations, appliedReports, recoveredReports, adoptedReports] = await Promise.all([
4863
+ const [reportingInstallations, bundleInstallations, appliedReports, recoveredReports, unchangedReports] = await Promise.all([
4866
4864
  reporting,
4867
4865
  measure(model.countInstallations({
4868
4866
  ...scope,
@@ -4871,7 +4869,7 @@ const createInsightsProvider = (model) => Object.freeze({
4871
4869
  })),
4872
4870
  countOutcome("applied"),
4873
4871
  countOutcome("recovered"),
4874
- countOutcome("adopted")
4872
+ countOutcome("unchanged")
4875
4873
  ]);
4876
4874
  return {
4877
4875
  ...scope,
@@ -4884,7 +4882,7 @@ const createInsightsProvider = (model) => Object.freeze({
4884
4882
  reportingInstallations: bundleInstallations,
4885
4883
  appliedReports,
4886
4884
  recoveredReports,
4887
- adoptedReports
4885
+ unchangedReports
4888
4886
  }
4889
4887
  };
4890
4888
  }
@@ -9120,10 +9118,10 @@ const hasValidBundleEventShape = (value) => {
9120
9118
  const type = field(value, "type");
9121
9119
  const fromBundleId = field(value, "from_bundle_id");
9122
9120
  const updateStrategy = field(value, "update_strategy");
9123
- return (type === "UPDATE_APPLIED" || type === "RECOVERED" || type === "RELEASE_ADOPTED") && typeof fromBundleId === "string" && (updateStrategy === "fingerprint" || updateStrategy === "appVersion") || type === "UNCHANGED" && fromBundleId === null && updateStrategy === null;
9121
+ return (type === "UPDATE_APPLIED" || type === "RECOVERED") && typeof fromBundleId === "string" && (updateStrategy === "fingerprint" || updateStrategy === "appVersion") || type === "UNCHANGED" && fromBundleId === null && updateStrategy === null;
9124
9122
  };
9125
9123
  const isBundleEventRow = (value) => typeof value === "object" && value !== null && typeof field(value, "id") === "string" && typeof field(value, "install_id") === "string" && isNullableString(field(value, "from_release_id")) && isNullableString(field(value, "to_release_id")) && typeof field(value, "to_bundle_id") === "string" && (field(value, "platform") === "ios" || field(value, "platform") === "android") && typeof field(value, "received_at_ms") === "number" && hasValidBundleEventShape(value);
9126
- const isInsightsInstallationRow = (value) => typeof value === "object" && value !== null && typeof field(value, "id") === "string" && typeof field(value, "install_id") === "string" && isNullableString(field(value, "user_id")) && isNullableString(field(value, "username")) && typeof field(value, "to_bundle_id") === "string" && (field(value, "type") === "UPDATE_APPLIED" || field(value, "type") === "RECOVERED" || field(value, "type") === "RELEASE_ADOPTED" || field(value, "type") === "UNCHANGED") && (field(value, "platform") === "ios" || field(value, "platform") === "android") && typeof field(value, "app_version") === "string" && typeof field(value, "channel") === "string" && typeof field(value, "cohort") === "string" && typeof field(value, "received_at_ms") === "number";
9124
+ const isInsightsInstallationRow = (value) => typeof value === "object" && value !== null && typeof field(value, "id") === "string" && typeof field(value, "install_id") === "string" && isNullableString(field(value, "user_id")) && isNullableString(field(value, "username")) && typeof field(value, "to_bundle_id") === "string" && (field(value, "type") === "UPDATE_APPLIED" || field(value, "type") === "RECOVERED" || field(value, "type") === "UNCHANGED") && (field(value, "platform") === "ios" || field(value, "platform") === "android") && typeof field(value, "app_version") === "string" && typeof field(value, "channel") === "string" && typeof field(value, "cohort") === "string" && typeof field(value, "received_at_ms") === "number";
9127
9125
  const isApiKeyRow = (value) => typeof value === "object" && value !== null && typeof field(value, "id") === "string" && typeof field(value, "hash") === "string" && typeof field(value, "name") === "string" && typeof field(value, "prefix") === "string" && field(value, "role") === "client" && typeof field(value, "created_at_ms") === "number" && (field(value, "revoked_at_ms") === null || typeof field(value, "revoked_at_ms") === "number");
9128
9126
  const parseOfficialRowItem = (value, partition, isRow) => {
9129
9127
  if (value.pk !== partition || typeof value.sk !== "string" || typeof value.version !== "number" || !isRow(value.row)) throw new DynamoDBStoredItemError();
@@ -9145,7 +9143,7 @@ const insightsBundlePartition = (filter) => {
9145
9143
  ]);
9146
9144
  return `${DYNAMODB_INSIGHTS_BUNDLE_PREFIX}${(0, node_crypto.createHash)("sha256").update(scope, "utf8").digest("hex")}`;
9147
9145
  };
9148
- const toInsightsBundleItem = (row) => row.type === "UNCHANGED" ? null : boundedDynamoDBMetadataItem({
9146
+ const toInsightsBundleItem = (row) => boundedDynamoDBMetadataItem({
9149
9147
  pk: insightsBundlePartition({
9150
9148
  platform: row.platform,
9151
9149
  channel: row.channel,
@@ -9231,10 +9229,10 @@ const recordDynamoDBInsightsEvent = async (store, { event: row, installation: ne
9231
9229
  TableName: store.tableName,
9232
9230
  Item: eventItem
9233
9231
  } },
9234
- ...bundleItem === null ? [] : [{ Put: {
9232
+ { Put: {
9235
9233
  TableName: store.tableName,
9236
9234
  Item: bundleItem
9237
- } }]
9235
+ } }
9238
9236
  ];
9239
9237
  if (current === null || advancesInsightsInstallation(next, current.row)) {
9240
9238
  actions.push({ Put: {
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@hot-updater/aws",
3
3
  "type": "module",
4
- "version": "1.0.0-rc.2",
4
+ "version": "1.0.0-rc.4",
5
5
  "description": "React Native OTA solution for self-hosted",
6
6
  "main": "dist/index.cjs",
7
7
  "module": "dist/index.mjs",
@@ -51,8 +51,8 @@
51
51
  "execa": "9.5.2",
52
52
  "mime": "^4.0.4",
53
53
  "@hot-updater/core": "1.0.0-rc.0",
54
- "@hot-updater/mock": "1.0.0-rc.2",
55
- "@hot-updater/test-utils": "1.0.0-rc.2"
54
+ "@hot-updater/mock": "1.0.0-rc.3",
55
+ "@hot-updater/test-utils": "1.0.0-rc.3"
56
56
  },
57
57
  "dependencies": {
58
58
  "@aws-sdk/client-cloudfront": "3.1066.0",
@@ -68,9 +68,9 @@
68
68
  "@aws-sdk/lib-dynamodb": "3.1066.0",
69
69
  "hono": "4.12.34",
70
70
  "aws-lambda": "1.0.7",
71
- "@hot-updater/cli-tools": "1.0.0-rc.2",
72
- "@hot-updater/server": "1.0.0-rc.2",
73
- "@hot-updater/plugin-core": "1.0.0-rc.2"
71
+ "@hot-updater/cli-tools": "1.0.0-rc.3",
72
+ "@hot-updater/plugin-core": "1.0.0-rc.3",
73
+ "@hot-updater/server": "1.0.0-rc.3"
74
74
  },
75
75
  "scripts": {
76
76
  "build": "tsdown",