valdres 1.0.0-beta.4 → 1.0.0-beta.6

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.
package/dist/index.js CHANGED
@@ -97,10 +97,10 @@ var equal = (a, b, updatedAtomsSet) => {
97
97
  };
98
98
 
99
99
  // src/utils/isAtom.ts
100
- var isAtom = (state) => Object.hasOwn(state, "defaultValue");
100
+ var isAtom = (state) => state && Object.hasOwn(state, "defaultValue");
101
101
 
102
102
  // src/utils/isGlobalAtom.ts
103
- var isGlobalAtom = (state) => Object.hasOwn(state, "stores");
103
+ var isGlobalAtom = (state) => state && Object.hasOwn(state, "stores");
104
104
 
105
105
  // src/utils/isSelector.ts
106
106
  var isSelector = (state) => state && Object.hasOwn(state, "get");
@@ -123,17 +123,18 @@ var isPromiseLike = (object) => {
123
123
  };
124
124
 
125
125
  // src/utils/deepFreeze.ts
126
- var deepFreeze = (obj, seen = new WeakSet) => {
126
+ var deepFreeze = (obj, seen) => {
127
127
  if (obj === null || obj === undefined)
128
128
  return obj;
129
129
  if (typeof obj !== "object" && typeof obj !== "function")
130
130
  return obj;
131
- if (seen.has(obj) || Object.isFrozen(obj))
131
+ if (Object.isFrozen(obj) || seen?.has(obj))
132
132
  return obj;
133
- seen.add(obj);
134
133
  if (Array.isArray(obj)) {
135
134
  for (const item of obj) {
136
135
  if (item && typeof item === "object") {
136
+ seen ??= new WeakSet;
137
+ seen.add(obj);
137
138
  deepFreeze(item, seen);
138
139
  }
139
140
  }
@@ -142,6 +143,8 @@ var deepFreeze = (obj, seen = new WeakSet) => {
142
143
  for (const name of propNames) {
143
144
  const value = obj[name];
144
145
  if (value && typeof value === "object") {
146
+ seen ??= new WeakSet;
147
+ seen.add(obj);
145
148
  deepFreeze(value, seen);
146
149
  }
147
150
  }
@@ -149,10 +152,8 @@ var deepFreeze = (obj, seen = new WeakSet) => {
149
152
  return Object.freeze(obj);
150
153
  };
151
154
 
152
- // src/lib/isProd.ts
153
- var isProd = () => {
154
- return false;
155
- };
155
+ // src/lib/IS_PROD.ts
156
+ var IS_PROD = typeof process !== "undefined" && process.env != null && process.env.NODE_ENV === "production";
156
157
 
157
158
  // src/lib/setValueInData.ts
158
159
  var trackScopeValue = (key, data) => {
@@ -172,7 +173,7 @@ var trackScopeValue = (key, data) => {
172
173
  var setValueInData = (atom, value, data) => {
173
174
  const isNewAtomInScope = data.parent && Object.hasOwn(atom, "defaultValue") && !data.values.has(atom);
174
175
  let written;
175
- if (atom.mutable || isProd()) {
176
+ if (atom.mutable || IS_PROD) {
176
177
  data.values.set(atom, value);
177
178
  written = value;
178
179
  } else {
@@ -180,8 +181,14 @@ var setValueInData = (atom, value, data) => {
180
181
  data.values.set(atom, frozenValue);
181
182
  written = frozenValue;
182
183
  }
183
- if (isNewAtomInScope)
184
+ if (isNewAtomInScope) {
184
185
  trackScopeValue(atom, data);
186
+ const subs = data.subscriptions.get(atom);
187
+ if (subs) {
188
+ for (const sub of subs)
189
+ sub.reRoot?.();
190
+ }
191
+ }
185
192
  if (atom.maxAge !== undefined) {
186
193
  data.lastValueWriteAt.set(atom, Date.now());
187
194
  }
@@ -275,8 +282,10 @@ var recursivelyUpdateIndexes = (data, family) => {
275
282
  const childScopesWithFamily = data.scopeValueIndex.get(family);
276
283
  if (!childScopesWithFamily || childScopesWithFamily.size === 0)
277
284
  return;
285
+ const parentIndex = data.values.get(family).__index;
278
286
  for (const scopedData of childScopesWithFamily) {
279
287
  const index = scopedData.values.get(family).__index;
288
+ index.parentIndex = parentIndex;
280
289
  index.rendered = null;
281
290
  index.renderedArray = null;
282
291
  scopedData.values.set(family, renderAtomFamilyIndex(index));
@@ -619,6 +628,14 @@ var evaluate = (selector, data, initializedAtomsSet, circularDependencySet) => {
619
628
  };
620
629
 
621
630
  // src/lib/propagateUpdatedAtoms.ts
631
+ var notifyEntryFor = (notify, data) => {
632
+ let entry = notify.get(data);
633
+ if (entry === undefined) {
634
+ entry = { subscriptions: new Set, families: new Map };
635
+ notify.set(data, entry);
636
+ }
637
+ return entry;
638
+ };
622
639
  var reEvaluateSelector = (selector, data, updatedAtoms, depsChange, existingValue) => {
623
640
  try {
624
641
  const rawValue = evaluateSelector(selector, data, updatedAtoms, undefined, depsChange);
@@ -665,6 +682,35 @@ var callSubscribers = (subscriptions, families) => {
665
682
  if (hasError)
666
683
  throw firstError;
667
684
  };
685
+ var notifyDeferred = (notify) => {
686
+ let firstError;
687
+ let hasError = false;
688
+ for (const entry of notify.values()) {
689
+ if (entry.subscriptions.size > 0) {
690
+ try {
691
+ callSubscribers(entry.subscriptions, entry.families);
692
+ } catch (error) {
693
+ if (!hasError) {
694
+ firstError = error;
695
+ hasError = true;
696
+ }
697
+ }
698
+ }
699
+ }
700
+ if (hasError)
701
+ throw firstError;
702
+ };
703
+ var collectFamilyAtomsForNotify = (entry, changedByFamily) => {
704
+ for (const [family, atoms] of changedByFamily) {
705
+ let target = entry.families.get(family);
706
+ if (target === undefined) {
707
+ target = new Set;
708
+ entry.families.set(family, target);
709
+ }
710
+ for (const atom of atoms)
711
+ target.add(atom);
712
+ }
713
+ };
668
714
  var addSetToSet = (fromSet, toSet) => {
669
715
  if (fromSet && fromSet.size > 0) {
670
716
  for (const item of fromSet) {
@@ -672,20 +718,24 @@ var addSetToSet = (fromSet, toSet) => {
672
718
  }
673
719
  }
674
720
  };
675
- var propagateDeletedAtoms = (atoms, data, subscriptions = new Set, families = new Map, timestamp = performance.now()) => {
721
+ var propagateDeletedAtoms = (atoms, data, subscriptions = new Set, deletedFamilyAtoms = new Map, timestamp = performance.now(), notify) => {
722
+ const notifyEntry = notify ? notifyEntryFor(notify, data) : undefined;
723
+ if (notifyEntry) {
724
+ subscriptions = notifyEntry.subscriptions;
725
+ }
676
726
  const selectors = new Set;
677
727
  for (const atom of atoms) {
678
728
  addSetToSet(data.stateDependents.get(atom), selectors);
679
729
  addSetToSet(data.subscriptions.get(atom), subscriptions);
680
730
  if (isFamilyAtom(atom)) {
681
- if (!families.has(atom.family)) {
682
- families.set(atom.family, new Set);
731
+ if (!deletedFamilyAtoms.has(atom.family)) {
732
+ deletedFamilyAtoms.set(atom.family, new Set);
683
733
  }
684
- families.get(atom.family).add(atom);
734
+ deletedFamilyAtoms.get(atom.family).add(atom);
685
735
  }
686
736
  }
687
- if (families.size > 0) {
688
- for (const [family, familyAtoms] of families) {
737
+ if (deletedFamilyAtoms.size > 0) {
738
+ for (const [family, familyAtoms] of deletedFamilyAtoms) {
689
739
  addSetToSet(data.stateDependents.get(family), selectors);
690
740
  addSetToSet(data.subscriptions.get(family), subscriptions);
691
741
  if (familyAtoms.size === 0)
@@ -693,10 +743,12 @@ var propagateDeletedAtoms = (atoms, data, subscriptions = new Set, families = ne
693
743
  deleteFamilyAtomsFromSet(family, familyAtoms, data, timestamp);
694
744
  }
695
745
  }
696
- propagateDirtySelectors(atoms, selectors, data, subscriptions, families);
697
- if (families.size > 0 && data.scopes && data.scopes.size > 0) {
746
+ propagateDirtySelectors(atoms, selectors, data, subscriptions, deletedFamilyAtoms, false, notify);
747
+ if (notifyEntry)
748
+ collectFamilyAtomsForNotify(notifyEntry, deletedFamilyAtoms);
749
+ if (deletedFamilyAtoms.size > 0 && data.scopes && data.scopes.size > 0) {
698
750
  const scopeFamilies = new Map;
699
- for (const family of families.keys()) {
751
+ for (const family of deletedFamilyAtoms.keys()) {
700
752
  const scopesWithFamily = data.scopeValueIndex.get(family);
701
753
  if (scopesWithFamily) {
702
754
  for (const scope of scopesWithFamily) {
@@ -710,11 +762,11 @@ var propagateDeletedAtoms = (atoms, data, subscriptions = new Set, families = ne
710
762
  }
711
763
  }
712
764
  for (const [scope, familiesInScope] of scopeFamilies) {
713
- propagateInScope(familiesInScope, scope);
765
+ propagateInScope(familiesInScope, scope, false, notify);
714
766
  }
715
767
  }
716
768
  };
717
- var propagateAtomUpdate = (atoms, data, isInitOnly = false) => {
769
+ var propagateAtomUpdate = (atoms, data, isInitOnly = false, notify) => {
718
770
  if (atoms.length === 1) {
719
771
  const atom = atoms[0];
720
772
  if (!isFamilyAtom(atom) && !isAtomFamily(atom)) {
@@ -722,28 +774,32 @@ var propagateAtomUpdate = (atoms, data, isInitOnly = false) => {
722
774
  if ((!dependents || dependents.size === 0) && (!data.scopes || data.scopes.size === 0)) {
723
775
  const subs = data.subscriptions.get(atom);
724
776
  if (subs && subs.size > 0) {
725
- callSubscribers(subs);
777
+ if (notify)
778
+ addSetToSet(subs, notifyEntryFor(notify, data).subscriptions);
779
+ else
780
+ callSubscribers(subs);
726
781
  }
727
782
  return;
728
783
  }
729
784
  }
730
785
  }
731
- const subscriptions = new Set;
732
- const families = new Map;
786
+ const notifyEntry = notify ? notifyEntryFor(notify, data) : undefined;
787
+ const subscriptions = notifyEntry ? notifyEntry.subscriptions : new Set;
788
+ const updatedFamilyAtoms = new Map;
733
789
  const selectors = new Set;
734
790
  for (const atom of atoms) {
735
791
  addSetToSet(data.stateDependents.get(atom), selectors);
736
792
  addSetToSet(data.subscriptions.get(atom), subscriptions);
737
793
  if (isFamilyAtom(atom)) {
738
- if (!families.has(atom.family)) {
739
- families.set(atom.family, new Set);
794
+ if (!updatedFamilyAtoms.has(atom.family)) {
795
+ updatedFamilyAtoms.set(atom.family, new Set);
740
796
  }
741
- families.get(atom.family).add(atom);
797
+ updatedFamilyAtoms.get(atom.family).add(atom);
742
798
  }
743
799
  }
744
- if (families.size > 0) {
800
+ if (updatedFamilyAtoms.size > 0) {
745
801
  const timestamp = performance.now();
746
- for (const [family, familyAtoms] of families) {
802
+ for (const [family, familyAtoms] of updatedFamilyAtoms) {
747
803
  addSetToSet(data.stateDependents.get(family), selectors);
748
804
  addSetToSet(data.subscriptions.get(family), subscriptions);
749
805
  if (familyAtoms.size === 0)
@@ -751,30 +807,39 @@ var propagateAtomUpdate = (atoms, data, isInitOnly = false) => {
751
807
  addFamilyAtomsToSet(family, familyAtoms, data, timestamp);
752
808
  }
753
809
  }
754
- propagateDirtySelectors(atoms, selectors, data, subscriptions, families, isInitOnly);
755
810
  if (data.scopes && data.scopes.size > 0) {
756
- propagateToScopes(atoms, data, isInitOnly);
811
+ for (const atom of atoms) {
812
+ if (isAtomFamily(atom) && !updatedFamilyAtoms.has(atom)) {
813
+ recursivelyUpdateIndexes(data, atom);
814
+ }
815
+ }
816
+ }
817
+ propagateDirtySelectors(atoms, selectors, data, subscriptions, updatedFamilyAtoms, isInitOnly, notify);
818
+ if (notifyEntry)
819
+ collectFamilyAtomsForNotify(notifyEntry, updatedFamilyAtoms);
820
+ if (data.scopes && data.scopes.size > 0) {
821
+ propagateToScopes(atoms, data, isInitOnly, notify);
757
822
  }
758
823
  };
759
- var propagateInScope = (atoms, data, isInitOnly = false) => {
760
- const subscriptions = new Set;
824
+ var propagateInScope = (atoms, data, isInitOnly = false, notify) => {
825
+ const subscriptions = notify ? notifyEntryFor(notify, data).subscriptions : new Set;
761
826
  const families = new Map;
762
827
  const selectors = new Set;
763
828
  for (const atom of atoms) {
764
829
  addSetToSet(data.stateDependents.get(atom), selectors);
765
830
  }
766
- propagateDirtySelectors(atoms, selectors, data, subscriptions, families, isInitOnly);
831
+ propagateDirtySelectors(atoms, selectors, data, subscriptions, families, isInitOnly, notify);
767
832
  if (data.scopes && data.scopes.size > 0) {
768
- propagateToScopes(atoms, data, isInitOnly);
833
+ propagateToScopes(atoms, data, isInitOnly, notify);
769
834
  }
770
835
  };
771
- var propagateToScopes = (atoms, data, isInitOnly) => {
836
+ var propagateToScopes = (atoms, data, isInitOnly, notify) => {
772
837
  if (atoms.length === 1) {
773
838
  const atom = atoms[0];
774
839
  const shadowingScopes = isAtomFamily(atom) ? undefined : data.scopeValueIndex.get(atom);
775
840
  for (const [, scope] of data.scopes) {
776
841
  if (!shadowingScopes || !shadowingScopes.has(scope)) {
777
- propagateInScope(atoms, scope, isInitOnly);
842
+ propagateInScope(atoms, scope, isInitOnly, notify);
778
843
  }
779
844
  }
780
845
  return;
@@ -794,7 +859,7 @@ var propagateToScopes = (atoms, data, isInitOnly) => {
794
859
  }
795
860
  if (!anyShadowed) {
796
861
  for (const [, scope] of data.scopes) {
797
- propagateInScope(atoms, scope, isInitOnly);
862
+ propagateInScope(atoms, scope, isInitOnly, notify);
798
863
  }
799
864
  return;
800
865
  }
@@ -811,16 +876,16 @@ var propagateToScopes = (atoms, data, isInitOnly) => {
811
876
  }
812
877
  }
813
878
  if (atomsToUpdateInScope.length > 0) {
814
- propagateInScope(atomsToUpdateInScope, scope, isInitOnly);
879
+ propagateInScope(atomsToUpdateInScope, scope, isInitOnly, notify);
815
880
  }
816
881
  }
817
882
  };
818
- var propagateDirtySelectors = (updatedAtoms, selectors, data, subscriptions, families, isInitOnly = false) => {
883
+ var propagateDirtySelectors = (updatedAtoms, selectors, data, subscriptions, families, isInitOnly = false, notify) => {
819
884
  const updatedInitializedAtoms = new Set(updatedAtoms);
820
885
  if (selectors.size > 0) {
821
886
  propagateSelectorUpdates(selectors, data, subscriptions, updatedInitializedAtoms, isInitOnly);
822
887
  }
823
- if (subscriptions.size > 0) {
888
+ if (!notify && subscriptions.size > 0) {
824
889
  callSubscribers(subscriptions, families);
825
890
  }
826
891
  };
@@ -857,13 +922,22 @@ var propagateDownstreamTopo = (seeds, data, collectedSubscribers, updatedInitial
857
922
  ready.push(s);
858
923
  }
859
924
  const needsEval = new Set(seeds);
925
+ let graphMutated = false;
860
926
  const advance = (selector, propagateChange) => {
861
927
  const downstream = data.stateDependents.get(selector);
862
928
  if (!downstream)
863
929
  return;
864
930
  for (const d of downstream) {
865
- if (!closure.has(d))
931
+ if (!closure.has(d)) {
932
+ if (propagateChange) {
933
+ graphMutated = true;
934
+ closure.add(d);
935
+ pending.set(d, 0);
936
+ needsEval.add(d);
937
+ ready.push(d);
938
+ }
866
939
  continue;
940
+ }
867
941
  const c = (pending.get(d) ?? 0) - 1;
868
942
  pending.set(d, c);
869
943
  if (propagateChange)
@@ -897,17 +971,20 @@ var propagateDownstreamTopo = (seeds, data, collectedSubscribers, updatedInitial
897
971
  const wasValueUpdated = reEvaluateSelector(selector, data, updatedInitializedAtoms, depsChange, currentValue);
898
972
  const added = depsChange.added;
899
973
  const removed = depsChange.removed;
900
- if ((added || removed) && isLive(selector, data)) {
901
- if (added) {
902
- for (const dep of added) {
903
- onLiveDependencyAdded(dep, data);
904
- mountTransitiveDeps(dep, data);
974
+ if (added || removed) {
975
+ graphMutated = true;
976
+ if (isLive(selector, data)) {
977
+ if (added) {
978
+ for (const dep of added) {
979
+ onLiveDependencyAdded(dep, data);
980
+ mountTransitiveDeps(dep, data);
981
+ }
905
982
  }
906
- }
907
- if (removed) {
908
- for (const dep of removed) {
909
- onLiveDependencyRemoved(dep, data);
910
- unmountOrphanedDeps(dep, data);
983
+ if (removed) {
984
+ for (const dep of removed) {
985
+ onLiveDependencyRemoved(dep, data);
986
+ unmountOrphanedDeps(dep, data);
987
+ }
911
988
  }
912
989
  }
913
990
  }
@@ -916,6 +993,62 @@ var propagateDownstreamTopo = (seeds, data, collectedSubscribers, updatedInitial
916
993
  addSetToSet(subscribers, collectedSubscribers);
917
994
  }
918
995
  }
996
+ if (!graphMutated)
997
+ return;
998
+ let stranded;
999
+ for (const s of closure) {
1000
+ if (needsEval.has(s) && (pending.get(s) ?? 0) > 0) {
1001
+ if (!stranded)
1002
+ stranded = new Set;
1003
+ stranded.add(s);
1004
+ }
1005
+ }
1006
+ if (!stranded)
1007
+ return;
1008
+ let work = stranded;
1009
+ while (work.size > 0) {
1010
+ const next = new Set;
1011
+ for (const selector of work) {
1012
+ const currentValue = data.values.get(selector);
1013
+ if (isPromiseLike(currentValue) && isInitOnly)
1014
+ continue;
1015
+ const dependents = data.stateDependents.get(selector);
1016
+ const subscribers = data.subscriptions.get(selector);
1017
+ if (!isPromiseLike(currentValue) && (!dependents || dependents.size === 0) && (!subscribers || subscribers.size === 0)) {
1018
+ data.values.delete(selector);
1019
+ continue;
1020
+ }
1021
+ depsChange.added = undefined;
1022
+ depsChange.removed = undefined;
1023
+ const wasValueUpdated = reEvaluateSelector(selector, data, updatedInitializedAtoms, depsChange, currentValue);
1024
+ const added = depsChange.added;
1025
+ const removed = depsChange.removed;
1026
+ if ((added || removed) && isLive(selector, data)) {
1027
+ if (added) {
1028
+ for (const dep of added) {
1029
+ onLiveDependencyAdded(dep, data);
1030
+ mountTransitiveDeps(dep, data);
1031
+ }
1032
+ }
1033
+ if (removed) {
1034
+ for (const dep of removed) {
1035
+ onLiveDependencyRemoved(dep, data);
1036
+ unmountOrphanedDeps(dep, data);
1037
+ }
1038
+ }
1039
+ }
1040
+ if (wasValueUpdated) {
1041
+ if (subscribers)
1042
+ addSetToSet(subscribers, collectedSubscribers);
1043
+ const downstream = data.stateDependents.get(selector);
1044
+ if (downstream) {
1045
+ for (const d of downstream)
1046
+ next.add(d);
1047
+ }
1048
+ }
1049
+ }
1050
+ work = next;
1051
+ }
919
1052
  };
920
1053
  var propagateSelectorUpdates = (selectors, data, collectedSubscribers, updatedInitializedAtoms, isInitOnly = false) => {
921
1054
  if (selectors.size === 0)
@@ -971,7 +1104,7 @@ var propagateSelectorUpdates = (selectors, data, collectedSubscribers, updatedIn
971
1104
  // src/lib/isFunction.ts
972
1105
  var isFunction = (value) => typeof value === "function";
973
1106
 
974
- // src/lib/setAtom.ts
1107
+ // src/lib/resolvePendingDefault.ts
975
1108
  var resolvePendingDefault = (atom, data, value) => {
976
1109
  let cur = data;
977
1110
  while (cur) {
@@ -984,6 +1117,8 @@ var resolvePendingDefault = (atom, data, value) => {
984
1117
  cur = "parent" in cur ? cur.parent : undefined;
985
1118
  }
986
1119
  };
1120
+
1121
+ // src/lib/setAtom.ts
987
1122
  var handlePromise = (atom, promise, currentValue, data, skipOnSet) => {
988
1123
  setValueInData(atom, promise, data);
989
1124
  promise.then((resolvedValue) => {
@@ -1504,14 +1639,18 @@ var installMaxAgeTimer = (state, data) => {
1504
1639
  };
1505
1640
  var subscribe = (state, callback, requireDeepEqualCheckBeforeCallback, data) => {
1506
1641
  let parentUnsubscribe;
1642
+ let dropDelegate;
1507
1643
  if (data.parent && (!data.values.has(state) && isAtom(state) || isAtomFamily(state))) {
1508
1644
  const originalCallback = callback;
1509
1645
  parentUnsubscribe = subscribe(state, originalCallback, requireDeepEqualCheckBeforeCallback, data.parent);
1510
- callback = (arg) => {
1646
+ dropDelegate = () => {
1511
1647
  if (parentUnsubscribe) {
1512
1648
  parentUnsubscribe();
1513
1649
  parentUnsubscribe = undefined;
1514
1650
  }
1651
+ };
1652
+ callback = (arg) => {
1653
+ dropDelegate();
1515
1654
  originalCallback(arg);
1516
1655
  };
1517
1656
  } else if (!data.values.has(state) && isAtom(state)) {
@@ -1533,12 +1672,14 @@ var subscribe = (state, callback, requireDeepEqualCheckBeforeCallback, data) =>
1533
1672
  subscription = {
1534
1673
  callback,
1535
1674
  state,
1536
- requireDeepEqualCheckBeforeCallback
1675
+ requireDeepEqualCheckBeforeCallback,
1676
+ reRoot: dropDelegate
1537
1677
  };
1538
1678
  } else {
1539
1679
  subscription = {
1540
1680
  callback,
1541
- requireDeepEqualCheckBeforeCallback
1681
+ requireDeepEqualCheckBeforeCallback,
1682
+ reRoot: dropDelegate
1542
1683
  };
1543
1684
  }
1544
1685
  subscribers.add(subscription);
@@ -1562,17 +1703,25 @@ var subscribe = (state, callback, requireDeepEqualCheckBeforeCallback, data) =>
1562
1703
  };
1563
1704
  };
1564
1705
 
1565
- // src/lib/setAtoms.ts
1566
- var setAtoms = (pairs, data, initializedAtomsSet, skipOnSet = false) => {
1706
+ // src/lib/writeAtoms.ts
1707
+ var writeAtoms = (pairs, data, initializedAtomsSet, skipOnSet = false, onSetQueue) => {
1567
1708
  const updatedAtoms = [];
1568
1709
  for (let [atom, value] of pairs) {
1569
1710
  const currentValue = getState(atom, data, initializedAtomsSet);
1570
- const areEqual = isPromiseLike(currentValue) || isPromiseLike(value) ? currentValue === value : atom.equal(currentValue, value);
1711
+ const currentIsPromise = isPromiseLike(currentValue);
1712
+ const areEqual = currentIsPromise || isPromiseLike(value) ? currentValue === value : atom.equal(currentValue, value);
1571
1713
  if (!areEqual) {
1572
1714
  updatedAtoms.push(atom);
1573
1715
  value = setValueInData(atom, value, data);
1574
- if (atom.onSet && !skipOnSet)
1575
- atom.onSet(value, data);
1716
+ if (atom.onSet && !skipOnSet) {
1717
+ if (onSetQueue)
1718
+ onSetQueue.push([atom, value, data]);
1719
+ else
1720
+ atom.onSet(value, data);
1721
+ }
1722
+ if (currentIsPromise && !isPromiseLike(value)) {
1723
+ resolvePendingDefault(atom, data, value);
1724
+ }
1576
1725
  } else {
1577
1726
  setValueInData(atom, value, data);
1578
1727
  }
@@ -1582,6 +1731,12 @@ var setAtoms = (pairs, data, initializedAtomsSet, skipOnSet = false) => {
1582
1731
  updatedAtoms.push(atom);
1583
1732
  }
1584
1733
  }
1734
+ return updatedAtoms;
1735
+ };
1736
+
1737
+ // src/lib/setAtoms.ts
1738
+ var setAtoms = (pairs, data, initializedAtomsSet, skipOnSet = false) => {
1739
+ const updatedAtoms = writeAtoms(pairs, data, initializedAtomsSet, skipOnSet);
1585
1740
  if (updatedAtoms.length > 0) {
1586
1741
  propagateAtomUpdate(updatedAtoms, data);
1587
1742
  }
@@ -1671,7 +1826,7 @@ class Transaction {
1671
1826
  } else {
1672
1827
  resolved = value;
1673
1828
  }
1674
- if (!atom.mutable && !isProd() && resolved !== null && (typeof resolved === "object" || typeof resolved === "function")) {
1829
+ if (!atom.mutable && !IS_PROD && resolved !== null && (typeof resolved === "object" || typeof resolved === "function")) {
1675
1830
  resolved = deepFreeze(resolved);
1676
1831
  }
1677
1832
  this._atomMap.set(atom, resolved);
@@ -1761,15 +1916,60 @@ class Transaction {
1761
1916
  }
1762
1917
  };
1763
1918
  commit = () => {
1764
- const initializedAtomsSet = new Set;
1765
- setAtoms(this._atomMap, this.data, initializedAtomsSet);
1766
- if (this.deleteSet?.size) {
1767
- deleteAtomFamilyAtoms(this.deleteSet, this.data);
1768
- propagateDeletedAtoms([...this.deleteSet], this.data);
1919
+ if (!this._scopedTransactions) {
1920
+ const initializedAtomsSet = new Set;
1921
+ if (this._deleteSet?.size) {
1922
+ const notify2 = new Map;
1923
+ const updatedAtoms = writeAtoms(this._atomMap, this.data, initializedAtomsSet);
1924
+ if (updatedAtoms.length > 0) {
1925
+ propagateAtomUpdate(updatedAtoms, this.data, false, notify2);
1926
+ }
1927
+ deleteAtomFamilyAtoms(this._deleteSet, this.data);
1928
+ propagateDeletedAtoms([...this._deleteSet], this.data, undefined, undefined, undefined, notify2);
1929
+ notifyDeferred(notify2);
1930
+ } else {
1931
+ setAtoms(this._atomMap, this.data, initializedAtomsSet);
1932
+ }
1933
+ return;
1934
+ }
1935
+ const plan = [];
1936
+ this.collectStores(plan);
1937
+ for (let i = plan.length - 1;i >= 0; i--) {
1938
+ const entry = plan[i];
1939
+ const txn = entry.txn;
1940
+ entry.updatedAtoms = writeAtoms(txn._atomMap, entry.data, new Set, false, entry.onSets);
1941
+ if (txn._deleteSet?.size) {
1942
+ deleteAtomFamilyAtoms(txn._deleteSet, entry.data);
1943
+ entry.deleted = [...txn._deleteSet];
1944
+ }
1945
+ }
1946
+ for (const entry of plan) {
1947
+ for (const [atom, value, data] of entry.onSets) {
1948
+ atom.onSet(value, data);
1949
+ }
1950
+ }
1951
+ const notify = new Map;
1952
+ for (const { data, updatedAtoms, deleted } of plan) {
1953
+ if (updatedAtoms.length > 0) {
1954
+ propagateAtomUpdate(updatedAtoms, data, false, notify);
1955
+ }
1956
+ if (deleted) {
1957
+ propagateDeletedAtoms(deleted, data, undefined, undefined, undefined, notify);
1958
+ }
1769
1959
  }
1960
+ notifyDeferred(notify);
1961
+ };
1962
+ collectStores = (plan) => {
1963
+ plan.push({
1964
+ txn: this,
1965
+ data: this.data,
1966
+ updatedAtoms: [],
1967
+ deleted: undefined,
1968
+ onSets: []
1969
+ });
1770
1970
  if (this._scopedTransactions) {
1771
1971
  for (const [, scopedTxn] of this._scopedTransactions) {
1772
- scopedTxn.commit();
1972
+ scopedTxn.collectStores(plan);
1773
1973
  }
1774
1974
  }
1775
1975
  };
@@ -2513,38 +2713,27 @@ var index = (family, callback, options) => {
2513
2713
  const map = new Map;
2514
2714
  const index2 = (term) => {
2515
2715
  const termKey = stableStringify(term);
2516
- if (map.has(termKey))
2517
- return map.get(termKey);
2518
- const termIndexSelectorSet = new Set;
2519
- const termIndexSelectorMap = new Map;
2520
- const termIndexSelector = selector((get) => {
2521
- const allFamilyAtoms = new Set(get(family));
2522
- const deletedAtoms = termIndexSelectorSet.symmetricDifference(allFamilyAtoms);
2523
- const addedAtoms = allFamilyAtoms.difference(termIndexSelectorSet);
2524
- if (deletedAtoms.size || addedAtoms.size) {
2525
- deletedAtoms.forEach((atom2) => {
2526
- termIndexSelectorSet.delete(atom2);
2527
- termIndexSelectorMap.delete(atom2);
2716
+ const existing = map.get(termKey);
2717
+ if (existing)
2718
+ return existing;
2719
+ const predicateSelectors = new WeakMap;
2720
+ const predicateFor = (atom2) => {
2721
+ let sel = predicateSelectors.get(atom2);
2722
+ if (!sel) {
2723
+ sel = selector((get) => callback(get(atom2), term), {
2724
+ name: `index:callback:selector:${atom2.name}`
2528
2725
  });
2529
- addedAtoms.forEach((atom2) => {
2530
- termIndexSelectorSet.add(atom2);
2531
- termIndexSelectorMap.set(atom2, selector((get2) => callback(get2(atom2), term), {
2532
- name: `index:callback:selector:${atom2.name}`
2533
- }));
2534
- });
2535
- return new Set(termIndexSelectorSet);
2536
- } else {
2537
- return termIndexSelectorSet;
2726
+ predicateSelectors.set(atom2, sel);
2538
2727
  }
2539
- }, { name: `index:${options?.name}(${termKey})` });
2728
+ return sel;
2729
+ };
2540
2730
  const filteredSelector = selector((get) => {
2541
- const set = get(termIndexSelector);
2542
2731
  const res = [];
2543
- set.forEach((atom2) => {
2544
- if (get(termIndexSelectorMap.get(atom2))) {
2732
+ const members = get(family);
2733
+ for (const atom2 of members) {
2734
+ if (get(predicateFor(atom2)))
2545
2735
  res.push(atom2);
2546
- }
2547
- });
2736
+ }
2548
2737
  return res;
2549
2738
  }, {
2550
2739
  name: `index:${options?.name}:${termKey}:termSelector`
@@ -2592,9 +2781,9 @@ var isFamilySelector = (state) => isFamilyState(state) && isSelector(state);
2592
2781
 
2593
2782
  // src/index.ts
2594
2783
  if (globalThis.__valdres__) {
2595
- throw new Error(`Error! An instance of valdres is already loaded. Loaded: ${globalThis.__valdres__}. Attempted to load: ${"1.0.0-beta.4"}`);
2784
+ throw new Error(`Error! An instance of valdres is already loaded. Loaded: ${globalThis.__valdres__}. Attempted to load: ${"1.0.0-beta.6"}`);
2596
2785
  } else {
2597
- globalThis.__valdres__ = "1.0.0-beta.4";
2786
+ globalThis.__valdres__ = "1.0.0-beta.6";
2598
2787
  }
2599
2788
  export {
2600
2789
  store,
@@ -1,5 +1,6 @@
1
1
  import type { AtomFamily } from "./types/AtomFamily";
2
+ import type { AtomFamilyAtom } from "./types/AtomFamilyAtom";
2
3
  import type { Selector } from "./types/Selector";
3
4
  export declare const index: <Term, Value extends any, FamilyArgs extends [any, ...any[]] = [any, ...any[]]>(family: AtomFamily<Value, FamilyArgs>, callback: (value: Value, term: Term) => boolean, options?: {
4
5
  name?: string;
5
- }) => ((term: Term) => Selector<Term[]>);
6
+ }) => ((term: Term) => Selector<AtomFamilyAtom<Value, FamilyArgs>[]>);
@@ -0,0 +1 @@
1
+ export declare const IS_PROD: boolean;
@@ -14,4 +14,5 @@ export type AtomFamilyIndex = {
14
14
  export declare const cloneAtomFamilyIndex: (index: AtomFamilyIndex, parentIndexOverride?: AtomFamilyIndex) => AtomFamilyIndex;
15
15
  export declare const createAtomFamilyIndex: (parentIndex?: AtomFamilyIndex) => AtomFamilyIndex;
16
16
  export declare const deleteFamilyAtomsFromSet: (family: Family<any>, familyAtoms: Set<AtomFamilyAtom<any>>, data: StoreData, timestamp: number) => void;
17
+ export declare const recursivelyUpdateIndexes: (data: StoreData, family: Family<any>) => void;
17
18
  export declare const addFamilyAtomsToSet: (family: Family<any>, familyAtoms: Set<AtomFamilyAtom<any>>, data: StoreData, timestamp: number) => void;
@@ -7,7 +7,13 @@ import type { Subscription } from "../types/Subscription";
7
7
  export type { AtomFamilyIndex, } from "./atomFamilyIndex";
8
8
  export { cloneAtomFamilyIndex, createAtomFamilyIndex, renderAtomFamilyIndex, } from "./atomFamilyIndex";
9
9
  type AtomInput = Atom<any> | AtomFamilyAtom<any, any> | AtomFamily<any, any>;
10
- export declare const propagateDeletedAtoms: (atoms: AtomFamilyAtom<any, any>[], data: StoreData, subscriptions?: Set<Subscription>, families?: Map<AtomFamily<any>, Set<AtomFamilyAtom<any>>>, timestamp?: number) => void;
11
- export declare const propagateAtomUpdate: (atoms: AtomInput[], data: StoreData, isInitOnly?: boolean) => void;
12
- export declare const propagateInScope: (atoms: AtomInput[], data: StoreData, isInitOnly?: boolean) => void;
13
- export declare const propagateDirtySelectors: (updatedAtoms: Atom[], selectors: Set<Selector>, data: StoreData, subscriptions: Set<Subscription>, families: Map<AtomFamily<any>, Set<AtomFamilyAtom<any>>>, isInitOnly?: boolean) => void;
10
+ type NotifyStoreEntry = {
11
+ subscriptions: Set<Subscription>;
12
+ families: Map<AtomFamily<any>, Set<AtomFamilyAtom<any>>>;
13
+ };
14
+ export type NotifyTarget = Map<StoreData, NotifyStoreEntry>;
15
+ export declare const notifyDeferred: (notify: NotifyTarget) => void;
16
+ export declare const propagateDeletedAtoms: (atoms: AtomFamilyAtom<any, any>[], data: StoreData, subscriptions?: Set<Subscription>, deletedFamilyAtoms?: Map<AtomFamily<any>, Set<AtomFamilyAtom<any>>>, timestamp?: number, notify?: NotifyTarget) => void;
17
+ export declare const propagateAtomUpdate: (atoms: AtomInput[], data: StoreData, isInitOnly?: boolean, notify?: NotifyTarget) => void;
18
+ export declare const propagateInScope: (atoms: AtomInput[], data: StoreData, isInitOnly?: boolean, notify?: NotifyTarget) => void;
19
+ export declare const propagateDirtySelectors: (updatedAtoms: Atom[], selectors: Set<Selector>, data: StoreData, subscriptions: Set<Subscription>, families: Map<AtomFamily<any>, Set<AtomFamilyAtom<any>>>, isInitOnly?: boolean, notify?: NotifyTarget) => void;
@@ -0,0 +1,32 @@
1
+ import type { Atom } from "../types/Atom";
2
+ import type { StoreData } from "../types/StoreData";
3
+ /** Resolve any outstanding pending-default suspense placeholder for `atom`
4
+ * with `value` and remove it. The placeholder may have been registered in a
5
+ * parent store (atoms with no `defaultValue` init in whichever scope first
6
+ * reads them, which `getState` resolves by walking up to root), so we walk
7
+ * the scope chain rather than only checking `data`.
8
+ *
9
+ * Called by every write path that lands a value on an atom that may carry a
10
+ * placeholder — `setAtom` (sync + async resolve) and `writeAtoms` (the
11
+ * transaction path).
12
+ *
13
+ * Caller contract: pass a SETTLED value, never a promise. The placeholder must
14
+ * resolve to the eventual real value, so an in-flight promise stored
15
+ * mid-sequence must not consume it — a later settled write still has to find
16
+ * the entry here. `setAtom` guarantees this by routing promises through
17
+ * `handlePromise` instead; `writeAtoms` by gating on `!isPromiseLike(value)`.
18
+ *
19
+ * Optional optimization (not required): a caller that already knows the prior
20
+ * value may skip the call when it isn't promise-like — a placeholder is always
21
+ * stored as a promise, so a non-promise prior value can't have one. `writeAtoms`
22
+ * does this (gating on `isPromiseLike(currentValue)`) to keep the write hot
23
+ * path off this function. `setAtom` calls unconditionally; the walk is a couple
24
+ * of WeakMap lookups that bail immediately when there's nothing to resolve.
25
+ *
26
+ * Idempotent and commit-safe: the entry is deleted on first resolve and a
27
+ * settled promise ignores later `resolve` calls, so calling this from more
28
+ * than one store pass in a single cross-scope commit is harmless. `resolve`
29
+ * only schedules the placeholder's `.then` microtasks — it runs no subscriber
30
+ * code synchronously — so calling it during a transaction's write phase
31
+ * cannot expose a half-applied commit. */
32
+ export declare const resolvePendingDefault: <Value>(atom: Atom<Value>, data: StoreData, value: Value) => void;
@@ -26,6 +26,7 @@ export declare class Transaction {
26
26
  execute: (callback: TransactionFn, autoCommit?: boolean) => any;
27
27
  private txnCommit;
28
28
  commit: () => void;
29
+ private collectStores;
29
30
  private get selectorCache();
30
31
  private get selectorDependencies();
31
32
  private get deleteSet();
@@ -0,0 +1,21 @@
1
+ import type { Atom } from "../types/Atom";
2
+ import type { StoreData } from "../types/StoreData";
3
+ /** A deferred onSet invocation: the hook, the written value, and the store it
4
+ * was written to. Collected during the write phase of a cross-scope commit so
5
+ * hooks fire only once the whole tree has been written. */
6
+ export type DeferredOnSet = [Atom<any>, any, StoreData];
7
+ /**
8
+ * Write phase for a single store. Applies every value in `pairs` to
9
+ * `data.values`, returning the atoms whose value actually changed (the
10
+ * propagation set), merged with any atoms lazily initialized during the
11
+ * equality checks. This does NOT propagate — see `setAtoms` (single-store
12
+ * fast path) or `Transaction.commit` (cross-scope path) for the notify pass.
13
+ *
14
+ * onSet handling:
15
+ * - `skipOnSet` true → never fire onSet.
16
+ * - `onSetQueue` given → defer onSet by pushing `[atom, value, data]`. The
17
+ * cross-scope commit uses this so a hook never observes a half-applied
18
+ * transaction — it fires only after every store's writes have landed.
19
+ * - otherwise → fire onSet inline (single-store path, unchanged).
20
+ */
21
+ export declare const writeAtoms: (pairs: Map<Atom<any>, any>, data: StoreData, initializedAtomsSet: Set<Atom>, skipOnSet?: boolean, onSetQueue?: DeferredOnSet[]) => Atom[];
@@ -3,9 +3,16 @@ export type AtomFamilySubscription<Value extends any = any, Args extends [any, .
3
3
  state: AtomFamily<Value, Args>;
4
4
  callback: (...args: Args) => void;
5
5
  requireDeepEqualCheckBeforeCallback: boolean;
6
+ /** Present only on a scope subscription that is delegating to an ancestor.
7
+ * Drops the ancestor delegate (idempotent). Called eagerly when the scope
8
+ * shadows the state so an ancestor write in the same transaction commit
9
+ * does not also notify this subscription. */
10
+ reRoot?: () => void;
6
11
  };
7
12
  export type SimpleSubscription = {
8
13
  requireDeepEqualCheckBeforeCallback: boolean;
9
14
  callback: () => void;
15
+ /** See AtomFamilySubscription.reRoot. */
16
+ reRoot?: () => void;
10
17
  };
11
18
  export type Subscription = SimpleSubscription | AtomFamilySubscription;
@@ -1 +1 @@
1
- export declare const deepFreeze: (obj: any, seen?: WeakSet<WeakKey>) => any;
1
+ export declare const deepFreeze: (obj: any, seen?: WeakSet<object>) => any;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "valdres",
3
- "version": "1.0.0-beta.4",
3
+ "version": "1.0.0-beta.6",
4
4
  "license": "MIT",
5
5
  "author": {
6
6
  "name": "Eigil Sagafos"
@@ -1 +0,0 @@
1
- export declare const isProd: () => boolean;