UNPKG

13.2 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 isEqualAtomValue = (a, b) => "v" in a && "v" in b && Object.is(a.v, b.v);
39const isEqualAtomError = (a, b) => "e" in a && "e" in b && Object.is(a.e, b.e);
40const hasPromiseAtomValue = (a) => "v" in a && a.v instanceof Promise;
41const returnAtomValue = (atomState) => {
42 if ("e" in atomState) {
43 throw atomState.e;
44 }
45 return atomState.v;
46};
47const createStore = () => {
48 const atomStateMap = /* @__PURE__ */ new WeakMap();
49 const mountedMap = /* @__PURE__ */ new WeakMap();
50 const pendingMap = /* @__PURE__ */ new Map();
51 let stateListeners;
52 let mountedAtoms;
53 if ((import.meta.env && import.meta.env.MODE) !== "production") {
54 stateListeners = /* @__PURE__ */ new Set();
55 mountedAtoms = /* @__PURE__ */ new Set();
56 }
57 const getAtomState = (atom) => atomStateMap.get(atom);
58 const setAtomState = (atom, atomState) => {
59 if ((import.meta.env && import.meta.env.MODE) !== "production") {
60 Object.freeze(atomState);
61 }
62 const prevAtomState = atomStateMap.get(atom);
63 atomStateMap.set(atom, atomState);
64 if (!pendingMap.has(atom)) {
65 pendingMap.set(atom, prevAtomState);
66 }
67 if (prevAtomState && hasPromiseAtomValue(prevAtomState)) {
68 const next = "v" in atomState ? atomState.v instanceof Promise ? atomState.v : Promise.resolve(atomState.v) : Promise.reject(atomState.e);
69 cancelPromise(prevAtomState.v, next);
70 }
71 };
72 const updateDependencies = (atom, nextAtomState, depSet) => {
73 const dependencies = /* @__PURE__ */ new Map();
74 let changed = false;
75 depSet.forEach((a) => {
76 const aState = a === atom ? nextAtomState : getAtomState(a);
77 if (aState) {
78 dependencies.set(a, aState);
79 if (nextAtomState.d.get(a) !== aState) {
80 changed = true;
81 }
82 } else if ((import.meta.env && import.meta.env.MODE) !== "production") {
83 console.warn("[Bug] atom state not found");
84 }
85 });
86 if (changed || nextAtomState.d.size !== dependencies.size) {
87 nextAtomState.d = dependencies;
88 }
89 };
90 const setAtomValue = (atom, value, depSet) => {
91 const prevAtomState = getAtomState(atom);
92 const nextAtomState = {
93 d: (prevAtomState == null ? void 0 : prevAtomState.d) || /* @__PURE__ */ new Map(),
94 v: value
95 };
96 if (depSet) {
97 updateDependencies(atom, nextAtomState, depSet);
98 }
99 if (prevAtomState && isEqualAtomValue(prevAtomState, nextAtomState) && prevAtomState.d === nextAtomState.d) {
100 return prevAtomState;
101 }
102 setAtomState(atom, nextAtomState);
103 return nextAtomState;
104 };
105 const setAtomError = (atom, error, depSet) => {
106 const prevAtomState = getAtomState(atom);
107 const nextAtomState = {
108 d: (prevAtomState == null ? void 0 : prevAtomState.d) || /* @__PURE__ */ new Map(),
109 e: error
110 };
111 if (depSet) {
112 updateDependencies(atom, nextAtomState, depSet);
113 }
114 if (prevAtomState && isEqualAtomError(prevAtomState, nextAtomState) && prevAtomState.d === nextAtomState.d) {
115 return prevAtomState;
116 }
117 setAtomState(atom, nextAtomState);
118 return nextAtomState;
119 };
120 const readAtomState = (atom, force) => {
121 if (!force) {
122 const atomState = getAtomState(atom);
123 if (atomState) {
124 atomState.d.forEach((_, a) => {
125 if (a !== atom && !mountedMap.has(a)) {
126 readAtomState(a);
127 }
128 });
129 if (Array.from(atomState.d).every(
130 ([a, s]) => a === atom || getAtomState(a) === s
131 )) {
132 return atomState;
133 }
134 }
135 }
136 const depSet = /* @__PURE__ */ new Set();
137 let isSync = true;
138 const getter = (a) => {
139 depSet.add(a);
140 if (a === atom) {
141 const aState2 = getAtomState(a);
142 if (aState2) {
143 return returnAtomValue(aState2);
144 }
145 if (hasInitialValue(a)) {
146 return a.init;
147 }
148 throw new Error("no atom init");
149 }
150 const aState = readAtomState(a);
151 return returnAtomValue(aState);
152 };
153 let controller;
154 let setSelf;
155 const options = {
156 get signal() {
157 if (!controller) {
158 controller = new AbortController();
159 }
160 return controller.signal;
161 },
162 get setSelf() {
163 if ((import.meta.env && import.meta.env.MODE) !== "production" && !isActuallyWritableAtom(atom)) {
164 console.warn("setSelf function cannot be used with read-only atom");
165 }
166 if (!setSelf && isActuallyWritableAtom(atom)) {
167 setSelf = (...args) => {
168 if ((import.meta.env && import.meta.env.MODE) !== "production" && isSync) {
169 console.warn("setSelf function cannot be called in sync");
170 }
171 if (!isSync) {
172 return writeAtom(atom, ...args);
173 }
174 };
175 }
176 return setSelf;
177 }
178 };
179 try {
180 const value = atom.read(getter, options);
181 if (value instanceof Promise) {
182 let continuePromise;
183 const promise = new Promise((resolve, reject) => {
184 value.then(
185 (v) => {
186 promise.status = "fulfilled";
187 promise.value = v;
188 resolve(v);
189 },
190 (e) => {
191 promise.status = "rejected";
192 promise.reason = e;
193 reject(e);
194 }
195 ).finally(() => {
196 setAtomValue(atom, promise, depSet);
197 });
198 continuePromise = (next) => resolve(next);
199 });
200 promise.status = "pending";
201 registerCancelPromise(promise, (next) => {
202 if (next) {
203 continuePromise(next);
204 }
205 controller == null ? void 0 : controller.abort();
206 });
207 return setAtomValue(atom, promise, depSet);
208 }
209 return setAtomValue(atom, value, depSet);
210 } catch (error) {
211 return setAtomError(atom, error, depSet);
212 } finally {
213 isSync = false;
214 }
215 };
216 const readAtom = (atom) => returnAtomValue(readAtomState(atom));
217 const addAtom = (atom) => {
218 let mounted = mountedMap.get(atom);
219 if (!mounted) {
220 mounted = mountAtom(atom);
221 }
222 return mounted;
223 };
224 const canUnmountAtom = (atom, mounted) => !mounted.l.size && (!mounted.t.size || mounted.t.size === 1 && mounted.t.has(atom));
225 const delAtom = (atom) => {
226 const mounted = mountedMap.get(atom);
227 if (mounted && canUnmountAtom(atom, mounted)) {
228 unmountAtom(atom);
229 }
230 };
231 const recomputeDependents = (atom) => {
232 const mounted = mountedMap.get(atom);
233 mounted == null ? void 0 : mounted.t.forEach((dependent) => {
234 if (dependent !== atom) {
235 const prevAtomState = getAtomState(dependent);
236 const nextAtomState = readAtomState(dependent);
237 if (!prevAtomState || !isEqualAtomValue(prevAtomState, nextAtomState)) {
238 recomputeDependents(dependent);
239 }
240 }
241 });
242 };
243 const writeAtomState = (atom, ...args) => {
244 let isSync = true;
245 const getter = (a) => returnAtomValue(readAtomState(a));
246 const setter = (a, ...args2) => {
247 let r;
248 if (a === atom) {
249 if (!hasInitialValue(a)) {
250 throw new Error("atom not writable");
251 }
252 const prevAtomState = getAtomState(a);
253 const nextAtomState = setAtomValue(a, args2[0]);
254 if (!prevAtomState || !isEqualAtomValue(prevAtomState, nextAtomState)) {
255 recomputeDependents(a);
256 }
257 } else {
258 r = writeAtomState(a, ...args2);
259 }
260 if (!isSync) {
261 flushPending();
262 }
263 return r;
264 };
265 const result = atom.write(getter, setter, ...args);
266 isSync = false;
267 return result;
268 };
269 const writeAtom = (atom, ...args) => {
270 const result = writeAtomState(atom, ...args);
271 flushPending();
272 return result;
273 };
274 const mountAtom = (atom, initialDependent) => {
275 const mounted = {
276 t: new Set(initialDependent && [initialDependent]),
277 l: /* @__PURE__ */ new Set()
278 };
279 mountedMap.set(atom, mounted);
280 if ((import.meta.env && import.meta.env.MODE) !== "production") {
281 mountedAtoms.add(atom);
282 }
283 readAtomState(atom).d.forEach((_, a) => {
284 const aMounted = mountedMap.get(a);
285 if (aMounted) {
286 aMounted.t.add(atom);
287 } else {
288 if (a !== atom) {
289 mountAtom(a, atom);
290 }
291 }
292 });
293 readAtomState(atom);
294 if (isActuallyWritableAtom(atom) && atom.onMount) {
295 const onUnmount = atom.onMount((...args) => writeAtom(atom, ...args));
296 if (onUnmount) {
297 mounted.u = onUnmount;
298 }
299 }
300 return mounted;
301 };
302 const unmountAtom = (atom) => {
303 var _a;
304 const onUnmount = (_a = mountedMap.get(atom)) == null ? void 0 : _a.u;
305 if (onUnmount) {
306 onUnmount();
307 }
308 mountedMap.delete(atom);
309 if ((import.meta.env && import.meta.env.MODE) !== "production") {
310 mountedAtoms.delete(atom);
311 }
312 const atomState = getAtomState(atom);
313 if (atomState) {
314 if (hasPromiseAtomValue(atomState)) {
315 cancelPromise(atomState.v);
316 }
317 atomState.d.forEach((_, a) => {
318 if (a !== atom) {
319 const mounted = mountedMap.get(a);
320 if (mounted) {
321 mounted.t.delete(atom);
322 if (canUnmountAtom(a, mounted)) {
323 unmountAtom(a);
324 }
325 }
326 }
327 });
328 } else if ((import.meta.env && import.meta.env.MODE) !== "production") {
329 console.warn("[Bug] could not find atom state to unmount", atom);
330 }
331 };
332 const mountDependencies = (atom, atomState, prevDependencies) => {
333 const depSet = new Set(atomState.d.keys());
334 prevDependencies == null ? void 0 : prevDependencies.forEach((_, a) => {
335 if (depSet.has(a)) {
336 depSet.delete(a);
337 return;
338 }
339 const mounted = mountedMap.get(a);
340 if (mounted) {
341 mounted.t.delete(atom);
342 if (canUnmountAtom(a, mounted)) {
343 unmountAtom(a);
344 }
345 }
346 });
347 depSet.forEach((a) => {
348 const mounted = mountedMap.get(a);
349 if (mounted) {
350 mounted.t.add(atom);
351 } else if (mountedMap.has(atom)) {
352 mountAtom(a, atom);
353 }
354 });
355 };
356 const flushPending = () => {
357 while (pendingMap.size) {
358 const pending = Array.from(pendingMap);
359 pendingMap.clear();
360 pending.forEach(([atom, prevAtomState]) => {
361 const atomState = getAtomState(atom);
362 if (atomState) {
363 if (atomState.d !== (prevAtomState == null ? void 0 : prevAtomState.d)) {
364 mountDependencies(atom, atomState, prevAtomState == null ? void 0 : prevAtomState.d);
365 }
366 const mounted = mountedMap.get(atom);
367 mounted == null ? void 0 : mounted.l.forEach((listener) => listener());
368 } else if ((import.meta.env && import.meta.env.MODE) !== "production") {
369 console.warn("[Bug] no atom state to flush");
370 }
371 });
372 }
373 if ((import.meta.env && import.meta.env.MODE) !== "production") {
374 stateListeners.forEach((l) => l());
375 }
376 };
377 const subscribeAtom = (atom, listener) => {
378 const mounted = addAtom(atom);
379 const listeners = mounted.l;
380 listeners.add(listener);
381 flushPending();
382 return () => {
383 listeners.delete(listener);
384 delAtom(atom);
385 };
386 };
387 const restoreAtoms = (values) => {
388 for (const [atom, value] of values) {
389 if (hasInitialValue(atom)) {
390 setAtomValue(atom, value);
391 recomputeDependents(atom);
392 }
393 }
394 flushPending();
395 };
396 if ((import.meta.env && import.meta.env.MODE) !== "production") {
397 return {
398 get: readAtom,
399 set: writeAtom,
400 sub: subscribeAtom,
401 res: restoreAtoms,
402 dev_subscribe_state: (l) => {
403 stateListeners.add(l);
404 return () => {
405 stateListeners.delete(l);
406 };
407 },
408 dev_get_mounted_atoms: () => mountedAtoms.values(),
409 dev_get_atom_state: (a) => atomStateMap.get(a),
410 dev_get_mounted: (a) => mountedMap.get(a)
411 };
412 }
413 return {
414 get: readAtom,
415 set: writeAtom,
416 sub: subscribeAtom,
417 res: restoreAtoms
418 };
419};
420let defaultStore;
421const getDefaultStore = () => {
422 if (!defaultStore) {
423 defaultStore = createStore();
424 }
425 return defaultStore;
426};
427
428export { atom, createStore, getDefaultStore };