@aws/nx-plugin 1.0.0-rc.1 → 1.0.0-rc.11

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 (468) hide show
  1. package/LICENSE-THIRD-PARTY +3399 -4104
  2. package/README.md +8 -8
  3. package/executors.json +9 -0
  4. package/generators.json +44 -5
  5. package/package.json +16 -12
  6. package/sdk/license.d.ts +8 -0
  7. package/sdk/license.js +13 -0
  8. package/sdk/license.js.map +1 -0
  9. package/src/connection/agent-runtime-config.d.ts +1 -1
  10. package/src/connection/agent-runtime-config.js.map +1 -1
  11. package/src/connection/agent-serve-local.d.ts +2 -2
  12. package/src/connection/agent-serve-local.js +1 -1
  13. package/src/connection/agent-serve-local.js.map +1 -1
  14. package/src/connection/generator.d.ts +5 -5
  15. package/src/connection/generator.js +56 -37
  16. package/src/connection/generator.js.map +1 -1
  17. package/src/connection/serve-local.d.ts +1 -1
  18. package/src/connection/serve-local.js +1 -1
  19. package/src/connection/serve-local.js.map +1 -1
  20. package/src/infra/app/__snapshots__/generator.spec.ts.snap +38 -49
  21. package/src/infra/app/generator.d.ts +3 -3
  22. package/src/infra/app/generator.js +16 -14
  23. package/src/infra/app/generator.js.map +1 -1
  24. package/src/license/__snapshots__/config.spec.ts.snap +23 -0
  25. package/src/license/config-types.d.ts +18 -1
  26. package/src/license/config-types.js +4 -0
  27. package/src/license/config-types.js.map +1 -1
  28. package/src/license/config.d.ts +65 -7
  29. package/src/license/config.js +315 -7
  30. package/src/license/config.js.map +1 -1
  31. package/src/license/dependency-check/check.d.ts +16 -0
  32. package/src/license/dependency-check/check.js +111 -0
  33. package/src/license/dependency-check/check.js.map +1 -0
  34. package/src/license/dependency-check/collectors/collector.d.ts +21 -0
  35. package/src/license/dependency-check/collectors/collector.js +76 -0
  36. package/src/license/dependency-check/collectors/collector.js.map +1 -0
  37. package/src/license/dependency-check/collectors/npm-collector.d.ts +10 -0
  38. package/src/license/dependency-check/collectors/npm-collector.js +75 -0
  39. package/src/license/dependency-check/collectors/npm-collector.js.map +1 -0
  40. package/src/license/dependency-check/collectors/python-collector.d.ts +23 -0
  41. package/src/license/dependency-check/collectors/python-collector.js +121 -0
  42. package/src/license/dependency-check/collectors/python-collector.js.map +1 -0
  43. package/src/license/dependency-check/evaluator.d.ts +9 -0
  44. package/src/license/dependency-check/evaluator.js +122 -0
  45. package/src/license/dependency-check/evaluator.js.map +1 -0
  46. package/src/license/dependency-check/executor-schema.json +6 -0
  47. package/src/license/dependency-check/executor.d.ts +9 -0
  48. package/src/license/dependency-check/executor.js +43 -0
  49. package/src/license/dependency-check/executor.js.map +1 -0
  50. package/src/license/dependency-check/pre-approved.d.ts +6 -0
  51. package/src/license/dependency-check/pre-approved.js +1280 -0
  52. package/src/license/dependency-check/pre-approved.js.map +1 -0
  53. package/src/license/dependency-check/types.d.ts +45 -0
  54. package/src/license/dependency-check/types.js +7 -0
  55. package/src/license/dependency-check/types.js.map +1 -0
  56. package/src/license/generator.d.ts +3 -3
  57. package/src/license/generator.js +113 -14
  58. package/src/license/generator.js.map +1 -1
  59. package/src/license/index.d.ts +8 -0
  60. package/src/license/index.js +13 -0
  61. package/src/license/index.js.map +1 -0
  62. package/src/license/known-exceptions.d.ts +7 -0
  63. package/src/license/known-exceptions.js +33 -0
  64. package/src/license/known-exceptions.js.map +1 -0
  65. package/src/license/schema.d.ts +5 -0
  66. package/src/license/schema.json +6 -0
  67. package/src/license/sync/generator.d.ts +2 -2
  68. package/src/license/sync/generator.js +15 -15
  69. package/src/license/sync/generator.js.map +1 -1
  70. package/src/license/sync/project-file-sync.d.ts +3 -3
  71. package/src/license/sync/project-file-sync.js.map +1 -1
  72. package/src/mcp-server/generator-info.d.ts +5 -1
  73. package/src/mcp-server/generator-info.js +7 -7
  74. package/src/mcp-server/generator-info.js.map +1 -1
  75. package/src/mcp-server/guide-pipeline.d.ts +1 -1
  76. package/src/mcp-server/guide-pipeline.js +2 -2
  77. package/src/mcp-server/guide-pipeline.js.map +1 -1
  78. package/src/mcp-server/mdx-ast.js +4 -0
  79. package/src/mcp-server/mdx-ast.js.map +1 -1
  80. package/src/mcp-server/server.js +3 -3
  81. package/src/mcp-server/server.js.map +1 -1
  82. package/src/mcp-server/tools/create-workspace-command.d.ts +1 -1
  83. package/src/mcp-server/tools/create-workspace-command.js +4 -4
  84. package/src/mcp-server/tools/create-workspace-command.js.map +1 -1
  85. package/src/mcp-server/tools/general-guidance.d.ts +2 -2
  86. package/src/mcp-server/tools/general-guidance.js +2 -2
  87. package/src/mcp-server/tools/general-guidance.js.map +1 -1
  88. package/src/mcp-server/tools/generator-guide.d.ts +2 -2
  89. package/src/mcp-server/tools/generator-guide.js.map +1 -1
  90. package/src/mcp-server/tools/list-generators.d.ts +2 -2
  91. package/src/mcp-server/tools/list-generators.js +1 -1
  92. package/src/mcp-server/tools/list-generators.js.map +1 -1
  93. package/src/open-api/ts-client/__snapshots__/generator.reserved-keywords.spec.ts.snap +1 -1
  94. package/src/open-api/ts-client/generator.d.ts +3 -3
  95. package/src/open-api/ts-client/generator.js +2 -2
  96. package/src/open-api/ts-client/generator.js.map +1 -1
  97. package/src/open-api/ts-hooks/generator.d.ts +3 -3
  98. package/src/open-api/ts-hooks/generator.js +2 -2
  99. package/src/open-api/ts-hooks/generator.js.map +1 -1
  100. package/src/open-api/ts-hooks/generator.spec.tsx +14 -14
  101. package/src/open-api/ts-metadata/generator.d.ts +3 -3
  102. package/src/open-api/ts-metadata/generator.js +1 -1
  103. package/src/open-api/ts-metadata/generator.js.map +1 -1
  104. package/src/open-api/utils/codegen-data/languages.d.ts +5 -1
  105. package/src/open-api/utils/codegen-data/languages.js +4 -4
  106. package/src/open-api/utils/codegen-data/languages.js.map +1 -1
  107. package/src/open-api/utils/codegen-data.d.ts +6 -2
  108. package/src/open-api/utils/codegen-data.js +6 -6
  109. package/src/open-api/utils/codegen-data.js.map +1 -1
  110. package/src/open-api/utils/normalise.d.ts +5 -1
  111. package/src/open-api/utils/normalise.js +5 -5
  112. package/src/open-api/utils/normalise.js.map +1 -1
  113. package/src/open-api/utils/parse.d.ts +2 -2
  114. package/src/open-api/utils/parse.js +4 -0
  115. package/src/open-api/utils/parse.js.map +1 -1
  116. package/src/open-api/utils/refs.d.ts +1 -1
  117. package/src/preset/__snapshots__/generator.spec.ts.snap +127 -13
  118. package/src/preset/generator.d.ts +4 -4
  119. package/src/preset/generator.js +21 -14
  120. package/src/preset/generator.js.map +1 -1
  121. package/src/preset/schema.d.ts +3 -1
  122. package/src/preset/schema.json +5 -0
  123. package/src/py/agent/__snapshots__/generator.spec.ts.snap +9 -8
  124. package/src/py/agent/a2a-connection/generator.d.ts +3 -3
  125. package/src/py/agent/a2a-connection/generator.js +4 -4
  126. package/src/py/agent/a2a-connection/generator.js.map +1 -1
  127. package/src/py/agent/files/common/agent.py.template +2 -0
  128. package/src/py/agent/files/http/init.py.template +2 -2
  129. package/src/py/agent/files/http/main.py.template +0 -1
  130. package/src/py/agent/generator.d.ts +3 -3
  131. package/src/py/agent/generator.js +22 -16
  132. package/src/py/agent/generator.js.map +1 -1
  133. package/src/py/agent/mcp-connection/__snapshots__/generator.spec.ts.snap +4 -0
  134. package/src/py/agent/mcp-connection/generator.d.ts +3 -3
  135. package/src/py/agent/mcp-connection/generator.js +4 -4
  136. package/src/py/agent/mcp-connection/generator.js.map +1 -1
  137. package/src/py/agent/react-connection/__snapshots__/generator.spec.ts.snap +7 -16
  138. package/src/py/agent/react-connection/generator.d.ts +3 -3
  139. package/src/py/agent/react-connection/generator.js +4 -4
  140. package/src/py/agent/react-connection/generator.js.map +1 -1
  141. package/src/py/agent/react-connection/serve-local.d.ts +2 -2
  142. package/src/py/agent/react-connection/serve-local.js.map +1 -1
  143. package/src/py/api/generator.d.ts +3 -3
  144. package/src/py/api/generator.js.map +1 -1
  145. package/src/py/api/schema.d.ts +1 -1
  146. package/src/py/api/schema.json +2 -2
  147. package/src/py/fast-api/generator.d.ts +3 -3
  148. package/src/py/fast-api/generator.js +71 -53
  149. package/src/py/fast-api/generator.js.map +1 -1
  150. package/src/py/fast-api/react/generator.d.ts +3 -3
  151. package/src/py/fast-api/react/generator.js +2 -2
  152. package/src/py/fast-api/react/generator.js.map +1 -1
  153. package/src/py/fast-api/react/open-api.d.ts +1 -1
  154. package/src/py/fast-api/react/open-api.js +5 -2
  155. package/src/py/fast-api/react/open-api.js.map +1 -1
  156. package/src/py/fast-api/schema.d.ts +1 -1
  157. package/src/py/fast-api/schema.json +2 -2
  158. package/src/py/lambda-function/generator.d.ts +3 -3
  159. package/src/py/lambda-function/generator.js +30 -27
  160. package/src/py/lambda-function/generator.js.map +1 -1
  161. package/src/py/lambda-function/schema.d.ts +3 -0
  162. package/src/py/lambda-function/schema.json +8 -0
  163. package/src/py/mcp-server/__snapshots__/generator.spec.ts.snap +4 -4
  164. package/src/py/mcp-server/generator.d.ts +3 -3
  165. package/src/py/mcp-server/generator.js +33 -26
  166. package/src/py/mcp-server/generator.js.map +1 -1
  167. package/src/py/project/generator.d.ts +3 -3
  168. package/src/py/project/generator.js +88 -69
  169. package/src/py/project/generator.js.map +1 -1
  170. package/src/smithy/project/__snapshots__/generator.spec.ts.snap +18 -16
  171. package/src/smithy/project/files/build.Dockerfile.template +1 -0
  172. package/src/smithy/project/generator.d.ts +3 -3
  173. package/src/smithy/project/generator.js +30 -28
  174. package/src/smithy/project/generator.js.map +1 -1
  175. package/src/smithy/react-connection/generator.d.ts +3 -3
  176. package/src/smithy/react-connection/generator.js +3 -3
  177. package/src/smithy/react-connection/generator.js.map +1 -1
  178. package/src/smithy/ts/api/__snapshots__/generator.spec.ts.snap +8 -34
  179. package/src/smithy/ts/api/generator.d.ts +3 -3
  180. package/src/smithy/ts/api/generator.js +89 -66
  181. package/src/smithy/ts/api/generator.js.map +1 -1
  182. package/src/smithy/ts/api/schema.d.ts +1 -1
  183. package/src/smithy/ts/api/schema.json +1 -1
  184. package/src/terraform/project/generator.d.ts +3 -3
  185. package/src/terraform/project/generator.js +13 -8
  186. package/src/terraform/project/generator.js.map +1 -1
  187. package/src/trpc/backend/generator.d.ts +3 -3
  188. package/src/trpc/backend/generator.js +67 -48
  189. package/src/trpc/backend/generator.js.map +1 -1
  190. package/src/trpc/backend/schema.d.ts +3 -3
  191. package/src/trpc/backend/schema.json +2 -2
  192. package/src/trpc/react/generator.d.ts +3 -3
  193. package/src/trpc/react/generator.js +5 -5
  194. package/src/trpc/react/generator.js.map +1 -1
  195. package/src/ts/agent/__snapshots__/generator.spec.ts.snap +10 -4
  196. package/src/ts/agent/a2a-connection/generator.d.ts +3 -3
  197. package/src/ts/agent/a2a-connection/generator.js +5 -5
  198. package/src/ts/agent/a2a-connection/generator.js.map +1 -1
  199. package/src/ts/agent/files/common/agent.ts.template +6 -2
  200. package/src/ts/agent/generator.d.ts +3 -3
  201. package/src/ts/agent/generator.js +18 -13
  202. package/src/ts/agent/generator.js.map +1 -1
  203. package/src/ts/agent/mcp-connection/__snapshots__/generator.spec.ts.snap +5 -2
  204. package/src/ts/agent/mcp-connection/generator.d.ts +3 -3
  205. package/src/ts/agent/mcp-connection/generator.js +5 -5
  206. package/src/ts/agent/mcp-connection/generator.js.map +1 -1
  207. package/src/ts/agent/react-connection/generator.d.ts +3 -3
  208. package/src/ts/agent/react-connection/generator.js +9 -9
  209. package/src/ts/agent/react-connection/generator.js.map +1 -1
  210. package/src/ts/agent/react-connection/serve-local.d.ts +2 -2
  211. package/src/ts/api/generator.d.ts +3 -3
  212. package/src/ts/api/generator.js +7 -5
  213. package/src/ts/api/generator.js.map +1 -1
  214. package/src/ts/api/schema.d.ts +3 -2
  215. package/src/ts/api/schema.json +1 -1
  216. package/src/ts/astro-docs/__snapshots__/generator.spec.ts.snap +7 -8
  217. package/src/ts/astro-docs/generator.d.ts +3 -3
  218. package/src/ts/astro-docs/generator.js +14 -11
  219. package/src/ts/astro-docs/generator.js.map +1 -1
  220. package/src/ts/docs/generator.d.ts +3 -3
  221. package/src/ts/docs/generator.js.map +1 -1
  222. package/src/ts/dynamodb/__snapshots__/generator.spec.ts.snap +1561 -0
  223. package/src/ts/dynamodb/agent-connection/__snapshots__/generator.spec.ts.snap +45 -0
  224. package/src/ts/dynamodb/agent-connection/generator.d.ts +10 -0
  225. package/src/ts/dynamodb/agent-connection/generator.js +32 -0
  226. package/src/ts/dynamodb/agent-connection/generator.js.map +1 -0
  227. package/src/ts/dynamodb/agent-connection/schema.d.ts +14 -0
  228. package/src/ts/dynamodb/agent-connection/schema.json +22 -0
  229. package/src/ts/dynamodb/files/dynamodb.config.json +14 -0
  230. package/src/ts/dynamodb/files/scripts/create-local-table.ts.template +168 -0
  231. package/src/ts/{rdb/files/scripts/docker-pull.ts.template → dynamodb/files/scripts/pull-image.ts.template} +1 -1
  232. package/src/ts/dynamodb/files/scripts/start-container.ts.template +81 -0
  233. package/src/ts/dynamodb/files/src/client.ts.template +45 -0
  234. package/src/ts/dynamodb/files/src/constants.ts.template +9 -0
  235. package/src/ts/dynamodb/files/src/entities/example.ts.template +47 -0
  236. package/src/ts/dynamodb/files/src/entities/index.ts.template +1 -0
  237. package/src/ts/dynamodb/files/src/index.ts.template +10 -0
  238. package/src/ts/dynamodb/generator.d.ts +10 -0
  239. package/src/ts/dynamodb/generator.js +108 -0
  240. package/src/ts/dynamodb/generator.js.map +1 -0
  241. package/src/ts/dynamodb/mcp-server-connection/__snapshots__/generator.spec.ts.snap +45 -0
  242. package/src/ts/dynamodb/mcp-server-connection/generator.d.ts +10 -0
  243. package/src/ts/dynamodb/mcp-server-connection/generator.js +32 -0
  244. package/src/ts/dynamodb/mcp-server-connection/generator.js.map +1 -0
  245. package/src/ts/dynamodb/mcp-server-connection/schema.d.ts +14 -0
  246. package/src/ts/dynamodb/mcp-server-connection/schema.json +22 -0
  247. package/src/ts/dynamodb/schema.d.ts +14 -0
  248. package/src/ts/dynamodb/schema.json +52 -0
  249. package/src/ts/dynamodb/smithy-connection/__snapshots__/generator.spec.ts.snap +23 -0
  250. package/src/ts/dynamodb/smithy-connection/generator.d.ts +10 -0
  251. package/src/ts/dynamodb/smithy-connection/generator.js +30 -0
  252. package/src/ts/dynamodb/smithy-connection/generator.js.map +1 -0
  253. package/src/ts/dynamodb/smithy-connection/schema.d.ts +12 -0
  254. package/src/ts/dynamodb/smithy-connection/schema.json +18 -0
  255. package/src/ts/dynamodb/trpc-connection/__snapshots__/generator.spec.ts.snap +23 -0
  256. package/src/ts/dynamodb/trpc-connection/generator.d.ts +10 -0
  257. package/src/ts/dynamodb/trpc-connection/generator.js +30 -0
  258. package/src/ts/dynamodb/trpc-connection/generator.js.map +1 -0
  259. package/src/ts/dynamodb/trpc-connection/schema.d.ts +12 -0
  260. package/src/ts/dynamodb/trpc-connection/schema.json +18 -0
  261. package/src/ts/lambda-function/generator.d.ts +3 -3
  262. package/src/ts/lambda-function/generator.js +34 -29
  263. package/src/ts/lambda-function/generator.js.map +1 -1
  264. package/src/ts/lambda-function/schema.d.ts +3 -0
  265. package/src/ts/lambda-function/schema.json +8 -0
  266. package/src/ts/lib/__snapshots__/generator.spec.ts.snap +32 -65
  267. package/src/ts/lib/__snapshots__/vitest.spec.ts.snap +24 -0
  268. package/src/ts/lib/biome.d.ts +7 -0
  269. package/src/ts/lib/biome.js +58 -0
  270. package/src/ts/lib/biome.js.map +1 -0
  271. package/src/ts/lib/generator.d.ts +3 -3
  272. package/src/ts/lib/generator.js +27 -29
  273. package/src/ts/lib/generator.js.map +1 -1
  274. package/src/ts/lib/grit/vitest-pass-with-no-tests.grit +1 -4
  275. package/src/ts/lib/ts-project-utils.d.ts +12 -2
  276. package/src/ts/lib/ts-project-utils.js +30 -10
  277. package/src/ts/lib/ts-project-utils.js.map +1 -1
  278. package/src/ts/lib/vitest.d.ts +2 -2
  279. package/src/ts/lib/vitest.js +1 -1
  280. package/src/ts/lib/vitest.js.map +1 -1
  281. package/src/ts/mcp-server/__snapshots__/generator.spec.ts.snap +3 -3
  282. package/src/ts/mcp-server/generator.d.ts +3 -3
  283. package/src/ts/mcp-server/generator.js +34 -12
  284. package/src/ts/mcp-server/generator.js.map +1 -1
  285. package/src/ts/nx-generator/__snapshots__/generator.spec.ts.snap +19 -14
  286. package/src/ts/nx-generator/generator.d.ts +2 -2
  287. package/src/ts/nx-generator/generator.js +23 -19
  288. package/src/ts/nx-generator/generator.js.map +1 -1
  289. package/src/ts/nx-plugin/generator.d.ts +3 -3
  290. package/src/ts/nx-plugin/generator.js +10 -9
  291. package/src/ts/nx-plugin/generator.js.map +1 -1
  292. package/src/ts/nx-plugin/utils.d.ts +1 -1
  293. package/src/ts/nx-plugin/utils.js +10 -4
  294. package/src/ts/nx-plugin/utils.js.map +1 -1
  295. package/src/ts/rdb/__snapshots__/generator.spec.ts.snap +118 -58
  296. package/src/ts/rdb/agent-connection/generator.d.ts +3 -3
  297. package/src/ts/rdb/agent-connection/generator.js +4 -4
  298. package/src/ts/rdb/agent-connection/generator.js.map +1 -1
  299. package/src/ts/rdb/files/scripts/pull-image.ts.template +15 -0
  300. package/src/ts/rdb/files/scripts/{docker-start.ts.template → start-container.ts.template} +23 -23
  301. package/src/ts/rdb/generator.d.ts +3 -3
  302. package/src/ts/rdb/generator.js +96 -75
  303. package/src/ts/rdb/generator.js.map +1 -1
  304. package/src/ts/rdb/mcp-server-connection/generator.d.ts +3 -3
  305. package/src/ts/rdb/mcp-server-connection/generator.js +4 -4
  306. package/src/ts/rdb/mcp-server-connection/generator.js.map +1 -1
  307. package/src/ts/rdb/schema.d.ts +1 -1
  308. package/src/ts/rdb/schema.json +1 -1
  309. package/src/ts/rdb/smithy-connection/generator.d.ts +3 -3
  310. package/src/ts/rdb/smithy-connection/generator.js +4 -4
  311. package/src/ts/rdb/smithy-connection/generator.js.map +1 -1
  312. package/src/ts/rdb/trpc-connection/generator.d.ts +3 -3
  313. package/src/ts/rdb/trpc-connection/generator.js +3 -3
  314. package/src/ts/rdb/trpc-connection/generator.js.map +1 -1
  315. package/src/ts/rdb/utils.d.ts +1 -1
  316. package/src/ts/react-website/agui/files/common/src/hooks/useAgui__agentNameClassName__.tsx.template +1 -16
  317. package/src/ts/react-website/agui/generator.d.ts +1 -1
  318. package/src/ts/react-website/agui/generator.js +4 -5
  319. package/src/ts/react-website/agui/generator.js.map +1 -1
  320. package/src/ts/react-website/app/__snapshots__/generator.spec.ts.snap +400 -363
  321. package/src/ts/react-website/app/generator.d.ts +3 -3
  322. package/src/ts/react-website/app/generator.js +101 -91
  323. package/src/ts/react-website/app/generator.js.map +1 -1
  324. package/src/ts/react-website/app/schema.d.ts +3 -0
  325. package/src/ts/react-website/cognito-auth/__snapshots__/generator.spec.ts.snap +1 -7
  326. package/src/ts/react-website/cognito-auth/__snapshots__/generator.ux-provider.spec.ts.snap +0 -20
  327. package/src/ts/react-website/cognito-auth/generator.d.ts +3 -3
  328. package/src/ts/react-website/cognito-auth/generator.js +10 -13
  329. package/src/ts/react-website/cognito-auth/generator.js.map +1 -1
  330. package/src/ts/react-website/cognito-auth/grit/cloudscape-auth-menu.grit +8 -6
  331. package/src/ts/react-website/cognito-auth/grit/cognito-auth-wrapper.grit +1 -1
  332. package/src/ts/react-website/cognito-auth/grit/none-auth-menu.grit +1 -0
  333. package/src/ts/react-website/cognito-auth/grit/shadcn-auth-menu.grit +3 -1
  334. package/src/ts/react-website/cognito-auth/grit/shadcn-state-declarations.grit +3 -1
  335. package/src/ts/react-website/cognito-auth/utils.d.ts +1 -1
  336. package/src/ts/react-website/cognito-auth/utils.js +1 -1
  337. package/src/ts/react-website/cognito-auth/utils.js.map +1 -1
  338. package/src/ts/react-website/runtime-config/generator.d.ts +3 -3
  339. package/src/ts/react-website/runtime-config/generator.js +4 -4
  340. package/src/ts/react-website/runtime-config/generator.js.map +1 -1
  341. package/src/ts/sync/generator.d.ts +3 -3
  342. package/src/ts/sync/generator.js +2 -2
  343. package/src/ts/sync/generator.js.map +1 -1
  344. package/src/ts/website/app/generator.d.ts +3 -3
  345. package/src/ts/website/app/generator.js +1 -1
  346. package/src/ts/website/app/generator.js.map +1 -1
  347. package/src/ts/website/app/schema.d.ts +2 -0
  348. package/src/ts/website/app/schema.json +8 -0
  349. package/src/ts/website/auth/generator.d.ts +3 -3
  350. package/src/ts/website/auth/generator.js +1 -1
  351. package/src/ts/website/auth/generator.js.map +1 -1
  352. package/src/utils/agent-connection/agent-connection.d.ts +1 -1
  353. package/src/utils/agent-connection/agent-connection.js +16 -10
  354. package/src/utils/agent-connection/agent-connection.js.map +1 -1
  355. package/src/utils/agent-connection/files/core-runtime-config/model-errors.ts.template +36 -0
  356. package/src/utils/agent-connection/files/py-core-runtime-config/model_errors.py.template +40 -0
  357. package/src/utils/agent-core-constructs/agent-core-constructs.d.ts +3 -3
  358. package/src/utils/agent-core-constructs/agent-core-constructs.js +3 -11
  359. package/src/utils/agent-core-constructs/agent-core-constructs.js.map +1 -1
  360. package/src/utils/api-constructs/api-constructs.d.ts +2 -2
  361. package/src/utils/api-constructs/api-constructs.js +1 -1
  362. package/src/utils/api-constructs/api-constructs.js.map +1 -1
  363. package/src/utils/api-constructs/open-api-metadata.d.ts +2 -2
  364. package/src/utils/api-constructs/open-api-metadata.js.map +1 -1
  365. package/src/utils/ast/website.d.ts +5 -1
  366. package/src/utils/ast/website.js +3 -3
  367. package/src/utils/ast/website.js.map +1 -1
  368. package/src/utils/ast.d.ts +33 -1
  369. package/src/utils/ast.js +89 -4
  370. package/src/utils/ast.js.map +1 -1
  371. package/src/utils/bundle/bundle.d.ts +1 -1
  372. package/src/utils/bundle/bundle.js +2 -2
  373. package/src/utils/bundle/bundle.js.map +1 -1
  374. package/src/utils/config/index.d.ts +5 -5
  375. package/src/utils/config/utils.d.ts +13 -3
  376. package/src/utils/config/utils.js +37 -5
  377. package/src/utils/config/utils.js.map +1 -1
  378. package/src/utils/connection/open-api/react.d.ts +1 -1
  379. package/src/utils/connection/open-api/react.js +4 -4
  380. package/src/utils/connection/open-api/react.js.map +1 -1
  381. package/src/utils/containers.d.ts +1 -1
  382. package/src/utils/dynamodb-constructs/dynamodb-constructs.d.ts +19 -0
  383. package/src/utils/dynamodb-constructs/dynamodb-constructs.js +49 -0
  384. package/src/utils/dynamodb-constructs/dynamodb-constructs.js.map +1 -0
  385. package/src/utils/dynamodb-constructs/files/cdk/app/dynamodb/__nameKebabCase__.ts.template +44 -0
  386. package/src/utils/dynamodb-constructs/files/cdk/core/dynamodb.ts.template +105 -0
  387. package/src/utils/dynamodb-constructs/files/terraform/app/dynamodb/__nameKebabCase__/__nameKebabCase__.tf.template +62 -0
  388. package/src/utils/dynamodb-constructs/files/terraform/core/dynamodb/dynamodb.tf.template +155 -0
  389. package/src/utils/format.d.ts +45 -2
  390. package/src/utils/format.js +167 -46
  391. package/src/utils/format.js.map +1 -1
  392. package/src/utils/fs.d.ts +1 -1
  393. package/src/utils/fs.js.map +1 -1
  394. package/src/utils/function-constructs/function-constructs.d.ts +2 -2
  395. package/src/utils/function-constructs/function-constructs.js +1 -1
  396. package/src/utils/function-constructs/function-constructs.js.map +1 -1
  397. package/src/utils/generators.d.ts +4 -0
  398. package/src/utils/generators.js +4 -4
  399. package/src/utils/generators.js.map +1 -1
  400. package/src/utils/git.d.ts +1 -1
  401. package/src/utils/git.js.map +1 -1
  402. package/src/utils/iac.d.ts +1 -1
  403. package/src/utils/iac.js.map +1 -1
  404. package/src/utils/identity-constructs/files/cdk/core/user-identity.ts.template +1 -5
  405. package/src/utils/identity-constructs/files/terraform/core/user-identity/identity/identity.tf.template +2 -4
  406. package/src/utils/identity-constructs/identity-constructs.d.ts +2 -2
  407. package/src/utils/identity-constructs/identity-constructs.js +1 -1
  408. package/src/utils/identity-constructs/identity-constructs.js.map +1 -1
  409. package/src/utils/js.d.ts +4 -0
  410. package/src/utils/js.js +4 -4
  411. package/src/utils/js.js.map +1 -1
  412. package/src/utils/mcp.d.ts +25 -0
  413. package/src/utils/mcp.js +81 -0
  414. package/src/utils/mcp.js.map +1 -0
  415. package/src/utils/metrics.d.ts +9 -3
  416. package/src/utils/metrics.js +58 -28
  417. package/src/utils/metrics.js.map +1 -1
  418. package/src/utils/npm-scope.d.ts +1 -1
  419. package/src/utils/npm-scope.js.map +1 -1
  420. package/src/utils/nx.d.ts +7 -2
  421. package/src/utils/nx.js +46 -14
  422. package/src/utils/nx.js.map +1 -1
  423. package/src/utils/nxlv-python.d.ts +1 -1
  424. package/src/utils/paths.d.ts +1 -1
  425. package/src/utils/pnpm-workspace.d.ts +1 -1
  426. package/src/utils/pnpm-workspace.js.map +1 -1
  427. package/src/utils/port.d.ts +30 -3
  428. package/src/utils/port.js +72 -4
  429. package/src/utils/port.js.map +1 -1
  430. package/src/utils/py.d.ts +2 -2
  431. package/src/utils/py.js +13 -6
  432. package/src/utils/py.js.map +1 -1
  433. package/src/utils/rdb-constructs/files/cdk/core/rdb/aurora.ts.template +30 -1
  434. package/src/utils/rdb-constructs/files/terraform/app/dbs/__nameKebabCase__/__nameKebabCase__.tf.template +4 -4
  435. package/src/utils/rdb-constructs/files/terraform/core/rdb/aurora/aurora.tf.template +18 -9
  436. package/src/utils/rdb-constructs/rdb-constructs.d.ts +4 -4
  437. package/src/utils/rdb-constructs/rdb-constructs.js +3 -11
  438. package/src/utils/rdb-constructs/rdb-constructs.js.map +1 -1
  439. package/src/utils/shared-constructs.d.ts +2 -2
  440. package/src/utils/shared-constructs.js +7 -7
  441. package/src/utils/shared-constructs.js.map +1 -1
  442. package/src/utils/shared-infra-config.d.ts +1 -1
  443. package/src/utils/shared-infra-config.js +1 -1
  444. package/src/utils/shared-infra-config.js.map +1 -1
  445. package/src/utils/shared-scripts.d.ts +1 -1
  446. package/src/utils/shared-scripts.js +2 -2
  447. package/src/utils/shared-scripts.js.map +1 -1
  448. package/src/utils/shared-shadcn.d.ts +1 -1
  449. package/src/utils/shared-shadcn.js +1 -13
  450. package/src/utils/shared-shadcn.js.map +1 -1
  451. package/src/utils/test.d.ts +5 -1
  452. package/src/utils/test.js +9 -5
  453. package/src/utils/test.js.map +1 -1
  454. package/src/utils/toml.d.ts +1 -1
  455. package/src/utils/toml.js +4 -0
  456. package/src/utils/toml.js.map +1 -1
  457. package/src/utils/versions.d.ts +71 -75
  458. package/src/utils/versions.js +70 -74
  459. package/src/utils/versions.js.map +1 -1
  460. package/src/utils/website-constructs/files/cdk/core/static-website.ts.template +52 -5
  461. package/src/utils/website-constructs/files/terraform/core/static-website/static-website.tf.template +38 -11
  462. package/src/utils/website-constructs/website-constructs.d.ts +2 -2
  463. package/src/utils/website-constructs/website-constructs.js +1 -1
  464. package/src/utils/website-constructs/website-constructs.js.map +1 -1
  465. package/src/ts/lib/eslint.d.ts +0 -14
  466. package/src/ts/lib/eslint.js +0 -90
  467. package/src/ts/lib/eslint.js.map +0 -1
  468. package/src/ts/lib/grit/eslint-dependency-checks-warn.grit +0 -1
