wirejs-deploy-amplify-basic 0.0.78-table-resource → 0.0.80-table-resource

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.
@@ -8,9 +8,11 @@ import { Table, AttributeType, BillingMode } from 'aws-cdk-lib/aws-dynamodb';
8
8
  import { api } from './functions/api/resource';
9
9
  import { auth } from './auth/resource';
10
10
  import { TableDefinition, indexName } from 'wirejs-resources';
11
+ import { TableIndexes } from './constructs/table-indexes';
11
12
 
12
13
  // @ts-ignore
13
14
  import generated from './generated-resources';
15
+ import { Key } from 'aws-cdk-lib/aws-kms';
14
16
 
15
17
  const APP_ID = process.env.AWS_APP_ID ?? process.env.PWD?.replace(/[^a-zA-Z0-9-_]/g, '_');
16
18
  const BRANCH_ID = process.env.AWS_BRANCH ?? process.env.USER ?? 'anonymous';
@@ -72,8 +74,10 @@ const PKFieldTypes = {
72
74
  for (const resource of generated) {
73
75
  if (isDistributedTable(resource)) {
74
76
  const sanitizedId = resource.options.absoluteId.replace(/[^a-zA-Z0-9-_]/g, '_');
77
+ const tableName = `${TABLE_NAME_PREFIX}${sanitizedId}`
75
78
 
76
79
  const table = new Table(backend.stack, sanitizedId, {
80
+ tableName,
77
81
  partitionKey: {
78
82
  name: resource.options.partitionKey.field,
79
83
  type: PKFieldTypes[resource.options.partitionKey.type],
@@ -83,25 +87,26 @@ for (const resource of generated) {
83
87
  type: PKFieldTypes[resource.options.sortKey.type],
84
88
  } : undefined,
85
89
  removalPolicy: RemovalPolicy.RETAIN,
86
- tableName: `${TABLE_NAME_PREFIX}${sanitizedId}`,
87
90
  billingMode: BillingMode.PAY_PER_REQUEST,
88
91
  pointInTimeRecovery: true,
89
92
  });
90
93
 
91
- for (const index of resource.options.indexes ?? []) {
92
- const gsi = table.addGlobalSecondaryIndex({
93
- indexName: indexName(index),
94
- partitionKey: {
95
- name: index.partition.field,
96
- type: PKFieldTypes[index.partition.type],
97
- },
98
- sortKey: index.sort ? {
99
- name: index.sort.field,
100
- type: PKFieldTypes[index.sort.type],
101
- } : undefined,
102
- })
103
-
104
- }
94
+ new TableIndexes(backend.stack, `${tableName}Indexes`,
95
+ {
96
+ tableName,
97
+ indexes: (resource.options.indexes ?? []).map((index) => ({
98
+ indexName: indexName(index as any),
99
+ partitionKey: {
100
+ name: index.partition.field,
101
+ type: PKFieldTypes[index.partition.type as keyof typeof PKFieldTypes],
102
+ },
103
+ sortKey: index.sort ? {
104
+ name: index.sort.field,
105
+ type: PKFieldTypes[index.sort.type as keyof typeof PKFieldTypes],
106
+ } : undefined,
107
+ }))
108
+ }
109
+ );
105
110
 
106
111
  table.grantReadWriteData(backend.api.resources.lambda);
107
112
  }
@@ -0,0 +1,115 @@
1
+ import {
2
+ DynamoDBClient,
3
+ UpdateTableCommand,
4
+ DescribeTableCommand,
5
+ } from '@aws-sdk/client-dynamodb';
6
+ import { GlobalSecondaryIndexProps } from 'aws-cdk-lib/aws-dynamodb';
7
+ import { CloudFormationCustomResourceEvent } from 'aws-lambda';
8
+
9
+ const ddb = new DynamoDBClient({});
10
+
11
+ function sleep(ms: number) {
12
+ return new Promise((res) => setTimeout(res, ms));
13
+ }
14
+
15
+ /**
16
+ * Polls DDB until status matches the desired status.
17
+ *
18
+ * @param tableName
19
+ * @param indexName
20
+ * @param desiredStatus
21
+ */
22
+ async function waitForGsiStatus(
23
+ tableName: string,
24
+ indexName: string,
25
+ desiredStatus: string
26
+ ) {
27
+ while (true) {
28
+ const table = await ddb.send(new DescribeTableCommand({ TableName: tableName }));
29
+ const gsis = table.Table?.GlobalSecondaryIndexes ?? [];
30
+ const index = gsis.find((gsi) => gsi.IndexName === indexName);
31
+ if (!index && desiredStatus === 'DELETED') break;
32
+ if (index?.IndexStatus === desiredStatus) break;
33
+ await sleep(5000);
34
+ }
35
+ }
36
+
37
+ export const handler = async (event: CloudFormationCustomResourceEvent) => {
38
+ console.log('Event:', JSON.stringify(event));
39
+
40
+ const tableName = event.ResourceProperties.TableName;
41
+ const desiredGsis: GlobalSecondaryIndexProps[] =
42
+ event.ResourceProperties.GlobalSecondaryIndexes || [];
43
+ const desiredNames = new Set(desiredGsis.map((gsi) => gsi.indexName));
44
+
45
+ const tableDesc = await ddb.send(new DescribeTableCommand({ TableName: tableName }));
46
+ const existingGsis = tableDesc.Table?.GlobalSecondaryIndexes ?? [];
47
+ const existingNames = new Set(existingGsis.map((gsi) => gsi.IndexName));
48
+
49
+ if (event.RequestType === 'Delete') {
50
+ // table-level delete will take care of deleting everything.
51
+ return { PhysicalResourceId: tableName };
52
+ }
53
+
54
+ // Delete unwanted GSIs first.
55
+ for (const gsi of existingGsis) {
56
+ if (!gsi.IndexName) {
57
+ console.warn('"Existing" GSI without IndexName found (1)', gsi);
58
+ continue;
59
+ }
60
+ if (!desiredNames.has(gsi.IndexName)) {
61
+ console.log(`Deleting GSI: ${gsi.IndexName}`);
62
+ await ddb.send(new UpdateTableCommand({
63
+ TableName: tableName,
64
+ GlobalSecondaryIndexUpdates: [
65
+ { Delete: { IndexName: gsi.IndexName } },
66
+ ],
67
+ }));
68
+ await waitForGsiStatus(tableName, gsi.IndexName, 'DELETED');
69
+ }
70
+ }
71
+
72
+ // Add new GSIs
73
+ for (const gsi of desiredGsis) {
74
+ if (!existingNames.has(gsi.indexName)) {
75
+ console.log(`Creating GSI: ${gsi.indexName}`);
76
+ await ddb.send(new UpdateTableCommand({
77
+ TableName: tableName,
78
+ AttributeDefinitions: [
79
+ {
80
+ AttributeName: gsi.partitionKey.name,
81
+ AttributeType: gsi.partitionKey.type,
82
+ },
83
+ ...(gsi.sortKey ? [{
84
+ AttributeName: gsi.sortKey.name,
85
+ AttributeType: gsi.sortKey.type,
86
+ }] : []),
87
+ ],
88
+ GlobalSecondaryIndexUpdates: [{
89
+ Create: {
90
+ IndexName: gsi.indexName,
91
+ KeySchema: [
92
+ {
93
+ AttributeName: gsi.partitionKey.name,
94
+ KeyType: 'HASH'
95
+ },
96
+ ...(gsi.sortKey ? [{
97
+ AttributeName: gsi.sortKey.name,
98
+ KeyType: 'RANGE' as const
99
+ }] : []),
100
+ ],
101
+ Projection: {
102
+ ProjectionType: gsi.projectionType,
103
+ }
104
+ }
105
+ }],
106
+ }));
107
+ await waitForGsiStatus(tableName, gsi.indexName, 'ACTIVE');
108
+ }
109
+ }
110
+
111
+ return {
112
+ PhysicalResourceId: tableName,
113
+ Data: { Message: 'GSIs synced successfully' },
114
+ };
115
+ };
@@ -0,0 +1,36 @@
1
+ import { Duration, CustomResource } from 'aws-cdk-lib';
2
+ import * as path from 'path';
3
+ import * as lambda from 'aws-cdk-lib/aws-lambda';
4
+ import * as cr from 'aws-cdk-lib/custom-resources';
5
+ import { GlobalSecondaryIndexProps } from 'aws-cdk-lib/aws-dynamodb';
6
+ import { Construct } from 'constructs';
7
+
8
+ export interface TableIndexesProps {
9
+ tableName: string;
10
+ indexes: GlobalSecondaryIndexProps[];
11
+ }
12
+
13
+ export class TableIndexes extends Construct {
14
+ constructor(scope: Construct, id: string, props: TableIndexesProps) {
15
+ super(scope, id);
16
+
17
+ const onEvent = new lambda.Function(this, 'TableIndexesHandler', {
18
+ runtime: lambda.Runtime.NODEJS_22_X,
19
+ handler: 'index.handler',
20
+ code: lambda.Code.fromAsset(path.join(__dirname, 'handler')),
21
+ timeout: Duration.minutes(15),
22
+ });
23
+
24
+ const provider = new cr.Provider(this, 'TableIndexesProvider', {
25
+ onEventHandler: onEvent,
26
+ });
27
+
28
+ new CustomResource(this, 'TableIndexesCustomResource', {
29
+ serviceToken: provider.serviceToken,
30
+ properties: {
31
+ TableName: props.tableName,
32
+ GlobalSecondaryIndexes: props.indexes,
33
+ },
34
+ });
35
+ }
36
+ }
@@ -3,6 +3,6 @@
3
3
  "dependencies": {
4
4
  "jsdom": "^25.0.1",
5
5
  "wirejs-dom": "^1.0.38",
6
- "wirejs-resources": "^0.1.46-table-resource"
6
+ "wirejs-resources": "^0.1.48-table-resource"
7
7
  }
8
8
  }
package/build.js CHANGED
@@ -55,6 +55,7 @@ async function installDeps() {
55
55
  ...{
56
56
  '@aws-amplify/backend': '^1.14.0',
57
57
  '@aws-amplify/backend-cli': '^1.4.8',
58
+ "@aws-sdk/client-dynamodb": "^3.799.0",
58
59
  'aws-cdk': '^2.177.0',
59
60
  'aws-cdk-lib': '^2.177.0',
60
61
  'constructs': '^10.4.2',
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "wirejs-deploy-amplify-basic",
3
- "version": "0.0.78-table-resource",
3
+ "version": "0.0.80-table-resource",
4
4
  "type": "module",
5
5
  "main": "./dist/index.js",
6
6
  "types": "./dist/index.d.ts",
@@ -32,7 +32,7 @@
32
32
  "recursive-copy": "^2.0.14",
33
33
  "rimraf": "^6.0.1",
34
34
  "wirejs-dom": "^1.0.38",
35
- "wirejs-resources": "^0.1.46-table-resource"
35
+ "wirejs-resources": "^0.1.48-table-resource"
36
36
  },
37
37
  "devDependencies": {
38
38
  "@aws-amplify/backend": "^1.14.0",