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
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
> **Pagination** = `object`
|
|
4
4
|
|
|
5
|
-
Defined in: packages/fate/lib/transport-
|
|
5
|
+
Defined in: packages/fate/lib/transport-BjGm\_\_3t.d.mts:1100
|
|
6
6
|
|
|
7
7
|
Pagination state returned alongside connection lists.
|
|
8
8
|
|
|
@@ -12,7 +12,7 @@ Pagination state returned alongside connection lists.
|
|
|
12
12
|
|
|
13
13
|
> **hasNext**: `boolean`
|
|
14
14
|
|
|
15
|
-
Defined in: packages/fate/lib/transport-
|
|
15
|
+
Defined in: packages/fate/lib/transport-BjGm\_\_3t.d.mts:1101
|
|
16
16
|
|
|
17
17
|
***
|
|
18
18
|
|
|
@@ -20,7 +20,7 @@ Defined in: packages/fate/lib/transport-BTgtSwOM.d.mts:1087
|
|
|
20
20
|
|
|
21
21
|
> **hasPrevious**: `boolean`
|
|
22
22
|
|
|
23
|
-
Defined in: packages/fate/lib/transport-
|
|
23
|
+
Defined in: packages/fate/lib/transport-BjGm\_\_3t.d.mts:1102
|
|
24
24
|
|
|
25
25
|
***
|
|
26
26
|
|
|
@@ -28,7 +28,7 @@ Defined in: packages/fate/lib/transport-BTgtSwOM.d.mts:1088
|
|
|
28
28
|
|
|
29
29
|
> `optional` **nextCursor?**: `string`
|
|
30
30
|
|
|
31
|
-
Defined in: packages/fate/lib/transport-
|
|
31
|
+
Defined in: packages/fate/lib/transport-BjGm\_\_3t.d.mts:1103
|
|
32
32
|
|
|
33
33
|
***
|
|
34
34
|
|
|
@@ -36,4 +36,4 @@ Defined in: packages/fate/lib/transport-BTgtSwOM.d.mts:1089
|
|
|
36
36
|
|
|
37
37
|
> `optional` **previousCursor?**: `string`
|
|
38
38
|
|
|
39
|
-
Defined in: packages/fate/lib/transport-
|
|
39
|
+
Defined in: packages/fate/lib/transport-BjGm\_\_3t.d.mts:1104
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
> **ViewRef**\<`TName`\> = `Readonly`\<\{ `__typename`: `TName`; `[ViewsTag]`: `Set`\<`string`\>; `id`: `string` \| `number`; \}\>
|
|
4
4
|
|
|
5
|
-
Defined in: packages/fate/lib/transport-
|
|
5
|
+
Defined in: packages/fate/lib/transport-BjGm\_\_3t.d.mts:1082
|
|
6
6
|
|
|
7
7
|
Reference to a normalized entity instance that can be resolved against one or more view tags.
|
|
8
8
|
|
package/docs/guide/actions.md
CHANGED
|
@@ -7,9 +7,9 @@ fate does not provide hooks for mutations like traditional data fetching librari
|
|
|
7
7
|
|
|
8
8
|
Server mutations are exposed automatically as actions and mutations by fate's Vite plugin. The transport determines where those mutations are declared:
|
|
9
9
|
|
|
10
|
-
- With the [native HTTP transport](server
|
|
11
|
-
- With the [tRPC adapter](server
|
|
12
|
-
- With [Void](void
|
|
10
|
+
- With the [native HTTP transport](../integrations/server.md#native-fate-protocol), mutations come from the `mutations` object passed to `createFateServer`.
|
|
11
|
+
- With the [tRPC adapter](../integrations/server.md#trpc-fate-setup), mutations come from tRPC mutation procedures exposed through your fate-enabled router.
|
|
12
|
+
- With [Void](../integrations/void.md), mutations use the same native fate server shape and are exposed through the Void route helpers.
|
|
13
13
|
|
|
14
14
|
If you have a mutation named `post.like`, a `LikeButton` component using fate Actions and an async component library could look like this:
|
|
15
15
|
|
|
@@ -148,9 +148,9 @@ You can call mutations from anywhere, and without waiting for previous mutations
|
|
|
148
148
|
|
|
149
149
|
fate Actions & Mutations are backed by regular server mutations. If you already know how your fate server is wired, the client-side API above is the same regardless of transport. If not, start with the server setup for your environment:
|
|
150
150
|
|
|
151
|
-
- [Native HTTP custom mutations](server
|
|
152
|
-
- [tRPC fate setup](server
|
|
153
|
-
- [Void integration](void
|
|
151
|
+
- [Native HTTP custom mutations](../integrations/server.md#custom-mutations) use `createFateServer({ mutations })`.
|
|
152
|
+
- [tRPC fate setup](../integrations/server.md#trpc-fate-setup) wires fate into your tRPC router; custom writes can use the same `fate.createPlan` and `fate.resolveById` helpers shown there.
|
|
153
|
+
- [Void integration](../integrations/void.md) exposes a native fate server from Void routes; define mutations with the native `createFateServer({ mutations })` API and serve them through `defineVoidFateRoute`.
|
|
154
154
|
|
|
155
155
|
Here is a native HTTP mutation for `post.like`:
|
|
156
156
|
|
|
@@ -223,7 +223,7 @@ export const postRouter = router({
|
|
|
223
223
|
});
|
|
224
224
|
```
|
|
225
225
|
|
|
226
|
-
See [Server Integration](server
|
|
226
|
+
See [Server Integration](../integrations/server.md) for complete native HTTP and tRPC setup examples, and [Void Integration](../integrations/void.md) for route helpers when your app runs on Void.
|
|
227
227
|
|
|
228
228
|
## Action & Mutation Error Handling
|
|
229
229
|
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
# Deferred Views
|
|
2
|
+
|
|
3
|
+
Use `defer` when a field should not block the parent view. The parent view receives a deferred handle immediately after the eager fields are available, and the component that reads that handle with `useView`, `useListView`, or `useLiveListView` decides which `Suspense` boundary handles the loading state.
|
|
4
|
+
|
|
5
|
+
```tsx
|
|
6
|
+
import { Suspense } from 'react';
|
|
7
|
+
import { defer, useListView, useView, view, Deferred, ViewRef } from 'react-fate';
|
|
8
|
+
|
|
9
|
+
const CommentView = view<Comment>()({
|
|
10
|
+
content: true,
|
|
11
|
+
id: true,
|
|
12
|
+
});
|
|
13
|
+
|
|
14
|
+
const CommentConnectionView = {
|
|
15
|
+
args: { first: 3 },
|
|
16
|
+
items: { node: CommentView },
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
const PostView = view<Post>()({
|
|
20
|
+
comments: defer(CommentConnectionView),
|
|
21
|
+
content: true,
|
|
22
|
+
id: true,
|
|
23
|
+
title: true,
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
function PostCard({ post: postRef }: { post: ViewRef<'Post'> }) {
|
|
27
|
+
const post = useView(PostView, postRef);
|
|
28
|
+
|
|
29
|
+
return (
|
|
30
|
+
<article>
|
|
31
|
+
<h2>{post.title}</h2>
|
|
32
|
+
<p>{post.content}</p>
|
|
33
|
+
<Suspense fallback={<CommentsSkeleton />}>
|
|
34
|
+
<PostComments comments={post.comments} />
|
|
35
|
+
</Suspense>
|
|
36
|
+
</article>
|
|
37
|
+
);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function PostComments({
|
|
41
|
+
comments,
|
|
42
|
+
}: {
|
|
43
|
+
comments: Deferred<{ items: ReadonlyArray<{ node: ViewRef<'Comment'> }> }>;
|
|
44
|
+
}) {
|
|
45
|
+
const [items, loadNext] = useListView(CommentConnectionView, comments);
|
|
46
|
+
|
|
47
|
+
return (
|
|
48
|
+
<section>
|
|
49
|
+
{items.map(({ node }) => (
|
|
50
|
+
<CommentCard comment={node} key={node.id} />
|
|
51
|
+
))}
|
|
52
|
+
{loadNext ? <button onClick={loadNext}>Load more</button> : null}
|
|
53
|
+
</section>
|
|
54
|
+
);
|
|
55
|
+
}
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
Deferred fields are not optional data. They are explicit handles that existing view APIs can read. If the deferred selection is missing from the normalized cache, fate fetches only that missing selection and suspends the component that tried to resolve it.
|
|
59
|
+
|
|
60
|
+
This keeps parent components simple: eager fields like `title` and `content` are available when `useView(PostView, postRef)` returns, while slower or secondary fields such as `comments` can load under their own boundary.
|
|
61
|
+
|
|
62
|
+
GraphQL transports use the same client semantics today. The deferred field is omitted from the eager request and fetched when the deferred handle is resolved. GraphQL `@defer` is the natural transport representation for this feature, but consuming incremental multipart patches requires additional transport support before fate can safely normalize streamed patches from a single GraphQL response.
|
|
@@ -8,11 +8,11 @@ Create a new fate app with Vite+:
|
|
|
8
8
|
vp create fate my-app
|
|
9
9
|
```
|
|
10
10
|
|
|
11
|
-
The template selector can create a Void app with Drizzle, a tRPC app with Drizzle or Prisma, a GraphQL app with Prisma, or a fate client for an existing GraphQL server. The template sources live in the fate repo under [`packages/create-fate/templates/fate`](https://github.com/nkzw-tech/fate/tree/main/packages/create-fate/templates/fate). They feature modern tools to deliver an incredibly fast development experience.
|
|
11
|
+
The template selector can create a React or Vue client for a Void app with Drizzle, a tRPC app with Drizzle or Prisma, a GraphQL app with Prisma, or a fate client for an existing GraphQL server. React is the default UI framework; pass `--framework vue` or choose Vue in the template selector to create a Vue app. The template sources live in the fate repo under [`packages/create-fate/templates/fate`](https://github.com/nkzw-tech/fate/tree/main/packages/create-fate/templates/fate). They feature modern tools to deliver an incredibly fast development experience.
|
|
12
12
|
|
|
13
13
|
## Manual Installation
|
|
14
14
|
|
|
15
|
-
|
|
15
|
+
For a React client, install `react-fate`. It requires React 19.2+:
|
|
16
16
|
|
|
17
17
|
::: code-group
|
|
18
18
|
|
|
@@ -30,7 +30,25 @@ yarn add react-fate
|
|
|
30
30
|
|
|
31
31
|
:::
|
|
32
32
|
|
|
33
|
-
|
|
33
|
+
For a Vue client, install `vue-fate`:
|
|
34
|
+
|
|
35
|
+
::: code-group
|
|
36
|
+
|
|
37
|
+
```bash [npm]
|
|
38
|
+
npm add vue-fate
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
```bash [pnpm]
|
|
42
|
+
pnpm add vue-fate
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
```bash [yarn]
|
|
46
|
+
yarn add vue-fate
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
:::
|
|
50
|
+
|
|
51
|
+
If your server is a separate package, install `@nkzw/fate` there as a runtime dependency too. Install `@nkzw/fate` on the client only for a barebones integration without a framework adapter:
|
|
34
52
|
|
|
35
53
|
::: code-group
|
|
36
54
|
|
|
@@ -0,0 +1,256 @@
|
|
|
1
|
+
# Vue
|
|
2
|
+
|
|
3
|
+
_fate_ also supports Vue through `vue-fate`. It exports the same core primitives as `react-fate` where Vue has a natural equivalent: `view`, `useRequest`, `useView`, `useListView`, `useLiveView`, `useLiveListView`, `useFateClient`, and the `FateClient` provider.
|
|
4
|
+
|
|
5
|
+
Vue components use fate through Vue resources built from refs, computed values, watchers, and `<Suspense>`. The view model, generated client, normalized cache, masking, request shapes, list views, live views, and mutations are shared with the React adapter.
|
|
6
|
+
|
|
7
|
+
## Installation
|
|
8
|
+
|
|
9
|
+
Install `vue-fate` in your Vue client:
|
|
10
|
+
|
|
11
|
+
::: code-group
|
|
12
|
+
|
|
13
|
+
```bash [npm]
|
|
14
|
+
npm add vue-fate
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
```bash [pnpm]
|
|
18
|
+
pnpm add vue-fate
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
```bash [yarn]
|
|
22
|
+
yarn add vue-fate
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
:::
|
|
26
|
+
|
|
27
|
+
If your server lives in a separate package, install `@nkzw/fate` there as a runtime dependency too.
|
|
28
|
+
|
|
29
|
+
## Vite Plugin
|
|
30
|
+
|
|
31
|
+
Use the Vue adapter's Vite plugin in the client app:
|
|
32
|
+
|
|
33
|
+
```ts
|
|
34
|
+
import { fate } from 'vue-fate/vite';
|
|
35
|
+
import { defineConfig } from 'vite';
|
|
36
|
+
import vue from '@vitejs/plugin-vue';
|
|
37
|
+
|
|
38
|
+
export default defineConfig({
|
|
39
|
+
plugins: [
|
|
40
|
+
vue(),
|
|
41
|
+
fate({
|
|
42
|
+
module: '@your-org/server/fate.ts',
|
|
43
|
+
transport: 'native',
|
|
44
|
+
}),
|
|
45
|
+
],
|
|
46
|
+
});
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
The plugin generates `vue-fate/client`, which contains the typed `createFateClient` helper for your app. The `module` and `transport` options are the same options used by the React adapter.
|
|
50
|
+
|
|
51
|
+
## Providing the Client
|
|
52
|
+
|
|
53
|
+
Create a client with `createFateClient` and provide it with `FateClient`:
|
|
54
|
+
|
|
55
|
+
```vue
|
|
56
|
+
<script setup lang="ts">
|
|
57
|
+
import { computed, ref } from 'vue';
|
|
58
|
+
import { FateClient } from 'vue-fate';
|
|
59
|
+
import { createFateClient } from 'vue-fate/client';
|
|
60
|
+
import AppRoutes from './AppRoutes.vue';
|
|
61
|
+
|
|
62
|
+
const token = ref<string | null>(null);
|
|
63
|
+
|
|
64
|
+
const fate = computed(() =>
|
|
65
|
+
createFateClient({
|
|
66
|
+
headers: () => ({
|
|
67
|
+
authorization: token.value ? `Bearer ${token.value}` : '',
|
|
68
|
+
}),
|
|
69
|
+
url: '/fate',
|
|
70
|
+
}),
|
|
71
|
+
);
|
|
72
|
+
</script>
|
|
73
|
+
|
|
74
|
+
<template>
|
|
75
|
+
<FateClient :client="fate">
|
|
76
|
+
<Suspense>
|
|
77
|
+
<AppRoutes />
|
|
78
|
+
</Suspense>
|
|
79
|
+
</FateClient>
|
|
80
|
+
</template>
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
The `client` prop accepts a plain client, a ref, a computed value, or a getter. Descendants always read the current client, so switching credentials, endpoints, or transports does not require remounting the provider.
|
|
84
|
+
|
|
85
|
+
You can also install the client as a Vue plugin:
|
|
86
|
+
|
|
87
|
+
```ts
|
|
88
|
+
import { createApp } from 'vue';
|
|
89
|
+
import { createFatePlugin } from 'vue-fate';
|
|
90
|
+
import { createFateClient } from 'vue-fate/client';
|
|
91
|
+
import App from './App.vue';
|
|
92
|
+
|
|
93
|
+
const fate = createFateClient({ url: '/fate' });
|
|
94
|
+
|
|
95
|
+
createApp(App).use(createFatePlugin(fate)).mount('#app');
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
## Defining Views
|
|
99
|
+
|
|
100
|
+
Views are plain TypeScript values and can live anywhere. In Vue apps, it is usually best to define shared views in `.ts` modules and import them from single-file components:
|
|
101
|
+
|
|
102
|
+
```ts
|
|
103
|
+
import type { Post, User } from '@your-org/server/views';
|
|
104
|
+
import { view } from 'vue-fate';
|
|
105
|
+
|
|
106
|
+
export const UserView = view<User>()({
|
|
107
|
+
id: true,
|
|
108
|
+
name: true,
|
|
109
|
+
username: true,
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
export const PostView = view<Post>()({
|
|
113
|
+
author: UserView,
|
|
114
|
+
id: true,
|
|
115
|
+
title: true,
|
|
116
|
+
});
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
Vue can import values across components, but single-file components have one default component export. Keeping reusable views in `.ts` files avoids coupling your data model to component files and makes view composition straightforward.
|
|
120
|
+
|
|
121
|
+
## Requests
|
|
122
|
+
|
|
123
|
+
`useRequest` declares the data a route, page, or component tree needs. It returns a resource with `data`, `pending`, `error`, `ready`, `refresh`, and `dispose`:
|
|
124
|
+
|
|
125
|
+
```vue
|
|
126
|
+
<script setup lang="ts">
|
|
127
|
+
import { useListView, useRequest } from 'vue-fate';
|
|
128
|
+
import { PostCardView } from '../fateViews';
|
|
129
|
+
import PostCard from '../ui/PostCard.vue';
|
|
130
|
+
|
|
131
|
+
const request = useRequest({
|
|
132
|
+
posts: {
|
|
133
|
+
args: { first: 20 },
|
|
134
|
+
list: PostCardView,
|
|
135
|
+
},
|
|
136
|
+
});
|
|
137
|
+
|
|
138
|
+
const { posts } = await request.ready();
|
|
139
|
+
const [postItems, loadNext] = useListView(PostCardView, posts);
|
|
140
|
+
</script>
|
|
141
|
+
|
|
142
|
+
<template>
|
|
143
|
+
<PostCard v-for="{ node } in postItems" :key="node.id" :post="node" />
|
|
144
|
+
<button v-if="loadNext" @click="loadNext()">Load more</button>
|
|
145
|
+
</template>
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
Awaiting `ready()` in `<script setup>` participates in Vue Suspense. If you do not await it, read `request.data.value`, `request.pending.value`, and `request.error.value` in script, or use the refs directly in templates.
|
|
149
|
+
|
|
150
|
+
## Views in Components
|
|
151
|
+
|
|
152
|
+
Use `useView` to read a `ViewRef` from the normalized cache and subscribe to updates for the selected fields:
|
|
153
|
+
|
|
154
|
+
```vue
|
|
155
|
+
<script setup lang="ts">
|
|
156
|
+
import type { ViewRef } from 'vue-fate';
|
|
157
|
+
import { useView } from 'vue-fate';
|
|
158
|
+
import { PostCardView, UserView } from '../fateViews';
|
|
159
|
+
import UserCard from './UserCard.vue';
|
|
160
|
+
|
|
161
|
+
const props = defineProps<{
|
|
162
|
+
post: ViewRef<'Post'>;
|
|
163
|
+
}>();
|
|
164
|
+
|
|
165
|
+
const post = useView(PostCardView, () => props.post);
|
|
166
|
+
const author = useView(UserView, () => post.value?.author ?? null);
|
|
167
|
+
</script>
|
|
168
|
+
|
|
169
|
+
<template>
|
|
170
|
+
<article v-if="post">
|
|
171
|
+
<h2>{{ post.title }}</h2>
|
|
172
|
+
<UserCard v-if="author" :user="author" />
|
|
173
|
+
</article>
|
|
174
|
+
</template>
|
|
175
|
+
```
|
|
176
|
+
|
|
177
|
+
Pass reactive props through a getter so fate tracks prop changes. In script, resources are refs and need `.value`. In templates, Vue unwraps them automatically.
|
|
178
|
+
|
|
179
|
+
## Lists and Live Views
|
|
180
|
+
|
|
181
|
+
`useListView` subscribes to a connection returned from `useRequest` or from a nested view field:
|
|
182
|
+
|
|
183
|
+
```ts
|
|
184
|
+
const [comments, loadNextCommentPage] = useListView(CommentView, () => post.value?.comments);
|
|
185
|
+
```
|
|
186
|
+
|
|
187
|
+
`useLiveView` and `useLiveListView` have the same resource shape as `useView` and `useListView`, but they also subscribe to server-pushed updates when the selected transport supports live views:
|
|
188
|
+
|
|
189
|
+
```ts
|
|
190
|
+
const post = useLiveView(PostCardView, () => props.post);
|
|
191
|
+
const [comments] = useLiveListView(CommentView, () => post.value?.comments);
|
|
192
|
+
```
|
|
193
|
+
|
|
194
|
+
Manual cleanup works through `dispose()`:
|
|
195
|
+
|
|
196
|
+
```ts
|
|
197
|
+
const post = useLiveView(PostCardView, () => props.post);
|
|
198
|
+
|
|
199
|
+
onBeforeUnmount(() => {
|
|
200
|
+
post.dispose();
|
|
201
|
+
});
|
|
202
|
+
```
|
|
203
|
+
|
|
204
|
+
Vue scope disposal also cleans up resources automatically.
|
|
205
|
+
|
|
206
|
+
## Mutations
|
|
207
|
+
|
|
208
|
+
Use `useFateClient` to access generated mutations:
|
|
209
|
+
|
|
210
|
+
```vue
|
|
211
|
+
<script setup lang="ts">
|
|
212
|
+
import { ref } from 'vue';
|
|
213
|
+
import { useFateClient } from 'vue-fate';
|
|
214
|
+
|
|
215
|
+
const props = defineProps<{
|
|
216
|
+
post: { id: string; likes: number };
|
|
217
|
+
}>();
|
|
218
|
+
|
|
219
|
+
const fate = useFateClient();
|
|
220
|
+
const pending = ref(false);
|
|
221
|
+
const error = ref<unknown>(null);
|
|
222
|
+
|
|
223
|
+
const like = async () => {
|
|
224
|
+
pending.value = true;
|
|
225
|
+
error.value = null;
|
|
226
|
+
|
|
227
|
+
try {
|
|
228
|
+
await fate.mutations.post.like({
|
|
229
|
+
input: { id: props.post.id },
|
|
230
|
+
optimistic: { likes: props.post.likes + 1 },
|
|
231
|
+
});
|
|
232
|
+
} catch (caughtError) {
|
|
233
|
+
error.value = caughtError;
|
|
234
|
+
} finally {
|
|
235
|
+
pending.value = false;
|
|
236
|
+
}
|
|
237
|
+
};
|
|
238
|
+
</script>
|
|
239
|
+
|
|
240
|
+
<template>
|
|
241
|
+
<button :disabled="pending" @click="like">Like</button>
|
|
242
|
+
</template>
|
|
243
|
+
```
|
|
244
|
+
|
|
245
|
+
The mutation call shape is the same as `react-fate`: `input`, `optimistic`, `insert`, and `view` work the same way. Vue does not have React Actions or `useActionState`, so Vue components should model pending and error state with Vue refs or your application state library.
|
|
246
|
+
|
|
247
|
+
## API Differences from React
|
|
248
|
+
|
|
249
|
+
The names intentionally mirror `react-fate` where Vue has an equivalent API. The main differences are Vue framework differences:
|
|
250
|
+
|
|
251
|
+
- `useRequest`, `useView`, and list hooks return Vue resources instead of throwing promises from render.
|
|
252
|
+
- Async setup and `<Suspense>` replace React's async component model.
|
|
253
|
+
- Mutations use `fate.mutations` directly; React-only `fate.actions` and `useActionState` patterns do not apply.
|
|
254
|
+
- Shared views are best kept in `.ts` modules instead of exporting named views from component files.
|
|
255
|
+
|
|
256
|
+
The generated client, server integrations, cache behavior, masking, pagination, optimistic updates, and live transport behavior are shared across adapters.
|
package/docs/index.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# GraphQL Integration
|
|
2
2
|
|
|
3
|
-
_fate_ can use an existing GraphQL API as its transport. This keeps the
|
|
3
|
+
_fate_ can use an existing GraphQL API as its transport. This keeps the adapter APIs, view composition, normalized cache, masking, requests, list views, live views, and mutations the same while replacing the native or tRPC backend with GraphQL operations.
|
|
4
4
|
|
|
5
5
|
Use the GraphQL transport when your backend already exposes GraphQL and you want fate's client model without adding fate's native server protocol.
|
|
6
6
|
|
|
@@ -100,13 +100,15 @@ export const fateGraphQL = {
|
|
|
100
100
|
} as const;
|
|
101
101
|
```
|
|
102
102
|
|
|
103
|
-
The data views describe the fields
|
|
103
|
+
The data views describe the fields client components are allowed to select. `Root` describes the root operations available to `useRequest`. `fateGraphQL.roots` maps those root names to actual GraphQL fields. If the GraphQL field has the same name as the fate root, the `field` entry can be omitted.
|
|
104
104
|
|
|
105
105
|
## Vite Plugin
|
|
106
106
|
|
|
107
107
|
Configure the fate Vite plugin with the GraphQL transport and point it at the mapping module:
|
|
108
108
|
|
|
109
|
-
|
|
109
|
+
::: code-group
|
|
110
|
+
|
|
111
|
+
```tsx [React]
|
|
110
112
|
import { fate } from 'react-fate/vite';
|
|
111
113
|
import { defineConfig } from 'vite';
|
|
112
114
|
|
|
@@ -120,13 +122,33 @@ export default defineConfig({
|
|
|
120
122
|
});
|
|
121
123
|
```
|
|
122
124
|
|
|
125
|
+
```ts [Vue]
|
|
126
|
+
import vue from '@vitejs/plugin-vue';
|
|
127
|
+
import { fate } from 'vue-fate/vite';
|
|
128
|
+
import { defineConfig } from 'vite';
|
|
129
|
+
|
|
130
|
+
export default defineConfig({
|
|
131
|
+
plugins: [
|
|
132
|
+
vue(),
|
|
133
|
+
fate({
|
|
134
|
+
module: './src/fate/graphql.ts',
|
|
135
|
+
transport: 'graphql',
|
|
136
|
+
}),
|
|
137
|
+
],
|
|
138
|
+
});
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
:::
|
|
142
|
+
|
|
123
143
|
The plugin generates a typed `createFateClient` helper from your views, roots, and GraphQL mapping. It also watches the mapping module and the files it imports during development.
|
|
124
144
|
|
|
125
145
|
## Creating a Client
|
|
126
146
|
|
|
127
147
|
Create the client with your GraphQL endpoint and provide it through the `FateClient` provider:
|
|
128
148
|
|
|
129
|
-
|
|
149
|
+
::: code-group
|
|
150
|
+
|
|
151
|
+
```tsx [React]
|
|
130
152
|
import { FateClient } from 'react-fate';
|
|
131
153
|
import { createFateClient } from 'react-fate/client';
|
|
132
154
|
|
|
@@ -142,6 +164,29 @@ export function App() {
|
|
|
142
164
|
}
|
|
143
165
|
```
|
|
144
166
|
|
|
167
|
+
```vue [Vue]
|
|
168
|
+
<script setup lang="ts">
|
|
169
|
+
import { FateClient } from 'vue-fate';
|
|
170
|
+
import { createFateClient } from 'vue-fate/client';
|
|
171
|
+
import AppRoutes from './AppRoutes.vue';
|
|
172
|
+
|
|
173
|
+
const fate = createFateClient({
|
|
174
|
+
headers: () => ({
|
|
175
|
+
authorization: `Bearer ${token}`,
|
|
176
|
+
}),
|
|
177
|
+
url: 'https://api.example.com/graphql',
|
|
178
|
+
});
|
|
179
|
+
</script>
|
|
180
|
+
|
|
181
|
+
<template>
|
|
182
|
+
<FateClient :client="fate">
|
|
183
|
+
<AppRoutes />
|
|
184
|
+
</FateClient>
|
|
185
|
+
</template>
|
|
186
|
+
```
|
|
187
|
+
|
|
188
|
+
:::
|
|
189
|
+
|
|
145
190
|
Use `fetch` when you need to customize credentials or reuse an application fetch wrapper:
|
|
146
191
|
|
|
147
192
|
```tsx
|
|
@@ -157,6 +202,8 @@ const fate = createFateClient({
|
|
|
157
202
|
|
|
158
203
|
GraphQL operations issued in the same microtask are batched into a single GraphQL query or mutation document with aliased fields.
|
|
159
204
|
|
|
205
|
+
Deferred view fields work with the GraphQL transport through the same normalized cache flow as native HTTP: the eager query omits `defer(...)` fields, and `useView`, `useListView`, or `useLiveListView` fetches the missing selection through `nodes(ids:)` when the deferred handle is read. GraphQL `@defer` is the natural wire format for this feature, but fate's GraphQL transport currently expects one JSON result per operation and does not consume incremental multipart patches yet.
|
|
206
|
+
|
|
160
207
|
## Object IDs
|
|
161
208
|
|
|
162
209
|
The transport converts between fate entity IDs and GraphQL node IDs. By default, it sends IDs as `${type}-${id}` and strips that prefix from returned IDs. Override this if your schema uses Relay global IDs, raw database IDs, or another encoding:
|
|
@@ -252,7 +299,7 @@ export const fateGraphQL = {
|
|
|
252
299
|
} as const;
|
|
253
300
|
```
|
|
254
301
|
|
|
255
|
-
|
|
302
|
+
Mutations use the same `mutation(...)` API described in the [Actions Guide](../guide/actions.md). React clients can also expose those mutations as Actions for `useActionState`.
|
|
256
303
|
|
|
257
304
|
## Live Views
|
|
258
305
|
|
|
@@ -295,4 +342,4 @@ const fate = createFateClient({
|
|
|
295
342
|
|
|
296
343
|
The GraphQL transport is intentionally a mapping layer. It does not require `createFateServer`, the Prisma adapter, or the Drizzle adapter. Your GraphQL server remains responsible for authorization, validation, resolver behavior, cursor pagination, and mutation side effects.
|
|
297
344
|
|
|
298
|
-
Use data views to expose only the fields the client should be able to select, keep GraphQL schema authorization in your server, and treat `src/fate/graphql.ts` as the contract between your GraphQL API and fate's
|
|
345
|
+
Use data views to expose only the fields the client should be able to select, keep GraphQL schema authorization in your server, and treat `src/fate/graphql.ts` as the contract between your GraphQL API and fate's client.
|