@figliolia/galena 2.3.5 → 3.0.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/README.md +172 -476
- package/dist/Galena.cjs +102 -0
- package/dist/Galena.d.cts +80 -0
- package/dist/Galena.d.cts.map +1 -0
- package/dist/Galena.d.mts +80 -0
- package/dist/Galena.d.mts.map +1 -0
- package/dist/Galena.mjs +103 -0
- package/dist/Galena.mjs.map +1 -0
- package/dist/Logger.cjs +46 -0
- package/dist/Logger.d.cts +35 -0
- package/dist/Logger.d.cts.map +1 -0
- package/dist/Logger.d.mts +35 -0
- package/dist/Logger.d.mts.map +1 -0
- package/dist/Logger.mjs +48 -0
- package/dist/Logger.mjs.map +1 -0
- package/dist/Middleware.cjs +62 -0
- package/dist/Middleware.d.cts +65 -0
- package/dist/Middleware.d.cts.map +1 -0
- package/dist/Middleware.d.mts +65 -0
- package/dist/Middleware.d.mts.map +1 -0
- package/dist/Middleware.mjs +64 -0
- package/dist/Middleware.mjs.map +1 -0
- package/dist/Profiler.cjs +41 -0
- package/dist/Profiler.d.cts +31 -0
- package/dist/Profiler.d.cts.map +1 -0
- package/dist/Profiler.d.mts +31 -0
- package/dist/Profiler.d.mts.map +1 -0
- package/dist/Profiler.mjs +43 -0
- package/dist/Profiler.mjs.map +1 -0
- package/dist/State.cjs +147 -0
- package/dist/State.d.cts +114 -0
- package/dist/State.d.cts.map +1 -0
- package/dist/State.d.mts +114 -0
- package/dist/State.d.mts.map +1 -0
- package/dist/State.mjs +148 -0
- package/dist/State.mjs.map +1 -0
- package/dist/index.cjs +13 -0
- package/dist/index.d.cts +7 -0
- package/dist/index.d.mts +7 -0
- package/dist/index.mjs +6 -0
- package/dist/types.d.cts +16 -0
- package/dist/types.d.cts.map +1 -0
- package/dist/types.d.mts +16 -0
- package/dist/types.d.mts.map +1 -0
- package/media/Logging.png +0 -0
- package/media/Profiling.png +0 -0
- package/package.json +38 -59
- package/src/Galena.ts +120 -0
- package/src/{Middlewares/Logger.ts → Logger.ts} +15 -14
- package/src/Middleware.ts +62 -0
- package/src/Profiler.ts +53 -0
- package/src/State.ts +167 -0
- package/src/index.ts +6 -3
- package/src/types.ts +28 -0
- package/dist/cjs/Galena/Galena.js +0 -223
- package/dist/cjs/Galena/Guards.js +0 -40
- package/dist/cjs/Galena/Scheduler.js +0 -84
- package/dist/cjs/Galena/State.js +0 -314
- package/dist/cjs/Galena/index.js +0 -22
- package/dist/cjs/Galena/types.js +0 -9
- package/dist/cjs/Middleware/Middleware.js +0 -46
- package/dist/cjs/Middleware/index.js +0 -20
- package/dist/cjs/Middleware/types.js +0 -8
- package/dist/cjs/Middlewares/Logger.js +0 -51
- package/dist/cjs/Middlewares/Profiler.js +0 -38
- package/dist/cjs/Middlewares/index.js +0 -7
- package/dist/cjs/index.js +0 -19
- package/dist/cjs/package.json +0 -3
- package/dist/mjs/Galena/Galena.js +0 -218
- package/dist/mjs/Galena/Guards.js +0 -36
- package/dist/mjs/Galena/Scheduler.js +0 -79
- package/dist/mjs/Galena/State.js +0 -313
- package/dist/mjs/Galena/index.js +0 -3
- package/dist/mjs/Galena/types.js +0 -6
- package/dist/mjs/Middleware/Middleware.js +0 -42
- package/dist/mjs/Middleware/index.js +0 -2
- package/dist/mjs/Middleware/types.js +0 -5
- package/dist/mjs/Middlewares/Logger.js +0 -44
- package/dist/mjs/Middlewares/Profiler.js +0 -35
- package/dist/mjs/Middlewares/index.js +0 -2
- package/dist/mjs/index.js +0 -3
- package/dist/mjs/package.json +0 -4
- package/dist/types/Galena/Galena.d.ts +0 -160
- package/dist/types/Galena/Guards.d.ts +0 -29
- package/dist/types/Galena/Scheduler.d.ts +0 -51
- package/dist/types/Galena/State.d.ts +0 -235
- package/dist/types/Galena/index.d.ts +0 -3
- package/dist/types/Galena/types.d.ts +0 -13
- package/dist/types/Middleware/Middleware.d.ts +0 -43
- package/dist/types/Middleware/index.d.ts +0 -2
- package/dist/types/Middleware/types.d.ts +0 -4
- package/dist/types/Middlewares/Logger.d.ts +0 -27
- package/dist/types/Middlewares/Profiler.d.ts +0 -22
- package/dist/types/Middlewares/index.d.ts +0 -2
- package/dist/types/index.d.ts +0 -3
- package/src/Galena/Galena.ts +0 -252
- package/src/Galena/Guards.ts +0 -49
- package/src/Galena/Scheduler.ts +0 -85
- package/src/Galena/State.ts +0 -344
- package/src/Galena/index.ts +0 -3
- package/src/Galena/types.ts +0 -18
- package/src/Middleware/Middleware.ts +0 -45
- package/src/Middleware/index.ts +0 -2
- package/src/Middleware/types.ts +0 -4
- package/src/Middlewares/Profiler.ts +0 -41
- package/src/Middlewares/index.ts +0 -2
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import type { State } from "./State";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Middleware
|
|
5
|
+
*
|
|
6
|
+
* Galena's middleware API is designed to provide hooks
|
|
7
|
+
* for state changes that you can tap into to run your
|
|
8
|
+
* own logic.
|
|
9
|
+
*
|
|
10
|
+
* Middleware is great for logging, analytics, and profiling:
|
|
11
|
+
*
|
|
12
|
+
* ```typescript
|
|
13
|
+
* export class Profiler<T = any> extends Middleware<T> {
|
|
14
|
+
* private previousState: T | null = null;
|
|
15
|
+
* private startTime: null | number = null;
|
|
16
|
+
* constructor(public readonly threshold: number = 16) {
|
|
17
|
+
* super();
|
|
18
|
+
* }
|
|
19
|
+
*
|
|
20
|
+
* public override onBeforeUpdate(state: State<T>) {
|
|
21
|
+
* this.startTime = performance.now();
|
|
22
|
+
* this.previousState = state.getSnapshot();
|
|
23
|
+
* }
|
|
24
|
+
*
|
|
25
|
+
* public override onUpdate(state: T) {
|
|
26
|
+
* const diff = performance.now() - this.startTime;
|
|
27
|
+
* if(diff >= this.threshold) {
|
|
28
|
+
* console.warn(`A slow state transition was detected when transitioning the following piece of state`);
|
|
29
|
+
* console.log('Previous state', this.previousState);
|
|
30
|
+
* console.log('Current state', state.getSnapshot());
|
|
31
|
+
* }
|
|
32
|
+
* }
|
|
33
|
+
* }
|
|
34
|
+
* ```
|
|
35
|
+
*
|
|
36
|
+
* To register your middleware, simply add it when constructing
|
|
37
|
+
* a `State` or `Galena` instance.
|
|
38
|
+
*
|
|
39
|
+
* ```typescript
|
|
40
|
+
* import { State } from "@figliolia/galena";
|
|
41
|
+
* import { Profiler } from './myProfiler';
|
|
42
|
+
*
|
|
43
|
+
* const myState = new State(5, new Profiler());
|
|
44
|
+
* ```
|
|
45
|
+
*/
|
|
46
|
+
export class Middleware<T = any> {
|
|
47
|
+
/**
|
|
48
|
+
* On Before Update
|
|
49
|
+
*
|
|
50
|
+
* Executes prior to a `State` instance being updated.
|
|
51
|
+
* Receives the state prior to its update as a parameter
|
|
52
|
+
*/
|
|
53
|
+
public onBeforeUpdate(_state: State<T>) {}
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* On Update
|
|
57
|
+
*
|
|
58
|
+
* Executes after a `State` instance has been update.
|
|
59
|
+
* Receives the most recent state as a parameter
|
|
60
|
+
*/
|
|
61
|
+
public onUpdate(_state: State<T>) {}
|
|
62
|
+
}
|
package/src/Profiler.ts
ADDED
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import { Middleware } from "./Middleware";
|
|
2
|
+
import type { State } from "./State";
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Profiler
|
|
6
|
+
*
|
|
7
|
+
* A logger for state transitions exceeding a given
|
|
8
|
+
* millisecond threshold
|
|
9
|
+
*
|
|
10
|
+
* ```typescript
|
|
11
|
+
* const AppState = new Galena({}, new Profiler());
|
|
12
|
+
* // or
|
|
13
|
+
* AppState.registerMiddlerware(new Profiler());
|
|
14
|
+
* // or
|
|
15
|
+
* const MyState = new State(4, new Profiler());
|
|
16
|
+
* // or
|
|
17
|
+
* MyState.registerMiddleware(new Profiler());
|
|
18
|
+
* ```
|
|
19
|
+
*/
|
|
20
|
+
export class Profiler<T = any> extends Middleware<T> {
|
|
21
|
+
private previousState: T | null = null;
|
|
22
|
+
private startTime: null | number = null;
|
|
23
|
+
constructor(public readonly threshold = 16) {
|
|
24
|
+
super();
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
public override onBeforeUpdate(state: State<T>) {
|
|
28
|
+
this.startTime = performance.now();
|
|
29
|
+
this.previousState = state.getSnapshot();
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
public override onUpdate(state: State<T>) {
|
|
33
|
+
if (this.startTime === null) {
|
|
34
|
+
return;
|
|
35
|
+
}
|
|
36
|
+
const diff = performance.now() - this.startTime;
|
|
37
|
+
if (diff >= this.threshold) {
|
|
38
|
+
console.warn(
|
|
39
|
+
`A slow state transition of ${diff.toFixed(1)}ms was detected when transitioning the following piece of state`,
|
|
40
|
+
);
|
|
41
|
+
console.log(
|
|
42
|
+
" %cPrevious State",
|
|
43
|
+
"color: #26ad65; font-weight: bold",
|
|
44
|
+
this.previousState,
|
|
45
|
+
);
|
|
46
|
+
console.log(
|
|
47
|
+
" %cCurrent State ",
|
|
48
|
+
"color: rgb(17, 118, 249); font-weight: bold",
|
|
49
|
+
state.getSnapshot(),
|
|
50
|
+
);
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
}
|
package/src/State.ts
ADDED
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
import { EventEmitter } from "@figliolia/event-emitter";
|
|
2
|
+
import type { Middleware } from "./Middleware";
|
|
3
|
+
import type { NonFunction, Setter, Subscriber } from "./types";
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* State
|
|
7
|
+
*
|
|
8
|
+
* The unit of reactivity for Galena. `State`'s can act
|
|
9
|
+
* as isolated instances or be part of your global app
|
|
10
|
+
* state (via `Galena` instances).
|
|
11
|
+
*
|
|
12
|
+
* There are three ways to create state instances
|
|
13
|
+
*
|
|
14
|
+
* ```typescript
|
|
15
|
+
* import { State, createState, useState, Profiler } from "@figliolia/galena";
|
|
16
|
+
* // for island states that can be shared between react components
|
|
17
|
+
* const myState = new State("<any value>", ...middleware);
|
|
18
|
+
* // or
|
|
19
|
+
* const myState = createState("<any value>", ...middleware);
|
|
20
|
+
*
|
|
21
|
+
* myState.set("<new-value>");
|
|
22
|
+
* myState.update(previousValue => "<new-value>");
|
|
23
|
+
* myState.subscribe(nextValue => {});
|
|
24
|
+
* myState.registerMiddleware(new Profiler());
|
|
25
|
+
*
|
|
26
|
+
* // Similarly if you wish to use your state inside a react component
|
|
27
|
+
* const MyComponent = () => {
|
|
28
|
+
* const [state, setState] = useState(myState);
|
|
29
|
+
* // or
|
|
30
|
+
* const [state, setState] = useMyState("<any-value>", ...middlware);
|
|
31
|
+
*
|
|
32
|
+
* return (
|
|
33
|
+
* // your jsx
|
|
34
|
+
* );
|
|
35
|
+
* }
|
|
36
|
+
* ```
|
|
37
|
+
*/
|
|
38
|
+
export class State<T> {
|
|
39
|
+
private state: NonFunction<T>;
|
|
40
|
+
public readonly middleware: Middleware<T>[] = [];
|
|
41
|
+
private readonly Emitter = new EventEmitter<{ change: NonFunction<T> }>();
|
|
42
|
+
constructor(
|
|
43
|
+
public readonly initialState: NonFunction<T>,
|
|
44
|
+
...middleware: Middleware<T>[]
|
|
45
|
+
) {
|
|
46
|
+
this.state = initialState;
|
|
47
|
+
this.registerMiddleware(...middleware);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Set
|
|
52
|
+
*
|
|
53
|
+
* Updates the current value of state notifying
|
|
54
|
+
* all interested parties
|
|
55
|
+
*/
|
|
56
|
+
public readonly set = this.withEmission((state: NonFunction<T>) => state);
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Update
|
|
60
|
+
*
|
|
61
|
+
* Updates the current value of state using a setter function
|
|
62
|
+
* receiving the previous state as a parameter. Notifies all
|
|
63
|
+
* interested parties
|
|
64
|
+
*/
|
|
65
|
+
public readonly update = this.withEmission((setter: Setter<T>) => {
|
|
66
|
+
if (this.diffSetter(setter)) {
|
|
67
|
+
return setter;
|
|
68
|
+
}
|
|
69
|
+
return setter(this.state);
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* Reset
|
|
74
|
+
*
|
|
75
|
+
* Resets the current state back to the state which the instance
|
|
76
|
+
* was initialized with. Notifies all interested parties
|
|
77
|
+
*/
|
|
78
|
+
public readonly reset = this.withEmission(() => this.initialState);
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* Get Snapshot
|
|
82
|
+
*
|
|
83
|
+
* Returns the current state. Designed for compatibility with
|
|
84
|
+
* `useSyncExternalStore`
|
|
85
|
+
*/
|
|
86
|
+
public readonly getSnapshot = () => {
|
|
87
|
+
return this.state;
|
|
88
|
+
};
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* Subscribe
|
|
92
|
+
*
|
|
93
|
+
* Registers a callback to be executed each time state
|
|
94
|
+
* changes. Returns an `unsubscribe` function
|
|
95
|
+
*/
|
|
96
|
+
public readonly subscribe = (fn: Subscriber<T>) => {
|
|
97
|
+
const ID = this.Emitter.on("change", fn);
|
|
98
|
+
return () => {
|
|
99
|
+
this.Emitter.off("change", ID);
|
|
100
|
+
};
|
|
101
|
+
};
|
|
102
|
+
|
|
103
|
+
/**
|
|
104
|
+
* Register Middleware
|
|
105
|
+
*
|
|
106
|
+
* Registers any number of `Middleware` instances on the
|
|
107
|
+
* current instance of `State`. Your middleware will begin
|
|
108
|
+
* executing at the next state transition
|
|
109
|
+
*/
|
|
110
|
+
public registerMiddleware(...middleware: Middleware<T>[]) {
|
|
111
|
+
this.middleware.push(...middleware);
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
private withEmission<
|
|
115
|
+
F extends (...args: any[]) => NonFunction<T> | Promise<NonFunction<T>>,
|
|
116
|
+
>(fn: F) {
|
|
117
|
+
return (...args: Parameters<F>) => {
|
|
118
|
+
const result = fn(...args);
|
|
119
|
+
if (result instanceof Promise) {
|
|
120
|
+
void result.then(resolved => this.emit(resolved));
|
|
121
|
+
return;
|
|
122
|
+
}
|
|
123
|
+
return this.emit(result);
|
|
124
|
+
};
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
private emit(nextState: NonFunction<T>) {
|
|
128
|
+
this.invokeMiddleware("onBeforeUpdate");
|
|
129
|
+
this.state = nextState;
|
|
130
|
+
this.Emitter.emit("change", this.state);
|
|
131
|
+
this.invokeMiddleware("onUpdate");
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
protected diffSetter(setter: Setter<T>): setter is NonFunction<T> {
|
|
135
|
+
return typeof setter !== "function";
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
private invokeMiddleware<K extends keyof Middleware<T>>(fn: K) {
|
|
139
|
+
for (const middleware of this.middleware) {
|
|
140
|
+
middleware[fn](this);
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
/**
|
|
146
|
+
* Create State
|
|
147
|
+
*
|
|
148
|
+
* Returns the unit of reactivity for Galena. `State`'s can act
|
|
149
|
+
* as isolated instances or be part of your global app
|
|
150
|
+
* state (via `Galena` instances);
|
|
151
|
+
*
|
|
152
|
+
* ```typescript
|
|
153
|
+
* import { createState, Profiler } from "@figliolia/galena";
|
|
154
|
+
*
|
|
155
|
+
* const myState = createState("<any value>", ...middleware);
|
|
156
|
+
*
|
|
157
|
+
* myState.set("<new-value>");
|
|
158
|
+
* myState.update(previousValue => "<new-value>");
|
|
159
|
+
* myState.subscribe(nextValue => {});
|
|
160
|
+
* myState.registerMiddleware(new Profiler());
|
|
161
|
+
* ```
|
|
162
|
+
*/
|
|
163
|
+
export function createState<T>(
|
|
164
|
+
...args: ConstructorParameters<typeof State<T>>
|
|
165
|
+
) {
|
|
166
|
+
return new State<T>(...args);
|
|
167
|
+
}
|
package/src/index.ts
CHANGED
package/src/types.ts
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import type { State } from "./State";
|
|
2
|
+
|
|
3
|
+
export type NonFunction<T> = T extends (...args: any[]) => any ? never : T;
|
|
4
|
+
|
|
5
|
+
export type PartialSupport<T> = T extends Record<string, any> ? Partial<T> : T;
|
|
6
|
+
|
|
7
|
+
export type Setter<T> =
|
|
8
|
+
| NonFunction<T>
|
|
9
|
+
| ((prevState: NonFunction<T>) => NonFunction<T> | Promise<NonFunction<T>>);
|
|
10
|
+
|
|
11
|
+
export type Subscriber<T> = ((state: NonFunction<T>) => void) | (() => void);
|
|
12
|
+
|
|
13
|
+
export interface GalenaSnapshot<
|
|
14
|
+
T extends Record<string, State<any>>,
|
|
15
|
+
K extends Extract<keyof T, string> = Extract<keyof T, string>,
|
|
16
|
+
> {
|
|
17
|
+
updated: T[K];
|
|
18
|
+
state: T;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export type AppSubscriber<
|
|
22
|
+
T extends Record<string, State<any>>,
|
|
23
|
+
K extends Extract<keyof T, string> = Extract<keyof T, string>,
|
|
24
|
+
> = ((payload: GalenaSnapshot<T, K>) => void) | (() => void);
|
|
25
|
+
|
|
26
|
+
export type StateTypes<T extends Record<string, State<any>>> = ReturnType<
|
|
27
|
+
T[keyof T]["getSnapshot"]
|
|
28
|
+
>;
|
|
@@ -1,223 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.Galena = void 0;
|
|
4
|
-
const event_emitter_1 = require("@figliolia/event-emitter");
|
|
5
|
-
const State_1 = require("./State");
|
|
6
|
-
const Guards_1 = require("./Guards");
|
|
7
|
-
/**
|
|
8
|
-
* ## Galena
|
|
9
|
-
*
|
|
10
|
-
* A performant global state solution that scales
|
|
11
|
-
*
|
|
12
|
-
* ### Creating State
|
|
13
|
-
*
|
|
14
|
-
* ```typescript
|
|
15
|
-
* // AppState.ts
|
|
16
|
-
* import { Galena } from "@figliolia/galena";
|
|
17
|
-
*
|
|
18
|
-
* const AppState = new Galena([...middleware]);
|
|
19
|
-
*
|
|
20
|
-
* const NavigationState = AppState.composeState("navigation", {
|
|
21
|
-
* currentRoute: "/",
|
|
22
|
-
* userID: "12345",
|
|
23
|
-
* permittedRoutes: ["/*"]
|
|
24
|
-
* });
|
|
25
|
-
* ```
|
|
26
|
-
*
|
|
27
|
-
* ### Subscribing to State Changes
|
|
28
|
-
* #### Using the Galena Instance
|
|
29
|
-
* ```typescript
|
|
30
|
-
* import { AppState } from "./AppState";
|
|
31
|
-
*
|
|
32
|
-
* AppState.subscribe(appState => {
|
|
33
|
-
* const navState = appState.get("navigation");
|
|
34
|
-
* const { currentRoute } = navState.state;
|
|
35
|
-
* // do something with state changes!
|
|
36
|
-
* });
|
|
37
|
-
* ```
|
|
38
|
-
* #### Using the State Instance
|
|
39
|
-
* ```typescript
|
|
40
|
-
* NavigationState.subscribe(navigation => {
|
|
41
|
-
* const { currentRoute } = navigation
|
|
42
|
-
* // do something with state changes!
|
|
43
|
-
* });
|
|
44
|
-
* ```
|
|
45
|
-
*
|
|
46
|
-
* #### Using Global Subscriptions
|
|
47
|
-
* ```typescript
|
|
48
|
-
* NavigationState.subscribeAll(nextState => {
|
|
49
|
-
* const { currentRoute } = nextState.navigation
|
|
50
|
-
* // do something with state changes!
|
|
51
|
-
* });
|
|
52
|
-
* ```
|
|
53
|
-
*
|
|
54
|
-
* ### Mutating State
|
|
55
|
-
* ```typescript
|
|
56
|
-
* NavigationState.update(state => {
|
|
57
|
-
* state.currentRoute = "/profile";
|
|
58
|
-
* // You can mutate state without creating new objects!
|
|
59
|
-
* });
|
|
60
|
-
* ```
|
|
61
|
-
*/
|
|
62
|
-
class Galena extends Guards_1.Guards {
|
|
63
|
-
constructor(middleware = []) {
|
|
64
|
-
super();
|
|
65
|
-
this.state = {};
|
|
66
|
-
this.middleware = [];
|
|
67
|
-
this.IDs = new event_emitter_1.AutoIncrementingID();
|
|
68
|
-
this.subscriptions = new Map();
|
|
69
|
-
this.middleware = middleware;
|
|
70
|
-
}
|
|
71
|
-
/**
|
|
72
|
-
* Compose State
|
|
73
|
-
*
|
|
74
|
-
* Creates a new `State` instance and returns it. Your new state
|
|
75
|
-
* becomes immediately available on your `Galena` instance and
|
|
76
|
-
* is wired into your middleware. All existing subscriptions to
|
|
77
|
-
* state will automatically receive updates when your new unit of
|
|
78
|
-
* state updates
|
|
79
|
-
*/
|
|
80
|
-
composeState(name, initialState, Model) {
|
|
81
|
-
this.guardDuplicateStates(name, this.state);
|
|
82
|
-
const StateModel = Model || (State_1.State);
|
|
83
|
-
const state = new StateModel(name, initialState);
|
|
84
|
-
state.registerMiddleware(...this.middleware);
|
|
85
|
-
this.mutable[name] = state;
|
|
86
|
-
this.reIndexSubscriptions(name);
|
|
87
|
-
return state;
|
|
88
|
-
}
|
|
89
|
-
/**
|
|
90
|
-
* Get State
|
|
91
|
-
*
|
|
92
|
-
* Returns a mutable state instance
|
|
93
|
-
*/
|
|
94
|
-
getState() {
|
|
95
|
-
return this.state;
|
|
96
|
-
}
|
|
97
|
-
/**
|
|
98
|
-
* Get
|
|
99
|
-
*
|
|
100
|
-
* Returns a unit of `State` by name
|
|
101
|
-
*/
|
|
102
|
-
get(name) {
|
|
103
|
-
this.warnForUndefinedStates(name, this.state);
|
|
104
|
-
return this.state[name];
|
|
105
|
-
}
|
|
106
|
-
/**
|
|
107
|
-
* Mutable
|
|
108
|
-
*
|
|
109
|
-
* Returns a mutable state instance
|
|
110
|
-
*/
|
|
111
|
-
get mutable() {
|
|
112
|
-
return this.state;
|
|
113
|
-
}
|
|
114
|
-
/**
|
|
115
|
-
* Update
|
|
116
|
-
*
|
|
117
|
-
* Runs a mutation on the specified unit of state
|
|
118
|
-
*/
|
|
119
|
-
update(name, mutation) {
|
|
120
|
-
return this.get(name).update(mutation);
|
|
121
|
-
}
|
|
122
|
-
/**
|
|
123
|
-
* Background Update
|
|
124
|
-
*
|
|
125
|
-
* Runs a higher priority mutation on the specified unit of
|
|
126
|
-
* state
|
|
127
|
-
*/
|
|
128
|
-
backgroundUpdate(name, mutation) {
|
|
129
|
-
return this.get(name).backgroundUpdate(mutation);
|
|
130
|
-
}
|
|
131
|
-
/**
|
|
132
|
-
* Priority Update
|
|
133
|
-
*
|
|
134
|
-
* Runs an immediate priority mutation on the specified unit
|
|
135
|
-
* of state
|
|
136
|
-
*/
|
|
137
|
-
priorityUpdate(name, mutation) {
|
|
138
|
-
return this.get(name).priorityUpdate(mutation);
|
|
139
|
-
}
|
|
140
|
-
/**
|
|
141
|
-
* Subscribe
|
|
142
|
-
*
|
|
143
|
-
* Given the name of a unit of state, this method registers
|
|
144
|
-
* a subscription on the target state instance. The callback
|
|
145
|
-
* you provide will execute each time state changes. Returns
|
|
146
|
-
* a unique identifier for your subscription. To clean up your
|
|
147
|
-
* subscription, call `Galena.unsubscribe()` with the ID returned
|
|
148
|
-
* by this method
|
|
149
|
-
*/
|
|
150
|
-
subscribe(name, callback) {
|
|
151
|
-
return this.get(name).subscribe(callback);
|
|
152
|
-
}
|
|
153
|
-
/**
|
|
154
|
-
* Unsubscribe
|
|
155
|
-
*
|
|
156
|
-
* Given a subscription ID returned from the `subscribe` method,
|
|
157
|
-
* this method removes and cleans up the corresponding subscription
|
|
158
|
-
*/
|
|
159
|
-
unsubscribe(name, ID) {
|
|
160
|
-
return this.get(name).unsubscribe(ID);
|
|
161
|
-
}
|
|
162
|
-
/**
|
|
163
|
-
* Subscribe All
|
|
164
|
-
*
|
|
165
|
-
* Registers a callback on each registered `State` instance and
|
|
166
|
-
* is invoked each time your state changes. Using `Galena`'s
|
|
167
|
-
* `subscribeAll` method, although performant, can be less
|
|
168
|
-
* performant than subscribing directly to a target `State`
|
|
169
|
-
* instance using `Galena.subscribe()`. To clean up your
|
|
170
|
-
* subscription, call `Galena.unsubscribeAll()` with the ID
|
|
171
|
-
* returned
|
|
172
|
-
*/
|
|
173
|
-
subscribeAll(callback) {
|
|
174
|
-
const stateSubscriptions = [];
|
|
175
|
-
for (const key in this.state) {
|
|
176
|
-
stateSubscriptions.push([
|
|
177
|
-
key,
|
|
178
|
-
this.state[key].subscribe(() => {
|
|
179
|
-
void callback(this.state);
|
|
180
|
-
}),
|
|
181
|
-
]);
|
|
182
|
-
}
|
|
183
|
-
const subscriptionID = this.IDs.get();
|
|
184
|
-
this.subscriptions.set(subscriptionID, stateSubscriptions);
|
|
185
|
-
return subscriptionID;
|
|
186
|
-
}
|
|
187
|
-
/**
|
|
188
|
-
* Unsubscribe
|
|
189
|
-
*
|
|
190
|
-
* Given a subscription ID returned from the `subscribeAll()` method,
|
|
191
|
-
* this method removes and cleans up the corresponding subscription
|
|
192
|
-
*/
|
|
193
|
-
unsubscribeAll(ID) {
|
|
194
|
-
const IDs = this.subscriptions.get(ID);
|
|
195
|
-
if (IDs) {
|
|
196
|
-
for (const [state, ID] of IDs) {
|
|
197
|
-
this.state[state].unsubscribe(ID);
|
|
198
|
-
}
|
|
199
|
-
this.subscriptions.delete(ID);
|
|
200
|
-
}
|
|
201
|
-
}
|
|
202
|
-
/**
|
|
203
|
-
* ReIndex Subscriptions
|
|
204
|
-
*
|
|
205
|
-
* When units of state are created lazily, this method updates
|
|
206
|
-
* each existing subscription to receive mutations occurring on
|
|
207
|
-
* recently created `State` instances that post-date prior
|
|
208
|
-
* subscriptions
|
|
209
|
-
*/
|
|
210
|
-
reIndexSubscriptions(name) {
|
|
211
|
-
var _a, _b;
|
|
212
|
-
for (const [ID, unitSubscriptions] of this.subscriptions) {
|
|
213
|
-
const [stateName, listenerID] = unitSubscriptions[0];
|
|
214
|
-
const subscriptions = this.state[stateName]["emitter"].storage.get(stateName);
|
|
215
|
-
const listener = (_b = (_a = subscriptions === null || subscriptions === void 0 ? void 0 : subscriptions.storage) === null || _a === void 0 ? void 0 : _a.get) === null || _b === void 0 ? void 0 : _b.call(_a, listenerID);
|
|
216
|
-
if (listener) {
|
|
217
|
-
unitSubscriptions.push([name, this.state[name].subscribe(listener)]);
|
|
218
|
-
this.subscriptions.set(ID, unitSubscriptions);
|
|
219
|
-
}
|
|
220
|
-
}
|
|
221
|
-
}
|
|
222
|
-
}
|
|
223
|
-
exports.Galena = Galena;
|
|
@@ -1,40 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.Guards = void 0;
|
|
4
|
-
/**
|
|
5
|
-
* Guards
|
|
6
|
-
*
|
|
7
|
-
* Development-only warnings and runtime errors designed to
|
|
8
|
-
* guard developers against possible pitfalls when using
|
|
9
|
-
* Galena. This interface provides composable error and
|
|
10
|
-
* warning methods that can be used to prevent invalid usage
|
|
11
|
-
* of the library
|
|
12
|
-
*/
|
|
13
|
-
class Guards {
|
|
14
|
-
/**
|
|
15
|
-
* Warn For Undefined States
|
|
16
|
-
*
|
|
17
|
-
* In Galena, it's normal to lazy initialize a unit of state
|
|
18
|
-
* in attached to a `Galena` instance. This warning lets
|
|
19
|
-
* developers know that they are attempting to manipulate a
|
|
20
|
-
* unit of state that has not yet been initialized
|
|
21
|
-
*/
|
|
22
|
-
warnForUndefinedStates(name, state) {
|
|
23
|
-
if (!(name in state)) {
|
|
24
|
-
console.warn(`A unit of state with the name "${name}" does not yet exist on this Galena instance. If this is expected, you can ignore this warning`);
|
|
25
|
-
}
|
|
26
|
-
}
|
|
27
|
-
/**
|
|
28
|
-
* Guard Duplicate States
|
|
29
|
-
*
|
|
30
|
-
* Throws an error if a developer attempts to create
|
|
31
|
-
* more than one state with the same name on a single
|
|
32
|
-
* `Galena` instance
|
|
33
|
-
*/
|
|
34
|
-
guardDuplicateStates(name, state) {
|
|
35
|
-
if (name in state) {
|
|
36
|
-
console.warn(`A unit of state with the name "${name}" already exists on this Galena instance. Please re-name this new unit of state to something unique`);
|
|
37
|
-
}
|
|
38
|
-
}
|
|
39
|
-
}
|
|
40
|
-
exports.Guards = Guards;
|
|
@@ -1,84 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.Scheduler = void 0;
|
|
4
|
-
const types_1 = require("./types");
|
|
5
|
-
/**
|
|
6
|
-
* Scheduler
|
|
7
|
-
*
|
|
8
|
-
* Scheduling dispatched events to state consumers is how Galena
|
|
9
|
-
* out-performs just about every state management library out there.
|
|
10
|
-
* The scheduler offers the ability to dispatch state updates on 3
|
|
11
|
-
* priorities:
|
|
12
|
-
*
|
|
13
|
-
* 1. Immediate - Immediate synchronous task execution and propagation of
|
|
14
|
-
* changes to consumers
|
|
15
|
-
* 2. Microtask - Immediate task execution and scheduled propagation of
|
|
16
|
-
* changes to consumers
|
|
17
|
-
* 3. Batched - Immediate task execution and batched propagation of
|
|
18
|
-
* changes to consumers
|
|
19
|
-
*
|
|
20
|
-
* This module manages the propagation of changes to State consumers
|
|
21
|
-
* by implementing the three priorities outlined above
|
|
22
|
-
*/
|
|
23
|
-
class Scheduler {
|
|
24
|
-
constructor() {
|
|
25
|
-
this.task = null;
|
|
26
|
-
this.schedule = null;
|
|
27
|
-
this.executeTasks = this.executeTasks.bind(this);
|
|
28
|
-
}
|
|
29
|
-
/**
|
|
30
|
-
* Schedule Task
|
|
31
|
-
*
|
|
32
|
-
* Given a task (the emission of state changes to consumers)
|
|
33
|
-
* and a priority, this method executes the task on the priority
|
|
34
|
-
* level specified
|
|
35
|
-
*/
|
|
36
|
-
scheduleTask(task, priority) {
|
|
37
|
-
this.task = task;
|
|
38
|
-
switch (priority) {
|
|
39
|
-
case types_1.Priority.IMMEDIATE:
|
|
40
|
-
return this.executeTasks();
|
|
41
|
-
case types_1.Priority.MICROTASK:
|
|
42
|
-
return Promise.resolve().then(() => {
|
|
43
|
-
return this.executeTasks();
|
|
44
|
-
});
|
|
45
|
-
case types_1.Priority.BATCHED:
|
|
46
|
-
default:
|
|
47
|
-
if (!this.schedule) {
|
|
48
|
-
this.createSchedule();
|
|
49
|
-
}
|
|
50
|
-
}
|
|
51
|
-
}
|
|
52
|
-
/**
|
|
53
|
-
* Create Schedule
|
|
54
|
-
*
|
|
55
|
-
* Schedules the execution of the current task after 5 milliseconds
|
|
56
|
-
*/
|
|
57
|
-
createSchedule() {
|
|
58
|
-
this.clearSchedule();
|
|
59
|
-
this.schedule = setTimeout(this.executeTasks, 5);
|
|
60
|
-
}
|
|
61
|
-
/**
|
|
62
|
-
* Clear Schedule
|
|
63
|
-
*
|
|
64
|
-
* Clears the schedule if it exists
|
|
65
|
-
*/
|
|
66
|
-
clearSchedule() {
|
|
67
|
-
if (this.schedule !== null) {
|
|
68
|
-
clearTimeout(this.schedule);
|
|
69
|
-
this.schedule = null;
|
|
70
|
-
}
|
|
71
|
-
}
|
|
72
|
-
/**
|
|
73
|
-
* Execute Tasks
|
|
74
|
-
*
|
|
75
|
-
* Clears the schedule if it exists and executes the current task
|
|
76
|
-
*/
|
|
77
|
-
executeTasks() {
|
|
78
|
-
var _a;
|
|
79
|
-
this.clearSchedule();
|
|
80
|
-
(_a = this.task) === null || _a === void 0 ? void 0 : _a.call(this);
|
|
81
|
-
this.task = null;
|
|
82
|
-
}
|
|
83
|
-
}
|
|
84
|
-
exports.Scheduler = Scheduler;
|