@aws/nx-plugin 0.0.0
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/README.md +63 -0
- package/generators.json +52 -0
- package/package.json +27 -0
- package/src/cloudscape-website/app/README.md +253 -0
- package/src/cloudscape-website/app/__snapshots__/generator.spec.ts.snap +539 -0
- package/src/cloudscape-website/app/files/app/src/config.ts.template +4 -0
- package/src/cloudscape-website/app/files/app/src/layouts/App/index.tsx.template +132 -0
- package/src/cloudscape-website/app/files/app/src/layouts/App/navitems.ts.template +8 -0
- package/src/cloudscape-website/app/files/app/src/layouts/Routes/index.tsx.template +18 -0
- package/src/cloudscape-website/app/files/app/src/main.tsx.template +22 -0
- package/src/cloudscape-website/app/files/app/src/pages/Home/index.tsx.template +25 -0
- package/src/cloudscape-website/app/files/common/constructs/src/__websiteNameKebabCase__/cloudfront-web-acl.ts.template +317 -0
- package/src/cloudscape-website/app/files/common/constructs/src/__websiteNameKebabCase__/index.ts.template +4 -0
- package/src/cloudscape-website/app/files/common/constructs/src/__websiteNameKebabCase__/static-website.ts.template +237 -0
- package/src/cloudscape-website/app/files/common/constructs/src/__websiteNameKebabCase__/webacl_event_handler/index.ts.template +301 -0
- package/src/cloudscape-website/app/files/e2e/cypress/src/e2e/app.cy.ts.template +13 -0
- package/src/cloudscape-website/app/files/e2e/cypress/src/support/app.po.ts.template +1 -0
- package/src/cloudscape-website/app/files/e2e/playwright/src/example.spec.ts.template +6 -0
- package/src/cloudscape-website/app/generator.d.ts +4 -0
- package/src/cloudscape-website/app/generator.js +177 -0
- package/src/cloudscape-website/app/generator.js.map +1 -0
- package/src/cloudscape-website/app/schema.d.js +6 -0
- package/src/cloudscape-website/app/schema.d.js.map +1 -0
- package/src/cloudscape-website/app/schema.d.ts +35 -0
- package/src/cloudscape-website/app/schema.json +189 -0
- package/src/cloudscape-website/cognito-auth/README.md +172 -0
- package/src/cloudscape-website/cognito-auth/__snapshots__/generator.spec.ts.snap +238 -0
- package/src/cloudscape-website/cognito-auth/files/app/components/CognitoAuth/index.tsx.template +50 -0
- package/src/cloudscape-website/cognito-auth/files/common/constructs/src/identity/index.ts.template +4 -0
- package/src/cloudscape-website/cognito-auth/files/common/constructs/src/identity/user-identity.ts.template +69 -0
- package/src/cloudscape-website/cognito-auth/files/common/constructs/src/identity/userpool-with-mfa.ts.template +70 -0
- package/src/cloudscape-website/cognito-auth/generator.d.ts +4 -0
- package/src/cloudscape-website/cognito-auth/generator.js +100 -0
- package/src/cloudscape-website/cognito-auth/generator.js.map +1 -0
- package/src/cloudscape-website/cognito-auth/schema.d.js +6 -0
- package/src/cloudscape-website/cognito-auth/schema.d.js.map +1 -0
- package/src/cloudscape-website/cognito-auth/schema.d.ts +4 -0
- package/src/cloudscape-website/cognito-auth/schema.json +36 -0
- package/src/cloudscape-website/runtime-config/__snapshots__/generator.spec.ts.snap +112 -0
- package/src/cloudscape-website/runtime-config/files/app/components/RuntimeConfig/index.tsx.template +46 -0
- package/src/cloudscape-website/runtime-config/generator.d.ts +4 -0
- package/src/cloudscape-website/runtime-config/generator.js +74 -0
- package/src/cloudscape-website/runtime-config/generator.js.map +1 -0
- package/src/cloudscape-website/runtime-config/schema.d.js +6 -0
- package/src/cloudscape-website/runtime-config/schema.d.js.map +1 -0
- package/src/cloudscape-website/runtime-config/schema.d.ts +3 -0
- package/src/cloudscape-website/runtime-config/schema.json +19 -0
- package/src/gitlab/files/.gitlab-ci.yml.template +26 -0
- package/src/gitlab/generator.d.ts +4 -0
- package/src/gitlab/generator.js +26 -0
- package/src/gitlab/generator.js.map +1 -0
- package/src/gitlab/schema.d.js +6 -0
- package/src/gitlab/schema.d.js.map +1 -0
- package/src/gitlab/schema.d.ts +5 -0
- package/src/gitlab/schema.json +52 -0
- package/src/index.d.ts +0 -0
- package/src/index.js +3 -0
- package/src/index.js.map +1 -0
- package/src/infra/app/README.md +175 -0
- package/src/infra/app/__snapshots__/generator.spec.ts.snap +864 -0
- package/src/infra/app/files/cdk.json +67 -0
- package/src/infra/app/files/src/main.ts.template +37 -0
- package/src/infra/app/files/src/stacks/application-stack.ts.template +10 -0
- package/src/infra/app/generator.d.ts +4 -0
- package/src/infra/app/generator.js +75 -0
- package/src/infra/app/generator.js.map +1 -0
- package/src/infra/app/schema.d.js +6 -0
- package/src/infra/app/schema.d.js.map +1 -0
- package/src/infra/app/schema.d.ts +6 -0
- package/src/infra/app/schema.json +35 -0
- package/src/trpc/backend/README.md +549 -0
- package/src/trpc/backend/__snapshots__/generator.spec.ts.snap +110 -0
- package/src/trpc/backend/files/backend/src/index.ts.template +1 -0
- package/src/trpc/backend/files/backend/src/lambdas/index.ts.template +1 -0
- package/src/trpc/backend/files/backend/src/lambdas/middleware.ts.template +146 -0
- package/src/trpc/backend/files/backend/src/lambdas/router.ts.template +36 -0
- package/src/trpc/backend/files/common/constructs/src/__apiNameKebabCase__/index.ts.template +64 -0
- package/src/trpc/backend/files/schema/src/index.ts.template +7 -0
- package/src/trpc/backend/generator.d.ts +4 -0
- package/src/trpc/backend/generator.js +128 -0
- package/src/trpc/backend/generator.js.map +1 -0
- package/src/trpc/backend/schema.d.js +6 -0
- package/src/trpc/backend/schema.d.js.map +1 -0
- package/src/trpc/backend/schema.d.ts +8 -0
- package/src/trpc/backend/schema.json +44 -0
- package/src/trpc/react/README.md +320 -0
- package/src/trpc/react/__snapshots__/generator.spec.ts.snap +98 -0
- package/src/trpc/react/files/src/components/TRPCClientProvider/index.tsx.template +34 -0
- package/src/trpc/react/files/src/hooks/useTrpc.tsx.template +5 -0
- package/src/trpc/react/generator.d.ts +4 -0
- package/src/trpc/react/generator.js +81 -0
- package/src/trpc/react/generator.js.map +1 -0
- package/src/trpc/react/schema.d.js +6 -0
- package/src/trpc/react/schema.d.js.map +1 -0
- package/src/trpc/react/schema.d.ts +5 -0
- package/src/trpc/react/schema.json +32 -0
- package/src/ts/cjs-to-esm/generator.d.ts +8 -0
- package/src/ts/cjs-to-esm/generator.js +201 -0
- package/src/ts/cjs-to-esm/generator.js.map +1 -0
- package/src/ts/cjs-to-esm/schema.d.js +6 -0
- package/src/ts/cjs-to-esm/schema.d.js.map +1 -0
- package/src/ts/cjs-to-esm/schema.d.ts +5 -0
- package/src/ts/cjs-to-esm/schema.json +28 -0
- package/src/ts/lib/README.md +149 -0
- package/src/ts/lib/__snapshots__/generator.spec.ts.snap +260 -0
- package/src/ts/lib/eslint.d.ts +3 -0
- package/src/ts/lib/eslint.js +41 -0
- package/src/ts/lib/eslint.js.map +1 -0
- package/src/ts/lib/files/src/index.ts.template +3 -0
- package/src/ts/lib/generator.d.ts +21 -0
- package/src/ts/lib/generator.js +61 -0
- package/src/ts/lib/generator.js.map +1 -0
- package/src/ts/lib/schema.d.js +6 -0
- package/src/ts/lib/schema.d.js.map +1 -0
- package/src/ts/lib/schema.d.ts +13 -0
- package/src/ts/lib/schema.json +46 -0
- package/src/ts/lib/ts-project-utils.d.ts +6 -0
- package/src/ts/lib/ts-project-utils.js +107 -0
- package/src/ts/lib/ts-project-utils.js.map +1 -0
- package/src/ts/lib/types.d.ts +10 -0
- package/src/ts/lib/types.js +6 -0
- package/src/ts/lib/types.js.map +1 -0
- package/src/ts/lib/vitest.d.ts +3 -0
- package/src/ts/lib/vitest.js +67 -0
- package/src/ts/lib/vitest.js.map +1 -0
- package/src/utils/files/common/constructs/src/index.ts.template +1 -0
- package/src/utils/files/common/constructs/src/runtime-config/index.ts.template +1 -0
- package/src/utils/files/common/constructs/src/runtime-config/runtime-config.ts.template +33 -0
- package/src/utils/files/common/types/src/index.ts.template +1 -0
- package/src/utils/files/common/types/src/runtime-config.ts.template +13 -0
- package/src/utils/npm-scope.d.ts +7 -0
- package/src/utils/npm-scope.js +37 -0
- package/src/utils/npm-scope.js.map +1 -0
- package/src/utils/paths.d.ts +3 -0
- package/src/utils/paths.js +32 -0
- package/src/utils/paths.js.map +1 -0
- package/src/utils/shared-constructs.d.ts +7 -0
- package/src/utils/shared-constructs.js +72 -0
- package/src/utils/shared-constructs.js.map +1 -0
- package/src/utils/versions.d.ts +31 -0
- package/src/utils/versions.js +49 -0
- package/src/utils/versions.js.map +1 -0
|
@@ -0,0 +1,549 @@
|
|
|
1
|
+
# tRPC Backend Generator
|
|
2
|
+
|
|
3
|
+
## Overview
|
|
4
|
+
This generator creates a new tRPC backend application with AWS CDK infrastructure setup. The generated backend uses AWS Lambda for serverless deployment and includes schema validation using Zod. The codebase is structured using ES Modules (ESM) for modern JavaScript module system compatibility. It sets up a complete type-safe API using tRPC with AWS Lambda integration, AWS X-Ray tracing, and AWS Lambda Powertools for observability.
|
|
5
|
+
|
|
6
|
+
## Usage
|
|
7
|
+
|
|
8
|
+
You can generate a new tRPC backend in two ways:
|
|
9
|
+
|
|
10
|
+
### 1. Using VSCode IDE
|
|
11
|
+
|
|
12
|
+
First, install the NX Console extension for VSCode:
|
|
13
|
+
1. Open VSCode
|
|
14
|
+
2. Go to Extensions (Ctrl+Shift+X / Cmd+Shift+X)
|
|
15
|
+
3. Search for "Nx Console"
|
|
16
|
+
4. Install [Nx Console](https://marketplace.visualstudio.com/items?itemName=nrwl.angular-console)
|
|
17
|
+
|
|
18
|
+
Then generate your API:
|
|
19
|
+
1. Open the NX Console in VSCode
|
|
20
|
+
2. Click on "Generate"
|
|
21
|
+
3. Search for "trpc#backend"
|
|
22
|
+
4. Fill in the required parameters in the form
|
|
23
|
+
5. Click "Run"
|
|
24
|
+
|
|
25
|
+
### 2. Using CLI
|
|
26
|
+
|
|
27
|
+
Generate the API:
|
|
28
|
+
```bash
|
|
29
|
+
nx g @aws/nx-plugin:trpc#backend my-api --apiNamespace=@myorg --directory=apps/api
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
You can also perform a dry-run to see what files would be generated without actually creating them:
|
|
33
|
+
```bash
|
|
34
|
+
nx g @aws/nx-plugin:trpc#backend my-api --apiNamespace=@myorg --directory=apps/api --dry-run
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
Both methods will create a new tRPC backend API in the specified directory with all the necessary configuration and infrastructure code.
|
|
38
|
+
|
|
39
|
+
## Input Parameters
|
|
40
|
+
|
|
41
|
+
| Parameter | Type | Default | Description |
|
|
42
|
+
|-----------|------|---------|-------------|
|
|
43
|
+
| apiName* | string | - | The name of the API (required). Used to generate class names and file paths. |
|
|
44
|
+
| apiNamespace* | string | - | The namespace for the API (required). Must be in the format @scope or @scope/subscore. |
|
|
45
|
+
| directory | string | "packages" | The directory to store the application in. |
|
|
46
|
+
| unitTestRunner | string | "vitest" | Test runner for unit tests. Options: jest, vitest, none |
|
|
47
|
+
|
|
48
|
+
*Required parameter
|
|
49
|
+
|
|
50
|
+
## Expected Output
|
|
51
|
+
|
|
52
|
+
The generator creates three main components:
|
|
53
|
+
|
|
54
|
+
### 1. Backend API Code
|
|
55
|
+
```
|
|
56
|
+
<directory>/<api-name>/backend/
|
|
57
|
+
├── src/
|
|
58
|
+
│ ├── index.ts # Backend entry point with tRPC setup
|
|
59
|
+
│ └── lambdas/ # Lambda function handlers
|
|
60
|
+
│ ├── index.ts # Lambda exports
|
|
61
|
+
│ ├── router.ts # tRPC router definition
|
|
62
|
+
│ └── middleware.ts # Middleware plugins
|
|
63
|
+
├── tsconfig.json # TypeScript configuration
|
|
64
|
+
└── project.json # Project configuration and build targets
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
### 2. Schema Code
|
|
68
|
+
```
|
|
69
|
+
<directory>/<api-name>/schema/
|
|
70
|
+
├── src/
|
|
71
|
+
│ └── index.ts # Shared schema definitions using Zod
|
|
72
|
+
├── tsconfig.json # TypeScript configuration
|
|
73
|
+
└── project.json # Project configuration and build targets
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
### 3. Infrastructure Code
|
|
77
|
+
```
|
|
78
|
+
common/constructs/
|
|
79
|
+
├── src/
|
|
80
|
+
│ ├── <api-name>/ # Infrastructure specific to this API
|
|
81
|
+
│ │ └── index.ts # API infrastructure stack
|
|
82
|
+
│ └── index.ts # Exports for all constructs
|
|
83
|
+
├── tsconfig.json # TypeScript configuration
|
|
84
|
+
└── project.json # Project configuration and build targets
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
Additionally, it:
|
|
88
|
+
1. Configures build settings for production deployment
|
|
89
|
+
2. Installs required dependencies:
|
|
90
|
+
- @trpc/server
|
|
91
|
+
- zod
|
|
92
|
+
- aws-xray-sdk-core
|
|
93
|
+
- aws-cdk-lib
|
|
94
|
+
- constructs
|
|
95
|
+
- @aws-lambda-powertools/logger
|
|
96
|
+
- @aws-lambda-powertools/metrics
|
|
97
|
+
- @aws-lambda-powertools/tracer
|
|
98
|
+
|
|
99
|
+
## Router and Middleware Setup
|
|
100
|
+
|
|
101
|
+
The generator creates a powerful tRPC router setup with integrated middleware for observability and error handling.
|
|
102
|
+
|
|
103
|
+
### Router Configuration
|
|
104
|
+
|
|
105
|
+
The router is configured in `router.ts` with AWS Lambda integration:
|
|
106
|
+
|
|
107
|
+
```typescript
|
|
108
|
+
import { initTRPC } from '@trpc/server';
|
|
109
|
+
import { awsLambdaRequestHandler } from '@trpc/server/adapters/aws-lambda';
|
|
110
|
+
import {
|
|
111
|
+
createErrorPlugin,
|
|
112
|
+
createLoggerPlugin,
|
|
113
|
+
createMetricsPlugin,
|
|
114
|
+
createTracerPlugin,
|
|
115
|
+
IMiddlewareContext,
|
|
116
|
+
} from './middleware.js';
|
|
117
|
+
|
|
118
|
+
// Initialize tRPC with context type
|
|
119
|
+
export type Context = IMiddlewareContext;
|
|
120
|
+
const t = initTRPC.context<Context>().create();
|
|
121
|
+
|
|
122
|
+
// Create base router and procedure which automatically instruments all middleware
|
|
123
|
+
export const router = t.router;
|
|
124
|
+
export const publicProcedure = t.procedure
|
|
125
|
+
.unstable_concat(createLoggerPlugin().loggerPlugin)
|
|
126
|
+
.unstable_concat(createTracerPlugin().tracerPlugin)
|
|
127
|
+
.unstable_concat(createMetricsPlugin().metricsPlugin)
|
|
128
|
+
.unstable_concat(createErrorPlugin().errorPlugin);
|
|
129
|
+
|
|
130
|
+
// Define your procedures here
|
|
131
|
+
const appRouter = router({
|
|
132
|
+
echo: publicProcedure
|
|
133
|
+
.input(z.string())
|
|
134
|
+
.output(EchoSchema)
|
|
135
|
+
.query((opts) => ({ result: opts.input })),
|
|
136
|
+
});
|
|
137
|
+
|
|
138
|
+
// Create Lambda handler
|
|
139
|
+
export const handler = awsLambdaRequestHandler({
|
|
140
|
+
router: appRouter
|
|
141
|
+
});
|
|
142
|
+
|
|
143
|
+
// Import this type in the frontend when setting up your integration
|
|
144
|
+
export type AppRouter = typeof appRouter;
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
### Middleware and Context
|
|
148
|
+
|
|
149
|
+
The generator includes four powerful middleware plugins whcih are automatically instrumented:
|
|
150
|
+
|
|
151
|
+
1. **Logger Plugin**
|
|
152
|
+
- Automatically logs procedure execution
|
|
153
|
+
- Captures errors with detailed context
|
|
154
|
+
- Uses structured logging format
|
|
155
|
+
|
|
156
|
+
2. **Metrics Plugin**
|
|
157
|
+
- Captures cold start metrics
|
|
158
|
+
- Tracks request counts
|
|
159
|
+
- Records success/error metrics
|
|
160
|
+
- Automatically publishes metrics to CloudWatch
|
|
161
|
+
|
|
162
|
+
3. **Tracer Plugin**
|
|
163
|
+
- Integrates with AWS X-Ray
|
|
164
|
+
- Creates subsegments for each procedure
|
|
165
|
+
- Annotates cold starts
|
|
166
|
+
- Adds error metadata automatically
|
|
167
|
+
|
|
168
|
+
4. **Error Plugin**
|
|
169
|
+
- Standardizes error handling
|
|
170
|
+
- Converts internal errors to tRPC errors
|
|
171
|
+
- Maintains error context for debugging
|
|
172
|
+
|
|
173
|
+
### Using Context in Procedures
|
|
174
|
+
|
|
175
|
+
You can access the context in your procedures to utilize the observability tools:
|
|
176
|
+
|
|
177
|
+
```typescript
|
|
178
|
+
const appRouter = router({
|
|
179
|
+
getData: publicProcedure
|
|
180
|
+
.input(z.string())
|
|
181
|
+
.query(async (opts) => {
|
|
182
|
+
// Access logger
|
|
183
|
+
opts.ctx.logger.info('Processing getData request', {
|
|
184
|
+
input: opts.input
|
|
185
|
+
});
|
|
186
|
+
|
|
187
|
+
// Add custom metrics
|
|
188
|
+
opts.ctx.metrics.addMetric('getData.calls', MetricUnit.Count, 1);
|
|
189
|
+
|
|
190
|
+
// Use tracer for subsegments
|
|
191
|
+
return opts.ctx.tracer.captureMethod('getData.process', async () => {
|
|
192
|
+
// Your business logic here
|
|
193
|
+
return { data: 'result' };
|
|
194
|
+
});
|
|
195
|
+
}),
|
|
196
|
+
});
|
|
197
|
+
```
|
|
198
|
+
|
|
199
|
+
## Infrastructure Architecture
|
|
200
|
+
|
|
201
|
+
```mermaid
|
|
202
|
+
graph TD
|
|
203
|
+
subgraph AWS Cloud
|
|
204
|
+
APIGW[API Gateway] --> Lambda[Lambda Functions]
|
|
205
|
+
Lambda --> XRay[X-Ray Tracing]
|
|
206
|
+
Lambda --> CW[CloudWatch Logs]
|
|
207
|
+
Lambda --> Metrics[CloudWatch Metrics]
|
|
208
|
+
end
|
|
209
|
+
```
|
|
210
|
+
|
|
211
|
+
The infrastructure stack deploys:
|
|
212
|
+
1. **API Gateway**
|
|
213
|
+
- HTTP API endpoint
|
|
214
|
+
- Request validation
|
|
215
|
+
- CORS configuration
|
|
216
|
+
|
|
217
|
+
2. **Lambda Functions**
|
|
218
|
+
- Serverless compute
|
|
219
|
+
- Auto-scaling
|
|
220
|
+
- Pay-per-use pricing
|
|
221
|
+
|
|
222
|
+
3. **Observability**
|
|
223
|
+
- X-Ray distributed tracing
|
|
224
|
+
- CloudWatch Logs integration
|
|
225
|
+
- CloudWatch Metrics via Lambda Powertools
|
|
226
|
+
- Structured logging with Lambda Powertools
|
|
227
|
+
|
|
228
|
+
|
|
229
|
+
## Using the Generated CDK Constructs
|
|
230
|
+
|
|
231
|
+
After generating your tRPC backend, you'll find a CDK construct in the `common/constructs` directory. Here's how to use it in your infrastructure:
|
|
232
|
+
|
|
233
|
+
### Basic Usage
|
|
234
|
+
|
|
235
|
+
```typescript
|
|
236
|
+
import { Stack } from 'aws-cdk-lib';
|
|
237
|
+
import { MyApi } from ':my-org/common-constructs';
|
|
238
|
+
import { HttpNoneAuthorizer } from '@aws-cdk-lib/aws-apigatewayv2-authorizers';
|
|
239
|
+
|
|
240
|
+
export class MyStack extends Stack {
|
|
241
|
+
constructor(scope: App, id: string) {
|
|
242
|
+
super(scope, id);
|
|
243
|
+
|
|
244
|
+
// Create the API with no authentication
|
|
245
|
+
const api = new MyApi(this, 'MyApi', {
|
|
246
|
+
defaultAuthorizer: new HttpNoneAuthorizer(),
|
|
247
|
+
allowedOrigins: ['http://localhost:4200']
|
|
248
|
+
});
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
```
|
|
252
|
+
|
|
253
|
+
### With Cognito Authentication
|
|
254
|
+
|
|
255
|
+
```typescript
|
|
256
|
+
import * as cdk from 'aws-cdk-lib';
|
|
257
|
+
import { Construct } from 'constructs';
|
|
258
|
+
import { UserIdentity, MyApi } from ':my-org/common-constructs'
|
|
259
|
+
import { HttpIamAuthorizer } from 'aws-cdk-lib/aws-apigatewayv2-authorizers';
|
|
260
|
+
|
|
261
|
+
export class ApplicationStack extends cdk.Stack {
|
|
262
|
+
constructor(scope: Construct, id: string, props?: cdk.StackProps) {
|
|
263
|
+
super(scope, id, props);
|
|
264
|
+
|
|
265
|
+
const identity = new UserIdentity(this, 'UserIdentity');
|
|
266
|
+
const myapi = new MyApi(this, 'MyApi', {
|
|
267
|
+
defaultAuthorizer: new HttpIamAuthorizer(),
|
|
268
|
+
});
|
|
269
|
+
myapi.grantInvokeAccess(identity.identityPool.authenticatedRole);
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
```
|
|
273
|
+
|
|
274
|
+
### Granting Access to Other Services
|
|
275
|
+
|
|
276
|
+
```typescript
|
|
277
|
+
import { Stack } from 'aws-cdk-lib';
|
|
278
|
+
import { MyApi } from ':my-org/common-constructs';
|
|
279
|
+
import { Role } from '@aws-cdk-lib/aws-iam';
|
|
280
|
+
|
|
281
|
+
export class MyStack extends Stack {
|
|
282
|
+
constructor(scope: App, id: string) {
|
|
283
|
+
super(scope, id);
|
|
284
|
+
|
|
285
|
+
const api = new MyApi(this, 'MyApi', {
|
|
286
|
+
// ... configuration
|
|
287
|
+
});
|
|
288
|
+
|
|
289
|
+
// Grant access to other roles if needed
|
|
290
|
+
const consumerRole = new Role(this, 'ConsumerRole', {
|
|
291
|
+
// ... role configuration
|
|
292
|
+
});
|
|
293
|
+
|
|
294
|
+
api.grantInvokeAccess(consumerRole);
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
```
|
|
298
|
+
|
|
299
|
+
The API URL will be automatically registered in the RuntimeConfig system and can be accessed in your frontend application.
|
|
300
|
+
|
|
301
|
+
## Schema Code and Zod
|
|
302
|
+
|
|
303
|
+
The generator creates a separate schema package that uses [Zod](https://zod.dev), a TypeScript-first schema declaration and validation library. This package can be shared between your backend and frontend code to ensure type safety across your entire application.
|
|
304
|
+
|
|
305
|
+
### Introduction to Zod
|
|
306
|
+
|
|
307
|
+
Zod is a schema declaration and validation library designed specifically for TypeScript. It allows you to:
|
|
308
|
+
- Define schemas with a fluent API
|
|
309
|
+
- Automatically infer TypeScript types from schemas
|
|
310
|
+
- Validate data at runtime
|
|
311
|
+
- Create complex nested schemas
|
|
312
|
+
- Transform data during validation
|
|
313
|
+
|
|
314
|
+
For complete documentation, visit the [Zod documentation](https://zod.dev).
|
|
315
|
+
|
|
316
|
+
### Defining Schemas
|
|
317
|
+
|
|
318
|
+
The generator creates a basic schema structure that you can extend:
|
|
319
|
+
|
|
320
|
+
```typescript
|
|
321
|
+
import { z } from 'zod';
|
|
322
|
+
|
|
323
|
+
// Basic object schema
|
|
324
|
+
export const UserSchema = z.object({
|
|
325
|
+
id: z.string().uuid(),
|
|
326
|
+
email: z.string().email(),
|
|
327
|
+
name: z.string().min(2),
|
|
328
|
+
age: z.number().min(0).optional(),
|
|
329
|
+
});
|
|
330
|
+
|
|
331
|
+
// Infer the TypeScript type
|
|
332
|
+
export type User = z.infer<typeof UserSchema>;
|
|
333
|
+
|
|
334
|
+
// Input schema for creating a user
|
|
335
|
+
export const CreateUserSchema = UserSchema.omit({ id: true });
|
|
336
|
+
|
|
337
|
+
// Input schema for updating a user
|
|
338
|
+
export const UpdateUserSchema = CreateUserSchema.partial();
|
|
339
|
+
|
|
340
|
+
// Response schema for a list of users
|
|
341
|
+
export const UserListSchema = z.array(UserSchema);
|
|
342
|
+
```
|
|
343
|
+
|
|
344
|
+
### Common Schema Patterns
|
|
345
|
+
|
|
346
|
+
#### Nested Objects
|
|
347
|
+
|
|
348
|
+
```typescript
|
|
349
|
+
import { z } from 'zod';
|
|
350
|
+
|
|
351
|
+
export const AddressSchema = z.object({
|
|
352
|
+
street: z.string(),
|
|
353
|
+
city: z.string(),
|
|
354
|
+
country: z.string(),
|
|
355
|
+
postalCode: z.string(),
|
|
356
|
+
});
|
|
357
|
+
|
|
358
|
+
export const CustomerSchema = z.object({
|
|
359
|
+
id: z.string().uuid(),
|
|
360
|
+
name: z.string(),
|
|
361
|
+
address: AddressSchema,
|
|
362
|
+
shippingAddresses: z.array(AddressSchema),
|
|
363
|
+
});
|
|
364
|
+
```
|
|
365
|
+
|
|
366
|
+
#### Enums and Unions
|
|
367
|
+
|
|
368
|
+
```typescript
|
|
369
|
+
import { z } from 'zod';
|
|
370
|
+
|
|
371
|
+
export const OrderStatusSchema = z.enum([
|
|
372
|
+
'pending',
|
|
373
|
+
'processing',
|
|
374
|
+
'shipped',
|
|
375
|
+
'delivered'
|
|
376
|
+
]);
|
|
377
|
+
|
|
378
|
+
export const PaymentMethodSchema = z.union([
|
|
379
|
+
z.object({ type: z.literal('credit_card'), cardNumber: z.string() }),
|
|
380
|
+
z.object({ type: z.literal('paypal'), email: z.string().email() }),
|
|
381
|
+
]);
|
|
382
|
+
|
|
383
|
+
export const OrderSchema = z.object({
|
|
384
|
+
id: z.string().uuid(),
|
|
385
|
+
status: OrderStatusSchema,
|
|
386
|
+
payment: PaymentMethodSchema,
|
|
387
|
+
});
|
|
388
|
+
```
|
|
389
|
+
|
|
390
|
+
#### Request/Response Schemas
|
|
391
|
+
|
|
392
|
+
```typescript
|
|
393
|
+
import { z } from 'zod';
|
|
394
|
+
|
|
395
|
+
// Pagination parameters
|
|
396
|
+
export const PaginationSchema = z.object({
|
|
397
|
+
page: z.number().min(1),
|
|
398
|
+
limit: z.number().min(1).max(100),
|
|
399
|
+
});
|
|
400
|
+
|
|
401
|
+
// Query parameters
|
|
402
|
+
export const SearchParamsSchema = z.object({
|
|
403
|
+
query: z.string().min(1),
|
|
404
|
+
filters: z.record(z.string()).optional(),
|
|
405
|
+
});
|
|
406
|
+
|
|
407
|
+
// API Response wrapper
|
|
408
|
+
export const ApiResponseSchema = <T extends z.ZodType>(dataSchema: T) =>
|
|
409
|
+
z.object({
|
|
410
|
+
success: z.boolean(),
|
|
411
|
+
data: dataSchema,
|
|
412
|
+
error: z.string().optional(),
|
|
413
|
+
metadata: z.record(z.unknown()).optional(),
|
|
414
|
+
});
|
|
415
|
+
|
|
416
|
+
// Usage example
|
|
417
|
+
export const GetUsersResponseSchema = ApiResponseSchema(
|
|
418
|
+
z.object({
|
|
419
|
+
users: z.array(UserSchema),
|
|
420
|
+
total: z.number(),
|
|
421
|
+
})
|
|
422
|
+
);
|
|
423
|
+
```
|
|
424
|
+
|
|
425
|
+
### Using Schemas with tRPC
|
|
426
|
+
|
|
427
|
+
Your schemas can be used directly in your tRPC procedures for input validation and type safety:
|
|
428
|
+
|
|
429
|
+
```typescript
|
|
430
|
+
import { router, publicProcedure } from './router';
|
|
431
|
+
import {
|
|
432
|
+
UserSchema,
|
|
433
|
+
CreateUserSchema,
|
|
434
|
+
UpdateUserSchema,
|
|
435
|
+
SearchParamsSchema
|
|
436
|
+
} from ':my-org/schema';
|
|
437
|
+
|
|
438
|
+
export const userRouter = router({
|
|
439
|
+
// Create user with input validation
|
|
440
|
+
create: publicProcedure
|
|
441
|
+
.input(CreateUserSchema)
|
|
442
|
+
.output(UserSchema)
|
|
443
|
+
.mutation(async (opts) => {
|
|
444
|
+
// Input is fully typed and validated
|
|
445
|
+
const userData = opts.input;
|
|
446
|
+
// ... create user logic
|
|
447
|
+
}),
|
|
448
|
+
|
|
449
|
+
// Search users with pagination
|
|
450
|
+
search: publicProcedure
|
|
451
|
+
.input(SearchParamsSchema.merge(PaginationSchema))
|
|
452
|
+
.output(GetUsersResponseSchema)
|
|
453
|
+
.query(async (opts) => {
|
|
454
|
+
const { query, filters, page, limit } = opts.input;
|
|
455
|
+
// ... search logic
|
|
456
|
+
}),
|
|
457
|
+
|
|
458
|
+
// Update user with partial data
|
|
459
|
+
update: publicProcedure
|
|
460
|
+
.input(z.object({
|
|
461
|
+
id: z.string().uuid(),
|
|
462
|
+
data: UpdateUserSchema,
|
|
463
|
+
}))
|
|
464
|
+
.output(UserSchema)
|
|
465
|
+
.mutation(async (opts) => {
|
|
466
|
+
const { id, data } = opts.input;
|
|
467
|
+
// ... update logic
|
|
468
|
+
}),
|
|
469
|
+
});
|
|
470
|
+
```
|
|
471
|
+
|
|
472
|
+
### Schema Best Practices
|
|
473
|
+
|
|
474
|
+
1. **Keep Schemas Centralized**: Store all schemas in the schema package to ensure they're easily shared between frontend and backend.
|
|
475
|
+
|
|
476
|
+
2. **Use Type Inference**: Let TypeScript infer types from your schemas instead of maintaining separate type definitions:
|
|
477
|
+
```typescript
|
|
478
|
+
// Do this:
|
|
479
|
+
export const UserSchema = z.object({ ... });
|
|
480
|
+
export type User = z.infer<typeof UserSchema>;
|
|
481
|
+
|
|
482
|
+
// Don't do this:
|
|
483
|
+
export interface User { ... }
|
|
484
|
+
export const UserSchema: z.ZodType<User> = z.object({ ... });
|
|
485
|
+
```
|
|
486
|
+
|
|
487
|
+
3. **Compose Schemas**: Build complex schemas by composing simpler ones:
|
|
488
|
+
```typescript
|
|
489
|
+
const BaseUserSchema = z.object({
|
|
490
|
+
email: z.string().email(),
|
|
491
|
+
name: z.string(),
|
|
492
|
+
});
|
|
493
|
+
|
|
494
|
+
const AdminSchema = BaseUserSchema.extend({
|
|
495
|
+
permissions: z.array(z.string()),
|
|
496
|
+
});
|
|
497
|
+
```
|
|
498
|
+
|
|
499
|
+
4. **Version Your Schemas**: When making breaking changes, consider versioning your schemas:
|
|
500
|
+
```typescript
|
|
501
|
+
export const UserSchemaV1 = z.object({ ... });
|
|
502
|
+
export const UserSchemaV2 = UserSchemaV1.extend({ ... });
|
|
503
|
+
```
|
|
504
|
+
|
|
505
|
+
5. **Document Your Schemas**: Add JSDoc comments to explain complex validation rules:
|
|
506
|
+
```typescript
|
|
507
|
+
export const ConfigSchema = z.object({
|
|
508
|
+
/**
|
|
509
|
+
* API key must be in format: prefix_<32 chars>
|
|
510
|
+
* Example: myapp_a1b2c3d4e5f6g7h8i9j0k1l2m3n4o5p6
|
|
511
|
+
*/
|
|
512
|
+
apiKey: z.string().regex(/^[a-z]+_[a-f0-9]{32}$/),
|
|
513
|
+
});
|
|
514
|
+
```
|
|
515
|
+
|
|
516
|
+
## Building the Application
|
|
517
|
+
|
|
518
|
+
To create a production build:
|
|
519
|
+
```bash
|
|
520
|
+
nx build @my-org/my-api
|
|
521
|
+
```
|
|
522
|
+
|
|
523
|
+
All built code is located in the `dist` folder at the root of your workspace. For example:
|
|
524
|
+
- Backend code: `dist/apps/api/my-api/backend`
|
|
525
|
+
- Schema code: `dist/apps/api/my-api/schema`
|
|
526
|
+
|
|
527
|
+
The production build:
|
|
528
|
+
- Bundles Lambda functions for optimal cold start performance
|
|
529
|
+
- Generates TypeScript declaration files
|
|
530
|
+
- Creates source maps for debugging
|
|
531
|
+
- Optimizes dependencies for AWS Lambda environment
|
|
532
|
+
|
|
533
|
+
## Troubleshooting
|
|
534
|
+
|
|
535
|
+
### `SyntaxError: Named export 'ListInferenceProfilesCommand' not found` or `TypeError: import_client_bedrock2.ListInferenceProfilesCommand is not a constructor`
|
|
536
|
+
|
|
537
|
+
If you see this error in a Lambda function related to `@aws-sdk`, it usually means that you are trying to use an operation which does not exist at runtime. This usually occurs when you are using the `@aws-sdk` provided by the Node Runtime. To bundle the required `@aws-sdk`, you can simply specify it in your `NodeJsFunction` as follows:
|
|
538
|
+
|
|
539
|
+
```typescript
|
|
540
|
+
new NodejsFunction(this, 'MyApiHandler', {
|
|
541
|
+
...,
|
|
542
|
+
bundling: {
|
|
543
|
+
bundleAwsSDK: true
|
|
544
|
+
},
|
|
545
|
+
});
|
|
546
|
+
```
|
|
547
|
+
|
|
548
|
+
This will ensure that whichever `@aws-sdk` version you have installed locally will be the one that is used in the Lambda.
|
|
549
|
+
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
|
|
2
|
+
|
|
3
|
+
exports[`trpc backend generator > should generate backend and schema projects > backend-index.ts 1`] = `
|
|
4
|
+
"export * from './lambdas/index.js';
|
|
5
|
+
"
|
|
6
|
+
`;
|
|
7
|
+
|
|
8
|
+
exports[`trpc backend generator > should generate backend and schema projects > schema-index.ts 1`] = `
|
|
9
|
+
"import { z } from 'zod';
|
|
10
|
+
|
|
11
|
+
export const EchoSchema = z.object({
|
|
12
|
+
result: z.string(),
|
|
13
|
+
});
|
|
14
|
+
|
|
15
|
+
export type IEcho = z.TypeOf<typeof EchoSchema>;
|
|
16
|
+
"
|
|
17
|
+
`;
|
|
18
|
+
|
|
19
|
+
exports[`trpc backend generator > should set up shared constructs > shared-constructs.ts 1`] = `
|
|
20
|
+
"import { Construct } from 'constructs';
|
|
21
|
+
import { Runtime, RuntimeFamily } from 'aws-cdk-lib/aws-lambda';
|
|
22
|
+
import { CfnOutput, Duration } from 'aws-cdk-lib';
|
|
23
|
+
import * as url from 'url';
|
|
24
|
+
import {
|
|
25
|
+
CorsHttpMethod,
|
|
26
|
+
HttpApi,
|
|
27
|
+
HttpMethod,
|
|
28
|
+
IHttpRouteAuthorizer,
|
|
29
|
+
} from 'aws-cdk-lib/aws-apigatewayv2';
|
|
30
|
+
import { HttpLambdaIntegration } from 'aws-cdk-lib/aws-apigatewayv2-integrations';
|
|
31
|
+
import { NodejsFunction } from 'aws-cdk-lib/aws-lambda-nodejs';
|
|
32
|
+
import { Effect, IRole, PolicyStatement } from 'aws-cdk-lib/aws-iam';
|
|
33
|
+
import { RuntimeConfig } from '../runtime-config/index.js';
|
|
34
|
+
|
|
35
|
+
export interface TestApiProps {
|
|
36
|
+
readonly defaultAuthorizer: IHttpRouteAuthorizer;
|
|
37
|
+
readonly allowedOrigins?: string[];
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export class TestApi extends Construct {
|
|
41
|
+
public readonly api: HttpApi;
|
|
42
|
+
|
|
43
|
+
constructor(scope: Construct, id: string, props: TestApiProps) {
|
|
44
|
+
super(scope, id);
|
|
45
|
+
|
|
46
|
+
const routerFunction = new NodejsFunction(this, 'TestApiHandler', {
|
|
47
|
+
timeout: Duration.seconds(30),
|
|
48
|
+
runtime: new Runtime('nodejs20.x', RuntimeFamily.NODEJS),
|
|
49
|
+
handler: 'handler',
|
|
50
|
+
entry: url.fileURLToPath(
|
|
51
|
+
new URL(
|
|
52
|
+
'../../../../../apps/test-api/backend/src/lambdas/router.ts',
|
|
53
|
+
import.meta.url
|
|
54
|
+
)
|
|
55
|
+
),
|
|
56
|
+
environment: {
|
|
57
|
+
AWS_CONNECTION_REUSE_ENABLED: '1',
|
|
58
|
+
},
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
this.api = new HttpApi(this, 'TestApi', {
|
|
62
|
+
corsPreflight: {
|
|
63
|
+
allowOrigins: props.allowedOrigins ?? ['*'],
|
|
64
|
+
allowMethods: [CorsHttpMethod.ANY],
|
|
65
|
+
allowHeaders: [
|
|
66
|
+
'authorization',
|
|
67
|
+
'content-type',
|
|
68
|
+
'x-amz-content-sha256',
|
|
69
|
+
'x-amz-date',
|
|
70
|
+
'x-amz-security-token',
|
|
71
|
+
],
|
|
72
|
+
},
|
|
73
|
+
defaultAuthorizer: props.defaultAuthorizer,
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
this.api.addRoutes({
|
|
77
|
+
path: '/{proxy+}',
|
|
78
|
+
methods: [
|
|
79
|
+
HttpMethod.GET,
|
|
80
|
+
HttpMethod.DELETE,
|
|
81
|
+
HttpMethod.POST,
|
|
82
|
+
HttpMethod.PUT,
|
|
83
|
+
HttpMethod.PATCH,
|
|
84
|
+
HttpMethod.HEAD,
|
|
85
|
+
],
|
|
86
|
+
integration: new HttpLambdaIntegration(
|
|
87
|
+
'RouterIntegration',
|
|
88
|
+
routerFunction
|
|
89
|
+
),
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
new CfnOutput(this, 'TestApiUrl', { value: this.api.url! });
|
|
93
|
+
|
|
94
|
+
RuntimeConfig.ensure(this).config.trpcApis = {
|
|
95
|
+
TestApi: this.api.url!,
|
|
96
|
+
};
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
public grantInvokeAccess(role: IRole) {
|
|
100
|
+
role.addToPrincipalPolicy(
|
|
101
|
+
new PolicyStatement({
|
|
102
|
+
effect: Effect.ALLOW,
|
|
103
|
+
actions: ['execute-api:Invoke'],
|
|
104
|
+
resources: [this.api.arnForExecuteApi('*', '/*', '*')],
|
|
105
|
+
})
|
|
106
|
+
);
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
"
|
|
110
|
+
`;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './lambdas/index.js';
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './router.js';
|