@nkzw/fate 1.3.3 → 1.4.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 (169) hide show
  1. package/docs/api/classes/FateClient.md +40 -40
  2. package/docs/api/functions/clientRoot.md +1 -1
  3. package/docs/api/functions/createClient.md +1 -1
  4. package/docs/api/functions/createGraphQLTransport.md +1 -1
  5. package/docs/api/functions/createHTTPTransport.md +1 -1
  6. package/docs/api/functions/createTRPCTransport.md +1 -1
  7. package/docs/api/functions/defer.md +1 -1
  8. package/docs/api/functions/getDeferredMetadata.md +1 -1
  9. package/docs/api/functions/getListEntries.md +1 -1
  10. package/docs/api/functions/getSelectionPlan.md +1 -1
  11. package/docs/api/functions/graphqlMutation.md +1 -1
  12. package/docs/api/functions/isDeferred.md +1 -1
  13. package/docs/api/functions/isRecord.md +1 -1
  14. package/docs/api/functions/isViewTag.md +1 -1
  15. package/docs/api/functions/liveConnectionTopic.md +1 -1
  16. package/docs/api/functions/liveEntityTopic.md +1 -1
  17. package/docs/api/functions/liveGlobalConnectionTopic.md +1 -1
  18. package/docs/api/functions/mutation.md +1 -1
  19. package/docs/api/functions/toEntityId.md +1 -1
  20. package/docs/api/functions/view.md +1 -1
  21. package/docs/api/interfaces/FateThenable.md +3 -3
  22. package/docs/api/interfaces/Transport.md +7 -7
  23. package/docs/api/server/classes/FateRequestError.md +5 -5
  24. package/docs/api/server/drizzle/functions/createDrizzleFate.md +1 -1
  25. package/docs/api/server/drizzle/functions/createDrizzleSourceAdapter.md +1 -1
  26. package/docs/api/server/drizzle/functions/createDrizzleSourceRegistry.md +1 -1
  27. package/docs/api/server/drizzle/type-aliases/DrizzleManyToManyConfig.md +1 -1
  28. package/docs/api/server/drizzle/type-aliases/DrizzleManyToManyInput.md +1 -1
  29. package/docs/api/server/drizzle/type-aliases/DrizzleQueryExtra.md +3 -3
  30. package/docs/api/server/drizzle/type-aliases/DrizzleSourceAdapter.md +9 -9
  31. package/docs/api/server/drizzle/type-aliases/DrizzleViewConfig.md +1 -1
  32. package/docs/api/server/functions/bindSourceProcedures.md +1 -1
  33. package/docs/api/server/functions/computed.md +1 -1
  34. package/docs/api/server/functions/count.md +1 -1
  35. package/docs/api/server/functions/createFateFetchHandler.md +1 -1
  36. package/docs/api/server/functions/createFateServer.md +1 -1
  37. package/docs/api/server/functions/createHonoFateHandler.md +1 -1
  38. package/docs/api/server/functions/createLiveEventBus.md +1 -1
  39. package/docs/api/server/functions/createNestedSourcePlan.md +1 -1
  40. package/docs/api/server/functions/createResolver.md +1 -1
  41. package/docs/api/server/functions/createSourcePlan.md +1 -1
  42. package/docs/api/server/functions/createSourceProcedures.md +1 -1
  43. package/docs/api/server/functions/dataView.md +1 -1
  44. package/docs/api/server/functions/field.md +1 -1
  45. package/docs/api/server/functions/getNestedSelection.md +1 -1
  46. package/docs/api/server/functions/getScopedArgs.md +1 -1
  47. package/docs/api/server/functions/hasNestedSelection.md +1 -1
  48. package/docs/api/server/functions/list.md +1 -1
  49. package/docs/api/server/functions/refetchSourceById.md +1 -1
  50. package/docs/api/server/functions/resolveSourceById.md +1 -1
  51. package/docs/api/server/functions/resolveSourceByIds.md +1 -1
  52. package/docs/api/server/functions/resolveSourceConnection.md +1 -1
  53. package/docs/api/server/functions/resolver.md +1 -1
  54. package/docs/api/server/functions/toPrismaSelect.md +1 -1
  55. package/docs/api/server/functions/withConnection.md +1 -1
  56. package/docs/api/server/prisma/functions/createPrismaFate.md +1 -1
  57. package/docs/api/server/prisma/functions/createPrismaSourceAdapter.md +1 -1
  58. package/docs/api/server/prisma/functions/createPrismaSourceRegistry.md +1 -1
  59. package/docs/api/server/prisma/functions/prismaConnectionArgs.md +1 -1
  60. package/docs/api/server/prisma/type-aliases/PrismaDelegate.md +4 -4
  61. package/docs/api/server/prisma/type-aliases/PrismaQueryExtra.md +1 -1
  62. package/docs/api/server/prisma/type-aliases/PrismaSourceAdapter.md +9 -9
  63. package/docs/api/server/prisma/type-aliases/PrismaViewConfig.md +1 -1
  64. package/docs/api/server/type-aliases/ComputedField.md +5 -5
  65. package/docs/api/server/type-aliases/ComputedSelection.md +1 -1
  66. package/docs/api/server/type-aliases/ConnectionItem.md +3 -3
  67. package/docs/api/server/type-aliases/ConnectionPagination.md +5 -5
  68. package/docs/api/server/type-aliases/ConnectionResult.md +3 -3
  69. package/docs/api/server/type-aliases/CountSelection.md +4 -4
  70. package/docs/api/server/type-aliases/CountWhere.md +1 -1
  71. package/docs/api/server/type-aliases/DataViewListOptions.md +2 -2
  72. package/docs/api/server/type-aliases/DataViewOrderBy.md +1 -1
  73. package/docs/api/server/type-aliases/DataViewOrderDirection.md +1 -1
  74. package/docs/api/server/type-aliases/DataViewResult.md +1 -1
  75. package/docs/api/server/type-aliases/Entity.md +1 -1
  76. package/docs/api/server/type-aliases/FateServer.md +5 -5
  77. package/docs/api/server/type-aliases/FateServerManifest.md +1 -1
  78. package/docs/api/server/type-aliases/FieldSelection.md +3 -3
  79. package/docs/api/server/type-aliases/LiveConnectionEventType.md +1 -1
  80. package/docs/api/server/type-aliases/LiveConnectionSourceEvent.md +1 -1
  81. package/docs/api/server/type-aliases/LiveEventBus.md +1 -1
  82. package/docs/api/server/type-aliases/LiveEventType.md +1 -1
  83. package/docs/api/server/type-aliases/LiveSourceEvent.md +1 -1
  84. package/docs/api/server/type-aliases/NativeFateAPI.md +4 -4
  85. package/docs/api/server/type-aliases/OrderDirection.md +1 -1
  86. package/docs/api/server/type-aliases/SourceConfig.md +6 -6
  87. package/docs/api/server/type-aliases/SourceDefinition.md +6 -6
  88. package/docs/api/server/type-aliases/SourceOrder.md +1 -1
  89. package/docs/api/server/type-aliases/SourceOrderField.md +3 -3
  90. package/docs/api/server/type-aliases/SourcePlan.md +1 -1
  91. package/docs/api/server/type-aliases/SourcePlanNode.md +1 -1
  92. package/docs/api/server/type-aliases/SourceRegistry.md +1 -1
  93. package/docs/api/server/type-aliases/SourceRelation.md +1 -1
  94. package/docs/api/server/type-aliases/SourceRelationConfig.md +1 -1
  95. package/docs/api/server/variables/byIdInput.md +1 -1
  96. package/docs/api/server/variables/connectionArgs.md +1 -1
  97. package/docs/api/type-aliases/ConnectionMetadata.md +1 -1
  98. package/docs/api/type-aliases/ConnectionRef.md +1 -1
  99. package/docs/api/type-aliases/Deferred.md +1 -1
  100. package/docs/api/type-aliases/DeferredMetadata.md +1 -1
  101. package/docs/api/type-aliases/DeferredSelection.md +1 -1
  102. package/docs/api/type-aliases/DeferredSnapshot.md +1 -1
  103. package/docs/api/type-aliases/Entity.md +2 -2
  104. package/docs/api/type-aliases/EntityId.md +1 -1
  105. package/docs/api/type-aliases/FateDehydratedState.md +1 -1
  106. package/docs/api/type-aliases/FateLiveConnectionEvent.md +1 -1
  107. package/docs/api/type-aliases/FateLiveEvent.md +1 -1
  108. package/docs/api/type-aliases/FateMutations.md +1 -1
  109. package/docs/api/type-aliases/FateOperation.md +1 -1
  110. package/docs/api/type-aliases/FateProtocolRequest.md +1 -1
  111. package/docs/api/type-aliases/FateProtocolResponse.md +1 -1
  112. package/docs/api/type-aliases/FateRecord.md +1 -1
  113. package/docs/api/type-aliases/FateRoots.md +1 -1
  114. package/docs/api/type-aliases/GraphQLMutationDefinition.md +1 -1
  115. package/docs/api/type-aliases/GraphQLMutationInput.md +1 -1
  116. package/docs/api/type-aliases/GraphQLMutationMap.md +1 -1
  117. package/docs/api/type-aliases/GraphQLMutationOutput.md +1 -1
  118. package/docs/api/type-aliases/GraphQLTransportOptions.md +11 -11
  119. package/docs/api/type-aliases/HydrateOptions.md +1 -1
  120. package/docs/api/type-aliases/HydrationLimits.md +1 -1
  121. package/docs/api/type-aliases/InferFateAPI.md +1 -1
  122. package/docs/api/type-aliases/List.md +1 -1
  123. package/docs/api/type-aliases/ListEntry.md +1 -1
  124. package/docs/api/type-aliases/ListItem.md +1 -1
  125. package/docs/api/type-aliases/Mask.md +1 -1
  126. package/docs/api/type-aliases/MutationDefinition.md +1 -1
  127. package/docs/api/type-aliases/MutationEntity.md +1 -1
  128. package/docs/api/type-aliases/MutationIdentifier.md +1 -1
  129. package/docs/api/type-aliases/MutationInput.md +1 -1
  130. package/docs/api/type-aliases/MutationResult.md +1 -1
  131. package/docs/api/type-aliases/NodesItem.md +1 -1
  132. package/docs/api/type-aliases/Pagination.md +5 -5
  133. package/docs/api/type-aliases/Request.md +1 -1
  134. package/docs/api/type-aliases/RequestMode.md +1 -1
  135. package/docs/api/type-aliases/RequestOptions.md +1 -1
  136. package/docs/api/type-aliases/RequestResult.md +1 -1
  137. package/docs/api/type-aliases/Selection.md +1 -1
  138. package/docs/api/type-aliases/Snapshot.md +1 -1
  139. package/docs/api/type-aliases/TypeConfig.md +4 -4
  140. package/docs/api/type-aliases/View.md +1 -1
  141. package/docs/api/type-aliases/ViewData.md +1 -1
  142. package/docs/api/type-aliases/ViewEntity.md +1 -1
  143. package/docs/api/type-aliases/ViewEntityName.md +1 -1
  144. package/docs/api/type-aliases/ViewRef.md +1 -1
  145. package/docs/api/type-aliases/ViewSelection.md +1 -1
  146. package/docs/api/type-aliases/ViewSnapshot.md +1 -1
  147. package/docs/api/type-aliases/ViewTag.md +1 -1
  148. package/docs/api/variables/ConnectionTag.md +1 -1
  149. package/docs/api/variables/DeferTag.md +1 -1
  150. package/docs/api/variables/DeferredTag.md +1 -1
  151. package/lib/cli.d.mts +1 -1
  152. package/lib/{executor-C8HNogYG.d.mts → executor-BRudKWDY.d.mts} +18 -65
  153. package/lib/{graphqlTransport-DskuXeUP.d.mts → graphqlTransport-BDR1pnUQ.d.mts} +3 -14
  154. package/lib/graphqlTransport.d.mts +1 -1
  155. package/lib/index.d.mts +6 -15
  156. package/lib/index.mjs +711 -623
  157. package/lib/{list-sI2UPhHc.d.mts → list-Bb1SeABn.d.mts} +1 -2
  158. package/lib/list.d.mts +1 -1
  159. package/lib/{record-CZyyeLa3.d.mts → record-BTUXkHQU.d.mts} +9 -18
  160. package/lib/server/drizzle.d.mts +45 -114
  161. package/lib/server/drizzle.mjs +1 -1
  162. package/lib/server/prisma.d.mts +52 -127
  163. package/lib/server/prisma.mjs +8 -2
  164. package/lib/server.d.mts +4 -16
  165. package/lib/server.mjs +2 -28
  166. package/lib/{transport-DhaVVq4P.d.mts → transport-Bo1Jgo9R.d.mts} +76 -103
  167. package/lib/vite.d.mts +0 -1
  168. package/lib/vite.mjs +1 -1
  169. package/package.json +5 -5
