@yaasl/core 0.10.0-alpha.0 → 0.10.0-alpha.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,4 +1,4 @@
1
- import { SetStateAction } from "@yaasl/utils";
1
+ import { Updater } from "@yaasl/utils";
2
2
  import { Stateful } from "./Stateful";
3
3
  import { EffectAtomCallback } from "../effects/createEffect";
4
4
  export interface AtomConfig<Value> {
@@ -7,20 +7,22 @@ export interface AtomConfig<Value> {
7
7
  /** Name of the atom. Must be unique among all atoms. Defaults to "atom-{number}". */
8
8
  name?: string;
9
9
  /** Effects that will be applied on the atom. */
10
- effects?: EffectAtomCallback<any>[];
10
+ effects?: EffectAtomCallback<any, any>[];
11
11
  }
12
12
  export declare class Atom<Value = unknown> extends Stateful<Value> {
13
13
  /** Default value of the atom. */
14
14
  readonly defaultValue: Value;
15
15
  /** Identifier of the atom. */
16
16
  readonly name: string;
17
+ private effects;
17
18
  constructor({ defaultValue, name, effects: localEffects, }: AtomConfig<Value>);
19
+ private initEffects;
18
20
  /** Set the value of the atom.
19
21
  *
20
22
  * @param next New value or function to create the
21
23
  * new value based off the previous value.
22
24
  */
23
- set(next: SetStateAction<Value>): void;
25
+ set(next: Updater<Value>): void;
24
26
  }
25
27
  /** Creates an atom store.
26
28
  *
@@ -1,4 +1,4 @@
1
- import { SetStateAction } from "@yaasl/utils";
1
+ import { Updater } from "@yaasl/utils";
2
2
  import { Atom } from "./createAtom";
3
3
  import { Stateful } from "./Stateful";
4
4
  type GetterFn<Value> = (context: {
@@ -6,7 +6,7 @@ type GetterFn<Value> = (context: {
6
6
  }) => Value;
7
7
  type SetterFn<Value> = (context: {
8
8
  value: Value;
9
- set: <V>(dep: Atom<V> | SettableDerive<V>, next: SetStateAction<V>) => void;
9
+ set: <V>(dep: Atom<V> | SettableDerive<V>, next: Updater<V>) => void;
10
10
  }) => void;
11
11
  export declare class Derive<Value> extends Stateful<Value> {
12
12
  private readonly getter;
@@ -24,7 +24,7 @@ export declare class SettableDerive<Value = unknown> extends Derive<Value> {
24
24
  * @param next New value or function to create the
25
25
  * new value based off the previous value.
26
26
  */
27
- set(next: SetStateAction<Value>): void;
27
+ set(next: Updater<Value>): void;
28
28
  private addSetDependency;
29
29
  private compareDependencies;
30
30
  }
@@ -1,15 +1,15 @@
1
- import { EffectAtomCallback } from "./createEffect";
1
+ import { ActionType, EffectAtomCallback } from "./createEffect";
2
2
  import { Atom } from "../base/createAtom";
