@aws/nx-plugin 1.0.0-rc.13 → 1.0.0-rc.14

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.
Files changed (82) hide show
  1. package/generators.json +28 -1
  2. package/package.json +1 -1
  3. package/src/connection/generator.d.ts +1 -1
  4. package/src/connection/generator.js +58 -42
  5. package/src/connection/generator.js.map +1 -1
  6. package/src/infra/app/__snapshots__/generator.spec.ts.snap +7 -7
  7. package/src/infra/app/generator.js +4 -4
  8. package/src/infra/app/generator.js.map +1 -1
  9. package/src/preset/__snapshots__/generator.spec.ts.snap +3 -1
  10. package/src/py/dynamodb/__snapshots__/generator.spec.ts.snap +1486 -0
  11. package/src/py/dynamodb/agent-connection/__snapshots__/generator.spec.ts.snap +45 -0
  12. package/src/py/dynamodb/agent-connection/generator.d.ts +10 -0
  13. package/src/py/dynamodb/agent-connection/generator.js +39 -0
  14. package/src/py/dynamodb/agent-connection/generator.js.map +1 -0
  15. package/src/py/dynamodb/agent-connection/schema.d.ts +14 -0
  16. package/src/py/dynamodb/agent-connection/schema.json +22 -0
  17. package/src/py/dynamodb/fast-api-connection/__snapshots__/generator.spec.ts.snap +23 -0
  18. package/src/py/dynamodb/fast-api-connection/generator.d.ts +10 -0
  19. package/src/py/dynamodb/fast-api-connection/generator.js +37 -0
  20. package/src/py/dynamodb/fast-api-connection/generator.js.map +1 -0
  21. package/src/py/dynamodb/fast-api-connection/schema.d.ts +12 -0
  22. package/src/py/dynamodb/fast-api-connection/schema.json +18 -0
  23. package/src/py/dynamodb/files/__name__/__init__.py.template +6 -0
  24. package/src/py/dynamodb/files/__name__/client.py.template +39 -0
  25. package/src/py/dynamodb/files/__name__/entities/__init__.py.template +4 -0
  26. package/src/py/dynamodb/files/__name__/entities/base.py.template +63 -0
  27. package/src/py/dynamodb/files/__name__/entities/example.py.template +64 -0
  28. package/src/py/dynamodb/files/config.json.template +24 -0
  29. package/src/py/dynamodb/generator.d.ts +10 -0
  30. package/src/py/dynamodb/generator.js +106 -0
  31. package/src/py/dynamodb/generator.js.map +1 -0
  32. package/src/py/dynamodb/mcp-server-connection/__snapshots__/generator.spec.ts.snap +45 -0
  33. package/src/py/dynamodb/mcp-server-connection/generator.d.ts +10 -0
  34. package/src/py/dynamodb/mcp-server-connection/generator.js +39 -0
  35. package/src/py/dynamodb/mcp-server-connection/generator.js.map +1 -0
  36. package/src/py/dynamodb/mcp-server-connection/schema.d.ts +14 -0
  37. package/src/py/dynamodb/mcp-server-connection/schema.json +22 -0
  38. package/src/py/dynamodb/schema.d.ts +15 -0
  39. package/src/py/dynamodb/schema.json +70 -0
  40. package/src/ts/dynamodb/__snapshots__/generator.spec.ts.snap +303 -512
  41. package/src/ts/dynamodb/files/config.json.template +24 -0
  42. package/src/ts/dynamodb/files/src/client.ts.template +26 -10
  43. package/src/ts/dynamodb/files/src/index.ts.template +0 -8
  44. package/src/ts/dynamodb/generator.js +15 -12
  45. package/src/ts/dynamodb/generator.js.map +1 -1
  46. package/src/ts/dynamodb/schema.d.ts +1 -0
  47. package/src/ts/dynamodb/schema.json +20 -2
  48. package/src/utils/dynamodb-constructs/dynamodb-constructs.d.ts +0 -1
  49. package/src/utils/dynamodb-constructs/dynamodb-constructs.js.map +1 -1
  50. package/src/utils/dynamodb-constructs/files/cdk/app/dynamodb/__nameKebabCase__.ts.template +12 -13
  51. package/src/utils/dynamodb-constructs/files/terraform/app/dynamodb/__nameKebabCase__/__nameKebabCase__.tf.template +3 -3
  52. package/src/{ts/dynamodb/files/scripts → utils/files/common/scripts/src/dynamodb}/create-local-table.ts.template +63 -36
  53. package/src/utils/files/common/scripts/src/dynamodb/pull-image.ts.template +13 -0
  54. package/src/{ts/dynamodb/files/scripts → utils/files/common/scripts/src/dynamodb}/start-container.ts.template +28 -18
  55. package/src/utils/port.d.ts +6 -5
  56. package/src/utils/port.js +7 -5
  57. package/src/utils/port.js.map +1 -1
  58. package/src/utils/shared-constructs-constants.d.ts +1 -0
  59. package/src/utils/shared-constructs-constants.js +2 -1
  60. package/src/utils/shared-constructs-constants.js.map +1 -1
  61. package/src/utils/shared-dynamodb-scripts.d.ts +11 -0
  62. package/src/utils/shared-dynamodb-scripts.js +23 -0
  63. package/src/utils/shared-dynamodb-scripts.js.map +1 -0
  64. package/src/utils/shared-infra-scripts.d.ts +11 -0
  65. package/src/utils/shared-infra-scripts.js +35 -0
  66. package/src/utils/shared-infra-scripts.js.map +1 -0
  67. package/src/utils/shared-scripts.d.ts +1 -4
  68. package/src/utils/shared-scripts.js +2 -23
  69. package/src/utils/shared-scripts.js.map +1 -1
  70. package/src/utils/versions.d.ts +1 -0
  71. package/src/utils/versions.js +1 -0
  72. package/src/utils/versions.js.map +1 -1
  73. package/src/ts/dynamodb/files/dynamodb.config.json +0 -14
  74. package/src/ts/dynamodb/files/scripts/pull-image.ts.template +0 -15
  75. package/src/ts/dynamodb/files/src/constants.ts.template +0 -9
  76. /package/src/utils/files/common/scripts/src/{index.ts.template → infra/index.ts.template} +0 -0
  77. /package/src/utils/files/common/scripts/src/{infra-deploy.ts.template → infra/infra-deploy.ts.template} +0 -0
  78. /package/src/utils/files/common/scripts/src/{infra-destroy.ts.template → infra/infra-destroy.ts.template} +0 -0
  79. /package/src/utils/files/common/scripts/src/{stage-credentials → infra/stage-credentials}/cdk-command.ts.template +0 -0
  80. /package/src/utils/files/common/scripts/src/{stage-credentials → infra/stage-credentials}/credentials.ts.template +0 -0
  81. /package/src/utils/files/common/scripts/src/{stage-credentials → infra/stage-credentials}/run.ts.template +0 -0
  82. /package/src/utils/files/common/scripts/src/{stage-credentials → infra/stage-credentials}/stage-parser.ts.template +0 -0
@@ -1,296 +1,33 @@
1
1
  // Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
2
2
 
