@esportsplus/reactivity 0.31.2 → 0.32.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.
Files changed (69) hide show
  1. package/.claude/CHANGELOG.md +43 -0
  2. package/README.md +13 -6
  3. package/bench/cellx.bench.ts +61 -0
  4. package/bench/dynamic.bench.ts +103 -0
  5. package/bench/kairo.bench.ts +368 -0
  6. package/bench/lib/reactive-adapter.ts +51 -0
  7. package/bench/molbench.bench.ts +75 -0
  8. package/{tests/bench/system.ts → bench/system.bench.ts} +3 -3
  9. package/build/compiler/array.d.ts +1 -1
  10. package/build/compiler/constants.d.ts +7 -6
  11. package/build/compiler/constants.js +6 -7
  12. package/build/compiler/object.d.ts +1 -1
  13. package/build/compiler/object.js +1 -2
  14. package/build/compiler/primitives.d.ts +1 -1
  15. package/build/constants.d.ts +3 -1
  16. package/build/constants.js +3 -1
  17. package/build/reactive/array.d.ts +8 -5
  18. package/build/reactive/array.js +14 -14
  19. package/build/reactive/index.d.ts +2 -2
  20. package/build/reactive/index.js +1 -1
  21. package/build/reactive/object.d.ts +4 -4
  22. package/build/reactive/object.js +8 -23
  23. package/build/system.d.ts +15 -6
  24. package/build/system.js +415 -69
  25. package/build/types.d.ts +16 -4
  26. package/package.json +5 -2
  27. package/pnpm-workspace.yaml +2 -0
  28. package/src/compiler/array.ts +1 -1
  29. package/src/compiler/constants.ts +8 -6
  30. package/src/compiler/index.ts +2 -1
  31. package/src/compiler/object.ts +2 -2
  32. package/src/compiler/plugins/vite.ts +1 -0
  33. package/src/compiler/primitives.ts +1 -1
  34. package/src/constants.ts +6 -2
  35. package/src/reactive/array.ts +26 -23
  36. package/src/reactive/index.ts +6 -6
  37. package/src/reactive/object.ts +16 -41
  38. package/src/system.ts +612 -80
  39. package/src/types.ts +29 -9
  40. package/test/async-computed.test.ts +323 -0
  41. package/test/async-errors.test.ts +146 -0
  42. package/test/async-hardening.test.ts +73 -0
  43. package/test/async-iterable.test.ts +221 -0
  44. package/test/async-nested.test.ts +19 -0
  45. package/test/deep-graphs.test.ts +215 -0
  46. package/{tests/effects.ts → test/effects.test.ts} +60 -0
  47. package/test/equals.test.ts +142 -0
  48. package/test/errors.test.ts +201 -0
  49. package/test/flush.test.ts +185 -0
  50. package/test/glitch-freedom.test.ts +115 -0
  51. package/test/invalidate.test.ts +147 -0
  52. package/test/lib/wait-for.ts +28 -0
  53. package/test/pending-writes.test.ts +139 -0
  54. package/{tests/reactive.ts → test/reactive/reactive.test.ts} +20 -19
  55. package/test/read-dedup.test.ts +120 -0
  56. package/test/signal-selector.test.ts +187 -0
  57. package/{tests/system.ts → test/system.test.ts} +214 -23
  58. package/test/tsconfig.json +16 -0
  59. package/test/untrack.test.ts +146 -0
  60. package/vitest.config.ts +2 -2
  61. package/tests/async-computed.ts +0 -239
  62. /package/{tests/bench/array.ts → bench/reactive/array.bench.ts} +0 -0
  63. /package/{tests/bench/reactive-object.ts → bench/reactive/reactive-object.bench.ts} +0 -0
  64. /package/{tests → bench}/tsconfig.json +0 -0
  65. /package/{tests/compiler.ts → test/compiler/compiler.test.ts} +0 -0
  66. /package/{tests/primitives.ts → test/primitives.test.ts} +0 -0
  67. /package/{tests/array.ts → test/reactive/array.test.ts} +0 -0
  68. /package/{tests/nested.ts → test/reactive/nested.test.ts} +0 -0
  69. /package/{tests/objects.ts → test/reactive/objects.test.ts} +0 -0
