@esportsplus/reactivity 0.0.22 → 0.0.23

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.
Files changed (49) hide show
  1. package/build/core.d.ts +30 -0
  2. package/build/core.js +218 -0
  3. package/build/index.d.ts +3 -12
  4. package/build/index.js +3 -4
  5. package/build/primitives/closure.d.ts +3 -0
  6. package/build/primitives/closure.js +10 -0
  7. package/build/primitives/computed.d.ts +3 -0
  8. package/build/primitives/computed.js +11 -0
  9. package/build/primitives/dispatch.d.ts +3 -0
  10. package/build/primitives/dispatch.js +13 -0
  11. package/build/primitives/dispose.d.ts +3 -0
  12. package/build/primitives/dispose.js +13 -0
  13. package/build/primitives/effect.d.ts +7 -0
  14. package/build/primitives/effect.js +9 -0
  15. package/build/primitives/index.d.ts +8 -0
  16. package/build/primitives/index.js +8 -0
  17. package/build/primitives/reactive.d.ts +3 -0
  18. package/build/primitives/reactive.js +35 -0
  19. package/build/primitives/reset.d.ts +3 -0
  20. package/build/primitives/reset.js +13 -0
  21. package/build/primitives/signal.d.ts +8 -0
  22. package/build/primitives/signal.js +14 -0
  23. package/build/signal.d.ts +16 -0
  24. package/build/signal.js +22 -0
  25. package/build/symbols.d.ts +10 -1
  26. package/build/symbols.js +10 -1
  27. package/build/types.d.ts +27 -13
  28. package/build/types.js +1 -1
  29. package/package.json +1 -1
  30. package/readme.md +2 -0
  31. package/src/core.ts +295 -0
  32. package/src/index.ts +3 -6
  33. package/src/primitives/closure.ts +15 -0
  34. package/src/primitives/computed.ts +16 -0
  35. package/src/primitives/dispatch.ts +17 -0
  36. package/src/primitives/dispose.ts +17 -0
  37. package/src/primitives/effect.ts +13 -0
  38. package/src/primitives/index.ts +8 -0
  39. package/src/primitives/reactive.ts +43 -0
  40. package/src/primitives/reset.ts +17 -0
  41. package/src/primitives/signal.ts +18 -0
  42. package/src/signal.ts +30 -0
  43. package/src/symbols.ts +22 -1
  44. package/src/types.ts +34 -13
  45. package/build/index.browser.js +0 -277
  46. package/src/methods/effect.ts +0 -6
  47. package/src/methods/index.ts +0 -5
  48. package/src/methods/reactive.ts +0 -71
  49. package/src/reactive.ts +0 -266
