@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.
- package/.claude/CHANGELOG.md +43 -0
- package/README.md +13 -6
- package/bench/cellx.bench.ts +61 -0
- package/bench/dynamic.bench.ts +103 -0
- package/bench/kairo.bench.ts +368 -0
- package/bench/lib/reactive-adapter.ts +51 -0
- package/bench/molbench.bench.ts +75 -0
- package/{tests/bench/system.ts → bench/system.bench.ts} +3 -3
- package/build/compiler/array.d.ts +1 -1
- package/build/compiler/constants.d.ts +7 -6
- package/build/compiler/constants.js +6 -7
- package/build/compiler/object.d.ts +1 -1
- package/build/compiler/object.js +1 -2
- package/build/compiler/primitives.d.ts +1 -1
- package/build/constants.d.ts +3 -1
- package/build/constants.js +3 -1
- package/build/reactive/array.d.ts +8 -5
- package/build/reactive/array.js +14 -14
- package/build/reactive/index.d.ts +2 -2
- package/build/reactive/index.js +1 -1
- package/build/reactive/object.d.ts +4 -4
- package/build/reactive/object.js +8 -23
- package/build/system.d.ts +15 -6
- package/build/system.js +415 -69
- package/build/types.d.ts +16 -4
- package/package.json +5 -2
- package/pnpm-workspace.yaml +2 -0
- package/src/compiler/array.ts +1 -1
- package/src/compiler/constants.ts +8 -6
- package/src/compiler/index.ts +2 -1
- package/src/compiler/object.ts +2 -2
- package/src/compiler/plugins/vite.ts +1 -0
- package/src/compiler/primitives.ts +1 -1
- package/src/constants.ts +6 -2
- package/src/reactive/array.ts +26 -23
- package/src/reactive/index.ts +6 -6
- package/src/reactive/object.ts +16 -41
- package/src/system.ts +612 -80
- package/src/types.ts +29 -9
- package/test/async-computed.test.ts +323 -0
- package/test/async-errors.test.ts +146 -0
- package/test/async-hardening.test.ts +73 -0
- package/test/async-iterable.test.ts +221 -0
- package/test/async-nested.test.ts +19 -0
- package/test/deep-graphs.test.ts +215 -0
- package/{tests/effects.ts → test/effects.test.ts} +60 -0
- package/test/equals.test.ts +142 -0
- package/test/errors.test.ts +201 -0
- package/test/flush.test.ts +185 -0
- package/test/glitch-freedom.test.ts +115 -0
- package/test/invalidate.test.ts +147 -0
- package/test/lib/wait-for.ts +28 -0
- package/test/pending-writes.test.ts +139 -0
- package/{tests/reactive.ts → test/reactive/reactive.test.ts} +20 -19
- package/test/read-dedup.test.ts +120 -0
- package/test/signal-selector.test.ts +187 -0
- package/{tests/system.ts → test/system.test.ts} +214 -23
- package/test/tsconfig.json +16 -0
- package/test/untrack.test.ts +146 -0
- package/vitest.config.ts +2 -2
- package/tests/async-computed.ts +0 -239
- /package/{tests/bench/array.ts → bench/reactive/array.bench.ts} +0 -0
- /package/{tests/bench/reactive-object.ts → bench/reactive/reactive-object.bench.ts} +0 -0
- /package/{tests → bench}/tsconfig.json +0 -0
- /package/{tests/compiler.ts → test/compiler/compiler.test.ts} +0 -0
- /package/{tests/primitives.ts → test/primitives.test.ts} +0 -0
- /package/{tests/array.ts → test/reactive/array.test.ts} +0 -0
- /package/{tests/nested.ts → test/reactive/nested.test.ts} +0 -0
- /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.
|
|
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
|
-
"
|
|
45
|
+
"test": "vitest run",
|
|
43
46
|
"-": "-"
|
|
44
47
|
}
|
|
45
48
|
}
|
package/src/compiler/array.ts
CHANGED
|
@@ -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
|
|
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 };
|
package/src/compiler/index.ts
CHANGED
|
@@ -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';
|
package/src/compiler/object.ts
CHANGED
|
@@ -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,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
|
};
|
package/src/reactive/array.ts
CHANGED
|
@@ -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
|
|
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
|
|
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
|
|
168
|
+
let entry = listener as AnyListener<T>,
|
|
169
|
+
listeners = this.listeners[event];
|
|
180
170
|
|
|
181
171
|
if (listeners === undefined) {
|
|
182
|
-
this.listeners[event] = [
|
|
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 ===
|
|
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] =
|
|
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<
|
|
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 });
|
package/src/reactive/index.ts
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
import { onCleanup, root } from '@esportsplus/reactivity';
|
|
2
2
|
import { isArray, isObject } from '@esportsplus/utilities';
|
|
3
|
-
import {
|
|
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:
|
|
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
|
|
26
|
+
response = new ReactiveObject(input) as unknown as Reactive<T>;
|
|
27
27
|
}
|
|
28
28
|
else if (isArray(input)) {
|
|
29
|
-
response = new ReactiveArray(...input) as
|
|
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
|
|
44
|
+
onCleanup(() => (value as unknown as { dispose: VoidFunction }).dispose());
|
|
45
45
|
}
|
|
46
46
|
|
|
47
47
|
return value;
|
package/src/reactive/object.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import { defineProperty, isArray
|
|
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
|
|
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
|
|
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
|
-
|
|
84
|
-
if (id !== v) {
|
|
85
|
-
return;
|
|
86
|
-
}
|
|
63
|
+
(this.disposers ??= []).push(() => dispose(node));
|
|
87
64
|
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
);
|
|
65
|
+
return node;
|
|
66
|
+
});
|
|
67
|
+
}
|
|
92
68
|
|
|
93
|
-
|
|
94
|
-
|
|
69
|
+
protected [REACTIVE_ARRAY]<U>(value: U[]): ReactiveArray<U> {
|
|
70
|
+
let node = new ReactiveArray(...value);
|
|
95
71
|
|
|
96
|
-
|
|
72
|
+
(this.disposers ??= []).push( () => node.dispose() );
|
|
97
73
|
|
|
98
|
-
|
|
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:
|
|
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 };
|