@yaasl/core 0.7.0 → 0.8.0-alpha.0

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,3 +1,4 @@
1
+ type Callback<Value> = (value: Value, previous: Value) => void;
1
2
  export declare class Stateful<Value = unknown> {
2
3
  protected value: Value;
3
4
  private listeners;
@@ -13,7 +14,8 @@ export declare class Stateful<Value = unknown> {
13
14
  *
14
15
  * @returns A callback to unsubscribe the passed callback.
15
16
  */
16
- subscribe(callback: (value: Value) => void): () => boolean;
17
+ subscribe(callback: Callback<Value>): () => boolean;
17
18
  private emit;
18
19
  protected update(value: Value): void;
19
20
  }
21
+ export {};
@@ -1,7 +1,7 @@
1
1
  import { SetStateAction } from "@yaasl/utils";
2
2
  import { Stateful } from "./Stateful";
3
3
  import { MiddlewareAtomCallback } from "../middleware/middleware";
4
- interface AtomConfig<AtomValue> {
4
+ export interface AtomConfig<AtomValue> {
5
5
  /** Value that will be returned if the atom is not defined in the store */
6
6
  defaultValue: AtomValue;
7
7
  /** Name of the atom. Must be unique among all atoms. */
@@ -12,7 +12,7 @@ interface AtomConfig<AtomValue> {
12
12
  export declare class Atom<AtomValue = unknown> extends Stateful<AtomValue> {
13
13
  readonly defaultValue: AtomValue;
14
14
  readonly name: string;
15
- didInit: Promise<void> | boolean;
15
+ didInit: PromiseLike<void> | boolean;
16
16
  constructor({ defaultValue, name, middleware, }: AtomConfig<AtomValue>);
17
17
  /** Set the value of the atom.
18
18
  *
@@ -35,4 +35,3 @@ export declare class Atom<AtomValue = unknown> extends Stateful<AtomValue> {
35
35
  * @returns An atom instance.
36
36
  **/
37
37
  export declare const atom: <AtomValue>(config: AtomConfig<AtomValue>) => Atom<AtomValue>;
38
- export {};
@@ -5,8 +5,9 @@ interface MiddlewareDispatcherConstructor {
5
5
  middleware: MiddlewareAtomCallback<any>[];
6
6
  }
7
7
  export declare class MiddlewareDispatcher {
8
- didInit: Promise<void> | boolean;
8
+ didInit: PromiseLike<void> | boolean;
9
9
  private middleware;
10
+ private scheduler;
10
11
  constructor({ atom, middleware }: MiddlewareDispatcherConstructor);
11
12
  private subscribeSetters;
12
13
  private callMiddlewareAction;
@@ -0,0 +1,7 @@
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 {};
@@ -3,5 +3,7 @@ export declare class Thenable<T = undefined> implements PromiseLike<T> {
3
3
  private value?;
4
4
  constructor(value?: T | undefined);
5
5
  then<Result = T, Reject = never>(onfulfilled?: Executor<T, Result>, onrejected?: Executor<any, Reject>): PromiseLike<Result | Reject>;
6
+ static isThenable(item: unknown): boolean;
7
+ static all(items: PromiseLike<any>[]): Promise<any[]> | Thenable<unknown[]>;
6
8
  }
7
9
  export {};
@@ -0,0 +1 @@
1
+ export declare const isPromise: (value: unknown) => value is Promise<any>;
@@ -0,0 +1 @@
1
+ export declare const isPromiseLike: (value: unknown) => value is PromiseLike<any>;
@@ -23,13 +23,14 @@ class Stateful {
23
23
  this.listeners.add(callback);
24
24
  return () => this.listeners.delete(callback);
25
25
  }
26
- emit() {
27
- this.listeners.forEach(listener => listener(this.get()));
26
+ emit(value, previous) {
27
+ this.listeners.forEach(listener => listener(value, previous));
28
28
  }
29
29
  update(value) {
30
30
  if (this.value !== value) {
31
+ const previous = this.value;
31
32
  this.value = value;
32
- this.emit();
33
+ this.emit(value, previous);
33
34
  }
34
35
  }
35
36
  }
@@ -1,34 +1,37 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.MiddlewareDispatcher = void 0;
4
- const Thenable_1 = require("../utils/Thenable");
5
- const isPromise = (value) => value instanceof Promise;
4
+ const isPromiseLike_1 = require("../utils/isPromiseLike");
5
+ const Scheduler_1 = require("../utils/Scheduler");
6
6
  class MiddlewareDispatcher {
7
7
  constructor({ atom, middleware }) {
8
8
  this.didInit = false;
9
9
  this.middleware = [];
10
+ this.scheduler = new Scheduler_1.Scheduler();
10
11
  this.middleware = middleware.map(create => create(atom));
11
- const result = this.callMiddlewareAction("init", atom)
12
- .then(() => this.subscribeSetters(atom))
13
- .then(() => this.callMiddlewareAction("didInit", atom))
14
- .then(() => {
12
+ this.callMiddlewareAction("init", atom);
13
+ this.subscribeSetters(atom);
14
+ this.callMiddlewareAction("didInit", atom);
15
+ const { queue } = this.scheduler;
16
+ if (!(0, isPromiseLike_1.isPromiseLike)(queue)) {
15
17
  this.didInit = true;
16
- });
17
- if (result instanceof Promise) {
18
- this.didInit = result;
18
+ return;
19
19
  }
20
+ this.didInit = queue.then(() => {
21
+ this.didInit = true;
22
+ });
20
23
  }
21
24
  subscribeSetters(atom) {
22
- atom.subscribe(value => void this.callMiddlewareAction("set", atom, value));
25
+ atom.subscribe(value => this.callMiddlewareAction("set", atom, () => value));
23
26
  }
24
- callMiddlewareAction(action, atom, value = atom.get()) {
25
- const result = this.middleware.map(middleware => {
26
- const { actions, options } = middleware;
27
+ callMiddlewareAction(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.middleware.map(({ actions, options }) => {
27
31
  const actionFn = actions[action];
28
- return actionFn === null || actionFn === void 0 ? void 0 : actionFn({ value, atom, options });
32
+ return () => actionFn === null || actionFn === void 0 ? void 0 : actionFn({ value: getValue(), atom, options });
29
33
  });
30
- const promises = result.filter(isPromise);
31
- return promises.length ? Promise.all(promises) : new Thenable_1.Thenable();
34
+ void this.scheduler.run(tasks);
32
35
  }
33
36
  }
34
37
  exports.MiddlewareDispatcher = MiddlewareDispatcher;
@@ -0,0 +1,31 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.Scheduler = void 0;
4
+ const isPromiseLike_1 = require("./isPromiseLike");
5
+ const Thenable_1 = require("./Thenable");
6
+ const toVoid = () => {
7
+ // omit args
8
+ };
9
+ class Scheduler {
10
+ constructor() {
11
+ this.queue = null;
12
+ }
13
+ run(...tasks) {
14
+ const run = () => this.execute(tasks.flat()).then(toVoid);
15
+ const promise = this.queue ? this.queue.then(run) : run();
16
+ this.queue = promise;
17
+ return promise.then(() => {
18
+ if (this.queue !== promise)
19
+ return;
20
+ this.queue = null;
21
+ });
22
+ }
23
+ execute(tasks) {
24
+ const result = tasks.map(task => {
25
+ const result = task();
26
+ return (0, isPromiseLike_1.isPromiseLike)(result) ? result : new Thenable_1.Thenable(result);
27
+ });
28
+ return Thenable_1.Thenable.all(result);
29
+ }
30
+ }
31
+ exports.Scheduler = Scheduler;
@@ -1,10 +1,7 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.Thenable = void 0;
4
- const isPromiselike = (value) => typeof value === "object" &&
5
- value !== null &&
6
- "then" in value &&
7
- typeof value.then === "function";
4
+ const isPromiseLike_1 = require("./isPromiseLike");
8
5
  class Thenable {
9
6
  constructor(value) {
10
7
  this.value = value;
@@ -12,14 +9,28 @@ class Thenable {
12
9
  then(onfulfilled, onrejected) {
13
10
  try {
14
11
  const result = !onfulfilled ? this.value : onfulfilled(this.value);
15
- return isPromiselike(result) ? result : new Thenable(result);
12
+ return (0, isPromiseLike_1.isPromiseLike)(result) ? result : new Thenable(result);
16
13
  }
17
14
  catch (error) {
18
15
  if (!onrejected)
19
16
  throw error;
20
17
  const result = onrejected(error);
21
- return isPromiselike(result) ? result : new Thenable(result);
18
+ return (0, isPromiseLike_1.isPromiseLike)(result) ? result : new Thenable(result);
22
19
  }
23
20
  }
21
+ static isThenable(item) {
22
+ return item instanceof Thenable;
23
+ }
24
+ static all(items) {
25
+ const onlyThenables = items.every(item => this.isThenable(item));
26
+ if (!onlyThenables) {
27
+ return Promise.all(items);
28
+ }
29
+ const result = [];
30
+ items.forEach(item => void item.then(value => {
31
+ result.push(value);
32
+ }));
33
+ return new Thenable(result);
34
+ }
24
35
  }
25
36
  exports.Thenable = Thenable;
@@ -0,0 +1,5 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.isPromise = void 0;
4
+ const isPromise = (value) => value instanceof Promise;
5
+ exports.isPromise = isPromise;
@@ -0,0 +1,8 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.isPromiseLike = void 0;
4
+ const isPromiseLike = (value) => typeof value === "object" &&
5
+ value !== null &&
6
+ "then" in value &&
7
+ typeof value.then === "function";
8
+ exports.isPromiseLike = isPromiseLike;
@@ -20,13 +20,14 @@ export class Stateful {
20
20
  this.listeners.add(callback);
21
21
  return () => this.listeners.delete(callback);
22
22
  }
23
- emit() {
24
- this.listeners.forEach(listener => listener(this.get()));
23
+ emit(value, previous) {
24
+ this.listeners.forEach(listener => listener(value, previous));
25
25
  }
26
26
  update(value) {
27
27
  if (this.value !== value) {
28
+ const previous = this.value;
28
29
  this.value = value;
29
- this.emit();
30
+ this.emit(value, previous);
30
31
  }
31
32
  }
32
33
  }
@@ -1,30 +1,33 @@
1
- import { Thenable } from "../utils/Thenable";
2
- const isPromise = (value) => value instanceof Promise;
1
+ import { isPromiseLike } from "../utils/isPromiseLike";
2
+ import { Scheduler } from "../utils/Scheduler";
3
3
  export class MiddlewareDispatcher {
4
4
  constructor({ atom, middleware }) {
5
5
  this.didInit = false;
6
6
  this.middleware = [];
7
+ this.scheduler = new Scheduler();
7
8
  this.middleware = middleware.map(create => create(atom));
8
- const result = this.callMiddlewareAction("init", atom)
9
- .then(() => this.subscribeSetters(atom))
10
- .then(() => this.callMiddlewareAction("didInit", atom))
11
- .then(() => {
9
+ this.callMiddlewareAction("init", atom);
10
+ this.subscribeSetters(atom);
11
+ this.callMiddlewareAction("didInit", atom);
12
+ const { queue } = this.scheduler;
13
+ if (!isPromiseLike(queue)) {
12
14
  this.didInit = true;
13
- });
14
- if (result instanceof Promise) {
15
- this.didInit = result;
15
+ return;
16
16
  }
17
+ this.didInit = queue.then(() => {
18
+ this.didInit = true;
19
+ });
17
20
  }
18
21
  subscribeSetters(atom) {
19
- atom.subscribe(value => void this.callMiddlewareAction("set", atom, value));
22
+ atom.subscribe(value => this.callMiddlewareAction("set", atom, () => value));
20
23
  }
21
- callMiddlewareAction(action, atom, value = atom.get()) {
22
- const result = this.middleware.map(middleware => {
23
- const { actions, options } = middleware;
24
+ callMiddlewareAction(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.middleware.map(({ actions, options }) => {
24
28
  const actionFn = actions[action];
25
- return actionFn?.({ value, atom, options });
29
+ return () => actionFn?.({ value: getValue(), atom, options });
26
30
  });
27
- const promises = result.filter(isPromise);
28
- return promises.length ? Promise.all(promises) : new Thenable();
31
+ void this.scheduler.run(tasks);
29
32
  }
30
33
  }
@@ -0,0 +1,27 @@
1
+ import { isPromiseLike } from "./isPromiseLike";
2
+ import { Thenable } from "./Thenable";
3
+ const toVoid = () => {
4
+ // omit args
5
+ };
6
+ export class Scheduler {
7
+ constructor() {
8
+ this.queue = null;
9
+ }
10
+ run(...tasks) {
11
+ const run = () => this.execute(tasks.flat()).then(toVoid);
12
+ const promise = this.queue ? this.queue.then(run) : run();
13
+ this.queue = promise;
14
+ return promise.then(() => {
15
+ if (this.queue !== promise)
16
+ return;
17
+ this.queue = null;
18
+ });
19
+ }
20
+ execute(tasks) {
21
+ const result = tasks.map(task => {
22
+ const result = task();
23
+ return isPromiseLike(result) ? result : new Thenable(result);
24
+ });
25
+ return Thenable.all(result);
26
+ }
27
+ }
@@ -1,7 +1,4 @@
1
- const isPromiselike = (value) => typeof value === "object" &&
2
- value !== null &&
3
- "then" in value &&
4
- typeof value.then === "function";
1
+ import { isPromiseLike } from "./isPromiseLike";
5
2
  export class Thenable {
6
3
  constructor(value) {
7
4
  this.value = value;
@@ -9,13 +6,27 @@ export class Thenable {
9
6
  then(onfulfilled, onrejected) {
10
7
  try {
11
8
  const result = !onfulfilled ? this.value : onfulfilled(this.value);
12
- return isPromiselike(result) ? result : new Thenable(result);
9
+ return isPromiseLike(result) ? result : new Thenable(result);
13
10
  }
14
11
  catch (error) {
15
12
  if (!onrejected)
16
13
  throw error;
17
14
  const result = onrejected(error);
18
- return isPromiselike(result) ? result : new Thenable(result);
15
+ return isPromiseLike(result) ? result : new Thenable(result);
19
16
  }
20
17
  }
18
+ static isThenable(item) {
19
+ return item instanceof Thenable;
20
+ }
21
+ static all(items) {
22
+ const onlyThenables = items.every(item => this.isThenable(item));
23
+ if (!onlyThenables) {
24
+ return Promise.all(items);
25
+ }
26
+ const result = [];
27
+ items.forEach(item => void item.then(value => {
28
+ result.push(value);
29
+ }));
30
+ return new Thenable(result);
31
+ }
21
32
  }
@@ -0,0 +1 @@
1
+ export const isPromise = (value) => value instanceof Promise;
@@ -0,0 +1,4 @@
1
+ export const isPromiseLike = (value) => typeof value === "object" &&
2
+ value !== null &&
3
+ "then" in value &&
4
+ typeof value.then === "function";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@yaasl/core",
3
- "version": "0.7.0",
3
+ "version": "0.8.0-alpha.0",
4
4
  "description": "yet another atomic store library (vanilla-js)",
5
5
  "author": "PrettyCoffee",
6
6
  "license": "MIT",
@@ -31,18 +31,14 @@
31
31
  "compile:mjs": "tsc -p ./tsconfig.node.json --outDir ./dist/mjs -m es2020 -t es2020",
32
32
  "compile:cjs": "tsc -p ./tsconfig.node.json --outDir ./dist/cjs -m commonjs -t es2015",
33
33
  "compile:types": "tsc -p ./tsconfig.node.json --outDir ./dist/@types --declaration --emitDeclarationOnly",
34
- "test": "jest --testMatch ./**/*.test.*",
35
- "test:watch": "npm run test -- --watchAll",
34
+ "test": "vitest run",
35
+ "test:watch": "vitest watch",
36
36
  "lint": "eslint ./src",
37
37
  "lint:fix": "eslint ./src --fix",
38
38
  "validate": "run-s lint test build"
39
39
  },
40
40
  "dependencies": {
41
- "@yaasl/utils": "0.7.0"
42
- },
43
- "jest": {
44
- "preset": "ts-jest",
45
- "testEnvironment": "jsdom"
41
+ "@yaasl/utils": "0.8.0-alpha.0"
46
42
  },
47
43
  "eslintConfig": {
48
44
  "extends": [