@yaasl/core 0.7.0 → 0.8.0-alpha.1
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.
- package/dist/@types/base/Stateful.d.ts +3 -1
- package/dist/@types/base/atom.d.ts +9 -10
- package/dist/@types/base/derive.d.ts +37 -11
- package/dist/@types/middleware/MiddlewareDispatcher.d.ts +2 -1
- package/dist/@types/utils/Scheduler.d.ts +7 -0
- package/dist/@types/utils/Thenable.d.ts +2 -0
- package/dist/@types/utils/isPromise.d.ts +1 -0
- package/dist/@types/utils/isPromiseLike.d.ts +1 -0
- package/dist/cjs/base/Stateful.js +4 -3
- package/dist/cjs/base/derive.js +61 -16
- package/dist/cjs/middleware/MiddlewareDispatcher.js +19 -16
- package/dist/cjs/utils/Scheduler.js +31 -0
- package/dist/cjs/utils/Thenable.js +17 -6
- package/dist/cjs/utils/isPromise.js +5 -0
- package/dist/cjs/utils/isPromiseLike.js +8 -0
- package/dist/mjs/base/Stateful.js +4 -3
- package/dist/mjs/base/derive.js +59 -15
- package/dist/mjs/middleware/MiddlewareDispatcher.js +19 -16
- package/dist/mjs/utils/Scheduler.js +27 -0
- package/dist/mjs/utils/Thenable.js +17 -6
- package/dist/mjs/utils/isPromise.js +1 -0
- package/dist/mjs/utils/isPromiseLike.js +4 -0
- package/package.json +4 -8
|
@@ -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:
|
|
17
|
+
subscribe(callback: Callback<Value>): () => boolean;
|
|
17
18
|
private emit;
|
|
18
19
|
protected update(value: Value): void;
|
|
19
20
|
}
|
|
21
|
+
export {};
|
|
@@ -1,30 +1,30 @@
|
|
|
1
1
|
import { SetStateAction } from "@yaasl/utils";
|
|
2
2
|
import { Stateful } from "./Stateful";
|
|
3
3
|
import { MiddlewareAtomCallback } from "../middleware/middleware";
|
|
4
|
-
interface AtomConfig<
|
|
4
|
+
export interface AtomConfig<Value> {
|
|
5
5
|
/** Value that will be returned if the atom is not defined in the store */
|
|
6
|
-
defaultValue:
|
|
6
|
+
defaultValue: Value;
|
|
7
7
|
/** Name of the atom. Must be unique among all atoms. */
|
|
8
8
|
name?: string;
|
|
9
9
|
/** Middleware that will be applied on the atom */
|
|
10
10
|
middleware?: MiddlewareAtomCallback<any>[];
|
|
11
11
|
}
|
|
12
|
-
export declare class Atom<
|
|
13
|
-
readonly defaultValue:
|
|
12
|
+
export declare class Atom<Value = unknown> extends Stateful<Value> {
|
|
13
|
+
readonly defaultValue: Value;
|
|
14
14
|
readonly name: string;
|
|
15
|
-
didInit:
|
|
16
|
-
constructor({ defaultValue, name, middleware, }: AtomConfig<
|
|
15
|
+
didInit: PromiseLike<void> | boolean;
|
|
16
|
+
constructor({ defaultValue, name, middleware, }: AtomConfig<Value>);
|
|
17
17
|
/** Set the value of the atom.
|
|
18
18
|
*
|
|
19
19
|
* @param next New value or function to create the
|
|
20
20
|
* new value based off the previous value.
|
|
21
21
|
*/
|
|
22
|
-
set(next: SetStateAction<
|
|
22
|
+
set(next: SetStateAction<Value>): void;
|
|
23
23
|
/** Resolve the value of a promise and set as atom value.
|
|
24
24
|
*
|
|
25
25
|
* @param promise Promise to unwrap
|
|
26
26
|
*/
|
|
27
|
-
unwrap(promise: Promise<
|
|
27
|
+
unwrap(promise: Promise<Value>): Promise<Value>;
|
|
28
28
|
}
|
|
29
29
|
/** Creates an atom store.
|
|
30
30
|
*
|
|
@@ -34,5 +34,4 @@ export declare class Atom<AtomValue = unknown> extends Stateful<AtomValue> {
|
|
|
34
34
|
*
|
|
35
35
|
* @returns An atom instance.
|
|
36
36
|
**/
|
|
37
|
-
export declare const atom: <
|
|
38
|
-
export {};
|
|
37
|
+
export declare const atom: <Value>(config: AtomConfig<Value>) => Atom<Value>;
|
|
@@ -1,19 +1,45 @@
|
|
|
1
|
+
import { SetStateAction } from "@yaasl/utils";
|
|
2
|
+
import { Atom } from "./atom";
|
|
1
3
|
import { Stateful } from "./Stateful";
|
|
2
|
-
type
|
|
4
|
+
type GetterFn<Value> = (context: {
|
|
3
5
|
get: <V>(dep: Stateful<V>) => V;
|
|
4
|
-
}) =>
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
6
|
+
}) => Value;
|
|
7
|
+
type SetterFn<Value> = (context: {
|
|
8
|
+
value: Value;
|
|
9
|
+
set: <V>(dep: Atom<V> | SettableDerive<V>, next: SetStateAction<V>) => void;
|
|
10
|
+
}) => void;
|
|
11
|
+
export declare class Derive<Value> extends Stateful<Value> {
|
|
12
|
+
private readonly getter;
|
|
13
|
+
protected readonly getterDependencies: Set<Stateful<any>>;
|
|
14
|
+
constructor(getter: GetterFn<Value>);
|
|
15
|
+
private addGetDependency;
|
|
10
16
|
private deriveUpdate;
|
|
11
17
|
}
|
|
12
|
-
|
|
18
|
+
export declare class SettableDerive<Value = unknown> extends Derive<Value> {
|
|
19
|
+
private readonly setter;
|
|
20
|
+
protected readonly setterDependencies: Set<Atom<any> | SettableDerive<any>>;
|
|
21
|
+
constructor(getter: GetterFn<Value>, setter: SetterFn<Value>);
|
|
22
|
+
/** Set the value of the derived atom.
|
|
23
|
+
*
|
|
24
|
+
* @param next New value or function to create the
|
|
25
|
+
* new value based off the previous value.
|
|
26
|
+
*/
|
|
27
|
+
set(next: SetStateAction<Value>): void;
|
|
28
|
+
private addSetDependency;
|
|
29
|
+
private compareDependencies;
|
|
30
|
+
}
|
|
31
|
+
/** A derive atom that allows deriving and elevating values from and
|
|
32
|
+
* to one or multiple other stateful elements.
|
|
33
|
+
*
|
|
34
|
+
* **Note:**
|
|
35
|
+
* - `getter` and `setter` should not have any side effects
|
|
36
|
+
* - `getter` and `setter` must use the same atoms
|
|
13
37
|
*
|
|
14
|
-
* @param
|
|
38
|
+
* @param getter Function to derive a new value from other stateful elements.
|
|
39
|
+
* @param setter Function to elevate a new value to it's stateful dependents.
|
|
15
40
|
*
|
|
16
|
-
* @returns A
|
|
41
|
+
* @returns A derive instance.
|
|
17
42
|
**/
|
|
18
|
-
export declare
|
|
43
|
+
export declare function derive<Value>(getter: GetterFn<Value>): Derive<Value>;
|
|
44
|
+
export declare function derive<Value>(getter: GetterFn<Value>, setter: SetterFn<Value>): SettableDerive<Value>;
|
|
19
45
|
export {};
|
|
@@ -5,8 +5,9 @@ interface MiddlewareDispatcherConstructor {
|
|
|
5
5
|
middleware: MiddlewareAtomCallback<any>[];
|
|
6
6
|
}
|
|
7
7
|
export declare class MiddlewareDispatcher {
|
|
8
|
-
didInit:
|
|
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;
|
|
@@ -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(
|
|
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
|
}
|
package/dist/cjs/base/derive.js
CHANGED
|
@@ -1,32 +1,77 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.derive = exports.Derive = void 0;
|
|
3
|
+
exports.derive = exports.SettableDerive = exports.Derive = void 0;
|
|
4
|
+
const utils_1 = require("@yaasl/utils");
|
|
4
5
|
const Stateful_1 = require("./Stateful");
|
|
5
6
|
class Derive extends Stateful_1.Stateful {
|
|
6
|
-
constructor(
|
|
7
|
+
constructor(getter) {
|
|
7
8
|
super(undefined);
|
|
8
|
-
this.
|
|
9
|
+
this.getter = getter;
|
|
9
10
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
10
|
-
this.
|
|
11
|
-
this.value =
|
|
11
|
+
this.getterDependencies = new Set();
|
|
12
|
+
this.value = getter({ get: dep => this.addGetDependency(dep) });
|
|
12
13
|
}
|
|
13
|
-
|
|
14
|
-
if (!this.
|
|
14
|
+
addGetDependency(dependency) {
|
|
15
|
+
if (!this.getterDependencies.has(dependency)) {
|
|
15
16
|
dependency.subscribe(() => this.deriveUpdate());
|
|
16
|
-
this.
|
|
17
|
+
this.getterDependencies.add(dependency);
|
|
17
18
|
}
|
|
18
19
|
return dependency.get();
|
|
19
20
|
}
|
|
20
21
|
deriveUpdate() {
|
|
21
|
-
this.update(this.
|
|
22
|
+
this.update(this.getter({ get: dep => this.addGetDependency(dep) }));
|
|
22
23
|
}
|
|
23
24
|
}
|
|
24
25
|
exports.Derive = Derive;
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
26
|
+
class SettableDerive extends Derive {
|
|
27
|
+
constructor(getter, setter) {
|
|
28
|
+
super(getter);
|
|
29
|
+
this.setter = setter;
|
|
30
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
31
|
+
this.setterDependencies = new Set();
|
|
32
|
+
setter({
|
|
33
|
+
value: this.get(),
|
|
34
|
+
set: dep => this.addSetDependency(dep),
|
|
35
|
+
});
|
|
36
|
+
if (!this.compareDependencies()) {
|
|
37
|
+
throw new Error((0, utils_1.consoleMessage)("The set and get dependencies of a derived atom do not match."));
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
/** Set the value of the derived atom.
|
|
41
|
+
*
|
|
42
|
+
* @param next New value or function to create the
|
|
43
|
+
* new value based off the previous value.
|
|
44
|
+
*/
|
|
45
|
+
set(next) {
|
|
46
|
+
const value = next instanceof Function ? next(this.get()) : next;
|
|
47
|
+
this.setter({
|
|
48
|
+
value,
|
|
49
|
+
set: (atom, next) => {
|
|
50
|
+
const value = next instanceof Function ? next(atom.get()) : next;
|
|
51
|
+
atom.set(value);
|
|
52
|
+
},
|
|
53
|
+
});
|
|
54
|
+
}
|
|
55
|
+
addSetDependency(dependency) {
|
|
56
|
+
if (!this.setterDependencies.has(dependency)) {
|
|
57
|
+
this.setterDependencies.add(dependency);
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
compareDependencies() {
|
|
61
|
+
const { getterDependencies, setterDependencies } = this;
|
|
62
|
+
if (getterDependencies.size !== setterDependencies.size) {
|
|
63
|
+
return false;
|
|
64
|
+
}
|
|
65
|
+
return Array.from(setterDependencies).every(dependency => getterDependencies.has(dependency));
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
exports.SettableDerive = SettableDerive;
|
|
69
|
+
function derive(getter, setter) {
|
|
70
|
+
if (setter) {
|
|
71
|
+
return new SettableDerive(getter, setter);
|
|
72
|
+
}
|
|
73
|
+
else {
|
|
74
|
+
return new Derive(getter);
|
|
75
|
+
}
|
|
76
|
+
}
|
|
32
77
|
exports.derive = derive;
|
|
@@ -1,34 +1,37 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.MiddlewareDispatcher = void 0;
|
|
4
|
-
const
|
|
5
|
-
const
|
|
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
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
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 =>
|
|
25
|
+
atom.subscribe(value => this.callMiddlewareAction("set", atom, () => value));
|
|
23
26
|
}
|
|
24
|
-
callMiddlewareAction(action, atom,
|
|
25
|
-
|
|
26
|
-
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|
|
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,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(
|
|
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
|
}
|
package/dist/mjs/base/derive.js
CHANGED
|
@@ -1,27 +1,71 @@
|
|
|
1
|
+
import { consoleMessage } from "@yaasl/utils";
|
|
1
2
|
import { Stateful } from "./Stateful";
|
|
2
3
|
export class Derive extends Stateful {
|
|
3
|
-
constructor(
|
|
4
|
+
constructor(getter) {
|
|
4
5
|
super(undefined);
|
|
5
|
-
this.
|
|
6
|
+
this.getter = getter;
|
|
6
7
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
7
|
-
this.
|
|
8
|
-
this.value =
|
|
8
|
+
this.getterDependencies = new Set();
|
|
9
|
+
this.value = getter({ get: dep => this.addGetDependency(dep) });
|
|
9
10
|
}
|
|
10
|
-
|
|
11
|
-
if (!this.
|
|
11
|
+
addGetDependency(dependency) {
|
|
12
|
+
if (!this.getterDependencies.has(dependency)) {
|
|
12
13
|
dependency.subscribe(() => this.deriveUpdate());
|
|
13
|
-
this.
|
|
14
|
+
this.getterDependencies.add(dependency);
|
|
14
15
|
}
|
|
15
16
|
return dependency.get();
|
|
16
17
|
}
|
|
17
18
|
deriveUpdate() {
|
|
18
|
-
this.update(this.
|
|
19
|
+
this.update(this.getter({ get: dep => this.addGetDependency(dep) }));
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
export class SettableDerive extends Derive {
|
|
23
|
+
constructor(getter, setter) {
|
|
24
|
+
super(getter);
|
|
25
|
+
this.setter = setter;
|
|
26
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
27
|
+
this.setterDependencies = new Set();
|
|
28
|
+
setter({
|
|
29
|
+
value: this.get(),
|
|
30
|
+
set: dep => this.addSetDependency(dep),
|
|
31
|
+
});
|
|
32
|
+
if (!this.compareDependencies()) {
|
|
33
|
+
throw new Error(consoleMessage("The set and get dependencies of a derived atom do not match."));
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
/** Set the value of the derived atom.
|
|
37
|
+
*
|
|
38
|
+
* @param next New value or function to create the
|
|
39
|
+
* new value based off the previous value.
|
|
40
|
+
*/
|
|
41
|
+
set(next) {
|
|
42
|
+
const value = next instanceof Function ? next(this.get()) : next;
|
|
43
|
+
this.setter({
|
|
44
|
+
value,
|
|
45
|
+
set: (atom, next) => {
|
|
46
|
+
const value = next instanceof Function ? next(atom.get()) : next;
|
|
47
|
+
atom.set(value);
|
|
48
|
+
},
|
|
49
|
+
});
|
|
50
|
+
}
|
|
51
|
+
addSetDependency(dependency) {
|
|
52
|
+
if (!this.setterDependencies.has(dependency)) {
|
|
53
|
+
this.setterDependencies.add(dependency);
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
compareDependencies() {
|
|
57
|
+
const { getterDependencies, setterDependencies } = this;
|
|
58
|
+
if (getterDependencies.size !== setterDependencies.size) {
|
|
59
|
+
return false;
|
|
60
|
+
}
|
|
61
|
+
return Array.from(setterDependencies).every(dependency => getterDependencies.has(dependency));
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
export function derive(getter, setter) {
|
|
65
|
+
if (setter) {
|
|
66
|
+
return new SettableDerive(getter, setter);
|
|
67
|
+
}
|
|
68
|
+
else {
|
|
69
|
+
return new Derive(getter);
|
|
19
70
|
}
|
|
20
71
|
}
|
|
21
|
-
/** Creates a value, derived from one or more atoms or other derived values.
|
|
22
|
-
*
|
|
23
|
-
* @param get Function to derive the new value.
|
|
24
|
-
*
|
|
25
|
-
* @returns A derived instance.
|
|
26
|
-
**/
|
|
27
|
-
export const derive = (get) => new Derive(get);
|
|
@@ -1,30 +1,33 @@
|
|
|
1
|
-
import {
|
|
2
|
-
|
|
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
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
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 =>
|
|
22
|
+
atom.subscribe(value => this.callMiddlewareAction("set", atom, () => value));
|
|
20
23
|
}
|
|
21
|
-
callMiddlewareAction(action, atom,
|
|
22
|
-
|
|
23
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
|
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;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@yaasl/core",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.8.0-alpha.1",
|
|
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": "
|
|
35
|
-
"test:watch": "
|
|
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.
|
|
42
|
-
},
|
|
43
|
-
"jest": {
|
|
44
|
-
"preset": "ts-jest",
|
|
45
|
-
"testEnvironment": "jsdom"
|
|
41
|
+
"@yaasl/utils": "0.8.0-alpha.1"
|
|
46
42
|
},
|
|
47
43
|
"eslintConfig": {
|
|
48
44
|
"extends": [
|