valdres 0.2.0-pre.8 → 1.0.0-beta.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 (51) hide show
  1. package/dist/index.js +1665 -565
  2. package/dist/types/cacheMeta.d.ts +4 -0
  3. package/dist/types/index.d.ts +6 -2
  4. package/dist/types/indexConstructor.d.ts +1 -1
  5. package/dist/types/lib/asyncDependencyTracking.d.ts +25 -0
  6. package/dist/types/lib/atomFamilyIndex.d.ts +17 -0
  7. package/dist/types/lib/createAtomFamily.d.ts +1 -1
  8. package/dist/types/lib/createGlobalAtomFamily.d.ts +1 -1
  9. package/dist/types/lib/createStoreData.d.ts +5 -2
  10. package/dist/types/lib/familyKey.d.ts +2 -0
  11. package/dist/types/lib/getState.d.ts +1 -1
  12. package/dist/types/lib/initAtom.d.ts +1 -1
  13. package/dist/types/lib/initSelector.d.ts +19 -5
  14. package/dist/types/lib/isFunction.d.ts +1 -1
  15. package/dist/types/lib/maxAgeCleanups.d.ts +4 -0
  16. package/dist/types/lib/mountAtom.d.ts +36 -0
  17. package/dist/types/lib/propagateUpdatedAtoms.d.ts +5 -4
  18. package/dist/types/lib/setAtom.d.ts +2 -1
  19. package/dist/types/lib/setValueInData.d.ts +4 -1
  20. package/dist/types/lib/subscribe.d.ts +2 -0
  21. package/dist/types/lib/transaction.d.ts +34 -5
  22. package/dist/types/lib/unsubscribe.d.ts +1 -1
  23. package/dist/types/selector.d.ts +2 -2
  24. package/dist/types/selectorFamily.d.ts +1 -1
  25. package/dist/types/store.d.ts +6 -1
  26. package/dist/types/types/Atom.d.ts +17 -3
  27. package/dist/types/types/AtomFamily.d.ts +2 -1
  28. package/dist/types/types/AtomFamilyAtom.d.ts +1 -1
  29. package/dist/types/types/AtomOnInit.d.ts +7 -0
  30. package/dist/types/types/AtomOnMount.d.ts +14 -0
  31. package/dist/types/types/AtomOptions.d.ts +6 -5
  32. package/dist/types/types/GlobalAtom.d.ts +6 -0
  33. package/dist/types/types/GlobalAtomSetSelfFunc.d.ts +2 -1
  34. package/dist/types/types/Reactive.d.ts +3 -0
  35. package/dist/types/types/Selector.d.ts +7 -2
  36. package/dist/types/types/SelectorFamily.d.ts +3 -1
  37. package/dist/types/types/SetAtomValue.d.ts +1 -1
  38. package/dist/types/types/Store.d.ts +9 -4
  39. package/dist/types/types/StoreData.d.ts +14 -4
  40. package/dist/types/types/TransactionFn.d.ts +2 -2
  41. package/dist/types/utils/isAtomFamily.d.ts +1 -1
  42. package/dist/types/utils/isFamilyAtom.d.ts +1 -1
  43. package/dist/types/utils/isFamilySelector.d.ts +1 -1
  44. package/dist/types/utils/isFamilyState.d.ts +1 -1
  45. package/dist/types/utils/isGlobalAtom.d.ts +2 -0
  46. package/dist/types/utils/isSelectorFamily.d.ts +1 -1
  47. package/dist/types/utils/resolveReactive.d.ts +4 -0
  48. package/package.json +1 -1
  49. package/dist/types/createStoreWithSelectorSet.d.ts +0 -2
  50. package/dist/types/lib/atomFamilyAtom.d.ts +0 -8
  51. package/dist/types/types/AtomFamilyGlobalAtom.d.ts +0 -3