package/package.json CHANGED
@@ -36,10 +36,13 @@
36
36
  },
37
37
  "type": "module",
38
38
  "types": "build/index.d.ts",
39
- "version": "0.31.2",
39
+ "version": "0.32.0",
40
40
  "scripts": {
41
+ "agent:bench": "vitest bench --run",
42
+ "agent:test": "tsc --noEmit && vitest run",
43
+ "bench": "vitest bench --run",
41
44
  "build": "tsc",
42
- "build:test": "pnpm build && vite build --config test/vite.config.ts",
45
+ "test": "vitest run",
43
46
  "-": "-"
44
47
  }
45
48
  }
@@ -0,0 +1,2 @@
1
+ allowBuilds:
2
+ esbuild: true
@@ -1,6 +1,6 @@
1
- import type { ReplacementIntent } from '@esportsplus/typescript/compiler';
2
1
  import { ts } from '@esportsplus/typescript';
3
2
  import { ast, imports } from '@esportsplus/typescript/compiler';
3
+ import type { ReplacementIntent } from '@esportsplus/typescript/compiler';
4
4
  import { ENTRYPOINT, NAMESPACE, PACKAGE_NAME, TYPES } from './constants';
5
5
  import type { Bindings } from './types';
6
6
 
@@ -8,12 +8,14 @@ const ENTRYPOINT_REGEX = /\breactive\b/;
8
8
  const NAMESPACE = uid('reactivity');
9
9
 
10
10
 
11
- const enum TYPES {
12
- Array,
13
- Computed,
14
- Object,
15
- Signal
16
- }
11
+ const TYPES = {
12
+ Array: 0,
13
+ Computed: 1,
14
+ Object: 2,
15
+ Signal: 3
16
+ } as const;
17
+
18
+ type TYPES = typeof TYPES[keyof typeof TYPES];
17
19
 
18
20
 
19
21
  export { ENTRYPOINT, ENTRYPOINT_REGEX, NAMESPACE, TYPES };
@@ -1,8 +1,9 @@
1
- import type { ImportIntent, ReplacementIntent, TransformContext } from '@esportsplus/typescript/compiler';
2
1
  import { ts } from '@esportsplus/typescript';
3
2
  import { imports } from '@esportsplus/typescript/compiler';
3
+ import type { ImportIntent, ReplacementIntent, TransformContext } from '@esportsplus/typescript/compiler';
4
4
  import { ENTRYPOINT, NAMESPACE, PACKAGE_NAME } from './constants';
5
5
  import type { Bindings } from './types';
6
+
6
7
  import array from './array';
7
8
  import object from './object';
8
9
  import primitives from './primitives';
@@ -1,6 +1,6 @@
1
- import { code, imports, type ReplacementIntent } from '@esportsplus/typescript/compiler';
2
1
  import { ts } from '@esportsplus/typescript';
3
- import { uid } from '@esportsplus/typescript/compiler';
2
+ import { code, imports, uid } from '@esportsplus/typescript/compiler';
3
+ import type { ReplacementIntent } from '@esportsplus/typescript/compiler';
4
4
  import { ENTRYPOINT, NAMESPACE, PACKAGE_NAME, TYPES } from './constants';
5
5
  import type { Bindings } from './types';
6
6
 
@@ -1,5 +1,6 @@
1
1
  import { plugin } from '@esportsplus/typescript/compiler';
2
2
  import { PACKAGE_NAME } from '../constants';
3
+
3
4
  import reactivity from '..';
4
5
 
5
6
 
@@ -1,5 +1,5 @@
1
- import type { ReplacementIntent } from '@esportsplus/typescript/compiler';
2
1
  import { ts } from '@esportsplus/typescript';
2
+ import type { ReplacementIntent } from '@esportsplus/typescript/compiler';
3
3
  import { NAMESPACE, TYPES } from './constants';
4
4
  import type { Bindings } from './types';
