@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.
- package/bin/aws-nx-mcp.js +90 -45
- package/docs/get_started/existing-project.mdx +7 -4
- package/docs/get_started/quick-start.mdx +58 -5
- package/docs/get_started/tutorials/dungeon-game/1.mdx +23 -20
- package/docs/get_started/tutorials/dungeon-game/2.mdx +11 -3
- package/docs/get_started/tutorials/dungeon-game/3.mdx +4 -0
- package/docs/get_started/tutorials/dungeon-game/4.mdx +7 -2
- package/docs/get_started/tutorials/dungeon-game/wrap-up.mdx +10 -1
- package/docs/guides/agentcore-gateway.mdx +4 -2
- package/docs/guides/agentcore-harness.mdx +2 -1
- package/docs/guides/astro-docs.mdx +25 -7
- package/docs/guides/connection/py-agent-a2a.mdx +2 -0
- package/docs/guides/connection/py-agent-dynamodb.mdx +1 -1
- package/docs/guides/connection/py-agent-gateway.mdx +3 -0
- package/docs/guides/connection/py-agent-mcp.mdx +18 -4
- package/docs/guides/connection/py-agent-rdb.mdx +5 -4
- package/docs/guides/connection/py-fast-api-dynamodb.mdx +1 -1
- package/docs/guides/connection/py-fast-api-rdb.mdx +7 -2
- package/docs/guides/connection/py-mcp-server-dynamodb.mdx +1 -1
- package/docs/guides/connection/py-mcp-server-rdb.mdx +6 -6
- package/docs/guides/connection/react-agui.mdx +34 -25
- package/docs/guides/connection/react-fastapi.mdx +114 -116
- package/docs/guides/connection/react-py-agent.mdx +4 -0
- package/docs/guides/connection/react-smithy.mdx +152 -98
- package/docs/guides/connection/react-trpc.mdx +13 -6
- package/docs/guides/connection/smithy-dynamodb.mdx +2 -8
- package/docs/guides/connection/smithy-rdb.mdx +3 -6
- package/docs/guides/connection/trpc-rdb.mdx +6 -6
- package/docs/guides/connection/ts-agent-a2a.mdx +4 -4
- package/docs/guides/connection/ts-agent-dynamodb.mdx +13 -11
- package/docs/guides/connection/ts-agent-gateway.mdx +2 -0
- package/docs/guides/connection/ts-agent-mcp.mdx +19 -7
- package/docs/guides/connection/ts-agent-rdb.mdx +2 -2
- package/docs/guides/connection/ts-mcp-server-dynamodb.mdx +11 -7
- package/docs/guides/connection/ts-mcp-server-rdb.mdx +4 -3
- package/docs/guides/docker-bundling.mdx +23 -3
- package/docs/guides/fastapi.mdx +16 -5
- package/docs/guides/license.mdx +30 -6
- package/docs/guides/nx-generator.mdx +11 -12
- package/docs/guides/py-agent.mdx +129 -54
- package/docs/guides/py-mcp-server.mdx +3 -1
- package/docs/guides/py-rdb.mdx +13 -4
- package/docs/guides/python-lambda-function.mdx +8 -8
- package/docs/guides/python-project.mdx +28 -25
- package/docs/guides/react-website-auth.mdx +8 -8
- package/docs/guides/react-website.mdx +46 -27
- package/docs/guides/runtime-config.mdx +24 -4
- package/docs/guides/security.mdx +1 -1
- package/docs/guides/terraform-project.mdx +8 -2
- package/docs/guides/trpc.mdx +96 -12
- package/docs/guides/ts-agent.mdx +17 -3
- package/docs/guides/ts-dcr-proxy.mdx +24 -6
- package/docs/guides/ts-lambda-function.mdx +7 -1
- package/docs/guides/ts-mcp-server.mdx +45 -15
- package/docs/guides/ts-nx-plugin.mdx +17 -7
- package/docs/guides/ts-rdb.mdx +9 -2
- package/docs/guides/ts-smithy-api.mdx +76 -7
- package/docs/guides/typescript-infrastructure.mdx +27 -11
- package/docs/guides/typescript-project.mdx +12 -5
- package/docs/guides/workspace.mdx +21 -9
- package/docs/snippets/api/type-safe-api-integrations.mdx +2 -0
- package/docs/snippets/connection/a2a-infrastructure.mdx +3 -0
- package/docs/snippets/connection/infra-project-prerequisite.mdx +8 -0
- package/docs/snippets/connection/strands-agent-rdb-ssl-requirements.mdx +1 -1
- package/docs/snippets/connection/ts-lambda-rdb-ssl-requirements.mdx +1 -1
- package/docs/snippets/connection/ts-mcp-server-rdb-ssl-requirements.mdx +1 -1
- package/docs/snippets/pdk-migration/faq/type-safe-api.mdx +1 -1
- package/docs/snippets/prerequisites.mdx +1 -1
- package/docs/snippets/required-prerequisites.mdx +1 -1
- package/package.json +1 -1
- package/src/init/schema.json +5 -0
- 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
|
-
:::
|
|
129
|
-
`
|
|
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.
|
|
150
|
-
if (item.isError) return <div>Error: {item.error.
|
|
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.
|
|
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.
|
|
375
|
+
if (items.isPending) {
|
|
374
376
|
return <LoadingSpinner />;
|
|
375
377
|
}
|
|
376
378
|
|
|
377
379
|
if (items.isError) {
|
|
378
|
-
return <ErrorMessage message={items.error.
|
|
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 `<
|
|
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
|
|
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.
|
|
521
|
-
<li key={err
|
|
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
|
|
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
|
|
535
|
+
<p>{createItem.error.error}</p>
|
|
532
536
|
</div>
|
|
533
537
|
);
|
|
534
538
|
case 500:
|
|
535
|
-
|
|
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
|
|
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.
|
|
576
|
-
<li key={err
|
|
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
|
|
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
|
|
592
|
+
<p>{error.error}</p>
|
|
587
593
|
</div>
|
|
588
594
|
);
|
|
589
595
|
case 500:
|
|
590
|
-
|
|
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
|
|
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:
|
|
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":
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
1058
|
+
// error.error is typed as ValidationErrorDetails
|
|
1063
1059
|
console.error('Validation error:', error.error.message);
|
|
1064
|
-
console.error('Field errors:', error.error.
|
|
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
|
-
|
|
1077
|
-
|
|
1078
|
-
|
|
1079
|
-
|
|
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
|
|
1139
|
+
// err.error is typed as ValidationErrorDetails
|
|
1141
1140
|
console.error('Validation error:', err.error.message);
|
|
1142
|
-
console.error('Field errors:', err.error.
|
|
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
|
|
1200
|
-
return <ErrorMessage message={err.error
|
|
1198
|
+
// err.error is a string as specified in the responses
|
|
1199
|
+
return <ErrorMessage message={err.error} />;
|
|
1201
1200
|
case 500:
|
|
1202
|
-
|
|
1203
|
-
|
|
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
|
|
1256
|
-
return <ErrorMessage message={err.error
|
|
1248
|
+
// err.error is a string as specified in the responses
|
|
1249
|
+
return <ErrorMessage message={err.error} />;
|
|
1257
1250
|
case 500:
|
|
1258
|
-
|
|
1259
|
-
|
|
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({
|
|
1289
|
+
await queryClient.cancelQueries({
|
|
1290
|
+
queryKey: api.listItems.queryKey({}),
|
|
1291
|
+
});
|
|
1302
1292
|
|
|
1303
1293
|
// Snapshot the previous value
|
|
1304
|
-
const previousItems = queryClient.getQueryData(
|
|
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) =>
|
|
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,
|
|
1311
|
+
onError: (err, _input, context) => {
|
|
1316
1312
|
// If the mutation fails, use the context returned from onMutate to roll back
|
|
1317
|
-
queryClient.setQueryData(
|
|
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
|
|
1415
|
+
// error.error is typed as ValidationErrorDetails
|
|
1417
1416
|
return (
|
|
1418
1417
|
<FormError
|
|
1419
1418
|
message="Invalid input"
|
|
1420
|
-
errors={error.error.
|
|
1419
|
+
errors={error.error.fieldErrors}
|
|
1421
1420
|
/>
|
|
1422
1421
|
);
|
|
1423
1422
|
case 403:
|
|
1424
|
-
// error.error is
|
|
1425
|
-
return <AuthError reason={error.error
|
|
1426
|
-
|
|
1427
|
-
// error.error is typed as
|
|
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
|
|
1465
|
-
console.error('Validation errors:', err.error.
|
|
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
|
|
1469
|
-
console.error('Not authorized:', err.error
|
|
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
|
-
|
|
1473
|
-
|
|
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.
|
|
1489
|
+
errors={error.error.fieldErrors}
|
|
1494
1490
|
/>
|
|
1495
1491
|
);
|
|
1496
1492
|
case 403:
|
|
1497
|
-
return <AuthError reason={error.error
|
|
1498
|
-
|
|
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:
|