UNPKG

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