react-fate 1.1.0 → 1.2.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 +745 -24
- package/docs/api/functions/FateClient.md +1 -1
- package/docs/api/functions/clientRoot.md +1 -1
- package/docs/api/functions/createClient.md +1 -1
- package/docs/api/functions/createGraphQLTransport.md +1 -1
- package/docs/api/functions/createHTTPTransport.md +1 -1
- package/docs/api/functions/createTRPCTransport.md +1 -1
- package/docs/api/functions/defer.md +21 -0
- package/docs/api/functions/graphqlMutation.md +1 -1
- package/docs/api/functions/mutation.md +1 -1
- package/docs/api/functions/useFateClient.md +1 -1
- package/docs/api/functions/useListView.md +4 -4
- package/docs/api/functions/useLiveListView.md +4 -4
- package/docs/api/functions/useLiveView.md +52 -11
- package/docs/api/functions/useRequest.md +1 -1
- package/docs/api/functions/useView.md +85 -11
- package/docs/api/functions/view.md +1 -1
- package/docs/api/index.md +2 -0
- package/docs/api/type-aliases/ConnectionRef.md +1 -1
- package/docs/api/type-aliases/Deferred.md +19 -0
- package/docs/api/type-aliases/FateDehydratedState.md +1 -1
- package/docs/api/type-aliases/GraphQLMutationDefinition.md +1 -1
- package/docs/api/type-aliases/GraphQLMutationInput.md +1 -1
- package/docs/api/type-aliases/GraphQLMutationMap.md +1 -1
- package/docs/api/type-aliases/GraphQLMutationOutput.md +1 -1
- package/docs/api/type-aliases/GraphQLTransportOptions.md +11 -11
- package/docs/api/type-aliases/HydrateOptions.md +1 -1
- package/docs/api/type-aliases/HydrationLimits.md +1 -1
- package/docs/api/type-aliases/InferFateAPI.md +1 -1
- package/docs/api/type-aliases/Pagination.md +5 -5
- package/docs/api/type-aliases/ViewRef.md +1 -1
- package/docs/api/variables/toEntityId.md +1 -1
- package/docs/guide/actions.md +7 -7
- package/docs/guide/deferred-views.md +62 -0
- package/docs/guide/getting-started.md +21 -3
- package/docs/guide/vue.md +256 -0
- package/docs/index.md +1 -0
- package/docs/{guide/graphql-integration.md → integrations/graphql.md} +53 -6
- package/docs/{guide/server-integration.md → integrations/server.md} +93 -7
- package/docs/{guide/void-integration.md → integrations/void.md} +94 -12
- package/lib/index.d.mts +17 -10
- package/lib/index.mjs +110 -16
- package/package.json +2 -2
|
@@ -4,7 +4,7 @@ Until now, we have focused on the client-side API of fate. You'll need a backend
|
|
|
4
4
|
|
|
5
5
|
- The native fate protocol, which is transport-agnostic and can be hosted by any Fetch-compatible server.
|
|
6
6
|
- The tRPC adapter, which keeps compatibility with existing tRPC backends.
|
|
7
|
-
- The [GraphQL transport](graphql
|
|
7
|
+
- The [GraphQL transport](graphql.md), which maps fate views and roots to an existing GraphQL schema.
|
|
8
8
|
|
|
9
9
|
_fate_ currently provides database adapters for Prisma and Drizzle, but the framework itself is not coupled to a particular ORM. The adapters plug into the same source execution runtime and can be exposed through the native protocol or through tRPC.
|
|
10
10
|
|
|
@@ -185,7 +185,9 @@ app.post('/fate/live', handler);
|
|
|
185
185
|
|
|
186
186
|
Configure the Vite plugin with the native transport:
|
|
187
187
|
|
|
188
|
-
|
|
188
|
+
::: code-group
|
|
189
|
+
|
|
190
|
+
```tsx [React]
|
|
189
191
|
import { fate } from 'react-fate/vite';
|
|
190
192
|
import { defineConfig } from 'vite';
|
|
191
193
|
|
|
@@ -199,9 +201,29 @@ export default defineConfig({
|
|
|
199
201
|
});
|
|
200
202
|
```
|
|
201
203
|
|
|
204
|
+
```ts [Vue]
|
|
205
|
+
import vue from '@vitejs/plugin-vue';
|
|
206
|
+
import { fate } from 'vue-fate/vite';
|
|
207
|
+
import { defineConfig } from 'vite';
|
|
208
|
+
|
|
209
|
+
export default defineConfig({
|
|
210
|
+
plugins: [
|
|
211
|
+
vue(),
|
|
212
|
+
fate({
|
|
213
|
+
module: '@your-org/server/fate.ts',
|
|
214
|
+
transport: 'native',
|
|
215
|
+
}),
|
|
216
|
+
],
|
|
217
|
+
});
|
|
218
|
+
```
|
|
219
|
+
|
|
220
|
+
:::
|
|
221
|
+
|
|
202
222
|
With the native transport, the Vite plugin handles the HTTP transport setup. If you need to create a client manually, use `createFateClient` with the same route:
|
|
203
223
|
|
|
204
|
-
|
|
224
|
+
::: code-group
|
|
225
|
+
|
|
226
|
+
```tsx [React]
|
|
205
227
|
import { createFateClient } from 'react-fate/client';
|
|
206
228
|
|
|
207
229
|
const client = createFateClient({
|
|
@@ -209,6 +231,16 @@ const client = createFateClient({
|
|
|
209
231
|
});
|
|
210
232
|
```
|
|
211
233
|
|
|
234
|
+
```ts [Vue]
|
|
235
|
+
import { createFateClient } from 'vue-fate/client';
|
|
236
|
+
|
|
237
|
+
const client = createFateClient({
|
|
238
|
+
url: '/fate',
|
|
239
|
+
});
|
|
240
|
+
```
|
|
241
|
+
|
|
242
|
+
:::
|
|
243
|
+
|
|
212
244
|
The HTTP transport batches operations issued in the same microtask into one `POST /fate` request. Live views use one `GET /fate/live` SSE stream per fate client and `POST /fate/live` control messages when views subscribe or unsubscribe.
|
|
213
245
|
|
|
214
246
|
### Custom Queries
|
|
@@ -585,7 +617,9 @@ export * from './views.ts';
|
|
|
585
617
|
|
|
586
618
|
Configure the fate Vite plugin with your server module:
|
|
587
619
|
|
|
588
|
-
|
|
620
|
+
::: code-group
|
|
621
|
+
|
|
622
|
+
```tsx [React]
|
|
589
623
|
import { fate } from 'react-fate/vite';
|
|
590
624
|
import { defineConfig } from 'vite';
|
|
591
625
|
|
|
@@ -598,11 +632,28 @@ export default defineConfig({
|
|
|
598
632
|
});
|
|
599
633
|
```
|
|
600
634
|
|
|
635
|
+
```ts [Vue]
|
|
636
|
+
import vue from '@vitejs/plugin-vue';
|
|
637
|
+
import { fate } from 'vue-fate/vite';
|
|
638
|
+
import { defineConfig } from 'vite';
|
|
639
|
+
|
|
640
|
+
export default defineConfig({
|
|
641
|
+
plugins: [
|
|
642
|
+
vue(),
|
|
643
|
+
fate({
|
|
644
|
+
module: '@your-org/server/trpc/router.ts',
|
|
645
|
+
}),
|
|
646
|
+
],
|
|
647
|
+
});
|
|
648
|
+
```
|
|
649
|
+
|
|
650
|
+
:::
|
|
651
|
+
|
|
601
652
|
_Note: fate uses the specified server module name to find the server types it needs. Make sure that the module is available to the client package's Vite config._
|
|
602
653
|
|
|
603
654
|
During development, the plugin watches the server module and the files it imports. When one of those files changes, fate updates the internal client wiring and invalidates `@nkzw/fate/client` in Vite's module graph.
|
|
604
655
|
|
|
605
|
-
For a barebones client without
|
|
656
|
+
For a barebones client without a framework adapter, import the plugin from `@nkzw/fate/vite` and the client APIs from `@nkzw/fate/client`. The plugin wires the same server types for the selected import path.
|
|
606
657
|
|
|
607
658
|
The plugin writes project-local types under `.fate/`. If your TypeScript config does not already include dot-directories, extend the generated config:
|
|
608
659
|
|
|
@@ -614,9 +665,11 @@ The plugin writes project-local types under `.fate/`. If your TypeScript config
|
|
|
614
665
|
|
|
615
666
|
## Creating a _fate_ Client
|
|
616
667
|
|
|
617
|
-
Now that the Vite plugin has connected the types, create a fate client instance and provide it to your
|
|
668
|
+
Now that the Vite plugin has connected the types, create a fate client instance and provide it to your app with the `FateClient` provider:
|
|
618
669
|
|
|
619
|
-
|
|
670
|
+
::: code-group
|
|
671
|
+
|
|
672
|
+
```tsx [React]
|
|
620
673
|
import { httpBatchLink } from '@trpc/client';
|
|
621
674
|
import { FateClient } from 'react-fate';
|
|
622
675
|
import { createFateClient } from 'react-fate/client';
|
|
@@ -642,4 +695,37 @@ export function App() {
|
|
|
642
695
|
}
|
|
643
696
|
```
|
|
644
697
|
|
|
698
|
+
```vue [Vue]
|
|
699
|
+
<script setup lang="ts">
|
|
700
|
+
import { httpBatchLink } from '@trpc/client';
|
|
701
|
+
import { computed } from 'vue';
|
|
702
|
+
import { FateClient } from 'vue-fate';
|
|
703
|
+
import { createFateClient } from 'vue-fate/client';
|
|
704
|
+
import AppRoutes from './AppRoutes.vue';
|
|
705
|
+
|
|
706
|
+
const fate = computed(() =>
|
|
707
|
+
createFateClient({
|
|
708
|
+
links: [
|
|
709
|
+
httpBatchLink({
|
|
710
|
+
fetch: (input, init) =>
|
|
711
|
+
fetch(input, {
|
|
712
|
+
...init,
|
|
713
|
+
credentials: 'include',
|
|
714
|
+
}),
|
|
715
|
+
url: `${env('SERVER_URL')}/trpc`,
|
|
716
|
+
}),
|
|
717
|
+
],
|
|
718
|
+
}),
|
|
719
|
+
);
|
|
720
|
+
</script>
|
|
721
|
+
|
|
722
|
+
<template>
|
|
723
|
+
<FateClient :client="fate">
|
|
724
|
+
<AppRoutes />
|
|
725
|
+
</FateClient>
|
|
726
|
+
</template>
|
|
727
|
+
```
|
|
728
|
+
|
|
729
|
+
:::
|
|
730
|
+
|
|
645
731
|
_And you are all set. Happy building!_
|
|
@@ -7,15 +7,25 @@ setup without copying its adapter glue.
|
|
|
7
7
|
|
|
8
8
|
## Install
|
|
9
9
|
|
|
10
|
-
|
|
11
|
-
|
|
10
|
+
::: code-group
|
|
11
|
+
|
|
12
|
+
```sh [React]
|
|
13
|
+
pnpm add @nkzw/fate react-fate void-fate void @void/react
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
```sh [Vue]
|
|
17
|
+
pnpm add @nkzw/fate vue-fate void-fate void @void/vue
|
|
12
18
|
```
|
|
13
19
|
|
|
20
|
+
:::
|
|
21
|
+
|
|
14
22
|
## Vite
|
|
15
23
|
|
|
16
|
-
Use the
|
|
24
|
+
Use the framework adapter's Vite plugin with the Void transport:
|
|
17
25
|
|
|
18
|
-
|
|
26
|
+
::: code-group
|
|
27
|
+
|
|
28
|
+
```tsx [React]
|
|
19
29
|
import { voidReact } from '@void/react/plugin';
|
|
20
30
|
import { fate } from 'react-fate/vite';
|
|
21
31
|
import { defineConfig } from 'vite-plus';
|
|
@@ -33,6 +43,26 @@ export default defineConfig({
|
|
|
33
43
|
});
|
|
34
44
|
```
|
|
35
45
|
|
|
46
|
+
```ts [Vue]
|
|
47
|
+
import { voidVue } from '@void/vue/plugin';
|
|
48
|
+
import { fate } from 'vue-fate/vite';
|
|
49
|
+
import { defineConfig } from 'vite-plus';
|
|
50
|
+
import { voidPlugin } from 'void';
|
|
51
|
+
|
|
52
|
+
export default defineConfig({
|
|
53
|
+
plugins: [
|
|
54
|
+
voidPlugin(),
|
|
55
|
+
voidVue(),
|
|
56
|
+
fate({
|
|
57
|
+
module: './src/fate/server.ts',
|
|
58
|
+
transport: 'void',
|
|
59
|
+
}),
|
|
60
|
+
],
|
|
61
|
+
});
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
:::
|
|
65
|
+
|
|
36
66
|
The Void transport uses `/fate` for RPC requests and `/fate-live` for live
|
|
37
67
|
updates by default. In SSR, it calls the exported fate server directly. In the
|
|
38
68
|
browser, it uses fetch and the SSE live endpoint.
|
|
@@ -104,12 +134,14 @@ export const { GET, POST } = defineVoidFateLiveRoute(fateServer, fateLive);
|
|
|
104
134
|
The live route handles `GET /fate-live` SSE connections and `POST /fate-live`
|
|
105
135
|
control messages. `void-fate` does not use WebSockets.
|
|
106
136
|
|
|
107
|
-
##
|
|
137
|
+
## Layout
|
|
108
138
|
|
|
109
|
-
Wrap your app with
|
|
110
|
-
provides the fate client through
|
|
139
|
+
Wrap your app with the Void fate client for your framework. It creates and
|
|
140
|
+
provides the fate client through the matching adapter.
|
|
111
141
|
|
|
112
|
-
|
|
142
|
+
::: code-group
|
|
143
|
+
|
|
144
|
+
```tsx [React]
|
|
113
145
|
import { useShared } from '@void/react';
|
|
114
146
|
import type { ReactNode } from 'react';
|
|
115
147
|
import { VoidFateClient } from 'void-fate/react';
|
|
@@ -128,9 +160,36 @@ export default function Layout({ children }: { children: ReactNode }) {
|
|
|
128
160
|
}
|
|
129
161
|
```
|
|
130
162
|
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
163
|
+
```vue [Vue]
|
|
164
|
+
<script setup lang="ts">
|
|
165
|
+
import { useShared } from '@void/vue';
|
|
166
|
+
import { computed } from 'vue';
|
|
167
|
+
import { FateClient } from 'vue-fate';
|
|
168
|
+
import { createFateClient } from 'vue-fate/client';
|
|
169
|
+
import type { SharedData } from '../src/lib/shared.ts';
|
|
170
|
+
|
|
171
|
+
const shared = useShared<SharedData>();
|
|
172
|
+
|
|
173
|
+
const fate = computed(() =>
|
|
174
|
+
createFateClient({
|
|
175
|
+
origin: typeof window === 'undefined' ? shared.origin : window.location.origin,
|
|
176
|
+
userId: shared.auth.user?.id,
|
|
177
|
+
}),
|
|
178
|
+
);
|
|
179
|
+
</script>
|
|
180
|
+
|
|
181
|
+
<template>
|
|
182
|
+
<FateClient :client="fate">
|
|
183
|
+
<slot />
|
|
184
|
+
</FateClient>
|
|
185
|
+
</template>
|
|
186
|
+
```
|
|
187
|
+
|
|
188
|
+
:::
|
|
189
|
+
|
|
190
|
+
`userId` is optional, but passing it lets the client be recreated when the
|
|
191
|
+
signed-in user changes. Browser requests include credentials when a `userId` is
|
|
192
|
+
present.
|
|
134
193
|
|
|
135
194
|
## Custom Paths
|
|
136
195
|
|
|
@@ -143,12 +202,35 @@ export const fateLive = createVoidFateLive({
|
|
|
143
202
|
});
|
|
144
203
|
```
|
|
145
204
|
|
|
146
|
-
|
|
205
|
+
::: code-group
|
|
206
|
+
|
|
207
|
+
```tsx [React]
|
|
147
208
|
<VoidFateClient livePath="/custom-fate-live" origin={origin} rpcPath="/custom-fate" userId={userId}>
|
|
148
209
|
{children}
|
|
149
210
|
</VoidFateClient>
|
|
150
211
|
```
|
|
151
212
|
|
|
213
|
+
```vue [Vue]
|
|
214
|
+
<script setup lang="ts">
|
|
215
|
+
const fate = computed(() =>
|
|
216
|
+
createFateClient({
|
|
217
|
+
livePath: '/custom-fate-live',
|
|
218
|
+
origin,
|
|
219
|
+
rpcPath: '/custom-fate',
|
|
220
|
+
userId,
|
|
221
|
+
}),
|
|
222
|
+
);
|
|
223
|
+
</script>
|
|
224
|
+
|
|
225
|
+
<template>
|
|
226
|
+
<FateClient :client="fate">
|
|
227
|
+
<slot />
|
|
228
|
+
</FateClient>
|
|
229
|
+
</template>
|
|
230
|
+
```
|
|
231
|
+
|
|
232
|
+
:::
|
|
233
|
+
|
|
152
234
|
The route helper does not own the route path. Make sure your Void route filename
|
|
153
235
|
or router configuration matches the paths you pass to the client.
|
|
154
236
|
|
package/lib/index.d.mts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { ConnectionRef, FateClient as FateClient$1, FateDehydratedState, FateMutations, FateRoots, GraphQLMutationDefinition, GraphQLMutationInput, GraphQLMutationMap, GraphQLMutationOutput, GraphQLTransportOptions, HydrateOptions, HydrationLimits, InferFateAPI, Pagination, Pagination as Pagination$1, Request, RequestOptions, RequestResult, View, ViewData, ViewEntity, ViewEntityName, ViewRef, ViewRef as ViewRef$1, ViewSelection, clientRoot, createClient, createGraphQLTransport, createHTTPTransport, createTRPCTransport, graphqlMutation, mutation, toEntityId, view } from "@nkzw/fate";
|
|
1
|
+
import { ConnectionRef, Deferred, Deferred as Deferred$1, FateClient as FateClient$1, FateDehydratedState, FateMutations, FateRoots, GraphQLMutationDefinition, GraphQLMutationInput, GraphQLMutationMap, GraphQLMutationOutput, GraphQLTransportOptions, HydrateOptions, HydrationLimits, InferFateAPI, Pagination, Pagination as Pagination$1, Request, RequestOptions, RequestResult, View, ViewData, ViewEntity, ViewEntityName, ViewRef, ViewRef as ViewRef$1, ViewSelection, clientRoot, createClient, createGraphQLTransport, createHTTPTransport, createTRPCTransport, defer, graphqlMutation, mutation, toEntityId, view } from "@nkzw/fate";
|
|
2
2
|
import { ReactNode } from "react";
|
|
3
3
|
|
|
4
4
|
//#region src/useRequest.d.ts
|
|
@@ -43,6 +43,7 @@ type ViewEntityWithTypename$1<V extends View<any, any>> = ViewEntity<V> & {
|
|
|
43
43
|
* const post = useLiveView(PostView, postRef);
|
|
44
44
|
*/
|
|
45
45
|
declare function useLiveView<V extends View<any, any>, R extends ViewRef$1<ViewEntityName<V>> | null>(view: V, ref: R): R extends null ? null : ViewData<ViewEntityWithTypename$1<V>, ViewSelection<V>>;
|
|
46
|
+
declare function useLiveView<V extends View<any, any>, R extends Deferred$1<ViewRef$1<ViewEntityName<V>>> | null>(view: V, ref: R): R extends null ? null : ViewData<ViewEntityWithTypename$1<V>, ViewSelection<V>>;
|
|
46
47
|
//#endregion
|
|
47
48
|
//#region src/listView.d.ts
|
|
48
49
|
type ConnectionItems<C> = C extends {
|
|
@@ -56,14 +57,16 @@ type ConnectionSelection = {
|
|
|
56
57
|
};
|
|
57
58
|
//#endregion
|
|
58
59
|
//#region src/useLiveListView.d.ts
|
|
60
|
+
type ConnectionValue$1 = {
|
|
61
|
+
items?: ReadonlyArray<any>;
|
|
62
|
+
pagination?: Pagination$1;
|
|
63
|
+
};
|
|
64
|
+
type ResolvedConnection$1<C> = C extends Deferred$1<infer Value> ? Value : NonNullable<C>;
|
|
59
65
|
/**
|
|
60
66
|
* Subscribes to a connection field, returning live-updating items and pagination
|
|
61
67
|
* helpers to load the next or previous page.
|
|
62
68
|
*/
|
|
63
|
-
declare function useLiveListView<C extends
|
|
64
|
-
items?: ReadonlyArray<any>;
|
|
65
|
-
pagination?: Pagination$1;
|
|
66
|
-
} | null | undefined>(selection: ConnectionSelection, connection: C): [ConnectionItems<NonNullable<C>>, LoadMoreFn | null, LoadMoreFn | null];
|
|
69
|
+
declare function useLiveListView<C extends ConnectionValue$1 | Deferred$1<ConnectionValue$1> | null | undefined>(selection: ConnectionSelection, connection: C): [ConnectionItems<ResolvedConnection$1<C>>, LoadMoreFn | null, LoadMoreFn | null];
|
|
67
70
|
//#endregion
|
|
68
71
|
//#region src/useView.d.ts
|
|
69
72
|
type ViewEntityWithTypename<V extends View<any, any>> = ViewEntity<V> & {
|
|
@@ -76,15 +79,19 @@ type ViewEntityWithTypename<V extends View<any, any>> = ViewEntity<V> & {
|
|
|
76
79
|
* const post = useView(PostView, postRef);
|
|
77
80
|
*/
|
|
78
81
|
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>>;
|
|
82
|
+
declare function useView<V extends View<any, any>, R extends Deferred$1<ViewRef$1<ViewEntityName<V>>> | null>(view: V, ref: R): R extends null ? null : ViewData<ViewEntityWithTypename<V>, ViewSelection<V>>;
|
|
83
|
+
declare function useView<V extends View<any, any>>(view: V, ref: Deferred$1<ViewRef$1<ViewEntityName<V>>> | ViewRef$1<ViewEntityName<V>> | null): ViewData<ViewEntityWithTypename<V>, ViewSelection<V>> | null;
|
|
79
84
|
//#endregion
|
|
80
85
|
//#region src/useListView.d.ts
|
|
86
|
+
type ConnectionValue = {
|
|
87
|
+
items?: ReadonlyArray<any>;
|
|
88
|
+
pagination?: Pagination$1;
|
|
89
|
+
};
|
|
90
|
+
type ResolvedConnection<C> = C extends Deferred$1<infer Value> ? Value : NonNullable<C>;
|
|
81
91
|
/**
|
|
82
92
|
* Subscribes to a connection field, returning the current items and pagination
|
|
83
93
|
* helpers to load the next or previous page.
|
|
84
94
|
*/
|
|
85
|
-
declare function useListView<C extends
|
|
86
|
-
items?: ReadonlyArray<any>;
|
|
87
|
-
pagination?: Pagination$1;
|
|
88
|
-
} | null | undefined>(selection: ConnectionSelection, connection: C): [ConnectionItems<NonNullable<C>>, LoadMoreFn | null, LoadMoreFn | null];
|
|
95
|
+
declare function useListView<C extends ConnectionValue | Deferred$1<ConnectionValue> | null | undefined>(selection: ConnectionSelection, connection: C): [ConnectionItems<ResolvedConnection<C>>, LoadMoreFn | null, LoadMoreFn | null];
|
|
89
96
|
//#endregion
|
|
90
|
-
export { type ConnectionRef, FateClient, type FateDehydratedState, type GraphQLMutationDefinition, type GraphQLMutationInput, type GraphQLMutationMap, type GraphQLMutationOutput, type GraphQLTransportOptions, type HydrateOptions, type HydrationLimits, type InferFateAPI, type Pagination, type ViewRef, clientRoot, createClient, createGraphQLTransport, createHTTPTransport, createTRPCTransport, graphqlMutation, mutation, toEntityId, useFateClient, useListView, useLiveListView, useLiveView, useRequest, useView, view };
|
|
97
|
+
export { type ConnectionRef, type Deferred, FateClient, type FateDehydratedState, type GraphQLMutationDefinition, type GraphQLMutationInput, type GraphQLMutationMap, type GraphQLMutationOutput, type GraphQLTransportOptions, type HydrateOptions, type HydrationLimits, type InferFateAPI, type Pagination, type ViewRef, clientRoot, createClient, createGraphQLTransport, createHTTPTransport, createTRPCTransport, defer, graphqlMutation, mutation, toEntityId, useFateClient, useListView, useLiveListView, useLiveView, useRequest, useView, view };
|
package/lib/index.mjs
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { ConnectionTag, clientRoot, createClient, createGraphQLTransport, createHTTPTransport, createTRPCTransport, graphqlMutation, isViewTag, mutation, toEntityId, view } from "@nkzw/fate";
|
|
1
|
+
import { ConnectionTag, clientRoot, createClient, createGraphQLTransport, createHTTPTransport, createTRPCTransport, defer, graphqlMutation, isDeferred, isViewTag, mutation, toEntityId, view } from "@nkzw/fate";
|
|
2
2
|
import { createContext, use, useCallback, useDeferredValue, useEffect, useEffectEvent, useMemo, useRef, useSyncExternalStore } from "react";
|
|
3
3
|
import { jsx } from "react/jsx-runtime";
|
|
4
4
|
import { getListEntries } from "@nkzw/fate/list";
|
|
@@ -22,6 +22,16 @@ function useFateClient() {
|
|
|
22
22
|
return context;
|
|
23
23
|
}
|
|
24
24
|
//#endregion
|
|
25
|
+
//#region src/thenable.ts
|
|
26
|
+
const isFulfilledThenable = (value) => "status" in value && value.status === "fulfilled" && "value" in value;
|
|
27
|
+
const fulfilledThenable = (value) => ({
|
|
28
|
+
status: "fulfilled",
|
|
29
|
+
then(onfulfilled, onrejected) {
|
|
30
|
+
return Promise.resolve(value).then(onfulfilled, onrejected);
|
|
31
|
+
},
|
|
32
|
+
value
|
|
33
|
+
});
|
|
34
|
+
//#endregion
|
|
25
35
|
//#region src/useView.tsx
|
|
26
36
|
const nullSnapshot = {
|
|
27
37
|
status: "fulfilled",
|
|
@@ -32,19 +42,99 @@ const nullSnapshot = {
|
|
|
32
42
|
};
|
|
33
43
|
function useView(view, ref) {
|
|
34
44
|
const client = useFateClient();
|
|
45
|
+
const isDeferredRef = isDeferred(ref);
|
|
35
46
|
const snapshotRef = useRef(null);
|
|
47
|
+
const mergedSnapshotRef = useRef(null);
|
|
48
|
+
const pendingRef = useRef(null);
|
|
49
|
+
const readViewSnapshot = useCallback((viewRef, coverage = [], cacheKey) => {
|
|
50
|
+
const snapshot = client.readView(view, viewRef);
|
|
51
|
+
const mergeCoverage = (value) => ({
|
|
52
|
+
...value,
|
|
53
|
+
coverage: coverage.length ? [...coverage, ...value.coverage] : value.coverage
|
|
54
|
+
});
|
|
55
|
+
if (isFulfilledThenable(snapshot)) {
|
|
56
|
+
if (coverage.length) {
|
|
57
|
+
const cached = mergedSnapshotRef.current;
|
|
58
|
+
const resolvedKey = `${viewRef.__typename}:${String(viewRef.id)}`;
|
|
59
|
+
if (cached?.source === snapshot.value && cached.cacheKey === cacheKey && cached.resolvedKey === resolvedKey) {
|
|
60
|
+
snapshotRef.current = cached.thenable.value;
|
|
61
|
+
return cached.thenable;
|
|
62
|
+
}
|
|
63
|
+
const value = mergeCoverage(snapshot.value);
|
|
64
|
+
const thenable = fulfilledThenable(value);
|
|
65
|
+
mergedSnapshotRef.current = {
|
|
66
|
+
cacheKey,
|
|
67
|
+
resolvedKey,
|
|
68
|
+
source: snapshot.value,
|
|
69
|
+
thenable
|
|
70
|
+
};
|
|
71
|
+
snapshotRef.current = value;
|
|
72
|
+
return thenable;
|
|
73
|
+
}
|
|
74
|
+
mergedSnapshotRef.current = null;
|
|
75
|
+
snapshotRef.current = snapshot.value;
|
|
76
|
+
return snapshot;
|
|
77
|
+
}
|
|
78
|
+
mergedSnapshotRef.current = null;
|
|
79
|
+
snapshotRef.current = null;
|
|
80
|
+
return Promise.resolve(snapshot).then((value) => {
|
|
81
|
+
const resolved = mergeCoverage(value);
|
|
82
|
+
snapshotRef.current = resolved;
|
|
83
|
+
return resolved;
|
|
84
|
+
});
|
|
85
|
+
}, [client, view]);
|
|
36
86
|
const getSnapshot = useCallback(() => {
|
|
37
87
|
if (ref === null) {
|
|
38
88
|
snapshotRef.current = null;
|
|
39
89
|
return nullSnapshot;
|
|
40
90
|
}
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
91
|
+
if (!isDeferredRef) {
|
|
92
|
+
pendingRef.current = null;
|
|
93
|
+
return readViewSnapshot(ref);
|
|
94
|
+
}
|
|
95
|
+
const deferred = ref;
|
|
96
|
+
const deferredSnapshot = client.readDeferred(deferred);
|
|
97
|
+
if (isFulfilledThenable(deferredSnapshot)) {
|
|
98
|
+
const resolvedRef = deferredSnapshot.value.data;
|
|
99
|
+
pendingRef.current = null;
|
|
100
|
+
if (resolvedRef === null) {
|
|
101
|
+
snapshotRef.current = {
|
|
102
|
+
coverage: deferredSnapshot.value.coverage,
|
|
103
|
+
data: null
|
|
104
|
+
};
|
|
105
|
+
return fulfilledThenable(snapshotRef.current);
|
|
106
|
+
}
|
|
107
|
+
return readViewSnapshot(client.ref(resolvedRef.__typename, resolvedRef.id, view), deferredSnapshot.value.coverage, deferred);
|
|
108
|
+
}
|
|
109
|
+
if (pendingRef.current?.deferred === ref && pendingRef.current.snapshot === deferredSnapshot) return pendingRef.current.viewSnapshot;
|
|
110
|
+
snapshotRef.current = null;
|
|
111
|
+
const viewSnapshot = Promise.resolve(deferredSnapshot).then((deferredValue) => {
|
|
112
|
+
const resolvedRef = deferredValue.data;
|
|
113
|
+
if (resolvedRef === null) {
|
|
114
|
+
const value = {
|
|
115
|
+
coverage: deferredValue.coverage,
|
|
116
|
+
data: null
|
|
117
|
+
};
|
|
118
|
+
snapshotRef.current = value;
|
|
119
|
+
return value;
|
|
120
|
+
}
|
|
121
|
+
return Promise.resolve(readViewSnapshot(client.ref(resolvedRef.__typename, resolvedRef.id, view), deferredValue.coverage, deferred)).then((value) => {
|
|
122
|
+
snapshotRef.current = value;
|
|
123
|
+
return value;
|
|
124
|
+
});
|
|
125
|
+
});
|
|
126
|
+
pendingRef.current = {
|
|
127
|
+
deferred,
|
|
128
|
+
snapshot: deferredSnapshot,
|
|
129
|
+
viewSnapshot
|
|
130
|
+
};
|
|
131
|
+
return viewSnapshot;
|
|
44
132
|
}, [
|
|
45
133
|
client,
|
|
46
134
|
view,
|
|
47
|
-
ref
|
|
135
|
+
ref,
|
|
136
|
+
isDeferredRef,
|
|
137
|
+
readViewSnapshot
|
|
48
138
|
]);
|
|
49
139
|
const snapshot = use(useDeferredValue(useSyncExternalStore(useCallback((onStoreChange) => {
|
|
50
140
|
if (ref === null) {
|
|
@@ -83,12 +173,14 @@ function useView(view, ref) {
|
|
|
83
173
|
//#region src/useLiveView.tsx
|
|
84
174
|
function useLiveView(view, ref) {
|
|
85
175
|
const client = useFateClient();
|
|
86
|
-
const
|
|
87
|
-
const
|
|
176
|
+
const resolvedRef = isDeferred(ref) ? use(client.readDeferred(ref)).data : ref;
|
|
177
|
+
const liveRef = resolvedRef ? client.ref(resolvedRef.__typename, resolvedRef.id, view) : null;
|
|
178
|
+
const liveId = liveRef?.id;
|
|
179
|
+
const liveType = liveRef?.__typename;
|
|
88
180
|
const subscribeLiveView = useEffectEvent(() => {
|
|
89
|
-
if (
|
|
181
|
+
if (liveRef === null) return;
|
|
90
182
|
client.assertLiveViewSupport();
|
|
91
|
-
return client.subscribeLiveView(view,
|
|
183
|
+
return client.subscribeLiveView(view, liveRef);
|
|
92
184
|
});
|
|
93
185
|
useEffect(() => subscribeLiveView(), [
|
|
94
186
|
client,
|
|
@@ -120,11 +212,12 @@ const useListViewInfo = (selection, connection) => ({
|
|
|
120
212
|
*/
|
|
121
213
|
function useListView(selection, connection) {
|
|
122
214
|
const client = useFateClient();
|
|
123
|
-
const
|
|
215
|
+
const resolvedConnection = isDeferred(connection) ? use(client.readDeferred(connection)).data : connection;
|
|
216
|
+
const { metadata, nodeView } = useListViewInfo(selection, resolvedConnection);
|
|
124
217
|
const subscribe = useCallback((onStoreChange) => metadata ? client.store.subscribeList(metadata.key, onStoreChange) : () => {}, [client, metadata]);
|
|
125
218
|
const getSnapshot = useCallback(() => metadata ? client.store.getListState(metadata.key) : void 0, [client, metadata]);
|
|
126
219
|
const listState = useDeferredValue(useSyncExternalStore(subscribe, getSnapshot, getSnapshot));
|
|
127
|
-
const pagination = listState?.pagination ??
|
|
220
|
+
const pagination = listState?.pagination ?? resolvedConnection?.pagination;
|
|
128
221
|
const hasNext = Boolean(pagination?.hasNext);
|
|
129
222
|
const hasPrevious = Boolean(pagination?.hasPrevious);
|
|
130
223
|
const nextCursor = pagination?.nextCursor;
|
|
@@ -135,10 +228,10 @@ function useListView(selection, connection) {
|
|
|
135
228
|
cursor,
|
|
136
229
|
node: client.rootListRef(id, nodeView)
|
|
137
230
|
}));
|
|
138
|
-
return
|
|
231
|
+
return resolvedConnection?.items;
|
|
139
232
|
}, [
|
|
140
233
|
client,
|
|
141
|
-
|
|
234
|
+
resolvedConnection?.items,
|
|
142
235
|
listState,
|
|
143
236
|
metadata,
|
|
144
237
|
nodeView
|
|
@@ -193,7 +286,8 @@ function useListView(selection, connection) {
|
|
|
193
286
|
*/
|
|
194
287
|
function useLiveListView(selection, connection) {
|
|
195
288
|
const client = useFateClient();
|
|
196
|
-
const
|
|
289
|
+
const resolvedConnection = isDeferred(connection) ? use(client.readDeferred(connection)).data : connection;
|
|
290
|
+
const { metadata, nodeView } = useListViewInfo(selection, resolvedConnection);
|
|
197
291
|
const subscribeLiveListView = useEffectEvent(() => {
|
|
198
292
|
if (!metadata) return;
|
|
199
293
|
client.assertLiveConnectionSupport();
|
|
@@ -204,7 +298,7 @@ function useLiveListView(selection, connection) {
|
|
|
204
298
|
metadata?.key,
|
|
205
299
|
nodeView
|
|
206
300
|
]);
|
|
207
|
-
return useListView(selection,
|
|
301
|
+
return useListView(selection, resolvedConnection);
|
|
208
302
|
}
|
|
209
303
|
//#endregion
|
|
210
304
|
//#region src/useRequest.tsx
|
|
@@ -233,4 +327,4 @@ function useRequest(request, options) {
|
|
|
233
327
|
return use(useDeferredValue(promise));
|
|
234
328
|
}
|
|
235
329
|
//#endregion
|
|
236
|
-
export { FateClient, clientRoot, createClient, createGraphQLTransport, createHTTPTransport, createTRPCTransport, graphqlMutation, mutation, toEntityId, useFateClient, useListView, useLiveListView, useLiveView, useRequest, useView, view };
|
|
330
|
+
export { FateClient, clientRoot, createClient, createGraphQLTransport, createHTTPTransport, createTRPCTransport, defer, graphqlMutation, mutation, toEntityId, useFateClient, useListView, useLiveListView, useLiveView, useRequest, useView, view };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "react-fate",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.2.0",
|
|
4
4
|
"description": "fate is a modern data client for React.",
|
|
5
5
|
"homepage": "https://github.com/nkzw-tech/fate",
|
|
6
6
|
"license": "MIT",
|
|
@@ -40,7 +40,7 @@
|
|
|
40
40
|
}
|
|
41
41
|
},
|
|
42
42
|
"dependencies": {
|
|
43
|
-
"@nkzw/fate": "^1.
|
|
43
|
+
"@nkzw/fate": "^1.2.0"
|
|
44
44
|
},
|
|
45
45
|
"devDependencies": {
|
|
46
46
|
"@types/react": "^19.2.16",
|