@nkzw/fate 1.0.3 → 1.1.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 (172) hide show
  1. package/README.md +354 -2
  2. package/docs/api/classes/FateClient.md +110 -40
  3. package/docs/api/functions/clientRoot.md +1 -1
  4. package/docs/api/functions/createClient.md +8 -4
  5. package/docs/api/functions/createGraphQLTransport.md +21 -0
  6. package/docs/api/functions/createHTTPTransport.md +1 -1
  7. package/docs/api/functions/createTRPCTransport.md +1 -1
  8. package/docs/api/functions/getListEntries.md +1 -1
  9. package/docs/api/functions/getSelectionPlan.md +1 -1
  10. package/docs/api/functions/graphqlMutation.md +39 -0
  11. package/docs/api/functions/isRecord.md +1 -1
  12. package/docs/api/functions/isViewTag.md +1 -1
  13. package/docs/api/functions/liveConnectionTopic.md +1 -1
  14. package/docs/api/functions/liveEntityTopic.md +1 -1
  15. package/docs/api/functions/liveGlobalConnectionTopic.md +1 -1
  16. package/docs/api/functions/mutation.md +1 -1
  17. package/docs/api/functions/toEntityId.md +1 -1
  18. package/docs/api/functions/view.md +1 -1
  19. package/docs/api/index.md +10 -0
  20. package/docs/api/interfaces/FateThenable.md +3 -3
  21. package/docs/api/interfaces/Transport.md +7 -7
  22. package/docs/api/server/classes/FateRequestError.md +5 -5
  23. package/docs/api/server/drizzle/functions/createDrizzleFate.md +1 -1
  24. package/docs/api/server/drizzle/functions/createDrizzleSourceAdapter.md +1 -1
  25. package/docs/api/server/drizzle/functions/createDrizzleSourceRegistry.md +1 -1
  26. package/docs/api/server/drizzle/type-aliases/DrizzleManyToManyConfig.md +1 -1
  27. package/docs/api/server/drizzle/type-aliases/DrizzleManyToManyInput.md +1 -1
  28. package/docs/api/server/drizzle/type-aliases/DrizzleQueryExtra.md +3 -3
  29. package/docs/api/server/drizzle/type-aliases/DrizzleSourceAdapter.md +9 -9
  30. package/docs/api/server/drizzle/type-aliases/DrizzleViewConfig.md +1 -1
  31. package/docs/api/server/functions/bindSourceProcedures.md +1 -1
  32. package/docs/api/server/functions/computed.md +1 -1
  33. package/docs/api/server/functions/count.md +1 -1
  34. package/docs/api/server/functions/createFateFetchHandler.md +1 -1
  35. package/docs/api/server/functions/createFateServer.md +1 -1
  36. package/docs/api/server/functions/createHonoFateHandler.md +1 -1
  37. package/docs/api/server/functions/createLiveEventBus.md +1 -1
  38. package/docs/api/server/functions/createNestedSourcePlan.md +1 -1
  39. package/docs/api/server/functions/createResolver.md +1 -1
  40. package/docs/api/server/functions/createSourcePlan.md +1 -1
  41. package/docs/api/server/functions/createSourceProcedures.md +1 -1
  42. package/docs/api/server/functions/dataView.md +1 -1
  43. package/docs/api/server/functions/field.md +1 -1
  44. package/docs/api/server/functions/getNestedSelection.md +1 -1
  45. package/docs/api/server/functions/getScopedArgs.md +1 -1
  46. package/docs/api/server/functions/hasNestedSelection.md +1 -1
  47. package/docs/api/server/functions/list.md +1 -1
  48. package/docs/api/server/functions/refetchSourceById.md +1 -1
  49. package/docs/api/server/functions/resolveSourceById.md +1 -1
  50. package/docs/api/server/functions/resolveSourceByIds.md +1 -1
  51. package/docs/api/server/functions/resolveSourceConnection.md +1 -1
  52. package/docs/api/server/functions/resolver.md +1 -1
  53. package/docs/api/server/functions/toPrismaSelect.md +1 -1
  54. package/docs/api/server/functions/withConnection.md +1 -1
  55. package/docs/api/server/prisma/functions/createPrismaFate.md +1 -1
  56. package/docs/api/server/prisma/functions/createPrismaSourceAdapter.md +1 -1
  57. package/docs/api/server/prisma/functions/createPrismaSourceRegistry.md +1 -1
  58. package/docs/api/server/prisma/functions/prismaConnectionArgs.md +1 -1
  59. package/docs/api/server/prisma/type-aliases/PrismaDelegate.md +4 -4
  60. package/docs/api/server/prisma/type-aliases/PrismaQueryExtra.md +1 -1
  61. package/docs/api/server/prisma/type-aliases/PrismaSourceAdapter.md +9 -9
  62. package/docs/api/server/prisma/type-aliases/PrismaViewConfig.md +1 -1
  63. package/docs/api/server/type-aliases/ComputedField.md +5 -5
  64. package/docs/api/server/type-aliases/ComputedSelection.md +1 -1
  65. package/docs/api/server/type-aliases/ConnectionItem.md +3 -3
  66. package/docs/api/server/type-aliases/ConnectionPagination.md +5 -5
  67. package/docs/api/server/type-aliases/ConnectionResult.md +3 -3
  68. package/docs/api/server/type-aliases/CountSelection.md +4 -4
  69. package/docs/api/server/type-aliases/CountWhere.md +1 -1
  70. package/docs/api/server/type-aliases/DataViewListOptions.md +2 -2
  71. package/docs/api/server/type-aliases/DataViewOrderBy.md +1 -1
  72. package/docs/api/server/type-aliases/DataViewOrderDirection.md +1 -1
  73. package/docs/api/server/type-aliases/DataViewResult.md +1 -1
  74. package/docs/api/server/type-aliases/Entity.md +1 -1
  75. package/docs/api/server/type-aliases/FateServer.md +5 -5
  76. package/docs/api/server/type-aliases/FateServerManifest.md +1 -1
  77. package/docs/api/server/type-aliases/FieldSelection.md +3 -3
  78. package/docs/api/server/type-aliases/LiveConnectionEventType.md +1 -1
  79. package/docs/api/server/type-aliases/LiveConnectionSourceEvent.md +1 -1
  80. package/docs/api/server/type-aliases/LiveEventBus.md +1 -1
  81. package/docs/api/server/type-aliases/LiveEventType.md +1 -1
  82. package/docs/api/server/type-aliases/LiveSourceEvent.md +1 -1
  83. package/docs/api/server/type-aliases/NativeFateAPI.md +4 -4
  84. package/docs/api/server/type-aliases/OrderDirection.md +1 -1
  85. package/docs/api/server/type-aliases/SourceConfig.md +6 -6
  86. package/docs/api/server/type-aliases/SourceDefinition.md +6 -6
  87. package/docs/api/server/type-aliases/SourceOrder.md +1 -1
  88. package/docs/api/server/type-aliases/SourceOrderField.md +3 -3
  89. package/docs/api/server/type-aliases/SourcePlan.md +1 -1
  90. package/docs/api/server/type-aliases/SourcePlanNode.md +1 -1
  91. package/docs/api/server/type-aliases/SourceRegistry.md +1 -1
  92. package/docs/api/server/type-aliases/SourceRelation.md +1 -1
  93. package/docs/api/server/type-aliases/SourceRelationConfig.md +1 -1
  94. package/docs/api/server/variables/byIdInput.md +1 -1
  95. package/docs/api/server/variables/connectionArgs.md +1 -1
  96. package/docs/api/type-aliases/ConnectionMetadata.md +1 -1
  97. package/docs/api/type-aliases/ConnectionRef.md +1 -1
  98. package/docs/api/type-aliases/Entity.md +2 -2
  99. package/docs/api/type-aliases/EntityId.md +1 -1
  100. package/docs/api/type-aliases/FateDehydratedState.md +11 -0
  101. package/docs/api/type-aliases/FateLiveConnectionEvent.md +1 -1
  102. package/docs/api/type-aliases/FateLiveEvent.md +1 -1
  103. package/docs/api/type-aliases/FateMutations.md +1 -1
  104. package/docs/api/type-aliases/FateOperation.md +1 -1
  105. package/docs/api/type-aliases/FateProtocolRequest.md +1 -1
  106. package/docs/api/type-aliases/FateProtocolResponse.md +1 -1
  107. package/docs/api/type-aliases/FateRecord.md +1 -1
  108. package/docs/api/type-aliases/FateRoots.md +1 -1
  109. package/docs/api/type-aliases/GraphQLMutationDefinition.md +19 -0
  110. package/docs/api/type-aliases/GraphQLMutationInput.md +11 -0
  111. package/docs/api/type-aliases/GraphQLMutationMap.md +11 -0
  112. package/docs/api/type-aliases/GraphQLMutationOutput.md +11 -0
  113. package/docs/api/type-aliases/GraphQLTransportOptions.md +119 -0
  114. package/docs/api/type-aliases/HydrateOptions.md +7 -0
  115. package/docs/api/type-aliases/HydrationLimits.md +7 -0
  116. package/docs/api/type-aliases/InferFateAPI.md +1 -1
  117. package/docs/api/type-aliases/List.md +1 -1
  118. package/docs/api/type-aliases/ListEntry.md +1 -1
  119. package/docs/api/type-aliases/ListItem.md +1 -1
  120. package/docs/api/type-aliases/Mask.md +1 -1
  121. package/docs/api/type-aliases/MutationDefinition.md +1 -1
  122. package/docs/api/type-aliases/MutationEntity.md +1 -1
  123. package/docs/api/type-aliases/MutationIdentifier.md +1 -1
  124. package/docs/api/type-aliases/MutationInput.md +1 -1
  125. package/docs/api/type-aliases/MutationResult.md +1 -1
  126. package/docs/api/type-aliases/NodesItem.md +1 -1
  127. package/docs/api/type-aliases/Pagination.md +5 -5
  128. package/docs/api/type-aliases/Request.md +1 -1
  129. package/docs/api/type-aliases/RequestMode.md +1 -1
  130. package/docs/api/type-aliases/RequestOptions.md +1 -1
  131. package/docs/api/type-aliases/RequestResult.md +1 -1
  132. package/docs/api/type-aliases/Selection.md +1 -1
  133. package/docs/api/type-aliases/Snapshot.md +1 -1
  134. package/docs/api/type-aliases/TypeConfig.md +4 -4
  135. package/docs/api/type-aliases/View.md +1 -1
  136. package/docs/api/type-aliases/ViewData.md +1 -1
  137. package/docs/api/type-aliases/ViewEntity.md +1 -1
  138. package/docs/api/type-aliases/ViewEntityName.md +1 -1
  139. package/docs/api/type-aliases/ViewRef.md +1 -1
  140. package/docs/api/type-aliases/ViewSelection.md +1 -1
  141. package/docs/api/type-aliases/ViewSnapshot.md +1 -1
  142. package/docs/api/type-aliases/ViewTag.md +1 -1
  143. package/docs/api/variables/ConnectionTag.md +1 -1
  144. package/docs/guide/getting-started.md +1 -1
  145. package/docs/guide/graphql-integration.md +298 -0
  146. package/docs/guide/requests.md +52 -0
  147. package/docs/guide/server-integration.md +2 -1
  148. package/lib/{executor-BqwHdN3n.d.mts → executor-sLcAqDMs.d.mts} +1 -1
  149. package/lib/graphqlTransport-DbRHbYtb.d.mts +74 -0
  150. package/lib/graphqlTransport-IFgmY-Qq.mjs +499 -0
  151. package/lib/graphqlTransport.d.mts +2 -0
  152. package/lib/graphqlTransport.mjs +2 -0
  153. package/lib/index.d.mts +5 -4
  154. package/lib/index.mjs +501 -89
  155. package/lib/{list-4wRNSVgI.d.mts → list-CMQOQGdn.d.mts} +1 -1
  156. package/lib/list.d.mts +1 -1
  157. package/lib/list.mjs +1 -1
  158. package/lib/{liveTopics-DzNtJaBD.mjs → liveTopics-JIDh3t3P.mjs} +1 -1
  159. package/lib/{record-CirbfZWX.d.mts → record-BtdKzEGI.d.mts} +2 -2
  160. package/lib/server/drizzle.d.mts +6 -8
  161. package/lib/server/drizzle.mjs +2 -2
  162. package/lib/server/prisma.d.mts +6 -8
  163. package/lib/server/prisma.mjs +2 -2
  164. package/lib/server.d.mts +3 -3
  165. package/lib/server.mjs +3 -3
  166. package/lib/{sourceRouter-C22uFcZg.mjs → sourceRouter-N9Vp0Cj6.mjs} +1 -1
  167. package/lib/{types-Dz46PXr3.d.mts → transport-BTgtSwOM.d.mts} +211 -143
  168. package/lib/vite.d.mts +1 -1
  169. package/lib/vite.mjs +150 -3
  170. package/package.json +11 -3
  171. /package/lib/{list-C_mi6GnE.mjs → list-_6fXYLtJ.mjs} +0 -0
  172. /package/lib/{record-AeZJC9fd.mjs → record-B07VwXd-.mjs} +0 -0