@@ -0,0 +1,1561 @@
1
+ // Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
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',
39
+ },
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;
183
+ }
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);
208
+ }
209
+ }
210
+ "
211
+ `;
212
+
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
+ exports[`ts#dynamodb generator > should generate terraform modules when iac is terraform 1`] = `
295
+ "terraform {
296
+ required_version = ">= 1.0"
297
+
298
+ required_providers {
299
+ aws = {
300
+ source = "hashicorp/aws"
301
+ version = "~> 6.0"
302
+ }
303
+ }
304
+ }
305
+
306
+ variable "name" {
307
+ description = "Name for the DynamoDB table."
308
+ type = string
309
+ }
310
+
311
+ variable "billing_mode" {
312
+ description = "Controls how you are charged for read and write throughput."
313
+ type = string
314
+ default = "PAY_PER_REQUEST"
315
+ }
316
+
317
+ variable "point_in_time_recovery_enabled" {
318
+ description = "Whether to enable point-in-time recovery for the table."
319
+ type = bool
320
+ default = true
321
+ }
322
+
323
+ variable "deletion_protection_enabled" {
324
+ description = "Whether to enable deletion protection on the table."
325
+ type = bool
326
+ default = true
327
+ }
328
+
329
+ variable "enable_key_rotation" {
330
+ description = "Whether to enable automatic key rotation on the KMS key used to encrypt the table."
331
+ type = bool
332
+ default = true
333
+ }
334
+
335
+ variable "global_secondary_indexes" {
336
+ description = "Global secondary indexes to create on the table. All key attributes must be of type string."
337
+ type = list(object({
338
+ name = string
339
+ hash_key = string
340
+ range_key = string
341
+ projection_type = string
342
+ }))
343
+ default = []
344
+ }
345
+
346
+ locals {
347
+ gsi_attribute_names = distinct(flatten([
348
+ for gsi in var.global_secondary_indexes : [gsi.hash_key, gsi.range_key]
349
+ ]))
350
+ }
351
+
352
+ data "aws_caller_identity" "current" {}
353
+ data "aws_partition" "current" {}
354
+
355
+ resource "aws_kms_key" "table" {
356
+ description = "KMS key for DynamoDB table \${var.name}"
357
+ enable_key_rotation = var.enable_key_rotation
358
+
359
+ policy = jsonencode({
360
+ Version = "2012-10-17"
361
+ Statement = [
362
+ {
363
+ Sid = "EnableRootAccess"
364
+ Effect = "Allow"
365
+ Principal = {
366
+ AWS = "arn:\${data.aws_partition.current.partition}:iam::\${data.aws_caller_identity.current.account_id}:root"
367
+ }
368
+ Action = "kms:*"
369
+ Resource = "*"
370
+ },
371
+ {
372
+ Sid = "AllowDynamoDBService"
373
+ Effect = "Allow"
374
+ Principal = {
375
+ Service = "dynamodb.amazonaws.com"
376
+ }
377
+ Action = [
378
+ "kms:Encrypt",
379
+ "kms:Decrypt",
380
+ "kms:ReEncrypt*",
381
+ "kms:GenerateDataKey*",
382
+ "kms:DescribeKey",
383
+ "kms:CreateGrant"
384
+ ]
385
+ Resource = "*"
386
+ }
387
+ ]
388
+ })
389
+ }
390
+
391
+ resource "aws_dynamodb_table" "table" {
392
+ name = var.name
393
+ billing_mode = var.billing_mode
394
+ hash_key = "pk"
395
+ range_key = "sk"
396
+ deletion_protection_enabled = var.deletion_protection_enabled
397
+
398
+ attribute {
399
+ name = "pk"
400
+ type = "S"
401
+ }
402
+
403
+ attribute {
404
+ name = "sk"
405
+ type = "S"
406
+ }
407
+
408
+ dynamic "attribute" {
409
+ for_each = toset(local.gsi_attribute_names)
410
+ content {
411
+ name = attribute.value
412
+ type = "S"
413
+ }
414
+ }
415
+
416
+ dynamic "global_secondary_index" {
417
+ for_each = var.global_secondary_indexes
418
+ content {
419
+ name = global_secondary_index.value.name
420
+ hash_key = global_secondary_index.value.hash_key
421
+ range_key = global_secondary_index.value.range_key
422
+ projection_type = global_secondary_index.value.projection_type
423
+ }
424
+ }
425
+
426
+ point_in_time_recovery {
427
+ enabled = var.point_in_time_recovery_enabled
428
+ }
429
+
430
+ server_side_encryption {
431
+ enabled = true
432
+ kms_key_arn = aws_kms_key.table.arn
433
+ }
434
+ }
435
+
436
+ output "table_name" {
437
+ description = "Name of the DynamoDB table."
438
+ value = aws_dynamodb_table.table.name
439
+ }
440
+
441
+ output "table_arn" {
442
+ description = "ARN of the DynamoDB table."
443
+ value = aws_dynamodb_table.table.arn
444
+ }
445
+
446
+ output "kms_key_arn" {
447
+ description = "ARN of the KMS key used to encrypt the DynamoDB table."
448
+ value = aws_kms_key.table.arn
449
+ }
450
+ "
451
+ `;
452
+
453
+ exports[`ts#dynamodb generator > should generate terraform modules when iac is terraform 2`] = `
454
+ "terraform {
455
+ required_providers {
456
+ aws = {
457
+ source = "hashicorp/aws"
458
+ version = "~> 6.0"
459
+ }
460
+ }
461
+ }
462
+
463
+ variable "billing_mode" {
464
+ description = "Controls how you are charged for read and write throughput."
465
+ type = string
466
+ default = "PAY_PER_REQUEST"
467
+ }
468
+
469
+ variable "point_in_time_recovery_enabled" {
470
+ description = "Whether to enable point-in-time recovery for the table."
471
+ type = bool
472
+ default = true
473
+ }
474
+
475
+ variable "deletion_protection_enabled" {
476
+ description = "Whether to enable deletion protection on the table."
477
+ type = bool
478
+ default = true
479
+ }
480
+
481
+ variable "enable_key_rotation" {
482
+ description = "Whether to enable automatic key rotation on the KMS key used to encrypt the table."
483
+ type = bool
484
+ default = true
485
+ }
486
+
487
+ 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 : {
490
+ name = gsi.indexName
491
+ hash_key = gsi.partitionKey
492
+ range_key = try(gsi.sortKey, null)
493
+ projection_type = "ALL"
494
+ }]
495
+ }
496
+
497
+ module "dynamodb_table" {
498
+ source = "../../../core/dynamodb"
499
+ name = "proj-my-table"
500
+ billing_mode = var.billing_mode
501
+ point_in_time_recovery_enabled = var.point_in_time_recovery_enabled
502
+ deletion_protection_enabled = var.deletion_protection_enabled
503
+ enable_key_rotation = var.enable_key_rotation
504
+ global_secondary_indexes = local.global_secondary_indexes
505
+ }
506
+
507
+ module "add_to_runtime_config" {
508
+ source = "../../../core/runtime-config/entry"
509
+
510
+ namespace = "dynamodb"
511
+ key = "MyTable"
512
+ value = {
513
+ tableName = module.dynamodb_table.table_name
514
+ }
515
+ }
516
+ "
517
+ `;
518
+
519
+ exports[`ts#dynamodb generator > should generate the dynamodb project > packages/my-table/scripts/create-local-table.ts 1`] = `
520
+ "import {
521
+ CreateTableCommand,
522
+ DescribeTableCommand,
523
+ DynamoDBClient,
524
+ GlobalSecondaryIndexDescription,
525
+ ResourceInUseException,
526
+ UpdateTableCommand,
527
+ } from '@aws-sdk/client-dynamodb';
528
+ import { readFileSync } from 'fs';
529
+ import { join, dirname } from 'path';
530
+ import { fileURLToPath } from 'url';
531
+
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
+ }[];
541
+ };
542
+
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);
548
+
549
+ const client = new DynamoDBClient({
550
+ endpoint,
551
+ region: 'us-east-1',
552
+ credentials: {
553
+ accessKeyId: 'accessKeyId',
554
+ secretAccessKey: 'secretAccessKey',
555
+ },
556
+ });
557
+
558
+ const waitForTableActive = async (
559
+ waitClient: DynamoDBClient,
560
+ waitTableName: string,
561
+ ): Promise<void> => {
562
+ for (;;) {
563
+ const { Table } = await waitClient.send(
564
+ new DescribeTableCommand({ TableName: waitTableName }),
565
+ { abortSignal: timeoutSignal() },
566
+ );
567
+ const allActive =
568
+ Table?.TableStatus === 'ACTIVE' &&
569
+ (Table?.GlobalSecondaryIndexes ?? []).every(
570
+ (g: GlobalSecondaryIndexDescription) => g.IndexStatus === 'ACTIVE',
571
+ );
572
+ if (allActive) return;
573
+ await new Promise((resolve) => setTimeout(resolve, 500));
574
+ }
575
+ };
576
+
577
+ const ensureTable = async (
578
+ tblClient: DynamoDBClient,
579
+ tblName: string,
580
+ gsIs: { indexName: string; partitionKey: string; sortKey?: string }[],
581
+ ): Promise<void> => {
582
+ const gsiAttributeDefinitions = gsIs.flatMap((gsi) => [
583
+ { AttributeName: gsi.partitionKey, AttributeType: 'S' as const },
584
+ ...(gsi.sortKey
585
+ ? [{ AttributeName: gsi.sortKey, AttributeType: 'S' as const }]
586
+ : []),
587
+ ]);
588
+ const attributeDefinitions = [
589
+ { AttributeName: 'pk', AttributeType: 'S' as const },
590
+ { AttributeName: 'sk', AttributeType: 'S' as const },
591
+ ...gsiAttributeDefinitions.filter(
592
+ (a, i, arr) =>
593
+ arr.findIndex((b) => b.AttributeName === a.AttributeName) === i,
594
+ ),
595
+ ];
596
+ const globalSecondaryIndexes = gsIs.map((gsi) => ({
597
+ IndexName: gsi.indexName,
598
+ KeySchema: [
599
+ { AttributeName: gsi.partitionKey, KeyType: 'HASH' as const },
600
+ ...(gsi.sortKey
601
+ ? [{ AttributeName: gsi.sortKey, KeyType: 'RANGE' as const }]
602
+ : []),
603
+ ],
604
+ Projection: { ProjectionType: 'ALL' as const },
605
+ }));
606
+
607
+ for (let attempt = 0; attempt < 30; attempt++) {
608
+ try {
609
+ // New table: create with all GSIs in one shot.
610
+ await tblClient.send(
611
+ new CreateTableCommand({
612
+ TableName: tblName,
613
+ AttributeDefinitions: attributeDefinitions,
614
+ KeySchema: [
615
+ { AttributeName: 'pk', KeyType: 'HASH' },
616
+ { AttributeName: 'sk', KeyType: 'RANGE' },
617
+ ],
618
+ BillingMode: 'PAY_PER_REQUEST',
619
+ ...(globalSecondaryIndexes.length > 0 && {
620
+ GlobalSecondaryIndexes: globalSecondaryIndexes,
621
+ }),
622
+ }),
623
+ { abortSignal: timeoutSignal() },
624
+ );
625
+ console.log(\`Created table: \${tblName}\`);
626
+ for (const gsi of globalSecondaryIndexes) {
627
+ console.log(\`Created GSI: \${gsi.IndexName}\`);
628
+ }
629
+ return;
630
+ } catch (e) {
631
+ // Table already exists: sync GSIs to match desired state.
632
+ if (e instanceof ResourceInUseException) {
633
+ const { Table } = await tblClient.send(
634
+ new DescribeTableCommand({ TableName: tblName }),
635
+ { abortSignal: timeoutSignal() },
636
+ );
637
+ const existingGSIs = Table?.GlobalSecondaryIndexes ?? [];
638
+ const existingNames = new Set(
639
+ existingGSIs.map(
640
+ (g: GlobalSecondaryIndexDescription) => g.IndexName ?? '',
641
+ ),
642
+ );
643
+ const desiredNames = new Set(gsIs.map((g) => g.indexName));
644
+
645
+ // DynamoDB Local only allows one GSI to be created or deleted at a time.
646
+ for (const existing of existingGSIs) {
647
+ if (!existing.IndexName || desiredNames.has(existing.IndexName))
648
+ continue;
649
+ await tblClient.send(
650
+ new UpdateTableCommand({
651
+ TableName: tblName,
652
+ GlobalSecondaryIndexUpdates: [
653
+ { Delete: { IndexName: existing.IndexName } },
654
+ ],
655
+ }),
656
+ { abortSignal: timeoutSignal() },
657
+ );
658
+ console.log(\`Deleted GSI: \${existing.IndexName}\`);
659
+ await waitForTableActive(tblClient, tblName);
660
+ }
661
+
662
+ for (const gsi of gsIs) {
663
+ if (existingNames.has(gsi.indexName)) continue;
664
+ await tblClient.send(
665
+ new UpdateTableCommand({
666
+ TableName: tblName,
667
+ AttributeDefinitions: [
668
+ { AttributeName: gsi.partitionKey, AttributeType: 'S' },
669
+ ...(gsi.sortKey
670
+ ? [
671
+ {
672
+ AttributeName: gsi.sortKey,
673
+ AttributeType: 'S' as const,
674
+ },
675
+ ]
676
+ : []),
677
+ ],
678
+ GlobalSecondaryIndexUpdates: [
679
+ {
680
+ Create: {
681
+ IndexName: gsi.indexName,
682
+ KeySchema: [
683
+ { AttributeName: gsi.partitionKey, KeyType: 'HASH' },
684
+ ...(gsi.sortKey
685
+ ? [{ AttributeName: gsi.sortKey, KeyType: 'RANGE' }]
686
+ : []),
687
+ ],
688
+ Projection: { ProjectionType: 'ALL' },
689
+ },
690
+ },
691
+ ],
692
+ }),
693
+ { abortSignal: timeoutSignal() },
694
+ );
695
+ console.log(\`Created GSI: \${gsi.indexName}\`);
696
+ await waitForTableActive(tblClient, tblName);
697
+ }
698
+ return;
699
+ }
700
+ if (attempt === 29) throw e;
701
+ await new Promise((resolve) => setTimeout(resolve, 1000));
702
+ }
703
+ }
704
+ };
705
+
706
+ await ensureTable(client, tableName, globalSecondaryIndexes);
707
+ "
708
+ `;
709
+
710
+ exports[`ts#dynamodb generator > should generate the dynamodb project > packages/my-table/scripts/pull-image.ts 1`] = `
711
+ "import { spawnSync } from 'child_process';
712
+
713
+ const ENGINE = 'docker';
714
+ const image = process.argv[2];
715
+
716
+ const inspect = spawnSync(ENGINE, ['image', 'inspect', image], {
717
+ stdio: 'ignore',
718
+ });
719
+
720
+ if (inspect.status !== 0) {
721
+ const pull = spawnSync(ENGINE, ['pull', image], { stdio: 'inherit' });
722
+ if (pull.status !== 0) {
723
+ process.exit(pull.status ?? 1);
724
+ }
725
+ }
726
+ "
727
+ `;
728
+
729
+ exports[`ts#dynamodb generator > should generate the dynamodb project > packages/my-table/scripts/start-container.ts 1`] = `
730
+ "import { spawn, spawnSync } from 'child_process';
731
+
732
+ const ENGINE = 'docker';
733
+
734
+ const [containerName, image, hostPort] = process.argv.slice(2);
735
+
736
+ const containerExists = (): boolean =>
737
+ spawnSync(ENGINE, ['container', 'inspect', containerName], {
738
+ stdio: 'ignore',
739
+ }).status === 0;
740
+
741
+ const isRunning = (): boolean => {
742
+ const result = spawnSync(
743
+ ENGINE,
744
+ ['container', 'inspect', '-f', '{{.State.Running}}', containerName],
745
+ { encoding: 'utf-8' },
746
+ );
747
+ return result.status === 0 && result.stdout.trim() === 'true';
748
+ };
749
+
750
+ // Multiple dynamo table packages run serve-local in parallel sharing the same
751
+ // container. All processes stream logs, but only the one that created the
752
+ // container stops it on exit — others just detach.
753
+ let created = false;
754
+ let runRetries = 0;
755
+
756
+ for (;;) {
757
+ if (containerExists()) {
758
+ if (!isRunning()) {
759
+ // Another process may have started it between our inspect and start — container engine start
760
+ // exits 0 even if the container is already running, so no special-casing needed.
761
+ const start = spawnSync(ENGINE, ['start', containerName], {
762
+ stdio: 'inherit',
763
+ });
764
+ if (start.status !== 0) process.exit(start.status ?? 1);
765
+ }
766
+ break;
767
+ }
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' });
774
+ if (create.status === 0) {
775
+ created = true;
776
+ break;
777
+ }
778
+ if (!containerExists() && ++runRetries > 3) {
779
+ process.stderr.write(create.stderr);
780
+ process.exit(create.status ?? 1);
781
+ }
782
+ }
783
+
784
+ const logs = spawn(ENGINE, ['logs', '-f', containerName], {
785
+ stdio: ['ignore', 'inherit', 'inherit'],
786
+ });
787
+
788
+ let exiting = false;
789
+
790
+ const cleanup = () => {
791
+ if (exiting) return;
792
+ exiting = true;
793
+ if (created) {
794
+ spawnSync(ENGINE, ['stop', containerName], { stdio: 'ignore' });
795
+ }
796
+ logs.kill();
797
+ process.exit(0);
798
+ };
799
+
800
+ process.on('SIGTERM', cleanup);
801
+ process.on('SIGINT', cleanup);
802
+ process.on('SIGHUP', cleanup);
803
+
804
+ logs.on('exit', (code) => {
805
+ if (!exiting) process.exit(code ?? 0);
806
+ });
807
+ "
808
+ `;
809
+
810
+ exports[`ts#dynamodb generator > should generate the dynamodb project > packages/my-table/src/client.ts 1`] = `
811
+ "import { DynamoDBClient } from '@aws-sdk/client-dynamodb';
812
+ 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';
821
+
822
+ let _client: DynamoDBClient | undefined;
823
+
824
+ 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({});
836
+ return _client;
837
+ };
838
+
839
+ type DynamoDBConfig = {
840
+ tableName: string;
841
+ };
842
+
843
+ let _tableName: string | undefined;
844
+
845
+ export const resolveTableName = async (): Promise<string> => {
846
+ if (process.env.SERVE_LOCAL === 'true') {
847
+ return LOCAL_TABLE_NAME;
848
+ }
849
+ if (_tableName === undefined) {
850
+ const appId = process.env.RUNTIME_CONFIG_APP_ID;
851
+ if (!appId) {
852
+ throw new Error('RUNTIME_CONFIG_APP_ID environment variable is not set');
853
+ }
854
+ const config = await getAppConfig<{
855
+ [key: string]: DynamoDBConfig | undefined;
856
+ }>('dynamodb', {
857
+ application: appId,
858
+ environment: 'default',
859
+ transform: 'json',
860
+ });
861
+ const tableName = config?.[PACKAGE_NAME]?.tableName;
862
+ if (!tableName) {
863
+ throw new Error('Could not resolve table name from runtime config');
864
+ }
865
+ _tableName = tableName;
866
+ }
867
+ return _tableName!;
868
+ };
869
+ "
870
+ `;
871
+
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
+ exports[`ts#dynamodb generator > should generate the dynamodb project > packages/my-table/src/entities/example.ts 1`] = `
886
+ "import { Entity } from 'electrodb';
887
+ import { getDynamoDBClient, resolveTableName } from '../client.js';
888
+
889
+ // Example entity - replace with your own entities using ElectroDB
890
+ // See https://electrodb.dev/en/modeling/entities/ for documentation
891
+ export const createExampleEntity = async () =>
892
+ new Entity(
893
+ {
894
+ model: {
895
+ entity: 'example',
896
+ version: '1',
897
+ service: 'MyTable',
898
+ },
899
+ attributes: {
900
+ id: {
901
+ type: 'string',
902
+ required: true,
903
+ },
904
+ createdAt: {
905
+ type: 'string',
906
+ required: true,
907
+ default: () => new Date().toISOString(),
908
+ readOnly: true,
909
+ },
910
+ updatedAt: {
911
+ type: 'string',
912
+ required: true,
913
+ default: () => new Date().toISOString(),
914
+ watch: '*',
915
+ set: () => new Date().toISOString(),
916
+ },
917
+ },
918
+ indexes: {
919
+ primary: {
920
+ pk: {
921
+ field: 'pk',
922
+ composite: ['id'],
923
+ },
924
+ sk: {
925
+ field: 'sk',
926
+ composite: [],
927
+ },
928
+ },
929
+ },
930
+ },
931
+ { client: getDynamoDBClient(), table: await resolveTableName() },
932
+ );
933
+ "
934
+ `;
935
+
936
+ exports[`ts#dynamodb generator > should generate the dynamodb project > packages/my-table/src/entities/index.ts 1`] = `
937
+ "export { createExampleEntity } from './example.js';
938
+ "
939
+ `;
940
+
941
+ 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';
951
+ export * from './entities/index.js';
952
+ "
953
+ `;
954
+
955
+ exports[`ts#dynamodb generator > should generate the dynamodb project 1`] = `
956
+ "import { Construct } from 'constructs';
957
+ import {
958
+ AttributeType,
959
+ BillingMode,
960
+ Table,
961
+ TableEncryption,
962
+ TableProps,
963
+ } from 'aws-cdk-lib/aws-dynamodb';
964
+ import { RemovalPolicy } from 'aws-cdk-lib';
965
+ import { Grant, IGrantable } from 'aws-cdk-lib/aws-iam';
966
+ import { Key } from 'aws-cdk-lib/aws-kms';
967
+ import { RuntimeConfig } from './runtime-config.js';
968
+
969
+ type _DynamoDBTableProps = Omit<
970
+ TableProps,
971
+ 'tableName' | 'partitionKey' | 'sortKey' | 'encryption' | 'encryptionKey'
972
+ >;
973
+
974
+ export interface DynamoDBTableProps extends _DynamoDBTableProps {
975
+ /**
976
+ * The DynamoDB table name. If omitted, CDK auto-generates a unique name
977
+ * from the stack name and construct path.
978
+ */
979
+ readonly tableName?: string;
980
+
981
+ /**
982
+ * RuntimeConfig key used under the \`dynamodb\` namespace.
983
+ */
984
+ readonly runtimeConfigKey: string;
985
+
986
+ /**
987
+ * Whether to enable automatic key rotation on the KMS key used to encrypt the table.
988
+ *
989
+ * @default true
990
+ */
991
+ readonly enableKeyRotation?: boolean;
992
+ }
993
+
994
+ export abstract class DynamoDBTable extends Construct {
995
+ public readonly table: Table;
996
+
997
+ constructor(
998
+ scope: Construct,
999
+ id: string,
1000
+ {
1001
+ tableName,
1002
+ runtimeConfigKey,
1003
+ billingMode = BillingMode.PAY_PER_REQUEST,
1004
+ pointInTimeRecoverySpecification = { pointInTimeRecoveryEnabled: true },
1005
+ deletionProtection = true,
1006
+ removalPolicy = RemovalPolicy.RETAIN,
1007
+ enableKeyRotation = true,
1008
+ ...rest
1009
+ }: DynamoDBTableProps,
1010
+ ) {
1011
+ super(scope, id);
1012
+
1013
+ const key = new Key(this, 'EncryptionKey', { enableKeyRotation });
1014
+
1015
+ this.table = new Table(this, runtimeConfigKey, {
1016
+ ...rest,
1017
+ ...(tableName !== undefined && { tableName }),
1018
+ partitionKey: { name: 'pk', type: AttributeType.STRING },
1019
+ sortKey: { name: 'sk', type: AttributeType.STRING },
1020
+ billingMode,
1021
+ pointInTimeRecoverySpecification,
1022
+ deletionProtection,
1023
+ encryptionKey: key,
1024
+ encryption: TableEncryption.CUSTOMER_MANAGED,
1025
+ removalPolicy,
1026
+ });
1027
+
1028
+ const rc = RuntimeConfig.ensure(this);
1029
+ rc.set('dynamodb', runtimeConfigKey, { tableName: this.table.tableName });
1030
+ }
1031
+
1032
+ public grantReadData(grantee: IGrantable): Grant {
1033
+ return this.table.grantReadData(grantee);
1034
+ }
1035
+
1036
+ public grantWriteData(grantee: IGrantable): Grant {
1037
+ return this.table.grantWriteData(grantee);
1038
+ }
1039
+
1040
+ public grantReadWriteData(grantee: IGrantable): Grant {
1041
+ return this.table.grantReadWriteData(grantee);
1042
+ }
1043
+
1044
+ public grantFullAccess(grantee: IGrantable): Grant {
1045
+ return this.table.grantFullAccess(grantee);
1046
+ }
1047
+
1048
+ public grantStreamRead(grantee: IGrantable): Grant {
1049
+ return this.table.grantStreamRead(grantee);
1050
+ }
1051
+
1052
+ public grantTableListStreams(grantee: IGrantable): Grant {
1053
+ return this.table.grantTableListStreams(grantee);
1054
+ }
1055
+
1056
+ public grant(grantee: IGrantable, ...actions: string[]): Grant {
1057
+ return this.table.grant(grantee, ...actions);
1058
+ }
1059
+
1060
+ public grantStream(grantee: IGrantable, ...actions: string[]): Grant {
1061
+ return this.table.grantStream(grantee, ...actions);
1062
+ }
1063
+ }
1064
+ "
1065
+ `;
1066
+
1067
+ exports[`ts#dynamodb generator > should generate the dynamodb project 2`] = `
1068
+ "import { Construct } from 'constructs';
1069
+ import { PACKAGE_NAME } from ':proj/my-table';
1070
+ import { AttributeType, ProjectionType } from 'aws-cdk-lib/aws-dynamodb';
1071
+ import { DynamoDBTable, DynamoDBTableProps } from '../../core/dynamodb.js';
1072
+ import { findWorkspaceRoot } from '../../core/workspace.js';
1073
+ import { readFileSync } from 'fs';
1074
+ import { join } from 'path';
1075
+ import { fileURLToPath } from 'url';
1076
+
1077
+ export type MyTableProps = Omit<DynamoDBTableProps, 'runtimeConfigKey'>;
1078
+
1079
+ export class MyTable extends DynamoDBTable {
1080
+ constructor(scope: Construct, id: string, props?: MyTableProps) {
1081
+ super(scope, id, {
1082
+ ...props,
1083
+ runtimeConfigKey: PACKAGE_NAME,
1084
+ });
1085
+
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) {
1103
+ this.table.addGlobalSecondaryIndex({
1104
+ indexName: gsi.indexName,
1105
+ partitionKey: { name: gsi.partitionKey, type: AttributeType.STRING },
1106
+ ...(gsi.sortKey && {
1107
+ sortKey: { name: gsi.sortKey, type: AttributeType.STRING },
1108
+ }),
1109
+ projectionType: ProjectionType.ALL,
1110
+ });
1111
+ }
1112
+ }
1113
+ }
1114
+ "
1115
+ `;
1116
+
1117
+ exports[`ts#dynamodb generator > should generate the dynamodb project 3`] = `
1118
+ "export * from './my-table.js';
1119
+ "
1120
+ `;
1121
+
1122
+ exports[`ts#dynamodb generator > should generate the dynamodb project 4`] = `
1123
+ "export * from './dynamodb/index.js';
1124
+ "
1125
+ `;
1126
+
1127
+ exports[`ts#dynamodb generator > should generate with infra=none then upgrade to infra=dynamodb > packages/my-table/scripts/create-local-table.ts 1`] = `
1128
+ "import {
1129
+ CreateTableCommand,
1130
+ DescribeTableCommand,
1131
+ DynamoDBClient,
1132
+ GlobalSecondaryIndexDescription,
1133
+ ResourceInUseException,
1134
+ UpdateTableCommand,
1135
+ } from '@aws-sdk/client-dynamodb';
1136
+ import { readFileSync } from 'fs';
1137
+ import { join, dirname } from 'path';
1138
+ import { fileURLToPath } from 'url';
1139
+
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
+ }[];
1149
+ };
1150
+
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);
1156
+
1157
+ const client = new DynamoDBClient({
1158
+ endpoint,
1159
+ region: 'us-east-1',
1160
+ credentials: {
1161
+ accessKeyId: 'accessKeyId',
1162
+ secretAccessKey: 'secretAccessKey',
1163
+ },
1164
+ });
1165
+
1166
+ const waitForTableActive = async (
1167
+ waitClient: DynamoDBClient,
1168
+ waitTableName: string,
1169
+ ): Promise<void> => {
1170
+ for (;;) {
1171
+ const { Table } = await waitClient.send(
1172
+ new DescribeTableCommand({ TableName: waitTableName }),
1173
+ { abortSignal: timeoutSignal() },
1174
+ );
1175
+ const allActive =
1176
+ Table?.TableStatus === 'ACTIVE' &&
1177
+ (Table?.GlobalSecondaryIndexes ?? []).every(
1178
+ (g: GlobalSecondaryIndexDescription) => g.IndexStatus === 'ACTIVE',
1179
+ );
1180
+ if (allActive) return;
1181
+ await new Promise((resolve) => setTimeout(resolve, 500));
1182
+ }
1183
+ };
1184
+
1185
+ const ensureTable = async (
1186
+ tblClient: DynamoDBClient,
1187
+ tblName: string,
1188
+ gsIs: { indexName: string; partitionKey: string; sortKey?: string }[],
1189
+ ): Promise<void> => {
1190
+ const gsiAttributeDefinitions = gsIs.flatMap((gsi) => [
1191
+ { AttributeName: gsi.partitionKey, AttributeType: 'S' as const },
1192
+ ...(gsi.sortKey
1193
+ ? [{ AttributeName: gsi.sortKey, AttributeType: 'S' as const }]
1194
+ : []),
1195
+ ]);
1196
+ const attributeDefinitions = [
1197
+ { AttributeName: 'pk', AttributeType: 'S' as const },
1198
+ { AttributeName: 'sk', AttributeType: 'S' as const },
1199
+ ...gsiAttributeDefinitions.filter(
1200
+ (a, i, arr) =>
1201
+ arr.findIndex((b) => b.AttributeName === a.AttributeName) === i,
1202
+ ),
1203
+ ];
1204
+ const globalSecondaryIndexes = gsIs.map((gsi) => ({
1205
+ IndexName: gsi.indexName,
1206
+ KeySchema: [
1207
+ { AttributeName: gsi.partitionKey, KeyType: 'HASH' as const },
1208
+ ...(gsi.sortKey
1209
+ ? [{ AttributeName: gsi.sortKey, KeyType: 'RANGE' as const }]
1210
+ : []),
1211
+ ],
1212
+ Projection: { ProjectionType: 'ALL' as const },
1213
+ }));
1214
+
1215
+ for (let attempt = 0; attempt < 30; attempt++) {
1216
+ try {
1217
+ // New table: create with all GSIs in one shot.
1218
+ await tblClient.send(
1219
+ new CreateTableCommand({
1220
+ TableName: tblName,
1221
+ AttributeDefinitions: attributeDefinitions,
1222
+ KeySchema: [
1223
+ { AttributeName: 'pk', KeyType: 'HASH' },
1224
+ { AttributeName: 'sk', KeyType: 'RANGE' },
1225
+ ],
1226
+ BillingMode: 'PAY_PER_REQUEST',
1227
+ ...(globalSecondaryIndexes.length > 0 && {
1228
+ GlobalSecondaryIndexes: globalSecondaryIndexes,
1229
+ }),
1230
+ }),
1231
+ { abortSignal: timeoutSignal() },
1232
+ );
1233
+ console.log(\`Created table: \${tblName}\`);
1234
+ for (const gsi of globalSecondaryIndexes) {
1235
+ console.log(\`Created GSI: \${gsi.IndexName}\`);
1236
+ }
1237
+ return;
1238
+ } catch (e) {
1239
+ // Table already exists: sync GSIs to match desired state.
1240
+ if (e instanceof ResourceInUseException) {
1241
+ const { Table } = await tblClient.send(
1242
+ new DescribeTableCommand({ TableName: tblName }),
1243
+ { abortSignal: timeoutSignal() },
1244
+ );
1245
+ const existingGSIs = Table?.GlobalSecondaryIndexes ?? [];
1246
+ const existingNames = new Set(
1247
+ existingGSIs.map(
1248
+ (g: GlobalSecondaryIndexDescription) => g.IndexName ?? '',
1249
+ ),
1250
+ );
1251
+ const desiredNames = new Set(gsIs.map((g) => g.indexName));
1252
+
1253
+ // DynamoDB Local only allows one GSI to be created or deleted at a time.
1254
+ for (const existing of existingGSIs) {
1255
+ if (!existing.IndexName || desiredNames.has(existing.IndexName))
1256
+ continue;
1257
+ await tblClient.send(
1258
+ new UpdateTableCommand({
1259
+ TableName: tblName,
1260
+ GlobalSecondaryIndexUpdates: [
1261
+ { Delete: { IndexName: existing.IndexName } },
1262
+ ],
1263
+ }),
1264
+ { abortSignal: timeoutSignal() },
1265
+ );
1266
+ console.log(\`Deleted GSI: \${existing.IndexName}\`);
1267
+ await waitForTableActive(tblClient, tblName);
1268
+ }
1269
+
1270
+ for (const gsi of gsIs) {
1271
+ if (existingNames.has(gsi.indexName)) continue;
1272
+ await tblClient.send(
1273
+ new UpdateTableCommand({
1274
+ TableName: tblName,
1275
+ AttributeDefinitions: [
1276
+ { AttributeName: gsi.partitionKey, AttributeType: 'S' },
1277
+ ...(gsi.sortKey
1278
+ ? [
1279
+ {
1280
+ AttributeName: gsi.sortKey,
1281
+ AttributeType: 'S' as const,
1282
+ },
1283
+ ]
1284
+ : []),
1285
+ ],
1286
+ GlobalSecondaryIndexUpdates: [
1287
+ {
1288
+ Create: {
1289
+ IndexName: gsi.indexName,
1290
+ KeySchema: [
1291
+ { AttributeName: gsi.partitionKey, KeyType: 'HASH' },
1292
+ ...(gsi.sortKey
1293
+ ? [{ AttributeName: gsi.sortKey, KeyType: 'RANGE' }]
1294
+ : []),
1295
+ ],
1296
+ Projection: { ProjectionType: 'ALL' },
1297
+ },
1298
+ },
1299
+ ],
1300
+ }),
1301
+ { abortSignal: timeoutSignal() },
1302
+ );
1303
+ console.log(\`Created GSI: \${gsi.indexName}\`);
1304
+ await waitForTableActive(tblClient, tblName);
1305
+ }
1306
+ return;
1307
+ }
1308
+ if (attempt === 29) throw e;
1309
+ await new Promise((resolve) => setTimeout(resolve, 1000));
1310
+ }
1311
+ }
1312
+ };
1313
+
1314
+ await ensureTable(client, tableName, globalSecondaryIndexes);
1315
+ "
1316
+ `;
1317
+
1318
+ exports[`ts#dynamodb generator > should generate with infra=none then upgrade to infra=dynamodb > packages/my-table/scripts/pull-image.ts 1`] = `
1319
+ "import { spawnSync } from 'child_process';
1320
+
1321
+ const ENGINE = 'docker';
1322
+ const image = process.argv[2];
1323
+
1324
+ const inspect = spawnSync(ENGINE, ['image', 'inspect', image], {
1325
+ stdio: 'ignore',
1326
+ });
1327
+
1328
+ if (inspect.status !== 0) {
1329
+ const pull = spawnSync(ENGINE, ['pull', image], { stdio: 'inherit' });
1330
+ if (pull.status !== 0) {
1331
+ process.exit(pull.status ?? 1);
1332
+ }
1333
+ }
1334
+ "
1335
+ `;
1336
+
1337
+ exports[`ts#dynamodb generator > should generate with infra=none then upgrade to infra=dynamodb > packages/my-table/scripts/start-container.ts 1`] = `
1338
+ "import { spawn, spawnSync } from 'child_process';
1339
+
1340
+ const ENGINE = 'docker';
1341
+
1342
+ const [containerName, image, hostPort] = process.argv.slice(2);
1343
+
1344
+ const containerExists = (): boolean =>
1345
+ spawnSync(ENGINE, ['container', 'inspect', containerName], {
1346
+ stdio: 'ignore',
1347
+ }).status === 0;
1348
+
1349
+ const isRunning = (): boolean => {
1350
+ const result = spawnSync(
1351
+ ENGINE,
1352
+ ['container', 'inspect', '-f', '{{.State.Running}}', containerName],
1353
+ { encoding: 'utf-8' },
1354
+ );
1355
+ return result.status === 0 && result.stdout.trim() === 'true';
1356
+ };
1357
+
1358
+ // Multiple dynamo table packages run serve-local in parallel sharing the same
1359
+ // container. All processes stream logs, but only the one that created the
1360
+ // container stops it on exit — others just detach.
1361
+ let created = false;
1362
+ let runRetries = 0;
1363
+
1364
+ for (;;) {
1365
+ if (containerExists()) {
1366
+ if (!isRunning()) {
1367
+ // Another process may have started it between our inspect and start — container engine start
1368
+ // exits 0 even if the container is already running, so no special-casing needed.
1369
+ const start = spawnSync(ENGINE, ['start', containerName], {
1370
+ stdio: 'inherit',
1371
+ });
1372
+ if (start.status !== 0) process.exit(start.status ?? 1);
1373
+ }
1374
+ break;
1375
+ }
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' });
1382
+ if (create.status === 0) {
1383
+ created = true;
1384
+ break;
1385
+ }
1386
+ if (!containerExists() && ++runRetries > 3) {
1387
+ process.stderr.write(create.stderr);
1388
+ process.exit(create.status ?? 1);
1389
+ }
1390
+ }
1391
+
1392
+ const logs = spawn(ENGINE, ['logs', '-f', containerName], {
1393
+ stdio: ['ignore', 'inherit', 'inherit'],
1394
+ });
1395
+
1396
+ let exiting = false;
1397
+
1398
+ const cleanup = () => {
1399
+ if (exiting) return;
1400
+ exiting = true;
1401
+ if (created) {
1402
+ spawnSync(ENGINE, ['stop', containerName], { stdio: 'ignore' });
1403
+ }
1404
+ logs.kill();
1405
+ process.exit(0);
1406
+ };
1407
+
1408
+ process.on('SIGTERM', cleanup);
1409
+ process.on('SIGINT', cleanup);
1410
+ process.on('SIGHUP', cleanup);
1411
+
1412
+ logs.on('exit', (code) => {
1413
+ if (!exiting) process.exit(code ?? 0);
1414
+ });
1415
+ "
1416
+ `;
1417
+
1418
+ exports[`ts#dynamodb generator > should generate with infra=none then upgrade to infra=dynamodb > packages/my-table/src/client.ts 1`] = `
1419
+ "import { DynamoDBClient } from '@aws-sdk/client-dynamodb';
1420
+ 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';
1429
+
1430
+ let _client: DynamoDBClient | undefined;
1431
+
1432
+ 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({});
1444
+ return _client;
1445
+ };
1446
+
1447
+ type DynamoDBConfig = {
1448
+ tableName: string;
1449
+ };
1450
+
1451
+ let _tableName: string | undefined;
1452
+
1453
+ export const resolveTableName = async (): Promise<string> => {
1454
+ if (process.env.SERVE_LOCAL === 'true') {
1455
+ return LOCAL_TABLE_NAME;
1456
+ }
1457
+ if (_tableName === undefined) {
1458
+ const appId = process.env.RUNTIME_CONFIG_APP_ID;
1459
+ if (!appId) {
1460
+ throw new Error('RUNTIME_CONFIG_APP_ID environment variable is not set');
1461
+ }
1462
+ const config = await getAppConfig<{
1463
+ [key: string]: DynamoDBConfig | undefined;
1464
+ }>('dynamodb', {
1465
+ application: appId,
1466
+ environment: 'default',
1467
+ transform: 'json',
1468
+ });
1469
+ const tableName = config?.[PACKAGE_NAME]?.tableName;
1470
+ if (!tableName) {
1471
+ throw new Error('Could not resolve table name from runtime config');
1472
+ }
1473
+ _tableName = tableName;
1474
+ }
1475
+ return _tableName!;
1476
+ };
1477
+ "
1478
+ `;
1479
+
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
+ exports[`ts#dynamodb generator > should generate with infra=none then upgrade to infra=dynamodb > packages/my-table/src/entities/example.ts 1`] = `
1494
+ "import { Entity } from 'electrodb';
1495
+ import { getDynamoDBClient, resolveTableName } from '../client.js';
1496
+
1497
+ // Example entity - replace with your own entities using ElectroDB
1498
+ // See https://electrodb.dev/en/modeling/entities/ for documentation
1499
+ export const createExampleEntity = async () =>
1500
+ new Entity(
1501
+ {
1502
+ model: {
1503
+ entity: 'example',
1504
+ version: '1',
1505
+ service: 'MyTable',
1506
+ },
1507
+ attributes: {
1508
+ id: {
1509
+ type: 'string',
1510
+ required: true,
1511
+ },
1512
+ createdAt: {
1513
+ type: 'string',
1514
+ required: true,
1515
+ default: () => new Date().toISOString(),
1516
+ readOnly: true,
1517
+ },
1518
+ updatedAt: {
1519
+ type: 'string',
1520
+ required: true,
1521
+ default: () => new Date().toISOString(),
1522
+ watch: '*',
1523
+ set: () => new Date().toISOString(),
1524
+ },
1525
+ },
1526
+ indexes: {
1527
+ primary: {
1528
+ pk: {
1529
+ field: 'pk',
1530
+ composite: ['id'],
1531
+ },
1532
+ sk: {
1533
+ field: 'sk',
1534
+ composite: [],
1535
+ },
1536
+ },
1537
+ },
1538
+ },
1539
+ { client: getDynamoDBClient(), table: await resolveTableName() },
1540
+ );
1541
+ "
1542
+ `;
1543
+
1544
+ exports[`ts#dynamodb generator > should generate with infra=none then upgrade to infra=dynamodb > packages/my-table/src/entities/index.ts 1`] = `
1545
+ "export { createExampleEntity } from './example.js';
1546
+ "
1547
+ `;
1548
+
1549
+ 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';
1559
+ export * from './entities/index.js';
1560
+ "
1561
+ `;