3
- exports[`ts#dynamodb generator > should generate scripts for finch engine > packages/my-table/scripts/create-local-table.ts 1`] = `
4
- "import {
5
- CreateTableCommand,
6
- DescribeTableCommand,
7
- DynamoDBClient,
8
- GlobalSecondaryIndexDescription,
9
- ResourceInUseException,
10
- UpdateTableCommand,
11
- } from '@aws-sdk/client-dynamodb';
12
- import { readFileSync } from 'fs';
13
- import { join, dirname } from 'path';
14
- import { fileURLToPath } from 'url';
15
-
16
- const __dirname = dirname(fileURLToPath(import.meta.url));
17
- const { globalSecondaryIndexes } = JSON.parse(
18
- readFileSync(join(__dirname, '../dynamodb.config.json'), 'utf-8'),
19
- ) as {
20
- globalSecondaryIndexes: {
21
- indexName: string;
22
- partitionKey: string;
23
- sortKey?: string;
24
- }[];
25
- };
26
-
27
- const [tableName, port] = process.argv.slice(2);
28
- const endpoint = \`http://localhost:\${port}\`;
29
-
30
- const REQUEST_TIMEOUT_MS = 10_000;
31
- const timeoutSignal = () => AbortSignal.timeout(REQUEST_TIMEOUT_MS);
32
-
33
- const client = new DynamoDBClient({
34
- endpoint,
35
- region: 'us-east-1',
36
- credentials: {
37
- accessKeyId: 'accessKeyId',
38
- secretAccessKey: 'secretAccessKey',
3
+ exports[`ts#dynamodb generator > should generate scripts for finch engine 1`] = `
4
+ "{
5
+ "runtimeConfigKey": "MyTable",
6
+ "serveLocal": {
7
+ "port": 8000,
8
+ "tableName": "proj-my-table",
9
+ "image": "public.ecr.aws/aws-dynamodb-local/aws-dynamodb-local:latest",
10
+ "containerName": "proj-dynamodb",
11
+ "containerEngine": "finch"
39
12
  },
