@nkzw/fate 0.0.8 → 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +133 -73
- package/lib/cli.mjs +32 -4
- package/lib/index.d.mts +9 -4
- package/lib/index.mjs +55 -21
- package/lib/server.d.mts +1 -1
- package/lib/{types-BPmcnouE.d.mts → types-CNKGKRtz.d.mts} +39 -18
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -18,7 +18,7 @@
|
|
|
18
18
|
- **Async React:** fate uses modern Async React features like Actions, Suspense, and `use` to support concurrent rendering and enable a seamless user experience.
|
|
19
19
|
- **Lists & Pagination:** fate provides built-in support for connection-style lists with cursor-based pagination, making it easy to implement infinite scrolling and "load-more" functionality.
|
|
20
20
|
- **Optimistic Updates:** fate supports declarative optimistic updates for mutations, allowing the UI to update immediately while the server request is in-flight. If the request fails, the cache and its associated views are rolled back to their previous state.
|
|
21
|
-
- **AI-Ready:** fate's minimal, predictable API and explicit data selection enable local reasoning,
|
|
21
|
+
- **AI-Ready:** fate's minimal, predictable API and explicit data selection enable local reasoning, enabling humans and AI tools to generate stable, type-safe data-fetching code.
|
|
22
22
|
|
|
23
23
|
### A modern data client for React & tRPC
|
|
24
24
|
|
|
@@ -32,14 +32,14 @@ However, GraphQL comes with its own type system and query language. If you are a
|
|
|
32
32
|
|
|
33
33
|
Many React data frameworks lack Relay's ergonomics, especially fragment composition, co-located data requirements, predictable caching, and deep integration with modern React features. Optimistic updates usually require manually managing keys and imperative data updates, which is error-prone and tedious.
|
|
34
34
|
|
|
35
|
-
|
|
35
|
+
_fate_ takes the great ideas from Relay and puts them on top of tRPC. You get the best of both worlds: type safety between the client and server, and GraphQL-like ergonomics for data fetching. Using _fate_ usually looks like this:
|
|
36
36
|
|
|
37
37
|
```tsx
|
|
38
38
|
export const PostView = view<Post>()({
|
|
39
|
+
author: UserView,
|
|
39
40
|
content: true,
|
|
40
41
|
id: true,
|
|
41
42
|
title: true,
|
|
42
|
-
author: UserView,
|
|
43
43
|
});
|
|
44
44
|
|
|
45
45
|
export const PostCard = ({ post: postRef }: { post: ViewRef<'Post'> }) => {
|
|
@@ -65,15 +65,15 @@ Get started with [a ready-made template](https://github.com/nkzw-tech/fate-templ
|
|
|
65
65
|
|
|
66
66
|
::: code-group
|
|
67
67
|
|
|
68
|
-
```npm
|
|
68
|
+
```bash [npm]
|
|
69
69
|
npx giget@latest gh:nkzw-tech/fate-template
|
|
70
70
|
```
|
|
71
71
|
|
|
72
|
-
```pnpm
|
|
72
|
+
```bash [pnpm]
|
|
73
73
|
pnpx giget@latest gh:nkzw-tech/fate-template
|
|
74
74
|
```
|
|
75
75
|
|
|
76
|
-
```yarn
|
|
76
|
+
```bash [yarn]
|
|
77
77
|
yarn dlx giget@latest gh:nkzw-tech/fate-template
|
|
78
78
|
```
|
|
79
79
|
|
|
@@ -87,15 +87,15 @@ yarn dlx giget@latest gh:nkzw-tech/fate-template
|
|
|
87
87
|
|
|
88
88
|
::: code-group
|
|
89
89
|
|
|
90
|
-
```npm
|
|
90
|
+
```bash [npm]
|
|
91
91
|
npm add react-fate
|
|
92
92
|
```
|
|
93
93
|
|
|
94
|
-
```pnpm
|
|
94
|
+
```bash [pnpm]
|
|
95
95
|
pnpm add react-fate
|
|
96
96
|
```
|
|
97
97
|
|
|
98
|
-
```yarn
|
|
98
|
+
```bash [yarn]
|
|
99
99
|
yarn add react-fate
|
|
100
100
|
```
|
|
101
101
|
|
|
@@ -105,15 +105,15 @@ And for your server, install the core `@nkzw/fate` package:
|
|
|
105
105
|
|
|
106
106
|
::: code-group
|
|
107
107
|
|
|
108
|
-
```npm
|
|
108
|
+
```bash [npm]
|
|
109
109
|
npm add @nkzw/fate
|
|
110
110
|
```
|
|
111
111
|
|
|
112
|
-
```pnpm
|
|
112
|
+
```bash [pnpm]
|
|
113
113
|
pnpm add @nkzw/fate
|
|
114
114
|
```
|
|
115
115
|
|
|
116
|
-
```yarn
|
|
116
|
+
```bash [yarn]
|
|
117
117
|
yarn add @nkzw/fate
|
|
118
118
|
```
|
|
119
119
|
|
|
@@ -202,34 +202,20 @@ Components using `useView` listen to changes for all selected fields. When data
|
|
|
202
202
|
|
|
203
203
|
### Fetching Data with `useRequest`
|
|
204
204
|
|
|
205
|
-
Now that we defined our view and component, we fetch the data from the server using the `useRequest` hook from fate. This hook allows us to declare what data we need for a specific screen or component tree. At the root of our
|
|
205
|
+
Now that we defined our view and component, we fetch the data from the server using the `useRequest` hook from fate. This hook allows us to declare what data we need for a specific screen or component tree. At the root of our app, we can request a list of posts like this:
|
|
206
206
|
|
|
207
207
|
```tsx
|
|
208
208
|
import { useRequest } from 'react-fate';
|
|
209
209
|
import { PostCard, PostView } from './PostCard.tsx';
|
|
210
210
|
|
|
211
|
-
export function
|
|
212
|
-
const { posts } = useRequest({
|
|
213
|
-
posts: { root: PostView, type: 'Post' },
|
|
214
|
-
} as const);
|
|
211
|
+
export function App() {
|
|
212
|
+
const { posts } = useRequest({ posts: { list: PostView } });
|
|
215
213
|
|
|
216
214
|
return posts.map((post) => <PostCard key={post.id} post={post} />);
|
|
217
215
|
}
|
|
218
216
|
```
|
|
219
217
|
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
```tsx
|
|
223
|
-
<ErrorBoundary FallbackComponent={ErrorComponent}>
|
|
224
|
-
<Suspense fallback={<div>Loading…</div>}>
|
|
225
|
-
<HomePage />
|
|
226
|
-
</Suspense>
|
|
227
|
-
</ErrorBoundary>
|
|
228
|
-
```
|
|
229
|
-
|
|
230
|
-
> [!NOTE]
|
|
231
|
-
>
|
|
232
|
-
> `useRequest` might issue multiple requests which are automatically batched together by tRPC's [HTTP Batch Link](https://trpc.io/docs/client/links/httpBatchLink).
|
|
218
|
+
_Learn more about `useRequest` in the [Requests Guide](/docs/guide/requests.md)._
|
|
233
219
|
|
|
234
220
|
### Composing Views
|
|
235
221
|
|
|
@@ -237,7 +223,7 @@ In the above example we are defining a single view for a `Post`. One of fate's c
|
|
|
237
223
|
|
|
238
224
|
```tsx
|
|
239
225
|
import { Suspense } from 'react';
|
|
240
|
-
import {
|
|
226
|
+
import { useView, ViewRef } from 'react-fate';
|
|
241
227
|
|
|
242
228
|
export const PostView = view<Post>()({
|
|
243
229
|
author: {
|
|
@@ -443,23 +429,62 @@ const PostDetail = ({ post: postRef }: { post: ViewRef<'Post'> }) => {
|
|
|
443
429
|
|
|
444
430
|
ViewRefs carry a set of view names they can resolve. `useView` throws if a ref does not include the required view.
|
|
445
431
|
|
|
446
|
-
|
|
432
|
+
## Requests
|
|
447
433
|
|
|
448
|
-
|
|
434
|
+
### Requesting Lists
|
|
449
435
|
|
|
450
|
-
|
|
451
|
-
- `stale-while-revalidate`: Returns data from the cache and simultaneously fetches fresh data from the network.
|
|
452
|
-
- `network-only`: Always fetches data from the network, bypassing the cache.
|
|
436
|
+
The `useRequest` hook can be used to declare our data needs for a specific screen or component tree. At the root of our app, we can request a list of posts like this:
|
|
453
437
|
|
|
454
|
-
|
|
438
|
+
```tsx
|
|
439
|
+
import { useRequest } from 'react-fate';
|
|
440
|
+
import { PostCard, PostView } from './PostCard.tsx';
|
|
441
|
+
|
|
442
|
+
export function App() {
|
|
443
|
+
const { posts } = useRequest({ posts: { list: PostView } });
|
|
444
|
+
return posts.map((post) => <PostCard key={post.id} post={post} />);
|
|
445
|
+
}
|
|
446
|
+
```
|
|
447
|
+
|
|
448
|
+
This component suspends or throws errors, which bubble up to the nearest error boundary. Wrap your component tree with `ErrorBoundary` and `Suspense` components to show error and loading states:
|
|
455
449
|
|
|
456
450
|
```tsx
|
|
457
|
-
|
|
458
|
-
{
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
451
|
+
<ErrorBoundary FallbackComponent={ErrorComponent}>
|
|
452
|
+
<Suspense fallback={<div>Loading…</div>}>
|
|
453
|
+
<App />
|
|
454
|
+
</Suspense>
|
|
455
|
+
</ErrorBoundary>
|
|
456
|
+
```
|
|
457
|
+
|
|
458
|
+
> [!NOTE]
|
|
459
|
+
>
|
|
460
|
+
> `useRequest` might issue multiple requests which are automatically batched together by tRPC's [HTTP Batch Link](https://trpc.io/docs/client/links/httpBatchLink) into a single network request.
|
|
461
|
+
|
|
462
|
+
### Requesting Objects by ID
|
|
463
|
+
|
|
464
|
+
If you want to fetch data for a single object instead of a list, you can specify the `id` and the associated `view` like this:
|
|
465
|
+
|
|
466
|
+
```tsx
|
|
467
|
+
const { post } = useRequest({
|
|
468
|
+
post: { id: '12', view: PostView },
|
|
469
|
+
});
|
|
470
|
+
```
|
|
471
|
+
|
|
472
|
+
If you want to fetch multiple objects by their IDs, you can use the `ids` field:
|
|
473
|
+
|
|
474
|
+
```tsx
|
|
475
|
+
const { posts } = useRequest({
|
|
476
|
+
posts: { ids: ['6', '7'], view: PostView },
|
|
477
|
+
});
|
|
478
|
+
```
|
|
479
|
+
|
|
480
|
+
### Other Types of Requests
|
|
481
|
+
|
|
482
|
+
For any other queries, pass only the `type` and `view`:
|
|
483
|
+
|
|
484
|
+
```tsx
|
|
485
|
+
const { viewer } = useRequest({
|
|
486
|
+
viewer: { view: UserView },
|
|
487
|
+
});
|
|
463
488
|
```
|
|
464
489
|
|
|
465
490
|
### Request Arguments
|
|
@@ -470,12 +495,32 @@ You can pass arguments to `useRequest` calls. This is useful for pagination, fil
|
|
|
470
495
|
const { posts } = useRequest({
|
|
471
496
|
posts: {
|
|
472
497
|
args: { first: 10 },
|
|
473
|
-
|
|
474
|
-
type: 'Post',
|
|
498
|
+
list: PostView,
|
|
475
499
|
},
|
|
476
500
|
});
|
|
477
501
|
```
|
|
478
502
|
|
|
503
|
+
### Request Modes
|
|
504
|
+
|
|
505
|
+
`useRequest` supports different request modes to control caching and data freshness. The available modes are:
|
|
506
|
+
|
|
507
|
+
- `cache-first` (_default_): Returns data from the cache if available, otherwise fetches from the network.
|
|
508
|
+
- `stale-while-revalidate`: Returns data from the cache and simultaneously fetches fresh data from the network.
|
|
509
|
+
- `network-only`: Always fetches data from the network, bypassing the cache.
|
|
510
|
+
|
|
511
|
+
You can pass the request mode as an option to `useRequest`:
|
|
512
|
+
|
|
513
|
+
```tsx
|
|
514
|
+
const { posts } = useRequest(
|
|
515
|
+
{
|
|
516
|
+
posts: { list: PostView },
|
|
517
|
+
},
|
|
518
|
+
{
|
|
519
|
+
mode: 'stale-while-revalidate',
|
|
520
|
+
},
|
|
521
|
+
);
|
|
522
|
+
```
|
|
523
|
+
|
|
479
524
|
## List Views
|
|
480
525
|
|
|
481
526
|
### Pagination with `useListView`
|
|
@@ -497,7 +542,7 @@ const CommentConnectionView = {
|
|
|
497
542
|
items: {
|
|
498
543
|
node: CommentView,
|
|
499
544
|
},
|
|
500
|
-
}
|
|
545
|
+
};
|
|
501
546
|
|
|
502
547
|
const PostView = view<Post>()({
|
|
503
548
|
comments: CommentConnectionView,
|
|
@@ -915,7 +960,7 @@ export const postDataView = dataView<PostItem>('Post')({
|
|
|
915
960
|
content: true,
|
|
916
961
|
id: true,
|
|
917
962
|
title: true,
|
|
918
|
-
}
|
|
963
|
+
});
|
|
919
964
|
```
|
|
920
965
|
|
|
921
966
|
### Data View Lists
|
|
@@ -950,6 +995,25 @@ export const Root = {
|
|
|
950
995
|
|
|
951
996
|
Entries that wrap their view in `list(...)` are treated as list resolvers and use the `procedure` name when calling the corresponding router procedure, defaulting to `list`. If you omit `list(...)`, fate treats the entry as a standard query and uses the view type name to infer the router name.
|
|
952
997
|
|
|
998
|
+
For the above `Root` definitions, you can make the following requests using `useRequest`:
|
|
999
|
+
|
|
1000
|
+
```tsx
|
|
1001
|
+
const query = 'Apple';
|
|
1002
|
+
|
|
1003
|
+
const { posts, categories, viewer } = useRequest({
|
|
1004
|
+
// Explicit Root queries:
|
|
1005
|
+
categories: { list: categoryView },
|
|
1006
|
+
commentSearch: { args: { query }, list: commentView },
|
|
1007
|
+
events: { list: eventView },
|
|
1008
|
+
posts: { list: postView },
|
|
1009
|
+
viewer: { view: userView },
|
|
1010
|
+
|
|
1011
|
+
// Queries by id, if those entities have a `byId` query defined:
|
|
1012
|
+
post: { id: '12', view: postView },
|
|
1013
|
+
comment: { ids: ['6', '7'], view: commentView },
|
|
1014
|
+
});
|
|
1015
|
+
```
|
|
1016
|
+
|
|
953
1017
|
### Data View Resolvers
|
|
954
1018
|
|
|
955
1019
|
fate data views support resolvers for computed fields. If we want to add a `commentCount` field to our `Post` data view, we can use the `resolver` helper that defines a Prisma selection for the database query together with a `resolve` function:
|
|
@@ -1015,34 +1079,30 @@ _Note: fate uses the specified server module name to extract the server types it
|
|
|
1015
1079
|
|
|
1016
1080
|
### Creating a _fate_ Client
|
|
1017
1081
|
|
|
1018
|
-
Now that we have generated the client types, all that remains is creating
|
|
1019
|
-
|
|
1020
|
-
Create a `fate.ts` file:
|
|
1021
|
-
|
|
1022
|
-
```tsx
|
|
1023
|
-
import { createFateClient } from './lib/fate.generated';
|
|
1024
|
-
|
|
1025
|
-
export const fate = createFateClient({
|
|
1026
|
-
links: [
|
|
1027
|
-
httpBatchLink({
|
|
1028
|
-
fetch: (input, init) =>
|
|
1029
|
-
fetch(input, {
|
|
1030
|
-
...init,
|
|
1031
|
-
credentials: 'include',
|
|
1032
|
-
}),
|
|
1033
|
-
url: `${env('SERVER_URL')}/trpc`,
|
|
1034
|
-
}),
|
|
1035
|
-
],
|
|
1036
|
-
});
|
|
1037
|
-
```
|
|
1038
|
-
|
|
1039
|
-
Now wrap your app with the `FateClient` provider:
|
|
1082
|
+
Now that we have generated the client types, all that remains is creating an instance of the fate client, and using it in our React app using the `FateClient` context provider:
|
|
1040
1083
|
|
|
1041
1084
|
```tsx
|
|
1085
|
+
import { httpBatchLink } from '@trpc/client';
|
|
1042
1086
|
import { FateClient } from 'react-fate';
|
|
1043
|
-
import {
|
|
1087
|
+
import { createFateClient } from './fate.ts';
|
|
1044
1088
|
|
|
1045
1089
|
export function App() {
|
|
1090
|
+
const fate = useMemo(
|
|
1091
|
+
() =>
|
|
1092
|
+
createFateClient({
|
|
1093
|
+
links: [
|
|
1094
|
+
httpBatchLink({
|
|
1095
|
+
fetch: (input, init) =>
|
|
1096
|
+
fetch(input, {
|
|
1097
|
+
...init,
|
|
1098
|
+
credentials: 'include',
|
|
1099
|
+
}),
|
|
1100
|
+
url: `${env('SERVER_URL')}/trpc`,
|
|
1101
|
+
}),
|
|
1102
|
+
],
|
|
1103
|
+
}),
|
|
1104
|
+
[],
|
|
1105
|
+
);
|
|
1046
1106
|
return <FateClient client={fate}>{/* Components go here */}</FateClient>;
|
|
1047
1107
|
}
|
|
1048
1108
|
```
|
|
@@ -1076,14 +1136,14 @@ Probably. One day. _Maybe._
|
|
|
1076
1136
|
|
|
1077
1137
|
## Future
|
|
1078
1138
|
|
|
1079
|
-
**_fate_** is not complete yet.
|
|
1139
|
+
**_fate_** is not complete yet. The library lacks core features such as garbage collection, a compiler to extract view definitions statically ahead of time, and there is too much backend boilerplate. The current implementation of _fate_ is not tied to tRPC or Prisma, those are just the ones we are starting with. We welcome contributions and ideas to improve fate. Here are some features we'd like to add:
|
|
1080
1140
|
|
|
1081
1141
|
- Support for Drizzle
|
|
1082
1142
|
- Support backends other than tRPC
|
|
1143
|
+
- Persistent storage for offline support
|
|
1144
|
+
- Implement garbage collection for the cache
|
|
1083
1145
|
- Better code generation and less type repetition
|
|
1084
1146
|
- Support for live views and real-time updates via `useLiveView` and SSE
|
|
1085
|
-
- Implement garbage collection for the cache
|
|
1086
|
-
- Add persistent storage for offline support
|
|
1087
1147
|
|
|
1088
1148
|
## Acknowledgements
|
|
1089
1149
|
|
package/lib/cli.mjs
CHANGED
|
@@ -135,7 +135,8 @@ const generate = async () => {
|
|
|
135
135
|
if (procedureName === root$1.procedure && type === "query") queryEntries.push({
|
|
136
136
|
name: queryName,
|
|
137
137
|
procedure: root$1.procedure,
|
|
138
|
-
router
|
|
138
|
+
router,
|
|
139
|
+
type: root$1.type
|
|
139
140
|
});
|
|
140
141
|
}
|
|
141
142
|
for (const [listName, root$1] of routerRoots) {
|
|
@@ -143,7 +144,8 @@ const generate = async () => {
|
|
|
143
144
|
if (procedureName === root$1.procedure && type === "query") listEntries.push({
|
|
144
145
|
list: listName,
|
|
145
146
|
procedure: root$1.procedure,
|
|
146
|
-
router
|
|
147
|
+
router,
|
|
148
|
+
type: root$1.type
|
|
147
149
|
});
|
|
148
150
|
}
|
|
149
151
|
}
|
|
@@ -152,6 +154,24 @@ const generate = async () => {
|
|
|
152
154
|
byIdEntries.sort((a, b) => a.entityType.localeCompare(b.entityType));
|
|
153
155
|
queryEntries.sort((a, b) => a.name.localeCompare(b.name));
|
|
154
156
|
listEntries.sort((a, b) => a.list.localeCompare(b.list));
|
|
157
|
+
const rootEntries = [
|
|
158
|
+
...byIdEntries.map(({ entityType, router }) => ({
|
|
159
|
+
name: router,
|
|
160
|
+
type: entityType,
|
|
161
|
+
value: `'${router}': clientRoot<RouterOutputs['${router}']['byId'], '${entityType}'>('${entityType}'),`
|
|
162
|
+
})),
|
|
163
|
+
...queryEntries.map(({ name, procedure, router, type }) => ({
|
|
164
|
+
name,
|
|
165
|
+
type,
|
|
166
|
+
value: `'${name}': clientRoot<RouterOutputs['${router}']['${procedure}'], '${type}'>('${type}'),`
|
|
167
|
+
})),
|
|
168
|
+
...listEntries.map(({ list, procedure, router, type }) => ({
|
|
169
|
+
name: list,
|
|
170
|
+
type,
|
|
171
|
+
value: `'${list}': clientRoot<RouterOutputs['${router}']['${procedure}'], '${type}'>('${type}'),`
|
|
172
|
+
}))
|
|
173
|
+
];
|
|
174
|
+
rootEntries.sort((a, b) => a.name.localeCompare(b.name));
|
|
155
175
|
const viewTypes = Array.from(["AppRouter", ...new Set(mutationEntries.map((entry) => entry.entityType))]).sort();
|
|
156
176
|
const mutationResolverLines = mutationEntries.map(({ name, procedure, router }) => `'${name}': (client: TRPCClientType) => client.${router}.${procedure}.mutate,`);
|
|
157
177
|
const mutationConfigLines = mutationEntries.map(({ entityType, name, procedure, router }) => `'${name}': mutation<
|
|
@@ -176,6 +196,7 @@ const generate = async () => {
|
|
|
176
196
|
const mutationResolverBlock = indentBlock(mutationResolverLines.join("\n"), 4);
|
|
177
197
|
const mutationConfigBlock = indentBlock(mutationConfigLines.join("\n"), 2);
|
|
178
198
|
const byIdBlock = indentBlock(byIdLines.join("\n"), 8);
|
|
199
|
+
const rootsBlock = indentBlock(rootEntries.map((entry) => entry.value).join("\n"), 2);
|
|
179
200
|
const listsBlockContent = listLines.join("\n");
|
|
180
201
|
const listsBlock = listLines.length ? ` lists: {\n${indentBlock(listsBlockContent, 8)}\n },\n` : "";
|
|
181
202
|
const queriesBlockContent = queryLines.join("\n");
|
|
@@ -183,7 +204,7 @@ const generate = async () => {
|
|
|
183
204
|
${typeImports}
|
|
184
205
|
import { createTRPCProxyClient } from '@trpc/client';
|
|
185
206
|
import { inferRouterInputs, inferRouterOutputs } from '@trpc/server';
|
|
186
|
-
import { createClient, createTRPCTransport, mutation } from 'react-fate';
|
|
207
|
+
import { clientRoot, createClient, createTRPCTransport, mutation } from 'react-fate';
|
|
187
208
|
|
|
188
209
|
type TRPCClientType = ReturnType<typeof createTRPCProxyClient<AppRouter>>;
|
|
189
210
|
type RouterInputs = inferRouterInputs<AppRouter>;
|
|
@@ -193,10 +214,16 @@ const mutations = {
|
|
|
193
214
|
${mutationConfigBlock}
|
|
194
215
|
} as const;
|
|
195
216
|
|
|
217
|
+
const roots = {
|
|
218
|
+
${rootsBlock}
|
|
219
|
+
} as const;
|
|
220
|
+
|
|
196
221
|
type GeneratedClientMutations = typeof mutations;
|
|
222
|
+
type GeneratedClientRoots = typeof roots;
|
|
197
223
|
|
|
198
224
|
declare module 'react-fate' {
|
|
199
225
|
interface ClientMutations extends GeneratedClientMutations {}
|
|
226
|
+
interface ClientRoots extends GeneratedClientRoots {}
|
|
200
227
|
}
|
|
201
228
|
|
|
202
229
|
export const createFateClient = (options: {
|
|
@@ -208,8 +235,9 @@ export const createFateClient = (options: {
|
|
|
208
235
|
${mutationResolverBlock}
|
|
209
236
|
} as const;
|
|
210
237
|
|
|
211
|
-
return createClient({
|
|
238
|
+
return createClient<[GeneratedClientRoots, GeneratedClientMutations]>({
|
|
212
239
|
mutations,
|
|
240
|
+
roots,
|
|
213
241
|
transport: createTRPCTransport<AppRouter, typeof trpcMutations>({
|
|
214
242
|
byId: {
|
|
215
243
|
${byIdBlock}
|
package/lib/index.d.mts
CHANGED
|
@@ -1,14 +1,19 @@
|
|
|
1
|
-
import { A as
|
|
1
|
+
import { A as ViewSelection, B as RequestOptions, C as TypeConfig, D as ViewEntity, E as ViewData, F as isViewTag, H as Transport, I as FateMutations, L as mutation, M as ViewTag, N as __FateEntityBrand, O as ViewEntityName, P as __FateSelectionBrand, R as FateClient, S as Snapshot, T as View, U as createTRPCTransport, V as createClient, W as getSelectionPlan, _ as Pagination, a as Entity, b as RootDefinition, c as FateThenable, d as MutationDefinition, f as MutationEntity, g as NodesItem, h as MutationResult, i as ConnectionTag, j as ViewSnapshot, k as ViewRef, l as ListItem, m as MutationInput, n as ConnectionMetadata, o as EntityId, p as MutationIdentifier, r as ConnectionRef, s as FateRoots, t as AnyRecord, u as Mask, v as Request, w as TypeName, x as Selection, y as RequestResult, z as RequestMode } from "./types-CNKGKRtz.mjs";
|
|
2
2
|
|
|
3
|
-
//#region src/
|
|
3
|
+
//#region src/root.d.ts
|
|
4
4
|
|
|
5
|
+
/**
|
|
6
|
+
* Defines a root query for an entity type, capturing the response shape.
|
|
7
|
+
*/
|
|
8
|
+
declare function clientRoot<Result, Type extends TypeName>(type: Type): RootDefinition<Type, Result>;
|
|
9
|
+
//#endregion
|
|
10
|
+
//#region src/ref.d.ts
|
|
5
11
|
/**
|
|
6
12
|
* Builds the canonical cache ID for an entity.
|
|
7
13
|
*/
|
|
8
14
|
declare const toEntityId: (type: TypeName, rawId: string | number) => EntityId;
|
|
9
15
|
//#endregion
|
|
10
16
|
//#region src/view.d.ts
|
|
11
|
-
|
|
12
17
|
type SelectionValidation<T extends Entity, S extends Selection<T>> = Exclude<keyof Omit<S, typeof __FateEntityBrand | typeof __FateSelectionBrand>, keyof Selection<T>> extends never ? unknown : never;
|
|
13
18
|
/**
|
|
14
19
|
* Creates a reusable view for an object using the declared selection.
|
|
@@ -21,4 +26,4 @@ type SelectionValidation<T extends Entity, S extends Selection<T>> = Exclude<key
|
|
|
21
26
|
*/
|
|
22
27
|
declare function view<T extends Entity>(): <S extends Selection<T>>(select: S & SelectionValidation<T, S>) => View<T, S>;
|
|
23
28
|
//#endregion
|
|
24
|
-
export { type ConnectionMetadata, type ConnectionRef, ConnectionTag, type Entity, type EntityId, FateClient, type FateMutations, type AnyRecord as FateRecord, type ListItem, type Mask, type MutationDefinition, type MutationEntity, type MutationIdentifier, type MutationInput, type MutationResult, type NodesItem, type Pagination, type Request, type RequestMode, type RequestOptions, type RequestResult, type Selection, type Snapshot, type Transport, type TypeConfig, type View, type ViewData, type ViewEntity, type ViewEntityName, type ViewRef, type ViewSelection, type ViewSnapshot, type ViewTag, createClient, createTRPCTransport, getSelectionPlan, isViewTag, mutation, toEntityId, view };
|
|
29
|
+
export { type ConnectionMetadata, type ConnectionRef, ConnectionTag, type Entity, type EntityId, FateClient, type FateMutations, type AnyRecord as FateRecord, type FateRoots, type FateThenable, type ListItem, type Mask, type MutationDefinition, type MutationEntity, type MutationIdentifier, type MutationInput, type MutationResult, type NodesItem, type Pagination, type Request, type RequestMode, type RequestOptions, type RequestResult, type Selection, type Snapshot, type Transport, type TypeConfig, type View, type ViewData, type ViewEntity, type ViewEntityName, type ViewRef, type ViewSelection, type ViewSnapshot, type ViewTag, clientRoot, createClient, createTRPCTransport, getSelectionPlan, isViewTag, mutation, toEntityId, view };
|
package/lib/index.mjs
CHANGED
|
@@ -422,6 +422,8 @@ function isNodesItem(item) {
|
|
|
422
422
|
function isQueryItem(item) {
|
|
423
423
|
return "view" in item && !("id" in item) && !("ids" in item);
|
|
424
424
|
}
|
|
425
|
+
/** Brand used on root definitions to mark their identity in the d.ts output. */
|
|
426
|
+
const RootKind = "__fate__root";
|
|
425
427
|
/** Brand used on mutation definitions to mark their identity in the d.ts output. */
|
|
426
428
|
const MutationKind = "__fate__mutation";
|
|
427
429
|
|
|
@@ -1006,18 +1008,18 @@ const getRequestCacheKey = (request) => {
|
|
|
1006
1008
|
const item = request[name];
|
|
1007
1009
|
if (!item) continue;
|
|
1008
1010
|
if (isNodeItem(item)) {
|
|
1009
|
-
parts.push(`node:${name}:${
|
|
1011
|
+
parts.push(`node:${name}:${getViewSignature(item.view)}:${item.id}`);
|
|
1010
1012
|
continue;
|
|
1011
1013
|
}
|
|
1012
1014
|
if (isNodesItem(item)) {
|
|
1013
|
-
parts.push(`node:${name}:${
|
|
1015
|
+
parts.push(`node:${name}:${getViewSignature(item.view)}:${item.ids.map(serializeId).join(",")}`);
|
|
1014
1016
|
continue;
|
|
1015
1017
|
}
|
|
1016
1018
|
if (isQueryItem(item)) {
|
|
1017
|
-
parts.push(`query:${name}:${
|
|
1019
|
+
parts.push(`query:${name}:${getViewSignature(item.view)}:${item.args ? hashArgs(item.args) : ""}`);
|
|
1018
1020
|
continue;
|
|
1019
1021
|
}
|
|
1020
|
-
parts.push(`list:${name}:${
|
|
1022
|
+
parts.push(`list:${name}:${getViewSignature(item.list)}:${item.args ? hashArgs(item.args) : ""}`);
|
|
1021
1023
|
}
|
|
1022
1024
|
return parts.join("$");
|
|
1023
1025
|
};
|
|
@@ -1055,14 +1057,15 @@ var FateClient = class {
|
|
|
1055
1057
|
this.stalledRequests = /* @__PURE__ */ new Set();
|
|
1056
1058
|
this.store = new Store();
|
|
1057
1059
|
this.viewDataCache = new ViewDataCache();
|
|
1060
|
+
this.actions = Object.create(null);
|
|
1061
|
+
this.mutationMap = Object.create(null);
|
|
1062
|
+
this.mutations = Object.create(null);
|
|
1063
|
+
this.roots = options.roots;
|
|
1058
1064
|
this.transport = options.transport;
|
|
1059
1065
|
this.types = new Map(options.types.map((entity) => [entity.type, {
|
|
1060
1066
|
getId,
|
|
1061
1067
|
...entity
|
|
1062
1068
|
}]));
|
|
1063
|
-
this.mutationMap = Object.create(null);
|
|
1064
|
-
this.mutations = Object.create(null);
|
|
1065
|
-
this.actions = Object.create(null);
|
|
1066
1069
|
if (options.mutations) for (const [key, definition] of Object.entries(options.mutations)) {
|
|
1067
1070
|
const mutation$1 = wrapMutation(this, {
|
|
1068
1071
|
...definition,
|
|
@@ -1423,30 +1426,34 @@ var FateClient = class {
|
|
|
1423
1426
|
this.executeRequest(request, { fetchAll: true }).catch(() => {});
|
|
1424
1427
|
return result;
|
|
1425
1428
|
}
|
|
1429
|
+
getRootType(name) {
|
|
1430
|
+
const root = this.roots[name];
|
|
1431
|
+
if (!root) throw new Error(`fate: Unknown root request '${name}'.`);
|
|
1432
|
+
return root.type;
|
|
1433
|
+
}
|
|
1426
1434
|
async executeRequest(request, options = {}) {
|
|
1427
1435
|
const fetchAll = options.fetchAll ?? false;
|
|
1428
1436
|
const groups = /* @__PURE__ */ new Map();
|
|
1429
1437
|
const promises = [];
|
|
1430
1438
|
for (const [name, item] of Object.entries(request)) {
|
|
1439
|
+
const type = this.getRootType(name);
|
|
1431
1440
|
const isNode = isNodeItem(item);
|
|
1432
1441
|
if (isNode || isNodesItem(item)) {
|
|
1433
1442
|
const plan = getSelectionPlan(item.view, null);
|
|
1434
1443
|
const fields = plan.paths;
|
|
1435
|
-
const
|
|
1436
|
-
const argsSignature = [...plan.args.entries()].map(([path, entry]) => `${path}:${entry.hash}`).sort().join(",");
|
|
1437
|
-
const groupKey = `${item.type}#${fieldsSignature}|${argsSignature}`;
|
|
1444
|
+
const groupKey = `${type}#${[...fields].slice().sort().join(",")}|${[...plan.args.entries()].map(([path, entry]) => `${path}:${entry.hash}`).sort().join(",")}`;
|
|
1438
1445
|
let group = groups.get(groupKey);
|
|
1439
1446
|
if (!group) {
|
|
1440
1447
|
group = {
|
|
1441
1448
|
fields,
|
|
1442
1449
|
ids: [],
|
|
1443
1450
|
plan,
|
|
1444
|
-
type
|
|
1451
|
+
type
|
|
1445
1452
|
};
|
|
1446
1453
|
groups.set(groupKey, group);
|
|
1447
1454
|
}
|
|
1448
1455
|
for (const raw of isNode ? [item.id] : item.ids) {
|
|
1449
|
-
const entityId = toEntityId(
|
|
1456
|
+
const entityId = toEntityId(type, raw);
|
|
1450
1457
|
const missing = this.store.missingForSelection(entityId, fields);
|
|
1451
1458
|
if (fetchAll || missing.size > 0) group.ids.push(raw);
|
|
1452
1459
|
}
|
|
@@ -1462,10 +1469,11 @@ var FateClient = class {
|
|
|
1462
1469
|
hasRequestData(request) {
|
|
1463
1470
|
for (const [name, item] of Object.entries(request)) {
|
|
1464
1471
|
const isNode = isNodeItem(item);
|
|
1472
|
+
const type = this.getRootType(name);
|
|
1465
1473
|
if (isNode || isNodesItem(item)) {
|
|
1466
1474
|
const fields = getSelectionPlan(item.view, null).paths;
|
|
1467
1475
|
for (const raw of isNode ? [item.id] : item.ids) {
|
|
1468
|
-
const entityId = toEntityId(
|
|
1476
|
+
const entityId = toEntityId(type, raw);
|
|
1469
1477
|
if (this.store.missingForSelection(entityId, fields).size > 0) return false;
|
|
1470
1478
|
}
|
|
1471
1479
|
continue;
|
|
@@ -1483,19 +1491,20 @@ var FateClient = class {
|
|
|
1483
1491
|
getRequestResult(request) {
|
|
1484
1492
|
const result = {};
|
|
1485
1493
|
for (const [name, item] of Object.entries(request)) {
|
|
1494
|
+
const type = this.getRootType(name);
|
|
1486
1495
|
if (isNodeItem(item)) {
|
|
1487
|
-
result[name] = this.ref(
|
|
1496
|
+
result[name] = this.ref(type, item.id, item.view);
|
|
1488
1497
|
continue;
|
|
1489
1498
|
}
|
|
1490
1499
|
if (isNodesItem(item)) {
|
|
1491
|
-
result[name] = item.ids.map((id$1) => this.ref(
|
|
1500
|
+
result[name] = item.ids.map((id$1) => this.ref(type, id$1, item.view));
|
|
1492
1501
|
continue;
|
|
1493
1502
|
}
|
|
1494
1503
|
if (isQueryItem(item)) {
|
|
1495
1504
|
const entityId = this.rootRequests.get(name);
|
|
1496
1505
|
if (entityId) {
|
|
1497
1506
|
const { id: id$1 } = parseEntityId(entityId);
|
|
1498
|
-
result[name] = createRef(
|
|
1507
|
+
result[name] = createRef(type, id$1, item.view, { root: true });
|
|
1499
1508
|
} else result[name] = null;
|
|
1500
1509
|
continue;
|
|
1501
1510
|
}
|
|
@@ -1519,7 +1528,7 @@ var FateClient = class {
|
|
|
1519
1528
|
owner: name,
|
|
1520
1529
|
procedure: `request.${name}`,
|
|
1521
1530
|
root: true,
|
|
1522
|
-
type
|
|
1531
|
+
type
|
|
1523
1532
|
};
|
|
1524
1533
|
Object.defineProperty(connection, ConnectionTag, {
|
|
1525
1534
|
configurable: false,
|
|
@@ -1543,21 +1552,22 @@ var FateClient = class {
|
|
|
1543
1552
|
if (!this.transport.fetchQuery) throw new Error(`fate: transport does not support queries. Please add support for 'fetchQuery' in your transport for '${name}'.`);
|
|
1544
1553
|
const record = await this.transport.fetchQuery(name, plan.paths, argsPayload);
|
|
1545
1554
|
if (!record || typeof record !== "object") return;
|
|
1546
|
-
const entityId = this.writeEntity(
|
|
1555
|
+
const entityId = this.writeEntity(this.getRootType(name), record, plan.paths, void 0, plan);
|
|
1547
1556
|
this.rootRequests.set(name, entityId);
|
|
1548
1557
|
}
|
|
1549
1558
|
async fetchListAndNormalize(name, item) {
|
|
1550
1559
|
if (!this.transport.fetchList) throw new Error(`fate: 'transport.fetchList' is not configured but request includes a list for key '${name}'.`);
|
|
1560
|
+
const type = this.getRootType(name);
|
|
1551
1561
|
const { argsPayload, plan } = this.resolveSelection(item.list, item.args);
|
|
1552
1562
|
const { items, pagination } = await this.transport.fetchList(name, plan.paths, argsPayload);
|
|
1553
1563
|
const ids = [];
|
|
1554
1564
|
const cursors = [];
|
|
1555
1565
|
for (const entry of items) {
|
|
1556
|
-
const id$1 = this.writeEntity(
|
|
1566
|
+
const id$1 = this.writeEntity(type, entry.node, plan.paths, void 0, plan);
|
|
1557
1567
|
ids.push(id$1);
|
|
1558
1568
|
cursors.push(entry.cursor);
|
|
1559
1569
|
}
|
|
1560
|
-
this.registerRootList(
|
|
1570
|
+
this.registerRootList(type, name);
|
|
1561
1571
|
this.store.setList(name, {
|
|
1562
1572
|
cursors,
|
|
1563
1573
|
ids,
|
|
@@ -1590,6 +1600,10 @@ var FateClient = class {
|
|
|
1590
1600
|
} else if (relationDescriptor && typeof relationDescriptor === "object" && "type" in relationDescriptor) {
|
|
1591
1601
|
if (isFieldBlocked) continue;
|
|
1592
1602
|
const childPaths = selectionTree.get(key) ?? emptySet;
|
|
1603
|
+
if (value === null) {
|
|
1604
|
+
result[key] = null;
|
|
1605
|
+
continue;
|
|
1606
|
+
}
|
|
1593
1607
|
if (value && typeof value === "object" && !isNodeRef(value)) {
|
|
1594
1608
|
const childType = relationDescriptor.type;
|
|
1595
1609
|
const childConfig = this.types.get(childType);
|
|
@@ -1600,6 +1614,10 @@ var FateClient = class {
|
|
|
1600
1614
|
} else if (relationDescriptor && typeof relationDescriptor === "object" && "listOf" in relationDescriptor) {
|
|
1601
1615
|
if (isFieldBlocked) continue;
|
|
1602
1616
|
const childPaths = selectionTree.get(key) ?? emptySet;
|
|
1617
|
+
if (value === null) {
|
|
1618
|
+
result[key] = null;
|
|
1619
|
+
continue;
|
|
1620
|
+
}
|
|
1603
1621
|
const childType = relationDescriptor.listOf;
|
|
1604
1622
|
const childConfig = this.types.get(childType);
|
|
1605
1623
|
if (!childConfig) throw new Error(`fate: Unknown related type '${childType}' (field '${type}.${key}').`);
|
|
@@ -1745,6 +1763,10 @@ var FateClient = class {
|
|
|
1745
1763
|
if (!(key in target)) target[key] = {};
|
|
1746
1764
|
const nextSelection = Object.keys(selectionWithoutArgs).length ? selectionWithoutArgs : selectionValue;
|
|
1747
1765
|
const value = record$1[key];
|
|
1766
|
+
if (value == null) {
|
|
1767
|
+
target[key] = null;
|
|
1768
|
+
continue;
|
|
1769
|
+
}
|
|
1748
1770
|
if (Array.isArray(value)) if (nextSelection.items && typeof nextSelection.items === "object") {
|
|
1749
1771
|
const selection = nextSelection.items;
|
|
1750
1772
|
const fieldArgs = plan.args.get(fieldPath);
|
|
@@ -1912,4 +1934,16 @@ function createTRPCTransport({ byId, client, lists, mutations, queries }) {
|
|
|
1912
1934
|
}
|
|
1913
1935
|
|
|
1914
1936
|
//#endregion
|
|
1915
|
-
|
|
1937
|
+
//#region src/root.ts
|
|
1938
|
+
/**
|
|
1939
|
+
* Defines a root query for an entity type, capturing the response shape.
|
|
1940
|
+
*/
|
|
1941
|
+
function clientRoot(type) {
|
|
1942
|
+
return Object.freeze({
|
|
1943
|
+
[RootKind]: true,
|
|
1944
|
+
type
|
|
1945
|
+
});
|
|
1946
|
+
}
|
|
1947
|
+
|
|
1948
|
+
//#endregion
|
|
1949
|
+
export { ConnectionTag, FateClient, clientRoot, createClient, createTRPCTransport, getSelectionPlan, isViewTag, mutation, toEntityId, view };
|
package/lib/server.d.mts
CHANGED
|
@@ -164,8 +164,9 @@ type RequestMode = /** (default) Use cached data if present, otherwise fetch. */
|
|
|
164
164
|
type RequestOptions = Readonly<{
|
|
165
165
|
mode?: RequestMode;
|
|
166
166
|
}>;
|
|
167
|
-
type FateClientOptions<Mutations extends FateMutations
|
|
167
|
+
type FateClientOptions<Roots extends FateRoots, Mutations extends FateMutations> = {
|
|
168
168
|
mutations?: Mutations;
|
|
169
|
+
roots: Roots;
|
|
169
170
|
transport: Transport<MutationMapFromDefinitions<Mutations>>;
|
|
170
171
|
types: ReadonlyArray<Omit<TypeConfig, 'getId'> & Partial<{
|
|
171
172
|
getId: TypeConfig['getId'];
|
|
@@ -175,7 +176,7 @@ type FateClientOptions<Mutations extends FateMutations = EmptyMutations> = {
|
|
|
175
176
|
* Core client that normalizes records, manages the view cache, and coordinates
|
|
176
177
|
* data fetching.
|
|
177
178
|
*/
|
|
178
|
-
declare class FateClient<Mutations extends FateMutations> {
|
|
179
|
+
declare class FateClient<Roots extends FateRoots, Mutations extends FateMutations> {
|
|
179
180
|
private readonly mutationMap;
|
|
180
181
|
private readonly parentLists;
|
|
181
182
|
private readonly rootLists;
|
|
@@ -192,7 +193,8 @@ declare class FateClient<Mutations extends FateMutations> {
|
|
|
192
193
|
private readonly viewDataCache;
|
|
193
194
|
readonly actions: MutationActionsFor<Mutations>;
|
|
194
195
|
readonly mutations: MutationFunctionsFor<Mutations>;
|
|
195
|
-
|
|
196
|
+
readonly roots: Roots;
|
|
197
|
+
constructor(options: FateClientOptions<Roots, Mutations>);
|
|
196
198
|
private initializeParentLists;
|
|
197
199
|
private registerRootList;
|
|
198
200
|
private insertIntoRootLists;
|
|
@@ -228,12 +230,13 @@ declare class FateClient<Mutations extends FateMutations> {
|
|
|
228
230
|
ids: ReadonlyArray<EntityId>;
|
|
229
231
|
pagination?: Pagination;
|
|
230
232
|
}> | undefined>;
|
|
231
|
-
request<R$1 extends Request>(request: R$1, options?: RequestOptions): Promise<RequestResult<R$1>>;
|
|
233
|
+
request<R$1 extends Request>(request: R$1, options?: RequestOptions): Promise<RequestResult<Roots, R$1>>;
|
|
232
234
|
releaseRequest(request: Request, mode: RequestMode): void;
|
|
233
235
|
private handleStoreAndNetworkRequest;
|
|
236
|
+
private getRootType;
|
|
234
237
|
private executeRequest;
|
|
235
238
|
private hasRequestData;
|
|
236
|
-
getRequestResult<R$1 extends Request>(request: R$1): RequestResult<R$1>;
|
|
239
|
+
getRequestResult<R$1 extends Request>(request: R$1): RequestResult<Roots, R$1>;
|
|
237
240
|
private fetchByIdAndNormalize;
|
|
238
241
|
private fetchQueryAndNormalize;
|
|
239
242
|
private fetchListAndNormalize;
|
|
@@ -245,7 +248,7 @@ declare class FateClient<Mutations extends FateMutations> {
|
|
|
245
248
|
private pendingPrefix;
|
|
246
249
|
private pendingKey;
|
|
247
250
|
}
|
|
248
|
-
declare function createClient<
|
|
251
|
+
declare function createClient<T$1 extends [FateRoots, FateMutations] = [Record<never, RootDefinition<any, any>>, Record<never, MutationDefinition<any, any, any>>]>(options: FateClientOptions<T$1[0], T$1[1]>): FateClient<T$1[0], T$1[1]>;
|
|
249
252
|
//#endregion
|
|
250
253
|
//#region src/mutation.d.ts
|
|
251
254
|
/**
|
|
@@ -303,7 +306,6 @@ type MutationIdentifierFor<K$1 extends string, Def extends MutationDefinition<an
|
|
|
303
306
|
}> : never;
|
|
304
307
|
type UnionToIntersection<U$1> = (U$1 extends any ? (k: U$1) => void : never) extends ((k: infer I) => void) ? I : never;
|
|
305
308
|
type NestedValue<Path extends string, Value> = Path extends `${infer Head}.${infer Tail}` ? { [K in Head]: NestedValue<Tail, Value> } : { [K in Path]: Value };
|
|
306
|
-
type EmptyMutations = Record<never, MutationDefinition<any, any, any>>;
|
|
307
309
|
/**
|
|
308
310
|
* Base type for client mutations.
|
|
309
311
|
*/
|
|
@@ -328,6 +330,8 @@ declare const __FateSelectionBrand: unique symbol;
|
|
|
328
330
|
declare const __FateMutationEntityBrand: unique symbol;
|
|
329
331
|
declare const __FateMutationInputBrand: unique symbol;
|
|
330
332
|
declare const __FateMutationResultBrand: unique symbol;
|
|
333
|
+
declare const __FateRootResultBrand: unique symbol;
|
|
334
|
+
declare const __FateRootTypeBrand: unique symbol;
|
|
331
335
|
type __ViewEntityAnchor<T$1 extends Entity> = {
|
|
332
336
|
readonly [__FateEntityBrand]?: T$1;
|
|
333
337
|
};
|
|
@@ -343,12 +347,21 @@ type __MutationInputAnchor<I$1> = {
|
|
|
343
347
|
type __MutationResultAnchor<R$1> = {
|
|
344
348
|
readonly [__FateMutationResultBrand]?: R$1;
|
|
345
349
|
};
|
|
350
|
+
type __RootResultAnchor<R$1> = {
|
|
351
|
+
readonly [__FateRootResultBrand]?: R$1;
|
|
352
|
+
};
|
|
353
|
+
type __RootTypeAnchor<T$1 extends TypeName> = {
|
|
354
|
+
readonly [__FateRootTypeBrand]?: T$1;
|
|
355
|
+
};
|
|
346
356
|
/** Unique key that identifies a view composition entry inside a selection or reference. */
|
|
347
357
|
type ViewTag = `__fate-view__${string}`;
|
|
348
358
|
/** Determines whether a property key is a fate view tag. */
|
|
349
359
|
declare function isViewTag(key: string): key is ViewTag;
|
|
350
360
|
/** Alias for a loose record used throughout the fate's internals. */
|
|
351
361
|
type AnyRecord = Record<string, unknown>;
|
|
362
|
+
type Nullish<T$1> = Extract<T$1, null | undefined>;
|
|
363
|
+
type NonNullish<T$1> = Exclude<T$1, null | undefined>;
|
|
364
|
+
type WithNullish<T$1, R$1> = R$1 | Nullish<T$1>;
|
|
352
365
|
type SelectionArgs = Readonly<{
|
|
353
366
|
args: AnyRecord;
|
|
354
367
|
}>;
|
|
@@ -478,7 +491,8 @@ type EntityName<T$1> = T$1 extends {
|
|
|
478
491
|
__typename: infer N extends string;
|
|
479
492
|
} ? N : never;
|
|
480
493
|
/** Recursively applies a view selection to an entity to mask fields that aren't selected. */
|
|
481
|
-
type
|
|
494
|
+
type MaskNonNullish<T$1, S> = T$1 extends Array<infer U extends Entity> ? S extends true ? Array<U> : S extends ConnectionSelection<U> ? ConnectionMask<U, S> : HasViewTag<S> extends true ? Array<ViewRef<U['__typename']>> : Array<Mask<U, S>> : S extends true ? T$1 : S extends object ? HasViewTag<S> extends true ? NonNullish<T$1> extends Entity ? ViewRef<EntityName<NonNullish<T$1>>> : ViewRef<EntityName<NonNullable<T$1>>> : { [K in keyof S as K extends 'args' ? never : K]: S[K] extends true ? NonNullish<T$1>[Extract<K, keyof T$1>] : Mask<NonNullish<T$1>[Extract<K, keyof T$1>], Extract<S[K], object>> } & (T$1 extends Entity ? Pick<NonNullish<T$1>, '__typename'> : Record<never, never>) : T$1;
|
|
495
|
+
type Mask<T$1, S> = WithNullish<T$1, MaskNonNullish<NonNullish<T$1>, S>>;
|
|
482
496
|
/** Entity type captured from a view definition. */
|
|
483
497
|
type ViewEntity<V> = V extends View<infer T, any> ? T : never;
|
|
484
498
|
/** Name of the entity type captured from a view definition. */
|
|
@@ -491,24 +505,20 @@ type ViewSelection<V> = V extends {
|
|
|
491
505
|
type ListItem<V extends View<any, any>> = Readonly<{
|
|
492
506
|
args?: Record<string, unknown>;
|
|
493
507
|
list: V;
|
|
494
|
-
type: ViewEntityName<V>;
|
|
495
508
|
}>;
|
|
496
509
|
/** Definition of a root-level query request. */
|
|
497
510
|
type QueryItem<V extends View<any, any>> = Readonly<{
|
|
498
511
|
args?: Record<string, unknown>;
|
|
499
|
-
type: ViewEntityName<V>;
|
|
500
512
|
view: V;
|
|
501
513
|
}>;
|
|
502
514
|
/** Definition of a node request with one explicit ID for fetching data from the backend. */
|
|
503
515
|
type NodeItem<V extends View<any, any>> = Readonly<{
|
|
504
516
|
id: string | number;
|
|
505
|
-
type: ViewEntityName<V>;
|
|
506
517
|
view: V;
|
|
507
518
|
}>;
|
|
508
519
|
/** Definition of a node request with explicit IDs for fetching data from the backend. */
|
|
509
520
|
type NodesItem<V extends View<any, any>> = Readonly<{
|
|
510
521
|
ids: ReadonlyArray<string | number>;
|
|
511
|
-
type: ViewEntityName<V>;
|
|
512
522
|
view: V;
|
|
513
523
|
}>;
|
|
514
524
|
type RequestItem = ListItem<View<any, any>> | NodeItem<View<any, any>> | NodesItem<View<any, any>> | QueryItem<View<any, any>>;
|
|
@@ -530,7 +540,7 @@ type ConnectionNodeType<Root> = Root extends {
|
|
|
530
540
|
node?: infer Node;
|
|
531
541
|
};
|
|
532
542
|
} ? ViewEntityName<Node & View<any, any>> : never;
|
|
533
|
-
type ListResult<Item extends AnyRequestItem> = Item extends AnyNodeItem ? ViewRef<
|
|
543
|
+
type ListResult<Item extends AnyRequestItem, Type extends TypeName, Result> = Item extends AnyNodeItem ? ViewRef<Type> : Item extends AnyNodesItem ? Array<ViewRef<Type>> : Item extends AnyQueryItem ? Result extends null ? ViewRef<Type> | null : ViewRef<Type> : Item extends AnyListItem ? Item['list'] extends {
|
|
534
544
|
items?: {
|
|
535
545
|
node?: View<any, any>;
|
|
536
546
|
};
|
|
@@ -540,14 +550,27 @@ type ListResult<Item extends AnyRequestItem> = Item extends AnyNodeItem ? ViewRe
|
|
|
540
550
|
node: ViewRef<ConnectionNodeType<Item['list']>>;
|
|
541
551
|
}>;
|
|
542
552
|
pagination?: Pagination;
|
|
543
|
-
}> : Array<ViewRef<
|
|
553
|
+
}> : Array<ViewRef<Type>> : never;
|
|
544
554
|
/**
|
|
545
555
|
* The result of a `FateClient.request` and `useRequest` call, mapping each
|
|
546
556
|
* request key to its corresponding result.
|
|
547
557
|
*/
|
|
548
|
-
type RequestResult<Q extends AnyRequest> = { [K in keyof Q]: ListResult<Q[K]
|
|
558
|
+
type RequestResult<R$1 extends FateRoots, Q extends AnyRequest> = { [K in keyof Q]: K extends keyof R$1 ? ListResult<Q[K], RootType<R$1[K]>, RootResult<R$1[K]>> : never };
|
|
559
|
+
/** Brand used on root definitions to mark their identity in the d.ts output. */
|
|
560
|
+
declare const RootKind = "__fate__root";
|
|
549
561
|
/** Brand used on mutation definitions to mark their identity in the d.ts output. */
|
|
550
562
|
declare const MutationKind = "__fate__mutation";
|
|
563
|
+
/** Metadata describing a root query for a particular entity and result shape. */
|
|
564
|
+
type RootDefinition<Type extends TypeName, Result> = Readonly<{
|
|
565
|
+
[RootKind]: true;
|
|
566
|
+
type: Type;
|
|
567
|
+
}> & __RootResultAnchor<Result> & __RootTypeAnchor<Type>;
|
|
568
|
+
/** Minimal root description used for typing root maps. */
|
|
569
|
+
type FateRoots = Record<string, RootDefinition<TypeName, unknown>>;
|
|
570
|
+
/** Extracts the entity type name from a root definition. */
|
|
571
|
+
type RootType<R$1> = R$1 extends __RootTypeAnchor<infer T> ? T : never;
|
|
572
|
+
/** Extracts the result type from a root definition. */
|
|
573
|
+
type RootResult<R$1> = R$1 extends __RootResultAnchor<infer Data> ? Data : never;
|
|
551
574
|
/** Metadata describing a mutation for a particular entity, input, and output. */
|
|
552
575
|
type MutationDefinition<T$1 extends Entity, I$1, R$1> = Readonly<{
|
|
553
576
|
entity: T$1['__typename'];
|
|
@@ -575,8 +598,6 @@ type MutationMapFromDefinitions<D extends FateMutations> = { [K in keyof D]: {
|
|
|
575
598
|
input: MutationInput<D[K]>;
|
|
576
599
|
output: MutationResult<D[K]>;
|
|
577
600
|
} };
|
|
578
|
-
type Nullish<T$1> = Extract<T$1, null | undefined>;
|
|
579
|
-
type NonNullish<T$1> = Exclude<T$1, null | undefined>;
|
|
580
601
|
type OptimisticUpdateValue<T$1> = T$1 extends ReadonlyArray<infer U> ? Array<OptimisticUpdateValue<U>> : NonNullish<T$1> extends AnyRecord ? OptimisticUpdate<NonNullish<T$1>> | Nullish<T$1> : NonNullish<T$1> | Nullish<T$1>;
|
|
581
602
|
/** Shape used to describe optimistic updates for mutations. */
|
|
582
603
|
type OptimisticUpdate<T$1> = { [K in keyof T$1]?: OptimisticUpdateValue<T$1[K]> };
|
|
@@ -591,4 +612,4 @@ interface FateThenable<T$1> extends PromiseLike<T$1> {
|
|
|
591
612
|
value: T$1;
|
|
592
613
|
}
|
|
593
614
|
//#endregion
|
|
594
|
-
export {
|
|
615
|
+
export { ViewSelection as A, RequestOptions as B, TypeConfig as C, ViewEntity as D, ViewData as E, isViewTag as F, Transport as H, FateMutations as I, mutation as L, ViewTag as M, __FateEntityBrand as N, ViewEntityName as O, __FateSelectionBrand as P, FateClient as R, Snapshot as S, View as T, createTRPCTransport as U, createClient as V, getSelectionPlan as W, Pagination as _, Entity as a, RootDefinition as b, FateThenable as c, MutationDefinition as d, MutationEntity as f, NodesItem as g, MutationResult as h, ConnectionTag as i, ViewSnapshot as j, ViewRef as k, ListItem as l, MutationInput as m, ConnectionMetadata as n, EntityId as o, MutationIdentifier as p, ConnectionRef as r, FateRoots as s, AnyRecord as t, Mask as u, Request as v, TypeName as w, Selection as x, RequestResult as y, RequestMode as z };
|