@nkzw/fate 1.4.0 → 1.5.1

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 +324 -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 +6 -1
  23. package/docs/api/interfaces/FateThenable.md +3 -3
  24. package/docs/api/interfaces/Persistence.md +5 -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/PersistedMutationStatus.md +5 -0
  139. package/docs/api/type-aliases/PersistenceSnapshot.md +5 -0
  140. package/docs/api/type-aliases/Request.md +1 -1
  141. package/docs/api/type-aliases/RequestMode.md +1 -1
  142. package/docs/api/type-aliases/RequestOptions.md +2 -2
  143. package/docs/api/type-aliases/RequestPersistenceOptions.md +5 -0
  144. package/docs/api/type-aliases/RequestResult.md +1 -1
  145. package/docs/api/type-aliases/Selection.md +1 -1
  146. package/docs/api/type-aliases/TypeConfig.md +4 -4
  147. package/docs/api/type-aliases/View.md +1 -1
  148. package/docs/api/type-aliases/ViewData.md +1 -1
  149. package/docs/api/type-aliases/ViewEntity.md +1 -1
  150. package/docs/api/type-aliases/ViewEntityName.md +1 -1
  151. package/docs/api/type-aliases/ViewRef.md +1 -1
  152. package/docs/api/type-aliases/ViewSelection.md +1 -1
  153. package/docs/api/type-aliases/ViewSnapshot.md +1 -1
  154. package/docs/api/type-aliases/ViewTag.md +1 -1
  155. package/docs/api/variables/ConnectionTag.md +1 -1
  156. package/docs/api/variables/DeferTag.md +1 -1
  157. package/docs/api/variables/DeferredTag.md +1 -1
  158. package/docs/guide/getting-started.md +2 -0
  159. package/docs/guide/persistence.md +321 -0
  160. package/lib/{liveTopics-uVP0LG-Q.mjs → args-CRLIRl_v.mjs} +1 -59
  161. package/lib/{executor-BRudKWDY.d.mts → executor-oPTYqgQV.d.mts} +1 -1
  162. package/lib/{graphqlTransport-BDR1pnUQ.d.mts → graphqlTransport-CGYE4NdD.d.mts} +3 -2
  163. package/lib/{graphqlTransport-DSBVS2Fq.mjs → graphqlTransport-DmheG3AO.mjs} +3 -2
  164. package/lib/graphqlTransport.d.mts +1 -1
  165. package/lib/graphqlTransport.mjs +1 -1
  166. package/lib/hydration-DPZRJgnu.mjs +273 -0
  167. package/lib/idempotency-types-DuqJ7KSd.d.mts +16 -0
  168. package/lib/idempotency.d.mts +26 -0
  169. package/lib/idempotency.mjs +34 -0
  170. package/lib/index.d.mts +5 -5
  171. package/lib/index.mjs +275 -1567
  172. package/lib/{list-Bb1SeABn.d.mts → list-5hrTVoiB.d.mts} +1 -1
  173. package/lib/list.d.mts +1 -1
  174. package/lib/liveTopics-DuFkDZQm.mjs +17 -0
  175. package/lib/persistence.d.mts +40 -0
  176. package/lib/persistence.mjs +1730 -0
  177. package/lib/protocol-DS5sf6sd.mjs +44 -0
  178. package/lib/{record-BTUXkHQU.d.mts → record-Dxjvpsep.d.mts} +8 -4
  179. package/lib/server/drizzle.d.mts +2 -2
  180. package/lib/server/drizzle.mjs +1 -1
  181. package/lib/server/prisma.d.mts +2 -2
  182. package/lib/server/prisma.mjs +1 -6
  183. package/lib/server.d.mts +3 -3
  184. package/lib/server.mjs +19 -7
  185. package/lib/sortObjectKeys-yhuitEQZ.mjs +9 -0
  186. package/lib/{sourceRouter-DSaADXSf.mjs → sourceRouter-51POBlEE.mjs} +9 -6
  187. package/lib/store-CakBBzWG.mjs +1386 -0
  188. package/lib/{transport-Bo1Jgo9R.d.mts → transport-DR0fwleU.d.mts} +607 -412
  189. package/lib/vite.mjs +18 -10
  190. package/package.json +12 -2
  191. package/docs/api/type-aliases/Snapshot.md +0 -7
package/lib/index.mjs CHANGED
@@ -1,7 +1,11 @@
1
1
  import { t as isRecord } from "./record-B07VwXd-.mjs";
2
- import { a as errorCodeFromStatus, c as cloneArgs, d as hashArgs, f as paginationArgKeys, i as FateRequestError, l as combineArgsPayload, m as scopeArgsPayload, n as liveEntityTopic, p as resolvedArgsFromPlan, r as liveGlobalConnectionTopic, s as applyArgsPayloadToPlan, t as liveConnectionTopic, u as filterConnectionArgs } from "./liveTopics-uVP0LG-Q.mjs";
2
+ import { a as hashArgs, c as scopeArgsPayload, i as filterConnectionArgs, r as combineArgsPayload, s as resolvedArgsFromPlan, t as applyArgsPayloadToPlan } from "./args-CRLIRl_v.mjs";
3
+ import { _ as isNodesItem, a as createNodeRef, c as ConnectionTag, f as RootKind, g as isNodeItem, i as resolveHydrationLimits, l as DeferTag, m as ViewsTag, o as getNodeRefId, r as encodeHydrationValue, s as isNodeRef, t as decodeClientHydrationState, u as DeferredTag, v as isQueryItem, y as isViewTag } from "./hydration-DPZRJgnu.mjs";
4
+ import { C as isDeferredSelection, S as isDeferred, _ as view, a as mutation, b as getDeferredMetadata, c as getDeferredSelectionPlan, d as createRefWithViewNames, f as getRootViewNames, g as getViewPayloads, h as getViewNames, l as getSelectionPlan, m as toEntityId, n as getListKey, p as parseEntityId, r as mergePreservingExisting, s as wrapMutation, t as Store, u as assignViewTag, v as createDeferred, x as getDeferredSelection, y as defer } from "./store-CakBBzWG.mjs";
3
5
  import { t as getListEntries } from "./list-_6fXYLtJ.mjs";
4
- import { n as graphqlMutation, t as createGraphQLTransport } from "./graphqlTransport-DSBVS2Fq.mjs";
6
+ import { n as errorCodeFromStatus, t as FateRequestError } from "./protocol-DS5sf6sd.mjs";
7
+ import { n as graphqlMutation, t as createGraphQLTransport } from "./graphqlTransport-DmheG3AO.mjs";
8
+ import { n as liveEntityTopic, r as liveGlobalConnectionTopic, t as liveConnectionTopic } from "./liveTopics-DuFkDZQm.mjs";
5
9
  //#region src/cache.ts
