@nkzw/fate 1.0.0 → 1.0.2

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.
Files changed (150) hide show
  1. package/README.md +98 -29
  2. package/docs/api/classes/FateClient.md +36 -36
  3. package/docs/api/functions/clientRoot.md +1 -1
  4. package/docs/api/functions/createClient.md +1 -1
  5. package/docs/api/functions/createHTTPTransport.md +1 -1
  6. package/docs/api/functions/createTRPCTransport.md +1 -1
  7. package/docs/api/functions/getListEntries.md +1 -1
  8. package/docs/api/functions/getSelectionPlan.md +1 -1
  9. package/docs/api/functions/isRecord.md +1 -1
  10. package/docs/api/functions/isViewTag.md +1 -1
  11. package/docs/api/functions/liveConnectionTopic.md +1 -1
  12. package/docs/api/functions/liveEntityTopic.md +1 -1
  13. package/docs/api/functions/liveGlobalConnectionTopic.md +1 -1
  14. package/docs/api/functions/mutation.md +1 -1
  15. package/docs/api/functions/toEntityId.md +1 -1
  16. package/docs/api/functions/view.md +1 -1
  17. package/docs/api/interfaces/FateThenable.md +3 -3
  18. package/docs/api/interfaces/Transport.md +7 -7
  19. package/docs/api/server/classes/FateRequestError.md +5 -5
  20. package/docs/api/server/drizzle/functions/createDrizzleFate.md +1 -1
  21. package/docs/api/server/drizzle/functions/createDrizzleSourceAdapter.md +1 -1
  22. package/docs/api/server/drizzle/functions/createDrizzleSourceRegistry.md +1 -1
  23. package/docs/api/server/drizzle/index.md +1 -1
  24. package/docs/api/server/drizzle/type-aliases/DrizzleManyToManyConfig.md +1 -1
  25. package/docs/api/server/drizzle/type-aliases/DrizzleManyToManyInput.md +1 -1
  26. package/docs/api/server/drizzle/type-aliases/DrizzleQueryExtra.md +3 -3
  27. package/docs/api/server/drizzle/type-aliases/DrizzleSourceAdapter.md +9 -9
  28. package/docs/api/server/drizzle/type-aliases/DrizzleViewConfig.md +1 -1
  29. package/docs/api/server/functions/bindSourceProcedures.md +1 -1
  30. package/docs/api/server/functions/computed.md +1 -1
  31. package/docs/api/server/functions/count.md +1 -1
  32. package/docs/api/server/functions/createFateFetchHandler.md +1 -1
  33. package/docs/api/server/functions/createFateServer.md +1 -1
  34. package/docs/api/server/functions/createHonoFateHandler.md +1 -1
  35. package/docs/api/server/functions/createLiveEventBus.md +2 -2
  36. package/docs/api/server/functions/createNestedSourcePlan.md +1 -1
  37. package/docs/api/server/functions/createResolver.md +1 -1
  38. package/docs/api/server/functions/createSourcePlan.md +1 -1
  39. package/docs/api/server/functions/createSourceProcedures.md +2 -2
  40. package/docs/api/server/functions/dataView.md +1 -1
  41. package/docs/api/server/functions/field.md +1 -1
  42. package/docs/api/server/functions/getNestedSelection.md +1 -1
  43. package/docs/api/server/functions/getScopedArgs.md +1 -1
  44. package/docs/api/server/functions/hasNestedSelection.md +1 -1
  45. package/docs/api/server/functions/list.md +1 -1
  46. package/docs/api/server/functions/refetchSourceById.md +1 -1
  47. package/docs/api/server/functions/resolveSourceById.md +1 -1
  48. package/docs/api/server/functions/resolveSourceByIds.md +1 -1
  49. package/docs/api/server/functions/resolveSourceConnection.md +1 -1
  50. package/docs/api/server/functions/resolver.md +1 -1
  51. package/docs/api/server/functions/toPrismaSelect.md +1 -1
  52. package/docs/api/server/functions/withConnection.md +1 -1
  53. package/docs/api/server/prisma/functions/createPrismaFate.md +1 -1
  54. package/docs/api/server/prisma/functions/createPrismaSourceAdapter.md +1 -1
  55. package/docs/api/server/prisma/functions/createPrismaSourceRegistry.md +1 -1
  56. package/docs/api/server/prisma/functions/prismaConnectionArgs.md +1 -1
  57. package/docs/api/server/prisma/index.md +1 -1
  58. package/docs/api/server/prisma/type-aliases/PrismaDelegate.md +4 -4
  59. package/docs/api/server/prisma/type-aliases/PrismaQueryExtra.md +1 -1
  60. package/docs/api/server/prisma/type-aliases/PrismaSourceAdapter.md +9 -9
  61. package/docs/api/server/prisma/type-aliases/PrismaViewConfig.md +1 -1
  62. package/docs/api/server/type-aliases/ComputedField.md +5 -5
  63. package/docs/api/server/type-aliases/ComputedSelection.md +1 -1
  64. package/docs/api/server/type-aliases/ConnectionItem.md +3 -3
  65. package/docs/api/server/type-aliases/ConnectionPagination.md +5 -5
  66. package/docs/api/server/type-aliases/ConnectionResult.md +3 -3
  67. package/docs/api/server/type-aliases/CountSelection.md +4 -4
  68. package/docs/api/server/type-aliases/CountWhere.md +1 -1
  69. package/docs/api/server/type-aliases/DataViewListOptions.md +2 -2
  70. package/docs/api/server/type-aliases/DataViewOrderBy.md +1 -1
  71. package/docs/api/server/type-aliases/DataViewOrderDirection.md +1 -1
  72. package/docs/api/server/type-aliases/DataViewResult.md +1 -1
  73. package/docs/api/server/type-aliases/Entity.md +1 -1
  74. package/docs/api/server/type-aliases/FateServer.md +5 -5
  75. package/docs/api/server/type-aliases/FateServerManifest.md +1 -1
  76. package/docs/api/server/type-aliases/FieldSelection.md +3 -3
  77. package/docs/api/server/type-aliases/LiveConnectionEventType.md +1 -1
  78. package/docs/api/server/type-aliases/LiveConnectionSourceEvent.md +1 -1
  79. package/docs/api/server/type-aliases/LiveEventBus.md +1 -1
  80. package/docs/api/server/type-aliases/LiveEventType.md +1 -1
  81. package/docs/api/server/type-aliases/LiveSourceEvent.md +1 -1
  82. package/docs/api/server/type-aliases/NativeFateAPI.md +4 -4
  83. package/docs/api/server/type-aliases/OrderDirection.md +1 -1
  84. package/docs/api/server/type-aliases/SourceConfig.md +6 -6
  85. package/docs/api/server/type-aliases/SourceDefinition.md +6 -6
  86. package/docs/api/server/type-aliases/SourceOrder.md +1 -1
  87. package/docs/api/server/type-aliases/SourceOrderField.md +3 -3
  88. package/docs/api/server/type-aliases/SourcePlan.md +1 -1
  89. package/docs/api/server/type-aliases/SourcePlanNode.md +1 -1
  90. package/docs/api/server/type-aliases/SourceRegistry.md +1 -1
  91. package/docs/api/server/type-aliases/SourceRelation.md +1 -1
  92. package/docs/api/server/type-aliases/SourceRelationConfig.md +1 -1
  93. package/docs/api/server/variables/byIdInput.md +1 -1
  94. package/docs/api/server/variables/connectionArgs.md +1 -1
  95. package/docs/api/type-aliases/ConnectionMetadata.md +1 -1
  96. package/docs/api/type-aliases/ConnectionRef.md +1 -1
  97. package/docs/api/type-aliases/Entity.md +2 -2
  98. package/docs/api/type-aliases/EntityId.md +1 -1
  99. package/docs/api/type-aliases/FateLiveConnectionEvent.md +1 -1
  100. package/docs/api/type-aliases/FateLiveEvent.md +1 -1
  101. package/docs/api/type-aliases/FateMutations.md +1 -1
  102. package/docs/api/type-aliases/FateOperation.md +1 -1
  103. package/docs/api/type-aliases/FateProtocolRequest.md +1 -1
  104. package/docs/api/type-aliases/FateProtocolResponse.md +1 -1
  105. package/docs/api/type-aliases/FateRecord.md +1 -1
  106. package/docs/api/type-aliases/FateRoots.md +1 -1
  107. package/docs/api/type-aliases/InferFateAPI.md +1 -1
  108. package/docs/api/type-aliases/List.md +1 -1
  109. package/docs/api/type-aliases/ListEntry.md +1 -1
  110. package/docs/api/type-aliases/ListItem.md +1 -1
  111. package/docs/api/type-aliases/Mask.md +1 -1
  112. package/docs/api/type-aliases/MutationDefinition.md +1 -1
  113. package/docs/api/type-aliases/MutationEntity.md +1 -1
  114. package/docs/api/type-aliases/MutationIdentifier.md +1 -1
  115. package/docs/api/type-aliases/MutationInput.md +1 -1
  116. package/docs/api/type-aliases/MutationResult.md +1 -1
  117. package/docs/api/type-aliases/NodesItem.md +1 -1
  118. package/docs/api/type-aliases/Pagination.md +5 -5
  119. package/docs/api/type-aliases/Request.md +1 -1
  120. package/docs/api/type-aliases/RequestMode.md +1 -1
  121. package/docs/api/type-aliases/RequestOptions.md +1 -1
  122. package/docs/api/type-aliases/RequestResult.md +1 -1
  123. package/docs/api/type-aliases/Selection.md +1 -1
  124. package/docs/api/type-aliases/Snapshot.md +1 -1
  125. package/docs/api/type-aliases/TypeConfig.md +4 -4
  126. package/docs/api/type-aliases/View.md +1 -1
  127. package/docs/api/type-aliases/ViewData.md +1 -1
  128. package/docs/api/type-aliases/ViewEntity.md +1 -1
  129. package/docs/api/type-aliases/ViewEntityName.md +1 -1
  130. package/docs/api/type-aliases/ViewRef.md +1 -1
  131. package/docs/api/type-aliases/ViewSelection.md +1 -1
  132. package/docs/api/type-aliases/ViewSnapshot.md +1 -1
  133. package/docs/api/type-aliases/ViewTag.md +1 -1
  134. package/docs/api/variables/ConnectionTag.md +1 -1
  135. package/docs/guide/actions.md +46 -4
  136. package/docs/guide/live-views.md +15 -2
  137. package/docs/guide/requests.md +1 -1
  138. package/docs/guide/server-integration.md +35 -21
  139. package/docs/guide/views.md +1 -1
  140. package/docs/guide/void-integration.md +3 -3
  141. package/lib/index.d.mts +1 -1
  142. package/lib/index.mjs +5 -4
  143. package/lib/{record-ENh92gyb.d.mts → record-CirbfZWX.d.mts} +2 -1
  144. package/lib/server/drizzle.d.mts +2 -0
  145. package/lib/server/drizzle.mjs +89 -6
  146. package/lib/server/prisma.mjs +2 -2
  147. package/lib/server.d.mts +2 -2
  148. package/lib/server.mjs +39 -8
  149. package/lib/{sourceRouter-Bwpkr062.mjs → sourceRouter-C22uFcZg.mjs} +1 -1
  150. package/package.json +1 -1
