@aws/nx-plugin-mcp 1.0.0-rc.96 → 1.0.0-rc.98

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 (72) hide show
  1. package/bin/aws-nx-mcp.js +90 -45
  2. package/docs/get_started/existing-project.mdx +7 -4
  3. package/docs/get_started/quick-start.mdx +58 -5
  4. package/docs/get_started/tutorials/dungeon-game/1.mdx +23 -20
  5. package/docs/get_started/tutorials/dungeon-game/2.mdx +11 -3
  6. package/docs/get_started/tutorials/dungeon-game/3.mdx +4 -0
  7. package/docs/get_started/tutorials/dungeon-game/4.mdx +7 -2
  8. package/docs/get_started/tutorials/dungeon-game/wrap-up.mdx +10 -1
  9. package/docs/guides/agentcore-gateway.mdx +4 -2
  10. package/docs/guides/agentcore-harness.mdx +2 -1
  11. package/docs/guides/astro-docs.mdx +25 -7
  12. package/docs/guides/connection/py-agent-a2a.mdx +2 -0
  13. package/docs/guides/connection/py-agent-dynamodb.mdx +1 -1
  14. package/docs/guides/connection/py-agent-gateway.mdx +3 -0
  15. package/docs/guides/connection/py-agent-mcp.mdx +18 -4
  16. package/docs/guides/connection/py-agent-rdb.mdx +5 -4
  17. package/docs/guides/connection/py-fast-api-dynamodb.mdx +1 -1
  18. package/docs/guides/connection/py-fast-api-rdb.mdx +7 -2
  19. package/docs/guides/connection/py-mcp-server-dynamodb.mdx +1 -1
  20. package/docs/guides/connection/py-mcp-server-rdb.mdx +6 -6
  21. package/docs/guides/connection/react-agui.mdx +34 -25
  22. package/docs/guides/connection/react-fastapi.mdx +114 -116
  23. package/docs/guides/connection/react-py-agent.mdx +4 -0
  24. package/docs/guides/connection/react-smithy.mdx +152 -98
  25. package/docs/guides/connection/react-trpc.mdx +13 -6
  26. package/docs/guides/connection/smithy-dynamodb.mdx +2 -8
  27. package/docs/guides/connection/smithy-rdb.mdx +3 -6
  28. package/docs/guides/connection/trpc-rdb.mdx +6 -6
  29. package/docs/guides/connection/ts-agent-a2a.mdx +4 -4
  30. package/docs/guides/connection/ts-agent-dynamodb.mdx +13 -11
  31. package/docs/guides/connection/ts-agent-gateway.mdx +2 -0
  32. package/docs/guides/connection/ts-agent-mcp.mdx +19 -7
  33. package/docs/guides/connection/ts-agent-rdb.mdx +2 -2
  34. package/docs/guides/connection/ts-mcp-server-dynamodb.mdx +11 -7
  35. package/docs/guides/connection/ts-mcp-server-rdb.mdx +4 -3
  36. package/docs/guides/docker-bundling.mdx +23 -3
  37. package/docs/guides/fastapi.mdx +16 -5
  38. package/docs/guides/license.mdx +30 -6
  39. package/docs/guides/nx-generator.mdx +11 -12
  40. package/docs/guides/py-agent.mdx +129 -54
  41. package/docs/guides/py-mcp-server.mdx +3 -1
  42. package/docs/guides/py-rdb.mdx +13 -4
  43. package/docs/guides/python-lambda-function.mdx +8 -8
  44. package/docs/guides/python-project.mdx +28 -25
  45. package/docs/guides/react-website-auth.mdx +8 -8
  46. package/docs/guides/react-website.mdx +46 -27
  47. package/docs/guides/runtime-config.mdx +24 -4
  48. package/docs/guides/security.mdx +1 -1
  49. package/docs/guides/terraform-project.mdx +8 -2
  50. package/docs/guides/trpc.mdx +96 -12
  51. package/docs/guides/ts-agent.mdx +17 -3
  52. package/docs/guides/ts-dcr-proxy.mdx +24 -6
  53. package/docs/guides/ts-lambda-function.mdx +7 -1
  54. package/docs/guides/ts-mcp-server.mdx +45 -15
  55. package/docs/guides/ts-nx-plugin.mdx +17 -7
  56. package/docs/guides/ts-rdb.mdx +9 -2
  57. package/docs/guides/ts-smithy-api.mdx +76 -7
  58. package/docs/guides/typescript-infrastructure.mdx +27 -11
  59. package/docs/guides/typescript-project.mdx +12 -5
  60. package/docs/guides/workspace.mdx +21 -9
  61. package/docs/snippets/api/type-safe-api-integrations.mdx +2 -0
  62. package/docs/snippets/connection/a2a-infrastructure.mdx +3 -0
  63. package/docs/snippets/connection/infra-project-prerequisite.mdx +8 -0
  64. package/docs/snippets/connection/strands-agent-rdb-ssl-requirements.mdx +1 -1
  65. package/docs/snippets/connection/ts-lambda-rdb-ssl-requirements.mdx +1 -1
  66. package/docs/snippets/connection/ts-mcp-server-rdb-ssl-requirements.mdx +1 -1
  67. package/docs/snippets/pdk-migration/faq/type-safe-api.mdx +1 -1
  68. package/docs/snippets/prerequisites.mdx +1 -1
  69. package/docs/snippets/required-prerequisites.mdx +1 -1
  70. package/package.json +1 -1
  71. package/src/init/schema.json +5 -0
  72. package/src/py/project/schema.json +3 -1
