@nkzw/fate 0.1.3 → 1.0.0-rc.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 (170) hide show
  1. package/README.md +810 -129
  2. package/docs/api/classes/FateClient.md +832 -0
  3. package/docs/api/functions/clientRoot.md +27 -0
  4. package/docs/api/functions/createClient.md +21 -0
  5. package/docs/api/functions/createHTTPTransport.md +51 -0
  6. package/docs/api/functions/createTRPCTransport.md +46 -0
  7. package/docs/api/functions/getListEntries.md +15 -0
  8. package/docs/api/functions/getSelectionPlan.md +36 -0
  9. package/docs/api/functions/isRecord.md +15 -0
  10. package/docs/api/functions/isViewTag.md +17 -0
  11. package/docs/api/functions/liveConnectionTopic.md +19 -0
  12. package/docs/api/functions/liveEntityTopic.md +19 -0
  13. package/docs/api/functions/liveGlobalConnectionTopic.md +15 -0
  14. package/docs/api/functions/mutation.md +32 -0
  15. package/docs/api/functions/toEntityId.md +21 -0
  16. package/docs/api/functions/view.md +26 -0
  17. package/docs/api/index.md +81 -0
  18. package/docs/api/interfaces/FateThenable.md +31 -0
  19. package/docs/api/interfaces/Transport.md +199 -0
  20. package/docs/api/server/classes/FateRequestError.md +67 -0
  21. package/docs/api/server/drizzle/functions/createDrizzleFate.md +431 -0
  22. package/docs/api/server/drizzle/functions/createDrizzleSourceAdapter.md +21 -0
  23. package/docs/api/server/drizzle/functions/createDrizzleSourceRegistry.md +21 -0
  24. package/docs/api/server/drizzle/index.md +23 -0
  25. package/docs/api/server/drizzle/type-aliases/DrizzleManyToManyConfig.md +15 -0
  26. package/docs/api/server/drizzle/type-aliases/DrizzleManyToManyInput.md +5 -0
  27. package/docs/api/server/drizzle/type-aliases/DrizzleQueryExtra.md +21 -0
  28. package/docs/api/server/drizzle/type-aliases/DrizzleSourceAdapter.md +337 -0
  29. package/docs/api/server/drizzle/type-aliases/DrizzleViewConfig.md +29 -0
  30. package/docs/api/server/functions/bindSourceProcedures.md +32 -0
  31. package/docs/api/server/functions/computed.md +43 -0
  32. package/docs/api/server/functions/count.md +21 -0
  33. package/docs/api/server/functions/createFateFetchHandler.md +21 -0
  34. package/docs/api/server/functions/createFateServer.md +41 -0
  35. package/docs/api/server/functions/createHonoFateHandler.md +21 -0
  36. package/docs/api/server/functions/createLiveEventBus.md +17 -0
  37. package/docs/api/server/functions/createNestedSourcePlan.md +103 -0
  38. package/docs/api/server/functions/createResolver.md +74 -0
  39. package/docs/api/server/functions/createSourcePlan.md +95 -0
  40. package/docs/api/server/functions/createSourceProcedures.md +46 -0
  41. package/docs/api/server/functions/dataView.md +32 -0
  42. package/docs/api/server/functions/field.md +15 -0
  43. package/docs/api/server/functions/getNestedSelection.md +19 -0
  44. package/docs/api/server/functions/getScopedArgs.md +21 -0
  45. package/docs/api/server/functions/hasNestedSelection.md +19 -0
  46. package/docs/api/server/functions/list.md +28 -0
  47. package/docs/api/server/functions/refetchSourceById.md +55 -0
  48. package/docs/api/server/functions/resolveSourceById.md +55 -0
  49. package/docs/api/server/functions/resolveSourceByIds.md +55 -0
  50. package/docs/api/server/functions/resolveSourceConnection.md +67 -0
  51. package/docs/api/server/functions/resolver.md +42 -0
  52. package/docs/api/server/functions/toPrismaSelect.md +25 -0
  53. package/docs/api/server/functions/withConnection.md +24 -0
  54. package/docs/api/server/index.md +109 -0
  55. package/docs/api/server/prisma/functions/createPrismaFate.md +435 -0
  56. package/docs/api/server/prisma/functions/createPrismaSourceAdapter.md +21 -0
  57. package/docs/api/server/prisma/functions/createPrismaSourceRegistry.md +21 -0
  58. package/docs/api/server/prisma/functions/prismaConnectionArgs.md +57 -0
  59. package/docs/api/server/prisma/index.md +23 -0
  60. package/docs/api/server/prisma/type-aliases/PrismaDelegate.md +59 -0
  61. package/docs/api/server/prisma/type-aliases/PrismaQueryExtra.md +5 -0
  62. package/docs/api/server/prisma/type-aliases/PrismaSourceAdapter.md +343 -0
  63. package/docs/api/server/prisma/type-aliases/PrismaViewConfig.md +31 -0
  64. package/docs/api/server/type-aliases/ComputedField.md +55 -0
  65. package/docs/api/server/type-aliases/ComputedSelection.md +5 -0
  66. package/docs/api/server/type-aliases/ConnectionItem.md +29 -0
  67. package/docs/api/server/type-aliases/ConnectionPagination.md +39 -0
  68. package/docs/api/server/type-aliases/ConnectionResult.md +29 -0
  69. package/docs/api/server/type-aliases/CountSelection.md +29 -0
  70. package/docs/api/server/type-aliases/CountWhere.md +5 -0
  71. package/docs/api/server/type-aliases/DataViewListOptions.md +13 -0
  72. package/docs/api/server/type-aliases/DataViewOrderBy.md +5 -0
  73. package/docs/api/server/type-aliases/DataViewOrderDirection.md +5 -0
  74. package/docs/api/server/type-aliases/DataViewResult.md +11 -0
  75. package/docs/api/server/type-aliases/Entity.md +21 -0
  76. package/docs/api/server/type-aliases/FateServer.md +75 -0
  77. package/docs/api/server/type-aliases/FateServerManifest.md +5 -0
  78. package/docs/api/server/type-aliases/FieldSelection.md +21 -0
  79. package/docs/api/server/type-aliases/LiveConnectionEventType.md +5 -0
  80. package/docs/api/server/type-aliases/LiveConnectionSourceEvent.md +5 -0
  81. package/docs/api/server/type-aliases/LiveEventBus.md +5 -0
  82. package/docs/api/server/type-aliases/LiveEventType.md +5 -0
  83. package/docs/api/server/type-aliases/LiveSourceEvent.md +5 -0
  84. package/docs/api/server/type-aliases/NativeFateAPI.md +47 -0
  85. package/docs/api/server/type-aliases/OrderDirection.md +5 -0
  86. package/docs/api/server/type-aliases/SourceConfig.md +55 -0
  87. package/docs/api/server/type-aliases/SourceDefinition.md +55 -0
  88. package/docs/api/server/type-aliases/SourceOrder.md +5 -0
  89. package/docs/api/server/type-aliases/SourceOrderField.md +21 -0
  90. package/docs/api/server/type-aliases/SourcePlan.md +29 -0
  91. package/docs/api/server/type-aliases/SourcePlanNode.md +29 -0
  92. package/docs/api/server/type-aliases/SourceRegistry.md +11 -0
  93. package/docs/api/server/type-aliases/SourceRelation.md +15 -0
  94. package/docs/api/server/type-aliases/SourceRelationConfig.md +23 -0
  95. package/docs/api/server/variables/byIdInput.md +5 -0
  96. package/docs/api/server/variables/connectionArgs.md +7 -0
  97. package/docs/api/type-aliases/ConnectionMetadata.md +7 -0
  98. package/docs/api/type-aliases/ConnectionRef.md +13 -0
  99. package/docs/api/type-aliases/Entity.md +15 -0
  100. package/docs/api/type-aliases/EntityId.md +7 -0
  101. package/docs/api/type-aliases/FateLiveConnectionEvent.md +5 -0
  102. package/docs/api/type-aliases/FateLiveEvent.md +5 -0
  103. package/docs/api/type-aliases/FateMutations.md +7 -0
  104. package/docs/api/type-aliases/FateOperation.md +5 -0
  105. package/docs/api/type-aliases/FateProtocolRequest.md +5 -0
  106. package/docs/api/type-aliases/FateProtocolResponse.md +5 -0
  107. package/docs/api/type-aliases/FateRecord.md +7 -0
  108. package/docs/api/type-aliases/FateRoots.md +7 -0
  109. package/docs/api/type-aliases/InferFateAPI.md +11 -0
  110. package/docs/api/type-aliases/List.md +5 -0
  111. package/docs/api/type-aliases/ListEntry.md +5 -0
  112. package/docs/api/type-aliases/ListItem.md +13 -0
  113. package/docs/api/type-aliases/Mask.md +15 -0
  114. package/docs/api/type-aliases/MutationDefinition.md +21 -0
  115. package/docs/api/type-aliases/MutationEntity.md +13 -0
  116. package/docs/api/type-aliases/MutationIdentifier.md +19 -0
  117. package/docs/api/type-aliases/MutationInput.md +13 -0
  118. package/docs/api/type-aliases/MutationResult.md +13 -0
  119. package/docs/api/type-aliases/NodesItem.md +13 -0
  120. package/docs/api/type-aliases/Pagination.md +39 -0
  121. package/docs/api/type-aliases/Request.md +7 -0
  122. package/docs/api/type-aliases/RequestMode.md +7 -0
  123. package/docs/api/type-aliases/RequestOptions.md +7 -0
  124. package/docs/api/type-aliases/RequestResult.md +18 -0
  125. package/docs/api/type-aliases/Selection.md +13 -0
  126. package/docs/api/type-aliases/Snapshot.md +7 -0
  127. package/docs/api/type-aliases/TypeConfig.md +41 -0
  128. package/docs/api/type-aliases/View.md +17 -0
  129. package/docs/api/type-aliases/ViewData.md +18 -0
  130. package/docs/api/type-aliases/ViewEntity.md +13 -0
  131. package/docs/api/type-aliases/ViewEntityName.md +13 -0
  132. package/docs/api/type-aliases/ViewRef.md +13 -0
  133. package/docs/api/type-aliases/ViewSelection.md +13 -0
  134. package/docs/api/type-aliases/ViewSnapshot.md +18 -0
  135. package/docs/api/type-aliases/ViewTag.md +7 -0
  136. package/docs/api/variables/ConnectionTag.md +7 -0
  137. package/docs/guide/actions.md +263 -0
  138. package/docs/guide/core-concepts.md +22 -0
  139. package/docs/guide/getting-started.md +57 -0
  140. package/docs/guide/list-views.md +86 -0
  141. package/docs/guide/live-views.md +245 -0
  142. package/docs/guide/requests.md +130 -0
  143. package/docs/guide/server-integration.md +630 -0
  144. package/docs/guide/views.md +278 -0
  145. package/docs/guide/void-integration.md +167 -0
  146. package/docs/index.md +4 -0
  147. package/lib/cli.mjs +17 -250
  148. package/lib/clientStub.d.mts +6 -0
  149. package/lib/clientStub.mjs +6 -0
  150. package/lib/executor-BqwHdN3n.d.mts +468 -0
  151. package/lib/index.d.mts +64 -2
  152. package/lib/index.mjs +1961 -555
  153. package/lib/list-4wRNSVgI.d.mts +10 -0
  154. package/lib/list-C_mi6GnE.mjs +16 -0
  155. package/lib/list.d.mts +2 -0
  156. package/lib/list.mjs +2 -0
  157. package/lib/liveTopics-DzNtJaBD.mjs +197 -0
  158. package/lib/{record-DnhZuvUe.mjs → record-AeZJC9fd.mjs} +1 -2
  159. package/lib/record-ENh92gyb.d.mts +315 -0
  160. package/lib/server/drizzle.d.mts +304 -0
  161. package/lib/server/drizzle.mjs +688 -0
  162. package/lib/server/prisma.d.mts +309 -0
  163. package/lib/server/prisma.mjs +345 -0
  164. package/lib/server.d.mts +82 -183
  165. package/lib/server.mjs +870 -416
  166. package/lib/sourceRouter-Bwpkr062.mjs +972 -0
  167. package/lib/{types-HECWvGpd.d.mts → types-Dz46PXr3.d.mts} +215 -63
  168. package/lib/vite.d.mts +17 -0
  169. package/lib/vite.mjs +592 -0
  170. package/package.json +44 -6
package/lib/index.mjs CHANGED
@@ -1,130 +1,6 @@
1
- import { t as isRecord } from "./record-DnhZuvUe.mjs";
2
-
3
- //#region src/args.ts
4
- const ensureSerializable = (value, path) => {
5
- const type = typeof value;
6
- if (type === "function" || type === "symbol") throw new Error(`fate: Argument '${path}' must be serializable. Received '${type}'.`);
7
- };
8
- const cloneArgs = (value, path) => {
9
- const cloneValue$1 = (entry, currentPath) => {
10
- if (Array.isArray(entry)) return entry.map((item, index) => cloneValue$1(item, `${currentPath}[${index}]`));
11
- if (isRecord(entry)) {
12
- const result = {};
13
- for (const [key, child] of Object.entries(entry)) result[key] = cloneValue$1(child, `${currentPath}.${key}`);
14
- return result;
15
- }
16
- ensureSerializable(entry, currentPath);
17
- return entry;
18
- };
19
- return cloneValue$1(value, path);
20
- };
21
- const stableSerialize = (value) => {
22
- if (value === null) return "null";
23
- const type = typeof value;
24
- if (type === "number" || type === "boolean" || type === "bigint") return `${type}:${String(value)}`;
25
- if (type === "string") return `string:${JSON.stringify(value)}`;
26
- if (type === "undefined") return "undefined";
27
- if (Array.isArray(value)) return `array:[${value.map(stableSerialize).join(",")}]`;
28
- if (typeof value === "object") return `object:{${Object.entries(value).map(([key, entry]) => [key, entry]).sort(([a], [b]) => a < b ? -1 : a > b ? 1 : 0).map(([_key, entry]) => `${JSON.stringify(_key)}:${stableSerialize(entry)}`).join(",")}}`;
29
- throw new Error(`fate: Unable to serialize argument value of type '${type}'.`);
30
- };
31
- const hashArgs = (argsValue, options = {}) => {
32
- const keys = {};
33
- for (const [key, value] of Object.entries(argsValue)) {
34
- if (options.ignoreKeys && options.ignoreKeys.has(key)) continue;
35
- keys[key] = value;
36
- }
37
- return stableSerialize(keys);
38
- };
39
- const mergeArgs = (target, source) => {
40
- for (const [key, value] of Object.entries(source)) {
41
- if (isRecord(value)) {
42
- const existing = target[key];
43
- if (isRecord(existing)) mergeArgs(existing, value);
44
- else target[key] = { ...value };
45
- continue;
46
- }
47
- target[key] = value;
48
- }
49
- };
50
- const resolvedArgsFromPlan = (plan) => {
51
- if (!plan || plan.args.size === 0) return;
52
- const result = {};
53
- for (const [path, entry] of plan.args.entries()) {
54
- if (path === "") {
55
- mergeArgs(result, entry.value);
56
- continue;
57
- }
58
- const segments = path.split(".");
59
- let current = result;
60
- segments.forEach((segment, index) => {
61
- if (index === segments.length - 1) {
62
- const existing$1 = current[segment];
63
- if (isRecord(existing$1)) mergeArgs(existing$1, entry.value);
64
- else current[segment] = { ...entry.value };
65
- return;
66
- }
67
- const existing = current[segment];
68
- if (isRecord(existing)) {
69
- current = existing;
70
- return;
71
- }
72
- const next = {};
73
- current[segment] = next;
74
- current = next;
75
- });
76
- }
77
- return result;
78
- };
79
- const hasEntries = (value) => Boolean(value && Object.keys(value).length > 0);
80
- const combineArgsPayload = (base, scoped) => {
81
- if (!hasEntries(base) && !hasEntries(scoped)) return;
82
- const result = hasEntries(base) ? { ...base } : {};
83
- if (hasEntries(scoped)) mergeArgs(result, scoped);
84
- return result;
85
- };
86
- const getArgsAtPath = (payload, path) => {
87
- if (!payload) return;
88
- if (path === "") return payload;
89
- const segments = path.split(".");
90
- let current = payload;
91
- for (const segment of segments) {
92
- if (!isRecord(current)) return;
93
- current = current[segment];
94
- if (current === void 0) return;
95
- }
96
- return isRecord(current) ? current : void 0;
97
- };
98
- const applyArgsPayloadToPlan = (plan, payload) => {
99
- for (const [path, entry] of plan.args.entries()) {
100
- const actual = path === "" ? payload : getArgsAtPath(payload, path);
101
- if (!actual) continue;
102
- const cloned = cloneArgs(actual, path);
103
- const hash = hashArgs(cloned, { ignoreKeys: entry.ignoreKeys });
104
- plan.args.set(path, {
105
- hash,
106
- ignoreKeys: entry.ignoreKeys,
107
- value: cloned
108
- });
109
- }
110
- };
111
- const scopeArgsPayload = (args, scope) => {
112
- const segments = scope.split(".");
113
- const result = {};
114
- let current = result;
115
- segments.forEach((segment, index) => {
116
- if (index === segments.length - 1) {
117
- current[segment] = args;
118
- return;
119
- }
120
- const next = {};
121
- current[segment] = next;
122
- current = next;
123
- });
124
- return result;
125
- };
126
-
127
- //#endregion
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";
128
4
  //#region src/cache.ts
