@nkzw/fate 1.3.3 → 1.5.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.
Files changed (191) hide show
  1. package/README.md +326 -0
  2. package/docs/api/classes/FateClient.md +51 -199
  3. package/docs/api/functions/clientRoot.md +1 -1
  4. package/docs/api/functions/createClient.md +1 -1
  5. package/docs/api/functions/createGraphQLTransport.md +1 -1
  6. package/docs/api/functions/createHTTPTransport.md +1 -1
  7. package/docs/api/functions/createTRPCTransport.md +5 -1
  8. package/docs/api/functions/defer.md +1 -1
  9. package/docs/api/functions/getDeferredMetadata.md +1 -1
  10. package/docs/api/functions/getListEntries.md +1 -1
  11. package/docs/api/functions/getSelectionPlan.md +1 -1
  12. package/docs/api/functions/graphqlMutation.md +1 -1
  13. package/docs/api/functions/isDeferred.md +1 -1
  14. package/docs/api/functions/isRecord.md +1 -1
  15. package/docs/api/functions/isViewTag.md +1 -1
  16. package/docs/api/functions/liveConnectionTopic.md +1 -1
  17. package/docs/api/functions/liveEntityTopic.md +1 -1
  18. package/docs/api/functions/liveGlobalConnectionTopic.md +1 -1
  19. package/docs/api/functions/mutation.md +1 -1
  20. package/docs/api/functions/toEntityId.md +1 -1
  21. package/docs/api/functions/view.md +1 -1
  22. package/docs/api/index.md +4 -1
  23. package/docs/api/interfaces/FateThenable.md +3 -3
  24. package/docs/api/interfaces/Persistence.md +23 -0
  25. package/docs/api/interfaces/PersistenceSession.md +109 -0
  26. package/docs/api/interfaces/Transport.md +46 -7
  27. package/docs/api/server/classes/FateRequestError.md +5 -5
  28. package/docs/api/server/drizzle/functions/createDrizzleFate.md +1 -1
  29. package/docs/api/server/drizzle/functions/createDrizzleSourceAdapter.md +1 -1
  30. package/docs/api/server/drizzle/functions/createDrizzleSourceRegistry.md +1 -1
  31. package/docs/api/server/drizzle/type-aliases/DrizzleManyToManyConfig.md +1 -1
  32. package/docs/api/server/drizzle/type-aliases/DrizzleManyToManyInput.md +1 -1
  33. package/docs/api/server/drizzle/type-aliases/DrizzleQueryExtra.md +3 -3
  34. package/docs/api/server/drizzle/type-aliases/DrizzleSourceAdapter.md +9 -9
  35. package/docs/api/server/drizzle/type-aliases/DrizzleViewConfig.md +1 -1
  36. package/docs/api/server/functions/bindSourceProcedures.md +1 -1
  37. package/docs/api/server/functions/computed.md +1 -1
  38. package/docs/api/server/functions/count.md +1 -1
  39. package/docs/api/server/functions/createFateFetchHandler.md +1 -1
  40. package/docs/api/server/functions/createFateServer.md +1 -1
  41. package/docs/api/server/functions/createHonoFateHandler.md +1 -1
  42. package/docs/api/server/functions/createLiveEventBus.md +1 -1
  43. package/docs/api/server/functions/createNestedSourcePlan.md +1 -1
  44. package/docs/api/server/functions/createResolver.md +1 -1
  45. package/docs/api/server/functions/createSourcePlan.md +1 -1
  46. package/docs/api/server/functions/createSourceProcedures.md +1 -1
  47. package/docs/api/server/functions/dataView.md +1 -1
  48. package/docs/api/server/functions/field.md +1 -1
  49. package/docs/api/server/functions/getNestedSelection.md +1 -1
  50. package/docs/api/server/functions/getScopedArgs.md +1 -1
  51. package/docs/api/server/functions/hasNestedSelection.md +1 -1
  52. package/docs/api/server/functions/list.md +1 -1
  53. package/docs/api/server/functions/refetchSourceById.md +1 -1
  54. package/docs/api/server/functions/resolveSourceById.md +1 -1
  55. package/docs/api/server/functions/resolveSourceByIds.md +1 -1
  56. package/docs/api/server/functions/resolveSourceConnection.md +1 -1
  57. package/docs/api/server/functions/resolver.md +1 -1
  58. package/docs/api/server/functions/toPrismaSelect.md +1 -1
  59. package/docs/api/server/functions/withConnection.md +1 -1
  60. package/docs/api/server/prisma/functions/createPrismaFate.md +1 -1
  61. package/docs/api/server/prisma/functions/createPrismaSourceAdapter.md +1 -1
  62. package/docs/api/server/prisma/functions/createPrismaSourceRegistry.md +1 -1
  63. package/docs/api/server/prisma/functions/prismaConnectionArgs.md +1 -1
  64. package/docs/api/server/prisma/type-aliases/PrismaDelegate.md +4 -4
  65. package/docs/api/server/prisma/type-aliases/PrismaQueryExtra.md +1 -1
  66. package/docs/api/server/prisma/type-aliases/PrismaSourceAdapter.md +9 -9
  67. package/docs/api/server/prisma/type-aliases/PrismaViewConfig.md +1 -1
  68. package/docs/api/server/type-aliases/ComputedField.md +5 -5
  69. package/docs/api/server/type-aliases/ComputedSelection.md +1 -1
  70. package/docs/api/server/type-aliases/ConnectionItem.md +3 -3
  71. package/docs/api/server/type-aliases/ConnectionPagination.md +5 -5
  72. package/docs/api/server/type-aliases/ConnectionResult.md +3 -3
  73. package/docs/api/server/type-aliases/CountSelection.md +4 -4
  74. package/docs/api/server/type-aliases/CountWhere.md +1 -1
  75. package/docs/api/server/type-aliases/DataViewListOptions.md +2 -2
  76. package/docs/api/server/type-aliases/DataViewOrderBy.md +1 -1
  77. package/docs/api/server/type-aliases/DataViewOrderDirection.md +1 -1
  78. package/docs/api/server/type-aliases/DataViewResult.md +1 -1
  79. package/docs/api/server/type-aliases/Entity.md +1 -1
  80. package/docs/api/server/type-aliases/FateServer.md +5 -5
  81. package/docs/api/server/type-aliases/FateServerManifest.md +1 -1
  82. package/docs/api/server/type-aliases/FieldSelection.md +3 -3
  83. package/docs/api/server/type-aliases/LiveConnectionEventType.md +1 -1
  84. package/docs/api/server/type-aliases/LiveConnectionSourceEvent.md +1 -1
  85. package/docs/api/server/type-aliases/LiveEventBus.md +1 -1
  86. package/docs/api/server/type-aliases/LiveEventType.md +1 -1
  87. package/docs/api/server/type-aliases/LiveSourceEvent.md +1 -1
  88. package/docs/api/server/type-aliases/NativeFateAPI.md +4 -4
  89. package/docs/api/server/type-aliases/OrderDirection.md +1 -1
  90. package/docs/api/server/type-aliases/SourceConfig.md +6 -6
  91. package/docs/api/server/type-aliases/SourceDefinition.md +6 -6
  92. package/docs/api/server/type-aliases/SourceOrder.md +1 -1
  93. package/docs/api/server/type-aliases/SourceOrderField.md +3 -3
  94. package/docs/api/server/type-aliases/SourcePlan.md +1 -1
  95. package/docs/api/server/type-aliases/SourcePlanNode.md +1 -1
  96. package/docs/api/server/type-aliases/SourceRegistry.md +1 -1
  97. package/docs/api/server/type-aliases/SourceRelation.md +1 -1
  98. package/docs/api/server/type-aliases/SourceRelationConfig.md +1 -1
  99. package/docs/api/server/variables/byIdInput.md +1 -1
  100. package/docs/api/server/variables/connectionArgs.md +1 -1
  101. package/docs/api/type-aliases/ConnectionMetadata.md +1 -1
  102. package/docs/api/type-aliases/ConnectionRef.md +1 -1
  103. package/docs/api/type-aliases/Deferred.md +1 -1
  104. package/docs/api/type-aliases/DeferredMetadata.md +1 -1
  105. package/docs/api/type-aliases/DeferredSelection.md +1 -1
  106. package/docs/api/type-aliases/DeferredSnapshot.md +1 -1
  107. package/docs/api/type-aliases/Entity.md +2 -2
  108. package/docs/api/type-aliases/EntityId.md +1 -1
  109. package/docs/api/type-aliases/FateDehydratedState.md +1 -1
  110. package/docs/api/type-aliases/FateLiveConnectionEvent.md +1 -1
  111. package/docs/api/type-aliases/FateLiveEvent.md +1 -1
  112. package/docs/api/type-aliases/FateMutations.md +1 -1
  113. package/docs/api/type-aliases/FateOperation.md +2 -2
  114. package/docs/api/type-aliases/FateProtocolRequest.md +1 -1
  115. package/docs/api/type-aliases/FateProtocolResponse.md +1 -1
  116. package/docs/api/type-aliases/FateRecord.md +1 -1
  117. package/docs/api/type-aliases/FateRoots.md +1 -1
  118. package/docs/api/type-aliases/GraphQLMutationDefinition.md +1 -1
  119. package/docs/api/type-aliases/GraphQLMutationInput.md +1 -1
  120. package/docs/api/type-aliases/GraphQLMutationMap.md +1 -1
  121. package/docs/api/type-aliases/GraphQLMutationOutput.md +1 -1
  122. package/docs/api/type-aliases/GraphQLTransportOptions.md +19 -11
  123. package/docs/api/type-aliases/HydrateOptions.md +1 -1
  124. package/docs/api/type-aliases/HydrationLimits.md +1 -1
  125. package/docs/api/type-aliases/InferFateAPI.md +1 -1
  126. package/docs/api/type-aliases/List.md +1 -1
  127. package/docs/api/type-aliases/ListEntry.md +1 -1
  128. package/docs/api/type-aliases/ListItem.md +1 -1
  129. package/docs/api/type-aliases/Mask.md +1 -1
  130. package/docs/api/type-aliases/MutationDefinition.md +1 -1
  131. package/docs/api/type-aliases/MutationEntity.md +1 -1
  132. package/docs/api/type-aliases/MutationIdentifier.md +1 -1
  133. package/docs/api/type-aliases/MutationIdentity.md +7 -0
  134. package/docs/api/type-aliases/MutationInput.md +1 -1
  135. package/docs/api/type-aliases/MutationResult.md +1 -1
  136. package/docs/api/type-aliases/NodesItem.md +1 -1
  137. package/docs/api/type-aliases/Pagination.md +5 -5
  138. package/docs/api/type-aliases/PersistenceSnapshot.md +5 -0
  139. package/docs/api/type-aliases/Request.md +1 -1
  140. package/docs/api/type-aliases/RequestMode.md +1 -1
  141. package/docs/api/type-aliases/RequestOptions.md +2 -2
  142. package/docs/api/type-aliases/RequestResult.md +1 -1
  143. package/docs/api/type-aliases/Selection.md +1 -1
  144. package/docs/api/type-aliases/TypeConfig.md +4 -4
  145. package/docs/api/type-aliases/View.md +1 -1
  146. package/docs/api/type-aliases/ViewData.md +1 -1
  147. package/docs/api/type-aliases/ViewEntity.md +1 -1
  148. package/docs/api/type-aliases/ViewEntityName.md +1 -1
  149. package/docs/api/type-aliases/ViewRef.md +1 -1
  150. package/docs/api/type-aliases/ViewSelection.md +1 -1
  151. package/docs/api/type-aliases/ViewSnapshot.md +1 -1
  152. package/docs/api/type-aliases/ViewTag.md +1 -1
  153. package/docs/api/variables/ConnectionTag.md +1 -1
  154. package/docs/api/variables/DeferTag.md +1 -1
  155. package/docs/api/variables/DeferredTag.md +1 -1
  156. package/docs/guide/getting-started.md +2 -0
  157. package/docs/guide/persistence.md +323 -0
  158. package/lib/{liveTopics-uVP0LG-Q.mjs → args-CRLIRl_v.mjs} +1 -59
  159. package/lib/cli.d.mts +1 -1
  160. package/lib/{executor-C8HNogYG.d.mts → executor-DoiTBiVv.d.mts} +18 -65
  161. package/lib/{graphqlTransport-DskuXeUP.d.mts → graphqlTransport-DGLYyRxL.d.mts} +4 -14
  162. package/lib/{graphqlTransport-DSBVS2Fq.mjs → graphqlTransport-DmheG3AO.mjs} +3 -2
  163. package/lib/graphqlTransport.d.mts +1 -1
  164. package/lib/graphqlTransport.mjs +1 -1
  165. package/lib/hydration-DPZRJgnu.mjs +273 -0
  166. package/lib/idempotency-types-CqA-BFpn.d.mts +15 -0
  167. package/lib/idempotency.d.mts +26 -0
  168. package/lib/idempotency.mjs +34 -0
  169. package/lib/index.d.mts +6 -15
  170. package/lib/index.mjs +560 -1774
  171. package/lib/{list-sI2UPhHc.d.mts → list-BRT1VtiT.d.mts} +1 -2
  172. package/lib/list.d.mts +1 -1
  173. package/lib/liveTopics-DuFkDZQm.mjs +17 -0
  174. package/lib/persistence.d.mts +38 -0
  175. package/lib/persistence.mjs +1529 -0
  176. package/lib/protocol-DS5sf6sd.mjs +44 -0
  177. package/lib/{record-CZyyeLa3.d.mts → record-DITM4zb_.d.mts} +14 -19
  178. package/lib/server/drizzle.d.mts +45 -114
  179. package/lib/server/drizzle.mjs +2 -2
  180. package/lib/server/prisma.d.mts +52 -127
  181. package/lib/server/prisma.mjs +9 -8
  182. package/lib/server.d.mts +4 -16
  183. package/lib/server.mjs +21 -35
  184. package/lib/sortObjectKeys-yhuitEQZ.mjs +9 -0
  185. package/lib/{sourceRouter-DSaADXSf.mjs → sourceRouter-51POBlEE.mjs} +9 -6
  186. package/lib/store-CeatPJeK.mjs +1350 -0
  187. package/lib/{transport-DhaVVq4P.d.mts → transport-DqjhjLfC.d.mts} +672 -517
  188. package/lib/vite.d.mts +0 -1
  189. package/lib/vite.mjs +19 -11
  190. package/package.json +15 -5
  191. package/docs/api/type-aliases/Snapshot.md +0 -7