package/lib/index.mjs CHANGED
@@ -4,7 +4,8 @@ import { t as getListEntries } from "./list-_6fXYLtJ.mjs";
4
4
  import { n as graphqlMutation, t as createGraphQLTransport } from "./graphqlTransport-DSBVS2Fq.mjs";
5
5
  //#region src/cache.ts
6
6
  var ViewDataCache = class {
7
- constructor() {
7
+ constructor(isEnabled = () => true) {
8
+ this.isEnabled = isEnabled;
8
9
  this.cache = /* @__PURE__ */ new Map();
9
10
  this.rootDependencies = /* @__PURE__ */ new Map();
10
11
  this.dependencyIndex = /* @__PURE__ */ new Map();
@@ -15,9 +16,11 @@ var ViewDataCache = class {
15
16
  this.dependencyIndex.clear();
16
17
  }
17
18
  get(entityId, view, ref) {
19
+ if (!this.isEnabled()) return null;
18
20
  return this.cache.get(entityId)?.get(view)?.get(ref) ?? null;
19
21
  }
20
22
  set(entityId, view, ref, thenable, dependencies) {
23
+ if (!this.isEnabled()) return;
21
24
  let entityMap = this.cache.get(entityId);
22
25
  if (!entityMap) {
23
26
  entityMap = /* @__PURE__ */ new WeakMap();
@@ -45,6 +48,7 @@ var ViewDataCache = class {
45
48
  }
46
49
  }
47
50
  invalidate(entityId) {
51
+ if (!this.isEnabled()) return;
48
52
  this.invalidateDependents(entityId, /* @__PURE__ */ new Set());
49
53
  }
50
54
  invalidateDependents(entityId, visited) {
@@ -351,94 +355,7 @@ const decodeClientHydrationState = (value, limits) => {
351
355
  return decoded;
352
356
  };
353
357
  //#endregion
354
- //#region src/mask.ts
355
- function emptyMask() {
356
- return {
357
- all: false,
358
- children: /* @__PURE__ */ new Map()
359
- };
360
- }
361
- function cloneMask(m) {
362
- const clone = {
363
- all: m.all,
364
- children: /* @__PURE__ */ new Map()
365
- };
366
- for (const [key, value] of m.children) clone.children.set(key, cloneMask(value));
367
- return clone;
368
- }
369
- function addPath(mask, path) {
370
- if (mask.all) return;
371
- const parts = path.split(".");
372
- let current = mask;
373
- for (let i = 0; i < parts.length; i++) {
374
- const seg = parts[i];
375
- let child = current.children.get(seg);
376
- if (!child) {
377
- child = emptyMask();
378
- current.children.set(seg, child);
379
- }
380
- current = child;
381
- }
382
- current.all = true;
383
- current.children.clear();
384
- }
385
- function union(into, b) {
386
- if (into.all || b.all) {
387
- into.all = true;
388
- into.children.clear();
389
- return;
390
- }
391
- for (const [key, child] of b.children) {
392
- const exist = into.children.get(key);
393
- if (!exist) into.children.set(key, cloneMask(child));
394
- else union(exist, child);
395
- }
396
- }
397
- function fromPaths(paths) {
398
- const mask = emptyMask();
399
- for (const path of paths) addPath(mask, path);
400
- return mask;
401
- }
402
- function toPaths(mask) {
403
- const paths = [];
404
- const visit = (current, prefix) => {
405
- if (current.all) {
406
- if (prefix) paths.push(prefix);
407
- return;
408
- }
409
- for (const [key, child] of current.children) visit(child, prefix ? `${prefix}.${key}` : key);
410
- };
411
- visit(mask, null);
412
- return paths;
413
- }
414
- function isCovered(mask, path) {
415
- if (mask.all) return true;
416
- const parts = path.split(".");
417
- let current = mask;
418
- for (let i = 0; i < parts.length; i++) {
419
- if (!current) return false;
420
- if (current.all) return true;
421
- current = current.children.get(parts[i]);
422
- }
423
- return !!current && (current.all || current.children.size === 0);
424
- }
425
- function diffPaths(paths, mask) {
426
- const missing = /* @__PURE__ */ new Set();
427
- for (const path of paths) if (!isCovered(mask, path)) missing.add(path);
428
- return missing;
429
- }
430
- function intersects(a, b) {
431
- if (a.all || b.all) return true;
432
- for (const [key, child] of a.children) {
433
- const otherChild = b.children.get(key);
434
- if (!otherChild) continue;
435
- if (child.all || otherChild.all) return true;
436
- if (intersects(child, otherChild)) return true;
437
- }
438
- return false;
439
- }
440
- //#endregion
441
- //#region ../../node_modules/.pnpm/@trpc+server@11.18.0_typescript@6.0.3/node_modules/@trpc/server/dist/getErrorShape-BPSzUA7W.mjs
358
+ //#region ../../node_modules/.pnpm/@trpc+server@11.18.0_typescript@7.0.2/node_modules/@trpc/server/dist/getErrorShape-BPSzUA7W.mjs
442
359
  var __create = Object.create;
443
360
  var __defProp = Object.defineProperty;
444
361
  var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
@@ -828,38 +745,37 @@ function wrapMutation(client, identifier) {
828
745
  } : optimistic : void 0;
829
746
  const optimisticRecordId = optimisticRecord !== void 0 ? maybeGetId(config.getId, optimisticRecord) : null;
830
747
  const optimisticEntityId = id != null ? toEntityId(identifier.entity, id) : optimisticRecordId != null ? toEntityId(identifier.entity, optimisticRecordId) : null;
831
- const snapshots = /* @__PURE__ */ new Map();
832
- const listSnapshots = deleteRecord || optimisticRecord ? /* @__PURE__ */ new Map() : void 0;
833
748
  const optimisticSelection = optimisticRecord ? collectImplicitSelectedPaths(optimisticRecord) : void 0;
834
749
  const selection = viewSelection || optimisticSelection ? /* @__PURE__ */ new Set([...viewSelection ? [...viewSelection] : [], ...optimisticSelection ? [...optimisticSelection] : []]) : /* @__PURE__ */ new Set();
835
750
  if (deleteRecord && id == null) throw new Error(`fate: Mutation '${identifier.key}' requires an 'id' to delete.`);
836
- const optimisticToken = optimisticEntityId ? client.registerOptimisticUpdate(optimisticEntityId, optimisticSelection ?? emptySet$1) : null;
837
- if (optimisticRecord && optimisticEntityId) client.write(identifier.entity, optimisticRecord, optimisticSelection ?? emptySet$1, snapshots, plan, null, null, insert, listSnapshots);
838
- if (deleteRecord && id != null) client.deleteRecord(identifier.entity, id, snapshots, listSnapshots);
751
+ const applyOptimistic = () => {
752
+ if (optimisticRecord && optimisticEntityId) client.write(identifier.entity, optimisticRecord, optimisticSelection ?? emptySet$1, plan, insert);
753
+ if (deleteRecord && id != null) client.deleteRecord(identifier.entity, id);
754
+ };
755
+ const settle = client.store.optimisticUpdate(applyOptimistic);
839
756
  const performMutation = async () => {
840
757
  try {
841
758
  const result = await client.executeMutation(identifier.key, input, selection, {
842
759
  args,
843
760
  plan
844
761
  });
845
- if (result && typeof result === "object" && (!deleteRecord || Boolean(view))) {
846
- const select = collectImplicitSelectedPaths(result);
847
- const pendingMask = optimisticEntityId ? client.getPendingOptimisticMask(optimisticEntityId, { excludeToken: optimisticToken }) : null;
848
- const filteredSelection = optimisticEntityId ? client.filterSelectionForPendingOptimistics(optimisticEntityId, select, { excludeToken: optimisticToken }) : select;
849
- client.write(identifier.entity, result, filteredSelection, void 0, plan, null, pendingMask, insert);
762
+ const shouldWriteResult = result && typeof result === "object" && (!deleteRecord || Boolean(view));
763
+ settle(() => {
764
+ if (shouldWriteResult) {
765
+ const select = collectImplicitSelectedPaths(result);
766
+ client.write(identifier.entity, result, select, plan, insert);
767
+ const resultId = maybeGetId(config.getId, result);
768
+ if (optimisticEntityId && resultId != null) client.resolveOptimisticEntity(optimisticEntityId, toEntityId(identifier.entity, resultId));
769
+ if (optimisticRecordId != null && resultId != null && optimisticRecordId !== resultId) client.deleteRecord(identifier.entity, optimisticRecordId);
770
+ }
850
771
  if (deleteRecord && id != null) client.deleteRecord(identifier.entity, id);
851
- const resultId = maybeGetId(config.getId, result);
852
- if (optimisticEntityId && resultId != null) client.resolveOptimisticEntity(optimisticEntityId, toEntityId(identifier.entity, resultId));
853
- if (optimisticRecordId != null && resultId != null && optimisticRecordId !== resultId) client.deleteRecord(identifier.entity, optimisticRecordId);
854
- }
772
+ });
855
773
  return {
856
774
  error: void 0,
857
775
  result
858
776
  };
859
777
  } catch (error) {
860
- client.clearOptimisticUpdate(optimisticToken);
861
- if (snapshots.size > 0) for (const [id, snapshot] of snapshots) client.restore(id, snapshot);
862
- if (listSnapshots && listSnapshots.size > 0) for (const [name, list] of listSnapshots) client.restoreList(name, list);
778
+ settle();
863
779
  if (error instanceof Error) {
864
780
  const statusCode = getErrorStatusCode(error);
865
781
  if ((statusCode ? categorizeHTTPErrorStatus(statusCode) : "boundary") === "boundary") throw error;
@@ -867,9 +783,7 @@ function wrapMutation(client, identifier) {
867
783
  error,
868
784
  result: void 0
869
785
  };
870
- } else throw new Error(`fate: Mutation '${identifier.key}' failed.`);
871
- } finally {
872
- client.clearOptimisticUpdate(optimisticToken);
786
+ } else throw new Error(`fate: Mutation '${identifier.key}' failed.`, { cause: error });
873
787
  }
874
788
  };
875
789
  const mutationPromise = client.trackPendingRequest(performMutation);
@@ -956,6 +870,93 @@ var OperationLifetime = class {
956
870
  }
957
871
  };
958
872
  //#endregion
873
+ //#region src/mask.ts
874
+ function emptyMask() {
875
+ return {
876
+ all: false,
877
+ children: /* @__PURE__ */ new Map()
878
+ };
879
+ }
880
+ function cloneMask(m) {
881
+ const clone = {
882
+ all: m.all,
883
+ children: /* @__PURE__ */ new Map()
884
+ };
885
+ for (const [key, value] of m.children) clone.children.set(key, cloneMask(value));
886
+ return clone;
887
+ }
888
+ function addPath(mask, path) {
889
+ if (mask.all) return;
890
+ const parts = path.split(".");
891
+ let current = mask;
892
+ for (let i = 0; i < parts.length; i++) {
893
+ const seg = parts[i];
894
+ let child = current.children.get(seg);
895
+ if (!child) {
896
+ child = emptyMask();
897
+ current.children.set(seg, child);
898
+ }
899
+ current = child;
900
+ }
901
+ current.all = true;
902
+ current.children.clear();
903
+ }
904
+ function union(into, b) {
905
+ if (into.all || b.all) {
906
+ into.all = true;
907
+ into.children.clear();
908
+ return;
909
+ }
910
+ for (const [key, child] of b.children) {
911
+ const exist = into.children.get(key);
912
+ if (!exist) into.children.set(key, cloneMask(child));
913
+ else union(exist, child);
914
+ }
915
+ }
916
+ function fromPaths(paths) {
917
+ const mask = emptyMask();
918
+ for (const path of paths) addPath(mask, path);
919
+ return mask;
920
+ }
921
+ function toPaths(mask) {
922
+ const paths = [];
923
+ const visit = (current, prefix) => {
924
+ if (current.all) {
925
+ if (prefix) paths.push(prefix);
926
+ return;
927
+ }
928
+ for (const [key, child] of current.children) visit(child, prefix ? `${prefix}.${key}` : key);
929
+ };
930
+ visit(mask, null);
931
+ return paths;
932
+ }
933
+ function isCovered(mask, path) {
934
+ if (mask.all) return true;
935
+ const parts = path.split(".");
936
+ let current = mask;
937
+ for (let i = 0; i < parts.length; i++) {
938
+ if (!current) return false;
939
+ if (current.all) return true;
940
+ current = current.children.get(parts[i]);
941
+ }
942
+ return !!current && (current.all || current.children.size === 0);
943
+ }
944
+ function diffPaths(paths, mask) {
945
+ const missing = /* @__PURE__ */ new Set();
946
+ for (const path of paths) if (!isCovered(mask, path)) missing.add(path);
947
+ return missing;
948
+ }
949
+ function intersects(a, b) {
950
+ if (a.all || b.all) return true;
951
+ for (const [key, child] of a.children) {
952
+ const otherChild = b.children.get(key);
953
+ if (!otherChild) continue;
954
+ if (child.all || otherChild.all) return true;
955
+ if (intersects(child, otherChild)) return true;
956
+ }
957
+ return false;
958
+ }
959
+ //#endregion
959
960
  //#region src/store.ts
960
961
  const listKeySeparator = " __fate__ ";
961
962
  const decodeListKeyPart = (part) => {
@@ -977,16 +978,6 @@ const parseListKey = (key) => {
977
978
  ownerId: decodeListKeyPart(ownerId)
978
979
  };
979
980
  };
980
- const cloneValue = (value) => {
981
- if (Array.isArray(value)) return value.map(cloneValue);
982
- if (isNodeRef(value)) return value;
983
- if (value != null && typeof value === "object") {
984
- const result = {};
985
- for (const [key, record] of Object.entries(value)) result[key] = cloneValue(record);
986
- return result;
987
- }
988
- return value;
989
- };
990
981
  const isPlainRecord = (value) => {
991
982
  if (value == null || typeof value !== "object" || Array.isArray(value) || isNodeRef(value)) return false;
992
983
  const prototype = Object.getPrototypeOf(value);
@@ -1021,14 +1012,17 @@ const areHydrationValuesEqual = (left, right) => {
1021
1012
  return leftKeys.length === rightKeys.length && leftKeys.every((key) => Object.hasOwn(right, key) && areHydrationValuesEqual(left[key], right[key]));
1022
1013
  };
1023
1014
  const areMasksEqual = (left, right) => {
1024
- if (!left || !right) return left === right;
1025
- const leftPaths = toPaths(left).sort();
1026
- const rightPaths = toPaths(right).sort();
1027
- return leftPaths.length === rightPaths.length && leftPaths.every((path, index) => path === rightPaths[index]);
1015
+ if (left === right || left?.all && right?.all) return true;
1016
+ if (!left || !right || left.all !== right.all || left.children.size !== right.children.size) return false;
1017
+ for (const [key, child] of left.children) if (!areMasksEqual(child, right.children.get(key))) return false;
1018
+ return true;
1028
1019
  };
1029
1020
  const emptyFunction = () => {};
1021
+ const hasReferences = (value) => isNodeRef(value) || Array.isArray(value) && value.some(isNodeRef);
1022
+ const areNormalizedValuesEqual = (left, right) => Object.is(left, right) || isNodeRef(left) && isNodeRef(right) && getNodeRefId(left) === getNodeRefId(right) || Array.isArray(left) && Array.isArray(right) && left.length === right.length && left.every((value, index) => areNormalizedValuesEqual(value, right[index]));
1030
1023
  var Store = class {
1031
- constructor() {
1024
+ constructor(onRebase) {
1025
+ this.onRebase = onRebase;
1032
1026
  this.coverage = /* @__PURE__ */ new Map();
1033
1027
  this.listKeysByOwnerField = /* @__PURE__ */ new Map();
1034
1028
  this.listKeysByReferencedEntity = /* @__PURE__ */ new Map();
@@ -1038,6 +1032,142 @@ var Store = class {
1038
1032
  this.records = /* @__PURE__ */ new Map();
1039
1033
  this.subscriptions = /* @__PURE__ */ new Map();
1040
1034
  this.listSubscriptions = /* @__PURE__ */ new Map();
1035
+ this.optimisticLayers = /* @__PURE__ */ new Set();
1036
+ this.optimisticBase = {
1037
+ lists: /* @__PURE__ */ new Map(),
1038
+ records: /* @__PURE__ */ new Map()
1039
+ };
1040
+ }
1041
+ get hasOptimisticUpdates() {
1042
+ return this.optimisticLayers.size > 0;
1043
+ }
1044
+ get isRebasing() {
1045
+ return this.rebase !== void 0;
1046
+ }
1047
+ /** Apply a synchronous cache update beneath the pending optimistic layers. */
1048
+ update(apply) {
1049
+ if (this.rebase || this.optimisticLayers.size === 0) return apply();
1050
+ const rebase = {
1051
+ lists: /* @__PURE__ */ new Map(),
1052
+ records: /* @__PURE__ */ new Map()
1053
+ };
1054
+ this.rebase = rebase;
1055
+ try {
1056
+ for (const [id, snapshot] of this.optimisticBase.records) this.restore(id, snapshot);
1057
+ for (const [key, list] of this.optimisticBase.lists) this.restoreList(key, list);
1058
+ this.optimisticBase.records.clear();
1059
+ this.optimisticBase.lists.clear();
1060
+ return apply();
1061
+ } finally {
1062
+ try {
1063
+ for (const layer of this.optimisticLayers) {
1064
+ layer.writes.clear();
1065
+ this.recordingLayer = layer;
1066
+ layer.apply();
1067
+ }
1068
+ } finally {
1069
+ this.recordingLayer = void 0;
1070
+ const records = this.getRebaseChanges(rebase);
1071
+ const lists = /* @__PURE__ */ new Set();
1072
+ for (const [key, before] of rebase.lists) if (!areHydrationValuesEqual(before, this.lists.get(key))) {
1073
+ lists.add(key);
1074
+ const owner = parseListKey(key);
1075
+ if (owner) {
1076
+ if (!records.has(owner.ownerId)) records.set(owner.ownerId, /* @__PURE__ */ new Set([owner.field]));
1077
+ else records.get(owner.ownerId)?.add(owner.field);
1078
+ }
1079
+ } else if (before) this.lists.set(key, before);
1080
+ this.rebase = void 0;
1081
+ this.onRebase?.(new Set(records.keys()));
1082
+ for (const [id, paths] of records) this.notify(id, paths);
1083
+ for (const key of lists) this.notifyListSubscribers(key);
1084
+ }
1085
+ }
1086
+ }
1087
+ getRebaseChanges(rebase) {
1088
+ const changes = /* @__PURE__ */ new Map();
1089
+ for (const [id, before] of rebase.records) {
1090
+ const after = this.records.get(id);
1091
+ const mask = this.coverage.get(id);
1092
+ if (Boolean(before.record) !== Boolean(after) || before.mask?.all !== mask?.all) {
1093
+ changes.set(id, void 0);
1094
+ continue;
1095
+ }
1096
+ const paths = /* @__PURE__ */ new Set();
1097
+ for (const key of /* @__PURE__ */ new Set([...Object.keys(before.record ?? {}), ...Object.keys(after ?? {})])) if (Object.hasOwn(before.record ?? {}, key) !== Object.hasOwn(after ?? {}, key) || !areNormalizedValuesEqual(before.record?.[key], after?.[key])) paths.add(key);
1098
+ if (!areMasksEqual(before.mask, mask)) {
1099
+ for (const path of before.mask ? toPaths(before.mask) : []) if (!mask || !isCovered(mask, path)) paths.add(path);
1100
+ for (const path of mask ? toPaths(mask) : []) if (!before.mask || !isCovered(before.mask, path)) paths.add(path);
1101
+ }
1102
+ if (paths.size > 0) changes.set(id, paths);
1103
+ else if (before.record) this.records.set(id, before.record);
1104
+ }
1105
+ return changes;
1106
+ }
1107
+ /** Returns an idempotent settlement function; omit its update to roll back. */
1108
+ optimisticUpdate(apply) {
1109
+ const layer = {
1110
+ apply,
1111
+ writes: /* @__PURE__ */ new Map()
1112
+ };
1113
+ const settle = (commit) => {
1114
+ if (!this.optimisticLayers.has(layer) || layer.settled) return;
1115
+ this.update(() => {
1116
+ const overlays = commit ? this.getPendingOverlap(layer, layer.writes) : /* @__PURE__ */ new Map();
1117
+ if (overlays.size > 0) {
1118
+ layer.settled = true;
1119
+ layer.apply = () => {
1120
+ for (const [id, { partial, paths }] of this.getPendingOverlap(layer, overlays)) this.merge(id, partial, paths);
1121
+ };
1122
+ } else this.optimisticLayers.delete(layer);
1123
+ for (const pending of this.optimisticLayers) {
1124
+ if (!pending.settled) break;
1125
+ this.optimisticLayers.delete(pending);
1126
+ }
1127
+ commit?.();
1128
+ });
1129
+ };
1130
+ this.optimisticLayers.add(layer);
1131
+ try {
1132
+ this.update(() => {});
1133
+ } catch (error) {
1134
+ settle();
1135
+ throw error;
1136
+ }
1137
+ return settle;
1138
+ }
1139
+ getPendingOverlap(layer, writes) {
1140
+ const earlier = [];
1141
+ for (const pending of this.optimisticLayers) {
1142
+ if (pending === layer) break;
1143
+ if (!pending.settled) earlier.push(pending);
1144
+ }
1145
+ const overlaps = /* @__PURE__ */ new Map();
1146
+ for (const [id, write] of writes) {
1147
+ const partial = Object.fromEntries(Object.entries(write.partial).filter(([key, value]) => !hasReferences(value) && earlier.some(({ writes }) => {
1148
+ const previous = writes.get(id)?.partial;
1149
+ return previous && Object.hasOwn(previous, key) && !hasReferences(previous[key]);
1150
+ })));
1151
+ if (Object.keys(partial).length) overlaps.set(id, {
1152
+ partial,
1153
+ paths: new Set([...write.paths].filter((path) => Object.hasOwn(partial, path.split(".")[0])))
1154
+ });
1155
+ }
1156
+ return overlaps;
1157
+ }
1158
+ captureRecord(id) {
1159
+ if (!this.rebase) return;
1160
+ for (const target of [this.rebase, this.recordingLayer && this.optimisticBase]) if (target && !target.records.has(id)) {
1161
+ const mask = this.coverage.get(id);
1162
+ target.records.set(id, {
1163
+ mask: mask ? cloneMask(mask) : void 0,
1164
+ record: this.records.get(id)
1165
+ });
1166
+ }
1167
+ }
1168
+ captureList(key) {
1169
+ if (!this.rebase) return;
1170
+ for (const target of [this.rebase, this.recordingLayer && this.optimisticBase]) if (target && !target.lists.has(key)) target.lists.set(key, this.lists.get(key));
1041
1171
  }
1042
1172
  dehydrate() {
1043
1173
  return {
@@ -1095,10 +1225,23 @@ var Store = class {
1095
1225
  return this.records.get(id);
1096
1226
  }
1097
1227
  merge(id, partial, paths) {
1098
- const changedPaths = this.mergeInternal(id, partial, paths);
1099
- if (changedPaths) this.notify(id, changedPaths);
1228
+ return this.update(() => {
1229
+ const changedPaths = this.mergeInternal(id, partial, paths);
1230
+ if (changedPaths) this.notify(id, changedPaths);
1231
+ });
1100
1232
  }
1101
1233
  mergeInternal(id, partial, paths) {
1234
+ this.captureRecord(id);
1235
+ const selectedPaths = this.recordingLayer ? new Set(paths) : paths;
1236
+ if (this.recordingLayer) {
1237
+ const write = this.recordingLayer.writes.get(id) ?? {
1238
+ partial: {},
1239
+ paths: /* @__PURE__ */ new Set()
1240
+ };
1241
+ Object.assign(write.partial, partial);
1242
+ for (const path of selectedPaths) write.paths.add(path);
1243
+ this.recordingLayer.writes.set(id, write);
1244
+ }
1102
1245
  const previous = this.records.get(id);
1103
1246
  const changedPaths = /* @__PURE__ */ new Set();
1104
1247
  let mask = this.coverage.get(id);
@@ -1106,7 +1249,7 @@ var Store = class {
1106
1249
  mask = emptyMask();
1107
1250
  this.coverage.set(id, mask);
1108
1251
  }
1109
- union(mask, fromPaths(paths));
1252
+ union(mask, fromPaths(selectedPaths));
1110
1253
  if (previous) {
1111
1254
  let hasChanges = false;
1112
1255
  for (const [key, value] of Object.entries(partial)) if (previous[key] !== value) {
@@ -1128,10 +1271,13 @@ var Store = class {
1128
1271
  return changedPaths;
1129
1272
  }
1130
1273
  deleteRecord(id) {
1131
- const record = this.records.get(id);
1132
- if (record) this.removeRecordReferenceIndexes(id, record);
1133
- this.records.delete(id);
1134
- this.coverage.delete(id);
1274
+ return this.update(() => {
1275
+ this.captureRecord(id);
1276
+ const record = this.records.get(id);
1277
+ if (record) this.removeRecordReferenceIndexes(id, record);
1278
+ this.records.delete(id);
1279
+ this.coverage.delete(id);
1280
+ });
1135
1281
  }
1136
1282
  missingForSelection(id, paths) {
1137
1283
  const requested = new Set(paths);
@@ -1166,6 +1312,7 @@ var Store = class {
1166
1312
  };
1167
1313
  }
1168
1314
  notify(id, paths) {
1315
+ if (this.rebase) return;
1169
1316
  const set = this.subscriptions.get(id);
1170
1317
  if (!set) return;
1171
1318
  const changedPaths = paths ? [...paths] : [];
@@ -1178,6 +1325,7 @@ var Store = class {
1178
1325
  }
1179
1326
  }
1180
1327
  notifyListSubscribers(key) {
1328
+ if (this.rebase) return;
1181
1329
  const set = this.listSubscriptions.get(key);
1182
1330
  if (!set) return;
1183
1331
  for (const fn of set) try {
@@ -1201,70 +1349,77 @@ var Store = class {
1201
1349
  return entries;
1202
1350
  }
1203
1351
  setList(key, state) {
1204
- const previous = this.lists.get(key);
1205
- if (previous) this.removeListIndexes(key, previous);
1206
- this.lists.set(key, state);
1207
- this.addListIndexes(key, state);
1208
- this.notifyListSubscribers(key);
1352
+ return this.update(() => {
1353
+ this.captureList(key);
1354
+ const previous = this.lists.get(key);
1355
+ if (previous) this.removeListIndexes(key, previous);
1356
+ this.lists.set(key, state);
1357
+ this.addListIndexes(key, state);
1358
+ this.notifyListSubscribers(key);
1359
+ });
1209
1360
  }
1210
1361
  replaceListEntityId(previousId, nextId) {
1211
- const keys = [...this.listKeysByReferencedEntity.get(previousId) ?? []];
1212
- for (const key of keys) {
1213
- const list = this.lists.get(key);
1214
- if (!list) continue;
1215
- let changed = false;
1216
- let ids = list.ids;
1217
- let cursors = list.cursors;
1218
- if (list.ids.includes(previousId)) {
1219
- const nextIds = [];
1220
- const nextCursors = list.cursors ? [] : void 0;
1221
- const seenIds = /* @__PURE__ */ new Set();
1222
- list.ids.forEach((id, index) => {
1223
- const resolved = id === previousId ? nextId : id;
1224
- if (seenIds.has(resolved)) return;
1225
- seenIds.add(resolved);
1226
- nextIds.push(resolved);
1227
- if (nextCursors) nextCursors.push(list.cursors?.[index]);
1362
+ return this.update(() => {
1363
+ const keys = [...this.listKeysByReferencedEntity.get(previousId) ?? []];
1364
+ for (const key of keys) {
1365
+ const list = this.lists.get(key);
1366
+ if (!list) continue;
1367
+ let changed = false;
1368
+ let ids = list.ids;
1369
+ let cursors = list.cursors;
1370
+ if (list.ids.includes(previousId)) {
1371
+ const nextIds = [];
1372
+ const nextCursors = list.cursors ? [] : void 0;
1373
+ const seenIds = /* @__PURE__ */ new Set();
1374
+ list.ids.forEach((id, index) => {
1375
+ const resolved = id === previousId ? nextId : id;
1376
+ if (seenIds.has(resolved)) return;
1377
+ seenIds.add(resolved);
1378
+ nextIds.push(resolved);
1379
+ if (nextCursors) nextCursors.push(list.cursors?.[index]);
1380
+ });
1381
+ changed = true;
1382
+ ids = nextIds;
1383
+ cursors = nextCursors;
1384
+ }
1385
+ const dedupe = (values) => {
1386
+ if (!values || !values.includes(previousId)) return;
1387
+ const seen = /* @__PURE__ */ new Set();
1388
+ const next = [];
1389
+ for (const value of values) {
1390
+ const resolved = value === previousId ? nextId : value;
1391
+ if (seen.has(resolved)) continue;
1392
+ seen.add(resolved);
1393
+ next.push(resolved);
1394
+ }
1395
+ changed = true;
1396
+ return next;
1397
+ };
1398
+ const pendingBeforeIds = dedupe(list.pendingBeforeIds) ?? list.pendingBeforeIds;
1399
+ const pendingAfterIds = dedupe(list.pendingAfterIds) ?? list.pendingAfterIds;
1400
+ const liveBeforeIds = dedupe(list.liveBeforeIds) ?? list.liveBeforeIds;
1401
+ const liveAfterIds = dedupe(list.liveAfterIds) ?? list.liveAfterIds;
1402
+ if (!changed) continue;
1403
+ const canonicalIds = new Set(ids);
1404
+ this.setList(key, {
1405
+ backwardPageLimit: list.backwardPageLimit,
1406
+ cursors,
1407
+ forwardPageLimit: list.forwardPageLimit,
1408
+ ids,
1409
+ liveAfterIds: liveAfterIds?.filter((id) => !canonicalIds.has(id)),
1410
+ liveBeforeIds: liveBeforeIds?.filter((id) => !canonicalIds.has(id)),
1411
+ pagination: list.pagination,
1412
+ pendingAfterIds: pendingAfterIds?.filter((id) => !canonicalIds.has(id)),
1413
+ pendingBeforeIds: pendingBeforeIds?.filter((id) => !canonicalIds.has(id))
1228
1414
  });
1229
- changed = true;
1230
- ids = nextIds;
1231
- cursors = nextCursors;
1232
1415
  }
1233
- const dedupe = (values) => {
1234
- if (!values || !values.includes(previousId)) return;
1235
- const seen = /* @__PURE__ */ new Set();
1236
- const next = [];
1237
- for (const value of values) {
1238
- const resolved = value === previousId ? nextId : value;
1239
- if (seen.has(resolved)) continue;
1240
- seen.add(resolved);
1241
- next.push(resolved);
1242
- }
1243
- changed = true;
1244
- return next;
1245
- };
1246
- const pendingBeforeIds = dedupe(list.pendingBeforeIds) ?? list.pendingBeforeIds;
1247
- const pendingAfterIds = dedupe(list.pendingAfterIds) ?? list.pendingAfterIds;
1248
- const liveBeforeIds = dedupe(list.liveBeforeIds) ?? list.liveBeforeIds;
1249
- const liveAfterIds = dedupe(list.liveAfterIds) ?? list.liveAfterIds;
1250
- if (!changed) continue;
1251
- const canonicalIds = new Set(ids);
1252
- this.setList(key, {
1253
- backwardPageLimit: list.backwardPageLimit,
1254
- cursors,
1255
- forwardPageLimit: list.forwardPageLimit,
1256
- ids,
1257
- liveAfterIds: liveAfterIds?.filter((id) => !canonicalIds.has(id)),
1258
- liveBeforeIds: liveBeforeIds?.filter((id) => !canonicalIds.has(id)),
1259
- pagination: list.pagination,
1260
- pendingAfterIds: pendingAfterIds?.filter((id) => !canonicalIds.has(id)),
1261
- pendingBeforeIds: pendingBeforeIds?.filter((id) => !canonicalIds.has(id))
1262
- });
1263
- }
1416
+ });
1264
1417
  }
1265
1418
  restoreList(key, list) {
1266
- if (list == null) this.deleteList(key);
1267
- else this.setList(key, list);
1419
+ return this.update(() => {
1420
+ if (list == null) this.deleteList(key);
1421
+ else this.setList(key, list);
1422
+ });
1268
1423
  }
1269
1424
  collectGarbage(markedRecords, markedLists, options = {}) {
1270
1425
  const records = /* @__PURE__ */ new Set();
@@ -1291,10 +1446,13 @@ var Store = class {
1291
1446
  };
1292
1447
  }
1293
1448
  deleteList(key) {
1294
- const previous = this.lists.get(key);
1295
- if (previous) this.removeListIndexes(key, previous);
1296
- this.lists.delete(key);
1297
- this.notifyListSubscribers(key);
1449
+ return this.update(() => {
1450
+ this.captureList(key);
1451
+ const previous = this.lists.get(key);
1452
+ if (previous) this.removeListIndexes(key, previous);
1453
+ this.lists.delete(key);
1454
+ this.notifyListSubscribers(key);
1455
+ });
1298
1456
  }
1299
1457
  subscribeList(key, fn) {
1300
1458
  let set = this.listSubscriptions.get(key);
@@ -1310,88 +1468,83 @@ var Store = class {
1310
1468
  if (subscribers.size === 0) this.listSubscriptions.delete(key);
1311
1469
  };
1312
1470
  }
1313
- removeReferencesTo(targetId, viewDataCache, snapshots, listSnapshots) {
1314
- const listKeys = [...this.listKeysByReferencedEntity.get(targetId) ?? []];
1315
- for (const key of listKeys) {
1316
- const list = this.lists.get(key);
1317
- if (!list) continue;
1318
- const { ids } = list;
1319
- const hasLiveAfter = Boolean(list.liveAfterIds?.includes(targetId));
1320
- const hasLiveBefore = Boolean(list.liveBeforeIds?.includes(targetId));
1321
- const hasPendingAfter = Boolean(list.pendingAfterIds?.includes(targetId));
1322
- const hasPendingBefore = Boolean(list.pendingBeforeIds?.includes(targetId));
1323
- if (!ids.includes(targetId) && !hasLiveAfter && !hasLiveBefore && !hasPendingAfter && !hasPendingBefore) continue;
1324
- if (listSnapshots && !listSnapshots.has(key)) listSnapshots.set(key, list);
1325
- const entityIds = [];
1326
- const cursors = list.cursors ? [] : void 0;
1327
- for (let index = 0; index < ids.length; index++) {
1328
- const id = ids[index];
1329
- if (id === targetId) continue;
1330
- entityIds.push(id);
1331
- if (cursors) cursors.push(list.cursors?.[index]);
1471
+ removeReferencesTo(targetId, viewDataCache) {
1472
+ return this.update(() => {
1473
+ const listKeys = [...this.listKeysByReferencedEntity.get(targetId) ?? []];
1474
+ for (const key of listKeys) {
1475
+ const list = this.lists.get(key);
1476
+ if (!list) continue;
1477
+ const { ids } = list;
1478
+ const hasLiveAfter = Boolean(list.liveAfterIds?.includes(targetId));
1479
+ const hasLiveBefore = Boolean(list.liveBeforeIds?.includes(targetId));
1480
+ const hasPendingAfter = Boolean(list.pendingAfterIds?.includes(targetId));
1481
+ const hasPendingBefore = Boolean(list.pendingBeforeIds?.includes(targetId));
1482
+ if (!ids.includes(targetId) && !hasLiveAfter && !hasLiveBefore && !hasPendingAfter && !hasPendingBefore) continue;
1483
+ const entityIds = [];
1484
+ const cursors = list.cursors ? [] : void 0;
1485
+ for (let index = 0; index < ids.length; index++) {
1486
+ const id = ids[index];
1487
+ if (id === targetId) continue;
1488
+ entityIds.push(id);
1489
+ if (cursors) cursors.push(list.cursors?.[index]);
1490
+ }
1491
+ this.setList(key, {
1492
+ backwardPageLimit: list.backwardPageLimit,
1493
+ cursors,
1494
+ forwardPageLimit: list.forwardPageLimit,
1495
+ ids: entityIds,
1496
+ liveAfterIds: list.liveAfterIds?.filter((id) => id !== targetId),
1497
+ liveBeforeIds: list.liveBeforeIds?.filter((id) => id !== targetId),
1498
+ pagination: list.pagination,
1499
+ pendingAfterIds: list.pendingAfterIds?.filter((id) => id !== targetId),
1500
+ pendingBeforeIds: list.pendingBeforeIds?.filter((id) => id !== targetId)
1501
+ });
1332
1502
  }
1333
- this.setList(key, {
1334
- backwardPageLimit: list.backwardPageLimit,
1335
- cursors,
1336
- forwardPageLimit: list.forwardPageLimit,
1337
- ids: entityIds,
1338
- liveAfterIds: list.liveAfterIds?.filter((id) => id !== targetId),
1339
- liveBeforeIds: list.liveBeforeIds?.filter((id) => id !== targetId),
1340
- pagination: list.pagination,
1341
- pendingAfterIds: list.pendingAfterIds?.filter((id) => id !== targetId),
1342
- pendingBeforeIds: list.pendingBeforeIds?.filter((id) => id !== targetId)
1343
- });
1344
- }
1345
- const ids = /* @__PURE__ */ new Map();
1346
- const recordEntries = [...this.recordReferencesByTarget.get(targetId)?.entries() ?? []];
1347
- for (const [id, fields] of recordEntries) {
1348
- const record = this.records.get(id);
1349
- if (!record) continue;
1350
- let updated = false;
1351
- const next = {};
1352
- const paths = /* @__PURE__ */ new Set();
1353
- for (const key of fields) {
1354
- const value = record[key];
1355
- if (Array.isArray(value)) {
1356
- const filtered = value.filter((item) => !(isNodeRef(item) && getNodeRefId(item) === targetId));
1357
- if (filtered.length !== value.length) {
1503
+ const ids = /* @__PURE__ */ new Map();
1504
+ const recordEntries = [...this.recordReferencesByTarget.get(targetId)?.entries() ?? []];
1505
+ for (const [id, fields] of recordEntries) {
1506
+ const record = this.records.get(id);
1507
+ if (!record) continue;
1508
+ let updated = false;
1509
+ const next = {};
1510
+ const paths = /* @__PURE__ */ new Set();
1511
+ for (const key of fields) {
1512
+ const value = record[key];
1513
+ if (Array.isArray(value)) {
1514
+ const filtered = value.filter((item) => !(isNodeRef(item) && getNodeRefId(item) === targetId));
1515
+ if (filtered.length !== value.length) {
1516
+ updated = true;
1517
+ paths.add(key);
1518
+ next[key] = filtered;
1519
+ }
1520
+ } else if (isNodeRef(value) && getNodeRefId(value) === targetId) {
1358
1521
  updated = true;
1359
1522
  paths.add(key);
1360
- next[key] = filtered;
1523
+ next[key] = null;
1361
1524
  }
1362
- } else if (isNodeRef(value) && getNodeRefId(value) === targetId) {
1363
- updated = true;
1364
- paths.add(key);
1365
- next[key] = null;
1366
1525
  }
1526
+ if (!updated) continue;
1527
+ viewDataCache.invalidate(id);
1528
+ this.mergeInternal(id, next, paths);
1529
+ ids.set(id, paths);
1367
1530
  }
1368
- if (!updated) continue;
1369
- if (snapshots && !snapshots.has(id)) snapshots.set(id, this.snapshot(id));
1370
- viewDataCache.invalidate(id);
1371
- this.mergeInternal(id, next, paths);
1372
- ids.set(id, paths);
1373
- }
1374
- for (const [id, paths] of ids) this.notify(id, paths);
1375
- }
1376
- snapshot(id) {
1377
- const record = this.records.get(id);
1378
- const mask = this.coverage.get(id);
1379
- return {
1380
- mask: mask ? cloneMask(mask) : void 0,
1381
- record: record ? cloneValue(record) : void 0
1382
- };
1531
+ for (const [id, paths] of ids) this.notify(id, paths);
1532
+ });
1383
1533
  }
1384
1534
  restore(id, snapshot) {
1385
- const previous = this.records.get(id);
1386
- if (previous) this.removeRecordReferenceIndexes(id, previous);
1387
- if (snapshot.record === void 0) this.records.delete(id);
1388
- else {
1389
- this.records.set(id, snapshot.record);
1390
- this.addRecordReferenceIndexes(id, snapshot.record);
1391
- }
1392
- if (snapshot.mask === void 0) this.coverage.delete(id);
1393
- else this.coverage.set(id, snapshot.mask);
1394
- this.notify(id);
1535
+ return this.update(() => {
1536
+ this.captureRecord(id);
1537
+ const previous = this.records.get(id);
1538
+ if (previous) this.removeRecordReferenceIndexes(id, previous);
1539
+ if (snapshot.record === void 0) this.records.delete(id);
1540
+ else {
1541
+ this.records.set(id, snapshot.record);
1542
+ this.addRecordReferenceIndexes(id, snapshot.record);
1543
+ }
1544
+ if (snapshot.mask === void 0) this.coverage.delete(id);
1545
+ else this.coverage.set(id, snapshot.mask);
1546
+ this.notify(id);
1547
+ });
1395
1548
  }
1396
1549
  addListIndexes(key, list) {
1397
1550
  const parsed = parseListKey(key);
@@ -1826,10 +1979,7 @@ var FateClient = class {
1826
1979
  this.pending = /* @__PURE__ */ new Map();
1827
1980
  this.pendingDeferred = /* @__PURE__ */ new Map();
1828
1981
  this.pendingOptimisticMutations = /* @__PURE__ */ new Map();
1829
- this.optimisticMasks = /* @__PURE__ */ new Map();
1830
- this.optimisticByEntity = /* @__PURE__ */ new Map();
1831
1982
  this.optimisticEntityResolutions = /* @__PURE__ */ new Map();
1832
- this.optimisticTokenCounter = 0;
1833
1983
  this.liveSubscriptions = /* @__PURE__ */ new Map();
1834
1984
  this.liveConnectionInvalidations = /* @__PURE__ */ new Map();
1835
1985
  this.stableRefs = /* @__PURE__ */ new Map();
@@ -1839,8 +1989,11 @@ var FateClient = class {
1839
1989
  this.gcPending = false;
1840
1990
  this.gcScheduled = false;
1841
1991
  this.pendingNetworkRequests = 0;
1842
- this.store = new Store();
1843
- this.viewDataCache = new ViewDataCache();
1992
+ this.store = new Store((ids) => {
1993
+ for (const id of ids) this.viewDataCache.invalidate(id);
1994
+ this.runPendingGarbageCollection();
1995
+ });
1996
+ this.viewDataCache = new ViewDataCache(() => !this.store.isRebasing);
1844
1997
  this.liveConnectionInvalidationToken = 0;
1845
1998
  this.actions = Object.create(null);
1846
1999
  this.mutationMap = Object.create(null);
@@ -1953,11 +2106,6 @@ var FateClient = class {
1953
2106
  }
1954
2107
  lists.add(key);
1955
2108
  }
1956
- snapshotList(key, listSnapshots) {
1957
- if (!listSnapshots || listSnapshots.has(key)) return;
1958
- const listState = this.store.getListState(key);
1959
- if (listState) listSnapshots.set(key, listState);
1960
- }
1961
2109
  applyListInsert(listState, entityId, insert) {
1962
2110
  const pendingBeforeIds = listState.pendingBeforeIds ?? [];
1963
2111
  const pendingAfterIds = listState.pendingAfterIds ?? [];
@@ -1986,7 +2134,7 @@ var FateClient = class {
1986
2134
  pending: false
1987
2135
  };
1988
2136
  }
1989
- insertIntoRootLists(type, entityId, insert, listSnapshots) {
2137
+ insertIntoRootLists(type, entityId, insert) {
1990
2138
  if (insert === "none") return;
1991
2139
  const listNames = this.rootLists.get(type);
1992
2140
  if (!listNames) return;
@@ -1995,7 +2143,6 @@ var FateClient = class {
1995
2143
  if (!listState) continue;
1996
2144
  const next = this.applyListInsert(listState, entityId, insert).list;
1997
2145
  if (next === listState) continue;
1998
- this.snapshotList(key, listSnapshots);
1999
2146
  this.store.setList(key, next);
2000
2147
  }
2001
2148
  }
@@ -2020,23 +2167,17 @@ var FateClient = class {
2020
2167
  } : argsPayload ? { args: argsPayload } : input;
2021
2168
  return await this.trackPendingRequest(() => this.transport.mutate(key, requestInput, select));
2022
2169
  }
2023
- write(type, data, select, snapshots, plan, pathPrefix = null, blockedMask, insert, listSnapshots) {
2024
- return this.writeEntity(type, data, select, snapshots, plan, pathPrefix, blockedMask, insert, listSnapshots);
2025
- }
2026
- deleteRecord(type, id, snapshots, listSnapshots) {
2027
- const entityId = toEntityId(type, id);
2028
- if (snapshots && !snapshots.has(entityId)) snapshots.set(entityId, this.store.snapshot(entityId));
2029
- this.viewDataCache.invalidate(entityId);
2030
- this.stableRefs.delete(entityId);
2031
- this.store.deleteRecord(entityId);
2032
- this.store.removeReferencesTo(entityId, this.viewDataCache, snapshots, listSnapshots);
2170
+ write(type, data, select, plan, insert) {
2171
+ return this.writeEntity(type, data, select, plan, null, insert);
2033
2172
  }
2034
- restore(id, snapshot) {
2035
- this.viewDataCache.invalidate(id);
2036
- this.store.restore(id, snapshot);
2037
- }
2038
- restoreList(name, list) {
2039
- this.store.restoreList(name, list);
2173
+ deleteRecord(type, id) {
2174
+ return this.store.update(() => {
2175
+ const entityId = toEntityId(type, id);
2176
+ this.viewDataCache.invalidate(entityId);
2177
+ this.stableRefs.delete(entityId);
2178
+ this.store.deleteRecord(entityId);
2179
+ this.store.removeReferencesTo(entityId, this.viewDataCache);
2180
+ });
2040
2181
  }
2041
2182
  stableRefWithViewNames(type, id, viewNames) {
2042
2183
  const entityId = toEntityId(type, id);
@@ -2221,10 +2362,8 @@ var FateClient = class {
2221
2362
  unsubscribe = this.transport.subscribeById(type, id, plan.paths, args, {
2222
2363
  onData: (record, liveSelect) => {
2223
2364
  if (!record || typeof record !== "object") return;
2224
- const pendingMask = this.getPendingOptimisticMask(entityId);
2225
2365
  const select = new Set(liveSelect ?? plan.paths);
2226
- const selection = this.filterSelectionForPendingOptimistics(entityId, select);
2227
- this.write(type, record, selection, void 0, plan, null, pendingMask, "none");
2366
+ this.write(type, record, select, plan, "none");
2228
2367
  },
2229
2368
  onDelete: (deletedId) => {
2230
2369
  this.deleteRecord(type, deletedId ?? id);
@@ -2278,24 +2417,26 @@ var FateClient = class {
2278
2417
  };
2279
2418
  }
2280
2419
  applyLiveConnectionEvent(view, connection, plan, event) {
2281
- if (event.type === "invalidate") {
2282
- this.invalidateLiveConnection(view, connection);
2283
- return;
2284
- }
2285
- if (event.type === "deleteEdge") {
2286
- this.deleteConnectionEdge(connection, toEntityId(event.nodeType, event.id));
2287
- return;
2288
- }
2289
- const node = event.edge.node;
2290
- if (!node || typeof node !== "object") return;
2291
- const nodeRecord = node;
2292
- const nodeType = event.nodeType;
2293
- if (!nodeType) {
2294
- this.reportLiveError(/* @__PURE__ */ new Error(`fate: Live connection event for '${connection.procedure}' did not include a node type.`));
2295
- return;
2296
- }
2297
- const entityId = this.write(nodeType, nodeRecord, plan.paths, void 0, plan, null, null, "none");
2298
- this.insertConnectionEdge(connection.key, entityId, event.edge.cursor, event.type, event.targetCursor, view, connection);
2420
+ return this.store.update(() => {
2421
+ if (event.type === "invalidate") {
2422
+ this.invalidateLiveConnection(view, connection);
2423
+ return;
2424
+ }
2425
+ if (event.type === "deleteEdge") {
2426
+ this.deleteConnectionEdge(connection, toEntityId(event.nodeType, event.id));
2427
+ return;
2428
+ }
2429
+ const node = event.edge.node;
2430
+ if (!node || typeof node !== "object") return;
2431
+ const nodeRecord = node;
2432
+ const nodeType = event.nodeType;
2433
+ if (!nodeType) {
2434
+ this.reportLiveError(/* @__PURE__ */ new Error(`fate: Live connection event for '${connection.procedure}' did not include a node type.`));
2435
+ return;
2436
+ }
2437
+ const entityId = this.write(nodeType, nodeRecord, plan.paths, plan, "none");
2438
+ this.insertConnectionEdge(connection.key, entityId, event.edge.cursor, event.type, event.targetCursor, view, connection);
2439
+ });
2299
2440
  }
2300
2441
  invalidateLiveConnection(view, connection) {
2301
2442
  const token = ++this.liveConnectionInvalidationToken;
@@ -2524,11 +2665,14 @@ var FateClient = class {
2524
2665
  const cursors = hasIncomingCursor || Boolean(previous?.cursors) || options.hasCursorArg ? ids.map((id) => cursorMap.get(id)) : void 0;
2525
2666
  const previousPagination = previous?.pagination;
2526
2667
  const newPagination = incomingPagination;
2668
+ const extendsExistingList = options.hasCursorArg && existingIds.length > 0;
2669
+ const leadingPagination = extendsExistingList && !isBackward ? previousPagination ?? newPagination : newPagination;
2670
+ const trailingPagination = extendsExistingList && isBackward ? previousPagination ?? newPagination : newPagination;
2527
2671
  const pagination = previousPagination || newPagination ? {
2528
- hasNext: !!(newPagination?.hasNext ?? previousPagination?.hasNext),
2529
- hasPrevious: !!(newPagination?.hasPrevious ?? previousPagination?.hasPrevious),
2530
- nextCursor: newPagination?.nextCursor ?? previousPagination?.nextCursor,
2531
- previousCursor: newPagination?.previousCursor ?? previousPagination?.previousCursor
2672
+ hasNext: !!(trailingPagination?.hasNext ?? previousPagination?.hasNext),
2673
+ hasPrevious: !!(leadingPagination?.hasPrevious ?? previousPagination?.hasPrevious),
2674
+ nextCursor: trailingPagination?.nextCursor ?? previousPagination?.nextCursor,
2675
+ previousCursor: leadingPagination?.previousCursor ?? previousPagination?.previousCursor
2532
2676
  } : void 0;
2533
2677
  return {
2534
2678
  backwardPageLimit: !options.hasCursorArg && isBackward && options.pageLimit != null ? Math.max(previous?.backwardPageLimit ?? 0, options.pageLimit) : previous?.backwardPageLimit,
@@ -2562,60 +2706,10 @@ var FateClient = class {
2562
2706
  this.optimisticEntityResolutions.set(optimisticEntityId, resolvedEntityId);
2563
2707
  this.store.replaceListEntityId(optimisticEntityId, resolvedEntityId);
2564
2708
  }
2565
- registerOptimisticUpdate(entityId, select) {
2566
- if (!entityId || select.size === 0) return null;
2567
- const mask = fromPaths(select);
2568
- const token = ++this.optimisticTokenCounter;
2569
- this.optimisticMasks.set(token, {
2570
- entityId,
2571
- mask
2572
- });
2573
- let entries = this.optimisticByEntity.get(entityId);
2574
- if (!entries) {
2575
- entries = /* @__PURE__ */ new Set();
2576
- this.optimisticByEntity.set(entityId, entries);
2577
- }
2578
- entries.add(token);
2579
- return token;
2580
- }
2581
- clearOptimisticUpdate(token) {
2582
- if (token == null) return;
2583
- const entry = this.optimisticMasks.get(token);
2584
- if (!entry) return;
2585
- this.optimisticMasks.delete(token);
2586
- const entityTokens = this.optimisticByEntity.get(entry.entityId);
2587
- if (entityTokens) {
2588
- entityTokens.delete(token);
2589
- if (entityTokens.size === 0) this.optimisticByEntity.delete(entry.entityId);
2590
- }
2591
- this.runPendingGarbageCollection();
2592
- }
2593
- getPendingOptimisticMask(entityId, options = {}) {
2594
- if (!entityId) return null;
2595
- const tokens = this.optimisticByEntity.get(entityId);
2596
- if (!tokens || tokens.size === 0) return null;
2597
- let mask = null;
2598
- for (const token of tokens) {
2599
- if (options.excludeToken != null && token === options.excludeToken) continue;
2600
- const entry = this.optimisticMasks.get(token);
2601
- if (!entry) continue;
2602
- if (!mask) mask = cloneMask(entry.mask);
2603
- else union(mask, entry.mask);
2604
- }
2605
- return mask;
2606
- }
2607
2709
  getPendingOptimisticMutations(entityId) {
2608
2710
  const pending = this.pendingOptimisticMutations.get(entityId);
2609
2711
  return pending && pending.size > 0 ? [...pending] : null;
2610
2712
  }
2611
- filterSelectionForPendingOptimistics(entityId, select, options = {}) {
2612
- if (!entityId || select.size === 0) return select;
2613
- const pendingMask = this.getPendingOptimisticMask(entityId, options);
2614
- if (!pendingMask) return select;
2615
- const filtered = /* @__PURE__ */ new Set();
2616
- for (const path of select) if (!isCovered(pendingMask, path)) filtered.add(path);
2617
- return filtered;
2618
- }
2619
2713
  async loadConnection(view, connection, args, options = {}) {
2620
2714
  return await this.trackPendingRequest(() => this.loadConnectionInternal(view, connection, args, options));
2621
2715
  }
@@ -2630,21 +2724,23 @@ var FateClient = class {
2630
2724
  const { argsPayload, plan } = resolveSelectionPlan(view, requestArgs);
2631
2725
  const { items, pagination } = await this.transport.fetchList(connection.field, plan.paths, argsPayload);
2632
2726
  if (!items) return this.store.getListState(connection.key);
2633
- const incomingIds = [];
2634
- const incomingCursors = [];
2635
- for (const entry of items) {
2636
- const id = this.write(connection.type, entry.node, plan.paths, void 0, plan, null, null, "none");
2637
- incomingIds.push(id);
2638
- incomingCursors.push(entry.cursor);
2639
- }
2640
- const previous = this.store.getListState(connection.key);
2641
- const nextListState = this.mergeListState(previous, incomingIds, incomingCursors, pagination, {
2642
- ...getPaginationMergeInfo(requestArgs),
2643
- replace: true
2727
+ this.store.update(() => {
2728
+ const incomingIds = [];
2729
+ const incomingCursors = [];
2730
+ for (const entry of items) {
2731
+ const id = this.write(connection.type, entry.node, plan.paths, plan, "none");
2732
+ incomingIds.push(id);
2733
+ incomingCursors.push(entry.cursor);
2734
+ }
2735
+ const previous = this.store.getListState(connection.key);
2736
+ const nextListState = this.mergeListState(previous, incomingIds, incomingCursors, pagination, {
2737
+ ...getPaginationMergeInfo(requestArgs),
2738
+ replace: true
2739
+ });
2740
+ if (!filterConnectionArgs(connection.args)) this.registerRootList(connection.type, connection.key);
2741
+ this.store.setList(connection.key, nextListState);
2644
2742
  });
2645
- if (!filterConnectionArgs(connection.args)) this.registerRootList(connection.type, connection.key);
2646
- this.store.setList(connection.key, nextListState);
2647
- return nextListState;
2743
+ return this.store.getListState(connection.key);
2648
2744
  }
2649
2745
  const owner = parseEntityId(connection.owner);
2650
2746
  const requestArgs = omitUndefinedValues({
@@ -2668,32 +2764,34 @@ var FateClient = class {
2668
2764
  pagination: void 0
2669
2765
  } : list;
2670
2766
  if (!connectionPayload) return this.store.getListState(connection.key);
2671
- const incomingIds = [];
2672
- const incomingCursors = [];
2673
- const nodeType = this.getListNodeType(owner.type, connection.field);
2674
- for (const entry of connectionPayload.items) {
2675
- const { node } = entry;
2676
- const id = this.write(nodeType, node, nodeSelection, void 0, plan);
2677
- incomingIds.push(id);
2678
- incomingCursors.push(entry.cursor);
2679
- }
2680
- const previous = this.store.getListState(connection.key);
2681
- const previousIds = previous?.ids ?? [];
2682
- const previousSet = new Set(previousIds);
2683
- const newIds = incomingIds.filter((id) => !previousSet.has(id));
2684
- const nextListState = this.mergeListState(previous, incomingIds, incomingCursors, connectionPayload.pagination, {
2685
- ...getPaginationMergeInfo(argsPayload),
2686
- direction,
2687
- hasCursorArg: true,
2688
- replace: true
2767
+ this.store.update(() => {
2768
+ const incomingIds = [];
2769
+ const incomingCursors = [];
2770
+ const nodeType = this.getListNodeType(owner.type, connection.field);
2771
+ for (const entry of connectionPayload.items) {
2772
+ const { node } = entry;
2773
+ const id = this.write(nodeType, node, nodeSelection, plan);
2774
+ incomingIds.push(id);
2775
+ incomingCursors.push(entry.cursor);
2776
+ }
2777
+ const previous = this.store.getListState(connection.key);
2778
+ const previousIds = previous?.ids ?? [];
2779
+ const previousSet = new Set(previousIds);
2780
+ const newIds = incomingIds.filter((id) => !previousSet.has(id));
2781
+ const nextListState = this.mergeListState(previous, incomingIds, incomingCursors, connectionPayload.pagination, {
2782
+ ...getPaginationMergeInfo(argsPayload),
2783
+ direction,
2784
+ hasCursorArg: true,
2785
+ replace: true
2786
+ });
2787
+ this.store.setList(connection.key, nextListState);
2788
+ const current = this.store.read(connection.owner);
2789
+ const existingField = Array.isArray(current?.[connection.field]) ? current?.[connection.field] || [] : [];
2790
+ const nodeRefs = createNodeRefsForIds(newIds, void 0);
2791
+ const nextField = direction === "forward" ? [...existingField, ...nodeRefs] : [...nodeRefs, ...existingField];
2792
+ this.viewDataCache.invalidate(connection.owner);
2793
+ this.store.merge(connection.owner, { [connection.field]: nextField }, [connection.field]);
2689
2794
  });
2690
- this.store.setList(connection.key, nextListState);
2691
- const current = this.store.read(connection.owner);
2692
- const existingField = Array.isArray(current?.[connection.field]) ? current?.[connection.field] || [] : [];
2693
- const nodeRefs = createNodeRefsForIds(newIds, void 0);
2694
- const nextField = direction === "forward" ? [...existingField, ...nodeRefs] : [...nodeRefs, ...existingField];
2695
- this.viewDataCache.invalidate(connection.owner);
2696
- this.store.merge(connection.owner, { [connection.field]: nextField }, [connection.field]);
2697
2795
  return this.store.getListState(connection.key);
2698
2796
  }
2699
2797
  request(request, options) {
@@ -2836,9 +2934,7 @@ var FateClient = class {
2836
2934
  case "stale-while-revalidate":
2837
2935
  execution = this.handleStoreAndNetworkRequest(handle.descriptor);
2838
2936
  break;
2839
- default:
2840
- execution = this.executeRequest(handle.descriptor, mode === "network-only" ? { fetchAll: true } : void 0).then(() => this.getRequestResultFromDescriptor(handle.descriptor));
2841
- break;
2937
+ default: execution = this.executeRequest(handle.descriptor, mode === "network-only" ? { fetchAll: true } : void 0).then(() => this.getRequestResultFromDescriptor(handle.descriptor));
2842
2938
  }
2843
2939
  handle.start(execution);
2844
2940
  }
@@ -2861,7 +2957,7 @@ var FateClient = class {
2861
2957
  walk(record);
2862
2958
  }
2863
2959
  hasActiveOptimisticUpdates() {
2864
- return this.optimisticMasks.size > 0 || this.optimisticByEntity.size > 0 || this.pendingOptimisticMutations.size > 0;
2960
+ return this.store.hasOptimisticUpdates || this.pendingOptimisticMutations.size > 0;
2865
2961
  }
2866
2962
  hasPendingRequests() {
2867
2963
  return this.pendingNetworkRequests > 0;
@@ -3028,7 +3124,7 @@ var FateClient = class {
3028
3124
  const resolvedArgs = resolvedArgsFromPlan(plan);
3029
3125
  await this.trackPendingRequest(async () => {
3030
3126
  const records = await this.transport.fetchById(type, ids, select, resolvedArgs);
3031
- for (const record of records) this.writeEntity(type, record, select, void 0, plan, prefix);
3127
+ for (const record of records) this.writeEntity(type, record, select, plan, prefix);
3032
3128
  });
3033
3129
  }
3034
3130
  async fetchQueryAndNormalize(item) {
@@ -3039,7 +3135,7 @@ var FateClient = class {
3039
3135
  this.rootRequests.set(item.queryKey, null);
3040
3136
  return;
3041
3137
  }
3042
- const entityId = this.writeEntity(item.type, record, item.plan.paths, void 0, item.plan);
3138
+ const entityId = this.writeEntity(item.type, record, item.plan.paths, item.plan);
3043
3139
  this.rootRequests.set(item.queryKey, entityId);
3044
3140
  });
3045
3141
  }
@@ -3047,133 +3143,130 @@ var FateClient = class {
3047
3143
  if (!this.transport.fetchList) throw new Error(`fate: 'transport.fetchList' is not configured but request includes a list for key '${item.name}'.`);
3048
3144
  await this.trackPendingRequest(async () => {
3049
3145
  const { items, pagination } = await this.transport.fetchList(item.name, item.plan.paths, item.argsPayload);
3050
- const ids = [];
3051
- const cursors = [];
3052
- for (const entry of items) {
3053
- const id = this.writeEntity(item.type, entry.node, item.plan.paths, void 0, item.plan);
3054
- ids.push(id);
3055
- cursors.push(entry.cursor);
3056
- }
3057
- if (!filterConnectionArgs(item.argsPayload)) this.registerRootList(item.type, item.listKey);
3058
- const previous = this.store.getListState(item.listKey);
3059
- this.store.setList(item.listKey, this.mergeListState(previous, ids, cursors, pagination, {
3060
- ...getPaginationMergeInfo(item.argsPayload),
3061
- replace: true
3062
- }));
3146
+ this.store.update(() => {
3147
+ const ids = [];
3148
+ const cursors = [];
3149
+ for (const entry of items) {
3150
+ const id = this.writeEntity(item.type, entry.node, item.plan.paths, item.plan);
3151
+ ids.push(id);
3152
+ cursors.push(entry.cursor);
3153
+ }
3154
+ if (!filterConnectionArgs(item.argsPayload)) this.registerRootList(item.type, item.listKey);
3155
+ const previous = this.store.getListState(item.listKey);
3156
+ this.store.setList(item.listKey, this.mergeListState(previous, ids, cursors, pagination, {
3157
+ ...getPaginationMergeInfo(item.argsPayload),
3158
+ replace: true
3159
+ }));
3160
+ });
3063
3161
  });
3064
3162
  }
3065
- writeEntity(type, record, select, snapshots, plan, pathPrefix = null, blockedMask, insert, listSnapshots) {
3066
- const config = this.types.get(type);
3067
- if (!config) throw new Error(`fate: Found unknown entity type '${type}' in normalization.`);
3068
- const entityId = toEntityId(type, config.getId(record));
3069
- const result = {};
3070
- const selectionTree = groupSelectionByPrefix(select);
3071
- if (config.fields) for (const [key, relationDescriptor] of Object.entries(config.fields)) {
3072
- const value = record[key];
3073
- const fieldPath = pathPrefix ? `${pathPrefix}.${key}` : key;
3074
- const fieldArgs = plan?.args.get(fieldPath);
3075
- const isFieldBlocked = blockedMask ? isCovered(blockedMask, fieldPath) : false;
3076
- if (relationDescriptor === "scalar") {
3077
- if (isFieldBlocked || !Object.prototype.hasOwnProperty.call(record, key)) continue;
3078
- result[key] = value;
3079
- } else if (relationDescriptor && typeof relationDescriptor === "object" && "type" in relationDescriptor) {
3080
- if (isFieldBlocked) continue;
3081
- const childPaths = selectionTree.get(key) ?? emptySet;
3082
- if (value === null) {
3083
- result[key] = null;
3084
- continue;
3085
- }
3086
- if (value && typeof value === "object" && !isNodeRef(value)) {
3087
- const childType = relationDescriptor.type;
3163
+ writeEntity(type, record, select, plan, pathPrefix = null, insert) {
3164
+ return this.store.update(() => {
3165
+ const config = this.types.get(type);
3166
+ if (!config) throw new Error(`fate: Found unknown entity type '${type}' in normalization.`);
3167
+ const id = config.getId(record);
3168
+ const entityId = toEntityId(type, id);
3169
+ const result = {};
3170
+ const selectionTree = groupSelectionByPrefix(select);
3171
+ if (config.fields) for (const [key, relationDescriptor] of Object.entries(config.fields)) {
3172
+ const value = record[key];
3173
+ const fieldPath = pathPrefix ? `${pathPrefix}.${key}` : key;
3174
+ const fieldArgs = plan?.args.get(fieldPath);
3175
+ if (relationDescriptor === "scalar") {
3176
+ if (!Object.hasOwn(record, key)) continue;
3177
+ result[key] = value;
3178
+ } else if (relationDescriptor && typeof relationDescriptor === "object" && "type" in relationDescriptor) {
3179
+ const childPaths = selectionTree.get(key) ?? emptySet;
3180
+ if (value === null) {
3181
+ result[key] = null;
3182
+ continue;
3183
+ }
3184
+ if (value && typeof value === "object" && !isNodeRef(value)) {
3185
+ const childType = relationDescriptor.type;
3186
+ const childConfig = this.types.get(childType);
3187
+ if (!childConfig) throw new Error(`fate: Unknown related type '${childType}' (field '${type}.${key}').`);
3188
+ result[key] = createNodeRef(toEntityId(childType, childConfig.getId(value)));
3189
+ this.writeEntity(childType, value, childPaths, plan, fieldPath);
3190
+ }
3191
+ } else if (relationDescriptor && typeof relationDescriptor === "object" && "listOf" in relationDescriptor) {
3192
+ const childPaths = selectionTree.get(key) ?? emptySet;
3193
+ if (value === null) {
3194
+ result[key] = null;
3195
+ continue;
3196
+ }
3197
+ const childType = relationDescriptor.listOf;
3088
3198
  const childConfig = this.types.get(childType);
3089
3199
  if (!childConfig) throw new Error(`fate: Unknown related type '${childType}' (field '${type}.${key}').`);
3090
- result[key] = createNodeRef(toEntityId(childType, childConfig.getId(value)));
3091
- this.writeEntity(childType, value, childPaths, snapshots, plan, fieldPath, blockedMask, void 0, listSnapshots);
3092
- }
3093
- } else if (relationDescriptor && typeof relationDescriptor === "object" && "listOf" in relationDescriptor) {
3094
- if (isFieldBlocked) continue;
3095
- const childPaths = selectionTree.get(key) ?? emptySet;
3096
- if (value === null) {
3097
- result[key] = null;
3098
- continue;
3099
- }
3100
- const childType = relationDescriptor.listOf;
3101
- const childConfig = this.types.get(childType);
3102
- if (!childConfig) throw new Error(`fate: Unknown related type '${childType}' (field '${type}.${key}').`);
3103
- const connection = (() => {
3104
- if (Array.isArray(value)) return { items: value.map((item) => ({ node: item })) };
3105
- if (value && typeof value === "object") {
3106
- const record = value;
3107
- if (Array.isArray(record.items)) return {
3108
- items: record.items.map((node) => {
3109
- if (node && typeof node === "object" && "node" in node) {
3110
- const itemRecord = node;
3111
- return {
3112
- cursor: itemRecord.cursor,
3113
- node: itemRecord.node
3114
- };
3115
- }
3116
- return { node };
3117
- }),
3118
- pagination: record.pagination
3119
- };
3120
- }
3121
- return null;
3122
- })();
3123
- if (connection) {
3124
- const ids = [];
3125
- const cursors = [];
3126
- const nodeSelection = childPaths.size > 0 ? /* @__PURE__ */ new Set() : childPaths;
3127
- if (childPaths.size > 0) for (const path of childPaths) {
3128
- if (path.startsWith("items.node.")) {
3129
- nodeSelection.add(path.slice(11));
3130
- continue;
3131
- }
3132
- if (path.startsWith("node.")) {
3133
- nodeSelection.add(path.slice(5));
3134
- continue;
3200
+ const connection = (() => {
3201
+ if (Array.isArray(value)) return { items: value.map((item) => ({ node: item })) };
3202
+ if (value && typeof value === "object") {
3203
+ const record = value;
3204
+ if (Array.isArray(record.items)) return {
3205
+ items: record.items.map((node) => {
3206
+ if (node && typeof node === "object" && "node" in node) {
3207
+ const itemRecord = node;
3208
+ return {
3209
+ cursor: itemRecord.cursor,
3210
+ node: itemRecord.node
3211
+ };
3212
+ }
3213
+ return { node };
3214
+ }),
3215
+ pagination: record.pagination
3216
+ };
3135
3217
  }
3136
- if (path === "items.node" || path.startsWith("items.")) continue;
3137
- nodeSelection.add(path);
3138
- }
3139
- for (const entry of connection.items) {
3140
- const node = entry.node;
3141
- const cursor = "cursor" in entry ? entry.cursor : void 0;
3142
- cursors.push(cursor);
3143
- if (isNodeRef(node)) {
3144
- ids.push(getNodeRefId(node));
3145
- continue;
3218
+ return null;
3219
+ })();
3220
+ if (connection) {
3221
+ const ids = [];
3222
+ const cursors = [];
3223
+ const nodeSelection = childPaths.size > 0 ? /* @__PURE__ */ new Set() : childPaths;
3224
+ if (childPaths.size > 0) for (const path of childPaths) {
3225
+ if (path.startsWith("items.node.")) {
3226
+ nodeSelection.add(path.slice(11));
3227
+ continue;
3228
+ }
3229
+ if (path.startsWith("node.")) {
3230
+ nodeSelection.add(path.slice(5));
3231
+ continue;
3232
+ }
3233
+ if (path === "items.node" || path.startsWith("items.")) continue;
3234
+ nodeSelection.add(path);
3146
3235
  }
3147
- if (node && typeof node === "object") {
3148
- const childId = toEntityId(childType, childConfig.getId(node));
3149
- this.writeEntity(childType, node, nodeSelection, snapshots, plan, fieldPath, blockedMask, void 0, listSnapshots);
3150
- ids.push(childId);
3151
- continue;
3236
+ for (const entry of connection.items) {
3237
+ const node = entry.node;
3238
+ const cursor = "cursor" in entry ? entry.cursor : void 0;
3239
+ cursors.push(cursor);
3240
+ if (isNodeRef(node)) {
3241
+ ids.push(getNodeRefId(node));
3242
+ continue;
3243
+ }
3244
+ if (node && typeof node === "object") {
3245
+ const childId = toEntityId(childType, childConfig.getId(node));
3246
+ this.writeEntity(childType, node, nodeSelection, plan, fieldPath);
3247
+ ids.push(childId);
3248
+ continue;
3249
+ }
3152
3250
  }
3251
+ const listKey = getListKey(entityId, key, fieldArgs?.hash);
3252
+ const previousList = this.store.getListState(listKey);
3253
+ const argsValue = fieldArgs?.value;
3254
+ const nextListState = this.mergeListState(previousList, ids, cursors, connection.pagination, getPaginationMergeInfo(argsValue));
3255
+ const listChanged = !areListStatesEqual(previousList, nextListState);
3256
+ result[key] = createNodeRefsForIds(nextListState.ids, this.store.read(entityId)?.[key], { reuseCurrentArray: !listChanged });
3257
+ this.store.setList(listKey, nextListState);
3153
3258
  }
3154
- const listKey = getListKey(entityId, key, fieldArgs?.hash);
3155
- const previousList = this.store.getListState(listKey);
3156
- const argsValue = fieldArgs?.value;
3157
- const nextListState = this.mergeListState(previousList, ids, cursors, connection.pagination, getPaginationMergeInfo(argsValue));
3158
- const listChanged = !areListStatesEqual(previousList, nextListState);
3159
- result[key] = createNodeRefsForIds(nextListState.ids, this.store.read(entityId)?.[key], { reuseCurrentArray: !listChanged });
3160
- this.store.setList(listKey, nextListState);
3161
- }
3162
- } else result[key] = value;
3163
- }
3164
- for (const [key, value] of Object.entries(record)) if (!(key in (config.fields ?? {}))) {
3165
- const fieldPath = pathPrefix ? `${pathPrefix}.${key}` : key;
3166
- if (blockedMask && isCovered(blockedMask, fieldPath)) continue;
3167
- result[key] = value;
3168
- }
3169
- if (snapshots && !snapshots.has(entityId)) snapshots.set(entityId, this.store.snapshot(entityId));
3170
- this.viewDataCache.invalidate(entityId);
3171
- this.store.merge(entityId, result, select);
3172
- this.linkParentLists(type, entityId, result, snapshots, listSnapshots, insert ?? "after");
3173
- if (!pathPrefix && insert) this.insertIntoRootLists(type, entityId, insert, listSnapshots);
3174
- return entityId;
3259
+ } else result[key] = value;
3260
+ }
3261
+ for (const [key, value] of Object.entries(record)) if (!(key in (config.fields ?? {}))) result[key] = value;
3262
+ this.viewDataCache.invalidate(entityId);
3263
+ this.store.merge(entityId, result, select);
3264
+ this.linkParentLists(type, entityId, result, insert ?? "after");
3265
+ if (!pathPrefix && insert) this.insertIntoRootLists(type, entityId, insert);
3266
+ return entityId;
3267
+ });
3175
3268
  }
3176
- linkParentLists(type, entityId, record, snapshots, listSnapshots, insert) {
3269
+ linkParentLists(type, entityId, record, insert) {
3177
3270
  if (insert === "none") return;
3178
3271
  const parents = this.parentLists.get(type);
3179
3272
  if (!parents) return;
@@ -3186,23 +3279,18 @@ var FateClient = class {
3186
3279
  if (!existing) continue;
3187
3280
  const current = Array.isArray(existing[parent.field]) ? existing[parent.field] : [];
3188
3281
  if (current.some((item) => isNodeRef(item) && getNodeRefId(item) === entityId)) continue;
3189
- if (snapshots && !snapshots.has(parentId)) snapshots.set(parentId, this.store.snapshot(parentId));
3190
3282
  this.viewDataCache.invalidate(parentId);
3191
3283
  const defaultListKey = getListKey(parentId, parent.field);
3192
3284
  const defaultListState = this.store.getListState(defaultListKey);
3193
3285
  let nextField = current;
3194
3286
  if (defaultListState) {
3195
3287
  const { list: nextDefaultListState, pending } = this.applyListInsert(defaultListState, entityId, insert);
3196
- if (nextDefaultListState !== defaultListState) {
3197
- this.snapshotList(defaultListKey, listSnapshots);
3198
- this.store.setList(defaultListKey, nextDefaultListState);
3199
- }
3288
+ if (nextDefaultListState !== defaultListState) this.store.setList(defaultListKey, nextDefaultListState);
3200
3289
  const nextFieldIds = pending ? getListEntries(nextDefaultListState).map(({ id }) => id) : nextDefaultListState.ids;
3201
3290
  nextField = createNodeRefsForIds(nextFieldIds, current);
3202
3291
  } else {
3203
3292
  const currentIds = current.map((item) => isNodeRef(item) ? getNodeRefId(item) : null).filter((id) => id != null);
3204
3293
  const nextDefaultListState = { ids: insert === "before" ? [entityId, ...currentIds] : [...currentIds, entityId] };
3205
- this.snapshotList(defaultListKey, listSnapshots);
3206
3294
  this.store.setList(defaultListKey, nextDefaultListState);
3207
3295
  nextField = createNodeRefsForIds(nextDefaultListState.ids, current);
3208
3296
  }
@@ -3210,7 +3298,6 @@ var FateClient = class {
3210
3298
  for (const [listKey, listState] of registeredLists) {
3211
3299
  const nextListState = this.applyListInsert(listState, entityId, insert).list;
3212
3300
  if (nextListState === listState) continue;
3213
- this.snapshotList(listKey, listSnapshots);
3214
3301
  this.store.setList(listKey, nextListState);
3215
3302
  }
3216
3303
  this.store.merge(parentId, { [parent.field]: nextField }, [parent.field]);
@@ -3258,92 +3345,93 @@ var FateClient = class {
3258
3345
  target[key] = null;
3259
3346
  continue;
3260
3347
  }
3261
- if (Array.isArray(value)) if (nextSelection.items && typeof nextSelection.items === "object") {
3262
- const selection = nextSelection.items;
3263
- const fieldArgs = plan.args.get(fieldPath);
3264
- const listKey = getListKey(parentId, key, fieldArgs?.hash);
3265
- const listState = this.store.getListState(listKey);
3266
- const connection = { items: (listState ? getListEntries(listState) : value.map((item) => ({
3267
- cursor: void 0,
3268
- id: isNodeRef(item) ? getNodeRefId(item) : null
3269
- }))).map((entry, index) => {
3270
- const entityId = entry.id;
3271
- if (!entityId) return {
3272
- cursor: listState?.cursors?.[index],
3273
- node: null
3274
- };
3348
+ if (Array.isArray(value)) {
3349
+ if (nextSelection.items && typeof nextSelection.items === "object") {
3350
+ const selection = nextSelection.items;
3351
+ const fieldArgs = plan.args.get(fieldPath);
3352
+ const listKey = getListKey(parentId, key, fieldArgs?.hash);
3353
+ const listState = this.store.getListState(listKey);
3354
+ const connection = { items: (listState ? getListEntries(listState) : value.map((item) => ({
3355
+ cursor: void 0,
3356
+ id: isNodeRef(item) ? getNodeRefId(item) : null
3357
+ }))).map((entry, index) => {
3358
+ const entityId = entry.id;
3359
+ if (!entityId) return {
3360
+ cursor: listState?.cursors?.[index],
3361
+ node: null
3362
+ };
3363
+ ids.add(entityId);
3364
+ const record = this.store.read(entityId);
3365
+ const { id, type } = parseEntityId(entityId);
3366
+ const node = {
3367
+ __typename: type,
3368
+ id
3369
+ };
3370
+ if (record) walk(selection.node, record, node, entityId, fieldPath);
3371
+ const itemResult = { node: record ? node : null };
3372
+ if (selection.cursor === true) itemResult.cursor = entry.cursor;
3373
+ return itemResult;
3374
+ }) };
3375
+ if ("pagination" in nextSelection && nextSelection.pagination) {
3376
+ const paginationSelection = nextSelection.pagination;
3377
+ const storedPagination = listState?.pagination;
3378
+ if (storedPagination) {
3379
+ const pagination = {};
3380
+ if (paginationSelection.nextCursor === true) {
3381
+ if (storedPagination.nextCursor !== void 0) pagination.nextCursor = storedPagination.nextCursor;
3382
+ }
3383
+ if (paginationSelection.previousCursor === true) {
3384
+ if (storedPagination.previousCursor !== void 0) pagination.previousCursor = storedPagination.previousCursor;
3385
+ }
3386
+ if (paginationSelection.hasNext === true) pagination.hasNext = storedPagination.hasNext;
3387
+ if (paginationSelection.hasPrevious === true) pagination.hasPrevious = storedPagination.hasPrevious;
3388
+ if (Object.keys(pagination).length > 0) connection.pagination = pagination;
3389
+ } else connection.pagination = void 0;
3390
+ }
3391
+ const { id: ownerRawId, type: parentType } = parseEntityId(parentId);
3392
+ if (parentType) {
3393
+ const childType = this.getListNodeType(parentType, key);
3394
+ const metadata = {
3395
+ args: (() => {
3396
+ if (!fieldArgs?.value && ownerRawId === void 0) return;
3397
+ const value = fieldArgs?.value ? { ...fieldArgs.value } : {};
3398
+ if (ownerRawId !== void 0) value.id = ownerRawId;
3399
+ return value;
3400
+ })(),
3401
+ field: key,
3402
+ hash: fieldArgs?.hash,
3403
+ key: listKey,
3404
+ live: plan.live.get(fieldPath),
3405
+ owner: parentId,
3406
+ procedure: `${parentType}.${key}`,
3407
+ root: false,
3408
+ type: childType
3409
+ };
3410
+ Object.defineProperty(connection, ConnectionTag, {
3411
+ configurable: false,
3412
+ enumerable: false,
3413
+ value: metadata,
3414
+ writable: false
3415
+ });
3416
+ }
3417
+ target[key] = connection;
3418
+ } else target[key] = value.map((item) => {
3419
+ const entityId = isNodeRef(item) ? getNodeRefId(item) : null;
3420
+ if (!entityId) return item;
3275
3421
  ids.add(entityId);
3276
3422
  const record = this.store.read(entityId);
3277
3423
  const { id, type } = parseEntityId(entityId);
3278
- const node = {
3279
- __typename: type,
3280
- id
3281
- };
3282
- if (record) walk(selection.node, record, node, entityId, fieldPath);
3283
- const itemResult = { node: record ? node : null };
3284
- if (selection.cursor === true) itemResult.cursor = entry.cursor;
3285
- return itemResult;
3286
- }) };
3287
- if ("pagination" in nextSelection && nextSelection.pagination) {
3288
- const paginationSelection = nextSelection.pagination;
3289
- const storedPagination = listState?.pagination;
3290
- if (storedPagination) {
3291
- const pagination = {};
3292
- if (paginationSelection.nextCursor === true) {
3293
- if (storedPagination.nextCursor !== void 0) pagination.nextCursor = storedPagination.nextCursor;
3294
- }
3295
- if (paginationSelection.previousCursor === true) {
3296
- if (storedPagination.previousCursor !== void 0) pagination.previousCursor = storedPagination.previousCursor;
3297
- }
3298
- if (paginationSelection.hasNext === true) pagination.hasNext = storedPagination.hasNext;
3299
- if (paginationSelection.hasPrevious === true) pagination.hasPrevious = storedPagination.hasPrevious;
3300
- if (Object.keys(pagination).length > 0) connection.pagination = pagination;
3301
- } else connection.pagination = void 0;
3302
- }
3303
- const { id: ownerRawId, type: parentType } = parseEntityId(parentId);
3304
- if (parentType) {
3305
- const childType = this.getListNodeType(parentType, key);
3306
- const metadata = {
3307
- args: (() => {
3308
- if (!fieldArgs?.value && ownerRawId === void 0) return;
3309
- const value = fieldArgs?.value ? { ...fieldArgs.value } : {};
3310
- if (ownerRawId !== void 0) value.id = ownerRawId;
3311
- return value;
3312
- })(),
3313
- field: key,
3314
- hash: fieldArgs?.hash,
3315
- key: listKey,
3316
- live: plan.live.get(fieldPath),
3317
- owner: parentId,
3318
- procedure: `${parentType}.${key}`,
3319
- root: false,
3320
- type: childType
3321
- };
3322
- Object.defineProperty(connection, ConnectionTag, {
3323
- configurable: false,
3324
- enumerable: false,
3325
- value: metadata,
3326
- writable: false
3327
- });
3328
- }
3329
- target[key] = connection;
3330
- } else target[key] = value.map((item) => {
3331
- const entityId = isNodeRef(item) ? getNodeRefId(item) : null;
3332
- if (!entityId) return item;
3333
- ids.add(entityId);
3334
- const record = this.store.read(entityId);
3335
- const { id, type } = parseEntityId(entityId);
3336
- if (record) {
3337
- const node = {
3338
- __typename: type,
3339
- id
3340
- };
3341
- walk(nextSelection, record, node, entityId, fieldPath);
3342
- return node;
3343
- }
3344
- return null;
3345
- });
3346
- else if (isNodeRef(value)) {
3424
+ if (record) {
3425
+ const node = {
3426
+ __typename: type,
3427
+ id
3428
+ };
3429
+ walk(nextSelection, record, node, entityId, fieldPath);
3430
+ return node;
3431
+ }
3432
+ return null;
3433
+ });
3434
+ } else if (isNodeRef(value)) {
3347
3435
  const entityId = getNodeRefId(value);
3348
3436
  ids.add(entityId);
3349
3437
  const relatedRecord = this.store.read(entityId);