129
5
  var ViewDataCache = class {
130
6
  constructor() {
@@ -132,19 +8,19 @@ var ViewDataCache = class {
132
8
  this.rootDependencies = /* @__PURE__ */ new Map();
133
9
  this.dependencyIndex = /* @__PURE__ */ new Map();
134
10
  }
135
- get(entityId, view$1, ref) {
136
- return this.cache.get(entityId)?.get(view$1)?.get(ref) ?? null;
11
+ get(entityId, view, ref) {
12
+ return this.cache.get(entityId)?.get(view)?.get(ref) ?? null;
137
13
  }
138
- set(entityId, view$1, ref, thenable, dependencies) {
14
+ set(entityId, view, ref, thenable, dependencies) {
139
15
  let entityMap = this.cache.get(entityId);
140
16
  if (!entityMap) {
141
17
  entityMap = /* @__PURE__ */ new WeakMap();
142
18
  this.cache.set(entityId, entityMap);
143
19
  }
144
- let viewMap = entityMap.get(view$1);
20
+ let viewMap = entityMap.get(view);
145
21
  if (!viewMap) {
146
22
  viewMap = /* @__PURE__ */ new WeakMap();
147
- entityMap.set(view$1, viewMap);
23
+ entityMap.set(view, viewMap);
148
24
  }
149
25
  viewMap.set(ref, thenable);
150
26
  let roots = this.rootDependencies.get(entityId);
@@ -186,7 +62,6 @@ var ViewDataCache = class {
186
62
  this.cache.delete(entityId);
187
63
  }
188
64
  };
189
-
190
65
  //#endregion
191
66
  //#region src/mask.ts
192
67
  function emptyMask() {
@@ -262,9 +137,8 @@ function intersects(a, b) {
262
137
  }
263
138
  return false;
264
139
  }
265
-
266
140
  //#endregion
267
- //#region ../../node_modules/.pnpm/@trpc+server@11.8.1_typescript@5.9.3/node_modules/@trpc/server/dist/getErrorShape-vC8mUXJD.mjs
141
+ //#region ../../node_modules/.pnpm/@trpc+server@11.17.0_typescript@5.9.3/node_modules/@trpc/server/dist/getErrorShape-BPSzUA7W.mjs
268
142
  var __create = Object.create;
269
143
  var __defProp = Object.defineProperty;
270
144
  var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
@@ -364,7 +238,7 @@ var require_defineProperty = __commonJS({ "../../node_modules/.pnpm/@oxc-project
364
238
  }
365
239
  module.exports = _defineProperty, module.exports.__esModule = true, module.exports["default"] = module.exports;
366
240
  } });
367
- var require_objectSpread2 = __commonJS({ "../../node_modules/.pnpm/@oxc-project+runtime@0.72.2/node_modules/@oxc-project/runtime/src/helpers/objectSpread2.js"(exports, module) {
241
+ __toESM(__commonJS({ "../../node_modules/.pnpm/@oxc-project+runtime@0.72.2/node_modules/@oxc-project/runtime/src/helpers/objectSpread2.js"(exports, module) {
368
242
  var defineProperty = require_defineProperty();
369
243
  function ownKeys(e, r) {
370
244
  var t = Object.keys(e);
@@ -388,9 +262,7 @@ var require_objectSpread2 = __commonJS({ "../../node_modules/.pnpm/@oxc-project+
388
262
  return e;
389
263
  }
390
264
  module.exports = _objectSpread2, module.exports.__esModule = true, module.exports["default"] = module.exports;
391
- } });
392
- var import_objectSpread2 = __toESM(require_objectSpread2(), 1);
393
-
265
+ } })(), 1);
394
266
  //#endregion
395
267
  //#region src/types.ts
396
268
  /** Internal marker added to objects that represent a view payload. */
@@ -403,8 +275,8 @@ const NodeRefTag = Symbol("__fate__node-ref");
403
275
  const ConnectionTag = Symbol("__fate__connection");
404
276
  const viewTag = "__fate-view__";
405
277
  /** Generates a stable view tag for a view definition. */
406
- function getViewTag(id$1) {
407
- return `${viewTag}${id$1}`;
278
+ function getViewTag(id) {
279
+ return `${viewTag}${id}`;
408
280
  }
409
281
  /** Determines whether a property key is a fate view tag. */
410
282
  function isViewTag(key) {
@@ -426,7 +298,6 @@ function isQueryItem(item) {
426
298
  const RootKind = "__fate__root";
427
299
  /** Brand used on mutation definitions to mark their identity in the d.ts output. */
428
300
  const MutationKind = "__fate__mutation";
429
-
430
301
  //#endregion
431
302
  //#region src/view.ts
432
303
  /**
@@ -452,7 +323,8 @@ const getSelectionViewNames = (selection) => {
452
323
  return getViewNames(selection);
453
324
  };
454
325
  let id = 0;
455
- const isDevelopment = import.meta?.env?.DEV || import.meta?.env?.NODE_ENV !== "production";
326
+ const importMetaEnvironment = import.meta.env;
327
+ const isDevelopment = typeof importMetaEnvironment?.DEV === "boolean" ? importMetaEnvironment.DEV : typeof process !== "undefined" ? process.env.NODE_ENV !== "production" : false;
456
328
  let viewModulePath = null;
457
329
  const getStableId = () => {
458
330
  if (isDevelopment) try {
@@ -495,7 +367,6 @@ function view() {
495
367
  return Object.freeze(viewComposition);
496
368
  };
497
369
  }
498
-
499
370
  //#endregion
500
371
  //#region src/ref.ts
501
372
  /**
@@ -505,14 +376,14 @@ const toEntityId = (type, rawId) => `${type}:${String(rawId)}`;
505
376
  /**
506
377
  * Splits a cache entity ID back into its type and raw identifier.
507
378
  */
508
- function parseEntityId(id$1) {
509
- const idx = id$1.indexOf(":");
379
+ function parseEntityId(id) {
380
+ const idx = id.indexOf(":");
510
381
  return idx < 0 ? {
511
- id: id$1,
382
+ id,
512
383
  type: ""
513
384
  } : {
514
- id: id$1.slice(idx + 1),
515
- type: id$1.slice(0, idx)
385
+ id: id.slice(idx + 1),
386
+ type: id.slice(0, idx)
516
387
  };
517
388
  }
518
389
  /**
@@ -526,32 +397,22 @@ function assignViewTag(target, value) {
526
397
  writable: false
527
398
  });
528
399
  }
529
- const getRootViewNames = (view$1) => {
530
- const names = new Set(getViewNames(view$1));
531
- const payloads = getViewPayloads(view$1, null);
400
+ const getRootViewNames = (view) => {
401
+ const names = new Set(getViewNames(view));
402
+ const payloads = getViewPayloads(view, null);
532
403
  for (const payload of payloads) for (const name of getSelectionViewNames(payload.select)) names.add(name);
533
404
  return names;
534
405
  };
535
- /**
536
- * Creates an immutable `ViewRef` for an entity, tagging it with all views from
537
- * the provided composition so `useView` can resolve the ref against a view.
538
- */
539
- function createRef(__typename, id$1, view$1, options) {
406
+ function createRefWithViewNames(__typename, id, names) {
540
407
  const ref = {
541
408
  __typename,
542
- id: id$1
409
+ id
543
410
  };
544
- assignViewTag(ref, options?.root ? getRootViewNames(view$1) : getViewNames(view$1));
411
+ assignViewTag(ref, names);
545
412
  return Object.freeze(ref);
546
413
  }
547
-
548
414
  //#endregion
549
415
  //#region src/selection.ts
550
- const paginationKeys = new Set([
551
- "after",
552
- "before",
553
- "cursor"
554
- ]);
555
416
  const isConnectionSelection = (value) => isRecord(value.items) && "node" in value.items;
556
417
  /**
557
418
  * Flattens a view into a `SelectionPlan`, expanding composed views and
@@ -559,6 +420,7 @@ const isConnectionSelection = (value) => isRecord(value.items) && "node" in valu
559
420
  */
560
421
  const getSelectionPlan = (viewComposition, ref) => {
561
422
  const args = /* @__PURE__ */ new Map();
423
+ const live = /* @__PURE__ */ new Map();
562
424
  const paths = /* @__PURE__ */ new Set();
563
425
  const assignArgs = (path, value, ignoreKeys) => {
564
426
  const hash = hashArgs(value, { ignoreKeys });
@@ -570,8 +432,12 @@ const getSelectionPlan = (viewComposition, ref) => {
570
432
  };
571
433
  const walk = (selection, prefix, context = "default") => {
572
434
  if (prefix === null && context !== "connection" && isConnectionSelection(selection)) {
573
- if (selection.args && isRecord(selection.args)) assignArgs("", cloneArgs(selection.args, "args"), isRecord(selection.items) && isRecord(selection.items.node) ? paginationKeys : void 0);
574
- const { args: args$1, ...withoutArgs } = selection;
435
+ if (selection.live && isRecord(selection.live)) live.set("", {
436
+ append: selection.live.append === "visible" ? "visible" : "edge",
437
+ prepend: selection.live.prepend === "visible" ? "visible" : "edge"
438
+ });
439
+ if (selection.args && isRecord(selection.args)) assignArgs("", cloneArgs(selection.args, "args"), isRecord(selection.items) && isRecord(selection.items.node) ? paginationArgKeys : void 0);
440
+ const { args: _args, live: _live, ...withoutArgs } = selection;
575
441
  walk(withoutArgs, prefix, "connection");
576
442
  return;
577
443
  }
@@ -579,7 +445,7 @@ const getSelectionPlan = (viewComposition, ref) => {
579
445
  const valueType = typeof value;
580
446
  const path = prefix ? `${prefix}.${key}` : key;
581
447
  if (context === "connection") {
582
- if (key === "args" || key === "pagination") continue;
448
+ if (key === "args" || key === "live" || key === "pagination") continue;
583
449
  if (key === "items" && isRecord(value)) {
584
450
  if (isRecord(value.node)) walk(value.node, prefix);
585
451
  continue;
@@ -596,16 +462,20 @@ const getSelectionPlan = (viewComposition, ref) => {
596
462
  if (isRecord(value)) {
597
463
  const selectionObject = value;
598
464
  if (isConnectionSelection(selectionObject)) {
599
- if (selectionObject.args && isRecord(selectionObject.args)) assignArgs(path, cloneArgs(selectionObject.args, path), isRecord(selectionObject.items) && isRecord(selectionObject.items.node) ? paginationKeys : void 0);
600
- const { args: _ignored, ...withoutArgs } = selectionObject;
465
+ if (selectionObject.live && isRecord(selectionObject.live)) live.set(path, {
466
+ append: selectionObject.live.append === "visible" ? "visible" : "edge",
467
+ prepend: selectionObject.live.prepend === "visible" ? "visible" : "edge"
468
+ });
469
+ if (selectionObject.args && isRecord(selectionObject.args)) assignArgs(path, cloneArgs(selectionObject.args, path), isRecord(selectionObject.items) && isRecord(selectionObject.items.node) ? paginationArgKeys : void 0);
470
+ const { args: _ignored, live: _live, ...withoutArgs } = selectionObject;
601
471
  walk(withoutArgs, path, "connection");
602
472
  continue;
603
473
  }
604
474
  const hasArgs = selectionObject.args && isRecord(selectionObject.args);
605
475
  let selectionWithoutArgs = selectionObject;
606
476
  if (hasArgs) {
607
- assignArgs(path, cloneArgs(selectionObject.args, path), isRecord(selectionObject.items) && isRecord(selectionObject.items?.node) ? paginationKeys : void 0);
608
- const { args: args$1, ...rest } = selectionObject;
477
+ assignArgs(path, cloneArgs(selectionObject.args, path), isRecord(selectionObject.items) && isRecord(selectionObject.items?.node) ? paginationArgKeys : void 0);
478
+ const { args: _args, ...rest } = selectionObject;
609
479
  selectionWithoutArgs = rest;
610
480
  }
611
481
  if (Object.keys(selectionWithoutArgs).length > 0) walk(selectionWithoutArgs, path);
@@ -618,10 +488,10 @@ const getSelectionPlan = (viewComposition, ref) => {
618
488
  else for (const payload of payloads) walk(payload.select, null);
619
489
  return {
620
490
  args,
491
+ live,
621
492
  paths
622
493
  };
623
494
  };
624
-
625
495
  //#endregion
626
496
  //#region src/mutation.ts
627
497
  /**
@@ -655,9 +525,9 @@ const collectImplicitSelectedPaths = (value) => {
655
525
  walk(value, null);
656
526
  return paths;
657
527
  };
658
- const maybeGetId = (getId$1, input) => {
528
+ const maybeGetId = (getId, input) => {
659
529
  try {
660
- return getId$1(input);
530
+ return getId(input);
661
531
  } catch {
662
532
  return null;
663
533
  }
@@ -669,38 +539,36 @@ const emptySet$1 = /* @__PURE__ */ new Set();
669
539
  */
670
540
  function wrapMutation(client, identifier) {
671
541
  const config = client.getTypeConfig(identifier.entity);
672
- return async ({ args, delete: deleteRecord, input, insert = "after", optimistic, view: view$1 }) => {
673
- const id$1 = maybeGetId(config.getId, input);
674
- const plan = view$1 ? getSelectionPlan(view$1, null) : void 0;
542
+ return async ({ args, delete: deleteRecord, input, insert = "after", optimistic, view }) => {
543
+ const id = maybeGetId(config.getId, input);
544
+ const plan = view ? getSelectionPlan(view, null) : void 0;
675
545
  const viewSelection = plan?.paths;
676
- const optimisticRecord = optimistic ? id$1 != null ? {
677
- id: id$1,
546
+ const optimisticRecord = optimistic ? id != null ? {
547
+ id,
678
548
  ...optimistic
679
549
  } : optimistic : void 0;
680
550
  const optimisticRecordId = optimisticRecord !== void 0 ? maybeGetId(config.getId, optimisticRecord) : null;
681
- const optimisticEntityId = id$1 != null ? toEntityId(identifier.entity, id$1) : optimisticRecordId != null ? toEntityId(identifier.entity, optimisticRecordId) : null;
551
+ const optimisticEntityId = id != null ? toEntityId(identifier.entity, id) : optimisticRecordId != null ? toEntityId(identifier.entity, optimisticRecordId) : null;
682
552
  const snapshots = /* @__PURE__ */ new Map();
683
- const listSnapshots = deleteRecord ? /* @__PURE__ */ new Map() : void 0;
553
+ const listSnapshots = deleteRecord || optimisticRecord ? /* @__PURE__ */ new Map() : void 0;
684
554
  const optimisticSelection = optimisticRecord ? collectImplicitSelectedPaths(optimisticRecord) : void 0;
685
555
  const selection = viewSelection || optimisticSelection ? new Set([...viewSelection ? [...viewSelection] : [], ...optimisticSelection ? [...optimisticSelection] : []]) : /* @__PURE__ */ new Set();
556
+ if (deleteRecord && id == null) throw new Error(`fate: Mutation '${identifier.key}' requires an 'id' to delete.`);
686
557
  const optimisticToken = optimisticEntityId ? client.registerOptimisticUpdate(optimisticEntityId, optimisticSelection ?? emptySet$1) : null;
687
- if (optimisticRecord && optimisticEntityId) client.write(identifier.entity, optimisticRecord, optimisticSelection ?? emptySet$1, snapshots, plan, null, null, insert);
688
- if (deleteRecord) {
689
- if (id$1 == null) throw new Error(`fate: Mutation '${identifier.key}' requires an 'id' to delete.`);
690
- client.deleteRecord(identifier.entity, id$1, snapshots, listSnapshots);
691
- }
558
+ if (optimisticRecord && optimisticEntityId) client.write(identifier.entity, optimisticRecord, optimisticSelection ?? emptySet$1, snapshots, plan, null, null, insert, listSnapshots);
559
+ if (deleteRecord && id != null) client.deleteRecord(identifier.entity, id, snapshots, listSnapshots);
692
560
  const performMutation = async () => {
693
561
  try {
694
562
  const result = await client.executeMutation(identifier.key, input, selection, {
695
563
  args,
696
564
  plan
697
565
  });
698
- if (result && typeof result === "object" && (!deleteRecord || Boolean(view$1))) {
566
+ if (result && typeof result === "object" && (!deleteRecord || Boolean(view))) {
699
567
  const select = collectImplicitSelectedPaths(result);
700
568
  const pendingMask = optimisticEntityId ? client.getPendingOptimisticMask(optimisticEntityId, { excludeToken: optimisticToken }) : null;
701
569
  const filteredSelection = optimisticEntityId ? client.filterSelectionForPendingOptimistics(optimisticEntityId, select, { excludeToken: optimisticToken }) : select;
702
570
  client.write(identifier.entity, result, filteredSelection, void 0, plan, null, pendingMask, insert);
703
- if (deleteRecord && id$1 != null) client.deleteRecord(identifier.entity, id$1);
571
+ if (deleteRecord && id != null) client.deleteRecord(identifier.entity, id);
704
572
  const resultId = maybeGetId(config.getId, result);
705
573
  if (optimisticEntityId && resultId != null) client.resolveOptimisticEntity(optimisticEntityId, toEntityId(identifier.entity, resultId));
706
574
  if (optimisticRecordId != null && resultId != null && optimisticRecordId !== resultId) client.deleteRecord(identifier.entity, optimisticRecordId);
@@ -711,11 +579,11 @@ function wrapMutation(client, identifier) {
711
579
  };
712
580
  } catch (error) {
713
581
  client.clearOptimisticUpdate(optimisticToken);
714
- if (snapshots.size > 0) for (const [id$2, snapshot] of snapshots) client.restore(id$2, snapshot);
582
+ if (snapshots.size > 0) for (const [id, snapshot] of snapshots) client.restore(id, snapshot);
715
583
  if (listSnapshots && listSnapshots.size > 0) for (const [name, list] of listSnapshots) client.restoreList(name, list);
716
584
  if (error instanceof Error) {
717
- const { data } = error;
718
- if ((data ? categorizeTRPCError(getHTTPStatusCodeFromError(data)) : "boundary") === "boundary") throw error;
585
+ const statusCode = getErrorStatusCode(error);
586
+ if ((statusCode ? categorizeHTTPErrorStatus(statusCode) : "boundary") === "boundary") throw error;
719
587
  return {
720
588
  error,
721
589
  result: void 0
@@ -730,7 +598,13 @@ function wrapMutation(client, identifier) {
730
598
  return mutationPromise;
731
599
  };
732
600
  }
733
- function categorizeTRPCError(statusCode) {
601
+ const getErrorStatusCode = (error) => {
602
+ if (error instanceof FateRequestError) return error.status;
603
+ const { data } = error;
604
+ if (!data || typeof data !== "object") return;
605
+ return getHTTPStatusCodeFromError(data);
606
+ };
607
+ const categorizeHTTPErrorStatus = (statusCode) => {
734
608
  switch (statusCode) {
735
609
  case 400:
736
610
  case 402:
@@ -743,27 +617,17 @@ function categorizeTRPCError(statusCode) {
743
617
  case 422:
744
618
  case 429:
745
619
  case 499: return "callSite";
746
- case 401:
747
- case 403:
748
- case 405:
749
- case 428:
750
- case 500:
751
- case 501:
752
- case 502:
753
- case 503:
754
- case 504:
755
620
  default: return "boundary";
756
621
  }
757
- }
758
-
622
+ };
759
623
  //#endregion
760
624
  //#region src/node-ref.ts
761
- function createNodeRef(id$1) {
625
+ function createNodeRef(id) {
762
626
  const ref = {};
763
627
  Object.defineProperty(ref, NodeRefTag, {
764
628
  configurable: false,
765
629
  enumerable: false,
766
- value: id$1,
630
+ value: id,
767
631
  writable: false
768
632
  });
769
633
  return Object.freeze(ref);
@@ -774,10 +638,84 @@ function isNodeRef(value) {
774
638
  function getNodeRefId(ref) {
775
639
  return ref[NodeRefTag];
776
640
  }
777
-
641
+ //#endregion
642
+ //#region src/operation-lifetime.ts
643
+ var OperationLifetime = class {
644
+ constructor(releaseBufferSize) {
645
+ this.releaseBufferSize = releaseBufferSize;
646
+ this.operations = /* @__PURE__ */ new Map();
647
+ this.releaseBuffer = [];
648
+ }
649
+ retain(descriptor, onDispose) {
650
+ let operation = this.operations.get(descriptor.key);
651
+ if (!operation) {
652
+ operation = {
653
+ descriptor,
654
+ refCount: 0
655
+ };
656
+ this.operations.set(descriptor.key, operation);
657
+ }
658
+ operation.refCount += 1;
659
+ this.removeFromReleaseBuffer(descriptor.key);
660
+ let disposed = false;
661
+ return { dispose: () => {
662
+ if (disposed) return;
663
+ disposed = true;
664
+ if (this.release(descriptor.key)) onDispose?.();
665
+ } };
666
+ }
667
+ getDescriptors() {
668
+ return [...this.operations.values()].map((operation) => operation.descriptor);
669
+ }
670
+ release(key) {
671
+ const operation = this.operations.get(key);
672
+ if (!operation) return false;
673
+ operation.refCount -= 1;
674
+ if (operation.refCount > 0) return false;
675
+ if (this.releaseBufferSize <= 0) {
676
+ this.operations.delete(key);
677
+ return true;
678
+ }
679
+ this.releaseBuffer.push(key);
680
+ let released = false;
681
+ while (this.releaseBuffer.length > this.releaseBufferSize) {
682
+ const releasedKey = this.releaseBuffer.shift();
683
+ if (!releasedKey) continue;
684
+ const releasedOperation = this.operations.get(releasedKey);
685
+ if (releasedOperation && releasedOperation.refCount <= 0) {
686
+ this.operations.delete(releasedKey);
687
+ released = true;
688
+ }
689
+ }
690
+ return released;
691
+ }
692
+ removeFromReleaseBuffer(key) {
693
+ const index = this.releaseBuffer.indexOf(key);
694
+ if (index !== -1) this.releaseBuffer.splice(index, 1);
695
+ }
696
+ };
778
697
  //#endregion
779
698
  //#region src/store.ts
780
- const getListKey = (ownerId, field, hash = "default") => `${ownerId} __fate__ ${field} __fate__ ${hash}`;
699
+ const listKeySeparator = " __fate__ ";
700
+ const decodeListKeyPart = (part) => {
701
+ try {
702
+ return decodeURIComponent(part);
703
+ } catch {
704
+ return part;
705
+ }
706
+ };
707
+ const encodeListKeyPart = (part) => encodeURIComponent(part);
708
+ const getListKey = (ownerId, field, hash = "default") => `${encodeListKeyPart(ownerId)}${listKeySeparator}${encodeListKeyPart(field)}${listKeySeparator}${encodeListKeyPart(hash)}`;
709
+ const getOwnerFieldKey = (ownerId, field) => JSON.stringify([ownerId, field]);
710
+ const parseListKey = (key) => {
711
+ const parts = key.split(listKeySeparator);
712
+ if (parts.length !== 3) return null;
713
+ const [ownerId, field] = parts;
714
+ return {
715
+ field: decodeListKeyPart(field),
716
+ ownerId: decodeListKeyPart(ownerId)
717
+ };
718
+ };
781
719
  const cloneValue = (value) => {
782
720
  if (Array.isArray(value)) return value.map(cloneValue);
783
721
  if (isNodeRef(value)) return value;
@@ -792,25 +730,29 @@ const emptyFunction = () => {};
792
730
  var Store = class {
793
731
  constructor() {
794
732
  this.coverage = /* @__PURE__ */ new Map();
733
+ this.listKeysByOwnerField = /* @__PURE__ */ new Map();
734
+ this.listKeysByReferencedEntity = /* @__PURE__ */ new Map();
795
735
  this.lists = /* @__PURE__ */ new Map();
736
+ this.recordReferenceFields = /* @__PURE__ */ new Map();
737
+ this.recordReferencesByTarget = /* @__PURE__ */ new Map();
796
738
  this.records = /* @__PURE__ */ new Map();
797
739
  this.subscriptions = /* @__PURE__ */ new Map();
798
740
  this.listSubscriptions = /* @__PURE__ */ new Map();
799
741
  }
800
- read(id$1) {
801
- return this.records.get(id$1);
742
+ read(id) {
743
+ return this.records.get(id);
802
744
  }
803
- merge(id$1, partial, paths) {
804
- const changedPaths = this.mergeInternal(id$1, partial, paths);
805
- if (changedPaths) this.notify(id$1, changedPaths);
745
+ merge(id, partial, paths) {
746
+ const changedPaths = this.mergeInternal(id, partial, paths);
747
+ if (changedPaths) this.notify(id, changedPaths);
806
748
  }
807
- mergeInternal(id$1, partial, paths) {
808
- const previous = this.records.get(id$1);
749
+ mergeInternal(id, partial, paths) {
750
+ const previous = this.records.get(id);
809
751
  const changedPaths = /* @__PURE__ */ new Set();
810
- let mask = this.coverage.get(id$1);
752
+ let mask = this.coverage.get(id);
811
753
  if (!mask) {
812
754
  mask = emptyMask();
813
- this.coverage.set(id$1, mask);
755
+ this.coverage.set(id, mask);
814
756
  }
815
757
  union(mask, fromPaths(paths));
816
758
  if (previous) {
@@ -820,25 +762,33 @@ var Store = class {
820
762
  changedPaths.add(key);
821
763
  }
822
764
  if (!hasChanges) return null;
823
- this.records.set(id$1, {
765
+ const nextRecord = {
824
766
  ...previous,
825
767
  ...partial
826
- });
827
- } else this.records.set(id$1, { ...partial });
768
+ };
769
+ this.records.set(id, nextRecord);
770
+ this.updateRecordReferenceIndexes(id, previous, nextRecord, changedPaths);
771
+ } else {
772
+ const nextRecord = { ...partial };
773
+ this.records.set(id, nextRecord);
774
+ this.updateRecordReferenceIndexes(id, void 0, nextRecord, Object.keys(nextRecord));
775
+ }
828
776
  return changedPaths;
829
777
  }
830
- deleteRecord(id$1) {
831
- this.records.delete(id$1);
832
- this.coverage.delete(id$1);
778
+ deleteRecord(id) {
779
+ const record = this.records.get(id);
780
+ if (record) this.removeRecordReferenceIndexes(id, record);
781
+ this.records.delete(id);
782
+ this.coverage.delete(id);
833
783
  }
834
- missingForSelection(id$1, paths) {
784
+ missingForSelection(id, paths) {
835
785
  const requested = new Set(paths);
836
- if (!this.records.has(id$1)) return requested;
837
- const mask = this.coverage.get(id$1);
786
+ if (!this.records.has(id)) return requested;
787
+ const mask = this.coverage.get(id);
838
788
  if (!mask) return requested;
839
789
  return diffPaths(requested, mask);
840
790
  }
841
- subscribe(id$1, selectionOrFn, callback) {
791
+ subscribe(id, selectionOrFn, callback) {
842
792
  let mask = null;
843
793
  let fn = emptyFunction;
844
794
  if (typeof selectionOrFn === "function") fn = selectionOrFn;
@@ -846,10 +796,10 @@ var Store = class {
846
796
  mask = selectionOrFn ? fromPaths(selectionOrFn) : null;
847
797
  fn = callback;
848
798
  }
849
- let subscribers = this.subscriptions.get(id$1);
799
+ let subscribers = this.subscriptions.get(id);
850
800
  if (!subscribers) {
851
801
  subscribers = /* @__PURE__ */ new Set();
852
- this.subscriptions.set(id$1, subscribers);
802
+ this.subscriptions.set(id, subscribers);
853
803
  }
854
804
  const subscription = {
855
805
  fn,
@@ -857,14 +807,14 @@ var Store = class {
857
807
  };
858
808
  subscribers.add(subscription);
859
809
  return () => {
860
- const set = this.subscriptions.get(id$1);
810
+ const set = this.subscriptions.get(id);
861
811
  if (!set) return;
862
812
  set.delete(subscription);
863
- if (set.size === 0) this.subscriptions.delete(id$1);
813
+ if (set.size === 0) this.subscriptions.delete(id);
864
814
  };
865
815
  }
866
- notify(id$1, paths) {
867
- const set = this.subscriptions.get(id$1);
816
+ notify(id, paths) {
817
+ const set = this.subscriptions.get(id);
868
818
  if (!set) return;
869
819
  const changedPaths = paths ? [...paths] : [];
870
820
  const changedMask = changedPaths.length > 0 ? fromPaths(changedPaths) : null;
@@ -890,18 +840,110 @@ var Store = class {
890
840
  }
891
841
  getListsForField(ownerId, field) {
892
842
  const entries = [];
893
- const prefix = getListKey(ownerId, field, "");
894
- for (const entry of this.lists.entries()) if (entry[0].startsWith(prefix)) entries.push(entry);
843
+ const keys = this.listKeysByOwnerField.get(getOwnerFieldKey(ownerId, field));
844
+ if (!keys) return entries;
845
+ for (const key of keys) {
846
+ const list = this.lists.get(key);
847
+ if (list) entries.push([key, list]);
848
+ }
895
849
  return entries;
896
850
  }
897
851
  setList(key, state) {
852
+ const previous = this.lists.get(key);
853
+ if (previous) this.removeListIndexes(key, previous);
898
854
  this.lists.set(key, state);
855
+ this.addListIndexes(key, state);
899
856
  this.notifyListSubscribers(key);
900
857
  }
858
+ replaceListEntityId(previousId, nextId) {
859
+ const keys = [...this.listKeysByReferencedEntity.get(previousId) ?? []];
860
+ for (const key of keys) {
861
+ const list = this.lists.get(key);
862
+ if (!list) continue;
863
+ let changed = false;
864
+ let ids = list.ids;
865
+ let cursors = list.cursors;
866
+ if (list.ids.includes(previousId)) {
867
+ const nextIds = [];
868
+ const nextCursors = list.cursors ? [] : void 0;
869
+ const seenIds = /* @__PURE__ */ new Set();
870
+ list.ids.forEach((id, index) => {
871
+ const resolved = id === previousId ? nextId : id;
872
+ if (seenIds.has(resolved)) return;
873
+ seenIds.add(resolved);
874
+ nextIds.push(resolved);
875
+ if (nextCursors) nextCursors.push(list.cursors?.[index]);
876
+ });
877
+ changed = true;
878
+ ids = nextIds;
879
+ cursors = nextCursors;
880
+ }
881
+ const dedupe = (values) => {
882
+ if (!values || !values.includes(previousId)) return;
883
+ const seen = /* @__PURE__ */ new Set();
884
+ const next = [];
885
+ for (const value of values) {
886
+ const resolved = value === previousId ? nextId : value;
887
+ if (seen.has(resolved)) continue;
888
+ seen.add(resolved);
889
+ next.push(resolved);
890
+ }
891
+ changed = true;
892
+ return next;
893
+ };
894
+ const pendingBeforeIds = dedupe(list.pendingBeforeIds) ?? list.pendingBeforeIds;
895
+ const pendingAfterIds = dedupe(list.pendingAfterIds) ?? list.pendingAfterIds;
896
+ const liveBeforeIds = dedupe(list.liveBeforeIds) ?? list.liveBeforeIds;
897
+ const liveAfterIds = dedupe(list.liveAfterIds) ?? list.liveAfterIds;
898
+ if (!changed) continue;
899
+ const canonicalIds = new Set(ids);
900
+ this.setList(key, {
901
+ backwardPageLimit: list.backwardPageLimit,
902
+ cursors,
903
+ forwardPageLimit: list.forwardPageLimit,
904
+ ids,
905
+ liveAfterIds: liveAfterIds?.filter((id) => !canonicalIds.has(id)),
906
+ liveBeforeIds: liveBeforeIds?.filter((id) => !canonicalIds.has(id)),
907
+ pagination: list.pagination,
908
+ pendingAfterIds: pendingAfterIds?.filter((id) => !canonicalIds.has(id)),
909
+ pendingBeforeIds: pendingBeforeIds?.filter((id) => !canonicalIds.has(id))
910
+ });
911
+ }
912
+ }
901
913
  restoreList(key, list) {
902
- if (list == null) this.lists.delete(key);
914
+ if (list == null) this.deleteList(key);
903
915
  else this.setList(key, list);
904
916
  }
917
+ collectGarbage(markedRecords, markedLists, options = {}) {
918
+ const records = /* @__PURE__ */ new Set();
919
+ const lists = /* @__PURE__ */ new Set();
920
+ for (const id of this.records.keys()) {
921
+ if (markedRecords.has(id)) continue;
922
+ records.add(id);
923
+ }
924
+ for (const key of this.lists.keys()) {
925
+ if (markedLists.has(key)) continue;
926
+ lists.add(key);
927
+ }
928
+ for (const id of records) {
929
+ const record = this.records.get(id);
930
+ if (record) this.removeRecordReferenceIndexes(id, record);
931
+ this.records.delete(id);
932
+ this.coverage.delete(id);
933
+ options.onRecordDeleted?.(id);
934
+ }
935
+ for (const key of lists) this.deleteList(key);
936
+ return {
937
+ lists,
938
+ records
939
+ };
940
+ }
941
+ deleteList(key) {
942
+ const previous = this.lists.get(key);
943
+ if (previous) this.removeListIndexes(key, previous);
944
+ this.lists.delete(key);
945
+ this.notifyListSubscribers(key);
946
+ }
905
947
  subscribeList(key, fn) {
906
948
  let set = this.listSubscriptions.get(key);
907
949
  if (!set) {
@@ -917,66 +959,373 @@ var Store = class {
917
959
  };
918
960
  }
919
961
  removeReferencesTo(targetId, viewDataCache, snapshots, listSnapshots) {
920
- for (const [key, list] of this.lists.entries()) {
921
- const { ids: ids$1 } = list;
922
- if (!ids$1.includes(targetId)) continue;
962
+ const listKeys = [...this.listKeysByReferencedEntity.get(targetId) ?? []];
963
+ for (const key of listKeys) {
964
+ const list = this.lists.get(key);
965
+ if (!list) continue;
966
+ const { ids } = list;
967
+ const hasLiveAfter = Boolean(list.liveAfterIds?.includes(targetId));
968
+ const hasLiveBefore = Boolean(list.liveBeforeIds?.includes(targetId));
969
+ const hasPendingAfter = Boolean(list.pendingAfterIds?.includes(targetId));
970
+ const hasPendingBefore = Boolean(list.pendingBeforeIds?.includes(targetId));
971
+ if (!ids.includes(targetId) && !hasLiveAfter && !hasLiveBefore && !hasPendingAfter && !hasPendingBefore) continue;
923
972
  if (listSnapshots && !listSnapshots.has(key)) listSnapshots.set(key, list);
924
973
  const entityIds = [];
925
974
  const cursors = list.cursors ? [] : void 0;
926
- for (let index = 0; index < ids$1.length; index++) {
927
- const id$1 = ids$1[index];
928
- if (id$1 === targetId) continue;
929
- entityIds.push(id$1);
975
+ for (let index = 0; index < ids.length; index++) {
976
+ const id = ids[index];
977
+ if (id === targetId) continue;
978
+ entityIds.push(id);
930
979
  if (cursors) cursors.push(list.cursors?.[index]);
931
980
  }
932
981
  this.setList(key, {
982
+ backwardPageLimit: list.backwardPageLimit,
933
983
  cursors,
984
+ forwardPageLimit: list.forwardPageLimit,
934
985
  ids: entityIds,
935
- pagination: list.pagination
986
+ liveAfterIds: list.liveAfterIds?.filter((id) => id !== targetId),
987
+ liveBeforeIds: list.liveBeforeIds?.filter((id) => id !== targetId),
988
+ pagination: list.pagination,
989
+ pendingAfterIds: list.pendingAfterIds?.filter((id) => id !== targetId),
990
+ pendingBeforeIds: list.pendingBeforeIds?.filter((id) => id !== targetId)
936
991
  });
937
992
  }
938
993
  const ids = /* @__PURE__ */ new Map();
939
- for (const [id$1, record] of this.records.entries()) {
994
+ const recordEntries = [...this.recordReferencesByTarget.get(targetId)?.entries() ?? []];
995
+ for (const [id, fields] of recordEntries) {
996
+ const record = this.records.get(id);
997
+ if (!record) continue;
940
998
  let updated = false;
941
999
  const next = {};
942
1000
  const paths = /* @__PURE__ */ new Set();
943
- for (const [key, value] of Object.entries(record)) if (Array.isArray(value)) {
944
- const filtered = value.filter((item) => !(isNodeRef(item) && getNodeRefId(item) === targetId));
945
- if (filtered.length !== value.length) {
1001
+ for (const key of fields) {
1002
+ const value = record[key];
1003
+ if (Array.isArray(value)) {
1004
+ const filtered = value.filter((item) => !(isNodeRef(item) && getNodeRefId(item) === targetId));
1005
+ if (filtered.length !== value.length) {
1006
+ updated = true;
1007
+ paths.add(key);
1008
+ next[key] = filtered;
1009
+ }
1010
+ } else if (isNodeRef(value) && getNodeRefId(value) === targetId) {
946
1011
  updated = true;
947
1012
  paths.add(key);
948
- next[key] = filtered;
1013
+ next[key] = null;
949
1014
  }
950
- } else if (isNodeRef(value) && getNodeRefId(value) === targetId) {
951
- updated = true;
952
- paths.add(key);
953
- next[key] = null;
954
1015
  }
955
1016
  if (!updated) continue;
956
- if (snapshots && !snapshots.has(id$1)) snapshots.set(id$1, this.snapshot(id$1));
957
- viewDataCache.invalidate(id$1);
958
- this.mergeInternal(id$1, next, paths);
959
- ids.set(id$1, paths);
1017
+ if (snapshots && !snapshots.has(id)) snapshots.set(id, this.snapshot(id));
1018
+ viewDataCache.invalidate(id);
1019
+ this.mergeInternal(id, next, paths);
1020
+ ids.set(id, paths);
960
1021
  }
961
- for (const [id$1, paths] of ids) this.notify(id$1, paths);
1022
+ for (const [id, paths] of ids) this.notify(id, paths);
962
1023
  }
963
- snapshot(id$1) {
964
- const record = this.records.get(id$1);
965
- const mask = this.coverage.get(id$1);
1024
+ snapshot(id) {
1025
+ const record = this.records.get(id);
1026
+ const mask = this.coverage.get(id);
966
1027
  return {
967
1028
  mask: mask ? cloneMask(mask) : void 0,
968
1029
  record: record ? cloneValue(record) : void 0
969
1030
  };
970
1031
  }
971
- restore(id$1, snapshot) {
972
- if (snapshot.record === void 0) this.records.delete(id$1);
973
- else this.records.set(id$1, snapshot.record);
974
- if (snapshot.mask === void 0) this.coverage.delete(id$1);
975
- else this.coverage.set(id$1, snapshot.mask);
976
- this.notify(id$1);
1032
+ restore(id, snapshot) {
1033
+ const previous = this.records.get(id);
1034
+ if (previous) this.removeRecordReferenceIndexes(id, previous);
1035
+ if (snapshot.record === void 0) this.records.delete(id);
1036
+ else {
1037
+ this.records.set(id, snapshot.record);
1038
+ this.addRecordReferenceIndexes(id, snapshot.record);
1039
+ }
1040
+ if (snapshot.mask === void 0) this.coverage.delete(id);
1041
+ else this.coverage.set(id, snapshot.mask);
1042
+ this.notify(id);
1043
+ }
1044
+ addListIndexes(key, list) {
1045
+ const parsed = parseListKey(key);
1046
+ if (parsed) {
1047
+ const ownerFieldKey = getOwnerFieldKey(parsed.ownerId, parsed.field);
1048
+ let keys = this.listKeysByOwnerField.get(ownerFieldKey);
1049
+ if (!keys) {
1050
+ keys = /* @__PURE__ */ new Set();
1051
+ this.listKeysByOwnerField.set(ownerFieldKey, keys);
1052
+ }
1053
+ keys.add(key);
1054
+ }
1055
+ for (const id of this.getListReferencedEntityIds(list)) {
1056
+ let keys = this.listKeysByReferencedEntity.get(id);
1057
+ if (!keys) {
1058
+ keys = /* @__PURE__ */ new Set();
1059
+ this.listKeysByReferencedEntity.set(id, keys);
1060
+ }
1061
+ keys.add(key);
1062
+ }
1063
+ }
1064
+ removeListIndexes(key, list) {
1065
+ const parsed = parseListKey(key);
1066
+ if (parsed) {
1067
+ const ownerFieldKey = getOwnerFieldKey(parsed.ownerId, parsed.field);
1068
+ const keys = this.listKeysByOwnerField.get(ownerFieldKey);
1069
+ if (keys) {
1070
+ keys.delete(key);
1071
+ if (keys.size === 0) this.listKeysByOwnerField.delete(ownerFieldKey);
1072
+ }
1073
+ }
1074
+ for (const id of this.getListReferencedEntityIds(list)) {
1075
+ const keys = this.listKeysByReferencedEntity.get(id);
1076
+ if (!keys) continue;
1077
+ keys.delete(key);
1078
+ if (keys.size === 0) this.listKeysByReferencedEntity.delete(id);
1079
+ }
1080
+ }
1081
+ getListReferencedEntityIds(list) {
1082
+ const ids = /* @__PURE__ */ new Set();
1083
+ for (const id of list.ids) ids.add(id);
1084
+ for (const id of list.liveAfterIds ?? []) ids.add(id);
1085
+ for (const id of list.liveBeforeIds ?? []) ids.add(id);
1086
+ for (const id of list.pendingAfterIds ?? []) ids.add(id);
1087
+ for (const id of list.pendingBeforeIds ?? []) ids.add(id);
1088
+ return ids;
1089
+ }
1090
+ getRecordFieldReferences(value) {
1091
+ if (Array.isArray(value)) {
1092
+ let ids = null;
1093
+ for (const item of value) {
1094
+ if (!isNodeRef(item)) continue;
1095
+ if (!ids) ids = /* @__PURE__ */ new Set();
1096
+ ids.add(getNodeRefId(item));
1097
+ }
1098
+ return ids;
1099
+ }
1100
+ if (isNodeRef(value)) return new Set([getNodeRefId(value)]);
1101
+ return null;
1102
+ }
1103
+ addRecordFieldReferenceIndex(id, field, value) {
1104
+ const targets = this.getRecordFieldReferences(value);
1105
+ if (!targets || targets.size === 0) return;
1106
+ let fields = this.recordReferenceFields.get(id);
1107
+ if (!fields) {
1108
+ fields = /* @__PURE__ */ new Map();
1109
+ this.recordReferenceFields.set(id, fields);
1110
+ }
1111
+ fields.set(field, targets);
1112
+ for (const targetId of targets) {
1113
+ let records = this.recordReferencesByTarget.get(targetId);
1114
+ if (!records) {
1115
+ records = /* @__PURE__ */ new Map();
1116
+ this.recordReferencesByTarget.set(targetId, records);
1117
+ }
1118
+ let targetFields = records.get(id);
1119
+ if (!targetFields) {
1120
+ targetFields = /* @__PURE__ */ new Set();
1121
+ records.set(id, targetFields);
1122
+ }
1123
+ targetFields.add(field);
1124
+ }
1125
+ }
1126
+ removeRecordFieldReferenceIndex(id, field) {
1127
+ const fields = this.recordReferenceFields.get(id);
1128
+ const targets = fields?.get(field);
1129
+ if (!fields || !targets) return;
1130
+ for (const targetId of targets) {
1131
+ const records = this.recordReferencesByTarget.get(targetId);
1132
+ const targetFields = records?.get(id);
1133
+ if (!records || !targetFields) continue;
1134
+ targetFields.delete(field);
1135
+ if (targetFields.size === 0) records.delete(id);
1136
+ if (records.size === 0) this.recordReferencesByTarget.delete(targetId);
1137
+ }
1138
+ fields.delete(field);
1139
+ if (fields.size === 0) this.recordReferenceFields.delete(id);
1140
+ }
1141
+ addRecordReferenceIndexes(id, record) {
1142
+ for (const [field, value] of Object.entries(record)) this.addRecordFieldReferenceIndex(id, field, value);
1143
+ }
1144
+ removeRecordReferenceIndexes(id, record) {
1145
+ for (const field of Object.keys(record)) this.removeRecordFieldReferenceIndex(id, field);
1146
+ }
1147
+ updateRecordReferenceIndexes(id, previous, next, fields) {
1148
+ for (const field of fields) {
1149
+ if (previous) this.removeRecordFieldReferenceIndex(id, field);
1150
+ this.addRecordFieldReferenceIndex(id, field, next[field]);
1151
+ }
1152
+ }
1153
+ };
1154
+ //#endregion
1155
+ //#region src/request-descriptor.ts
1156
+ const serializeId = (value) => `${typeof value}:${String(value)}`;
1157
+ const getViewSignature = (view) => {
1158
+ const viewNames = /* @__PURE__ */ new Set();
1159
+ const seen = /* @__PURE__ */ new Set();
1160
+ const collect = (value) => {
1161
+ if (!value || typeof value !== "object" || seen.has(value)) return;
1162
+ seen.add(value);
1163
+ for (const [key, entry] of Object.entries(value)) {
1164
+ if (isViewTag(key)) viewNames.add(key);
1165
+ collect(entry);
1166
+ }
1167
+ };
1168
+ collect(view);
1169
+ return viewNames.size ? [...viewNames].sort().join(",") : "";
1170
+ };
1171
+ const getRootDescriptorKey = (name, argsPayload, plan) => {
1172
+ if (!argsPayload) return name;
1173
+ return getListKey("__root__", name, plan.args.get("")?.hash ?? hashArgs(argsPayload));
1174
+ };
1175
+ const getRootListDescriptorKey = (name, argsPayload, plan) => {
1176
+ if (!argsPayload) return name;
1177
+ return getListKey("__root__", name, plan.args.get("")?.hash ?? hashArgs(filterConnectionArgs(argsPayload) ?? {}));
1178
+ };
1179
+ const hasCursorArgs = (argsPayload) => Boolean(argsPayload && ("after" in argsPayload || "before" in argsPayload || "cursor" in argsPayload));
1180
+ const getPaginationArgsInfo = (argsPayload) => ({
1181
+ direction: argsPayload?.before !== void 0 || argsPayload?.last !== void 0 ? "backward" : "forward",
1182
+ hasCursorArg: hasCursorArgs(argsPayload)
1183
+ });
1184
+ const getRequestDescriptorKey = (items) => {
1185
+ const parts = [];
1186
+ const sorted = [...items].sort((a, b) => a.name.localeCompare(b.name));
1187
+ for (const item of sorted) {
1188
+ if (item.kind === "node") {
1189
+ parts.push(`node:${item.name}:${item.viewSignature}:${item.ids[0]}`);
1190
+ continue;
1191
+ }
1192
+ if (item.kind === "nodes") {
1193
+ parts.push(`node:${item.name}:${item.viewSignature}:${item.ids.map(serializeId).join(",")}`);
1194
+ continue;
1195
+ }
1196
+ if (item.kind === "query") {
1197
+ parts.push(`query:${item.name}:${item.viewSignature}:${item.argsPayload ? hashArgs(item.argsPayload) : ""}`);
1198
+ continue;
1199
+ }
1200
+ if (item.kind === "list") parts.push(`list:${item.name}:${item.viewSignature}:${item.argsPayload ? hashArgs(item.argsPayload) : ""}`);
1201
+ }
1202
+ return parts.join("$");
1203
+ };
1204
+ const createRequestDescriptor = (request, getRootType) => {
1205
+ const items = [];
1206
+ for (const [name, item] of Object.entries(request)) {
1207
+ const type = getRootType(name);
1208
+ if (isNodeItem(item)) {
1209
+ items.push({
1210
+ ids: [item.id],
1211
+ kind: "node",
1212
+ name,
1213
+ plan: getSelectionPlan(item.view, null),
1214
+ refViewNames: new Set(getViewNames(item.view)),
1215
+ type,
1216
+ viewSignature: getViewSignature(item.view)
1217
+ });
1218
+ continue;
1219
+ }
1220
+ if (isNodesItem(item)) {
1221
+ items.push({
1222
+ ids: item.ids,
1223
+ kind: "nodes",
1224
+ name,
1225
+ plan: getSelectionPlan(item.view, null),
1226
+ refViewNames: new Set(getViewNames(item.view)),
1227
+ type,
1228
+ viewSignature: getViewSignature(item.view)
1229
+ });
1230
+ continue;
1231
+ }
1232
+ if (isQueryItem(item)) {
1233
+ const { argsPayload, plan } = resolveSelectionPlan(item.view, item.args);
1234
+ items.push({
1235
+ argsPayload,
1236
+ kind: "query",
1237
+ name,
1238
+ plan,
1239
+ queryKey: getRootDescriptorKey(name, argsPayload, plan),
1240
+ refViewNames: getRootViewNames(item.view),
1241
+ type,
1242
+ viewSignature: getViewSignature(item.view)
1243
+ });
1244
+ continue;
1245
+ }
1246
+ const { argsPayload, plan } = resolveSelectionPlan(item.list, item.args);
1247
+ const hasItems = item.list && typeof item.list === "object" && "items" in item.list;
1248
+ const nodeView = hasItems && item.list.items ? item.list.items.node ?? item.list : item.list;
1249
+ items.push({
1250
+ argsPayload,
1251
+ hasItems,
1252
+ kind: "list",
1253
+ listKey: getRootListDescriptorKey(name, argsPayload, plan),
1254
+ name,
1255
+ nodeRefViewNames: getRootViewNames(nodeView),
1256
+ plan,
1257
+ type,
1258
+ viewSignature: getViewSignature(item.list)
1259
+ });
1260
+ }
1261
+ return {
1262
+ items,
1263
+ key: getRequestDescriptorKey(items)
1264
+ };
1265
+ };
1266
+ const resolveSelectionPlan = (view, args) => {
1267
+ const plan = getSelectionPlan(view, null);
1268
+ const argsPayload = combineArgsPayload(args, resolvedArgsFromPlan(plan));
1269
+ if (argsPayload) applyArgsPayloadToPlan(plan, argsPayload);
1270
+ return {
1271
+ argsPayload,
1272
+ plan
1273
+ };
1274
+ };
1275
+ //#endregion
1276
+ //#region src/request-promise.ts
1277
+ let _Symbol$toStringTag;
1278
+ var FateRequestPromise = class {
1279
+ static {
1280
+ _Symbol$toStringTag = Symbol.toStringTag;
1281
+ }
1282
+ constructor(descriptor, readValue) {
1283
+ this.descriptor = descriptor;
1284
+ this.readValue = readValue;
1285
+ this[_Symbol$toStringTag] = "Promise";
1286
+ this.status = "pending";
1287
+ this.execution = null;
1288
+ }
1289
+ get value() {
1290
+ return this.readValue();
1291
+ }
1292
+ start(execution) {
1293
+ this.execution = execution;
1294
+ this.reason = void 0;
1295
+ this.status = "pending";
1296
+ execution.then(() => {
1297
+ if (this.execution !== execution) return;
1298
+ this.execution = null;
1299
+ this.status = "fulfilled";
1300
+ }, (error) => {
1301
+ if (this.execution !== execution) return;
1302
+ this.execution = null;
1303
+ this.reason = error;
1304
+ this.status = "rejected";
1305
+ });
1306
+ }
1307
+ then(onfulfilled, onrejected) {
1308
+ switch (this.status) {
1309
+ case "fulfilled": return Promise.resolve().then(() => this.readValue()).then(onfulfilled ?? void 0, onrejected ?? void 0);
1310
+ case "rejected": return Promise.reject(this.reason).then(onfulfilled ?? void 0, onrejected ?? void 0);
1311
+ default:
1312
+ if (!this.execution) throw new Error(`fate: Pending request is missing its execution.`);
1313
+ return this.execution.then(onfulfilled ?? void 0, onrejected ?? void 0);
1314
+ }
1315
+ }
1316
+ catch(onrejected) {
1317
+ return this.then(void 0, onrejected ?? void 0);
1318
+ }
1319
+ finally(onfinally) {
1320
+ return this.then(async (value) => {
1321
+ await onfinally?.();
1322
+ return value;
1323
+ }, async (error) => {
1324
+ await onfinally?.();
1325
+ throw error;
1326
+ });
977
1327
  }
978
1328
  };
979
-
980
1329
  //#endregion
981
1330
  //#region src/client.ts
982
1331
  const getId = (record) => {
@@ -987,6 +1336,88 @@ const getId = (record) => {
987
1336
  return value;
988
1337
  };
989
1338
  const emptySet = /* @__PURE__ */ new Set();
1339
+ const emptyDispose = () => void 0;
1340
+ const dropCanonicalListIds = (listIds, ids) => {
1341
+ if (!listIds || listIds.length === 0) return;
1342
+ const canonicalIds = new Set(ids);
1343
+ const next = listIds.filter((id) => !canonicalIds.has(id));
1344
+ return next.length > 0 ? next : void 0;
1345
+ };
1346
+ const removeListId = (listIds, entityId) => listIds?.includes(entityId) ? listIds.filter((id) => id !== entityId) : listIds;
1347
+ const areArraysEqual = (left, right) => {
1348
+ if (left === right) return true;
1349
+ if (!left || !right || left.length !== right.length) return false;
1350
+ return left.every((value, index) => value === right[index]);
1351
+ };
1352
+ const arePaginationStatesEqual = (left, right) => left === right || (!left || !right ? false : left.hasNext === right.hasNext && left.hasPrevious === right.hasPrevious && left.nextCursor === right.nextCursor && left.previousCursor === right.previousCursor);
1353
+ const areListStatesEqual = (left, right) => Boolean(left) && areArraysEqual(left?.ids, right.ids) && areArraysEqual(left?.cursors, right.cursors) && areArraysEqual(left?.liveAfterIds, right.liveAfterIds) && areArraysEqual(left?.liveBeforeIds, right.liveBeforeIds) && areArraysEqual(left?.pendingAfterIds, right.pendingAfterIds) && areArraysEqual(left?.pendingBeforeIds, right.pendingBeforeIds) && left?.backwardPageLimit === right.backwardPageLimit && left?.forwardPageLimit === right.forwardPageLimit && arePaginationStatesEqual(left?.pagination, right.pagination);
1354
+ const createNodeRefsForIds = (ids, current, options = {}) => {
1355
+ if (options.reuseCurrentArray !== false && Array.isArray(current) && current.length === ids.length) {
1356
+ let matches = true;
1357
+ for (let index = 0; index < ids.length; index += 1) {
1358
+ const value = current[index];
1359
+ if (!isNodeRef(value) || getNodeRefId(value) !== ids[index]) {
1360
+ matches = false;
1361
+ break;
1362
+ }
1363
+ }
1364
+ if (matches) return current;
1365
+ }
1366
+ const existingRefsById = /* @__PURE__ */ new Map();
1367
+ if (Array.isArray(current)) {
1368
+ for (const value of current) if (isNodeRef(value)) existingRefsById.set(getNodeRefId(value), value);
1369
+ }
1370
+ return ids.map((id) => existingRefsById.get(id) ?? createNodeRef(id));
1371
+ };
1372
+ const getConnectionPageLimit = (args, direction) => {
1373
+ const value = direction === "forward" ? args?.first ?? args?.last : args?.last ?? args?.first;
1374
+ return typeof value === "number" && Number.isFinite(value) && value >= 0 ? value : null;
1375
+ };
1376
+ const isConnectionWindowFull = (listState, connection, direction) => {
1377
+ const limit = getConnectionPageLimit(connection.args, direction);
1378
+ return limit != null && listState.ids.length >= limit;
1379
+ };
1380
+ const hasConnectionPageCoverage = (listState, args, direction) => {
1381
+ const limit = getConnectionPageLimit(args, direction);
1382
+ const pagination = listState.pagination;
1383
+ if (direction === "forward") {
1384
+ if (pagination?.hasPrevious) return false;
1385
+ return limit == null || listState.ids.length >= limit || listState.forwardPageLimit != null && listState.forwardPageLimit >= limit || pagination?.hasNext === false;
1386
+ }
1387
+ if (pagination?.hasNext) return false;
1388
+ return limit == null || listState.ids.length >= limit || listState.backwardPageLimit != null && listState.backwardPageLimit >= limit || pagination?.hasPrevious === false;
1389
+ };
1390
+ const getPaginationMergeInfo = (args) => {
1391
+ const paginationArgs = getPaginationArgsInfo(args);
1392
+ return {
1393
+ ...paginationArgs,
1394
+ pageLimit: getConnectionPageLimit(args, paginationArgs.direction)
1395
+ };
1396
+ };
1397
+ const getConnectionBoundaryCursor = (listState, direction) => {
1398
+ if (!listState.cursors || listState.cursors.length === 0) return;
1399
+ return direction === "forward" ? listState.cursors.at(-1) : listState.cursors[0];
1400
+ };
1401
+ const markConnectionPageAvailable = (listState, direction) => {
1402
+ const pagination = {
1403
+ hasNext: Boolean(listState.pagination?.hasNext),
1404
+ hasPrevious: Boolean(listState.pagination?.hasPrevious),
1405
+ nextCursor: listState.pagination?.nextCursor,
1406
+ previousCursor: listState.pagination?.previousCursor
1407
+ };
1408
+ const cursor = getConnectionBoundaryCursor(listState, direction);
1409
+ if (direction === "forward") {
1410
+ pagination.hasNext = true;
1411
+ pagination.nextCursor = cursor ?? pagination.nextCursor;
1412
+ } else {
1413
+ pagination.hasPrevious = true;
1414
+ pagination.previousCursor = cursor ?? pagination.previousCursor;
1415
+ }
1416
+ return {
1417
+ ...listState,
1418
+ pagination
1419
+ };
1420
+ };
990
1421
  const setNestedValue = (target, key, value) => {
991
1422
  const path = key.split(".");
992
1423
  let current = target;
@@ -1002,32 +1433,10 @@ const setNestedValue = (target, key, value) => {
1002
1433
  current = current[segment];
1003
1434
  }
1004
1435
  };
1005
- const serializeId = (value) => `${typeof value}:${String(value)}`;
1006
- const getViewSignature = (view$1) => {
1007
- const viewNames = getViewNames(view$1);
1008
- return viewNames.size ? [...viewNames].sort().join(",") : "";
1009
- };
1010
- const getRequestCacheKey = (request) => {
1011
- const parts = [];
1012
- const names = Object.keys(request).sort();
1013
- for (const name of names) {
1014
- const item = request[name];
1015
- if (!item) continue;
1016
- if (isNodeItem(item)) {
1017
- parts.push(`node:${name}:${getViewSignature(item.view)}:${item.id}`);
1018
- continue;
1019
- }
1020
- if (isNodesItem(item)) {
1021
- parts.push(`node:${name}:${getViewSignature(item.view)}:${item.ids.map(serializeId).join(",")}`);
1022
- continue;
1023
- }
1024
- if (isQueryItem(item)) {
1025
- parts.push(`query:${name}:${getViewSignature(item.view)}:${item.args ? hashArgs(item.args) : ""}`);
1026
- continue;
1027
- }
1028
- parts.push(`list:${name}:${getViewSignature(item.list)}:${item.args ? hashArgs(item.args) : ""}`);
1029
- }
1030
- return parts.join("$");
1436
+ const omitUndefinedValues = (record) => {
1437
+ const result = {};
1438
+ for (const [key, value] of Object.entries(record)) if (value !== void 0) result[key] = value;
1439
+ return result;
1031
1440
  };
1032
1441
  const groupSelectionByPrefix = (select) => {
1033
1442
  if (select.size === 0) return /* @__PURE__ */ new Map();
@@ -1060,14 +1469,22 @@ var FateClient = class {
1060
1469
  this.optimisticByEntity = /* @__PURE__ */ new Map();
1061
1470
  this.optimisticEntityResolutions = /* @__PURE__ */ new Map();
1062
1471
  this.optimisticTokenCounter = 0;
1472
+ this.liveSubscriptions = /* @__PURE__ */ new Map();
1473
+ this.liveConnectionInvalidations = /* @__PURE__ */ new Map();
1474
+ this.stableRefs = /* @__PURE__ */ new Map();
1063
1475
  this.requests = /* @__PURE__ */ new Map();
1064
1476
  this.rootRequests = /* @__PURE__ */ new Map();
1065
1477
  this.stalledRequests = /* @__PURE__ */ new Set();
1478
+ this.gcPending = false;
1479
+ this.gcScheduled = false;
1066
1480
  this.store = new Store();
1067
1481
  this.viewDataCache = new ViewDataCache();
1482
+ this.liveConnectionInvalidationToken = 0;
1068
1483
  this.actions = Object.create(null);
1069
1484
  this.mutationMap = Object.create(null);
1070
1485
  this.mutations = Object.create(null);
1486
+ this.onLiveError = options.onLiveError;
1487
+ this.operationLifetime = new OperationLifetime(options.gcReleaseBufferSize ?? 10);
1071
1488
  this.roots = options.roots;
1072
1489
  this.transport = options.transport;
1073
1490
  this.types = new Map(options.types.map((entity) => [entity.type, {
@@ -1075,12 +1492,12 @@ var FateClient = class {
1075
1492
  ...entity
1076
1493
  }]));
1077
1494
  if (options.mutations) for (const [key, definition] of Object.entries(options.mutations)) {
1078
- const mutation$1 = wrapMutation(this, {
1495
+ const mutation = wrapMutation(this, {
1079
1496
  ...definition,
1080
1497
  key
1081
1498
  });
1082
- this.mutationMap[key] = mutation$1;
1083
- setNestedValue(this.mutations, key, mutation$1);
1499
+ this.mutationMap[key] = mutation;
1500
+ setNestedValue(this.mutations, key, mutation);
1084
1501
  setNestedValue(this.actions, key, async (_previousState, data) => data === "reset" ? null : await this.mutationMap[key](data));
1085
1502
  }
1086
1503
  this.initializeParentLists();
@@ -1120,20 +1537,50 @@ var FateClient = class {
1120
1537
  }
1121
1538
  lists.add(key);
1122
1539
  }
1123
- insertIntoRootLists(type, entityId, insert) {
1540
+ snapshotList(key, listSnapshots) {
1541
+ if (!listSnapshots || listSnapshots.has(key)) return;
1542
+ const listState = this.store.getListState(key);
1543
+ if (listState) listSnapshots.set(key, listState);
1544
+ }
1545
+ applyListInsert(listState, entityId, insert) {
1546
+ const pendingBeforeIds = listState.pendingBeforeIds ?? [];
1547
+ const pendingAfterIds = listState.pendingAfterIds ?? [];
1548
+ if (listState.ids.includes(entityId) || pendingBeforeIds.includes(entityId) || pendingAfterIds.includes(entityId)) return {
1549
+ list: listState,
1550
+ pending: false
1551
+ };
1552
+ if (insert === "before" ? Boolean(listState.pagination?.hasPrevious) : Boolean(listState.pagination?.hasNext)) return {
1553
+ list: insert === "before" ? {
1554
+ ...listState,
1555
+ pendingBeforeIds: [entityId, ...pendingBeforeIds]
1556
+ } : {
1557
+ ...listState,
1558
+ pendingAfterIds: [...pendingAfterIds, entityId]
1559
+ },
1560
+ pending: true
1561
+ };
1562
+ const ids = insert === "before" ? [entityId, ...listState.ids] : [...listState.ids, entityId];
1563
+ const cursors = listState.cursors ? insert === "before" ? [void 0, ...listState.cursors] : [...listState.cursors, void 0] : listState.cursors;
1564
+ return {
1565
+ list: {
1566
+ ...listState,
1567
+ cursors,
1568
+ ids
1569
+ },
1570
+ pending: false
1571
+ };
1572
+ }
1573
+ insertIntoRootLists(type, entityId, insert, listSnapshots) {
1124
1574
  if (insert === "none") return;
1125
1575
  const listNames = this.rootLists.get(type);
1126
1576
  if (!listNames) return;
1127
1577
  for (const key of listNames) {
1128
1578
  const listState = this.store.getListState(key);
1129
- if (!listState || listState.ids.includes(entityId)) continue;
1130
- const ids = insert === "before" ? [entityId, ...listState.ids] : [...listState.ids, entityId];
1131
- const cursors = listState.cursors ? insert === "before" ? [void 0, ...listState.cursors] : [...listState.cursors, void 0] : listState.cursors;
1132
- this.store.setList(key, {
1133
- cursors,
1134
- ids,
1135
- pagination: listState.pagination
1136
- });
1579
+ if (!listState) continue;
1580
+ const next = this.applyListInsert(listState, entityId, insert).list;
1581
+ if (next === listState) continue;
1582
+ this.snapshotList(key, listSnapshots);
1583
+ this.store.setList(key, next);
1137
1584
  }
1138
1585
  }
1139
1586
  getTypeConfig(type) {
@@ -1152,63 +1599,78 @@ var FateClient = class {
1152
1599
  } : argsPayload ? { args: argsPayload } : input;
1153
1600
  return await this.transport.mutate(key, requestInput, select);
1154
1601
  }
1155
- write(type, data, select, snapshots, plan, pathPrefix = null, blockedMask, insert) {
1156
- return this.writeEntity(type, data, select, snapshots, plan, pathPrefix, blockedMask, insert);
1602
+ write(type, data, select, snapshots, plan, pathPrefix = null, blockedMask, insert, listSnapshots) {
1603
+ return this.writeEntity(type, data, select, snapshots, plan, pathPrefix, blockedMask, insert, listSnapshots);
1157
1604
  }
1158
- deleteRecord(type, id$1, snapshots, listSnapshots) {
1159
- const entityId = toEntityId(type, id$1);
1605
+ deleteRecord(type, id, snapshots, listSnapshots) {
1606
+ const entityId = toEntityId(type, id);
1160
1607
  if (snapshots && !snapshots.has(entityId)) snapshots.set(entityId, this.store.snapshot(entityId));
1161
1608
  this.viewDataCache.invalidate(entityId);
1609
+ this.stableRefs.delete(entityId);
1162
1610
  this.store.deleteRecord(entityId);
1163
1611
  this.store.removeReferencesTo(entityId, this.viewDataCache, snapshots, listSnapshots);
1164
1612
  }
1165
- restore(id$1, snapshot) {
1166
- this.viewDataCache.invalidate(id$1);
1167
- this.store.restore(id$1, snapshot);
1613
+ restore(id, snapshot) {
1614
+ this.viewDataCache.invalidate(id);
1615
+ this.store.restore(id, snapshot);
1168
1616
  }
1169
1617
  restoreList(name, list) {
1170
1618
  this.store.restoreList(name, list);
1171
1619
  }
1172
- ref(type, id$1, view$1) {
1173
- return createRef(type, id$1, view$1);
1620
+ stableRefWithViewNames(type, id, viewNames) {
1621
+ const entityId = toEntityId(type, id);
1622
+ const key = `${typeof id}:${String(id)}:${[...viewNames].sort().join(",")}`;
1623
+ let refs = this.stableRefs.get(entityId);
1624
+ if (!refs) {
1625
+ refs = /* @__PURE__ */ new Map();
1626
+ this.stableRefs.set(entityId, refs);
1627
+ }
1628
+ const existing = refs.get(key);
1629
+ if (existing) return existing;
1630
+ const ref = createRefWithViewNames(type, id, viewNames);
1631
+ refs.set(key, ref);
1632
+ return ref;
1633
+ }
1634
+ ref(type, id, view) {
1635
+ return this.stableRefWithViewNames(type, id, getViewNames(view));
1174
1636
  }
1175
1637
  rootListRef(entityId, rootView) {
1176
- const { id: id$1, type } = parseEntityId(entityId);
1177
- return createRef(type, id$1, rootView, { root: true });
1638
+ const { id, type } = parseEntityId(entityId);
1639
+ return this.stableRefWithViewNames(type, id, getRootViewNames(rootView));
1178
1640
  }
1179
- readView(view$1, ref) {
1180
- const id$1 = ref.id;
1641
+ readView(view, ref) {
1642
+ const id = ref.id;
1181
1643
  const type = ref.__typename;
1182
- if (id$1 == null) {
1644
+ if (id == null) {
1183
1645
  const received = Object.keys(ref).length > 0 ? `'${JSON.stringify(ref)}'` : "an empty object";
1184
1646
  throw new Error(`fate: Invalid view reference. Expected 'id' to be provided as part of the reference, received ${received}. Did you forget to spread the correct view into its parent or pass the wrong ref to 'useView'?`);
1185
1647
  }
1186
1648
  if (type == null) throw new Error(`fate: Invalid view reference. Expected '__typename' to be provided as part of the reference, received '${JSON.stringify(ref)}'.`);
1187
- const entityId = toEntityId(type, id$1);
1649
+ const entityId = toEntityId(type, id);
1188
1650
  const resolvedEntityId = this.optimisticEntityResolutions.get(entityId) ?? null;
1189
1651
  if (resolvedEntityId && resolvedEntityId !== entityId) {
1190
1652
  const { id: resolvedId, type: resolvedType } = parseEntityId(resolvedEntityId);
1191
- return this.readView(view$1, createRef(resolvedType, resolvedId, view$1));
1653
+ return this.readView(view, this.stableRefWithViewNames(resolvedType, resolvedId, getViewNames(view)));
1192
1654
  }
1193
- const viewNames = getViewNames(view$1);
1655
+ const viewNames = getViewNames(view);
1194
1656
  const refViews = ref[ViewsTag];
1195
1657
  if (!refViews || ![...viewNames].every((name) => refViews.has(name))) {
1196
1658
  const received = refViews ? [...refViews].join(", ") : JSON.stringify(ref);
1197
1659
  throw new Error(`fate: Invalid view reference. Expected the provided ref to include the view(s) '${[...viewNames].join("', '")}', received '${received}'. You can fix this issue by spreading the correct view into its parent so fate can create the correct view refs for you.`);
1198
1660
  }
1199
- const cached = this.viewDataCache.get(entityId, view$1, ref);
1661
+ const cached = this.viewDataCache.get(entityId, view, ref);
1200
1662
  if (cached) return cached;
1201
- const plan = getSelectionPlan(view$1, ref);
1663
+ const plan = getSelectionPlan(view, ref);
1202
1664
  const selectedPaths = plan.paths;
1203
1665
  const missing = this.store.missingForSelection(entityId, selectedPaths);
1204
1666
  const resolveSnapshot = () => {
1205
- const resolvedView = this.readViewSelection(view$1, ref, entityId, plan);
1667
+ const resolvedView = this.readViewSelection(view, ref, entityId, plan);
1206
1668
  const thenable = {
1207
1669
  status: "fulfilled",
1208
1670
  then: (onfulfilled, onrejected) => Promise.resolve(resolvedView).then(onfulfilled, onrejected),
1209
1671
  value: resolvedView
1210
1672
  };
1211
- this.viewDataCache.set(entityId, view$1, ref, thenable, new Set(resolvedView.coverage.map(([id$2]) => id$2)));
1673
+ this.viewDataCache.set(entityId, view, ref, thenable, new Set(resolvedView.coverage.map(([id]) => id)));
1212
1674
  return thenable;
1213
1675
  };
1214
1676
  if (missing.size === 0) {
@@ -1221,38 +1683,350 @@ var FateClient = class {
1221
1683
  if (pendingOptimistic) {
1222
1684
  const pending = this.pending.get(key);
1223
1685
  if (pending) return pending;
1224
- const promise$1 = Promise.all(pendingOptimistic).then(() => this.readView(view$1, ref)).finally(() => this.pending.delete(key));
1225
- this.pending.set(key, promise$1);
1226
- return promise$1;
1686
+ const promise = Promise.all(pendingOptimistic).then(() => this.readView(view, ref)).finally(() => this.pending.delete(key));
1687
+ this.pending.set(key, promise);
1688
+ return promise;
1227
1689
  }
1228
1690
  if (this.stalledRequests.has(key)) return resolveSnapshot();
1229
1691
  const pendingPromise = this.pending.get(key) || null;
1230
1692
  if (pendingPromise) return pendingPromise;
1231
- const promise = this.fetchByIdAndNormalize(type, [id$1], missing, plan).finally(() => this.pending.delete(key)).then(() => {
1693
+ const promise = this.fetchByIdAndNormalize(type, [id], missing, plan).finally(() => this.pending.delete(key)).then(() => {
1232
1694
  if (this.store.missingForSelection(entityId, selectedPaths).size > 0) {
1233
1695
  this.stalledRequests.add(key);
1234
1696
  return resolveSnapshot();
1235
1697
  }
1236
1698
  this.stalledRequests.delete(key);
1237
- return this.readView(view$1, ref);
1699
+ return this.readView(view, ref);
1238
1700
  });
1239
1701
  this.pending.set(key, promise);
1240
1702
  return promise;
1241
1703
  }
1242
1704
  return resolveSnapshot();
1243
1705
  }
1706
+ assertLiveViewSupport() {
1707
+ if (!this.transport.subscribeById) throw new Error(`fate: transport does not support live views. Please provide a 'subscribeById' implementation in your transport.`);
1708
+ }
1709
+ assertLiveConnectionSupport() {
1710
+ if (!this.transport.subscribeConnection) throw new Error(`fate: transport does not support live list views. Please provide a 'subscribeConnection' implementation in your transport.`);
1711
+ }
1712
+ subscribeLiveView(view, ref) {
1713
+ this.assertLiveViewSupport();
1714
+ const id = ref.id;
1715
+ const type = ref.__typename;
1716
+ if (id == null) throw new Error(`fate: Invalid live view reference. Expected 'id' to be provided as part of the reference.`);
1717
+ if (type == null) throw new Error(`fate: Invalid live view reference. Expected '__typename' to be provided as part of the reference.`);
1718
+ const viewNames = getViewNames(view);
1719
+ const refViews = ref[ViewsTag];
1720
+ if (!refViews || ![...viewNames].every((name) => refViews.has(name))) {
1721
+ const received = refViews ? [...refViews].join(", ") : JSON.stringify(ref);
1722
+ throw new Error(`fate: Invalid live view reference. Expected the provided ref to include the view(s) '${[...viewNames].join("', '")}', received '${received}'.`);
1723
+ }
1724
+ const entityId = toEntityId(type, id);
1725
+ const plan = getSelectionPlan(view, ref);
1726
+ const key = this.liveSubscriptionKey(entityId, plan);
1727
+ const existing = this.liveSubscriptions.get(key);
1728
+ if (existing) {
1729
+ existing.count += 1;
1730
+ existing.releaseScheduled = false;
1731
+ return () => {
1732
+ this.releaseLiveSubscription(key);
1733
+ };
1734
+ }
1735
+ const args = resolvedArgsFromPlan(plan);
1736
+ let unsubscribe;
1737
+ try {
1738
+ unsubscribe = this.transport.subscribeById(type, id, plan.paths, args, {
1739
+ onData: (record, liveSelect) => {
1740
+ if (!record || typeof record !== "object") return;
1741
+ const pendingMask = this.getPendingOptimisticMask(entityId);
1742
+ const select = new Set(liveSelect ?? plan.paths);
1743
+ const selection = this.filterSelectionForPendingOptimistics(entityId, select);
1744
+ this.write(type, record, selection, void 0, plan, null, pendingMask, "none");
1745
+ },
1746
+ onDelete: (deletedId) => {
1747
+ this.deleteRecord(type, deletedId ?? id);
1748
+ },
1749
+ onError: (error) => this.reportLiveError(error)
1750
+ });
1751
+ } catch (error) {
1752
+ this.reportLiveError(error);
1753
+ return emptyDispose;
1754
+ }
1755
+ this.liveSubscriptions.set(key, {
1756
+ count: 1,
1757
+ unsubscribe
1758
+ });
1759
+ return () => {
1760
+ this.releaseLiveSubscription(key);
1761
+ };
1762
+ }
1763
+ subscribeLiveListView(view, connection) {
1764
+ this.assertLiveConnectionSupport();
1765
+ const plan = getSelectionPlan(view, null);
1766
+ const connectionArgs = filterConnectionArgs(connection.args);
1767
+ const selectionArgs = resolvedArgsFromPlan(plan);
1768
+ const key = this.liveConnectionSubscriptionKey(connection, plan, connectionArgs);
1769
+ const existing = this.liveSubscriptions.get(key);
1770
+ if (existing) {
1771
+ existing.count += 1;
1772
+ existing.releaseScheduled = false;
1773
+ return () => {
1774
+ this.releaseLiveSubscription(key);
1775
+ };
1776
+ }
1777
+ let unsubscribe;
1778
+ try {
1779
+ unsubscribe = this.transport.subscribeConnection(connection.procedure, connection.type, connectionArgs, plan.paths, selectionArgs, {
1780
+ onError: (error) => this.reportLiveError(error),
1781
+ onEvent: (event) => {
1782
+ this.applyLiveConnectionEvent(view, connection, plan, event);
1783
+ }
1784
+ });
1785
+ } catch (error) {
1786
+ this.reportLiveError(error);
1787
+ return emptyDispose;
1788
+ }
1789
+ this.liveSubscriptions.set(key, {
1790
+ count: 1,
1791
+ unsubscribe
1792
+ });
1793
+ return () => {
1794
+ this.releaseLiveSubscription(key);
1795
+ };
1796
+ }
1797
+ applyLiveConnectionEvent(view, connection, plan, event) {
1798
+ if (event.type === "invalidate") {
1799
+ this.invalidateLiveConnection(view, connection);
1800
+ return;
1801
+ }
1802
+ if (event.type === "deleteEdge") {
1803
+ this.deleteConnectionEdge(connection, toEntityId(event.nodeType, event.id));
1804
+ return;
1805
+ }
1806
+ const node = event.edge.node;
1807
+ if (!node || typeof node !== "object") return;
1808
+ const nodeRecord = node;
1809
+ const nodeType = event.nodeType;
1810
+ if (!nodeType) {
1811
+ this.reportLiveError(/* @__PURE__ */ new Error(`fate: Live connection event for '${connection.procedure}' did not include a node type.`));
1812
+ return;
1813
+ }
1814
+ const entityId = this.write(nodeType, nodeRecord, plan.paths, void 0, plan, null, null, "none");
1815
+ this.insertConnectionEdge(connection.key, entityId, event.edge.cursor, event.type, event.targetCursor, view, connection);
1816
+ }
1817
+ invalidateLiveConnection(view, connection) {
1818
+ const token = ++this.liveConnectionInvalidationToken;
1819
+ this.liveConnectionInvalidations.set(connection.key, token);
1820
+ const previous = this.store.getListState(connection.key);
1821
+ this.store.restoreList(connection.key, void 0);
1822
+ this.loadConnection(view, connection, connection.args ?? {}).then(() => {
1823
+ if (this.liveConnectionInvalidations.get(connection.key) === token) this.liveConnectionInvalidations.delete(connection.key);
1824
+ }).catch((error) => {
1825
+ if (this.liveConnectionInvalidations.get(connection.key) === token) {
1826
+ this.store.restoreList(connection.key, previous);
1827
+ this.liveConnectionInvalidations.delete(connection.key);
1828
+ }
1829
+ this.reportLiveError(error);
1830
+ });
1831
+ }
1832
+ deleteConnectionEdge(connection, entityId) {
1833
+ const listState = this.store.getListState(connection.key);
1834
+ if (!listState) {
1835
+ this.syncNestedConnectionOwnerField(connection, entityId, void 0, "delete");
1836
+ return;
1837
+ }
1838
+ const ids = [];
1839
+ const cursors = listState.cursors ? [] : void 0;
1840
+ let changed = false;
1841
+ listState.ids.forEach((id, index) => {
1842
+ if (id === entityId) {
1843
+ changed = true;
1844
+ return;
1845
+ }
1846
+ ids.push(id);
1847
+ if (cursors) cursors.push(listState.cursors?.[index]);
1848
+ });
1849
+ const liveAfterIds = removeListId(listState.liveAfterIds, entityId);
1850
+ const liveBeforeIds = removeListId(listState.liveBeforeIds, entityId);
1851
+ const pendingAfterIds = removeListId(listState.pendingAfterIds, entityId);
1852
+ const pendingBeforeIds = removeListId(listState.pendingBeforeIds, entityId);
1853
+ changed = changed || liveAfterIds?.length !== listState.liveAfterIds?.length || liveBeforeIds?.length !== listState.liveBeforeIds?.length || pendingAfterIds?.length !== listState.pendingAfterIds?.length || pendingBeforeIds?.length !== listState.pendingBeforeIds?.length;
1854
+ if (changed) {
1855
+ const nextListState = {
1856
+ ...listState,
1857
+ cursors,
1858
+ ids,
1859
+ liveAfterIds,
1860
+ liveBeforeIds,
1861
+ pendingAfterIds,
1862
+ pendingBeforeIds
1863
+ };
1864
+ this.store.setList(connection.key, nextListState);
1865
+ this.syncNestedConnectionOwnerField(connection, entityId, nextListState, "delete");
1866
+ return;
1867
+ }
1868
+ this.syncNestedConnectionOwnerField(connection, entityId, listState, "delete");
1869
+ }
1870
+ insertConnectionEdge(key, entityId, cursor, type, targetCursor, view, connection) {
1871
+ const listState = this.store.getListState(key) ?? { ids: [] };
1872
+ const isAppendEvent = type === "appendEdge" || type === "appendNode";
1873
+ const isPrependEvent = type === "prependEdge" || type === "prependNode";
1874
+ const appendMode = connection.live?.append ?? "edge";
1875
+ const prependMode = connection.live?.prepend ?? "edge";
1876
+ if (isAppendEvent && listState.pendingAfterIds?.includes(entityId) || isPrependEvent && listState.pendingBeforeIds?.includes(entityId)) {
1877
+ this.syncNestedConnectionOwnerField(connection, entityId, listState, "insert");
1878
+ return;
1879
+ }
1880
+ const visibleIndex = listState.ids.indexOf(entityId);
1881
+ if (visibleIndex >= 0 && (isAppendEvent && appendMode === "visible" || isPrependEvent && prependMode === "visible")) {
1882
+ const nextListState = cursor === void 0 ? listState : {
1883
+ ...listState,
1884
+ cursors: listState.ids.map((_, index) => index === visibleIndex ? cursor : listState.cursors?.[index])
1885
+ };
1886
+ if (nextListState !== listState) this.store.setList(key, nextListState);
1887
+ this.syncNestedConnectionOwnerField(connection, entityId, nextListState, "insert");
1888
+ return;
1889
+ }
1890
+ const withoutExisting = this.removeEntityFromListState(listState, entityId);
1891
+ const keepLiveBeforeHidden = isPrependEvent && prependMode !== "visible" && (Boolean(withoutExisting.pagination?.hasPrevious) || isConnectionWindowFull(withoutExisting, connection, "backward"));
1892
+ const keepLiveAfterHidden = isAppendEvent && appendMode !== "visible" && (Boolean(withoutExisting.pagination?.hasNext) || isConnectionWindowFull(withoutExisting, connection, "forward"));
1893
+ const keepVisibleBeforePending = isPrependEvent && prependMode === "visible" && (withoutExisting.pagination?.hasPrevious === true || withoutExisting.pagination?.hasPrevious === void 0 && isConnectionWindowFull(withoutExisting, connection, "backward"));
1894
+ const keepVisibleAfterPending = isAppendEvent && appendMode === "visible" && (withoutExisting.pagination?.hasNext === true || withoutExisting.pagination?.hasNext === void 0 && isConnectionWindowFull(withoutExisting, connection, "forward"));
1895
+ if (keepLiveBeforeHidden) {
1896
+ const nextList = markConnectionPageAvailable(withoutExisting, "backward");
1897
+ const nextListState = {
1898
+ ...nextList,
1899
+ liveBeforeIds: [entityId, ...nextList.liveBeforeIds ?? []]
1900
+ };
1901
+ this.store.setList(key, nextListState);
1902
+ this.syncNestedConnectionOwnerField(connection, entityId, nextListState, "insert");
1903
+ return;
1904
+ }
1905
+ if (keepLiveAfterHidden) {
1906
+ const nextList = markConnectionPageAvailable(withoutExisting, "forward");
1907
+ const nextListState = {
1908
+ ...nextList,
1909
+ liveAfterIds: [...nextList.liveAfterIds ?? [], entityId]
1910
+ };
1911
+ this.store.setList(key, nextListState);
1912
+ this.syncNestedConnectionOwnerField(connection, entityId, nextListState, "insert");
1913
+ return;
1914
+ }
1915
+ if (keepVisibleBeforePending) {
1916
+ const nextList = markConnectionPageAvailable(withoutExisting, "backward");
1917
+ const nextListState = {
1918
+ ...nextList,
1919
+ pendingBeforeIds: [entityId, ...nextList.pendingBeforeIds ?? []]
1920
+ };
1921
+ this.store.setList(key, nextListState);
1922
+ this.syncNestedConnectionOwnerField(connection, entityId, nextListState, "insert");
1923
+ return;
1924
+ }
1925
+ if (keepVisibleAfterPending) {
1926
+ const nextList = markConnectionPageAvailable(withoutExisting, "forward");
1927
+ const nextListState = {
1928
+ ...nextList,
1929
+ pendingAfterIds: [...nextList.pendingAfterIds ?? [], entityId]
1930
+ };
1931
+ this.store.setList(key, nextListState);
1932
+ this.syncNestedConnectionOwnerField(connection, entityId, nextListState, "insert");
1933
+ return;
1934
+ }
1935
+ const index = this.getConnectionInsertIndex(withoutExisting, type, targetCursor);
1936
+ if (index == null) {
1937
+ this.invalidateLiveConnection(view, connection);
1938
+ return;
1939
+ }
1940
+ const ids = [...withoutExisting.ids];
1941
+ ids.splice(index, 0, entityId);
1942
+ const cursors = Boolean(withoutExisting.cursors) || cursor !== void 0 ? [...withoutExisting.cursors ?? []] : void 0;
1943
+ if (cursors) {
1944
+ while (cursors.length < withoutExisting.ids.length) cursors.push(void 0);
1945
+ cursors.splice(index, 0, cursor);
1946
+ }
1947
+ const nextListState = {
1948
+ ...withoutExisting,
1949
+ cursors,
1950
+ ids,
1951
+ liveAfterIds: removeListId(withoutExisting.liveAfterIds, entityId),
1952
+ liveBeforeIds: removeListId(withoutExisting.liveBeforeIds, entityId)
1953
+ };
1954
+ this.store.setList(key, nextListState);
1955
+ this.syncNestedConnectionOwnerField(connection, entityId, nextListState, "insert");
1956
+ }
1957
+ syncNestedConnectionOwnerField(connection, entityId, listState, action) {
1958
+ if (connection.root) return;
1959
+ const owner = this.store.read(connection.owner);
1960
+ const current = Array.isArray(owner?.[connection.field]) ? owner?.[connection.field] : [];
1961
+ const currentIds = current.map((item) => isNodeRef(item) ? getNodeRefId(item) : null).filter((id) => id != null);
1962
+ const nextIds = currentIds.filter((id) => id !== entityId);
1963
+ if (action === "insert" && listState) {
1964
+ const visibleIds = getListEntries(listState).map(({ id }) => id);
1965
+ const visibleIndex = visibleIds.indexOf(entityId);
1966
+ if (visibleIndex >= 0) {
1967
+ const nextIndexById = new Map(nextIds.map((id, index) => [id, index]));
1968
+ let insertIndex = null;
1969
+ for (let index = visibleIndex - 1; index >= 0; index -= 1) {
1970
+ const currentIndex = nextIndexById.get(visibleIds[index]);
1971
+ if (currentIndex != null) {
1972
+ insertIndex = currentIndex + 1;
1973
+ break;
1974
+ }
1975
+ }
1976
+ if (insertIndex == null) for (let index = visibleIndex + 1; index < visibleIds.length; index += 1) {
1977
+ const currentIndex = nextIndexById.get(visibleIds[index]);
1978
+ if (currentIndex != null) {
1979
+ insertIndex = currentIndex;
1980
+ break;
1981
+ }
1982
+ }
1983
+ nextIds.splice(insertIndex ?? (visibleIndex === 0 ? 0 : nextIds.length), 0, entityId);
1984
+ }
1985
+ }
1986
+ if (currentIds.length === nextIds.length && currentIds.every((id, index) => id === nextIds[index])) return;
1987
+ this.viewDataCache.invalidate(connection.owner);
1988
+ this.store.merge(connection.owner, { [connection.field]: createNodeRefsForIds(nextIds, current) }, [connection.field]);
1989
+ }
1990
+ getConnectionInsertIndex(listState, type, targetCursor) {
1991
+ if (type === "prependEdge" || type === "prependNode") return 0;
1992
+ if (type === "appendEdge" || type === "appendNode") return listState.ids.length;
1993
+ if (targetCursor && listState.cursors) {
1994
+ const index = listState.cursors.indexOf(targetCursor);
1995
+ if (index >= 0) return type === "insertEdgeBefore" ? index : index + 1;
1996
+ }
1997
+ return null;
1998
+ }
1999
+ removeEntityFromListState(listState, entityId) {
2000
+ const ids = [];
2001
+ const cursors = listState.cursors ? [] : void 0;
2002
+ listState.ids.forEach((id, index) => {
2003
+ if (id === entityId) return;
2004
+ ids.push(id);
2005
+ if (cursors) cursors.push(listState.cursors?.[index]);
2006
+ });
2007
+ return {
2008
+ ...listState,
2009
+ cursors,
2010
+ ids,
2011
+ liveAfterIds: removeListId(listState.liveAfterIds, entityId),
2012
+ liveBeforeIds: removeListId(listState.liveBeforeIds, entityId),
2013
+ pendingAfterIds: removeListId(listState.pendingAfterIds, entityId),
2014
+ pendingBeforeIds: removeListId(listState.pendingBeforeIds, entityId)
2015
+ };
2016
+ }
1244
2017
  mergeListState(previous, incomingIds, incomingCursors, incomingPagination, options) {
1245
2018
  const existingIds = previous?.ids ?? [];
1246
2019
  const existingSet = new Set(existingIds);
1247
2020
  const isBackward = options.direction === "backward";
1248
2021
  const mergeIds = () => {
1249
2022
  if (!previous) return [...incomingIds];
2023
+ if (!options.hasCursorArg && options.replace) return [...incomingIds];
1250
2024
  if (!options.hasCursorArg) {
1251
2025
  const incomingSet = new Set(incomingIds);
1252
- const remaining = existingIds.filter((id$1) => !incomingSet.has(id$1));
2026
+ const remaining = existingIds.filter((id) => !incomingSet.has(id));
1253
2027
  return isBackward ? [...remaining, ...incomingIds] : [...incomingIds, ...remaining];
1254
2028
  }
1255
- const newIds = incomingIds.filter((id$1) => !existingSet.has(id$1));
2029
+ const newIds = incomingIds.filter((id) => !existingSet.has(id));
1256
2030
  return isBackward ? [...newIds, ...existingIds] : [...existingIds, ...newIds];
1257
2031
  };
1258
2032
  const ids = mergeIds();
@@ -1264,18 +2038,25 @@ var FateClient = class {
1264
2038
  incomingCursors.forEach((cursor, index) => {
1265
2039
  if (cursor !== void 0) cursorMap.set(incomingIds[index], cursor);
1266
2040
  });
1267
- const cursors = hasIncomingCursor || Boolean(previous?.cursors) || options.hasCursorArg ? ids.map((id$1) => cursorMap.get(id$1)) : void 0;
2041
+ const cursors = hasIncomingCursor || Boolean(previous?.cursors) || options.hasCursorArg ? ids.map((id) => cursorMap.get(id)) : void 0;
1268
2042
  const previousPagination = previous?.pagination;
1269
2043
  const newPagination = incomingPagination;
2044
+ const pagination = previousPagination || newPagination ? {
2045
+ hasNext: !!(newPagination?.hasNext ?? previousPagination?.hasNext),
2046
+ hasPrevious: !!(newPagination?.hasPrevious ?? previousPagination?.hasPrevious),
2047
+ nextCursor: newPagination?.nextCursor ?? previousPagination?.nextCursor,
2048
+ previousCursor: newPagination?.previousCursor ?? previousPagination?.previousCursor
2049
+ } : void 0;
1270
2050
  return {
2051
+ backwardPageLimit: !options.hasCursorArg && isBackward && options.pageLimit != null ? Math.max(previous?.backwardPageLimit ?? 0, options.pageLimit) : previous?.backwardPageLimit,
1271
2052
  cursors,
2053
+ forwardPageLimit: !options.hasCursorArg && !isBackward && options.pageLimit != null ? Math.max(previous?.forwardPageLimit ?? 0, options.pageLimit) : previous?.forwardPageLimit,
1272
2054
  ids,
1273
- pagination: previousPagination || newPagination ? {
1274
- hasNext: !!(newPagination?.hasNext ?? previousPagination?.hasNext),
1275
- hasPrevious: !!(newPagination?.hasPrevious ?? previousPagination?.hasPrevious),
1276
- nextCursor: newPagination?.nextCursor ?? previousPagination?.nextCursor,
1277
- previousCursor: newPagination?.previousCursor ?? previousPagination?.previousCursor
1278
- } : void 0
2055
+ liveAfterIds: dropCanonicalListIds(previous?.liveAfterIds, ids),
2056
+ liveBeforeIds: dropCanonicalListIds(previous?.liveBeforeIds, ids),
2057
+ pagination,
2058
+ pendingAfterIds: dropCanonicalListIds(previous?.pendingAfterIds, ids),
2059
+ pendingBeforeIds: dropCanonicalListIds(previous?.pendingBeforeIds, ids)
1279
2060
  };
1280
2061
  }
1281
2062
  registerPendingOptimisticMutation(entityId, promise) {
@@ -1291,10 +2072,12 @@ var FateClient = class {
1291
2072
  if (!current) return;
1292
2073
  current.delete(trackedPromise);
1293
2074
  if (current.size === 0) this.pendingOptimisticMutations.delete(entityId);
2075
+ this.runPendingGarbageCollection();
1294
2076
  });
1295
2077
  }
1296
2078
  resolveOptimisticEntity(optimisticEntityId, resolvedEntityId) {
1297
2079
  this.optimisticEntityResolutions.set(optimisticEntityId, resolvedEntityId);
2080
+ this.store.replaceListEntityId(optimisticEntityId, resolvedEntityId);
1298
2081
  }
1299
2082
  registerOptimisticUpdate(entityId, select) {
1300
2083
  if (!entityId || select.size === 0) return null;
@@ -1322,6 +2105,7 @@ var FateClient = class {
1322
2105
  entityTokens.delete(token);
1323
2106
  if (entityTokens.size === 0) this.optimisticByEntity.delete(entry.entityId);
1324
2107
  }
2108
+ this.runPendingGarbageCollection();
1325
2109
  }
1326
2110
  getPendingOptimisticMask(entityId, options = {}) {
1327
2111
  if (!entityId) return null;
@@ -1349,43 +2133,40 @@ var FateClient = class {
1349
2133
  for (const path of select) if (!isCovered(pendingMask, path)) filtered.add(path);
1350
2134
  return filtered;
1351
2135
  }
1352
- async loadConnection(view$1, connection, args, options = {}) {
2136
+ async loadConnection(view, connection, args, options = {}) {
1353
2137
  const direction = options.direction ?? "forward";
1354
2138
  if (connection.root) {
1355
2139
  if (!this.transport.fetchList) throw new Error(`fate: transport does not support list fetching. Please add support for 'fetchList' in your transport.`);
1356
- const requestArgs$1 = {
2140
+ const requestArgs = omitUndefinedValues({
1357
2141
  ...connection.args,
1358
2142
  ...args
1359
- };
1360
- const { argsPayload: argsPayload$1, plan: plan$1 } = this.resolveSelection(view$1, requestArgs$1);
1361
- const { items, pagination } = await this.transport.fetchList(connection.field, plan$1.paths, argsPayload$1);
2143
+ });
2144
+ const { argsPayload, plan } = resolveSelectionPlan(view, requestArgs);
2145
+ const { items, pagination } = await this.transport.fetchList(connection.field, plan.paths, argsPayload);
1362
2146
  if (!items) return this.store.getListState(connection.key);
1363
- const incomingIds$1 = [];
1364
- const incomingCursors$1 = [];
2147
+ const incomingIds = [];
2148
+ const incomingCursors = [];
1365
2149
  for (const entry of items) {
1366
- const id$1 = this.write(connection.type, entry.node, plan$1.paths, void 0, plan$1, null, null, "after");
1367
- incomingIds$1.push(id$1);
1368
- incomingCursors$1.push(entry.cursor);
2150
+ const id = this.write(connection.type, entry.node, plan.paths, void 0, plan, null, null, "none");
2151
+ incomingIds.push(id);
2152
+ incomingCursors.push(entry.cursor);
1369
2153
  }
1370
- const previous$1 = this.store.getListState(connection.key);
1371
- const argsValue = args;
1372
- const hasCursorArg = Boolean(argsValue && ("after" in argsValue || "before" in argsValue || "cursor" in argsValue));
1373
- const isBackward = Boolean(argsValue && (argsValue.before !== void 0 || argsValue.last !== void 0));
1374
- const nextListState$1 = this.mergeListState(previous$1, incomingIds$1, incomingCursors$1, pagination, {
1375
- direction: isBackward ? "backward" : "forward",
1376
- hasCursorArg
2154
+ const previous = this.store.getListState(connection.key);
2155
+ const nextListState = this.mergeListState(previous, incomingIds, incomingCursors, pagination, {
2156
+ ...getPaginationMergeInfo(requestArgs),
2157
+ replace: true
1377
2158
  });
1378
- this.registerRootList(connection.type, connection.key);
1379
- this.store.setList(connection.key, nextListState$1);
1380
- return nextListState$1;
2159
+ if (!connection.args) this.registerRootList(connection.type, connection.key);
2160
+ this.store.setList(connection.key, nextListState);
2161
+ return nextListState;
1381
2162
  }
1382
2163
  const owner = parseEntityId(connection.owner);
1383
- const requestArgs = {
2164
+ const requestArgs = omitUndefinedValues({
1384
2165
  ...connection.args,
1385
2166
  ...args
1386
- };
2167
+ });
1387
2168
  if (requestArgs.id === void 0 && owner.id) requestArgs.id = owner.id;
1388
- const { argsPayload, plan } = this.resolveSelection(view$1, requestArgs);
2169
+ const { argsPayload, plan } = resolveSelectionPlan(view, requestArgs);
1389
2170
  const nodeSelection = plan.paths;
1390
2171
  const scopedArgsPayload = argsPayload ? scopeArgsPayload(argsPayload, connection.field) : void 0;
1391
2172
  const parentSelection = /* @__PURE__ */ new Set();
@@ -1408,22 +2189,24 @@ var FateClient = class {
1408
2189
  if (!nodeType) throw new Error(`fate: Could not find node type for '${owner.type}.${connection.field}'.`);
1409
2190
  for (const entry of connectionPayload.items) {
1410
2191
  const { node } = entry;
1411
- const id$1 = this.write(nodeType, node, nodeSelection, void 0, plan);
1412
- incomingIds.push(id$1);
2192
+ const id = this.write(nodeType, node, nodeSelection, void 0, plan);
2193
+ incomingIds.push(id);
1413
2194
  incomingCursors.push(entry.cursor);
1414
2195
  }
1415
2196
  const previous = this.store.getListState(connection.key);
1416
2197
  const previousIds = previous?.ids ?? [];
1417
2198
  const previousSet = new Set(previousIds);
1418
- const newIds = incomingIds.filter((id$1) => !previousSet.has(id$1));
2199
+ const newIds = incomingIds.filter((id) => !previousSet.has(id));
1419
2200
  const nextListState = this.mergeListState(previous, incomingIds, incomingCursors, connectionPayload.pagination, {
2201
+ ...getPaginationMergeInfo(argsPayload),
1420
2202
  direction,
1421
- hasCursorArg: true
2203
+ hasCursorArg: true,
2204
+ replace: true
1422
2205
  });
1423
2206
  this.store.setList(connection.key, nextListState);
1424
2207
  const current = this.store.read(connection.owner);
1425
2208
  const existingField = Array.isArray(current?.[connection.field]) ? current?.[connection.field] || [] : [];
1426
- const nodeRefs = newIds.map((id$1) => createNodeRef(id$1));
2209
+ const nodeRefs = createNodeRefsForIds(newIds, void 0);
1427
2210
  const nextField = direction === "forward" ? [...existingField, ...nodeRefs] : [...nodeRefs, ...existingField];
1428
2211
  this.viewDataCache.invalidate(connection.owner);
1429
2212
  this.store.merge(connection.owner, { [connection.field]: nextField }, [connection.field]);
@@ -1431,41 +2214,179 @@ var FateClient = class {
1431
2214
  }
1432
2215
  request(request, options) {
1433
2216
  const mode = options?.mode ?? "cache-first";
1434
- const requestKey = getRequestCacheKey(request);
2217
+ return this.requestWithDescriptor(this.createRequestDescriptor(request), mode, { revalidateExisting: true });
2218
+ }
2219
+ requestForRender(request, options) {
2220
+ return this.prepareRequestForRender(request, options).promise;
2221
+ }
2222
+ prepareRequestForRender(request, options) {
2223
+ const mode = options?.mode ?? "cache-first";
2224
+ const descriptor = this.createRequestDescriptor(request);
2225
+ return {
2226
+ promise: this.requestWithDescriptor(descriptor, mode, { revalidateExisting: false }),
2227
+ requestKey: descriptor.key
2228
+ };
2229
+ }
2230
+ requestWithDescriptor(descriptor, mode, { revalidateExisting }) {
2231
+ const requestKey = descriptor.key;
1435
2232
  const existingRequest = this.requests.get(requestKey)?.get(mode);
1436
- if (existingRequest) return existingRequest;
1437
- let promise;
1438
- switch (mode) {
1439
- case "stale-while-revalidate":
1440
- promise = this.handleStoreAndNetworkRequest(request);
1441
- break;
1442
- case "cache-first":
1443
- case "network-only":
1444
- default:
1445
- promise = this.executeRequest(request, mode === "network-only" ? { fetchAll: true } : void 0).then(() => this.getRequestResult(request));
1446
- break;
2233
+ if (existingRequest) {
2234
+ if (revalidateExisting && existingRequest.status !== "pending" && (mode === "network-only" || mode === "stale-while-revalidate" || mode === "cache-first" && (existingRequest.status === "rejected" || existingRequest.status === "fulfilled" && !this.hasRequestData(existingRequest.descriptor)))) this.executeRequestHandle(existingRequest, mode);
2235
+ return existingRequest;
1447
2236
  }
2237
+ const handle = new FateRequestPromise(descriptor, () => this.getRequestResultFromDescriptor(descriptor));
1448
2238
  let requests = this.requests.get(requestKey);
1449
2239
  if (!requests) {
1450
2240
  requests = /* @__PURE__ */ new Map();
1451
2241
  this.requests.set(requestKey, requests);
1452
2242
  }
1453
- requests.set(mode, promise);
1454
- return promise;
2243
+ requests.set(mode, handle);
2244
+ this.executeRequestHandle(handle, mode);
2245
+ return handle;
1455
2246
  }
1456
2247
  releaseRequest(request, mode) {
1457
- const requestKey = getRequestCacheKey(request);
2248
+ this.releaseRequestKey(this.createRequestDescriptor(request).key, mode);
2249
+ }
2250
+ releaseRequestKey(requestKey, mode) {
1458
2251
  const requests = this.requests.get(requestKey);
1459
2252
  if (!requests) return;
1460
2253
  requests.delete(mode);
1461
2254
  if (requests.size === 0) this.requests.delete(requestKey);
1462
2255
  }
2256
+ retainRequestKey(requestKey, mode) {
2257
+ const requests = this.requests.get(requestKey);
2258
+ const descriptor = (mode ? requests?.get(mode)?.descriptor : void 0) ?? requests?.values().next().value?.descriptor;
2259
+ return descriptor ? this.operationLifetime.retain(descriptor, () => this.scheduleGarbageCollection()) : { dispose: emptyDispose };
2260
+ }
2261
+ /**
2262
+ * Keeps the records and lists reachable from a request alive until the
2263
+ * returned handle is disposed.
2264
+ *
2265
+ * `useRequest` calls this automatically while the component is mounted.
2266
+ * Use it directly for non-React request lifetimes, tests, or preloaded data
2267
+ * that must survive manual garbage collection.
2268
+ */
2269
+ retain(request) {
2270
+ return this.operationLifetime.retain(this.createRequestDescriptor(request), () => this.scheduleGarbageCollection());
2271
+ }
2272
+ scheduleGarbageCollection() {
2273
+ if (this.gcScheduled) return;
2274
+ this.gcScheduled = true;
2275
+ queueMicrotask(() => {
2276
+ this.gcScheduled = false;
2277
+ this.gc();
2278
+ });
2279
+ }
2280
+ /**
2281
+ * Removes records and lists that are not reachable from retained requests or
2282
+ * the release buffer.
2283
+ *
2284
+ * Garbage collection is scheduled automatically when retained requests are
2285
+ * released. Calling this method manually is mainly useful in tests and custom
2286
+ * memory-management flows.
2287
+ */
2288
+ gc() {
2289
+ if (this.hasActiveOptimisticUpdates()) {
2290
+ this.gcPending = true;
2291
+ return;
2292
+ }
2293
+ this.gcPending = false;
2294
+ const markedLists = /* @__PURE__ */ new Set();
2295
+ const markedRecords = /* @__PURE__ */ new Set();
2296
+ const queue = [];
2297
+ const markRecord = (entityId) => {
2298
+ if (!entityId || markedRecords.has(entityId)) return;
2299
+ markedRecords.add(entityId);
2300
+ queue.push(entityId);
2301
+ };
2302
+ const markList = (key) => {
2303
+ markedLists.add(key);
2304
+ const listState = this.store.getListState(key);
2305
+ if (!listState) return;
2306
+ for (const id of listState.pendingBeforeIds ?? []) markRecord(id);
2307
+ for (const id of listState.ids) markRecord(id);
2308
+ for (const id of listState.pendingAfterIds ?? []) markRecord(id);
2309
+ };
2310
+ for (const descriptor of this.operationLifetime.getDescriptors()) for (const item of descriptor.items) {
2311
+ if (item.kind === "node" || item.kind === "nodes") {
2312
+ for (const raw of item.ids) markRecord(toEntityId(item.type, raw));
2313
+ continue;
2314
+ }
2315
+ if (item.kind === "query") {
2316
+ markRecord(this.rootRequests.get(item.queryKey));
2317
+ continue;
2318
+ }
2319
+ if (item.kind === "list") markList(item.listKey);
2320
+ }
2321
+ for (let queueIndex = 0; queueIndex < queue.length; queueIndex += 1) {
2322
+ const entityId = queue[queueIndex];
2323
+ if (!entityId) continue;
2324
+ const record = this.store.read(entityId);
2325
+ if (!record) continue;
2326
+ this.markRecordReferences(record, markRecord);
2327
+ const { type } = parseEntityId(entityId);
2328
+ const config = this.types.get(type);
2329
+ if (!config?.fields) continue;
2330
+ for (const [field, descriptor] of Object.entries(config.fields)) {
2331
+ if (!descriptor || descriptor === "scalar" || !("listOf" in descriptor)) continue;
2332
+ for (const [listKey] of this.store.getListsForField(entityId, field)) markList(listKey);
2333
+ }
2334
+ }
2335
+ const swept = this.store.collectGarbage(markedRecords, markedLists, { onRecordDeleted: (id) => {
2336
+ this.viewDataCache.invalidate(id);
2337
+ this.stableRefs.delete(id);
2338
+ this.clearStalledRequestsForEntity(id);
2339
+ } });
2340
+ if (swept.records.size > 0) {
2341
+ for (const [key, entityId] of this.rootRequests.entries()) if (entityId && swept.records.has(entityId)) this.rootRequests.delete(key);
2342
+ }
2343
+ if (swept.lists.size > 0) for (const [type, keys] of this.rootLists.entries()) {
2344
+ for (const key of swept.lists) keys.delete(key);
2345
+ if (keys.size === 0) this.rootLists.delete(type);
2346
+ }
2347
+ }
2348
+ executeRequestHandle(handle, mode) {
2349
+ let execution;
2350
+ switch (mode) {
2351
+ case "stale-while-revalidate":
2352
+ execution = this.handleStoreAndNetworkRequest(handle.descriptor);
2353
+ break;
2354
+ default:
2355
+ execution = this.executeRequest(handle.descriptor, mode === "network-only" ? { fetchAll: true } : void 0).then(() => this.getRequestResultFromDescriptor(handle.descriptor));
2356
+ break;
2357
+ }
2358
+ handle.start(execution);
2359
+ }
2360
+ markRecordReferences(record, markRecord) {
2361
+ const seen = /* @__PURE__ */ new Set();
2362
+ const walk = (value) => {
2363
+ if (isNodeRef(value)) {
2364
+ markRecord(getNodeRefId(value));
2365
+ return;
2366
+ }
2367
+ if (!value || typeof value !== "object") return;
2368
+ if (seen.has(value)) return;
2369
+ seen.add(value);
2370
+ if (Array.isArray(value)) {
2371
+ for (const item of value) walk(item);
2372
+ return;
2373
+ }
2374
+ for (const child of Object.values(value)) walk(child);
2375
+ };
2376
+ walk(record);
2377
+ }
2378
+ hasActiveOptimisticUpdates() {
2379
+ return this.optimisticMasks.size > 0 || this.optimisticByEntity.size > 0 || this.pendingOptimisticMutations.size > 0;
2380
+ }
2381
+ runPendingGarbageCollection() {
2382
+ if (this.gcPending && !this.hasActiveOptimisticUpdates()) this.gc();
2383
+ }
1463
2384
  async handleStoreAndNetworkRequest(request) {
1464
2385
  if (!this.hasRequestData(request)) {
1465
2386
  await this.executeRequest(request, { fetchAll: true });
1466
- return this.getRequestResult(request);
2387
+ return this.getRequestResultFromDescriptor(request);
1467
2388
  }
1468
- const result = this.getRequestResult(request);
2389
+ const result = this.getRequestResultFromDescriptor(request);
1469
2390
  this.executeRequest(request, { fetchAll: true }).catch(() => {});
1470
2391
  return result;
1471
2392
  }
@@ -1474,104 +2395,125 @@ var FateClient = class {
1474
2395
  if (!root) throw new Error(`fate: Unknown root request '${name}'.`);
1475
2396
  return root.type;
1476
2397
  }
2398
+ createRequestDescriptor(request) {
2399
+ return createRequestDescriptor(request, (name) => this.getRootType(name));
2400
+ }
2401
+ getRequestKey(request) {
2402
+ return this.createRequestDescriptor(request).key;
2403
+ }
2404
+ hasRootListData(item) {
2405
+ const listState = this.store.getListState(item.listKey);
2406
+ if (!listState) return false;
2407
+ const paginationArgs = getPaginationArgsInfo(item.argsPayload);
2408
+ if (!paginationArgs.hasCursorArg && !hasConnectionPageCoverage(listState, item.argsPayload, paginationArgs.direction)) return false;
2409
+ for (const id of listState.ids) if (this.store.missingForSelection(id, item.plan.paths).size > 0) return false;
2410
+ return true;
2411
+ }
1477
2412
  async executeRequest(request, options = {}) {
1478
2413
  const fetchAll = options.fetchAll ?? false;
1479
2414
  const groups = /* @__PURE__ */ new Map();
1480
2415
  const promises = [];
1481
- for (const [name, item] of Object.entries(request)) {
1482
- const type = this.getRootType(name);
1483
- const isNode = isNodeItem(item);
1484
- if (isNode || isNodesItem(item)) {
1485
- const plan = getSelectionPlan(item.view, null);
1486
- const fields = plan.paths;
1487
- const groupKey = `${type}#${[...fields].slice().sort().join(",")}|${[...plan.args.entries()].map(([path, entry]) => `${path}:${entry.hash}`).sort().join(",")}`;
1488
- let group = groups.get(groupKey);
1489
- if (!group) {
1490
- group = {
1491
- fields,
1492
- ids: [],
1493
- plan,
1494
- type
1495
- };
1496
- groups.set(groupKey, group);
1497
- }
1498
- for (const raw of isNode ? [item.id] : item.ids) {
1499
- const entityId = toEntityId(type, raw);
1500
- const missing = this.store.missingForSelection(entityId, fields);
1501
- if (fetchAll || missing.size > 0) group.ids.push(raw);
1502
- }
1503
- } else if (isQueryItem(item)) {
1504
- const { argsPayload, plan } = this.resolveSelection(item.view, item.args);
1505
- const entityId = this.rootRequests.get(name);
1506
- const missing = entityId ? this.store.missingForSelection(entityId, plan.paths) : plan.paths;
1507
- if (fetchAll || missing.size > 0) promises.push(this.fetchQueryAndNormalize(name, item, plan, argsPayload));
1508
- } else promises.push(this.fetchListAndNormalize(name, item));
2416
+ for (const item of request.items) if (item.kind === "node" || item.kind === "nodes") {
2417
+ const fields = item.plan.paths;
2418
+ const fieldsSignature = [...fields].slice().sort().join(",");
2419
+ const argsSignature = [...item.plan.args.entries()].map(([path, entry]) => `${path}:${entry.hash}`).sort().join(",");
2420
+ const groupKey = `${item.type}#${fieldsSignature}|${argsSignature}`;
2421
+ let group = groups.get(groupKey);
2422
+ if (!group) {
2423
+ group = {
2424
+ fields,
2425
+ ids: [],
2426
+ plan: item.plan,
2427
+ type: item.type
2428
+ };
2429
+ groups.set(groupKey, group);
2430
+ }
2431
+ for (const raw of item.ids) {
2432
+ const entityId = toEntityId(item.type, raw);
2433
+ const missing = this.store.missingForSelection(entityId, fields);
2434
+ if (fetchAll || missing.size > 0) group.ids.push(raw);
2435
+ }
2436
+ } else {
2437
+ if (item.kind === "query") {
2438
+ const hasResult = this.rootRequests.has(item.queryKey);
2439
+ const entityId = this.rootRequests.get(item.queryKey);
2440
+ const missing = hasResult && entityId ? this.store.missingForSelection(entityId, item.plan.paths) : item.plan.paths;
2441
+ if (fetchAll || !hasResult || entityId && missing.size > 0) promises.push(this.fetchQueryAndNormalize(item));
2442
+ }
2443
+ if (item.kind !== "list") continue;
2444
+ if (!fetchAll && !getPaginationArgsInfo(item.argsPayload).hasCursorArg && this.hasRootListData(item)) continue;
2445
+ promises.push(this.fetchListAndNormalize(item));
1509
2446
  }
1510
2447
  await Promise.all([...promises, ...Array.from(groups.values()).map((group) => group.ids.length ? this.fetchByIdAndNormalize(group.type, group.ids, group.fields, group.plan) : Promise.resolve())]);
1511
2448
  }
1512
2449
  hasRequestData(request) {
1513
- for (const [name, item] of Object.entries(request)) {
1514
- const isNode = isNodeItem(item);
1515
- const type = this.getRootType(name);
1516
- if (isNode || isNodesItem(item)) {
1517
- const fields = getSelectionPlan(item.view, null).paths;
1518
- for (const raw of isNode ? [item.id] : item.ids) {
1519
- const entityId = toEntityId(type, raw);
2450
+ for (const item of request.items) {
2451
+ if (item.kind === "node" || item.kind === "nodes") {
2452
+ const fields = item.plan.paths;
2453
+ for (const raw of item.ids) {
2454
+ const entityId = toEntityId(item.type, raw);
1520
2455
  if (this.store.missingForSelection(entityId, fields).size > 0) return false;
1521
2456
  }
1522
2457
  continue;
1523
2458
  }
1524
- if (isQueryItem(item)) {
1525
- const { plan } = this.resolveSelection(item.view, item.args);
1526
- const entityId = this.rootRequests.get(name);
1527
- if ((entityId ? this.store.missingForSelection(entityId, plan.paths) : plan.paths).size > 0) return false;
2459
+ if (item.kind === "query") {
2460
+ const hasResult = this.rootRequests.has(item.queryKey);
2461
+ const entityId = this.rootRequests.get(item.queryKey);
2462
+ const missing = hasResult && entityId ? this.store.missingForSelection(entityId, item.plan.paths) : item.plan.paths;
2463
+ if (!hasResult || entityId && missing.size > 0) return false;
1528
2464
  continue;
1529
2465
  }
1530
- if (!this.store.getList(name)) return false;
2466
+ if (item.kind !== "list") continue;
2467
+ if (!this.hasRootListData(item)) return false;
1531
2468
  }
1532
2469
  return true;
1533
2470
  }
1534
2471
  getRequestResult(request) {
2472
+ return this.getRequestResultFromDescriptor(this.createRequestDescriptor(request));
2473
+ }
2474
+ getRequestResultFromDescriptor(request) {
1535
2475
  const result = {};
1536
- for (const [name, item] of Object.entries(request)) {
1537
- const type = this.getRootType(name);
1538
- if (isNodeItem(item)) {
1539
- result[name] = this.ref(type, item.id, item.view);
2476
+ for (const item of request.items) {
2477
+ if (item.kind === "node") {
2478
+ result[item.name] = this.stableRefWithViewNames(item.type, item.ids[0], item.refViewNames);
1540
2479
  continue;
1541
2480
  }
1542
- if (isNodesItem(item)) {
1543
- result[name] = item.ids.map((id$1) => this.ref(type, id$1, item.view));
2481
+ if (item.kind === "nodes") {
2482
+ result[item.name] = item.ids.map((id) => this.stableRefWithViewNames(item.type, id, item.refViewNames));
1544
2483
  continue;
1545
2484
  }
1546
- if (isQueryItem(item)) {
1547
- const entityId = this.rootRequests.get(name);
2485
+ if (item.kind === "query") {
2486
+ const entityId = this.rootRequests.get(item.queryKey);
1548
2487
  if (entityId) {
1549
- const { id: id$1 } = parseEntityId(entityId);
1550
- result[name] = createRef(type, id$1, item.view, { root: true });
1551
- } else result[name] = null;
2488
+ const { id } = parseEntityId(entityId);
2489
+ result[item.name] = this.stableRefWithViewNames(item.type, id, item.refViewNames);
2490
+ } else result[item.name] = null;
1552
2491
  continue;
1553
2492
  }
1554
- const listState = this.store.getListState(name);
1555
- const hasItems = item.list && typeof item.list === "object" && "items" in item.list;
1556
- const nodeView = hasItems && item.list.items ? item.list.items.node ?? item.list : item.list;
1557
- const nodes = (listState?.ids ?? []).map((id$1) => this.rootListRef(id$1, nodeView));
1558
- if (hasItems) {
2493
+ if (item.kind !== "list") continue;
2494
+ const listState = this.store.getListState(item.listKey);
2495
+ const entries = getListEntries(listState);
2496
+ const nodes = entries.map(({ id }) => {
2497
+ const { id: rawId, type } = parseEntityId(id);
2498
+ return this.stableRefWithViewNames(type, rawId, item.nodeRefViewNames);
2499
+ });
2500
+ if (item.hasItems) {
1559
2501
  const connection = {
1560
2502
  items: nodes.map((node, index) => ({
1561
- cursor: listState?.cursors?.[index],
2503
+ cursor: entries[index]?.cursor,
1562
2504
  node
1563
2505
  })),
1564
2506
  pagination: listState?.pagination
1565
2507
  };
1566
- const { argsPayload } = this.resolveSelection(item.list, item.args);
1567
2508
  const metadata = {
1568
- args: argsPayload,
1569
- field: name,
1570
- key: name,
1571
- owner: name,
1572
- procedure: `request.${name}`,
2509
+ args: item.argsPayload,
2510
+ field: item.name,
2511
+ key: item.listKey,
2512
+ live: item.plan.live.get(""),
2513
+ owner: item.name,
2514
+ procedure: item.name,
1573
2515
  root: true,
1574
- type
2516
+ type: item.type
1575
2517
  };
1576
2518
  Object.defineProperty(connection, ConnectionTag, {
1577
2519
  configurable: false,
@@ -1579,10 +2521,10 @@ var FateClient = class {
1579
2521
  value: metadata,
1580
2522
  writable: false
1581
2523
  });
1582
- result[name] = connection;
2524
+ result[item.name] = connection;
1583
2525
  continue;
1584
2526
  }
1585
- result[name] = nodes;
2527
+ result[item.name] = nodes;
1586
2528
  }
1587
2529
  return result;
1588
2530
  }
@@ -1591,42 +2533,34 @@ var FateClient = class {
1591
2533
  const records = await this.transport.fetchById(type, ids, select, resolvedArgs);
1592
2534
  for (const record of records) this.writeEntity(type, record, select, void 0, plan, prefix);
1593
2535
  }
1594
- async fetchQueryAndNormalize(name, item, plan, argsPayload) {
1595
- if (!this.transport.fetchQuery) throw new Error(`fate: transport does not support queries. Please add support for 'fetchQuery' in your transport for '${name}'.`);
1596
- const record = await this.transport.fetchQuery(name, plan.paths, argsPayload);
1597
- if (!record || typeof record !== "object") return;
1598
- const entityId = this.writeEntity(this.getRootType(name), record, plan.paths, void 0, plan);
1599
- this.rootRequests.set(name, entityId);
1600
- }
1601
- async fetchListAndNormalize(name, item) {
1602
- if (!this.transport.fetchList) throw new Error(`fate: 'transport.fetchList' is not configured but request includes a list for key '${name}'.`);
1603
- const type = this.getRootType(name);
1604
- const { argsPayload, plan } = this.resolveSelection(item.list, item.args);
1605
- const { items, pagination } = await this.transport.fetchList(name, plan.paths, argsPayload);
2536
+ async fetchQueryAndNormalize(item) {
2537
+ if (!this.transport.fetchQuery) throw new Error(`fate: transport does not support queries. Please add support for 'fetchQuery' in your transport for '${item.name}'.`);
2538
+ const record = await this.transport.fetchQuery(item.name, item.plan.paths, item.argsPayload);
2539
+ if (!record || typeof record !== "object") {
2540
+ this.rootRequests.set(item.queryKey, null);
2541
+ return;
2542
+ }
2543
+ const entityId = this.writeEntity(item.type, record, item.plan.paths, void 0, item.plan);
2544
+ this.rootRequests.set(item.queryKey, entityId);
2545
+ }
2546
+ async fetchListAndNormalize(item) {
2547
+ if (!this.transport.fetchList) throw new Error(`fate: 'transport.fetchList' is not configured but request includes a list for key '${item.name}'.`);
2548
+ const { items, pagination } = await this.transport.fetchList(item.name, item.plan.paths, item.argsPayload);
1606
2549
  const ids = [];
1607
2550
  const cursors = [];
1608
2551
  for (const entry of items) {
1609
- const id$1 = this.writeEntity(type, entry.node, plan.paths, void 0, plan);
1610
- ids.push(id$1);
2552
+ const id = this.writeEntity(item.type, entry.node, item.plan.paths, void 0, item.plan);
2553
+ ids.push(id);
1611
2554
  cursors.push(entry.cursor);
1612
2555
  }
1613
- this.registerRootList(type, name);
1614
- this.store.setList(name, {
1615
- cursors,
1616
- ids,
1617
- pagination
1618
- });
1619
- }
1620
- resolveSelection(view$1, args) {
1621
- const plan = getSelectionPlan(view$1, null);
1622
- const argsPayload = combineArgsPayload(args, resolvedArgsFromPlan(plan));
1623
- if (argsPayload) applyArgsPayloadToPlan(plan, argsPayload);
1624
- return {
1625
- argsPayload,
1626
- plan
1627
- };
1628
- }
1629
- writeEntity(type, record, select, snapshots, plan, pathPrefix = null, blockedMask, insert) {
2556
+ if (!item.argsPayload) this.registerRootList(item.type, item.listKey);
2557
+ const previous = this.store.getListState(item.listKey);
2558
+ this.store.setList(item.listKey, this.mergeListState(previous, ids, cursors, pagination, {
2559
+ ...getPaginationMergeInfo(item.argsPayload),
2560
+ replace: true
2561
+ }));
2562
+ }
2563
+ writeEntity(type, record, select, snapshots, plan, pathPrefix = null, blockedMask, insert, listSnapshots) {
1630
2564
  const config = this.types.get(type);
1631
2565
  if (!config) throw new Error(`fate: Found unknown entity type '${type}' in normalization.`);
1632
2566
  const entityId = toEntityId(type, config.getId(record));
@@ -1638,7 +2572,7 @@ var FateClient = class {
1638
2572
  const fieldArgs = plan?.args.get(fieldPath);
1639
2573
  const isFieldBlocked = blockedMask ? isCovered(blockedMask, fieldPath) : false;
1640
2574
  if (relationDescriptor === "scalar") {
1641
- if (isFieldBlocked) continue;
2575
+ if (isFieldBlocked || !Object.prototype.hasOwnProperty.call(record, key)) continue;
1642
2576
  result[key] = value;
1643
2577
  } else if (relationDescriptor && typeof relationDescriptor === "object" && "type" in relationDescriptor) {
1644
2578
  if (isFieldBlocked) continue;
@@ -1652,7 +2586,7 @@ var FateClient = class {
1652
2586
  const childConfig = this.types.get(childType);
1653
2587
  if (!childConfig) throw new Error(`fate: Unknown related type '${childType}' (field '${type}.${key}').`);
1654
2588
  result[key] = createNodeRef(toEntityId(childType, childConfig.getId(value)));
1655
- this.writeEntity(childType, value, childPaths, snapshots, plan, fieldPath, blockedMask);
2589
+ this.writeEntity(childType, value, childPaths, snapshots, plan, fieldPath, blockedMask, void 0, listSnapshots);
1656
2590
  }
1657
2591
  } else if (relationDescriptor && typeof relationDescriptor === "object" && "listOf" in relationDescriptor) {
1658
2592
  if (isFieldBlocked) continue;
@@ -1667,9 +2601,9 @@ var FateClient = class {
1667
2601
  const connection = (() => {
1668
2602
  if (Array.isArray(value)) return { items: value.map((item) => ({ node: item })) };
1669
2603
  if (value && typeof value === "object") {
1670
- const record$1 = value;
1671
- if (Array.isArray(record$1.items)) return {
1672
- items: record$1.items.map((node) => {
2604
+ const record = value;
2605
+ if (Array.isArray(record.items)) return {
2606
+ items: record.items.map((node) => {
1673
2607
  if (node && typeof node === "object" && "node" in node) {
1674
2608
  const itemRecord = node;
1675
2609
  return {
@@ -1679,7 +2613,7 @@ var FateClient = class {
1679
2613
  }
1680
2614
  return { node };
1681
2615
  }),
1682
- pagination: record$1.pagination
2616
+ pagination: record.pagination
1683
2617
  };
1684
2618
  }
1685
2619
  return null;
@@ -1710,7 +2644,7 @@ var FateClient = class {
1710
2644
  }
1711
2645
  if (node && typeof node === "object") {
1712
2646
  const childId = toEntityId(childType, childConfig.getId(node));
1713
- this.writeEntity(childType, node, nodeSelection, snapshots, plan, fieldPath, blockedMask);
2647
+ this.writeEntity(childType, node, nodeSelection, snapshots, plan, fieldPath, blockedMask, void 0, listSnapshots);
1714
2648
  ids.push(childId);
1715
2649
  continue;
1716
2650
  }
@@ -1718,13 +2652,9 @@ var FateClient = class {
1718
2652
  const listKey = getListKey(entityId, key, fieldArgs?.hash);
1719
2653
  const previousList = this.store.getListState(listKey);
1720
2654
  const argsValue = fieldArgs?.value;
1721
- const hasCursorArg = Boolean(argsValue && ("after" in argsValue || "before" in argsValue || "cursor" in argsValue));
1722
- const isBackward = Boolean(argsValue && (argsValue.before !== void 0 || argsValue.last !== void 0));
1723
- const nextListState = this.mergeListState(previousList, ids, cursors, connection.pagination, {
1724
- direction: isBackward ? "backward" : "forward",
1725
- hasCursorArg
1726
- });
1727
- result[key] = nextListState.ids.map((id$1) => createNodeRef(id$1));
2655
+ const nextListState = this.mergeListState(previousList, ids, cursors, connection.pagination, getPaginationMergeInfo(argsValue));
2656
+ const listChanged = !areListStatesEqual(previousList, nextListState);
2657
+ result[key] = createNodeRefsForIds(nextListState.ids, this.store.read(entityId)?.[key], { reuseCurrentArray: !listChanged });
1728
2658
  this.store.setList(listKey, nextListState);
1729
2659
  }
1730
2660
  } else result[key] = value;
@@ -1737,11 +2667,11 @@ var FateClient = class {
1737
2667
  if (snapshots && !snapshots.has(entityId)) snapshots.set(entityId, this.store.snapshot(entityId));
1738
2668
  this.viewDataCache.invalidate(entityId);
1739
2669
  this.store.merge(entityId, result, select);
1740
- this.linkParentLists(type, entityId, result, snapshots, insert ?? "after");
1741
- if (!pathPrefix && insert) this.insertIntoRootLists(type, entityId, insert);
2670
+ this.linkParentLists(type, entityId, result, snapshots, listSnapshots, insert ?? "after");
2671
+ if (!pathPrefix && insert) this.insertIntoRootLists(type, entityId, insert, listSnapshots);
1742
2672
  return entityId;
1743
2673
  }
1744
- linkParentLists(type, entityId, record, snapshots, insert) {
2674
+ linkParentLists(type, entityId, record, snapshots, listSnapshots, insert) {
1745
2675
  if (insert === "none") return;
1746
2676
  const parents = this.parentLists.get(type);
1747
2677
  if (!parents) return;
@@ -1756,28 +2686,31 @@ var FateClient = class {
1756
2686
  if (current.some((item) => isNodeRef(item) && getNodeRefId(item) === entityId)) continue;
1757
2687
  if (snapshots && !snapshots.has(parentId)) snapshots.set(parentId, this.store.snapshot(parentId));
1758
2688
  this.viewDataCache.invalidate(parentId);
1759
- const nextList = insert === "before" ? [createNodeRef(entityId), ...current] : [...current, createNodeRef(entityId)];
1760
- const ids = nextList.map((item) => isNodeRef(item) ? getNodeRefId(item) : null).filter((id$1) => id$1 != null);
1761
2689
  const defaultListKey = getListKey(parentId, parent.field);
1762
2690
  const defaultListState = this.store.getListState(defaultListKey);
1763
- const nextDefaultCursors = defaultListState?.cursors ? insert === "before" ? [void 0, ...defaultListState.cursors] : [...defaultListState.cursors, void 0] : defaultListState?.cursors;
1764
- this.store.setList(defaultListKey, {
1765
- cursors: nextDefaultCursors,
1766
- ids,
1767
- pagination: defaultListState?.pagination
1768
- });
2691
+ let nextField = current;
2692
+ if (defaultListState) {
2693
+ const { list: nextDefaultListState, pending } = this.applyListInsert(defaultListState, entityId, insert);
2694
+ if (nextDefaultListState !== defaultListState) {
2695
+ this.snapshotList(defaultListKey, listSnapshots);
2696
+ this.store.setList(defaultListKey, nextDefaultListState);
2697
+ }
2698
+ nextField = createNodeRefsForIds(pending ? getListEntries(nextDefaultListState).map(({ id }) => id) : nextDefaultListState.ids, current);
2699
+ } else {
2700
+ const currentIds = current.map((item) => isNodeRef(item) ? getNodeRefId(item) : null).filter((id) => id != null);
2701
+ const nextDefaultListState = { ids: insert === "before" ? [entityId, ...currentIds] : [...currentIds, entityId] };
2702
+ this.snapshotList(defaultListKey, listSnapshots);
2703
+ this.store.setList(defaultListKey, nextDefaultListState);
2704
+ nextField = createNodeRefsForIds(nextDefaultListState.ids, current);
2705
+ }
1769
2706
  const registeredLists = this.store.getListsForField(parentId, parent.field).filter(([key]) => key !== defaultListKey);
1770
2707
  for (const [listKey, listState] of registeredLists) {
1771
- if (listState.ids.includes(entityId)) continue;
1772
- const listIds = insert === "before" ? [entityId, ...listState.ids] : [...listState.ids, entityId];
1773
- const listCursors = listState.cursors ? insert === "before" ? [void 0, ...listState.cursors] : [...listState.cursors, void 0] : void 0;
1774
- this.store.setList(listKey, {
1775
- cursors: listCursors,
1776
- ids: listIds,
1777
- pagination: listState.pagination
1778
- });
2708
+ const nextListState = this.applyListInsert(listState, entityId, insert).list;
2709
+ if (nextListState === listState) continue;
2710
+ this.snapshotList(listKey, listSnapshots);
2711
+ this.store.setList(listKey, nextListState);
1779
2712
  }
1780
- this.store.merge(parentId, { [parent.field]: nextList }, [parent.field]);
2713
+ this.store.merge(parentId, { [parent.field]: nextField }, [parent.field]);
1781
2714
  }
1782
2715
  }
1783
2716
  readViewSelection(viewComposition, ref, entityId, plan, pathPrefix = null) {
@@ -1785,7 +2718,7 @@ var FateClient = class {
1785
2718
  const ids = /* @__PURE__ */ new Set();
1786
2719
  ids.add(entityId);
1787
2720
  const coverageById = /* @__PURE__ */ new Map();
1788
- const walk = (viewPayload, record$1, target, parentId, prefix) => {
2721
+ const walk = (viewPayload, record, target, parentId, prefix) => {
1789
2722
  for (const [key, selectionKind] of Object.entries(viewPayload)) {
1790
2723
  if (isViewTag(key)) {
1791
2724
  if (!target[ViewsTag]) assignViewTag(target, /* @__PURE__ */ new Set());
@@ -1795,17 +2728,17 @@ var FateClient = class {
1795
2728
  coverageById.set(parentId, (coverageById.get(parentId) ?? /* @__PURE__ */ new Set()).add(key));
1796
2729
  const fieldPath = prefix ? `${prefix}.${key}` : key;
1797
2730
  const selectionType = typeof selectionKind;
1798
- if (selectionType === "boolean" && selectionKind) target[key] = record$1[key];
2731
+ if (selectionType === "boolean" && selectionKind) target[key] = record[key];
1799
2732
  else if (selectionKind && selectionType === "object") {
1800
2733
  const selectionValue = selectionKind;
1801
2734
  const { args: selectionArgs, ...selectionWithoutArgs } = selectionValue;
1802
2735
  if (Boolean(selectionArgs) && typeof selectionArgs === "object" && Object.keys(selectionWithoutArgs).length === 0) {
1803
- target[key] = record$1[key];
2736
+ target[key] = record[key];
1804
2737
  continue;
1805
2738
  }
1806
2739
  if (!(key in target)) target[key] = {};
1807
2740
  const nextSelection = Object.keys(selectionWithoutArgs).length ? selectionWithoutArgs : selectionValue;
1808
- const value = record$1[key];
2741
+ const value = record[key];
1809
2742
  if (value == null) {
1810
2743
  target[key] = null;
1811
2744
  continue;
@@ -1815,23 +2748,26 @@ var FateClient = class {
1815
2748
  const fieldArgs = plan.args.get(fieldPath);
1816
2749
  const listKey = getListKey(parentId, key, fieldArgs?.hash);
1817
2750
  const listState = this.store.getListState(listKey);
1818
- const connection = { items: value.map((item, index) => {
1819
- const entityId$1 = isNodeRef(item) ? getNodeRefId(item) : null;
1820
- if (!entityId$1) return {
2751
+ const connection = { items: (listState ? getListEntries(listState) : value.map((item) => ({
2752
+ cursor: void 0,
2753
+ id: isNodeRef(item) ? getNodeRefId(item) : null
2754
+ }))).map((entry, index) => {
2755
+ const entityId = entry.id;
2756
+ if (!entityId) return {
1821
2757
  cursor: listState?.cursors?.[index],
1822
2758
  node: null
1823
2759
  };
1824
- ids.add(entityId$1);
1825
- const record$2 = this.store.read(entityId$1);
1826
- const { id: id$1, type } = parseEntityId(entityId$1);
2760
+ ids.add(entityId);
2761
+ const record = this.store.read(entityId);
2762
+ const { id, type } = parseEntityId(entityId);
1827
2763
  const node = {
1828
2764
  __typename: type,
1829
- id: id$1
2765
+ id
1830
2766
  };
1831
- if (record$2) walk(selection.node, record$2, node, entityId$1, fieldPath);
1832
- const entry = { node: record$2 ? node : null };
1833
- if (selection.cursor === true) entry.cursor = listState?.cursors?.[index];
1834
- return entry;
2767
+ if (record) walk(selection.node, record, node, entityId, fieldPath);
2768
+ const itemResult = { node: record ? node : null };
2769
+ if (selection.cursor === true) itemResult.cursor = entry.cursor;
2770
+ return itemResult;
1835
2771
  }) };
1836
2772
  if ("pagination" in nextSelection && nextSelection.pagination) {
1837
2773
  const paginationSelection = nextSelection.pagination;
@@ -1858,13 +2794,14 @@ var FateClient = class {
1858
2794
  const metadata = {
1859
2795
  args: (() => {
1860
2796
  if (!fieldArgs?.value && ownerRawId === void 0) return;
1861
- const value$1 = fieldArgs?.value ? { ...fieldArgs.value } : {};
1862
- if (ownerRawId !== void 0) value$1.id = ownerRawId;
1863
- return value$1;
2797
+ const value = fieldArgs?.value ? { ...fieldArgs.value } : {};
2798
+ if (ownerRawId !== void 0) value.id = ownerRawId;
2799
+ return value;
1864
2800
  })(),
1865
2801
  field: key,
1866
2802
  hash: fieldArgs?.hash,
1867
2803
  key: listKey,
2804
+ live: plan.live.get(fieldPath),
1868
2805
  owner: parentId,
1869
2806
  procedure: `${parentType}.${key}`,
1870
2807
  root: false,
@@ -1879,31 +2816,31 @@ var FateClient = class {
1879
2816
  }
1880
2817
  target[key] = connection;
1881
2818
  } else target[key] = value.map((item) => {
1882
- const entityId$1 = isNodeRef(item) ? getNodeRefId(item) : null;
1883
- if (!entityId$1) return item;
1884
- ids.add(entityId$1);
1885
- const record$2 = this.store.read(entityId$1);
1886
- const { id: id$1, type } = parseEntityId(entityId$1);
1887
- if (record$2) {
2819
+ const entityId = isNodeRef(item) ? getNodeRefId(item) : null;
2820
+ if (!entityId) return item;
2821
+ ids.add(entityId);
2822
+ const record = this.store.read(entityId);
2823
+ const { id, type } = parseEntityId(entityId);
2824
+ if (record) {
1888
2825
  const node = {
1889
2826
  __typename: type,
1890
- id: id$1
2827
+ id
1891
2828
  };
1892
- walk(nextSelection, record$2, node, entityId$1, fieldPath);
2829
+ walk(nextSelection, record, node, entityId, fieldPath);
1893
2830
  return node;
1894
2831
  }
1895
2832
  return null;
1896
2833
  });
1897
2834
  else if (isNodeRef(value)) {
1898
- const entityId$1 = getNodeRefId(value);
1899
- ids.add(entityId$1);
1900
- const relatedRecord = this.store.read(entityId$1);
1901
- const { id: id$1, type } = parseEntityId(entityId$1);
2835
+ const entityId = getNodeRefId(value);
2836
+ ids.add(entityId);
2837
+ const relatedRecord = this.store.read(entityId);
2838
+ const { id, type } = parseEntityId(entityId);
1902
2839
  const targetRecord = target[key];
1903
- targetRecord.id = id$1;
2840
+ targetRecord.id = id;
1904
2841
  targetRecord.__typename = type;
1905
- if (relatedRecord) walk(nextSelection, relatedRecord, targetRecord, entityId$1, fieldPath);
1906
- } else walk(nextSelection, record$1, target[key], entityId, fieldPath);
2842
+ if (relatedRecord) walk(nextSelection, relatedRecord, targetRecord, entityId, fieldPath);
2843
+ } else walk(nextSelection, value, target[key], entityId, fieldPath);
1907
2844
  }
1908
2845
  }
1909
2846
  };
@@ -1924,11 +2861,39 @@ var FateClient = class {
1924
2861
  pendingKey(entityId, missingFields) {
1925
2862
  return `${this.pendingPrefix(entityId)}${[...missingFields].sort().join("|")}`;
1926
2863
  }
2864
+ liveSubscriptionKey(entityId, plan) {
2865
+ return `${entityId}|${[...plan.paths].sort().join("|")}|${[...plan.args.entries()].map(([path, entry]) => `${path}:${entry.hash}`).sort().join("|")}`;
2866
+ }
2867
+ liveConnectionSubscriptionKey(connection, plan, args) {
2868
+ const paths = [...plan.paths].sort().join("|");
2869
+ const selectionArgs = [...plan.args.entries()].map(([path, entry]) => `${path}:${entry.hash}`).sort().join("|");
2870
+ return `connection|${connection.procedure}|${connection.type}|${hashArgs(args ?? {})}|${paths}|${selectionArgs}`;
2871
+ }
2872
+ releaseLiveSubscription(key) {
2873
+ const subscription = this.liveSubscriptions.get(key);
2874
+ if (!subscription) return;
2875
+ if (subscription.count <= 0) return;
2876
+ subscription.count -= 1;
2877
+ if (subscription.count > 0) return;
2878
+ subscription.releaseScheduled = true;
2879
+ queueMicrotask(() => {
2880
+ const current = this.liveSubscriptions.get(key);
2881
+ if (!current || !current.releaseScheduled || current.count > 0) return;
2882
+ current.unsubscribe();
2883
+ this.liveSubscriptions.delete(key);
2884
+ });
2885
+ }
2886
+ reportLiveError(error) {
2887
+ const onLiveError = this.onLiveError;
2888
+ if (!onLiveError) return;
2889
+ queueMicrotask(() => {
2890
+ onLiveError(error);
2891
+ });
2892
+ }
1927
2893
  };
1928
2894
  function createClient(options) {
1929
2895
  return new FateClient(options);
1930
2896
  }
1931
-
1932
2897
  //#endregion
1933
2898
  //#region src/transport.ts
1934
2899
  /**
@@ -1950,7 +2915,7 @@ function createTRPCTransport({ byId, client, lists, mutations, queries }) {
1950
2915
  if (!lists) throw new Error(`fate(trpc): No list resolvers configured; cannot call "${procedure}".`);
1951
2916
  const resolver = lists[procedure];
1952
2917
  if (!resolver) throw new Error(`fate(trpc): Missing list resolver for procedure "${procedure}"`);
1953
- return resolver(client)({
2918
+ return await resolver(client)({
1954
2919
  args,
1955
2920
  select: [...select]
1956
2921
  });
@@ -1975,7 +2940,449 @@ function createTRPCTransport({ byId, client, lists, mutations, queries }) {
1975
2940
  };
1976
2941
  return transport;
1977
2942
  }
1978
-
2943
+ //#endregion
2944
+ //#region src/httpTransport.ts
2945
+ const reportSubscriptionError = (subscription, error) => {
2946
+ subscription.handlers.onError?.(error);
2947
+ };
2948
+ const notifyLiveData = (handlers, record, select) => {
2949
+ if (select) {
2950
+ handlers.onData(record, select);
2951
+ return;
2952
+ }
2953
+ handlers.onData(record);
2954
+ };
2955
+ const protocolError = (message) => new FateRequestError(message.error.code, message.error.message, { issues: message.error.issues });
2956
+ const pathsIntersect = (left, right) => left === right || left.startsWith(`${right}.`) || right.startsWith(`${left}.`);
2957
+ const filterLiveSelection = (select, changed) => {
2958
+ if (!changed) return null;
2959
+ const changedPaths = changed.filter((path) => path.length > 0);
2960
+ if (changedPaths.length === 0) return [];
2961
+ const selected = select.filter((path) => changedPaths.some((changedPath) => pathsIntersect(path, changedPath)));
2962
+ if (selected.length === 0) return [];
2963
+ const result = new Set(selected);
2964
+ result.add("id");
2965
+ for (const path of select) {
2966
+ if (!path.endsWith(".id")) continue;
2967
+ const parentPath = path.slice(0, -3);
2968
+ if (selected.some((selectedPath) => pathsIntersect(selectedPath, parentPath))) result.add(path);
2969
+ }
2970
+ return [...result];
2971
+ };
2972
+ const hasSelectedDataPath = (value, segments) => {
2973
+ if (segments.length === 0) return true;
2974
+ if (Array.isArray(value)) return value.every((entry) => hasSelectedDataPath(entry, segments));
2975
+ if (!isRecord(value)) return false;
2976
+ const [field, ...rest] = segments;
2977
+ return field in value && hasSelectedDataPath(value[field], rest);
2978
+ };
2979
+ const canUseLivePayloadData = (data, select) => {
2980
+ if (!isRecord(data)) return false;
2981
+ for (const path of select) if (!hasSelectedDataPath(data, path.split("."))) return false;
2982
+ return true;
2983
+ };
2984
+ const defaultFetch = (input, init) => globalThis.fetch(input, init);
2985
+ const resolveHeaders = async (headers) => typeof headers === "function" ? await headers() : headers ?? {};
2986
+ const requestHeaders = async (defaults, headers) => {
2987
+ const result = new Headers(defaults);
2988
+ new Headers(await resolveHeaders(headers)).forEach((value, key) => {
2989
+ result.set(key, value);
2990
+ });
2991
+ return result;
2992
+ };
2993
+ const normalizeEndpoint = (url) => String(url).replace(/\/$/, "");
2994
+ const liveEndpoint = (url) => `${normalizeEndpoint(url)}/live`;
2995
+ const responseError = async (response) => {
2996
+ let message = response.statusText || `HTTP ${response.status}`;
2997
+ try {
2998
+ const payload = await response.clone().json();
2999
+ if (isRecord(payload) && Array.isArray(payload.results) && isRecord(payload.results[0]) && isRecord(payload.results[0].error) && typeof payload.results[0].error.message === "string") message = payload.results[0].error.message;
3000
+ } catch {
3001
+ try {
3002
+ const text = await response.text();
3003
+ if (text) message = text;
3004
+ } catch {}
3005
+ }
3006
+ return new FateRequestError(errorCodeFromStatus(response.status), message, { status: response.status });
3007
+ };
3008
+ const assertProtocolResponse = (value) => {
3009
+ if (!isRecord(value) || value.version !== 1 || !Array.isArray(value.results) || value.results.some((entry) => !isRecord(entry) || typeof entry.id !== "string")) throw new Error("fate(http): Invalid protocol response.");
3010
+ return value;
3011
+ };
3012
+ const resultValue = (result) => {
3013
+ if (result.ok) return result.data;
3014
+ throw new FateRequestError(result.error.code, result.error.message, { issues: result.error.issues });
3015
+ };
3016
+ function createHTTPTransport({ eventSource, fetch: fetchImpl = defaultFetch, headers, live = true, liveRetryMs = 1e3, liveUrl, url }) {
3017
+ const endpoint = normalizeEndpoint(url);
3018
+ const liveEndpointUrl = liveUrl ? String(liveUrl) : liveEndpoint(endpoint);
3019
+ const liveConnector = typeof live === "function" ? live : void 0;
3020
+ const liveEnabled = live !== false;
3021
+ let nextId = 0;
3022
+ let pending = [];
3023
+ let scheduled = false;
3024
+ const enqueue = (operation) => new Promise((resolve, reject) => {
3025
+ pending.push({
3026
+ operation: {
3027
+ ...operation,
3028
+ id: String(++nextId)
3029
+ },
3030
+ reject,
3031
+ resolve
3032
+ });
3033
+ if (!scheduled) {
3034
+ scheduled = true;
3035
+ queueMicrotask(flush);
3036
+ }
3037
+ });
3038
+ const flush = async () => {
3039
+ scheduled = false;
3040
+ const batch = pending;
3041
+ pending = [];
3042
+ if (!batch.length) return;
3043
+ try {
3044
+ const response = await fetchImpl(endpoint, {
3045
+ body: JSON.stringify({
3046
+ operations: batch.map((entry) => entry.operation),
3047
+ version: 1
3048
+ }),
3049
+ headers: await requestHeaders({ "content-type": "application/json" }, headers),
3050
+ method: "POST"
3051
+ });
3052
+ if (!response.ok) throw await responseError(response);
3053
+ const payload = assertProtocolResponse(await response.json());
3054
+ const results = new Map(payload.results.map((result) => [result.id, result]));
3055
+ for (const entry of batch) {
3056
+ const result = results.get(entry.operation.id);
3057
+ if (!result) {
3058
+ entry.reject(/* @__PURE__ */ new Error(`fate(http): Missing result for operation '${entry.operation.id}'.`));
3059
+ continue;
3060
+ }
3061
+ try {
3062
+ entry.resolve(resultValue(result));
3063
+ } catch (error) {
3064
+ entry.reject(error);
3065
+ }
3066
+ }
3067
+ } catch (error) {
3068
+ for (const entry of batch) entry.reject(error);
3069
+ }
3070
+ };
3071
+ const transport = {
3072
+ fetchById(type, ids, select, args) {
3073
+ return enqueue({
3074
+ args,
3075
+ ids,
3076
+ kind: "byId",
3077
+ select: [...select],
3078
+ type
3079
+ });
3080
+ },
3081
+ fetchList(name, select, args) {
3082
+ return enqueue({
3083
+ args,
3084
+ kind: "list",
3085
+ name,
3086
+ select: [...select]
3087
+ });
3088
+ },
3089
+ fetchQuery(name, select, args) {
3090
+ return enqueue({
3091
+ args,
3092
+ kind: "query",
3093
+ name,
3094
+ select: [...select]
3095
+ });
3096
+ },
3097
+ mutate(name, input, select) {
3098
+ return enqueue({
3099
+ input,
3100
+ kind: "mutation",
3101
+ name,
3102
+ select: [...select]
3103
+ });
3104
+ }
3105
+ };
3106
+ if (liveEnabled) {
3107
+ const liveSubscriptions = /* @__PURE__ */ new Map();
3108
+ let liveClient;
3109
+ let nativeLiveClient;
3110
+ let liveNextId = 0;
3111
+ const getLiveClient = () => {
3112
+ if (!liveConnector) throw new Error("fate(http): A live connector is required for topic subscriptions.");
3113
+ return liveClient ??= liveConnector(liveEndpointUrl, {
3114
+ eventSource,
3115
+ fetch: fetchImpl,
3116
+ headers,
3117
+ onError(error) {
3118
+ for (const subscription of new Set(liveSubscriptions.values())) subscription.handlers.onError?.(error);
3119
+ },
3120
+ retryDelay: liveRetryMs,
3121
+ withCredentials: true
3122
+ });
3123
+ };
3124
+ const getNativeLiveClient = () => {
3125
+ if (nativeLiveClient) return nativeLiveClient;
3126
+ const EventSourceCtor = eventSource ?? globalThis.EventSource;
3127
+ if (!EventSourceCtor) throw new Error("fate(http): EventSource is required for live views.");
3128
+ const connectionId = globalThis.crypto?.randomUUID?.() ?? `${Date.now().toString(36)}-${Math.random().toString(36).slice(2)}`;
3129
+ const operations = /* @__PURE__ */ new Map();
3130
+ const lastEventIds = /* @__PURE__ */ new Map();
3131
+ let opened = false;
3132
+ let resolveOpen;
3133
+ let rejectOpen;
3134
+ const open = new Promise((resolve, reject) => {
3135
+ resolveOpen = resolve;
3136
+ rejectOpen = reject;
3137
+ });
3138
+ const sourceUrl = new URL(liveEndpointUrl, typeof globalThis.location === "object" ? globalThis.location.href : "http://local");
3139
+ sourceUrl.searchParams.set("connectionId", connectionId);
3140
+ const source = new EventSourceCtor(sourceUrl.href, { withCredentials: true });
3141
+ const control = async (controlOperations) => {
3142
+ const response = await fetchImpl(liveEndpointUrl, {
3143
+ body: JSON.stringify({
3144
+ connectionId,
3145
+ operations: controlOperations,
3146
+ version: 1
3147
+ }),
3148
+ headers: await requestHeaders({ "content-type": "application/json" }, headers),
3149
+ method: "POST"
3150
+ });
3151
+ if (!response.ok) throw await responseError(response);
3152
+ const payload = assertProtocolResponse(await response.json());
3153
+ for (const result of payload.results) resultValue(result);
3154
+ };
3155
+ const withLastEventId = (operation) => {
3156
+ const lastEventId = lastEventIds.get(operation.id);
3157
+ return lastEventId ? {
3158
+ ...operation,
3159
+ lastEventId
3160
+ } : operation;
3161
+ };
3162
+ const reportError = (error) => {
3163
+ for (const subscription of new Set(liveSubscriptions.values())) reportSubscriptionError(subscription, error);
3164
+ };
3165
+ source.addEventListener("open", () => {
3166
+ if (!opened) {
3167
+ opened = true;
3168
+ resolveOpen?.();
3169
+ return;
3170
+ }
3171
+ const resubscribe = [...operations.values()].map(withLastEventId);
3172
+ if (resubscribe.length > 0) control(resubscribe).catch(reportError);
3173
+ });
3174
+ const handleLiveMessage = (event) => {
3175
+ const message = JSON.parse(event.data);
3176
+ const subscription = liveSubscriptions.get(message.id);
3177
+ if (!subscription) return;
3178
+ const lastEventId = event.lastEventId;
3179
+ if (lastEventId) lastEventIds.set(message.id, lastEventId);
3180
+ if (message.kind === "error") {
3181
+ reportSubscriptionError(subscription, protocolError(message));
3182
+ return;
3183
+ }
3184
+ if (message.kind === "connection") {
3185
+ if ("onEvent" in subscription.handlers) subscription.handlers.onEvent(message.event);
3186
+ return;
3187
+ }
3188
+ if ("delete" in message.event && message.event.delete) {
3189
+ if ("onDelete" in subscription.handlers) subscription.handlers.onDelete?.(message.event.id ?? subscription.targetId);
3190
+ return;
3191
+ }
3192
+ if ("onData" in subscription.handlers) notifyLiveData(subscription.handlers, message.event.data, message.event.select);
3193
+ };
3194
+ source.addEventListener("error", (event) => {
3195
+ if ("data" in event) {
3196
+ handleLiveMessage(event);
3197
+ return;
3198
+ }
3199
+ if (!opened) rejectOpen?.(event);
3200
+ reportError(event);
3201
+ });
3202
+ source.addEventListener("message", handleLiveMessage);
3203
+ source.addEventListener("next", handleLiveMessage);
3204
+ source.addEventListener("connection", handleLiveMessage);
3205
+ nativeLiveClient = {
3206
+ add(operation) {
3207
+ operations.set(operation.id, operation);
3208
+ open.then(() => control([withLastEventId(operation)])).catch((error) => {
3209
+ operations.delete(operation.id);
3210
+ liveSubscriptions.delete(operation.id);
3211
+ reportError(error);
3212
+ });
3213
+ },
3214
+ remove(id) {
3215
+ operations.delete(id);
3216
+ lastEventIds.delete(id);
3217
+ open.then(() => control([{
3218
+ id,
3219
+ kind: "unsubscribe"
3220
+ }])).catch(reportError);
3221
+ if (operations.size === 0) {
3222
+ source.close();
3223
+ nativeLiveClient = void 0;
3224
+ }
3225
+ }
3226
+ };
3227
+ return nativeLiveClient;
3228
+ };
3229
+ const fetchLiveRecord = async (subscription, select = subscription.select) => {
3230
+ const [record] = await transport.fetchById(subscription.type, [subscription.targetId], select, subscription.args);
3231
+ return record;
3232
+ };
3233
+ const resolveConnectionNode = async (subscription, id) => {
3234
+ const [record] = await transport.fetchById(subscription.type, [id], subscription.select, subscription.selectionArgs);
3235
+ return record;
3236
+ };
3237
+ transport.subscribeById = (type, id, select, args, handlers) => {
3238
+ const liveId = String(++liveNextId);
3239
+ const subscription = {
3240
+ args,
3241
+ handlers,
3242
+ select: [...select],
3243
+ targetId: id,
3244
+ type
3245
+ };
3246
+ liveSubscriptions.set(liveId, subscription);
3247
+ if (!liveConnector) {
3248
+ getNativeLiveClient().add({
3249
+ args,
3250
+ entityId: id,
3251
+ id: liveId,
3252
+ kind: "subscribe",
3253
+ select: [...select],
3254
+ type
3255
+ });
3256
+ return () => {
3257
+ if (!liveSubscriptions.delete(liveId)) return;
3258
+ getNativeLiveClient().remove(liveId);
3259
+ };
3260
+ }
3261
+ const unsubscribePromise = getLiveClient().subscribe({
3262
+ id: liveId,
3263
+ onEvent(event) {
3264
+ if (event.type === "delete") {
3265
+ handlers.onDelete?.(event.data.id ?? id);
3266
+ return;
3267
+ }
3268
+ const liveSelect = (Array.isArray(event.data.select) ? event.data.select : null) ?? filterLiveSelection(subscription.select, event.data.changed);
3269
+ if (liveSelect?.length === 0) return;
3270
+ if ("data" in event.data && event.data.data !== void 0) {
3271
+ if (!liveSelect || canUseLivePayloadData(event.data.data, liveSelect)) {
3272
+ notifyLiveData(handlers, event.data.data, liveSelect ?? void 0);
3273
+ return;
3274
+ }
3275
+ }
3276
+ fetchLiveRecord(subscription, liveSelect ?? subscription.select).then((record) => {
3277
+ if (record == null) handlers.onDelete?.(id);
3278
+ else notifyLiveData(handlers, record, liveSelect ?? void 0);
3279
+ }).catch((error) => reportSubscriptionError(subscription, error));
3280
+ },
3281
+ topic: liveEntityTopic(type, id)
3282
+ }).catch((error) => {
3283
+ liveSubscriptions.delete(liveId);
3284
+ reportSubscriptionError(subscription, error);
3285
+ return null;
3286
+ });
3287
+ return () => {
3288
+ if (!liveSubscriptions.delete(liveId)) return;
3289
+ unsubscribePromise.then((unsubscribe) => unsubscribe?.());
3290
+ };
3291
+ };
3292
+ transport.subscribeConnection = (procedure, type, args, select, selectionArgs, handlers) => {
3293
+ const liveId = String(++liveNextId);
3294
+ const globalLiveId = `${liveId}:global`;
3295
+ const subscription = {
3296
+ args,
3297
+ handlers,
3298
+ procedure,
3299
+ select: [...select],
3300
+ selectionArgs,
3301
+ type
3302
+ };
3303
+ const handleEvent = (event) => {
3304
+ const data = isRecord(event.data) ? event.data : {};
3305
+ if (event.type === "invalidate") {
3306
+ handlers.onEvent({ type: "invalidate" });
3307
+ return;
3308
+ }
3309
+ const nodeType = typeof data.nodeType === "string" ? data.nodeType : type;
3310
+ const id = data.id;
3311
+ if (event.type === "deleteEdge") {
3312
+ if (typeof id === "string" || typeof id === "number") handlers.onEvent({
3313
+ id,
3314
+ nodeType,
3315
+ type: "deleteEdge"
3316
+ });
3317
+ else reportSubscriptionError(subscription, /* @__PURE__ */ new Error(`fate(http): Live connection delete event for '${procedure}' missed an id.`));
3318
+ return;
3319
+ }
3320
+ if (event.type !== "appendEdge" && event.type !== "appendNode" && event.type !== "insertEdgeAfter" && event.type !== "insertEdgeBefore" && event.type !== "prependEdge" && event.type !== "prependNode") return;
3321
+ const eventType = event.type;
3322
+ const sendNode = (node) => {
3323
+ handlers.onEvent({
3324
+ edge: {
3325
+ cursor: typeof data.cursor === "string" ? data.cursor : void 0,
3326
+ node
3327
+ },
3328
+ nodeType,
3329
+ targetCursor: typeof data.targetCursor === "string" ? data.targetCursor : void 0,
3330
+ type: eventType
3331
+ });
3332
+ };
3333
+ if ("node" in data && data.node !== void 0) {
3334
+ sendNode(data.node);
3335
+ return;
3336
+ }
3337
+ if (typeof id !== "string" && typeof id !== "number") {
3338
+ reportSubscriptionError(subscription, /* @__PURE__ */ new Error(`fate(http): Live connection event for '${procedure}' missed a node id.`));
3339
+ return;
3340
+ }
3341
+ resolveConnectionNode(subscription, id).then(sendNode).catch((error) => reportSubscriptionError(subscription, error));
3342
+ };
3343
+ liveSubscriptions.set(liveId, subscription);
3344
+ if (!liveConnector) {
3345
+ getNativeLiveClient().add({
3346
+ args,
3347
+ id: liveId,
3348
+ kind: "subscribeConnection",
3349
+ procedure,
3350
+ select: [...select],
3351
+ selectionArgs,
3352
+ type
3353
+ });
3354
+ return () => {
3355
+ if (!liveSubscriptions.delete(liveId)) return;
3356
+ getNativeLiveClient().remove(liveId);
3357
+ };
3358
+ }
3359
+ liveSubscriptions.set(globalLiveId, subscription);
3360
+ const client = getLiveClient();
3361
+ const unsubscribeSpecific = client.subscribe({
3362
+ id: liveId,
3363
+ onEvent: handleEvent,
3364
+ topic: liveConnectionTopic(procedure, args)
3365
+ });
3366
+ const unsubscribeGlobal = client.subscribe({
3367
+ id: globalLiveId,
3368
+ onEvent: handleEvent,
3369
+ topic: liveGlobalConnectionTopic(procedure)
3370
+ });
3371
+ const unsubscribePromise = Promise.all([unsubscribeSpecific, unsubscribeGlobal]).catch((error) => {
3372
+ liveSubscriptions.delete(liveId);
3373
+ liveSubscriptions.delete(globalLiveId);
3374
+ reportSubscriptionError(subscription, error);
3375
+ return [];
3376
+ });
3377
+ return () => {
3378
+ if (!(liveSubscriptions.delete(liveId) || liveSubscriptions.delete(globalLiveId))) return;
3379
+ liveSubscriptions.delete(globalLiveId);
3380
+ unsubscribePromise.then((unsubscribers) => Promise.all(unsubscribers.map((unsubscribe) => unsubscribe())));
3381
+ };
3382
+ };
3383
+ }
3384
+ return transport;
3385
+ }
1979
3386
  //#endregion
1980
3387
  //#region src/root.ts
1981
3388
  /**
@@ -1987,6 +3394,5 @@ function clientRoot(type) {
1987
3394
  type
1988
3395
  });
1989
3396
  }
1990
-
1991
3397
  //#endregion
1992
- export { ConnectionTag, FateClient, clientRoot, createClient, createTRPCTransport, getSelectionPlan, isViewTag, mutation, toEntityId, view };
3398
+ export { ConnectionTag, FateClient, clientRoot, createClient, createHTTPTransport, createTRPCTransport, getListEntries, getSelectionPlan, isRecord, isViewTag, liveConnectionTopic, liveEntityTopic, liveGlobalConnectionTopic, mutation, toEntityId, view };