@nkzw/fate 1.0.3 → 1.2.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 (186) hide show
  1. package/README.md +1220 -147
  2. package/docs/api/classes/FateClient.md +134 -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/defer.md +21 -0
  9. package/docs/api/functions/getDeferredMetadata.md +21 -0
  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 +39 -0
  13. package/docs/api/functions/isDeferred.md +15 -0
  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 +19 -0
  23. package/docs/api/interfaces/FateThenable.md +3 -3
  24. package/docs/api/interfaces/Transport.md +7 -7
  25. package/docs/api/server/classes/FateRequestError.md +5 -5
  26. package/docs/api/server/drizzle/functions/createDrizzleFate.md +1 -1
  27. package/docs/api/server/drizzle/functions/createDrizzleSourceAdapter.md +1 -1
  28. package/docs/api/server/drizzle/functions/createDrizzleSourceRegistry.md +1 -1
  29. package/docs/api/server/drizzle/type-aliases/DrizzleManyToManyConfig.md +1 -1
  30. package/docs/api/server/drizzle/type-aliases/DrizzleManyToManyInput.md +1 -1
  31. package/docs/api/server/drizzle/type-aliases/DrizzleQueryExtra.md +3 -3
  32. package/docs/api/server/drizzle/type-aliases/DrizzleSourceAdapter.md +9 -9
  33. package/docs/api/server/drizzle/type-aliases/DrizzleViewConfig.md +1 -1
  34. package/docs/api/server/functions/bindSourceProcedures.md +1 -1
  35. package/docs/api/server/functions/computed.md +1 -1
  36. package/docs/api/server/functions/count.md +1 -1
  37. package/docs/api/server/functions/createFateFetchHandler.md +1 -1
  38. package/docs/api/server/functions/createFateServer.md +1 -1
  39. package/docs/api/server/functions/createHonoFateHandler.md +1 -1
  40. package/docs/api/server/functions/createLiveEventBus.md +1 -1
  41. package/docs/api/server/functions/createNestedSourcePlan.md +1 -1
  42. package/docs/api/server/functions/createResolver.md +1 -1
  43. package/docs/api/server/functions/createSourcePlan.md +1 -1
  44. package/docs/api/server/functions/createSourceProcedures.md +1 -1
  45. package/docs/api/server/functions/dataView.md +1 -1
  46. package/docs/api/server/functions/field.md +1 -1
  47. package/docs/api/server/functions/getNestedSelection.md +1 -1
  48. package/docs/api/server/functions/getScopedArgs.md +1 -1
  49. package/docs/api/server/functions/hasNestedSelection.md +1 -1
  50. package/docs/api/server/functions/list.md +1 -1
  51. package/docs/api/server/functions/refetchSourceById.md +1 -1
  52. package/docs/api/server/functions/resolveSourceById.md +1 -1
  53. package/docs/api/server/functions/resolveSourceByIds.md +1 -1
  54. package/docs/api/server/functions/resolveSourceConnection.md +1 -1
  55. package/docs/api/server/functions/resolver.md +1 -1
  56. package/docs/api/server/functions/toPrismaSelect.md +1 -1
  57. package/docs/api/server/functions/withConnection.md +1 -1
  58. package/docs/api/server/prisma/functions/createPrismaFate.md +1 -1
  59. package/docs/api/server/prisma/functions/createPrismaSourceAdapter.md +1 -1
  60. package/docs/api/server/prisma/functions/createPrismaSourceRegistry.md +1 -1
  61. package/docs/api/server/prisma/functions/prismaConnectionArgs.md +1 -1
  62. package/docs/api/server/prisma/type-aliases/PrismaDelegate.md +4 -4
  63. package/docs/api/server/prisma/type-aliases/PrismaQueryExtra.md +1 -1
  64. package/docs/api/server/prisma/type-aliases/PrismaSourceAdapter.md +9 -9
  65. package/docs/api/server/prisma/type-aliases/PrismaViewConfig.md +1 -1
  66. package/docs/api/server/type-aliases/ComputedField.md +5 -5
  67. package/docs/api/server/type-aliases/ComputedSelection.md +1 -1
  68. package/docs/api/server/type-aliases/ConnectionItem.md +3 -3
  69. package/docs/api/server/type-aliases/ConnectionPagination.md +5 -5
  70. package/docs/api/server/type-aliases/ConnectionResult.md +3 -3
  71. package/docs/api/server/type-aliases/CountSelection.md +4 -4
  72. package/docs/api/server/type-aliases/CountWhere.md +1 -1
  73. package/docs/api/server/type-aliases/DataViewListOptions.md +2 -2
  74. package/docs/api/server/type-aliases/DataViewOrderBy.md +1 -1
  75. package/docs/api/server/type-aliases/DataViewOrderDirection.md +1 -1
  76. package/docs/api/server/type-aliases/DataViewResult.md +1 -1
  77. package/docs/api/server/type-aliases/Entity.md +1 -1
  78. package/docs/api/server/type-aliases/FateServer.md +5 -5
  79. package/docs/api/server/type-aliases/FateServerManifest.md +1 -1
  80. package/docs/api/server/type-aliases/FieldSelection.md +3 -3
  81. package/docs/api/server/type-aliases/LiveConnectionEventType.md +1 -1
  82. package/docs/api/server/type-aliases/LiveConnectionSourceEvent.md +1 -1
  83. package/docs/api/server/type-aliases/LiveEventBus.md +1 -1
  84. package/docs/api/server/type-aliases/LiveEventType.md +1 -1
  85. package/docs/api/server/type-aliases/LiveSourceEvent.md +1 -1
  86. package/docs/api/server/type-aliases/NativeFateAPI.md +4 -4
  87. package/docs/api/server/type-aliases/OrderDirection.md +1 -1
  88. package/docs/api/server/type-aliases/SourceConfig.md +6 -6
  89. package/docs/api/server/type-aliases/SourceDefinition.md +6 -6
  90. package/docs/api/server/type-aliases/SourceOrder.md +1 -1
  91. package/docs/api/server/type-aliases/SourceOrderField.md +3 -3
  92. package/docs/api/server/type-aliases/SourcePlan.md +1 -1
  93. package/docs/api/server/type-aliases/SourcePlanNode.md +1 -1
  94. package/docs/api/server/type-aliases/SourceRegistry.md +1 -1
  95. package/docs/api/server/type-aliases/SourceRelation.md +1 -1
  96. package/docs/api/server/type-aliases/SourceRelationConfig.md +1 -1
  97. package/docs/api/server/variables/byIdInput.md +1 -1
  98. package/docs/api/server/variables/connectionArgs.md +1 -1
  99. package/docs/api/type-aliases/ConnectionMetadata.md +1 -1
  100. package/docs/api/type-aliases/ConnectionRef.md +1 -1
  101. package/docs/api/type-aliases/Deferred.md +19 -0
  102. package/docs/api/type-aliases/DeferredMetadata.md +7 -0
  103. package/docs/api/type-aliases/DeferredSelection.md +13 -0
  104. package/docs/api/type-aliases/DeferredSnapshot.md +11 -0
  105. package/docs/api/type-aliases/Entity.md +2 -2
  106. package/docs/api/type-aliases/EntityId.md +1 -1
  107. package/docs/api/type-aliases/FateDehydratedState.md +11 -0
  108. package/docs/api/type-aliases/FateLiveConnectionEvent.md +1 -1
  109. package/docs/api/type-aliases/FateLiveEvent.md +1 -1
  110. package/docs/api/type-aliases/FateMutations.md +1 -1
  111. package/docs/api/type-aliases/FateOperation.md +1 -1
  112. package/docs/api/type-aliases/FateProtocolRequest.md +1 -1
  113. package/docs/api/type-aliases/FateProtocolResponse.md +1 -1
  114. package/docs/api/type-aliases/FateRecord.md +1 -1
  115. package/docs/api/type-aliases/FateRoots.md +1 -1
  116. package/docs/api/type-aliases/GraphQLMutationDefinition.md +19 -0
  117. package/docs/api/type-aliases/GraphQLMutationInput.md +11 -0
  118. package/docs/api/type-aliases/GraphQLMutationMap.md +11 -0
  119. package/docs/api/type-aliases/GraphQLMutationOutput.md +11 -0
  120. package/docs/api/type-aliases/GraphQLTransportOptions.md +119 -0
  121. package/docs/api/type-aliases/HydrateOptions.md +7 -0
  122. package/docs/api/type-aliases/HydrationLimits.md +7 -0
  123. package/docs/api/type-aliases/InferFateAPI.md +1 -1
  124. package/docs/api/type-aliases/List.md +1 -1
  125. package/docs/api/type-aliases/ListEntry.md +1 -1
  126. package/docs/api/type-aliases/ListItem.md +1 -1
  127. package/docs/api/type-aliases/Mask.md +1 -1
  128. package/docs/api/type-aliases/MutationDefinition.md +1 -1
  129. package/docs/api/type-aliases/MutationEntity.md +1 -1
  130. package/docs/api/type-aliases/MutationIdentifier.md +1 -1
  131. package/docs/api/type-aliases/MutationInput.md +1 -1
  132. package/docs/api/type-aliases/MutationResult.md +1 -1
  133. package/docs/api/type-aliases/NodesItem.md +1 -1
  134. package/docs/api/type-aliases/Pagination.md +5 -5
  135. package/docs/api/type-aliases/Request.md +1 -1
  136. package/docs/api/type-aliases/RequestMode.md +1 -1
  137. package/docs/api/type-aliases/RequestOptions.md +1 -1
  138. package/docs/api/type-aliases/RequestResult.md +1 -1
  139. package/docs/api/type-aliases/Selection.md +1 -1
  140. package/docs/api/type-aliases/Snapshot.md +1 -1
  141. package/docs/api/type-aliases/TypeConfig.md +4 -4
  142. package/docs/api/type-aliases/View.md +1 -1
  143. package/docs/api/type-aliases/ViewData.md +1 -1
  144. package/docs/api/type-aliases/ViewEntity.md +1 -1
  145. package/docs/api/type-aliases/ViewEntityName.md +1 -1
  146. package/docs/api/type-aliases/ViewRef.md +1 -1
  147. package/docs/api/type-aliases/ViewSelection.md +1 -1
  148. package/docs/api/type-aliases/ViewSnapshot.md +1 -1
  149. package/docs/api/type-aliases/ViewTag.md +1 -1
  150. package/docs/api/variables/ConnectionTag.md +1 -1
  151. package/docs/api/variables/DeferTag.md +7 -0
  152. package/docs/api/variables/DeferredTag.md +7 -0
  153. package/docs/guide/actions.md +7 -7
  154. package/docs/guide/deferred-views.md +62 -0
  155. package/docs/guide/getting-started.md +21 -3
  156. package/docs/guide/requests.md +52 -0
  157. package/docs/guide/vue.md +256 -0
  158. package/docs/index.md +1 -0
  159. package/docs/integrations/graphql.md +345 -0
  160. package/docs/{guide/server-integration.md → integrations/server.md} +94 -7
  161. package/docs/{guide/void-integration.md → integrations/void.md} +94 -12
  162. package/lib/{executor-BqwHdN3n.d.mts → executor-Dh-MLUXc.d.mts} +1 -1
  163. package/lib/graphqlTransport-B3cbhvJX.d.mts +74 -0
  164. package/lib/graphqlTransport-IFgmY-Qq.mjs +499 -0
  165. package/lib/graphqlTransport.d.mts +2 -0
  166. package/lib/graphqlTransport.mjs +2 -0
  167. package/lib/index.d.mts +10 -4
  168. package/lib/index.mjs +619 -91
  169. package/lib/{list-4wRNSVgI.d.mts → list-BrL6PD_6.d.mts} +1 -1
  170. package/lib/list.d.mts +1 -1
  171. package/lib/list.mjs +1 -1
  172. package/lib/{liveTopics-DzNtJaBD.mjs → liveTopics-JIDh3t3P.mjs} +1 -1
  173. package/lib/{record-CirbfZWX.d.mts → record-BkvZwvFk.d.mts} +2 -2
  174. package/lib/server/drizzle.d.mts +6 -8
  175. package/lib/server/drizzle.mjs +2 -2
  176. package/lib/server/prisma.d.mts +6 -8
  177. package/lib/server/prisma.mjs +2 -2
  178. package/lib/server.d.mts +3 -3
  179. package/lib/server.mjs +3 -3
  180. package/lib/{sourceRouter-C22uFcZg.mjs → sourceRouter-N9Vp0Cj6.mjs} +1 -1
  181. package/lib/{types-Dz46PXr3.d.mts → transport-BjGm__3t.d.mts} +246 -145
  182. package/lib/vite.d.mts +2 -2
  183. package/lib/vite.mjs +150 -3
  184. package/package.json +11 -3
  185. /package/lib/{list-C_mi6GnE.mjs → list-_6fXYLtJ.mjs} +0 -0
  186. /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,288 @@ 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