package/src/core.ts ADDED
@@ -0,0 +1,295 @@
1
+ import { CHECK, CLEAN, COMPUTED, DIRTY, DISPOSED, DISPOSE, EFFECT, RESET, SIGNAL, UPDATE } from './symbols';
2
+ import { Computed, Effect, Fn, Listener, Options, Root, Scheduler } from './types';
3
+ import Signal from './signal';
4
+
5
+
6
+ let index = 0,
7
+ observer: Signal | null = null,
8
+ observers: Signal[] | null = null,
9
+ scope: Root | null = null;
10
+
11
+
12
+ function changed(a: unknown, b: unknown) {
13
+ return a !== b;
14
+ }
15
+
16
+ function notify(nodes: Signal[], state: typeof CHECK | typeof DIRTY) {
17
+ for (let i = 0, n = nodes.length; i < n; i++) {
18
+ let node = nodes[i];
19
+
20
+ if (node.state < state) {
21
+ if (node.type === EFFECT && node.state === CLEAN) {
22
+ (node as Effect).root.scheduler((node as Effect).task);
23
+ }
24
+
25
+ node.state = state;
26
+
27
+ if (node.observers) {
28
+ notify(node.observers, CHECK);
29
+ }
30
+ }
31
+ }
32
+ }
33
+
34
+ function removeSourceObservers(node: Signal, start: number) {
35
+ if (!node.sources) {
36
+ return;
37
+ }
38
+
39
+ for (let i = start, n = node.sources.length; i < n; i++) {
40
+ let source = node.sources[i];
41
+
42
+ if (!source?.observers) {
43
+ continue;
44
+ }
45
+
46
+ source.observers[source.observers.indexOf(node)] = source.observers[source.observers.length - 1];
47
+ source.observers.pop();
48
+ }
49
+ }
50
+
51
+ function sync(node: Signal) {
52
+ if (node.state === CHECK && node.sources) {
53
+ for (let i = 0, n = node.sources.length; i < n; i++) {
54
+ sync(node.sources[i]);
55
+
56
+ // Stop the loop here so we won't trigger updates on other parents unnecessarily
57
+ // If our computation changes to no longer use some sources, we don't
58
+ // want to update() a source we used last time, but now don't use.
59
+ if ((node.state as Signal['state']) === DIRTY) {
60
+ break;
61
+ }
62
+ }
63
+ }
64
+
65
+ if (node.state === DIRTY) {
66
+ update(node);
67
+ }
68
+ else {
69
+ node.state = CLEAN;
70
+ }
71
+ }
72
+
73
+ function update(node: Signal) {
74
+ let i = index,
75
+ o = observer,
76
+ os = observers;
77
+
78
+ index = 0;
79
+ observer = node;
80
+ observers = null as typeof observers;
81
+
82
+ try {
83
+ if (node.listeners) {
84
+ dispatch(UPDATE, node);
85
+ }
86
+
87
+ let value = node.fn!.call( node.context );
88
+
89
+ if (observers) {
90
+ if (node.sources) {
91
+ removeSourceObservers(node, index);
92
+ }
93
+
94
+ if (node.sources && index > 0) {
95
+ node.sources.length = index + observers.length;
96
+
97
+ for (let i = 0, n = observers.length; i < n; i++) {
98
+ node.sources[index + i] = observers[i];
99
+ }
100
+ }
101
+ else {
102
+ node.sources = observers;
103
+ }
104
+
105
+ for (let i = index, n = node.sources.length; i < n; i++) {
106
+ let source = node.sources[i];
107
+
108
+ if (!source.observers) {
109
+ source.observers = [node];
110
+ }
111
+ else {
112
+ source.observers.push(node);
113
+ }
114
+ }
115
+ }
116
+ else if (node.sources && index < node.sources.length) {
117
+ removeSourceObservers(node, index);
118
+ node.sources.length = index;
119
+ }
120
+
121
+ if (node.type === COMPUTED) {
122
+ write(node, value);
123
+ }
124
+ }
125
+ catch {
126
+ if (node.state === DIRTY && node.sources) {
127
+ removeSourceObservers(node, 0);
128
+ }
129
+ return;
130
+ }
131
+ finally {
132
+ index = i;
133
+ observer = o;
134
+ observers = os;
135
+ }
136
+
137
+ node.state = CLEAN;
138
+ }
139
+
140
+
141
+ const computed = <T>(fn: Fn<T>, options: Options = {}) => {
142
+ let node = new Signal(undefined as unknown, DIRTY, COMPUTED, options);
143
+
144
+ node.fn = fn;
145
+
146
+ return node as Computed;
147
+ };
148
+
149
+ const dispose = (node: Signal) => {
150
+ if (node.state === DISPOSED) {
151
+ return;
152
+ }
153
+
154
+ node.state = DISPOSED;
155
+
156
+ if (node.listeners) {
157
+ dispatch(DISPOSE, node);
158
+ }
159
+
160
+ if (node.sources) {
161
+ removeSourceObservers(node, 0);
162
+ }
163
+
164
+ node.observers = null;
165
+ node.sources = null;
166
+ };
167
+
168
+ const effect = <T>(fn: Fn<T>, options: Options = {}) => {
169
+ if (!scope) {
170
+ throw new Error('Reactivity: effects cannot be created without a reactive root');
171
+ }
172
+
173
+ let node = new Signal(undefined as unknown, DIRTY, EFFECT, options);
174
+
175
+ node.fn = fn;
176
+ node.root = scope;
177
+ node.task = () => read(node);
178
+
179
+ update(node);
180
+
181
+ return node as Effect;
182
+ };
183
+
184
+ const dispatch = (event: symbol, node: Signal) => {
185
+ if (!node.listeners?.[event]) {
186
+ return;
187
+ }
188
+
189
+ let listeners = node.listeners[event],
190
+ value = node.value;
191
+
192
+ for (let i = 0, n = listeners.length; i < n; i++) {
193
+ listeners[i](value);
194
+ }
195
+
196
+ node.listeners = null;
197
+ };
198
+
199
+ const on = (event: symbol, listener: Listener, node: Signal) => {
200
+ if (!node.listeners?.[event]) {
201
+ node.listeners ??= {};
202
+ node.listeners[event] = [listener];
203
+ }
204
+ else {
205
+ node.listeners[event].push(listener);
206
+ }
207
+ };
208
+
209
+ const read = (node: Signal): typeof node['value'] => {
210
+ if (node.state === DISPOSED) {
211
+ return node.value;
212
+ }
213
+
214
+ if (observer) {
215
+ if (!observers) {
216
+ if (observer?.sources?.[index] == node) {
217
+ index++;
218
+ }
219
+ else {
220
+ observers = [node];
221
+ }
222
+ }
223
+ else {
224
+ observers.push(node);
225
+ }
226
+ }
227
+
228
+ if (node.fn) {
229
+ sync(node);
230
+ }
231
+
232
+ return node.value;
233
+ };
234
+
235
+ const reset = (node: Signal) => {
236
+ if (node.listeners) {
237
+ dispatch(RESET, node);
238
+ }
239
+
240
+ if (node.sources) {
241
+ removeSourceObservers(node, 0);
242
+ }
243
+
244
+ node.observers = null;
245
+ node.sources = null;
246
+
247
+ if (node.type === COMPUTED) {
248
+ node.state = DIRTY;
249
+ node.value = undefined;
250
+ }
251
+ else if (node.type === SIGNAL) {
252
+ node.state = CLEAN;
253
+ }
254
+ };
255
+
256
+ const root = <T>(fn: () => T, properties: { scheduler?: Scheduler } = {}) => {
257
+ let o = observer,
258
+ s = scope;
259
+
260
+ properties.scheduler = properties?.scheduler || scope?.scheduler;
261
+
262
+ if (!properties.scheduler) {
263
+ throw new Error('Reactivity: root cannot be created without a task scheduler');
264
+ }
265
+
266
+ observer = null;
267
+ scope = properties as Root;
268
+
269
+ let result = fn();
270
+
271
+ observer = o;
272
+ scope = s;
273
+
274
+ return result;
275
+ };
276
+
277
+ const signal = <T>(data: T, options: Options = {}) => {
278
+ return new Signal(data, CLEAN, SIGNAL, options);
279
+ };
280
+
281
+ const write = (node: Signal, value: unknown) => {
282
+ if ((node?.changed || changed)(node.value, value)) {
283
+ node.value = value;
284
+
285
+ if (node.observers) {
286
+ notify(node.observers, DIRTY);
287
+ }
288
+ }
289
+
290
+ return node.value;
291
+ };
292
+
293
+
294
+ export default { computed, dispatch, dispose, effect, on, read, reset, root, signal, write };
295
+ export { computed, dispatch, dispose, effect, on, read, reset, root, signal, write };
package/src/index.ts CHANGED
@@ -1,6 +1,3 @@
1
- import { scheduler } from './reactive';
2
- import { effect, reactive } from './methods';
3
-
4
-
5
- export default { effect, reactive, scheduler };
6
- export { effect, reactive, scheduler };
1
+ export * from './primitives';
2
+ export { default as core } from './core';
3
+ export { DISPOSE, RESET, UPDATE } from './symbols';
@@ -0,0 +1,15 @@
1
+ import { computed, read } from '~/core';
2
+ import { NODE } from '~/symbols';
3
+ import { Options } from '~/types';
4
+
5
+
6
+ export default (fn: () => (...args: unknown[]) => unknown, options: Options = {}) => {
7
+ let node = computed(fn, options);
8
+
9
+ node.context = function (...args: Parameters<ReturnType<typeof fn>>) {
10
+ return (read(node) as ReturnType<typeof fn>)(...args);
11
+ };
12
+ node.context[NODE] = node;
13
+
14
+ return node.context;
15
+ };
@@ -0,0 +1,16 @@
1
+ import { computed, on, read } from '~/core';
2
+ import { NODE } from '~/symbols';
3
+ import { Fn, Listener, Options } from '~/types';
4
+
5
+
6
+ export default <T>(fn: Fn<T>, options: Options = {}) => {
7
+ let node = computed(fn, options);
8
+
9
+ node.context = function () {
10
+ return read(node);
11
+ };
12
+ node.context[NODE] = node;
13
+ node.context.on = (event: symbol, listener: Listener) => on(event, listener, node);
14
+
15
+ return node.context;
16
+ };
@@ -0,0 +1,17 @@
1
+ import { dispatch } from '~/core';
2
+ import { NODE, NODES } from '~/symbols';
3
+ import { Wrapper } from '~/types';
4
+
5
+
6
+ export default (key: symbol, wrapper: Wrapper) => {
7
+ if (wrapper[NODE]) {
8
+ dispatch(key, wrapper[NODE]);
9
+ }
10
+ else if (wrapper[NODES]) {
11
+ let nodes = wrapper[NODES];
12
+
13
+ for (let i = 0, n = nodes.length; i < n; i++) {
14
+ dispatch(key, nodes[i]);
15
+ }
16
+ }
17
+ };
@@ -0,0 +1,17 @@
1
+ import { dispose } from '~/core';
2
+ import { NODE, NODES } from '~/symbols';
3
+ import { Wrapper } from '~/types';
4
+
5
+
6
+ export default (wrapper: Wrapper) => {
7
+ if (wrapper[NODE]) {
8
+ dispose(wrapper[NODE]);
9
+ }
10
+ else if (wrapper[NODES]) {
11
+ let nodes = wrapper[NODES];
12
+
13
+ for (let i = 0, n = nodes.length; i < n; i++) {
14
+ dispose(nodes[i]);
15
+ }
16
+ }
17
+ };
@@ -0,0 +1,13 @@
1
+ import { effect, on } from '~/core';
2
+ import { NODE } from '~/symbols';
3
+ import { Listener, Options } from '~/types';
4
+
5
+
6
+ export default (fn: () => unknown, options: Options = {}) => {
7
+ let node = effect(fn, options);
8
+
9
+ return node.context = {
10
+ [NODE]: node,
11
+ on: (event: symbol, listener: Listener) => on(event, listener, node)
12
+ };
13
+ };
@@ -0,0 +1,8 @@
1
+ export { default as closure } from './closure';
2
+ export { default as computed } from './computed';
3
+ export { default as dispatch } from './dispatch';
4
+ export { default as dispose } from './dispose';
5
+ export { default as effect } from './effect';
6
+ export { default as reactive } from './reactive';
7
+ export { default as reset } from './reset';
8
+ export { default as signal } from './signal';
@@ -0,0 +1,43 @@
1
+ import { computed as c, read, signal as s, write } from '~/core';
2
+ import { NODES } from '~/symbols';
3
+ import { Fn, Infer, Options, Signal } from '~/types';
4
+ import computed from './computed';
5
+ import signal from './signal';
6
+
7
+
8
+ export default <T>(value: Fn<T> | T, options: Options = {}) => {
9
+ if (typeof value === 'object' && value !== null && (value.constructor === Object)) {
10
+ let nodes: Record<string, Signal> = {},
11
+ properties: PropertyDescriptorMap = {};
12
+
13
+ for (let key in value) {
14
+ if (typeof value[key] === 'function') {
15
+ nodes[key] = c(value[key] as Parameters<typeof c>[0], options);
16
+ properties[key] = {
17
+ get() {
18
+ return read(nodes[key]);
19
+ }
20
+ };
21
+ }
22
+ else {
23
+ nodes[key] = s(value[key], options);
24
+ properties[key] = {
25
+ get() {
26
+ return read(nodes[key]);
27
+ },
28
+ set(value) {
29
+ write(nodes[key], value);
30
+ }
31
+ };
32
+ }
33
+ }
34
+
35
+ return Object.defineProperties({ [NODES]: Object.values(nodes) }, properties) as Infer<typeof value>;
36
+ }
37
+
38
+ if (typeof value === 'function') {
39
+ return computed(value as Parameters<typeof computed>[0], options);
40
+ }
41
+
42
+ return signal(value, options);
43
+ };
@@ -0,0 +1,17 @@
1
+ import { reset } from '~/core';
2
+ import { NODE, NODES } from '~/symbols';
3
+ import { Wrapper } from '~/types';
4
+
5
+
6
+ export default (wrapper: Wrapper) => {
7
+ if (wrapper[NODE]) {
8
+ reset(wrapper[NODE]);
9
+ }
10
+ else if (wrapper[NODES]) {
11
+ let nodes = wrapper[NODES];
12
+
13
+ for (let i = 0, n = nodes.length; i < n; i++) {
14
+ reset(nodes[i]);
15
+ }
16
+ }
17
+ };
@@ -0,0 +1,18 @@
1
+ import { read, signal, write } from '~/core';
2
+ import { NODE } from '~/symbols';
3
+ import { Options } from '~/types';
4
+
5
+
6
+ export default <T>(data: T, options: Options = {}) => {
7
+ let node = signal(data, options);
8
+
9
+ return node.context = {
10
+ [NODE]: node,
11
+ get: () => {
12
+ return read(node);
13
+ },
14
+ set: (value: T) => {
15
+ write(node, value);
16
+ }
17
+ };
18
+ };
package/src/signal.ts ADDED
@@ -0,0 +1,30 @@
1
+ import { Changed, Fn, Listener, Options, Root, Scheduler, State, Type, Wrapper } from './types';
2
+
3
+
4
+ class Signal<T = unknown> {
5
+ changed: Changed | null = null;
6
+ context: any & Wrapper | null = null;
7
+ fn: Fn<T> | null = null;
8
+ listeners: Record<symbol, Listener[]> | null = null;
9
+ observers: Signal[] | null = null;
10
+ root: Root | null = null;
11
+ sources: Signal[] | null = null;
12
+ task: Parameters<Scheduler>[0] | null = null;
13
+ type: Type;
14
+ state: State;
15
+ value: T;
16
+
17
+
18
+ constructor(data: T, state: Signal['state'], type: Signal['type'], options: Options = {}) {
19
+ if (options?.changed) {
20
+ this.changed = options.changed;
21
+ }
22
+
23
+ this.state = state;
24
+ this.type = type;
25
+ this.value = data;
26
+ }
27
+ }
28
+
29
+
30
+ export default Signal;
package/src/symbols.ts CHANGED
@@ -4,5 +4,26 @@ const CHECK = 1;
4
4
 
