UNPKG

13.6 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 createPendingPair = () => [/* @__PURE__ */ new Set(), /* @__PURE__ */ new Set()];
33const addPending = (pendingPair, pending) => {
34 (pendingPair[0] || pendingPair[1]).add(pending);
35};
36const flushPending = (pendingPair, isAsync) => {
37 let pendingSet;
38 if (isAsync) {
39 if (pendingPair[0]) {
40 return;
41 }
42 pendingSet = pendingPair[1];
43 } else {
44 if (!pendingPair[0]) {
45 throw new Error("[Bug] cannot sync flush twice");
46 }
47 pendingSet = pendingPair[0];
48 }
49 const flushed = /* @__PURE__ */ new Set();
50 while (pendingSet.size) {
51 const copy = new Set(pendingSet);
52 pendingSet.clear();
53 copy.forEach((pending) => {
54 if (typeof pending === "function") {
55 pending();
56 } else {
57 const [atom, atomState] = pending;
58 if (!flushed.has(atom) && atomState.m) {
59 atomState.m.l.forEach((listener) => listener());
60 flushed.add(atom);
61 }
62 }
63 });
64 }
65 pendingPair[0] = void 0;
66 return flushed;
67};
68const CONTINUE_PROMISE = Symbol(
69 (import.meta.env ? import.meta.env.MODE : void 0) !== "production" ? "CONTINUE_PROMISE" : ""
70);
71const PENDING = "pending";
72const FULFILLED = "fulfilled";
73const REJECTED = "rejected";
74const isContinuablePromise = (promise) => typeof promise === "object" && promise !== null && CONTINUE_PROMISE in promise;
75const continuablePromiseMap = /* @__PURE__ */ new WeakMap();
76const createContinuablePromise = (promise, abort, complete) => {
77 if (!continuablePromiseMap.has(promise)) {
78 let continuePromise;
79 const p = new Promise((resolve, reject) => {
80 let curr = promise;
81 const onFullfilled = (me) => (v) => {
82 if (curr === me) {
83 p.status = FULFILLED;
84 p.value = v;
85 resolve(v);
86 complete();
87 }
88 };
89 const onRejected = (me) => (e) => {
90 if (curr === me) {
91 p.status = REJECTED;
92 p.reason = e;
93 reject(e);
94 complete();
95 }
96 };
97 promise.then(onFullfilled(promise), onRejected(promise));
98 continuePromise = (nextPromise, nextAbort) => {
99 if (nextPromise) {
100 continuablePromiseMap.set(nextPromise, p);
101 curr = nextPromise;
102 nextPromise.then(onFullfilled(nextPromise), onRejected(nextPromise));
103 }
104 abort();
105 abort = nextAbort;
106 };
107 });
108 p.status = PENDING;
109 p[CONTINUE_PROMISE] = continuePromise;
110 continuablePromiseMap.set(promise, p);
111 }
112 return continuablePromiseMap.get(promise);
113};
114const isPromiseLike = (x) => typeof (x == null ? void 0 : x.then) === "function";
115const getPendingContinuablePromise = (atomState) => {
116 var _a;
117 const value = (_a = atomState.s) == null ? void 0 : _a.v;
118 if (isContinuablePromise(value) && value.status === PENDING) {
119 return value;
120 }
121 return null;
122};
123const returnAtomValue = (atomState) => {
124 if ("e" in atomState.s) {
125 throw atomState.s.e;
126 }
127 return atomState.s.v;
128};
129const setAtomStateValueOrPromise = (atomState, valueOrPromise, abortPromise = () => {
130}, completePromise = () => {
131}) => {
132 const pendingPromise = getPendingContinuablePromise(atomState);
133 if (isPromiseLike(valueOrPromise)) {
134 if (pendingPromise) {
135 if (pendingPromise !== valueOrPromise) {
136 pendingPromise[CONTINUE_PROMISE](valueOrPromise, abortPromise);
137 }
138 } else {
139 const continuablePromise = createContinuablePromise(
140 valueOrPromise,
141 abortPromise,
142 completePromise
143 );
144 atomState.s = { v: continuablePromise };
145 }
146 } else {
147 if (pendingPromise) {
148 pendingPromise[CONTINUE_PROMISE](
149 Promise.resolve(valueOrPromise),
150 abortPromise
151 );
152 }
153 atomState.s = { v: valueOrPromise };
154 }
155};
156const createStore = () => {
157 const atomStateMap = /* @__PURE__ */ new WeakMap();
158 const getAtomState = (atom) => {
159 let atomState = atomStateMap.get(atom);
160 if (!atomState) {
161 atomState = { d: /* @__PURE__ */ new Map(), t: /* @__PURE__ */ new Set() };
162 atomStateMap.set(atom, atomState);
163 }
164 return atomState;
165 };
166 const clearDependencies = (atom) => {
167 const atomState = getAtomState(atom);
168 for (const a of atomState.d.keys()) {
169 getAtomState(a).t.delete(atom);
170 }
171 atomState.d.clear();
172 };
173 const addDependency = (atom, a, aState, isSync) => {
174 if ((import.meta.env ? import.meta.env.MODE : void 0) !== "production" && a === atom) {
175 throw new Error("[Bug] atom cannot depend on itself");
176 }
177 const atomState = getAtomState(atom);
178 atomState.d.set(a, aState.s);
179 aState.t.add(atom);
180 if (!isSync && atomState.m) {
181 const pendingPair = createPendingPair();
182 mountDependencies(pendingPair, atomState);
183 flushPending(pendingPair);
184 }
185 };
186 const readAtomState = (atom, force) => {
187 const atomState = getAtomState(atom);
188 if (!force && "s" in atomState) {
189 if (atomState.m) {
190 return atomState;
191 }
192 if (Array.from(atomState.d).every(([a, s]) => {
193 const aState = readAtomState(a);
194 return "v" in s && "v" in aState.s && Object.is(s.v, aState.s.v);
195 })) {
196 return atomState;
197 }
198 }
199 clearDependencies(atom);
200 let isSync = true;
201 const getter = (a) => {
202 if (isSelfAtom(atom, a)) {
203 const aState2 = getAtomState(a);
204 if (!aState2.s) {
205 if (hasInitialValue(a)) {
206 setAtomStateValueOrPromise(aState2, a.init);
207 } else {
208 throw new Error("no atom init");
209 }
210 }
211 return returnAtomValue(aState2);
212 }
213 const aState = readAtomState(a);
214 addDependency(atom, a, aState, isSync);
215 return returnAtomValue(aState);
216 };
217 let controller;
218 let setSelf;
219 const options = {
220 get signal() {
221 if (!controller) {
222 controller = new AbortController();
223 }
224 return controller.signal;
225 },
226 get setSelf() {
227 if ((import.meta.env ? import.meta.env.MODE : void 0) !== "production" && !isActuallyWritableAtom(atom)) {
228 console.warn("setSelf function cannot be used with read-only atom");
229 }
230 if (!setSelf && isActuallyWritableAtom(atom)) {
231 setSelf = (...args) => {
232 if ((import.meta.env ? import.meta.env.MODE : void 0) !== "production" && isSync) {
233 console.warn("setSelf function cannot be called in sync");
234 }
235 if (!isSync) {
236 return writeAtom(atom, ...args);
237 }
238 };
239 }
240 return setSelf;
241 }
242 };
243 try {
244 const valueOrPromise = atom.read(getter, options);
245 setAtomStateValueOrPromise(
246 atomState,
247 valueOrPromise,
248 () => controller == null ? void 0 : controller.abort(),
249 () => {
250 if (atomState.m) {
251 const pendingPair = createPendingPair();
252 mountDependencies(pendingPair, atomState);
253 flushPending(pendingPair);
254 }
255 }
256 );
257 return atomState;
258 } catch (error) {
259 atomState.s = { e: error };
260 return atomState;
261 } finally {
262 isSync = false;
263 }
264 };
265 const readAtom = (atom) => returnAtomValue(readAtomState(atom));
266 const recomputeDependents = (pendingPair, atom) => {
267 const topsortedAtoms = [];
268 const markedAtoms = /* @__PURE__ */ new Set();
269 const visit = (n) => {
270 if (markedAtoms.has(n)) {
271 return;
272 }
273 markedAtoms.add(n);
274 for (const m of getAtomState(n).t) {
275 if (n !== m) {
276 visit(m);
277 }
278 }
279 topsortedAtoms.push(n);
280 };
281 visit(atom);
282 const changedAtoms = /* @__PURE__ */ new Set([atom]);
283 for (let i = topsortedAtoms.length - 1; i >= 0; --i) {
284 const a = topsortedAtoms[i];
285 const aState = getAtomState(a);
286 const prev = aState.s;
287 let hasChangedDeps = false;
288 for (const dep of aState.d.keys()) {
289 if (dep !== a && changedAtoms.has(dep)) {
290 hasChangedDeps = true;
291 break;
292 }
293 }
294 if (hasChangedDeps) {
295 if (aState.m || getPendingContinuablePromise(aState)) {
296 readAtomState(a, true);
297 mountDependencies(pendingPair, aState);
298 if (!prev || !("v" in prev) || !("v" in aState.s) || !Object.is(prev.v, aState.s.v)) {
299 addPending(pendingPair, [a, aState]);
300 changedAtoms.add(a);
301 }
302 }
303 }
304 }
305 };
306 const writeAtomState = (pendingPair, atom, ...args) => {
307 const getter = (a) => returnAtomValue(readAtomState(a));
308 const setter = (a, ...args2) => {
309 let r;
310 if (isSelfAtom(atom, a)) {
311 if (!hasInitialValue(a)) {
312 throw new Error("atom not writable");
313 }
314 const aState = getAtomState(a);
315 const prev = aState.s;
316 const v = args2[0];
317 setAtomStateValueOrPromise(aState, v);
318 mountDependencies(pendingPair, aState);
319 const curr = aState.s;
320 if (!prev || !("v" in prev) || !("v" in curr) || !Object.is(prev.v, curr.v)) {
321 addPending(pendingPair, [a, aState]);
322 recomputeDependents(pendingPair, a);
323 }
324 } else {
325 r = writeAtomState(pendingPair, a, ...args2);
326 }
327 flushPending(pendingPair, true);
328 return r;
329 };
330 const result = atom.write(getter, setter, ...args);
331 return result;
332 };
333 const writeAtom = (atom, ...args) => {
334 const pendingPair = createPendingPair();
335 const result = writeAtomState(pendingPair, atom, ...args);
336 flushPending(pendingPair);
337 return result;
338 };
339 const mountDependencies = (pendingPair, atomState) => {
340 if (atomState.m && !getPendingContinuablePromise(atomState)) {
341 for (const a of atomState.d.keys()) {
342 if (!atomState.m.d.has(a)) {
343 mountAtom(pendingPair, a);
344 atomState.m.d.add(a);
345 }
346 }
347 for (const a of atomState.m.d || []) {
348 if (!atomState.d.has(a)) {
349 unmountAtom(pendingPair, a);
350 atomState.m.d.delete(a);
351 }
352 }
353 }
354 };
355 const mountAtom = (pendingPair, atom) => {
356 const atomState = getAtomState(atom);
357 if (!atomState.m) {
358 readAtomState(atom);
359 for (const a of atomState.d.keys()) {
360 mountAtom(pendingPair, a);
361 }
362 atomState.m = { l: /* @__PURE__ */ new Set(), d: new Set(atomState.d.keys()) };
363 if (isActuallyWritableAtom(atom) && atom.onMount) {
364 const mounted = atomState.m;
365 const { onMount } = atom;
366 addPending(pendingPair, () => {
367 const onUnmount = onMount(
368 (...args) => writeAtomState(pendingPair, atom, ...args)
369 );
370 if (onUnmount) {
371 mounted.u = onUnmount;
372 }
373 });
374 }
375 }
376 return atomState.m;
377 };
378 const unmountAtom = (pendingPair, atom) => {
379 const atomState = getAtomState(atom);
380 if (atomState.m && !atomState.m.l.size && !Array.from(atomState.t).some((a) => getAtomState(a).m)) {
381 const onUnmount = atomState.m.u;
382 if (onUnmount) {
383 addPending(pendingPair, onUnmount);
384 }
385 delete atomState.m;
386 for (const a of atomState.d.keys()) {
387 unmountAtom(pendingPair, a);
388 }
389 const pendingPromise = getPendingContinuablePromise(atomState);
390 if (pendingPromise) {
391 pendingPromise[CONTINUE_PROMISE](void 0, () => {
392 });
393 }
394 }
395 };
396 const subscribeAtom = (atom, listener) => {
397 const pendingPair = createPendingPair();
398 const mounted = mountAtom(pendingPair, atom);
399 flushPending(pendingPair);
400 const listeners = mounted.l;
401 listeners.add(listener);
402 return () => {
403 listeners.delete(listener);
404 const pendingPair2 = createPendingPair();
405 unmountAtom(pendingPair2, atom);
406 flushPending(pendingPair2);
407 };
408 };
409 if ((import.meta.env ? import.meta.env.MODE : void 0) !== "production") {
410 const store = {
411 get: readAtom,
412 set: writeAtom,
413 sub: subscribeAtom,
414 // store dev methods (these are tentative and subject to change without notice)
415 dev4_get_internal_weak_map: () => atomStateMap,
416 dev4_override_method: (key, fn) => {
417 store[key] = fn;
418 }
419 };
420 return store;
421 }
422 return {
423 get: readAtom,
424 set: writeAtom,
425 sub: subscribeAtom
426 };
427};
428let defaultStore;
429const getDefaultStore = () => {
430 if (!defaultStore) {
431 defaultStore = createStore();
432 if ((import.meta.env ? import.meta.env.MODE : void 0) !== "production") {
433 globalThis.__JOTAI_DEFAULT_STORE__ || (globalThis.__JOTAI_DEFAULT_STORE__ = defaultStore);
434 if (globalThis.__JOTAI_DEFAULT_STORE__ !== defaultStore) {
435 console.warn(
436 "Detected multiple Jotai instances. It may cause unexpected behavior with the default store. https://github.com/pmndrs/jotai/discussions/2044"
437 );
438 }
439 }
440 }
441 return defaultStore;
442};
443
444export { atom, createStore, getDefaultStore };