+ /** 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 = new Set([
310
+ "backwardPageLimit",
311
+ "cursors",
312
+ "forwardPageLimit",
313
+ "ids",
314
+ "liveAfterIds",
315
+ "liveBeforeIds",
316
+ "pagination",
317
+ "pendingAfterIds",
318
+ "pendingBeforeIds"
319
+ ]);
320
+ const paginationKeys = new Set([
321
+ "hasNext",
322
+ "hasPrevious",
323
+ "nextCursor",
324
+ "previousCursor"
325
+ ]);
326
+ const clientStateKeys = new Set([
327
+ "rootLists",
328
+ "rootRequests",
329
+ "store"
330
+ ]);
331
+ const storeStateKeys = 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
66
354
  //#region src/mask.ts
67
355
  function emptyMask() {
68
356
  return {
@@ -111,6 +399,18 @@ function fromPaths(paths) {
111
399
  for (const path of paths) addPath(mask, path);
112
400
  return mask;
113
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
+ }
114
414
  function isCovered(mask, path) {
115
415
  if (mask.all) return true;
116
416
  const parts = path.split(".");
@@ -138,7 +438,7 @@ function intersects(a, b) {
138
438
  return false;
139
439
  }
140
440
  //#endregion
141
- //#region ../../node_modules/.pnpm/@trpc+server@11.17.0_typescript@5.9.3/node_modules/@trpc/server/dist/getErrorShape-BPSzUA7W.mjs
441
+ //#region ../../node_modules/.pnpm/@trpc+server@11.17.0_typescript@6.0.3/node_modules/@trpc/server/dist/getErrorShape-BPSzUA7W.mjs
142
442
  var __create = Object.create;
143
443
  var __defProp = Object.defineProperty;
144
444
  var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
@@ -264,41 +564,6 @@ __toESM(__commonJS({ "../../node_modules/.pnpm/@oxc-project+runtime@0.72.2/node_
264
564
  module.exports = _objectSpread2, module.exports.__esModule = true, module.exports["default"] = module.exports;
265
565
  } })(), 1);
266
566
  //#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
567
  //#region src/view.ts
303
568
  /**
304
569
  * Collects all view payloads that apply to the given ref.
@@ -455,6 +720,7 @@ const getSelectionPlan = (viewComposition, ref) => {
455
720
  if (value) paths.add(path);
456
721
  continue;
457
722
  }
723
+ if (isDeferredSelection(value)) continue;
458
724
  if (isViewTag(key)) {
459
725
  if (!ref || ref[ViewsTag] && ref[ViewsTag].has(key)) walk(value.select, prefix);
460
726
  continue;
@@ -492,6 +758,9 @@ const getSelectionPlan = (viewComposition, ref) => {
492
758
  paths
493
759
  };
494
760
  };
761
+ const getDeferredSelectionPlan = (field, selection) => {
762
+ return getSelectionPlan({ [field]: selection }, null);
763
+ };
495
764
  //#endregion
496
765
  //#region src/mutation.ts
497
766
  /**
@@ -593,7 +862,7 @@ function wrapMutation(client, identifier) {
593
862
  client.clearOptimisticUpdate(optimisticToken);
594
863
  }
595
864
  };
596
- const mutationPromise = performMutation();
865
+ const mutationPromise = client.trackPendingRequest(performMutation);
597
866
  if (optimisticEntityId) client.registerPendingOptimisticMutation(optimisticEntityId, mutationPromise);
598
867
  return mutationPromise;
599
868
  };
@@ -621,24 +890,6 @@ const categorizeHTTPErrorStatus = (statusCode) => {
621
890
  }
622
891
  };
623
892
  //#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
893
  //#region src/operation-lifetime.ts
643
894
  var OperationLifetime = class {
644
895
  constructor(releaseBufferSize) {
@@ -726,6 +977,45 @@ const cloneValue = (value) => {
726
977
  }
727
978
  return value;
728
979
  };
980
+ const isPlainRecord = (value) => {
981
+ if (value == null || typeof value !== "object" || Array.isArray(value) || isNodeRef(value)) return false;
982
+ const prototype = Object.getPrototypeOf(value);
983
+ return prototype === Object.prototype || prototype === null;
984
+ };
985
+ const isDate = (value) => value != null && typeof value === "object" && Object.prototype.toString.call(value) === "[object Date]" && typeof value.getTime === "function";
986
+ const mergePreservingExisting = (incoming, existing) => {
987
+ if (!isPlainRecord(incoming) || !isPlainRecord(existing)) return existing;
988
+ const result = Object.create(null);
989
+ for (const [key, value] of Object.entries(incoming)) Object.defineProperty(result, key, {
990
+ configurable: true,
991
+ enumerable: true,
992
+ value,
993
+ writable: true
994
+ });
995
+ for (const [key, value] of Object.entries(existing)) Object.defineProperty(result, key, {
996
+ configurable: true,
997
+ enumerable: true,
998
+ value: Object.hasOwn(result, key) ? mergePreservingExisting(result[key], value) : value,
999
+ writable: true
1000
+ });
1001
+ return result;
1002
+ };
1003
+ const areHydrationValuesEqual = (left, right) => {
1004
+ if (Object.is(left, right)) return true;
1005
+ if (isNodeRef(left) || isNodeRef(right)) return isNodeRef(left) && isNodeRef(right) && getNodeRefId(left) === getNodeRefId(right);
1006
+ if (isDate(left) || isDate(right)) return isDate(left) && isDate(right) && left.getTime() === right.getTime();
1007
+ 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]));
1008
+ if (!isPlainRecord(left) || !isPlainRecord(right)) return false;
1009
+ const leftKeys = Object.keys(left);
1010
+ const rightKeys = Object.keys(right);
1011
+ return leftKeys.length === rightKeys.length && leftKeys.every((key) => Object.hasOwn(right, key) && areHydrationValuesEqual(left[key], right[key]));
1012
+ };
1013
+ const areMasksEqual = (left, right) => {
1014
+ if (!left || !right) return left === right;
1015
+ const leftPaths = toPaths(left).sort();
1016
+ const rightPaths = toPaths(right).sort();
1017
+ return leftPaths.length === rightPaths.length && leftPaths.every((path, index) => path === rightPaths[index]);
1018
+ };
729
1019
  const emptyFunction = () => {};
730
1020
  var Store = class {
731
1021
  constructor() {
@@ -739,6 +1029,58 @@ var Store = class {
739
1029
  this.subscriptions = /* @__PURE__ */ new Map();
