UNPKG

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