@@ -2,7 +2,7 @@
2
2
 
3
3
  > **TypeConfig** = `object`
4
4
 
5
- Defined in: [packages/fate/src/types.ts:117](https://github.com/nkzw-tech/fate/blob/17081c4f677e224e55b75d9a31bd6e0a7ee94af1/packages/fate/src/types.ts#L117)
5
+ Defined in: [packages/fate/src/types.ts:117](https://github.com/nkzw-tech/fate/blob/2b217058fdff50f3598b704f486529cd23a8dbb6/packages/fate/src/types.ts#L117)
6
6
 
7
7
  Configuration for a server entity type used by the client cache.
8
8
 
@@ -12,7 +12,7 @@ Configuration for a server entity type used by the client cache.
12
12
 
13
13
  > `optional` **fields?**: `Record`\<`string`, `RelationDescriptor`\>
14
14
 
15
- Defined in: [packages/fate/src/types.ts:118](https://github.com/nkzw-tech/fate/blob/17081c4f677e224e55b75d9a31bd6e0a7ee94af1/packages/fate/src/types.ts#L118)
15
+ Defined in: [packages/fate/src/types.ts:118](https://github.com/nkzw-tech/fate/blob/2b217058fdff50f3598b704f486529cd23a8dbb6/packages/fate/src/types.ts#L118)
16
16
 
17
17
  ***
18
18
 
@@ -20,7 +20,7 @@ Defined in: [packages/fate/src/types.ts:118](https://github.com/nkzw-tech/fate/b
20
20
 
21
21
  > **getId**: (`record`) => `string` \| `number`
22
22
 
23
- Defined in: [packages/fate/src/types.ts:119](https://github.com/nkzw-tech/fate/blob/17081c4f677e224e55b75d9a31bd6e0a7ee94af1/packages/fate/src/types.ts#L119)
23
+ Defined in: [packages/fate/src/types.ts:119](https://github.com/nkzw-tech/fate/blob/2b217058fdff50f3598b704f486529cd23a8dbb6/packages/fate/src/types.ts#L119)
24
24
 
25
25
  #### Parameters
26
26
 
@@ -38,4 +38,4 @@ Defined in: [packages/fate/src/types.ts:119](https://github.com/nkzw-tech/fate/b
38
38
 
39
39
  > **type**: `string`
40
40
 
41
- Defined in: [packages/fate/src/types.ts:120](https://github.com/nkzw-tech/fate/blob/17081c4f677e224e55b75d9a31bd6e0a7ee94af1/packages/fate/src/types.ts#L120)
41
+ Defined in: [packages/fate/src/types.ts:120](https://github.com/nkzw-tech/fate/blob/2b217058fdff50f3598b704f486529cd23a8dbb6/packages/fate/src/types.ts#L120)
@@ -2,7 +2,7 @@
2
2
 
3
3
  > **View**\<`T`, `S`\> = `Readonly`\<\{\[`viewTag`: `` `__fate-view__${string}` ``\]: `Readonly`\<\{ `[ViewKind]`: `true`; `select`: `S`; \}\>; \}\> & `__ViewEntityAnchor`\<`T`\> & `__ViewSelectionAnchor`\<`S`\>
4
4
 
5
- Defined in: [packages/fate/src/types.ts:215](https://github.com/nkzw-tech/fate/blob/17081c4f677e224e55b75d9a31bd6e0a7ee94af1/packages/fate/src/types.ts#L215)
5
+ Defined in: [packages/fate/src/types.ts:215](https://github.com/nkzw-tech/fate/blob/2b217058fdff50f3598b704f486529cd23a8dbb6/packages/fate/src/types.ts#L215)
6
6
 
7
7
  Definition of a view over an entity type, including the selection of fields.
8
8
 
@@ -2,7 +2,7 @@
2
2
 
3
3
  > **ViewData**\<`T`, `S`\> = `Readonly`\<`S` *extends* [`Selection`](Selection.md)\<`T`\> ? [`Mask`](Mask.md)\<`T`, `S`\> : `T` & `object`\>
4
4
 
5
- Defined in: [packages/fate/src/types.ts:231](https://github.com/nkzw-tech/fate/blob/17081c4f677e224e55b75d9a31bd6e0a7ee94af1/packages/fate/src/types.ts#L231)
5
+ Defined in: [packages/fate/src/types.ts:231](https://github.com/nkzw-tech/fate/blob/2b217058fdff50f3598b704f486529cd23a8dbb6/packages/fate/src/types.ts#L231)
6
6
 
7
7
  Data returned from a resolved view with masking applied and view tags
8
8
  attached for downstream composition.
@@ -2,7 +2,7 @@
2
2
 
3
3
  > **ViewEntity**\<`V`\> = `V` *extends* [`View`](View.md)\<infer T, `any`\> ? `T` : `never`
4
4
 
5
- Defined in: [packages/fate/src/types.ts:303](https://github.com/nkzw-tech/fate/blob/17081c4f677e224e55b75d9a31bd6e0a7ee94af1/packages/fate/src/types.ts#L303)
5
+ Defined in: [packages/fate/src/types.ts:303](https://github.com/nkzw-tech/fate/blob/2b217058fdff50f3598b704f486529cd23a8dbb6/packages/fate/src/types.ts#L303)
6
6
 
7
7
  Entity type captured from a view definition.
8
8
 
@@ -2,7 +2,7 @@
2
2
 
3
3
  > **ViewEntityName**\<`V`\> = [`ViewEntity`](ViewEntity.md)\<`V`\>\[`"__typename"`\] & `string`
4
4
 
5
- Defined in: [packages/fate/src/types.ts:306](https://github.com/nkzw-tech/fate/blob/17081c4f677e224e55b75d9a31bd6e0a7ee94af1/packages/fate/src/types.ts#L306)
5
+ Defined in: [packages/fate/src/types.ts:306](https://github.com/nkzw-tech/fate/blob/2b217058fdff50f3598b704f486529cd23a8dbb6/packages/fate/src/types.ts#L306)
6
6
 
7
7
  Name of the entity type captured from a view definition.
8
8
 
@@ -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/src/types.ts:101](https://github.com/nkzw-tech/fate/blob/17081c4f677e224e55b75d9a31bd6e0a7ee94af1/packages/fate/src/types.ts#L101)
5
+ Defined in: [packages/fate/src/types.ts:101](https://github.com/nkzw-tech/fate/blob/2b217058fdff50f3598b704f486529cd23a8dbb6/packages/fate/src/types.ts#L101)
6
6
 
7
7
  Reference to a normalized entity instance that can be resolved against one or more view tags.
8
8
 
@@ -2,7 +2,7 @@
2
2
 
3
3
  > **ViewSelection**\<`V`\> = `V` *extends* `object` ? `S` : `never`
4
4
 
5
- Defined in: [packages/fate/src/types.ts:309](https://github.com/nkzw-tech/fate/blob/17081c4f677e224e55b75d9a31bd6e0a7ee94af1/packages/fate/src/types.ts#L309)
5
+ Defined in: [packages/fate/src/types.ts:309](https://github.com/nkzw-tech/fate/blob/2b217058fdff50f3598b704f486529cd23a8dbb6/packages/fate/src/types.ts#L309)
6
6
 
7
7
  Selection captured from a view definition.
8
8
 
@@ -2,7 +2,7 @@
2
2
 
3
3
  > **ViewSnapshot**\<`T`, `S`\> = `Readonly`\<\{ `coverage`: `ReadonlyArray`\<readonly \[[`EntityId`](EntityId.md), `ReadonlySet`\<`string`\>\]\>; `data`: [`ViewData`](ViewData.md)\<`T`, `S`\>; \}\>
4
4
 
5
- Defined in: [packages/fate/src/types.ts:241](https://github.com/nkzw-tech/fate/blob/17081c4f677e224e55b75d9a31bd6e0a7ee94af1/packages/fate/src/types.ts#L241)
5
+ Defined in: [packages/fate/src/types.ts:241](https://github.com/nkzw-tech/fate/blob/2b217058fdff50f3598b704f486529cd23a8dbb6/packages/fate/src/types.ts#L241)
6
6
 
7
7
  Snapshot returned by the cache for a view, including the masked data and all
8
8
  referenced entity IDs.
@@ -2,6 +2,6 @@
2
2
 
3
3
  > **ViewTag** = `` `__fate-view__${string}` ``
4
4
 
5
- Defined in: [packages/fate/src/types.ts:49](https://github.com/nkzw-tech/fate/blob/17081c4f677e224e55b75d9a31bd6e0a7ee94af1/packages/fate/src/types.ts#L49)
5
+ Defined in: [packages/fate/src/types.ts:49](https://github.com/nkzw-tech/fate/blob/2b217058fdff50f3598b704f486529cd23a8dbb6/packages/fate/src/types.ts#L49)
6
6
 
7
7
  Unique key that identifies a view composition entry inside a selection or reference.
@@ -2,6 +2,6 @@
2
2
 
3
3
  > `const` **ConnectionTag**: *typeof* `ConnectionTag`
4
4
 
5
- Defined in: [packages/fate/src/types.ts:20](https://github.com/nkzw-tech/fate/blob/17081c4f677e224e55b75d9a31bd6e0a7ee94af1/packages/fate/src/types.ts#L20)
5
+ Defined in: [packages/fate/src/types.ts:20](https://github.com/nkzw-tech/fate/blob/2b217058fdff50f3598b704f486529cd23a8dbb6/packages/fate/src/types.ts#L20)
6
6
 
7
7
  Symbol attached to connection results so pagination helpers can find their metadata.
@@ -5,9 +5,13 @@ fate does not provide hooks for mutations like traditional data fetching librari
5
5
  - `fate.actions` for use with [`useActionState`](https://react.dev/reference/react/useActionState) and React Actions.
6
6
  - `fate.mutations` for traditional imperative mutation calls.
7
7
 
8
- Mutations in your tRPC backend are made available as actions and mutations by fate's generated client.
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
- Let's assume that our `Post` entity has a tRPC mutation for liking a post called `post.like`. A `LikeButton` component using fate Actions and an async component library could then look like this:
10
+ - With the [native HTTP transport](server-integration.md#native-fate-protocol), mutations come from the `mutations` object passed to `createFateServer`.
11
+ - With the [tRPC adapter](server-integration.md#trpc-fate-setup), mutations come from tRPC mutation procedures exposed through your fate-enabled router.
12
+ - With [Void](void-integration.md), mutations use the same native fate server shape and are exposed through the Void route helpers.
13
+
14
+ If you have a mutation named `post.like`, a `LikeButton` component using fate Actions and an async component library could look like this:
11
15
 
12
16
  ```tsx
13
17
  import { useActionState } from 'react';
@@ -142,7 +146,45 @@ You can call mutations from anywhere, and without waiting for previous mutations
142
146
 
143
147
  ## Mutation Server Implementation
144
148
 
145
- fate Actions & Mutations are backed by regular tRPC mutations on the server. Here is an example implementation of the `like` mutation in the `postRouter`.
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
+
151
+ - [Native HTTP custom mutations](server-integration.md#custom-mutations) use `createFateServer({ mutations })`.
152
+ - [tRPC fate setup](server-integration.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](void-integration.md) exposes a native fate server from Void routes; define mutations with the native `createFateServer({ mutations })` API and serve them through `defineVoidFateRoute`.
154
+
155
+ Here is a native HTTP mutation for `post.like`:
156
+
157
+ ```tsx
158
+ export const fate = createFateServer({
159
+ mutations: {
160
+ 'post.like': {
161
+ input: likeInput,
162
+ resolve: async ({ ctx, input, select }) => {
163
+ await ctx.prisma.post.update({
164
+ data: {
165
+ likes: {
166
+ increment: 1,
167
+ },
168
+ },
169
+ where: { id: input.id },
170
+ });
171
+
172
+ return sources.resolveById({
173
+ ctx,
174
+ id: input.id,
175
+ input: { select },
176
+ view: postDataView,
177
+ });
178
+ },
179
+ type: 'Post',
180
+ },
181
+ },
182
+ roots: Root,
183
+ sources,
184
+ });
185
+ ```
186
+
187
+ The equivalent tRPC mutation lives in your router and returns the selected shape that the client asked for:
146
188
 
147
189
  ```tsx
148
190
  import { z } from 'zod';
@@ -181,7 +223,7 @@ export const postRouter = router({
181
223
  });
182
224
  ```
183
225
 
184
- See the [Server Integration](#server-integration) section for more details on how to integrate tRPC routers with fate.
226
+ See [Server Integration](server-integration.md) for complete native HTTP and tRPC setup examples, and [Void Integration](void-integration.md) for route helpers when your app runs on Void.
185
227
 
186
228
  ## Action & Mutation Error Handling
187
229
 
@@ -22,7 +22,7 @@ The API mirrors `useView`: pass a view and a ref, and get back the same masked d
22
22
 
23
23
  ## How Live Updates Work
24
24
 
25
- The native HTTP transport opens one Server-Sent Events (SSE) connection per Fate client. When components mount or unmount live views, the client sends subscribe and unsubscribe control messages to the server. The server keeps those selections on the connection and sends updates only for records that connection subscribed to.
25
+ The native HTTP transport opens one Server-Sent Events (SSE) connection per fate client. When components mount or unmount live views, the client sends subscribe and unsubscribe control messages to the server. The server keeps those selections on the connection and sends updates only for records that connection subscribed to.
26
26
 
27
27
  When the server sends an update, fate normalizes the selected record into the same cache used by requests, actions, and mutations. Components that read affected fields re-render automatically.
28
28
 
@@ -32,7 +32,7 @@ Live deletion events remove the record from the normalized cache in the same way
32
32
 
33
33
  ## Client Setup
34
34
 
35
- Generate the client with the native transport and point it at your Fate endpoint:
35
+ Configure the native transport and point the client at your fate endpoint:
36
36
 
37
37
  ```tsx
38
38
  import { FateClient } from 'react-fate';
@@ -83,6 +83,19 @@ export const fate = createFateServer<AppContext>({
83
83
  app.all('/fate/*', createHonoFateHandler(fate));
84
84
  ```
85
85
 
86
+ fate keeps a bounded in-memory queue for each native SSE connection while live events are waiting to be resolved and sent. The default limit is `1000` queued events per connection. If a client falls behind and exceeds the limit, fate closes that live connection so server memory cannot grow without bound. You can tune the limit by passing the object form:
87
+
88
+ ```tsx
89
+ export const fate = createFateServer<AppContext>({
90
+ live: {
91
+ bus: live,
92
+ maxQueueSize: 500,
93
+ },
94
+ roots: Root,
95
+ sources,
96
+ });
97
+ ```
98
+
86
99
  Once this is in place, components can switch from `useView` to `useLiveView` without changing their view definitions or return types.
87
100
 
88
101
  ## Live List Views
@@ -26,7 +26,7 @@ This component suspends or throws errors, which bubble up to the nearest error b
26
26
 
27
27
  > [!NOTE]
28
28
  >
29
- > `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.
29
+ > `useRequest` may issue multiple operations in the same render pass. fate transports can batch those operations into fewer network requests: the native HTTP transport batches same-microtask operations into one `POST /fate` request, and the tRPC adapter can use tRPC's [HTTP Batch Link](https://trpc.io/docs/client/links/httpBatchLink).
30
30
 
31
31
  ## Requesting Objects by ID
32
32
 
@@ -1,8 +1,8 @@
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 follows fate's data protocol so the Vite plugin can provide a typed client module. _fate_ currently ships two server paths:
3
+ Until now, we have focused on the client-side API of fate. You'll need a backend that follows fate's data protocol so the Vite plugin can wire the typed fate APIs into your app. _fate_ currently ships two server paths:
4
4
 
5
- - The native Fate protocol, which is transport-agnostic and can be hosted by any Fetch-compatible server.
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
7
 
8
8
  _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.
@@ -16,7 +16,7 @@ fate expects that data is served by a backend that follows these conventions:
16
16
 
17
17
  Objects are identified by their ID and type name (`__typename`, e.g. `Post`, `User`), and stored by `__typename:id` (e.g. "Post:123") in the client cache. fate keeps list orderings under stable keys derived from the backend procedure and args. Relations are stored as IDs and returned to components as ViewRef tokens.
18
18
 
19
- fate's type definitions might seem verbose at first glance. However, with fate's minimal API surface, AI tools can easily generate this code for you, or you can let the Vite plugin provide the typed client module for your app.
19
+ fate's type definitions might seem verbose at first glance. However, with fate's minimal API surface, AI tools can easily write this code for you, and the Vite plugin takes care of connecting it to your app.
20
20
 
21
21
  > [!NOTE]
22
22
  > You can adopt _fate_ incrementally in an existing tRPC codebase without changing your existing schema by adding these queries alongside your existing procedures.
@@ -112,9 +112,9 @@ export const Root = {
112
112
  };
113
113
  ```
114
114
 
115
- Entries that wrap their view in `list(...)` are treated as list resolvers. In the native protocol, the root key is the operation name sent by the generated client. In the tRPC adapter, `procedure` can point that root at a specific router procedure. If you omit `list(...)`, fate treats the entry as a standard query.
115
+ Entries that wrap their view in `list(...)` are treated as list resolvers. In the native protocol, the root key is the operation name used by the client. In the tRPC adapter, `procedure` can point that root at a specific router procedure. If you omit `list(...)`, fate treats the entry as a standard query.
116
116
 
117
- You can pass default list options such as `orderBy` to `list(...)`. Ordering is scoped to that specific list wrapper: `Root.posts` can order posts by `createdAt desc`, while `categoryDataView.posts` or `postDataView.comments` can choose their own order. If no order is provided, Fate orders by `id asc`. Fate always appends `id asc` as a tie-breaker when no `id` order is present; include `id` yourself when you need a different tie-breaker direction such as `id desc`. Use the array form when ordering by multiple fields so the priority is unambiguous.
117
+ You can pass default list options such as `orderBy` to `list(...)`. Ordering is scoped to that specific list wrapper: `Root.posts` can order posts by `createdAt desc`, while `categoryDataView.posts` or `postDataView.comments` can choose their own order. If no order is provided, fate orders by `id asc`. fate always appends `id asc` as a tie-breaker when no `id` order is present; include `id` yourself when you need a different tie-breaker direction such as `id desc`. Use the array form when ordering by multiple fields so the priority is unambiguous.
118
118
 
119
119
  For the above `Root` definitions, you can make the following requests using `useRequest`:
120
120
 
@@ -135,7 +135,7 @@ const { posts, categories, viewer } = useRequest({
135
135
  });
136
136
  ```
137
137
 
138
- ## Native Fate Protocol
138
+ ## Native fate protocol
139
139
 
140
140
  The native protocol keeps tRPC optional. Create a source adapter from your ORM integration, pass it to `createFateServer`, and expose the returned server through a Fetch-compatible handler.
141
141
 
@@ -198,7 +198,7 @@ export default defineConfig({
198
198
  });
199
199
  ```
200
200
 
201
- The generated client uses `createHTTPTransport`:
201
+ 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
202
 
203
203
  ```tsx
204
204
  import { createFateClient } from 'react-fate/client';
@@ -208,7 +208,7 @@ const client = createFateClient({
208
208
  });
209
209
  ```
210
210
 
211
- 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.
211
+ 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
212
 
213
213
  ### Custom Queries
214
214
 
@@ -297,15 +297,17 @@ live.update('Post', post.id, {
297
297
 
298
298
  `changed` is optional. When provided, fate resolves only the changed fields selected by each live subscription and skips subscriptions that do not select those fields. `createLiveEventBus` is an in-memory fanout bus. It forwards `eventId` to SSE clients, but it does not replay events after reconnects. If your app needs lossless reconnect behavior, provide a durable live bus implementation that uses the `lastEventId` passed to `listen`, `listenConnection`, `subscribe`, and `subscribeConnection`.
299
299
 
300
- ## tRPC Fate Setup
300
+ Native SSE connections keep a bounded in-memory queue while events are waiting to be resolved and sent. The default is `1000` queued events per connection. If a client falls behind and exceeds that limit, fate closes the live connection instead of buffering indefinitely. Configure it with `live: { bus: live, maxQueueSize: 500 }`.
301
+
302
+ ## tRPC fate setup
301
303
 
302
304
  The Prisma and Drizzle tRPC integrations connect your data views to your database, bind fate's standard tRPC procedures, and expose helpers for custom queries and mutations.
303
305
 
304
- Pass the `Root` export from `views.ts` to Fate in your tRPC `init.ts` file. Fate walks that view graph to find the data views it needs. `id` defaults to `"id"`, and Fate uses it as the fallback ordering for cursor pagination. Relations are inferred from the data view and ORM schema: a nested data view is loaded as a singular relation, `list(view)` is loaded as a list relation, and Drizzle join tables are discovered from relation metadata.
306
+ Pass the `Root` export from `views.ts` to fate in your tRPC `init.ts` file. fate walks that view graph to find the data views it needs. `id` defaults to `"id"`, and fate uses it as the fallback ordering for cursor pagination. Relations are inferred from the data view and ORM schema: a nested data view is loaded as a singular relation, `list(view)` is loaded as a list relation, and Drizzle join tables are discovered from relation metadata.
305
307
 
306
308
  ### Prisma
307
309
 
308
- Use `createPrismaFate` from `@nkzw/fate/server/prisma` next to your tRPC helpers. By default, Fate reads Prisma delegates from `ctx.prisma` using each data view's type name:
310
+ Use `createPrismaFate` from `@nkzw/fate/server/prisma` next to your tRPC helpers. By default, fate reads Prisma delegates from `ctx.prisma` using each data view's type name:
309
311
 
310
312
  ```tsx
311
313
  import { initTRPC } from '@trpc/server';
@@ -354,7 +356,7 @@ return plan.resolve(post);
354
356
 
355
357
  ### Drizzle
356
358
 
357
- Use `createDrizzleFate` from `@nkzw/fate/server/drizzle`. Fate matches data view type names to Drizzle tables from your schema. The `db` option can be a Drizzle database object or a function that receives your tRPC context and returns a request-scoped database object:
359
+ Use `createDrizzleFate` from `@nkzw/fate/server/drizzle`. fate matches data view type names to Drizzle tables from your schema. The `db` option can be a Drizzle database object or a function that receives your tRPC context and returns a request-scoped database object:
358
360
 
359
361
  ```tsx
360
362
  import { initTRPC } from '@trpc/server';
@@ -393,9 +395,21 @@ export const fate = createDrizzleFate<AppContext, typeof procedure>({
393
395
 
394
396
  The Drizzle adapter builds SQL queries from your registered data views. It selects only requested columns, hydrates singular, list, and many-to-many relations, supports nested cursor pagination, and hydrates computed `count(...)` dependencies with SQL grouped counts. Count filters may be plain equality objects or Drizzle SQL predicates written as `(columns) => eq(columns.status, 'GOING')`.
395
397
 
398
+ Nested paginated relations are resolved with one child-page query per parent row. fate runs those child queries with a default concurrency limit of `10` so a single request cannot flood the database connection pool. Tune this with `nestedPaginationConcurrency` if your database pool or workload needs a different limit:
399
+
400
+ ```tsx
401
+ export const fate = createDrizzleFate<AppContext, typeof procedure>({
402
+ db,
403
+ nestedPaginationConcurrency: 5,
404
+ procedure,
405
+ schema,
406
+ views: Root,
407
+ });
408
+ ```
409
+
396
410
  For request-specific sorting, prefer a custom root query that validates and translates explicit sort args.
397
411
 
398
- For many-to-many relations, define the join table relations in your Drizzle schema. Fate discovers a join table that points at both the source table and the target table:
412
+ For many-to-many relations, define the join table relations in your Drizzle schema. fate discovers a join table that points at both the source table and the target table:
399
413
 
400
414
  ```tsx
401
415
  export const fate = createDrizzleFate<AppContext, typeof procedure>({
@@ -453,7 +467,7 @@ return post;
453
467
 
454
468
  ## tRPC Procedures
455
469
 
456
- Use `fate.procedures` to build the standard `byId` and `list` procedures expected by the generated fate client:
470
+ Use `fate.procedures` to build the standard `byId` and `list` procedures expected by fate's request APIs:
457
471
 
458
472
  ```tsx
459
473
  import { fate, router } from '../init.ts';
@@ -547,11 +561,11 @@ export const postDataView = dataView<PostItem>('Post')({
547
561
 
548
562
  The adapters fetch the hidden `field(...)` and `count(...)` dependencies for you. This keeps private fields like `email` available to the resolver without exposing them to the client selection.
549
563
 
550
- ## Configuring the typed client
564
+ ## Connecting the Client
551
565
 
552
- Now that we have defined our client views and our tRPC server, we need to connect them with some glue code. We recommend using fate's Vite plugin for convenience.
566
+ Now that we have defined our client views and our server module, add fate's Vite plugin to the client app. The plugin reads your server exports and wires the typed fate APIs into your app.
553
567
 
554
- First, make sure our tRPC `router.ts` file exports the `appRouter` object, `AppRouter` type and all the views we have defined:
568
+ For tRPC, make sure the `router.ts` file exports the `appRouter` object, `AppRouter` type and all the views we have defined:
555
569
 
556
570
  ```tsx
557
571
  import { router } from './init.ts';
@@ -583,11 +597,11 @@ export default defineConfig({
583
597
  });
584
598
  ```
585
599
 
586
- _Note: fate uses the specified server module name to extract the server types it needs and uses the same module name in the generated client. Make sure that the module is available to the client package's Vite config._
600
+ _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._
587
601
 
588
- During development, the plugin watches the server module and the files it imports. When one of those files changes, fate regenerates the project-local client and invalidates `@nkzw/fate/client` in Vite's module graph.
602
+ 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.
589
603
 
590
- For a barebones client without React, import the plugin from `@nkzw/fate/vite` and the generated client from `@nkzw/fate/client`. The plugin writes the project-local client for the selected client module path.
604
+ For a barebones client without React, 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.
591
605
 
592
606
  The plugin writes project-local types under `.fate/`. If your TypeScript config does not already include dot-directories, extend the generated config:
593
607
 
@@ -599,7 +613,7 @@ The plugin writes project-local types under `.fate/`. If your TypeScript config
599
613
 
600
614
  ## Creating a _fate_ Client
601
615
 
602
- Now that the Vite plugin provides 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:
616
+ Now that the Vite plugin has connected the types, create a fate client instance and provide it to your React app with the `FateClient` context provider:
603
617
 
604
618
  ```tsx
605
619
  import { httpBatchLink } from '@trpc/client';
@@ -104,7 +104,7 @@ This code fetches the author associated with the Post and makes it available to
104
104
  In fate, views are composable and reusable. Instead of inlining the selection, we can define a `UserView` and compose it into the `PostView` like this:
105
105
 
106
106
  ```tsx
107
- import type { Post, User } from '@your-org/server/trpc/views';
107
+ import type { Post, User } from '@your-org/server/views';
108
108
  import { view } from 'react-fate';
109
109
 
110
110
  export const UserView = view<User>()({
@@ -33,7 +33,7 @@ export default defineConfig({
33
33
  });
34
34
  ```
35
35
 
36
- The generated client uses `/fate` for RPC requests and `/fate-live` for live
36
+ The Void transport uses `/fate` for RPC requests and `/fate-live` for live
37
37
  updates by default. In SSR, it calls the exported fate server directly. In the
38
38
  browser, it uses fetch and the SSE live endpoint.
39
39
 
@@ -106,8 +106,8 @@ control messages. `void-fate` does not use WebSockets.
106
106
 
107
107
  ## React Layout
108
108
 
109
- Wrap your app with `VoidFateClient` from `void-fate/react`. It creates the
110
- generated fate client and provides it through `react-fate`:
109
+ Wrap your app with `VoidFateClient` from `void-fate/react`. It creates and
110
+ provides the fate client through `react-fate`:
111
111
 
112
112
  ```tsx
113
113
  import { useShared } from '@void/react';
package/lib/index.d.mts CHANGED
@@ -1,5 +1,5 @@
1
1
  import { A as ViewRef, B as RequestMode, C as Snapshot, D as ViewData, E as View, F as __FateSelectionBrand, G as createTRPCTransport, H as createClient, I as isViewTag, J as getSelectionPlan, L as FateMutations, M as ViewSnapshot, N as ViewTag, O as ViewEntity, P as __FateEntityBrand, R as mutation, S as Selection, T as TypeName, U as List, V as RequestOptions, W as Transport, _ as NodesItem, a as Entity, b as RequestResult, c as FateThenable, d as MutationDefinition, f as MutationEntity, g as MutationShape, h as MutationResult, i as ConnectionTag, j as ViewSelection, k as ViewEntityName, l as ListItem, m as MutationInput, n as ConnectionMetadata, o as EntityId, p as MutationIdentifier, r as ConnectionRef, s as FateRoots, t as AnyRecord, u as Mask, v as Pagination, w as TypeConfig, x as RootDefinition, y as Request$1, z as FateClient } from "./types-Dz46PXr3.mjs";
2
- import { C as liveGlobalConnectionTopic, S as liveEntityTopic, a as FateProtocolRequest, i as FateOperation, n as FateLiveConnectionEvent, o as FateProtocolResponse, r as FateLiveEvent, t as isRecord, u as InferFateAPI, x as liveConnectionTopic } from "./record-ENh92gyb.mjs";
2
+ import { C as liveGlobalConnectionTopic, S as liveEntityTopic, a as FateProtocolRequest, i as FateOperation, n as FateLiveConnectionEvent, o as FateProtocolResponse, r as FateLiveEvent, t as isRecord, u as InferFateAPI, x as liveConnectionTopic } from "./record-CirbfZWX.mjs";
3
3
  import { n as getListEntries, t as ListEntry } from "./list-4wRNSVgI.mjs";
4
4
 
5
5
  //#region src/httpTransport.d.ts
package/lib/index.mjs CHANGED
@@ -1197,7 +1197,7 @@ const getRequestDescriptorKey = (items) => {
1197
1197
  parts.push(`query:${item.name}:${item.viewSignature}:${item.argsPayload ? hashArgs(item.argsPayload) : ""}`);
1198
1198
  continue;
1199
1199
  }
1200
- if (item.kind === "list") parts.push(`list:${item.name}:${item.viewSignature}:${item.argsPayload ? hashArgs(item.argsPayload) : ""}`);
1200
+ if (item.kind === "list") parts.push(`list:${item.name}:${item.hasItems ? "connection" : "entity"}:${item.viewSignature}:${item.argsPayload ? hashArgs(item.argsPayload) : ""}`);
1201
1201
  }
1202
1202
  return parts.join("$");
1203
1203
  };
@@ -2156,7 +2156,7 @@ var FateClient = class {
2156
2156
  ...getPaginationMergeInfo(requestArgs),
2157
2157
  replace: true
2158
2158
  });
2159
- if (!connection.args) this.registerRootList(connection.type, connection.key);
2159
+ if (!filterConnectionArgs(connection.args)) this.registerRootList(connection.type, connection.key);
2160
2160
  this.store.setList(connection.key, nextListState);
2161
2161
  return nextListState;
2162
2162
  }
@@ -2231,7 +2231,8 @@ var FateClient = class {
2231
2231
  const requestKey = descriptor.key;
2232
2232
  const existingRequest = this.requests.get(requestKey)?.get(mode);
2233
2233
  if (existingRequest) {
2234
- if (revalidateExisting && existingRequest.status !== "pending" && (mode === "network-only" || mode === "stale-while-revalidate" || mode === "cache-first" && (existingRequest.status === "rejected" || existingRequest.status === "fulfilled" && !this.hasRequestData(existingRequest.descriptor)))) this.executeRequestHandle(existingRequest, mode);
2234
+ const isFulfilledCacheFirstWithMissingData = mode === "cache-first" && existingRequest.status === "fulfilled" && !this.hasRequestData(existingRequest.descriptor);
2235
+ if (existingRequest.status !== "pending" && (isFulfilledCacheFirstWithMissingData || revalidateExisting && (mode === "network-only" || mode === "stale-while-revalidate" || mode === "cache-first" && existingRequest.status === "rejected"))) this.executeRequestHandle(existingRequest, mode);
2235
2236
  return existingRequest;
2236
2237
  }
2237
2238
  const handle = new FateRequestPromise(descriptor, () => this.getRequestResultFromDescriptor(descriptor));
@@ -2553,7 +2554,7 @@ var FateClient = class {
2553
2554
  ids.push(id);
2554
2555
  cursors.push(entry.cursor);
2555
2556
  }
2556
- if (!item.argsPayload) this.registerRootList(item.type, item.listKey);
2557
+ if (!filterConnectionArgs(item.argsPayload)) this.registerRootList(item.type, item.listKey);
2557
2558
  const previous = this.store.getListState(item.listKey);
2558
2559
  this.store.setList(item.listKey, this.mergeListState(previous, ids, cursors, pagination, {
2559
2560
  ...getPaginationMergeInfo(item.argsPayload),
@@ -87,7 +87,7 @@ type LiveEventBus = Readonly<{
87
87
  }) => void;
88
88
  }>;
89
89
  /**
90
- * Creates a small in-memory event bus for Fate live view subscriptions.
90
+ * Creates a small in-memory event bus for fate live view subscriptions.
91
91
  *
92
92
  * The bus signals that an entity changed and can optionally include changed
93
93
  * field paths. The native live handler refetches the selected record, or only
@@ -146,6 +146,7 @@ type MutationDefinition<Context, Input = unknown, Output = unknown> = {
146
146
  };
147
147
  type LiveConfig = LiveEventBus | {
148
148
  bus: LiveEventBus;
149
+ maxQueueSize?: number;
149
150
  };
150
151
  type ContextOptions<AdapterContext> = {
151
152
  adapterContext?: AdapterContext;
@@ -45,6 +45,7 @@ type DrizzleViewConfig<Item extends AnyRecord = AnyRecord, TTable extends Drizzl
45
45
  type DrizzleViewsInput = Array<DataView<AnyRecord> | DrizzleViewConfig<AnyRecord>> | DataViewModule;
46
46
  type DrizzleSourceAdapterOptions<Context> = {
47
47
  db: DrizzleDatabaseInput<Context>;
48
+ nestedPaginationConcurrency?: number;
48
49
  schema?: Record<string, unknown>;
49
50
  views: DrizzleViewsInput;
50
51
  };
@@ -124,6 +125,7 @@ type SourceInput = {
124
125
  };
125
126
  declare function createDrizzleSourceAdapter<Context>({
126
127
  db,
128
+ nestedPaginationConcurrency,
127
129
  schema,
128
130
  views
129
131
  }: DrizzleSourceAdapterOptions<Context>): DrizzleSourceAdapter<Context>;