@nkzw/fate 1.3.3 → 1.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (191) hide show
  1. package/README.md +326 -0
  2. package/docs/api/classes/FateClient.md +51 -199
  3. package/docs/api/functions/clientRoot.md +1 -1
  4. package/docs/api/functions/createClient.md +1 -1
  5. package/docs/api/functions/createGraphQLTransport.md +1 -1
  6. package/docs/api/functions/createHTTPTransport.md +1 -1
  7. package/docs/api/functions/createTRPCTransport.md +5 -1
  8. package/docs/api/functions/defer.md +1 -1
  9. package/docs/api/functions/getDeferredMetadata.md +1 -1
  10. package/docs/api/functions/getListEntries.md +1 -1
  11. package/docs/api/functions/getSelectionPlan.md +1 -1
  12. package/docs/api/functions/graphqlMutation.md +1 -1
  13. package/docs/api/functions/isDeferred.md +1 -1
  14. package/docs/api/functions/isRecord.md +1 -1
  15. package/docs/api/functions/isViewTag.md +1 -1
  16. package/docs/api/functions/liveConnectionTopic.md +1 -1
  17. package/docs/api/functions/liveEntityTopic.md +1 -1
  18. package/docs/api/functions/liveGlobalConnectionTopic.md +1 -1
  19. package/docs/api/functions/mutation.md +1 -1
  20. package/docs/api/functions/toEntityId.md +1 -1
  21. package/docs/api/functions/view.md +1 -1
  22. package/docs/api/index.md +4 -1
  23. package/docs/api/interfaces/FateThenable.md +3 -3
  24. package/docs/api/interfaces/Persistence.md +23 -0
  25. package/docs/api/interfaces/PersistenceSession.md +109 -0
  26. package/docs/api/interfaces/Transport.md +46 -7
  27. package/docs/api/server/classes/FateRequestError.md +5 -5
  28. package/docs/api/server/drizzle/functions/createDrizzleFate.md +1 -1
  29. package/docs/api/server/drizzle/functions/createDrizzleSourceAdapter.md +1 -1
  30. package/docs/api/server/drizzle/functions/createDrizzleSourceRegistry.md +1 -1
  31. package/docs/api/server/drizzle/type-aliases/DrizzleManyToManyConfig.md +1 -1
  32. package/docs/api/server/drizzle/type-aliases/DrizzleManyToManyInput.md +1 -1
  33. package/docs/api/server/drizzle/type-aliases/DrizzleQueryExtra.md +3 -3
  34. package/docs/api/server/drizzle/type-aliases/DrizzleSourceAdapter.md +9 -9
  35. package/docs/api/server/drizzle/type-aliases/DrizzleViewConfig.md +1 -1
  36. package/docs/api/server/functions/bindSourceProcedures.md +1 -1
  37. package/docs/api/server/functions/computed.md +1 -1
  38. package/docs/api/server/functions/count.md +1 -1
  39. package/docs/api/server/functions/createFateFetchHandler.md +1 -1
  40. package/docs/api/server/functions/createFateServer.md +1 -1
  41. package/docs/api/server/functions/createHonoFateHandler.md +1 -1
  42. package/docs/api/server/functions/createLiveEventBus.md +1 -1
  43. package/docs/api/server/functions/createNestedSourcePlan.md +1 -1
  44. package/docs/api/server/functions/createResolver.md +1 -1
  45. package/docs/api/server/functions/createSourcePlan.md +1 -1
  46. package/docs/api/server/functions/createSourceProcedures.md +1 -1
  47. package/docs/api/server/functions/dataView.md +1 -1
  48. package/docs/api/server/functions/field.md +1 -1
  49. package/docs/api/server/functions/getNestedSelection.md +1 -1
  50. package/docs/api/server/functions/getScopedArgs.md +1 -1
  51. package/docs/api/server/functions/hasNestedSelection.md +1 -1
  52. package/docs/api/server/functions/list.md +1 -1
  53. package/docs/api/server/functions/refetchSourceById.md +1 -1
  54. package/docs/api/server/functions/resolveSourceById.md +1 -1
  55. package/docs/api/server/functions/resolveSourceByIds.md +1 -1
  56. package/docs/api/server/functions/resolveSourceConnection.md +1 -1
  57. package/docs/api/server/functions/resolver.md +1 -1
  58. package/docs/api/server/functions/toPrismaSelect.md +1 -1
  59. package/docs/api/server/functions/withConnection.md +1 -1
  60. package/docs/api/server/prisma/functions/createPrismaFate.md +1 -1
  61. package/docs/api/server/prisma/functions/createPrismaSourceAdapter.md +1 -1
  62. package/docs/api/server/prisma/functions/createPrismaSourceRegistry.md +1 -1
  63. package/docs/api/server/prisma/functions/prismaConnectionArgs.md +1 -1
  64. package/docs/api/server/prisma/type-aliases/PrismaDelegate.md +4 -4
  65. package/docs/api/server/prisma/type-aliases/PrismaQueryExtra.md +1 -1
  66. package/docs/api/server/prisma/type-aliases/PrismaSourceAdapter.md +9 -9
  67. package/docs/api/server/prisma/type-aliases/PrismaViewConfig.md +1 -1
  68. package/docs/api/server/type-aliases/ComputedField.md +5 -5
  69. package/docs/api/server/type-aliases/ComputedSelection.md +1 -1
  70. package/docs/api/server/type-aliases/ConnectionItem.md +3 -3
  71. package/docs/api/server/type-aliases/ConnectionPagination.md +5 -5
  72. package/docs/api/server/type-aliases/ConnectionResult.md +3 -3
  73. package/docs/api/server/type-aliases/CountSelection.md +4 -4
  74. package/docs/api/server/type-aliases/CountWhere.md +1 -1
  75. package/docs/api/server/type-aliases/DataViewListOptions.md +2 -2
  76. package/docs/api/server/type-aliases/DataViewOrderBy.md +1 -1
  77. package/docs/api/server/type-aliases/DataViewOrderDirection.md +1 -1
  78. package/docs/api/server/type-aliases/DataViewResult.md +1 -1
  79. package/docs/api/server/type-aliases/Entity.md +1 -1
  80. package/docs/api/server/type-aliases/FateServer.md +5 -5
  81. package/docs/api/server/type-aliases/FateServerManifest.md +1 -1
  82. package/docs/api/server/type-aliases/FieldSelection.md +3 -3
  83. package/docs/api/server/type-aliases/LiveConnectionEventType.md +1 -1
  84. package/docs/api/server/type-aliases/LiveConnectionSourceEvent.md +1 -1
  85. package/docs/api/server/type-aliases/LiveEventBus.md +1 -1
  86. package/docs/api/server/type-aliases/LiveEventType.md +1 -1
  87. package/docs/api/server/type-aliases/LiveSourceEvent.md +1 -1
  88. package/docs/api/server/type-aliases/NativeFateAPI.md +4 -4
  89. package/docs/api/server/type-aliases/OrderDirection.md +1 -1
  90. package/docs/api/server/type-aliases/SourceConfig.md +6 -6
  91. package/docs/api/server/type-aliases/SourceDefinition.md +6 -6
  92. package/docs/api/server/type-aliases/SourceOrder.md +1 -1
  93. package/docs/api/server/type-aliases/SourceOrderField.md +3 -3
  94. package/docs/api/server/type-aliases/SourcePlan.md +1 -1
  95. package/docs/api/server/type-aliases/SourcePlanNode.md +1 -1
  96. package/docs/api/server/type-aliases/SourceRegistry.md +1 -1
  97. package/docs/api/server/type-aliases/SourceRelation.md +1 -1
  98. package/docs/api/server/type-aliases/SourceRelationConfig.md +1 -1
  99. package/docs/api/server/variables/byIdInput.md +1 -1
  100. package/docs/api/server/variables/connectionArgs.md +1 -1
  101. package/docs/api/type-aliases/ConnectionMetadata.md +1 -1
  102. package/docs/api/type-aliases/ConnectionRef.md +1 -1
  103. package/docs/api/type-aliases/Deferred.md +1 -1
  104. package/docs/api/type-aliases/DeferredMetadata.md +1 -1
  105. package/docs/api/type-aliases/DeferredSelection.md +1 -1
  106. package/docs/api/type-aliases/DeferredSnapshot.md +1 -1
  107. package/docs/api/type-aliases/Entity.md +2 -2
  108. package/docs/api/type-aliases/EntityId.md +1 -1
  109. package/docs/api/type-aliases/FateDehydratedState.md +1 -1
  110. package/docs/api/type-aliases/FateLiveConnectionEvent.md +1 -1
  111. package/docs/api/type-aliases/FateLiveEvent.md +1 -1
  112. package/docs/api/type-aliases/FateMutations.md +1 -1
  113. package/docs/api/type-aliases/FateOperation.md +2 -2
  114. package/docs/api/type-aliases/FateProtocolRequest.md +1 -1
  115. package/docs/api/type-aliases/FateProtocolResponse.md +1 -1
  116. package/docs/api/type-aliases/FateRecord.md +1 -1
  117. package/docs/api/type-aliases/FateRoots.md +1 -1
  118. package/docs/api/type-aliases/GraphQLMutationDefinition.md +1 -1
  119. package/docs/api/type-aliases/GraphQLMutationInput.md +1 -1
  120. package/docs/api/type-aliases/GraphQLMutationMap.md +1 -1
  121. package/docs/api/type-aliases/GraphQLMutationOutput.md +1 -1
  122. package/docs/api/type-aliases/GraphQLTransportOptions.md +19 -11
  123. package/docs/api/type-aliases/HydrateOptions.md +1 -1
  124. package/docs/api/type-aliases/HydrationLimits.md +1 -1
  125. package/docs/api/type-aliases/InferFateAPI.md +1 -1
  126. package/docs/api/type-aliases/List.md +1 -1
  127. package/docs/api/type-aliases/ListEntry.md +1 -1
  128. package/docs/api/type-aliases/ListItem.md +1 -1
  129. package/docs/api/type-aliases/Mask.md +1 -1
  130. package/docs/api/type-aliases/MutationDefinition.md +1 -1
  131. package/docs/api/type-aliases/MutationEntity.md +1 -1
  132. package/docs/api/type-aliases/MutationIdentifier.md +1 -1
  133. package/docs/api/type-aliases/MutationIdentity.md +7 -0
  134. package/docs/api/type-aliases/MutationInput.md +1 -1
  135. package/docs/api/type-aliases/MutationResult.md +1 -1
  136. package/docs/api/type-aliases/NodesItem.md +1 -1
  137. package/docs/api/type-aliases/Pagination.md +5 -5
  138. package/docs/api/type-aliases/PersistenceSnapshot.md +5 -0
  139. package/docs/api/type-aliases/Request.md +1 -1
  140. package/docs/api/type-aliases/RequestMode.md +1 -1
  141. package/docs/api/type-aliases/RequestOptions.md +2 -2
  142. package/docs/api/type-aliases/RequestResult.md +1 -1
  143. package/docs/api/type-aliases/Selection.md +1 -1
  144. package/docs/api/type-aliases/TypeConfig.md +4 -4
  145. package/docs/api/type-aliases/View.md +1 -1
  146. package/docs/api/type-aliases/ViewData.md +1 -1
  147. package/docs/api/type-aliases/ViewEntity.md +1 -1
  148. package/docs/api/type-aliases/ViewEntityName.md +1 -1
  149. package/docs/api/type-aliases/ViewRef.md +1 -1
  150. package/docs/api/type-aliases/ViewSelection.md +1 -1
  151. package/docs/api/type-aliases/ViewSnapshot.md +1 -1
  152. package/docs/api/type-aliases/ViewTag.md +1 -1
  153. package/docs/api/variables/ConnectionTag.md +1 -1
  154. package/docs/api/variables/DeferTag.md +1 -1
  155. package/docs/api/variables/DeferredTag.md +1 -1
  156. package/docs/guide/getting-started.md +2 -0
  157. package/docs/guide/persistence.md +323 -0
  158. package/lib/{liveTopics-uVP0LG-Q.mjs → args-CRLIRl_v.mjs} +1 -59
  159. package/lib/cli.d.mts +1 -1
  160. package/lib/{executor-C8HNogYG.d.mts → executor-DoiTBiVv.d.mts} +18 -65
  161. package/lib/{graphqlTransport-DskuXeUP.d.mts → graphqlTransport-DGLYyRxL.d.mts} +4 -14
  162. package/lib/{graphqlTransport-DSBVS2Fq.mjs → graphqlTransport-DmheG3AO.mjs} +3 -2
  163. package/lib/graphqlTransport.d.mts +1 -1
  164. package/lib/graphqlTransport.mjs +1 -1
  165. package/lib/hydration-DPZRJgnu.mjs +273 -0
  166. package/lib/idempotency-types-CqA-BFpn.d.mts +15 -0
  167. package/lib/idempotency.d.mts +26 -0
  168. package/lib/idempotency.mjs +34 -0
  169. package/lib/index.d.mts +6 -15
  170. package/lib/index.mjs +560 -1774
  171. package/lib/{list-sI2UPhHc.d.mts → list-BRT1VtiT.d.mts} +1 -2
  172. package/lib/list.d.mts +1 -1
  173. package/lib/liveTopics-DuFkDZQm.mjs +17 -0
  174. package/lib/persistence.d.mts +38 -0
  175. package/lib/persistence.mjs +1529 -0
  176. package/lib/protocol-DS5sf6sd.mjs +44 -0
  177. package/lib/{record-CZyyeLa3.d.mts → record-DITM4zb_.d.mts} +14 -19
  178. package/lib/server/drizzle.d.mts +45 -114
  179. package/lib/server/drizzle.mjs +2 -2
  180. package/lib/server/prisma.d.mts +52 -127
  181. package/lib/server/prisma.mjs +9 -8
  182. package/lib/server.d.mts +4 -16
  183. package/lib/server.mjs +21 -35
  184. package/lib/sortObjectKeys-yhuitEQZ.mjs +9 -0
  185. package/lib/{sourceRouter-DSaADXSf.mjs → sourceRouter-51POBlEE.mjs} +9 -6
  186. package/lib/store-CeatPJeK.mjs +1350 -0
  187. package/lib/{transport-DhaVVq4P.d.mts → transport-DqjhjLfC.d.mts} +672 -517
  188. package/lib/vite.d.mts +0 -1
  189. package/lib/vite.mjs +19 -11
  190. package/package.json +15 -5
  191. package/docs/api/type-aliases/Snapshot.md +0 -7