package/lib/index.mjs CHANGED
@@ -1,6 +1,7 @@
1
- import { t as isRecord } from "./record-AeZJC9fd.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-DzNtJaBD.mjs";
3
- import { t as getListEntries } from "./list-C_mi6GnE.mjs";
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-JIDh3t3P.mjs";
3
+ import { t as getListEntries } from "./list-_6fXYLtJ.mjs";
4
+ import { n as graphqlMutation, t as createGraphQLTransport } from "./graphqlTransport-IFgmY-Qq.mjs";
4
5
  //#region src/cache.ts
5
6
  var ViewDataCache = class {
6
7
  constructor() {
@@ -8,6 +9,11 @@ var ViewDataCache = class {
8
9
  this.rootDependencies = /* @__PURE__ */ new Map();
9
10
  this.dependencyIndex = /* @__PURE__ */ new Map();
10
11
  }
12
+ clear() {
13
+ this.cache.clear();
14
+ this.rootDependencies.clear();
15
+ this.dependencyIndex.clear();
16
+ }
11
17
  get(entityId, view, ref) {
12
18
  return this.cache.get(entityId)?.get(view)?.get(ref) ?? null;
13
19
  }
@@ -63,6 +69,274 @@ var ViewDataCache = class {
63
69
  }
64
70
  };
65
71
  //#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
+ const viewTag = "__fate-view__";
82
+ /** Generates a stable view tag for a view definition. */
83
+ function getViewTag(id) {
84
+ return `${viewTag}${id}`;
85
+ }
86
+ /** Determines whether a property key is a fate view tag. */
87
+ function isViewTag(key) {
88
+ return key.startsWith(viewTag);
89
+ }
90
+ /** Indicates whether a request item represents an explicit node ID. */
91
+ function isNodeItem(item) {
92
+ return "id" in item;
93
+ }
94
+ /** Indicates whether a request item represents explicit node IDs. */
95
+ function isNodesItem(item) {
96
+ return "ids" in item;
97
+ }
98
+ /** Indicates whether a request item represents a root query. */
99
+ function isQueryItem(item) {
100
+ return "view" in item && !("id" in item) && !("ids" in item);
101
+ }
102
+ /** Brand used on root definitions to mark their identity in the d.ts output. */
103
+ const RootKind = "__fate__root";
104
+ /** Brand used on mutation definitions to mark their identity in the d.ts output. */
105
+ const MutationKind = "__fate__mutation";
106
+ //#endregion
107
+ //#region src/node-ref.ts
108
+ function createNodeRef(id) {
109
+ const ref = {};
110
+ Object.defineProperty(ref, NodeRefTag, {
111
+ configurable: false,
112
+ enumerable: false,
113
+ value: id,
114
+ writable: false
115
+ });
116
+ return Object.freeze(ref);
117
+ }
118
+ function isNodeRef(value) {
119
+ return !value || typeof value !== "object" ? false : NodeRefTag in value;
120
+ }
121
+ function getNodeRefId(ref) {
122
+ return ref[NodeRefTag];
123
+ }
124
+ //#endregion
125
+ //#region src/hydration.ts
126
+ const maxDepth = 64;
127
+ const defaultHydrationLimits = {
128
+ maxCollectionLength: 1e5,
129
+ maxNodes: 25e4,
130
+ maxStringLength: 1e6
131
+ };
132
+ const resolveHydrationLimits = (limits) => {
133
+ const result = {
134
+ ...defaultHydrationLimits,
135
+ ...limits
136
+ };
137
+ 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.`);
138
+ return result;
139
+ };
140
+ const error = (path, message) => /* @__PURE__ */ new Error(`fate: Cannot serialize hydration value at '${path}': ${message}`);
141
+ const isPlainObject = (value) => {
142
+ const prototype = Object.getPrototypeOf(value);
143
+ return prototype === Object.prototype || prototype === null;
144
+ };
145
+ const isDenseArray = (value) => {
146
+ for (let index = 0; index < value.length; index += 1) if (!Object.hasOwn(value, index)) return false;
147
+ return true;
148
+ };
149
+ const isDate$1 = (value) => Object.prototype.toString.call(value) === "[object Date]" && typeof value.getTime === "function" && typeof value.toISOString === "function";
150
+ const encodeHydrationValue = (value, limits) => {
151
+ const resolvedLimits = resolveHydrationLimits(limits);
152
+ const seen = /* @__PURE__ */ new Set();
153
+ let nodes = 0;
154
+ const encode = (entry, path, depth) => {
155
+ nodes += 1;
156
+ if (nodes > resolvedLimits.maxNodes) throw error(path, `Maximum node count of ${String(resolvedLimits.maxNodes)} exceeded.`);
157
+ if (depth > maxDepth) throw error(path, `Maximum depth of ${String(maxDepth)} exceeded.`);
158
+ if (entry === null) return ["null"];
159
+ const type = typeof entry;
160
+ if (typeof entry === "string" || typeof entry === "boolean") {
161
+ if (typeof entry === "string" && entry.length > resolvedLimits.maxStringLength) throw error(path, `Maximum string length of ${String(resolvedLimits.maxStringLength)} exceeded.`);
162
+ return [typeof entry, entry];
163
+ }
164
+ if (type === "number") {
165
+ if (Number.isNaN(entry)) return ["number", "NaN"];
166
+ if (entry === Infinity) return ["number", "Infinity"];
167
+ if (entry === -Infinity) return ["number", "-Infinity"];
168
+ if (Object.is(entry, -0)) return ["number", "-0"];
169
+ return ["number", entry];
170
+ }
171
+ if (type === "undefined") return ["undefined"];
172
+ if (type === "bigint") {
173
+ const bigint = String(entry);
174
+ if (bigint.length > resolvedLimits.maxStringLength) throw error(path, `Maximum string length of ${String(resolvedLimits.maxStringLength)} exceeded.`);
175
+ return ["bigint", bigint];
176
+ }
177
+ if (type === "function" || type === "symbol") throw error(path, `Received unsupported '${type}' value.`);
178
+ if (!entry || typeof entry !== "object") throw error(path, `Received unsupported '${type}' value.`);
179
+ if (isNodeRef(entry)) {
180
+ const id = getNodeRefId(entry);
181
+ if (id.length > resolvedLimits.maxStringLength) throw error(path, `Maximum string length of ${String(resolvedLimits.maxStringLength)} exceeded.`);
182
+ return ["ref", id];
183
+ }
184
+ if (isDate$1(entry)) {
185
+ if (Number.isNaN(entry.getTime())) throw error(path, `Received an invalid Date.`);
186
+ return ["date", entry.toISOString()];
187
+ }
188
+ if (seen.has(entry)) throw error(path, `Circular references are not supported.`);
189
+ seen.add(entry);
190
+ try {
191
+ if (Array.isArray(entry)) {
192
+ if (!isDenseArray(entry)) throw error(path, `Sparse arrays are not supported.`);
193
+ if (entry.length > resolvedLimits.maxCollectionLength) throw error(path, `Maximum collection length of ${String(resolvedLimits.maxCollectionLength)} exceeded.`);
194
+ return ["array", entry.map((item, index) => encode(item, `${path}[${String(index)}]`, depth + 1))];
195
+ }
196
+ if (!isPlainObject(entry)) throw error(path, `Received unsupported '${entry.constructor?.name ?? "object"}' value.`);
197
+ if (Object.getOwnPropertySymbols(entry).length > 0) throw error(path, `Received an unsupported symbol-keyed property.`);
198
+ const entries = Object.entries(entry);
199
+ if (entries.length > resolvedLimits.maxCollectionLength) throw error(path, `Maximum collection length of ${String(resolvedLimits.maxCollectionLength)} exceeded.`);
200
+ return ["object", entries.map(([key, item]) => {
201
+ if (key.length > resolvedLimits.maxStringLength) throw error(path, `Maximum string length of ${String(resolvedLimits.maxStringLength)} exceeded.`);
202
+ return [key, encode(item, `${path}.${key}`, depth + 1)];
203
+ })];
204
+ } finally {
205
+ seen.delete(entry);
206
+ }
207
+ };
208
+ return encode(value, "data", 0);
209
+ };
210
+ const invalidPayload = (path) => /* @__PURE__ */ new Error(`fate: Invalid hydration payload at '${path}'.`);
211
+ const tagLengths = {
212
+ array: 2,
213
+ bigint: 2,
214
+ boolean: 2,
215
+ date: 2,
216
+ null: 1,
217
+ number: 2,
218
+ object: 2,
219
+ ref: 2,
220
+ string: 2,
221
+ undefined: 1
222
+ };
223
+ const decodeHydrationValue = (value, limits) => {
224
+ const resolvedLimits = resolveHydrationLimits(limits);
225
+ let nodes = 0;
226
+ const decode = (entry, path, depth) => {
227
+ nodes += 1;
228
+ if (nodes > resolvedLimits.maxNodes || depth > maxDepth || !Array.isArray(entry) || typeof entry[0] !== "string") throw invalidPayload(path);
229
+ const [tag, payload] = entry;
230
+ if (entry.length !== tagLengths[tag]) throw invalidPayload(path);
231
+ switch (tag) {
232
+ case "null": return null;
233
+ case "undefined": return;
234
+ case "string":
235
+ if (typeof payload !== "string" || payload.length > resolvedLimits.maxStringLength) throw invalidPayload(path);
236
+ return payload;
237
+ case "boolean":
238
+ if (typeof payload !== "boolean") throw invalidPayload(path);
239
+ return payload;
240
+ case "number":
241
+ if (typeof payload === "number") {
242
+ if (!Number.isFinite(payload) || Object.is(payload, -0)) throw invalidPayload(path);
243
+ return payload;
244
+ }
245
+ if (payload === "NaN") return NaN;
246
+ if (payload === "Infinity") return Infinity;
247
+ if (payload === "-Infinity") return -Infinity;
248
+ if (payload === "-0") return -0;
249
+ throw invalidPayload(path);
250
+ case "bigint":
251
+ if (typeof payload !== "string" || payload.length > resolvedLimits.maxStringLength) throw invalidPayload(path);
252
+ try {
253
+ const bigint = BigInt(payload);
254
+ if (String(bigint) !== payload) throw invalidPayload(path);
255
+ return bigint;
256
+ } catch {
257
+ throw invalidPayload(path);
258
+ }
259
+ case "date": {
260
+ if (typeof payload !== "string" || payload.length > resolvedLimits.maxStringLength) throw invalidPayload(path);
261
+ const date = new Date(payload);
262
+ if (Number.isNaN(date.getTime()) || date.toISOString() !== payload) throw invalidPayload(path);
263
+ return date;
264
+ }
265
+ case "ref":
266
+ if (typeof payload !== "string" || payload.length > resolvedLimits.maxStringLength) throw invalidPayload(path);
267
+ return createNodeRef(payload);
268
+ case "array":
269
+ if (!Array.isArray(payload) || !isDenseArray(payload) || payload.length > resolvedLimits.maxCollectionLength) throw invalidPayload(path);
270
+ return payload.map((item, index) => decode(item, `${path}[${String(index)}]`, depth + 1));
271
+ case "object": {
272
+ if (!Array.isArray(payload) || payload.length > resolvedLimits.maxCollectionLength) throw invalidPayload(path);
273
+ const result = Object.create(null);
274
+ for (const item of payload) {
275
+ if (!Array.isArray(item) || item.length !== 2 || typeof item[0] !== "string" || item[0].length > resolvedLimits.maxStringLength) throw invalidPayload(path);
276
+ if (item[0] in result) throw invalidPayload(path);
277
+ result[item[0]] = decode(item[1], `${path}.${item[0]}`, depth + 1);
278
+ }
279
+ return result;
280
+ }
281
+ default: throw invalidPayload(path);
282
+ }
283
+ };
284
+ return decode(value, "data", 0);
285
+ };
286
+ const isRecord$1 = (value) => {
287
+ if (!value || typeof value !== "object" || Array.isArray(value) || isNodeRef(value)) return false;
288
+ const prototype = Object.getPrototypeOf(value);
289
+ return prototype === Object.prototype || prototype === null;
290
+ };
291
+ const isStringArray = (value) => Array.isArray(value) && value.every((entry) => typeof entry === "string");
292
+ const hasDuplicates = (values) => new Set(values).size !== values.length;
293
+ const isUniqueStringArray = (value) => isStringArray(value) && !hasDuplicates(value);
294
+ const hasOnlyKeys = (value, keys) => Object.keys(value).every((key) => keys.has(key));
295
+ const listKeys = new Set([
296
+ "backwardPageLimit",
297
+ "cursors",
298
+ "forwardPageLimit",
299
+ "ids",
300
+ "liveAfterIds",
301
+ "liveBeforeIds",
302
+ "pagination",
303
+ "pendingAfterIds",
304
+ "pendingBeforeIds"
305
+ ]);
306
+ const paginationKeys = new Set([
307
+ "hasNext",
308
+ "hasPrevious",
309
+ "nextCursor",
310
+ "previousCursor"
311
+ ]);
312
+ const clientStateKeys = new Set([
313
+ "rootLists",
314
+ "rootRequests",
315
+ "store"
316
+ ]);
317
+ const storeStateKeys = new Set([
318
+ "coverage",
319
+ "lists",
320
+ "records"
321
+ ]);
322
+ const isEntry = (value) => Array.isArray(value) && value.length === 2 && typeof value[0] === "string";
323
+ const isOptionalLimit = (value) => value === void 0 || typeof value === "number" && Number.isInteger(value) && value >= 0;
324
+ 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");
325
+ const isList = (value) => {
326
+ 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;
327
+ 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));
328
+ };
329
+ const isFieldPath = (value) => value.length > 0 && value.split(".").every((segment) => segment.length > 0);
330
+ const isFieldPathArray = (value) => isUniqueStringArray(value) && value.every(isFieldPath);
331
+ const hasDuplicateEntries = (entries) => hasDuplicates(entries.map((entry) => entry[0]));
332
+ const decodeClientHydrationState = (value, limits) => {
333
+ const decoded = decodeHydrationValue(value, limits);
334
+ 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");
335
+ 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");
336
+ 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");
337
+ return decoded;
338
+ };
339
+ //#endregion
66
340
  //#region src/mask.ts
67
341
  function emptyMask() {
68
342
  return {
@@ -111,6 +385,18 @@ function fromPaths(paths) {
111
385
  for (const path of paths) addPath(mask, path);
112
386
  return mask;
113
387
  }
388
+ function toPaths(mask) {
389
+ const paths = [];
390
+ const visit = (current, prefix) => {
391
+ if (current.all) {
392
+ if (prefix) paths.push(prefix);
393
+ return;
394
+ }
395
+ for (const [key, child] of current.children) visit(child, prefix ? `${prefix}.${key}` : key);
396
+ };
397
+ visit(mask, null);
398
+ return paths;
399
+ }
114
400
  function isCovered(mask, path) {
115
401
  if (mask.all) return true;
116
402
  const parts = path.split(".");
@@ -138,7 +424,7 @@ function intersects(a, b) {
138
424
  return false;
139
425
  }
140
426
  //#endregion
141
- //#region ../../node_modules/.pnpm/@trpc+server@11.17.0_typescript@5.9.3/node_modules/@trpc/server/dist/getErrorShape-BPSzUA7W.mjs
427
+ //#region ../../node_modules/.pnpm/@trpc+server@11.17.0_typescript@6.0.3/node_modules/@trpc/server/dist/getErrorShape-BPSzUA7W.mjs
142
428
  var __create = Object.create;
143
429
  var __defProp = Object.defineProperty;
144
430
  var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
@@ -264,41 +550,6 @@ __toESM(__commonJS({ "../../node_modules/.pnpm/@oxc-project+runtime@0.72.2/node_
264
550
  module.exports = _objectSpread2, module.exports.__esModule = true, module.exports["default"] = module.exports;
265
551
  } })(), 1);
266
552
  //#endregion
267
- //#region src/types.ts
268
- /** Internal marker added to objects that represent a view payload. */
269
- const ViewKind = Symbol("__fate__view");
270
- /** Symbol used to attach the set of view tags that were spread into a ref or masked record. */
271
- const ViewsTag = Symbol("__fate__views");
272
- /** Symbol used to brand a value as a node reference inside the cache. */
273
- const NodeRefTag = Symbol("__fate__node-ref");
274
- /** Symbol attached to connection results so pagination helpers can find their metadata. */
275
- const ConnectionTag = Symbol("__fate__connection");
276
- const viewTag = "__fate-view__";
277
- /** Generates a stable view tag for a view definition. */
278
- function getViewTag(id) {
279
- return `${viewTag}${id}`;
280
- }
281
- /** Determines whether a property key is a fate view tag. */
282
- function isViewTag(key) {
283
- return key.startsWith(viewTag);
284
- }
285
- /** Indicates whether a request item represents an explicit node ID. */
286
- function isNodeItem(item) {
287
- return "id" in item;
288
- }
289
- /** Indicates whether a request item represents explicit node IDs. */
290
- function isNodesItem(item) {
291
- return "ids" in item;
292
- }
293
- /** Indicates whether a request item represents a root query. */
294
- function isQueryItem(item) {
295
- return "view" in item && !("id" in item) && !("ids" in item);
296
- }
297
- /** Brand used on root definitions to mark their identity in the d.ts output. */
298
- const RootKind = "__fate__root";
299
- /** Brand used on mutation definitions to mark their identity in the d.ts output. */
300
- const MutationKind = "__fate__mutation";
301
- //#endregion
302
553
  //#region src/view.ts
303
554
  /**
304
555
  * Collects all view payloads that apply to the given ref.
@@ -593,7 +844,7 @@ function wrapMutation(client, identifier) {
593
844
  client.clearOptimisticUpdate(optimisticToken);
594
845
  }
595
846
  };
596
- const mutationPromise = performMutation();
847
+ const mutationPromise = client.trackPendingRequest(performMutation);
597
848
  if (optimisticEntityId) client.registerPendingOptimisticMutation(optimisticEntityId, mutationPromise);
598
849
  return mutationPromise;
599
850
  };
@@ -621,24 +872,6 @@ const categorizeHTTPErrorStatus = (statusCode) => {
621
872
  }
622
873
  };
623
874
  //#endregion
624
- //#region src/node-ref.ts
625
- function createNodeRef(id) {
626
- const ref = {};
627
- Object.defineProperty(ref, NodeRefTag, {
628
- configurable: false,
629
- enumerable: false,
630
- value: id,
631
- writable: false
632
- });
633
- return Object.freeze(ref);
634
- }
635
- function isNodeRef(value) {
636
- return !value || typeof value !== "object" ? false : NodeRefTag in value;
637
- }
638
- function getNodeRefId(ref) {
639
- return ref[NodeRefTag];
640
- }
641
- //#endregion
642
875
  //#region src/operation-lifetime.ts
643
876
  var OperationLifetime = class {
644
877
  constructor(releaseBufferSize) {
@@ -726,6 +959,45 @@ const cloneValue = (value) => {
726
959
  }
727
960
  return value;
728
961
  };
962
+ const isPlainRecord = (value) => {
963
+ if (value == null || typeof value !== "object" || Array.isArray(value) || isNodeRef(value)) return false;
964
+ const prototype = Object.getPrototypeOf(value);
965
+ return prototype === Object.prototype || prototype === null;
966
+ };
967
+ const isDate = (value) => value != null && typeof value === "object" && Object.prototype.toString.call(value) === "[object Date]" && typeof value.getTime === "function";
968
+ const mergePreservingExisting = (incoming, existing) => {
969
+ if (!isPlainRecord(incoming) || !isPlainRecord(existing)) return existing;
970
+ const result = Object.create(null);
971
+ for (const [key, value] of Object.entries(incoming)) Object.defineProperty(result, key, {
972
+ configurable: true,
973
+ enumerable: true,
974
+ value,
975
+ writable: true
976
+ });
977
+ for (const [key, value] of Object.entries(existing)) Object.defineProperty(result, key, {
978
+ configurable: true,
979
+ enumerable: true,
980
+ value: Object.hasOwn(result, key) ? mergePreservingExisting(result[key], value) : value,
981
+ writable: true
982
+ });
983
+ return result;
984
+ };
985
+ const areHydrationValuesEqual = (left, right) => {
986
+ if (Object.is(left, right)) return true;
987
+ if (isNodeRef(left) || isNodeRef(right)) return isNodeRef(left) && isNodeRef(right) && getNodeRefId(left) === getNodeRefId(right);
988
+ if (isDate(left) || isDate(right)) return isDate(left) && isDate(right) && left.getTime() === right.getTime();
989
+ 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]));
990
+ if (!isPlainRecord(left) || !isPlainRecord(right)) return false;
991
+ const leftKeys = Object.keys(left);
992
+ const rightKeys = Object.keys(right);
993
+ return leftKeys.length === rightKeys.length && leftKeys.every((key) => Object.hasOwn(right, key) && areHydrationValuesEqual(left[key], right[key]));
994
+ };
995
+ const areMasksEqual = (left, right) => {
996
+ if (!left || !right) return left === right;
997
+ const leftPaths = toPaths(left).sort();
998
+ const rightPaths = toPaths(right).sort();
999
+ return leftPaths.length === rightPaths.length && leftPaths.every((path, index) => path === rightPaths[index]);
1000
+ };
729
1001
  const emptyFunction = () => {};
730
1002
  var Store = class {
731
1003
  constructor() {
@@ -739,6 +1011,58 @@ var Store = class {
739
1011
  this.subscriptions = /* @__PURE__ */ new Map();
740
1012
  this.listSubscriptions = /* @__PURE__ */ new Map();
741
1013
  }
1014
+ dehydrate() {
1015
+ return {
1016
+ coverage: [...this.coverage].map(([id, mask]) => [id, toPaths(mask)]),
1017
+ lists: [...this.lists],
1018
+ records: [...this.records]
1019
+ };
1020
+ }
1021
+ hydrate(state, mode, options = {}) {
1022
+ const previousCoverage = this.coverage;
1023
+ const previousLists = this.lists;
1024
+ const previousRecords = this.records;
1025
+ const coverage = mode === "replace" ? /* @__PURE__ */ new Map() : new Map([...previousCoverage].map(([id, mask]) => [id, cloneMask(mask)]));
1026
+ const lists = mode === "replace" ? /* @__PURE__ */ new Map() : new Map(previousLists);
1027
+ const records = mode === "replace" ? /* @__PURE__ */ new Map() : new Map(previousRecords);
1028
+ for (const [id, incoming] of state.records) {
1029
+ const previous = records.get(id);
1030
+ const next = previous && mode === "preserve-existing" ? mergePreservingExisting(incoming, previous) : incoming;
1031
+ records.set(id, previous && areHydrationValuesEqual(previous, next) ? previous : next);
1032
+ }
1033
+ for (const [id, paths] of state.coverage) {
1034
+ const incoming = fromPaths(paths);
1035
+ const previous = coverage.get(id);
1036
+ if (previous && mode === "preserve-existing") union(previous, incoming);
1037
+ else coverage.set(id, incoming);
1038
+ }
1039
+ for (const [key, list] of state.lists) {
1040
+ const previous = lists.get(key);
1041
+ if (mode === "replace" || !previous) lists.set(key, previous && areHydrationValuesEqual(previous, list) ? previous : list);
1042
+ }
1043
+ this.coverage = coverage;
1044
+ this.lists = lists;
1045
+ this.records = records;
1046
+ this.rebuildIndexes();
1047
+ const changedRecordIds = /* @__PURE__ */ new Set();
1048
+ for (const id of new Set([...previousRecords.keys(), ...records.keys()])) if (!areHydrationValuesEqual(previousRecords.get(id), records.get(id)) || !areMasksEqual(previousCoverage.get(id), coverage.get(id))) changedRecordIds.add(id);
1049
+ const changedListKeys = /* @__PURE__ */ new Set();
1050
+ for (const key of new Set([...previousLists.keys(), ...lists.keys()])) if (!areHydrationValuesEqual(previousLists.get(key), lists.get(key))) changedListKeys.add(key);
1051
+ const notify = () => {
1052
+ for (const id of changedRecordIds) this.notify(id);
1053
+ for (const key of changedListKeys) this.notifyListSubscribers(key);
1054
+ };
1055
+ if (!options.deferNotifications) notify();
1056
+ return notify;
1057
+ }
1058
+ rebuildIndexes() {
1059
+ this.listKeysByOwnerField.clear();
1060
+ this.listKeysByReferencedEntity.clear();
1061
+ this.recordReferenceFields.clear();
1062
+ this.recordReferencesByTarget.clear();
1063
+ for (const [id, record] of this.records) this.addRecordReferenceIndexes(id, record);
1064
+ for (const [key, list] of this.lists) this.addListIndexes(key, list);
1065
+ }
742
1066
  read(id) {
743
1067
  return this.records.get(id);
744
1068
  }
@@ -1337,6 +1661,14 @@ const getId = (record) => {
1337
1661
  };
1338
1662
  const emptySet = /* @__PURE__ */ new Set();
1339
1663
  const emptyDispose = () => void 0;
1664
+ const compareStrings = (left, right) => left < right ? -1 : left > right ? 1 : 0;
1665
+ const getDefaultHydrationScope = (roots, types) => JSON.stringify({
1666
+ roots: Object.entries(roots ?? {}).map(([name, root]) => [name, root.type]).sort(([left], [right]) => compareStrings(left, right)),
1667
+ types: types.map(({ fields, type }) => ({
1668
+ fields: Object.entries(fields ?? {}).map(([field, descriptor]) => [field, descriptor]).sort(([left], [right]) => compareStrings(left, right)),
1669
+ type
1670
+ })).sort((left, right) => compareStrings(left.type, right.type))
1671
+ });
1340
1672
  const dropCanonicalListIds = (listIds, ids) => {
1341
1673
  if (!listIds || listIds.length === 0) return;
1342
1674
  const canonicalIds = new Set(ids);
@@ -1477,6 +1809,7 @@ var FateClient = class {
1477
1809
  this.stalledRequests = /* @__PURE__ */ new Set();
1478
1810
  this.gcPending = false;
1479
1811
  this.gcScheduled = false;
1812
+ this.pendingNetworkRequests = 0;
1480
1813
  this.store = new Store();
1481
1814
  this.viewDataCache = new ViewDataCache();
1482
1815
  this.liveConnectionInvalidationToken = 0;
@@ -1485,6 +1818,10 @@ var FateClient = class {
1485
1818
  this.mutations = Object.create(null);
1486
1819
  this.onLiveError = options.onLiveError;
1487
1820
  this.operationLifetime = new OperationLifetime(options.gcReleaseBufferSize ?? 10);
1821
+ this.hydrationLimits = resolveHydrationLimits(options.hydrationLimits);
1822
+ const hydrationScope = options.hydrationScope ?? getDefaultHydrationScope(options.roots, options.types);
1823
+ if (typeof hydrationScope !== "string" || hydrationScope.length === 0) throw new Error(`fate: Hydration scope must be a non-empty string.`);
1824
+ this.hydrationScope = hydrationScope;
1488
1825
  this.roots = options.roots;
1489
1826
  this.transport = options.transport;
1490
1827
  this.types = new Map(options.types.map((entity) => [entity.type, {
@@ -1529,6 +1866,56 @@ var FateClient = class {
1529
1866
  }
1530
1867
  }
1531
1868
  }
1869
+ /**
1870
+ * Exports the durable normalized cache as an opaque JSON-safe value for SSR
1871
+ * transfer or persistence. Runtime requests, subscriptions, retainers, and
1872
+ * optimistic state are intentionally excluded.
1873
+ */
1874
+ dehydrate() {
1875
+ if (this.hasActiveOptimisticUpdates()) throw new Error(`fate: Cannot dehydrate while optimistic updates are active.`);
1876
+ if (this.hasPendingRequests()) throw new Error(`fate: Cannot dehydrate while requests are pending.`);
1877
+ return {
1878
+ data: encodeHydrationValue({
1879
+ rootLists: [...this.rootLists].map(([type, keys]) => [type, [...keys]]),
1880
+ rootRequests: [...this.rootRequests],
1881
+ store: this.store.dehydrate()
1882
+ }, this.hydrationLimits),
1883
+ scope: this.hydrationScope,
1884
+ version: 1
1885
+ };
1886
+ }
1887
+ /**
1888
+ * Restores a durable normalized cache snapshot without restoring runtime
1889
+ * request handles, retainers, subscriptions, or optimistic update state.
1890
+ * Hydrate before rendering SSR content. Replaying the same snapshot is safe.
1891
+ */
1892
+ hydrate(state, options = {}) {
1893
+ if (!state || typeof state !== "object" || state.version !== 1) throw new Error(`fate: Unsupported hydration state version.`);
1894
+ if (state.scope !== this.hydrationScope) throw new Error(`fate: Hydration state scope does not match this client.`);
1895
+ if (this.hasActiveOptimisticUpdates()) throw new Error(`fate: Cannot hydrate while optimistic updates are active.`);
1896
+ if (this.hasPendingRequests()) throw new Error(`fate: Cannot hydrate while requests are pending.`);
1897
+ const mode = options.merge ?? "preserve-existing";
1898
+ if (mode !== "preserve-existing" && mode !== "replace") throw new Error(`fate: Unsupported hydration merge mode.`);
1899
+ const decoded = decodeClientHydrationState(state.data, this.hydrationLimits);
1900
+ const notify = this.store.hydrate(decoded.store, mode, { deferNotifications: true });
1901
+ if (mode === "replace") {
1902
+ this.rootLists.clear();
1903
+ this.rootRequests.clear();
1904
+ this.stableRefs.clear();
1905
+ }
1906
+ for (const [type, incomingKeys] of decoded.rootLists) {
1907
+ let keys = this.rootLists.get(type);
1908
+ if (!keys) {
1909
+ keys = /* @__PURE__ */ new Set();
1910
+ this.rootLists.set(type, keys);
1911
+ }
1912
+ for (const key of incomingKeys) keys.add(key);
1913
+ }
1914
+ for (const [key, entityId] of decoded.rootRequests) if (mode === "replace" || !this.rootRequests.has(key)) this.rootRequests.set(key, entityId);
1915
+ this.stalledRequests.clear();
1916
+ this.viewDataCache.clear();
1917
+ notify();
1918
+ }
1532
1919
  registerRootList(type, key) {
1533
1920
  let lists = this.rootLists.get(type);
1534
1921
  if (!lists) {
@@ -1597,7 +1984,7 @@ var FateClient = class {
1597
1984
  ...baseRecord,
1598
1985
  args: argsPayload
1599
1986
  } : argsPayload ? { args: argsPayload } : input;
1600
- return await this.transport.mutate(key, requestInput, select);
1987
+ return await this.trackPendingRequest(() => this.transport.mutate(key, requestInput, select));
1601
1988
  }
1602
1989
  write(type, data, select, snapshots, plan, pathPrefix = null, blockedMask, insert, listSnapshots) {
1603
1990
  return this.writeEntity(type, data, select, snapshots, plan, pathPrefix, blockedMask, insert, listSnapshots);
@@ -1690,13 +2077,18 @@ var FateClient = class {
1690
2077
  if (this.stalledRequests.has(key)) return resolveSnapshot();
1691
2078
  const pendingPromise = this.pending.get(key) || null;
1692
2079
  if (pendingPromise) return pendingPromise;
1693
- const promise = this.fetchByIdAndNormalize(type, [id], missing, plan).finally(() => this.pending.delete(key)).then(() => {
1694
- if (this.store.missingForSelection(entityId, selectedPaths).size > 0) {
1695
- this.stalledRequests.add(key);
1696
- return resolveSnapshot();
2080
+ const promise = this.trackPendingRequest(async () => {
2081
+ await this.fetchByIdAndNormalize(type, [id], missing, plan);
2082
+ try {
2083
+ if (this.store.missingForSelection(entityId, selectedPaths).size > 0) {
2084
+ this.stalledRequests.add(key);
2085
+ return resolveSnapshot();
2086
+ }
2087
+ this.stalledRequests.delete(key);
2088
+ return this.readView(view, ref);
2089
+ } finally {
2090
+ this.pending.delete(key);
1697
2091
  }
1698
- this.stalledRequests.delete(key);
1699
- return this.readView(view, ref);
1700
2092
  });
1701
2093
  this.pending.set(key, promise);
1702
2094
  return promise;
@@ -2134,6 +2526,9 @@ var FateClient = class {
2134
2526
  return filtered;
2135
2527
  }
2136
2528
  async loadConnection(view, connection, args, options = {}) {
2529
+ return await this.trackPendingRequest(() => this.loadConnectionInternal(view, connection, args, options));
2530
+ }
2531
+ async loadConnectionInternal(view, connection, args, options = {}) {
2137
2532
  const direction = options.direction ?? "forward";
2138
2533
  if (connection.root) {
2139
2534
  if (!this.transport.fetchList) throw new Error(`fate: transport does not support list fetching. Please add support for 'fetchList' in your transport.`);
@@ -2379,6 +2774,17 @@ var FateClient = class {
2379
2774
  hasActiveOptimisticUpdates() {
2380
2775
  return this.optimisticMasks.size > 0 || this.optimisticByEntity.size > 0 || this.pendingOptimisticMutations.size > 0;
2381
2776
  }
2777
+ hasPendingRequests() {
2778
+ return this.pendingNetworkRequests > 0;
2779
+ }
2780
+ async trackPendingRequest(request) {
2781
+ this.pendingNetworkRequests += 1;
2782
+ try {
2783
+ return await request();
2784
+ } finally {
2785
+ this.pendingNetworkRequests -= 1;
2786
+ }
2787
+ }
2382
2788
  runPendingGarbageCollection() {
2383
2789
  if (this.gcPending && !this.hasActiveOptimisticUpdates()) this.gc();
2384
2790
  }
@@ -2531,35 +2937,41 @@ var FateClient = class {
2531
2937
  }
2532
2938
  async fetchByIdAndNormalize(type, ids, select, plan, prefix = null) {
2533
2939
  const resolvedArgs = resolvedArgsFromPlan(plan);
2534
- const records = await this.transport.fetchById(type, ids, select, resolvedArgs);
2535
- for (const record of records) this.writeEntity(type, record, select, void 0, plan, prefix);
2940
+ await this.trackPendingRequest(async () => {
2941
+ const records = await this.transport.fetchById(type, ids, select, resolvedArgs);
2942
+ for (const record of records) this.writeEntity(type, record, select, void 0, plan, prefix);
2943
+ });
2536
2944
  }
2537
2945
  async fetchQueryAndNormalize(item) {
2538
2946
  if (!this.transport.fetchQuery) throw new Error(`fate: transport does not support queries. Please add support for 'fetchQuery' in your transport for '${item.name}'.`);
2539
- const record = await this.transport.fetchQuery(item.name, item.plan.paths, item.argsPayload);
2540
- if (!record || typeof record !== "object") {
2541
- this.rootRequests.set(item.queryKey, null);
2542
- return;
2543
- }
2544
- const entityId = this.writeEntity(item.type, record, item.plan.paths, void 0, item.plan);
2545
- this.rootRequests.set(item.queryKey, entityId);
2947
+ await this.trackPendingRequest(async () => {
2948
+ const record = await this.transport.fetchQuery(item.name, item.plan.paths, item.argsPayload);
2949
+ if (!record || typeof record !== "object") {
2950
+ this.rootRequests.set(item.queryKey, null);
2951
+ return;
2952
+ }
2953
+ const entityId = this.writeEntity(item.type, record, item.plan.paths, void 0, item.plan);
2954
+ this.rootRequests.set(item.queryKey, entityId);
2955
+ });
2546
2956
  }
2547
2957
  async fetchListAndNormalize(item) {
2548
2958
  if (!this.transport.fetchList) throw new Error(`fate: 'transport.fetchList' is not configured but request includes a list for key '${item.name}'.`);
2549
- const { items, pagination } = await this.transport.fetchList(item.name, item.plan.paths, item.argsPayload);
2550
- const ids = [];
2551
- const cursors = [];
2552
- for (const entry of items) {
2553
- const id = this.writeEntity(item.type, entry.node, item.plan.paths, void 0, item.plan);
2554
- ids.push(id);
2555
- cursors.push(entry.cursor);
2556
- }
2557
- if (!filterConnectionArgs(item.argsPayload)) this.registerRootList(item.type, item.listKey);
2558
- const previous = this.store.getListState(item.listKey);
2559
- this.store.setList(item.listKey, this.mergeListState(previous, ids, cursors, pagination, {
2560
- ...getPaginationMergeInfo(item.argsPayload),
2561
- replace: true
2562
- }));
2959
+ await this.trackPendingRequest(async () => {
2960
+ const { items, pagination } = await this.transport.fetchList(item.name, item.plan.paths, item.argsPayload);
2961
+ const ids = [];
2962
+ const cursors = [];
2963
+ for (const entry of items) {
2964
+ const id = this.writeEntity(item.type, entry.node, item.plan.paths, void 0, item.plan);
2965
+ ids.push(id);
2966
+ cursors.push(entry.cursor);
2967
+ }
2968
+ if (!filterConnectionArgs(item.argsPayload)) this.registerRootList(item.type, item.listKey);
2969
+ const previous = this.store.getListState(item.listKey);
2970
+ this.store.setList(item.listKey, this.mergeListState(previous, ids, cursors, pagination, {
2971
+ ...getPaginationMergeInfo(item.argsPayload),
2972
+ replace: true
2973
+ }));
2974
+ });
2563
2975
  }
2564
2976
  writeEntity(type, record, select, snapshots, plan, pathPrefix = null, blockedMask, insert, listSnapshots) {
2565
2977
  const config = this.types.get(type);
@@ -3396,4 +3808,4 @@ function clientRoot(type) {
3396
3808
  });
3397
3809
  }
3398
3810
  //#endregion
3399
- export { ConnectionTag, FateClient, clientRoot, createClient, createHTTPTransport, createTRPCTransport, getListEntries, getSelectionPlan, isRecord, isViewTag, liveConnectionTopic, liveEntityTopic, liveGlobalConnectionTopic, mutation, toEntityId, view };
3811
+ export { ConnectionTag, FateClient, clientRoot, createClient, createGraphQLTransport, createHTTPTransport, createTRPCTransport, getListEntries, getSelectionPlan, graphqlMutation, isRecord, isViewTag, liveConnectionTopic, liveEntityTopic, liveGlobalConnectionTopic, mutation, toEntityId, view };