3
- interface EffectDispatcherConstructor {
4
- atom: Atom<any>;
5
- effects: EffectAtomCallback<any>[];
3
+ export declare class EffectActions<Value> {
4
+ private readonly atom;
5
+ private options;
6
+ private actions;
7
+ constructor(atom: Atom<Value>, effectCreator: EffectAtomCallback<unknown, Value>);
8
+ createAction(action: ActionType): ((prev: Value) => Value | PromiseLike<Value>) | undefined;
6
9
  }
7
- export declare class EffectDispatcher {
8
- didInit: PromiseLike<void> | boolean;
10
+ export declare class EffectDispatcher<Value> {
9
11
  private effects;
10
- private scheduler;
11
- constructor({ atom, effects }: EffectDispatcherConstructor);
12
- private subscribeSetters;
13
- private callEffectAction;
12
+ private queue;
13
+ constructor(atom: Atom<Value>, effects: EffectAtomCallback<unknown, Value>[]);
14
+ dispatch(action: ActionType, startValue: Value): PromiseLike<Value>;
14
15
  }
15
- export {};
@@ -1,13 +1,22 @@
1
+ import { Updater, Dispatch } from "@yaasl/utils";
1
2
  import { Atom } from "../base";
2
3
  export type ActionType = "init" | "didInit" | "set";
3
4
  export interface EffectPayload<Options = undefined, AtomValue = any> {
5
+ /** Current value of the atom */
4
6
  value: AtomValue;
7
+ /** Function to set the value of the atom */
8
+ set: Dispatch<Updater<AtomValue>>;
9
+ /** The atom which the effect is applied on */
5
10
  atom: Atom<AtomValue>;
11
+ /** Options passed to the effect */
6
12
  options: Options;
7
13
  }
8
- interface EffectActions<Options, AtomValue> {
9
- init?: (payload: EffectPayload<Options, AtomValue>) => Promise<any> | void;
10
- didInit?: (payload: EffectPayload<Options, AtomValue>) => Promise<any> | void;
14
+ export interface EffectActions<Options, AtomValue> {
15
+ /** Action to be called when the atom is created */
16
+ init?: (payload: EffectPayload<Options, AtomValue>) => PromiseLike<any> | void;
17
+ /** Action to be called after the init phase */
18
+ didInit?: (payload: EffectPayload<Options, AtomValue>) => PromiseLike<any> | void;
19
+ /** Action to be called when the atom's value is set */
11
20
  set?: (payload: EffectPayload<Options, AtomValue>) => void;
12
21
  }
13
22
  interface EffectSetupProps<Options, AtomValue> {
@@ -28,7 +37,7 @@ export interface Effect<Options = unknown, AtomValue = unknown> {
28
37
  * May return a promise that can be awaited by using `atom.didInit`.
29
38
  * - `didInit`: Action to be called when the atom is created, but after subscribing to `set` events.
30
39
  * May return a promise that can be awaited by using `atom.didInit`.
31
- * - `set`: Action to be called when the atom's `set` function is called.
40
+ * - `set`: Action to be called when the atom's value is set.
32
41
  *
33
42
  * @param setup Effect actions or function to create effect actions.
34
43
  * Effect actions are fired in the atom lifecycle, alongside to the subscriptions.
@@ -1,5 +1,5 @@
1
1
  export { createEffect } from "./createEffect";
2
- export type { Effect, EffectPayload } from "./createEffect";
2
+ export type { Effect, EffectPayload, EffectAtomCallback } from "./createEffect";
3
3
  export { localStorage } from "./localStorage";
4
4
  export type { LocalStorageOptions, LocalStorageParser } from "./localStorage";
5
5
  export { indexedDb } from "./indexedDb";
@@ -12,4 +12,4 @@ export interface IndexedDbOptions {
12
12
  *
13
13
  * @returns The effect to be used on atoms.
14
14
  **/
15
- export declare const indexedDb: (...[optionsArg]: [] | [undefined] | [IndexedDbOptions]) => import("./createEffect").EffectAtomCallback<IndexedDbOptions | undefined, any>;
15
+ export declare const indexedDb: (...[optionsArg]: [] | [undefined] | [IndexedDbOptions]) => import("./createEffect").EffectAtomCallback<IndexedDbOptions | undefined, unknown>;
@@ -25,4 +25,4 @@ export interface LocalStorageOptions {
25
25
  *
26
26
  * @returns The effect to be used on atoms.
27
27
  **/
28
- export declare const localStorage: (...[optionsArg]: [] | [undefined] | [LocalStorageOptions]) => import("./createEffect").EffectAtomCallback<LocalStorageOptions | undefined, any>;
28
+ export declare const localStorage: (...[optionsArg]: [] | [undefined] | [LocalStorageOptions]) => import("./createEffect").EffectAtomCallback<LocalStorageOptions | undefined, unknown>;
@@ -0,0 +1,8 @@
1
+ type Task<Value> = (prev: Value) => PromiseLike<Value> | Value;
2
+ export declare class Queue<T = void> {
3
+ private last;
4
+ private queue;
5
+ push(...tasks: Task<T>[]): this;
6
+ run(prev: T): PromiseLike<T>;
7
+ }
8
+ export {};
@@ -1,6 +1,7 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.createAtom = exports.Atom = void 0;
4
+ const utils_1 = require("@yaasl/utils");
4
5
  const config_1 = require("./config");
5
6
  const Stateful_1 = require("./Stateful");
6
7
  const EffectDispatcher_1 = require("../effects/EffectDispatcher");
@@ -10,13 +11,33 @@ class Atom extends Stateful_1.Stateful {
10
11
  super(defaultValue);
11
12
  this.name = name;
12
13
  this.defaultValue = defaultValue;
13
- const effects = [...config_1.CONFIG.globalEffects, ...localEffects];
14
+ const effects = [
15
+ ...config_1.CONFIG.globalEffects,
16
+ ...localEffects,
17
+ ];
18
+ this.effects = new EffectDispatcher_1.EffectDispatcher(this, effects);
14
19
  if (effects.length === 0) {
15
- this.didInit = true;
20
+ this.setDidInit(true);
16
21
  return;
17
22
  }
18
- const { didInit } = new EffectDispatcher_1.EffectDispatcher({ atom: this, effects });
19
- this.setDidInit(didInit);
23
+ const result = this.initEffects();
24
+ if (result instanceof utils_1.Thenable) {
25
+ this.setDidInit(true);
26
+ }
27
+ else {
28
+ this.setDidInit(result.then(utils_1.toVoid));
29
+ }
30
+ }
31
+ initEffects() {
32
+ const updateValue = (value) => {
33
+ super.update(value);
34
+ return value;
35
+ };
36
+ return new utils_1.Thenable(this.defaultValue)
37
+ .then(value => this.effects.dispatch("init", value))
38
+ .then(updateValue)
39
+ .then(value => this.effects.dispatch("didInit", value))
40
+ .then(updateValue);
20
41
  }
21
42
  /** Set the value of the atom.
22
43
  *
@@ -24,8 +45,10 @@ class Atom extends Stateful_1.Stateful {
24
45
  * new value based off the previous value.
25
46
  */
26
47
  set(next) {
27
- const value = next instanceof Function ? next(this.get()) : next;
28
- super.update(value);
48
+ const newState = (0, utils_1.updater)(next, this.get());
49
+ void this.effects
50
+ .dispatch("set", newState)
51
+ .then(value => super.update(value));
29
52
  }
30
53
  }
31
54
  exports.Atom = Atom;
@@ -50,11 +50,11 @@ class SettableDerive extends Derive {
50
50
  * new value based off the previous value.
51
51
  */
52
52
  set(next) {
53
- const value = next instanceof Function ? next(this.get()) : next;
53
+ const value = (0, utils_1.updater)(next, this.get());
54
54
  this.setter({
55
55
  value,
56
56
  set: (atom, next) => {
57
- const value = next instanceof Function ? next(atom.get()) : next;
57
+ const value = (0, utils_1.updater)(next, atom.get());
58
58
  atom.set(value);
59
59
  },
60
60
  });
@@ -1,37 +1,47 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.EffectDispatcher = void 0;
3
+ exports.EffectDispatcher = exports.EffectActions = void 0;
4
4
  const utils_1 = require("@yaasl/utils");
5
- const Scheduler_1 = require("../utils/Scheduler");
6
- class EffectDispatcher {
7
- constructor({ atom, effects }) {
8
- this.didInit = false;
9
- this.effects = [];
10
- this.scheduler = new Scheduler_1.Scheduler();
11
- this.effects = effects.map(create => create(atom));
12
- this.callEffectAction("init", atom);
13
- this.subscribeSetters(atom);
14
- this.callEffectAction("didInit", atom);
15
- const { queue } = this.scheduler;
16
- if (!(0, utils_1.isPromiseLike)(queue)) {
17
- this.didInit = true;
5
+ const Queue_1 = require("../utils/Queue");
6
+ const isTruthy = (value) => !!value;
7
+ class EffectActions {
8
+ constructor(atom, effectCreator) {
9
+ this.atom = atom;
10
+ const { actions, options } = effectCreator(atom);
11
+ this.options = options;
12
+ this.actions = actions;
13
+ }
14
+ createAction(action) {
15
+ const actionFn = this.actions[action];
16
+ if (!actionFn)
18
17
  return;
19
- }
20
- this.didInit = queue.then(() => {
21
- this.didInit = true;
22
- });
18
+ return (prev) => {
19
+ const nextValue = { current: prev };
20
+ const set = (next) => (nextValue.current = (0, utils_1.updater)(next, nextValue.current));
21
+ const result = actionFn({
22
+ value: prev,
23
+ set,
24
+ atom: this.atom,
25
+ options: this.options,
26
+ });
27
+ return (0, utils_1.isPromiseLike)(result)
28
+ ? result.then(() => nextValue.current)
29
+ : nextValue.current;
30
+ };
23
31
  }
24
- subscribeSetters(atom) {
25
- atom.subscribe(value => this.callEffectAction("set", atom, () => value));
32
+ }
33
+ exports.EffectActions = EffectActions;
34
+ class EffectDispatcher {
35
+ constructor(atom, effects) {
36
+ this.effects = [];
37
+ this.queue = new Queue_1.Queue();
38
+ this.effects = effects.map(create => new EffectActions(atom, create));
26
39
  }
27
- callEffectAction(action, atom,
28
- /* Must be a function to make sure it is using the latest value when used in promise */
29
- getValue = () => atom.get()) {
30
- const tasks = this.effects.map(({ actions, options }) => {
31
- const actionFn = actions[action];
32
- return () => actionFn === null || actionFn === void 0 ? void 0 : actionFn({ value: getValue(), atom, options });
33
- });
34
- void this.scheduler.run(tasks);
40
+ dispatch(action, startValue) {
41
+ const tasks = this.effects
42
+ .map(effect => effect.createAction(action))
43
+ .filter(isTruthy);
44
+ return this.queue.push(...tasks).run(startValue);
35
45
  }
36
46
  }
37
47
  exports.EffectDispatcher = EffectDispatcher;
@@ -9,7 +9,7 @@ exports.createEffect = void 0;
9
9
  * May return a promise that can be awaited by using `atom.didInit`.
10
10
  * - `didInit`: Action to be called when the atom is created, but after subscribing to `set` events.
11
11
  * May return a promise that can be awaited by using `atom.didInit`.
12
- * - `set`: Action to be called when the atom's `set` function is called.
12
+ * - `set`: Action to be called when the atom's value is set.
13
13
  *
14
14
  * @param setup Effect actions or function to create effect actions.
15
15
  * Effect actions are fired in the atom lifecycle, alongside to the subscriptions.
@@ -28,20 +28,19 @@ exports.indexedDb = (0, createEffect_1.createEffect)(({ atom, options }) => {
28
28
  var _a;
29
29
  const key = (_a = options === null || options === void 0 ? void 0 : options.key) !== null && _a !== void 0 ? _a : atom.name;
30
30
  return {
31
- init: ({ atom }) => {
32
- var _a;
31
+ init: (_b) => __awaiter(void 0, [_b], void 0, function* ({ atom, set }) {
32
+ var _c;
33
33
  if (!atomDb) {
34
- atomDb = new Store_1.Store((_a = base_1.CONFIG.name) !== null && _a !== void 0 ? _a : "yaasl");
34
+ atomDb = new Store_1.Store((_c = base_1.CONFIG.name) !== null && _c !== void 0 ? _c : "yaasl");
35
35
  }
36
- return atomDb.get(key).then((value) => __awaiter(void 0, void 0, void 0, function* () {
37
- if (value !== undefined) {
38
- atom.set(value);
39
- }
40
- else {
41
- yield (atomDb === null || atomDb === void 0 ? void 0 : atomDb.set(key, atom.defaultValue));
42
- }
43
- }));
44
- },
36
+ const existing = yield atomDb.get(key);
37
+ if (existing != null) {
38
+ set(existing);
39
+ }
40
+ else {
41
+ yield atomDb.set(key, atom.defaultValue);
42
+ }
43
+ }),
45
44
  set: ({ value }) => {
46
45
  void (atomDb === null || atomDb === void 0 ? void 0 : atomDb.set(key, value));
47
46
  },
@@ -22,17 +22,19 @@ exports.localStorage = (0, createEffect_1.createEffect)(({ atom, options = {} })
22
22
  onTabSync: noTabSync ? undefined : value => atom.set(value),
23
23
  });
24
24
  return {
25
- init: ({ atom }) => {
25
+ init: ({ set }) => {
26
26
  const existing = storage.get();
27
- if (existing === null) {
28
- storage.set(atom.defaultValue);
27
+ if (existing != null) {
28
+ set(existing);
29
+ }
30
+ },
31
+ set: ({ value, atom }) => {
32
+ if (value === atom.defaultValue) {
33
+ storage.remove();
29
34
  }
30
35
  else {
31
- atom.set(existing);
36
+ storage.set(value);
32
37
  }
33
38
  },
34
- set: ({ value }) => {
35
- storage.set(value);
36
- },
37
39
  };
38
40
  });
@@ -69,7 +69,7 @@ const performMigration = (atom, version, migrations) => {
69
69
  * @returns The effect to be used on atoms.
70
70
  **/
71
71
  exports.migration = (0, createEffect_1.createEffect)({
72
- didInit: ({ atom, options }) => {
72
+ didInit: ({ atom, options, set }) => {
73
73
  var _a, _b;
74
74
  const steps = sortMigrations(options.steps);
75
75
  const currentVersion = getVersion(atom);
@@ -94,7 +94,7 @@ exports.migration = (0, createEffect_1.createEffect)({
94
94
  if (version == null)
95
95
  return;
96
96
  setVersion(atom, version);
97
- atom.set(data);
97
+ set(data);
98
98
  },
99
99
  });
100
100
  /** Helper to create a step for the migration effect.
@@ -0,0 +1,27 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.Queue = void 0;
4
+ const utils_1 = require("@yaasl/utils");
5
+ class Queue {
6
+ constructor() {
7
+ this.last = null;
8
+ this.queue = [];
9
+ }
10
+ push(...tasks) {
11
+ this.queue.push(...tasks);
12
+ return this;
13
+ }
14
+ run(prev) {
15
+ const init = this.last ? this.last.then(() => prev) : new utils_1.Thenable(prev);
16
+ const result = this.queue.reduce((result, next) => result.then(prev => next(prev)), init);
17
+ this.queue = [];
18
+ this.last = result.then(value => {
19
+ if (this.last === result) {
20
+ this.last = null;
21
+ }
22
+ return value;
23
+ });
24
+ return result;
25
+ }
26
+ }
27
+ exports.Queue = Queue;
@@ -1,3 +1,4 @@
1
+ import { updater, Thenable, toVoid } from "@yaasl/utils";
1
2
  import { CONFIG } from "./config";
2
3
  import { Stateful } from "./Stateful";
3
4
  import { EffectDispatcher } from "../effects/EffectDispatcher";
@@ -7,17 +8,38 @@ export class Atom extends Stateful {
7
8
  defaultValue;
8
9
  /** Identifier of the atom. */
9
10
  name;
11
+ effects;
10
12
  constructor({ defaultValue, name = `atom-${++key}`, effects: localEffects = [], }) {
11
13
  super(defaultValue);
12
14
  this.name = name;
13
15
  this.defaultValue = defaultValue;
14
- const effects = [...CONFIG.globalEffects, ...localEffects];
16
+ const effects = [
17
+ ...CONFIG.globalEffects,
18
+ ...localEffects,
19
+ ];
20
+ this.effects = new EffectDispatcher(this, effects);
15
21
  if (effects.length === 0) {
16
- this.didInit = true;
22
+ this.setDidInit(true);
17
23
  return;
18
24
  }
19
- const { didInit } = new EffectDispatcher({ atom: this, effects });
20
- this.setDidInit(didInit);
25
+ const result = this.initEffects();
26
+ if (result instanceof Thenable) {
27
+ this.setDidInit(true);
28
+ }
29
+ else {
30
+ this.setDidInit(result.then(toVoid));
31
+ }
32
+ }
33
+ initEffects() {
34
+ const updateValue = (value) => {
35
+ super.update(value);
36
+ return value;
37
+ };
38
+ return new Thenable(this.defaultValue)
39
+ .then(value => this.effects.dispatch("init", value))
40
+ .then(updateValue)
41
+ .then(value => this.effects.dispatch("didInit", value))
42
+ .then(updateValue);
21
43
  }
22
44
  /** Set the value of the atom.
23
45
  *
@@ -25,8 +47,10 @@ export class Atom extends Stateful {
25
47
  * new value based off the previous value.
26
48
  */
27
49
  set(next) {
28
- const value = next instanceof Function ? next(this.get()) : next;
29
- super.update(value);
50
+ const newState = updater(next, this.get());
51
+ void this.effects
52
+ .dispatch("set", newState)
53
+ .then(value => super.update(value));
30
54
  }
31
55
  }
32
56
  /** Creates an atom store.
@@ -1,4 +1,4 @@
1
- import { consoleMessage, toVoid } from "@yaasl/utils";
1
+ import { consoleMessage, toVoid, updater } from "@yaasl/utils";
2
2
  import { Stateful } from "./Stateful";
3
3
  const allDidInit = (atoms) => {
4
4
  const inits = atoms
@@ -48,11 +48,11 @@ export class SettableDerive extends Derive {
48
48
  * new value based off the previous value.
49
49
  */
50
50
  set(next) {
51
- const value = next instanceof Function ? next(this.get()) : next;
51
+ const value = updater(next, this.get());
52
52
  this.setter({
53
53
  value,
54
54
  set: (atom, next) => {
55
- const value = next instanceof Function ? next(atom.get()) : next;
55
+ const value = updater(next, atom.get());
56
56
  atom.set(value);
57
57
  },
58
58
  });
@@ -1,33 +1,45 @@
1
- import { isPromiseLike } from "@yaasl/utils";
2
- import { Scheduler } from "../utils/Scheduler";
3
- export class EffectDispatcher {
4
- didInit = false;
5
- effects = [];
6
- scheduler = new Scheduler();
7
- constructor({ atom, effects }) {
8
- this.effects = effects.map(create => create(atom));
9
- this.callEffectAction("init", atom);
10
- this.subscribeSetters(atom);
11
- this.callEffectAction("didInit", atom);
12
- const { queue } = this.scheduler;
13
- if (!isPromiseLike(queue)) {
14
- this.didInit = true;
1
+ import { isPromiseLike, updater } from "@yaasl/utils";
2
+ import { Queue } from "../utils/Queue";
3
+ const isTruthy = (value) => !!value;
4
+ export class EffectActions {
5
+ atom;
6
+ options;
7
+ actions;
8
+ constructor(atom, effectCreator) {
9
+ this.atom = atom;
10
+ const { actions, options } = effectCreator(atom);
11
+ this.options = options;
12
+ this.actions = actions;
13
+ }
14
+ createAction(action) {
15
+ const actionFn = this.actions[action];
16
+ if (!actionFn)
15
17
  return;
16
- }
17
- this.didInit = queue.then(() => {
18
- this.didInit = true;
19
- });
18
+ return (prev) => {
19
+ const nextValue = { current: prev };
20
+ const set = (next) => (nextValue.current = updater(next, nextValue.current));
21
+ const result = actionFn({
22
+ value: prev,
23
+ set,
24
+ atom: this.atom,
25
+ options: this.options,
26
+ });
27
+ return isPromiseLike(result)
28
+ ? result.then(() => nextValue.current)
29
+ : nextValue.current;
30
+ };
20
31
  }
21
- subscribeSetters(atom) {
22
- atom.subscribe(value => this.callEffectAction("set", atom, () => value));
32
+ }
33
+ export class EffectDispatcher {
34
+ effects = [];
35
+ queue = new Queue();
36
+ constructor(atom, effects) {
37
+ this.effects = effects.map(create => new EffectActions(atom, create));
23
38
  }
24
- callEffectAction(action, atom,
25
- /* Must be a function to make sure it is using the latest value when used in promise */
26
- getValue = () => atom.get()) {
27
- const tasks = this.effects.map(({ actions, options }) => {
28
- const actionFn = actions[action];
29
- return () => actionFn?.({ value: getValue(), atom, options });
30
- });
31
- void this.scheduler.run(tasks);
39
+ dispatch(action, startValue) {
40
+ const tasks = this.effects
41
+ .map(effect => effect.createAction(action))
42
+ .filter(isTruthy);
43
+ return this.queue.push(...tasks).run(startValue);
32
44
  }
33
45
  }
@@ -6,7 +6,7 @@
6
6
  * May return a promise that can be awaited by using `atom.didInit`.
7
7
  * - `didInit`: Action to be called when the atom is created, but after subscribing to `set` events.
8
8
  * May return a promise that can be awaited by using `atom.didInit`.
9
- * - `set`: Action to be called when the atom's `set` function is called.
9
+ * - `set`: Action to be called when the atom's value is set.
10
10
  *
11
11
  * @param setup Effect actions or function to create effect actions.
12
12
  * Effect actions are fired in the atom lifecycle, alongside to the subscriptions.
@@ -15,18 +15,17 @@ let atomDb = null;
15
15
  export const indexedDb = createEffect(({ atom, options }) => {
16
16
  const key = options?.key ?? atom.name;
17
17
  return {
18
- init: ({ atom }) => {
18
+ init: async ({ atom, set }) => {
19
19
  if (!atomDb) {
20
20
  atomDb = new Store(CONFIG.name ?? "yaasl");
21
21
  }
22
- return atomDb.get(key).then(async (value) => {
23
- if (value !== undefined) {
24
- atom.set(value);
25
- }
26
- else {
27
- await atomDb?.set(key, atom.defaultValue);
28
- }
29
- });
22
+ const existing = await atomDb.get(key);
23
+ if (existing != null) {
24
+ set(existing);
25
+ }
26
+ else {
27
+ await atomDb.set(key, atom.defaultValue);
28
+ }
30
29
  },
31
30
  set: ({ value }) => {
32
31
  void atomDb?.set(key, value);
@@ -19,17 +19,19 @@ export const localStorage = createEffect(({ atom, options = {} }) => {
19
19
  onTabSync: noTabSync ? undefined : value => atom.set(value),
20
20
  });
21
21
  return {
22
- init: ({ atom }) => {
22
+ init: ({ set }) => {
23
23
  const existing = storage.get();
24
- if (existing === null) {
25
- storage.set(atom.defaultValue);
24
+ if (existing != null) {
25
+ set(existing);
26
+ }
27
+ },
28
+ set: ({ value, atom }) => {
29
+ if (value === atom.defaultValue) {
30
+ storage.remove();
26
31
  }
27
32
  else {
28
- atom.set(existing);
33
+ storage.set(value);
29
34
  }
30
35
  },
31
- set: ({ value }) => {
32
- storage.set(value);
33
- },
34
36
  };
35
37
  });
@@ -66,7 +66,7 @@ const performMigration = (atom, version, migrations) => {
66
66
  * @returns The effect to be used on atoms.
67
67
  **/
68
68
  export const migration = createEffect({
69
- didInit: ({ atom, options }) => {
69
+ didInit: ({ atom, options, set }) => {
70
70
  const steps = sortMigrations(options.steps);
71
71
  const currentVersion = getVersion(atom);
72
72
  const isLatestVersion = currentVersion === steps.at(-1)?.version;
@@ -90,7 +90,7 @@ export const migration = createEffect({
90
90
  if (version == null)
91
91
  return;
92
92
  setVersion(atom, version);
93
- atom.set(data);
93
+ set(data);
94
94
  },
95
95
  });
96
96
  /** Helper to create a step for the migration effect.
@@ -0,0 +1,21 @@
1
+ import { Thenable } from "@yaasl/utils";
2
+ export class Queue {
3
+ last = null;
4
+ queue = [];
5
+ push(...tasks) {
6
+ this.queue.push(...tasks);
7
+ return this;
8
+ }
9
+ run(prev) {
10
+ const init = this.last ? this.last.then(() => prev) : new Thenable(prev);
11
+ const result = this.queue.reduce((result, next) => result.then(prev => next(prev)), init);
12
+ this.queue = [];
13
+ this.last = result.then(value => {
14
+ if (this.last === result) {
15
+ this.last = null;
16
+ }
17
+ return value;
18
+ });
19
+ return result;
20
+ }
21
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@yaasl/core",
3
- "version": "0.10.0-alpha.0",
3
+ "version": "0.10.0-alpha.2",
4
4
  "description": "yet another atomic store library (vanilla-js)",
5
5
  "author": "PrettyCoffee",
6
6
  "license": "MIT",
@@ -38,7 +38,7 @@
38
38
  "validate": "run-s lint test build"
39
39
  },
40
40
  "dependencies": {
41
- "@yaasl/utils": "0.10.0-alpha.0"
41
+ "@yaasl/utils": "0.10.0-alpha.2"
42
42
  },
43
43
  "eslintConfig": {
44
44
  "extends": [
@@ -1,7 +0,0 @@
1
- type Task = () => PromiseLike<unknown> | unknown;
2
- export declare class Scheduler {
3
- queue: PromiseLike<void> | null;
4
- run(...tasks: Task[] | Task[][]): PromiseLike<void>;
5
- private execute;
6
- }
7
- export {};
@@ -1,27 +0,0 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.Scheduler = void 0;
4
- const utils_1 = require("@yaasl/utils");
5
- class Scheduler {
6
- constructor() {
7
- this.queue = null;
8
- }
9
- run(...tasks) {
10
- const run = () => this.execute(tasks.flat()).then(utils_1.toVoid);
11
- const promise = this.queue ? this.queue.then(run) : run();
12
- this.queue = promise;
13
- return promise.then(() => {
14
- if (this.queue !== promise)
15
- return;
16
- this.queue = null;
17
- });
18
- }
19
- execute(tasks) {
20
- const result = tasks.map(task => {
21
- const result = task();
22
- return (0, utils_1.isPromiseLike)(result) ? result : new utils_1.Thenable(result);
23
- });
24
- return utils_1.Thenable.all(result);
25
- }
26
- }
27
- exports.Scheduler = Scheduler;
@@ -1,21 +0,0 @@
1
- import { toVoid, isPromiseLike, Thenable } from "@yaasl/utils";
2
- export class Scheduler {
3
- queue = null;
4
- run(...tasks) {
5
- const run = () => this.execute(tasks.flat()).then(toVoid);
6
- const promise = this.queue ? this.queue.then(run) : run();
7
- this.queue = promise;
8
- return promise.then(() => {
9
- if (this.queue !== promise)
10
- return;
11
- this.queue = null;
12
- });
13
- }
14
- execute(tasks) {
15
- const result = tasks.map(task => {
16
- const result = task();
17
- return isPromiseLike(result) ? result : new Thenable(result);
18
- });
19
- return Thenable.all(result);
20
- }
21
- }