5
5
 
package/src/constants.ts CHANGED
@@ -28,14 +28,18 @@ const STATE_IN_HEAP = 1 << 3;
28
28
 
29
29
  const STATE_COMPUTED = 1 << 4;
30
30
 
31
+ const STATE_ERROR = 1 << 5;
32
+
33
+ const STATE_EFFECT = 1 << 6;
34
+
31
35
  const STATE_NOTIFY_MASK = (STATE_CHECK | STATE_DIRTY);
32
36
 
33
37
 
34
38
  export {
35
39
  COMPUTED,
36
- REACTIVE_ARRAY, REACTIVE_OBJECT,
37
40
  PACKAGE_NAME,
41
+ REACTIVE_ARRAY, REACTIVE_OBJECT,
38
42
  SIGNAL,
39
43
  STABILIZER_IDLE, STABILIZER_RESCHEDULE, STABILIZER_RUNNING, STABILIZER_SCHEDULED,
40
- STATE_CHECK, STATE_COMPUTED, STATE_DIRTY, STATE_IN_HEAP, STATE_NONE, STATE_NOTIFY_MASK, STATE_RECOMPUTING
44
+ STATE_CHECK, STATE_COMPUTED, STATE_DIRTY, STATE_EFFECT, STATE_ERROR, STATE_IN_HEAP, STATE_NONE, STATE_NOTIFY_MASK, STATE_RECOMPUTING
41
45
  };
@@ -1,6 +1,6 @@
1
1
  import { isArray } from '@esportsplus/utilities';
2
- import { read, signal, write } from '~/system';
3
2
  import { REACTIVE_ARRAY } from '~/constants';
3
+ import { read, signal, write } from '~/system';
4
4
  import type { Signal } from '~/types';
5
5
  import { isReactiveObject } from './object';
6
6
 
@@ -42,7 +42,9 @@ type Listener<V> = {
42
42
  (value: V): void;
43
43
  };
44
44
 
45
- type Listeners = Record<string, (Listener<any> | null)[]>;
45
+ type AnyListener<T> = Listener<Events<T>[keyof Events<T>]>;
46
+
47
+ type Listeners<T> = { [K in keyof Events<T>]?: (AnyListener<T> | null)[] };
46
48
 
47
49
 