@@ -0,0 +1,323 @@
1
+ # Persistence
2
+
3
+ fate can optionally keep data available across page reloads and save mutations while a user is offline. When the user comes back, their views can show previously loaded data and their pending changes continue where they left off.
4
+
5
+ Persistence builds on fate's normalized cache, [Requests](requests.md), and [Actions](actions.md). You configure storage only once, without changing how you use fate.
6
+
7
+ ## Client Setup
8
+
9
+ The persistence layer is part of `@nkzw/fate`, with storage adapters installed separately. For a browser app using IndexedDB, add the adapter:
10
+
11
+ ```sh
12
+ vp add @nkzw/fate-indexeddb
13
+ ```
14
+
15
+ Then pass `persistence` when creating the client:
16
+
17
+ ```tsx
18
+ import { createPersistence } from '@nkzw/fate/persistence';
19
+ import { indexedDB } from '@nkzw/fate-indexeddb';
20
+ import { createFateClient } from 'react-fate/client';
21
+
22
+ const fate = createFateClient({
23
+ persistence: createPersistence({
24
+ key: `workspace:${workspaceId}:user:${userId}`,
25
+ storage: indexedDB(),
26
+ }),
27
+ url: '/api/fate',
28
+ });
29
+ ```
30
+
31
+ By default, fate keeps loaded data for **one day**, with a storage budget of **25 MiB**. You can change both values:
32
+
33
+ ```tsx
34
+ persistence: createPersistence({
35
+ key: `workspace:${workspaceId}:user:${userId}`,
36
+ maxAge: 24 * 60 * 60 * 1000,
37
+ maxBytes: 25 * 1024 * 1024,
38
+ storage: indexedDB(),
39
+ }),
40
+ ```
41
+
42
+ Create the browser client after you know which user is signed in. The `key` separates saved data and mutations by account and workspace, and must match the authenticated scope in the [server setup](#server-deduplication). When switching accounts, dispose the previous persistence session and create a new client with the new account's key.
43
+
44
+ > [!NOTE]
45
+ >
46
+ > Persistence saves your app's data. To load the app itself while offline, you'll also need a service worker that makes its HTML, JavaScript, and other assets available. fate does not install a service worker or download pages the user hasn't visited.
47
+
48
+ ## Cache Lifetime
49
+
50
+ You can keep the data for a specific screen longer by passing `persist` to `useRequest`. For example, to keep a list of posts for three days:
51
+
52
+ ```tsx
53
+ const { posts } = useRequest(
54
+ {
55
+ posts: { list: PostView },
56
+ },
57
+ {
58
+ persist: { maxAge: 3 * 24 * 60 * 60 * 1000 },
59
+ },
60
+ );
61
+ ```
62
+
63
+ The same option works with `fate.request(...)` and Vue's `useRequest`. If a screen uses several requests, set the option on each request whose data you want to keep longer.
64
+
65
+ fate stores objects by their type and ID, just like the in-memory cache. If a post appears in both your feed and a detail screen, both requests share the same saved post. Each request describes which fields and related objects it needs, including list membership and pagination state.
66
+
67
+ For example, your feed might keep posts for one day, while the detail screen keeps them for three days. After one day, the fields needed by the detail screen remain available. Fields selected only by the feed can be removed. You don't need to coordinate separate copies of the same post or manually patch each request's cache.
68
+
69
+ `maxAge` is measured in milliseconds from when the data was fetched successfully. Reading saved data or rendering the screen again does not restart that lifetime. If a request fetches one missing post, it only renews the data it fetched. Other posts already in the cache keep their original age. Changing a cached request's `maxAge` also uses the original fetch time.
70
+
71
+ When callers share a pending request, the latest explicit `persist.maxAge` also applies to that request's eventual cache write.
72
+
73
+ Pass `persist: { maxAge: 0 }` to skip saving data for a request. Shared objects may still be saved for other requests, so this option does not delete all copies of an object.
74
+
75
+ The [in-memory garbage collector](requests.md#cache-lifetime) continues to work independently. A post can be removed from memory while its saved copy remains available for a later visit. On startup, fate restores pending mutations, then loads saved data as requests need it. It does not load the entire saved cache into memory.
76
+
77
+ ## Refreshing Data
78
+
79
+ Keeping data for three days doesn't mean waiting three days for updates. You can use the existing [request modes](requests.md#request-modes) to choose when to fetch fresh data:
80
+
81
+ - `cache-first` (_default_): Uses available saved data and fetches missing data from the network.
82
+ - `stale-while-revalidate`: Shows saved data and refreshes it in the background. If the refresh fails, the saved data stays visible.
83
+ - `network-only`: Requires a network response, even if saved data is available.
84
+
85
+ For example, to show the previous session's posts immediately and update them on reload:
86
+
87
+ ```tsx
88
+ const { posts } = useRequest(
89
+ {
90
+ posts: { list: PostView },
91
+ },
92
+ {
93
+ mode: 'stale-while-revalidate',
94
+ persist: { maxAge: 3 * 24 * 60 * 60 * 1000 },
95
+ },
96
+ );
97
+ ```
98
+
99
+ Expiration controls how long data stays in storage. It does not remove data from a mounted view or change the in-memory fetch policy. For ongoing server updates, use [Live Views](live-views.md).
100
+
101
+ ## Cache Size
102
+
103
+ When saved data reaches `maxBytes`, fate removes expired data first, then releases the least recently used requests until the new data fits. Objects still needed by another saved request remain available, and shared objects count toward the budget only once. A response that is too large to save can still be used in memory without removing other saved requests to make room for it.
104
+
105
+ The budget includes encoded data, keys, cache metadata, and the saved mutation queue. Your storage backend may use additional space for its own bookkeeping. `maxBytes` controls saved data; the existing garbage collector controls the lifetime of data in memory.
106
+
107
+ fate batches cache writes and yields during large traversals and writes so other work on the page can continue. The mutation journal stores each entry separately and writes only changed entries, so confirming a mutation does not rewrite the entire queue. Changes to scalar fields write the affected record, while changes to relationships also update the saved data that depends on them.
108
+
109
+ Pending mutations are never removed to make room for cached data. If a new mutation cannot fit, it fails before fate applies its optimistic update or sends it to the server.
110
+
111
+ > [!NOTE]
112
+ >
113
+ > Already accepted mutations must still be able to finish. Their recovery data and saved results can exceed the budget if they grow or you lower `maxBytes`. In that case, fate releases the read cache and rejects new durable mutations until capacity is available. Local confirmation receipts count toward the budget until their cache changes have been saved. fate then removes them automatically, making room for new mutations.
114
+
115
+ ## Actions & Mutations
116
+
117
+ With persistence configured, fate saves actions and mutations locally before applying their optimistic updates or sending them to the server. The API is the same as for regular [Actions](actions.md):
118
+
119
+ ```tsx
120
+ const [result, like] = useActionState(fate.actions.post.like, null);
121
+
122
+ like({
123
+ input: { id: post.id },
124
+ optimistic: { likes: post.likes + 1 },
125
+ });
126
+ ```
127
+
128
+ If the user likes a post while offline, the like count updates immediately. Reloading the page restores the pending action and its optimistic update. When the connection returns and a client is running, fate sends the action to the server and updates the post with the confirmed result.
129
+
130
+ Mutations from one client are saved in invocation order and sent in the order they were saved, one at a time for each persistence key. Tabs sharing that key coordinate delivery. Temporary network and server failures are retried with increasing delays, up to 30 seconds between attempts. Authentication failures stay pending so delivery can resume after the user signs in again. A terminal client error rolls back the optimistic update and follows fate's existing [error handling](actions.md#action--mutation-error-handling).
131
+
132
+ The mutation promise resolves after remote confirmation has been saved locally. It can remain pending while offline. Closing the page loses the JavaScript promise, but the saved mutation and optimistic update remain. After a reload, use the [persistence state](#persistence-state) to show pending and failed changes.
133
+
134
+ ### Skipping Persistence
135
+
136
+ For a call that should run immediately without being saved or retried, pass `persist: false`:
137
+
138
+ ```tsx
139
+ await fate.mutations.analytics.record({
140
+ input: { event: 'opened-settings' },
141
+ persist: false,
142
+ });
143
+ ```
144
+
145
+ The same option works with `fate.actions`. A successful result can still update the normal cache. Without persistence configured, actions and mutations keep their existing behavior.
146
+
147
+ ### Creating Objects Offline
148
+
149
+ Use a stable, client-generated ID when creating an object offline. For example, a new comment and a later edit to that comment can use the same ID, allowing fate to send the creation before the edit when the user reconnects.
150
+
151
+ If your server assigns IDs, wait for the creation result before constructing a dependent mutation. fate does not rewrite arbitrary foreign keys inside saved inputs. A failed creation also does not automatically cancel later mutations; your server should validate them as usual.
152
+
153
+ Mutation inputs and optimistic updates must be serializable with fate's hydration codec. Functions, streams, and `File` objects cannot be queued. For uploads, save the content first and queue a reference to it, or use `persist: false` for the upload itself.
154
+
155
+ ## Server Deduplication
156
+
157
+ A connection can fail after the server has already applied a mutation. For example, the server might increment a post's like count, but the response never reaches the browser. Retrying that mutation without server support would increment the count twice.
158
+
159
+ fate assigns an identity to each saved mutation and reuses it on every attempt. The server records the result alongside the mutation's database changes, in **the same transaction**. When the same mutation arrives again, the server returns the saved result.
160
+
161
+ For the native HTTP transport, configure `createMutationIdempotency` on your server. The following example uses application-provided helpers to lock a mutation identity and read or insert its receipt:
162
+
163
+ ```ts
164
+ import { createMutationIdempotency } from '@nkzw/fate/persistence/server';
165
+ import { createFateServer } from '@nkzw/fate/server';
166
+
167
+ const server = createFateServer({
168
+ // ...roots, sources, mutations, context...
169
+ idempotency: createMutationIdempotency({
170
+ scope: (ctx) => `workspace:${ctx.workspace.id}:user:${ctx.user.id}`,
171
+ store: {
172
+ transaction: (ctx, scope, id, run) =>
173
+ database.transaction(async (tx) => {
174
+ await lockMutationIdentity(tx, scope, id);
175
+ return run({
176
+ context: { ...ctx, db: tx },
177
+ read: () => readReceipt(tx, scope, id),
178
+ write: (receipt) => insertReceipt(tx, scope, id, receipt),
179
+ });
180
+ }),
181
+ },
182
+ }),
183
+ });
184
+ ```
185
+
186
+ Your app supplies `database`, `lockMutationIdentity`, `readReceipt`, and `insertReceipt`. The lock must serialize attempts with the same `(scope, id)` across server processes, including the first attempt before a receipt exists. Mutation resolvers must use the transaction's `ctx.db`, so their changes and the receipt commit together. A unique index on the receipt table alone cannot protect changes made outside that transaction.
187
+
188
+ See [`example/persistence/server.ts`](https://github.com/nkzw-tech/fate/blob/main/example/persistence/server.ts) for a complete SQLite implementation, including the receipt table and transaction handling.
189
+
190
+ The helper checks the identity against the authenticated scope and rejects reuse with different mutation names, inputs, or selections. Server receipts do not expire: a user might reconnect much later with a mutation whose response was lost. Keep receipts for as long as an old mutation could still arrive.
191
+
192
+ A database transaction cannot roll back an email, payment, or webhook sent to another service. For those effects, use a transactional outbox and the destination's idempotency support. The transaction integration guarantees one committed database effect; network requests and resolver attempts can still happen more than once.
193
+
194
+ Native HTTP sends durable mutations and receipt-only recovery using protocol version 2, so older servers reject unsupported requests before executing them. Ordinary requests and live subscriptions continue to use version 1. A current server without the idempotency integration also rejects durable mutations before running their resolvers.
195
+
196
+ ## tRPC, GraphQL, and Custom Transports
197
+
198
+ For tRPC, GraphQL, or a custom transport, provide a `mutateDurably(name, input, select, identity)` method that sends the identity to an endpoint with server deduplication. Regular calls continue to use `mutate(name, input, select)`.
199
+
200
+ Both `createTRPCTransport` and `createGraphQLTransport`, including their generated clients, accept `mutateDurably`. For example, you can use a native fate endpoint for durable mutations alongside your existing transport:
201
+
202
+ ```tsx
203
+ const durableHTTP = createHTTPTransport<MyAPI>({
204
+ url: '/api/fate',
205
+ // Use the same authenticated headers as your other transport.
206
+ });
207
+
208
+ const fate = createFateClient({
209
+ // ...your generated tRPC or GraphQL client options...
210
+ persistence: createPersistence({ key: accountKey, storage: indexedDB() }),
211
+ mutateDurably: durableHTTP.mutateDurably,
212
+ });
213
+ ```
214
+
215
+ Both endpoints need to agree on mutation names, inputs, results, and entity IDs. A custom GraphQL adapter should unwrap its response and decode GraphQL global IDs before returning data to fate.
216
+
217
+ You can also use the same idempotency helper inside an existing tRPC or GraphQL resolver. Validate the input and identity at the endpoint, then pass them to `execute`:
218
+
219
+ ```ts
220
+ return idempotency.execute({
221
+ ctx,
222
+ identity: input.identity,
223
+ input: input.update,
224
+ name: 'post.update',
225
+ select: input.select,
226
+ resolve: (transactionContext) => updatePost(transactionContext, input.update),
227
+ });
228
+ ```
229
+
230
+ The adapter must pass the entire identity on every attempt, including `replayOnly` when present. With `replayOnly: true`, the endpoint must return the existing receipt or a 404 if none exists, without executing the mutation. `createMutationIdempotency` handles both delivery and receipt-only recovery. Adding an identity to a header that the server ignores does not prevent duplicate effects.
231
+
232
+ If a client has registered mutations but no durable adapter, the persistence session reports a configuration error. Saved reads and `persist: false` calls still work. New durable calls fail before being queued, and existing queued mutations stay saved until a client with the adapter can deliver them.
233
+
234
+ ## Persistence State
235
+
236
+ Use `fate.persistence` to observe pending changes and storage errors:
237
+
238
+ ```tsx
239
+ const session = fate.persistence!;
240
+ await session.ready;
241
+
242
+ const unsubscribe = session.subscribe(() => {
243
+ const { status, error, mutations } = session.getSnapshot();
244
+ // Show pending changes or report a storage error.
245
+ });
246
+ ```
247
+
248
+ `getSnapshot` returns a stable value between updates and can be used with React's `useSyncExternalStore`. The same subscription API works with Vue. Each mutation exposes its ID, name, input, status (`queued`, `sending`, or `failed`), and last error.
249
+
250
+ A failed initial restoration rejects `ready`. Storage errors also appear in the snapshot. If reading the saved cache fails, fate can still fetch the data from the network. If saving a new mutation fails, the call rejects before it is sent.
251
+
252
+ Cache writes are batched. To save the latest read cache before a reload you control, call `flush()`:
253
+
254
+ ```tsx
255
+ await fate.persistence!.flush();
256
+ window.location.reload();
257
+ ```
258
+
259
+ A failed cache write keeps its pending data for a later `flush()` retry. Mutations are saved individually before delivery and do not depend on a delayed cache write or a page-unload event. On confirmation, fate saves the receipt, its confirmed cache changes, and the remaining mutations' recovery data together before resolving the call. Saving the read cache separately means a cache write failure cannot block an already confirmed mutation. The receipt and confirmed cache changes remain in the journal until the read cache checkpoint succeeds, then fate removes them. A tab that missed the confirmation recovers its result from the server receipt when it reconnects. This lookup cannot execute a discarded mutation. After a restart, fate repairs those changes before serving saved reads; if storage is still unavailable, reads fall back to the network instead of returning stale saved data.
260
+
261
+ You can also retry delivery or discard a mutation:
262
+
263
+ ```tsx
264
+ session.retry();
265
+ await session.discard(mutationId);
266
+ ```
267
+
268
+ `retry()` respects the saved retry deadline. `discard()` removes an unsent mutation or a terminal failure. A mutation that was already attempted cannot be discarded while its result is unknown: it may have committed remotely, and fate must recover that result first.
269
+
270
+ When leaving an account, dispose its session and unsubscribe:
271
+
272
+ ```tsx
273
+ session.dispose();
274
+ unsubscribe();
275
+ ```
276
+
277
+ Disposing stops that client's delivery, rejects its pending promises, and removes its optimistic updates from memory. Saved mutations remain available to the next client using the same key. Responses arriving after disposal cannot update the old client's cache.
278
+
279
+ ## Clearing Saved Data
280
+
281
+ To remove the saved read cache, call `clearCache()`:
282
+
283
+ ```tsx
284
+ await fate.persistence!.clearCache();
285
+ ```
286
+
287
+ This leaves current in-memory data, queued mutations, and their recovery data intact. Confirmation receipts waiting for a cache checkpoint also remain available; fate removes them after a successful checkpoint.
288
+
289
+ For a complete local account reset, stop every client using the key before removing its journal header, individual mutation entries, and namespaced cache entries from storage. This also removes unsent changes. It cannot undo mutations that already ran on the server.
290
+
291
+ Saved data carries the client's [hydration scope](requests.md#ssr-and-hydration). An incompatible read cache is discarded, while incompatible queued mutations are kept as failures for inspection. Change `hydrationScope` when deploying incompatible cache schemas or changing the meaning of mutation inputs. An unknown or corrupt mutation journal blocks restoration and is left untouched so the saved work can be recovered.
292
+
293
+ ## Other Storage Backends
294
+
295
+ The core persistence layer has no IndexedDB dependency. You can use another backend by implementing `PersistenceStorage`:
296
+
297
+ ```ts
298
+ interface PersistenceStorage {
299
+ read(key: string): Promise<unknown>;
300
+ write(key: string, value: unknown): Promise<void>;
301
+ scan(
302
+ prefix: string,
303
+ after?: string,
304
+ limit?: number,
305
+ ): Promise<Array<{ key: string; value: unknown }>>;
306
+ writeBatch(entries: ReadonlyArray<readonly [string, unknown]>): Promise<void>;
307
+ exclusive<T>(key: string, run: () => Promise<T>): Promise<T>;
308
+ subscribe?(key: string, listener: () => void): () => void;
309
+ }
310
+ ```
311
+
312
+ Values use fate's hydration codec and can be stored as JSON. The adapter handles storage and coordination:
313
+
314
+ - `read` returns the saved value for a key.
315
+ - `write` replaces one value atomically and resolves after it has committed.
316
+ - `scan` returns keys under a prefix in ascending order, strictly after the optional cursor, up to the limit (64 by default). Use your backend's ordered index to keep each scan small.
317
+ - `writeBatch` commits all entries atomically. An `undefined` value deletes the key.
318
+ - `exclusive` coordinates every tab or process sharing the backend. Different lock names are independent: fate holds a delivery lock during network work and acquires a separate write lock when updating storage. Your adapter must allow that nesting.
319
+ - `subscribe` notifies other clients after a change commits, including keys changed by `writeBatch`. Without notifications, clients check saved mutations during delivery attempts and explicit `retry()` calls.
320
+
321
+ The IndexedDB adapter implements this with `idb`, IndexedDB transactions, Web Locks, and BroadcastChannel. It requires a secure browser context with Web Locks. Browser storage can still be cleared or evicted; if your app needs stronger retention, request persistent browser storage or choose another backend.
322
+
323
+ Persistence coordinates mutation delivery across tabs, but it does not keep every tab's read cache synchronized or resolve application conflicts. Use refetching or [Live Views](live-views.md) to receive fresh server data.
@@ -136,62 +136,4 @@ const scopeArgsPayload = (args, scope) => {
136
136
  return result;
137
137
  };
138
138
  //#endregion
139
- //#region src/protocol.ts
140
- var FateRequestError = class extends Error {
141
- constructor(code, message, options = {}) {
142
- super(message);
143
- this.name = "FateRequestError";
144
- this.code = code;
145
- this.issues = options.issues;
146
- this.status = options.status ?? statusFromErrorCode(code);
147
- }
148
- };
149
- const statusFromErrorCode = (code) => {
150
- switch (code) {
151
- case "BAD_REQUEST":
152
- case "VALIDATION_ERROR": return 400;
153
- case "UNAUTHORIZED": return 401;
154
- case "FORBIDDEN": return 403;
155
- case "NOT_FOUND": return 404;
156
- case "INTERNAL_ERROR": return 500;
157
- }
158
- };
159
- const errorCodeFromStatus = (status) => {
160
- if (status === 400) return "BAD_REQUEST";
161
- if (status === 401) return "UNAUTHORIZED";
162
- if (status === 403) return "FORBIDDEN";
163
- if (status === 404) return "NOT_FOUND";
164
- return "INTERNAL_ERROR";
165
- };
166
- const toProtocolError = (error) => {
167
- if (error instanceof FateRequestError) return {
168
- code: error.code,
169
- issues: error.issues,
170
- message: error.message
171
- };
172
- if (error instanceof Error) return {
173
- code: "INTERNAL_ERROR",
174
- message: "Internal server error."
175
- };
176
- return {
177
- code: "INTERNAL_ERROR",
178
- message: "Internal server error."
179
- };
180
- };
181
- //#endregion
182
- //#region src/liveTopics.ts
183
- const encodeTopicPart = (value) => encodeURIComponent(String(value));
184
- const normalizeConnectionArgs = (args) => {
185
- const filtered = filterConnectionArgs(args);
186
- if (!filtered) return;
187
- const id = filtered.id;
188
- return typeof id === "string" || typeof id === "number" ? {
189
- ...filtered,
190
- id: String(id)
191
- } : filtered;
192
- };
193
- const liveEntityTopic = (type, id) => `entity:${encodeTopicPart(type)}:${encodeTopicPart(id)}`;
194
- const liveConnectionTopic = (procedure, args) => `connection:${encodeTopicPart(procedure)}:${encodeTopicPart(hashArgs(normalizeConnectionArgs(args) ?? {}))}`;
195
- const liveGlobalConnectionTopic = (procedure) => `connection:${encodeTopicPart(procedure)}:*`;
196
- //#endregion
197
- export { errorCodeFromStatus as a, cloneArgs as c, hashArgs as d, paginationArgKeys as f, FateRequestError as i, combineArgsPayload as l, scopeArgsPayload as m, liveEntityTopic as n, toProtocolError as o, resolvedArgsFromPlan as p, liveGlobalConnectionTopic as r, applyArgsPayloadToPlan as s, liveConnectionTopic as t, filterConnectionArgs as u };
139
+ export { hashArgs as a, scopeArgsPayload as c, filterConnectionArgs as i, cloneArgs as n, paginationArgKeys as o, combineArgsPayload as r, resolvedArgsFromPlan as s, applyArgsPayloadToPlan as t };
package/lib/cli.d.mts CHANGED
@@ -1 +1 @@
1
- export { };
1
+ export {}
@@ -1,6 +1,5 @@
1
- import { r as AnyRecord, rt as QueryProcedure } from "./transport-DhaVVq4P.mjs";
1
+ import { st as QueryProcedure, v as AnyRecord } from "./transport-DqjhjLfC.mjs";
2
2
  import { z } from "zod";
3
-
4
3
  //#region src/server/dataView.d.ts
5
4
  declare const dataViewFieldsKey: unique symbol;
6
5
  declare const dataViewBaseKey: unique symbol;
@@ -28,7 +27,7 @@ type CountSelection = {
28
27
  where?: CountWhere;
29
28
  };
30
29
  type ComputedSelection = CountSelection | FieldSelection;
31
- type ComputedDeps<Select extends Record<string, ComputedSelection> | undefined> = Select extends Record<string, ComputedSelection> ? { [Key in keyof Select]: Select[Key] extends CountSelection ? number : Select[Key] extends FieldSelection ? unknown : never } : Record<string, unknown>;
30
+ type ComputedDeps<Select extends Record<string, ComputedSelection> | undefined> = Select extends Record<string, ComputedSelection> ? { [Key in keyof Select]: Select[Key] extends CountSelection ? number : Select[Key] extends FieldSelection ? unknown : never; } : Record<string, unknown>;
32
31
  type ComputedResolve<Item extends AnyRecord, Result, Select extends Record<string, ComputedSelection> | undefined, Context> = Bivariant<(item: Item, deps: ComputedDeps<Select>, context?: Context, args?: AnyRecord) => Promise<Result> | Result>;
33
32
  /**
34
33
  * Field configuration for selecting and resolving a computed value on the backend.
@@ -49,7 +48,7 @@ type DataField<Item extends AnyRecord> = true | ComputedField<Item, any, any, an
49
48
  /**
50
49
  * Recursively serializes resolver results for transport across the network.
51
50
  */
52
- type Serializable<T> = T extends Date ? string : T extends Array<infer U> ? Array<Serializable<U>> : T extends object ? { [K in keyof T]: Serializable<T[K]> } : T;
51
+ type Serializable<T> = T extends Date ? string : T extends Array<infer U> ? Array<Serializable<U>> : T extends object ? { [K in keyof T]: Serializable<T[K]>; } : T;
53
52
  /**
54
53
  * Server-side mirror of a view definition describing how to select and resolve
55
54
  * fields when fulfilling a client request.
@@ -89,7 +88,7 @@ declare function dataView<Item extends AnyRecord>(typeName: string): <Fields ext
89
88
  * connection information.
90
89
  */
91
90
  declare const list: <Item extends AnyRecord>(view: DataView<Item>, options?: DataViewListOptions) => DataView<Item> & DataViewInternals<Item> & {
92
- kind: "list";
91
+ kind: 'list';
93
92
  };
94
93
  /**
95
94
  * Declares a resolver field inside a data view, optionally providing a
@@ -118,7 +117,7 @@ type ViewFieldConfig<V extends DataView<AnyRecord>> = V extends {
118
117
  readonly [dataViewFieldsKey]: infer Fields;
119
118
  } ? Fields : V['fields'];
120
119
  type RawFieldResult<Item extends AnyRecord, Key extends PropertyKey, Field extends DataField<Item>> = Field extends true ? Key extends keyof Item ? Item[Key] : never : Field extends DataView<infer ChildItem> ? Key extends keyof Item ? RelationResult<Item[Key], DataView<ChildItem>> : never : Field extends ComputedField<Item, any, any, any> ? ComputedResult<Field> : Field extends ResolverField<Item, any, any> ? ResolverResult<Field> : never;
121
- type RawDataViewResult<V extends DataView<AnyRecord>> = V extends DataView<infer Item> ? { [K in keyof ViewFieldConfig<V>]: RawFieldResult<Item, K, ViewFieldConfig<V>[K]> } : never;
120
+ type RawDataViewResult<V extends DataView<AnyRecord>> = V extends DataView<infer Item> ? { [K in keyof ViewFieldConfig<V>]: RawFieldResult<Item, K, ViewFieldConfig<V>[K]>; } : never;
122
121
  type DataViewResult<V extends DataView<AnyRecord>> = Serializable<RawDataViewResult<V>>;
123
122
  type WithTypename<T, Name extends string> = T & {
124
123
  __typename: Name;
@@ -150,12 +149,7 @@ type ViewPlan<Item extends AnyRecord = AnyRecord, Context = unknown> = {
150
149
  * Builds a resolver that applies a client's selection to a server data view,
151
150
  * filtering fields, running nested resolvers, and shaping Prisma selects.
152
151
  */
153
- declare function createResolver<Item extends AnyRecord, Context = unknown>({
154
- args,
155
- ctx,
156
- select,
157
- view
158
- }: {
152
+ declare function createResolver<Item extends AnyRecord, Context = unknown>({ args, ctx, select, view }: {
159
153
  args?: AnyRecord;
160
154
  ctx?: Context;
161
155
  select: Iterable<string>;
@@ -172,7 +166,7 @@ declare function createResolver<Item extends AnyRecord, Context = unknown>({
172
166
  type ConnectionInput = z.infer<typeof connectionInput>;
173
167
  type AdditionalInputSchema = z.ZodObject<Record<string, z.ZodTypeAny>>;
174
168
  type ConnectionInputWithAdditional<TAdditionalInput extends AdditionalInputSchema | undefined> = ConnectionInput & {
175
- args?: ConnectionInput['args'] extends infer A ? A & (TAdditionalInput extends AdditionalInputSchema ? z.infer<TAdditionalInput> : object) : never;
169
+ args?: ConnectionInput['args'] extends (infer A) ? A & (TAdditionalInput extends AdditionalInputSchema ? z.infer<TAdditionalInput> : object) : never;
176
170
  };
177
171
  type ConnectionCursor = string;
178
172
  /**
@@ -314,32 +308,22 @@ type SourcePlan<Item extends AnyRecord = AnyRecord, Context = unknown, Adapter =
314
308
  type DataViewModule = Record<string, unknown>;
315
309
  declare const hasNestedSelection: (select: Iterable<string>, field: string) => boolean;
316
310
  declare const getNestedSelection: (select: Iterable<string>, field: string) => Array<string>;
317
- declare function createSourcePlan<Item extends AnyRecord, Context = unknown, Adapter = unknown>({
318
- args,
319
- ctx,
320
- select,
321
- source
322
- }: {
311
+ declare function createSourcePlan<Item extends AnyRecord, Context = unknown, Adapter = unknown>({ args, ctx, select, source }: {
323
312
  args?: Record<string, unknown>;
324
313
  ctx?: Context;
325
314
  select: Iterable<string>;
326
315
  source: SourceDefinition<Item, Adapter>;
327
316
  }): {
328
- root: SourcePlanNode<unknown, Adapter>;
329
- source: SourceDefinition<Item, Adapter>;
330
317
  args: AnyRecord | undefined;
331
318
  ctx: Context | undefined;
332
319
  resolve: (item: Item) => Promise<AnyRecord>;
333
320
  resolveMany: (items: Array<AnyRecord>) => Promise<Array<AnyRecord>>;
334
321
  selectedPaths: Set<string>;
335
322
  view: DataView<Item>;
323
+ root: SourcePlanNode<unknown, Adapter>;
324
+ source: SourceDefinition<Item, Adapter>;
336
325
  };
337
- declare function createNestedSourcePlan<Item extends AnyRecord, Context = unknown, Adapter = unknown>({
338
- ctx,
339
- field,
340
- input,
341
- source
342
- }: {
326
+ declare function createNestedSourcePlan<Item extends AnyRecord, Context = unknown, Adapter = unknown>({ ctx, field, input, source }: {
343
327
  ctx?: Context;
344
328
  field: string;
345
329
  input: {
@@ -348,14 +332,14 @@ declare function createNestedSourcePlan<Item extends AnyRecord, Context = unknow
348
332
  };
349
333
  source: SourceDefinition<Item, Adapter>;
350
334
  }): {
351
- root: SourcePlanNode<unknown, Adapter>;
352
- source: SourceDefinition<Item, Adapter>;
353
335
  args: AnyRecord | undefined;
354
336
  ctx: Context | undefined;
355
337
  resolve: (item: Item) => Promise<AnyRecord>;
356
338
  resolveMany: (items: Array<AnyRecord>) => Promise<Array<AnyRecord>>;
357
339
  selectedPaths: Set<string>;
358
340
  view: DataView<Item>;
341
+ root: SourcePlanNode<unknown, Adapter>;
342
+ source: SourceDefinition<Item, Adapter>;
359
343
  };
360
344
  //#endregion
361
345
  //#region src/server/executor.d.ts
@@ -386,14 +370,7 @@ type SourceExecutor<Context, Item extends AnyRecord = AnyRecord, ByIdExtra = unk
386
370
  connection?: SourceConnectionHandler<Context, ConnectionExtra, Item>;
387
371
  };
388
372
  type SourceRegistry<Context> = Map<SourceDefinition<AnyRecord, unknown>, SourceExecutor<Context, AnyRecord, any, any, any>>;
389
- declare const resolveSourceByIds: <Context, Item extends AnyRecord>({
390
- ctx,
391
- extra,
392
- ids,
393
- input,
394
- registry,
395
- source
396
- }: {
373
+ declare const resolveSourceByIds: <Context, Item extends AnyRecord>({ ctx, extra, ids, input, registry, source }: {
397
374
  ctx: Context;
398
375
  extra?: unknown;
399
376
  ids: Array<string>;
@@ -404,14 +381,7 @@ declare const resolveSourceByIds: <Context, Item extends AnyRecord>({
404
381
  registry: SourceRegistry<Context>;
405
382
  source: SourceDefinition<Item, unknown>;
406
383
  }) => Promise<AnyRecord[]>;
407
- declare const resolveSourceById: <Context, Item extends AnyRecord>({
408
- ctx,
409
- extra,
410
- id,
411
- input,
412
- registry,
413
- source
414
- }: {
384
+ declare const resolveSourceById: <Context, Item extends AnyRecord>({ ctx, extra, id, input, registry, source }: {
415
385
  ctx: Context;
416
386
  extra?: unknown;
417
387
  id: string;
@@ -422,20 +392,10 @@ declare const resolveSourceById: <Context, Item extends AnyRecord>({
422
392
  registry: SourceRegistry<Context>;
423
393
  source: SourceDefinition<Item, unknown>;
424
394
  }) => Promise<AnyRecord | null>;
425
- declare const resolveSourceConnection: <Context, Item extends AnyRecord>({
426
- ctx,
427
- cursor,
428
- direction,
429
- extra,
430
- input,
431
- registry,
432
- skip,
433
- source,
434
- take
435
- }: {
395
+ declare const resolveSourceConnection: <Context, Item extends AnyRecord>({ ctx, cursor, direction, extra, input, registry, skip, source, take }: {
436
396
  ctx: Context;
437
397
  cursor?: string;
438
- direction: "backward" | "forward";
398
+ direction: 'backward' | 'forward';
439
399
  extra?: unknown;
440
400
  input: {
441
401
  args?: Record<string, unknown>;
@@ -446,14 +406,7 @@ declare const resolveSourceConnection: <Context, Item extends AnyRecord>({
446
406
  source: SourceDefinition<Item, unknown>;
447
407
  take: number;
448
408
  }) => Promise<AnyRecord[]>;
449
- declare const refetchSourceById: <Context, Item extends AnyRecord>({
450
- ctx,
451
- extra,
452
- id,
453
- input,
454
- registry,
455
- source
456
- }: {
409
+ declare const refetchSourceById: <Context, Item extends AnyRecord>({ ctx, extra, id, input, registry, source }: {
457
410
  ctx: Context;
458
411
  extra?: unknown;
459
412
  id: string;
@@ -1,5 +1,4 @@
1
- import { C as MutationShape, f as Entity, j as TypeConfig, t as Transport } from "./transport-DhaVVq4P.mjs";
2
-
1
+ import { D as Entity, G as TypeConfig, R as MutationShape, t as Transport } from "./transport-DqjhjLfC.mjs";
3
2
  //#region src/graphqlTransport.d.ts
4
3
  type TransportMutations = Record<string, MutationShape>;
5
4
  type EmptyTransportMutations = Record<never, MutationShape>;
@@ -31,7 +30,7 @@ type GraphQLMutationOutput<Definition> = Definition extends GraphQLMutationDefin
31
30
  type GraphQLMutationMap<Mutations> = Mutations extends Record<string, GraphQLMutationDefinition> ? { [K in keyof Mutations]: {
32
31
  input: GraphQLMutationInput<Mutations[K]>;
33
32
  output: GraphQLMutationOutput<Mutations[K]>;
34
- } } : EmptyTransportMutations;
33
+ }; } : EmptyTransportMutations;
35
34
  type GraphQLMutationRuntimeConfig = {
36
35
  entity: string;
37
36
  field: string;
@@ -50,6 +49,7 @@ type GraphQLTransportOptions<Mutations extends TransportMutations = EmptyTranspo
50
49
  fetch?: FetchLike;
51
50
  headers?: HeadersFactory;
52
51
  live?: boolean | GraphQLLiveOptions;
52
+ mutateDurably?: Transport<Mutations>['mutateDurably'];
53
53
  mutations?: Record<Extract<keyof Mutations, string>, GraphQLMutationRuntimeConfig>;
54
54
  roots?: Record<string, GraphQLRootConfig>;
55
55
  types: ReadonlyArray<Omit<TypeConfig, 'getId'> & Partial<Pick<TypeConfig, 'getId'>>>;
@@ -59,16 +59,6 @@ declare function graphqlMutation<T extends Entity, Input, Output>(entity: T['__t
59
59
  field: string;
60
60
  inputArg?: false | string;
61
61
  }): GraphQLMutationDefinition<T, Input, Output>;
62
- declare function createGraphQLTransport<Mutations extends TransportMutations = EmptyTransportMutations>({
63
- decodeNodeId,
64
- encodeNodeId,
65
- fetch: fetchImpl,
66
- headers,
67
- live,
68
- mutations,
69
- roots,
70
- types: typeConfigs,
71
- url
72
- }: GraphQLTransportOptions<Mutations>): Transport<Mutations>;
62
+ declare function createGraphQLTransport<Mutations extends TransportMutations = EmptyTransportMutations>({ decodeNodeId, encodeNodeId, fetch: fetchImpl, headers, live, mutateDurably, mutations, roots, types: typeConfigs, url }: GraphQLTransportOptions<Mutations>): Transport<Mutations>;
73
63
  //#endregion
74
64
  export { GraphQLTransportOptions as a, GraphQLMutationOutput as i, GraphQLMutationInput as n, createGraphQLTransport as o, GraphQLMutationMap as r, graphqlMutation as s, GraphQLMutationDefinition as t };
@@ -230,7 +230,7 @@ function graphqlMutation(entity, options) {
230
230
  inputArg: options.inputArg
231
231
  });
232
232
  }
233
- function createGraphQLTransport({ decodeNodeId = defaultDecodeNodeId, encodeNodeId = defaultEncodeNodeId, fetch: fetchImpl = defaultFetch, headers, live = true, mutations, roots, types: typeConfigs, url }) {
233
+ function createGraphQLTransport({ decodeNodeId = defaultDecodeNodeId, encodeNodeId = defaultEncodeNodeId, fetch: fetchImpl = defaultFetch, headers, live = true, mutateDurably, mutations, roots, types: typeConfigs, url }) {
234
234
  const endpoint = normalizeEndpoint(url);
235
235
  const types = new Map(typeConfigs.map((type) => [type.type, type]));
236
236
  let nextId = 0;
@@ -387,7 +387,8 @@ function createGraphQLTransport({ decodeNodeId = defaultDecodeNodeId, encodeNode
387
387
  value
388
388
  })
389
389
  });
390
- }
390
+ },
391
+ mutateDurably
391
392
  };
392
393
  if (live !== false) {
393
394
  const liveOptions = typeof live === "object" ? live : {};
@@ -1,2 +1,2 @@
1
- import { a as GraphQLTransportOptions, i as GraphQLMutationOutput, n as GraphQLMutationInput, o as createGraphQLTransport, r as GraphQLMutationMap, s as graphqlMutation, t as GraphQLMutationDefinition } from "./graphqlTransport-DskuXeUP.mjs";
1
+ import { a as GraphQLTransportOptions, i as GraphQLMutationOutput, n as GraphQLMutationInput, o as createGraphQLTransport, r as GraphQLMutationMap, s as graphqlMutation, t as GraphQLMutationDefinition } from "./graphqlTransport-DGLYyRxL.mjs";
2
2
  export { GraphQLMutationDefinition, GraphQLMutationInput, GraphQLMutationMap, GraphQLMutationOutput, GraphQLTransportOptions, createGraphQLTransport, graphqlMutation };
@@ -1,2 +1,2 @@
1
- import { n as graphqlMutation, t as createGraphQLTransport } from "./graphqlTransport-DSBVS2Fq.mjs";
1
+ import { n as graphqlMutation, t as createGraphQLTransport } from "./graphqlTransport-DmheG3AO.mjs";
2
2
  export { createGraphQLTransport, graphqlMutation };