@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.
Files changed (89) hide show
  1. package/bin/aws-nx-mcp.js +23 -35
  2. package/docs/guides/astro-docs.mdx +142 -0
  3. package/docs/guides/connection/py-agent-a2a.mdx +105 -0
  4. package/docs/guides/connection/py-agent-mcp.mdx +147 -0
  5. package/docs/guides/connection/react-agui.mdx +240 -0
  6. package/docs/guides/connection/react-fastapi.mdx +1476 -0
  7. package/docs/guides/connection/react-py-agent.mdx +198 -0
  8. package/docs/guides/connection/react-smithy.mdx +1355 -0
  9. package/docs/guides/connection/react-trpc.mdx +447 -0
  10. package/docs/guides/connection/react-ts-agent.mdx +198 -0
  11. package/docs/guides/connection/smithy-rdb.mdx +161 -0
  12. package/docs/guides/connection/trpc-rdb.mdx +127 -0
  13. package/docs/guides/connection/ts-agent-a2a.mdx +106 -0
  14. package/docs/guides/connection/ts-agent-mcp.mdx +144 -0
  15. package/docs/guides/connection/ts-agent-rdb.mdx +141 -0
  16. package/docs/guides/connection/ts-mcp-server-rdb.mdx +135 -0
  17. package/docs/guides/connection.mdx +126 -0
  18. package/docs/guides/docker-bundling.mdx +403 -0
  19. package/docs/guides/fastapi.mdx +509 -0
  20. package/docs/guides/license.mdx +293 -0
  21. package/docs/guides/nx-generator.mdx +588 -0
  22. package/docs/guides/py-agent.mdx +483 -0
  23. package/docs/guides/py-mcp-server.mdx +161 -0
  24. package/docs/guides/python-lambda-function.mdx +207 -0
  25. package/docs/guides/python-project.mdx +228 -0
  26. package/docs/guides/react-website-auth.mdx +216 -0
  27. package/docs/guides/react-website.mdx +421 -0
  28. package/docs/guides/runtime-config.mdx +312 -0
  29. package/docs/guides/terraform-project.mdx +317 -0
  30. package/docs/guides/trpc.mdx +816 -0
  31. package/docs/guides/ts-agent.mdx +436 -0
  32. package/docs/guides/ts-lambda-function.mdx +218 -0
  33. package/docs/guides/ts-mcp-server.mdx +169 -0
  34. package/docs/guides/ts-nx-plugin.mdx +159 -0
  35. package/docs/guides/ts-rdb.mdx +759 -0
  36. package/docs/guides/ts-smithy-api.mdx +661 -0
  37. package/docs/guides/typescript-infrastructure.mdx +408 -0
  38. package/docs/guides/typescript-project.mdx +312 -0
  39. package/docs/guides/workspace.mdx +181 -0
  40. package/docs/snippets/agent/architecture.mdx +72 -0
  41. package/docs/snippets/agent/bedrock-deployment.mdx +172 -0
  42. package/docs/snippets/agent/runtime-arn.mdx +64 -0
  43. package/docs/snippets/api/api-architecture.mdx +93 -0
  44. package/docs/snippets/api/api-choice-note.mdx +6 -0
  45. package/docs/snippets/api/cors-configuration-cdk-note.mdx +25 -0
  46. package/docs/snippets/api/cors-configuration-terraform-note.mdx +28 -0
  47. package/docs/snippets/api/shared-constructs.mdx +38 -0
  48. package/docs/snippets/api/type-safe-api-integrations.mdx +643 -0
  49. package/docs/snippets/api/waf-configuration.mdx +37 -0
  50. package/docs/snippets/connection/a2a-infrastructure.mdx +63 -0
  51. package/docs/snippets/connection/lambda-rdb-ssl-requirements.mdx +40 -0
  52. package/docs/snippets/connection/mcp-server-rdb-ssl-requirements.mdx +35 -0
  53. package/docs/snippets/connection/rdb-api-infrastructure.mdx +72 -0
  54. package/docs/snippets/connection/react-agent-infrastructure.mdx +61 -0
  55. package/docs/snippets/connection/strands-agent-rdb-ssl-requirements.mdx +35 -0
  56. package/docs/snippets/lambda-function/architecture.mdx +36 -0
  57. package/docs/snippets/lambda-function/deploying-your-function.mdx +118 -0
  58. package/docs/snippets/mcp/architecture.mdx +58 -0
  59. package/docs/snippets/mcp/assistant-docs.mdx +10 -0
  60. package/docs/snippets/mcp/bedrock-deployment.mdx +167 -0
  61. package/docs/snippets/mcp/config.mdx +13 -0
  62. package/docs/snippets/mcp/configuration-py.mdx +42 -0
  63. package/docs/snippets/mcp/configuration-ts.mdx +53 -0
  64. package/docs/snippets/mcp/observability.mdx +8 -0
  65. package/docs/snippets/mcp/shared-constructs.mdx +32 -0
  66. package/docs/snippets/pdk-migration/example/01-migrate-api.mdx +602 -0
  67. package/docs/snippets/pdk-migration/example/02-migrate-website.mdx +915 -0
  68. package/docs/snippets/pdk-migration/example/03-migrate-infra.mdx +161 -0
  69. package/docs/snippets/pdk-migration/example/04-deploy.mdx +229 -0
  70. package/docs/snippets/pdk-migration/faq/aws-arch.mdx +17 -0
  71. package/docs/snippets/pdk-migration/faq/cdk-graph.mdx +29 -0
  72. package/docs/snippets/pdk-migration/faq/infrastructure-python-java.mdx +19 -0
  73. package/docs/snippets/pdk-migration/faq/pdk-nag.mdx +15 -0
  74. package/docs/snippets/pdk-migration/faq/pipeline.mdx +15 -0
  75. package/docs/snippets/pdk-migration/faq/type-safe-api.mdx +310 -0
  76. package/docs/snippets/pdk-migration/faq/use-of-projen.mdx +15 -0
  77. package/docs/snippets/prerequisites.mdx +20 -0
  78. package/docs/snippets/required-prerequisites.mdx +12 -0
  79. package/docs/snippets/shared-constructs.mdx +40 -0
  80. package/docs/snippets/tools/acurl.mdx +73 -0
  81. package/docs/snippets/ts-bundle.mdx +14 -0
  82. package/package.json +1 -1
  83. package/src/py/agent/schema.json +1 -1
  84. package/src/py/fast-api/schema.json +2 -2
  85. package/src/py/mcp-server/schema.json +1 -1
  86. package/src/smithy/ts/api/schema.json +2 -2
  87. package/src/trpc/backend/schema.json +2 -2
  88. package/src/ts/agent/schema.json +1 -1
  89. 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
+ :::