740
1030
  this.listSubscriptions = /* @__PURE__ */ new Map();
741
1031
  }
1032
+ dehydrate() {
1033
+ return {
1034
+ coverage: [...this.coverage].map(([id, mask]) => [id, toPaths(mask)]),
1035
+ lists: [...this.lists],
1036
+ records: [...this.records]
1037
+ };
1038
+ }
1039
+ hydrate(state, mode, options = {}) {
1040
+ const previousCoverage = this.coverage;
1041
+ const previousLists = this.lists;
1042
+ const previousRecords = this.records;
1043
+ const coverage = mode === "replace" ? /* @__PURE__ */ new Map() : new Map([...previousCoverage].map(([id, mask]) => [id, cloneMask(mask)]));
1044
+ const lists = mode === "replace" ? /* @__PURE__ */ new Map() : new Map(previousLists);
1045
+ const records = mode === "replace" ? /* @__PURE__ */ new Map() : new Map(previousRecords);
1046
+ for (const [id, incoming] of state.records) {
1047
+ const previous = records.get(id);
1048
+ const next = previous && mode === "preserve-existing" ? mergePreservingExisting(incoming, previous) : incoming;
1049
+ records.set(id, previous && areHydrationValuesEqual(previous, next) ? previous : next);
1050
+ }
1051
+ for (const [id, paths] of state.coverage) {
1052
+ const incoming = fromPaths(paths);
1053
+ const previous = coverage.get(id);
1054
+ if (previous && mode === "preserve-existing") union(previous, incoming);
1055
+ else coverage.set(id, incoming);
1056
+ }
1057
+ for (const [key, list] of state.lists) {
1058
+ const previous = lists.get(key);
1059
+ if (mode === "replace" || !previous) lists.set(key, previous && areHydrationValuesEqual(previous, list) ? previous : list);
1060
+ }
1061
+ this.coverage = coverage;
1062
+ this.lists = lists;
1063
+ this.records = records;
1064
+ this.rebuildIndexes();
1065
+ const changedRecordIds = /* @__PURE__ */ new Set();
1066
+ 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);
1067
+ const changedListKeys = /* @__PURE__ */ new Set();
1068
+ for (const key of new Set([...previousLists.keys(), ...lists.keys()])) if (!areHydrationValuesEqual(previousLists.get(key), lists.get(key))) changedListKeys.add(key);
1069
+ const notify = () => {
1070
+ for (const id of changedRecordIds) this.notify(id);
1071
+ for (const key of changedListKeys) this.notifyListSubscribers(key);
1072
+ };
1073
+ if (!options.deferNotifications) notify();
1074
+ return notify;
1075
+ }
1076
+ rebuildIndexes() {
1077
+ this.listKeysByOwnerField.clear();
1078
+ this.listKeysByReferencedEntity.clear();
1079
+ this.recordReferenceFields.clear();
1080
+ this.recordReferencesByTarget.clear();
1081
+ for (const [id, record] of this.records) this.addRecordReferenceIndexes(id, record);
1082
+ for (const [key, list] of this.lists) this.addListIndexes(key, list);
1083
+ }
742
1084
  read(id) {
743
1085
  return this.records.get(id);
744
1086
  }
