@aws/nx-plugin-mcp 0.119.0 → 0.121.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/bin/aws-nx-mcp.js +23 -35
- package/docs/guides/astro-docs.mdx +142 -0
- package/docs/guides/connection/py-agent-a2a.mdx +105 -0
- package/docs/guides/connection/py-agent-mcp.mdx +147 -0
- package/docs/guides/connection/react-agui.mdx +240 -0
- package/docs/guides/connection/react-fastapi.mdx +1476 -0
- package/docs/guides/connection/react-py-agent.mdx +198 -0
- package/docs/guides/connection/react-smithy.mdx +1355 -0
- package/docs/guides/connection/react-trpc.mdx +447 -0
- package/docs/guides/connection/react-ts-agent.mdx +198 -0
- package/docs/guides/connection/smithy-rdb.mdx +161 -0
- package/docs/guides/connection/trpc-rdb.mdx +127 -0
- package/docs/guides/connection/ts-agent-a2a.mdx +106 -0
- package/docs/guides/connection/ts-agent-mcp.mdx +144 -0
- package/docs/guides/connection/ts-agent-rdb.mdx +141 -0
- package/docs/guides/connection/ts-mcp-server-rdb.mdx +135 -0
- package/docs/guides/connection.mdx +126 -0
- package/docs/guides/docker-bundling.mdx +403 -0
- package/docs/guides/fastapi.mdx +509 -0
- package/docs/guides/license.mdx +293 -0
- package/docs/guides/nx-generator.mdx +588 -0
- package/docs/guides/py-agent.mdx +483 -0
- package/docs/guides/py-mcp-server.mdx +161 -0
- package/docs/guides/python-lambda-function.mdx +207 -0
- package/docs/guides/python-project.mdx +228 -0
- package/docs/guides/react-website-auth.mdx +216 -0
- package/docs/guides/react-website.mdx +421 -0
- package/docs/guides/runtime-config.mdx +312 -0
- package/docs/guides/terraform-project.mdx +317 -0
- package/docs/guides/trpc.mdx +816 -0
- package/docs/guides/ts-agent.mdx +436 -0
- package/docs/guides/ts-lambda-function.mdx +218 -0
- package/docs/guides/ts-mcp-server.mdx +169 -0
- package/docs/guides/ts-nx-plugin.mdx +159 -0
- package/docs/guides/ts-rdb.mdx +759 -0
- package/docs/guides/ts-smithy-api.mdx +661 -0
- package/docs/guides/typescript-infrastructure.mdx +408 -0
- package/docs/guides/typescript-project.mdx +312 -0
- package/docs/guides/workspace.mdx +181 -0
- package/docs/snippets/agent/architecture.mdx +72 -0
- package/docs/snippets/agent/bedrock-deployment.mdx +172 -0
- package/docs/snippets/agent/runtime-arn.mdx +64 -0
- package/docs/snippets/api/api-architecture.mdx +93 -0
- package/docs/snippets/api/api-choice-note.mdx +6 -0
- package/docs/snippets/api/cors-configuration-cdk-note.mdx +25 -0
- package/docs/snippets/api/cors-configuration-terraform-note.mdx +28 -0
- package/docs/snippets/api/shared-constructs.mdx +38 -0
- package/docs/snippets/api/type-safe-api-integrations.mdx +643 -0
- package/docs/snippets/api/waf-configuration.mdx +37 -0
- package/docs/snippets/connection/a2a-infrastructure.mdx +63 -0
- package/docs/snippets/connection/lambda-rdb-ssl-requirements.mdx +40 -0
- package/docs/snippets/connection/mcp-server-rdb-ssl-requirements.mdx +35 -0
- package/docs/snippets/connection/rdb-api-infrastructure.mdx +72 -0
- package/docs/snippets/connection/react-agent-infrastructure.mdx +61 -0
- package/docs/snippets/connection/strands-agent-rdb-ssl-requirements.mdx +35 -0
- package/docs/snippets/lambda-function/architecture.mdx +36 -0
- package/docs/snippets/lambda-function/deploying-your-function.mdx +118 -0
- package/docs/snippets/mcp/architecture.mdx +58 -0
- package/docs/snippets/mcp/assistant-docs.mdx +10 -0
- package/docs/snippets/mcp/bedrock-deployment.mdx +167 -0
- package/docs/snippets/mcp/config.mdx +13 -0
- package/docs/snippets/mcp/configuration-py.mdx +42 -0
- package/docs/snippets/mcp/configuration-ts.mdx +53 -0
- package/docs/snippets/mcp/observability.mdx +8 -0
- package/docs/snippets/mcp/shared-constructs.mdx +32 -0
- package/docs/snippets/pdk-migration/example/01-migrate-api.mdx +602 -0
- package/docs/snippets/pdk-migration/example/02-migrate-website.mdx +915 -0
- package/docs/snippets/pdk-migration/example/03-migrate-infra.mdx +161 -0
- package/docs/snippets/pdk-migration/example/04-deploy.mdx +229 -0
- package/docs/snippets/pdk-migration/faq/aws-arch.mdx +17 -0
- package/docs/snippets/pdk-migration/faq/cdk-graph.mdx +29 -0
- package/docs/snippets/pdk-migration/faq/infrastructure-python-java.mdx +19 -0
- package/docs/snippets/pdk-migration/faq/pdk-nag.mdx +15 -0
- package/docs/snippets/pdk-migration/faq/pipeline.mdx +15 -0
- package/docs/snippets/pdk-migration/faq/type-safe-api.mdx +310 -0
- package/docs/snippets/pdk-migration/faq/use-of-projen.mdx +15 -0
- package/docs/snippets/prerequisites.mdx +20 -0
- package/docs/snippets/required-prerequisites.mdx +12 -0
- package/docs/snippets/shared-constructs.mdx +40 -0
- package/docs/snippets/tools/acurl.mdx +73 -0
- package/docs/snippets/ts-bundle.mdx +14 -0
- package/package.json +1 -1
- package/src/py/agent/schema.json +1 -1
- package/src/py/fast-api/schema.json +2 -2
- package/src/py/mcp-server/schema.json +1 -1
- package/src/smithy/ts/api/schema.json +2 -2
- package/src/trpc/backend/schema.json +2 -2
- package/src/ts/agent/schema.json +1 -1
- package/src/ts/mcp-server/schema.json +1 -1
|
@@ -0,0 +1,602 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Migrate the API
|
|
3
|
+
---
|
|
4
|
+
import { Steps, Tabs, TabItem } from '@astrojs/starlight/components';
|
|
5
|
+
import CreateNxWorkspaceCommand from '@components/create-nx-workspace-command.astro';
|
|
6
|
+
import RunGenerator from '@components/run-generator.astro';
|
|
7
|
+
import Link from '@components/link.astro';
|
|
8
|
+
import NxCommands from '@components/nx-commands.astro';
|
|
9
|
+
import Drawer from '@components/drawer.astro';
|
|
10
|
+
import InstallCommand from '@components/install-command.astro';
|
|
11
|
+
|
|
12
|
+
The `TypeSafeApiProject` used in the shopping list application made use of:
|
|
13
|
+
|
|
14
|
+
- [Smithy](https://smithy.io/2.0/) as the modelling language
|
|
15
|
+
- TypeScript for implementing operations
|
|
16
|
+
- TypeScript hook generation for integrating with a react website
|
|
17
|
+
|
|
18
|
+
We can therefore use the <Link path="/guides/ts-smithy-api">`ts#smithy-api` generator</Link> to provide equivalent functionality.
|
|
19
|
+
|
|
20
|
+
:::note
|
|
21
|
+
If you used OpenAPI or TypeSpec as your modelling language, or have handlers implemented in Python or Java, please refer to the [FAQ](#frequently-asked-questions) for potential options.
|
|
22
|
+
:::
|
|
23
|
+
|
|
24
|
+
#### Generate a TypeScript Smithy API
|
|
25
|
+
|
|
26
|
+
Run the <Link path="/guides/ts-smithy-api">`ts#smithy-api` generator</Link> to set up your api project in `packages/api`:
|
|
27
|
+
|
|
28
|
+
<RunGenerator generator="ts#smithy-api" noInteractive requiredParameters={{ name: 'api', namespace: 'com.aws', auth: 'IAM' }} />
|
|
29
|
+
|
|
30
|
+
You will notice this generates a `model` project, as well as a `backend` project. The `model` project contains your Smithy model, and `backend` contains your server implementation.
|
|
31
|
+
|
|
32
|
+
The backend uses the [Smithy Server Generator for TypeScript](https://smithy.io/2.0/languages/typescript/ts-ssdk/index.html). We will explore this further below.
|
|
33
|
+
|
|
34
|
+
#### Migrate the Smithy Model
|
|
35
|
+
|
|
36
|
+
Now that we have the basic structure for our Smithy API project, we can migrate the model:
|
|
37
|
+
|
|
38
|
+
<Steps>
|
|
39
|
+
|
|
40
|
+
1. Delete the generated example Smithy files in `packages/api/model/src`
|
|
41
|
+
1. Copy your model from the PDK project's `packages/api/model/src/main/smithy` directory into your new project's `packages/api/model/src` directory.
|
|
42
|
+
1. Update the service name and namespace in `smithy-build.json` to match the PDK application:
|
|
43
|
+
|
|
44
|
+
```json {4}
|
|
45
|
+
// smithy-build.json
|
|
46
|
+
"plugins": {
|
|
47
|
+
"openapi": {
|
|
48
|
+
"service": "com.aws#MyApi",
|
|
49
|
+
...
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
1. Update the service in `main.smithy` to add the `ValidationException` error, which is required when using the Smithy TypeScript Server SDK.
|
|
53
|
+
|
|
54
|
+
```smithy {2, 17}
|
|
55
|
+
// main.smithy
|
|
56
|
+
use smithy.framework#ValidationException
|
|
57
|
+
|
|
58
|
+
/// My Shopping List API
|
|
59
|
+
@restJson1
|
|
60
|
+
service MyApi {
|
|
61
|
+
version: "1.0"
|
|
62
|
+
operations: [
|
|
63
|
+
GetShoppingLists
|
|
64
|
+
PutShoppingList
|
|
65
|
+
DeleteShoppingList
|
|
66
|
+
]
|
|
67
|
+
errors: [
|
|
68
|
+
BadRequestError
|
|
69
|
+
NotAuthorizedError
|
|
70
|
+
InternalFailureError
|
|
71
|
+
ValidationException
|
|
72
|
+
]
|
|
73
|
+
}
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
1. Add an `extensions.smithy` file to `packages/api/model/src` where we will define a trait that provides pagination information to the generated client:
|
|
77
|
+
|
|
78
|
+
```smithy
|
|
79
|
+
// extensions.smithy
|
|
80
|
+
$version: "2"
|
|
81
|
+
namespace com.aws
|
|
82
|
+
|
|
83
|
+
use smithy.openapi#specificationExtension
|
|
84
|
+
|
|
85
|
+
@trait
|
|
86
|
+
@specificationExtension(as: "x-cursor")
|
|
87
|
+
structure cursor {
|
|
88
|
+
inputToken: String
|
|
89
|
+
enabled: Boolean
|
|
90
|
+
}
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
1. Add the new `@cursor` trait to the `GetShoppingLists` operation in `get-shopping-lists.smithy`:
|
|
94
|
+
|
|
95
|
+
```smithy {5}
|
|
96
|
+
// operations/get-shopping-lists.smithy
|
|
97
|
+
@readonly
|
|
98
|
+
@http(method: "GET", uri: "/shopping-list")
|
|
99
|
+
@paginated(inputToken: "nextToken", outputToken: "nextToken", pageSize: "pageSize", items: "shoppingLists")
|
|
100
|
+
@cursor(inputToken: "nextToken")
|
|
101
|
+
@handler(language: "typescript")
|
|
102
|
+
operation GetShoppingLists {
|
|
103
|
+
input := with [PaginatedInputMixin] {
|
|
104
|
+
@httpQuery("shoppingListId")
|
|
105
|
+
shoppingListId: ShoppingListId
|
|
106
|
+
}
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
Any `@paginated` operations should also use `@cursor` if you're using the client generator provided by the Nx Plugin for AWS (via the <Link path="guides/connection/react-smithy">`api-connection`</Link> generator).
|
|
110
|
+
|
|
111
|
+
1. Finally, remove the `@handler` trait from all operations as this isn't supported by the Nx Plugin for AWS. Using `ts#smithy-api`, we don't need the auto-generated lambda function CDK constructs and bundling targets generated by this trait, as we use a single bundle for all lambda functions.
|
|
112
|
+
|
|
113
|
+
</Steps>
|
|
114
|
+
|
|
115
|
+
At this point, let's run a build to check our model changes and ensure we have some generated server code to work with. There will be some failures in the backend project (`@shopping-list/api`) but we'll address those next.
|
|
116
|
+
|
|
117
|
+
<NxCommands commands={["run-many --target build"]} />
|
|
118
|
+
|
|
119
|
+
:::note
|
|
120
|
+
You may see a build failure due to lint issues. These can usually be automatically fixed:
|
|
121
|
+
|
|
122
|
+
<NxCommands commands={["run-many --target lint --fix"]} />
|
|
123
|
+
:::
|
|
124
|
+
|
|
125
|
+
#### Migrate the Lambda Handlers
|
|
126
|
+
|
|
127
|
+
You can consider the `api/backend` project as somewhat equivalent to Type Safe API's `api/handlers/typescript` project.
|
|
128
|
+
|
|
129
|
+
One of the main differences between Type Safe API and the `ts#smithy-api` generator is that handlers are implemented using the [Smithy Server Generator for TypeScript](https://smithy.io/2.0/languages/typescript/ts-ssdk/index.html), rather than Type Safe API's own generated handler wrappers (found in the `api/generated/typescript/runtime` project).
|
|
130
|
+
|
|
131
|
+
The shopping list application's lambda handlers rely on the `@aws-sdk/client-dynamodb` package, so let's install that first:
|
|
132
|
+
|
|
133
|
+
<InstallCommand pkg="@aws-sdk/client-dynamodb" />
|
|
134
|
+
|
|
135
|
+
Then, let's copy the `handlers/src/dynamo-client.ts` file from the PDK project to `backend/src/operations` so it's available for our handlers.
|
|
136
|
+
|
|
137
|
+
To migrate the handlers, you can follow these general steps:
|
|
138
|
+
|
|
139
|
+
<Steps>
|
|
140
|
+
|
|
141
|
+
1. Copy the handler from your PDK project's `packages/api/handlers/typescript/src` directory to your new project's `packages/api/backend/src/operations` directory.
|
|
142
|
+
|
|
143
|
+
1. Remove `my-api-typescript-runtime` imports and instead import the operation type from the generated TypeScript Server SDK, as well as the `ServiceContext` for example:
|
|
144
|
+
|
|
145
|
+
```diff lang="ts" wrap
|
|
146
|
+
- import {
|
|
147
|
+
- deleteShoppingListHandler,
|
|
148
|
+
- DeleteShoppingListChainedHandlerFunction,
|
|
149
|
+
- INTERCEPTORS,
|
|
150
|
+
- Response,
|
|
151
|
+
- LoggingInterceptor,
|
|
152
|
+
- } from 'myapi-typescript-runtime';
|
|
153
|
+
+ import { DeleteShoppingList as DeleteShoppingListOperation } from '../generated/ssdk/index.js';
|
|
154
|
+
+ import { ServiceContext } from '../context.js';
|
|
155
|
+
```
|
|
156
|
+
|
|
157
|
+
1. Delete the handler wrapper export
|
|
158
|
+
|
|
159
|
+
```diff lang="ts" wrap
|
|
160
|
+
- export const handler = deleteShoppingListHandler(
|
|
161
|
+
- ...INTERCEPTORS,
|
|
162
|
+
- deleteShoppingList,
|
|
163
|
+
- );
|
|
164
|
+
```
|
|
165
|
+
|
|
166
|
+
1. Update the signature for your operation handler to use the SSDK:
|
|
167
|
+
|
|
168
|
+
```diff lang="ts" wrap
|
|
169
|
+
- export const deleteShoppingList: DeleteShoppingListChainedHandlerFunction = async (request) => {
|
|
170
|
+
+ export const DeleteShoppingList: DeleteShoppingListOperation<ServiceContext> = async (input, ctx) => {
|
|
171
|
+
```
|
|
172
|
+
|
|
173
|
+
1. Replace usage of the `LoggingInterceptor` with `ctx.logger`. (Also applies to metrics and tracing interceptors):
|
|
174
|
+
|
|
175
|
+
```diff lang="ts" wrap
|
|
176
|
+
- LoggingInterceptor.getLogger(request).info('...');
|
|
177
|
+
+ ctx.logger.info('...');
|
|
178
|
+
```
|
|
179
|
+
|
|
180
|
+
1. Update references to input parameters. Since the SSDK provides types that match your Smithy model exactly (rather than grouping path/query/header parameters separately to the body parameter), update any input references accordingly:
|
|
181
|
+
|
|
182
|
+
```diff lang="ts" wrap
|
|
183
|
+
- const shoppingListId = request.input.requestParameters.shoppingListId;
|
|
184
|
+
+ const shoppingListId = input.shoppingListId;
|
|
185
|
+
```
|
|
186
|
+
|
|
187
|
+
1. Remove use of `Response`. We instead just return plain objects in the SSDK.
|
|
188
|
+
|
|
189
|
+
```diff lang="ts" wrap
|
|
190
|
+
- return Response.success({ shoppingListId });
|
|
191
|
+
+ return { shoppingListId };
|
|
192
|
+
```
|
|
193
|
+
|
|
194
|
+
We also no longer throw or return `Response`, instead we throw the SSDK's generated errors:
|
|
195
|
+
|
|
196
|
+
```diff lang="ts" wrap
|
|
197
|
+
- throw Response.badRequest({ message: 'oh no' });
|
|
198
|
+
- return Response.badRequest({ message: 'oh no' });
|
|
199
|
+
+ import { BadRequestError } from '../generated/ssdk/index.js';
|
|
200
|
+
+ throw new BadRequestError({ message: 'oh no' });
|
|
201
|
+
```
|
|
202
|
+
|
|
203
|
+
1. Update any imports to use ESM syntax, namely adding the `.js` extension to relative imports.
|
|
204
|
+
|
|
205
|
+
1. Add the operation to `service.ts`
|
|
206
|
+
|
|
207
|
+
```ts {4, 12}
|
|
208
|
+
// service.ts
|
|
209
|
+
import { ServiceContext } from './context.js';
|
|
210
|
+
import { MyApiService } from './generated/ssdk/index.js';
|
|
211
|
+
import { DeleteShoppingList } from './operations/delete-shopping-list.js';
|
|
212
|
+
import { GetShoppingLists } from './operations/get-shopping-lists.js';
|
|
213
|
+
import { PutShoppingList } from './operations/put-shopping-list.js';
|
|
214
|
+
|
|
215
|
+
// Register operations to the service here
|
|
216
|
+
export const Service: MyApiService<ServiceContext> = {
|
|
217
|
+
PutShoppingList,
|
|
218
|
+
GetShoppingLists,
|
|
219
|
+
DeleteShoppingList,
|
|
220
|
+
};
|
|
221
|
+
```
|
|
222
|
+
|
|
223
|
+
</Steps>
|
|
224
|
+
|
|
225
|
+
<Drawer title="Shopping List Handler Migration" trigger="Click here for full before/after examples for the three shopping list operations from the tutorial">
|
|
226
|
+
|
|
227
|
+
<h4>Delete Shopping List</h4>
|
|
228
|
+
|
|
229
|
+
<Tabs syncKey="pdk-migration">
|
|
230
|
+
<TabItem label="Before">
|
|
231
|
+
```ts
|
|
232
|
+
// handlers/typescript/src/delete-shopping-list.ts
|
|
233
|
+
import { DeleteItemCommand } from '@aws-sdk/client-dynamodb';
|
|
234
|
+
import {
|
|
235
|
+
deleteShoppingListHandler,
|
|
236
|
+
DeleteShoppingListChainedHandlerFunction,
|
|
237
|
+
INTERCEPTORS,
|
|
238
|
+
Response,
|
|
239
|
+
LoggingInterceptor,
|
|
240
|
+
} from 'myapi-typescript-runtime';
|
|
241
|
+
import { ddbClient } from './dynamo-client';
|
|
242
|
+
|
|
243
|
+
/**
|
|
244
|
+
* Type-safe handler for the DeleteShoppingList operation
|
|
245
|
+
*/
|
|
246
|
+
export const deleteShoppingList: DeleteShoppingListChainedHandlerFunction = async (request) => {
|
|
247
|
+
LoggingInterceptor.getLogger(request).info(
|
|
248
|
+
'Start DeleteShoppingList Operation',
|
|
249
|
+
);
|
|
250
|
+
|
|
251
|
+
const shoppingListId = request.input.requestParameters.shoppingListId;
|
|
252
|
+
await ddbClient.send(
|
|
253
|
+
new DeleteItemCommand({
|
|
254
|
+
TableName: 'shopping_list',
|
|
255
|
+
Key: {
|
|
256
|
+
shoppingListId: {
|
|
257
|
+
S: shoppingListId,
|
|
258
|
+
},
|
|
259
|
+
},
|
|
260
|
+
}),
|
|
261
|
+
);
|
|
262
|
+
|
|
263
|
+
return Response.success({
|
|
264
|
+
shoppingListId,
|
|
265
|
+
});
|
|
266
|
+
};
|
|
267
|
+
|
|
268
|
+
/**
|
|
269
|
+
* Entry point for the AWS Lambda handler for the DeleteShoppingList operation.
|
|
270
|
+
* The deleteShoppingListHandler method wraps the type-safe handler and manages marshalling inputs and outputs
|
|
271
|
+
*/
|
|
272
|
+
export const handler = deleteShoppingListHandler(
|
|
273
|
+
...INTERCEPTORS,
|
|
274
|
+
deleteShoppingList,
|
|
275
|
+
);
|
|
276
|
+
```
|
|
277
|
+
</TabItem>
|
|
278
|
+
<TabItem label="After">
|
|
279
|
+
```ts
|
|
280
|
+
// backend/src/operations/delete-shopping-list.ts
|
|
281
|
+
import { DeleteItemCommand } from '@aws-sdk/client-dynamodb';
|
|
282
|
+
import { ddbClient } from './dynamo-client.js';
|
|
283
|
+
import { DeleteShoppingList as DeleteShoppingListOperation } from '../generated/ssdk/index.js';
|
|
284
|
+
import { ServiceContext } from '../context.js';
|
|
285
|
+
|
|
286
|
+
/**
|
|
287
|
+
* Type-safe handler for the DeleteShoppingList operation
|
|
288
|
+
*/
|
|
289
|
+
export const DeleteShoppingList: DeleteShoppingListOperation<ServiceContext> = async (input, ctx) => {
|
|
290
|
+
ctx.logger.info(
|
|
291
|
+
'Start DeleteShoppingList Operation',
|
|
292
|
+
);
|
|
293
|
+
|
|
294
|
+
const shoppingListId = input.shoppingListId;
|
|
295
|
+
await ddbClient.send(
|
|
296
|
+
new DeleteItemCommand({
|
|
297
|
+
TableName: 'shopping_list',
|
|
298
|
+
Key: {
|
|
299
|
+
shoppingListId: {
|
|
300
|
+
S: shoppingListId!,
|
|
301
|
+
},
|
|
302
|
+
},
|
|
303
|
+
}),
|
|
304
|
+
);
|
|
305
|
+
|
|
306
|
+
return {
|
|
307
|
+
shoppingListId,
|
|
308
|
+
};
|
|
309
|
+
};
|
|
310
|
+
```
|
|
311
|
+
</TabItem>
|
|
312
|
+
</Tabs>
|
|
313
|
+
|
|
314
|
+
<h4>Get Shopping Lists</h4>
|
|
315
|
+
|
|
316
|
+
<Tabs syncKey="pdk-migration">
|
|
317
|
+
<TabItem label="Before">
|
|
318
|
+
```ts
|
|
319
|
+
// handlers/typescript/src/get-shopping-lists.ts
|
|
320
|
+
import { DynamoDBClient, QueryCommand, QueryCommandInput, ScanCommand, ScanCommandInput } from '@aws-sdk/client-dynamodb';
|
|
321
|
+
import {
|
|
322
|
+
getShoppingListsHandler,
|
|
323
|
+
GetShoppingListsChainedHandlerFunction,
|
|
324
|
+
INTERCEPTORS,
|
|
325
|
+
Response,
|
|
326
|
+
LoggingInterceptor,
|
|
327
|
+
ShoppingList,
|
|
328
|
+
} from 'myapi-typescript-runtime';
|
|
329
|
+
import { ddbClient } from './dynamo-client';
|
|
330
|
+
|
|
331
|
+
/**
|
|
332
|
+
* Type-safe handler for the GetShoppingLists operation
|
|
333
|
+
*/
|
|
334
|
+
export const getShoppingLists: GetShoppingListsChainedHandlerFunction = async (request) => {
|
|
335
|
+
LoggingInterceptor.getLogger(request).info('Start GetShoppingLists Operation');
|
|
336
|
+
|
|
337
|
+
const nextToken = request.input.requestParameters.nextToken;
|
|
338
|
+
const pageSize = request.input.requestParameters.pageSize;
|
|
339
|
+
const shoppingListId = request.input.requestParameters.shoppingListId;
|
|
340
|
+
const commandInput: ScanCommandInput | QueryCommandInput = {
|
|
341
|
+
TableName: 'shopping_list',
|
|
342
|
+
ConsistentRead: true,
|
|
343
|
+
Limit: pageSize,
|
|
344
|
+
ExclusiveStartKey: nextToken ? fromToken(nextToken) : undefined,
|
|
345
|
+
...(shoppingListId ? {
|
|
346
|
+
KeyConditionExpression: 'shoppingListId = :shoppingListId',
|
|
347
|
+
ExpressionAttributeValues: {
|
|
348
|
+
':shoppingListId': {
|
|
349
|
+
S: request.input.requestParameters.shoppingListId!,
|
|
350
|
+
},
|
|
351
|
+
},
|
|
352
|
+
} : {}),
|
|
353
|
+
};
|
|
354
|
+
const response = await ddbClient.send(shoppingListId ? new QueryCommand(commandInput) : new ScanCommand(commandInput));
|
|
355
|
+
|
|
356
|
+
return Response.success({
|
|
357
|
+
shoppingLists: (response.Items || [])
|
|
358
|
+
.map<ShoppingList>(item => ({
|
|
359
|
+
shoppingListId: item.shoppingListId.S!,
|
|
360
|
+
name: item.name.S!,
|
|
361
|
+
shoppingItems: JSON.parse(item.shoppingItems.S || '[]'),
|
|
362
|
+
})),
|
|
363
|
+
nextToken: response.LastEvaluatedKey ? toToken(response.LastEvaluatedKey) : undefined,
|
|
364
|
+
});
|
|
365
|
+
};
|
|
366
|
+
|
|
367
|
+
/**
|
|
368
|
+
* Decode a stringified token
|
|
369
|
+
* @param token a token passed to the paginated request
|
|
370
|
+
*/
|
|
371
|
+
const fromToken = <T>(token?: string): T | undefined =>
|
|
372
|
+
token ? (JSON.parse(Buffer.from(decodeURIComponent(token), 'base64').toString()) as T) : undefined;
|
|
373
|
+
|
|
374
|
+
/**
|
|
375
|
+
* Encode pagination details into an opaque stringified token
|
|
376
|
+
* @param paginationToken pagination token details
|
|
377
|
+
*/
|
|
378
|
+
const toToken = <T>(paginationToken?: T): string | undefined =>
|
|
379
|
+
paginationToken ? encodeURIComponent(Buffer.from(JSON.stringify(paginationToken)).toString('base64')) : undefined;
|
|
380
|
+
|
|
381
|
+
/**
|
|
382
|
+
* Entry point for the AWS Lambda handler for the GetShoppingLists operation.
|
|
383
|
+
* The getShoppingListsHandler method wraps the type-safe handler and manages marshalling inputs and outputs
|
|
384
|
+
*/
|
|
385
|
+
export const handler = getShoppingListsHandler(...INTERCEPTORS, getShoppingLists);
|
|
386
|
+
```
|
|
387
|
+
</TabItem>
|
|
388
|
+
<TabItem label="After">
|
|
389
|
+
```ts
|
|
390
|
+
// backend/src/operations/get-shopping-lists.ts
|
|
391
|
+
import { QueryCommand, QueryCommandInput, ScanCommand, ScanCommandInput } from '@aws-sdk/client-dynamodb';
|
|
392
|
+
import { ddbClient } from './dynamo-client.js';
|
|
393
|
+
import { GetShoppingLists as GetShoppingListsOperation, ShoppingList } from '../generated/ssdk/index.js';
|
|
394
|
+
import { ServiceContext } from '../context.js';
|
|
395
|
+
|
|
396
|
+
/**
|
|
397
|
+
* Type-safe handler for the GetShoppingLists operation
|
|
398
|
+
*/
|
|
399
|
+
export const GetShoppingLists: GetShoppingListsOperation<ServiceContext> = async (input, ctx) => {
|
|
400
|
+
ctx.logger.info('Start GetShoppingLists Operation');
|
|
401
|
+
|
|
402
|
+
const nextToken = input.nextToken;
|
|
403
|
+
const pageSize = input.pageSize;
|
|
404
|
+
const shoppingListId = input.shoppingListId;
|
|
405
|
+
const commandInput: ScanCommandInput | QueryCommandInput = {
|
|
406
|
+
TableName: 'shopping_list',
|
|
407
|
+
ConsistentRead: true,
|
|
408
|
+
Limit: pageSize,
|
|
409
|
+
ExclusiveStartKey: nextToken ? fromToken(nextToken) : undefined,
|
|
410
|
+
...(shoppingListId ? {
|
|
411
|
+
KeyConditionExpression: 'shoppingListId = :shoppingListId',
|
|
412
|
+
ExpressionAttributeValues: {
|
|
413
|
+
':shoppingListId': {
|
|
414
|
+
S: input.shoppingListId!,
|
|
415
|
+
},
|
|
416
|
+
},
|
|
417
|
+
} : {}),
|
|
418
|
+
};
|
|
419
|
+
const response = await ddbClient.send(shoppingListId ? new QueryCommand(commandInput) : new ScanCommand(commandInput));
|
|
420
|
+
|
|
421
|
+
return {
|
|
422
|
+
shoppingLists: (response.Items || [])
|
|
423
|
+
.map<ShoppingList>(item => ({
|
|
424
|
+
shoppingListId: item.shoppingListId.S!,
|
|
425
|
+
name: item.name.S!,
|
|
426
|
+
shoppingItems: JSON.parse(item.shoppingItems.S || '[]'),
|
|
427
|
+
})),
|
|
428
|
+
nextToken: response.LastEvaluatedKey ? toToken(response.LastEvaluatedKey) : undefined,
|
|
429
|
+
};
|
|
430
|
+
};
|
|
431
|
+
|
|
432
|
+
/**
|
|
433
|
+
* Decode a stringified token
|
|
434
|
+
* @param token a token passed to the paginated request
|
|
435
|
+
*/
|
|
436
|
+
const fromToken = <T>(token?: string): T | undefined =>
|
|
437
|
+
token ? (JSON.parse(Buffer.from(decodeURIComponent(token), 'base64').toString()) as T) : undefined;
|
|
438
|
+
|
|
439
|
+
/**
|
|
440
|
+
* Encode pagination details into an opaque stringified token
|
|
441
|
+
* @param paginationToken pagination token details
|
|
442
|
+
*/
|
|
443
|
+
const toToken = <T>(paginationToken?: T): string | undefined =>
|
|
444
|
+
paginationToken ? encodeURIComponent(Buffer.from(JSON.stringify(paginationToken)).toString('base64')) : undefined;
|
|
445
|
+
```
|
|
446
|
+
</TabItem>
|
|
447
|
+
</Tabs>
|
|
448
|
+
|
|
449
|
+
<h4>Put Shopping List</h4>
|
|
450
|
+
|
|
451
|
+
<Tabs syncKey="pdk-migration">
|
|
452
|
+
<TabItem label="Before">
|
|
453
|
+
```ts
|
|
454
|
+
// handlers/typescript/src/put-shopping-list.ts
|
|
455
|
+
import { randomUUID } from 'crypto';
|
|
456
|
+
import { DynamoDBClient, PutItemCommand } from '@aws-sdk/client-dynamodb';
|
|
457
|
+
import {
|
|
458
|
+
putShoppingListHandler,
|
|
459
|
+
PutShoppingListChainedHandlerFunction,
|
|
460
|
+
INTERCEPTORS,
|
|
461
|
+
Response,
|
|
462
|
+
LoggingInterceptor,
|
|
463
|
+
} from 'myapi-typescript-runtime';
|
|
464
|
+
import { ddbClient } from './dynamo-client';
|
|
465
|
+
|
|
466
|
+
/**
|
|
467
|
+
* Type-safe handler for the PutShoppingList operation
|
|
468
|
+
*/
|
|
469
|
+
export const putShoppingList: PutShoppingListChainedHandlerFunction = async (request) => {
|
|
470
|
+
LoggingInterceptor.getLogger(request).info('Start PutShoppingList Operation');
|
|
471
|
+
|
|
472
|
+
const shoppingListId = request.input.body.shoppingListId ?? randomUUID();
|
|
473
|
+
await ddbClient.send(new PutItemCommand({
|
|
474
|
+
TableName: 'shopping_list',
|
|
475
|
+
Item: {
|
|
476
|
+
shoppingListId: {
|
|
477
|
+
S: shoppingListId,
|
|
478
|
+
},
|
|
479
|
+
name: {
|
|
480
|
+
S: request.input.body.name,
|
|
481
|
+
},
|
|
482
|
+
shoppingItems: {
|
|
483
|
+
S: JSON.stringify(request.input.body.shoppingItems || []),
|
|
484
|
+
},
|
|
485
|
+
},
|
|
486
|
+
}));
|
|
487
|
+
|
|
488
|
+
return Response.success({
|
|
489
|
+
shoppingListId,
|
|
490
|
+
});
|
|
491
|
+
};
|
|
492
|
+
|
|
493
|
+
/**
|
|
494
|
+
* Entry point for the AWS Lambda handler for the PutShoppingList operation.
|
|
495
|
+
* The putShoppingListHandler method wraps the type-safe handler and manages marshalling inputs and outputs
|
|
496
|
+
*/
|
|
497
|
+
export const handler = putShoppingListHandler(...INTERCEPTORS, putShoppingList);
|
|
498
|
+
```
|
|
499
|
+
</TabItem>
|
|
500
|
+
<TabItem label="After">
|
|
501
|
+
```ts
|
|
502
|
+
// backend/src/operations/put-shopping-list.ts
|
|
503
|
+
import { randomUUID } from 'crypto';
|
|
504
|
+
import { PutItemCommand } from '@aws-sdk/client-dynamodb';
|
|
505
|
+
import { ddbClient } from './dynamo-client.js';
|
|
506
|
+
import { PutShoppingList as PutShoppingListOperation } from '../generated/ssdk/index.js';
|
|
507
|
+
import { ServiceContext } from '../context.js';
|
|
508
|
+
|
|
509
|
+
/**
|
|
510
|
+
* Type-safe handler for the PutShoppingList operation
|
|
511
|
+
*/
|
|
512
|
+
export const PutShoppingList: PutShoppingListOperation<ServiceContext> = async (input, ctx) => {
|
|
513
|
+
ctx.logger.info('Start PutShoppingList Operation');
|
|
514
|
+
|
|
515
|
+
const shoppingListId = input.shoppingListId ?? randomUUID();
|
|
516
|
+
await ddbClient.send(new PutItemCommand({
|
|
517
|
+
TableName: 'shopping_list',
|
|
518
|
+
Item: {
|
|
519
|
+
shoppingListId: {
|
|
520
|
+
S: shoppingListId,
|
|
521
|
+
},
|
|
522
|
+
name: {
|
|
523
|
+
S: input.name!,
|
|
524
|
+
},
|
|
525
|
+
shoppingItems: {
|
|
526
|
+
S: JSON.stringify(input.shoppingItems || []),
|
|
527
|
+
},
|
|
528
|
+
},
|
|
529
|
+
}));
|
|
530
|
+
|
|
531
|
+
return {
|
|
532
|
+
shoppingListId,
|
|
533
|
+
};
|
|
534
|
+
};
|
|
535
|
+
```
|
|
536
|
+
</TabItem>
|
|
537
|
+
</Tabs>
|
|
538
|
+
|
|
539
|
+
</Drawer>
|
|
540
|
+
|
|
541
|
+
We generated the Smithy API project with the name `api` initially as we wanted it to be added to `packages/api` for consistency with the PDK project. Since our Smithy API now defines `service MyApi` instead of `service Api`, we need to update any instances of `getApiServiceHandler` with `getMyApiServiceHandler`.
|
|
542
|
+
|
|
543
|
+
Make this change to `handler.ts`:
|
|
544
|
+
|
|
545
|
+
```diff lang="ts"
|
|
546
|
+
// packages/api/backend/src/handler.ts
|
|
547
|
+
- import { getApiServiceHandler } from './generated/ssdk/index.js';
|
|
548
|
+
+ import { getMyApiServiceHandler } from './generated/ssdk/index.js';
|
|
549
|
+
|
|
550
|
+
process.env.POWERTOOLS_METRICS_NAMESPACE = 'Api';
|
|
551
|
+
process.env.POWERTOOLS_SERVICE_NAME = 'Api';
|
|
552
|
+
|
|
553
|
+
const tracer = new Tracer();
|
|
554
|
+
const logger = new Logger();
|
|
555
|
+
const metrics = new Metrics();
|
|
556
|
+
|
|
557
|
+
- const serviceHandler = getApiServiceHandler(Service);
|
|
558
|
+
+ const serviceHandler = getMyApiServiceHandler(Service);
|
|
559
|
+
```
|
|
560
|
+
|
|
561
|
+
And to `local-server.ts`:
|
|
562
|
+
|
|
563
|
+
```diff lang="ts"
|
|
564
|
+
// packages/api/backend/src/local-server.ts
|
|
565
|
+
-import { getApiServiceHandler } from './generated/ssdk/index.js';
|
|
566
|
+
+import { getMyApiServiceHandler } from './generated/ssdk/index.js';
|
|
567
|
+
|
|
568
|
+
const PORT = 3001;
|
|
569
|
+
|
|
570
|
+
const tracer = new Tracer();
|
|
571
|
+
const logger = new Logger();
|
|
572
|
+
const metrics = new Metrics();
|
|
573
|
+
|
|
574
|
+
-const serviceHandler = getApiServiceHandler(Service);
|
|
575
|
+
+const serviceHandler = getMyApiServiceHandler(Service);
|
|
576
|
+
```
|
|
577
|
+
|
|
578
|
+
Additionally, update `packages/api/backend/project.json` and update `metadata.apiName` to `my-api`:
|
|
579
|
+
|
|
580
|
+
```diff lang="json"
|
|
581
|
+
// packages/api/backend/project.json
|
|
582
|
+
"metadata": {
|
|
583
|
+
"generator": "ts#smithy-api",
|
|
584
|
+
- "apiName": "api",
|
|
585
|
+
+ "apiName": "my-api",
|
|
586
|
+
"auth": "IAM",
|
|
587
|
+
"modelProject": "@shopping-list/api-model",
|
|
588
|
+
"ports": [3001]
|
|
589
|
+
},
|
|
590
|
+
```
|
|
591
|
+
|
|
592
|
+
#### Verify with a Build
|
|
593
|
+
|
|
594
|
+
We can now build the project to check that the migration has worked so far:
|
|
595
|
+
|
|
596
|
+
<NxCommands commands={["run-many --target build"]} />
|
|
597
|
+
|
|
598
|
+
:::note
|
|
599
|
+
You may see a build failure due to lint issues. These can usually be automatically fixed:
|
|
600
|
+
|
|
601
|
+
<NxCommands commands={["run-many --target lint --fix"]} />
|
|
602
|
+
:::
|