40
- });
41
-
42
- const waitForTableActive = async (
43
- waitClient: DynamoDBClient,
44
- waitTableName: string,
45
- ): Promise<void> => {
46
- for (;;) {
47
- const { Table } = await waitClient.send(
48
- new DescribeTableCommand({ TableName: waitTableName }),
49
- { abortSignal: timeoutSignal() },
50
- );
51
- const allActive =
52
- Table?.TableStatus === 'ACTIVE' &&
53
- (Table?.GlobalSecondaryIndexes ?? []).every(
54
- (g: GlobalSecondaryIndexDescription) => g.IndexStatus === 'ACTIVE',
55
- );
56
- if (allActive) return;
57
- await new Promise((resolve) => setTimeout(resolve, 500));
58
- }
59
- };
60
-
61
- const ensureTable = async (
62
- tblClient: DynamoDBClient,
63
- tblName: string,
64
- gsIs: { indexName: string; partitionKey: string; sortKey?: string }[],
65
- ): Promise<void> => {
66
- const gsiAttributeDefinitions = gsIs.flatMap((gsi) => [
67
- { AttributeName: gsi.partitionKey, AttributeType: 'S' as const },
68
- ...(gsi.sortKey
69
- ? [{ AttributeName: gsi.sortKey, AttributeType: 'S' as const }]
70
- : []),
71
- ]);
72
- const attributeDefinitions = [
73
- { AttributeName: 'pk', AttributeType: 'S' as const },
74
- { AttributeName: 'sk', AttributeType: 'S' as const },
75
- ...gsiAttributeDefinitions.filter(
76
- (a, i, arr) =>
77
- arr.findIndex((b) => b.AttributeName === a.AttributeName) === i,
78
- ),
79
- ];
80
- const globalSecondaryIndexes = gsIs.map((gsi) => ({
81
- IndexName: gsi.indexName,
82
- KeySchema: [
83
- { AttributeName: gsi.partitionKey, KeyType: 'HASH' as const },
84
- ...(gsi.sortKey
85
- ? [{ AttributeName: gsi.sortKey, KeyType: 'RANGE' as const }]
86
- : []),
87
- ],
88
- Projection: { ProjectionType: 'ALL' as const },
89
- }));
90
-
91
- for (let attempt = 0; attempt < 30; attempt++) {
92
- try {
93
- // New table: create with all GSIs in one shot.
94
- await tblClient.send(
95
- new CreateTableCommand({
96
- TableName: tblName,
97
- AttributeDefinitions: attributeDefinitions,
98
- KeySchema: [
99
- { AttributeName: 'pk', KeyType: 'HASH' },
100
- { AttributeName: 'sk', KeyType: 'RANGE' },
101
- ],
102
- BillingMode: 'PAY_PER_REQUEST',
103
- ...(globalSecondaryIndexes.length > 0 && {
104
- GlobalSecondaryIndexes: globalSecondaryIndexes,
105
- }),
106
- }),
107
- { abortSignal: timeoutSignal() },
108
- );
109
- console.log(\`Created table: \${tblName}\`);
110
- for (const gsi of globalSecondaryIndexes) {
111
- console.log(\`Created GSI: \${gsi.IndexName}\`);
112
- }
113
- return;
114
- } catch (e) {
115
- // Table already exists: sync GSIs to match desired state.
116
- if (e instanceof ResourceInUseException) {
117
- const { Table } = await tblClient.send(
118
- new DescribeTableCommand({ TableName: tblName }),
119
- { abortSignal: timeoutSignal() },
120
- );
121
- const existingGSIs = Table?.GlobalSecondaryIndexes ?? [];
122
- const existingNames = new Set(
123
- existingGSIs.map(
124
- (g: GlobalSecondaryIndexDescription) => g.IndexName ?? '',
125
- ),
126
- );
127
- const desiredNames = new Set(gsIs.map((g) => g.indexName));
128
-
129
- // DynamoDB Local only allows one GSI to be created or deleted at a time.
130
- for (const existing of existingGSIs) {
131
- if (!existing.IndexName || desiredNames.has(existing.IndexName))
132
- continue;
133
- await tblClient.send(
134
- new UpdateTableCommand({
135
- TableName: tblName,
136
- GlobalSecondaryIndexUpdates: [
137
- { Delete: { IndexName: existing.IndexName } },
138
- ],
139
- }),
140
- { abortSignal: timeoutSignal() },
141
- );
142
- console.log(\`Deleted GSI: \${existing.IndexName}\`);
143
- await waitForTableActive(tblClient, tblName);
144
- }
145
-
146
- for (const gsi of gsIs) {
147
- if (existingNames.has(gsi.indexName)) continue;
148
- await tblClient.send(
149
- new UpdateTableCommand({
150
- TableName: tblName,
151
- AttributeDefinitions: [
152
- { AttributeName: gsi.partitionKey, AttributeType: 'S' },
153
- ...(gsi.sortKey
154
- ? [
155
- {
156
- AttributeName: gsi.sortKey,
157
- AttributeType: 'S' as const,
158
- },
159
- ]
160
- : []),
161
- ],
162
- GlobalSecondaryIndexUpdates: [
163
- {
164
- Create: {
165
- IndexName: gsi.indexName,
166
- KeySchema: [
167
- { AttributeName: gsi.partitionKey, KeyType: 'HASH' },
168
- ...(gsi.sortKey
169
- ? [{ AttributeName: gsi.sortKey, KeyType: 'RANGE' }]
170
- : []),
171
- ],
172
- Projection: { ProjectionType: 'ALL' },
173
- },
174
- },
175
- ],
176
- }),
177
- { abortSignal: timeoutSignal() },
178
- );
179
- console.log(\`Created GSI: \${gsi.indexName}\`);
180
- await waitForTableActive(tblClient, tblName);
181
- }
182
- return;
13
+ "tableConfig": {
14
+ "globalSecondaryIndexes": [
15
+ {
16
+ "indexName": "gsi1pk-gsi1sk-index",
17
+ "partitionKey": "gsi1pk",
18
+ "sortKey": "gsi1sk"
19
+ },
20
+ {
21
+ "indexName": "gsi2pk-gsi2sk-index",
22
+ "partitionKey": "gsi2pk",
23
+ "sortKey": "gsi2sk"
183
24
  }
184
- if (attempt === 29) throw e;
185
- await new Promise((resolve) => setTimeout(resolve, 1000));
186
- }
187
- }
188
- };
189
-
190
- await ensureTable(client, tableName, globalSecondaryIndexes);
191
- "
192
- `;
193
-
194
- exports[`ts#dynamodb generator > should generate scripts for finch engine > packages/my-table/scripts/pull-image.ts 1`] = `
195
- "import { spawnSync } from 'child_process';
196
-
197
- const ENGINE = 'finch';
198
- const image = process.argv[2];
199
-
200
- const inspect = spawnSync(ENGINE, ['image', 'inspect', image], {
201
- stdio: 'ignore',
202
- });
203
-
204
- if (inspect.status !== 0) {
205
- const pull = spawnSync(ENGINE, ['pull', image], { stdio: 'inherit' });
206
- if (pull.status !== 0) {
207
- process.exit(pull.status ?? 1);
25
+ ]
208
26
  }
209
27
  }
210
28
  "
211
29
  `;
212
30
 
213
- exports[`ts#dynamodb generator > should generate scripts for finch engine > packages/my-table/scripts/start-container.ts 1`] = `
214
- "import { spawn, spawnSync } from 'child_process';
215
-
216
- const ENGINE = 'finch';
217
-
218
- const [containerName, image, hostPort] = process.argv.slice(2);
219
-
220
- const containerExists = (): boolean =>
221
- spawnSync(ENGINE, ['container', 'inspect', containerName], {
222
- stdio: 'ignore',
223
- }).status === 0;
224
-
225
- const isRunning = (): boolean => {
226
- const result = spawnSync(
227
- ENGINE,
228
- ['container', 'inspect', '-f', '{{.State.Running}}', containerName],
229
- { encoding: 'utf-8' },
230
- );
231
- return result.status === 0 && result.stdout.trim() === 'true';
232
- };
233
-
234
- // Multiple dynamo table packages run serve-local in parallel sharing the same
235
- // container. All processes stream logs, but only the one that created the
236
- // container stops it on exit — others just detach.
237
- let created = false;
238
- let runRetries = 0;
239
-
240
- for (;;) {
241
- if (containerExists()) {
242
- if (!isRunning()) {
243
- // Another process may have started it between our inspect and start — container engine start
244
- // exits 0 even if the container is already running, so no special-casing needed.
245
- const start = spawnSync(ENGINE, ['start', containerName], {
246
- stdio: 'inherit',
247
- });
248
- if (start.status !== 0) process.exit(start.status ?? 1);
249
- }
250
- break;
251
- }
252
- const runArgs = (
253
- \`run --name \${containerName} -u root -w /home/dynamodblocal\` +
254
- \` -p \${hostPort}:\${hostPort} -v \${containerName}-data:/home/dynamodblocal/data\` +
255
- \` -d \${image} -jar DynamoDBLocal.jar -sharedDb -dbPath ./data -port \${hostPort} -optimizeDbBeforeStartup\`
256
- ).split(' ');
257
- const create = spawnSync(ENGINE, runArgs, { stdio: 'pipe' });
258
- if (create.status === 0) {
259
- created = true;
260
- break;
261
- }
262
- if (!containerExists() && ++runRetries > 3) {
263
- process.stderr.write(create.stderr);
264
- process.exit(create.status ?? 1);
265
- }
266
- }
267
-
268
- const logs = spawn(ENGINE, ['logs', '-f', containerName], {
269
- stdio: ['ignore', 'inherit', 'inherit'],
270
- });
271
-
272
- let exiting = false;
273
-
274
- const cleanup = () => {
275
- if (exiting) return;
276
- exiting = true;
277
- if (created) {
278
- spawnSync(ENGINE, ['rm', '-f', containerName], { stdio: 'ignore' });
279
- }
280
- logs.kill();
281
- process.exit(0);
282
- };
283
-
284
- process.on('SIGTERM', cleanup);
285
- process.on('SIGINT', cleanup);
286
- process.on('SIGHUP', cleanup);
287
-
288
- logs.on('exit', (code) => {
289
- if (!exiting) process.exit(code ?? 0);
290
- });
291
- "
292
- `;
293
-
294
31
  exports[`ts#dynamodb generator > should generate terraform modules when iac is terraform 1`] = `
295
32
  "terraform {
296
33
  required_version = ">= 1.0"
@@ -485,8 +222,8 @@ variable "enable_key_rotation" {
485
222
  }
486
223
 
487
224
  locals {
488
- dynamodb_config = jsondecode(file("\${path.module}/../../../../../../../packages/my-table/dynamodb.config.json"))
489
- global_secondary_indexes = [for gsi in local.dynamodb_config.globalSecondaryIndexes : {
225
+ dynamodb_config = jsondecode(file("\${path.module}/../../../../../../../packages/my-table/config.json"))
226
+ global_secondary_indexes = [for gsi in local.dynamodb_config.tableConfig.globalSecondaryIndexes : {
490
227
  name = gsi.indexName
491
228
  hash_key = gsi.partitionKey
492
229
  range_key = try(gsi.sortKey, null)
@@ -508,7 +245,7 @@ module "add_to_runtime_config" {
508
245
  source = "../../../core/runtime-config/entry"
509
246
 
510
247
  namespace = "dynamodb"
511
- key = "MyTable"
248
+ key = local.dynamodb_config.runtimeConfigKey
512
249
  value = {
513
250
  tableName = module.dynamodb_table.table_name
514
251
  }
@@ -516,43 +253,43 @@ module "add_to_runtime_config" {
516
253
  "
517
254
  `;
518
255
 
519
- exports[`ts#dynamodb generator > should generate the dynamodb project > packages/my-table/scripts/create-local-table.ts 1`] = `
256
+ exports[`ts#dynamodb generator > should generate the dynamodb project > packages/common/scripts/src/dynamodb/create-local-table.ts 1`] = `
520
257
  "import {
258
+ BillingMode,
521
259
  CreateTableCommand,
522
260
  DescribeTableCommand,
523
261
  DynamoDBClient,
524
262
  GlobalSecondaryIndexDescription,
263
+ IndexStatus,
264
+ KeyType,
265
+ ProjectionType,
525
266
  ResourceInUseException,
267
+ ScalarAttributeType,
268
+ TableStatus,
526
269
  UpdateTableCommand,
527
270
  } from '@aws-sdk/client-dynamodb';
528
271
  import { readFileSync } from 'fs';
529
- import { join, dirname } from 'path';
530
- import { fileURLToPath } from 'url';
531
272
 
532
- const __dirname = dirname(fileURLToPath(import.meta.url));
533
- const { globalSecondaryIndexes } = JSON.parse(
534
- readFileSync(join(__dirname, '../dynamodb.config.json'), 'utf-8'),
535
- ) as {
536
- globalSecondaryIndexes: {
537
- indexName: string;
538
- partitionKey: string;
539
- sortKey?: string;
540
- }[];
273
+ const {
274
+ serveLocal: { port, tableName },
275
+ tableConfig: { globalSecondaryIndexes },
276
+ } = JSON.parse(readFileSync('config.json', 'utf-8')) as {
277
+ serveLocal: { port: number; tableName: string };
278
+ tableConfig: {
279
+ globalSecondaryIndexes: {
280
+ indexName: string;
281
+ partitionKey: string;
282
+ sortKey?: string;
283
+ }[];
284
+ };
541
285
  };
542
286
 
543
- const [tableName, port] = process.argv.slice(2);
544
- const endpoint = \`http://localhost:\${port}\`;
545
-
546
- const REQUEST_TIMEOUT_MS = 10_000;
547
- const timeoutSignal = () => AbortSignal.timeout(REQUEST_TIMEOUT_MS);
287
+ const timeoutSignal = () => AbortSignal.timeout(10000);
548
288
 
549
289
  const client = new DynamoDBClient({
550
- endpoint,
290
+ endpoint: \`http://localhost:\${port}\`,
551
291
  region: 'us-east-1',
552
- credentials: {
553
- accessKeyId: 'accessKeyId',
554
- secretAccessKey: 'secretAccessKey',
555
- },
292
+ credentials: { accessKeyId: 'UNUSED', secretAccessKey: 'UNUSED' },
556
293
  });
557
294
 
558
295
  const waitForTableActive = async (
@@ -565,9 +302,10 @@ const waitForTableActive = async (
565
302
  { abortSignal: timeoutSignal() },
566
303
  );
567
304
  const allActive =
568
- Table?.TableStatus === 'ACTIVE' &&
305
+ Table?.TableStatus === TableStatus.ACTIVE &&
569
306
  (Table?.GlobalSecondaryIndexes ?? []).every(
570
- (g: GlobalSecondaryIndexDescription) => g.IndexStatus === 'ACTIVE',
307
+ (g: GlobalSecondaryIndexDescription) =>
308
+ g.IndexStatus === IndexStatus.ACTIVE,
571
309
  );
572
310
  if (allActive) return;
573
311
  await new Promise((resolve) => setTimeout(resolve, 500));
@@ -580,14 +318,14 @@ const ensureTable = async (
580
318
  gsIs: { indexName: string; partitionKey: string; sortKey?: string }[],
581
319
  ): Promise<void> => {
582
320
  const gsiAttributeDefinitions = gsIs.flatMap((gsi) => [
583
- { AttributeName: gsi.partitionKey, AttributeType: 'S' as const },
321
+ { AttributeName: gsi.partitionKey, AttributeType: ScalarAttributeType.S },
584
322
  ...(gsi.sortKey
585
- ? [{ AttributeName: gsi.sortKey, AttributeType: 'S' as const }]
323
+ ? [{ AttributeName: gsi.sortKey, AttributeType: ScalarAttributeType.S }]
586
324
  : []),
587
325
  ]);
588
326
  const attributeDefinitions = [
589
- { AttributeName: 'pk', AttributeType: 'S' as const },
590
- { AttributeName: 'sk', AttributeType: 'S' as const },
327
+ { AttributeName: 'pk', AttributeType: ScalarAttributeType.S },
328
+ { AttributeName: 'sk', AttributeType: ScalarAttributeType.S },
591
329
  ...gsiAttributeDefinitions.filter(
592
330
  (a, i, arr) =>
593
331
  arr.findIndex((b) => b.AttributeName === a.AttributeName) === i,
@@ -596,12 +334,12 @@ const ensureTable = async (
596
334
  const globalSecondaryIndexes = gsIs.map((gsi) => ({
597
335
  IndexName: gsi.indexName,
598
336
  KeySchema: [
599
- { AttributeName: gsi.partitionKey, KeyType: 'HASH' as const },
337
+ { AttributeName: gsi.partitionKey, KeyType: KeyType.HASH },
600
338
  ...(gsi.sortKey
601
- ? [{ AttributeName: gsi.sortKey, KeyType: 'RANGE' as const }]
339
+ ? [{ AttributeName: gsi.sortKey, KeyType: KeyType.RANGE }]
602
340
  : []),
603
341
  ],
604
- Projection: { ProjectionType: 'ALL' as const },
342
+ Projection: { ProjectionType: ProjectionType.ALL },
605
343
  }));
606
344
 
607
345
  for (let attempt = 0; attempt < 30; attempt++) {
@@ -612,10 +350,10 @@ const ensureTable = async (
612
350
  TableName: tblName,
613
351
  AttributeDefinitions: attributeDefinitions,
614
352
  KeySchema: [
615
- { AttributeName: 'pk', KeyType: 'HASH' },
616
- { AttributeName: 'sk', KeyType: 'RANGE' },
353
+ { AttributeName: 'pk', KeyType: KeyType.HASH },
354
+ { AttributeName: 'sk', KeyType: KeyType.RANGE },
617
355
  ],
618
- BillingMode: 'PAY_PER_REQUEST',
356
+ BillingMode: BillingMode.PAY_PER_REQUEST,
619
357
  ...(globalSecondaryIndexes.length > 0 && {
620
358
  GlobalSecondaryIndexes: globalSecondaryIndexes,
621
359
  }),
@@ -665,12 +403,15 @@ const ensureTable = async (
665
403
  new UpdateTableCommand({
666
404
  TableName: tblName,
667
405
  AttributeDefinitions: [
668
- { AttributeName: gsi.partitionKey, AttributeType: 'S' },
406
+ {
407
+ AttributeName: gsi.partitionKey,
408
+ AttributeType: ScalarAttributeType.S,
409
+ },
669
410
  ...(gsi.sortKey
670
411
  ? [
671
412
  {
672
413
  AttributeName: gsi.sortKey,
673
- AttributeType: 'S' as const,
414
+ AttributeType: ScalarAttributeType.S,
674
415
  },
675
416
  ]
676
417
  : []),
@@ -680,12 +421,20 @@ const ensureTable = async (
680
421
  Create: {
681
422
  IndexName: gsi.indexName,
682
423
  KeySchema: [
683
- { AttributeName: gsi.partitionKey, KeyType: 'HASH' },
424
+ {
425
+ AttributeName: gsi.partitionKey,
426
+ KeyType: KeyType.HASH,
427
+ },
684
428
  ...(gsi.sortKey
685
- ? [{ AttributeName: gsi.sortKey, KeyType: 'RANGE' }]
429
+ ? [
430
+ {
431
+ AttributeName: gsi.sortKey,
432
+ KeyType: KeyType.RANGE,
433
+ },
434
+ ]
686
435
  : []),
687
436
  ],
688
- Projection: { ProjectionType: 'ALL' },
437
+ Projection: { ProjectionType: ProjectionType.ALL },
689
438
  },
690
439
  },
691
440
  ],
@@ -707,18 +456,21 @@ await ensureTable(client, tableName, globalSecondaryIndexes);
707
456
  "
708
457
  `;
709
458
 
710
- exports[`ts#dynamodb generator > should generate the dynamodb project > packages/my-table/scripts/pull-image.ts 1`] = `
459
+ exports[`ts#dynamodb generator > should generate the dynamodb project > packages/common/scripts/src/dynamodb/pull-image.ts 1`] = `
711
460
  "import { spawnSync } from 'child_process';
461
+ import { readFileSync } from 'fs';
712
462
 
713
- const ENGINE = 'docker';
714
- const image = process.argv[2];
463
+ const { containerEngine, image } = JSON.parse(
464
+ readFileSync('config.json', 'utf-8'),
465
+ ).serveLocal;
715
466
 
716
- const inspect = spawnSync(ENGINE, ['image', 'inspect', image], {
467
+ const inspect = spawnSync(containerEngine, ['image', 'inspect', image], {
717
468
  stdio: 'ignore',
718
469
  });
719
-
720
470
  if (inspect.status !== 0) {
721
- const pull = spawnSync(ENGINE, ['pull', image], { stdio: 'inherit' });
471
+ const pull = spawnSync(containerEngine, ['pull', image], {
472
+ stdio: 'inherit',
473
+ });
722
474
  if (pull.status !== 0) {
723
475
  process.exit(pull.status ?? 1);
724
476
  }
@@ -726,21 +478,22 @@ if (inspect.status !== 0) {
726
478
  "
727
479
  `;
728
480
 
729
- exports[`ts#dynamodb generator > should generate the dynamodb project > packages/my-table/scripts/start-container.ts 1`] = `
481
+ exports[`ts#dynamodb generator > should generate the dynamodb project > packages/common/scripts/src/dynamodb/start-container.ts 1`] = `
730
482
  "import { spawn, spawnSync } from 'child_process';
483
+ import { readFileSync } from 'fs';
731
484
 
732
- const ENGINE = 'docker';
733
-
734
- const [containerName, image, hostPort] = process.argv.slice(2);
485
+ const { containerEngine, containerName, image, port } = JSON.parse(
486
+ readFileSync('config.json', 'utf-8'),
487
+ ).serveLocal;
735
488
 
736
489
  const containerExists = (): boolean =>
737
- spawnSync(ENGINE, ['container', 'inspect', containerName], {
490
+ spawnSync(containerEngine, ['container', 'inspect', containerName], {
738
491
  stdio: 'ignore',
739
492
  }).status === 0;
740
493
 
741
494
  const isRunning = (): boolean => {
742
495
  const result = spawnSync(
743
- ENGINE,
496
+ containerEngine,
744
497
  ['container', 'inspect', '-f', '{{.State.Running}}', containerName],
745
498
  { encoding: 'utf-8' },
746
499
  );
@@ -758,19 +511,38 @@ for (;;) {
758
511
  if (!isRunning()) {
759
512
  // Another process may have started it between our inspect and start — container engine start
760
513
  // exits 0 even if the container is already running, so no special-casing needed.
761
- const start = spawnSync(ENGINE, ['start', containerName], {
514
+ const start = spawnSync(containerEngine, ['start', containerName], {
762
515
  stdio: 'inherit',
763
516
  });
764
517
  if (start.status !== 0) process.exit(start.status ?? 1);
765
518
  }
766
519
  break;
767
520
  }
768
- const runArgs = (
769
- \`run --rm --name \${containerName} -u root -w /home/dynamodblocal\` +
770
- \` -p \${hostPort}:\${hostPort} -v \${containerName}-data:/home/dynamodblocal/data\` +
771
- \` -d \${image} -jar DynamoDBLocal.jar -sharedDb -dbPath ./data -port \${hostPort} -optimizeDbBeforeStartup\`
772
- ).split(' ');
773
- const create = spawnSync(ENGINE, runArgs, { stdio: 'pipe' });
521
+ const runArgs = [
522
+ 'run',
523
+ ...(containerEngine === 'docker' ? ['--rm'] : []),
524
+ '--name',
525
+ containerName,
526
+ '-u',
527
+ 'root',
528
+ '-w',
529
+ '/home/dynamodblocal',
530
+ \`-p\`,
531
+ \`\${port}:\${port}\`,
532
+ '-v',
533
+ \`\${containerName}-data:/home/dynamodblocal/data\`,
534
+ '-d',
535
+ image,
536
+ '-jar',
537
+ 'DynamoDBLocal.jar',
538
+ '-sharedDb',
539
+ '-dbPath',
540
+ './data',
541
+ '-port',
542
+ \`\${port}\`,
543
+ '-optimizeDbBeforeStartup',
544
+ ];
545
+ const create = spawnSync(containerEngine, runArgs, { stdio: 'pipe' });
774
546
  if (create.status === 0) {
775
547
  created = true;
776
548
  break;
@@ -781,7 +553,7 @@ for (;;) {
781
553
  }
782
554
  }
783
555
 
784
- const logs = spawn(ENGINE, ['logs', '-f', containerName], {
556
+ const logs = spawn(containerEngine, ['logs', '-f', containerName], {
785
557
  stdio: ['ignore', 'inherit', 'inherit'],
786
558
  });
787
559
 
@@ -791,7 +563,13 @@ const cleanup = () => {
791
563
  if (exiting) return;
792
564
  exiting = true;
793
565
  if (created) {
794
- spawnSync(ENGINE, ['stop', containerName], { stdio: 'ignore' });
566
+ if (containerEngine === 'docker') {
567
+ spawnSync(containerEngine, ['stop', containerName], { stdio: 'ignore' });
568
+ } else {
569
+ spawnSync(containerEngine, ['rm', '-f', containerName], {
570
+ stdio: 'ignore',
571
+ });
572
+ }
795
573
  }
796
574
  logs.kill();
797
575
  process.exit(0);
@@ -810,29 +588,34 @@ logs.on('exit', (code) => {
810
588
  exports[`ts#dynamodb generator > should generate the dynamodb project > packages/my-table/src/client.ts 1`] = `
811
589
  "import { DynamoDBClient } from '@aws-sdk/client-dynamodb';
812
590
  import { getAppConfig } from '@aws-lambda-powertools/parameters/appconfig';
813
- import {
814
- PACKAGE_NAME,
815
- LOCAL_ACCESS_KEY_ID,
816
- LOCAL_ENDPOINT,
817
- LOCAL_REGION,
818
- LOCAL_SECRET_ACCESS_KEY,
819
- LOCAL_TABLE_NAME,
820
- } from './constants.js';
591
+ import { readFileSync } from 'fs';
592
+ import { join, dirname } from 'path';
593
+ import { fileURLToPath } from 'url';
594
+
595
+ // Generated: must match runtimeConfigKey in config.json
596
+ const runtimeConfigKey = 'MyTable';
597
+
598
+ const __dirname = dirname(fileURLToPath(import.meta.url));
599
+ // config.json is only present in local dev (not included in the deployed bundle); guard with SERVE_LOCAL check
600
+ const serveLocal = () =>
601
+ JSON.parse(readFileSync(join(__dirname, '../config.json'), 'utf-8'))
602
+ .serveLocal;
821
603
 
822
604
  let _client: DynamoDBClient | undefined;
823
605
 
824
606
  export const getDynamoDBClient = (): DynamoDBClient => {
825
- _client ??=
826
- process.env.SERVE_LOCAL === 'true'
827
- ? new DynamoDBClient({
828
- endpoint: LOCAL_ENDPOINT,
829
- region: LOCAL_REGION,
830
- credentials: {
831
- accessKeyId: LOCAL_ACCESS_KEY_ID,
832
- secretAccessKey: LOCAL_SECRET_ACCESS_KEY,
833
- },
834
- })
835
- : new DynamoDBClient({});
607
+ if (!_client) {
608
+ if (process.env.SERVE_LOCAL === 'true') {
609
+ const { port } = serveLocal();
610
+ _client = new DynamoDBClient({
611
+ endpoint: \`http://localhost:\${port}\`,
612
+ region: 'us-east-1',
613
+ credentials: { accessKeyId: 'UNUSED', secretAccessKey: 'UNUSED' },
614
+ });
615
+ } else {
616
+ _client = new DynamoDBClient({});
617
+ }
618
+ }
836
619
  return _client;
837
620
  };
838
621
 
@@ -844,7 +627,8 @@ let _tableName: string | undefined;
844
627
 
845
628
  export const resolveTableName = async (): Promise<string> => {
846
629
  if (process.env.SERVE_LOCAL === 'true') {
847
- return LOCAL_TABLE_NAME;
630
+ const { tableName } = serveLocal();
631
+ return tableName;
848
632
  }
849
633
  if (_tableName === undefined) {
850
634
  const appId = process.env.RUNTIME_CONFIG_APP_ID;
@@ -858,7 +642,7 @@ export const resolveTableName = async (): Promise<string> => {
858
642
  environment: 'default',
859
643
  transform: 'json',
860
644
  });
861
- const tableName = config?.[PACKAGE_NAME]?.tableName;
645
+ const tableName = config?.[runtimeConfigKey]?.tableName;
862
646
  if (!tableName) {
863
647
  throw new Error('Could not resolve table name from runtime config');
864
648
  }
@@ -869,19 +653,6 @@ export const resolveTableName = async (): Promise<string> => {
869
653
  "
870
654
  `;
871
655
 
872
- exports[`ts#dynamodb generator > should generate the dynamodb project > packages/my-table/src/constants.ts 1`] = `
873
- "export const PACKAGE_NAME = 'MyTable';
874
-
875
- // Local development connection details (used when SERVE_LOCAL=true, see serve-local Nx target)
876
- export const LOCAL_PORT = 8000;
877
- export const LOCAL_REGION = 'us-east-1';
878
- export const LOCAL_ACCESS_KEY_ID = 'accessKeyId';
879
- export const LOCAL_SECRET_ACCESS_KEY = 'secretAccessKey';
880
- export const LOCAL_ENDPOINT = \`http://localhost:\${LOCAL_PORT}\`;
881
- export const LOCAL_TABLE_NAME = 'proj-my-table';
882
- "
883
- `;
884
-
885
656
  exports[`ts#dynamodb generator > should generate the dynamodb project > packages/my-table/src/entities/example.ts 1`] = `
886
657
  "import { Entity } from 'electrodb';
887
658
  import { getDynamoDBClient, resolveTableName } from '../client.js';
@@ -939,15 +710,7 @@ exports[`ts#dynamodb generator > should generate the dynamodb project > packages
939
710
  `;
940
711
 
941
712
  exports[`ts#dynamodb generator > should generate the dynamodb project > packages/my-table/src/index.ts 1`] = `
942
- "export {
943
- PACKAGE_NAME,
944
- LOCAL_ACCESS_KEY_ID,
945
- LOCAL_ENDPOINT,
946
- LOCAL_REGION,
947
- LOCAL_SECRET_ACCESS_KEY,
948
- LOCAL_TABLE_NAME,
949
- } from './constants.js';
950
- export { getDynamoDBClient, resolveTableName } from './client.js';
713
+ "export { getDynamoDBClient, resolveTableName } from './client.js';
951
714
  export * from './entities/index.js';
952
715
  "
953
716
  `;
@@ -1066,7 +829,6 @@ export abstract class DynamoDBTable extends Construct {
1066
829
 
1067
830
  exports[`ts#dynamodb generator > should generate the dynamodb project 2`] = `
1068
831
  "import { Construct } from 'constructs';
1069
- import { PACKAGE_NAME } from ':proj/my-table';
1070
832
  import { AttributeType, ProjectionType } from 'aws-cdk-lib/aws-dynamodb';
1071
833
  import { DynamoDBTable, DynamoDBTableProps } from '../../core/dynamodb.js';
1072
834
  import { findWorkspaceRoot } from '../../core/workspace.js';
@@ -1074,32 +836,35 @@ import { readFileSync } from 'fs';
1074
836
  import { join } from 'path';
1075
837
  import { fileURLToPath } from 'url';
1076
838
 
839
+ const { runtimeConfigKey, tableConfig } = JSON.parse(
840
+ readFileSync(
841
+ join(
842
+ findWorkspaceRoot(fileURLToPath(new URL(import.meta.url))),
843
+ 'packages/my-table/config.json',
844
+ ),
845
+ 'utf-8',
846
+ ),
847
+ ) as {
848
+ runtimeConfigKey: string;
849
+ tableConfig: {
850
+ globalSecondaryIndexes: {
851
+ indexName: string;
852
+ partitionKey: string;
853
+ sortKey?: string;
854
+ }[];
855
+ };
856
+ };
857
+
1077
858
  export type MyTableProps = Omit<DynamoDBTableProps, 'runtimeConfigKey'>;
1078
859
 
1079
860
  export class MyTable extends DynamoDBTable {
1080
861
  constructor(scope: Construct, id: string, props?: MyTableProps) {
1081
862
  super(scope, id, {
1082
863
  ...props,
1083
- runtimeConfigKey: PACKAGE_NAME,
864
+ runtimeConfigKey,
1084
865
  });
1085
866
 
1086
- const { globalSecondaryIndexes } = JSON.parse(
1087
- readFileSync(
1088
- join(
1089
- findWorkspaceRoot(fileURLToPath(new URL(import.meta.url))),
1090
- 'packages/my-table/dynamodb.config.json',
1091
- ),
1092
- 'utf-8',
1093
- ),
1094
- ) as {
1095
- globalSecondaryIndexes: {
1096
- indexName: string;
1097
- partitionKey: string;
1098
- sortKey?: string;
1099
- }[];
1100
- };
1101
-
1102
- for (const gsi of globalSecondaryIndexes) {
867
+ for (const gsi of tableConfig.globalSecondaryIndexes) {
1103
868
  this.table.addGlobalSecondaryIndex({
1104
869
  indexName: gsi.indexName,
1105
870
  partitionKey: { name: gsi.partitionKey, type: AttributeType.STRING },
@@ -1124,43 +889,43 @@ exports[`ts#dynamodb generator > should generate the dynamodb project 4`] = `
1124
889
  "
1125
890
  `;
1126
891
 
1127
- exports[`ts#dynamodb generator > should generate with infra=none then upgrade to infra=dynamodb > packages/my-table/scripts/create-local-table.ts 1`] = `
892
+ exports[`ts#dynamodb generator > should generate with infra=none then upgrade to infra=dynamodb > packages/common/scripts/src/dynamodb/create-local-table.ts 1`] = `
1128
893
  "import {
894
+ BillingMode,
1129
895
  CreateTableCommand,
1130
896
  DescribeTableCommand,
1131
897
  DynamoDBClient,
1132
898
  GlobalSecondaryIndexDescription,
899
+ IndexStatus,
900
+ KeyType,
901
+ ProjectionType,
1133
902
  ResourceInUseException,
903
+ ScalarAttributeType,
904
+ TableStatus,
1134
905
  UpdateTableCommand,
1135
906
  } from '@aws-sdk/client-dynamodb';
1136
907
  import { readFileSync } from 'fs';
1137
- import { join, dirname } from 'path';
1138
- import { fileURLToPath } from 'url';
1139
908
 
1140
- const __dirname = dirname(fileURLToPath(import.meta.url));
1141
- const { globalSecondaryIndexes } = JSON.parse(
1142
- readFileSync(join(__dirname, '../dynamodb.config.json'), 'utf-8'),
1143
- ) as {
1144
- globalSecondaryIndexes: {
1145
- indexName: string;
1146
- partitionKey: string;
1147
- sortKey?: string;
1148
- }[];
909
+ const {
910
+ serveLocal: { port, tableName },
911
+ tableConfig: { globalSecondaryIndexes },
912
+ } = JSON.parse(readFileSync('config.json', 'utf-8')) as {
913
+ serveLocal: { port: number; tableName: string };
914
+ tableConfig: {
915
+ globalSecondaryIndexes: {
916
+ indexName: string;
917
+ partitionKey: string;
918
+ sortKey?: string;
919
+ }[];
920
+ };
1149
921
  };
1150
922
 
1151
- const [tableName, port] = process.argv.slice(2);
1152
- const endpoint = \`http://localhost:\${port}\`;
1153
-
1154
- const REQUEST_TIMEOUT_MS = 10_000;
1155
- const timeoutSignal = () => AbortSignal.timeout(REQUEST_TIMEOUT_MS);
923
+ const timeoutSignal = () => AbortSignal.timeout(10000);
1156
924
 
1157
925
  const client = new DynamoDBClient({
1158
- endpoint,
926
+ endpoint: \`http://localhost:\${port}\`,
1159
927
  region: 'us-east-1',
1160
- credentials: {
1161
- accessKeyId: 'accessKeyId',
1162
- secretAccessKey: 'secretAccessKey',
1163
- },
928
+ credentials: { accessKeyId: 'UNUSED', secretAccessKey: 'UNUSED' },
1164
929
  });
1165
930
 
1166
931
  const waitForTableActive = async (
@@ -1173,9 +938,10 @@ const waitForTableActive = async (
1173
938
  { abortSignal: timeoutSignal() },
1174
939
  );
1175
940
  const allActive =
1176
- Table?.TableStatus === 'ACTIVE' &&
941
+ Table?.TableStatus === TableStatus.ACTIVE &&
1177
942
  (Table?.GlobalSecondaryIndexes ?? []).every(
1178
- (g: GlobalSecondaryIndexDescription) => g.IndexStatus === 'ACTIVE',
943
+ (g: GlobalSecondaryIndexDescription) =>
944
+ g.IndexStatus === IndexStatus.ACTIVE,
1179
945
  );
1180
946
  if (allActive) return;
1181
947
  await new Promise((resolve) => setTimeout(resolve, 500));
@@ -1188,14 +954,14 @@ const ensureTable = async (
1188
954
  gsIs: { indexName: string; partitionKey: string; sortKey?: string }[],
1189
955
  ): Promise<void> => {
1190
956
  const gsiAttributeDefinitions = gsIs.flatMap((gsi) => [
1191
- { AttributeName: gsi.partitionKey, AttributeType: 'S' as const },
957
+ { AttributeName: gsi.partitionKey, AttributeType: ScalarAttributeType.S },
1192
958
  ...(gsi.sortKey
1193
- ? [{ AttributeName: gsi.sortKey, AttributeType: 'S' as const }]
959
+ ? [{ AttributeName: gsi.sortKey, AttributeType: ScalarAttributeType.S }]
1194
960
  : []),
1195
961
  ]);
1196
962
  const attributeDefinitions = [
1197
- { AttributeName: 'pk', AttributeType: 'S' as const },
1198
- { AttributeName: 'sk', AttributeType: 'S' as const },
963
+ { AttributeName: 'pk', AttributeType: ScalarAttributeType.S },
964
+ { AttributeName: 'sk', AttributeType: ScalarAttributeType.S },
1199
965
  ...gsiAttributeDefinitions.filter(
1200
966
  (a, i, arr) =>
1201
967
  arr.findIndex((b) => b.AttributeName === a.AttributeName) === i,
@@ -1204,12 +970,12 @@ const ensureTable = async (
1204
970
  const globalSecondaryIndexes = gsIs.map((gsi) => ({
1205
971
  IndexName: gsi.indexName,
1206
972
  KeySchema: [
1207
- { AttributeName: gsi.partitionKey, KeyType: 'HASH' as const },
973
+ { AttributeName: gsi.partitionKey, KeyType: KeyType.HASH },
1208
974
  ...(gsi.sortKey
1209
- ? [{ AttributeName: gsi.sortKey, KeyType: 'RANGE' as const }]
975
+ ? [{ AttributeName: gsi.sortKey, KeyType: KeyType.RANGE }]
1210
976
  : []),
1211
977
  ],
1212
- Projection: { ProjectionType: 'ALL' as const },
978
+ Projection: { ProjectionType: ProjectionType.ALL },
1213
979
  }));
1214
980
 
1215
981
  for (let attempt = 0; attempt < 30; attempt++) {
@@ -1220,10 +986,10 @@ const ensureTable = async (
1220
986
  TableName: tblName,
1221
987
  AttributeDefinitions: attributeDefinitions,
1222
988
  KeySchema: [
1223
- { AttributeName: 'pk', KeyType: 'HASH' },
1224
- { AttributeName: 'sk', KeyType: 'RANGE' },
989
+ { AttributeName: 'pk', KeyType: KeyType.HASH },
990
+ { AttributeName: 'sk', KeyType: KeyType.RANGE },
1225
991
  ],
1226
- BillingMode: 'PAY_PER_REQUEST',
992
+ BillingMode: BillingMode.PAY_PER_REQUEST,
1227
993
  ...(globalSecondaryIndexes.length > 0 && {
1228
994
  GlobalSecondaryIndexes: globalSecondaryIndexes,
1229
995
  }),
@@ -1273,12 +1039,15 @@ const ensureTable = async (
1273
1039
  new UpdateTableCommand({
1274
1040
  TableName: tblName,
1275
1041
  AttributeDefinitions: [
1276
- { AttributeName: gsi.partitionKey, AttributeType: 'S' },
1042
+ {
1043
+ AttributeName: gsi.partitionKey,
1044
+ AttributeType: ScalarAttributeType.S,
1045
+ },
1277
1046
  ...(gsi.sortKey
1278
1047
  ? [
1279
1048
  {
1280
1049
  AttributeName: gsi.sortKey,
1281
- AttributeType: 'S' as const,
1050
+ AttributeType: ScalarAttributeType.S,
1282
1051
  },
1283
1052
  ]
1284
1053
  : []),
@@ -1288,12 +1057,20 @@ const ensureTable = async (
1288
1057
  Create: {
1289
1058
  IndexName: gsi.indexName,
1290
1059
  KeySchema: [
1291
- { AttributeName: gsi.partitionKey, KeyType: 'HASH' },
1060
+ {
1061
+ AttributeName: gsi.partitionKey,
1062
+ KeyType: KeyType.HASH,
1063
+ },
1292
1064
  ...(gsi.sortKey
1293
- ? [{ AttributeName: gsi.sortKey, KeyType: 'RANGE' }]
1065
+ ? [
1066
+ {
1067
+ AttributeName: gsi.sortKey,
1068
+ KeyType: KeyType.RANGE,
1069
+ },
1070
+ ]
1294
1071
  : []),
1295
1072
  ],
1296
- Projection: { ProjectionType: 'ALL' },
1073
+ Projection: { ProjectionType: ProjectionType.ALL },
1297
1074
  },
1298
1075
  },
1299
1076
  ],
@@ -1315,18 +1092,21 @@ await ensureTable(client, tableName, globalSecondaryIndexes);
1315
1092
  "
1316
1093
  `;
1317
1094
 
1318
- exports[`ts#dynamodb generator > should generate with infra=none then upgrade to infra=dynamodb > packages/my-table/scripts/pull-image.ts 1`] = `
1095
+ exports[`ts#dynamodb generator > should generate with infra=none then upgrade to infra=dynamodb > packages/common/scripts/src/dynamodb/pull-image.ts 1`] = `
1319
1096
  "import { spawnSync } from 'child_process';
1097
+ import { readFileSync } from 'fs';
1320
1098
 
1321
- const ENGINE = 'docker';
1322
- const image = process.argv[2];
1099
+ const { containerEngine, image } = JSON.parse(
1100
+ readFileSync('config.json', 'utf-8'),
1101
+ ).serveLocal;
1323
1102
 
1324
- const inspect = spawnSync(ENGINE, ['image', 'inspect', image], {
1103
+ const inspect = spawnSync(containerEngine, ['image', 'inspect', image], {
1325
1104
  stdio: 'ignore',
1326
1105
  });
1327
-
1328
1106
  if (inspect.status !== 0) {
1329
- const pull = spawnSync(ENGINE, ['pull', image], { stdio: 'inherit' });
1107
+ const pull = spawnSync(containerEngine, ['pull', image], {
1108
+ stdio: 'inherit',
1109
+ });
1330
1110
  if (pull.status !== 0) {
1331
1111
  process.exit(pull.status ?? 1);
1332
1112
  }
@@ -1334,21 +1114,22 @@ if (inspect.status !== 0) {
1334
1114
  "
1335
1115
  `;
1336
1116
 
1337
- exports[`ts#dynamodb generator > should generate with infra=none then upgrade to infra=dynamodb > packages/my-table/scripts/start-container.ts 1`] = `
1117
+ exports[`ts#dynamodb generator > should generate with infra=none then upgrade to infra=dynamodb > packages/common/scripts/src/dynamodb/start-container.ts 1`] = `
1338
1118
  "import { spawn, spawnSync } from 'child_process';
1119
+ import { readFileSync } from 'fs';
1339
1120
 
1340
- const ENGINE = 'docker';
1341
-
1342
- const [containerName, image, hostPort] = process.argv.slice(2);
1121
+ const { containerEngine, containerName, image, port } = JSON.parse(
1122
+ readFileSync('config.json', 'utf-8'),
1123
+ ).serveLocal;
1343
1124
 
1344
1125
  const containerExists = (): boolean =>
1345
- spawnSync(ENGINE, ['container', 'inspect', containerName], {
1126
+ spawnSync(containerEngine, ['container', 'inspect', containerName], {
1346
1127
  stdio: 'ignore',
1347
1128
  }).status === 0;
1348
1129
 
1349
1130
  const isRunning = (): boolean => {
1350
1131
  const result = spawnSync(
1351
- ENGINE,
1132
+ containerEngine,
1352
1133
  ['container', 'inspect', '-f', '{{.State.Running}}', containerName],
1353
1134
  { encoding: 'utf-8' },
1354
1135
  );
@@ -1366,19 +1147,38 @@ for (;;) {
1366
1147
  if (!isRunning()) {
1367
1148
  // Another process may have started it between our inspect and start — container engine start
1368
1149
  // exits 0 even if the container is already running, so no special-casing needed.
1369
- const start = spawnSync(ENGINE, ['start', containerName], {
1150
+ const start = spawnSync(containerEngine, ['start', containerName], {
1370
1151
  stdio: 'inherit',
1371
1152
  });
1372
1153
  if (start.status !== 0) process.exit(start.status ?? 1);
1373
1154
  }
1374
1155
  break;
1375
1156
  }
1376
- const runArgs = (
1377
- \`run --rm --name \${containerName} -u root -w /home/dynamodblocal\` +
1378
- \` -p \${hostPort}:\${hostPort} -v \${containerName}-data:/home/dynamodblocal/data\` +
1379
- \` -d \${image} -jar DynamoDBLocal.jar -sharedDb -dbPath ./data -port \${hostPort} -optimizeDbBeforeStartup\`
1380
- ).split(' ');
1381
- const create = spawnSync(ENGINE, runArgs, { stdio: 'pipe' });
1157
+ const runArgs = [
1158
+ 'run',
1159
+ ...(containerEngine === 'docker' ? ['--rm'] : []),
1160
+ '--name',
1161
+ containerName,
1162
+ '-u',
1163
+ 'root',
1164
+ '-w',
1165
+ '/home/dynamodblocal',
1166
+ \`-p\`,
1167
+ \`\${port}:\${port}\`,
1168
+ '-v',
1169
+ \`\${containerName}-data:/home/dynamodblocal/data\`,
1170
+ '-d',
1171
+ image,
1172
+ '-jar',
1173
+ 'DynamoDBLocal.jar',
1174
+ '-sharedDb',
1175
+ '-dbPath',
1176
+ './data',
1177
+ '-port',
1178
+ \`\${port}\`,
1179
+ '-optimizeDbBeforeStartup',
1180
+ ];
1181
+ const create = spawnSync(containerEngine, runArgs, { stdio: 'pipe' });
1382
1182
  if (create.status === 0) {
1383
1183
  created = true;
1384
1184
  break;
@@ -1389,7 +1189,7 @@ for (;;) {
1389
1189
  }
1390
1190
  }
1391
1191
 
1392
- const logs = spawn(ENGINE, ['logs', '-f', containerName], {
1192
+ const logs = spawn(containerEngine, ['logs', '-f', containerName], {
1393
1193
  stdio: ['ignore', 'inherit', 'inherit'],
1394
1194
  });
1395
1195
 
@@ -1399,7 +1199,13 @@ const cleanup = () => {
1399
1199
  if (exiting) return;
1400
1200
  exiting = true;
1401
1201
  if (created) {
1402
- spawnSync(ENGINE, ['stop', containerName], { stdio: 'ignore' });
1202
+ if (containerEngine === 'docker') {
1203
+ spawnSync(containerEngine, ['stop', containerName], { stdio: 'ignore' });
1204
+ } else {
1205
+ spawnSync(containerEngine, ['rm', '-f', containerName], {
1206
+ stdio: 'ignore',
1207
+ });
1208
+ }
1403
1209
  }
1404
1210
  logs.kill();
1405
1211
  process.exit(0);
@@ -1418,29 +1224,34 @@ logs.on('exit', (code) => {
1418
1224
  exports[`ts#dynamodb generator > should generate with infra=none then upgrade to infra=dynamodb > packages/my-table/src/client.ts 1`] = `
1419
1225
  "import { DynamoDBClient } from '@aws-sdk/client-dynamodb';
1420
1226
  import { getAppConfig } from '@aws-lambda-powertools/parameters/appconfig';
1421
- import {
1422
- PACKAGE_NAME,
1423
- LOCAL_ACCESS_KEY_ID,
1424
- LOCAL_ENDPOINT,
1425
- LOCAL_REGION,
1426
- LOCAL_SECRET_ACCESS_KEY,
1427
- LOCAL_TABLE_NAME,
1428
- } from './constants.js';
1227
+ import { readFileSync } from 'fs';
1228
+ import { join, dirname } from 'path';
1229
+ import { fileURLToPath } from 'url';
1230
+
1231
+ // Generated: must match runtimeConfigKey in config.json
1232
+ const runtimeConfigKey = 'MyTable';
1233
+
1234
+ const __dirname = dirname(fileURLToPath(import.meta.url));
1235
+ // config.json is only present in local dev (not included in the deployed bundle); guard with SERVE_LOCAL check
1236
+ const serveLocal = () =>
1237
+ JSON.parse(readFileSync(join(__dirname, '../config.json'), 'utf-8'))
1238
+ .serveLocal;
1429
1239
 
1430
1240
  let _client: DynamoDBClient | undefined;
1431
1241
 
1432
1242
  export const getDynamoDBClient = (): DynamoDBClient => {
1433
- _client ??=
1434
- process.env.SERVE_LOCAL === 'true'
1435
- ? new DynamoDBClient({
1436
- endpoint: LOCAL_ENDPOINT,
1437
- region: LOCAL_REGION,
1438
- credentials: {
1439
- accessKeyId: LOCAL_ACCESS_KEY_ID,
1440
- secretAccessKey: LOCAL_SECRET_ACCESS_KEY,
1441
- },
1442
- })
1443
- : new DynamoDBClient({});
1243
+ if (!_client) {
1244
+ if (process.env.SERVE_LOCAL === 'true') {
1245
+ const { port } = serveLocal();
1246
+ _client = new DynamoDBClient({
1247
+ endpoint: \`http://localhost:\${port}\`,
1248
+ region: 'us-east-1',
1249
+ credentials: { accessKeyId: 'UNUSED', secretAccessKey: 'UNUSED' },
1250
+ });
1251
+ } else {
1252
+ _client = new DynamoDBClient({});
1253
+ }
1254
+ }
1444
1255
  return _client;
1445
1256
  };
1446
1257
 
@@ -1452,7 +1263,8 @@ let _tableName: string | undefined;
1452
1263
 
1453
1264
  export const resolveTableName = async (): Promise<string> => {
1454
1265
  if (process.env.SERVE_LOCAL === 'true') {
1455
- return LOCAL_TABLE_NAME;
1266
+ const { tableName } = serveLocal();
1267
+ return tableName;
1456
1268
  }
1457
1269
  if (_tableName === undefined) {
1458
1270
  const appId = process.env.RUNTIME_CONFIG_APP_ID;
@@ -1466,7 +1278,7 @@ export const resolveTableName = async (): Promise<string> => {
1466
1278
  environment: 'default',
1467
1279
  transform: 'json',
1468
1280
  });
1469
- const tableName = config?.[PACKAGE_NAME]?.tableName;
1281
+ const tableName = config?.[runtimeConfigKey]?.tableName;
1470
1282
  if (!tableName) {
1471
1283
  throw new Error('Could not resolve table name from runtime config');
1472
1284
  }
@@ -1477,19 +1289,6 @@ export const resolveTableName = async (): Promise<string> => {
1477
1289
  "
1478
1290
  `;
1479
1291
 
1480
- exports[`ts#dynamodb generator > should generate with infra=none then upgrade to infra=dynamodb > packages/my-table/src/constants.ts 1`] = `
1481
- "export const PACKAGE_NAME = 'MyTable';
1482
-
1483
- // Local development connection details (used when SERVE_LOCAL=true, see serve-local Nx target)
1484
- export const LOCAL_PORT = 8000;
1485
- export const LOCAL_REGION = 'us-east-1';
1486
- export const LOCAL_ACCESS_KEY_ID = 'accessKeyId';
1487
- export const LOCAL_SECRET_ACCESS_KEY = 'secretAccessKey';
1488
- export const LOCAL_ENDPOINT = \`http://localhost:\${LOCAL_PORT}\`;
1489
- export const LOCAL_TABLE_NAME = 'proj-my-table';
1490
- "
1491
- `;
1492
-
1493
1292
  exports[`ts#dynamodb generator > should generate with infra=none then upgrade to infra=dynamodb > packages/my-table/src/entities/example.ts 1`] = `
1494
1293
  "import { Entity } from 'electrodb';
1495
1294
  import { getDynamoDBClient, resolveTableName } from '../client.js';
@@ -1547,15 +1346,7 @@ exports[`ts#dynamodb generator > should generate with infra=none then upgrade to
1547
1346
  `;
1548
1347
 
1549
1348
  exports[`ts#dynamodb generator > should generate with infra=none then upgrade to infra=dynamodb > packages/my-table/src/index.ts 1`] = `
1550
- "export {
1551
- PACKAGE_NAME,
1552
- LOCAL_ACCESS_KEY_ID,
1553
- LOCAL_ENDPOINT,
1554
- LOCAL_REGION,
1555
- LOCAL_SECRET_ACCESS_KEY,
1556
- LOCAL_TABLE_NAME,
1557
- } from './constants.js';
1558
- export { getDynamoDBClient, resolveTableName } from './client.js';
1349
+ "export { getDynamoDBClient, resolveTableName } from './client.js';
1559
1350
  export * from './entities/index.js';
1560
1351
  "
1561
1352
  `;