48
50
  function dispose(value: unknown) {
@@ -55,7 +57,7 @@ function dispose(value: unknown) {
55
57
  class ReactiveArray<T> extends Array<T> {
56
58
  private _length: Signal<number>;
57
59
 
58
- listeners: Listeners = {};
60
+ listeners: Listeners<T> = {};
59
61
 
60
62
 
61
63
  constructor(...items: T[]) {
@@ -64,19 +66,6 @@ class ReactiveArray<T> extends Array<T> {
64
66
  }
65
67
 
66
68
 
67
- get $length() {
68
- return read(this._length);
69
- }
70
-
71
- set $length(value: number) {
72
- if (value > this.length) {
73
- throw Error(`@esportsplus/reactivity: cannot set length to a value larger than the current length, use splice instead.`);
74
- }
75
-
76
- this.splice(value, this.length);
77
- }
78
-
79
-
80
69
  $set(i: number, value: T) {
81
70
  let prev = this[i];
82
71
 
@@ -130,7 +119,7 @@ class ReactiveArray<T> extends Array<T> {
130
119
  return this;
131
120
  }
132
121
 
133
- dispatch<K extends keyof Events<T>, V>(event: K, value?: V) {
122
+ dispatch<K extends keyof Events<T>>(event: K, value?: Events<T>[K]) {
134
123
  let listeners = this.listeners[event];
135
124
 
136
125
  if (!listeners) {
@@ -147,7 +136,7 @@ class ReactiveArray<T> extends Array<T> {
147
136
  }
148
137
 
149
138
  try {
150
- listener(value);
139
+ listener(value as Events<T>[K]);
151
140
 
152
141
  if (listener.once !== undefined) {
153
142
  dirty = true;
@@ -176,10 +165,11 @@ class ReactiveArray<T> extends Array<T> {
176
165
  }
177
166
 
178
167
  on<K extends keyof Events<T>>(event: K, listener: Listener<Events<T>[K]>) {
179
- let listeners = this.listeners[event];
168
+ let entry = listener as AnyListener<T>,
169
+ listeners = this.listeners[event];
180
170
 
181
171
  if (listeners === undefined) {
182
- this.listeners[event] = [listener];
172
+ this.listeners[event] = [entry];
183
173
  }
184
174
  else {
185
175
  let hole = listeners.length;
@@ -187,7 +177,7 @@ class ReactiveArray<T> extends Array<T> {
187
177
  for (let i = 0, n = hole; i < n; i++) {
188
178
  let l = listeners[i];
189
179
 
190
- if (l === listener) {
180
+ if (l === entry) {
191
181
  return;
192
182
  }
193
183
  else if (l === null && hole === n) {
@@ -195,7 +185,7 @@ class ReactiveArray<T> extends Array<T> {
195
185
  }
196
186
  }
197
187
 
198
- listeners[hole] = listener;
188
+ listeners[hole] = entry;
199
189
 
200
190
  while (listeners.length && listeners[listeners.length - 1] === null) {
201
191
  listeners.pop();
@@ -264,7 +254,7 @@ class ReactiveArray<T> extends Array<T> {
264
254
 
265
255
  super.sort(fn);
266
256
 
267
- let buckets = new Map<any, number[]>(),
257
+ let buckets = new Map<T, number[]>(),
268
258
  order = new Array(n);
269
259
 
270
260
  for (let i = 0; i < n; i++) {
@@ -322,6 +312,19 @@ class ReactiveArray<T> extends Array<T> {
322
312
 
323
313
  return length;
324
314
  }
315
+
316
+
317
+ get $length() {
318
+ return read(this._length);
319
+ }
320
+
321
+ set $length(value: number) {
322
+ if (value > this.length) {
323
+ throw Error(`@esportsplus/reactivity: cannot set length to a value larger than the current length, use splice instead.`);
324
+ }
325
+
326
+ this.splice(value, this.length);
327
+ }
325
328
  }
326
329
 
327
330
  Object.defineProperty(ReactiveArray.prototype, REACTIVE_ARRAY, { value: true });
@@ -1,14 +1,14 @@
1
1
  import { onCleanup, root } from '@esportsplus/reactivity';
2
2
  import { isArray, isObject } from '@esportsplus/utilities';
3
- import { Reactive } from '~/types';
3
+ import { PACKAGE_NAME } from '~/constants';
4
+ import type { Reactive } from '~/types';
4
5
  import { ReactiveArray } from './array';
5
6
  import { ReactiveObject } from './object';
6
- import { PACKAGE_NAME } from '~/constants';
7
7
 
8
8
 
9
9
  type Guard<T> =
10
10
  T extends Record<PropertyKey, unknown>
11
- ? T extends { dispose: any }
11
+ ? T extends { dispose: unknown }
12
12
  ? { never: '[ dispose ] is a reserved key' }
13
13
  : T
14
14
  : never;
@@ -23,10 +23,10 @@ function reactive<T>(input: T): Reactive<T> {
23
23
  let response: Reactive<T> | undefined;
24
24
 
25
25
  if (isObject(input)) {
26
- response = new ReactiveObject(input) as any as Reactive<T>;
26
+ response = new ReactiveObject(input) as unknown as Reactive<T>;
27
27
  }
28
28
  else if (isArray(input)) {
29
- response = new ReactiveArray(...input) as any as Reactive<T>;
29
+ response = new ReactiveArray(...input) as unknown as Reactive<T>;
30
30
  }
31
31
 
32
32
  if (response) {
@@ -41,7 +41,7 @@ function reactive<T>(input: T): Reactive<T> {
41
41
  });
42
42
 
43
43
  if (dispose) {
44
- onCleanup(() => (value as any as { dispose: VoidFunction }).dispose());
44
+ onCleanup(() => (value as unknown as { dispose: VoidFunction }).dispose());
45
45
  }
46
46
 
47
47
  return value;
@@ -1,7 +1,7 @@
1
- import { defineProperty, isArray, isPromise } from '@esportsplus/utilities';
2
- import { computed, dispose, effect, read, root, signal, write } from '~/system';
3
- import { Computed, Signal } from '~/types';
1
+ import { defineProperty, isArray } from '@esportsplus/utilities';
4
2
  import { COMPUTED, REACTIVE_ARRAY, REACTIVE_OBJECT, SIGNAL } from '~/constants';
3
+ import { computed, dispose, read, root, signal, write } from '~/system';
4
+ import { Computed } from '~/types';
5
5
  import { ReactiveArray } from './array';
6
6
 
7
7
 
@@ -32,10 +32,7 @@ class ReactiveObject<T extends Record<PropertyKey, unknown>> {
32
32
  continue;
33
33
  }
34
34
 
35
- if (value == null || type !== 'object') {
36
- // Skip isArray when possible
37
- }
38
- else if (isArray(value)) {
35
+ if (value != null && type === 'object' && isArray(value)) {
39
36
  defineProperty(this, key, {
40
37
  enumerable: true,
41
38
  value: this[REACTIVE_ARRAY](value)
@@ -59,44 +56,22 @@ class ReactiveObject<T extends Record<PropertyKey, unknown>> {
59
56
  }
60
57
 
61
58
 
62
- protected [REACTIVE_ARRAY]<U>(value: U[]): ReactiveArray<U> {
63
- let node = new ReactiveArray(...value);
64
-
65
- (this.disposers ??= []).push( () => node.dispose() );
66
-
67
- return node;
68
- }
69
-
70
59
  protected [COMPUTED]<T extends Computed<ReturnType<T>>['fn']>(value: T) {
71
60
  return root(() => {
72
- let node: Computed<ReturnType<T>> | Signal<ReturnType<T> | undefined> = computed(value);
73
-
74
- if (isPromise(node.value)) {
75
- let factory = node as Computed<ReturnType<T>>,
76
- out = signal<ReturnType<T> | undefined>(undefined),
77
- v = 0;
78
-
79
- (this.disposers ??= []).push(
80
- effect(() => {
81
- let id = ++v;
61
+ let node = computed(value);
82
62
 
83
- (read(factory) as Promise<ReturnType<T>>).then((resolved) => {
84
- if (id !== v) {
85
- return;
86
- }
63
+ (this.disposers ??= []).push(() => dispose(node));
87
64
 
88
- write(out as Signal<typeof resolved>, resolved);
89
- });
90
- })
91
- );
65
+ return node;
66
+ });
67
+ }
92
68
 
93
- return out;
94
- }
69
+ protected [REACTIVE_ARRAY]<U>(value: U[]): ReactiveArray<U> {
70
+ let node = new ReactiveArray(...value);
95
71
 
96
- (this.disposers ??= []).push(() => dispose(node as Computed<ReturnType<T>>));
72
+ (this.disposers ??= []).push( () => node.dispose() );
97
73
 
98
- return node;
99
- });
74
+ return node;
100
75
  }
101
76
 
102
77
  protected [SIGNAL]<T>(value: T) {
@@ -121,9 +96,9 @@ class ReactiveObject<T extends Record<PropertyKey, unknown>> {
121
96
  Object.defineProperty(ReactiveObject.prototype, REACTIVE_OBJECT, { value: true });
122
97
 
123
98
 
124
- const isReactiveObject = (value: any): value is ReactiveObject<any> => {
125
- return typeof value === 'object' && value !== null && value[REACTIVE_OBJECT] === true;
99
+ const isReactiveObject = (value: unknown): value is ReactiveObject<Record<PropertyKey, unknown>> => {
100
+ return typeof value === 'object' && value !== null && (value as Record<PropertyKey, unknown>)[REACTIVE_OBJECT] === true;
126
101
  };
127
102
 
128
103
 
129
- export { isReactiveObject, ReactiveObject };
104
+ export { isReactiveObject, ReactiveObject };