5
5
  const DIRTY = 2;
6
6
 
7
+ const DISPOSED = 3;
7
8
 
8
- export { CLEAN, CHECK, DIRTY };
9
+
10
+ const COMPUTED = 0;
11
+
12
+ const EFFECT = 1;
13
+
14
+ const SIGNAL = 2;
15
+
16
+
17
+ const NODE = Symbol();
18
+
19
+ const NODES = Symbol();
20
+
21
+
22
+ const DISPOSE = Symbol();
23
+
24
+ const RESET = Symbol();
25
+
26
+ const UPDATE = Symbol();
27
+
28
+
29
+ export { CHECK, CLEAN, COMPUTED, DIRTY, DISPOSED, DISPOSE, EFFECT, NODE, NODES, RESET, SIGNAL, UPDATE };
package/src/types.ts CHANGED
@@ -1,28 +1,49 @@
1
- import { CLEAN, CHECK, DIRTY } from './symbols';
1
+ import { CHECK, CLEAN, COMPUTED, DIRTY, DISPOSED, EFFECT, NODE, NODES, SIGNAL } from './symbols';
2
+ import S from './signal';
2
3
 
3
4
 
4
- type Fn = () => Promise<unknown> | unknown;
5
+ type Changed = (a: unknown, b: unknown) => boolean;
6
+
7
+ type Computed<T = unknown> = {
8
+ fn: NonNullable<Signal<T>['fn']>
9
+ } & Signal<T>;
10
+
11
+ type Effect<T = unknown> = {
12
+ root: NonNullable<Signal<T>['root']>;
13
+ task: NonNullable<Signal<T>['task']>
14
+ } & Computed<T>;
15
+
16
+ type Fn<T> = () => T;
5
17
 
6
18
  type Infer<T> =
7
19
  T extends (...args: any[]) => any
8
20
  ? ReturnType<T>
9
- : T extends Record<string, Primitives>
21
+ : T extends Record<string, unknown>
10
22
  ? { [K in keyof T]: Infer<T[K]> }
11
23
  : T;
12
24
 
13
- type Primitives = any[] | boolean | number | string | null | undefined | ((...args: any[]) => any);
25
+ type Listener = <T>(value: T) => void;
14
26
 
15
- type ReactiveFn<T> = (onCleanup?: (fn: VoidFunction) => void) => T;
27
+ type Options = {
28
+ changed?: Changed;
29
+ };
16
30
 
17
- type Scheduler = {
18
- schedule(): void;
19
- tasks: {
20
- add: (fn: Fn) => void;
21
- delete: (fn: Fn) => void;
22
- }
31
+ type Root = {
32
+ scheduler: Scheduler
23
33
  };
24
34
 
25
- type State = typeof CHECK | typeof CLEAN | typeof DIRTY;
35
+ type Scheduler = (fn: (...args: unknown[]) => Promise<unknown> | unknown) => unknown;
36
+
37
+ type Signal<T = unknown> = S<T>;
38
+
39
+ type State = typeof CHECK | typeof CLEAN | typeof DIRTY | typeof DISPOSED;
40
+
41
+ type Type = typeof COMPUTED | typeof EFFECT | typeof SIGNAL;
42
+
43
+ type Wrapper = {
44
+ [NODE]?: Signal,
45
+ [NODES]?: Signal[]
46
+ };
26
47
 
27
48
 
28
- export { Infer, ReactiveFn, Scheduler, State };
49
+ export { Changed, Computed, Effect, Fn, Infer, Listener, Options, Root, Scheduler, Signal, State, Type, Wrapper };