@esportsplus/reactivity 0.1.8 → 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;
@@ -209,9 +335,9 @@ function update<T>(node: Signal<T>) {
209
335
  node.updating = true;
210
336
 
211
337
  // @ts-ignore
212
- let value = node.fn.call(node, node.value);
338
+ let value = node.fn.call(node);
213
339
 
214
- node.updating = null;
340
+ node.updating = false;
215
341
 
216
342
  if (observers) {
217
343
  removeSourceObservers(node, index);
@@ -244,7 +370,7 @@ function update<T>(node: Signal<T>) {
244
370
  }
245
371
 
246
372
  if (node.type === COMPUTED) {
247
- write(node, value);
373
+ write(node as Computed<T>, value as T);
248
374
  }
249
375
  }
250
376
  catch {
@@ -261,17 +387,22 @@ function update<T>(node: Signal<T>) {
261
387
  node.state = CLEAN;
262
388
  }
263
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
+ }
264
395
 
265
- const computed = <T>(fn: Computed<T>['fn'], options: Options = {}) => {
266
- let node = new Signal(options.value as T, DIRTY, COMPUTED, options) as Computed<T>;
396
+ return value;
397
+ }
267
398
 
268
- node.fn = fn;
269
399
 
270
- return node;
400
+ const computed = <T>(fn: Computed<T>['fn'], options?: Options) => {
401
+ return new Computed(fn, options);
271
402
  };
272
403
 
273
- const dispose = <T extends { dispose: () => void }>(dispose?: T[] | T) => {
274
- if (dispose === undefined) {
404
+ const dispose = <T extends { dispose: VoidFunction }>(dispose?: T[] | T | null) => {
405
+ if (dispose == null) {
275
406
  }
276
407
  else if (isArray(dispose)) {
277
408
  for (let i = 0, n = dispose.length; i < n; i++) {
@@ -285,55 +416,12 @@ const dispose = <T extends { dispose: () => void }>(dispose?: T[] | T) => {
285
416
  return dispose;
286
417
  };
287
418
 
288
- const effect = (fn: Effect['fn'], options: Omit<Options, 'value'> = {}) => {
289
- let node = new Signal(void 0, DIRTY, EFFECT, options) as Effect;
290
-
291
- if (scope !== null) {
292
- node.root = scope;
293
- }
294
- else if (observer !== null && observer.type === EFFECT && observer.root !== null) {
295
- node.root = observer.root;
296
- }
297
- else {
298
- throw new Error('Reactivity: `effects` cannot be created without a reactive root');
299
- }
300
-
301
- node.fn = fn;
302
- node.task = () => read(node);
303
-
304
- read(node);
305
-
306
- return node;
419
+ const effect = (fn: Effect['fn']) => {
420
+ return new Effect(fn);
307
421
  };
308
422
 
309
- const read = <T>(node: Signal<T>): typeof node['value'] => {
310
- if (node.state === DISPOSED) {
311
- return node.value;
312
- }
313
-
314
- if (observer !== null) {
315
- if (observers === null) {
316
- if (observer.sources !== null && observer.sources[index] == node) {
317
- index++;
318
- }
319
- else {
320
- observers = [node];
321
- }
322
- }
323
- else {
324
- observers.push(node);
325
- }
326
- }
327
-
328
- if (node.fn !== null) {
329
- sync(node);
330
- }
331
-
332
- return node.value;
333
- };
334
-
335
- const reset = <T extends { reset: () => void }>(reset?: T[] | T) => {
336
- if (reset === undefined) {
423
+ const reset = <T extends { reset: VoidFunction }>(reset?: T[] | T | null) => {
424
+ if (reset == null) {
337
425
  }
338
426
  else if (isArray(reset)) {
339
427
  for (let i = 0, n = reset.length; i < n; i++) {
@@ -347,22 +435,22 @@ const reset = <T extends { reset: () => void }>(reset?: T[] | T) => {
347
435
  return reset;
348
436
  };
349
437
 
350
- function root<T>(fn: NeverAsync<() => T>, properties?: Root) {
438
+ const root = <T>(fn: NeverAsync<(root: Root) => T>, scheduler?: Root['scheduler']) => {
351
439
  let o = observer,
352
440
  s = scope;
353
441
 
354
- if (properties === undefined) {
442
+ if (scheduler === undefined) {
355
443
  if (scope === null) {
356
444
  throw new Error('Reactivity: `root` cannot be created without a task scheduler');
357
445
  }
358
446
 
359
- properties = scope;
447
+ scheduler = scope.scheduler;
360
448
  }
361
449
 
362
450
  observer = null;
363
- scope = properties;
451
+ scope = new Root(scheduler, fn.length > 0);
364
452
 
365
- let result = fn();
453
+ let result = fn(scope);
366
454
 
367
455
  observer = o;
368
456
  scope = s;
@@ -370,19 +458,9 @@ function root<T>(fn: NeverAsync<() => T>, properties?: Root) {
370
458
  return result;
371
459
  };
372
460
 
373
- const signal = <T>(data: T, options: Omit<Options, 'value'> = {}) => {
374
- return new Signal(data, CLEAN, SIGNAL, options);
375
- };
376
-
377
- const write = <T>(node: Signal<T>, value: unknown) => {
378
- if ((node.changed === null ? changed : node.changed)(node.value, value)) {
379
- node.value = value as T;
380
- notify(node.observers, DIRTY);
381
- }
382
-
383
- return node.value;
461
+ const signal = <T>(value: T, options?: Options) => {
462
+ return new Signal(value, options);
384
463
  };
385
464
 
386
465
 
387
- export default Signal;
388
- 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 };