@esportsplus/reactivity 0.12.3 → 0.13.0

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.
@@ -1,76 +1,87 @@
1
1
  import { defineProperty, isArray, isFunction, isObject, isPromise, Prettify } from '@esportsplus/utilities';
2
- import { computed, dispose, effect, isComputed, read, root, set, signal } from '~/system';
2
+ import { computed, dispose, effect, read, root, set, signal } from '~/system';
3
3
  import { Computed, Infer, Signal } from '~/types';
4
4
  import { REACTIVE_OBJECT } from '~/constants';
5
- import array, { isReactiveArray } from './array';
5
+ import array from './array';
6
6
 
7
7
 
8
- type API<T extends Record<PropertyKey, unknown>> = Prettify<{ [K in keyof T]: Infer<T[K]> }> & ReactiveObject<T>;
8
+ type API<T> = Prettify<{ [K in keyof T]: Infer<T[K]> } & { dispose: VoidFunction } >;
9
9
 
10
10
 
11
11
  class ReactiveObject<T extends Record<PropertyKey, unknown>> {
12
12
  [REACTIVE_OBJECT] = true;
13
13
 
14
14
 
15
+ private disposers: VoidFunction[] | null = null;
16
+
17
+
15
18
  constructor(data: T) {
16
- for (let key in data) {
17
- let value = data[key];
19
+ let keys = Object.keys(data);
20
+
21
+ for (let i = 0, n = keys.length; i < n; i++) {
22
+ let key = keys[i],
23
+ value = data[key];
18
24
 
19
25
  if (isArray(value)) {
20
- let a = array(value);
26
+ let node = array(value);
27
+
28
+ (this.disposers ??= []).push( () => node.dispose() );
21
29
 
22
30
  defineProperty(this, key, {
23
31
  enumerable: true,
24
- get() {
25
- return a;
26
- }
32
+ value: node
27
33
  });
28
34
  }
29
35
  else if (isFunction(value)) {
30
- let c: Computed<T[typeof key]> | Signal<T[typeof key] | undefined> | undefined;
36
+ let node: Computed<T[typeof key]> | Signal<T[typeof key] | undefined> | undefined;
31
37
 
32
38
  defineProperty(this, key, {
33
39
  enumerable: true,
34
- get() {
35
- if (c === undefined) {
40
+ get: () => {
41
+ if (node === undefined) {
36
42
  root(() => {
37
- c = computed(value as Computed<T[typeof key]>['fn']);
43
+ node = computed(value as Computed<T[typeof key]>['fn']);
38
44
 
39
- if (isPromise(c.value)) {
40
- let factory = c,
45
+ if (isPromise(node.value)) {
46
+ let factory = node,
41
47
  version = 0;
42
48
 
43
- c = signal(undefined);
49
+ node = signal<T[typeof key] | undefined>(undefined);
44
50
 
45
- effect(() => {
46
- let id = ++version;
51
+ (this.disposers ??= []).push(
52
+ effect(() => {
53
+ let id = ++version;
47
54
 
48
- (read(factory) as Promise<T[typeof key]>).then((value) => {
49
- if (id !== version) {
50
- return;
51
- }
55
+ (read(factory) as Promise<T[typeof key]>).then((v) => {
56
+ if (id !== version) {
57
+ return;
58
+ }
52
59
 
53
- set(c as Signal<typeof value>, value);
54
- });
55
- });
60
+ set(node as Signal<typeof v>, v);
61
+ });
62
+ })
63
+ )
64
+ }
65
+ else {
66
+ (this.disposers ??= []).push(() => dispose(node as Computed<T[typeof key]>));
56
67
  }
57
68
  });
58
69
  }
59
70
 
60
- return read(c!);
71
+ return read(node!);
61
72
  }
62
73
  });
63
74
  }
64
75
  else {
65
- let s = signal(value);
76
+ let node = signal(value);
66
77
 
67
78
  defineProperty(this, key, {
68
79
  enumerable: true,
69
80
  get() {
70
- return read(s);
81
+ return read(node);
71
82
  },
72
83
  set(v: typeof value) {
73
- set(s, v);
84
+ set(node, v);
74
85
  }
75
86
  });
76
87
  }
@@ -79,17 +90,15 @@ class ReactiveObject<T extends Record<PropertyKey, unknown>> {
79
90
 
80
91
 
81
92
  dispose() {
82
- let value;
93
+ let disposers = this.disposers,
94
+ disposer;
83
95
 
84
- for (let key in this) {
85
- value = this[key];
96
+ if (!disposers) {
97
+ return;
98
+ }
86
99
 
87
- if (isReactiveArray(value) || isReactiveObject(value)) {
88
- value.dispose();
89
- }
90
- else if (isComputed(value)) {
91
- dispose(value);
92
- }
100
+ while (disposer = disposers.pop()) {
101
+ disposer();
93
102
  }
94
103
  }
95
104
  }
package/src/system.ts CHANGED
@@ -2,15 +2,15 @@ import { isArray, isObject } from '@esportsplus/utilities';
2
2
  import {
3
3
  COMPUTED, SIGNAL,
4
4
  STABILIZER_IDLE, STABILIZER_RESCHEDULE, STABILIZER_RUNNING, STABILIZER_SCHEDULED,
5
- STATE_CHECK, STATE_DIRTY, STATE_IN_HEAP, STATE_NONE, STATE_RECOMPUTING
5
+ STATE_CHECK, STATE_DIRTY, STATE_IN_HEAP, STATE_NONE, STATE_NOTIFY_MASK, STATE_RECOMPUTING
6
6
  } from './constants';
7
7
  import { Computed, Link, Signal } from './types';
8
8
 
9
9
 
10
10
  let depth = 0,
11
- heap: (Computed<unknown> | undefined)[] = new Array(2048),
12
- index = 0,
13
- length = 0,
11
+ heap: (Computed<unknown> | undefined)[] = new Array(64),
12
+ heap_i = 0,
13
+ heap_n = 0,
14
14
  microtask = queueMicrotask,
15
15
  notified = false,
16
16
  observer: Computed<unknown> | null = null,
@@ -93,12 +93,12 @@ function insertIntoHeap<T>(computed: Computed<T>) {
93
93
  heapAtHeight.prevHeap = computed;
94
94
  }
95
95
 
96
- if (height > length) {
97
- length = height;
96
+ if (height > heap_n) {
97
+ heap_n = height;
98
98
 
99
99
  // Simple auto adjust to avoid manual management within apps.
100
100
  if (height >= heap.length) {
101
- heap.length = Math.round(heap.length * 1.5);
101
+ heap.length = Math.max(height + 1, Math.ceil(heap.length * 2));
102
102
  }
103
103
  }
104
104
  }
@@ -163,7 +163,7 @@ function link<T>(dep: Signal<T> | Computed<T>, sub: Computed<T>) {
163
163
  function notify<T>(computed: Computed<T>, newState = STATE_DIRTY) {
164
164
  let state = computed.state;
165
165
 
166
- if ((state & (STATE_CHECK | STATE_DIRTY)) >= newState) {
166
+ if ((state & STATE_NOTIFY_MASK) >= newState) {
167
167
  return;
168
168
  }
169
169
 
@@ -183,7 +183,9 @@ function recompute<T>(computed: Computed<T>, del: boolean) {
183
183
  computed.prevHeap = computed;
184
184
  }
185
185
 
186
- cleanup(computed);
186
+ if (computed.cleanup) {
187
+ cleanup(computed);
188
+ }
187
189
 
188
190
  let o = observer,
189
191
  ok = true,
@@ -258,10 +260,10 @@ function stabilize() {
258
260
  observer = null;
259
261
  stabilizer = STABILIZER_RUNNING;
260
262
 
261
- for (index = 0; index <= length; index++) {
262
- let computed = heap[index];
263
+ for (heap_i = 0; heap_i <= heap_n; heap_i++) {
264
+ let computed = heap[heap_i];
263
265
 
264
- heap[index] = undefined;
266
+ heap[heap_i] = undefined;
265
267
 
266
268
  while (computed !== undefined) {
267
269
  let next = computed.nextHeap;
@@ -272,6 +274,10 @@ function stabilize() {
272
274
  }
273
275
  }
274
276
 
277
+ while (heap_n > 0 && heap[heap_n] === undefined) {
278
+ heap_n--;
279
+ }
280
+
275
281
  observer = o;
276
282
 
277
283
  if (stabilizer === STABILIZER_RESCHEDULE) {
@@ -284,7 +290,10 @@ function stabilize() {
284
290
 
285
291
  // https://github.com/stackblitz/alien-signals/blob/v2.0.3/src/system.ts#L100
286
292
  function unlink(link: Link): Link | null {
287
- let { dep, nextDep, nextSub, prevSub } = link;
293
+ let dep = link.dep,
294
+ nextDep = link.nextDep,
295
+ nextSub = link.nextSub,
296
+ prevSub = link.prevSub;
288
297
 
289
298
  if (nextSub !== null) {
290
299
  nextSub.prevSub = prevSub;
@@ -377,7 +386,9 @@ const dispose = <T>(computed: Computed<T>) => {
377
386
 
378
387
  computed.deps = null;
379
388
 
380
- cleanup(computed);
389
+ if (computed.cleanup) {
390
+ cleanup(computed);
391
+ }
381
392
  };
382
393
 
383
394
  const effect = <T>(fn: Computed<T>['fn']) => {
@@ -401,14 +412,16 @@ const onCleanup = (fn: VoidFunction): typeof fn => {
401
412
  return fn;
402
413
  }
403
414
 
404
- if (!observer.cleanup) {
415
+ let cleanup = observer.cleanup;
416
+
417
+ if (!cleanup) {
405
418
  observer.cleanup = fn;
406
419
  }
407
- else if (isArray(observer.cleanup)) {
408
- observer.cleanup.push(fn);
420
+ else if (isArray(cleanup)) {
421
+ cleanup.push(fn);
409
422
  }
410
423
  else {
411
- observer.cleanup = [observer.cleanup, fn];
424
+ observer.cleanup = [cleanup, fn];
412
425
  }
413
426
 
414
427
  return fn;
@@ -425,14 +438,11 @@ const read = <T>(node: Signal<T> | Computed<T>): T => {
425
438
  observer.height = height + 1;
426
439
  }
427
440
 
428
- if (
429
- height >= index ||
430
- node.state & (STATE_DIRTY | STATE_CHECK)
431
- ) {
441
+ if (height >= heap_i || node.state & STATE_NOTIFY_MASK) {
432
442
  if (!notified) {
433
443
  notified = true;
434
444
 
435
- for (let i = 0; i <= length; i++) {
445
+ for (let i = 0; i <= heap_n; i++) {
436
446
  for (let computed = heap[i]; computed !== undefined; computed = computed.nextHeap) {
437
447
  notify(computed);
438
448
  }
package/src/types.ts CHANGED
@@ -1,5 +1,4 @@
1
1
  import { COMPUTED, SIGNAL, STATE_CHECK, STATE_DIRTY, STATE_IN_HEAP, STATE_NONE, STATE_RECOMPUTING } from './constants';
2
- import { ReactiveArray } from './reactive/array';
3
2
  import { ReactiveObject } from './reactive/object';
4
3
 
5
4
 
@@ -29,7 +28,7 @@ type Infer<T> =
29
28
  : T extends (...args: any[]) => infer R
30
29
  ? R
31
30
  : T extends (infer U)[]
32
- ? ReactiveArray<U>
31
+ ? Infer<U>[]
33
32
  : T extends ReactiveObject<any>
34
33
  ? T
35
34
  : T extends Record<PropertyKey, unknown>
@@ -45,10 +44,6 @@ interface Link {
45
44
  version: number;
46
45
  }
47
46
 
48
- type Reactive<T> = T extends Record<PropertyKey, unknown>
49
- ? ReactiveObject<T>
50
- : ReactiveArray<T>;
51
-
52
47
  type Signal<T> = {
53
48
  [SIGNAL]: true;
54
49
  subs: Link | null;
@@ -62,5 +57,5 @@ export type {
62
57
  Infer,
63
58
  Link,
64
59
  Signal,
65
- Reactive, ReactiveArray, ReactiveObject
60
+ ReactiveObject
66
61
  };