react-fate 1.0.3 → 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 +1220 -147
- package/docs/api/functions/FateClient.md +1 -1
- package/docs/api/functions/clientRoot.md +1 -1
- package/docs/api/functions/createClient.md +8 -4
- package/docs/api/functions/createGraphQLTransport.md +21 -0
- 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 +39 -0
- 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 +13 -0
- package/docs/api/type-aliases/ConnectionRef.md +2 -2
- package/docs/api/type-aliases/Deferred.md +19 -0
- package/docs/api/type-aliases/FateDehydratedState.md +11 -0
- package/docs/api/type-aliases/GraphQLMutationDefinition.md +19 -0
- package/docs/api/type-aliases/GraphQLMutationInput.md +11 -0
- package/docs/api/type-aliases/GraphQLMutationMap.md +11 -0
- package/docs/api/type-aliases/GraphQLMutationOutput.md +11 -0
- package/docs/api/type-aliases/GraphQLTransportOptions.md +119 -0
- package/docs/api/type-aliases/HydrateOptions.md +7 -0
- package/docs/api/type-aliases/HydrationLimits.md +7 -0
- package/docs/api/type-aliases/InferFateAPI.md +1 -1
- package/docs/api/type-aliases/Pagination.md +39 -0
- 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/requests.md +52 -0
- package/docs/guide/vue.md +256 -0
- package/docs/index.md +1 -0
- package/docs/integrations/graphql.md +345 -0
- package/docs/{guide/server-integration.md → integrations/server.md} +94 -7
- package/docs/{guide/void-integration.md → integrations/void.md} +94 -12
- package/lib/index.d.mts +20 -15
- package/lib/index.mjs +110 -16
- package/package.json +5 -5
|
@@ -0,0 +1,345 @@
|
|
|
1
|
+
# GraphQL Integration
|
|
2
|
+
|
|
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
|
+
|
|
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
|
+
|
|
7
|
+
## Template
|
|
8
|
+
|
|
9
|
+
Create a client for an existing GraphQL server with:
|
|
10
|
+
|
|
11
|
+
```bash
|
|
12
|
+
vp create fate my-app --template graphql-client
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
Create a full GraphQL + Prisma example app with:
|
|
16
|
+
|
|
17
|
+
```bash
|
|
18
|
+
vp create fate my-app --template graphql
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
The client-only template is the smallest reference for the integration. It contains a `src/fate/graphql.ts` file that maps your GraphQL schema to fate views and roots.
|
|
22
|
+
|
|
23
|
+
## GraphQL Schema Shape
|
|
24
|
+
|
|
25
|
+
The GraphQL transport expects a schema with Relay-style object identity and pagination:
|
|
26
|
+
|
|
27
|
+
- Entity objects include `id` and `__typename`.
|
|
28
|
+
- Object fetches go through a `nodes(ids:)` field.
|
|
29
|
+
- List fields return Relay connections with `edges`, `cursor`, `node`, and `pageInfo`.
|
|
30
|
+
- Root queries and mutations return the entity type selected by the fate view.
|
|
31
|
+
|
|
32
|
+
For example, a `Post` list can be exposed as a normal GraphQL connection:
|
|
33
|
+
|
|
34
|
+
```graphql
|
|
35
|
+
type Query {
|
|
36
|
+
posts(first: Int, after: String): PostConnection!
|
|
37
|
+
viewer: User
|
|
38
|
+
nodes(ids: [ID!]!): [Node]!
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
type PostConnection {
|
|
42
|
+
edges: [PostEdge!]!
|
|
43
|
+
pageInfo: PageInfo!
|
|
44
|
+
}
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
If your schema uses different root field names, keep the fate names you want on the client and map them with `fateGraphQL.roots`.
|
|
48
|
+
|
|
49
|
+
## Mapping Your Schema
|
|
50
|
+
|
|
51
|
+
Create a module that exports data views, `Root`, and an optional `fateGraphQL` config. The Vite plugin reads this module during development and build time, generates the client wiring, and leaves your runtime GraphQL server unchanged.
|
|
52
|
+
|
|
53
|
+
```tsx
|
|
54
|
+
import { graphqlMutation } from '@nkzw/fate';
|
|
55
|
+
import { dataView, list, type Entity } from '@nkzw/fate/server';
|
|
56
|
+
|
|
57
|
+
type GraphQLUser = {
|
|
58
|
+
id: string;
|
|
59
|
+
name?: string | null;
|
|
60
|
+
username?: string | null;
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
type GraphQLPost = {
|
|
64
|
+
author?: GraphQLUser | null;
|
|
65
|
+
id: string;
|
|
66
|
+
title: string;
|
|
67
|
+
};
|
|
68
|
+
|
|
69
|
+
export const userDataView = dataView<GraphQLUser>('User')({
|
|
70
|
+
id: true,
|
|
71
|
+
name: true,
|
|
72
|
+
username: true,
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
export const postDataView = dataView<GraphQLPost>('Post')({
|
|
76
|
+
author: userDataView,
|
|
77
|
+
id: true,
|
|
78
|
+
title: true,
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
export type User = Entity<typeof userDataView, 'User'>;
|
|
82
|
+
export type Post = Entity<
|
|
83
|
+
typeof postDataView,
|
|
84
|
+
'Post',
|
|
85
|
+
{
|
|
86
|
+
author: User | null;
|
|
87
|
+
}
|
|
88
|
+
>;
|
|
89
|
+
|
|
90
|
+
export const Root = {
|
|
91
|
+
posts: list(postDataView),
|
|
92
|
+
viewer: userDataView,
|
|
93
|
+
};
|
|
94
|
+
|
|
95
|
+
export const fateGraphQL = {
|
|
96
|
+
roots: {
|
|
97
|
+
posts: { field: 'posts' },
|
|
98
|
+
viewer: { field: 'viewer' },
|
|
99
|
+
},
|
|
100
|
+
} as const;
|
|
101
|
+
```
|
|
102
|
+
|
|
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
|
+
|
|
105
|
+
## Vite Plugin
|
|
106
|
+
|
|
107
|
+
Configure the fate Vite plugin with the GraphQL transport and point it at the mapping module:
|
|
108
|
+
|
|
109
|
+
::: code-group
|
|
110
|
+
|
|
111
|
+
```tsx [React]
|
|
112
|
+
import { fate } from 'react-fate/vite';
|
|
113
|
+
import { defineConfig } from 'vite';
|
|
114
|
+
|
|
115
|
+
export default defineConfig({
|
|
116
|
+
plugins: [
|
|
117
|
+
fate({
|
|
118
|
+
module: './src/fate/graphql.ts',
|
|
119
|
+
transport: 'graphql',
|
|
120
|
+
}),
|
|
121
|
+
],
|
|
122
|
+
});
|
|
123
|
+
```
|
|
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
|
+
|
|
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.
|
|
144
|
+
|
|
145
|
+
## Creating a Client
|
|
146
|
+
|
|
147
|
+
Create the client with your GraphQL endpoint and provide it through the `FateClient` provider:
|
|
148
|
+
|
|
149
|
+
::: code-group
|
|
150
|
+
|
|
151
|
+
```tsx [React]
|
|
152
|
+
import { FateClient } from 'react-fate';
|
|
153
|
+
import { createFateClient } from 'react-fate/client';
|
|
154
|
+
|
|
155
|
+
const fate = createFateClient({
|
|
156
|
+
headers: () => ({
|
|
157
|
+
authorization: `Bearer ${token}`,
|
|
158
|
+
}),
|
|
159
|
+
url: 'https://api.example.com/graphql',
|
|
160
|
+
});
|
|
161
|
+
|
|
162
|
+
export function App() {
|
|
163
|
+
return <FateClient client={fate}>{/* Components go here */}</FateClient>;
|
|
164
|
+
}
|
|
165
|
+
```
|
|
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
|
+
|
|
190
|
+
Use `fetch` when you need to customize credentials or reuse an application fetch wrapper:
|
|
191
|
+
|
|
192
|
+
```tsx
|
|
193
|
+
const fate = createFateClient({
|
|
194
|
+
fetch: (input, init) =>
|
|
195
|
+
fetch(input, {
|
|
196
|
+
...init,
|
|
197
|
+
credentials: 'include',
|
|
198
|
+
}),
|
|
199
|
+
url: `${env('SERVER_URL')}/graphql`,
|
|
200
|
+
});
|
|
201
|
+
```
|
|
202
|
+
|
|
203
|
+
GraphQL operations issued in the same microtask are batched into a single GraphQL query or mutation document with aliased fields.
|
|
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
|
+
|
|
207
|
+
## Object IDs
|
|
208
|
+
|
|
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:
|
|
210
|
+
|
|
211
|
+
```tsx
|
|
212
|
+
const fate = createFateClient({
|
|
213
|
+
decodeNodeId: (type, id) => {
|
|
214
|
+
const [nodeType, nodeId] = atob(String(id)).split(':');
|
|
215
|
+
if (nodeType !== type) {
|
|
216
|
+
throw new Error(`Expected a ${type} node id.`);
|
|
217
|
+
}
|
|
218
|
+
return nodeId;
|
|
219
|
+
},
|
|
220
|
+
encodeNodeId: (type, id) => btoa(`${type}:${id}`),
|
|
221
|
+
url: '/graphql',
|
|
222
|
+
});
|
|
223
|
+
```
|
|
224
|
+
|
|
225
|
+
If your GraphQL API already accepts and returns the same IDs you use in the app, return `id` from both functions.
|
|
226
|
+
|
|
227
|
+
## Requests and Arguments
|
|
228
|
+
|
|
229
|
+
Client code keeps using `useRequest` with the same shape as the other transports:
|
|
230
|
+
|
|
231
|
+
```tsx
|
|
232
|
+
const { posts, viewer } = useRequest({
|
|
233
|
+
posts: {
|
|
234
|
+
args: { first: 10 },
|
|
235
|
+
list: PostView,
|
|
236
|
+
},
|
|
237
|
+
viewer: { view: UserView },
|
|
238
|
+
});
|
|
239
|
+
```
|
|
240
|
+
|
|
241
|
+
Root arguments are sent to the root GraphQL field. Nested relation arguments are scoped by relation name:
|
|
242
|
+
|
|
243
|
+
```tsx
|
|
244
|
+
const { posts } = useRequest({
|
|
245
|
+
posts: {
|
|
246
|
+
args: {
|
|
247
|
+
comments: { first: 3 },
|
|
248
|
+
first: 10,
|
|
249
|
+
},
|
|
250
|
+
list: PostWithCommentsView,
|
|
251
|
+
},
|
|
252
|
+
});
|
|
253
|
+
```
|
|
254
|
+
|
|
255
|
+
This produces a root `posts(first: 10)` field and a nested `comments(first: 3)` field in the generated GraphQL selection.
|
|
256
|
+
|
|
257
|
+
## Mutations
|
|
258
|
+
|
|
259
|
+
Map fate mutation names to GraphQL mutation fields with `graphqlMutation`:
|
|
260
|
+
|
|
261
|
+
```tsx
|
|
262
|
+
export const fateGraphQL = {
|
|
263
|
+
mutations: {
|
|
264
|
+
'post.like': graphqlMutation<Post, { id: string }, Post>('Post', {
|
|
265
|
+
field: 'postLike',
|
|
266
|
+
}),
|
|
267
|
+
},
|
|
268
|
+
roots: {
|
|
269
|
+
posts: { field: 'posts' },
|
|
270
|
+
},
|
|
271
|
+
} as const;
|
|
272
|
+
```
|
|
273
|
+
|
|
274
|
+
By default, the input is sent as an `input` argument:
|
|
275
|
+
|
|
276
|
+
```graphql
|
|
277
|
+
mutation {
|
|
278
|
+
postLike(input: { id: "12" }) {
|
|
279
|
+
id
|
|
280
|
+
likes
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
```
|
|
284
|
+
|
|
285
|
+
Use `inputArg` when your schema uses a different argument name, or `inputArg: false` when the input object should be spread into field arguments:
|
|
286
|
+
|
|
287
|
+
```tsx
|
|
288
|
+
export const fateGraphQL = {
|
|
289
|
+
mutations: {
|
|
290
|
+
'post.like': graphqlMutation<Post, { id: string }, Post>('Post', {
|
|
291
|
+
field: 'likePost',
|
|
292
|
+
inputArg: 'payload',
|
|
293
|
+
}),
|
|
294
|
+
'user.follow': graphqlMutation<User, { id: string }, User>('User', {
|
|
295
|
+
field: 'followUser',
|
|
296
|
+
inputArg: false,
|
|
297
|
+
}),
|
|
298
|
+
},
|
|
299
|
+
} as const;
|
|
300
|
+
```
|
|
301
|
+
|
|
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`.
|
|
303
|
+
|
|
304
|
+
## Live Views
|
|
305
|
+
|
|
306
|
+
GraphQL live views use [GraphQL SSE](https://github.com/enisdenjo/graphql-sse). Install `graphql-sse` in the client package and leave `live` enabled, or pass `live: false` when your schema does not support subscriptions.
|
|
307
|
+
|
|
308
|
+
```tsx
|
|
309
|
+
const fate = createFateClient({
|
|
310
|
+
live: {
|
|
311
|
+
url: 'https://api.example.com/graphql/stream',
|
|
312
|
+
},
|
|
313
|
+
url: 'https://api.example.com/graphql',
|
|
314
|
+
});
|
|
315
|
+
```
|
|
316
|
+
|
|
317
|
+
The default subscription fields are `fateLiveNode` for `useLiveView` and `fateLiveConnection` for `useLiveListView`. Rename them with `entityField` and `connectionField`:
|
|
318
|
+
|
|
319
|
+
```tsx
|
|
320
|
+
const fate = createFateClient({
|
|
321
|
+
live: {
|
|
322
|
+
connectionField: 'liveConnection',
|
|
323
|
+
entityField: 'liveNode',
|
|
324
|
+
url: '/graphql/stream',
|
|
325
|
+
},
|
|
326
|
+
url: '/graphql',
|
|
327
|
+
});
|
|
328
|
+
```
|
|
329
|
+
|
|
330
|
+
The live node subscription returns `{ data, delete, id, select }`. The live connection subscription returns events such as `appendNode`, `prependNode`, `deleteEdge`, and `invalidate`. These payloads match fate's live transport events, so the cache update behavior is the same as the native transport.
|
|
331
|
+
|
|
332
|
+
If you do not need live views, disable them explicitly:
|
|
333
|
+
|
|
334
|
+
```tsx
|
|
335
|
+
const fate = createFateClient({
|
|
336
|
+
live: false,
|
|
337
|
+
url: '/graphql',
|
|
338
|
+
});
|
|
339
|
+
```
|
|
340
|
+
|
|
341
|
+
## Existing Servers
|
|
342
|
+
|
|
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.
|
|
344
|
+
|
|
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.
|
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
# Server Integration
|
|
2
2
|
|
|
3
|
-
Until now, we have focused on the client-side API of fate. You'll need a backend that
|
|
3
|
+
Until now, we have focused on the client-side API of fate. You'll need a backend that can be wired into fate's typed request model so the Vite plugin can connect the typed fate APIs to your app. _fate_ currently ships three integration paths:
|
|
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.md), which maps fate views and roots to an existing GraphQL schema.
|
|
7
8
|
|
|
8
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.
|
|
9
10
|
|
|
@@ -184,7 +185,9 @@ app.post('/fate/live', handler);
|
|
|
184
185
|
|
|
185
186
|
Configure the Vite plugin with the native transport:
|
|
186
187
|
|
|
187
|
-
|
|
188
|
+
::: code-group
|
|
189
|
+
|
|
190
|
+
```tsx [React]
|
|
188
191
|
import { fate } from 'react-fate/vite';
|
|
189
192
|
import { defineConfig } from 'vite';
|
|
190
193
|
|
|
@@ -198,9 +201,29 @@ export default defineConfig({
|
|
|
198
201
|
});
|
|
199
202
|
```
|
|
200
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
|
+
|
|
201
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:
|
|
202
223
|
|
|
203
|
-
|
|
224
|
+
::: code-group
|
|
225
|
+
|
|
226
|
+
```tsx [React]
|
|
204
227
|
import { createFateClient } from 'react-fate/client';
|
|
205
228
|
|
|
206
229
|
const client = createFateClient({
|
|
@@ -208,6 +231,16 @@ const client = createFateClient({
|
|
|
208
231
|
});
|
|
209
232
|
```
|
|
210
233
|
|
|
234
|
+
```ts [Vue]
|
|
235
|
+
import { createFateClient } from 'vue-fate/client';
|
|
236
|
+
|
|
237
|
+
const client = createFateClient({
|
|
238
|
+
url: '/fate',
|
|
239
|
+
});
|
|
240
|
+
```
|
|
241
|
+
|
|
242
|
+
:::
|
|
243
|
+
|
|
211
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.
|
|
212
245
|
|
|
213
246
|
### Custom Queries
|
|
@@ -584,7 +617,9 @@ export * from './views.ts';
|
|
|
584
617
|
|
|
585
618
|
Configure the fate Vite plugin with your server module:
|
|
586
619
|
|
|
587
|
-
|
|
620
|
+
::: code-group
|
|
621
|
+
|
|
622
|
+
```tsx [React]
|
|
588
623
|
import { fate } from 'react-fate/vite';
|
|
589
624
|
import { defineConfig } from 'vite';
|
|
590
625
|
|
|
@@ -597,11 +632,28 @@ export default defineConfig({
|
|
|
597
632
|
});
|
|
598
633
|
```
|
|
599
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
|
+
|
|
600
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._
|
|
601
653
|
|
|
602
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.
|
|
603
655
|
|
|
604
|
-
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.
|
|
605
657
|
|
|
606
658
|
The plugin writes project-local types under `.fate/`. If your TypeScript config does not already include dot-directories, extend the generated config:
|
|
607
659
|
|
|
@@ -613,9 +665,11 @@ The plugin writes project-local types under `.fate/`. If your TypeScript config
|
|
|
613
665
|
|
|
614
666
|
## Creating a _fate_ Client
|
|
615
667
|
|
|
616
|
-
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:
|
|
617
669
|
|
|
618
|
-
|
|
670
|
+
::: code-group
|
|
671
|
+
|
|
672
|
+
```tsx [React]
|
|
619
673
|
import { httpBatchLink } from '@trpc/client';
|
|
620
674
|
import { FateClient } from 'react-fate';
|
|
621
675
|
import { createFateClient } from 'react-fate/client';
|
|
@@ -641,4 +695,37 @@ export function App() {
|
|
|
641
695
|
}
|
|
642
696
|
```
|
|
643
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
|
+
|
|
644
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
|
|