package/lib/index.mjs CHANGED
@@ -1,10 +1,15 @@
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-CeatPJeK.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
- constructor() {
11
+ constructor(isEnabled = () => true) {
12
+ this.isEnabled = isEnabled;
8
13
  this.cache = /* @__PURE__ */ new Map();
9
14
  this.rootDependencies = /* @__PURE__ */ new Map();
10
15
  this.dependencyIndex = /* @__PURE__ */ new Map();
@@ -15,9 +20,11 @@ var ViewDataCache = class {
15
20
  this.dependencyIndex.clear();
16
21
  }
17
22
  get(entityId, view, ref) {
23
+ if (!this.isEnabled()) return null;
18
24
  return this.cache.get(entityId)?.get(view)?.get(ref) ?? null;
19
25
  }
20
26
  set(entityId, view, ref, thenable, dependencies) {
27
+ if (!this.isEnabled()) return;
21
28
  let entityMap = this.cache.get(entityId);
22
29
  if (!entityMap) {
23
30
  entityMap = /* @__PURE__ */ new WeakMap();
@@ -45,6 +52,7 @@ var ViewDataCache = class {
45
52
  }
46
53
  }
47
54
  invalidate(entityId) {
55
+ if (!this.isEnabled()) return;
48
56
  this.invalidateDependents(entityId, /* @__PURE__ */ new Set());
49
57
  }
50
58
  invalidateDependents(entityId, visited) {
@@ -69,837 +77,6 @@ var ViewDataCache = class {
69
77
  }
70
78
  };
71
79
  //#endregion
72
- //#region src/types.ts
73
- /** Internal marker added to objects that represent a view payload. */
74
- const ViewKind = Symbol("__fate__view");
75
- /** Symbol used to attach the set of view tags that were spread into a ref or masked record. */
76
- const ViewsTag = Symbol("__fate__views");
77
- /** Symbol used to brand a value as a node reference inside the cache. */
78
- const NodeRefTag = Symbol("__fate__node-ref");
79
- /** Symbol attached to connection results so pagination helpers can find their metadata. */
80
- const ConnectionTag = Symbol("__fate__connection");
81
- /** Symbol used to mark a selection as deferred. */
82
- const DeferTag = Symbol("__fate__defer");
83
- /** Symbol attached to deferred values returned from masked views. */
84
- const DeferredTag = Symbol("__fate__deferred");
85
- const viewTag = "__fate-view__";
86
- /** Generates a stable view tag for a view definition. */
87
- function getViewTag(id) {
88
- return `${viewTag}${id}`;
89
- }
90
- /** Determines whether a property key is a fate view tag. */
91
- function isViewTag(key) {
92
- return key.startsWith(viewTag);
93
- }
94
- /** Indicates whether a request item represents an explicit node ID. */
95
- function isNodeItem(item) {
96
- return "id" in item;
97
- }
98
- /** Indicates whether a request item represents explicit node IDs. */
99
- function isNodesItem(item) {
100
- return "ids" in item;
101
- }
102
- /** Indicates whether a request item represents a root query. */
103
- function isQueryItem(item) {
104
- return "view" in item && !("id" in item) && !("ids" in item);
105
- }
106
- /** Brand used on root definitions to mark their identity in the d.ts output. */
107
- const RootKind = "__fate__root";
108
- /** Brand used on mutation definitions to mark their identity in the d.ts output. */
109
- const MutationKind = "__fate__mutation";
110
- //#endregion
111
- //#region src/defer.ts
112
- function defer(selection) {
113
- return Object.freeze({ [DeferTag]: selection });
114
- }
115
- const isDeferredSelection = (value) => typeof value === "object" && value !== null && DeferTag in value;
116
- const getDeferredSelection = (selection) => selection[DeferTag];
117
- const createDeferred = (metadata) => Object.freeze({ [DeferredTag]: metadata });
118
- const isDeferred = (value) => typeof value === "object" && value !== null && DeferredTag in value;
119
- const getDeferredMetadata = (deferred) => deferred[DeferredTag];
120
- //#endregion
121
- //#region src/node-ref.ts
122
- function createNodeRef(id) {
123
- const ref = {};
124
- Object.defineProperty(ref, NodeRefTag, {
125
- configurable: false,
126
- enumerable: false,
127
- value: id,
128
- writable: false
129
- });
130
- return Object.freeze(ref);
131
- }
132
- function isNodeRef(value) {
133
- return !value || typeof value !== "object" ? false : NodeRefTag in value;
134
- }
135
- function getNodeRefId(ref) {
136
- return ref[NodeRefTag];
137
- }
138
- //#endregion
139
- //#region src/hydration.ts
140
- const maxDepth = 64;
141
- const defaultHydrationLimits = {
142
- maxCollectionLength: 1e5,
143
- maxNodes: 25e4,
144
- maxStringLength: 1e6
145
- };
146
- const resolveHydrationLimits = (limits) => {
147
- const result = {
148
- ...defaultHydrationLimits,
149
- ...limits
150
- };
151
- 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.`);
152
- return result;
153
- };
154
- const error = (path, message) => /* @__PURE__ */ new Error(`fate: Cannot serialize hydration value at '${path}': ${message}`);
155
- const isPlainObject = (value) => {
156
- const prototype = Object.getPrototypeOf(value);
157
- return prototype === Object.prototype || prototype === null;
158
- };
159
- const isDenseArray = (value) => {
160
- for (let index = 0; index < value.length; index += 1) if (!Object.hasOwn(value, index)) return false;
161
- return true;
162
- };
163
- const isDate$1 = (value) => Object.prototype.toString.call(value) === "[object Date]" && typeof value.getTime === "function" && typeof value.toISOString === "function";
164
- const encodeHydrationValue = (value, limits) => {
165
- const resolvedLimits = resolveHydrationLimits(limits);
166
- const seen = /* @__PURE__ */ new Set();
167
- let nodes = 0;
168
- const encode = (entry, path, depth) => {
169
- nodes += 1;
170
- if (nodes > resolvedLimits.maxNodes) throw error(path, `Maximum node count of ${String(resolvedLimits.maxNodes)} exceeded.`);
171
- if (depth > maxDepth) throw error(path, `Maximum depth of ${String(maxDepth)} exceeded.`);
172
- if (entry === null) return ["null"];
173
- const type = typeof entry;
174
- if (typeof entry === "string" || typeof entry === "boolean") {
175
- if (typeof entry === "string" && entry.length > resolvedLimits.maxStringLength) throw error(path, `Maximum string length of ${String(resolvedLimits.maxStringLength)} exceeded.`);
176
- return [typeof entry, entry];
177
- }
178
- if (type === "number") {
179
- if (Number.isNaN(entry)) return ["number", "NaN"];
180
- if (entry === Infinity) return ["number", "Infinity"];
181
- if (entry === -Infinity) return ["number", "-Infinity"];
182
- if (Object.is(entry, -0)) return ["number", "-0"];
183
- return ["number", entry];
184
- }
185
- if (type === "undefined") return ["undefined"];
186
- if (type === "bigint") {
187
- const bigint = String(entry);
188
- if (bigint.length > resolvedLimits.maxStringLength) throw error(path, `Maximum string length of ${String(resolvedLimits.maxStringLength)} exceeded.`);
189
- return ["bigint", bigint];
190
- }
191
- if (type === "function" || type === "symbol") throw error(path, `Received unsupported '${type}' value.`);
192
- if (!entry || typeof entry !== "object") throw error(path, `Received unsupported '${type}' value.`);
193
- if (isNodeRef(entry)) {
194
- const id = getNodeRefId(entry);
195
- if (id.length > resolvedLimits.maxStringLength) throw error(path, `Maximum string length of ${String(resolvedLimits.maxStringLength)} exceeded.`);
196
- return ["ref", id];
197
- }
198
- if (isDate$1(entry)) {
199
- if (Number.isNaN(entry.getTime())) throw error(path, `Received an invalid Date.`);
200
- return ["date", entry.toISOString()];
201
- }
202
- if (seen.has(entry)) throw error(path, `Circular references are not supported.`);
203
- seen.add(entry);
204
- try {
205
- if (Array.isArray(entry)) {
206
- if (!isDenseArray(entry)) throw error(path, `Sparse arrays are not supported.`);
207
- if (entry.length > resolvedLimits.maxCollectionLength) throw error(path, `Maximum collection length of ${String(resolvedLimits.maxCollectionLength)} exceeded.`);
208
- return ["array", entry.map((item, index) => encode(item, `${path}[${String(index)}]`, depth + 1))];
209
- }
210
- if (!isPlainObject(entry)) throw error(path, `Received unsupported '${entry.constructor?.name ?? "object"}' value.`);
211
- if (Object.getOwnPropertySymbols(entry).length > 0) throw error(path, `Received an unsupported symbol-keyed property.`);
212
- const entries = Object.entries(entry);
213
- if (entries.length > resolvedLimits.maxCollectionLength) throw error(path, `Maximum collection length of ${String(resolvedLimits.maxCollectionLength)} exceeded.`);
214
- return ["object", entries.map(([key, item]) => {
215
- if (key.length > resolvedLimits.maxStringLength) throw error(path, `Maximum string length of ${String(resolvedLimits.maxStringLength)} exceeded.`);
216
- return [key, encode(item, `${path}.${key}`, depth + 1)];
217
- })];
218
- } finally {
219
- seen.delete(entry);
220
- }
221
- };
222
- return encode(value, "data", 0);
223
- };
224
- const invalidPayload = (path) => /* @__PURE__ */ new Error(`fate: Invalid hydration payload at '${path}'.`);
225
- const tagLengths = {
226
- array: 2,
227
- bigint: 2,
228
- boolean: 2,
229
- date: 2,
230
- null: 1,
231
- number: 2,
232
- object: 2,
233
- ref: 2,
234
- string: 2,
235
- undefined: 1
236
- };
237
- const decodeHydrationValue = (value, limits) => {
238
- const resolvedLimits = resolveHydrationLimits(limits);
239
- let nodes = 0;
240
- const decode = (entry, path, depth) => {
241
- nodes += 1;
242
- if (nodes > resolvedLimits.maxNodes || depth > maxDepth || !Array.isArray(entry) || typeof entry[0] !== "string") throw invalidPayload(path);
243
- const [tag, payload] = entry;
244
- if (entry.length !== tagLengths[tag]) throw invalidPayload(path);
245
- switch (tag) {
246
- case "null": return null;
247
- case "undefined": return;
248
- case "string":
249
- if (typeof payload !== "string" || payload.length > resolvedLimits.maxStringLength) throw invalidPayload(path);
250
- return payload;
251
- case "boolean":
252
- if (typeof payload !== "boolean") throw invalidPayload(path);
253
- return payload;
254
- case "number":
255
- if (typeof payload === "number") {
256
- if (!Number.isFinite(payload) || Object.is(payload, -0)) throw invalidPayload(path);
257
- return payload;
258
- }
259
- if (payload === "NaN") return NaN;
260
- if (payload === "Infinity") return Infinity;
261
- if (payload === "-Infinity") return -Infinity;
262
- if (payload === "-0") return -0;
263
- throw invalidPayload(path);
264
- case "bigint":
265
- if (typeof payload !== "string" || payload.length > resolvedLimits.maxStringLength) throw invalidPayload(path);
266
- try {
267
- const bigint = BigInt(payload);
268
- if (String(bigint) !== payload) throw invalidPayload(path);
269
- return bigint;
270
- } catch {
271
- throw invalidPayload(path);
272
- }
273
- case "date": {
274
- if (typeof payload !== "string" || payload.length > resolvedLimits.maxStringLength) throw invalidPayload(path);
275
- const date = new Date(payload);
276
- if (Number.isNaN(date.getTime()) || date.toISOString() !== payload) throw invalidPayload(path);
277
- return date;
278
- }
279
- case "ref":
280
- if (typeof payload !== "string" || payload.length > resolvedLimits.maxStringLength) throw invalidPayload(path);
281
- return createNodeRef(payload);
282
- case "array":
283
- if (!Array.isArray(payload) || !isDenseArray(payload) || payload.length > resolvedLimits.maxCollectionLength) throw invalidPayload(path);
284
- return payload.map((item, index) => decode(item, `${path}[${String(index)}]`, depth + 1));
285
- case "object": {
286
- if (!Array.isArray(payload) || payload.length > resolvedLimits.maxCollectionLength) throw invalidPayload(path);
287
- const result = Object.create(null);
288
- for (const item of payload) {
289
- if (!Array.isArray(item) || item.length !== 2 || typeof item[0] !== "string" || item[0].length > resolvedLimits.maxStringLength) throw invalidPayload(path);
290
- if (item[0] in result) throw invalidPayload(path);
291
- result[item[0]] = decode(item[1], `${path}.${item[0]}`, depth + 1);
292
- }
293
- return result;
294
- }
295
- default: throw invalidPayload(path);
296
- }
297
- };
298
- return decode(value, "data", 0);
299
- };
300
- const isRecord$1 = (value) => {
301
- if (!value || typeof value !== "object" || Array.isArray(value) || isNodeRef(value)) return false;
302
- const prototype = Object.getPrototypeOf(value);
303
- return prototype === Object.prototype || prototype === null;
304
- };
305
- const isStringArray = (value) => Array.isArray(value) && value.every((entry) => typeof entry === "string");
306
- const hasDuplicates = (values) => new Set(values).size !== values.length;
307
- const isUniqueStringArray = (value) => isStringArray(value) && !hasDuplicates(value);
308
- const hasOnlyKeys = (value, keys) => Object.keys(value).every((key) => keys.has(key));
309
- const listKeys = /* @__PURE__ */ new Set([
310
- "backwardPageLimit",
311
- "cursors",
312
- "forwardPageLimit",
313
- "ids",
314
- "liveAfterIds",
315
- "liveBeforeIds",
316
- "pagination",
317
- "pendingAfterIds",
318
- "pendingBeforeIds"
319
- ]);
320
- const paginationKeys = /* @__PURE__ */ new Set([
321
- "hasNext",
322
- "hasPrevious",
323
- "nextCursor",
324
- "previousCursor"
325
- ]);
326
- const clientStateKeys = /* @__PURE__ */ new Set([
327
- "rootLists",
328
- "rootRequests",
329
- "store"
330
- ]);
331
- const storeStateKeys = /* @__PURE__ */ new Set([
332
- "coverage",
333
- "lists",
334
- "records"
335
- ]);
336
- const isEntry = (value) => Array.isArray(value) && value.length === 2 && typeof value[0] === "string";
337
- const isOptionalLimit = (value) => value === void 0 || typeof value === "number" && Number.isInteger(value) && value >= 0;
338
- 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");
339
- const isList = (value) => {
340
- 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;
341
- 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));
342
- };
343
- const isFieldPath = (value) => value.length > 0 && value.split(".").every((segment) => segment.length > 0);
344
- const isFieldPathArray = (value) => isUniqueStringArray(value) && value.every(isFieldPath);
345
- const hasDuplicateEntries = (entries) => hasDuplicates(entries.map((entry) => entry[0]));
346
- const decodeClientHydrationState = (value, limits) => {
347
- const decoded = decodeHydrationValue(value, limits);
348
- 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");
349
- 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");
350
- 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");
351
- return decoded;
352
- };
353
- //#endregion
354
- //#region src/mask.ts
355
- function emptyMask() {
356
- return {
357
- all: false,
358
- children: /* @__PURE__ */ new Map()
359
- };
360
- }
361
- function cloneMask(m) {
362
- const clone = {
363
- all: m.all,
364
- children: /* @__PURE__ */ new Map()
365
- };
366
- for (const [key, value] of m.children) clone.children.set(key, cloneMask(value));
367
- return clone;
368
- }
369
- function addPath(mask, path) {
370
- if (mask.all) return;
371
- const parts = path.split(".");
372
- let current = mask;
373
- for (let i = 0; i < parts.length; i++) {
374
- const seg = parts[i];
375
- let child = current.children.get(seg);
376
- if (!child) {
377
- child = emptyMask();
378
- current.children.set(seg, child);
379
- }
380
- current = child;
381
- }
382
- current.all = true;
383
- current.children.clear();
384
- }
385
- function union(into, b) {
386
- if (into.all || b.all) {
387
- into.all = true;
388
- into.children.clear();
389
- return;
390
- }
391
- for (const [key, child] of b.children) {
392
- const exist = into.children.get(key);
393
- if (!exist) into.children.set(key, cloneMask(child));
394
- else union(exist, child);
395
- }
396
- }
397
- function fromPaths(paths) {
398
- const mask = emptyMask();
399
- for (const path of paths) addPath(mask, path);
400
- return mask;
401
- }
402
- function toPaths(mask) {
403
- const paths = [];
404
- const visit = (current, prefix) => {
405
- if (current.all) {
406
- if (prefix) paths.push(prefix);
407
- return;
408
- }
409
- for (const [key, child] of current.children) visit(child, prefix ? `${prefix}.${key}` : key);
410
- };
411
- visit(mask, null);
412
- return paths;
413
- }
414
- function isCovered(mask, path) {
415
- if (mask.all) return true;
416
- const parts = path.split(".");
417
- let current = mask;
418
- for (let i = 0; i < parts.length; i++) {
419
- if (!current) return false;
420
- if (current.all) return true;
421
- current = current.children.get(parts[i]);
422
- }
423
- return !!current && (current.all || current.children.size === 0);
424
- }
425
- function diffPaths(paths, mask) {
426
- const missing = /* @__PURE__ */ new Set();
427
- for (const path of paths) if (!isCovered(mask, path)) missing.add(path);
428
- return missing;
429
- }
430
- function intersects(a, b) {
431
- if (a.all || b.all) return true;
432
- for (const [key, child] of a.children) {
433
- const otherChild = b.children.get(key);
434
- if (!otherChild) continue;
435
- if (child.all || otherChild.all) return true;
436
- if (intersects(child, otherChild)) return true;
437
- }
438
- return false;
439
- }
440
- //#endregion
441
- //#region ../../node_modules/.pnpm/@trpc+server@11.18.0_typescript@6.0.3/node_modules/@trpc/server/dist/getErrorShape-BPSzUA7W.mjs
442
- var __create = Object.create;
443
- var __defProp = Object.defineProperty;
444
- var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
445
- var __getOwnPropNames = Object.getOwnPropertyNames;
446
- var __getProtoOf = Object.getPrototypeOf;
447
- var __hasOwnProp = Object.prototype.hasOwnProperty;
448
- var __commonJS = (cb, mod) => function() {
449
- return mod || (0, cb[__getOwnPropNames(cb)[0]])((mod = { exports: {} }).exports, mod), mod.exports;
450
- };
451
- var __copyProps = (to, from, except, desc) => {
452
- if (from && typeof from === "object" || typeof from === "function") for (var keys = __getOwnPropNames(from), i = 0, n = keys.length, key; i < n; i++) {
453
- key = keys[i];
454
- if (!__hasOwnProp.call(to, key) && key !== except) __defProp(to, key, {
455
- get: ((k) => from[k]).bind(null, key),
456
- enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable
457
- });
458
- }
459
- return to;
460
- };
461
- var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", {
462
- value: mod,
463
- enumerable: true
464
- }) : target, mod));
465
- const JSONRPC2_TO_HTTP_CODE = {
466
- PARSE_ERROR: 400,
467
- BAD_REQUEST: 400,
468
- UNAUTHORIZED: 401,
469
- PAYMENT_REQUIRED: 402,
470
- FORBIDDEN: 403,
471
- NOT_FOUND: 404,
472
- METHOD_NOT_SUPPORTED: 405,
473
- TIMEOUT: 408,
474
- CONFLICT: 409,
475
- PRECONDITION_FAILED: 412,
476
- PAYLOAD_TOO_LARGE: 413,
477
- UNSUPPORTED_MEDIA_TYPE: 415,
478
- UNPROCESSABLE_CONTENT: 422,
479
- PRECONDITION_REQUIRED: 428,
480
- TOO_MANY_REQUESTS: 429,
481
- CLIENT_CLOSED_REQUEST: 499,
482
- INTERNAL_SERVER_ERROR: 500,
483
- NOT_IMPLEMENTED: 501,
484
- BAD_GATEWAY: 502,
485
- SERVICE_UNAVAILABLE: 503,
486
- GATEWAY_TIMEOUT: 504
487
- };
488
- function getStatusCodeFromKey(code) {
489
- var _JSONRPC2_TO_HTTP_COD;
490
- return (_JSONRPC2_TO_HTTP_COD = JSONRPC2_TO_HTTP_CODE[code]) !== null && _JSONRPC2_TO_HTTP_COD !== void 0 ? _JSONRPC2_TO_HTTP_COD : 500;
491
- }
492
- function getHTTPStatusCodeFromError(error) {
493
- return getStatusCodeFromKey(error.code);
494
- }
495
- var require_typeof = __commonJS({ "../../node_modules/.pnpm/@oxc-project+runtime@0.72.2/node_modules/@oxc-project/runtime/src/helpers/typeof.js"(exports, module) {
496
- function _typeof$2(o) {
497
- "@babel/helpers - typeof";
498
- return module.exports = _typeof$2 = "function" == typeof Symbol && "symbol" == typeof Symbol.iterator ? function(o$1) {
499
- return typeof o$1;
500
- } : function(o$1) {
501
- return o$1 && "function" == typeof Symbol && o$1.constructor === Symbol && o$1 !== Symbol.prototype ? "symbol" : typeof o$1;
502
- }, module.exports.__esModule = true, module.exports["default"] = module.exports, _typeof$2(o);
503
- }
504
- module.exports = _typeof$2, module.exports.__esModule = true, module.exports["default"] = module.exports;
505
- } });
506
- var require_toPrimitive = __commonJS({ "../../node_modules/.pnpm/@oxc-project+runtime@0.72.2/node_modules/@oxc-project/runtime/src/helpers/toPrimitive.js"(exports, module) {
507
- var _typeof$1 = require_typeof()["default"];
508
- function toPrimitive$1(t, r) {
509
- if ("object" != _typeof$1(t) || !t) return t;
510
- var e = t[Symbol.toPrimitive];
511
- if (void 0 !== e) {
512
- var i = e.call(t, r || "default");
513
- if ("object" != _typeof$1(i)) return i;
514
- throw new TypeError("@@toPrimitive must return a primitive value.");
515
- }
516
- return ("string" === r ? String : Number)(t);
517
- }
518
- module.exports = toPrimitive$1, module.exports.__esModule = true, module.exports["default"] = module.exports;
519
- } });
520
- var require_toPropertyKey = __commonJS({ "../../node_modules/.pnpm/@oxc-project+runtime@0.72.2/node_modules/@oxc-project/runtime/src/helpers/toPropertyKey.js"(exports, module) {
521
- var _typeof = require_typeof()["default"];
522
- var toPrimitive = require_toPrimitive();
523
- function toPropertyKey$1(t) {
524
- var i = toPrimitive(t, "string");
525
- return "symbol" == _typeof(i) ? i : i + "";
526
- }
527
- module.exports = toPropertyKey$1, module.exports.__esModule = true, module.exports["default"] = module.exports;
528
- } });
529
- var require_defineProperty = __commonJS({ "../../node_modules/.pnpm/@oxc-project+runtime@0.72.2/node_modules/@oxc-project/runtime/src/helpers/defineProperty.js"(exports, module) {
530
- var toPropertyKey = require_toPropertyKey();
531
- function _defineProperty(e, r, t) {
532
- return (r = toPropertyKey(r)) in e ? Object.defineProperty(e, r, {
533
- value: t,
534
- enumerable: !0,
535
- configurable: !0,
536
- writable: !0
537
- }) : e[r] = t, e;
538
- }
539
- module.exports = _defineProperty, module.exports.__esModule = true, module.exports["default"] = module.exports;
540
- } });
541
- __toESM(__commonJS({ "../../node_modules/.pnpm/@oxc-project+runtime@0.72.2/node_modules/@oxc-project/runtime/src/helpers/objectSpread2.js"(exports, module) {
542
- var defineProperty = require_defineProperty();
543
- function ownKeys(e, r) {
544
- var t = Object.keys(e);
545
- if (Object.getOwnPropertySymbols) {
546
- var o = Object.getOwnPropertySymbols(e);
547
- r && (o = o.filter(function(r$1) {
548
- return Object.getOwnPropertyDescriptor(e, r$1).enumerable;
549
- })), t.push.apply(t, o);
550
- }
551
- return t;
552
- }
553
- function _objectSpread2(e) {
554
- for (var r = 1; r < arguments.length; r++) {
555
- var t = null != arguments[r] ? arguments[r] : {};
556
- r % 2 ? ownKeys(Object(t), !0).forEach(function(r$1) {
557
- defineProperty(e, r$1, t[r$1]);
558
- }) : Object.getOwnPropertyDescriptors ? Object.defineProperties(e, Object.getOwnPropertyDescriptors(t)) : ownKeys(Object(t)).forEach(function(r$1) {
559
- Object.defineProperty(e, r$1, Object.getOwnPropertyDescriptor(t, r$1));
560
- });
561
- }
562
- return e;
563
- }
564
- module.exports = _objectSpread2, module.exports.__esModule = true, module.exports["default"] = module.exports;
565
- } })(), 1);
566
- //#endregion
567
- //#region src/view.ts
568
- /**
569
- * Collects all view payloads that apply to the given ref.
570
- */
571
- const getViewPayloads = (view, ref) => {
572
- const result = [];
573
- for (const [key, value] of Object.entries(view)) if (isViewTag(key) && (!ref || ref[ViewsTag]?.has(key))) result.push(value);
574
- return result;
575
- };
576
- /**
577
- * Returns the set of view tags defined on a view composition.
578
- */
579
- const getViewNames = (view) => {
580
- const result = /* @__PURE__ */ new Set();
581
- for (const key of Object.keys(view)) if (isViewTag(key)) result.add(key);
582
- return result;
583
- };
584
- /**
585
- * Extracts view tags from a nested selection object.
586
- */
587
- const getSelectionViewNames = (selection) => {
588
- return getViewNames(selection);
589
- };
590
- let id = 0;
591
- const importMetaEnvironment = import.meta.env;
592
- const isDevelopment = typeof importMetaEnvironment?.DEV === "boolean" ? importMetaEnvironment.DEV : typeof process !== "undefined" ? process.env.NODE_ENV !== "production" : false;
593
- let viewModulePath = null;
594
- const getStableId = () => {
595
- if (isDevelopment) try {
596
- if (viewModulePath == null) viewModulePath = new URL(import.meta.url).pathname;
597
- const stack = (/* @__PURE__ */ new Error()).stack?.split("\n");
598
- if (stack) for (let i = 1; i < stack.length; i++) {
599
- const match = stack[i].trim().match(/\(?([^()]+):(\d+):(\d+)\)?$/);
600
- if (!match) continue;
601
- const [, source, line, column] = match;
602
- if (!source.includes(viewModulePath)) {
603
- const file = source.startsWith("at ") ? source.slice(3) : source;
604
- return `${/^[A-Za-z][\d+.A-Za-z-]*:/.test(file) ? new URL(file).pathname : file}:${line}:${column}`;
605
- }
606
- }
607
- } catch {}
608
- return String(id++);
609
- };
610
- /**
611
- * Creates a reusable view for an object using the declared selection.
612
- *
613
- * @example
614
- * const PostView = view<Post>()({
615
- * id: true,
616
- * title: true,
617
- * });
618
- */
619
- function view() {
620
- const viewId = getStableId();
621
- return (select) => {
622
- const payload = Object.freeze({
623
- select,
624
- [ViewKind]: true
625
- });
626
- const viewComposition = Object.defineProperty({}, getViewTag(viewId), {
627
- configurable: false,
628
- enumerable: true,
629
- value: payload,
630
- writable: false
631
- });
632
- return Object.freeze(viewComposition);
633
- };
634
- }
635
- //#endregion
636
- //#region src/ref.ts
637
- /**
638
- * Builds the canonical cache ID for an entity.
639
- */
640
- const toEntityId = (type, rawId) => `${type}:${String(rawId)}`;
641
- /**
642
- * Splits a cache entity ID back into its type and raw identifier.
643
- */
644
- function parseEntityId(id) {
645
- const idx = id.indexOf(":");
646
- return idx < 0 ? {
647
- id,
648
- type: ""
649
- } : {
650
- id: id.slice(idx + 1),
651
- type: id.slice(0, idx)
652
- };
653
- }
654
- /**
655
- * Attaches view tags to a ref without leaking the symbol.
656
- */
657
- function assignViewTag(target, value) {
658
- Object.defineProperty(target, ViewsTag, {
659
- configurable: false,
660
- enumerable: false,
661
- value,
662
- writable: false
663
- });
664
- }
665
- const getRootViewNames = (view) => {
666
- const names = new Set(getViewNames(view));
667
- const payloads = getViewPayloads(view, null);
668
- for (const payload of payloads) for (const name of getSelectionViewNames(payload.select)) names.add(name);
669
- return names;
670
- };
671
- function createRefWithViewNames(__typename, id, names) {
672
- const ref = {
673
- __typename,
674
- id
675
- };
676
- assignViewTag(ref, names);
677
- return Object.freeze(ref);
678
- }
679
- //#endregion
680
- //#region src/selection.ts
681
- const isConnectionSelection = (value) => isRecord(value.items) && "node" in value.items;
682
- /**
683
- * Flattens a view into a `SelectionPlan`, expanding composed views and
684
- * partitioning nested args so the client can fetch exactly what is declared.
685
- */
686
- const getSelectionPlan = (viewComposition, ref) => {
687
- const args = /* @__PURE__ */ new Map();
688
- const live = /* @__PURE__ */ new Map();
689
- const paths = /* @__PURE__ */ new Set();
690
- const assignArgs = (path, value, ignoreKeys) => {
691
- const hash = hashArgs(value, { ignoreKeys });
692
- args.set(path, {
693
- hash,
694
- ignoreKeys,
695
- value
696
- });
697
- };
698
- const walk = (selection, prefix, context = "default") => {
699
- if (prefix === null && context !== "connection" && isConnectionSelection(selection)) {
700
- if (selection.live && isRecord(selection.live)) live.set("", {
701
- append: selection.live.append === "visible" ? "visible" : "edge",
702
- prepend: selection.live.prepend === "visible" ? "visible" : "edge"
703
- });
704
- if (selection.args && isRecord(selection.args)) {
705
- const clonedArgs = cloneArgs(selection.args, "args");
706
- const ignoreKeys = isRecord(selection.items) && isRecord(selection.items.node) ? paginationArgKeys : void 0;
707
- assignArgs("", clonedArgs, ignoreKeys);
708
- }
709
- const { args: _args, live: _live, ...withoutArgs } = selection;
710
- walk(withoutArgs, prefix, "connection");
711
- return;
712
- }
713
- for (const [key, value] of Object.entries(selection)) {
714
- const valueType = typeof value;
715
- const path = prefix ? `${prefix}.${key}` : key;
716
- if (context === "connection") {
717
- if (key === "args" || key === "live" || key === "pagination") continue;
718
- if (key === "items" && isRecord(value)) {
719
- if (isRecord(value.node)) walk(value.node, prefix);
720
- continue;
721
- }
722
- }
723
- if (valueType === "boolean") {
724
- if (value) paths.add(path);
725
- continue;
726
- }
727
- if (isDeferredSelection(value)) continue;
728
- if (isViewTag(key)) {
729
- if (!ref || ref[ViewsTag] && ref[ViewsTag].has(key)) walk(value.select, prefix);
730
- continue;
731
- }
732
- if (isRecord(value)) {
733
- const selectionObject = value;
734
- if (isConnectionSelection(selectionObject)) {
735
- if (selectionObject.live && isRecord(selectionObject.live)) live.set(path, {
736
- append: selectionObject.live.append === "visible" ? "visible" : "edge",
737
- prepend: selectionObject.live.prepend === "visible" ? "visible" : "edge"
738
- });
739
- if (selectionObject.args && isRecord(selectionObject.args)) {
740
- const clonedArgs = cloneArgs(selectionObject.args, path);
741
- const ignoreKeys = isRecord(selectionObject.items) && isRecord(selectionObject.items.node) ? paginationArgKeys : void 0;
742
- assignArgs(path, clonedArgs, ignoreKeys);
743
- }
744
- const { args: _ignored, live: _live, ...withoutArgs } = selectionObject;
745
- walk(withoutArgs, path, "connection");
746
- continue;
747
- }
748
- const hasArgs = selectionObject.args && isRecord(selectionObject.args);
749
- let selectionWithoutArgs = selectionObject;
750
- if (hasArgs) {
751
- const clonedArgs = cloneArgs(selectionObject.args, path);
752
- const ignoreKeys = isRecord(selectionObject.items) && isRecord(selectionObject.items?.node) ? paginationArgKeys : void 0;
753
- assignArgs(path, clonedArgs, ignoreKeys);
754
- const { args: _args, ...rest } = selectionObject;
755
- selectionWithoutArgs = rest;
756
- }
757
- if (Object.keys(selectionWithoutArgs).length > 0) walk(selectionWithoutArgs, path);
758
- else if (hasArgs) paths.add(path);
759
- }
760
- }
761
- };
762
- const payloads = getViewPayloads(viewComposition, ref);
763
- if (payloads.length === 0 && isRecord(viewComposition)) walk(viewComposition, null);
764
- else for (const payload of payloads) walk(payload.select, null);
765
- return {
766
- args,
767
- live,
768
- paths
769
- };
770
- };
771
- const getDeferredSelectionPlan = (field, selection) => {
772
- return getSelectionPlan({ [field]: selection }, null);
773
- };
774
- //#endregion
775
- //#region src/mutation.ts
776
- /**
777
- * Defines a mutation for a given entity type, preserving the input and output
778
- * types for transports.
779
- */
780
- function mutation(entity) {
781
- return Object.freeze({
782
- entity,
783
- [MutationKind]: true
784
- });
785
- }
786
- const collectImplicitSelectedPaths = (value) => {
787
- const paths = /* @__PURE__ */ new Set();
788
- const walk = (current, prefix) => {
789
- if (!current || typeof current !== "object") {
790
- if (prefix) paths.add(prefix);
791
- return;
792
- }
793
- if (Array.isArray(current)) {
794
- if (prefix) paths.add(prefix);
795
- for (const child of current) walk(child, prefix);
796
- return;
797
- }
798
- for (const [key, child] of Object.entries(current)) {
799
- const next = prefix ? `${prefix}.${key}` : key;
800
- paths.add(next);
801
- walk(child, next);
802
- }
803
- };
804
- walk(value, null);
805
- return paths;
806
- };
807
- const maybeGetId = (getId, input) => {
808
- try {
809
- return getId(input);
810
- } catch {
811
- return null;
812
- }
813
- };
814
- const emptySet$1 = /* @__PURE__ */ new Set();
815
- /**
816
- * Binds a mutation definition to a `FateClient`, wiring up optimistic updates,
817
- * cache writes, and error handling.
818
- */
819
- function wrapMutation(client, identifier) {
820
- const config = client.getTypeConfig(identifier.entity);
821
- return async ({ args, delete: deleteRecord, input, insert = "after", optimistic, view }) => {
822
- const id = maybeGetId(config.getId, input);
823
- const plan = view ? getSelectionPlan(view, null) : void 0;
824
- const viewSelection = plan?.paths;
825
- const optimisticRecord = optimistic ? id != null ? {
826
- id,
827
- ...optimistic
828
- } : optimistic : void 0;
829
- const optimisticRecordId = optimisticRecord !== void 0 ? maybeGetId(config.getId, optimisticRecord) : null;
830
- const optimisticEntityId = id != null ? toEntityId(identifier.entity, id) : optimisticRecordId != null ? toEntityId(identifier.entity, optimisticRecordId) : null;
831
- const snapshots = /* @__PURE__ */ new Map();
832
- const listSnapshots = deleteRecord || optimisticRecord ? /* @__PURE__ */ new Map() : void 0;
833
- const optimisticSelection = optimisticRecord ? collectImplicitSelectedPaths(optimisticRecord) : void 0;
834
- const selection = viewSelection || optimisticSelection ? /* @__PURE__ */ new Set([...viewSelection ? [...viewSelection] : [], ...optimisticSelection ? [...optimisticSelection] : []]) : /* @__PURE__ */ new Set();
835
- if (deleteRecord && id == null) throw new Error(`fate: Mutation '${identifier.key}' requires an 'id' to delete.`);
836
- const optimisticToken = optimisticEntityId ? client.registerOptimisticUpdate(optimisticEntityId, optimisticSelection ?? emptySet$1) : null;
837
- if (optimisticRecord && optimisticEntityId) client.write(identifier.entity, optimisticRecord, optimisticSelection ?? emptySet$1, snapshots, plan, null, null, insert, listSnapshots);
838
- if (deleteRecord && id != null) client.deleteRecord(identifier.entity, id, snapshots, listSnapshots);
839
- const performMutation = async () => {
840
- try {
841
- const result = await client.executeMutation(identifier.key, input, selection, {
842
- args,
843
- plan
844
- });
845
- if (result && typeof result === "object" && (!deleteRecord || Boolean(view))) {
846
- const select = collectImplicitSelectedPaths(result);
847
- const pendingMask = optimisticEntityId ? client.getPendingOptimisticMask(optimisticEntityId, { excludeToken: optimisticToken }) : null;
848
- const filteredSelection = optimisticEntityId ? client.filterSelectionForPendingOptimistics(optimisticEntityId, select, { excludeToken: optimisticToken }) : select;
849
- client.write(identifier.entity, result, filteredSelection, void 0, plan, null, pendingMask, insert);
850
- if (deleteRecord && id != null) client.deleteRecord(identifier.entity, id);
851
- const resultId = maybeGetId(config.getId, result);
852
- if (optimisticEntityId && resultId != null) client.resolveOptimisticEntity(optimisticEntityId, toEntityId(identifier.entity, resultId));
853
- if (optimisticRecordId != null && resultId != null && optimisticRecordId !== resultId) client.deleteRecord(identifier.entity, optimisticRecordId);
854
- }
855
- return {
856
- error: void 0,
857
- result
858
- };
859
- } catch (error) {
860
- client.clearOptimisticUpdate(optimisticToken);
861
- if (snapshots.size > 0) for (const [id, snapshot] of snapshots) client.restore(id, snapshot);
862
- if (listSnapshots && listSnapshots.size > 0) for (const [name, list] of listSnapshots) client.restoreList(name, list);
863
- if (error instanceof Error) {
864
- const statusCode = getErrorStatusCode(error);
865
- if ((statusCode ? categorizeHTTPErrorStatus(statusCode) : "boundary") === "boundary") throw error;
866
- return {
867
- error,
868
- result: void 0
869
- };
870
- } else throw new Error(`fate: Mutation '${identifier.key}' failed.`);
871
- } finally {
872
- client.clearOptimisticUpdate(optimisticToken);
873
- }
874
- };
875
- const mutationPromise = client.trackPendingRequest(performMutation);
876
- if (optimisticEntityId) client.registerPendingOptimisticMutation(optimisticEntityId, mutationPromise);
877
- return mutationPromise;
878
- };
879
- }
880
- const getErrorStatusCode = (error) => {
881
- if (error instanceof FateRequestError) return error.status;
882
- const { data } = error;
883
- if (!data || typeof data !== "object") return;
884
- return getHTTPStatusCodeFromError(data);
885
- };
886
- const categorizeHTTPErrorStatus = (statusCode) => {
887
- switch (statusCode) {
888
- case 400:
889
- case 402:
890
- case 404:
891
- case 408:
892
- case 409:
893
- case 412:
894
- case 413:
895
- case 415:
896
- case 422:
897
- case 429:
898
- case 499: return "callSite";
899
- default: return "boundary";
900
- }
901
- };
902
- //#endregion
903
80
  //#region src/operation-lifetime.ts
904
81
  var OperationLifetime = class {
905
82
  constructor(releaseBufferSize) {
@@ -928,6 +105,15 @@ var OperationLifetime = class {
928
105
  getDescriptors() {
929
106
  return [...this.operations.values()].map((operation) => operation.descriptor);
930
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
+ }
931
117
  release(key) {
932
118
  const operation = this.operations.get(key);
933
119
  if (!operation) return false;
@@ -956,554 +142,6 @@ var OperationLifetime = class {
956
142
  }
957
143
  };
958
144
  //#endregion
959
- //#region src/store.ts
960
- const listKeySeparator = " __fate__ ";
961
- const decodeListKeyPart = (part) => {
962
- try {
963
- return decodeURIComponent(part);
964
- } catch {
965
- return part;
966
- }
967
- };
968
- const encodeListKeyPart = (part) => encodeURIComponent(part);
969
- const getListKey = (ownerId, field, hash = "default") => `${encodeListKeyPart(ownerId)}${listKeySeparator}${encodeListKeyPart(field)}${listKeySeparator}${encodeListKeyPart(hash)}`;
970
- const getOwnerFieldKey = (ownerId, field) => JSON.stringify([ownerId, field]);
971
- const parseListKey = (key) => {
972
- const parts = key.split(listKeySeparator);
973
- if (parts.length !== 3) return null;
974
- const [ownerId, field] = parts;
975
- return {
976
- field: decodeListKeyPart(field),
977
- ownerId: decodeListKeyPart(ownerId)
978
- };
979
- };
980
- const cloneValue = (value) => {
981
- if (Array.isArray(value)) return value.map(cloneValue);
982
- if (isNodeRef(value)) return value;
983
- if (value != null && typeof value === "object") {
984
- const result = {};
985
- for (const [key, record] of Object.entries(value)) result[key] = cloneValue(record);
986
- return result;
987
- }
988
- return value;
989
- };
990
- const isPlainRecord = (value) => {
991
- if (value == null || typeof value !== "object" || Array.isArray(value) || isNodeRef(value)) return false;
992
- const prototype = Object.getPrototypeOf(value);
993
- return prototype === Object.prototype || prototype === null;
994
- };
995
- const isDate = (value) => value != null && typeof value === "object" && Object.prototype.toString.call(value) === "[object Date]" && typeof value.getTime === "function";
996
- const mergePreservingExisting = (incoming, existing) => {
997
- if (!isPlainRecord(incoming) || !isPlainRecord(existing)) return existing;
998
- const result = Object.create(null);
999
- for (const [key, value] of Object.entries(incoming)) Object.defineProperty(result, key, {
1000
- configurable: true,
1001
- enumerable: true,
1002
- value,
1003
- writable: true
1004
- });
1005
- for (const [key, value] of Object.entries(existing)) Object.defineProperty(result, key, {
1006
- configurable: true,
1007
- enumerable: true,
1008
- value: Object.hasOwn(result, key) ? mergePreservingExisting(result[key], value) : value,
1009
- writable: true
1010
- });
1011
- return result;
1012
- };
1013
- const areHydrationValuesEqual = (left, right) => {
1014
- if (Object.is(left, right)) return true;
1015
- if (isNodeRef(left) || isNodeRef(right)) return isNodeRef(left) && isNodeRef(right) && getNodeRefId(left) === getNodeRefId(right);
1016
- if (isDate(left) || isDate(right)) return isDate(left) && isDate(right) && left.getTime() === right.getTime();
1017
- 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]));
1018
- if (!isPlainRecord(left) || !isPlainRecord(right)) return false;
1019
- const leftKeys = Object.keys(left);
1020
- const rightKeys = Object.keys(right);
1021
- return leftKeys.length === rightKeys.length && leftKeys.every((key) => Object.hasOwn(right, key) && areHydrationValuesEqual(left[key], right[key]));
1022
- };
1023
- const areMasksEqual = (left, right) => {
1024
- if (!left || !right) return left === right;
1025
- const leftPaths = toPaths(left).sort();
1026
- const rightPaths = toPaths(right).sort();
1027
- return leftPaths.length === rightPaths.length && leftPaths.every((path, index) => path === rightPaths[index]);
1028
- };
1029
- const emptyFunction = () => {};
1030
- var Store = class {
1031
- constructor() {
1032
- this.coverage = /* @__PURE__ */ new Map();
1033
- this.listKeysByOwnerField = /* @__PURE__ */ new Map();
1034
- this.listKeysByReferencedEntity = /* @__PURE__ */ new Map();
1035
- this.lists = /* @__PURE__ */ new Map();
1036
- this.recordReferenceFields = /* @__PURE__ */ new Map();
1037
- this.recordReferencesByTarget = /* @__PURE__ */ new Map();
1038
- this.records = /* @__PURE__ */ new Map();
1039
- this.subscriptions = /* @__PURE__ */ new Map();
1040
- this.listSubscriptions = /* @__PURE__ */ new Map();
1041
- }
1042
- dehydrate() {
1043
- return {
1044
- coverage: [...this.coverage].map(([id, mask]) => [id, toPaths(mask)]),
1045
- lists: [...this.lists],
1046
- records: [...this.records]
1047
- };
1048
- }
1049
- hydrate(state, mode, options = {}) {
1050
- const previousCoverage = this.coverage;
1051
- const previousLists = this.lists;
1052
- const previousRecords = this.records;
1053
- const coverage = mode === "replace" ? /* @__PURE__ */ new Map() : new Map([...previousCoverage].map(([id, mask]) => [id, cloneMask(mask)]));
1054
- const lists = mode === "replace" ? /* @__PURE__ */ new Map() : new Map(previousLists);
1055
- const records = mode === "replace" ? /* @__PURE__ */ new Map() : new Map(previousRecords);
1056
- for (const [id, incoming] of state.records) {
1057
- const previous = records.get(id);
1058
- const next = previous && mode === "preserve-existing" ? mergePreservingExisting(incoming, previous) : incoming;
1059
- records.set(id, previous && areHydrationValuesEqual(previous, next) ? previous : next);
1060
- }
1061
- for (const [id, paths] of state.coverage) {
1062
- const incoming = fromPaths(paths);
1063
- const previous = coverage.get(id);
1064
- if (previous && mode === "preserve-existing") union(previous, incoming);
1065
- else coverage.set(id, incoming);
1066
- }
1067
- for (const [key, list] of state.lists) {
1068
- const previous = lists.get(key);
1069
- if (mode === "replace" || !previous) lists.set(key, previous && areHydrationValuesEqual(previous, list) ? previous : list);
1070
- }
1071
- this.coverage = coverage;
1072
- this.lists = lists;
1073
- this.records = records;
1074
- this.rebuildIndexes();
1075
- const changedRecordIds = /* @__PURE__ */ new Set();
1076
- 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);
1077
- const changedListKeys = /* @__PURE__ */ new Set();
1078
- for (const key of /* @__PURE__ */ new Set([...previousLists.keys(), ...lists.keys()])) if (!areHydrationValuesEqual(previousLists.get(key), lists.get(key))) changedListKeys.add(key);
1079
- const notify = () => {
1080
- for (const id of changedRecordIds) this.notify(id);
1081
- for (const key of changedListKeys) this.notifyListSubscribers(key);
1082
- };
1083
- if (!options.deferNotifications) notify();
1084
- return notify;
1085
- }
1086
- rebuildIndexes() {
1087
- this.listKeysByOwnerField.clear();
1088
- this.listKeysByReferencedEntity.clear();
1089
- this.recordReferenceFields.clear();
1090
- this.recordReferencesByTarget.clear();
1091
- for (const [id, record] of this.records) this.addRecordReferenceIndexes(id, record);
1092
- for (const [key, list] of this.lists) this.addListIndexes(key, list);
1093
- }
1094
- read(id) {
1095
- return this.records.get(id);
1096
- }
1097
- merge(id, partial, paths) {
1098
- const changedPaths = this.mergeInternal(id, partial, paths);
1099
- if (changedPaths) this.notify(id, changedPaths);
1100
- }
1101
- mergeInternal(id, partial, paths) {
1102
- const previous = this.records.get(id);
1103
- const changedPaths = /* @__PURE__ */ new Set();
1104
- let mask = this.coverage.get(id);
1105
- if (!mask) {
1106
- mask = emptyMask();
1107
- this.coverage.set(id, mask);
1108
- }
1109
- union(mask, fromPaths(paths));
1110
- if (previous) {
1111
- let hasChanges = false;
1112
- for (const [key, value] of Object.entries(partial)) if (previous[key] !== value) {
1113
- hasChanges = true;
1114
- changedPaths.add(key);
1115
- }
1116
- if (!hasChanges) return null;
1117
- const nextRecord = {
1118
- ...previous,
1119
- ...partial
1120
- };
1121
- this.records.set(id, nextRecord);
1122
- this.updateRecordReferenceIndexes(id, previous, nextRecord, changedPaths);
1123
- } else {
1124
- const nextRecord = { ...partial };
1125
- this.records.set(id, nextRecord);
1126
- this.updateRecordReferenceIndexes(id, void 0, nextRecord, Object.keys(nextRecord));
1127
- }
1128
- return changedPaths;
1129
- }
1130
- deleteRecord(id) {
1131
- const record = this.records.get(id);
1132
- if (record) this.removeRecordReferenceIndexes(id, record);
1133
- this.records.delete(id);
1134
- this.coverage.delete(id);
1135
- }
1136
- missingForSelection(id, paths) {
1137
- const requested = new Set(paths);
1138
- if (!this.records.has(id)) return requested;
1139
- const mask = this.coverage.get(id);
1140
- if (!mask) return requested;
1141
- return diffPaths(requested, mask);
1142
- }
1143
- subscribe(id, selectionOrFn, callback) {
1144
- let mask = null;
1145
- let fn = emptyFunction;
1146
- if (typeof selectionOrFn === "function") fn = selectionOrFn;
1147
- else if (callback) {
1148
- mask = selectionOrFn ? fromPaths(selectionOrFn) : null;
1149
- fn = callback;
1150
- }
1151
- let subscribers = this.subscriptions.get(id);
1152
- if (!subscribers) {
1153
- subscribers = /* @__PURE__ */ new Set();
1154
- this.subscriptions.set(id, subscribers);
1155
- }
1156
- const subscription = {
1157
- fn,
1158
- mask
1159
- };
1160
- subscribers.add(subscription);
1161
- return () => {
1162
- const set = this.subscriptions.get(id);
1163
- if (!set) return;
1164
- set.delete(subscription);
1165
- if (set.size === 0) this.subscriptions.delete(id);
1166
- };
1167
- }
1168
- notify(id, paths) {
1169
- const set = this.subscriptions.get(id);
1170
- if (!set) return;
1171
- const changedPaths = paths ? [...paths] : [];
1172
- const changedMask = changedPaths.length > 0 ? fromPaths(changedPaths) : null;
1173
- for (const { fn, mask } of set) {
1174
- if (mask && changedMask && !intersects(changedMask, mask)) continue;
1175
- try {
1176
- fn();
1177
- } catch {}
1178
- }
1179
- }
1180
- notifyListSubscribers(key) {
1181
- const set = this.listSubscriptions.get(key);
1182
- if (!set) return;
1183
- for (const fn of set) try {
1184
- fn();
1185
- } catch {}
1186
- }
1187
- getList(key) {
1188
- return this.lists.get(key)?.ids;
1189
- }
1190
- getListState(key) {
1191
- return this.lists.get(key);
1192
- }
1193
- getListsForField(ownerId, field) {
1194
- const entries = [];
1195
- const keys = this.listKeysByOwnerField.get(getOwnerFieldKey(ownerId, field));
1196
- if (!keys) return entries;
1197
- for (const key of keys) {
1198
- const list = this.lists.get(key);
1199
- if (list) entries.push([key, list]);
1200
- }
1201
- return entries;
1202
- }
1203
- setList(key, state) {
1204
- const previous = this.lists.get(key);
1205
- if (previous) this.removeListIndexes(key, previous);
1206
- this.lists.set(key, state);
1207
- this.addListIndexes(key, state);
1208
- this.notifyListSubscribers(key);
1209
- }
1210
- replaceListEntityId(previousId, nextId) {
1211
- const keys = [...this.listKeysByReferencedEntity.get(previousId) ?? []];
1212
- for (const key of keys) {
1213
- const list = this.lists.get(key);
1214
- if (!list) continue;
1215
- let changed = false;
1216
- let ids = list.ids;
1217
- let cursors = list.cursors;
1218
- if (list.ids.includes(previousId)) {
1219
- const nextIds = [];
1220
- const nextCursors = list.cursors ? [] : void 0;
1221
- const seenIds = /* @__PURE__ */ new Set();
1222
- list.ids.forEach((id, index) => {
1223
- const resolved = id === previousId ? nextId : id;
1224
- if (seenIds.has(resolved)) return;
1225
- seenIds.add(resolved);
1226
- nextIds.push(resolved);
1227
- if (nextCursors) nextCursors.push(list.cursors?.[index]);
1228
- });
1229
- changed = true;
1230
- ids = nextIds;
1231
- cursors = nextCursors;
1232
- }
1233
- const dedupe = (values) => {
1234
- if (!values || !values.includes(previousId)) return;
1235
- const seen = /* @__PURE__ */ new Set();
1236
- const next = [];
1237
- for (const value of values) {
1238
- const resolved = value === previousId ? nextId : value;
1239
- if (seen.has(resolved)) continue;
1240
- seen.add(resolved);
1241
- next.push(resolved);
1242
- }
1243
- changed = true;
1244
- return next;
1245
- };
1246
- const pendingBeforeIds = dedupe(list.pendingBeforeIds) ?? list.pendingBeforeIds;
1247
- const pendingAfterIds = dedupe(list.pendingAfterIds) ?? list.pendingAfterIds;
1248
- const liveBeforeIds = dedupe(list.liveBeforeIds) ?? list.liveBeforeIds;
1249
- const liveAfterIds = dedupe(list.liveAfterIds) ?? list.liveAfterIds;
1250
- if (!changed) continue;
1251
- const canonicalIds = new Set(ids);
1252
- this.setList(key, {
1253
- backwardPageLimit: list.backwardPageLimit,
1254
- cursors,
1255
- forwardPageLimit: list.forwardPageLimit,
1256
- ids,
1257
- liveAfterIds: liveAfterIds?.filter((id) => !canonicalIds.has(id)),
1258
- liveBeforeIds: liveBeforeIds?.filter((id) => !canonicalIds.has(id)),
1259
- pagination: list.pagination,
1260
- pendingAfterIds: pendingAfterIds?.filter((id) => !canonicalIds.has(id)),
1261
- pendingBeforeIds: pendingBeforeIds?.filter((id) => !canonicalIds.has(id))
1262
- });
1263
- }
1264
- }
1265
- restoreList(key, list) {
1266
- if (list == null) this.deleteList(key);
1267
- else this.setList(key, list);
1268
- }
1269
- collectGarbage(markedRecords, markedLists, options = {}) {
1270
- const records = /* @__PURE__ */ new Set();
1271
- const lists = /* @__PURE__ */ new Set();
1272
- for (const id of this.records.keys()) {
1273
- if (markedRecords.has(id)) continue;
1274
- records.add(id);
1275
- }
1276
- for (const key of this.lists.keys()) {
1277
- if (markedLists.has(key)) continue;
1278
- lists.add(key);
1279
- }
1280
- for (const id of records) {
1281
- const record = this.records.get(id);
1282
- if (record) this.removeRecordReferenceIndexes(id, record);
1283
- this.records.delete(id);
1284
- this.coverage.delete(id);
1285
- options.onRecordDeleted?.(id);
1286
- }
1287
- for (const key of lists) this.deleteList(key);
1288
- return {
1289
- lists,
1290
- records
1291
- };
1292
- }
1293
- deleteList(key) {
1294
- const previous = this.lists.get(key);
1295
- if (previous) this.removeListIndexes(key, previous);
1296
- this.lists.delete(key);
1297
- this.notifyListSubscribers(key);
1298
- }
1299
- subscribeList(key, fn) {
1300
- let set = this.listSubscriptions.get(key);
1301
- if (!set) {
1302
- set = /* @__PURE__ */ new Set();
1303
- this.listSubscriptions.set(key, set);
1304
- }
1305
- set.add(fn);
1306
- return () => {
1307
- const subscribers = this.listSubscriptions.get(key);
1308
- if (!subscribers) return;
1309
- subscribers.delete(fn);
1310
- if (subscribers.size === 0) this.listSubscriptions.delete(key);
1311
- };
1312
- }
1313
- removeReferencesTo(targetId, viewDataCache, snapshots, listSnapshots) {
1314
- const listKeys = [...this.listKeysByReferencedEntity.get(targetId) ?? []];
1315
- for (const key of listKeys) {
1316
- const list = this.lists.get(key);
1317
- if (!list) continue;
1318
- const { ids } = list;
1319
- const hasLiveAfter = Boolean(list.liveAfterIds?.includes(targetId));
1320
- const hasLiveBefore = Boolean(list.liveBeforeIds?.includes(targetId));
1321
- const hasPendingAfter = Boolean(list.pendingAfterIds?.includes(targetId));
1322
- const hasPendingBefore = Boolean(list.pendingBeforeIds?.includes(targetId));
1323
- if (!ids.includes(targetId) && !hasLiveAfter && !hasLiveBefore && !hasPendingAfter && !hasPendingBefore) continue;
1324
- if (listSnapshots && !listSnapshots.has(key)) listSnapshots.set(key, list);
1325
- const entityIds = [];
1326
- const cursors = list.cursors ? [] : void 0;
1327
- for (let index = 0; index < ids.length; index++) {
1328
- const id = ids[index];
1329
- if (id === targetId) continue;
1330
- entityIds.push(id);
1331
- if (cursors) cursors.push(list.cursors?.[index]);
1332
- }
1333
- this.setList(key, {
1334
- backwardPageLimit: list.backwardPageLimit,
1335
- cursors,
1336
- forwardPageLimit: list.forwardPageLimit,
1337
- ids: entityIds,
1338
- liveAfterIds: list.liveAfterIds?.filter((id) => id !== targetId),
1339
- liveBeforeIds: list.liveBeforeIds?.filter((id) => id !== targetId),
1340
- pagination: list.pagination,
1341
- pendingAfterIds: list.pendingAfterIds?.filter((id) => id !== targetId),
1342
- pendingBeforeIds: list.pendingBeforeIds?.filter((id) => id !== targetId)
1343
- });
1344
- }
1345
- const ids = /* @__PURE__ */ new Map();
1346
- const recordEntries = [...this.recordReferencesByTarget.get(targetId)?.entries() ?? []];
1347
- for (const [id, fields] of recordEntries) {
1348
- const record = this.records.get(id);
1349
- if (!record) continue;
1350
- let updated = false;
1351
- const next = {};
1352
- const paths = /* @__PURE__ */ new Set();
1353
- for (const key of fields) {
1354
- const value = record[key];
1355
- if (Array.isArray(value)) {
1356
- const filtered = value.filter((item) => !(isNodeRef(item) && getNodeRefId(item) === targetId));
1357
- if (filtered.length !== value.length) {
1358
- updated = true;
1359
- paths.add(key);
1360
- next[key] = filtered;
1361
- }
1362
- } else if (isNodeRef(value) && getNodeRefId(value) === targetId) {
1363
- updated = true;
1364
- paths.add(key);
1365
- next[key] = null;
1366
- }
1367
- }
1368
- if (!updated) continue;
1369
- if (snapshots && !snapshots.has(id)) snapshots.set(id, this.snapshot(id));
1370
- viewDataCache.invalidate(id);
1371
- this.mergeInternal(id, next, paths);
1372
- ids.set(id, paths);
1373
- }
1374
- for (const [id, paths] of ids) this.notify(id, paths);
1375
- }
1376
- snapshot(id) {
1377
- const record = this.records.get(id);
1378
- const mask = this.coverage.get(id);
1379
- return {
1380
- mask: mask ? cloneMask(mask) : void 0,
1381
- record: record ? cloneValue(record) : void 0
1382
- };
1383
- }
1384
- restore(id, snapshot) {
1385
- const previous = this.records.get(id);
1386
- if (previous) this.removeRecordReferenceIndexes(id, previous);
1387
- if (snapshot.record === void 0) this.records.delete(id);
1388
- else {
1389
- this.records.set(id, snapshot.record);
1390
- this.addRecordReferenceIndexes(id, snapshot.record);
1391
- }
1392
- if (snapshot.mask === void 0) this.coverage.delete(id);
1393
- else this.coverage.set(id, snapshot.mask);
1394
- this.notify(id);
1395
- }
1396
- addListIndexes(key, list) {
1397
- const parsed = parseListKey(key);
1398
- if (parsed) {
1399
- const ownerFieldKey = getOwnerFieldKey(parsed.ownerId, parsed.field);
1400
- let keys = this.listKeysByOwnerField.get(ownerFieldKey);
1401
- if (!keys) {
1402
- keys = /* @__PURE__ */ new Set();
1403
- this.listKeysByOwnerField.set(ownerFieldKey, keys);
1404
- }
1405
- keys.add(key);
1406
- }
1407
- for (const id of this.getListReferencedEntityIds(list)) {
1408
- let keys = this.listKeysByReferencedEntity.get(id);
1409
- if (!keys) {
1410
- keys = /* @__PURE__ */ new Set();
1411
- this.listKeysByReferencedEntity.set(id, keys);
1412
- }
1413
- keys.add(key);
1414
- }
1415
- }
1416
- removeListIndexes(key, list) {
1417
- const parsed = parseListKey(key);
1418
- if (parsed) {
1419
- const ownerFieldKey = getOwnerFieldKey(parsed.ownerId, parsed.field);
1420
- const keys = this.listKeysByOwnerField.get(ownerFieldKey);
1421
- if (keys) {
1422
- keys.delete(key);
1423
- if (keys.size === 0) this.listKeysByOwnerField.delete(ownerFieldKey);
1424
- }
1425
- }
1426
- for (const id of this.getListReferencedEntityIds(list)) {
1427
- const keys = this.listKeysByReferencedEntity.get(id);
1428
- if (!keys) continue;
1429
- keys.delete(key);
1430
- if (keys.size === 0) this.listKeysByReferencedEntity.delete(id);
1431
- }
1432
- }
1433
- getListReferencedEntityIds(list) {
1434
- const ids = /* @__PURE__ */ new Set();
1435
- for (const id of list.ids) ids.add(id);
1436
- for (const id of list.liveAfterIds ?? []) ids.add(id);
1437
- for (const id of list.liveBeforeIds ?? []) ids.add(id);
1438
- for (const id of list.pendingAfterIds ?? []) ids.add(id);
1439
- for (const id of list.pendingBeforeIds ?? []) ids.add(id);
1440
- return ids;
1441
- }
1442
- getRecordFieldReferences(value) {
1443
- if (Array.isArray(value)) {
1444
- let ids = null;
1445
- for (const item of value) {
1446
- if (!isNodeRef(item)) continue;
1447
- if (!ids) ids = /* @__PURE__ */ new Set();
1448
- ids.add(getNodeRefId(item));
1449
- }
1450
- return ids;
1451
- }
1452
- if (isNodeRef(value)) return /* @__PURE__ */ new Set([getNodeRefId(value)]);
1453
- return null;
1454
- }
1455
- addRecordFieldReferenceIndex(id, field, value) {
1456
- const targets = this.getRecordFieldReferences(value);
1457
- if (!targets || targets.size === 0) return;
1458
- let fields = this.recordReferenceFields.get(id);
1459
- if (!fields) {
1460
- fields = /* @__PURE__ */ new Map();
1461
- this.recordReferenceFields.set(id, fields);
1462
- }
1463
- fields.set(field, targets);
1464
- for (const targetId of targets) {
1465
- let records = this.recordReferencesByTarget.get(targetId);
1466
- if (!records) {
1467
- records = /* @__PURE__ */ new Map();
1468
- this.recordReferencesByTarget.set(targetId, records);
1469
- }
1470
- let targetFields = records.get(id);
1471
- if (!targetFields) {
1472
- targetFields = /* @__PURE__ */ new Set();
1473
- records.set(id, targetFields);
1474
- }
1475
- targetFields.add(field);
1476
- }
1477
- }
1478
- removeRecordFieldReferenceIndex(id, field) {
1479
- const fields = this.recordReferenceFields.get(id);
1480
- const targets = fields?.get(field);
1481
- if (!fields || !targets) return;
1482
- for (const targetId of targets) {
1483
- const records = this.recordReferencesByTarget.get(targetId);
1484
- const targetFields = records?.get(id);
1485
- if (!records || !targetFields) continue;
1486
- targetFields.delete(field);
1487
- if (targetFields.size === 0) records.delete(id);
1488
- if (records.size === 0) this.recordReferencesByTarget.delete(targetId);
1489
- }
1490
- fields.delete(field);
1491
- if (fields.size === 0) this.recordReferenceFields.delete(id);
1492
- }
1493
- addRecordReferenceIndexes(id, record) {
1494
- for (const [field, value] of Object.entries(record)) this.addRecordFieldReferenceIndex(id, field, value);
1495
- }
1496
- removeRecordReferenceIndexes(id, record) {
1497
- for (const field of Object.keys(record)) this.removeRecordFieldReferenceIndex(id, field);
1498
- }
1499
- updateRecordReferenceIndexes(id, previous, next, fields) {
1500
- for (const field of fields) {
1501
- if (previous) this.removeRecordFieldReferenceIndex(id, field);
1502
- this.addRecordFieldReferenceIndex(id, field, next[field]);
1503
- }
1504
- }
1505
- };
1506
- //#endregion
1507
145
  //#region src/request-descriptor.ts
1508
146
  const serializeId = (value) => `${typeof value}:${String(value)}`;
1509
147
  const getViewSignature = (view) => {
@@ -1826,24 +464,27 @@ var FateClient = class {
1826
464
  this.pending = /* @__PURE__ */ new Map();
1827
465
  this.pendingDeferred = /* @__PURE__ */ new Map();
1828
466
  this.pendingOptimisticMutations = /* @__PURE__ */ new Map();
1829
- this.optimisticMasks = /* @__PURE__ */ new Map();
1830
- this.optimisticByEntity = /* @__PURE__ */ new Map();
1831
467
  this.optimisticEntityResolutions = /* @__PURE__ */ new Map();
1832
- this.optimisticTokenCounter = 0;
1833
468
  this.liveSubscriptions = /* @__PURE__ */ new Map();
1834
469
  this.liveConnectionInvalidations = /* @__PURE__ */ new Map();
1835
470
  this.stableRefs = /* @__PURE__ */ new Map();
1836
471
  this.requests = /* @__PURE__ */ new Map();
472
+ this.requestExecutionOptions = /* @__PURE__ */ new WeakMap();
1837
473
  this.rootRequests = /* @__PURE__ */ new Map();
1838
474
  this.stalledRequests = /* @__PURE__ */ new Set();
1839
475
  this.gcPending = false;
1840
476
  this.gcScheduled = false;
1841
477
  this.pendingNetworkRequests = 0;
1842
- this.store = new Store();
1843
- this.viewDataCache = new ViewDataCache();
478
+ this.store = new Store((ids) => {
479
+ for (const id of ids) this.viewDataCache.invalidate(id);
480
+ this.runPendingGarbageCollection();
481
+ }, (kind, key, paths, previousList) => this.persistence?.changed(kind, key, paths, previousList));
482
+ this.viewDataCache = new ViewDataCache(() => !this.store.isRebasing);
1844
483
  this.liveConnectionInvalidationToken = 0;
484
+ this.persistenceDisposal = options.persistence ? new AbortController() : void 0;
1845
485
  this.actions = Object.create(null);
1846
486
  this.mutationMap = Object.create(null);
487
+ if (options.persistence) this.mutationEntities = new Map(Object.entries(options.mutations ?? {}).map(([key, definition]) => [key, definition.entity]));
1847
488
  this.mutations = Object.create(null);
1848
489
  this.onLiveError = options.onLiveError;
1849
490
  this.operationLifetime = new OperationLifetime(options.gcReleaseBufferSize ?? 10);
@@ -1867,6 +508,7 @@ var FateClient = class {
1867
508
  setNestedValue(this.actions, key, async (_previousState, data) => data === "reset" ? null : await this.mutationMap[key](data));
1868
509
  }
1869
510
  this.initializeParentLists();
511
+ this.persistence = options.persistence?.attach(this);
1870
512
  }
1871
513
  initializeParentLists() {
1872
514
  for (const config of this.types.values()) {
@@ -1945,6 +587,60 @@ var FateClient = class {
1945
587
  this.viewDataCache.clear();
1946
588
  notify();
1947
589
  }
590
+ /** @internal */
591
+ exportPersistenceState() {
592
+ return {
593
+ data: encodeHydrationValue({
594
+ rootLists: [...this.rootLists].map(([type, keys]) => [type, [...keys]]),
595
+ rootRequests: [...this.rootRequests],
596
+ store: this.store.dehydrateConfirmed()
597
+ }, this.hydrationLimits),
598
+ scope: this.hydrationScope,
599
+ version: 1
600
+ };
601
+ }
602
+ /** @internal */
603
+ retainPersistenceRequest(request) {
604
+ return this.operationLifetime.retain(request, () => this.scheduleGarbageCollection());
605
+ }
606
+ /** @internal */
607
+ getPersistenceScope() {
608
+ return this.hydrationScope;
609
+ }
610
+ /** @internal */
611
+ getPersistenceRootLists(type) {
612
+ return this.rootLists.get(type) ?? /* @__PURE__ */ new Set();
613
+ }
614
+ /** @internal */
615
+ getPersistenceQuery(key) {
616
+ return this.rootRequests.get(key);
617
+ }
618
+ /** @internal Merge a small disk read beneath optimistic layers. */
619
+ restorePersistenceData(state) {
620
+ this.store.update(() => {
621
+ const coverage = new Map(state.store.coverage);
622
+ for (const [id, record] of state.store.records) {
623
+ const existing = this.store.read(id);
624
+ const paths = coverage.get(id) ?? [];
625
+ this.store.merge(id, existing ? mergePreservingExisting(record, existing) : record, paths);
626
+ }
627
+ for (const [key, list] of state.store.lists) if (!this.store.getListState(key)) this.store.setList(key, list);
628
+ for (const [key, id] of state.rootRequests) if (!this.rootRequests.has(key)) this.rootRequests.set(key, id);
629
+ for (const [type, keys] of state.rootLists) for (const key of keys) this.registerRootList(type, key);
630
+ });
631
+ }
632
+ /** @internal */
633
+ getRetainedRequests() {
634
+ return this.operationLifetime.getDescriptors();
635
+ }
636
+ /** @internal */
637
+ restoreRetainedRequests(requests) {
638
+ this.operationLifetime.restore(requests);
639
+ }
640
+ /** @internal */
641
+ hasMutation(key, entity) {
642
+ return this.mutationEntities?.get(key) === entity && this.types.has(entity);
643
+ }
1948
644
  registerRootList(type, key) {
1949
645
  let lists = this.rootLists.get(type);
1950
646
  if (!lists) {
@@ -1953,11 +649,6 @@ var FateClient = class {
1953
649
  }
1954
650
  lists.add(key);
1955
651
  }
1956
- snapshotList(key, listSnapshots) {
1957
- if (!listSnapshots || listSnapshots.has(key)) return;
1958
- const listState = this.store.getListState(key);
1959
- if (listState) listSnapshots.set(key, listState);
1960
- }
1961
652
  applyListInsert(listState, entityId, insert) {
1962
653
  const pendingBeforeIds = listState.pendingBeforeIds ?? [];
1963
654
  const pendingAfterIds = listState.pendingAfterIds ?? [];
@@ -1986,7 +677,7 @@ var FateClient = class {
1986
677
  pending: false
1987
678
  };
1988
679
  }
1989
- insertIntoRootLists(type, entityId, insert, listSnapshots) {
680
+ insertIntoRootLists(type, entityId, insert) {
1990
681
  if (insert === "none") return;
1991
682
  const listNames = this.rootLists.get(type);
1992
683
  if (!listNames) return;
@@ -1995,7 +686,6 @@ var FateClient = class {
1995
686
  if (!listState) continue;
1996
687
  const next = this.applyListInsert(listState, entityId, insert).list;
1997
688
  if (next === listState) continue;
1998
- this.snapshotList(key, listSnapshots);
1999
689
  this.store.setList(key, next);
2000
690
  }
2001
691
  }
@@ -2009,8 +699,28 @@ var FateClient = class {
2009
699
  if (!descriptor || descriptor === "scalar" || !("listOf" in descriptor)) throw new Error(`fate: Could not find node type for '${type}.${field}'.`);
2010
700
  return descriptor.listOf;
2011
701
  }
702
+ /** @internal Validates local wiring without requiring network connectivity. */
703
+ validatePersistence() {
704
+ if (this.mutationEntities?.size) this.requireDurableMutationTransport();
705
+ }
706
+ /** @internal */
707
+ assertPersistenceActive() {
708
+ if (this.persistenceDisposal?.signal.aborted || this.persistence?.getSnapshot().status === "disposed") throw new Error("fate: Persistence was disposed.");
709
+ }
710
+ /** @internal */
711
+ disposePersistence() {
712
+ this.persistenceDisposal?.abort();
713
+ for (const { unsubscribe } of this.liveSubscriptions.values()) try {
714
+ unsubscribe();
715
+ } catch {}
716
+ this.liveSubscriptions.clear();
717
+ }
718
+ requireDurableMutationTransport() {
719
+ 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.");
720
+ }
2012
721
  async executeMutation(key, input, select, options = {}) {
2013
- if (!this.transport.mutate) throw new Error(`fate: transport does not support mutations. Please provide a 'mutate' implementation in your transport.`);
722
+ if (options.identity) this.requireDurableMutationTransport();
723
+ else if (!this.transport.mutate) throw new Error(`fate: transport does not support mutations. Please provide a 'mutate' implementation in your transport.`);
2014
724
  const baseRecord = input && typeof input === "object" ? input : void 0;
2015
725
  const inputArgs = baseRecord && typeof baseRecord.args === "object" ? baseRecord.args : void 0;
2016
726
  const argsPayload = combineArgsPayload(options.plan ? resolvedArgsFromPlan(options.plan) : void 0, combineArgsPayload(inputArgs, options.args));
@@ -2018,25 +728,19 @@ var FateClient = class {
2018
728
  ...baseRecord,
2019
729
  args: argsPayload
2020
730
  } : argsPayload ? { args: argsPayload } : input;
2021
- return await this.trackPendingRequest(() => this.transport.mutate(key, requestInput, select));
2022
- }
2023
- write(type, data, select, snapshots, plan, pathPrefix = null, blockedMask, insert, listSnapshots) {
2024
- return this.writeEntity(type, data, select, snapshots, plan, pathPrefix, blockedMask, insert, listSnapshots);
2025
- }
2026
- deleteRecord(type, id, snapshots, listSnapshots) {
2027
- const entityId = toEntityId(type, id);
2028
- if (snapshots && !snapshots.has(entityId)) snapshots.set(entityId, this.store.snapshot(entityId));
2029
- this.viewDataCache.invalidate(entityId);
2030
- this.stableRefs.delete(entityId);
2031
- this.store.deleteRecord(entityId);
2032
- this.store.removeReferencesTo(entityId, this.viewDataCache, snapshots, listSnapshots);
2033
- }
2034
- restore(id, snapshot) {
2035
- this.viewDataCache.invalidate(id);
2036
- this.store.restore(id, snapshot);
2037
- }
2038
- restoreList(name, list) {
2039
- this.store.restoreList(name, list);
731
+ return await this.trackPendingRequest(() => options.identity ? this.transport.mutateDurably(key, requestInput, select, options.identity) : this.transport.mutate(key, requestInput, select));
732
+ }
733
+ write(type, data, select, plan, insert) {
734
+ return this.writeEntity(type, data, select, plan, null, insert);
735
+ }
736
+ deleteRecord(type, id) {
737
+ return this.store.update(() => {
738
+ const entityId = toEntityId(type, id);
739
+ this.viewDataCache.invalidate(entityId);
740
+ this.stableRefs.delete(entityId);
741
+ this.store.deleteRecord(entityId);
742
+ this.store.removeReferencesTo(entityId, this.viewDataCache);
743
+ });
2040
744
  }
2041
745
  stableRefWithViewNames(type, id, viewNames) {
2042
746
  const entityId = toEntityId(type, id);
@@ -2060,6 +764,18 @@ var FateClient = class {
2060
764
  return this.stableRefWithViewNames(type, id, getRootViewNames(rootView));
2061
765
  }
2062
766
  readView(view, ref) {
767
+ if (this.persistence && this.persistence.getSnapshot().status !== "ready") {
768
+ const key = `restore:${ref.__typename}:${ref.id}:${[...getViewNames(view)].sort().join(",")}`;
769
+ let pending = this.pending.get(key);
770
+ if (!pending) {
771
+ pending = this.persistence.ready.then(() => {
772
+ if (this.persistence?.getSnapshot().status === "disposed") throw new Error("fate: Persistence was disposed.");
773
+ return this.readView(view, ref);
774
+ }).finally(() => this.pending.delete(key));
775
+ this.pending.set(key, pending);
776
+ }
777
+ return pending;
778
+ }
2063
779
  const id = ref.id;
2064
780
  const type = ref.__typename;
2065
781
  if (id == null) {
@@ -2112,7 +828,36 @@ var FateClient = class {
2112
828
  const pendingPromise = this.pending.get(key) || null;
2113
829
  if (pendingPromise) return pendingPromise;
2114
830
  const promise = this.trackPendingRequest(async () => {
2115
- await this.fetchByIdAndNormalize(type, [id], missing, plan);
831
+ if (this.persistence) {
832
+ const persistedRequest = {
833
+ items: [{
834
+ ids: [id],
835
+ kind: "node",
836
+ name: type,
837
+ plan,
838
+ refViewNames: /* @__PURE__ */ new Set(),
839
+ type,
840
+ viewSignature: ""
841
+ }],
842
+ key: `view:${key}`
843
+ };
844
+ await this.persistence.restoreRequest(persistedRequest);
845
+ const remaining = this.store.missingForSelection(entityId, selectedPaths);
846
+ if (remaining.size) {
847
+ await this.fetchByIdAndNormalize(type, [id], remaining, plan);
848
+ this.persistence.fetched({
849
+ ...persistedRequest,
850
+ items: persistedRequest.items.map((item) => ({
851
+ ...item,
852
+ plan: {
853
+ ...item.plan,
854
+ paths: remaining
855
+ }
856
+ })),
857
+ key: `${persistedRequest.key}:${JSON.stringify([...remaining].sort())}`
858
+ });
859
+ }
860
+ } else await this.fetchByIdAndNormalize(type, [id], missing, plan);
2116
861
  try {
2117
862
  if (this.store.missingForSelection(entityId, selectedPaths).size > 0) {
2118
863
  this.stalledRequests.add(key);
@@ -2193,6 +938,7 @@ var FateClient = class {
2193
938
  if (!this.transport.subscribeConnection) throw new Error(`fate: transport does not support live list views. Please provide a 'subscribeConnection' implementation in your transport.`);
2194
939
  }
2195
940
  subscribeLiveView(view, ref) {
941
+ this.assertPersistenceActive();
2196
942
  this.assertLiveViewSupport();
2197
943
  const id = ref.id;
2198
944
  const type = ref.__typename;
@@ -2220,21 +966,26 @@ var FateClient = class {
2220
966
  try {
2221
967
  unsubscribe = this.transport.subscribeById(type, id, plan.paths, args, {
2222
968
  onData: (record, liveSelect) => {
2223
- if (!record || typeof record !== "object") return;
2224
- const pendingMask = this.getPendingOptimisticMask(entityId);
969
+ if (this.persistenceDisposal?.signal.aborted || !record || typeof record !== "object") return;
2225
970
  const select = new Set(liveSelect ?? plan.paths);
2226
- const selection = this.filterSelectionForPendingOptimistics(entityId, select);
2227
- this.write(type, record, selection, void 0, plan, null, pendingMask, "none");
971
+ this.write(type, record, select, plan, "none");
2228
972
  },
2229
973
  onDelete: (deletedId) => {
974
+ if (this.persistenceDisposal?.signal.aborted) return;
2230
975
  this.deleteRecord(type, deletedId ?? id);
2231
976
  },
2232
- onError: (error) => this.reportLiveError(error)
977
+ onError: (error) => {
978
+ if (!this.persistenceDisposal?.signal.aborted) this.reportLiveError(error);
979
+ }
2233
980
  });
2234
981
  } catch (error) {
2235
982
  this.reportLiveError(error);
2236
983
  return emptyDispose;
2237
984
  }
985
+ if (this.persistenceDisposal?.signal.aborted) {
986
+ unsubscribe();
987
+ return emptyDispose;
988
+ }
2238
989
  this.liveSubscriptions.set(key, {
2239
990
  count: 1,
2240
991
  unsubscribe
@@ -2244,6 +995,7 @@ var FateClient = class {
2244
995
  };
2245
996
  }
2246
997
  subscribeLiveListView(view, connection) {
998
+ this.assertPersistenceActive();
2247
999
  this.assertLiveConnectionSupport();
2248
1000
  const plan = getSelectionPlan(view, null);
2249
1001
  const connectionArgs = filterConnectionArgs(connection.args);
@@ -2260,8 +1012,11 @@ var FateClient = class {
2260
1012
  let unsubscribe;
2261
1013
  try {
2262
1014
  unsubscribe = this.transport.subscribeConnection(connection.procedure, connection.type, connectionArgs, plan.paths, selectionArgs, {
2263
- onError: (error) => this.reportLiveError(error),
1015
+ onError: (error) => {
1016
+ if (!this.persistenceDisposal?.signal.aborted) this.reportLiveError(error);
1017
+ },
2264
1018
  onEvent: (event) => {
1019
+ if (this.persistenceDisposal?.signal.aborted) return;
2265
1020
  this.applyLiveConnectionEvent(view, connection, plan, event);
2266
1021
  }
2267
1022
  });
@@ -2269,6 +1024,10 @@ var FateClient = class {
2269
1024
  this.reportLiveError(error);
2270
1025
  return emptyDispose;
2271
1026
  }
1027
+ if (this.persistenceDisposal?.signal.aborted) {
1028
+ unsubscribe();
1029
+ return emptyDispose;
1030
+ }
2272
1031
  this.liveSubscriptions.set(key, {
2273
1032
  count: 1,
2274
1033
  unsubscribe
@@ -2278,24 +1037,26 @@ var FateClient = class {
2278
1037
  };
2279
1038
  }
2280
1039
  applyLiveConnectionEvent(view, connection, plan, event) {
2281
- if (event.type === "invalidate") {
2282
- this.invalidateLiveConnection(view, connection);
2283
- return;
2284
- }
2285
- if (event.type === "deleteEdge") {
2286
- this.deleteConnectionEdge(connection, toEntityId(event.nodeType, event.id));
2287
- return;
2288
- }
2289
- const node = event.edge.node;
2290
- if (!node || typeof node !== "object") return;
2291
- const nodeRecord = node;
2292
- const nodeType = event.nodeType;
2293
- if (!nodeType) {
2294
- this.reportLiveError(/* @__PURE__ */ new Error(`fate: Live connection event for '${connection.procedure}' did not include a node type.`));
2295
- return;
2296
- }
2297
- const entityId = this.write(nodeType, nodeRecord, plan.paths, void 0, plan, null, null, "none");
2298
- this.insertConnectionEdge(connection.key, entityId, event.edge.cursor, event.type, event.targetCursor, view, connection);
1040
+ return this.store.update(() => {
1041
+ if (event.type === "invalidate") {
1042
+ this.invalidateLiveConnection(view, connection);
1043
+ return;
1044
+ }
1045
+ if (event.type === "deleteEdge") {
1046
+ this.deleteConnectionEdge(connection, toEntityId(event.nodeType, event.id));
1047
+ return;
1048
+ }
1049
+ const node = event.edge.node;
1050
+ if (!node || typeof node !== "object") return;
1051
+ const nodeRecord = node;
1052
+ const nodeType = event.nodeType;
1053
+ if (!nodeType) {
1054
+ this.reportLiveError(/* @__PURE__ */ new Error(`fate: Live connection event for '${connection.procedure}' did not include a node type.`));
1055
+ return;
1056
+ }
1057
+ const entityId = this.write(nodeType, nodeRecord, plan.paths, plan, "none");
1058
+ this.insertConnectionEdge(connection.key, entityId, event.edge.cursor, event.type, event.targetCursor, view, connection);
1059
+ });
2299
1060
  }
2300
1061
  invalidateLiveConnection(view, connection) {
2301
1062
  const token = ++this.liveConnectionInvalidationToken;
@@ -2524,11 +1285,14 @@ var FateClient = class {
2524
1285
  const cursors = hasIncomingCursor || Boolean(previous?.cursors) || options.hasCursorArg ? ids.map((id) => cursorMap.get(id)) : void 0;
2525
1286
  const previousPagination = previous?.pagination;
2526
1287
  const newPagination = incomingPagination;
1288
+ const extendsExistingList = options.hasCursorArg && existingIds.length > 0;
1289
+ const leadingPagination = extendsExistingList && !isBackward ? previousPagination ?? newPagination : newPagination;
1290
+ const trailingPagination = extendsExistingList && isBackward ? previousPagination ?? newPagination : newPagination;
2527
1291
  const pagination = previousPagination || newPagination ? {
2528
- hasNext: !!(newPagination?.hasNext ?? previousPagination?.hasNext),
2529
- hasPrevious: !!(newPagination?.hasPrevious ?? previousPagination?.hasPrevious),
2530
- nextCursor: newPagination?.nextCursor ?? previousPagination?.nextCursor,
2531
- previousCursor: newPagination?.previousCursor ?? previousPagination?.previousCursor
1292
+ hasNext: !!(trailingPagination?.hasNext ?? previousPagination?.hasNext),
1293
+ hasPrevious: !!(leadingPagination?.hasPrevious ?? previousPagination?.hasPrevious),
1294
+ nextCursor: trailingPagination?.nextCursor ?? previousPagination?.nextCursor,
1295
+ previousCursor: leadingPagination?.previousCursor ?? previousPagination?.previousCursor
2532
1296
  } : void 0;
2533
1297
  return {
2534
1298
  backwardPageLimit: !options.hasCursorArg && isBackward && options.pageLimit != null ? Math.max(previous?.backwardPageLimit ?? 0, options.pageLimit) : previous?.backwardPageLimit,
@@ -2562,60 +1326,10 @@ var FateClient = class {
2562
1326
  this.optimisticEntityResolutions.set(optimisticEntityId, resolvedEntityId);
2563
1327
  this.store.replaceListEntityId(optimisticEntityId, resolvedEntityId);
2564
1328
  }
2565
- registerOptimisticUpdate(entityId, select) {
2566
- if (!entityId || select.size === 0) return null;
2567
- const mask = fromPaths(select);
2568
- const token = ++this.optimisticTokenCounter;
2569
- this.optimisticMasks.set(token, {
2570
- entityId,
2571
- mask
2572
- });
2573
- let entries = this.optimisticByEntity.get(entityId);
2574
- if (!entries) {
2575
- entries = /* @__PURE__ */ new Set();
2576
- this.optimisticByEntity.set(entityId, entries);
2577
- }
2578
- entries.add(token);
2579
- return token;
2580
- }
2581
- clearOptimisticUpdate(token) {
2582
- if (token == null) return;
2583
- const entry = this.optimisticMasks.get(token);
2584
- if (!entry) return;
2585
- this.optimisticMasks.delete(token);
2586
- const entityTokens = this.optimisticByEntity.get(entry.entityId);
2587
- if (entityTokens) {
2588
- entityTokens.delete(token);
2589
- if (entityTokens.size === 0) this.optimisticByEntity.delete(entry.entityId);
2590
- }
2591
- this.runPendingGarbageCollection();
2592
- }
2593
- getPendingOptimisticMask(entityId, options = {}) {
2594
- if (!entityId) return null;
2595
- const tokens = this.optimisticByEntity.get(entityId);
2596
- if (!tokens || tokens.size === 0) return null;
2597
- let mask = null;
2598
- for (const token of tokens) {
2599
- if (options.excludeToken != null && token === options.excludeToken) continue;
2600
- const entry = this.optimisticMasks.get(token);
2601
- if (!entry) continue;
2602
- if (!mask) mask = cloneMask(entry.mask);
2603
- else union(mask, entry.mask);
2604
- }
2605
- return mask;
2606
- }
2607
1329
  getPendingOptimisticMutations(entityId) {
2608
1330
  const pending = this.pendingOptimisticMutations.get(entityId);
2609
1331
  return pending && pending.size > 0 ? [...pending] : null;
2610
1332
  }
2611
- filterSelectionForPendingOptimistics(entityId, select, options = {}) {
2612
- if (!entityId || select.size === 0) return select;
2613
- const pendingMask = this.getPendingOptimisticMask(entityId, options);
2614
- if (!pendingMask) return select;
2615
- const filtered = /* @__PURE__ */ new Set();
2616
- for (const path of select) if (!isCovered(pendingMask, path)) filtered.add(path);
2617
- return filtered;
2618
- }
2619
1333
  async loadConnection(view, connection, args, options = {}) {
2620
1334
  return await this.trackPendingRequest(() => this.loadConnectionInternal(view, connection, args, options));
2621
1335
  }
@@ -2629,22 +1343,25 @@ var FateClient = class {
2629
1343
  });
2630
1344
  const { argsPayload, plan } = resolveSelectionPlan(view, requestArgs);
2631
1345
  const { items, pagination } = await this.transport.fetchList(connection.field, plan.paths, argsPayload);
1346
+ this.assertPersistenceActive();
2632
1347
  if (!items) return this.store.getListState(connection.key);
2633
- const incomingIds = [];
2634
- const incomingCursors = [];
2635
- for (const entry of items) {
2636
- const id = this.write(connection.type, entry.node, plan.paths, void 0, plan, null, null, "none");
2637
- incomingIds.push(id);
2638
- incomingCursors.push(entry.cursor);
2639
- }
2640
- const previous = this.store.getListState(connection.key);
2641
- const nextListState = this.mergeListState(previous, incomingIds, incomingCursors, pagination, {
2642
- ...getPaginationMergeInfo(requestArgs),
2643
- replace: true
1348
+ this.store.update(() => {
1349
+ const incomingIds = [];
1350
+ const incomingCursors = [];
1351
+ for (const entry of items) {
1352
+ const id = this.write(connection.type, entry.node, plan.paths, plan, "none");
1353
+ incomingIds.push(id);
1354
+ incomingCursors.push(entry.cursor);
1355
+ }
1356
+ const previous = this.store.getListState(connection.key);
1357
+ const nextListState = this.mergeListState(previous, incomingIds, incomingCursors, pagination, {
1358
+ ...getPaginationMergeInfo(requestArgs),
1359
+ replace: true
1360
+ });
1361
+ if (!filterConnectionArgs(connection.args)) this.registerRootList(connection.type, connection.key);
1362
+ this.store.setList(connection.key, nextListState);
2644
1363
  });
2645
- if (!filterConnectionArgs(connection.args)) this.registerRootList(connection.type, connection.key);
2646
- this.store.setList(connection.key, nextListState);
2647
- return nextListState;
1364
+ return this.store.getListState(connection.key);
2648
1365
  }
2649
1366
  const owner = parseEntityId(connection.owner);
2650
1367
  const requestArgs = omitUndefinedValues({
@@ -2658,6 +1375,7 @@ var FateClient = class {
2658
1375
  const parentSelection = /* @__PURE__ */ new Set();
2659
1376
  for (const path of nodeSelection) parentSelection.add(`${connection.field}.${path}`);
2660
1377
  const [parentRecord] = await this.transport.fetchById(owner.type, [owner.id], parentSelection, scopedArgsPayload);
1378
+ this.assertPersistenceActive();
2661
1379
  if (!parentRecord || typeof parentRecord !== "object") return this.store.getListState(connection.key);
2662
1380
  const list = parentRecord[connection.field];
2663
1381
  const connectionPayload = Array.isArray(list) ? {
@@ -2668,37 +1386,39 @@ var FateClient = class {
2668
1386
  pagination: void 0
2669
1387
  } : list;
2670
1388
  if (!connectionPayload) return this.store.getListState(connection.key);
2671
- const incomingIds = [];
2672
- const incomingCursors = [];
2673
- const nodeType = this.getListNodeType(owner.type, connection.field);
2674
- for (const entry of connectionPayload.items) {
2675
- const { node } = entry;
2676
- const id = this.write(nodeType, node, nodeSelection, void 0, plan);
2677
- incomingIds.push(id);
2678
- incomingCursors.push(entry.cursor);
2679
- }
2680
- const previous = this.store.getListState(connection.key);
2681
- const previousIds = previous?.ids ?? [];
2682
- const previousSet = new Set(previousIds);
2683
- const newIds = incomingIds.filter((id) => !previousSet.has(id));
2684
- const nextListState = this.mergeListState(previous, incomingIds, incomingCursors, connectionPayload.pagination, {
2685
- ...getPaginationMergeInfo(argsPayload),
2686
- direction,
2687
- hasCursorArg: true,
2688
- replace: true
1389
+ this.store.update(() => {
1390
+ const incomingIds = [];
1391
+ const incomingCursors = [];
1392
+ const nodeType = this.getListNodeType(owner.type, connection.field);
1393
+ for (const entry of connectionPayload.items) {
1394
+ const { node } = entry;
1395
+ const id = this.write(nodeType, node, nodeSelection, plan);
1396
+ incomingIds.push(id);
1397
+ incomingCursors.push(entry.cursor);
1398
+ }
1399
+ const previous = this.store.getListState(connection.key);
1400
+ const previousIds = previous?.ids ?? [];
1401
+ const previousSet = new Set(previousIds);
1402
+ const newIds = incomingIds.filter((id) => !previousSet.has(id));
1403
+ const nextListState = this.mergeListState(previous, incomingIds, incomingCursors, connectionPayload.pagination, {
1404
+ ...getPaginationMergeInfo(argsPayload),
1405
+ direction,
1406
+ hasCursorArg: true,
1407
+ replace: true
1408
+ });
1409
+ this.store.setList(connection.key, nextListState);
1410
+ const current = this.store.read(connection.owner);
1411
+ const existingField = Array.isArray(current?.[connection.field]) ? current?.[connection.field] || [] : [];
1412
+ const nodeRefs = createNodeRefsForIds(newIds, void 0);
1413
+ const nextField = direction === "forward" ? [...existingField, ...nodeRefs] : [...nodeRefs, ...existingField];
1414
+ this.viewDataCache.invalidate(connection.owner);
1415
+ this.store.merge(connection.owner, { [connection.field]: nextField }, [connection.field]);
2689
1416
  });
2690
- this.store.setList(connection.key, nextListState);
2691
- const current = this.store.read(connection.owner);
2692
- const existingField = Array.isArray(current?.[connection.field]) ? current?.[connection.field] || [] : [];
2693
- const nodeRefs = createNodeRefsForIds(newIds, void 0);
2694
- const nextField = direction === "forward" ? [...existingField, ...nodeRefs] : [...nodeRefs, ...existingField];
2695
- this.viewDataCache.invalidate(connection.owner);
2696
- this.store.merge(connection.owner, { [connection.field]: nextField }, [connection.field]);
2697
1417
  return this.store.getListState(connection.key);
2698
1418
  }
2699
1419
  request(request, options) {
2700
1420
  const mode = options?.mode ?? "cache-first";
2701
- return this.requestWithDescriptor(this.createRequestDescriptor(request), mode, { revalidateExisting: true });
1421
+ return this.requestWithDescriptor(this.createRequestDescriptor(request), mode, { revalidateExisting: true }, options);
2702
1422
  }
2703
1423
  requestForRender(request, options) {
2704
1424
  return this.prepareRequestForRender(request, options).promise;
@@ -2707,16 +1427,23 @@ var FateClient = class {
2707
1427
  const mode = options?.mode ?? "cache-first";
2708
1428
  const descriptor = this.createRequestDescriptor(request);
2709
1429
  return {
2710
- promise: this.requestWithDescriptor(descriptor, mode, { revalidateExisting: false }),
1430
+ promise: this.requestWithDescriptor(descriptor, mode, { revalidateExisting: false }, options),
2711
1431
  requestKey: descriptor.key
2712
1432
  };
2713
1433
  }
2714
- requestWithDescriptor(descriptor, mode, { revalidateExisting }) {
1434
+ requestWithDescriptor(descriptor, mode, { revalidateExisting }, options) {
1435
+ this.assertPersistenceActive();
1436
+ if (this.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.");
2715
1437
  const requestKey = descriptor.key;
2716
1438
  const existingRequest = this.requests.get(requestKey)?.get(mode);
2717
1439
  if (existingRequest) {
1440
+ if (this.persistence && options?.persist) {
1441
+ const executionOptions = this.requestExecutionOptions.get(existingRequest.descriptor);
1442
+ if (executionOptions) executionOptions.persist = { ...options.persist };
1443
+ }
1444
+ if (this.persistence && options?.persist && existingRequest.status === "fulfilled") this.persistence.used(existingRequest.descriptor, options.persist);
2718
1445
  const isFulfilledCacheFirstWithMissingData = mode === "cache-first" && existingRequest.status === "fulfilled" && !this.hasRequestData(existingRequest.descriptor);
2719
- if (existingRequest.status !== "pending" && (isFulfilledCacheFirstWithMissingData || revalidateExisting && (mode === "network-only" || mode === "stale-while-revalidate" || mode === "cache-first" && existingRequest.status === "rejected"))) this.executeRequestHandle(existingRequest, mode);
1446
+ if (existingRequest.status !== "pending" && (isFulfilledCacheFirstWithMissingData || revalidateExisting && (mode === "network-only" || mode === "stale-while-revalidate" || mode === "cache-first" && existingRequest.status === "rejected"))) this.executeRequestHandle(existingRequest, mode, options);
2720
1447
  return existingRequest;
2721
1448
  }
2722
1449
  const handle = new FateRequestPromise(descriptor, () => this.getRequestResultFromDescriptor(descriptor));
@@ -2726,7 +1453,7 @@ var FateClient = class {
2726
1453
  this.requests.set(requestKey, requests);
2727
1454
  }
2728
1455
  requests.set(mode, handle);
2729
- this.executeRequestHandle(handle, mode);
1456
+ this.executeRequestHandle(handle, mode, options);
2730
1457
  return handle;
2731
1458
  }
2732
1459
  releaseRequest(request, mode) {
@@ -2771,7 +1498,7 @@ var FateClient = class {
2771
1498
  * memory-management flows.
2772
1499
  */
2773
1500
  gc() {
2774
- if (this.hasActiveOptimisticUpdates()) {
1501
+ if (this.persistence ? this.store.hasTransientOptimisticUpdates : this.hasActiveOptimisticUpdates()) {
2775
1502
  this.gcPending = true;
2776
1503
  return;
2777
1504
  }
@@ -2792,6 +1519,10 @@ var FateClient = class {
2792
1519
  for (const id of listState.ids) markRecord(id);
2793
1520
  for (const id of listState.pendingAfterIds ?? []) markRecord(id);
2794
1521
  };
1522
+ const optimisticRoots = this.store.getOptimisticRoots();
1523
+ optimisticRoots.baseRecords.forEach((record) => this.markRecordReferences(record, markRecord));
1524
+ optimisticRoots.records.forEach(markRecord);
1525
+ optimisticRoots.lists.forEach(markList);
2795
1526
  for (const descriptor of this.operationLifetime.getDescriptors()) for (const item of descriptor.items) {
2796
1527
  if (item.kind === "node" || item.kind === "nodes") {
2797
1528
  for (const raw of item.ids) markRecord(toEntityId(item.type, raw));
@@ -2830,17 +1561,18 @@ var FateClient = class {
2830
1561
  if (keys.size === 0) this.rootLists.delete(type);
2831
1562
  }
2832
1563
  }
2833
- executeRequestHandle(handle, mode) {
2834
- let execution;
2835
- switch (mode) {
2836
- case "stale-while-revalidate":
2837
- execution = this.handleStoreAndNetworkRequest(handle.descriptor);
2838
- break;
2839
- default:
2840
- execution = this.executeRequest(handle.descriptor, mode === "network-only" ? { fetchAll: true } : void 0).then(() => this.getRequestResultFromDescriptor(handle.descriptor));
2841
- break;
2842
- }
2843
- handle.start(execution);
1564
+ executeRequestHandle(handle, mode, options) {
1565
+ const executionOptions = {
1566
+ fetchAll: mode !== "cache-first",
1567
+ persist: options?.persist ? { ...options.persist } : void 0
1568
+ };
1569
+ if (this.persistence) this.requestExecutionOptions.set(handle.descriptor, executionOptions);
1570
+ const execute = () => {
1571
+ this.assertPersistenceActive();
1572
+ if (mode === "stale-while-revalidate") return this.handleStoreAndNetworkRequest(handle.descriptor, executionOptions);
1573
+ return this.executeRequest(handle.descriptor, executionOptions).then(() => this.getRequestResultFromDescriptor(handle.descriptor));
1574
+ };
1575
+ handle.start(this.persistence ? this.withPersistenceLifecycle(() => this.persistence.ready.then(() => mode === "network-only" ? void 0 : this.persistence.restoreRequest(handle.descriptor)).then(execute)) : execute());
2844
1576
  }
2845
1577
  markRecordReferences(record, markRecord) {
2846
1578
  const seen = /* @__PURE__ */ new Set();
@@ -2861,29 +1593,52 @@ var FateClient = class {
2861
1593
  walk(record);
2862
1594
  }
2863
1595
  hasActiveOptimisticUpdates() {
2864
- return this.optimisticMasks.size > 0 || this.optimisticByEntity.size > 0 || this.pendingOptimisticMutations.size > 0;
1596
+ return this.store.hasOptimisticUpdates || this.pendingOptimisticMutations.size > 0;
2865
1597
  }
2866
1598
  hasPendingRequests() {
2867
1599
  return this.pendingNetworkRequests > 0;
2868
1600
  }
2869
- async trackPendingRequest(request) {
2870
- this.pendingNetworkRequests += 1;
1601
+ /** @internal */
1602
+ async withPersistenceLifecycle(request) {
1603
+ this.assertPersistenceActive();
1604
+ const signal = this.persistenceDisposal?.signal;
1605
+ let onDispose;
2871
1606
  try {
2872
- return await request();
1607
+ const execution = request();
1608
+ const result = signal ? await Promise.race([execution, new Promise((_, reject) => {
1609
+ onDispose = () => reject(/* @__PURE__ */ new Error("fate: Persistence was disposed."));
1610
+ signal.addEventListener("abort", onDispose, { once: true });
1611
+ if (signal.aborted) onDispose();
1612
+ })]) : await execution;
1613
+ this.assertPersistenceActive();
1614
+ return result;
2873
1615
  } finally {
2874
- this.pendingNetworkRequests -= 1;
1616
+ if (onDispose) signal?.removeEventListener("abort", onDispose);
2875
1617
  }
2876
1618
  }
1619
+ async trackPendingRequest(request) {
1620
+ return this.withPersistenceLifecycle(async () => {
1621
+ if (this.persistence) await this.persistence.ready;
1622
+ this.assertPersistenceActive();
1623
+ this.pendingNetworkRequests += 1;
1624
+ try {
1625
+ return await this.withPersistenceLifecycle(request);
1626
+ } finally {
1627
+ this.pendingNetworkRequests -= 1;
1628
+ this.persistence?.changed();
1629
+ }
1630
+ });
1631
+ }
2877
1632
  runPendingGarbageCollection() {
2878
1633
  if (this.gcPending && !this.hasActiveOptimisticUpdates()) this.gc();
2879
1634
  }
2880
- async handleStoreAndNetworkRequest(request) {
1635
+ async handleStoreAndNetworkRequest(request, options) {
2881
1636
  if (!this.hasRequestData(request)) {
2882
- await this.executeRequest(request, { fetchAll: true });
1637
+ await this.executeRequest(request, options);
2883
1638
  return this.getRequestResultFromDescriptor(request);
2884
1639
  }
2885
1640
  const result = this.getRequestResultFromDescriptor(request);
2886
- this.executeRequest(request, { fetchAll: true }).catch(() => {});
1641
+ this.executeRequest(request, options).catch(() => {});
2887
1642
  return result;
2888
1643
  }
2889
1644
  getRootType(name) {
@@ -2909,6 +1664,8 @@ var FateClient = class {
2909
1664
  const fetchAll = options.fetchAll ?? false;
2910
1665
  const groups = /* @__PURE__ */ new Map();
2911
1666
  const promises = [];
1667
+ const fetchedItems = [];
1668
+ let fetchedAll = true;
2912
1669
  for (const item of request.items) if (item.kind === "node" || item.kind === "nodes") {
2913
1670
  const fields = item.plan.paths;
2914
1671
  const fieldsSignature = [...fields].slice().sort().join(",");
@@ -2924,23 +1681,46 @@ var FateClient = class {
2924
1681
  };
2925
1682
  groups.set(groupKey, group);
2926
1683
  }
1684
+ const fetchedIds = [];
2927
1685
  for (const raw of item.ids) {
2928
1686
  const entityId = toEntityId(item.type, raw);
2929
1687
  const missing = this.store.missingForSelection(entityId, fields);
2930
- if (fetchAll || missing.size > 0) group.ids.push(raw);
1688
+ if (fetchAll || missing.size > 0) {
1689
+ group.ids.push(raw);
1690
+ fetchedIds.push(raw);
1691
+ }
2931
1692
  }
1693
+ if (fetchedIds.length) fetchedItems.push({
1694
+ ...item,
1695
+ ids: fetchedIds
1696
+ });
1697
+ fetchedAll &&= fetchedIds.length === item.ids.length;
2932
1698
  } else {
2933
1699
  if (item.kind === "query") {
2934
1700
  const hasResult = this.rootRequests.has(item.queryKey);
2935
1701
  const entityId = this.rootRequests.get(item.queryKey);
2936
1702
  const missing = hasResult && entityId ? this.store.missingForSelection(entityId, item.plan.paths) : item.plan.paths;
2937
- if (fetchAll || !hasResult || entityId && missing.size > 0) promises.push(this.fetchQueryAndNormalize(item));
1703
+ if (fetchAll || !hasResult || entityId && missing.size > 0) {
1704
+ promises.push(this.fetchQueryAndNormalize(item));
1705
+ fetchedItems.push(item);
1706
+ } else fetchedAll = false;
2938
1707
  }
2939
1708
  if (item.kind !== "list") continue;
2940
- if (!fetchAll && !getPaginationArgsInfo(item.argsPayload).hasCursorArg && this.hasRootListData(item)) continue;
1709
+ if (!fetchAll && !getPaginationArgsInfo(item.argsPayload).hasCursorArg && this.hasRootListData(item)) {
1710
+ fetchedAll = false;
1711
+ continue;
1712
+ }
2941
1713
  promises.push(this.fetchListAndNormalize(item));
1714
+ fetchedItems.push(item);
2942
1715
  }
2943
1716
  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())]);
1717
+ if (this.persistence) {
1718
+ if (fetchedItems.length) this.persistence.fetched(fetchedAll ? request : {
1719
+ items: fetchedItems,
1720
+ key: `partial:${JSON.stringify([request.key, fetchedItems.map((item) => [item.name, "ids" in item ? item.ids : null])])}`
1721
+ }, options.persist);
1722
+ if (!fetchedItems.length || !fetchedAll) this.persistence.used(request, options.persist);
1723
+ }
2944
1724
  }
2945
1725
  hasRequestData(request) {
2946
1726
  for (const item of request.items) {
@@ -3028,18 +1808,20 @@ var FateClient = class {
3028
1808
  const resolvedArgs = resolvedArgsFromPlan(plan);
3029
1809
  await this.trackPendingRequest(async () => {
3030
1810
  const records = await this.transport.fetchById(type, ids, select, resolvedArgs);
3031
- for (const record of records) this.writeEntity(type, record, select, void 0, plan, prefix);
1811
+ this.assertPersistenceActive();
1812
+ for (const record of records) this.writeEntity(type, record, select, plan, prefix);
3032
1813
  });
3033
1814
  }
3034
1815
  async fetchQueryAndNormalize(item) {
3035
1816
  if (!this.transport.fetchQuery) throw new Error(`fate: transport does not support queries. Please add support for 'fetchQuery' in your transport for '${item.name}'.`);
3036
1817
  await this.trackPendingRequest(async () => {
3037
1818
  const record = await this.transport.fetchQuery(item.name, item.plan.paths, item.argsPayload);
1819
+ this.assertPersistenceActive();
3038
1820
  if (!record || typeof record !== "object") {
3039
1821
  this.rootRequests.set(item.queryKey, null);
3040
1822
  return;
3041
1823
  }
3042
- const entityId = this.writeEntity(item.type, record, item.plan.paths, void 0, item.plan);
1824
+ const entityId = this.writeEntity(item.type, record, item.plan.paths, item.plan);
3043
1825
  this.rootRequests.set(item.queryKey, entityId);
3044
1826
  });
3045
1827
  }
@@ -3047,133 +1829,132 @@ var FateClient = class {
3047
1829
  if (!this.transport.fetchList) throw new Error(`fate: 'transport.fetchList' is not configured but request includes a list for key '${item.name}'.`);
3048
1830
  await this.trackPendingRequest(async () => {
3049
1831
  const { items, pagination } = await this.transport.fetchList(item.name, item.plan.paths, item.argsPayload);
3050
- const ids = [];
3051
- const cursors = [];
3052
- for (const entry of items) {
3053
- const id = this.writeEntity(item.type, entry.node, item.plan.paths, void 0, item.plan);
3054
- ids.push(id);
3055
- cursors.push(entry.cursor);
3056
- }
3057
- if (!filterConnectionArgs(item.argsPayload)) this.registerRootList(item.type, item.listKey);
3058
- const previous = this.store.getListState(item.listKey);
3059
- this.store.setList(item.listKey, this.mergeListState(previous, ids, cursors, pagination, {
3060
- ...getPaginationMergeInfo(item.argsPayload),
3061
- replace: true
3062
- }));
1832
+ this.assertPersistenceActive();
1833
+ this.store.update(() => {
1834
+ const ids = [];
1835
+ const cursors = [];
1836
+ for (const entry of items) {
1837
+ const id = this.writeEntity(item.type, entry.node, item.plan.paths, item.plan);
1838
+ ids.push(id);
1839
+ cursors.push(entry.cursor);
1840
+ }
1841
+ if (!filterConnectionArgs(item.argsPayload)) this.registerRootList(item.type, item.listKey);
1842
+ const previous = this.store.getListState(item.listKey);
1843
+ this.store.setList(item.listKey, this.mergeListState(previous, ids, cursors, pagination, {
1844
+ ...getPaginationMergeInfo(item.argsPayload),
1845
+ replace: true
1846
+ }));
1847
+ });
3063
1848
  });
3064
1849
  }
3065
- writeEntity(type, record, select, snapshots, plan, pathPrefix = null, blockedMask, insert, listSnapshots) {
3066
- const config = this.types.get(type);
3067
- if (!config) throw new Error(`fate: Found unknown entity type '${type}' in normalization.`);
3068
- const entityId = toEntityId(type, config.getId(record));
3069
- const result = {};
3070
- const selectionTree = groupSelectionByPrefix(select);
3071
- if (config.fields) for (const [key, relationDescriptor] of Object.entries(config.fields)) {
3072
- const value = record[key];
3073
- const fieldPath = pathPrefix ? `${pathPrefix}.${key}` : key;
3074
- const fieldArgs = plan?.args.get(fieldPath);
3075
- const isFieldBlocked = blockedMask ? isCovered(blockedMask, fieldPath) : false;
3076
- if (relationDescriptor === "scalar") {
3077
- if (isFieldBlocked || !Object.prototype.hasOwnProperty.call(record, key)) continue;
3078
- result[key] = value;
3079
- } else if (relationDescriptor && typeof relationDescriptor === "object" && "type" in relationDescriptor) {
3080
- if (isFieldBlocked) continue;
3081
- const childPaths = selectionTree.get(key) ?? emptySet;
3082
- if (value === null) {
3083
- result[key] = null;
3084
- continue;
3085
- }
3086
- if (value && typeof value === "object" && !isNodeRef(value)) {
3087
- const childType = relationDescriptor.type;
1850
+ writeEntity(type, record, select, plan, pathPrefix = null, insert) {
1851
+ return this.store.update(() => {
1852
+ const config = this.types.get(type);
1853
+ if (!config) throw new Error(`fate: Found unknown entity type '${type}' in normalization.`);
1854
+ const id = config.getId(record);
1855
+ const entityId = toEntityId(type, id);
1856
+ const result = {};
1857
+ const selectionTree = groupSelectionByPrefix(select);
1858
+ if (config.fields) for (const [key, relationDescriptor] of Object.entries(config.fields)) {
1859
+ const value = record[key];
1860
+ const fieldPath = pathPrefix ? `${pathPrefix}.${key}` : key;
1861
+ const fieldArgs = plan?.args.get(fieldPath);
1862
+ if (relationDescriptor === "scalar") {
1863
+ if (!Object.hasOwn(record, key)) continue;
1864
+ result[key] = value;
1865
+ } else if (relationDescriptor && typeof relationDescriptor === "object" && "type" in relationDescriptor) {
1866
+ const childPaths = selectionTree.get(key) ?? emptySet;
1867
+ if (value === null) {
1868
+ result[key] = null;
1869
+ continue;
1870
+ }
1871
+ if (value && typeof value === "object" && !isNodeRef(value)) {
1872
+ const childType = relationDescriptor.type;
1873
+ const childConfig = this.types.get(childType);
1874
+ if (!childConfig) throw new Error(`fate: Unknown related type '${childType}' (field '${type}.${key}').`);
1875
+ const childId = toEntityId(childType, childConfig.getId(value));
1876
+ result[key] = createNodeRef(childId);
1877
+ this.writeEntity(childType, value, childPaths, plan, fieldPath);
1878
+ }
1879
+ } else if (relationDescriptor && typeof relationDescriptor === "object" && "listOf" in relationDescriptor) {
1880
+ const childPaths = selectionTree.get(key) ?? emptySet;
1881
+ if (value === null) {
1882
+ result[key] = null;
1883
+ continue;
1884
+ }
1885
+ const childType = relationDescriptor.listOf;
3088
1886
  const childConfig = this.types.get(childType);
3089
1887
  if (!childConfig) throw new Error(`fate: Unknown related type '${childType}' (field '${type}.${key}').`);
3090
- result[key] = createNodeRef(toEntityId(childType, childConfig.getId(value)));
3091
- this.writeEntity(childType, value, childPaths, snapshots, plan, fieldPath, blockedMask, void 0, listSnapshots);
3092
- }
3093
- } else if (relationDescriptor && typeof relationDescriptor === "object" && "listOf" in relationDescriptor) {
3094
- if (isFieldBlocked) continue;
3095
- const childPaths = selectionTree.get(key) ?? emptySet;
3096
- if (value === null) {
3097
- result[key] = null;
3098
- continue;
3099
- }
3100
- const childType = relationDescriptor.listOf;
3101
- const childConfig = this.types.get(childType);
3102
- if (!childConfig) throw new Error(`fate: Unknown related type '${childType}' (field '${type}.${key}').`);
3103
- const connection = (() => {
3104
- if (Array.isArray(value)) return { items: value.map((item) => ({ node: item })) };
3105
- if (value && typeof value === "object") {
3106
- const record = value;
3107
- if (Array.isArray(record.items)) return {
3108
- items: record.items.map((node) => {
3109
- if (node && typeof node === "object" && "node" in node) {
3110
- const itemRecord = node;
3111
- return {
3112
- cursor: itemRecord.cursor,
3113
- node: itemRecord.node
3114
- };
3115
- }
3116
- return { node };
3117
- }),
3118
- pagination: record.pagination
3119
- };
3120
- }
3121
- return null;
3122
- })();
3123
- if (connection) {
3124
- const ids = [];
3125
- const cursors = [];
3126
- const nodeSelection = childPaths.size > 0 ? /* @__PURE__ */ new Set() : childPaths;
3127
- if (childPaths.size > 0) for (const path of childPaths) {
3128
- if (path.startsWith("items.node.")) {
3129
- nodeSelection.add(path.slice(11));
3130
- continue;
3131
- }
3132
- if (path.startsWith("node.")) {
3133
- nodeSelection.add(path.slice(5));
3134
- continue;
1888
+ const connection = (() => {
1889
+ if (Array.isArray(value)) return { items: value.map((item) => ({ node: item })) };
1890
+ if (value && typeof value === "object") {
1891
+ const record = value;
1892
+ if (Array.isArray(record.items)) return {
1893
+ items: record.items.map((node) => {
1894
+ if (node && typeof node === "object" && "node" in node) {
1895
+ const itemRecord = node;
1896
+ return {
1897
+ cursor: itemRecord.cursor,
1898
+ node: itemRecord.node
1899
+ };
1900
+ }
1901
+ return { node };
1902
+ }),
1903
+ pagination: record.pagination
1904
+ };
3135
1905
  }
3136
- if (path === "items.node" || path.startsWith("items.")) continue;
3137
- nodeSelection.add(path);
3138
- }
3139
- for (const entry of connection.items) {
3140
- const node = entry.node;
3141
- const cursor = "cursor" in entry ? entry.cursor : void 0;
3142
- cursors.push(cursor);
3143
- if (isNodeRef(node)) {
3144
- ids.push(getNodeRefId(node));
3145
- continue;
1906
+ return null;
1907
+ })();
1908
+ if (connection) {
1909
+ const ids = [];
1910
+ const cursors = [];
1911
+ const nodeSelection = childPaths.size > 0 ? /* @__PURE__ */ new Set() : childPaths;
1912
+ if (childPaths.size > 0) for (const path of childPaths) {
1913
+ if (path.startsWith("items.node.")) {
1914
+ nodeSelection.add(path.slice(11));
1915
+ continue;
1916
+ }
1917
+ if (path.startsWith("node.")) {
1918
+ nodeSelection.add(path.slice(5));
1919
+ continue;
1920
+ }
1921
+ if (path === "items.node" || path.startsWith("items.")) continue;
1922
+ nodeSelection.add(path);
3146
1923
  }
3147
- if (node && typeof node === "object") {
3148
- const childId = toEntityId(childType, childConfig.getId(node));
3149
- this.writeEntity(childType, node, nodeSelection, snapshots, plan, fieldPath, blockedMask, void 0, listSnapshots);
3150
- ids.push(childId);
3151
- continue;
1924
+ for (const entry of connection.items) {
1925
+ const node = entry.node;
1926
+ const cursor = "cursor" in entry ? entry.cursor : void 0;
1927
+ cursors.push(cursor);
1928
+ if (isNodeRef(node)) {
1929
+ ids.push(getNodeRefId(node));
1930
+ continue;
1931
+ }
1932
+ if (node && typeof node === "object") {
1933
+ const childId = toEntityId(childType, childConfig.getId(node));
1934
+ this.writeEntity(childType, node, nodeSelection, plan, fieldPath);
1935
+ ids.push(childId);
1936
+ continue;
1937
+ }
3152
1938
  }
1939
+ const listKey = getListKey(entityId, key, fieldArgs?.hash);
1940
+ const previousList = this.store.getListState(listKey);
1941
+ const argsValue = fieldArgs?.value;
1942
+ const nextListState = this.mergeListState(previousList, ids, cursors, connection.pagination, getPaginationMergeInfo(argsValue));
1943
+ const listChanged = !areListStatesEqual(previousList, nextListState);
1944
+ result[key] = createNodeRefsForIds(nextListState.ids, this.store.read(entityId)?.[key], { reuseCurrentArray: !listChanged });
1945
+ this.store.setList(listKey, nextListState);
3153
1946
  }
3154
- const listKey = getListKey(entityId, key, fieldArgs?.hash);
3155
- const previousList = this.store.getListState(listKey);
3156
- const argsValue = fieldArgs?.value;
3157
- const nextListState = this.mergeListState(previousList, ids, cursors, connection.pagination, getPaginationMergeInfo(argsValue));
3158
- const listChanged = !areListStatesEqual(previousList, nextListState);
3159
- result[key] = createNodeRefsForIds(nextListState.ids, this.store.read(entityId)?.[key], { reuseCurrentArray: !listChanged });
3160
- this.store.setList(listKey, nextListState);
3161
- }
3162
- } else result[key] = value;
3163
- }
3164
- for (const [key, value] of Object.entries(record)) if (!(key in (config.fields ?? {}))) {
3165
- const fieldPath = pathPrefix ? `${pathPrefix}.${key}` : key;
3166
- if (blockedMask && isCovered(blockedMask, fieldPath)) continue;
3167
- result[key] = value;
3168
- }
3169
- if (snapshots && !snapshots.has(entityId)) snapshots.set(entityId, this.store.snapshot(entityId));
3170
- this.viewDataCache.invalidate(entityId);
3171
- this.store.merge(entityId, result, select);
3172
- this.linkParentLists(type, entityId, result, snapshots, listSnapshots, insert ?? "after");
3173
- if (!pathPrefix && insert) this.insertIntoRootLists(type, entityId, insert, listSnapshots);
3174
- return entityId;
1947
+ } else result[key] = value;
1948
+ }
1949
+ for (const [key, value] of Object.entries(record)) if (!(key in (config.fields ?? {}))) result[key] = value;
1950
+ this.viewDataCache.invalidate(entityId);
1951
+ this.store.merge(entityId, result, select);
1952
+ this.linkParentLists(type, entityId, result, insert ?? "after");
1953
+ if (!pathPrefix && insert) this.insertIntoRootLists(type, entityId, insert);
1954
+ return entityId;
1955
+ });
3175
1956
  }
3176
- linkParentLists(type, entityId, record, snapshots, listSnapshots, insert) {
1957
+ linkParentLists(type, entityId, record, insert) {
3177
1958
  if (insert === "none") return;
3178
1959
  const parents = this.parentLists.get(type);
3179
1960
  if (!parents) return;
@@ -3186,23 +1967,18 @@ var FateClient = class {
3186
1967
  if (!existing) continue;
3187
1968
  const current = Array.isArray(existing[parent.field]) ? existing[parent.field] : [];
3188
1969
  if (current.some((item) => isNodeRef(item) && getNodeRefId(item) === entityId)) continue;
3189
- if (snapshots && !snapshots.has(parentId)) snapshots.set(parentId, this.store.snapshot(parentId));
3190
1970
  this.viewDataCache.invalidate(parentId);
3191
1971
  const defaultListKey = getListKey(parentId, parent.field);
3192
1972
  const defaultListState = this.store.getListState(defaultListKey);
3193
1973
  let nextField = current;
3194
1974
  if (defaultListState) {
3195
1975
  const { list: nextDefaultListState, pending } = this.applyListInsert(defaultListState, entityId, insert);
3196
- if (nextDefaultListState !== defaultListState) {
3197
- this.snapshotList(defaultListKey, listSnapshots);
3198
- this.store.setList(defaultListKey, nextDefaultListState);
3199
- }
1976
+ if (nextDefaultListState !== defaultListState) this.store.setList(defaultListKey, nextDefaultListState);
3200
1977
  const nextFieldIds = pending ? getListEntries(nextDefaultListState).map(({ id }) => id) : nextDefaultListState.ids;
3201
1978
  nextField = createNodeRefsForIds(nextFieldIds, current);
3202
1979
  } else {
3203
1980
  const currentIds = current.map((item) => isNodeRef(item) ? getNodeRefId(item) : null).filter((id) => id != null);
3204
1981
  const nextDefaultListState = { ids: insert === "before" ? [entityId, ...currentIds] : [...currentIds, entityId] };
3205
- this.snapshotList(defaultListKey, listSnapshots);
3206
1982
  this.store.setList(defaultListKey, nextDefaultListState);
3207
1983
  nextField = createNodeRefsForIds(nextDefaultListState.ids, current);
3208
1984
  }
@@ -3210,7 +1986,6 @@ var FateClient = class {
3210
1986
  for (const [listKey, listState] of registeredLists) {
3211
1987
  const nextListState = this.applyListInsert(listState, entityId, insert).list;
3212
1988
  if (nextListState === listState) continue;
3213
- this.snapshotList(listKey, listSnapshots);
3214
1989
  this.store.setList(listKey, nextListState);
3215
1990
  }
3216
1991
  this.store.merge(parentId, { [parent.field]: nextField }, [parent.field]);
@@ -3258,92 +2033,93 @@ var FateClient = class {
3258
2033
  target[key] = null;
3259
2034
  continue;
3260
2035
  }
3261
- if (Array.isArray(value)) if (nextSelection.items && typeof nextSelection.items === "object") {
3262
- const selection = nextSelection.items;
3263
- const fieldArgs = plan.args.get(fieldPath);
3264
- const listKey = getListKey(parentId, key, fieldArgs?.hash);
3265
- const listState = this.store.getListState(listKey);
3266
- const connection = { items: (listState ? getListEntries(listState) : value.map((item) => ({
3267
- cursor: void 0,
3268
- id: isNodeRef(item) ? getNodeRefId(item) : null
3269
- }))).map((entry, index) => {
3270
- const entityId = entry.id;
3271
- if (!entityId) return {
3272
- cursor: listState?.cursors?.[index],
3273
- node: null
3274
- };
2036
+ if (Array.isArray(value)) {
2037
+ if (nextSelection.items && typeof nextSelection.items === "object") {
2038
+ const selection = nextSelection.items;
2039
+ const fieldArgs = plan.args.get(fieldPath);
2040
+ const listKey = getListKey(parentId, key, fieldArgs?.hash);
2041
+ const listState = this.store.getListState(listKey);
2042
+ const connection = { items: (listState ? getListEntries(listState) : value.map((item) => ({
2043
+ cursor: void 0,
2044
+ id: isNodeRef(item) ? getNodeRefId(item) : null
2045
+ }))).map((entry, index) => {
2046
+ const entityId = entry.id;
2047
+ if (!entityId) return {
2048
+ cursor: listState?.cursors?.[index],
2049
+ node: null
2050
+ };
2051
+ ids.add(entityId);
2052
+ const record = this.store.read(entityId);
2053
+ const { id, type } = parseEntityId(entityId);
2054
+ const node = {
2055
+ __typename: type,
2056
+ id
2057
+ };
2058
+ if (record) walk(selection.node, record, node, entityId, fieldPath);
2059
+ const itemResult = { node: record ? node : null };
2060
+ if (selection.cursor === true) itemResult.cursor = entry.cursor;
2061
+ return itemResult;
2062
+ }) };
2063
+ if ("pagination" in nextSelection && nextSelection.pagination) {
2064
+ const paginationSelection = nextSelection.pagination;
2065
+ const storedPagination = listState?.pagination;
2066
+ if (storedPagination) {
2067
+ const pagination = {};
2068
+ if (paginationSelection.nextCursor === true) {
2069
+ if (storedPagination.nextCursor !== void 0) pagination.nextCursor = storedPagination.nextCursor;
2070
+ }
2071
+ if (paginationSelection.previousCursor === true) {
2072
+ if (storedPagination.previousCursor !== void 0) pagination.previousCursor = storedPagination.previousCursor;
2073
+ }
2074
+ if (paginationSelection.hasNext === true) pagination.hasNext = storedPagination.hasNext;
2075
+ if (paginationSelection.hasPrevious === true) pagination.hasPrevious = storedPagination.hasPrevious;
2076
+ if (Object.keys(pagination).length > 0) connection.pagination = pagination;
2077
+ } else connection.pagination = void 0;
2078
+ }
2079
+ const { id: ownerRawId, type: parentType } = parseEntityId(parentId);
2080
+ if (parentType) {
2081
+ const childType = this.getListNodeType(parentType, key);
2082
+ const metadata = {
2083
+ args: (() => {
2084
+ if (!fieldArgs?.value && ownerRawId === void 0) return;
2085
+ const value = fieldArgs?.value ? { ...fieldArgs.value } : {};
2086
+ if (ownerRawId !== void 0) value.id = ownerRawId;
2087
+ return value;
2088
+ })(),
2089
+ field: key,
2090
+ hash: fieldArgs?.hash,
2091
+ key: listKey,
2092
+ live: plan.live.get(fieldPath),
2093
+ owner: parentId,
2094
+ procedure: `${parentType}.${key}`,
2095
+ root: false,
2096
+ type: childType
2097
+ };
2098
+ Object.defineProperty(connection, ConnectionTag, {
2099
+ configurable: false,
2100
+ enumerable: false,
2101
+ value: metadata,
2102
+ writable: false
2103
+ });
2104
+ }
2105
+ target[key] = connection;
2106
+ } else target[key] = value.map((item) => {
2107
+ const entityId = isNodeRef(item) ? getNodeRefId(item) : null;
2108
+ if (!entityId) return item;
3275
2109
  ids.add(entityId);
3276
2110
  const record = this.store.read(entityId);
3277
2111
  const { id, type } = parseEntityId(entityId);
3278
- const node = {
3279
- __typename: type,
3280
- id
3281
- };
3282
- if (record) walk(selection.node, record, node, entityId, fieldPath);
3283
- const itemResult = { node: record ? node : null };
3284
- if (selection.cursor === true) itemResult.cursor = entry.cursor;
3285
- return itemResult;
3286
- }) };
3287
- if ("pagination" in nextSelection && nextSelection.pagination) {
3288
- const paginationSelection = nextSelection.pagination;
3289
- const storedPagination = listState?.pagination;
3290
- if (storedPagination) {
3291
- const pagination = {};
3292
- if (paginationSelection.nextCursor === true) {
3293
- if (storedPagination.nextCursor !== void 0) pagination.nextCursor = storedPagination.nextCursor;
3294
- }
3295
- if (paginationSelection.previousCursor === true) {
3296
- if (storedPagination.previousCursor !== void 0) pagination.previousCursor = storedPagination.previousCursor;
3297
- }
3298
- if (paginationSelection.hasNext === true) pagination.hasNext = storedPagination.hasNext;
3299
- if (paginationSelection.hasPrevious === true) pagination.hasPrevious = storedPagination.hasPrevious;
3300
- if (Object.keys(pagination).length > 0) connection.pagination = pagination;
3301
- } else connection.pagination = void 0;
3302
- }
3303
- const { id: ownerRawId, type: parentType } = parseEntityId(parentId);
3304
- if (parentType) {
3305
- const childType = this.getListNodeType(parentType, key);
3306
- const metadata = {
3307
- args: (() => {
3308
- if (!fieldArgs?.value && ownerRawId === void 0) return;
3309
- const value = fieldArgs?.value ? { ...fieldArgs.value } : {};
3310
- if (ownerRawId !== void 0) value.id = ownerRawId;
3311
- return value;
3312
- })(),
3313
- field: key,
3314
- hash: fieldArgs?.hash,
3315
- key: listKey,
3316
- live: plan.live.get(fieldPath),
3317
- owner: parentId,
3318
- procedure: `${parentType}.${key}`,
3319
- root: false,
3320
- type: childType
3321
- };
3322
- Object.defineProperty(connection, ConnectionTag, {
3323
- configurable: false,
3324
- enumerable: false,
3325
- value: metadata,
3326
- writable: false
3327
- });
3328
- }
3329
- target[key] = connection;
3330
- } else target[key] = value.map((item) => {
3331
- const entityId = isNodeRef(item) ? getNodeRefId(item) : null;
3332
- if (!entityId) return item;
3333
- ids.add(entityId);
3334
- const record = this.store.read(entityId);
3335
- const { id, type } = parseEntityId(entityId);
3336
- if (record) {
3337
- const node = {
3338
- __typename: type,
3339
- id
3340
- };
3341
- walk(nextSelection, record, node, entityId, fieldPath);
3342
- return node;
3343
- }
3344
- return null;
3345
- });
3346
- else if (isNodeRef(value)) {
2112
+ if (record) {
2113
+ const node = {
2114
+ __typename: type,
2115
+ id
2116
+ };
2117
+ walk(nextSelection, record, node, entityId, fieldPath);
2118
+ return node;
2119
+ }
2120
+ return null;
2121
+ });
2122
+ } else if (isNodeRef(value)) {
3347
2123
  const entityId = getNodeRefId(value);
3348
2124
  ids.add(entityId);
3349
2125
  const relatedRecord = this.store.read(entityId);
@@ -3440,7 +2216,7 @@ function createClient(options) {
3440
2216
  * Builds a `Transport` backed by a tRPC client using the configured resolvers
3441
2217
  * for by-id queries, lists, and mutations.
3442
2218
  */
3443
- function createTRPCTransport({ byId, client, lists, mutations, queries }) {
2219
+ function createTRPCTransport({ byId, client, lists, mutateDurably, mutations, queries }) {
3444
2220
  const transport = {
3445
2221
  async fetchById(type, ids, select, args) {
3446
2222
  const resolver = byId[type];
@@ -3468,7 +2244,8 @@ function createTRPCTransport({ byId, client, lists, mutations, queries }) {
3468
2244
  args,
3469
2245
  select: [...select]
3470
2246
  });
3471
- }
2247
+ },
2248
+ mutateDurably
3472
2249
  };
3473
2250
  transport.mutate = async (procedure, input, select) => {
3474
2251
  const resolver = mutations?.[procedure];
@@ -3546,7 +2323,7 @@ const responseError = async (response) => {
3546
2323
  return new FateRequestError(errorCodeFromStatus(response.status), message, { status: response.status });
3547
2324
  };
3548
2325
  const assertProtocolResponse = (value) => {
3549
- 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.");
2326
+ 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.");
3550
2327
  return value;
3551
2328
  };
3552
2329
  const resultValue = (result) => {
@@ -3584,7 +2361,7 @@ function createHTTPTransport({ eventSource, fetch: fetchImpl = defaultFetch, hea
3584
2361
  const response = await fetchImpl(endpoint, {
3585
2362
  body: JSON.stringify({
3586
2363
  operations: batch.map((entry) => entry.operation),
3587
- version: 1
2364
+ version: batch.some((entry) => entry.operation.mutation) ? 2 : 1
3588
2365
  }),
3589
2366
  headers: await requestHeaders({ "content-type": "application/json" }, headers),
3590
2367
  method: "POST"
@@ -3641,6 +2418,15 @@ function createHTTPTransport({ eventSource, fetch: fetchImpl = defaultFetch, hea
3641
2418
  name,
3642
2419
  select: [...select]
3643
2420
  });
2421
+ },
2422
+ mutateDurably(name, input, select, identity) {
2423
+ return enqueue({
2424
+ input,
2425
+ kind: "mutation",
2426
+ mutation: identity,
2427
+ name,
2428
+ select: [...select]
2429
+ });
3644
2430
  }
3645
2431
  };
3646
2432
  if (liveEnabled) {