6
10
  var ViewDataCache = class {
7
11
  constructor(isEnabled = () => true) {
@@ -73,747 +77,6 @@ var ViewDataCache = class {
73
77
  }
74
78
  };
75
79
  //#endregion
76
- //#region src/types.ts
77
- /** Internal marker added to objects that represent a view payload. */
78
- const ViewKind = Symbol("__fate__view");
79
- /** Symbol used to attach the set of view tags that were spread into a ref or masked record. */
80
- const ViewsTag = Symbol("__fate__views");
81
- /** Symbol used to brand a value as a node reference inside the cache. */
82
- const NodeRefTag = Symbol("__fate__node-ref");
83
- /** Symbol attached to connection results so pagination helpers can find their metadata. */
84
- const ConnectionTag = Symbol("__fate__connection");
85
- /** Symbol used to mark a selection as deferred. */
86
- const DeferTag = Symbol("__fate__defer");
87
- /** Symbol attached to deferred values returned from masked views. */
88
- const DeferredTag = Symbol("__fate__deferred");
89
- const viewTag = "__fate-view__";
90
- /** Generates a stable view tag for a view definition. */
91
- function getViewTag(id) {
92
- return `${viewTag}${id}`;
93
- }
94
- /** Determines whether a property key is a fate view tag. */
95
- function isViewTag(key) {
96
- return key.startsWith(viewTag);
97
- }
98
- /** Indicates whether a request item represents an explicit node ID. */
99
- function isNodeItem(item) {
100
- return "id" in item;
101
- }
102
- /** Indicates whether a request item represents explicit node IDs. */
103
- function isNodesItem(item) {
104
- return "ids" in item;
105
- }
106
- /** Indicates whether a request item represents a root query. */
107
- function isQueryItem(item) {
108
- return "view" in item && !("id" in item) && !("ids" in item);
109
- }
110
- /** Brand used on root definitions to mark their identity in the d.ts output. */
111
- const RootKind = "__fate__root";
112
- /** Brand used on mutation definitions to mark their identity in the d.ts output. */
113
- const MutationKind = "__fate__mutation";
114
- //#endregion
115
- //#region src/defer.ts
116
- function defer(selection) {
117
- return Object.freeze({ [DeferTag]: selection });
118
- }
119
- const isDeferredSelection = (value) => typeof value === "object" && value !== null && DeferTag in value;
120
- const getDeferredSelection = (selection) => selection[DeferTag];
121
- const createDeferred = (metadata) => Object.freeze({ [DeferredTag]: metadata });
122
- const isDeferred = (value) => typeof value === "object" && value !== null && DeferredTag in value;
123
- const getDeferredMetadata = (deferred) => deferred[DeferredTag];
124
- //#endregion
125
- //#region src/node-ref.ts
126
- function createNodeRef(id) {
127
- const ref = {};
128
- Object.defineProperty(ref, NodeRefTag, {
129
- configurable: false,
130
- enumerable: false,
131
- value: id,
132
- writable: false
133
- });
134
- return Object.freeze(ref);
135
- }
136
- function isNodeRef(value) {
137
- return !value || typeof value !== "object" ? false : NodeRefTag in value;
138
- }
139
- function getNodeRefId(ref) {
140
- return ref[NodeRefTag];
141
- }
142
- //#endregion
143
- //#region src/hydration.ts
144
- const maxDepth = 64;
145
- const defaultHydrationLimits = {
146
- maxCollectionLength: 1e5,
147
- maxNodes: 25e4,
148
- maxStringLength: 1e6
149
- };
150
- const resolveHydrationLimits = (limits) => {
151
- const result = {
152
- ...defaultHydrationLimits,
153
- ...limits
154
- };
155
- for (const [key, value] of Object.entries(result)) if (!Number.isInteger(value) || value < 1) throw new Error(`fate: Hydration limit '${key}' must be a positive integer.`);
156
- return result;
157
- };
158
- const error = (path, message) => /* @__PURE__ */ new Error(`fate: Cannot serialize hydration value at '${path}': ${message}`);
159
- const isPlainObject = (value) => {
160
- const prototype = Object.getPrototypeOf(value);
161
- return prototype === Object.prototype || prototype === null;
162
- };
163
- const isDenseArray = (value) => {
164
- for (let index = 0; index < value.length; index += 1) if (!Object.hasOwn(value, index)) return false;
165
- return true;
166
- };
167
- const isDate$1 = (value) => Object.prototype.toString.call(value) === "[object Date]" && typeof value.getTime === "function" && typeof value.toISOString === "function";
168
- const encodeHydrationValue = (value, limits) => {
169
- const resolvedLimits = resolveHydrationLimits(limits);
170
- const seen = /* @__PURE__ */ new Set();
171
- let nodes = 0;
172
- const encode = (entry, path, depth) => {
173
- nodes += 1;
174
- if (nodes > resolvedLimits.maxNodes) throw error(path, `Maximum node count of ${String(resolvedLimits.maxNodes)} exceeded.`);
175
- if (depth > maxDepth) throw error(path, `Maximum depth of ${String(maxDepth)} exceeded.`);
176
- if (entry === null) return ["null"];
177
- const type = typeof entry;
178
- if (typeof entry === "string" || typeof entry === "boolean") {
179
- if (typeof entry === "string" && entry.length > resolvedLimits.maxStringLength) throw error(path, `Maximum string length of ${String(resolvedLimits.maxStringLength)} exceeded.`);
180
- return [typeof entry, entry];
181
- }
182
- if (type === "number") {
183
- if (Number.isNaN(entry)) return ["number", "NaN"];
184
- if (entry === Infinity) return ["number", "Infinity"];
185
- if (entry === -Infinity) return ["number", "-Infinity"];
186
- if (Object.is(entry, -0)) return ["number", "-0"];
187
- return ["number", entry];
188
- }
189
- if (type === "undefined") return ["undefined"];
190
- if (type === "bigint") {
191
- const bigint = String(entry);
192
- if (bigint.length > resolvedLimits.maxStringLength) throw error(path, `Maximum string length of ${String(resolvedLimits.maxStringLength)} exceeded.`);
193
- return ["bigint", bigint];
194
- }
195
- if (type === "function" || type === "symbol") throw error(path, `Received unsupported '${type}' value.`);
196
- if (!entry || typeof entry !== "object") throw error(path, `Received unsupported '${type}' value.`);
197
- if (isNodeRef(entry)) {
198
- const id = getNodeRefId(entry);
199
- if (id.length > resolvedLimits.maxStringLength) throw error(path, `Maximum string length of ${String(resolvedLimits.maxStringLength)} exceeded.`);
200
- return ["ref", id];
201
- }
202
- if (isDate$1(entry)) {
203
- if (Number.isNaN(entry.getTime())) throw error(path, `Received an invalid Date.`);
204
- return ["date", entry.toISOString()];
205
- }
206
- if (seen.has(entry)) throw error(path, `Circular references are not supported.`);
207
- seen.add(entry);
208
- try {
209
- if (Array.isArray(entry)) {
210
- if (!isDenseArray(entry)) throw error(path, `Sparse arrays are not supported.`);
211
- if (entry.length > resolvedLimits.maxCollectionLength) throw error(path, `Maximum collection length of ${String(resolvedLimits.maxCollectionLength)} exceeded.`);
212
- return ["array", entry.map((item, index) => encode(item, `${path}[${String(index)}]`, depth + 1))];
213
- }
214
- if (!isPlainObject(entry)) throw error(path, `Received unsupported '${entry.constructor?.name ?? "object"}' value.`);
215
- if (Object.getOwnPropertySymbols(entry).length > 0) throw error(path, `Received an unsupported symbol-keyed property.`);
216
- const entries = Object.entries(entry);
217
- if (entries.length > resolvedLimits.maxCollectionLength) throw error(path, `Maximum collection length of ${String(resolvedLimits.maxCollectionLength)} exceeded.`);
218
- return ["object", entries.map(([key, item]) => {
219
- if (key.length > resolvedLimits.maxStringLength) throw error(path, `Maximum string length of ${String(resolvedLimits.maxStringLength)} exceeded.`);
220
- return [key, encode(item, `${path}.${key}`, depth + 1)];
221
- })];
222
- } finally {
223
- seen.delete(entry);
224
- }
225
- };
226
- return encode(value, "data", 0);
227
- };
228
- const invalidPayload = (path) => /* @__PURE__ */ new Error(`fate: Invalid hydration payload at '${path}'.`);
229
- const tagLengths = {
230
- array: 2,
231
- bigint: 2,
232
- boolean: 2,
233
- date: 2,
234
- null: 1,
235
- number: 2,
236
- object: 2,
237
- ref: 2,
238
- string: 2,
239
- undefined: 1
240
- };
241
- const decodeHydrationValue = (value, limits) => {
242
- const resolvedLimits = resolveHydrationLimits(limits);
243
- let nodes = 0;
244
- const decode = (entry, path, depth) => {
245
- nodes += 1;
246
- if (nodes > resolvedLimits.maxNodes || depth > maxDepth || !Array.isArray(entry) || typeof entry[0] !== "string") throw invalidPayload(path);
247
- const [tag, payload] = entry;
248
- if (entry.length !== tagLengths[tag]) throw invalidPayload(path);
249
- switch (tag) {
250
- case "null": return null;
251
- case "undefined": return;
252
- case "string":
253
- if (typeof payload !== "string" || payload.length > resolvedLimits.maxStringLength) throw invalidPayload(path);
254
- return payload;
255
- case "boolean":
256
- if (typeof payload !== "boolean") throw invalidPayload(path);
257
- return payload;
258
- case "number":
259
- if (typeof payload === "number") {
260
- if (!Number.isFinite(payload) || Object.is(payload, -0)) throw invalidPayload(path);
261
- return payload;
262
- }
263
- if (payload === "NaN") return NaN;
264
- if (payload === "Infinity") return Infinity;
265
- if (payload === "-Infinity") return -Infinity;
266
- if (payload === "-0") return -0;
267
- throw invalidPayload(path);
268
- case "bigint":
269
- if (typeof payload !== "string" || payload.length > resolvedLimits.maxStringLength) throw invalidPayload(path);
270
- try {
271
- const bigint = BigInt(payload);
272
- if (String(bigint) !== payload) throw invalidPayload(path);
273
- return bigint;
274
- } catch {
275
- throw invalidPayload(path);
276
- }
277
- case "date": {
278
- if (typeof payload !== "string" || payload.length > resolvedLimits.maxStringLength) throw invalidPayload(path);
279
- const date = new Date(payload);
280
- if (Number.isNaN(date.getTime()) || date.toISOString() !== payload) throw invalidPayload(path);
281
- return date;
282
- }
283
- case "ref":
284
- if (typeof payload !== "string" || payload.length > resolvedLimits.maxStringLength) throw invalidPayload(path);
285
- return createNodeRef(payload);
286
- case "array":
287
- if (!Array.isArray(payload) || !isDenseArray(payload) || payload.length > resolvedLimits.maxCollectionLength) throw invalidPayload(path);
288
- return payload.map((item, index) => decode(item, `${path}[${String(index)}]`, depth + 1));
289
- case "object": {
290
- if (!Array.isArray(payload) || payload.length > resolvedLimits.maxCollectionLength) throw invalidPayload(path);
291
- const result = Object.create(null);
292
- for (const item of payload) {
293
- if (!Array.isArray(item) || item.length !== 2 || typeof item[0] !== "string" || item[0].length > resolvedLimits.maxStringLength) throw invalidPayload(path);
294
- if (item[0] in result) throw invalidPayload(path);
295
- result[item[0]] = decode(item[1], `${path}.${item[0]}`, depth + 1);
296
- }
297
- return result;
298
- }
299
- default: throw invalidPayload(path);
300
- }
301
- };
302
- return decode(value, "data", 0);
303
- };
304
- const isRecord$1 = (value) => {
305
- if (!value || typeof value !== "object" || Array.isArray(value) || isNodeRef(value)) return false;
306
- const prototype = Object.getPrototypeOf(value);
307
- return prototype === Object.prototype || prototype === null;
308
- };
309
- const isStringArray = (value) => Array.isArray(value) && value.every((entry) => typeof entry === "string");
310
- const hasDuplicates = (values) => new Set(values).size !== values.length;
311
- const isUniqueStringArray = (value) => isStringArray(value) && !hasDuplicates(value);
312
- const hasOnlyKeys = (value, keys) => Object.keys(value).every((key) => keys.has(key));
313
- const listKeys = /* @__PURE__ */ new Set([
314
- "backwardPageLimit",
315
- "cursors",
316
- "forwardPageLimit",
317
- "ids",
318
- "liveAfterIds",
319
- "liveBeforeIds",
320
- "pagination",
321
- "pendingAfterIds",
322
- "pendingBeforeIds"
323
- ]);
324
- const paginationKeys = /* @__PURE__ */ new Set([
325
- "hasNext",
326
- "hasPrevious",
327
- "nextCursor",
328
- "previousCursor"
329
- ]);
330
- const clientStateKeys = /* @__PURE__ */ new Set([
331
- "rootLists",
332
- "rootRequests",
333
- "store"
334
- ]);
335
- const storeStateKeys = /* @__PURE__ */ new Set([
336
- "coverage",
337
- "lists",
338
- "records"
339
- ]);
340
- const isEntry = (value) => Array.isArray(value) && value.length === 2 && typeof value[0] === "string";
341
- const isOptionalLimit = (value) => value === void 0 || typeof value === "number" && Number.isInteger(value) && value >= 0;
342
- const isPagination = (value) => value === void 0 || isRecord$1(value) && hasOnlyKeys(value, paginationKeys) && typeof value.hasNext === "boolean" && typeof value.hasPrevious === "boolean" && (value.nextCursor === void 0 || typeof value.nextCursor === "string") && (value.previousCursor === void 0 || typeof value.previousCursor === "string");
343
- const isList = (value) => {
344
- if (!isRecord$1(value) || !hasOnlyKeys(value, listKeys) || !isStringArray(value.ids) || hasDuplicates(value.ids) || value.cursors !== void 0 && (!Array.isArray(value.cursors) || value.cursors.length !== value.ids.length)) return false;
345
- return (value.cursors === void 0 || Array.isArray(value.cursors) && value.cursors.every((entry) => entry === void 0 || typeof entry === "string")) && (value.liveAfterIds === void 0 || isUniqueStringArray(value.liveAfterIds)) && (value.liveBeforeIds === void 0 || isUniqueStringArray(value.liveBeforeIds)) && isOptionalLimit(value.backwardPageLimit) && isOptionalLimit(value.forwardPageLimit) && isPagination(value.pagination) && (value.pendingAfterIds === void 0 || isUniqueStringArray(value.pendingAfterIds)) && (value.pendingBeforeIds === void 0 || isUniqueStringArray(value.pendingBeforeIds));
346
- };
347
- const isFieldPath = (value) => value.length > 0 && value.split(".").every((segment) => segment.length > 0);
348
- const isFieldPathArray = (value) => isUniqueStringArray(value) && value.every(isFieldPath);
349
- const hasDuplicateEntries = (entries) => hasDuplicates(entries.map((entry) => entry[0]));
350
- const decodeClientHydrationState = (value, limits) => {
351
- const decoded = decodeHydrationValue(value, limits);
352
- if (!isRecord$1(decoded) || !hasOnlyKeys(decoded, clientStateKeys) || !Array.isArray(decoded.rootLists) || !Array.isArray(decoded.rootRequests) || !isRecord$1(decoded.store) || !hasOnlyKeys(decoded.store, storeStateKeys) || !Array.isArray(decoded.store.coverage) || !Array.isArray(decoded.store.lists) || !Array.isArray(decoded.store.records)) throw invalidPayload("data");
353
- if (decoded.rootLists.some((entry) => !isEntry(entry) || !isStringArray(entry[1])) || decoded.rootRequests.some((entry) => !isEntry(entry) || entry[1] !== null && typeof entry[1] !== "string") || decoded.store.coverage.some((entry) => !isEntry(entry) || !isFieldPathArray(entry[1])) || decoded.store.lists.some((entry) => !isEntry(entry) || !isList(entry[1])) || decoded.store.records.some((entry) => !isEntry(entry) || !isRecord$1(entry[1]))) throw invalidPayload("data");
354
- if (hasDuplicateEntries(decoded.rootLists) || hasDuplicateEntries(decoded.rootRequests) || hasDuplicateEntries(decoded.store.coverage) || hasDuplicateEntries(decoded.store.lists) || hasDuplicateEntries(decoded.store.records) || decoded.rootLists.some(([, keys]) => hasDuplicates(keys))) throw invalidPayload("data");
355
- return decoded;
356
- };
357
- //#endregion
358
- //#region ../../node_modules/.pnpm/@trpc+server@11.18.0_typescript@7.0.2/node_modules/@trpc/server/dist/getErrorShape-BPSzUA7W.mjs
359
- var __create = Object.create;
360
- var __defProp = Object.defineProperty;
361
- var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
362
- var __getOwnPropNames = Object.getOwnPropertyNames;
363
- var __getProtoOf = Object.getPrototypeOf;
364
- var __hasOwnProp = Object.prototype.hasOwnProperty;
365
- var __commonJS = (cb, mod) => function() {
366
- return mod || (0, cb[__getOwnPropNames(cb)[0]])((mod = { exports: {} }).exports, mod), mod.exports;
367
- };
368
- var __copyProps = (to, from, except, desc) => {
369
- if (from && typeof from === "object" || typeof from === "function") for (var keys = __getOwnPropNames(from), i = 0, n = keys.length, key; i < n; i++) {
370
- key = keys[i];
371
- if (!__hasOwnProp.call(to, key) && key !== except) __defProp(to, key, {
372
- get: ((k) => from[k]).bind(null, key),
373
- enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable
374
- });
375
- }
376
- return to;
377
- };
378
- var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", {
379
- value: mod,
380
- enumerable: true
381
- }) : target, mod));
382
- const JSONRPC2_TO_HTTP_CODE = {
383
- PARSE_ERROR: 400,
384
- BAD_REQUEST: 400,
385
- UNAUTHORIZED: 401,
386
- PAYMENT_REQUIRED: 402,
387
- FORBIDDEN: 403,
388
- NOT_FOUND: 404,
389
- METHOD_NOT_SUPPORTED: 405,
390
- TIMEOUT: 408,
391
- CONFLICT: 409,
392
- PRECONDITION_FAILED: 412,
393
- PAYLOAD_TOO_LARGE: 413,
394
- UNSUPPORTED_MEDIA_TYPE: 415,
395
- UNPROCESSABLE_CONTENT: 422,
396
- PRECONDITION_REQUIRED: 428,
397
- TOO_MANY_REQUESTS: 429,
398
- CLIENT_CLOSED_REQUEST: 499,
399
- INTERNAL_SERVER_ERROR: 500,
400
- NOT_IMPLEMENTED: 501,
401
- BAD_GATEWAY: 502,
402
- SERVICE_UNAVAILABLE: 503,
403
- GATEWAY_TIMEOUT: 504
404
- };
405
- function getStatusCodeFromKey(code) {
406
- var _JSONRPC2_TO_HTTP_COD;
407
- return (_JSONRPC2_TO_HTTP_COD = JSONRPC2_TO_HTTP_CODE[code]) !== null && _JSONRPC2_TO_HTTP_COD !== void 0 ? _JSONRPC2_TO_HTTP_COD : 500;
408
- }
409
- function getHTTPStatusCodeFromError(error) {
410
- return getStatusCodeFromKey(error.code);
411
- }
412
- var require_typeof = __commonJS({ "../../node_modules/.pnpm/@oxc-project+runtime@0.72.2/node_modules/@oxc-project/runtime/src/helpers/typeof.js"(exports, module) {
413
- function _typeof$2(o) {
414
- "@babel/helpers - typeof";
415
- return module.exports = _typeof$2 = "function" == typeof Symbol && "symbol" == typeof Symbol.iterator ? function(o$1) {
416
- return typeof o$1;
417
- } : function(o$1) {
418
- return o$1 && "function" == typeof Symbol && o$1.constructor === Symbol && o$1 !== Symbol.prototype ? "symbol" : typeof o$1;
419
- }, module.exports.__esModule = true, module.exports["default"] = module.exports, _typeof$2(o);
420
- }
421
- module.exports = _typeof$2, module.exports.__esModule = true, module.exports["default"] = module.exports;
422
- } });
423
- var require_toPrimitive = __commonJS({ "../../node_modules/.pnpm/@oxc-project+runtime@0.72.2/node_modules/@oxc-project/runtime/src/helpers/toPrimitive.js"(exports, module) {
424
- var _typeof$1 = require_typeof()["default"];
425
- function toPrimitive$1(t, r) {
426
- if ("object" != _typeof$1(t) || !t) return t;
427
- var e = t[Symbol.toPrimitive];
428
- if (void 0 !== e) {
429
- var i = e.call(t, r || "default");
430
- if ("object" != _typeof$1(i)) return i;
431
- throw new TypeError("@@toPrimitive must return a primitive value.");
432
- }
433
- return ("string" === r ? String : Number)(t);
434
- }
435
- module.exports = toPrimitive$1, module.exports.__esModule = true, module.exports["default"] = module.exports;
436
- } });
437
- var require_toPropertyKey = __commonJS({ "../../node_modules/.pnpm/@oxc-project+runtime@0.72.2/node_modules/@oxc-project/runtime/src/helpers/toPropertyKey.js"(exports, module) {
438
- var _typeof = require_typeof()["default"];
439
- var toPrimitive = require_toPrimitive();
440
- function toPropertyKey$1(t) {
441
- var i = toPrimitive(t, "string");
442
- return "symbol" == _typeof(i) ? i : i + "";
443
- }
444
- module.exports = toPropertyKey$1, module.exports.__esModule = true, module.exports["default"] = module.exports;
445
- } });
446
- var require_defineProperty = __commonJS({ "../../node_modules/.pnpm/@oxc-project+runtime@0.72.2/node_modules/@oxc-project/runtime/src/helpers/defineProperty.js"(exports, module) {
447
- var toPropertyKey = require_toPropertyKey();
448
- function _defineProperty(e, r, t) {
449
- return (r = toPropertyKey(r)) in e ? Object.defineProperty(e, r, {
450
- value: t,
451
- enumerable: !0,
452
- configurable: !0,
453
- writable: !0
454
- }) : e[r] = t, e;
455
- }
456
- module.exports = _defineProperty, module.exports.__esModule = true, module.exports["default"] = module.exports;
457
- } });
458
- __toESM(__commonJS({ "../../node_modules/.pnpm/@oxc-project+runtime@0.72.2/node_modules/@oxc-project/runtime/src/helpers/objectSpread2.js"(exports, module) {
459
- var defineProperty = require_defineProperty();
460
- function ownKeys(e, r) {
461
- var t = Object.keys(e);
462
- if (Object.getOwnPropertySymbols) {
463
- var o = Object.getOwnPropertySymbols(e);
464
- r && (o = o.filter(function(r$1) {
465
- return Object.getOwnPropertyDescriptor(e, r$1).enumerable;
466
- })), t.push.apply(t, o);
467
- }
468
- return t;
469
- }
470
- function _objectSpread2(e) {
471
- for (var r = 1; r < arguments.length; r++) {
472
- var t = null != arguments[r] ? arguments[r] : {};
473
- r % 2 ? ownKeys(Object(t), !0).forEach(function(r$1) {
474
- defineProperty(e, r$1, t[r$1]);
475
- }) : Object.getOwnPropertyDescriptors ? Object.defineProperties(e, Object.getOwnPropertyDescriptors(t)) : ownKeys(Object(t)).forEach(function(r$1) {
476
- Object.defineProperty(e, r$1, Object.getOwnPropertyDescriptor(t, r$1));
477
- });
478
- }
479
- return e;
480
- }
481
- module.exports = _objectSpread2, module.exports.__esModule = true, module.exports["default"] = module.exports;
482
- } })(), 1);
483
- //#endregion
484
- //#region src/view.ts
485
- /**
486
- * Collects all view payloads that apply to the given ref.
487
- */
488
- const getViewPayloads = (view, ref) => {
489
- const result = [];
490
- for (const [key, value] of Object.entries(view)) if (isViewTag(key) && (!ref || ref[ViewsTag]?.has(key))) result.push(value);
491
- return result;
492
- };
493
- /**
494
- * Returns the set of view tags defined on a view composition.
495
- */
496
- const getViewNames = (view) => {
497
- const result = /* @__PURE__ */ new Set();
498
- for (const key of Object.keys(view)) if (isViewTag(key)) result.add(key);
499
- return result;
500
- };
501
- /**
502
- * Extracts view tags from a nested selection object.
503
- */
504
- const getSelectionViewNames = (selection) => {
505
- return getViewNames(selection);
506
- };
507
- let id = 0;
508
- const importMetaEnvironment = import.meta.env;
509
- const isDevelopment = typeof importMetaEnvironment?.DEV === "boolean" ? importMetaEnvironment.DEV : typeof process !== "undefined" ? process.env.NODE_ENV !== "production" : false;
510
- let viewModulePath = null;
511
- const getStableId = () => {
512
- if (isDevelopment) try {
513
- if (viewModulePath == null) viewModulePath = new URL(import.meta.url).pathname;
514
- const stack = (/* @__PURE__ */ new Error()).stack?.split("\n");
515
- if (stack) for (let i = 1; i < stack.length; i++) {
516
- const match = stack[i].trim().match(/\(?([^()]+):(\d+):(\d+)\)?$/);
517
- if (!match) continue;
518
- const [, source, line, column] = match;
519
- if (!source.includes(viewModulePath)) {
520
- const file = source.startsWith("at ") ? source.slice(3) : source;
521
- return `${/^[A-Za-z][\d+.A-Za-z-]*:/.test(file) ? new URL(file).pathname : file}:${line}:${column}`;
522
- }
523
- }
524
- } catch {}
525
- return String(id++);
526
- };
527
- /**
528
- * Creates a reusable view for an object using the declared selection.
529
- *
530
- * @example
531
- * const PostView = view<Post>()({
532
- * id: true,
533
- * title: true,
534
- * });
535
- */
536
- function view() {
537
- const viewId = getStableId();
538
- return (select) => {
539
- const payload = Object.freeze({
540
- select,
541
- [ViewKind]: true
542
- });
543
- const viewComposition = Object.defineProperty({}, getViewTag(viewId), {
544
- configurable: false,
545
- enumerable: true,
546
- value: payload,
547
- writable: false
548
- });
549
- return Object.freeze(viewComposition);
550
- };
551
- }
552
- //#endregion
553
- //#region src/ref.ts
554
- /**
555
- * Builds the canonical cache ID for an entity.
556
- */
557
- const toEntityId = (type, rawId) => `${type}:${String(rawId)}`;
558
- /**
559
- * Splits a cache entity ID back into its type and raw identifier.
560
- */
561
- function parseEntityId(id) {
562
- const idx = id.indexOf(":");
563
- return idx < 0 ? {
564
- id,
565
- type: ""
566
- } : {
567
- id: id.slice(idx + 1),
568
- type: id.slice(0, idx)
569
- };
570
- }
571
- /**
572
- * Attaches view tags to a ref without leaking the symbol.
573
- */
574
- function assignViewTag(target, value) {
575
- Object.defineProperty(target, ViewsTag, {
576
- configurable: false,
577
- enumerable: false,
578
- value,
579
- writable: false
580
- });
581
- }
582
- const getRootViewNames = (view) => {
583
- const names = new Set(getViewNames(view));
584
- const payloads = getViewPayloads(view, null);
585
- for (const payload of payloads) for (const name of getSelectionViewNames(payload.select)) names.add(name);
586
- return names;
587
- };
588
- function createRefWithViewNames(__typename, id, names) {
589
- const ref = {
590
- __typename,
591
- id
592
- };
593
- assignViewTag(ref, names);
594
- return Object.freeze(ref);
595
- }
596
- //#endregion
597
- //#region src/selection.ts
598
- const isConnectionSelection = (value) => isRecord(value.items) && "node" in value.items;
599
- /**
600
- * Flattens a view into a `SelectionPlan`, expanding composed views and
601
- * partitioning nested args so the client can fetch exactly what is declared.
602
- */
603
- const getSelectionPlan = (viewComposition, ref) => {
604
- const args = /* @__PURE__ */ new Map();
605
- const live = /* @__PURE__ */ new Map();
606
- const paths = /* @__PURE__ */ new Set();
607
- const assignArgs = (path, value, ignoreKeys) => {
608
- const hash = hashArgs(value, { ignoreKeys });
609
- args.set(path, {
610
- hash,
611
- ignoreKeys,
612
- value
613
- });
614
- };
615
- const walk = (selection, prefix, context = "default") => {
616
- if (prefix === null && context !== "connection" && isConnectionSelection(selection)) {
617
- if (selection.live && isRecord(selection.live)) live.set("", {
618
- append: selection.live.append === "visible" ? "visible" : "edge",
619
- prepend: selection.live.prepend === "visible" ? "visible" : "edge"
620
- });
621
- if (selection.args && isRecord(selection.args)) {
622
- const clonedArgs = cloneArgs(selection.args, "args");
623
- const ignoreKeys = isRecord(selection.items) && isRecord(selection.items.node) ? paginationArgKeys : void 0;
624
- assignArgs("", clonedArgs, ignoreKeys);
625
- }
626
- const { args: _args, live: _live, ...withoutArgs } = selection;
627
- walk(withoutArgs, prefix, "connection");
628
- return;
629
- }
630
- for (const [key, value] of Object.entries(selection)) {
631
- const valueType = typeof value;
632
- const path = prefix ? `${prefix}.${key}` : key;
633
- if (context === "connection") {
634
- if (key === "args" || key === "live" || key === "pagination") continue;
635
- if (key === "items" && isRecord(value)) {
636
- if (isRecord(value.node)) walk(value.node, prefix);
637
- continue;
638
- }
639
- }
640
- if (valueType === "boolean") {
641
- if (value) paths.add(path);
642
- continue;
643
- }
644
- if (isDeferredSelection(value)) continue;
645
- if (isViewTag(key)) {
646
- if (!ref || ref[ViewsTag] && ref[ViewsTag].has(key)) walk(value.select, prefix);
647
- continue;
648
- }
649
- if (isRecord(value)) {
650
- const selectionObject = value;
651
- if (isConnectionSelection(selectionObject)) {
652
- if (selectionObject.live && isRecord(selectionObject.live)) live.set(path, {
653
- append: selectionObject.live.append === "visible" ? "visible" : "edge",
654
- prepend: selectionObject.live.prepend === "visible" ? "visible" : "edge"
655
- });
656
- if (selectionObject.args && isRecord(selectionObject.args)) {
657
- const clonedArgs = cloneArgs(selectionObject.args, path);
658
- const ignoreKeys = isRecord(selectionObject.items) && isRecord(selectionObject.items.node) ? paginationArgKeys : void 0;
659
- assignArgs(path, clonedArgs, ignoreKeys);
660
- }
661
- const { args: _ignored, live: _live, ...withoutArgs } = selectionObject;
662
- walk(withoutArgs, path, "connection");
663
- continue;
664
- }
665
- const hasArgs = selectionObject.args && isRecord(selectionObject.args);
666
- let selectionWithoutArgs = selectionObject;
667
- if (hasArgs) {
668
- const clonedArgs = cloneArgs(selectionObject.args, path);
669
- const ignoreKeys = isRecord(selectionObject.items) && isRecord(selectionObject.items?.node) ? paginationArgKeys : void 0;
670
- assignArgs(path, clonedArgs, ignoreKeys);
671
- const { args: _args, ...rest } = selectionObject;
672
- selectionWithoutArgs = rest;
673
- }
674
- if (Object.keys(selectionWithoutArgs).length > 0) walk(selectionWithoutArgs, path);
675
- else if (hasArgs) paths.add(path);
676
- }
677
- }
678
- };
679
- const payloads = getViewPayloads(viewComposition, ref);
680
- if (payloads.length === 0 && isRecord(viewComposition)) walk(viewComposition, null);
681
- else for (const payload of payloads) walk(payload.select, null);
682
- return {
683
- args,
684
- live,
685
- paths
686
- };
687
- };
688
- const getDeferredSelectionPlan = (field, selection) => {
689
- return getSelectionPlan({ [field]: selection }, null);
690
- };
691
- //#endregion
692
- //#region src/mutation.ts
693
- /**
694
- * Defines a mutation for a given entity type, preserving the input and output
695
- * types for transports.
696
- */
697
- function mutation(entity) {
698
- return Object.freeze({
699
- entity,
700
- [MutationKind]: true
701
- });
702
- }
703
- const collectImplicitSelectedPaths = (value) => {
704
- const paths = /* @__PURE__ */ new Set();
705
- const walk = (current, prefix) => {
706
- if (!current || typeof current !== "object") {
707
- if (prefix) paths.add(prefix);
708
- return;
709
- }
710
- if (Array.isArray(current)) {
711
- if (prefix) paths.add(prefix);
712
- for (const child of current) walk(child, prefix);
713
- return;
714
- }
715
- for (const [key, child] of Object.entries(current)) {
716
- const next = prefix ? `${prefix}.${key}` : key;
717
- paths.add(next);
718
- walk(child, next);
719
- }
720
- };
721
- walk(value, null);
722
- return paths;
723
- };
724
- const maybeGetId = (getId, input) => {
725
- try {
726
- return getId(input);
727
- } catch {
728
- return null;
729
- }
730
- };
731
- const emptySet$1 = /* @__PURE__ */ new Set();
732
- /**
733
- * Binds a mutation definition to a `FateClient`, wiring up optimistic updates,
734
- * cache writes, and error handling.
735
- */
736
- function wrapMutation(client, identifier) {
737
- const config = client.getTypeConfig(identifier.entity);
738
- return async ({ args, delete: deleteRecord, input, insert = "after", optimistic, view }) => {
739
- const id = maybeGetId(config.getId, input);
740
- const plan = view ? getSelectionPlan(view, null) : void 0;
741
- const viewSelection = plan?.paths;
742
- const optimisticRecord = optimistic ? id != null ? {
743
- id,
744
- ...optimistic
745
- } : optimistic : void 0;
746
- const optimisticRecordId = optimisticRecord !== void 0 ? maybeGetId(config.getId, optimisticRecord) : null;
747
- const optimisticEntityId = id != null ? toEntityId(identifier.entity, id) : optimisticRecordId != null ? toEntityId(identifier.entity, optimisticRecordId) : null;
748
- const optimisticSelection = optimisticRecord ? collectImplicitSelectedPaths(optimisticRecord) : void 0;
749
- const selection = viewSelection || optimisticSelection ? /* @__PURE__ */ new Set([...viewSelection ? [...viewSelection] : [], ...optimisticSelection ? [...optimisticSelection] : []]) : /* @__PURE__ */ new Set();
750
- if (deleteRecord && id == null) throw new Error(`fate: Mutation '${identifier.key}' requires an 'id' to delete.`);
751
- const applyOptimistic = () => {
752
- if (optimisticRecord && optimisticEntityId) client.write(identifier.entity, optimisticRecord, optimisticSelection ?? emptySet$1, plan, insert);
753
- if (deleteRecord && id != null) client.deleteRecord(identifier.entity, id);
754
- };
755
- const settle = client.store.optimisticUpdate(applyOptimistic);
756
- const performMutation = async () => {
757
- try {
758
- const result = await client.executeMutation(identifier.key, input, selection, {
759
- args,
760
- plan
761
- });
762
- const shouldWriteResult = result && typeof result === "object" && (!deleteRecord || Boolean(view));
763
- settle(() => {
764
- if (shouldWriteResult) {
765
- const select = collectImplicitSelectedPaths(result);
766
- client.write(identifier.entity, result, select, plan, insert);
767
- const resultId = maybeGetId(config.getId, result);
768
- if (optimisticEntityId && resultId != null) client.resolveOptimisticEntity(optimisticEntityId, toEntityId(identifier.entity, resultId));
769
- if (optimisticRecordId != null && resultId != null && optimisticRecordId !== resultId) client.deleteRecord(identifier.entity, optimisticRecordId);
770
- }
771
- if (deleteRecord && id != null) client.deleteRecord(identifier.entity, id);
772
- });
773
- return {
774
- error: void 0,
775
- result
776
- };
777
- } catch (error) {
778
- settle();
779
- if (error instanceof Error) {
780
- const statusCode = getErrorStatusCode(error);
781
- if ((statusCode ? categorizeHTTPErrorStatus(statusCode) : "boundary") === "boundary") throw error;
782
- return {
783
- error,
784
- result: void 0
785
- };
786
- } else throw new Error(`fate: Mutation '${identifier.key}' failed.`, { cause: error });
787
- }
788
- };
789
- const mutationPromise = client.trackPendingRequest(performMutation);
790
- if (optimisticEntityId) client.registerPendingOptimisticMutation(optimisticEntityId, mutationPromise);
791
- return mutationPromise;
792
- };
793
- }
794
- const getErrorStatusCode = (error) => {
795
- if (error instanceof FateRequestError) return error.status;
796
- const { data } = error;
797
- if (!data || typeof data !== "object") return;
798
- return getHTTPStatusCodeFromError(data);
799
- };
800
- const categorizeHTTPErrorStatus = (statusCode) => {
801
- switch (statusCode) {
802
- case 400:
803
- case 402:
804
- case 404:
805
- case 408:
806
- case 409:
807
- case 412:
808
- case 413:
809
- case 415:
810
- case 422:
811
- case 429:
812
- case 499: return "callSite";
813
- default: return "boundary";
814
- }
815
- };
816
- //#endregion
817
80
  //#region src/operation-lifetime.ts
