1 | let keyCount = 0;
|
2 | function atom(read, write) {
|
3 | const key = `atom${++keyCount}`;
|
4 | const config = {
|
5 | toString: () => key
|
6 | };
|
7 | if (typeof read === "function") {
|
8 | config.read = read;
|
9 | } else {
|
10 | config.init = read;
|
11 | config.read = (get) => get(config);
|
12 | config.write = (get, set, arg) => set(
|
13 | config,
|
14 | typeof arg === "function" ? arg(get(config)) : arg
|
15 | );
|
16 | }
|
17 | if (write) {
|
18 | config.write = write;
|
19 | }
|
20 | return config;
|
21 | }
|
22 |
|
23 | const hasInitialValue = (atom) => "init" in atom;
|
24 | const isActuallyWritableAtom = (atom) => !!atom.write;
|
25 | const cancelPromiseMap = new WeakMap();
|
26 | const registerCancelPromise = (promise, cancel) => {
|
27 | cancelPromiseMap.set(promise, cancel);
|
28 | promise.catch(() => {
|
29 | }).finally(() => cancelPromiseMap.delete(promise));
|
30 | };
|
31 | const cancelPromise = (promise, next) => {
|
32 | const cancel = cancelPromiseMap.get(promise);
|
33 | if (cancel) {
|
34 | cancelPromiseMap.delete(promise);
|
35 | cancel(next);
|
36 | }
|
37 | };
|
38 | const resolvePromise = (promise, value) => {
|
39 | promise.status = "fulfilled";
|
40 | promise.value = value;
|
41 | };
|
42 | const rejectPromise = (promise, e) => {
|
43 | promise.status = "rejected";
|
44 | promise.reason = e;
|
45 | };
|
46 | const isPromiseLike = (x) => typeof (x == null ? void 0 : x.then) === "function";
|
47 | const isEqualAtomValue = (a, b) => "v" in a && "v" in b && Object.is(a.v, b.v);
|
48 | const isEqualAtomError = (a, b) => "e" in a && "e" in b && Object.is(a.e, b.e);
|
49 | const hasPromiseAtomValue = (a) => "v" in a && a.v instanceof Promise;
|
50 | const isEqualPromiseAtomValue = (a, b) => "v" in a && "v" in b && a.v.orig && a.v.orig === b.v.orig;
|
51 | const returnAtomValue = (atomState) => {
|
52 | if ("e" in atomState) {
|
53 | throw atomState.e;
|
54 | }
|
55 | return atomState.v;
|
56 | };
|
57 | const createStore = () => {
|
58 | const atomStateMap = new WeakMap();
|
59 | const mountedMap = new WeakMap();
|
60 | const pendingMap = new Map();
|
61 | let storeListenersRev1;
|
62 | let storeListenersRev2;
|
63 | let mountedAtoms;
|
64 | if ((import.meta.env ? import.meta.env.MODE : void 0) !== "production") {
|
65 | storeListenersRev1 = new Set();
|
66 | storeListenersRev2 = new Set();
|
67 | mountedAtoms = new Set();
|
68 | }
|
69 | const getAtomState = (atom) => atomStateMap.get(atom);
|
70 | const setAtomState = (atom, atomState) => {
|
71 | if ((import.meta.env ? import.meta.env.MODE : void 0) !== "production") {
|
72 | Object.freeze(atomState);
|
73 | }
|
74 | const prevAtomState = atomStateMap.get(atom);
|
75 | atomStateMap.set(atom, atomState);
|
76 | if (!pendingMap.has(atom)) {
|
77 | pendingMap.set(atom, prevAtomState);
|
78 | }
|
79 | if (prevAtomState && hasPromiseAtomValue(prevAtomState)) {
|
80 | const next = "v" in atomState ? atomState.v instanceof Promise ? atomState.v : Promise.resolve(atomState.v) : Promise.reject(atomState.e);
|
81 | cancelPromise(prevAtomState.v, next);
|
82 | }
|
83 | };
|
84 | const updateDependencies = (atom, nextAtomState, nextDependencies) => {
|
85 | const dependencies = new Map();
|
86 | let changed = false;
|
87 | nextDependencies.forEach((aState, a) => {
|
88 | if (!aState && a === atom) {
|
89 | aState = nextAtomState;
|
90 | }
|
91 | if (aState) {
|
92 | dependencies.set(a, aState);
|
93 | if (nextAtomState.d.get(a) !== aState) {
|
94 | changed = true;
|
95 | }
|
96 | } else if ((import.meta.env ? import.meta.env.MODE : void 0) !== "production") {
|
97 | console.warn("[Bug] atom state not found");
|
98 | }
|
99 | });
|
100 | if (changed || nextAtomState.d.size !== dependencies.size) {
|
101 | nextAtomState.d = dependencies;
|
102 | }
|
103 | };
|
104 | const setAtomValue = (atom, value, nextDependencies) => {
|
105 | const prevAtomState = getAtomState(atom);
|
106 | const nextAtomState = {
|
107 | d: (prevAtomState == null ? void 0 : prevAtomState.d) || new Map(),
|
108 | v: value
|
109 | };
|
110 | if (nextDependencies) {
|
111 | updateDependencies(atom, nextAtomState, nextDependencies);
|
112 | }
|
113 | if (prevAtomState && isEqualAtomValue(prevAtomState, nextAtomState) && prevAtomState.d === nextAtomState.d) {
|
114 | return prevAtomState;
|
115 | }
|
116 | if (prevAtomState && hasPromiseAtomValue(prevAtomState) && hasPromiseAtomValue(nextAtomState) && isEqualPromiseAtomValue(prevAtomState, nextAtomState)) {
|
117 | if (prevAtomState.d === nextAtomState.d) {
|
118 | return prevAtomState;
|
119 | } else {
|
120 | nextAtomState.v = prevAtomState.v;
|
121 | }
|
122 | }
|
123 | setAtomState(atom, nextAtomState);
|
124 | return nextAtomState;
|
125 | };
|
126 | const setAtomValueOrPromise = (atom, valueOrPromise, nextDependencies, abortPromise) => {
|
127 | if (isPromiseLike(valueOrPromise)) {
|
128 | let continuePromise;
|
129 | const promise = new Promise((resolve, reject) => {
|
130 | let settled = false;
|
131 | valueOrPromise.then(
|
132 | (v) => {
|
133 | if (!settled) {
|
134 | settled = true;
|
135 | const prevAtomState = getAtomState(atom);
|
136 | const nextAtomState = setAtomValue(
|
137 | atom,
|
138 | promise,
|
139 | nextDependencies
|
140 | );
|
141 | resolvePromise(promise, v);
|
142 | resolve(v);
|
143 | if ((prevAtomState == null ? void 0 : prevAtomState.d) !== nextAtomState.d) {
|
144 | mountDependencies(atom, nextAtomState, prevAtomState == null ? void 0 : prevAtomState.d);
|
145 | }
|
146 | }
|
147 | },
|
148 | (e) => {
|
149 | if (!settled) {
|
150 | settled = true;
|
151 | const prevAtomState = getAtomState(atom);
|
152 | const nextAtomState = setAtomValue(
|
153 | atom,
|
154 | promise,
|
155 | nextDependencies
|
156 | );
|
157 | rejectPromise(promise, e);
|
158 | reject(e);
|
159 | if ((prevAtomState == null ? void 0 : prevAtomState.d) !== nextAtomState.d) {
|
160 | mountDependencies(atom, nextAtomState, prevAtomState == null ? void 0 : prevAtomState.d);
|
161 | }
|
162 | }
|
163 | }
|
164 | );
|
165 | continuePromise = (next) => {
|
166 | if (!settled) {
|
167 | settled = true;
|
168 | next.then(
|
169 | (v) => resolvePromise(promise, v),
|
170 | (e) => rejectPromise(promise, e)
|
171 | );
|
172 | resolve(next);
|
173 | }
|
174 | };
|
175 | });
|
176 | promise.orig = valueOrPromise;
|
177 | promise.status = "pending";
|
178 | registerCancelPromise(promise, (next) => {
|
179 | if (next) {
|
180 | continuePromise(next);
|
181 | }
|
182 | abortPromise == null ? void 0 : abortPromise();
|
183 | });
|
184 | return setAtomValue(atom, promise, nextDependencies);
|
185 | }
|
186 | return setAtomValue(atom, valueOrPromise, nextDependencies);
|
187 | };
|
188 | const setAtomError = (atom, error, nextDependencies) => {
|
189 | const prevAtomState = getAtomState(atom);
|
190 | const nextAtomState = {
|
191 | d: (prevAtomState == null ? void 0 : prevAtomState.d) || new Map(),
|
192 | e: error
|
193 | };
|
194 | if (nextDependencies) {
|
195 | updateDependencies(atom, nextAtomState, nextDependencies);
|
196 | }
|
197 | if (prevAtomState && isEqualAtomError(prevAtomState, nextAtomState) && prevAtomState.d === nextAtomState.d) {
|
198 | return prevAtomState;
|
199 | }
|
200 | setAtomState(atom, nextAtomState);
|
201 | return nextAtomState;
|
202 | };
|
203 | const readAtomState = (atom) => {
|
204 | const atomState = getAtomState(atom);
|
205 | if (atomState) {
|
206 | atomState.d.forEach((_, a) => {
|
207 | if (a !== atom && !mountedMap.has(a)) {
|
208 | readAtomState(a);
|
209 | }
|
210 | });
|
211 | if (Array.from(atomState.d).every(
|
212 | ([a, s]) => a === atom || getAtomState(a) === s
|
213 | )) {
|
214 | return atomState;
|
215 | }
|
216 | }
|
217 | const nextDependencies = new Map();
|
218 | let isSync = true;
|
219 | const getter = (a) => {
|
220 | if (a === atom) {
|
221 | const aState2 = getAtomState(a);
|
222 | if (aState2) {
|
223 | nextDependencies.set(a, aState2);
|
224 | return returnAtomValue(aState2);
|
225 | }
|
226 | if (hasInitialValue(a)) {
|
227 | nextDependencies.set(a, void 0);
|
228 | return a.init;
|
229 | }
|
230 | throw new Error("no atom init");
|
231 | }
|
232 | const aState = readAtomState(a);
|
233 | nextDependencies.set(a, aState);
|
234 | return returnAtomValue(aState);
|
235 | };
|
236 | let controller;
|
237 | let setSelf;
|
238 | const options = {
|
239 | get signal() {
|
240 | if (!controller) {
|
241 | controller = new AbortController();
|
242 | }
|
243 | return controller.signal;
|
244 | },
|
245 | get setSelf() {
|
246 | if ((import.meta.env ? import.meta.env.MODE : void 0) !== "production" && !isActuallyWritableAtom(atom)) {
|
247 | console.warn("setSelf function cannot be used with read-only atom");
|
248 | }
|
249 | if (!setSelf && isActuallyWritableAtom(atom)) {
|
250 | setSelf = (...args) => {
|
251 | if ((import.meta.env ? import.meta.env.MODE : void 0) !== "production" && isSync) {
|
252 | console.warn("setSelf function cannot be called in sync");
|
253 | }
|
254 | if (!isSync) {
|
255 | return writeAtom(atom, ...args);
|
256 | }
|
257 | };
|
258 | }
|
259 | return setSelf;
|
260 | }
|
261 | };
|
262 | try {
|
263 | const valueOrPromise = atom.read(getter, options);
|
264 | return setAtomValueOrPromise(
|
265 | atom,
|
266 | valueOrPromise,
|
267 | nextDependencies,
|
268 | () => controller == null ? void 0 : controller.abort()
|
269 | );
|
270 | } catch (error) {
|
271 | return setAtomError(atom, error, nextDependencies);
|
272 | } finally {
|
273 | isSync = false;
|
274 | }
|
275 | };
|
276 | const readAtom = (atom) => returnAtomValue(readAtomState(atom));
|
277 | const addAtom = (atom) => {
|
278 | let mounted = mountedMap.get(atom);
|
279 | if (!mounted) {
|
280 | mounted = mountAtom(atom);
|
281 | }
|
282 | return mounted;
|
283 | };
|
284 | const canUnmountAtom = (atom, mounted) => !mounted.l.size && (!mounted.t.size || mounted.t.size === 1 && mounted.t.has(atom));
|
285 | const delAtom = (atom) => {
|
286 | const mounted = mountedMap.get(atom);
|
287 | if (mounted && canUnmountAtom(atom, mounted)) {
|
288 | unmountAtom(atom);
|
289 | }
|
290 | };
|
291 | const recomputeDependents = (atom) => {
|
292 | const dependencyMap = new Map();
|
293 | const dirtyMap = new WeakMap();
|
294 | const loop1 = (a) => {
|
295 | const mounted = mountedMap.get(a);
|
296 | mounted == null ? void 0 : mounted.t.forEach((dependent) => {
|
297 | if (dependent !== a) {
|
298 | dependencyMap.set(
|
299 | dependent,
|
300 | (dependencyMap.get(dependent) || new Set()).add(a)
|
301 | );
|
302 | dirtyMap.set(dependent, (dirtyMap.get(dependent) || 0) + 1);
|
303 | loop1(dependent);
|
304 | }
|
305 | });
|
306 | };
|
307 | loop1(atom);
|
308 | const loop2 = (a) => {
|
309 | const mounted = mountedMap.get(a);
|
310 | mounted == null ? void 0 : mounted.t.forEach((dependent) => {
|
311 | var _a;
|
312 | if (dependent !== a) {
|
313 | let dirtyCount = dirtyMap.get(dependent);
|
314 | if (dirtyCount) {
|
315 | dirtyMap.set(dependent, --dirtyCount);
|
316 | }
|
317 | if (!dirtyCount) {
|
318 | let isChanged = !!((_a = dependencyMap.get(dependent)) == null ? void 0 : _a.size);
|
319 | if (isChanged) {
|
320 | const prevAtomState = getAtomState(dependent);
|
321 | const nextAtomState = readAtomState(dependent);
|
322 | isChanged = !prevAtomState || !isEqualAtomValue(prevAtomState, nextAtomState);
|
323 | }
|
324 | if (!isChanged) {
|
325 | dependencyMap.forEach((s) => s.delete(dependent));
|
326 | }
|
327 | }
|
328 | loop2(dependent);
|
329 | }
|
330 | });
|
331 | };
|
332 | loop2(atom);
|
333 | };
|
334 | const writeAtomState = (atom, ...args) => {
|
335 | let isSync = true;
|
336 | const getter = (a) => returnAtomValue(readAtomState(a));
|
337 | const setter = (a, ...args2) => {
|
338 | let r;
|
339 | if (a === atom) {
|
340 | if (!hasInitialValue(a)) {
|
341 | throw new Error("atom not writable");
|
342 | }
|
343 | const prevAtomState = getAtomState(a);
|
344 | const nextAtomState = setAtomValueOrPromise(a, args2[0]);
|
345 | if (!prevAtomState || !isEqualAtomValue(prevAtomState, nextAtomState)) {
|
346 | recomputeDependents(a);
|
347 | }
|
348 | } else {
|
349 | r = writeAtomState(a, ...args2);
|
350 | }
|
351 | if (!isSync) {
|
352 | const flushed = flushPending();
|
353 | if ((import.meta.env ? import.meta.env.MODE : void 0) !== "production") {
|
354 | storeListenersRev2.forEach(
|
355 | (l) => l({ type: "async-write", flushed })
|
356 | );
|
357 | }
|
358 | }
|
359 | return r;
|
360 | };
|
361 | const result = atom.write(getter, setter, ...args);
|
362 | isSync = false;
|
363 | return result;
|
364 | };
|
365 | const writeAtom = (atom, ...args) => {
|
366 | const result = writeAtomState(atom, ...args);
|
367 | const flushed = flushPending();
|
368 | if ((import.meta.env ? import.meta.env.MODE : void 0) !== "production") {
|
369 | storeListenersRev2.forEach(
|
370 | (l) => l({ type: "write", flushed })
|
371 | );
|
372 | }
|
373 | return result;
|
374 | };
|
375 | const mountAtom = (atom, initialDependent) => {
|
376 | const mounted = {
|
377 | t: new Set(initialDependent && [initialDependent]),
|
378 | l: new Set()
|
379 | };
|
380 | mountedMap.set(atom, mounted);
|
381 | if ((import.meta.env ? import.meta.env.MODE : void 0) !== "production") {
|
382 | mountedAtoms.add(atom);
|
383 | }
|
384 | readAtomState(atom).d.forEach((_, a) => {
|
385 | const aMounted = mountedMap.get(a);
|
386 | if (aMounted) {
|
387 | aMounted.t.add(atom);
|
388 | } else {
|
389 | if (a !== atom) {
|
390 | mountAtom(a, atom);
|
391 | }
|
392 | }
|
393 | });
|
394 | readAtomState(atom);
|
395 | if (isActuallyWritableAtom(atom) && atom.onMount) {
|
396 | const onUnmount = atom.onMount((...args) => writeAtom(atom, ...args));
|
397 | if (onUnmount) {
|
398 | mounted.u = onUnmount;
|
399 | }
|
400 | }
|
401 | return mounted;
|
402 | };
|
403 | const unmountAtom = (atom) => {
|
404 | var _a;
|
405 | const onUnmount = (_a = mountedMap.get(atom)) == null ? void 0 : _a.u;
|
406 | if (onUnmount) {
|
407 | onUnmount();
|
408 | }
|
409 | mountedMap.delete(atom);
|
410 | if ((import.meta.env ? import.meta.env.MODE : void 0) !== "production") {
|
411 | mountedAtoms.delete(atom);
|
412 | }
|
413 | const atomState = getAtomState(atom);
|
414 | if (atomState) {
|
415 | if (hasPromiseAtomValue(atomState)) {
|
416 | cancelPromise(atomState.v);
|
417 | }
|
418 | atomState.d.forEach((_, a) => {
|
419 | if (a !== atom) {
|
420 | const mounted = mountedMap.get(a);
|
421 | if (mounted) {
|
422 | mounted.t.delete(atom);
|
423 | if (canUnmountAtom(a, mounted)) {
|
424 | unmountAtom(a);
|
425 | }
|
426 | }
|
427 | }
|
428 | });
|
429 | } else if ((import.meta.env ? import.meta.env.MODE : void 0) !== "production") {
|
430 | console.warn("[Bug] could not find atom state to unmount", atom);
|
431 | }
|
432 | };
|
433 | const mountDependencies = (atom, atomState, prevDependencies) => {
|
434 | const depSet = new Set(atomState.d.keys());
|
435 | prevDependencies == null ? void 0 : prevDependencies.forEach((_, a) => {
|
436 | if (depSet.has(a)) {
|
437 | depSet.delete(a);
|
438 | return;
|
439 | }
|
440 | const mounted = mountedMap.get(a);
|
441 | if (mounted) {
|
442 | mounted.t.delete(atom);
|
443 | if (canUnmountAtom(a, mounted)) {
|
444 | unmountAtom(a);
|
445 | }
|
446 | }
|
447 | });
|
448 | depSet.forEach((a) => {
|
449 | const mounted = mountedMap.get(a);
|
450 | if (mounted) {
|
451 | mounted.t.add(atom);
|
452 | } else if (mountedMap.has(atom)) {
|
453 | mountAtom(a, atom);
|
454 | }
|
455 | });
|
456 | };
|
457 | const flushPending = () => {
|
458 | let flushed;
|
459 | if ((import.meta.env ? import.meta.env.MODE : void 0) !== "production") {
|
460 | flushed = new Set();
|
461 | }
|
462 | while (pendingMap.size) {
|
463 | const pending = Array.from(pendingMap);
|
464 | pendingMap.clear();
|
465 | pending.forEach(([atom, prevAtomState]) => {
|
466 | const atomState = getAtomState(atom);
|
467 | if (atomState) {
|
468 | if (atomState.d !== (prevAtomState == null ? void 0 : prevAtomState.d)) {
|
469 | mountDependencies(atom, atomState, prevAtomState == null ? void 0 : prevAtomState.d);
|
470 | }
|
471 | const mounted = mountedMap.get(atom);
|
472 | if (mounted && !
|
473 |
|
474 | (prevAtomState && !hasPromiseAtomValue(prevAtomState) && (isEqualAtomValue(prevAtomState, atomState) || isEqualAtomError(prevAtomState, atomState)))) {
|
475 | mounted.l.forEach((listener) => listener());
|
476 | if ((import.meta.env ? import.meta.env.MODE : void 0) !== "production") {
|
477 | flushed.add(atom);
|
478 | }
|
479 | }
|
480 | } else if ((import.meta.env ? import.meta.env.MODE : void 0) !== "production") {
|
481 | console.warn("[Bug] no atom state to flush");
|
482 | }
|
483 | });
|
484 | }
|
485 | if ((import.meta.env ? import.meta.env.MODE : void 0) !== "production") {
|
486 | storeListenersRev1.forEach((l) => l("state"));
|
487 | return flushed;
|
488 | }
|
489 | };
|
490 | const subscribeAtom = (atom, listener) => {
|
491 | const mounted = addAtom(atom);
|
492 | const flushed = flushPending();
|
493 | const listeners = mounted.l;
|
494 | listeners.add(listener);
|
495 | if ((import.meta.env ? import.meta.env.MODE : void 0) !== "production") {
|
496 | storeListenersRev1.forEach((l) => l("sub"));
|
497 | storeListenersRev2.forEach(
|
498 | (l) => l({ type: "sub", flushed })
|
499 | );
|
500 | }
|
501 | return () => {
|
502 | listeners.delete(listener);
|
503 | delAtom(atom);
|
504 | if ((import.meta.env ? import.meta.env.MODE : void 0) !== "production") {
|
505 | storeListenersRev1.forEach((l) => l("unsub"));
|
506 | storeListenersRev2.forEach((l) => l({ type: "unsub" }));
|
507 | }
|
508 | };
|
509 | };
|
510 | if ((import.meta.env ? import.meta.env.MODE : void 0) !== "production") {
|
511 | return {
|
512 | get: readAtom,
|
513 | set: writeAtom,
|
514 | sub: subscribeAtom,
|
515 | // store dev methods (these are tentative and subject to change without notice)
|
516 | dev_subscribe_store: (l, rev) => {
|
517 | if (rev !== 2) {
|
518 | console.warn(
|
519 | "The current StoreListener revision is 2. The older ones are deprecated."
|
520 | );
|
521 | storeListenersRev1.add(l);
|
522 | return () => {
|
523 | storeListenersRev1.delete(l);
|
524 | };
|
525 | }
|
526 | storeListenersRev2.add(l);
|
527 | return () => {
|
528 | storeListenersRev2.delete(l);
|
529 | };
|
530 | },
|
531 | dev_get_mounted_atoms: () => mountedAtoms.values(),
|
532 | dev_get_atom_state: (a) => atomStateMap.get(a),
|
533 | dev_get_mounted: (a) => mountedMap.get(a),
|
534 | dev_restore_atoms: (values) => {
|
535 | for (const [atom, valueOrPromise] of values) {
|
536 | if (hasInitialValue(atom)) {
|
537 | setAtomValueOrPromise(atom, valueOrPromise);
|
538 | recomputeDependents(atom);
|
539 | }
|
540 | }
|
541 | const flushed = flushPending();
|
542 | storeListenersRev2.forEach(
|
543 | (l) => l({ type: "restore", flushed })
|
544 | );
|
545 | }
|
546 | };
|
547 | }
|
548 | return {
|
549 | get: readAtom,
|
550 | set: writeAtom,
|
551 | sub: subscribeAtom
|
552 | };
|
553 | };
|
554 | let defaultStore;
|
555 | const getDefaultStore = () => {
|
556 | if (!defaultStore) {
|
557 | defaultStore = createStore();
|
558 | }
|
559 | return defaultStore;
|
560 | };
|
561 |
|
562 | export { atom, createStore, getDefaultStore };
|