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