UNPKG

18.7 kBJavaScriptView Raw
1let keyCount = 0;
2function 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
23const hasInitialValue = (atom) => "init" in atom;
24const isActuallyWritableAtom = (atom) => !!atom.write;
25const cancelPromiseMap = /* @__PURE__ */ new WeakMap();
26const registerCancelPromise = (promise, cancel) => {
27 cancelPromiseMap.set(promise, cancel);
28 promise.catch(() => {
29 }).finally(() => cancelPromiseMap.delete(promise));
30};
31const cancelPromise = (promise, next) => {
32 const cancel = cancelPromiseMap.get(promise);
33 if (cancel) {
34 cancelPromiseMap.delete(promise);
35 cancel(next);
36 }
37};
38const resolvePromise = (promise, value) => {
39 promise.status = "fulfilled";
40 promise.value = value;
41};
42const rejectPromise = (promise, e) => {
43 promise.status = "rejected";
44 promise.reason = e;
45};
46const isPromiseLike = (x) => typeof (x == null ? void 0 : x.then) === "function";
47const isEqualAtomValue = (a, b) => "v" in a && "v" in b && Object.is(a.v, b.v);
48const isEqualAtomError = (a, b) => "e" in a && "e" in b && Object.is(a.e, b.e);
49const hasPromiseAtomValue = (a) => "v" in a && a.v instanceof Promise;
50const isEqualPromiseAtomValue = (a, b) => "v" in a && "v" in b && a.v.orig && a.v.orig === b.v.orig;
51const returnAtomValue = (atomState) => {
52 if ("e" in atomState) {
53 throw atomState.e;
54 }
55 return atomState.v;
56};
57const createStore = () => {
58 const atomStateMap = /* @__PURE__ */ new WeakMap();
59 const mountedMap = /* @__PURE__ */ new WeakMap();
60 const pendingMap = /* @__PURE__ */ 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 = /* @__PURE__ */ new Set();
66 storeListenersRev2 = /* @__PURE__ */ new Set();
67 mountedAtoms = /* @__PURE__ */ 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 = /* @__PURE__ */ 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) || /* @__PURE__ */ 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) || /* @__PURE__ */ 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 = /* @__PURE__ */ 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 = /* @__PURE__ */ new Map();
293 const dirtyMap = /* @__PURE__ */ 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) || /* @__PURE__ */ 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: /* @__PURE__ */ 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 = /* @__PURE__ */ 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 && !// TODO This seems pretty hacky. Hope to fix it.
473 // Maybe we could `mountDependencies` in `setAtomState`?
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};
554let defaultStore;
555const getDefaultStore = () => {
556 if (!defaultStore) {
557 defaultStore = createStore();
558 }
559 return defaultStore;
560};
561
562export { atom, createStore, getDefaultStore };