react-fate 0.0.8 → 0.1.1
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/index.d.mts +17 -15
- package/lib/index.mjs +24 -14
- package/package.json +2 -2
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/index.d.mts
CHANGED
|
@@ -1,7 +1,18 @@
|
|
|
1
|
-
import { ConnectionRef, FateClient as FateClient$1, FateMutations, Pagination, Request, RequestOptions, RequestResult, View, ViewData, ViewEntity, ViewEntityName, ViewRef, ViewRef as ViewRef$1, ViewSelection, createClient, createTRPCTransport, mutation, view } from "@nkzw/fate";
|
|
1
|
+
import { ConnectionRef, FateClient as FateClient$1, FateMutations, FateRoots, Pagination, Request, RequestOptions, RequestResult, View, ViewData, ViewEntity, ViewEntityName, ViewRef, ViewRef as ViewRef$1, ViewSelection, clientRoot, createClient, createTRPCTransport, mutation, toEntityId, view } from "@nkzw/fate";
|
|
2
2
|
import { ReactNode } from "react";
|
|
3
3
|
import * as react_jsx_runtime0 from "react/jsx-runtime";
|
|
4
4
|
|
|
5
|
+
//#region src/useRequest.d.ts
|
|
6
|
+
type Roots = keyof ClientRoots extends never ? FateRoots : ClientRoots;
|
|
7
|
+
/**
|
|
8
|
+
* Declares the data a screen needs and kicks off fetching, suspending while the
|
|
9
|
+
* request resolves.
|
|
10
|
+
*
|
|
11
|
+
* @example
|
|
12
|
+
* const { posts } = useRequest({ posts: { list: PostView } });
|
|
13
|
+
*/
|
|
14
|
+
declare function useRequest<R extends Request, O extends FateRoots = Roots>(request: R, options?: RequestOptions): RequestResult<O, R>;
|
|
15
|
+
//#endregion
|
|
5
16
|
//#region src/context.d.ts
|
|
6
17
|
type Mutations = keyof ClientMutations extends never ? FateMutations : ClientMutations;
|
|
7
18
|
/**
|
|
@@ -12,12 +23,12 @@ declare function FateClient({
|
|
|
12
23
|
client
|
|
13
24
|
}: {
|
|
14
25
|
children: ReactNode;
|
|
15
|
-
client: FateClient$1<any>;
|
|
26
|
+
client: FateClient$1<any, any>;
|
|
16
27
|
}): react_jsx_runtime0.JSX.Element;
|
|
17
28
|
/**
|
|
18
29
|
* Returns the nearest `FateClient` from context.
|
|
19
30
|
*/
|
|
20
|
-
declare function useFateClient<
|
|
31
|
+
declare function useFateClient<T extends [Roots, Mutations]>(): FateClient$1<T[0], T[1]>;
|
|
21
32
|
//#endregion
|
|
22
33
|
//#region src/useView.d.ts
|
|
23
34
|
type ViewEntityWithTypename<V extends View<any, any>> = ViewEntity<V> & {
|
|
@@ -29,17 +40,7 @@ type ViewEntityWithTypename<V extends View<any, any>> = ViewEntity<V> & {
|
|
|
29
40
|
* @example
|
|
30
41
|
* const post = useView(PostView, postRef);
|
|
31
42
|
*/
|
|
32
|
-
declare function useView<V extends View<any, any>>(view: V, ref:
|
|
33
|
-
//#endregion
|
|
34
|
-
//#region src/useRequest.d.ts
|
|
35
|
-
/**
|
|
36
|
-
* Declares the data a screen needs and kicks off fetching, suspending while the
|
|
37
|
-
* request resolves.
|
|
38
|
-
*
|
|
39
|
-
* @example
|
|
40
|
-
* const { posts } = useRequest({ posts: { root: PostView, type: 'Post' } as const });
|
|
41
|
-
*/
|
|
42
|
-
declare function useRequest<R extends Request>(request: R, options?: RequestOptions): RequestResult<R>;
|
|
43
|
+
declare function useView<V extends View<any, any>, R extends ViewRef$1<ViewEntityName<V>> | null>(view: V, ref: R): R extends null ? null : ViewData<ViewEntityWithTypename<V>, ViewSelection<V>>;
|
|
43
44
|
//#endregion
|
|
44
45
|
//#region src/useListView.d.ts
|
|
45
46
|
type ConnectionItems<C> = C extends {
|
|
@@ -62,5 +63,6 @@ declare function useListView<C extends {
|
|
|
62
63
|
//#endregion
|
|
63
64
|
//#region src/index.d.ts
|
|
64
65
|
interface ClientMutations {}
|
|
66
|
+
interface ClientRoots {}
|
|
65
67
|
//#endregion
|
|
66
|
-
export { ClientMutations, type ConnectionRef, FateClient, type ViewRef, createClient, createTRPCTransport, mutation, useFateClient, useListView, useRequest, useView, view };
|
|
68
|
+
export { ClientMutations, ClientRoots, type ConnectionRef, FateClient, type ViewRef, clientRoot, createClient, createTRPCTransport, mutation, toEntityId, useFateClient, useListView, useRequest, useView, view };
|
package/lib/index.mjs
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { ConnectionTag, createClient, createTRPCTransport, isViewTag, mutation, view } from "@nkzw/fate";
|
|
1
|
+
import { ConnectionTag, clientRoot, createClient, createTRPCTransport, isViewTag, mutation, toEntityId, view } from "@nkzw/fate";
|
|
2
2
|
import { createContext, use, useCallback, useDeferredValue, useEffect, useMemo, useRef, useSyncExternalStore } from "react";
|
|
3
3
|
import { jsx } from "react/jsx-runtime";
|
|
4
4
|
|
|
@@ -24,25 +24,34 @@ function useFateClient() {
|
|
|
24
24
|
|
|
25
25
|
//#endregion
|
|
26
26
|
//#region src/useView.tsx
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
27
|
+
const nullSnapshot = {
|
|
28
|
+
status: "fulfilled",
|
|
29
|
+
then(onfulfilled, onrejected) {
|
|
30
|
+
return Promise.resolve(null).then(onfulfilled, onrejected);
|
|
31
|
+
},
|
|
32
|
+
value: null
|
|
33
|
+
};
|
|
33
34
|
function useView(view$1, ref) {
|
|
34
35
|
const client = useFateClient();
|
|
35
36
|
const snapshotRef = useRef(null);
|
|
36
37
|
const getSnapshot = useCallback(() => {
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
38
|
+
if (ref === null) {
|
|
39
|
+
snapshotRef.current = null;
|
|
40
|
+
return nullSnapshot;
|
|
41
|
+
}
|
|
42
|
+
const snapshot$1 = client.readView(view$1, ref);
|
|
43
|
+
snapshotRef.current = snapshot$1.status === "fulfilled" ? snapshot$1.value : null;
|
|
44
|
+
return snapshot$1;
|
|
40
45
|
}, [
|
|
41
46
|
client,
|
|
42
47
|
view$1,
|
|
43
48
|
ref
|
|
44
49
|
]);
|
|
45
|
-
|
|
50
|
+
const snapshot = use(useDeferredValue(useSyncExternalStore(useCallback((onStoreChange) => {
|
|
51
|
+
if (ref === null) {
|
|
52
|
+
snapshotRef.current = null;
|
|
53
|
+
return () => {};
|
|
54
|
+
}
|
|
46
55
|
const subscriptions = /* @__PURE__ */ new Map();
|
|
47
56
|
const onChange = () => {
|
|
48
57
|
updateSubscriptions();
|
|
@@ -68,7 +77,8 @@ function useView(view$1, ref) {
|
|
|
68
77
|
for (const unsubscribe of subscriptions.values()) unsubscribe();
|
|
69
78
|
subscriptions.clear();
|
|
70
79
|
};
|
|
71
|
-
}, [client.store]), getSnapshot, getSnapshot)))
|
|
80
|
+
}, [client.store, ref]), getSnapshot, getSnapshot)));
|
|
81
|
+
return snapshot ? snapshot.data : null;
|
|
72
82
|
}
|
|
73
83
|
|
|
74
84
|
//#endregion
|
|
@@ -78,7 +88,7 @@ function useView(view$1, ref) {
|
|
|
78
88
|
* request resolves.
|
|
79
89
|
*
|
|
80
90
|
* @example
|
|
81
|
-
* const { posts } = useRequest({ posts: {
|
|
91
|
+
* const { posts } = useRequest({ posts: { list: PostView } });
|
|
82
92
|
*/
|
|
83
93
|
function useRequest(request, options) {
|
|
84
94
|
const client = useFateClient();
|
|
@@ -173,4 +183,4 @@ function useListView(selection, connection) {
|
|
|
173
183
|
}
|
|
174
184
|
|
|
175
185
|
//#endregion
|
|
176
|
-
export { FateClient, createClient, createTRPCTransport, mutation, useFateClient, useListView, useRequest, useView, view };
|
|
186
|
+
export { FateClient, clientRoot, createClient, createTRPCTransport, mutation, toEntityId, useFateClient, useListView, useRequest, useView, view };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "react-fate",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.1.1",
|
|
4
4
|
"description": "fate is a modern data client for React.",
|
|
5
5
|
"homepage": "https://github.com/nkzw-tech/fate",
|
|
6
6
|
"repository": {
|
|
@@ -28,7 +28,7 @@
|
|
|
28
28
|
"lib"
|
|
29
29
|
],
|
|
30
30
|
"dependencies": {
|
|
31
|
-
"@nkzw/fate": "^0.
|
|
31
|
+
"@nkzw/fate": "^0.1.1"
|
|
32
32
|
},
|
|
33
33
|
"devDependencies": {
|
|
34
34
|
"@types/react": "^19.2.7",
|