@esportsplus/reactivity 0.1.9 → 0.1.11

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,31 @@ class Signal<T> {
68
87
  return;
69
88
  }
70
89
 
71
- this.state = DISPOSED;
72
-
73
90
  this.dispatch('dispose', this);
74
- flush(this);
91
+
92
+ removeSourceObservers(this, 0);
93
+
94
+ this.listeners = null;
95
+ this.observers = null;
96
+ this.sources = null;
97
+ this.state = DISPOSED;
75
98
  }
76
99
 
77
- on(event: Event, listener: Listener<any>) {
100
+ on<T>(event: Event, listener: Listener<T>) {
78
101
  if (this.updating) {
79
102
  listener.once = true;
80
103
  }
81
104
 
82
- if (this.listeners === null || !(event in this.listeners)) {
83
- this.listeners ??= {};
84
- this.listeners[event] = [listener];
105
+ if (this.listeners === null) {
106
+ this.listeners = { [event]: [listener] };
85
107
  }
86
108
  else {
87
109
  let listeners = this.listeners[event];
88
110
 
89
- if (listeners.indexOf(listener) === -1) {
111
+ if (listeners === undefined) {
112
+ this.listeners[event] = [listener];
113
+ }
114
+ else if (listeners.indexOf(listener) === -1) {
90
115
  let i = listeners.indexOf(null);
91
116
 
92
117
  if (i === -1) {
@@ -99,45 +124,100 @@ class Signal<T> {
99
124
  }
100
125
  }
101
126
 
102
- once(event: Event, listener: Listener<any>) {
127
+ once<T>(event: Event, listener: Listener<T>) {
103
128
  listener.once = true;
104
129
  this.on(event, listener);
105
130
  }
131
+ }
106
132
 
107
- reset() {
108
- this.dispatch('reset', this);
109
- flush(this);
133
+ class Computed<T> extends Core<T> {
134
+ changed: Changed;
135
+ fn: NeverAsync<() => T>;
110
136
 
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
- }
137
+
138
+ constructor(fn: Computed<T>['fn'], options?: Options) {
139
+ super(DIRTY, undefined as T);
140
+ this.changed = options?.changed || changed;
141
+ this.fn = fn;
142
+ }
143
+
144
+
145
+ get type(): Type {
146
+ return COMPUTED;
147
+ }
148
+
149
+
150
+ get() {
151
+ return read(this);
122
152
  }
123
153
  }
124
154
 
155
+ class Effect extends Core<null> {
156
+ fn: NeverAsync<(node: Effect) => void>;
157
+ task: Function;
125
158
 
126
- function changed(a: unknown, b: unknown) {
127
- return a !== b;
159
+
160
+ constructor(fn: Effect['fn']) {
161
+ super(DIRTY, null);
162
+ this.fn = fn;
163
+ this.task = () => read(this);
164
+
165
+ update(this);
166
+ }
167
+
168
+
169
+ get type(): Type {
170
+ return EFFECT;
171
+ }
128
172
  }
129
173
 
130
- function flush<T>(node: Signal<T>) {
131
- if (node.sources !== null) {
132
- removeSourceObservers(node, 0);
174
+ class Root extends Core<null> {
175
+ scheduler: (fn: Function) => unknown;
176
+ tracking: boolean;
177
+
178
+
179
+ constructor(scheduler: Root['scheduler'], tracking: boolean) {
180
+ super(CLEAN, null);
181
+ this.scheduler = scheduler;
182
+ this.tracking = tracking;
183
+ }
184
+
185
+
186
+ get type(): Type {
187
+ return ROOT;
188
+ }
189
+ }
190
+
191
+ class Signal<T> extends Core<T> {
192
+ changed: Changed;
193
+
194
+
195
+ constructor(data: T, options?: Options) {
196
+ super(CLEAN, data);
197
+ this.changed = options?.changed || changed;
198
+ }
199
+
200
+
201
+ get type(): Type {
202
+ return SIGNAL;
203
+ }
204
+
205
+
206
+ get() {
207
+ return read(this);
133
208
  }
134
209
 
135
- node.listeners = null;
136
- node.observers = null;
137
- node.sources = null;
210
+ set(value: T): T {
211
+ return write(this, value);
212
+ }
138
213
  }
139
214
 
140
- function notify<T>(nodes: Signal<T>[] | null, state: typeof CHECK | typeof DIRTY) {
215
+
216
+ function changed(a: unknown, b: unknown) {
217
+ return a !== b;
218
+ }
219
+
220
+ function notify<T>(nodes: Core<T>[] | null, state: typeof CHECK | typeof DIRTY) {
141
221
  if (nodes === null) {
142
222
  return;
143
223
  }
@@ -147,7 +227,7 @@ function notify<T>(nodes: Signal<T>[] | null, state: typeof CHECK | typeof DIRTY
147
227
 
148
228
  if (node.state < state) {
149
229
  if (node.type === EFFECT && node.state === CLEAN) {
150
- node.root!.scheduler(node.task!);
230
+ node.root!.scheduler((node as any as Effect).task);
151
231
  }
152
232
 
153
233
  node.state = state;
@@ -156,7 +236,33 @@ function notify<T>(nodes: Signal<T>[] | null, state: typeof CHECK | typeof DIRTY
156
236
  }
157
237
  }
158
238
 
159
- function removeSourceObservers<T>(node: Signal<T>, start: number) {
239
+ function read<T>(node: Core<T>) {
240
+ if (node.state === DISPOSED) {
241
+ return node.value;
242
+ }
243
+
244
+ if (observer !== null) {
245
+ if (observers === null) {
246
+ if (observer.sources !== null && observer.sources[index] == node) {
247
+ index++;
248
+ }
249
+ else {
250
+ observers = [node];
251
+ }
252
+ }
253
+ else {
254
+ observers.push(node);
255
+ }
256
+ }
257
+
258
+ if (node.type === COMPUTED || node.type === EFFECT) {
259
+ sync(node);
260
+ }
261
+
262
+ return node.value;
263
+ }
264
+
265
+ function removeSourceObservers<T>(node: Core<T>, start: number) {
160
266
  if (node.sources === null) {
161
267
  return;
162
268
  }
@@ -173,7 +279,7 @@ function removeSourceObservers<T>(node: Signal<T>, start: number) {
173
279
  }
174
280
  }
175
281
 
176
- function sync<T>(node: Signal<T>) {
282
+ function sync<T>(node: Core<T>) {
177
283
  if (node.state === CHECK && node.sources !== null) {
178
284
  for (let i = 0, n = node.sources.length; i < n; i++) {
179
285
  sync(node.sources[i]);
@@ -188,14 +294,14 @@ function sync<T>(node: Signal<T>) {
188
294
  }
189
295
 
190
296
  if (node.state === DIRTY) {
191
- update(node);
297
+ update(node as Computed<T> | Effect);
192
298
  }
193
299
  else {
194
300
  node.state = CLEAN;
195
301
  }
196
302
  }
197
303
 
198
- function update<T>(node: Signal<T>) {
304
+ function update<T>(node: Computed<T> | Effect) {
199
305
  let i = index,
200
306
  o = observer,
201
307
  os = observers;
@@ -208,11 +314,10 @@ function update<T>(node: Signal<T>) {
208
314
  node.dispatch('update');
209
315
  node.updating = true;
210
316
 
211
- let value = (
212
- node as typeof node extends Effect ? Effect : Computed<T>
213
- ).fn.call(node, node.value);
317
+ // @ts-ignore
318
+ let value = node.fn.call(node);
214
319
 
215
- node.updating = null;
320
+ node.updating = false;
216
321
 
217
322
  if (observers) {
218
323
  removeSourceObservers(node, index);
@@ -245,7 +350,7 @@ function update<T>(node: Signal<T>) {
245
350
  }
246
351
 
247
352
  if (node.type === COMPUTED) {
248
- write(node, value);
353
+ write(node as Computed<T>, value as T);
249
354
  }
250
355
  }
251
356
  catch {
@@ -262,17 +367,22 @@ function update<T>(node: Signal<T>) {
262
367
  node.state = CLEAN;
263
368
  }
264
369
 
370
+ function write<T>(node: Computed<T> | Signal<T>, value: T) {
371
+ if (node.changed(node.value, value)) {
372
+ node.value = value;
373
+ notify(node.observers, DIRTY);
374
+ }
265
375
 
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>;
376
+ return value;
377
+ }
268
378
 
269
- node.fn = fn;
270
379
 
271
- return node;
380
+ const computed = <T>(fn: Computed<T>['fn'], options?: Options) => {
381
+ return new Computed(fn, options);
272
382
  };
273
383
 
274
- const dispose = <T extends { dispose: () => void }>(dispose?: T[] | T) => {
275
- if (dispose === undefined) {
384
+ const dispose = <T extends { dispose: VoidFunction }>(dispose?: T[] | T | null) => {
385
+ if (dispose == null) {
276
386
  }
277
387
  else if (isArray(dispose)) {
278
388
  for (let i = 0, n = dispose.length; i < n; i++) {
@@ -286,84 +396,26 @@ const dispose = <T extends { dispose: () => void }>(dispose?: T[] | T) => {
286
396
  return dispose;
287
397
  };
288
398
 
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;
399
+ const effect = (fn: Effect['fn']) => {
400
+ return new Effect(fn);
308
401
  };
309
402
 
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) {
338
- }
339
- else if (isArray(reset)) {
340
- for (let i = 0, n = reset.length; i < n; i++) {
341
- reset[i].reset();
342
- }
343
- }
344
- else {
345
- reset.reset();
346
- }
347
-
348
- return reset;
349
- };
350
-
351
- function root<T>(fn: NeverAsync<() => T>, properties?: Root) {
403
+ const root = <T>(fn: NeverAsync<(root: Root) => T>, scheduler?: Root['scheduler']) => {
352
404
  let o = observer,
353
405
  s = scope;
354
406
 
355
- if (properties === undefined) {
407
+ if (scheduler === undefined) {
356
408
  if (scope === null) {
357
409
  throw new Error('Reactivity: `root` cannot be created without a task scheduler');
358
410
  }
359
411
 
360
- properties = scope;
412
+ scheduler = scope.scheduler;
361
413
  }
362
414
 
363
415
  observer = null;
364
- scope = properties;
416
+ scope = new Root(scheduler, fn.length > 0);
365
417
 
366
- let result = fn();
418
+ let result = fn(scope);
367
419
 
368
420
  observer = o;
369
421
  scope = s;
@@ -371,19 +423,10 @@ function root<T>(fn: NeverAsync<() => T>, properties?: Root) {
371
423
  return result;
372
424
  };
373
425
 
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;
426
+ const signal = <T>(value: T, options?: Options) => {
427
+ return new Signal(value, options);
385
428
  };
386
429
 
387
430
 
388
- export default Signal;
389
- export { computed, dispose, effect, read, reset, root, signal, write };
431
+ export { computed, dispose, effect, root, signal };
432
+ export { 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 };