@@ -125,8 +125,8 @@ For more fine-grained control, you can use the `watch-generate:<ApiName>-client`
125
125
  />
126
126
  :::
127
127
 
128
- :::warning[File Watcher Dependency]
129
- `watch-generate:<ApiName>-client` relies on the [`nx watch`](https://nx.dev/docs/guides/tasks--caching/workspace-watching) command, which requires the [Nx Daemon](https://nx.dev/docs/concepts/nx-daemon) to be running. Therefore if you have disabled the daemon, the client won't automatically regenerate when making changes to your FastAPI.
128
+ :::note[The dev target requires the Nx Daemon]
129
+ The `dev` target hot-reloads local dev servers. Many of these rely on [`nx watch`](https://nx.dev/docs/guides/tasks--caching/workspace-watching) to achieve this, which requires the [Nx Daemon](https://nx.dev/docs/concepts/nx-daemon) to be enabled.
130
130
  :::
131
131
 
132
132
  ### Using the API Hook
@@ -146,13 +146,15 @@ function MyComponent() {
146
146
  const api = useMyApi();
147
147
  const item = useQuery(api.getItem.queryOptions({ itemId: 'some-id' }));
148
148
 
149
- if (item.isLoading) return <div>Loading...</div>;
150
- if (item.isError) return <div>Error: {item.error.message}</div>;
149
+ if (item.isPending) return <div>Loading...</div>;
150
+ if (item.isError) return <div>Error: {JSON.stringify(item.error.error)}</div>;
151
151
 
152
152
  return <div>Item: {item.data.name}</div>;
153
153
  }
154
154
  ```
155
155
 
156
+ The error is a discriminated union of `{ status, error }` objects rather than an `Error`, so there is no top-level `message` to render. Narrow on `status` to read a specific response body — see [Error Handling](#error-handling) below.
157
+
156
158
  <Drawer title="Using the API client directly" trigger="Click here for an example using the vanilla client directly.">
157
159
  ```tsx {5,13}
158
160
  import { useState, useEffect } from 'react';
@@ -222,7 +224,7 @@ function CreateItemForm() {
222
224
 
223
225
  {createItem.isError && (
224
226
  <div className="error">
225
- Error: {createItem.error.message}
227
+ Error: {JSON.stringify(createItem.error.error)}
226
228
  </div>
227
229
  )}
228
230
  </form>
@@ -248,7 +250,7 @@ const createItem = useMutation({
248
250
  onSettled: () => {
249
251
  // This will run when the mutation completes (success or error)
250
252
  // Good place to invalidate queries that might be affected
251
- queryClient.invalidateQueries({ queryKey: api.listItems.queryKey() });
253
+ queryClient.invalidateQueries({ queryKey: api.listItems.queryKey({}) });
252
254
  }
253
255
  });
254
256
  ```
@@ -370,12 +372,12 @@ function ItemList() {
370
372
  }),
371
373
  });
372
374
 
373
- if (items.isLoading) {
375
+ if (items.isPending) {
374
376
  return <LoadingSpinner />;
375
377
  }
376
378
 
377
379
  if (items.isError) {
378
- return <ErrorMessage message={items.error.message} />;
380
+ return <ErrorMessage message={JSON.stringify(items.error.error)} />;
379
381
  }
380
382
 
381
383
  return (
@@ -495,7 +497,9 @@ function ItemList() {
495
497
 
496
498
  ### Error Handling
497
499
 
498
- The integration includes built-in error handling with typed error responses. An `<operation-name>Error` type is generated which encapsulates the possible error responses defined in the OpenAPI specification. Each error has a `status` and `error` property, and by checking the value of `status` you can narrow to a specific type of error.
500
+ The integration includes built-in error handling with typed error responses. An `<OperationName>Error` type is generated which is a union of one `<OperationName><StatusCode>Error` member per error status the operation's `responses` declares. Each member has a `status` and an `error` property, so switching on `status` narrows `error` to that status's model.
501
+
502
+ The example below assumes `create_item` declares the [`responses` defined further down this page](#specifying-response-models) — a `400` `ValidationErrorDetails`, a `403` `str` and a `500` `ErrorDetails`. Only the statuses you declare appear in the union (plus FastAPI's own `422`), so a `case` for a status you haven't declared is a compile error.
499
503
 
500
504
  ```tsx {12}
501
505
  import { useMutation } from '@tanstack/react-query';
@@ -511,34 +515,32 @@ function MyComponent() {
511
515
  if (createItem.error) {
512
516
  switch (createItem.error.status) {
513
517
  case 400:
514
- // error.error is typed as CreateItem400Response
518
+ // error.error is typed as ValidationErrorDetails
515
519
  return (
516
520
  <div>
517
521
  <h2>Invalid input:</h2>
518
522
  <p>{createItem.error.error.message}</p>
519
523
  <ul>
520
- {createItem.error.error.validationErrors.map((err) => (
521
- <li key={err.field}>{err.message}</li>
524
+ {createItem.error.error.fieldErrors.map((err) => (
525
+ <li key={err}>{err}</li>
522
526
  ))}
523
527
  </ul>
524
528
  </div>
525
529
  );
526
530
  case 403:
527
- // error.error is typed as CreateItem403Response
531
+ // error.error is a string as specified in the responses
528
532
  return (
529
533
  <div>
530
534
  <h2>Not authorized:</h2>
531
- <p>{createItem.error.error.reason}</p>
535
+ <p>{createItem.error.error}</p>
532
536
  </div>
533
537
  );
534
538
  case 500:
535
- case 502:
536
- // error.error is typed as CreateItem5XXResponse
539
+ // error.error is typed as ErrorDetails
537
540
  return (
538
541
  <div>
539
542
  <h2>Server error:</h2>
540
543
  <p>{createItem.error.error.message}</p>
541
- <p>Trace ID: {createItem.error.error.traceId}</p>
542
544
  </div>
543
545
  );
544
546
  }
@@ -548,6 +550,10 @@ function MyComponent() {
548
550
  }
549
551
  ```
550
552
 
553
+ :::note[Python field names]
554
+ The generated client camelCases model fields, so a Pydantic model declaring `field_errors` is read as `error.fieldErrors`.
555
+ :::
556
+
551
557
  <Drawer title="Error handling using the API client directly" trigger="Click here for an example using the vanilla client directly.">
552
558
  ```tsx {9,15}
553
559
  function MyComponent() {
@@ -566,34 +572,32 @@ function MyComponent() {
566
572
  if (error) {
567
573
  switch (error.status) {
568
574
  case 400:
569
- // error.error is typed as CreateItem400Response
575
+ // error.error is typed as ValidationErrorDetails
570
576
  return (
571
577
  <div>
572
578
  <h2>Invalid input:</h2>
573
579
  <p>{error.error.message}</p>
574
580
  <ul>
575
- {error.error.validationErrors.map((err) => (
576
- <li key={err.field}>{err.message}</li>
581
+ {error.error.fieldErrors.map((err) => (
582
+ <li key={err}>{err}</li>
577
583
  ))}
578
584
  </ul>
579
585
  </div>
580
586
  );
581
587
  case 403:
582
- // error.error is typed as CreateItem403Response
588
+ // error.error is a string as specified in the responses
583
589
  return (
584
590
  <div>
585
591
  <h2>Not authorized:</h2>
586
- <p>{error.error.reason}</p>
592
+ <p>{error.error}</p>
587
593
  </div>
588
594
  );
589
595
  case 500:
590
- case 502:
591
- // error.error is typed as CreateItem5XXResponse
596
+ // error.error is typed as ErrorDetails
592
597
  return (
593
598
  <div>
594
599
  <h2>Server error:</h2>
595
600
  <p>{error.error.message}</p>
596
- <p>Trace ID: {error.error.traceId}</p>
597
601
  </div>
598
602
  );
599
603
  }
@@ -784,6 +788,8 @@ def list_items(page: int = 1, limit: int = 10):
784
788
 
785
789
  The generated hooks and client methods are automatically organized based on the OpenAPI tags in your FastAPI endpoints. This helps keep your API calls organized and makes it easier to find related operations.
786
790
 
791
+ Within a group each operation keeps its own camelCased name, taken from your endpoint function's name — so `def list_items()` tagged `"items"` is reached at `api.items.listItems`.
792
+
787
793
  For example:
788
794
 
789
795
  ```python title="items.py"
@@ -791,7 +797,7 @@ For example:
791
797
  "/items",
792
798
  tags=["items"],
793
799
  )
794
- def list():
800
+ def list(cursor: str | None = None):
795
801
  # ...
796
802
 
797
803
  @app.post(
@@ -811,6 +817,10 @@ def list():
811
817
  # ...
812
818
  ```
813
819
 
820
+ :::tip[Repeated function names]
821
+ Two endpoints named `list` in different tags don't clash — the generator qualifies a duplicated name with its tag, so both are reachable at `api.items.list` and `api.users.list`.
822
+ :::
823
+
814
824
  The generated hooks will be grouped by these tags:
815
825
 
816
826
  ```tsx
@@ -821,7 +831,7 @@ function ItemsAndUsers() {
821
831
  const api = useMyApi();
822
832
 
823
833
  // Items operations are grouped under api.items
824
- const items = useQuery(api.items.list.queryOptions());
834
+ const items = useQuery(api.items.list.queryOptions({}));
825
835
  const createItem = useMutation(api.items.create.mutationOptions());
826
836
 
827
837
  // Users operations are grouped under api.users
@@ -873,7 +883,7 @@ function ItemsAndUsers() {
873
883
  setIsLoading(true);
874
884
 
875
885
  // Items operations are grouped under api.items
876
- const itemsData = await api.items.list();
886
+ const itemsData = await api.items.list({});
877
887
  setItems(itemsData);
878
888
 
879
889
  // Users operations are grouped under api.users
@@ -943,7 +953,7 @@ from pydantic import BaseModel
943
953
  class ErrorDetails(BaseModel):
944
954
  message: str
945
955
 
946
- class ValidationError(BaseModel):
956
+ class ValidationErrorDetails(BaseModel):
947
957
  message: str
948
958
  field_errors: list[str]
949
959
  ```
@@ -958,7 +968,7 @@ class NotFoundException(Exception):
958
968
  self.message = message
959
969
 
960
970
  class ValidationException(Exception):
961
- def __init__(self, details: ValidationError):
971
+ def __init__(self, details: ValidationErrorDetails):
962
972
  self.details = details
963
973
  ```
964
974
 
@@ -997,8 +1007,8 @@ Finally, specify the response models for different error status codes in your en
997
1007
  @app.get(
998
1008
  "/items/{item_id}",
999
1009
  responses={
1000
- 404: {"model": str}
1001
- 500: {"model": ErrorDetails}
1010
+ 404: {"model": str},
1011
+ 500: {"model": ErrorDetails},
1002
1012
  }
1003
1013
  )
1004
1014
  def get_item(item_id: str) -> Item:
@@ -1010,14 +1020,15 @@ def get_item(item_id: str) -> Item:
1010
1020
  @app.post(
1011
1021
  "/items",
1012
1022
  responses={
1013
- 400: {"model": ValidationError},
1014
- 403: {"model": str}
1023
+ 400: {"model": ValidationErrorDetails},
1024
+ 403: {"model": str},
1025
+ 500: {"model": ErrorDetails},
1015
1026
  }
1016
1027
  )
1017
1028
  def create_item(item: Item) -> Item:
1018
1029
  if not is_valid(item):
1019
1030
  raise ValidationException(
1020
- ValidationError(
1031
+ ValidationErrorDetails(
1021
1032
  message="Invalid item data",
1022
1033
  field_errors=["name is required"]
1023
1034
  )
@@ -1035,33 +1046,18 @@ import { useMutation, useQuery } from '@tanstack/react-query';
1035
1046
  function ItemComponent() {
1036
1047
  const api = useMyApi();
1037
1048
 
1038
- // Query with typed error handling
1039
- const getItem = useQuery({
1040
- ...api.getItem.queryOptions({ itemId: '123' }),
1041
- onError: (error) => {
1042
- // Error is typed based on the responses in your FastAPI
1043
- switch (error.status) {
1044
- case 404:
1045
- // error.error is a string as specified in the responses
1046
- console.error('Not found:', error.error);
1047
- break;
1048
- case 500:
1049
- // error.error is typed as ErrorDetails
1050
- console.error('Server error:', error.error.message);
1051
- break;
1052
- }
1053
- }
1054
- });
1049
+ const getItem = useQuery(api.getItem.queryOptions({ itemId: '123' }));
1055
1050
 
1056
1051
  // Mutation with typed error handling
1057
1052
  const createItem = useMutation({
1058
1053
  ...api.createItem.mutationOptions(),
1059
1054
  onError: (error) => {
1055
+ // Error is typed based on the responses in your FastAPI
1060
1056
  switch (error.status) {
1061
1057
  case 400:
1062
- // error.error is typed as ValidationError
1058
+ // error.error is typed as ValidationErrorDetails
1063
1059
  console.error('Validation error:', error.error.message);
1064
- console.error('Field errors:', error.error.field_errors);
1060
+ console.error('Field errors:', error.error.fieldErrors);
1065
1061
  break;
1066
1062
  case 403:
1067
1063
  // error.error is a string as specified in the responses
@@ -1073,10 +1069,13 @@ function ItemComponent() {
1073
1069
 
1074
1070
  // Component rendering with error handling
1075
1071
  if (getItem.isError) {
1076
- if (getItem.error.status === 404) {
1077
- return <NotFoundMessage message={getItem.error.error} />;
1078
- } else {
1079
- return <ErrorMessage message={getItem.error.error.message} />;
1072
+ switch (getItem.error.status) {
1073
+ case 404:
1074
+ // error.error is a string as specified in the responses
1075
+ return <NotFoundMessage message={getItem.error.error} />;
1076
+ case 500:
1077
+ // error.error is typed as ErrorDetails
1078
+ return <ErrorMessage message={getItem.error.error.message} />;
1080
1079
  }
1081
1080
  }
1082
1081
 
@@ -1137,9 +1136,9 @@ function ItemComponent() {
1137
1136
 
1138
1137
  switch (err.status) {
1139
1138
  case 400:
1140
- // err.error is typed as ValidationError
1139
+ // err.error is typed as ValidationErrorDetails
1141
1140
  console.error('Validation error:', err.error.message);
1142
- console.error('Field errors:', err.error.field_errors);
1141
+ console.error('Field errors:', err.error.fieldErrors);
1143
1142
  break;
1144
1143
  case 403:
1145
1144
  // err.error is a string as specified in the responses
@@ -1186,7 +1185,7 @@ import { useQuery } from '@tanstack/react-query';
1186
1185
 
1187
1186
  function ItemList() {
1188
1187
  const api = useMyApi();
1189
- const items = useQuery(api.listItems.queryOptions());
1188
+ const items = useQuery(api.listItems.queryOptions({}));
1190
1189
 
1191
1190
  if (items.isLoading) {
1192
1191
  return <LoadingSpinner />;
@@ -1196,17 +1195,11 @@ function ItemList() {
1196
1195
  const err = items.error;
1197
1196
  switch (err.status) {
1198
1197
  case 403:
1199
- // err.error is typed as ListItems403Response
1200
- return <ErrorMessage message={err.error.reason} />;
1198
+ // err.error is a string as specified in the responses
1199
+ return <ErrorMessage message={err.error} />;
1201
1200
  case 500:
1202
- case 502:
1203
- // err.error is typed as ListItems5XXResponse
1204
- return (
1205
- <ErrorMessage
1206
- message={err.error.message}
1207
- details={`Trace ID: ${err.error.traceId}`}
1208
- />
1209
- );
1201
+ // err.error is typed as ErrorDetails
1202
+ return <ErrorMessage message={err.error.message} />;
1210
1203
  default:
1211
1204
  return <ErrorMessage message="An unknown error occurred" />;
1212
1205
  }
@@ -1214,7 +1207,7 @@ function ItemList() {
1214
1207
 
1215
1208
  return (
1216
1209
  <ul>
1217
- {items.data.map((item) => (
1210
+ {items.data?.items.map((item) => (
1218
1211
  <li key={item.id}>{item.name}</li>
1219
1212
  ))}
1220
1213
  </ul>
@@ -1233,7 +1226,7 @@ function ItemList() {
1233
1226
  useEffect(() => {
1234
1227
  const fetchItems = async () => {
1235
1228
  try {
1236
- const data = await api.listItems();
1229
+ const data = await api.listItems({});
1237
1230
  setItems(data);
1238
1231
  } catch (err) {
1239
1232
  setError(err);
@@ -1252,17 +1245,11 @@ function ItemList() {
1252
1245
  const err = error as ListItemsError;
1253
1246
  switch (err.status) {
1254
1247
  case 403:
1255
- // err.error is typed as ListItems403Response
1256
- return <ErrorMessage message={err.error.reason} />;
1248
+ // err.error is a string as specified in the responses
1249
+ return <ErrorMessage message={err.error} />;
1257
1250
  case 500:
1258
- case 502:
1259
- // err.error is typed as ListItems5XXResponse
1260
- return (
1261
- <ErrorMessage
1262
- message={err.error.message}
1263
- details={`Trace ID: ${err.error.traceId}`}
1264
- />
1265
- );
1251
+ // err.error is typed as ErrorDetails
1252
+ return <ErrorMessage message={err.error.message} />;
1266
1253
  default:
1267
1254
  return <ErrorMessage message="An unknown error occurred" />;
1268
1255
  }
@@ -1285,41 +1272,53 @@ Implement optimistic updates for a better user experience:
1285
1272
 
1286
1273
  ```tsx
1287
1274
  import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';
1275
+ import type { ListItemsOutput } from '../generated/my-api/types.gen';
1288
1276
 
1289
1277
  function ItemList() {
1290
1278
  const api = useMyApi();
1291
1279
  const queryClient = useQueryClient();
1292
1280
 
1293
1281
  // Query to fetch items
1294
- const itemsQuery = useQuery(api.listItems.queryOptions());
1282
+ const itemsQuery = useQuery(api.listItems.queryOptions({}));
1295
1283
 
1296
1284
  // Mutation for deleting items with optimistic updates
1297
1285
  const deleteMutation = useMutation({
1298
1286
  ...api.deleteItem.mutationOptions(),
1299
- onMutate: async (itemId) => {
1287
+ onMutate: async ({ itemId }) => {
1300
1288
  // Cancel any outgoing refetches
1301
- await queryClient.cancelQueries({ queryKey: api.listItems.queryKey() });
1289
+ await queryClient.cancelQueries({
1290
+ queryKey: api.listItems.queryKey({}),
1291
+ });
1302
1292
 
1303
1293
  // Snapshot the previous value
1304
- const previousItems = queryClient.getQueryData(api.listItems.queryKey());
1294
+ const previousItems = queryClient.getQueryData<ListItemsOutput>(
1295
+ api.listItems.queryKey({}),
1296
+ );
1305
1297
 
1306
1298
  // Optimistically update to the new value
1307
- queryClient.setQueryData(
1308
- api.listItems.queryKey(),
1309
- (old) => old.filter((item) => item.id !== itemId)
1299
+ queryClient.setQueryData<ListItemsOutput>(
1300
+ api.listItems.queryKey({}),
1301
+ (old) =>
1302
+ old && {
1303
+ ...old,
1304
+ items: old.items.filter((item) => item.id !== itemId),
1305
+ },
1310
1306
  );
1311
1307
 
1312
1308
  // Return a context object with the snapshot
1313
1309
  return { previousItems };
1314
1310
  },
1315
- onError: (err, itemId, context) => {
1311
+ onError: (err, _input, context) => {
1316
1312
  // If the mutation fails, use the context returned from onMutate to roll back
1317
- queryClient.setQueryData(api.listItems.queryKey(), context.previousItems);
1313
+ queryClient.setQueryData(
1314
+ api.listItems.queryKey({}),
1315
+ context?.previousItems,
1316
+ );
1318
1317
  console.error('Failed to delete item:', err);
1319
1318
  },
1320
1319
  onSettled: () => {
1321
1320
  // Always refetch after error or success to ensure data is in sync with server
1322
- queryClient.invalidateQueries({ queryKey: api.listItems.queryKey() });
1321
+ queryClient.invalidateQueries({ queryKey: api.listItems.queryKey({}) });
1323
1322
  },
1324
1323
  });
1325
1324
 
@@ -1333,11 +1332,11 @@ function ItemList() {
1333
1332
 
1334
1333
  return (
1335
1334
  <ul>
1336
- {itemsQuery.data.map((item) => (
1335
+ {itemsQuery.data?.items.map((item) => (
1337
1336
  <li key={item.id}>
1338
1337
  {item.name}
1339
1338
  <button
1340
- onClick={() => deleteMutation.mutate(item.id)}
1339
+ onClick={() => deleteMutation.mutate({ itemId: item.id })}
1341
1340
  disabled={deleteMutation.isPending}
1342
1341
  >
1343
1342
  {deleteMutation.isPending ? 'Deleting...' : 'Delete'}
@@ -1413,19 +1412,22 @@ function ItemForm() {
1413
1412
  const error = createItem.error;
1414
1413
  switch (error.status) {
1415
1414
  case 400:
1416
- // error.error is typed as CreateItem400Response
1415
+ // error.error is typed as ValidationErrorDetails
1417
1416
  return (
1418
1417
  <FormError
1419
1418
  message="Invalid input"
1420
- errors={error.error.validationErrors}
1419
+ errors={error.error.fieldErrors}
1421
1420
  />
1422
1421
  );
1423
1422
  case 403:
1424
- // error.error is typed as CreateItem403Response
1425
- return <AuthError reason={error.error.reason} />;
1426
- default:
1427
- // error.error is typed as CreateItem5XXResponse for 500, 502, etc.
1423
+ // error.error is a string as specified in the responses
1424
+ return <AuthError reason={error.error} />;
1425
+ case 500:
1426
+ // error.error is typed as ErrorDetails
1428
1427
  return <ServerError message={error.error.message} />;
1428
+ default:
1429
+ // FastAPI adds a 422 to every endpoint, so `default` isn't narrowed
1430
+ return <ServerError message="An unknown error occurred" />;
1429
1431
  }
1430
1432
  }
1431
1433
 
@@ -1461,22 +1463,16 @@ function ItemForm() {
1461
1463
  const err = e as CreateItemError;
1462
1464
  switch (err.status) {
1463
1465
  case 400:
1464
- // err.error is typed as CreateItem400Response
1465
- console.error('Validation errors:', err.error.validationErrors);
1466
+ // err.error is typed as ValidationErrorDetails
1467
+ console.error('Validation errors:', err.error.fieldErrors);
1466
1468
  break;
1467
1469
  case 403:
1468
- // err.error is typed as CreateItem403Response
1469
- console.error('Not authorized:', err.error.reason);
1470
+ // err.error is a string as specified in the responses
1471
+ console.error('Not authorized:', err.error);
1470
1472
  break;
1471
1473
  case 500:
1472
- case 502:
1473
- // err.error is typed as CreateItem5XXResponse
1474
- console.error(
1475
- 'Server error:',
1476
- err.error.message,
1477
- 'Trace:',
1478
- err.error.traceId,
1479
- );
1474
+ // err.error is typed as ErrorDetails
1475
+ console.error('Server error:', err.error.message);
1480
1476
  break;
1481
1477
  }
1482
1478
  setError(err);
@@ -1490,13 +1486,15 @@ function ItemForm() {
1490
1486
  return (
1491
1487
  <FormError
1492
1488
  message="Invalid input"
1493
- errors={error.error.validationErrors}
1489
+ errors={error.error.fieldErrors}
1494
1490
  />
1495
1491
  );
1496
1492
  case 403:
1497
- return <AuthError reason={error.error.reason} />;
1498
- default:
1493
+ return <AuthError reason={error.error} />;
1494
+ case 500:
1499
1495
  return <ServerError message={error.error.message} />;
1496
+ default:
1497
+ return <ServerError message="An unknown error occurred" />;
1500
1498
  }
1501
1499
  }
1502
1500
 
@@ -183,6 +183,10 @@ The connection generator automatically configures `dev` integration:
183
183
  The website and connected agent will hot-reload, enabling you to quickly iterate on both together without deploying to AWS.
184
184
  :::
185
185
 
186
+ :::note[The dev target requires the Nx Daemon]
187
+ The `dev` target hot-reloads local dev servers. Many of these rely on [`nx watch`](https://nx.dev/docs/guides/tasks--caching/workspace-watching) to achieve this, which requires the [Nx Daemon](https://nx.dev/docs/concepts/nx-daemon) to be enabled.
188
+ :::
189
+
186
190
  ## More Information
187
191
 
188
192
  For more information, please refer to: