@esportsplus/reactivity 0.1.9 → 0.1.10

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/src/signal.ts CHANGED
@@ -1,46 +1,66 @@
1
- import { CHECK, CLEAN, COMPUTED, DIRTY, DISPOSED, EFFECT, SIGNAL } from './constants';
2
- import { Changed, Computed, Effect, Event, Listener, NeverAsync, Options, Root, Scheduler, State, Type } from './types';
1
+ import { CHECK, CLEAN, COMPUTED, DIRTY, DISPOSED, EFFECT, ROOT, SIGNAL } from './constants';
2
+ import { Changed, Event, Function, Listener, NeverAsync, Options, State, Type } from './types';
3
3
  import { isArray } from './utilities';
4
4
 
5
5
 
6
6
  let index = 0,
7
- observer: Signal<any> | null = null,
8
- observers: Signal<any>[] | null = null,
7
+ observer: Core<any> | null = null,
8
+ observers: Core<any>[] | null = null,
9
9
  scope: Root | null = null;
10
10
 
11
11
 
12
- class Signal<T> {
13
- changed: Changed | null = null;
14
- fn: Computed<T>['fn'] | Effect['fn'] | null = null;
12
+ class Core<T> {
15
13
  listeners: Record<Event, (Listener<any> | null)[]> | null = null;
16
- observers: Signal<T>[] | null = null;
17
- root: Root | null = null;
18
- sources: Signal<T>[] | null = null;
14
+ observers: Core<any>[] | null = null;
15
+ root: Root | null;
16
+ sources: Core<any>[] | null = null;
19
17
  state: State;
20
- task: Parameters<Scheduler>[0] | null = null;
21
- type: Type;
22
- updating: boolean | null = null;
18
+ updating: boolean = false;
23
19
  value: T;
24
20
 
25
21
 
26
- constructor(data: T, state: Signal<T>['state'], type: Signal<T>['type'], options: Options = {}) {
27
- if (options.changed !== undefined) {
28
- this.changed = options.changed;
22
+ constructor(state: State, value: T) {
23
+ let root = null;
24
+
25
+ if (this.type !== ROOT) {
26
+ if (scope !== null) {
27
+ root = scope;
28
+ }
29
+ else if (observer !== null) {
30
+ root = observer.root;
31
+ }
32
+
33
+ if (root == null) {
34
+ if (this.type === EFFECT) {
35
+ throw new Error(`Reactivity: 'effect' cannot be created without a reactive root`);
36
+ }
37
+ }
38
+ else if (root.tracking) {
39
+ root.on('dispose', () => this.dispose());
40
+ }
29
41
  }
30
42
 
43
+ this.root = root;
31
44
  this.state = state;
32
- this.type = type;
33
- this.value = data;
45
+ this.value = value;
46
+ }
47
+
48
+
49
+ get type(): Type | never {
50
+ throw new Error(`Reactivity: reactive primitives require 'type' getters`);
34
51
  }
35
52
 
36
53
 
37
54
  dispatch<D>(event: Event, data?: D) {
38
- if (this.listeners === null || !(event in this.listeners)) {
55
+ if (this.listeners === null || this.listeners[event] === undefined) {
39
56
  return;
40
57
  }
41
58
 
42
59
  let listeners = this.listeners[event],
43
- value = this.value;
60
+ parameter = {
61
+ data,
62
+ value: this.value
63
+ };
44
64
 
45
65
  for (let i = 0, n = listeners.length; i < n; i++) {
46
66
  let listener = listeners[i];
@@ -50,16 +70,15 @@ class Signal<T> {
50
70
  }
51
71
 
52
72
  try {
53
- // @ts-ignore
54
- listener( listener.length === 0 ? null : { data, value } );
73
+ listener(parameter);
74
+
75
+ if (listener.once !== undefined) {
76
+ listeners[i] = null;
77
+ }
55
78
  }
56
79
  catch {
57
80
  listeners[i] = null;
58
81
  }
59
-
60
- if (listener.once !== undefined) {
61
- listeners[i] = null;
62
- }
63
82
  }
64
83
  }
65
84
 
@@ -68,25 +87,24 @@ class Signal<T> {
68
87
  return;
69
88
  }
70
89
 
71
- this.state = DISPOSED;
72
-
73
- this.dispatch('dispose', this);
74
- flush(this);
90
+ flush('dispose', this, DISPOSED, this.value);
75
91
  }
76
92
 
77
- on(event: Event, listener: Listener<any>) {
93
+ on<T>(event: Event, listener: Listener<T>) {
78
94
  if (this.updating) {
79
95
  listener.once = true;
80
96
  }
81
97
 
82
- if (this.listeners === null || !(event in this.listeners)) {
83
- this.listeners ??= {};
84
- this.listeners[event] = [listener];
98
+ if (this.listeners === null) {
99
+ this.listeners = { [event]: [listener] };
85
100
  }
86
101
  else {
87
102
  let listeners = this.listeners[event];
88
103
 
89
- if (listeners.indexOf(listener) === -1) {
104
+ if (listeners === undefined) {
105
+ this.listeners[event] = [listener];
106
+ }
107
+ else if (listeners.indexOf(listener) === -1) {
90
108
  let i = listeners.indexOf(null);
91
109
 
92
110
  if (i === -1) {
@@ -99,26 +117,105 @@ class Signal<T> {
99
117
  }
100
118
  }
101
119
 
102
- once(event: Event, listener: Listener<any>) {
120
+ once<T>(event: Event, listener: Listener<T>) {
103
121
  listener.once = true;
104
122
  this.on(event, listener);
105
123
  }
124
+ }
125
+
126
+ class Computed<T> extends Core<T> {
127
+ changed: Changed;
128
+ fn: NeverAsync<() => T>;
129
+
130
+
131
+ constructor(fn: Computed<T>['fn'], options?: Options) {
132
+ super(DIRTY, undefined as T);
133
+ this.changed = options?.changed || changed;
134
+ this.fn = fn;
135
+ }
136
+
137
+
138
+ get type(): Type {
139
+ return COMPUTED;
140
+ }
141
+
142
+
143
+ get() {
144
+ return read(this);
145
+ }
106
146
 
107
147
  reset() {
108
- this.dispatch('reset', this);
109
- flush(this);
148
+ flush('reset', this, DIRTY, undefined as T);
149
+ }
150
+ }
110
151
 
111
- if (this.type === COMPUTED) {
112
- this.state = DIRTY;
113
- this.value = undefined as T;
114
- }
115
- else if (this.type === EFFECT) {
116
- this.state = DIRTY;
117
- update(this);
118
- }
119
- else if (this.type === SIGNAL) {
120
- this.state = CLEAN;
121
- }
152
+ class Effect extends Core<null> {
153
+ fn: NeverAsync<(node: Effect) => void>;
154
+ task: Function;
155
+
156
+
157
+ constructor(fn: Effect['fn']) {
158
+ super(DIRTY, null);
159
+ this.fn = fn;
160
+ this.task = () => read(this);
161
+
162
+ update(this);
163
+ }
164
+
165
+
166
+ get type(): Type {
167
+ return EFFECT;
168
+ }
169
+
170
+
171
+ reset() {
172
+ flush('reset', this, DIRTY, null);
173
+ update(this);
174
+ }
175
+ }
176
+
177
+ class Root extends Core<null> {
178
+ scheduler: (fn: Function) => unknown;
179
+ tracking: boolean;
180
+
181
+
182
+ constructor(scheduler: Root['scheduler'], tracking: boolean) {
183
+ super(CLEAN, null);
184
+ this.scheduler = scheduler;
185
+ this.tracking = tracking;
186
+ }
187
+
188
+
189
+ get type(): Type {
190
+ return ROOT;
191
+ }
192
+ }
193
+
194
+ class Signal<T> extends Core<T> {
195
+ changed: Changed;
196
+
197
+
198
+ constructor(data: T, options?: Options) {
199
+ super(CLEAN, data);
200
+ this.changed = options?.changed || changed;
201
+ }
202
+
203
+
204
+ get type(): Type {
205
+ return SIGNAL;
206
+ }
207
+
208
+
209
+ get() {
210
+ return read(this);
211
+ }
212
+
213
+ reset() {
214
+ flush('reset', this, CLEAN, this.value);
215
+ }
216
+
217
+ set(value: T): T {
218
+ return write(this, value);
122
219
  }
123
220
  }
124
221
 
@@ -127,17 +224,19 @@ function changed(a: unknown, b: unknown) {
127
224
  return a !== b;
128
225
  }
129
226
 
130
- function flush<T>(node: Signal<T>) {
131
- if (node.sources !== null) {
132
- removeSourceObservers(node, 0);
133
- }
227
+ function flush<T>(event: Event, node: Core<T>, state: State, value: T) {
228
+ node.dispatch(event, node);
229
+
230
+ removeSourceObservers(node, 0);
134
231
 
135
232
  node.listeners = null;
136
233
  node.observers = null;
137
234
  node.sources = null;
235
+ node.state = state;
236
+ node.value = value;
138
237
  }
139
238
 
140
- function notify<T>(nodes: Signal<T>[] | null, state: typeof CHECK | typeof DIRTY) {
239
+ function notify<T>(nodes: Core<T>[] | null, state: typeof CHECK | typeof DIRTY) {
141
240
  if (nodes === null) {
142
241
  return;
143
242
  }
@@ -147,7 +246,7 @@ function notify<T>(nodes: Signal<T>[] | null, state: typeof CHECK | typeof DIRTY
147
246
 
148
247
  if (node.state < state) {
149
248
  if (node.type === EFFECT && node.state === CLEAN) {
150
- node.root!.scheduler(node.task!);
249
+ node.root!.scheduler((node as any as Effect).task);
151
250
  }
152
251
 
153
252
  node.state = state;
@@ -156,7 +255,33 @@ function notify<T>(nodes: Signal<T>[] | null, state: typeof CHECK | typeof DIRTY
156
255
  }
157
256
  }
158
257
 
159
- function removeSourceObservers<T>(node: Signal<T>, start: number) {
258
+ function read<T>(node: Core<T>) {
259
+ if (node.state === DISPOSED) {
260
+ return node.value;
261
+ }
262
+
263
+ if (observer !== null) {
264
+ if (observers === null) {
265
+ if (observer.sources !== null && observer.sources[index] == node) {
266
+ index++;
267
+ }
268
+ else {
269
+ observers = [node];
270
+ }
271
+ }
272
+ else {
273
+ observers.push(node);
274
+ }
275
+ }
276
+
277
+ if (node.type === COMPUTED || node.type === EFFECT) {
278
+ sync(node);
279
+ }
280
+
281
+ return node.value;
282
+ }
283
+
284
+ function removeSourceObservers<T>(node: Core<T>, start: number) {
160
285
  if (node.sources === null) {
161
286
  return;
162
287
  }
@@ -173,7 +298,7 @@ function removeSourceObservers<T>(node: Signal<T>, start: number) {
173
298
  }
174
299
  }
175
300
 
176
- function sync<T>(node: Signal<T>) {
301
+ function sync<T>(node: Core<T>) {
177
302
  if (node.state === CHECK && node.sources !== null) {
178
303
  for (let i = 0, n = node.sources.length; i < n; i++) {
179
304
  sync(node.sources[i]);
@@ -188,6 +313,7 @@ function sync<T>(node: Signal<T>) {
188
313
  }
189
314
 
190
315
  if (node.state === DIRTY) {
316
+ // @ts-ignore
191
317
  update(node);
192
318
  }
193
319
  else {
@@ -195,7 +321,7 @@ function sync<T>(node: Signal<T>) {
195
321
  }
196
322
  }
197
323
 
198
- function update<T>(node: Signal<T>) {
324
+ function update<T>(node: Computed<T> | Effect) {
199
325
  let i = index,
200
326
  o = observer,
201
327
  os = observers;
@@ -208,11 +334,10 @@ function update<T>(node: Signal<T>) {
208
334
  node.dispatch('update');
209
335
  node.updating = true;
210
336
 
211
- let value = (
212
- node as typeof node extends Effect ? Effect : Computed<T>
213
- ).fn.call(node, node.value);
337
+ // @ts-ignore
338
+ let value = node.fn.call(node);
214
339
 
215
- node.updating = null;
340
+ node.updating = false;
216
341
 
217
342
  if (observers) {
218
343
  removeSourceObservers(node, index);
@@ -245,7 +370,7 @@ function update<T>(node: Signal<T>) {
245
370
  }
246
371
 
247
372
  if (node.type === COMPUTED) {
248
- write(node, value);
373
+ write(node as Computed<T>, value as T);
249
374
  }
250
375
  }
251
376
  catch {
@@ -262,17 +387,22 @@ function update<T>(node: Signal<T>) {
262
387
  node.state = CLEAN;
263
388
  }
264
389
 
390
+ function write<T>(node: Computed<T> | Signal<T>, value: T) {
391
+ if (node.changed(node.value, value)) {
392
+ node.value = value;
393
+ notify(node.observers, DIRTY);
394
+ }
265
395
 
266
- const computed = <T>(fn: Computed<T>['fn'], options: Options = {}) => {
267
- let node = new Signal(options.value as T, DIRTY, COMPUTED, options) as Computed<T>;
396
+ return value;
397
+ }
268
398
 
269
- node.fn = fn;
270
399
 
271
- return node;
400
+ const computed = <T>(fn: Computed<T>['fn'], options?: Options) => {
401
+ return new Computed(fn, options);
272
402
  };
273
403
 
274
- const dispose = <T extends { dispose: () => void }>(dispose?: T[] | T) => {
275
- if (dispose === undefined) {
404
+ const dispose = <T extends { dispose: VoidFunction }>(dispose?: T[] | T | null) => {
405
+ if (dispose == null) {
276
406
  }
277
407
  else if (isArray(dispose)) {
278
408
  for (let i = 0, n = dispose.length; i < n; i++) {
@@ -286,55 +416,12 @@ const dispose = <T extends { dispose: () => void }>(dispose?: T[] | T) => {
286
416
  return dispose;
287
417
  };
288
418
 
289
- const effect = (fn: Effect['fn'], options: Omit<Options, 'value'> = {}) => {
290
- let node = new Signal(void 0, DIRTY, EFFECT, options) as Effect;
291
-
292
- if (scope !== null) {
293
- node.root = scope;
294
- }
295
- else if (observer !== null && observer.type === EFFECT && observer.root !== null) {
296
- node.root = observer.root;
297
- }
298
- else {
299
- throw new Error('Reactivity: `effects` cannot be created without a reactive root');
300
- }
301
-
302
- node.fn = fn;
303
- node.task = () => read(node);
304
-
305
- read(node);
306
-
307
- return node;
419
+ const effect = (fn: Effect['fn']) => {
420
+ return new Effect(fn);
308
421
  };
309
422
 
310
- const read = <T>(node: Signal<T>): typeof node['value'] => {
311
- if (node.state === DISPOSED) {
312
- return node.value;
313
- }
314
-
315
- if (observer !== null) {
316
- if (observers === null) {
317
- if (observer.sources !== null && observer.sources[index] == node) {
318
- index++;
319
- }
320
- else {
321
- observers = [node];
322
- }
323
- }
324
- else {
325
- observers.push(node);
326
- }
327
- }
328
-
329
- if (node.fn !== null) {
330
- sync(node);
331
- }
332
-
333
- return node.value;
334
- };
335
-
336
- const reset = <T extends { reset: () => void }>(reset?: T[] | T) => {
337
- if (reset === undefined) {
423
+ const reset = <T extends { reset: VoidFunction }>(reset?: T[] | T | null) => {
424
+ if (reset == null) {
338
425
  }
339
426
  else if (isArray(reset)) {
340
427
  for (let i = 0, n = reset.length; i < n; i++) {
@@ -348,22 +435,22 @@ const reset = <T extends { reset: () => void }>(reset?: T[] | T) => {
348
435
  return reset;
349
436
  };
350
437
 
351
- function root<T>(fn: NeverAsync<() => T>, properties?: Root) {
438
+ const root = <T>(fn: NeverAsync<(root: Root) => T>, scheduler?: Root['scheduler']) => {
352
439
  let o = observer,
353
440
  s = scope;
354
441
 
355
- if (properties === undefined) {
442
+ if (scheduler === undefined) {
356
443
  if (scope === null) {
357
444
  throw new Error('Reactivity: `root` cannot be created without a task scheduler');
358
445
  }
359
446
 
360
- properties = scope;
447
+ scheduler = scope.scheduler;
361
448
  }
362
449
 
363
450
  observer = null;
364
- scope = properties;
451
+ scope = new Root(scheduler, fn.length > 0);
365
452
 
366
- let result = fn();
453
+ let result = fn(scope);
367
454
 
368
455
  observer = o;
369
456
  scope = s;
@@ -371,19 +458,9 @@ function root<T>(fn: NeverAsync<() => T>, properties?: Root) {
371
458
  return result;
372
459
  };
373
460
 
374
- const signal = <T>(data: T, options: Omit<Options, 'value'> = {}) => {
375
- return new Signal(data, CLEAN, SIGNAL, options);
376
- };
377
-
378
- const write = <T>(node: Signal<T>, value: unknown) => {
379
- if ((node.changed === null ? changed : node.changed)(node.value, value)) {
380
- node.value = value as T;
381
- notify(node.observers, DIRTY);
382
- }
383
-
384
- return node.value;
461
+ const signal = <T>(value: T, options?: Options) => {
462
+ return new Signal(value, options);
385
463
  };
386
464
 
387
465
 
388
- export default Signal;
389
- export { computed, dispose, effect, read, reset, root, signal, write };
466
+ export { computed, dispose, effect, reset, root, signal, Computed, Effect, Signal };
package/src/types.ts CHANGED
@@ -1,21 +1,10 @@
1
1
  import { Function, NeverAsync, Prettify } from '@esportsplus/typescript'
2
- import { CHECK, CLEAN, COMPUTED, DIRTY, DISPOSED, EFFECT, SIGNAL } from './constants';
3
- import Signal from './signal';
2
+ import { CHECK, CLEAN, COMPUTED, DIRTY, DISPOSED, EFFECT, ROOT, SIGNAL } from './constants';
3
+ import { Computed, Effect, Signal } from './signal';
4
4
 
5
5
 
6
6
  type Changed = (a: unknown, b: unknown) => boolean;
7
7
 
8
- type Computed<T> = {
9
- fn: NeverAsync<(previous: T) => T>;
10
- } & Omit<Signal<T>, 'fn'>;
11
-
12
- type Effect = {
13
- fn: NeverAsync<(node: Effect) => void>;
14
- root: Root;
15
- task: Function
16
- value: void;
17
- } & Omit<Signal<void>, 'fn' | 'root' | 'task' | 'value'>;
18
-
19
8
  type Event = string;
20
9
 
21
10
  type Listener<D> = {
@@ -28,18 +17,11 @@ type Object = Record<PropertyKey, unknown>;
28
17
 
29
18
  type Options = {
30
19
  changed?: Changed;
31
- value?: unknown;
32
20
  };
33
21
 
34
- type Root = {
35
- scheduler: Scheduler
36
- };
37
-
38
- type Scheduler = (fn: Function) => unknown;
39
-
40
22
  type State = typeof CHECK | typeof CLEAN | typeof DIRTY | typeof DISPOSED;
41
23
 
42
- type Type = typeof COMPUTED | typeof EFFECT | typeof SIGNAL;
24
+ type Type = typeof COMPUTED | typeof EFFECT | typeof ROOT | typeof SIGNAL;
43
25
 
44
26
 
45
- export { Changed, Computed, Effect, Event, Listener, Object, Options, NeverAsync, Prettify, Root, Scheduler, Signal, State, Type };
27
+ export { Changed, Computed, Effect, Event, Function, Listener, Object, Options, NeverAsync, Prettify, Signal, State, Type };