package/dist/index.js CHANGED
@@ -99,69 +99,38 @@ var equal = (a, b, updatedAtomsSet) => {
99
99
  // src/utils/isAtom.ts
100
100
  var isAtom = (state) => Object.hasOwn(state, "defaultValue");
101
101
 
102
+ // src/utils/isGlobalAtom.ts
103
+ var isGlobalAtom = (state) => Object.hasOwn(state, "stores");
104
+
105
+ // src/utils/isSelector.ts
106
+ var isSelector = (state) => state && Object.hasOwn(state, "get");
107
+
108
+ // src/utils/isAtomFamily.ts
109
+ var isAtomFamily = (state) => state && Object.hasOwn(state, "__valdresAtomFamilyMap");
110
+
102
111
  // src/utils/isFamilyState.ts
103
112
  var isFamilyState = (state) => state && Object.hasOwn(state, "family");
104
113
 
105
114
  // src/utils/isFamilyAtom.ts
106
115
  var isFamilyAtom = (state) => isFamilyState(state) && isAtom(state);
107
116
 
117
+ // src/utils/isSelectorFamily.ts
118
+ var isSelectorFamily = (state) => state && Object.hasOwn(state, "__valdresSelectorFamilyMap");
119
+
108
120
  // src/utils/isPromiseLike.ts
109
121
  var isPromiseLike = (object) => {
110
122
  return object && object.then && typeof object.then === "function";
111
123
  };
112
124
 
113
- // src/errors/lib/generateSelectorTrace.ts
114
- var generateSelectorTrace = (selectors) => {
115
- const lastIndex = selectors.length - 1;
116
- return [...selectors].reverse().map((selector, index) => {
117
- const name = selector.name ?? "Anonymous Selector";
118
- if (index === 0) {
119
- return `[START] ${name}`;
120
- } else if (index === lastIndex) {
121
- return `[CRASH] ${name}`;
122
- } else {
123
- return ` ${" ".repeat(index)}${name}`;
124
- }
125
- }).join(`
126
- `);
127
- };
128
-
129
- // src/errors/SelectorEvaluationError.ts
130
- class SelectorEvaluationError extends Error {
131
- selectors;
132
- constructor(cause) {
133
- super();
134
- this.cause = cause;
135
- this.selectors = [];
136
- }
137
- track(selector) {
138
- this.selectors.push(selector);
139
- }
140
- get message() {
141
- const firstSelectorName = this.selectors[0].name ?? "Anonymous Selector";
142
- return `Selector eval crashed in '${firstSelectorName}'
143
- ${generateSelectorTrace(this.selectors)}`;
144
- }
145
- }
146
-
147
- // src/utils/isAtomFamily.ts
148
- var isAtomFamily = (state) => state && Object.hasOwn(state, "__valdresAtomFamilyMap");
149
-
150
- // src/utils/isSelector.ts
151
- var isSelector = (state) => state && Object.hasOwn(state, "get");
152
-
153
- // src/utils/isSelectorFamily.ts
154
- var isSelectorFamily = (state) => state && Object.hasOwn(state, "__valdresSelectorFamilyMap");
155
-
156
- // src/lib/isFunction.ts
157
- var isFunction = (value) => typeof value === "function";
158
-
159
125
  // src/utils/deepFreeze.ts
160
126
  var deepFreeze = (obj, seen = new WeakSet) => {
161
- if (obj === null || obj === undefined || seen.has(obj))
127
+ if (obj === null || obj === undefined)
128
+ return obj;
129
+ if (typeof obj !== "object" && typeof obj !== "function")
162
130
  return obj;
163
- if (obj && typeof obj === "object")
164
- seen.add(obj);
131
+ if (seen.has(obj) || Object.isFrozen(obj))
132
+ return obj;
133
+ seen.add(obj);
165
134
  if (Array.isArray(obj)) {
166
135
  for (const item of obj) {
167
136
  if (item && typeof item === "object") {
@@ -186,120 +155,196 @@ var isProd = () => {
186
155
  };
187
156
 
188
157
  // src/lib/setValueInData.ts
158
+ var trackScopeValue = (key, data) => {
159
+ const parent = data.parent;
160
+ let set = parent.scopeValueIndex.get(key);
161
+ if (!set) {
162
+ set = new Set;
163
+ parent.scopeValueIndex.set(key, set);
164
+ }
165
+ set.add(data);
166
+ data.scopeIndexKeys.add(key);
167
+ };
189
168
  var setValueInData = (atom, value, data) => {
169
+ const isNewAtomInScope = "parent" in data && Object.hasOwn(atom, "defaultValue") && !data.values.has(atom);
170
+ let written;
190
171
  if (atom.mutable || isProd()) {
191
172
  data.values.set(atom, value);
192
- return value;
173
+ written = value;
193
174
  } else {
194
- const frozenValue = deepFreeze(value);
175
+ const frozenValue = value !== null && (typeof value === "object" || typeof value === "function") ? deepFreeze(value) : value;
195
176
  data.values.set(atom, frozenValue);
196
- return frozenValue;
177
+ written = frozenValue;
197
178
  }
179
+ if (isNewAtomInScope)
180
+ trackScopeValue(atom, data);
181
+ if (atom.maxAge !== undefined) {
182
+ data.lastValueWriteAt.set(atom, Date.now());
183
+ }
184
+ return written;
198
185
  };
199
186
 
200
- // src/lib/setAtom.ts
201
- var setAtom = (atom, newValue, data, skipOnSet = false) => {
202
- const initializedAtomsSet = new Set;
203
- const currentValue = getState(atom, data, initializedAtomsSet);
204
- if (isFunction(newValue)) {
205
- newValue = newValue(currentValue);
206
- if (isPromiseLike(newValue) || isPromiseLike(currentValue))
207
- throw new Error("Todo, how should we handle this?");
187
+ // src/lib/atomFamilyIndex.ts
188
+ var getAtomFamilyRenderedMap = (index) => {
189
+ if (index.rendered)
190
+ return index.rendered;
191
+ const result = new Map(index.parentIndex ? getAtomFamilyRenderedMap(index.parentIndex) : undefined);
192
+ for (const [atom, timestamp] of index.created) {
193
+ result.set(atom, timestamp);
194
+ }
195
+ for (const [atom, timestamp] of index.deleted) {
196
+ result.delete(atom);
197
+ }
198
+ index.rendered = result;
199
+ return result;
200
+ };
201
+ var getSortedKeysByValues = (map) => {
202
+ return Array.from(map.entries()).sort((a, b) => a[1] > b[1] ? 1 : a[1] < b[1] ? -1 : 0).map((entry) => entry[0]);
203
+ };
204
+ var renderAtomFamilyIndex = (index) => {
205
+ if (index.renderedArray) {
206
+ return index.renderedArray;
207
+ }
208
+ const renderedMap = getAtomFamilyRenderedMap(index);
209
+ const array = getSortedKeysByValues(renderedMap);
210
+ array.__index = index;
211
+ index.renderedArray = array;
212
+ return array;
213
+ };
214
+ var cloneAtomFamilyIndex = (index, parentIndexOverride) => {
215
+ return {
216
+ created: new Map(index.created),
217
+ deleted: new Map(index.deleted),
218
+ rendered: null,
219
+ renderedArray: null,
220
+ parentIndex: parentIndexOverride || index.parentIndex
221
+ };
222
+ };
223
+ var createAtomFamilyIndex = (parentIndex) => {
224
+ return {
225
+ created: new Map,
226
+ deleted: new Map,
227
+ rendered: null,
228
+ renderedArray: null,
229
+ parentIndex
230
+ };
231
+ };
232
+ var deleteFamilyAtomsFromSet = (family, familyAtoms, data, timestamp) => {
233
+ if (familyAtoms.size === 0)
234
+ return;
235
+ const index = findFamilyIndex(family, data);
236
+ for (const atom of familyAtoms) {
237
+ index.deleted.set(atom, timestamp);
238
+ }
239
+ index.rendered = null;
240
+ index.renderedArray = null;
241
+ data.values.set(family, renderAtomFamilyIndex(index));
242
+ recursivelyUpdateIndexes(data, family);
243
+ };
244
+ var initFamilyIndex = (family, data) => {
245
+ if (data.values.has(family))
246
+ return data.values.get(family).__index;
247
+ let parentIndex;
248
+ if ("parent" in data) {
249
+ parentIndex = initFamilyIndex(family, data.parent);
250
+ if (!parentIndex)
251
+ throw new Error("Parent index is missing");
252
+ }
253
+ const index = createAtomFamilyIndex(parentIndex);
254
+ data.values.set(family, renderAtomFamilyIndex(index));
255
+ if ("parent" in data) {
256
+ trackScopeValue(family, data);
257
+ }
258
+ return index;
259
+ };
260
+ var findFamilyIndex = (family, data) => {
261
+ if (!data.values.has(family)) {
262
+ initFamilyIndex(family, data);
208
263
  }
209
- if (atom.equal(currentValue, newValue))
210
- return newValue;
211
- newValue = setValueInData(atom, newValue, data);
212
- if (atom.onSet && !skipOnSet)
213
- atom.onSet(newValue, data);
214
- if (currentValue?.__isEmptyAtomPromise__) {
215
- currentValue.__resolveEmptyAtomPromise__(newValue);
264
+ const value = data.values.get(family);
265
+ if (!value?.__index) {
266
+ throw new Error("Family index is missing");
216
267
  }
217
- if (initializedAtomsSet.size > 0) {
218
- const all = new Set([...initializedAtomsSet, atom]);
219
- propagateUpdatedAtoms([...all], data);
220
- } else {
221
- propagateUpdatedAtoms([atom], data);
268
+ return value.__index;
269
+ };
270
+ var recursivelyUpdateIndexes = (data, family) => {
271
+ const childScopesWithFamily = data.scopeValueIndex.get(family);
272
+ if (!childScopesWithFamily || childScopesWithFamily.size === 0)
273
+ return;
274
+ for (const scopedData of childScopesWithFamily) {
275
+ const index = scopedData.values.get(family).__index;
276
+ index.rendered = null;
277
+ index.renderedArray = null;
278
+ scopedData.values.set(family, renderAtomFamilyIndex(index));
279
+ recursivelyUpdateIndexes(scopedData, family);
222
280
  }
223
- return newValue;
281
+ };
282
+ var addFamilyAtomsToSet = (family, familyAtoms, data, timestamp) => {
283
+ if (familyAtoms.size === 0)
284
+ return;
285
+ const index = findFamilyIndex(family, data);
286
+ if (!index)
287
+ throw new Error("index not found");
288
+ for (const atom of familyAtoms) {
289
+ index.created.set(atom, timestamp);
290
+ index.deleted.delete(atom);
291
+ }
292
+ index.rendered = null;
293
+ index.renderedArray = null;
294
+ data.values.set(family, renderAtomFamilyIndex(index));
295
+ recursivelyUpdateIndexes(data, family);
224
296
  };
225
297
 
226
- // src/lib/initAtom.ts
227
- var getAtomInitValue = (atom, data, initializedAtomsSet) => {
228
- if (atom.defaultValue === undefined) {
229
- let promiseResolve;
230
- const promise = new Promise((resolve) => {
231
- promiseResolve = resolve;
232
- });
233
- promise.__isEmptyAtomPromise__ = true;
234
- promise.__resolveEmptyAtomPromise__ = promiseResolve;
235
- return promise;
236
- } else if (typeof atom.defaultValue === "function") {
237
- const value = atom.defaultValue();
238
- if (isPromiseLike(value)) {
239
- value.then((resolvedValue) => {
240
- setValueInData(atom, resolvedValue, data);
241
- propagateUpdatedAtoms([atom], data);
242
- });
298
+ // src/errors/lib/generateSelectorTrace.ts
299
+ var generateSelectorTrace = (selectors) => {
300
+ const lastIndex = selectors.length - 1;
301
+ return [...selectors].reverse().map((selector, index) => {
302
+ const name = selector.name ?? "Anonymous Selector";
303
+ if (index === 0) {
304
+ return `[START] ${name}`;
305
+ } else if (index === lastIndex) {
306
+ return `[CRASH] ${name}`;
307
+ } else {
308
+ return ` ${" ".repeat(index)}${name}`;
243
309
  }
244
- return value;
245
- } else if (isSelector(atom.defaultValue)) {
246
- return getState(atom.defaultValue, data, initializedAtomsSet);
247
- } else {
248
- return atom.defaultValue;
249
- }
250
- };
251
- var initAtom = (atom, data, initializedAtomsSet) => {
252
- const tmpVal = getAtomInitValue(atom, data, initializedAtomsSet);
253
- let value = setValueInData(atom, tmpVal, data);
254
- if (atom.onInit)
255
- atom.onInit((newVal) => {
256
- value = newVal;
257
- setAtom(atom, newVal, data, true);
258
- }, data);
310
+ }).join(`
311
+ `);
259
312
  };
260
313
 
261
- // src/lib/getState.ts
262
- function getState(state, data, initializedAtomsSet, circularDependencySet) {
263
- if (data.values.has(state))
264
- return data.values.get(state);
265
- if (isAtom(state)) {
266
- if ("parent" in data)
267
- return getState(state, data.parent, initializedAtomsSet, circularDependencySet);
268
- initAtom(state, data, initializedAtomsSet);
269
- initializedAtomsSet.add(state);
270
- return data.values.get(state);
314
+ // src/errors/SelectorEvaluationError.ts
315
+ class SelectorEvaluationError extends Error {
316
+ selectors;
317
+ constructor(cause) {
318
+ super();
319
+ this.cause = cause;
320
+ this.selectors = [];
271
321
  }
272
- if (isSelector(state)) {
273
- initSelector(state, data, initializedAtomsSet, circularDependencySet);
274
- return data.values.get(state);
322
+ track(selector) {
323
+ this.selectors.push(selector);
275
324
  }
276
- if (isAtomFamily(state)) {
277
- if ("parent" in data) {
278
- const closestData = findClosestStoreWithAtomInitialized(state, data);
279
- return getState(state, closestData, initializedAtomsSet, circularDependencySet);
280
- }
281
- data.values.set(state, []);
282
- initializedAtomsSet.add(state);
283
- return data.values.get(state);
325
+ get message() {
326
+ const firstSelectorName = this.selectors[0].name ?? "Anonymous Selector";
327
+ return `Selector eval crashed in '${firstSelectorName}'
328
+ ${generateSelectorTrace(this.selectors)}`;
284
329
  }
285
- if (isSelectorFamily(state)) {
286
- const array = Array.from(state.__valdresSelectorFamilyMap.keys());
287
- if (equal(array, state._keyArray))
288
- return state._keyArray;
289
- state._keyArray = array;
290
- return array;
330
+ }
331
+
332
+ // src/errors/SelectorCircularDependencyError.ts
333
+ class SelectorCircularDependencyError extends SelectorEvaluationError {
334
+ constructor() {
335
+ super();
336
+ }
337
+ get message() {
338
+ const firstSelectorName = this.selectors[0].name ?? "Anonymous Selector";
339
+ return `Circular dependency detected in '${firstSelectorName}'
340
+ ${generateSelectorTrace(this.selectors)}`;
291
341
  }
292
- throw new Error("Invalid object passed to get");
293
342
  }
294
- var findClosestStoreWithAtomInitialized = (atom, data) => {
295
- if ("parent" in data === false)
296
- return data;
297
- if (data.values.has(atom))
298
- return data;
299
- return findClosestStoreWithAtomInitialized(atom, data.parent);
300
- };
301
343
 
302
- // src/lib/initSelector.ts
344
+ // src/lib/asyncDependencyTracking.ts
345
+ var pendingAsyncDeps = new WeakMap;
346
+ var latestEvalContext = new WeakMap;
347
+
303
348
  class SuspendAndWaitForResolveError extends Error {
304
349
  promise;
305
350
  constructor(promise) {
@@ -307,6 +352,9 @@ class SuspendAndWaitForResolveError extends Error {
307
352
  this.promise = promise;
308
353
  }
309
354
  }
355
+ var isSuspendError = (e) => {
356
+ return e instanceof SuspendAndWaitForResolveError;
357
+ };
310
358
  var getOrInitDependentsSet = (state, data) => {
311
359
  const set = data.stateDependents.get(state);
312
360
  if (set)
@@ -315,20 +363,103 @@ var getOrInitDependentsSet = (state, data) => {
315
363
  data.stateDependents.set(state, newSet);
316
364
  return newSet;
317
365
  };
318
- var evaluateSelector = (selector, data, initializedAtomsSet, circularDependencySet = new WeakSet) => {
319
- const updatedDependencies = new Set;
320
- if (circularDependencySet.has(selector)) {}
366
+ var lateGet = (state, selector, data) => {
367
+ let deps = data.stateDependencies.get(selector);
368
+ if (!deps) {
369
+ deps = new Set;
370
+ data.stateDependencies.set(selector, deps);
371
+ }
372
+ const isNewDep = !deps.has(state);
373
+ if (isNewDep) {
374
+ deps.add(state);
375
+ const dependents = getOrInitDependentsSet(state, data);
376
+ dependents.add(selector);
377
+ }
378
+ const lateInitSet = new Set;
379
+ try {
380
+ return getState(state, data, lateInitSet);
381
+ } finally {
382
+ if (isNewDep && isTransitivelySubscribed(selector, data)) {
383
+ mountTransitiveDeps(state, data);
384
+ }
385
+ }
386
+ };
387
+ var cleanUpRejectedPromise = (selector, data, promise) => {
388
+ if (data.values.has(selector) && data.values.get(selector) !== promise)
389
+ return;
390
+ data.values.delete(selector);
391
+ };
392
+
393
+ // src/lib/initSelector.ts
394
+ class NeedsInitError {
395
+ selector;
396
+ constructor(selector) {
397
+ this.selector = selector;
398
+ }
399
+ }
400
+ var _evalDepth = 0;
401
+ var _inTrampoline = false;
402
+ var MAX_EVAL_DEPTH = 100;
403
+ var sharedCircularDepSet = new WeakSet;
404
+ var neverAbortedSignal = new AbortController().signal;
405
+ var syncOptionsCache = new WeakMap;
406
+ var evaluateSelector = (selector, data, initializedAtomsSet, circularDependencySet = sharedCircularDepSet, addedDepsOut, removedDepsOut) => {
407
+ const currentDependencies = data.stateDependencies.get(selector);
408
+ const updatedDepsArray = [];
409
+ let depsChanged = false;
410
+ let evaluationComplete = false;
411
+ const prevCtx = latestEvalContext.get(selector);
412
+ if (prevCtx)
413
+ prevCtx.revoked = true;
414
+ const evalCtx = { revoked: false };
415
+ latestEvalContext.set(selector, evalCtx);
416
+ if (circularDependencySet.has(selector)) {
417
+ throw new SelectorCircularDependencyError;
418
+ }
321
419
  circularDependencySet.add(selector);
420
+ const prev = data.abortControllers.get(selector);
421
+ let options;
422
+ if (prev === false) {
423
+ let cached = syncOptionsCache.get(data);
424
+ if (!cached) {
425
+ cached = { signal: neverAbortedSignal, storeId: data.id };
426
+ syncOptionsCache.set(data, cached);
427
+ }
428
+ options = cached;
429
+ } else {
430
+ if (prev)
431
+ prev.abort();
432
+ const abortController = new AbortController;
433
+ data.abortControllers.set(selector, abortController);
434
+ options = { signal: abortController.signal, storeId: data.id };
435
+ }
436
+ let allDepsThisEval;
322
437
  let result;
323
438
  try {
324
439
  result = selector.get((state) => {
440
+ if (evaluationComplete) {
441
+ if (!evalCtx.revoked && allDepsThisEval) {
442
+ allDepsThisEval.add(state);
443
+ }
444
+ if (evalCtx.revoked) {
445
+ return getState(state, data, new Set);
446
+ }
447
+ return lateGet(state, selector, data);
448
+ }
325
449
  const value = getState(state, data, initializedAtomsSet, circularDependencySet);
326
- updatedDependencies.add(state);
450
+ updatedDepsArray.push(state);
451
+ if (!depsChanged && (!currentDependencies || !currentDependencies.has(state))) {
452
+ depsChanged = true;
453
+ }
327
454
  if (isPromiseLike(value))
328
455
  throw new SuspendAndWaitForResolveError(value);
329
456
  return value;
330
- }, data.id);
457
+ }, options);
331
458
  } catch (error) {
459
+ if (error instanceof NeedsInitError) {
460
+ circularDependencySet.delete(selector);
461
+ throw error;
462
+ }
332
463
  if (error instanceof SuspendAndWaitForResolveError) {
333
464
  result = error;
334
465
  } else if (error instanceof SelectorEvaluationError) {
@@ -337,55 +468,168 @@ var evaluateSelector = (selector, data, initializedAtomsSet, circularDependencyS
337
468
  throw new SelectorEvaluationError(error);
338
469
  }
339
470
  }
340
- const currentDependencies = data.stateDependencies.get(selector) ?? new Set;
341
- const added = updatedDependencies?.difference(currentDependencies);
342
- const removed = currentDependencies?.difference(updatedDependencies);
343
- for (const state of added) {
344
- const set = getOrInitDependentsSet(state, data);
345
- set.add(selector);
471
+ evaluationComplete = true;
472
+ const isAsyncResult = result instanceof SuspendAndWaitForResolveError || isPromiseLike(result);
473
+ if (!isAsyncResult && !depsChanged && currentDependencies && currentDependencies.size !== updatedDepsArray.length) {
474
+ depsChanged = true;
475
+ }
476
+ if (depsChanged || !currentDependencies) {
477
+ const updatedDependencies = new Set(updatedDepsArray);
478
+ if (isAsyncResult && currentDependencies) {
479
+ for (const dep of currentDependencies) {
480
+ updatedDependencies.add(dep);
481
+ }
482
+ }
483
+ const prev2 = currentDependencies ?? new Set;
484
+ for (const state of updatedDependencies) {
485
+ if (!prev2.has(state)) {
486
+ const set = getOrInitDependentsSet(state, data);
487
+ set.add(selector);
488
+ if (addedDepsOut)
489
+ addedDepsOut.add(state);
490
+ }
491
+ }
492
+ if (!isAsyncResult) {
493
+ for (const state of prev2) {
494
+ if (!updatedDependencies.has(state)) {
495
+ const set = getOrInitDependentsSet(state, data);
496
+ set.delete(selector);
497
+ if (removedDepsOut)
498
+ removedDepsOut.add(state);
499
+ }
500
+ }
501
+ }
502
+ data.stateDependencies.set(selector, updatedDependencies);
346
503
  }
347
- for (const state of removed) {
348
- const set = getOrInitDependentsSet(state, data);
349
- set.delete(selector);
504
+ if (isPromiseLike(result)) {
505
+ allDepsThisEval = new Set(updatedDepsArray);
506
+ pendingAsyncDeps.set(result, allDepsThisEval);
350
507
  }
351
- data.stateDependencies.set(selector, updatedDependencies);
508
+ circularDependencySet.delete(selector);
352
509
  return result;
353
510
  };
354
511
  var handleSelectorResult = (value, selector, data) => {
355
512
  if (value instanceof SuspendAndWaitForResolveError) {
356
- value.promise.then(() => {
513
+ data.abortControllers.delete(selector);
514
+ const promise = value.promise;
515
+ promise.then(() => {
516
+ if (!data.stateDependencies.has(selector))
517
+ return;
518
+ if (data.values.has(selector) && data.values.get(selector) !== promise)
519
+ return;
357
520
  const initializedAtomsSet = new Set;
358
521
  const res = initSelector(selector, data, initializedAtomsSet);
359
522
  if (initializedAtomsSet.size > 0) {
360
523
  propagateUpdatedAtoms([...initializedAtomsSet], data);
361
524
  }
362
525
  return res;
526
+ }).catch(() => {
527
+ cleanUpRejectedPromise(selector, data, promise);
363
528
  });
364
- return value.promise;
529
+ return promise;
365
530
  } else if (isPromiseLike(value)) {
366
531
  value.then((resolved) => {
532
+ if (!data.stateDependencies.has(selector)) {
533
+ pendingAsyncDeps.delete(value);
534
+ return;
535
+ }
536
+ if (data.values.has(selector) && data.values.get(selector) !== value) {
537
+ pendingAsyncDeps.delete(value);
538
+ return;
539
+ }
540
+ const evalDeps = pendingAsyncDeps.get(value);
541
+ if (evalDeps) {
542
+ pendingAsyncDeps.delete(value);
543
+ const currentDeps = data.stateDependencies.get(selector);
544
+ if (currentDeps) {
545
+ for (const dep of currentDeps) {
546
+ if (!evalDeps.has(dep)) {
547
+ currentDeps.delete(dep);
548
+ const dependents2 = data.stateDependents.get(dep);
549
+ if (dependents2)
550
+ dependents2.delete(selector);
551
+ unmountOrphanedDeps(dep, data);
552
+ }
553
+ }
554
+ }
555
+ }
367
556
  setValueInData(selector, resolved, data);
368
557
  const dependents = data.stateDependents.get(selector);
369
558
  const subs = data.subscriptions.get(selector);
370
559
  if (subs && subs.size > 0 || dependents && dependents.size > 0) {
371
560
  propagateDirtySelectors([], new Set(dependents), data, new Set(subs), new Map);
372
561
  }
562
+ }).catch(() => {
563
+ pendingAsyncDeps.delete(value);
564
+ cleanUpRejectedPromise(selector, data, value);
373
565
  });
374
566
  return value;
375
567
  } else {
568
+ data.abortControllers.set(selector, false);
376
569
  return value;
377
570
  }
378
571
  };
379
- var initSelector = (selector, data, initializedAtomsSet, circularDependencySet = new WeakSet) => {
572
+ var initSelectorDirect = (selector, data, initializedAtomsSet, circularDependencySet) => {
380
573
  const existingValue = data.values.get(selector);
381
- const udpatedValue = evaluate(selector, data, initializedAtomsSet, circularDependencySet);
382
- if (selector.equal(existingValue, udpatedValue)) {
574
+ const updatedValue = evaluate(selector, data, initializedAtomsSet, circularDependencySet);
575
+ const areEqual = isPromiseLike(existingValue) || isPromiseLike(updatedValue) ? existingValue === updatedValue : selector.equal(existingValue, updatedValue);
576
+ if (areEqual) {
383
577
  return false;
384
578
  } else {
385
- setValueInData(selector, udpatedValue, data);
579
+ setValueInData(selector, updatedValue, data);
386
580
  return true;
387
581
  }
388
582
  };
583
+ var initSelectorTrampoline = (selector, data, initializedAtomsSet, circularDependencySet) => {
584
+ const stack = [selector];
585
+ const inStack = new Set([selector]);
586
+ while (stack.length > 0) {
587
+ const current = stack[stack.length - 1];
588
+ if (data.values.has(current)) {
589
+ stack.pop();
590
+ inStack.delete(current);
591
+ continue;
592
+ }
593
+ try {
594
+ initSelectorDirect(current, data, initializedAtomsSet, circularDependencySet);
595
+ stack.pop();
596
+ inStack.delete(current);
597
+ } catch (e) {
598
+ if (e instanceof NeedsInitError) {
599
+ if (inStack.has(e.selector)) {
600
+ throw new SelectorCircularDependencyError;
601
+ }
602
+ stack.push(e.selector);
603
+ inStack.add(e.selector);
604
+ } else {
605
+ throw e;
606
+ }
607
+ }
608
+ }
609
+ };
610
+ var initSelector = (selector, data, initializedAtomsSet, circularDependencySet = sharedCircularDepSet) => {
611
+ const isTopLevel = _evalDepth === 0 && !_inTrampoline;
612
+ _evalDepth++;
613
+ const existingValue = data.values.get(selector);
614
+ try {
615
+ return initSelectorDirect(selector, data, initializedAtomsSet, circularDependencySet);
616
+ } catch (e) {
617
+ if (e instanceof NeedsInitError && isTopLevel) {
618
+ _inTrampoline = true;
619
+ try {
620
+ initSelectorTrampoline(selector, data, initializedAtomsSet, circularDependencySet);
621
+ } finally {
622
+ _inTrampoline = false;
623
+ }
624
+ const newValue = data.values.get(selector);
625
+ const areEqual = isPromiseLike(existingValue) || isPromiseLike(newValue) ? existingValue === newValue : selector.equal(existingValue, newValue);
626
+ return !areEqual;
627
+ }
628
+ throw e;
629
+ } finally {
630
+ _evalDepth--;
631
+ }
632
+ };
389
633
  var evaluate = (selector, data, initializedAtomsSet, circularDependencySet) => {
390
634
  let tmpValue;
391
635
  try {
@@ -401,20 +645,55 @@ var evaluate = (selector, data, initializedAtomsSet, circularDependencySet) => {
401
645
  // src/lib/propagateUpdatedAtoms.ts
402
646
  var reEvaluteSelector = (selector, data, updatedAtoms) => {
403
647
  const existingValue = data.values.get(selector);
648
+ const addedDeps = new Set;
649
+ const removedDeps = new Set;
404
650
  try {
405
- const udpatedValue = evaluateSelector(selector, data, updatedAtoms);
406
- if (selector.equal(existingValue, udpatedValue, updatedAtoms)) {
407
- return [false, false];
651
+ const rawValue = evaluateSelector(selector, data, updatedAtoms, undefined, addedDeps, removedDeps);
652
+ const udpatedValue = handleSelectorResult(rawValue, selector, data);
653
+ const areEqual = isPromiseLike(existingValue) || isPromiseLike(udpatedValue) ? existingValue === udpatedValue : selector.equal(existingValue, udpatedValue, updatedAtoms);
654
+ if (areEqual) {
655
+ return [false, false, undefined, addedDeps, removedDeps];
408
656
  } else {
409
657
  setValueInData(selector, udpatedValue, data);
410
- return [true, false];
658
+ return [true, false, undefined, addedDeps, removedDeps];
411
659
  }
412
660
  } catch (error) {
413
- data.expiredValues.set(selector, data.values.get(selector));
414
661
  data.values.delete(selector);
415
- return [true, true, error];
662
+ return [true, true, error, addedDeps, removedDeps];
416
663
  }
417
664
  };
665
+ var callSubscribers = (subscriptions, families) => {
666
+ let firstError;
667
+ let hasError = false;
668
+ for (const subscription of subscriptions) {
669
+ if ("state" in subscription) {
670
+ const updatedFamilyAtoms = families?.get(subscription.state);
671
+ if (updatedFamilyAtoms) {
672
+ for (const atom of updatedFamilyAtoms) {
673
+ try {
674
+ subscription.callback(...atom.familyArgs);
675
+ } catch (error) {
676
+ if (!hasError) {
677
+ firstError = error;
678
+ hasError = true;
679
+ }
680
+ }
681
+ }
682
+ }
683
+ } else {
684
+ try {
685
+ subscription.callback();
686
+ } catch (error) {
687
+ if (!hasError) {
688
+ firstError = error;
689
+ hasError = true;
690
+ }
691
+ }
692
+ }
693
+ }
694
+ if (hasError)
695
+ throw firstError;
696
+ };
418
697
  var addSetToSet = (fromSet, toSet) => {
419
698
  if (fromSet && fromSet.size > 0) {
420
699
  for (const item of fromSet) {
@@ -422,30 +701,7 @@ var addSetToSet = (fromSet, toSet) => {
422
701
  }
423
702
  }
424
703
  };
425
- var findClosestStoreWithAtomInitialized2 = (atom, data) => {
426
- if ("parent" in data === false)
427
- return data;
428
- if (data.values.has(atom))
429
- return data;
430
- return findClosestStoreWithAtomInitialized2(atom, data.parent);
431
- };
432
- var findInClosestStore = (state, data) => {
433
- const store = findClosestStoreWithAtomInitialized2(state, data);
434
- return store.values.get(state);
435
- };
436
- var addFamilyAtomsToSet = (family, familyAtoms, data) => {
437
- const currentAtoms = findInClosestStore(family, data) || [];
438
- const atomsToAdd = [];
439
- for (const familyAtom of familyAtoms) {
440
- if (!currentAtoms.includes(familyAtom)) {
441
- atomsToAdd.push(familyAtom);
442
- }
443
- }
444
- if (atomsToAdd.length > 0) {
445
- data.values.set(family, [...currentAtoms, ...atomsToAdd]);
446
- }
447
- };
448
- var propagateUpdatedAtoms = (atoms, data, subscriptions = new Set, families = new Map, isRecursive = false) => {
704
+ var propagateDeletedAtoms = (atoms, data, subscriptions = new Set, families = new Map, timestamp = performance.now()) => {
449
705
  const selectors = new Set;
450
706
  for (const atom of atoms) {
451
707
  addSetToSet(data.stateDependents.get(atom), selectors);
@@ -463,103 +719,398 @@ var propagateUpdatedAtoms = (atoms, data, subscriptions = new Set, families = ne
463
719
  addSetToSet(data.subscriptions.get(family), subscriptions);
464
720
  if (familyAtoms.size === 0)
465
721
  throw new Error("Should not be possible");
466
- addFamilyAtomsToSet(family, familyAtoms, data);
722
+ deleteFamilyAtomsFromSet(family, familyAtoms, data, timestamp);
723
+ }
724
+ }
725
+ propagateDirtySelectors(atoms, selectors, data, subscriptions, families);
726
+ if (families.size > 0 && data.scopes && data.scopes.size > 0) {
727
+ const scopeFamilies = new Map;
728
+ for (const family of families.keys()) {
729
+ const scopesWithFamily = data.scopeValueIndex.get(family);
730
+ if (scopesWithFamily) {
731
+ for (const scope of scopesWithFamily) {
732
+ let list = scopeFamilies.get(scope);
733
+ if (!list) {
734
+ list = [];
735
+ scopeFamilies.set(scope, list);
736
+ }
737
+ list.push(family);
738
+ }
739
+ }
740
+ }
741
+ for (const [scope, familiesInScope] of scopeFamilies) {
742
+ propagateUpdatedAtoms(familiesInScope, scope, undefined, undefined, false, timestamp, true);
743
+ }
744
+ }
745
+ };
746
+ var propagateUpdatedAtoms = (atoms, data, subscriptions, families, isRecursive = false, timestamp = performance.now(), selectorsOnly = false, isInitOnly = false) => {
747
+ if (atoms.length === 1 && !isRecursive && !selectorsOnly) {
748
+ const atom = atoms[0];
749
+ if (!isFamilyAtom(atom) && !isAtomFamily(atom)) {
750
+ const dependents = data.stateDependents.get(atom);
751
+ if ((!dependents || dependents.size === 0) && (!data.scopes || data.scopes.size === 0)) {
752
+ const subs = data.subscriptions.get(atom);
753
+ if (subs && subs.size > 0) {
754
+ callSubscribers(subs);
755
+ }
756
+ return;
757
+ }
758
+ }
759
+ }
760
+ if (!subscriptions)
761
+ subscriptions = new Set;
762
+ if (!families)
763
+ families = new Map;
764
+ const selectors = new Set;
765
+ for (const atom of atoms) {
766
+ addSetToSet(data.stateDependents.get(atom), selectors);
767
+ if (!selectorsOnly) {
768
+ addSetToSet(data.subscriptions.get(atom), subscriptions);
769
+ if (isFamilyAtom(atom)) {
770
+ if (!families.has(atom.family)) {
771
+ families.set(atom.family, new Set);
772
+ }
773
+ families.get(atom.family).add(atom);
774
+ }
775
+ }
776
+ }
777
+ if (families.size > 0) {
778
+ for (const [family, familyAtoms] of families) {
779
+ addSetToSet(data.stateDependents.get(family), selectors);
780
+ addSetToSet(data.subscriptions.get(family), subscriptions);
781
+ if (familyAtoms.size === 0)
782
+ throw new Error("Should not be possible");
783
+ addFamilyAtomsToSet(family, familyAtoms, data, timestamp);
467
784
  }
468
785
  }
469
786
  if (!isRecursive) {
470
- propagateDirtySelectors(atoms, selectors, data, subscriptions, families);
787
+ propagateDirtySelectors(atoms, selectors, data, subscriptions, families, isInitOnly);
788
+ if (data.scopes && data.scopes.size > 0) {
789
+ if (atoms.length === 1) {
790
+ const atom = atoms[0];
791
+ const shadowingScopes = isAtomFamily(atom) ? undefined : data.scopeValueIndex.get(atom);
792
+ for (const [, scope] of data.scopes) {
793
+ if (!shadowingScopes || !shadowingScopes.has(scope)) {
794
+ propagateUpdatedAtoms(atoms, scope, undefined, undefined, false, timestamp, true, isInitOnly);
795
+ }
796
+ }
797
+ } else {
798
+ let anyShadowed = false;
799
+ let atomShadows;
800
+ for (const atom of atoms) {
801
+ if (!isAtomFamily(atom)) {
802
+ const s = data.scopeValueIndex.get(atom);
803
+ if (s && s.size > 0) {
804
+ if (!atomShadows)
805
+ atomShadows = new Map;
806
+ atomShadows.set(atom, s);
807
+ anyShadowed = true;
808
+ }
809
+ }
810
+ }
811
+ if (!anyShadowed) {
812
+ for (const [, scope] of data.scopes) {
813
+ propagateUpdatedAtoms(atoms, scope, undefined, undefined, false, timestamp, true, isInitOnly);
814
+ }
815
+ } else {
816
+ for (const [, scope] of data.scopes) {
817
+ const atomsToUpdateInScope = [];
818
+ for (const atom of atoms) {
819
+ if (isAtomFamily(atom)) {
820
+ atomsToUpdateInScope.push(atom);
821
+ } else {
822
+ const s = atomShadows.get(atom);
823
+ if (!s || !s.has(scope)) {
824
+ atomsToUpdateInScope.push(atom);
825
+ }
826
+ }
827
+ }
828
+ if (atomsToUpdateInScope.length > 0) {
829
+ propagateUpdatedAtoms(atomsToUpdateInScope, scope, undefined, undefined, false, timestamp, true, isInitOnly);
830
+ }
831
+ }
832
+ }
833
+ }
834
+ }
471
835
  }
472
836
  };
473
- var propagateDirtySelectors = (updatedAtoms, selectors, data, subscriptions, families) => {
474
- const initialUpdatedAtoms = new Set(updatedAtoms);
475
- const updatedInitializedAtoms = new Set(initialUpdatedAtoms);
837
+ var propagateDirtySelectors = (updatedAtoms, selectors, data, subscriptions, families, isInitOnly = false) => {
838
+ const updatedInitializedAtoms = new Set(updatedAtoms);
476
839
  if (selectors.size > 0) {
477
- recursivlyHandleSelectorUpdates(selectors, data, subscriptions, updatedInitializedAtoms);
478
- }
479
- const addedAtoms = initialUpdatedAtoms.symmetricDifference(updatedInitializedAtoms);
480
- if (addedAtoms.size) {
481
- console.log("addedAtoms", addedAtoms);
482
- console.log("Valdres TODO: Support this case with new atoms added");
840
+ propagateSelectorUpdates(selectors, data, subscriptions, updatedInitializedAtoms, isInitOnly);
483
841
  }
484
842
  if (subscriptions.size > 0) {
485
- for (const subscription of subscriptions) {
486
- if ("state" in subscription) {
487
- const updatedFamilyAtoms = families.get(subscription.state);
488
- if (updatedFamilyAtoms) {
489
- for (const atom of updatedFamilyAtoms) {
490
- subscription.callback(...atom.familyArgs);
843
+ callSubscribers(subscriptions, families);
844
+ }
845
+ };
846
+ var propagateSelectorUpdates = (selectors, data, collectedSubscribers, updatedInitializedAtoms, isInitOnly = false) => {
847
+ let currentSelectors = selectors;
848
+ while (currentSelectors.size > 0) {
849
+ const selectorsForNextPass = new Set;
850
+ for (const selector of currentSelectors) {
851
+ const currentValue = data.values.get(selector);
852
+ if (isPromiseLike(currentValue) && isInitOnly) {
853
+ continue;
854
+ }
855
+ const dependents = data.stateDependents.get(selector);
856
+ const subscribers = data.subscriptions.get(selector);
857
+ if (!isPromiseLike(currentValue) && (!dependents || dependents.size === 0) && (!subscribers || subscribers.size === 0)) {
858
+ data.values.delete(selector);
859
+ } else {
860
+ const [wasValueUpdated, didEvalCrash, error, addedDeps, removedDeps] = reEvaluteSelector(selector, data, updatedInitializedAtoms);
861
+ if ((addedDeps.size > 0 || removedDeps.size > 0) && isTransitivelySubscribed(selector, data)) {
862
+ for (const dep of addedDeps) {
863
+ mountTransitiveDeps(dep, data);
864
+ }
865
+ for (const dep of removedDeps) {
866
+ unmountOrphanedDeps(dep, data);
491
867
  }
492
868
  }
493
- } else {
494
- subscription.callback();
869
+ if (!wasValueUpdated)
870
+ continue;
871
+ addSetToSet(data.stateDependents.get(selector), selectorsForNextPass);
872
+ addSetToSet(subscribers, collectedSubscribers);
495
873
  }
496
874
  }
875
+ currentSelectors = selectorsForNextPass;
497
876
  }
498
877
  };
499
- var recursivlyHandleSelectorUpdates = (selectors, data, collectedSubscribers, updatedInitializedAtoms, seen = new Set) => {
500
- const selectorsForNextPass = new Set;
501
- for (const selector of selectors) {
502
- const currentValue = data.values.get(selector);
503
- if (isPromiseLike(currentValue)) {
504
- continue;
878
+
879
+ // src/lib/isFunction.ts
880
+ var isFunction = (value) => typeof value === "function";
881
+
882
+ // src/lib/setAtom.ts
883
+ var handlePromise = (atom, promise, currentValue, data, skipOnSet) => {
884
+ const emptyAtomPromise = currentValue?.__isEmptyAtomPromise__ ? currentValue : currentValue?.__emptyAtomPromiseOrigin__ ?? null;
885
+ if (emptyAtomPromise) {
886
+ promise.__emptyAtomPromiseOrigin__ = emptyAtomPromise;
887
+ }
888
+ setValueInData(atom, promise, data);
889
+ promise.then((resolvedValue) => {
890
+ if (data.values.get(atom) !== promise)
891
+ return;
892
+ setValueInData(atom, resolvedValue, data);
893
+ if (atom.onSet && !skipOnSet)
894
+ atom.onSet(resolvedValue, data);
895
+ if (emptyAtomPromise) {
896
+ emptyAtomPromise.__resolveEmptyAtomPromise__(resolvedValue);
505
897
  }
506
- seen.add(selector);
507
- const dependents = data.stateDependents.get(selector);
508
- const subscribers = data.subscriptions.get(selector);
509
- if ((!dependents || dependents.size === 0) && (!subscribers || subscribers.size === 0)) {
510
- data.expiredValues.set(selector, data.values.get(selector));
511
- data.values.delete(selector);
898
+ propagateUpdatedAtoms([atom], data);
899
+ }).catch(() => {
900
+ if (data.values.get(atom) !== promise)
901
+ return;
902
+ setValueInData(atom, currentValue, data);
903
+ propagateUpdatedAtoms([atom], data);
904
+ });
905
+ };
906
+ var setAtom = (atom, newValue, data, skipOnSet = false) => {
907
+ let initializedAtomsSet;
908
+ let currentValue;
909
+ if (data.values.has(atom)) {
910
+ currentValue = data.values.get(atom);
911
+ } else {
912
+ initializedAtomsSet = new Set;
913
+ currentValue = getState(atom, data, initializedAtomsSet);
914
+ }
915
+ if (isFunction(newValue)) {
916
+ newValue = newValue(currentValue);
917
+ }
918
+ if (isPromiseLike(newValue)) {
919
+ const promise = Promise.resolve(newValue);
920
+ if (currentValue === promise)
921
+ return promise;
922
+ handlePromise(atom, promise, currentValue, data, skipOnSet);
923
+ if (initializedAtomsSet && initializedAtomsSet.size > 0) {
924
+ initializedAtomsSet.add(atom);
925
+ propagateUpdatedAtoms([...initializedAtomsSet], data);
512
926
  } else {
513
- const [wasValueUpdated, didEvalCrash, error] = reEvaluteSelector(selector, data, updatedInitializedAtoms);
514
- if (!wasValueUpdated)
515
- continue;
516
- addSetToSet(data.stateDependents.get(selector), selectorsForNextPass);
517
- addSetToSet(subscribers, collectedSubscribers);
927
+ propagateUpdatedAtoms([atom], data);
518
928
  }
929
+ return promise;
930
+ }
931
+ let syncValue = newValue;
932
+ const areEqual = isPromiseLike(currentValue) ? currentValue === syncValue : atom.equal(currentValue, syncValue);
933
+ if (areEqual)
934
+ return syncValue;
935
+ syncValue = setValueInData(atom, syncValue, data);
936
+ if (atom.onSet && !skipOnSet)
937
+ atom.onSet(syncValue, data);
938
+ if (currentValue?.__isEmptyAtomPromise__) {
939
+ currentValue.__resolveEmptyAtomPromise__(syncValue);
519
940
  }
520
- if (selectorsForNextPass.size > 0) {
521
- recursivlyHandleSelectorUpdates(selectorsForNextPass, data, collectedSubscribers, updatedInitializedAtoms, seen);
941
+ if (initializedAtomsSet && initializedAtomsSet.size > 0) {
942
+ initializedAtomsSet.add(atom);
943
+ propagateUpdatedAtoms([...initializedAtomsSet], data);
944
+ } else {
945
+ propagateUpdatedAtoms([atom], data);
522
946
  }
947
+ return syncValue;
948
+ };
949
+
950
+ // src/lib/initAtom.ts
951
+ var getAtomInitValue = (atom, data, initializedAtomsSet) => {
952
+ if (atom.defaultValue === undefined) {
953
+ let promiseResolve;
954
+ const promise = new Promise((resolve) => {
955
+ promiseResolve = resolve;
956
+ });
957
+ promise.__isEmptyAtomPromise__ = true;
958
+ promise.__resolveEmptyAtomPromise__ = promiseResolve;
959
+ return promise;
960
+ } else if (typeof atom.defaultValue === "function") {
961
+ const value = atom.defaultValue();
962
+ if (isPromiseLike(value)) {
963
+ value.then((resolvedValue) => {
964
+ if (data.values.get(atom) !== value)
965
+ return;
966
+ setValueInData(atom, resolvedValue, data);
967
+ propagateUpdatedAtoms([atom], data);
968
+ }, () => {
969
+ if (data.values.get(atom) === value) {
970
+ data.values.delete(atom);
971
+ }
972
+ });
973
+ }
974
+ return value;
975
+ } else if (isSelector(atom.defaultValue)) {
976
+ return getState(atom.defaultValue, data, initializedAtomsSet);
977
+ } else {
978
+ return atom.defaultValue;
979
+ }
980
+ };
981
+ var initAtom = (atom, data, initializedAtomsSet) => {
982
+ const tmpVal = getAtomInitValue(atom, data, initializedAtomsSet);
983
+ let value = setValueInData(atom, tmpVal, data);
984
+ if (atom.onInit)
985
+ atom.onInit((newVal) => {
986
+ value = newVal;
987
+ setAtom(atom, newVal, data, true);
988
+ }, data);
989
+ };
990
+
991
+ // src/lib/getState.ts
992
+ function getState(state, data, initializedAtomsSet, circularDependencySet) {
993
+ if (data.values.has(state))
994
+ return data.values.get(state);
995
+ if (isAtom(state)) {
996
+ if ("parent" in data)
997
+ return getState(state, data.parent, initializedAtomsSet, circularDependencySet);
998
+ if (isFamilyAtom(state)) {
999
+ const familyValue = data.values.get(state.family);
1000
+ if (familyValue?.__index) {
1001
+ if (isAtomDeletedInFamilyIndex(state, familyValue.__index)) {
1002
+ return state.defaultValue;
1003
+ }
1004
+ }
1005
+ }
1006
+ initAtom(state, data, initializedAtomsSet);
1007
+ initializedAtomsSet.add(state);
1008
+ return data.values.get(state);
1009
+ }
1010
+ if (isSelector(state)) {
1011
+ if (_evalDepth >= MAX_EVAL_DEPTH) {
1012
+ throw new NeedsInitError(state);
1013
+ }
1014
+ initSelector(state, data, initializedAtomsSet, circularDependencySet);
1015
+ return data.values.get(state);
1016
+ }
1017
+ if (isAtomFamily(state)) {
1018
+ if ("parent" in data) {
1019
+ const closestData = findClosestStoreWithAtomInitialized(state, data);
1020
+ return getState(state, closestData, initializedAtomsSet, circularDependencySet);
1021
+ }
1022
+ data.values.set(state, renderAtomFamilyIndex(createAtomFamilyIndex()));
1023
+ initializedAtomsSet.add(state);
1024
+ return data.values.get(state);
1025
+ }
1026
+ if (isSelectorFamily(state)) {
1027
+ const array = Array.from(state.__valdresSelectorFamilyMap.keys());
1028
+ if (equal(array, state._keyArray))
1029
+ return state._keyArray;
1030
+ state._keyArray = array;
1031
+ return array;
1032
+ }
1033
+ throw new Error("Invalid object passed to get");
1034
+ }
1035
+ var findClosestStoreWithAtomInitialized = (atom, data) => {
1036
+ if ("parent" in data === false)
1037
+ return data;
1038
+ if (data.values.has(atom))
1039
+ return data;
1040
+ return findClosestStoreWithAtomInitialized(atom, data.parent);
1041
+ };
1042
+ var isAtomDeletedInFamilyIndex = (atom, index) => {
1043
+ if (index.deleted.has(atom))
1044
+ return true;
1045
+ if (index.created.has(atom))
1046
+ return false;
1047
+ if (index.parentIndex)
1048
+ return isAtomDeletedInFamilyIndex(atom, index.parentIndex);
1049
+ return false;
1050
+ };
1051
+
1052
+ // src/utils/resolveReactive.ts
1053
+ var isReactive = (value) => typeof value === "object" && value !== null && (isAtom(value) || isSelector(value));
1054
+ var resolveReactive = (value, data) => {
1055
+ if (typeof value === "object" && value !== null && (isAtom(value) || isSelector(value))) {
1056
+ if (data.values.has(value))
1057
+ return data.values.get(value);
1058
+ return getState(value, data, new Set);
1059
+ }
1060
+ return value;
523
1061
  };
524
1062
 
525
1063
  // src/lib/createStoreData.ts
526
- var generateId = () => (Math.random() + 1).toString(36).substring(7);
527
- var generateStoreData = (id = generateId()) => {
1064
+ var nextId = 0;
1065
+ var generateId = () => "__valdres_store_" + nextId++;
1066
+ function makeLazyGetter(key) {
528
1067
  return {
529
- id,
530
- values: new WeakMap,
531
- expiredValues: new WeakMap,
532
- subscriptions: new WeakMap,
533
- subscriptionsRequireEqualCheck: new WeakMap,
534
- stateDependents: new WeakMap,
535
- stateDependencies: new WeakMap,
536
- scopes: {}
1068
+ get() {
1069
+ const map = new WeakMap;
1070
+ Object.defineProperty(this, key, {
1071
+ value: map,
1072
+ writable: true,
1073
+ configurable: true
1074
+ });
1075
+ return map;
1076
+ },
1077
+ configurable: true
537
1078
  };
538
- };
539
- function createStoreData(id, parent) {
1079
+ }
1080
+ var lazyProto = Object.create(Object.prototype);
1081
+ Object.defineProperties(lazyProto, {
1082
+ subscriptions: makeLazyGetter("subscriptions"),
1083
+ subscriptionsRequireEqualCheck: makeLazyGetter("subscriptionsRequireEqualCheck"),
1084
+ stateDependents: makeLazyGetter("stateDependents"),
1085
+ stateDependencies: makeLazyGetter("stateDependencies"),
1086
+ mounts: makeLazyGetter("mounts"),
1087
+ abortControllers: makeLazyGetter("abortControllers"),
1088
+ lastValueWriteAt: makeLazyGetter("lastValueWriteAt")
1089
+ });
1090
+ function createStoreData(id, parent, options) {
1091
+ const data = Object.create(lazyProto);
1092
+ data.id = id ?? generateId();
1093
+ data.values = new WeakMap;
1094
+ data.scopes = new Map;
1095
+ data.scopeValueIndex = new WeakMap;
1096
+ if (options?.batchUpdates) {
1097
+ data.batchUpdates = true;
1098
+ }
540
1099
  if (parent) {
541
- return {
542
- ...generateStoreData(id),
543
- parent,
544
- scopeConsumers: parent ? new Set : undefined
545
- };
546
- } else {
547
- return generateStoreData(id);
1100
+ data.parent = parent;
1101
+ data.scopeConsumers = new Set;
1102
+ data.scopeIndexKeys = new Set;
548
1103
  }
1104
+ return data;
549
1105
  }
550
1106
 
551
1107
  // src/lib/deleteFamilyAtom.ts
552
1108
  var deleteFamilyAtom = (atom, data) => {
553
- const array = data.values.get(atom.family);
554
- const index = array.indexOf(atom);
555
- const newArray = [
556
- ...array.slice(0, index),
557
- ...array.slice(index + 1)
558
- ];
559
1109
  data.values.delete(atom);
560
- propagateUpdatedAtoms([atom], data);
561
- setValueInData(atom.family, newArray, data);
562
- propagateUpdatedAtoms([atom.family], data);
1110
+ if (atom.family) {
1111
+ atom.family.release(...atom.familyArgs);
1112
+ }
1113
+ propagateDeletedAtoms([atom], data);
563
1114
  };
564
1115
 
565
1116
  // src/lib/resetAtom.ts
@@ -579,8 +1130,25 @@ var resetAtom = (atom, data) => {
579
1130
  // src/utils/isFamily.ts
580
1131
  var isFamily = (state) => isAtomFamily(state) || isSelectorFamily(state);
581
1132
 
1133
+ // src/lib/maxAgeCleanups.ts
1134
+ var maxAgeCleanups = new WeakMap;
1135
+ var setMaxAgeCleanup = (data, state, cleanup) => {
1136
+ let storeMap = maxAgeCleanups.get(data);
1137
+ if (!storeMap) {
1138
+ storeMap = new WeakMap;
1139
+ maxAgeCleanups.set(data, storeMap);
1140
+ }
1141
+ storeMap.set(state, cleanup);
1142
+ };
1143
+ var getMaxAgeCleanup = (data, state) => {
1144
+ return maxAgeCleanups.get(data)?.get(state);
1145
+ };
1146
+ var deleteMaxAgeCleanup = (data, state) => {
1147
+ maxAgeCleanups.get(data)?.delete(state);
1148
+ };
1149
+
582
1150
  // src/lib/unsubscribe.ts
583
- var unsubscribe = (state, subscription, data, mount, maxAgeCleanup) => {
1151
+ var unsubscribe = (state, subscription, data) => {
584
1152
  const subscribers = data.subscriptions.get(state);
585
1153
  if (subscribers) {
586
1154
  subscribers.delete(subscription);
@@ -597,17 +1165,40 @@ var unsubscribe = (state, subscription, data, mount, maxAgeCleanup) => {
597
1165
  }
598
1166
  }
599
1167
  if (subscribers.size === 0) {
600
- if (maxAgeCleanup)
1168
+ const maxAgeCleanup = getMaxAgeCleanup(data, state);
1169
+ if (maxAgeCleanup) {
601
1170
  maxAgeCleanup();
1171
+ deleteMaxAgeCleanup(data, state);
1172
+ }
602
1173
  data.subscriptions.delete(state);
1174
+ unmountOrphanedDeps(state, data);
1175
+ cleanupOrphanedDeps(state, data);
603
1176
  }
604
- if (mount) {
605
- if (subscribers.size === mount.mountSubscriptions.size) {
606
- if (typeof mount.onUnmount === "function") {
607
- mount.onUnmount();
608
- }
1177
+ }
1178
+ };
1179
+ var cleanupOrphanedDeps = (state, data, visited = new Set) => {
1180
+ if (visited.has(state))
1181
+ return;
1182
+ visited.add(state);
1183
+ if (isTransitivelySubscribed(state, data))
1184
+ return;
1185
+ const deps = data.stateDependencies.get(state);
1186
+ if (deps) {
1187
+ for (const dep of deps) {
1188
+ const depDependents = data.stateDependents.get(dep);
1189
+ if (depDependents) {
1190
+ depDependents.delete(state);
609
1191
  }
610
1192
  }
1193
+ data.stateDependencies.delete(state);
1194
+ data.values.delete(state);
1195
+ data.abortControllers.delete(state);
1196
+ }
1197
+ const dependents = data.stateDependents.get(state);
1198
+ if (dependents) {
1199
+ for (const dep of [...dependents]) {
1200
+ cleanupOrphanedDeps(dep, data, visited);
1201
+ }
611
1202
  }
612
1203
  };
613
1204
 
@@ -617,6 +1208,203 @@ var initSubscribers = (state, data) => {
617
1208
  data.subscriptions.set(state, set);
618
1209
  return set;
619
1210
  };
1211
+ var installMaxAgeTimer = (state, data) => {
1212
+ if (!state.maxAge)
1213
+ return;
1214
+ const globalState = isGlobalAtom(state) ? state : undefined;
1215
+ const existing = globalState?.maxAgeInterval;
1216
+ if (existing) {
1217
+ existing.refCount++;
1218
+ const metaAtom2 = state.__cacheMeta ??= { equal, defaultValue: null };
1219
+ for (const s of globalState.stores) {
1220
+ if (s !== data && s.values.has(metaAtom2)) {
1221
+ setValueInData(metaAtom2, s.values.get(metaAtom2), data);
1222
+ propagateUpdatedAtoms([metaAtom2], data);
1223
+ break;
1224
+ }
1225
+ }
1226
+ setMaxAgeCleanup(data, state, () => {
1227
+ if (existing.refCount <= 0)
1228
+ return;
1229
+ existing.refCount--;
1230
+ if (existing.refCount === 0) {
1231
+ existing.cleanup();
1232
+ if (globalState.maxAgeInterval === existing) {
1233
+ globalState.maxAgeInterval = undefined;
1234
+ }
1235
+ }
1236
+ });
1237
+ return;
1238
+ }
1239
+ const pendingTimeouts = new Set;
1240
+ let revalidating = false;
1241
+ let cancelled = false;
1242
+ let lastSuccessTime = Date.now();
1243
+ const NO_VALUE = Symbol();
1244
+ let lastGoodValue = NO_VALUE;
1245
+ let currentInterval;
1246
+ const getMaxAge = () => resolveReactive(state.maxAge, data);
1247
+ const getSWR = () => state.staleWhileRevalidate !== undefined ? resolveReactive(state.staleWhileRevalidate, data) : Infinity;
1248
+ const getStaleIfError = () => state.staleIfError !== undefined ? resolveReactive(state.staleIfError, data) : Infinity;
1249
+ const metaAtom = state.__cacheMeta ??= { equal, defaultValue: null };
1250
+ const updateMeta = () => {
1251
+ const meta = {
1252
+ isRevalidating: revalidating,
1253
+ lastSuccessAt: lastSuccessTime,
1254
+ maxAge: getMaxAge(),
1255
+ staleWhileRevalidate: getSWR(),
1256
+ staleIfError: getStaleIfError()
1257
+ };
1258
+ if (globalState) {
1259
+ for (const store of globalState.stores) {
1260
+ setValueInData(metaAtom, meta, store);
1261
+ propagateUpdatedAtoms([metaAtom], store);
1262
+ }
1263
+ } else {
1264
+ setValueInData(metaAtom, meta, data);
1265
+ propagateUpdatedAtoms([metaAtom], data);
1266
+ }
1267
+ };
1268
+ const isPastStaleIfErrorWindow = () => {
1269
+ const elapsed = Date.now() - lastSuccessTime;
1270
+ return elapsed >= getMaxAge() + getStaleIfError();
1271
+ };
1272
+ const setAndPropagate = (atom, val) => {
1273
+ if (globalState) {
1274
+ for (const store of globalState.stores) {
1275
+ setValueInData(atom, val, store);
1276
+ propagateUpdatedAtoms([atom], store);
1277
+ }
1278
+ } else {
1279
+ setValueInData(atom, val, data);
1280
+ propagateUpdatedAtoms([atom], data);
1281
+ }
1282
+ };
1283
+ const getValueStore = () => {
1284
+ if (globalState) {
1285
+ for (const s of globalState.stores)
1286
+ return s;
1287
+ }
1288
+ return data;
1289
+ };
1290
+ const tick = () => {
1291
+ if (revalidating)
1292
+ return;
1293
+ if (typeof state.defaultValue !== "function")
1294
+ return;
1295
+ const valueStore = getValueStore();
1296
+ if (valueStore.values.has(state)) {
1297
+ const currentValue = valueStore.values.get(state);
1298
+ if (!isPromiseLike(currentValue)) {
1299
+ lastGoodValue = currentValue;
1300
+ }
1301
+ }
1302
+ const value = state.defaultValue();
1303
+ if (isPromiseLike(value)) {
1304
+ revalidating = true;
1305
+ updateMeta();
1306
+ const swr = getSWR();
1307
+ const handleResolve = (resolved) => {
1308
+ if (cancelled)
1309
+ return;
1310
+ revalidating = false;
1311
+ lastSuccessTime = Date.now();
1312
+ lastGoodValue = resolved;
1313
+ setAndPropagate(state, resolved);
1314
+ updateMeta();
1315
+ };
1316
+ const handleReject = () => {
1317
+ if (cancelled)
1318
+ return;
1319
+ revalidating = false;
1320
+ if (!isPastStaleIfErrorWindow() && lastGoodValue !== NO_VALUE) {
1321
+ setAndPropagate(state, lastGoodValue);
1322
+ } else {
1323
+ setAndPropagate(state, value);
1324
+ }
1325
+ updateMeta();
1326
+ };
1327
+ if (swr > 0) {
1328
+ let timeoutRef;
1329
+ if (Number.isFinite(swr)) {
1330
+ timeoutRef = setTimeout(() => {
1331
+ pendingTimeouts.delete(timeoutRef);
1332
+ if (cancelled || !revalidating)
1333
+ return;
1334
+ setAndPropagate(state, value);
1335
+ }, swr);
1336
+ pendingTimeouts.add(timeoutRef);
1337
+ }
1338
+ value.then((resolved) => {
1339
+ if (timeoutRef !== undefined) {
1340
+ clearTimeout(timeoutRef);
1341
+ pendingTimeouts.delete(timeoutRef);
1342
+ }
1343
+ handleResolve(resolved);
1344
+ }, () => {
1345
+ if (timeoutRef !== undefined) {
1346
+ clearTimeout(timeoutRef);
1347
+ pendingTimeouts.delete(timeoutRef);
1348
+ }
1349
+ handleReject();
1350
+ });
1351
+ } else {
1352
+ setAndPropagate(state, value);
1353
+ value.then(handleResolve, handleReject);
1354
+ }
1355
+ } else {
1356
+ lastSuccessTime = Date.now();
1357
+ lastGoodValue = value;
1358
+ setAndPropagate(state, value);
1359
+ updateMeta();
1360
+ }
1361
+ };
1362
+ const startInterval = () => {
1363
+ currentInterval = setInterval(tick, getMaxAge());
1364
+ };
1365
+ startInterval();
1366
+ updateMeta();
1367
+ const configUnsubs = [];
1368
+ if (isReactive(state.maxAge)) {
1369
+ configUnsubs.push(subscribe(state.maxAge, () => {
1370
+ clearInterval(currentInterval);
1371
+ startInterval();
1372
+ updateMeta();
1373
+ }, false, data));
1374
+ }
1375
+ if (state.staleWhileRevalidate && isReactive(state.staleWhileRevalidate)) {
1376
+ configUnsubs.push(subscribe(state.staleWhileRevalidate, () => updateMeta(), false, data));
1377
+ }
1378
+ if (state.staleIfError && isReactive(state.staleIfError)) {
1379
+ configUnsubs.push(subscribe(state.staleIfError, () => updateMeta(), false, data));
1380
+ }
1381
+ const cleanup = () => {
1382
+ cancelled = true;
1383
+ clearInterval(currentInterval);
1384
+ for (const t of pendingTimeouts)
1385
+ clearTimeout(t);
1386
+ pendingTimeouts.clear();
1387
+ for (const unsub of configUnsubs)
1388
+ unsub();
1389
+ };
1390
+ if (globalState) {
1391
+ const entry = { cleanup, refCount: 1 };
1392
+ globalState.maxAgeInterval = entry;
1393
+ setMaxAgeCleanup(data, state, () => {
1394
+ if (entry.refCount <= 0)
1395
+ return;
1396
+ entry.refCount--;
1397
+ if (entry.refCount === 0) {
1398
+ entry.cleanup();
1399
+ if (globalState.maxAgeInterval === entry) {
1400
+ globalState.maxAgeInterval = undefined;
1401
+ }
1402
+ }
1403
+ });
1404
+ } else {
1405
+ setMaxAgeCleanup(data, state, cleanup);
1406
+ }
1407
+ };
620
1408
  var subscribe = (state, callback, requireDeepEqualCheckBeforeCallback, data) => {
621
1409
  let parentUnsubscribe;
622
1410
  if ("parent" in data && (!data.values.has(state) && isAtom(state) || isAtomFamily(state))) {
@@ -657,45 +1445,12 @@ var subscribe = (state, callback, requireDeepEqualCheckBeforeCallback, data) =>
657
1445
  };
658
1446
  }
659
1447
  subscribers.add(subscription);
660
- let mount;
661
- let maxAgeCleanup;
662
1448
  if (subscribers.size === 1) {
663
1449
  if (isAtom(state) && state.maxAge) {
664
- let timeout;
665
- const interval = setInterval(() => {
666
- let value = getAtomInitValue(state, data);
667
- if (isPromiseLike(value)) {
668
- if (state.staleWhileRevalidate) {
669
- const oldValue = data.values.get(state);
670
- timeout = setTimeout(() => {
671
- const nowValue = data.values.get(state);
672
- console.log("todo", oldValue);
673
- }, state.staleWhileRevalidate);
674
- value.then((res) => clearTimeout(timeout));
675
- }
676
- } else {
677
- setValueInData(state, value, data);
678
- propagateUpdatedAtoms([state], data);
679
- }
680
- }, state.maxAge);
681
- maxAgeCleanup = () => {
682
- clearInterval(interval);
683
- if (timeout)
684
- clearTimeout(timeout);
685
- };
1450
+ installMaxAgeTimer(state, data);
686
1451
  }
687
- if (state.onMount) {
688
- const store = storeFromStoreData(data);
689
- const mountSubscriptions = new Set;
690
- const originalSub = store.sub;
691
- store.sub = (state2, callback2) => {
692
- mountSubscriptions.add(callback2);
693
- return originalSub(state2, callback2);
694
- };
695
- mount = {
696
- onUnmount: state.onMount(store, state),
697
- mountSubscriptions
698
- };
1452
+ if (!isFamily(state)) {
1453
+ mountTransitiveDeps(state, data);
699
1454
  }
700
1455
  }
701
1456
  if (requireDeepEqualCheckBeforeCallback && data.subscriptionsRequireEqualCheck.get(state) !== true) {
@@ -705,7 +1460,7 @@ var subscribe = (state, callback, requireDeepEqualCheckBeforeCallback, data) =>
705
1460
  if (parentUnsubscribe) {
706
1461
  parentUnsubscribe();
707
1462
  }
708
- unsubscribe(state, subscription, data, mount, maxAgeCleanup);
1463
+ unsubscribe(state, subscription, data);
709
1464
  };
710
1465
  };
711
1466
 
@@ -714,188 +1469,264 @@ var setAtoms = (pairs, data, initializedAtomsSet) => {
714
1469
  const updatedAtoms = [];
715
1470
  for (let [atom, value] of pairs) {
716
1471
  const currentValue = getState(atom, data, initializedAtomsSet);
717
- if (!atom.equal(currentValue, value)) {
1472
+ const areEqual = isPromiseLike(currentValue) || isPromiseLike(value) ? currentValue === value : atom.equal(currentValue, value);
1473
+ if (!areEqual) {
718
1474
  updatedAtoms.push(atom);
719
1475
  value = setValueInData(atom, value, data);
720
1476
  if (atom.onSet)
721
1477
  atom.onSet(value, data);
1478
+ } else {
1479
+ setValueInData(atom, value, data);
722
1480
  }
723
1481
  }
724
- const result = new Set([...updatedAtoms, ...initializedAtomsSet]);
725
- if (result.size > 0) {
726
- propagateUpdatedAtoms([...result], data);
727
- }
728
- };
729
-
730
- // src/lib/transaction.ts
731
- var findDependencies = (state, data, result = new Set) => {
732
- const dependents = data.stateDependents.get(state);
733
- if (dependents?.size) {
734
- for (const dependent of dependents) {
735
- if (!result.has(dependent)) {
736
- result.add(dependent);
737
- findDependencies(dependent, data, result);
738
- }
1482
+ if (initializedAtomsSet.size > 0) {
1483
+ for (const atom of initializedAtomsSet) {
1484
+ updatedAtoms.push(atom);
739
1485
  }
740
1486
  }
741
- return result;
742
- };
743
- var recursivlyResetTxnSelectorCache = (state, txnSubscribers, txnSelectorCache) => {
744
- for (const dep of txnSubscribers.get(state)) {
745
- txnSelectorCache.delete(dep);
746
- if (txnSubscribers.get(dep)?.size) {
747
- recursivlyResetTxnSelectorCache(dep, txnSubscribers, txnSelectorCache);
748
- }
1487
+ if (updatedAtoms.length > 0) {
1488
+ propagateUpdatedAtoms(updatedAtoms, data);
749
1489
  }
750
1490
  };
751
- var captureScopedTransaction = (scopedData, parentGetInTxnOrData) => {
752
- let txn;
753
- transaction((scopedTxn) => {
754
- txn = scopedTxn;
755
- }, scopedData, false, parentGetInTxnOrData);
756
- return txn;
757
- };
1491
+
1492
+ // src/lib/transaction.ts
758
1493
  var deleteAtomFamilyAtoms = (set, data) => {
759
1494
  set.forEach((atom) => {
760
1495
  data.values.delete(atom);
761
1496
  });
762
1497
  };
763
- var transaction = (callback, data, autoCommit = true, parentGetInTxnOrData) => {
764
- const txnAtomMap = new Map;
765
- const txnAtomDeleteSet = new Set;
766
- const txnSelectorCache = new Map;
767
- const txnSubscribers = new Map;
768
- const dirtySelectors = new Set;
769
- let scopedTransactions;
770
- const getInTxnOrData = (state) => {
771
- if (txnAtomMap.has(state)) {
772
- return txnAtomMap.get(state);
1498
+
1499
+ class Transaction {
1500
+ data;
1501
+ parentTransaction;
1502
+ dirty;
1503
+ _scopedTransactions;
1504
+ _initializedAtomsSet;
1505
+ _deleteSet;
1506
+ _selectorDependencies;
1507
+ _selectorCache;
1508
+ _atomMap;
1509
+ constructor(data, parentTransaction, childTransaction) {
1510
+ this.data = data;
1511
+ this.parentTransaction = parentTransaction;
1512
+ this.dirty = false;
1513
+ this._atomMap = new Map;
1514
+ if (childTransaction) {
1515
+ this._scopedTransactions = new Map([
1516
+ [childTransaction.data.id, childTransaction]
1517
+ ]);
773
1518
  }
774
- if (data.values.has(state)) {
775
- return data.values.get(state);
1519
+ }
1520
+ hasTxnOrData = (state) => {
1521
+ if (this._atomMap.has(state))
1522
+ return true;
1523
+ if (this.data.values.has(state))
1524
+ return true;
1525
+ if (this.parentTransaction)
1526
+ return this.parentTransaction.hasTxnOrData(state);
1527
+ return false;
1528
+ };
1529
+ valueFromTxnOrData = (state) => {
1530
+ if (this._atomMap.has(state)) {
1531
+ return this._atomMap.get(state);
1532
+ }
1533
+ if (this.data.values.has(state)) {
1534
+ return this.data.values.get(state);
776
1535
  }
777
- if (parentGetInTxnOrData) {
778
- return parentGetInTxnOrData(state);
1536
+ if (this.parentTransaction) {
1537
+ return this.parentTransaction.valueFromTxnOrData(state);
779
1538
  }
780
1539
  };
781
- const initializedAtomsSet = new Set;
782
- const txnGet = (state) => {
783
- if (isAtom(state)) {
784
- const value = getInTxnOrData(state);
785
- if (value)
786
- return value;
787
- return getState(state, data, initializedAtomsSet);
1540
+ get = (state) => {
1541
+ if (isAtom(state) || isAtomFamily(state)) {
1542
+ if (this.hasTxnOrData(state)) {
1543
+ return this.valueFromTxnOrData(state);
1544
+ }
1545
+ return getState(state, this.data, this.initializedAtomsSet);
788
1546
  } else if (isSelector(state)) {
789
- if (txnSelectorCache.has(state)) {
790
- return txnSelectorCache.get(state);
1547
+ if (this.dirty) {
1548
+ this.selectorCache.clear();
1549
+ this.selectorDependencies.clear();
1550
+ this.dirty = false;
1551
+ } else if (this.selectorCache.has(state)) {
1552
+ return this.selectorCache.get(state);
791
1553
  }
792
- const deps = new Set;
793
1554
  const res = state.get((s) => {
794
- deps.add(s);
795
- return txnGet(s);
796
- }, data.id);
797
- for (const dep of deps) {
798
- if (!txnSubscribers.has(dep)) {
799
- txnSubscribers.set(dep, new Set);
1555
+ if (!this.selectorDependencies.has(s)) {
1556
+ this.selectorDependencies.add(s);
800
1557
  }
801
- txnSubscribers.get(dep).add(state);
802
- }
803
- txnSelectorCache.set(state, res);
1558
+ return this.get(s);
1559
+ }, this.data.id);
1560
+ this.selectorCache.set(state, res);
804
1561
  return res;
805
- } else if (isAtomFamily(state)) {
806
- const value = getInTxnOrData(state);
807
- if (value)
808
- return value;
809
- return getState(state, data, initializedAtomsSet);
810
1562
  } else {
811
1563
  throw new Error("Unsupported state");
812
1564
  }
813
1565
  };
814
- const txnSet = (atom, value) => {
1566
+ set = (atom, value) => {
815
1567
  if (!isAtom(atom))
816
1568
  throw new Error("Not an atom");
817
1569
  if (isFunction(value)) {
818
- const currentValue = txnGet(atom);
1570
+ const currentValue = this.get(atom);
819
1571
  value = value(currentValue);
820
1572
  }
821
- for (const selector of findDependencies(atom, data)) {
822
- dirtySelectors.add(selector);
823
- txnSelectorCache.delete(selector);
824
- }
825
- if (txnSubscribers.get(atom)?.size) {
826
- recursivlyResetTxnSelectorCache(atom, txnSubscribers, txnSelectorCache);
827
- }
828
- if (isProd()) {
829
- txnAtomMap.set(atom, value);
830
- } else {
831
- txnAtomMap.set(atom, deepFreeze(value));
1573
+ if (!atom.mutable && !isProd() && value !== null && (typeof value === "object" || typeof value === "function")) {
1574
+ value = deepFreeze(value);
832
1575
  }
1576
+ this._atomMap.set(atom, value);
1577
+ if (!this.dirty)
1578
+ this.dirty = true;
833
1579
  if (isFamilyAtom(atom)) {
834
- const currentFamilyList = txnGet(atom.family);
835
- if (!currentFamilyList.includes(atom)) {
836
- const newArr = [...currentFamilyList, atom];
837
- txnAtomMap.set(atom.family, newArr);
1580
+ if (!this._atomMap.has(atom.family)) {
1581
+ this.cloneFamilyIntoTxn(atom.family);
838
1582
  }
1583
+ const index = this._atomMap.get(atom.family).__index;
1584
+ index.created.set(atom, performance.now());
1585
+ index.deleted.delete(atom);
1586
+ index.rendered = null;
1587
+ index.renderedArray = null;
1588
+ this.recursivelyUpdateAtomFamilyIndexes(atom.family);
839
1589
  }
840
1590
  return value;
841
1591
  };
842
- const txnReset = (atom) => {
843
- const value = getAtomInitValue(atom, data, initializedAtomsSet);
844
- txnAtomMap.set(atom, value);
1592
+ batchSetFamilyAtoms = (family, pairs) => {
1593
+ if (!this._atomMap.has(family)) {
1594
+ this.cloneFamilyIntoTxn(family);
1595
+ }
1596
+ const index = this._atomMap.get(family).__index;
1597
+ for (const [atom, value] of pairs) {
1598
+ if (atom.family !== family) {
1599
+ throw new Error("Atom does not belong to the provided family");
1600
+ }
1601
+ index.created.set(atom, performance.now());
1602
+ if (index.deleted.has(atom))
1603
+ index.deleted.delete(atom);
1604
+ this._atomMap.set(atom, value);
1605
+ }
1606
+ index.rendered = null;
1607
+ index.renderedArray = null;
1608
+ this.recursivelyUpdateAtomFamilyIndexes(family);
1609
+ };
1610
+ del = (atom) => {
1611
+ if (!this._atomMap.has(atom.family)) {
1612
+ this.cloneFamilyIntoTxn(atom.family);
1613
+ }
1614
+ const index = this._atomMap.get(atom.family).__index;
1615
+ index.created.delete(atom);
1616
+ index.deleted.set(atom, performance.now());
1617
+ index.rendered = null;
1618
+ index.renderedArray = null;
1619
+ this._atomMap.set(atom.family, renderAtomFamilyIndex(index));
1620
+ this.recursivelyUpdateAtomFamilyIndexes(atom.family);
1621
+ if (this.data.values.has(atom)) {
1622
+ this.deleteSet.add(atom);
1623
+ }
1624
+ if (this._atomMap.has(atom)) {
1625
+ this._atomMap.delete(atom);
1626
+ }
1627
+ };
1628
+ scope = (scopeId, callback) => {
1629
+ if (this.data.scopes.has(scopeId)) {
1630
+ return this.scopedTransaction(scopeId).execute(callback, false);
1631
+ } else {
1632
+ throw new Error(`Scope '${scopeId}' not found. Registered scopes: ${[...this.data.scopes.keys()].join(", ")}`);
1633
+ }
1634
+ };
1635
+ parentScope = (callback) => {
1636
+ if (!this.parentTransaction) {
1637
+ this.parentTransaction = new Transaction(this.data.parent, undefined, this);
1638
+ }
1639
+ return this.parentTransaction.execute(callback, false);
1640
+ };
1641
+ reset = (atom) => {
1642
+ const value = getAtomInitValue(atom, this.data, this.initializedAtomsSet);
1643
+ this._atomMap.set(atom, value);
845
1644
  return value;
846
1645
  };
847
- const txnDel = (atom) => {
848
- const array = txnGet(atom.family);
849
- const index = array.indexOf(atom);
850
- const newArr = [
851
- ...array.slice(0, index),
852
- ...array.slice(index + 1)
853
- ];
854
- txnAtomMap.set(atom.family, newArr);
855
- if (data.values.has(atom)) {
856
- txnAtomDeleteSet.add(atom);
857
- }
858
- if (txnAtomMap.has(atom)) {
859
- txnAtomMap.delete(atom);
1646
+ execute = (callback, autoCommit = true) => {
1647
+ const result = callback(this);
1648
+ if (autoCommit)
1649
+ this.txnCommit();
1650
+ return result;
1651
+ };
1652
+ txnCommit = () => {
1653
+ if (this.parentTransaction) {
1654
+ this.parentTransaction.txnCommit();
1655
+ } else {
1656
+ this.commit();
860
1657
  }
861
1658
  };
862
- const commit = () => {
863
- setAtoms(txnAtomMap, data, initializedAtomsSet);
864
- if (txnAtomDeleteSet.size) {
865
- deleteAtomFamilyAtoms(txnAtomDeleteSet, data);
866
- }
867
- dirtySelectors.clear();
868
- if (scopedTransactions) {
869
- for (const scopedTxn of Object.values(scopedTransactions)) {
1659
+ commit = () => {
1660
+ const initializedAtomsSet = new Set;
1661
+ setAtoms(this._atomMap, this.data, initializedAtomsSet);
1662
+ if (this.deleteSet?.size) {
1663
+ deleteAtomFamilyAtoms(this.deleteSet, this.data);
1664
+ propagateDeletedAtoms([...this.deleteSet], this.data);
1665
+ }
1666
+ if (this._scopedTransactions) {
1667
+ for (const [, scopedTxn] of this._scopedTransactions) {
870
1668
  scopedTxn.commit();
871
1669
  }
872
1670
  }
873
1671
  };
874
- const result = callback({
875
- set: txnSet,
876
- get: txnGet,
877
- del: txnDel,
878
- reset: txnReset,
879
- commit,
880
- scope: (scopeId, callback2) => {
881
- if (scopeId in data.scopes) {
882
- const scopedData = data.scopes[scopeId];
883
- if (scopedTransactions === undefined) {
884
- scopedTransactions = {};
885
- }
886
- if (scopedTransactions[scopeId] === undefined) {
887
- scopedTransactions[scopeId] = captureScopedTransaction(scopedData, getInTxnOrData);
888
- }
889
- return callback2(scopedTransactions[scopeId]);
890
- } else {
891
- throw new Error(`Scope '${scopeId}' not found. Registered scopes: ${Object.keys(data.scopes).join(", ")}`);
1672
+ get selectorCache() {
1673
+ if (!this._selectorCache)
1674
+ this._selectorCache = new Map;
1675
+ return this._selectorCache;
1676
+ }
1677
+ get selectorDependencies() {
1678
+ if (!this._selectorDependencies)
1679
+ this._selectorDependencies = new Set;
1680
+ return this._selectorDependencies;
1681
+ }
1682
+ get deleteSet() {
1683
+ if (!this._deleteSet)
1684
+ this._deleteSet = new Set;
1685
+ return this._deleteSet;
1686
+ }
1687
+ get initializedAtomsSet() {
1688
+ if (!this._initializedAtomsSet)
1689
+ this._initializedAtomsSet = new Set;
1690
+ return this._initializedAtomsSet;
1691
+ }
1692
+ scopedTransaction(scopeId) {
1693
+ if (!this._scopedTransactions)
1694
+ this._scopedTransactions = new Map;
1695
+ if (!this._scopedTransactions.has(scopeId)) {
1696
+ const scopedData = this.data.scopes.get(scopeId);
1697
+ const scopedTransaction = new Transaction(scopedData, this);
1698
+ this._scopedTransactions.set(scopeId, scopedTransaction);
1699
+ }
1700
+ return this._scopedTransactions.get(scopeId);
1701
+ }
1702
+ cloneFamilyIntoTxn(family, parentIndex, moveUpIfParent = true) {
1703
+ if (moveUpIfParent && this.parentTransaction)
1704
+ return this.parentTransaction.cloneFamilyIntoTxn(family, parentIndex, moveUpIfParent);
1705
+ const currentFamilyList = this.get(family);
1706
+ const clonedIndex = cloneAtomFamilyIndex(currentFamilyList.__index, parentIndex);
1707
+ if (this._scopedTransactions?.size) {
1708
+ for (const [, scopedTxn] of this._scopedTransactions) {
1709
+ scopedTxn.cloneFamilyIntoTxn(family, clonedIndex, false);
892
1710
  }
893
- },
894
- data
895
- });
896
- if (autoCommit)
897
- commit();
898
- return result;
1711
+ }
1712
+ this._atomMap.set(family, renderAtomFamilyIndex(clonedIndex));
1713
+ }
1714
+ recursivelyUpdateAtomFamilyIndexes(atomFamily) {
1715
+ const currentIndex = this._atomMap.get(atomFamily).__index;
1716
+ currentIndex.rendered = null;
1717
+ currentIndex.renderedArray = null;
1718
+ const updatedValue = renderAtomFamilyIndex(currentIndex);
1719
+ this._atomMap.set(atomFamily, updatedValue);
1720
+ if (this._scopedTransactions?.size) {
1721
+ for (const [, scopedTxn] of this._scopedTransactions) {
1722
+ scopedTxn.recursivelyUpdateAtomFamilyIndexes(atomFamily);
1723
+ }
1724
+ }
1725
+ }
1726
+ }
1727
+ var transaction = (callback, data) => {
1728
+ const txn = new Transaction(data);
1729
+ return txn.execute(callback);
899
1730
  };
900
1731
 
901
1732
  // src/lib/storeFromStoreData.ts
@@ -906,43 +1737,150 @@ Only \`atom\` cam be set.
906
1737
  var InvalidStateSetError = `Invalid state object passed to set().
907
1738
  Only \`atom\` can be set.
908
1739
  `;
1740
+ var isCachedValueStale = (state, data) => {
1741
+ const atom = state;
1742
+ const maxAge = atom.maxAge;
1743
+ if (maxAge === undefined)
1744
+ return false;
1745
+ if (isGlobalAtom(atom)) {
1746
+ if (atom.maxAgeInterval !== undefined)
1747
+ return false;
1748
+ } else {
1749
+ const subs = data.subscriptions.get(state);
1750
+ if (subs && subs.size > 0)
1751
+ return false;
1752
+ }
1753
+ const lastWrite = data.lastValueWriteAt.get(state);
1754
+ if (lastWrite === undefined)
1755
+ return false;
1756
+ const ttl = typeof maxAge === "number" ? maxAge : resolveReactive(maxAge, data);
1757
+ return Date.now() - lastWrite > ttl;
1758
+ };
909
1759
  function storeFromStoreData(data, detach) {
910
- const get = (state) => {
911
- const set2 = new Set;
912
- const res = getState(state, data, set2);
913
- if (set2.size) {
914
- propagateUpdatedAtoms([...set2], data);
1760
+ const _initSet = new Set;
1761
+ let _pendingTxn = null;
1762
+ const flushPendingTxn = () => {
1763
+ if (_pendingTxn) {
1764
+ const txnToCommit = _pendingTxn;
1765
+ _pendingTxn = null;
1766
+ txnToCommit.commit();
1767
+ }
1768
+ };
1769
+ const ensurePendingTxn = () => {
1770
+ if (!_pendingTxn) {
1771
+ _pendingTxn = new Transaction(data);
1772
+ queueMicrotask(() => {
1773
+ try {
1774
+ flushPendingTxn();
1775
+ } catch (error) {
1776
+ setTimeout(() => {
1777
+ throw error;
1778
+ }, 0);
1779
+ }
1780
+ });
1781
+ }
1782
+ return _pendingTxn;
1783
+ };
1784
+ const getDefault = (state) => {
1785
+ if (data.values.has(state)) {
1786
+ if (!isCachedValueStale(state, data)) {
1787
+ return data.values.get(state);
1788
+ }
1789
+ data.values.delete(state);
1790
+ data.lastValueWriteAt.delete(state);
1791
+ }
1792
+ let res;
1793
+ try {
1794
+ res = getState(state, data, _initSet);
1795
+ } finally {
1796
+ if (_initSet.size) {
1797
+ const atoms = [..._initSet];
1798
+ _initSet.clear();
1799
+ propagateUpdatedAtoms(atoms, data, undefined, undefined, false, undefined, false, true);
1800
+ }
915
1801
  }
916
1802
  return res;
917
1803
  };
918
- const set = (state, value) => {
1804
+ const getBatched = (state) => {
1805
+ if (_pendingTxn) {
1806
+ return _pendingTxn.get(state);
1807
+ }
1808
+ return getDefault(state);
1809
+ };
1810
+ const get = data.batchUpdates ? getBatched : getDefault;
1811
+ const setDefault = (state, value) => {
919
1812
  if (isAtom(state))
920
1813
  return setAtom(state, value, data);
921
1814
  if (isSelector(state))
922
1815
  throw new Error(SelectorProvidedToSetError);
923
1816
  throw new Error(InvalidStateSetError);
924
1817
  };
925
- const reset = (atom) => resetAtom(atom, data);
926
- const del = (atom) => deleteFamilyAtom(atom, data);
1818
+ const setBatched = (state, value) => {
1819
+ if (isAtom(state)) {
1820
+ return ensurePendingTxn().set(state, value);
1821
+ }
1822
+ if (isSelector(state))
1823
+ throw new Error(SelectorProvidedToSetError);
1824
+ throw new Error(InvalidStateSetError);
1825
+ };
1826
+ const set = data.batchUpdates ? setBatched : setDefault;
1827
+ const reset = (atom) => {
1828
+ if (data.batchUpdates)
1829
+ flushPendingTxn();
1830
+ return resetAtom(atom, data);
1831
+ };
1832
+ const del = (atom) => {
1833
+ if (data.batchUpdates)
1834
+ flushPendingTxn();
1835
+ return deleteFamilyAtom(atom, data);
1836
+ };
927
1837
  const sub = (state, callback, deepEqualCheckBeforeCallback = true) => subscribe(state, callback, deepEqualCheckBeforeCallback, data);
928
- const txn = (callback) => transaction(callback, data);
929
- const scope = (scopeId) => {
930
- let scopedStoreData;
931
- if (scopeId in data.scopes) {
932
- scopedStoreData = data.scopes[scopeId];
1838
+ const txn = (callback) => {
1839
+ if (data.batchUpdates)
1840
+ flushPendingTxn();
1841
+ return transaction(callback, data);
1842
+ };
1843
+ const scope = (scopeId, callback) => {
1844
+ if (callback) {
1845
+ if (!data.scopes.has(scopeId)) {
1846
+ throw new Error(`Scope ${scopeId} does not exist`);
1847
+ }
1848
+ const scopedStoreData = data.scopes.get(scopeId);
1849
+ const scopedStore = storeFromStoreData(scopedStoreData);
1850
+ const res = callback(scopedStore);
1851
+ return res;
933
1852
  } else {
934
- scopedStoreData = createStoreData(scopeId, data);
935
- data.scopes[scopeId] = scopedStoreData;
936
- }
937
- const detach2 = () => {
938
- scopedStoreData.scopeConsumers.delete(detach2);
939
- if (scopedStoreData.scopeConsumers.size === 0) {
940
- delete data.scopes[scopeId];
1853
+ let scopedStoreData;
1854
+ if (data.scopes.has(scopeId)) {
1855
+ scopedStoreData = data.scopes.get(scopeId);
1856
+ } else {
1857
+ scopedStoreData = createStoreData(scopeId, data, data.batchUpdates ? { batchUpdates: true } : undefined);
1858
+ data.scopes.set(scopeId, scopedStoreData);
941
1859
  }
942
- };
943
- scopedStoreData.scopeConsumers.add(detach2);
944
- const newStore = storeFromStoreData(data.scopes[scopeId], detach2);
945
- return newStore;
1860
+ const detach2 = (expectedToDestroy = false) => {
1861
+ scopedStoreData.scopeConsumers.delete(detach2);
1862
+ if (scopedStoreData.scopeConsumers.size === 0) {
1863
+ data.scopes.delete(scopeId);
1864
+ for (const key of scopedStoreData.scopeIndexKeys) {
1865
+ const set2 = data.scopeValueIndex.get(key);
1866
+ if (set2) {
1867
+ set2.delete(scopedStoreData);
1868
+ if (set2.size === 0)
1869
+ data.scopeValueIndex.delete(key);
1870
+ }
1871
+ }
1872
+ scopedStoreData.scopeIndexKeys.clear();
1873
+ return true;
1874
+ }
1875
+ if (expectedToDestroy) {
1876
+ console.warn(`Scope ${scopeId} still has ${scopedStoreData.scopeConsumers.size} consumers, will not detach`);
1877
+ }
1878
+ return false;
1879
+ };
1880
+ scopedStoreData.scopeConsumers.add(detach2);
1881
+ const newStore = storeFromStoreData(data.scopes.get(scopeId), detach2);
1882
+ return newStore;
1883
+ }
946
1884
  };
947
1885
  if (detach) {
948
1886
  return {
@@ -970,9 +1908,114 @@ function storeFromStoreData(data, detach) {
970
1908
  }
971
1909
  }
972
1910
 
1911
+ // src/lib/mountAtom.ts
1912
+ var isTransitivelySubscribed = (state, data, visited = new Set) => {
1913
+ const stack = [state];
1914
+ while (stack.length > 0) {
1915
+ const current = stack.pop();
1916
+ if (visited.has(current))
1917
+ continue;
1918
+ visited.add(current);
1919
+ const subs = data.subscriptions.get(current);
1920
+ if (subs && subs.size > 0)
1921
+ return true;
1922
+ const dependents = data.stateDependents.get(current);
1923
+ if (dependents) {
1924
+ for (const dep of dependents) {
1925
+ if (!visited.has(dep))
1926
+ stack.push(dep);
1927
+ }
1928
+ }
1929
+ }
1930
+ return false;
1931
+ };
1932
+ var mountAtom = (state, data) => {
1933
+ const onMountFn = state.__valdresOnMount ?? state.onMount;
1934
+ if (!onMountFn || data.mounts.has(state))
1935
+ return;
1936
+ const mountEntry = {};
1937
+ data.mounts.set(state, mountEntry);
1938
+ const store = data.storeRef ?? storeFromStoreData(data);
1939
+ try {
1940
+ mountEntry.cleanup = onMountFn(store, state);
1941
+ } catch (error) {
1942
+ data.mounts.delete(state);
1943
+ throw error;
1944
+ }
1945
+ };
1946
+ var unmountAtom = (state, data) => {
1947
+ const mount = data.mounts.get(state);
1948
+ if (!mount)
1949
+ return;
1950
+ data.mounts.delete(state);
1951
+ if (typeof mount.cleanup === "function") {
1952
+ mount.cleanup();
1953
+ }
1954
+ };
1955
+ var mountTransitiveDeps = (state, data, visited = new Set) => {
1956
+ let firstError = null;
1957
+ const stack = [state];
1958
+ while (stack.length > 0) {
1959
+ const current = stack.pop();
1960
+ if (visited.has(current))
1961
+ continue;
1962
+ visited.add(current);
1963
+ if (current.__valdresOnMount || current.onMount) {
1964
+ try {
1965
+ mountAtom(current, data);
1966
+ } catch (error) {
1967
+ if (!firstError)
1968
+ firstError = { value: error };
1969
+ }
1970
+ }
1971
+ const deps = data.stateDependencies.get(current);
1972
+ if (deps) {
1973
+ for (const dep of deps) {
1974
+ if (!visited.has(dep))
1975
+ stack.push(dep);
1976
+ }
1977
+ }
1978
+ }
1979
+ if (firstError) {
1980
+ throw firstError.value;
1981
+ }
1982
+ };
1983
+ var unmountOrphanedDeps = (state, data, visited = new Set) => {
1984
+ let firstError = null;
1985
+ const stack = [state];
1986
+ while (stack.length > 0) {
1987
+ const current = stack.pop();
1988
+ if (visited.has(current))
1989
+ continue;
1990
+ visited.add(current);
1991
+ if ((current.__valdresOnMount || current.onMount) && data.mounts.has(current)) {
1992
+ if (!isTransitivelySubscribed(current, data)) {
1993
+ try {
1994
+ unmountAtom(current, data);
1995
+ } catch (error) {
1996
+ if (!firstError)
1997
+ firstError = { value: error };
1998
+ }
1999
+ }
2000
+ }
2001
+ const deps = data.stateDependencies.get(current);
2002
+ if (deps) {
2003
+ for (const dep of deps) {
2004
+ if (!visited.has(dep))
2005
+ stack.push(dep);
2006
+ }
2007
+ }
2008
+ }
2009
+ if (firstError) {
2010
+ throw firstError.value;
2011
+ }
2012
+ };
2013
+
973
2014
  // src/store.ts
974
- var store = (id) => {
975
- const data = createStoreData(id);
2015
+ var store = (idOrOptions) => {
2016
+ const id = typeof idOrOptions === "string" ? idOrOptions : idOrOptions?.id;
2017
+ const options = typeof idOrOptions === "object" ? idOrOptions : undefined;
2018
+ const data = createStoreData(id, undefined, options);
976
2019
  return storeFromStoreData(data);
977
2020
  };
978
2021
 
@@ -985,61 +2028,114 @@ var globalStore = Object.assign(store("valdres-global-store"), {
985
2028
  // src/lib/globalAtom.ts
986
2029
  var globalAtom = (defaultValue, options) => {
987
2030
  const stores = new Set;
988
- let value;
989
- let initialized = false;
990
- let onReset;
991
2031
  if (options.onSet)
992
2032
  throw new Error("onSet on globalAtom is currently not supported");
993
2033
  const onInit = (setSelf2, data) => {
994
2034
  setSelf2(globalStore.get(atom));
995
- if (!initialized && options.onInit) {
996
- onReset = options.onInit((newVal) => {
997
- setSelf2(newVal);
998
- value = newVal;
999
- }, data);
1000
- initialized = true;
1001
- }
1002
2035
  stores.add(data);
1003
2036
  };
1004
2037
  const onSet = (newValue, currentStore) => {
1005
- value = newValue;
1006
2038
  if (stores.size > 1) {
1007
2039
  for (const store2 of stores) {
1008
2040
  if (store2.id !== currentStore.id) {
1009
- setAtom(atom, value, store2, true);
2041
+ setAtom(atom, newValue, store2, true);
1010
2042
  }
1011
2043
  }
1012
2044
  }
1013
2045
  };
2046
+ let mountCount = 0;
2047
+ let userCleanup;
2048
+ const userOnMount = options.onMount;
2049
+ const onMount = userOnMount ? (...args) => {
2050
+ mountCount++;
2051
+ if (mountCount === 1) {
2052
+ try {
2053
+ userCleanup = userOnMount(...args);
2054
+ } catch (error) {
2055
+ mountCount--;
2056
+ userCleanup = undefined;
2057
+ throw error;
2058
+ }
2059
+ }
2060
+ return () => {
2061
+ if (mountCount <= 0)
2062
+ return;
2063
+ mountCount--;
2064
+ if (mountCount === 0 && typeof userCleanup === "function") {
2065
+ const cleanup = userCleanup;
2066
+ userCleanup = undefined;
2067
+ cleanup();
2068
+ }
2069
+ };
2070
+ } : undefined;
1014
2071
  const getSelf = () => globalStore.get(atom);
1015
2072
  const setSelf = (newValue) => globalStore.set(atom, newValue);
1016
2073
  const resetSelf = () => {
1017
- value = undefined;
1018
- initialized = false;
1019
- for (const store2 of stores) {
1020
- if (store2.stateDependencies.has(atom)) {
1021
- throw new Error("TODO: Reset support for stateDependencies");
2074
+ const snapshot = [...stores];
2075
+ const subscribedStores = [];
2076
+ for (const s of snapshot) {
2077
+ if (isTransitivelySubscribed(atom, s)) {
2078
+ subscribedStores.push(s);
1022
2079
  }
1023
- store2.values.delete(atom);
1024
- store2.expiredValues.delete(atom);
1025
- propagateUpdatedAtoms([atom], store2);
2080
+ }
2081
+ let firstError;
2082
+ const recordError = (e) => {
2083
+ if (!firstError)
2084
+ firstError = e;
2085
+ };
2086
+ for (const s of snapshot) {
2087
+ try {
2088
+ unmountAtom(atom, s);
2089
+ } catch (e) {
2090
+ recordError(e);
2091
+ }
2092
+ }
2093
+ if (atom.maxAgeInterval) {
2094
+ atom.maxAgeInterval.cleanup();
2095
+ atom.maxAgeInterval.refCount = 0;
2096
+ atom.maxAgeInterval = undefined;
2097
+ }
2098
+ for (const store2 of snapshot) {
1026
2099
  stores.delete(store2);
1027
- onReset?.();
2100
+ store2.values.delete(atom);
2101
+ try {
2102
+ propagateUpdatedAtoms([atom], store2);
2103
+ } catch (e) {
2104
+ recordError(e);
2105
+ }
2106
+ }
2107
+ for (const s of subscribedStores) {
2108
+ stores.add(s);
2109
+ if (atom.maxAge && (s.subscriptions.get(atom)?.size ?? 0) > 0) {
2110
+ installMaxAgeTimer(atom, s);
2111
+ }
2112
+ try {
2113
+ mountAtom(atom, s);
2114
+ } catch (e) {
2115
+ recordError(e);
2116
+ }
1028
2117
  }
2118
+ if (firstError)
2119
+ throw firstError;
2120
+ };
2121
+ const detach = (storeData) => {
2122
+ stores.delete(storeData);
1029
2123
  };
1030
2124
  const atom = {
1031
2125
  equal,
1032
2126
  ...options,
1033
2127
  defaultValue,
1034
- name: options?.name,
1035
2128
  onInit,
1036
2129
  onSet,
2130
+ onMount,
1037
2131
  setSelf,
1038
2132
  getSelf,
1039
2133
  resetSelf,
2134
+ detach,
1040
2135
  get stores() {
1041
2136
  return stores;
1042
- }
2137
+ },
2138
+ maxAgeInterval: undefined
1043
2139
  };
1044
2140
  return atom;
1045
2141
  };
@@ -1057,17 +2153,6 @@ function atom(defaultValue, options) {
1057
2153
  ...options
1058
2154
  };
1059
2155
  }
1060
- // src/lib/atomFamilyAtom.ts
1061
- function atomFamilyAtom(defaultValue, options) {
1062
- if (options.global) {
1063
- return globalAtom(defaultValue, options);
1064
- }
1065
- return {
1066
- ...options,
1067
- defaultValue
1068
- };
1069
- }
1070
-
1071
2156
  // src/lib/stableStringify.ts
1072
2157
  var stableStringifyRecurse = (x, key) => {
1073
2158
  if (typeof x === "string" && !x.includes('"') && !x.includes("\\")) {
@@ -1127,47 +2212,66 @@ var stringifyFamilyArgs = (args) => {
1127
2212
  return args.length === 1 ? stableStringify(args[0]) : stableStringify(args);
1128
2213
  };
1129
2214
 
1130
- // src/lib/createAtomFamily.ts
1131
- var createOptions = (options = {}, family, familyArgs, familyArgsStringified) => {
1132
- if (options.name) {
1133
- return {
1134
- equal,
1135
- ...options,
1136
- name: options?.name + "_" + familyArgsStringified,
1137
- family,
1138
- familyArgs,
1139
- familyArgsStringified
1140
- };
1141
- } else {
1142
- return { equal, ...options, family, familyArgs, familyArgsStringified };
1143
- }
1144
- };
1145
- var handleDefaultValue = (defaultValue, ...args) => {
1146
- if (isSelectorFamily(defaultValue))
1147
- return defaultValue(...args);
1148
- if (typeof defaultValue === "function")
1149
- return () => defaultValue(...args);
1150
- return defaultValue;
2215
+ // src/lib/familyKey.ts
2216
+ var familyKey = (args) => {
2217
+ if (args.length === 1) {
2218
+ const a = args[0];
2219
+ const t = typeof a;
2220
+ if (t === "string" || t === "number" || t === "boolean")
2221
+ return a;
2222
+ }
2223
+ return stringifyFamilyArgs(args);
1151
2224
  };
2225
+
2226
+ // src/lib/createAtomFamily.ts
1152
2227
  var createAtomFamily = (defaultValue, options) => {
1153
2228
  const map = new Map;
2229
+ const isSelectorFamilyDefault = isSelectorFamily(defaultValue);
2230
+ const isFunctionDefault = !isSelectorFamilyDefault && typeof defaultValue === "function";
2231
+ const hasName = !!options?.name;
2232
+ const isGlobal = !!options?.global;
1154
2233
  const atomFamily = (...args) => {
1155
- const argsStringified = stringifyFamilyArgs(args);
1156
- if (map.has(argsStringified)) {
1157
- return map.get(argsStringified);
2234
+ const key = familyKey(args);
2235
+ const cached = map.get(key);
2236
+ if (cached !== undefined)
2237
+ return cached;
2238
+ let dv;
2239
+ if (isSelectorFamilyDefault) {
2240
+ dv = defaultValue(...args);
2241
+ } else if (isFunctionDefault) {
2242
+ dv = () => defaultValue(...args);
2243
+ } else {
2244
+ dv = defaultValue;
2245
+ }
2246
+ const memberName = hasName ? options.name + "_" + key : undefined;
2247
+ let familyAtom;
2248
+ if (isGlobal) {
2249
+ familyAtom = globalAtom(dv, {
2250
+ ...options,
2251
+ name: memberName
2252
+ });
2253
+ } else {
2254
+ familyAtom = {
2255
+ equal,
2256
+ ...options,
2257
+ defaultValue: dv,
2258
+ name: memberName
2259
+ };
1158
2260
  }
1159
- const familyAtom = atomFamilyAtom(handleDefaultValue(defaultValue, ...args), createOptions(options, atomFamily, args, argsStringified));
1160
- map.set(argsStringified, familyAtom);
2261
+ familyAtom.family = atomFamily;
2262
+ familyAtom.familyArgs = args;
2263
+ familyAtom.familyArgsStringified = key;
2264
+ map.set(key, familyAtom);
1161
2265
  return familyAtom;
1162
2266
  };
1163
- if (options?.name)
2267
+ if (hasName)
1164
2268
  Object.defineProperty(atomFamily, "name", {
1165
2269
  value: options.name,
1166
2270
  writable: false
1167
2271
  });
1168
2272
  return Object.assign(atomFamily, {
1169
2273
  __valdresAtomFamilyMap: map,
1170
- release: (...args) => map.delete(stringifyFamilyArgs(args)),
2274
+ release: (...args) => map.delete(familyKey(args)),
1171
2275
  equal
1172
2276
  });
1173
2277
  };
@@ -1190,30 +2294,26 @@ function atomFamily(defaultValue, options) {
1190
2294
  return createGlobalAtomFamily(defaultValue, options);
1191
2295
  return createAtomFamily(defaultValue, options);
1192
2296
  }
1193
- // src/createStoreWithSelectorSet.ts
1194
- var setSelector = (selector, values, store2) => {
1195
- return selector.set(store2.set, store2.get, store2.reset, ...values);
1196
- };
1197
- var createStoreWithSelectorSet = (id) => {
1198
- const data = createStoreData(id);
1199
- const store2 = storeFromStoreData(data);
1200
- store2.set = (state, value, ...rest) => {
1201
- if (isAtom(state))
1202
- return setAtom(state, value, data);
1203
- if (isSelector(state))
1204
- return setSelector(state, [value, ...rest], store2);
1205
- throw new Error("Invalid state object");
1206
- };
1207
- store2.kind = "storeWithSelectorSet";
1208
- return store2;
1209
- };
1210
2297
  // src/selector.ts
1211
2298
  var selector = (get, options) => {
2299
+ if (get.constructor?.name === "AsyncFunction") {
2300
+ throw new Error("selector() does not accept async functions. " + "Use a sync function that returns a Promise instead.");
2301
+ }
1212
2302
  if (!options)
1213
2303
  return { equal, get };
1214
2304
  return { equal, ...options, get };
1215
2305
  };
1216
2306
 
2307
+ // src/cacheMeta.ts
2308
+ var cacheMeta = (sourceAtom) => {
2309
+ if (sourceAtom.__cacheMetaSelector)
2310
+ return sourceAtom.__cacheMetaSelector;
2311
+ if (!sourceAtom.__cacheMeta) {
2312
+ sourceAtom.__cacheMeta = { equal, defaultValue: null };
2313
+ }
2314
+ sourceAtom.__cacheMetaSelector = selector((get) => get(sourceAtom.__cacheMeta));
2315
+ return sourceAtom.__cacheMetaSelector;
2316
+ };
1217
2317
  // src/indexConstructor.ts
1218
2318
  var index = (family, callback, options) => {
1219
2319
  const map = new Map;
@@ -1264,32 +2364,30 @@ var index = (family, callback, options) => {
1264
2364
  });
1265
2365
  };
1266
2366
  // src/selectorFamily.ts
1267
- var createOptions2 = (options = {}, family, familyArgs, familyArgsStringified) => {
1268
- if (options.name) {
1269
- return {
1270
- equal,
1271
- ...options,
1272
- name: options?.name + "_" + familyArgsStringified,
1273
- family,
1274
- familyArgs,
1275
- familyArgsStringified
1276
- };
1277
- } else {
1278
- return { equal, ...options, family, familyArgs, familyArgsStringified };
1279
- }
1280
- };
1281
2367
  var selectorFamily = (callback, options) => {
1282
2368
  const map = new Map;
2369
+ const hasName = !!options?.name;
1283
2370
  const selectorFamily2 = (...args) => {
1284
- const argsStringified = stringifyFamilyArgs(args);
1285
- if (map.has(argsStringified))
1286
- return map.get(argsStringified);
1287
- const newSelector = selector((selectorArgs) => callback(...args)(selectorArgs), createOptions2(options, selectorFamily2, args, argsStringified));
1288
- map.set(argsStringified, newSelector);
2371
+ const key = familyKey(args);
2372
+ const cached = map.get(key);
2373
+ if (cached !== undefined)
2374
+ return cached;
2375
+ const get = (selectorArgs) => callback(...args)(selectorArgs);
2376
+ const newSelector = {
2377
+ equal,
2378
+ ...options,
2379
+ get,
2380
+ family: selectorFamily2,
2381
+ familyArgs: args,
2382
+ familyArgsStringified: key,
2383
+ name: hasName ? options.name + "_" + key : undefined
2384
+ };
2385
+ map.set(key, newSelector);
1289
2386
  return newSelector;
1290
2387
  };
1291
2388
  selectorFamily2.__valdresSelectorFamilyMap = map;
1292
- if (options?.name)
2389
+ selectorFamily2.release = (...args) => map.delete(familyKey(args));
2390
+ if (hasName)
1293
2391
  Object.defineProperty(selectorFamily2, "name", {
1294
2392
  value: options.name,
1295
2393
  writable: false
@@ -1301,14 +2399,15 @@ var isFamilySelector = (state) => isFamilyState(state) && isSelector(state);
1301
2399
 
1302
2400
  // src/index.ts
1303
2401
  if (globalThis.__valdres__) {
1304
- throw new Error(`Error! An instance of valdres is already loaded. Loaded: ${globalThis.__valdres__}. Attempted to load: ${"0.2.0-pre.8"}`);
2402
+ throw new Error(`Error! An instance of valdres is already loaded. Loaded: ${globalThis.__valdres__}. Attempted to load: ${"1.0.0-beta.0"}`);
1305
2403
  } else {
1306
- globalThis.__valdres__ = "0.2.0-pre.8";
2404
+ globalThis.__valdres__ = "1.0.0-beta.0";
1307
2405
  }
1308
2406
  export {
1309
2407
  store,
1310
2408
  selectorFamily,
1311
2409
  selector,
2410
+ isSuspendError,
1312
2411
  isSelectorFamily,
1313
2412
  isSelector,
1314
2413
  isPromiseLike,
@@ -1321,7 +2420,8 @@ export {
1321
2420
  index,
1322
2421
  globalStore,
1323
2422
  deepFreeze,
1324
- createStoreWithSelectorSet,
2423
+ cacheMeta,
1325
2424
  atomFamily,
1326
- atom
2425
+ atom,
2426
+ Transaction
1327
2427
  };