@@ -1337,6 +1679,14 @@ const getId = (record) => {
1337
1679
  };
1338
1680
  const emptySet = /* @__PURE__ */ new Set();
1339
1681
  const emptyDispose = () => void 0;
1682
+ const compareStrings = (left, right) => left < right ? -1 : left > right ? 1 : 0;
1683
+ const getDefaultHydrationScope = (roots, types) => JSON.stringify({
1684
+ roots: Object.entries(roots ?? {}).map(([name, root]) => [name, root.type]).sort(([left], [right]) => compareStrings(left, right)),
1685
+ types: types.map(({ fields, type }) => ({
1686
+ fields: Object.entries(fields ?? {}).map(([field, descriptor]) => [field, descriptor]).sort(([left], [right]) => compareStrings(left, right)),
1687
+ type
1688
+ })).sort((left, right) => compareStrings(left.type, right.type))
1689
+ });
1340
1690
  const dropCanonicalListIds = (listIds, ids) => {
1341
1691
  if (!listIds || listIds.length === 0) return;
1342
1692
  const canonicalIds = new Set(ids);
@@ -1464,6 +1814,7 @@ var FateClient = class {
1464
1814
  this.parentLists = /* @__PURE__ */ new Map();
1465
1815
  this.rootLists = /* @__PURE__ */ new Map();
1466
1816
  this.pending = /* @__PURE__ */ new Map();
1817
+ this.pendingDeferred = /* @__PURE__ */ new Map();
1467
1818
  this.pendingOptimisticMutations = /* @__PURE__ */ new Map();
1468
1819
  this.optimisticMasks = /* @__PURE__ */ new Map();
1469
1820
  this.optimisticByEntity = /* @__PURE__ */ new Map();
@@ -1477,6 +1828,7 @@ var FateClient = class {
1477
1828
  this.stalledRequests = /* @__PURE__ */ new Set();
1478
1829
  this.gcPending = false;
1479
1830
  this.gcScheduled = false;
1831
+ this.pendingNetworkRequests = 0;
1480
1832
  this.store = new Store();
1481
1833
  this.viewDataCache = new ViewDataCache();
1482
1834
  this.liveConnectionInvalidationToken = 0;
@@ -1485,6 +1837,10 @@ var FateClient = class {
1485
1837
  this.mutations = Object.create(null);
1486
1838
  this.onLiveError = options.onLiveError;
1487
1839
  this.operationLifetime = new OperationLifetime(options.gcReleaseBufferSize ?? 10);
1840
+ this.hydrationLimits = resolveHydrationLimits(options.hydrationLimits);
1841
+ const hydrationScope = options.hydrationScope ?? getDefaultHydrationScope(options.roots, options.types);
1842
+ if (typeof hydrationScope !== "string" || hydrationScope.length === 0) throw new Error(`fate: Hydration scope must be a non-empty string.`);
1843
+ this.hydrationScope = hydrationScope;
1488
1844
  this.roots = options.roots;
1489
1845
  this.transport = options.transport;
1490
1846
  this.types = new Map(options.types.map((entity) => [entity.type, {
@@ -1529,6 +1885,56 @@ var FateClient = class {
1529
1885
  }
1530
1886
  }
1531
1887
  }
1888
+ /**
1889
+ * Exports the durable normalized cache as an opaque JSON-safe value for SSR
1890
+ * transfer or persistence. Runtime requests, subscriptions, retainers, and
1891
+ * optimistic state are intentionally excluded.
1892
+ */
1893
+ dehydrate() {
1894
+ if (this.hasActiveOptimisticUpdates()) throw new Error(`fate: Cannot dehydrate while optimistic updates are active.`);
1895
+ if (this.hasPendingRequests()) throw new Error(`fate: Cannot dehydrate while requests are pending.`);
1896
+ return {
1897
+ data: encodeHydrationValue({
1898
+ rootLists: [...this.rootLists].map(([type, keys]) => [type, [...keys]]),
1899
+ rootRequests: [...this.rootRequests],
1900
+ store: this.store.dehydrate()
1901
+ }, this.hydrationLimits),
1902
+ scope: this.hydrationScope,
1903
+ version: 1
1904
+ };
1905
+ }
1906
+ /**
1907
+ * Restores a durable normalized cache snapshot without restoring runtime
1908
+ * request handles, retainers, subscriptions, or optimistic update state.
1909
+ * Hydrate before rendering SSR content. Replaying the same snapshot is safe.
1910
+ */
1911
+ hydrate(state, options = {}) {
1912
+ if (!state || typeof state !== "object" || state.version !== 1) throw new Error(`fate: Unsupported hydration state version.`);
1913
+ if (state.scope !== this.hydrationScope) throw new Error(`fate: Hydration state scope does not match this client.`);
1914
+ if (this.hasActiveOptimisticUpdates()) throw new Error(`fate: Cannot hydrate while optimistic updates are active.`);
1915
+ if (this.hasPendingRequests()) throw new Error(`fate: Cannot hydrate while requests are pending.`);
1916
+ const mode = options.merge ?? "preserve-existing";
1917
+ if (mode !== "preserve-existing" && mode !== "replace") throw new Error(`fate: Unsupported hydration merge mode.`);
1918
+ const decoded = decodeClientHydrationState(state.data, this.hydrationLimits);
1919
+ const notify = this.store.hydrate(decoded.store, mode, { deferNotifications: true });
1920
+ if (mode === "replace") {
1921
+ this.rootLists.clear();
1922
+ this.rootRequests.clear();
1923
+ this.stableRefs.clear();
1924
+ }
1925
+ for (const [type, incomingKeys] of decoded.rootLists) {
1926
+ let keys = this.rootLists.get(type);
1927
+ if (!keys) {
1928
+ keys = /* @__PURE__ */ new Set();
1929
+ this.rootLists.set(type, keys);
1930
+ }
1931
+ for (const key of incomingKeys) keys.add(key);
1932
+ }
1933
+ for (const [key, entityId] of decoded.rootRequests) if (mode === "replace" || !this.rootRequests.has(key)) this.rootRequests.set(key, entityId);
1934
+ this.stalledRequests.clear();
1935
+ this.viewDataCache.clear();
1936
+ notify();
1937
+ }
1532
1938
  registerRootList(type, key) {
1533
1939
  let lists = this.rootLists.get(type);
1534
1940
  if (!lists) {
@@ -1597,7 +2003,7 @@ var FateClient = class {
1597
2003
  ...baseRecord,
1598
2004
  args: argsPayload
1599
2005
  } : argsPayload ? { args: argsPayload } : input;
1600
- return await this.transport.mutate(key, requestInput, select);
2006
+ return await this.trackPendingRequest(() => this.transport.mutate(key, requestInput, select));
1601
2007
  }
1602
2008
  write(type, data, select, snapshots, plan, pathPrefix = null, blockedMask, insert, listSnapshots) {
1603
2009
  return this.writeEntity(type, data, select, snapshots, plan, pathPrefix, blockedMask, insert, listSnapshots);
@@ -1690,19 +2096,81 @@ var FateClient = class {
1690
2096
  if (this.stalledRequests.has(key)) return resolveSnapshot();
1691
2097
  const pendingPromise = this.pending.get(key) || null;
1692
2098
  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();
2099
+ const promise = this.trackPendingRequest(async () => {
2100
+ await this.fetchByIdAndNormalize(type, [id], missing, plan);
2101
+ try {
2102
+ if (this.store.missingForSelection(entityId, selectedPaths).size > 0) {
2103
+ this.stalledRequests.add(key);
2104
+ return resolveSnapshot();
2105
+ }
2106
+ this.stalledRequests.delete(key);
2107
+ return this.readView(view, ref);
2108
+ } finally {
2109
+ this.pending.delete(key);
1697
2110
  }
1698
- this.stalledRequests.delete(key);
1699
- return this.readView(view, ref);
1700
2111
  });
1701
2112
  this.pending.set(key, promise);
1702
2113
  return promise;
1703
2114
  }
1704
2115
  return resolveSnapshot();
1705
2116
  }
2117
+ readDeferred(deferred) {
2118
+ const metadata = getDeferredMetadata(deferred);
2119
+ const resolvedOwner = this.optimisticEntityResolutions.get(metadata.owner) ?? metadata.owner;
2120
+ const parsedOwner = parseEntityId(resolvedOwner);
2121
+ const ownerRecord = this.store.read(resolvedOwner);
2122
+ const rawOwnerId = ownerRecord && (typeof ownerRecord.id === "string" || typeof ownerRecord.id === "number") ? ownerRecord.id : parsedOwner.id;
2123
+ const ownerType = parsedOwner.type || metadata.type;
2124
+ const plan = getDeferredSelectionPlan(metadata.field, metadata.selection);
2125
+ const selectedPaths = plan.paths;
2126
+ const missing = this.store.missingForSelection(resolvedOwner, selectedPaths);
2127
+ const hasMissingList = () => this.hasMissingDeferredList(resolvedOwner, ownerType, metadata.field, plan);
2128
+ const listMissing = hasMissingList();
2129
+ const fetchPaths = listMissing ? selectedPaths : missing;
2130
+ const resolveSnapshot = () => {
2131
+ const snapshot = this.readViewSelection({ [metadata.field]: metadata.selection }, this.stableRefWithViewNames(ownerType, rawOwnerId, /* @__PURE__ */ new Set()), resolvedOwner, plan);
2132
+ const resolved = {
2133
+ coverage: snapshot.coverage,
2134
+ data: snapshot.data[metadata.field]
2135
+ };
2136
+ return {
2137
+ status: "fulfilled",
2138
+ then: (onfulfilled, onrejected) => Promise.resolve(resolved).then(onfulfilled, onrejected),
2139
+ value: resolved
2140
+ };
2141
+ };
2142
+ if (missing.size === 0 && !listMissing) {
2143
+ this.clearStalledRequestsForEntity(resolvedOwner);
2144
+ return resolveSnapshot();
2145
+ }
2146
+ const key = this.deferredPendingKey(resolvedOwner, fetchPaths, plan, this.deferredSelectionSignature(metadata.selection));
2147
+ if (this.stalledRequests.has(key)) return resolveSnapshot();
2148
+ const pendingOptimistic = this.getPendingOptimisticMutations(resolvedOwner);
2149
+ if (pendingOptimistic) {
2150
+ const pending = this.pendingDeferred.get(key);
2151
+ if (pending) return pending;
2152
+ const promise = Promise.all(pendingOptimistic).then(() => this.readDeferred(deferred)).finally(() => this.pendingDeferred.delete(key));
2153
+ this.pendingDeferred.set(key, promise);
2154
+ return promise;
2155
+ }
2156
+ const pendingPromise = this.pendingDeferred.get(key) || null;
2157
+ if (pendingPromise) return pendingPromise;
2158
+ const promise = this.trackPendingRequest(async () => {
2159
+ try {
2160
+ await this.fetchByIdAndNormalize(ownerType, [rawOwnerId], fetchPaths, plan);
2161
+ if (this.store.missingForSelection(resolvedOwner, selectedPaths).size > 0 || hasMissingList()) {
2162
+ this.stalledRequests.add(key);
2163
+ return resolveSnapshot().value;
2164
+ }
2165
+ this.stalledRequests.delete(key);
2166
+ return this.readDeferred(deferred).value;
2167
+ } finally {
2168
+ this.pendingDeferred.delete(key);
2169
+ }
2170
+ });
2171
+ this.pendingDeferred.set(key, promise);
2172
+ return promise;
2173
+ }
1706
2174
  assertLiveViewSupport() {
1707
2175
  if (!this.transport.subscribeById) throw new Error(`fate: transport does not support live views. Please provide a 'subscribeById' implementation in your transport.`);
1708
2176
  }
@@ -2134,6 +2602,9 @@ var FateClient = class {
2134
2602
  return filtered;
2135
2603
  }
2136
2604
  async loadConnection(view, connection, args, options = {}) {
2605
+ return await this.trackPendingRequest(() => this.loadConnectionInternal(view, connection, args, options));
2606
+ }
2607
+ async loadConnectionInternal(view, connection, args, options = {}) {
2137
2608
  const direction = options.direction ?? "forward";
2138
2609
  if (connection.root) {
2139
2610
  if (!this.transport.fetchList) throw new Error(`fate: transport does not support list fetching. Please add support for 'fetchList' in your transport.`);
@@ -2379,6 +2850,17 @@ var FateClient = class {
2379
2850
  hasActiveOptimisticUpdates() {
2380
2851
  return this.optimisticMasks.size > 0 || this.optimisticByEntity.size > 0 || this.pendingOptimisticMutations.size > 0;
2381
2852
  }
2853
+ hasPendingRequests() {
2854
+ return this.pendingNetworkRequests > 0;
2855
+ }
2856
+ async trackPendingRequest(request) {
2857
+ this.pendingNetworkRequests += 1;
2858
+ try {
2859
+ return await request();
2860
+ } finally {
2861
+ this.pendingNetworkRequests -= 1;
2862
+ }
2863
+ }
2382
2864
  runPendingGarbageCollection() {
2383
2865
  if (this.gcPending && !this.hasActiveOptimisticUpdates()) this.gc();
2384
2866
  }
@@ -2531,35 +3013,41 @@ var FateClient = class {
2531
3013
  }
2532
3014
  async fetchByIdAndNormalize(type, ids, select, plan, prefix = null) {
2533
3015
  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);
3016
+ await this.trackPendingRequest(async () => {
3017
+ const records = await this.transport.fetchById(type, ids, select, resolvedArgs);
3018
+ for (const record of records) this.writeEntity(type, record, select, void 0, plan, prefix);
3019
+ });
2536
3020
  }
2537
3021
  async fetchQueryAndNormalize(item) {
2538
3022
  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);
3023
+ await this.trackPendingRequest(async () => {
3024
+ const record = await this.transport.fetchQuery(item.name, item.plan.paths, item.argsPayload);
3025
+ if (!record || typeof record !== "object") {
3026
+ this.rootRequests.set(item.queryKey, null);
3027
+ return;
3028
+ }
3029
+ const entityId = this.writeEntity(item.type, record, item.plan.paths, void 0, item.plan);
3030
+ this.rootRequests.set(item.queryKey, entityId);
3031
+ });
2546
3032
  }
2547
3033
  async fetchListAndNormalize(item) {
2548
3034
  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
- }));
3035
+ await this.trackPendingRequest(async () => {
3036
+ const { items, pagination } = await this.transport.fetchList(item.name, item.plan.paths, item.argsPayload);
3037
+ const ids = [];
3038
+ const cursors = [];
3039
+ for (const entry of items) {
3040
+ const id = this.writeEntity(item.type, entry.node, item.plan.paths, void 0, item.plan);
3041
+ ids.push(id);
3042
+ cursors.push(entry.cursor);
3043
+ }
3044
+ if (!filterConnectionArgs(item.argsPayload)) this.registerRootList(item.type, item.listKey);
3045
+ const previous = this.store.getListState(item.listKey);
3046
+ this.store.setList(item.listKey, this.mergeListState(previous, ids, cursors, pagination, {
3047
+ ...getPaginationMergeInfo(item.argsPayload),
3048
+ replace: true
3049
+ }));
3050
+ });
2563
3051
  }
2564
3052
  writeEntity(type, record, select, snapshots, plan, pathPrefix = null, blockedMask, insert, listSnapshots) {
2565
3053
  const config = this.types.get(type);
@@ -2726,8 +3214,20 @@ var FateClient = class {
2726
3214
  target[ViewsTag].add(key);
2727
3215
  continue;
2728
3216
  }
2729
- coverageById.set(parentId, (coverageById.get(parentId) ?? /* @__PURE__ */ new Set()).add(key));
2730
3217
  const fieldPath = prefix ? `${prefix}.${key}` : key;
3218
+ if (isDeferredSelection(selectionKind)) {
3219
+ const { id, type } = parseEntityId(parentId);
3220
+ coverageById.set(parentId, (coverageById.get(parentId) ?? /* @__PURE__ */ new Set()).add(key));
3221
+ target[key] = createDeferred({
3222
+ field: key,
3223
+ id,
3224
+ owner: parentId,
3225
+ selection: getDeferredSelection(selectionKind),
3226
+ type
3227
+ });
3228
+ continue;
3229
+ }
3230
+ coverageById.set(parentId, (coverageById.get(parentId) ?? /* @__PURE__ */ new Set()).add(key));
2731
3231
  const selectionType = typeof selectionKind;
2732
3232
  if (selectionType === "boolean" && selectionKind) target[key] = record[key];
2733
3233
  else if (selectionKind && selectionType === "object") {
@@ -2846,7 +3346,9 @@ var FateClient = class {
2846
3346
  }
2847
3347
  };
2848
3348
  const data = { __typename: parseEntityId(entityId).type };
2849
- for (const viewPayload of getViewPayloads(viewComposition, ref)) walk(viewPayload.select, record, data, entityId, pathPrefix);
3349
+ const payloads = getViewPayloads(viewComposition, ref);
3350
+ if (payloads.length === 0 && isRecord(viewComposition)) walk(viewComposition, record, data, entityId, pathPrefix);
3351
+ else for (const viewPayload of payloads) walk(viewPayload.select, record, data, entityId, pathPrefix);
2850
3352
  return {
2851
3353
  coverage: [...coverageById.entries()],
2852
3354
  data
@@ -2862,6 +3364,32 @@ var FateClient = class {
2862
3364
  pendingKey(entityId, missingFields) {
2863
3365
  return `${this.pendingPrefix(entityId)}${[...missingFields].sort().join("|")}`;
2864
3366
  }
3367
+ deferredPendingKey(entityId, fields, plan, selectionSignature) {
3368
+ const args = [...plan.args.entries()].map(([path, entry]) => `${path}:${entry.hash}`).sort().join("|");
3369
+ return `${this.pendingKey(entityId, new Set(fields))}|${args}|${selectionSignature}`;
3370
+ }
3371
+ deferredSelectionSignature(selection) {
3372
+ const seen = /* @__PURE__ */ new Set();
3373
+ const serialize = (value) => {
3374
+ if (value === null || typeof value !== "object") return `${typeof value}:${String(value)}`;
3375
+ if (seen.has(value)) return "[Circular]";
3376
+ seen.add(value);
3377
+ if (Array.isArray(value)) return `[${value.map((item) => serialize(item)).join(",")}]`;
3378
+ return `{${Object.entries(value).sort(([left], [right]) => left.localeCompare(right)).map(([key, entry]) => isViewTag(key) ? `view:${key}` : `${key}:${serialize(entry)}`).join(",")}}`;
3379
+ };
3380
+ return serialize(selection);
3381
+ }
3382
+ hasMissingDeferredList(owner, type, field, plan) {
3383
+ const descriptor = this.types.get(type)?.fields?.[field];
3384
+ if (!descriptor || descriptor === "scalar" || !("listOf" in descriptor)) return false;
3385
+ const fieldArgs = plan.args.get(field);
3386
+ const listState = this.store.getListState(getListKey(owner, field, fieldArgs?.hash));
3387
+ if (!listState) return true;
3388
+ const args = fieldArgs?.value;
3389
+ const paginationArgs = getPaginationArgsInfo(args);
3390
+ if (paginationArgs.hasCursorArg) return true;
3391
+ return !hasConnectionPageCoverage(listState, args, paginationArgs.direction);
3392
+ }
2865
3393
  liveSubscriptionKey(entityId, plan) {
2866
3394
  return `${entityId}|${[...plan.paths].sort().join("|")}|${[...plan.args.entries()].map(([path, entry]) => `${path}:${entry.hash}`).sort().join("|")}`;
2867
3395
  }
@@ -3396,4 +3924,4 @@ function clientRoot(type) {
3396
3924
  });
3397
3925
  }
3398
3926
  //#endregion
3399
- export { ConnectionTag, FateClient, clientRoot, createClient, createHTTPTransport, createTRPCTransport, getListEntries, getSelectionPlan, isRecord, isViewTag, liveConnectionTopic, liveEntityTopic, liveGlobalConnectionTopic, mutation, toEntityId, view };
3927
+ export { ConnectionTag, DeferTag, DeferredTag, FateClient, clientRoot, createClient, createGraphQLTransport, createHTTPTransport, createTRPCTransport, defer, getDeferredMetadata, getListEntries, getSelectionPlan, graphqlMutation, isDeferred, isRecord, isViewTag, liveConnectionTopic, liveEntityTopic, liveGlobalConnectionTopic, mutation, toEntityId, view };