818
81
  var OperationLifetime = class {
819
82
  constructor(releaseBufferSize) {
@@ -842,6 +105,15 @@ var OperationLifetime = class {
842
105
  getDescriptors() {
843
106
  return [...this.operations.values()].map((operation) => operation.descriptor);
844
107
  }
108
+ restore(descriptors) {
109
+ for (const descriptor of descriptors.slice(-Math.max(1, this.releaseBufferSize))) if (!this.operations.has(descriptor.key)) {
110
+ this.operations.set(descriptor.key, {
111
+ descriptor,
112
+ refCount: 0
113
+ });
114
+ this.releaseBuffer.push(descriptor.key);
115
+ }
116
+ }
845
117
  release(key) {
846
118
  const operation = this.operations.get(key);
847
119
  if (!operation) return false;
@@ -870,793 +142,6 @@ var OperationLifetime = class {
870
142
  }
871
143
  };
872
144
  //#endregion
873
- //#region src/mask.ts
874
- function emptyMask() {
875
- return {
876
- all: false,
877
- children: /* @__PURE__ */ new Map()
878
- };
879
- }
880
- function cloneMask(m) {
881
- const clone = {
882
- all: m.all,
883
- children: /* @__PURE__ */ new Map()
884
- };
885
- for (const [key, value] of m.children) clone.children.set(key, cloneMask(value));
886
- return clone;
887
- }
888
- function addPath(mask, path) {
889
- if (mask.all) return;
890
- const parts = path.split(".");
891
- let current = mask;
892
- for (let i = 0; i < parts.length; i++) {
893
- const seg = parts[i];
894
- let child = current.children.get(seg);
895
- if (!child) {
896
- child = emptyMask();
897
- current.children.set(seg, child);
898
- }
899
- current = child;
900
- }
901
- current.all = true;
902
- current.children.clear();
903
- }
904
- function union(into, b) {
905
- if (into.all || b.all) {
906
- into.all = true;
907
- into.children.clear();
908
- return;
909
- }
910
- for (const [key, child] of b.children) {
911
- const exist = into.children.get(key);
912
- if (!exist) into.children.set(key, cloneMask(child));
913
- else union(exist, child);
914
- }
915
- }
916
- function fromPaths(paths) {
917
- const mask = emptyMask();
918
- for (const path of paths) addPath(mask, path);
919
- return mask;
920
- }
921
- function toPaths(mask) {
922
- const paths = [];
923
- const visit = (current, prefix) => {
924
- if (current.all) {
925
- if (prefix) paths.push(prefix);
926
- return;
927
- }
928
- for (const [key, child] of current.children) visit(child, prefix ? `${prefix}.${key}` : key);
929
- };
930
- visit(mask, null);
931
- return paths;
932
- }
933
- function isCovered(mask, path) {
934
- if (mask.all) return true;
935
- const parts = path.split(".");
936
- let current = mask;
937
- for (let i = 0; i < parts.length; i++) {
938
- if (!current) return false;
939
- if (current.all) return true;
940
- current = current.children.get(parts[i]);
941
- }
942
- return !!current && (current.all || current.children.size === 0);
943
- }
944
- function diffPaths(paths, mask) {
945
- const missing = /* @__PURE__ */ new Set();
946
- for (const path of paths) if (!isCovered(mask, path)) missing.add(path);
947
- return missing;
948
- }
949
- function intersects(a, b) {
950
- if (a.all || b.all) return true;
951
- for (const [key, child] of a.children) {
952
- const otherChild = b.children.get(key);
953
- if (!otherChild) continue;
954
- if (child.all || otherChild.all) return true;
955
- if (intersects(child, otherChild)) return true;
956
- }
957
- return false;
958
- }
959
- //#endregion
960
- //#region src/store.ts
961
- const listKeySeparator = " __fate__ ";
962
- const decodeListKeyPart = (part) => {
963
- try {
964
- return decodeURIComponent(part);
965
- } catch {
966
- return part;
967
- }
968
- };
969
- const encodeListKeyPart = (part) => encodeURIComponent(part);
970
- const getListKey = (ownerId, field, hash = "default") => `${encodeListKeyPart(ownerId)}${listKeySeparator}${encodeListKeyPart(field)}${listKeySeparator}${encodeListKeyPart(hash)}`;
971
- const getOwnerFieldKey = (ownerId, field) => JSON.stringify([ownerId, field]);
972
- const parseListKey = (key) => {
973
- const parts = key.split(listKeySeparator);
974
- if (parts.length !== 3) return null;
975
- const [ownerId, field] = parts;
976
- return {
977
- field: decodeListKeyPart(field),
978
- ownerId: decodeListKeyPart(ownerId)
979
- };
980
- };
981
- const isPlainRecord = (value) => {
982
- if (value == null || typeof value !== "object" || Array.isArray(value) || isNodeRef(value)) return false;
983
- const prototype = Object.getPrototypeOf(value);
984
- return prototype === Object.prototype || prototype === null;
985
- };
986
- const isDate = (value) => value != null && typeof value === "object" && Object.prototype.toString.call(value) === "[object Date]" && typeof value.getTime === "function";
987
- const mergePreservingExisting = (incoming, existing) => {
988
- if (!isPlainRecord(incoming) || !isPlainRecord(existing)) return existing;
989
- const result = Object.create(null);
990
- for (const [key, value] of Object.entries(incoming)) Object.defineProperty(result, key, {
991
- configurable: true,
992
- enumerable: true,
993
- value,
994
- writable: true
995
- });
996
- for (const [key, value] of Object.entries(existing)) Object.defineProperty(result, key, {
997
- configurable: true,
998
- enumerable: true,
999
- value: Object.hasOwn(result, key) ? mergePreservingExisting(result[key], value) : value,
1000
- writable: true
1001
- });
1002
- return result;
1003
- };
1004
- const areHydrationValuesEqual = (left, right) => {
1005
- if (Object.is(left, right)) return true;
1006
- if (isNodeRef(left) || isNodeRef(right)) return isNodeRef(left) && isNodeRef(right) && getNodeRefId(left) === getNodeRefId(right);
1007
- if (isDate(left) || isDate(right)) return isDate(left) && isDate(right) && left.getTime() === right.getTime();
1008
- if (Array.isArray(left) || Array.isArray(right)) return Array.isArray(left) && Array.isArray(right) && left.length === right.length && left.every((value, index) => areHydrationValuesEqual(value, right[index]));
1009
- if (!isPlainRecord(left) || !isPlainRecord(right)) return false;
1010
- const leftKeys = Object.keys(left);
1011
- const rightKeys = Object.keys(right);
1012
- return leftKeys.length === rightKeys.length && leftKeys.every((key) => Object.hasOwn(right, key) && areHydrationValuesEqual(left[key], right[key]));
1013
- };
1014
- const areMasksEqual = (left, right) => {
1015
- if (left === right || left?.all && right?.all) return true;
1016
- if (!left || !right || left.all !== right.all || left.children.size !== right.children.size) return false;
1017
- for (const [key, child] of left.children) if (!areMasksEqual(child, right.children.get(key))) return false;
1018
- return true;
1019
- };
1020
- const emptyFunction = () => {};
1021
- const hasReferences = (value) => isNodeRef(value) || Array.isArray(value) && value.some(isNodeRef);
1022
- const areNormalizedValuesEqual = (left, right) => Object.is(left, right) || isNodeRef(left) && isNodeRef(right) && getNodeRefId(left) === getNodeRefId(right) || Array.isArray(left) && Array.isArray(right) && left.length === right.length && left.every((value, index) => areNormalizedValuesEqual(value, right[index]));
1023
- var Store = class {
1024
- constructor(onRebase) {
1025
- this.onRebase = onRebase;
1026
- this.coverage = /* @__PURE__ */ new Map();
1027
- this.listKeysByOwnerField = /* @__PURE__ */ new Map();
1028
- this.listKeysByReferencedEntity = /* @__PURE__ */ new Map();
1029
- this.lists = /* @__PURE__ */ new Map();
1030
- this.recordReferenceFields = /* @__PURE__ */ new Map();
1031
- this.recordReferencesByTarget = /* @__PURE__ */ new Map();
1032
- this.records = /* @__PURE__ */ new Map();
1033
- this.subscriptions = /* @__PURE__ */ new Map();
1034
- this.listSubscriptions = /* @__PURE__ */ new Map();
1035
- this.optimisticLayers = /* @__PURE__ */ new Set();
1036
- this.optimisticBase = {
1037
- lists: /* @__PURE__ */ new Map(),
1038
- records: /* @__PURE__ */ new Map()
1039
- };
1040
- }
1041
- get hasOptimisticUpdates() {
1042
- return this.optimisticLayers.size > 0;
1043
- }
1044
- get isRebasing() {
1045
- return this.rebase !== void 0;
1046
- }
1047
- /** Apply a synchronous cache update beneath the pending optimistic layers. */
1048
- update(apply) {
1049
- if (this.rebase || this.optimisticLayers.size === 0) return apply();
1050
- const rebase = {
1051
- lists: /* @__PURE__ */ new Map(),
1052
- records: /* @__PURE__ */ new Map()
1053
- };
1054
- this.rebase = rebase;
1055
- try {
1056
- for (const [id, snapshot] of this.optimisticBase.records) this.restore(id, snapshot);
1057
- for (const [key, list] of this.optimisticBase.lists) this.restoreList(key, list);
1058
- this.optimisticBase.records.clear();
1059
- this.optimisticBase.lists.clear();
1060
- return apply();
1061
- } finally {
1062
- try {
1063
- for (const layer of this.optimisticLayers) {
1064
- layer.writes.clear();
1065
- this.recordingLayer = layer;
1066
- layer.apply();
1067
- }
1068
- } finally {
1069
- this.recordingLayer = void 0;
1070
- const records = this.getRebaseChanges(rebase);
1071
- const lists = /* @__PURE__ */ new Set();
1072
- for (const [key, before] of rebase.lists) if (!areHydrationValuesEqual(before, this.lists.get(key))) {
1073
- lists.add(key);
1074
- const owner = parseListKey(key);
1075
- if (owner) {
1076
- if (!records.has(owner.ownerId)) records.set(owner.ownerId, /* @__PURE__ */ new Set([owner.field]));
1077
- else records.get(owner.ownerId)?.add(owner.field);
1078
- }
1079
- } else if (before) this.lists.set(key, before);
1080
- this.rebase = void 0;
1081
- this.onRebase?.(new Set(records.keys()));
1082
- for (const [id, paths] of records) this.notify(id, paths);
1083
- for (const key of lists) this.notifyListSubscribers(key);
1084
- }
1085
- }
1086
- }
1087
- getRebaseChanges(rebase) {
1088
- const changes = /* @__PURE__ */ new Map();
1089
- for (const [id, before] of rebase.records) {
1090
- const after = this.records.get(id);
1091
- const mask = this.coverage.get(id);
1092
- if (Boolean(before.record) !== Boolean(after) || before.mask?.all !== mask?.all) {
1093
- changes.set(id, void 0);
1094
- continue;
1095
- }
1096
- const paths = /* @__PURE__ */ new Set();
1097
- for (const key of /* @__PURE__ */ new Set([...Object.keys(before.record ?? {}), ...Object.keys(after ?? {})])) if (Object.hasOwn(before.record ?? {}, key) !== Object.hasOwn(after ?? {}, key) || !areNormalizedValuesEqual(before.record?.[key], after?.[key])) paths.add(key);
1098
- if (!areMasksEqual(before.mask, mask)) {
1099
- for (const path of before.mask ? toPaths(before.mask) : []) if (!mask || !isCovered(mask, path)) paths.add(path);
1100
- for (const path of mask ? toPaths(mask) : []) if (!before.mask || !isCovered(before.mask, path)) paths.add(path);
1101
- }
1102
- if (paths.size > 0) changes.set(id, paths);
1103
- else if (before.record) this.records.set(id, before.record);
1104
- }
1105
- return changes;
1106
- }
1107
- /** Returns an idempotent settlement function; omit its update to roll back. */
1108
- optimisticUpdate(apply) {
1109
- const layer = {
1110
- apply,
1111
- writes: /* @__PURE__ */ new Map()
1112
- };
1113
- const settle = (commit) => {
1114
- if (!this.optimisticLayers.has(layer) || layer.settled) return;
1115
- this.update(() => {
1116
- const overlays = commit ? this.getPendingOverlap(layer, layer.writes) : /* @__PURE__ */ new Map();
1117
- if (overlays.size > 0) {
1118
- layer.settled = true;
1119
- layer.apply = () => {
1120
- for (const [id, { partial, paths }] of this.getPendingOverlap(layer, overlays)) this.merge(id, partial, paths);
1121
- };
1122
- } else this.optimisticLayers.delete(layer);
1123
- for (const pending of this.optimisticLayers) {
1124
- if (!pending.settled) break;
1125
- this.optimisticLayers.delete(pending);
1126
- }
1127
- commit?.();
1128
- });
1129
- };
1130
- this.optimisticLayers.add(layer);
1131
- try {
1132
- this.update(() => {});
1133
- } catch (error) {
1134
- settle();
1135
- throw error;
1136
- }
1137
- return settle;
1138
- }
1139
- getPendingOverlap(layer, writes) {
1140
- const earlier = [];
1141
- for (const pending of this.optimisticLayers) {
1142
- if (pending === layer) break;
1143
- if (!pending.settled) earlier.push(pending);
1144
- }
1145
- const overlaps = /* @__PURE__ */ new Map();
1146
- for (const [id, write] of writes) {
1147
- const partial = Object.fromEntries(Object.entries(write.partial).filter(([key, value]) => !hasReferences(value) && earlier.some(({ writes }) => {
1148
- const previous = writes.get(id)?.partial;
1149
- return previous && Object.hasOwn(previous, key) && !hasReferences(previous[key]);
1150
- })));
1151
- if (Object.keys(partial).length) overlaps.set(id, {
1152
- partial,
1153
- paths: new Set([...write.paths].filter((path) => Object.hasOwn(partial, path.split(".")[0])))
1154
- });
1155
- }
1156
- return overlaps;
1157
- }
1158
- captureRecord(id) {
1159
- if (!this.rebase) return;
1160
- for (const target of [this.rebase, this.recordingLayer && this.optimisticBase]) if (target && !target.records.has(id)) {
1161
- const mask = this.coverage.get(id);
1162
- target.records.set(id, {
1163
- mask: mask ? cloneMask(mask) : void 0,
1164
- record: this.records.get(id)
1165
- });
1166
- }
1167
- }
1168
- captureList(key) {
1169
- if (!this.rebase) return;
1170
- for (const target of [this.rebase, this.recordingLayer && this.optimisticBase]) if (target && !target.lists.has(key)) target.lists.set(key, this.lists.get(key));
1171
- }
1172
- dehydrate() {
1173
- return {
1174
- coverage: [...this.coverage].map(([id, mask]) => [id, toPaths(mask)]),
1175
- lists: [...this.lists],
1176
- records: [...this.records]
1177
- };
1178
- }
1179
- hydrate(state, mode, options = {}) {
1180
- const previousCoverage = this.coverage;
1181
- const previousLists = this.lists;
1182
- const previousRecords = this.records;
1183
- const coverage = mode === "replace" ? /* @__PURE__ */ new Map() : new Map([...previousCoverage].map(([id, mask]) => [id, cloneMask(mask)]));
1184
- const lists = mode === "replace" ? /* @__PURE__ */ new Map() : new Map(previousLists);
1185
- const records = mode === "replace" ? /* @__PURE__ */ new Map() : new Map(previousRecords);
1186
- for (const [id, incoming] of state.records) {
1187
- const previous = records.get(id);
1188
- const next = previous && mode === "preserve-existing" ? mergePreservingExisting(incoming, previous) : incoming;
1189
- records.set(id, previous && areHydrationValuesEqual(previous, next) ? previous : next);
1190
- }
1191
- for (const [id, paths] of state.coverage) {
1192
- const incoming = fromPaths(paths);
1193
- const previous = coverage.get(id);
1194
- if (previous && mode === "preserve-existing") union(previous, incoming);
1195
- else coverage.set(id, incoming);
1196
- }
1197
- for (const [key, list] of state.lists) {
1198
- const previous = lists.get(key);
1199
- if (mode === "replace" || !previous) lists.set(key, previous && areHydrationValuesEqual(previous, list) ? previous : list);
1200
- }
1201
- this.coverage = coverage;
1202
- this.lists = lists;
1203
- this.records = records;
1204
- this.rebuildIndexes();
1205
- const changedRecordIds = /* @__PURE__ */ new Set();
1206
- for (const id of /* @__PURE__ */ new Set([...previousRecords.keys(), ...records.keys()])) if (!areHydrationValuesEqual(previousRecords.get(id), records.get(id)) || !areMasksEqual(previousCoverage.get(id), coverage.get(id))) changedRecordIds.add(id);
1207
- const changedListKeys = /* @__PURE__ */ new Set();
1208
- for (const key of /* @__PURE__ */ new Set([...previousLists.keys(), ...lists.keys()])) if (!areHydrationValuesEqual(previousLists.get(key), lists.get(key))) changedListKeys.add(key);
1209
- const notify = () => {
1210
- for (const id of changedRecordIds) this.notify(id);
1211
- for (const key of changedListKeys) this.notifyListSubscribers(key);
1212
- };
1213
- if (!options.deferNotifications) notify();
1214
- return notify;
1215
- }
1216
- rebuildIndexes() {
1217
- this.listKeysByOwnerField.clear();
1218
- this.listKeysByReferencedEntity.clear();
1219
- this.recordReferenceFields.clear();
1220
- this.recordReferencesByTarget.clear();
1221
- for (const [id, record] of this.records) this.addRecordReferenceIndexes(id, record);
1222
- for (const [key, list] of this.lists) this.addListIndexes(key, list);
1223
- }
1224
- read(id) {
1225
- return this.records.get(id);
1226
- }
1227
- merge(id, partial, paths) {
1228
- return this.update(() => {
1229
- const changedPaths = this.mergeInternal(id, partial, paths);
1230
- if (changedPaths) this.notify(id, changedPaths);
1231
- });
1232
- }
1233
- mergeInternal(id, partial, paths) {
1234
- this.captureRecord(id);
1235
- const selectedPaths = this.recordingLayer ? new Set(paths) : paths;
1236
- if (this.recordingLayer) {
1237
- const write = this.recordingLayer.writes.get(id) ?? {
1238
- partial: {},
1239
- paths: /* @__PURE__ */ new Set()
1240
- };
1241
- Object.assign(write.partial, partial);
1242
- for (const path of selectedPaths) write.paths.add(path);
1243
- this.recordingLayer.writes.set(id, write);
1244
- }
1245
- const previous = this.records.get(id);
1246
- const changedPaths = /* @__PURE__ */ new Set();
1247
- let mask = this.coverage.get(id);
1248
- if (!mask) {
1249
- mask = emptyMask();
1250
- this.coverage.set(id, mask);
1251
- }
1252
- union(mask, fromPaths(selectedPaths));
1253
- if (previous) {
1254
- let hasChanges = false;
1255
- for (const [key, value] of Object.entries(partial)) if (previous[key] !== value) {
1256
- hasChanges = true;
1257
- changedPaths.add(key);
1258
- }
1259
- if (!hasChanges) return null;
1260
- const nextRecord = {
1261
- ...previous,
1262
- ...partial
1263
- };
1264
- this.records.set(id, nextRecord);
1265
- this.updateRecordReferenceIndexes(id, previous, nextRecord, changedPaths);
1266
- } else {
1267
- const nextRecord = { ...partial };
1268
- this.records.set(id, nextRecord);
1269
- this.updateRecordReferenceIndexes(id, void 0, nextRecord, Object.keys(nextRecord));
1270
- }
1271
- return changedPaths;
1272
- }
1273
- deleteRecord(id) {
1274
- return this.update(() => {
1275
- this.captureRecord(id);
1276
- const record = this.records.get(id);
1277
- if (record) this.removeRecordReferenceIndexes(id, record);
1278
- this.records.delete(id);
1279
- this.coverage.delete(id);
1280
- });
1281
- }
1282
- missingForSelection(id, paths) {
1283
- const requested = new Set(paths);
1284
- if (!this.records.has(id)) return requested;
1285
- const mask = this.coverage.get(id);
1286
- if (!mask) return requested;
1287
- return diffPaths(requested, mask);
1288
- }
1289
- subscribe(id, selectionOrFn, callback) {
1290
- let mask = null;
1291
- let fn = emptyFunction;
1292
- if (typeof selectionOrFn === "function") fn = selectionOrFn;
1293
- else if (callback) {
1294
- mask = selectionOrFn ? fromPaths(selectionOrFn) : null;
1295
- fn = callback;
1296
- }
1297
- let subscribers = this.subscriptions.get(id);
1298
- if (!subscribers) {
1299
- subscribers = /* @__PURE__ */ new Set();
1300
- this.subscriptions.set(id, subscribers);
1301
- }
1302
- const subscription = {
1303
- fn,
1304
- mask
1305
- };
1306
- subscribers.add(subscription);
1307
- return () => {
1308
- const set = this.subscriptions.get(id);
1309
- if (!set) return;
1310
- set.delete(subscription);
1311
- if (set.size === 0) this.subscriptions.delete(id);
1312
- };
1313
- }
1314
- notify(id, paths) {
1315
- if (this.rebase) return;
1316
- const set = this.subscriptions.get(id);
1317
- if (!set) return;
1318
- const changedPaths = paths ? [...paths] : [];
1319
- const changedMask = changedPaths.length > 0 ? fromPaths(changedPaths) : null;
1320
- for (const { fn, mask } of set) {
1321
- if (mask && changedMask && !intersects(changedMask, mask)) continue;
1322
- try {
1323
- fn();
1324
- } catch {}
1325
- }
1326
- }
1327
- notifyListSubscribers(key) {
1328
- if (this.rebase) return;
1329
- const set = this.listSubscriptions.get(key);
1330
- if (!set) return;
1331
- for (const fn of set) try {
1332
- fn();
1333
- } catch {}
1334
- }
1335
- getList(key) {
1336
- return this.lists.get(key)?.ids;
1337
- }
1338
- getListState(key) {
1339
- return this.lists.get(key);
1340
- }
1341
- getListsForField(ownerId, field) {
1342
- const entries = [];
1343
- const keys = this.listKeysByOwnerField.get(getOwnerFieldKey(ownerId, field));
1344
- if (!keys) return entries;
1345
- for (const key of keys) {
1346
- const list = this.lists.get(key);
1347
- if (list) entries.push([key, list]);
1348
- }
1349
- return entries;
1350
- }
1351
- setList(key, state) {
1352
- return this.update(() => {
1353
- this.captureList(key);
1354
- const previous = this.lists.get(key);
1355
- if (previous) this.removeListIndexes(key, previous);
1356
- this.lists.set(key, state);
1357
- this.addListIndexes(key, state);
1358
- this.notifyListSubscribers(key);
1359
- });
1360
- }
1361
- replaceListEntityId(previousId, nextId) {
1362
- return this.update(() => {
1363
- const keys = [...this.listKeysByReferencedEntity.get(previousId) ?? []];
1364
- for (const key of keys) {
1365
- const list = this.lists.get(key);
1366
- if (!list) continue;
1367
- let changed = false;
1368
- let ids = list.ids;
1369
- let cursors = list.cursors;
1370
- if (list.ids.includes(previousId)) {
1371
- const nextIds = [];
1372
- const nextCursors = list.cursors ? [] : void 0;
1373
- const seenIds = /* @__PURE__ */ new Set();
1374
- list.ids.forEach((id, index) => {
1375
- const resolved = id === previousId ? nextId : id;
1376
- if (seenIds.has(resolved)) return;
1377
- seenIds.add(resolved);
1378
- nextIds.push(resolved);
1379
- if (nextCursors) nextCursors.push(list.cursors?.[index]);
1380
- });
1381
- changed = true;
1382
- ids = nextIds;
1383
- cursors = nextCursors;
1384
- }
1385
- const dedupe = (values) => {
1386
- if (!values || !values.includes(previousId)) return;
1387
- const seen = /* @__PURE__ */ new Set();
1388
- const next = [];
1389
- for (const value of values) {
1390
- const resolved = value === previousId ? nextId : value;
1391
- if (seen.has(resolved)) continue;
1392
- seen.add(resolved);
1393
- next.push(resolved);
1394
- }
1395
- changed = true;
1396
- return next;
1397
- };
1398
- const pendingBeforeIds = dedupe(list.pendingBeforeIds) ?? list.pendingBeforeIds;
1399
- const pendingAfterIds = dedupe(list.pendingAfterIds) ?? list.pendingAfterIds;
1400
- const liveBeforeIds = dedupe(list.liveBeforeIds) ?? list.liveBeforeIds;
1401
- const liveAfterIds = dedupe(list.liveAfterIds) ?? list.liveAfterIds;
1402
- if (!changed) continue;
1403
- const canonicalIds = new Set(ids);
1404
- this.setList(key, {
1405
- backwardPageLimit: list.backwardPageLimit,
1406
- cursors,
1407
- forwardPageLimit: list.forwardPageLimit,
1408
- ids,
1409
- liveAfterIds: liveAfterIds?.filter((id) => !canonicalIds.has(id)),
1410
- liveBeforeIds: liveBeforeIds?.filter((id) => !canonicalIds.has(id)),
1411
- pagination: list.pagination,
1412
- pendingAfterIds: pendingAfterIds?.filter((id) => !canonicalIds.has(id)),
1413
- pendingBeforeIds: pendingBeforeIds?.filter((id) => !canonicalIds.has(id))
1414
- });
1415
- }
1416
- });
1417
- }
1418
- restoreList(key, list) {
1419
- return this.update(() => {
1420
- if (list == null) this.deleteList(key);
1421
- else this.setList(key, list);
1422
- });
1423
- }
1424
- collectGarbage(markedRecords, markedLists, options = {}) {
1425
- const records = /* @__PURE__ */ new Set();
1426
- const lists = /* @__PURE__ */ new Set();
1427
- for (const id of this.records.keys()) {
1428
- if (markedRecords.has(id)) continue;
1429
- records.add(id);
1430
- }
1431
- for (const key of this.lists.keys()) {
1432
- if (markedLists.has(key)) continue;
1433
- lists.add(key);
1434
- }
1435
- for (const id of records) {
1436
- const record = this.records.get(id);
1437
- if (record) this.removeRecordReferenceIndexes(id, record);
1438
- this.records.delete(id);
1439
- this.coverage.delete(id);
1440
- options.onRecordDeleted?.(id);
1441
- }
1442
- for (const key of lists) this.deleteList(key);
1443
- return {
1444
- lists,
1445
- records
1446
- };
1447
- }
1448
- deleteList(key) {
1449
- return this.update(() => {
1450
- this.captureList(key);
1451
- const previous = this.lists.get(key);
1452
- if (previous) this.removeListIndexes(key, previous);
1453
- this.lists.delete(key);
1454
- this.notifyListSubscribers(key);
1455
- });
1456
- }
1457
- subscribeList(key, fn) {
1458
- let set = this.listSubscriptions.get(key);
1459
- if (!set) {
1460
- set = /* @__PURE__ */ new Set();
1461
- this.listSubscriptions.set(key, set);
1462
- }
1463
- set.add(fn);
1464
- return () => {
1465
- const subscribers = this.listSubscriptions.get(key);
1466
- if (!subscribers) return;
1467
- subscribers.delete(fn);
1468
- if (subscribers.size === 0) this.listSubscriptions.delete(key);
1469
- };
1470
- }
1471
- removeReferencesTo(targetId, viewDataCache) {
1472
- return this.update(() => {
1473
- const listKeys = [...this.listKeysByReferencedEntity.get(targetId) ?? []];
1474
- for (const key of listKeys) {
1475
- const list = this.lists.get(key);
1476
- if (!list) continue;
1477
- const { ids } = list;
1478
- const hasLiveAfter = Boolean(list.liveAfterIds?.includes(targetId));
1479
- const hasLiveBefore = Boolean(list.liveBeforeIds?.includes(targetId));
1480
- const hasPendingAfter = Boolean(list.pendingAfterIds?.includes(targetId));
1481
- const hasPendingBefore = Boolean(list.pendingBeforeIds?.includes(targetId));
1482
- if (!ids.includes(targetId) && !hasLiveAfter && !hasLiveBefore && !hasPendingAfter && !hasPendingBefore) continue;
1483
- const entityIds = [];
1484
- const cursors = list.cursors ? [] : void 0;
1485
- for (let index = 0; index < ids.length; index++) {
1486
- const id = ids[index];
1487
- if (id === targetId) continue;
1488
- entityIds.push(id);
1489
- if (cursors) cursors.push(list.cursors?.[index]);
1490
- }
1491
- this.setList(key, {
1492
- backwardPageLimit: list.backwardPageLimit,
1493
- cursors,
1494
- forwardPageLimit: list.forwardPageLimit,
1495
- ids: entityIds,
1496
- liveAfterIds: list.liveAfterIds?.filter((id) => id !== targetId),
1497
- liveBeforeIds: list.liveBeforeIds?.filter((id) => id !== targetId),
1498
- pagination: list.pagination,
1499
- pendingAfterIds: list.pendingAfterIds?.filter((id) => id !== targetId),
1500
- pendingBeforeIds: list.pendingBeforeIds?.filter((id) => id !== targetId)
1501
- });
1502
- }
1503
- const ids = /* @__PURE__ */ new Map();
1504
- const recordEntries = [...this.recordReferencesByTarget.get(targetId)?.entries() ?? []];
1505
- for (const [id, fields] of recordEntries) {
1506
- const record = this.records.get(id);
1507
- if (!record) continue;
1508
- let updated = false;
1509
- const next = {};
1510
- const paths = /* @__PURE__ */ new Set();
1511
- for (const key of fields) {
1512
- const value = record[key];
1513
- if (Array.isArray(value)) {
1514
- const filtered = value.filter((item) => !(isNodeRef(item) && getNodeRefId(item) === targetId));
1515
- if (filtered.length !== value.length) {
1516
- updated = true;
1517
- paths.add(key);
1518
- next[key] = filtered;
1519
- }
1520
- } else if (isNodeRef(value) && getNodeRefId(value) === targetId) {
1521
- updated = true;
1522
- paths.add(key);
1523
- next[key] = null;
1524
- }
1525
- }
1526
- if (!updated) continue;
1527
- viewDataCache.invalidate(id);
1528
- this.mergeInternal(id, next, paths);
1529
- ids.set(id, paths);
1530
- }
1531
- for (const [id, paths] of ids) this.notify(id, paths);
1532
- });
1533
- }
1534
- restore(id, snapshot) {
1535
- return this.update(() => {
1536
- this.captureRecord(id);
1537
- const previous = this.records.get(id);
1538
- if (previous) this.removeRecordReferenceIndexes(id, previous);
1539
- if (snapshot.record === void 0) this.records.delete(id);
1540
- else {
1541
- this.records.set(id, snapshot.record);
1542
- this.addRecordReferenceIndexes(id, snapshot.record);
1543
- }
1544
- if (snapshot.mask === void 0) this.coverage.delete(id);
1545
- else this.coverage.set(id, snapshot.mask);
1546
- this.notify(id);
1547
- });
1548
- }
1549
- addListIndexes(key, list) {
1550
- const parsed = parseListKey(key);
1551
- if (parsed) {
1552
- const ownerFieldKey = getOwnerFieldKey(parsed.ownerId, parsed.field);
1553
- let keys = this.listKeysByOwnerField.get(ownerFieldKey);
1554
- if (!keys) {
1555
- keys = /* @__PURE__ */ new Set();
1556
- this.listKeysByOwnerField.set(ownerFieldKey, keys);
1557
- }
1558
- keys.add(key);
1559
- }
1560
- for (const id of this.getListReferencedEntityIds(list)) {
1561
- let keys = this.listKeysByReferencedEntity.get(id);
1562
- if (!keys) {
1563
- keys = /* @__PURE__ */ new Set();
1564
- this.listKeysByReferencedEntity.set(id, keys);
1565
- }
1566
- keys.add(key);
1567
- }
1568
- }
1569
- removeListIndexes(key, list) {
1570
- const parsed = parseListKey(key);
1571
- if (parsed) {
1572
- const ownerFieldKey = getOwnerFieldKey(parsed.ownerId, parsed.field);
1573
- const keys = this.listKeysByOwnerField.get(ownerFieldKey);
1574
- if (keys) {
1575
- keys.delete(key);
1576
- if (keys.size === 0) this.listKeysByOwnerField.delete(ownerFieldKey);
1577
- }
1578
- }
1579
- for (const id of this.getListReferencedEntityIds(list)) {
1580
- const keys = this.listKeysByReferencedEntity.get(id);
1581
- if (!keys) continue;
1582
- keys.delete(key);
1583
- if (keys.size === 0) this.listKeysByReferencedEntity.delete(id);
1584
- }
1585
- }
1586
- getListReferencedEntityIds(list) {
1587
- const ids = /* @__PURE__ */ new Set();
1588
- for (const id of list.ids) ids.add(id);
1589
- for (const id of list.liveAfterIds ?? []) ids.add(id);
1590
- for (const id of list.liveBeforeIds ?? []) ids.add(id);
1591
- for (const id of list.pendingAfterIds ?? []) ids.add(id);
1592
- for (const id of list.pendingBeforeIds ?? []) ids.add(id);
1593
- return ids;
1594
- }
1595
- getRecordFieldReferences(value) {
1596
- if (Array.isArray(value)) {
1597
- let ids = null;
1598
- for (const item of value) {
1599
- if (!isNodeRef(item)) continue;
1600
- if (!ids) ids = /* @__PURE__ */ new Set();
1601
- ids.add(getNodeRefId(item));
1602
- }
1603
- return ids;
1604
- }
1605
- if (isNodeRef(value)) return /* @__PURE__ */ new Set([getNodeRefId(value)]);
1606
- return null;
1607
- }
1608
- addRecordFieldReferenceIndex(id, field, value) {
1609
- const targets = this.getRecordFieldReferences(value);
1610
- if (!targets || targets.size === 0) return;
1611
- let fields = this.recordReferenceFields.get(id);
1612
- if (!fields) {
1613
- fields = /* @__PURE__ */ new Map();
1614
- this.recordReferenceFields.set(id, fields);
1615
- }
1616
- fields.set(field, targets);
1617
- for (const targetId of targets) {
1618
- let records = this.recordReferencesByTarget.get(targetId);
1619
- if (!records) {
1620
- records = /* @__PURE__ */ new Map();
1621
- this.recordReferencesByTarget.set(targetId, records);
1622
- }
1623
- let targetFields = records.get(id);
1624
- if (!targetFields) {
1625
- targetFields = /* @__PURE__ */ new Set();
1626
- records.set(id, targetFields);
1627
- }
1628
- targetFields.add(field);
1629
- }
1630
- }
1631
- removeRecordFieldReferenceIndex(id, field) {
1632
- const fields = this.recordReferenceFields.get(id);
1633
- const targets = fields?.get(field);
1634
- if (!fields || !targets) return;
1635
- for (const targetId of targets) {
1636
- const records = this.recordReferencesByTarget.get(targetId);
1637
- const targetFields = records?.get(id);
1638
- if (!records || !targetFields) continue;
1639
- targetFields.delete(field);
1640
- if (targetFields.size === 0) records.delete(id);
1641
- if (records.size === 0) this.recordReferencesByTarget.delete(targetId);
1642
- }
1643
- fields.delete(field);
1644
- if (fields.size === 0) this.recordReferenceFields.delete(id);
1645
- }
1646
- addRecordReferenceIndexes(id, record) {
1647
- for (const [field, value] of Object.entries(record)) this.addRecordFieldReferenceIndex(id, field, value);
1648
- }
1649
- removeRecordReferenceIndexes(id, record) {
1650
- for (const field of Object.keys(record)) this.removeRecordFieldReferenceIndex(id, field);
1651
- }
1652
- updateRecordReferenceIndexes(id, previous, next, fields) {
1653
- for (const field of fields) {
1654
- if (previous) this.removeRecordFieldReferenceIndex(id, field);
1655
- this.addRecordFieldReferenceIndex(id, field, next[field]);
1656
- }
1657
- }
1658
- };
1659
- //#endregion
1660
145
  //#region src/request-descriptor.ts
1661
146
  const serializeId = (value) => `${typeof value}:${String(value)}`;
1662
147
  const getViewSignature = (view) => {
@@ -1984,6 +469,7 @@ var FateClient = class {
1984
469
  this.liveConnectionInvalidations = /* @__PURE__ */ new Map();
1985
470
  this.stableRefs = /* @__PURE__ */ new Map();
1986
471
  this.requests = /* @__PURE__ */ new Map();
472
+ this.requestExecutionOptions = /* @__PURE__ */ new WeakMap();
1987
473
  this.rootRequests = /* @__PURE__ */ new Map();
1988
474
  this.stalledRequests = /* @__PURE__ */ new Set();
1989
475
  this.gcPending = false;
@@ -1992,11 +478,13 @@ var FateClient = class {
1992
478
  this.store = new Store((ids) => {
1993
479
  for (const id of ids) this.viewDataCache.invalidate(id);
1994
480
  this.runPendingGarbageCollection();
1995
- });
481
+ }, (change) => this.persistenceRuntime?.changed(change));
1996
482
  this.viewDataCache = new ViewDataCache(() => !this.store.isRebasing);
1997
483
  this.liveConnectionInvalidationToken = 0;
484
+ this.persistenceDisposal = options.persistence ? new AbortController() : void 0;
1998
485
  this.actions = Object.create(null);
1999
486
  this.mutationMap = Object.create(null);
487
+ if (options.persistence) this.mutationEntities = new Map(Object.entries(options.mutations ?? {}).map(([key, definition]) => [key, definition.entity]));
2000
488
  this.mutations = Object.create(null);
2001
489
  this.onLiveError = options.onLiveError;
2002
490
  this.operationLifetime = new OperationLifetime(options.gcReleaseBufferSize ?? 10);
@@ -2020,6 +508,8 @@ var FateClient = class {
2020
508
  setNestedValue(this.actions, key, async (_previousState, data) => data === "reset" ? null : await this.mutationMap[key](data));
2021
509
  }
2022
510
  this.initializeParentLists();
511
+ this.persistenceRuntime = options.persistence?.attach(this);
512
+ this.persistence = this.persistenceRuntime;
2023
513
  }
2024
514
  initializeParentLists() {
2025
515
  for (const config of this.types.values()) {
@@ -2098,6 +588,60 @@ var FateClient = class {
2098
588
  this.viewDataCache.clear();
2099
589
  notify();
2100
590
  }
591
+ /** @internal */
592
+ exportPersistenceState() {
593
+ return {
594
+ data: encodeHydrationValue({
595
+ rootLists: [...this.rootLists].map(([type, keys]) => [type, [...keys]]),
596
+ rootRequests: [...this.rootRequests],
597
+ store: this.store.dehydrateConfirmed()
598
+ }, this.hydrationLimits),
599
+ scope: this.hydrationScope,
600
+ version: 1
601
+ };
602
+ }
603
+ /** @internal */
604
+ retainPersistenceRequest(request) {
605
+ return this.operationLifetime.retain(request, () => this.scheduleGarbageCollection());
606
+ }
607
+ /** @internal */
608
+ getPersistenceScope() {
609
+ return this.hydrationScope;
610
+ }
611
+ /** @internal */
612
+ getPersistenceRootLists(type) {
613
+ return this.rootLists.get(type) ?? /* @__PURE__ */ new Set();
614
+ }
615
+ /** @internal */
616
+ getPersistenceQuery(key) {
617
+ return this.rootRequests.get(key);
618
+ }
619
+ /** @internal Merge a small disk read beneath optimistic layers. */
620
+ restorePersistenceData(state) {
621
+ this.store.update(() => {
622
+ const coverage = new Map(state.store.coverage);
623
+ for (const [id, record] of state.store.records) {
624
+ const existing = this.store.read(id);
625
+ const paths = coverage.get(id) ?? [];
626
+ this.store.merge(id, existing ? mergePreservingExisting(record, existing) : record, paths);
627
+ }
628
+ for (const [key, list] of state.store.lists) if (!this.store.getListState(key)) this.store.setList(key, list);
629
+ for (const [key, id] of state.rootRequests) if (!this.rootRequests.has(key)) this.rootRequests.set(key, id);
630
+ for (const [type, keys] of state.rootLists) for (const key of keys) this.registerRootList(type, key);
631
+ });
632
+ }
633
+ /** @internal */
634
+ getRetainedRequests() {
635
+ return this.operationLifetime.getDescriptors();
636
+ }
637
+ /** @internal */
638
+ restoreRetainedRequests(requests) {
639
+ this.operationLifetime.restore(requests);
640
+ }
641
+ /** @internal */
642
+ hasMutation(key, entity) {
643
+ return this.mutationEntities?.get(key) === entity && this.types.has(entity);
644
+ }
2101
645
  registerRootList(type, key) {
2102
646
  let lists = this.rootLists.get(type);
2103
647
  if (!lists) {
@@ -2156,8 +700,33 @@ var FateClient = class {
2156
700
  if (!descriptor || descriptor === "scalar" || !("listOf" in descriptor)) throw new Error(`fate: Could not find node type for '${type}.${field}'.`);
2157
701
  return descriptor.listOf;
2158
702
  }
703
+ /** @internal Validates local wiring without requiring network connectivity. */
704
+ validatePersistence() {
705
+ if (this.mutationEntities?.size) this.requireDurableMutationTransport();
706
+ }
707
+ /** @internal */
708
+ persistMutation(command) {
709
+ if (!this.persistenceRuntime) throw new Error("fate: Persistence is not configured.");
710
+ return this.persistenceRuntime.mutate(command);
711
+ }
712
+ /** @internal */
713
+ assertPersistenceActive() {
714
+ if (this.persistenceDisposal?.signal.aborted || this.persistence?.getSnapshot().status === "disposed") throw new Error("fate: Persistence was disposed.");
715
+ }
716
+ /** @internal */
717
+ disposePersistence() {
718
+ this.persistenceDisposal?.abort();
719
+ for (const { unsubscribe } of this.liveSubscriptions.values()) try {
720
+ unsubscribe();
721
+ } catch {}
722
+ this.liveSubscriptions.clear();
723
+ }
724
+ requireDurableMutationTransport() {
725
+ if (!this.transport.mutateDurably) throw new FateRequestError("BAD_REQUEST", "fate: Persisted mutations require transport.mutateDurably connected to a server idempotency handler. Configure that adapter, or use persist: false for immediate calls. Cache persistence remains available.");
726
+ }
2159
727
  async executeMutation(key, input, select, options = {}) {
2160
- if (!this.transport.mutate) throw new Error(`fate: transport does not support mutations. Please provide a 'mutate' implementation in your transport.`);
728
+ if (options.identity) this.requireDurableMutationTransport();
729
+ else if (!this.transport.mutate) throw new Error(`fate: transport does not support mutations. Please provide a 'mutate' implementation in your transport.`);
2161
730
  const baseRecord = input && typeof input === "object" ? input : void 0;
2162
731
  const inputArgs = baseRecord && typeof baseRecord.args === "object" ? baseRecord.args : void 0;
2163
732
  const argsPayload = combineArgsPayload(options.plan ? resolvedArgsFromPlan(options.plan) : void 0, combineArgsPayload(inputArgs, options.args));
@@ -2165,7 +734,7 @@ var FateClient = class {
2165
734
  ...baseRecord,
2166
735
  args: argsPayload
2167
736
  } : argsPayload ? { args: argsPayload } : input;
2168
- return await this.trackPendingRequest(() => this.transport.mutate(key, requestInput, select));
737
+ return await this.trackPendingRequest(() => options.identity ? this.transport.mutateDurably(key, requestInput, select, options.identity) : this.transport.mutate(key, requestInput, select));
2169
738
  }
2170
739
  write(type, data, select, plan, insert) {
2171
740
  return this.writeEntity(type, data, select, plan, null, insert);
@@ -2201,6 +770,18 @@ var FateClient = class {
2201
770
  return this.stableRefWithViewNames(type, id, getRootViewNames(rootView));
2202
771
  }
2203
772
  readView(view, ref) {
773
+ if (this.persistence && this.persistence.getSnapshot().status !== "ready") {
774
+ const key = `restore:${ref.__typename}:${ref.id}:${[...getViewNames(view)].sort().join(",")}`;
775
+ let pending = this.pending.get(key);
776
+ if (!pending) {
777
+ pending = this.persistence.ready.then(() => {
778
+ if (this.persistence?.getSnapshot().status === "disposed") throw new Error("fate: Persistence was disposed.");
779
+ return this.readView(view, ref);
780
+ }).finally(() => this.pending.delete(key));
781
+ this.pending.set(key, pending);
782
+ }
783
+ return pending;
784
+ }
2204
785
  const id = ref.id;
2205
786
  const type = ref.__typename;
2206
787
  if (id == null) {
@@ -2253,7 +834,37 @@ var FateClient = class {
2253
834
  const pendingPromise = this.pending.get(key) || null;
2254
835
  if (pendingPromise) return pendingPromise;
2255
836
  const promise = this.trackPendingRequest(async () => {
2256
- await this.fetchByIdAndNormalize(type, [id], missing, plan);
837
+ const persistence = this.persistenceRuntime;
838
+ if (persistence) {
839
+ const persistedRequest = {
840
+ items: [{
841
+ ids: [id],
842
+ kind: "node",
843
+ name: type,
844
+ plan,
845
+ refViewNames: /* @__PURE__ */ new Set(),
846
+ type,
847
+ viewSignature: ""
848
+ }],
849
+ key: `view:${key}`
850
+ };
851
+ await persistence.restoreRequest(persistedRequest);
852
+ const remaining = this.store.missingForSelection(entityId, selectedPaths);
853
+ if (remaining.size) {
854
+ await this.fetchByIdAndNormalize(type, [id], remaining, plan);
855
+ persistence.fetched({
856
+ ...persistedRequest,
857
+ items: persistedRequest.items.map((item) => ({
858
+ ...item,
859
+ plan: {
860
+ ...item.plan,
861
+ paths: remaining
862
+ }
863
+ })),
864
+ key: `${persistedRequest.key}:${JSON.stringify([...remaining].sort())}`
865
+ });
866
+ }
867
+ } else await this.fetchByIdAndNormalize(type, [id], missing, plan);
2257
868
  try {
2258
869
  if (this.store.missingForSelection(entityId, selectedPaths).size > 0) {
2259
870
  this.stalledRequests.add(key);
@@ -2334,6 +945,7 @@ var FateClient = class {
2334
945
  if (!this.transport.subscribeConnection) throw new Error(`fate: transport does not support live list views. Please provide a 'subscribeConnection' implementation in your transport.`);
2335
946
  }
2336
947
  subscribeLiveView(view, ref) {
948
+ this.assertPersistenceActive();
2337
949
  this.assertLiveViewSupport();
2338
950
  const id = ref.id;
2339
951
  const type = ref.__typename;
@@ -2361,19 +973,26 @@ var FateClient = class {
2361
973
  try {
2362
974
  unsubscribe = this.transport.subscribeById(type, id, plan.paths, args, {
2363
975
  onData: (record, liveSelect) => {
2364
- if (!record || typeof record !== "object") return;
976
+ if (this.persistenceDisposal?.signal.aborted || !record || typeof record !== "object") return;
2365
977
  const select = new Set(liveSelect ?? plan.paths);
2366
978
  this.write(type, record, select, plan, "none");
2367
979
  },
2368
980
  onDelete: (deletedId) => {
981
+ if (this.persistenceDisposal?.signal.aborted) return;
2369
982
  this.deleteRecord(type, deletedId ?? id);
2370
983
  },
2371
- onError: (error) => this.reportLiveError(error)
984
+ onError: (error) => {
985
+ if (!this.persistenceDisposal?.signal.aborted) this.reportLiveError(error);
986
+ }
2372
987
  });
2373
988
  } catch (error) {
2374
989
  this.reportLiveError(error);
2375
990
  return emptyDispose;
2376
991
  }
992
+ if (this.persistenceDisposal?.signal.aborted) {
993
+ unsubscribe();
994
+ return emptyDispose;
995
+ }
2377
996
  this.liveSubscriptions.set(key, {
2378
997
  count: 1,
2379
998
  unsubscribe
@@ -2383,6 +1002,7 @@ var FateClient = class {
2383
1002
  };
2384
1003
  }
2385
1004
  subscribeLiveListView(view, connection) {
1005
+ this.assertPersistenceActive();
2386
1006
  this.assertLiveConnectionSupport();
2387
1007
  const plan = getSelectionPlan(view, null);
2388
1008
  const connectionArgs = filterConnectionArgs(connection.args);
@@ -2399,8 +1019,11 @@ var FateClient = class {
2399
1019
  let unsubscribe;
2400
1020
  try {
2401
1021
  unsubscribe = this.transport.subscribeConnection(connection.procedure, connection.type, connectionArgs, plan.paths, selectionArgs, {
2402
- onError: (error) => this.reportLiveError(error),
1022
+ onError: (error) => {
1023
+ if (!this.persistenceDisposal?.signal.aborted) this.reportLiveError(error);
1024
+ },
2403
1025
  onEvent: (event) => {
1026
+ if (this.persistenceDisposal?.signal.aborted) return;
2404
1027
  this.applyLiveConnectionEvent(view, connection, plan, event);
2405
1028
  }
2406
1029
  });
@@ -2408,6 +1031,10 @@ var FateClient = class {
2408
1031
  this.reportLiveError(error);
2409
1032
  return emptyDispose;
2410
1033
  }
1034
+ if (this.persistenceDisposal?.signal.aborted) {
1035
+ unsubscribe();
1036
+ return emptyDispose;
1037
+ }
2411
1038
  this.liveSubscriptions.set(key, {
2412
1039
  count: 1,
2413
1040
  unsubscribe
@@ -2723,6 +1350,7 @@ var FateClient = class {
2723
1350
  });
2724
1351
  const { argsPayload, plan } = resolveSelectionPlan(view, requestArgs);
2725
1352
  const { items, pagination } = await this.transport.fetchList(connection.field, plan.paths, argsPayload);
1353
+ this.assertPersistenceActive();
2726
1354
  if (!items) return this.store.getListState(connection.key);
2727
1355
  this.store.update(() => {
2728
1356
  const incomingIds = [];
@@ -2754,6 +1382,7 @@ var FateClient = class {
2754
1382
  const parentSelection = /* @__PURE__ */ new Set();
2755
1383
  for (const path of nodeSelection) parentSelection.add(`${connection.field}.${path}`);
2756
1384
  const [parentRecord] = await this.transport.fetchById(owner.type, [owner.id], parentSelection, scopedArgsPayload);
1385
+ this.assertPersistenceActive();
2757
1386
  if (!parentRecord || typeof parentRecord !== "object") return this.store.getListState(connection.key);
2758
1387
  const list = parentRecord[connection.field];
2759
1388
  const connectionPayload = Array.isArray(list) ? {
@@ -2796,7 +1425,7 @@ var FateClient = class {
2796
1425
  }
2797
1426
  request(request, options) {
2798
1427
  const mode = options?.mode ?? "cache-first";
2799
- return this.requestWithDescriptor(this.createRequestDescriptor(request), mode, { revalidateExisting: true });
1428
+ return this.requestWithDescriptor(this.createRequestDescriptor(request), mode, { revalidateExisting: true }, options);
2800
1429
  }
2801
1430
  requestForRender(request, options) {
2802
1431
  return this.prepareRequestForRender(request, options).promise;
@@ -2805,16 +1434,24 @@ var FateClient = class {
2805
1434
  const mode = options?.mode ?? "cache-first";
2806
1435
  const descriptor = this.createRequestDescriptor(request);
2807
1436
  return {
2808
- promise: this.requestWithDescriptor(descriptor, mode, { revalidateExisting: false }),
1437
+ promise: this.requestWithDescriptor(descriptor, mode, { revalidateExisting: false }, options),
2809
1438
  requestKey: descriptor.key
2810
1439
  };
2811
1440
  }
2812
- requestWithDescriptor(descriptor, mode, { revalidateExisting }) {
1441
+ requestWithDescriptor(descriptor, mode, { revalidateExisting }, options) {
1442
+ this.assertPersistenceActive();
1443
+ const persistence = this.persistenceRuntime;
1444
+ if (persistence && options?.persist && (!Number.isSafeInteger(options.persist.maxAge) || options.persist.maxAge < 0 || options.persist.maxAge > Number.MAX_SAFE_INTEGER - Date.now())) throw new Error("fate: Persistence maxAge must be a non-negative finite duration.");
2813
1445
  const requestKey = descriptor.key;
2814
1446
  const existingRequest = this.requests.get(requestKey)?.get(mode);
2815
1447
  if (existingRequest) {
1448
+ if (persistence && options?.persist) {
1449
+ const executionOptions = this.requestExecutionOptions.get(existingRequest.descriptor);
1450
+ if (executionOptions) executionOptions.persist = { ...options.persist };
1451
+ }
1452
+ if (persistence && options?.persist && existingRequest.status === "fulfilled") persistence.used(existingRequest.descriptor, options.persist);
2816
1453
  const isFulfilledCacheFirstWithMissingData = mode === "cache-first" && existingRequest.status === "fulfilled" && !this.hasRequestData(existingRequest.descriptor);
2817
- if (existingRequest.status !== "pending" && (isFulfilledCacheFirstWithMissingData || revalidateExisting && (mode === "network-only" || mode === "stale-while-revalidate" || mode === "cache-first" && existingRequest.status === "rejected"))) this.executeRequestHandle(existingRequest, mode);
1454
+ if (existingRequest.status !== "pending" && (isFulfilledCacheFirstWithMissingData || revalidateExisting && (mode === "network-only" || mode === "stale-while-revalidate" || mode === "cache-first" && existingRequest.status === "rejected"))) this.executeRequestHandle(existingRequest, mode, options);
2818
1455
  return existingRequest;
2819
1456
  }
2820
1457
  const handle = new FateRequestPromise(descriptor, () => this.getRequestResultFromDescriptor(descriptor));
@@ -2824,7 +1461,7 @@ var FateClient = class {
2824
1461
  this.requests.set(requestKey, requests);
2825
1462
  }
2826
1463
  requests.set(mode, handle);
2827
- this.executeRequestHandle(handle, mode);
1464
+ this.executeRequestHandle(handle, mode, options);
2828
1465
  return handle;
2829
1466
  }
2830
1467
  releaseRequest(request, mode) {
@@ -2869,7 +1506,7 @@ var FateClient = class {
2869
1506
  * memory-management flows.
2870
1507
  */
2871
1508
  gc() {
2872
- if (this.hasActiveOptimisticUpdates()) {
1509
+ if (this.persistence ? this.store.hasTransientOptimisticUpdates : this.hasActiveOptimisticUpdates()) {
2873
1510
  this.gcPending = true;
2874
1511
  return;
2875
1512
  }
@@ -2890,6 +1527,10 @@ var FateClient = class {
2890
1527
  for (const id of listState.ids) markRecord(id);
2891
1528
  for (const id of listState.pendingAfterIds ?? []) markRecord(id);
2892
1529
  };
1530
+ const optimisticRoots = this.store.getOptimisticRoots();
1531
+ optimisticRoots.baseRecords.forEach((record) => this.markRecordReferences(record, markRecord));
1532
+ optimisticRoots.records.forEach(markRecord);
1533
+ optimisticRoots.lists.forEach(markList);
2893
1534
  for (const descriptor of this.operationLifetime.getDescriptors()) for (const item of descriptor.items) {
2894
1535
  if (item.kind === "node" || item.kind === "nodes") {
2895
1536
  for (const raw of item.ids) markRecord(toEntityId(item.type, raw));
@@ -2928,15 +1569,19 @@ var FateClient = class {
2928
1569
  if (keys.size === 0) this.rootLists.delete(type);
2929
1570
  }
2930
1571
  }
2931
- executeRequestHandle(handle, mode) {
2932
- let execution;
2933
- switch (mode) {
2934
- case "stale-while-revalidate":
2935
- execution = this.handleStoreAndNetworkRequest(handle.descriptor);
2936
- break;
2937
- default: execution = this.executeRequest(handle.descriptor, mode === "network-only" ? { fetchAll: true } : void 0).then(() => this.getRequestResultFromDescriptor(handle.descriptor));
2938
- }
2939
- handle.start(execution);
1572
+ executeRequestHandle(handle, mode, options) {
1573
+ const persistence = this.persistenceRuntime;
1574
+ const executionOptions = {
1575
+ fetchAll: mode !== "cache-first",
1576
+ persist: options?.persist ? { ...options.persist } : void 0
1577
+ };
1578
+ if (persistence) this.requestExecutionOptions.set(handle.descriptor, executionOptions);
1579
+ const execute = () => {
1580
+ this.assertPersistenceActive();
1581
+ if (mode === "stale-while-revalidate") return this.handleStoreAndNetworkRequest(handle.descriptor, executionOptions);
1582
+ return this.executeRequest(handle.descriptor, executionOptions).then(() => this.getRequestResultFromDescriptor(handle.descriptor));
1583
+ };
1584
+ handle.start(persistence ? this.withPersistenceLifecycle(() => persistence.ready.then(() => mode === "network-only" ? void 0 : persistence.restoreRequest(handle.descriptor)).then(execute)) : execute());
2940
1585
  }
2941
1586
  markRecordReferences(record, markRecord) {
2942
1587
  const seen = /* @__PURE__ */ new Set();
@@ -2962,24 +1607,47 @@ var FateClient = class {
2962
1607
  hasPendingRequests() {
2963
1608
  return this.pendingNetworkRequests > 0;
2964
1609
  }
2965
- async trackPendingRequest(request) {
2966
- this.pendingNetworkRequests += 1;
1610
+ /** @internal */
1611
+ async withPersistenceLifecycle(request) {
1612
+ this.assertPersistenceActive();
1613
+ const signal = this.persistenceDisposal?.signal;
1614
+ let onDispose;
2967
1615
  try {
2968
- return await request();
1616
+ const execution = request();
1617
+ const result = signal ? await Promise.race([execution, new Promise((_, reject) => {
1618
+ onDispose = () => reject(/* @__PURE__ */ new Error("fate: Persistence was disposed."));
1619
+ signal.addEventListener("abort", onDispose, { once: true });
1620
+ if (signal.aborted) onDispose();
1621
+ })]) : await execution;
1622
+ this.assertPersistenceActive();
1623
+ return result;
2969
1624
  } finally {
2970
- this.pendingNetworkRequests -= 1;
1625
+ if (onDispose) signal?.removeEventListener("abort", onDispose);
2971
1626
  }
2972
1627
  }
1628
+ async trackPendingRequest(request) {
1629
+ return this.withPersistenceLifecycle(async () => {
1630
+ if (this.persistence) await this.persistence.ready;
1631
+ this.assertPersistenceActive();
1632
+ this.pendingNetworkRequests += 1;
1633
+ try {
1634
+ return await this.withPersistenceLifecycle(request);
1635
+ } finally {
1636
+ this.pendingNetworkRequests -= 1;
1637
+ this.persistenceRuntime?.changed();
1638
+ }
1639
+ });
1640
+ }
2973
1641
  runPendingGarbageCollection() {
2974
1642
  if (this.gcPending && !this.hasActiveOptimisticUpdates()) this.gc();
2975
1643
  }
2976
- async handleStoreAndNetworkRequest(request) {
1644
+ async handleStoreAndNetworkRequest(request, options) {
2977
1645
  if (!this.hasRequestData(request)) {
2978
- await this.executeRequest(request, { fetchAll: true });
1646
+ await this.executeRequest(request, options);
2979
1647
  return this.getRequestResultFromDescriptor(request);
2980
1648
  }
2981
1649
  const result = this.getRequestResultFromDescriptor(request);
2982
- this.executeRequest(request, { fetchAll: true }).catch(() => {});
1650
+ this.executeRequest(request, options).catch(() => {});
2983
1651
  return result;
2984
1652
  }
2985
1653
  getRootType(name) {
@@ -3005,6 +1673,8 @@ var FateClient = class {
3005
1673
  const fetchAll = options.fetchAll ?? false;
3006
1674
  const groups = /* @__PURE__ */ new Map();
3007
1675
  const promises = [];
1676
+ const fetchedItems = [];
1677
+ let fetchedAll = true;
3008
1678
  for (const item of request.items) if (item.kind === "node" || item.kind === "nodes") {
3009
1679
  const fields = item.plan.paths;
3010
1680
  const fieldsSignature = [...fields].slice().sort().join(",");
@@ -3020,23 +1690,47 @@ var FateClient = class {
3020
1690
  };
3021
1691
  groups.set(groupKey, group);
3022
1692
  }
1693
+ const fetchedIds = [];
3023
1694
  for (const raw of item.ids) {
3024
1695
  const entityId = toEntityId(item.type, raw);
3025
1696
  const missing = this.store.missingForSelection(entityId, fields);
3026
- if (fetchAll || missing.size > 0) group.ids.push(raw);
1697
+ if (fetchAll || missing.size > 0) {
1698
+ group.ids.push(raw);
1699
+ fetchedIds.push(raw);
1700
+ }
3027
1701
  }
1702
+ if (fetchedIds.length) fetchedItems.push({
1703
+ ...item,
1704
+ ids: fetchedIds
1705
+ });
1706
+ fetchedAll &&= fetchedIds.length === item.ids.length;
3028
1707
  } else {
3029
1708
  if (item.kind === "query") {
3030
1709
  const hasResult = this.rootRequests.has(item.queryKey);
3031
1710
  const entityId = this.rootRequests.get(item.queryKey);
3032
1711
  const missing = hasResult && entityId ? this.store.missingForSelection(entityId, item.plan.paths) : item.plan.paths;
3033
- if (fetchAll || !hasResult || entityId && missing.size > 0) promises.push(this.fetchQueryAndNormalize(item));
1712
+ if (fetchAll || !hasResult || entityId && missing.size > 0) {
1713
+ promises.push(this.fetchQueryAndNormalize(item));
1714
+ fetchedItems.push(item);
1715
+ } else fetchedAll = false;
3034
1716
  }
3035
1717
  if (item.kind !== "list") continue;
3036
- if (!fetchAll && !getPaginationArgsInfo(item.argsPayload).hasCursorArg && this.hasRootListData(item)) continue;
1718
+ if (!fetchAll && !getPaginationArgsInfo(item.argsPayload).hasCursorArg && this.hasRootListData(item)) {
1719
+ fetchedAll = false;
1720
+ continue;
1721
+ }
3037
1722
  promises.push(this.fetchListAndNormalize(item));
1723
+ fetchedItems.push(item);
3038
1724
  }
3039
1725
  await Promise.all([...promises, ...Array.from(groups.values()).map((group) => group.ids.length ? this.fetchByIdAndNormalize(group.type, group.ids, group.fields, group.plan) : Promise.resolve())]);
1726
+ const persistence = this.persistenceRuntime;
1727
+ if (persistence) {
1728
+ if (fetchedItems.length) persistence.fetched(fetchedAll ? request : {
1729
+ items: fetchedItems,
1730
+ key: `partial:${JSON.stringify([request.key, fetchedItems.map((item) => [item.name, "ids" in item ? item.ids : null])])}`
1731
+ }, options.persist);
1732
+ if (!fetchedItems.length || !fetchedAll) persistence.used(request, options.persist);
1733
+ }
3040
1734
  }
3041
1735
  hasRequestData(request) {
3042
1736
  for (const item of request.items) {
@@ -3124,6 +1818,7 @@ var FateClient = class {
3124
1818
  const resolvedArgs = resolvedArgsFromPlan(plan);
3125
1819
  await this.trackPendingRequest(async () => {
3126
1820
  const records = await this.transport.fetchById(type, ids, select, resolvedArgs);
1821
+ this.assertPersistenceActive();
3127
1822
  for (const record of records) this.writeEntity(type, record, select, plan, prefix);
3128
1823
  });
3129
1824
  }
@@ -3131,6 +1826,7 @@ var FateClient = class {
3131
1826
  if (!this.transport.fetchQuery) throw new Error(`fate: transport does not support queries. Please add support for 'fetchQuery' in your transport for '${item.name}'.`);
3132
1827
  await this.trackPendingRequest(async () => {
3133
1828
  const record = await this.transport.fetchQuery(item.name, item.plan.paths, item.argsPayload);
1829
+ this.assertPersistenceActive();
3134
1830
  if (!record || typeof record !== "object") {
3135
1831
  this.rootRequests.set(item.queryKey, null);
3136
1832
  return;
@@ -3143,6 +1839,7 @@ var FateClient = class {
3143
1839
  if (!this.transport.fetchList) throw new Error(`fate: 'transport.fetchList' is not configured but request includes a list for key '${item.name}'.`);
3144
1840
  await this.trackPendingRequest(async () => {
3145
1841
  const { items, pagination } = await this.transport.fetchList(item.name, item.plan.paths, item.argsPayload);
1842
+ this.assertPersistenceActive();
3146
1843
  this.store.update(() => {
3147
1844
  const ids = [];
3148
1845
  const cursors = [];
@@ -3185,7 +1882,8 @@ var FateClient = class {
3185
1882
  const childType = relationDescriptor.type;
3186
1883
  const childConfig = this.types.get(childType);
3187
1884
  if (!childConfig) throw new Error(`fate: Unknown related type '${childType}' (field '${type}.${key}').`);
3188
- result[key] = createNodeRef(toEntityId(childType, childConfig.getId(value)));
1885
+ const childId = toEntityId(childType, childConfig.getId(value));
1886
+ result[key] = createNodeRef(childId);
3189
1887
  this.writeEntity(childType, value, childPaths, plan, fieldPath);
3190
1888
  }
3191
1889
  } else if (relationDescriptor && typeof relationDescriptor === "object" && "listOf" in relationDescriptor) {
@@ -3528,7 +2226,7 @@ function createClient(options) {
3528
2226
  * Builds a `Transport` backed by a tRPC client using the configured resolvers
3529
2227
  * for by-id queries, lists, and mutations.
3530
2228
  */
3531
- function createTRPCTransport({ byId, client, lists, mutations, queries }) {
2229
+ function createTRPCTransport({ byId, client, lists, mutateDurably, mutations, queries }) {
3532
2230
  const transport = {
3533
2231
  async fetchById(type, ids, select, args) {
3534
2232
  const resolver = byId[type];
@@ -3556,7 +2254,8 @@ function createTRPCTransport({ byId, client, lists, mutations, queries }) {
3556
2254
  args,
3557
2255
  select: [...select]
3558
2256
  });
3559
- }
2257
+ },
2258
+ mutateDurably
3560
2259
  };
3561
2260
  transport.mutate = async (procedure, input, select) => {
3562
2261
  const resolver = mutations?.[procedure];
@@ -3634,7 +2333,7 @@ const responseError = async (response) => {
3634
2333
  return new FateRequestError(errorCodeFromStatus(response.status), message, { status: response.status });
3635
2334
  };
3636
2335
  const assertProtocolResponse = (value) => {
3637
- if (!isRecord(value) || value.version !== 1 || !Array.isArray(value.results) || value.results.some((entry) => !isRecord(entry) || typeof entry.id !== "string")) throw new Error("fate(http): Invalid protocol response.");
2336
+ if (!isRecord(value) || value.version !== 1 && value.version !== 2 || !Array.isArray(value.results) || value.results.some((entry) => !isRecord(entry) || typeof entry.id !== "string")) throw new Error("fate(http): Invalid protocol response.");
3638
2337
  return value;
3639
2338
  };
3640
2339
  const resultValue = (result) => {
@@ -3672,7 +2371,7 @@ function createHTTPTransport({ eventSource, fetch: fetchImpl = defaultFetch, hea
3672
2371
  const response = await fetchImpl(endpoint, {
3673
2372
  body: JSON.stringify({
3674
2373
  operations: batch.map((entry) => entry.operation),
3675
- version: 1
2374
+ version: batch.some((entry) => entry.operation.mutation) ? 2 : 1
3676
2375
  }),
3677
2376
  headers: await requestHeaders({ "content-type": "application/json" }, headers),
3678
2377
  method: "POST"
@@ -3729,6 +2428,15 @@ function createHTTPTransport({ eventSource, fetch: fetchImpl = defaultFetch, hea
3729
2428
  name,
3730
2429
  select: [...select]
3731
2430
  });
2431
+ },
2432
+ mutateDurably(name, input, select, identity) {
2433
+ return enqueue({
2434
+ input,
2435
+ kind: "mutation",
2436
+ mutation: identity,
2437
+ name,
2438
+ select: [...select]
2439
+ });
3732
2440
  }
3733
2441
  };
3734
2442
  if (liveEnabled) {