@esportsplus/reactivity 0.39.2 → 0.41.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/README.md +12 -6
- package/bench/compiler.bench.ts +115 -0
- package/bench/kairo.bench.ts +1 -1
- package/bench/reactive/array.bench.ts +1 -0
- package/bench/reactive/reactive-object.bench.ts +3 -2
- package/build/compiler/array.js +28 -25
- package/build/compiler/bindings.d.ts +3 -1
- package/build/compiler/bindings.js +32 -9
- package/build/compiler/index.d.ts +3 -2
- package/build/compiler/index.js +21 -13
- package/build/compiler/object.js +12 -13
- package/build/compiler/plugins/vite.d.ts +13 -4
- package/build/compiler/primitives.js +9 -9
- package/build/constants.d.ts +2 -1
- package/build/constants.js +2 -1
- package/build/index.d.ts +1 -0
- package/build/reactive/array.d.ts +40 -1
- package/build/reactive/array.js +82 -16
- package/build/reactive/index.d.ts +1 -1
- package/build/reactive/object.d.ts +3 -3
- package/build/reactive/object.js +1 -1
- package/build/system.d.ts +3 -2
- package/build/system.js +10 -5
- package/oxlintrc.json +3 -0
- package/package.json +15 -7
- package/pnpm-workspace.yaml +4 -7
- package/src/compiler/array.ts +30 -27
- package/src/compiler/bindings.ts +49 -10
- package/src/compiler/index.ts +37 -20
- package/src/compiler/object.ts +14 -16
- package/src/compiler/primitives.ts +13 -10
- package/src/constants.ts +4 -1
- package/src/index.ts +1 -0
- package/src/reactive/array.ts +133 -22
- package/src/reactive/index.ts +3 -3
- package/src/reactive/object.ts +4 -4
- package/src/system.ts +20 -5
- package/test/compiler/__snapshots__/output.test.ts.snap +157 -0
- package/test/compiler/compiler.test.ts +51 -9
- package/test/compiler/composition.test.ts +110 -0
- package/test/compiler/coverage.test.ts +22 -22
- package/test/compiler/origins.test.ts +156 -0
- package/test/compiler/output.test.ts +116 -0
- package/test/ownership.test.ts +100 -1
- package/test/reactive/array.test.ts +189 -8
- package/test/reactive/reactive.test.ts +4 -4
- package/test/system.test.ts +3 -4
- package/test/untrack.test.ts +1 -1
- package/tsconfig.build.json +3 -0
- package/tsconfig.json +2 -2
- package/vitest.config.ts +2 -2
- package/bench/tsconfig.json +0 -16
- package/test/tsconfig.json +0 -16
package/build/constants.d.ts
CHANGED
|
@@ -15,5 +15,6 @@ declare const STATE_IN_HEAP: number;
|
|
|
15
15
|
declare const STATE_COMPUTED: number;
|
|
16
16
|
declare const STATE_ERROR: number;
|
|
17
17
|
declare const STATE_EFFECT: number;
|
|
18
|
+
declare const STATE_DISPOSED: number;
|
|
18
19
|
declare const STATE_NOTIFY_MASK: number;
|
|
19
|
-
export { COMPUTED, PACKAGE_NAME, REACTIVE_ARRAY, REACTIVE_OBJECT, SIGNAL, STABILIZER_DEFERRED, STABILIZER_IDLE, STABILIZER_RESCHEDULE, STABILIZER_RUNNING, STABILIZER_SCHEDULED, STATE_CHECK, STATE_COMPUTED, STATE_DIRTY, STATE_EFFECT, STATE_ERROR, STATE_IN_HEAP, STATE_NOTIFY_MASK, STATE_RECOMPUTING };
|
|
20
|
+
export { COMPUTED, PACKAGE_NAME, REACTIVE_ARRAY, REACTIVE_OBJECT, SIGNAL, STABILIZER_DEFERRED, STABILIZER_IDLE, STABILIZER_RESCHEDULE, STABILIZER_RUNNING, STABILIZER_SCHEDULED, STATE_CHECK, STATE_COMPUTED, STATE_DIRTY, STATE_DISPOSED, STATE_EFFECT, STATE_ERROR, STATE_IN_HEAP, STATE_NOTIFY_MASK, STATE_RECOMPUTING };
|
package/build/constants.js
CHANGED
|
@@ -15,5 +15,6 @@ const STATE_IN_HEAP = 1 << 3;
|
|
|
15
15
|
const STATE_COMPUTED = 1 << 4;
|
|
16
16
|
const STATE_ERROR = 1 << 5;
|
|
17
17
|
const STATE_EFFECT = 1 << 6;
|
|
18
|
+
const STATE_DISPOSED = 1 << 7;
|
|
18
19
|
const STATE_NOTIFY_MASK = (STATE_CHECK | STATE_DIRTY);
|
|
19
|
-
export { COMPUTED, PACKAGE_NAME, REACTIVE_ARRAY, REACTIVE_OBJECT, SIGNAL, STABILIZER_DEFERRED, STABILIZER_IDLE, STABILIZER_RESCHEDULE, STABILIZER_RUNNING, STABILIZER_SCHEDULED, STATE_CHECK, STATE_COMPUTED, STATE_DIRTY, STATE_EFFECT, STATE_ERROR, STATE_IN_HEAP, STATE_NOTIFY_MASK, STATE_RECOMPUTING };
|
|
20
|
+
export { COMPUTED, PACKAGE_NAME, REACTIVE_ARRAY, REACTIVE_OBJECT, SIGNAL, STABILIZER_DEFERRED, STABILIZER_IDLE, STABILIZER_RESCHEDULE, STABILIZER_RUNNING, STABILIZER_SCHEDULED, STATE_CHECK, STATE_COMPUTED, STATE_DIRTY, STATE_DISPOSED, STATE_EFFECT, STATE_ERROR, STATE_IN_HEAP, STATE_NOTIFY_MASK, STATE_RECOMPUTING };
|
package/build/index.d.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
export { isPromise } from '@esportsplus/utilities';
|
|
2
2
|
export { COMPUTED, REACTIVE_ARRAY, REACTIVE_OBJECT, SIGNAL } from './constants.js';
|
|
3
3
|
export { default as reactive, ReactiveArray, ReactiveObject } from './reactive/index.js';
|
|
4
|
+
export type { ArrayOp } from './reactive/array.js';
|
|
4
5
|
export * from './system.js';
|
|
5
6
|
export * from './types.js';
|
|
@@ -1,4 +1,37 @@
|
|
|
1
|
+
type ArrayOp<T> = {
|
|
2
|
+
items: T[];
|
|
3
|
+
op: 'concat';
|
|
4
|
+
} | {
|
|
5
|
+
deleteCount: number;
|
|
6
|
+
items: T[];
|
|
7
|
+
op: 'splice';
|
|
8
|
+
start: number;
|
|
9
|
+
} | {
|
|
10
|
+
items: T[];
|
|
11
|
+
op: 'push';
|
|
12
|
+
} | {
|
|
13
|
+
index: number;
|
|
14
|
+
item: T;
|
|
15
|
+
op: 'set';
|
|
16
|
+
} | {
|
|
17
|
+
items: T[];
|
|
18
|
+
op: 'unshift';
|
|
19
|
+
} | {
|
|
20
|
+
op: 'clear';
|
|
21
|
+
} | {
|
|
22
|
+
item: T;
|
|
23
|
+
op: 'pop';
|
|
24
|
+
} | {
|
|
25
|
+
op: 'reverse';
|
|
26
|
+
} | {
|
|
27
|
+
item: T;
|
|
28
|
+
op: 'shift';
|
|
29
|
+
} | {
|
|
30
|
+
op: 'sort';
|
|
31
|
+
order: number[];
|
|
32
|
+
};
|
|
1
33
|
type Events<T> = {
|
|
34
|
+
batch: ArrayOp<T>[];
|
|
2
35
|
clear: undefined;
|
|
3
36
|
concat: {
|
|
4
37
|
items: T[];
|
|
@@ -39,12 +72,18 @@ type Listeners<T> = {
|
|
|
39
72
|
};
|
|
40
73
|
declare class ReactiveArray<T> extends Array<T> {
|
|
41
74
|
private _length;
|
|
75
|
+
private batched;
|
|
76
|
+
private delivering;
|
|
42
77
|
listeners: Listeners<T>;
|
|
43
78
|
constructor(items?: readonly T[]);
|
|
44
79
|
static get [Symbol.species](): ArrayConstructor;
|
|
45
80
|
get $length(): number;
|
|
46
81
|
set $length(value: number);
|
|
47
82
|
$set(i: number, value: T): void;
|
|
83
|
+
private batch;
|
|
84
|
+
private prune;
|
|
85
|
+
private record;
|
|
86
|
+
private unbatch;
|
|
48
87
|
clear(): void;
|
|
49
88
|
concat(...items: ConcatArray<T>[]): ReactiveArray<T>;
|
|
50
89
|
concat(...items: (T | ConcatArray<T>)[]): ReactiveArray<T>;
|
|
@@ -52,7 +91,6 @@ declare class ReactiveArray<T> extends Array<T> {
|
|
|
52
91
|
dispose(): void;
|
|
53
92
|
on<K extends keyof Events<T>>(event: K, listener: Listener<Events<T>[K]>): VoidFunction;
|
|
54
93
|
once<K extends keyof Events<T>>(event: K, listener: Listener<Events<T>[K]>): VoidFunction;
|
|
55
|
-
private prune;
|
|
56
94
|
pop(): T | undefined;
|
|
57
95
|
push(...items: T[]): number;
|
|
58
96
|
reverse(): this;
|
|
@@ -62,3 +100,4 @@ declare class ReactiveArray<T> extends Array<T> {
|
|
|
62
100
|
unshift(...items: T[]): number;
|
|
63
101
|
}
|
|
64
102
|
export { ReactiveArray };
|
|
103
|
+
export type { ArrayOp };
|
package/build/reactive/array.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { isArray } from '@esportsplus/utilities';
|
|
2
2
|
import { PACKAGE_NAME, REACTIVE_ARRAY } from '../constants.js';
|
|
3
|
-
import { read, signal, write } from '../system.js';
|
|
3
|
+
import { effect, read, root, signal, write } from '../system.js';
|
|
4
4
|
import { isReactiveObject } from './object.js';
|
|
5
5
|
function dispose(value) {
|
|
6
6
|
if (isReactiveObject(value)) {
|
|
@@ -9,6 +9,8 @@ function dispose(value) {
|
|
|
9
9
|
}
|
|
10
10
|
class ReactiveArray extends Array {
|
|
11
11
|
_length;
|
|
12
|
+
batched = null;
|
|
13
|
+
delivering = false;
|
|
12
14
|
listeners = {};
|
|
13
15
|
constructor(items) {
|
|
14
16
|
super();
|
|
@@ -41,12 +43,71 @@ class ReactiveArray extends Array {
|
|
|
41
43
|
write(this._length, i + 1);
|
|
42
44
|
}
|
|
43
45
|
this.dispatch('set', { index: i, item: value });
|
|
46
|
+
this.record({ index: i, item: value, op: 'set' });
|
|
44
47
|
dispose(prev);
|
|
45
48
|
}
|
|
49
|
+
batch() {
|
|
50
|
+
let tick = signal(0);
|
|
51
|
+
this.batched = {
|
|
52
|
+
ops: null,
|
|
53
|
+
stop: root(() => effect(() => {
|
|
54
|
+
read(tick);
|
|
55
|
+
let batched = this.batched, ops = batched?.ops;
|
|
56
|
+
if (!ops) {
|
|
57
|
+
return;
|
|
58
|
+
}
|
|
59
|
+
batched.ops = null;
|
|
60
|
+
this.delivering = true;
|
|
61
|
+
try {
|
|
62
|
+
root(() => this.dispatch('batch', ops));
|
|
63
|
+
}
|
|
64
|
+
finally {
|
|
65
|
+
this.delivering = false;
|
|
66
|
+
}
|
|
67
|
+
if (this.listeners.batch === undefined) {
|
|
68
|
+
queueMicrotask(() => {
|
|
69
|
+
if (this.listeners.batch === undefined) {
|
|
70
|
+
this.unbatch();
|
|
71
|
+
}
|
|
72
|
+
});
|
|
73
|
+
}
|
|
74
|
+
})),
|
|
75
|
+
tick
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
prune(event, listeners) {
|
|
79
|
+
while (listeners.length && listeners[listeners.length - 1] === null) {
|
|
80
|
+
listeners.pop();
|
|
81
|
+
}
|
|
82
|
+
if (listeners.length === 0 && this.listeners[event] === listeners) {
|
|
83
|
+
delete this.listeners[event];
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
record(op) {
|
|
87
|
+
let batched = this.batched;
|
|
88
|
+
if (batched === null) {
|
|
89
|
+
return;
|
|
90
|
+
}
|
|
91
|
+
if (op.op === 'clear' || batched.ops === null) {
|
|
92
|
+
batched.ops = [op];
|
|
93
|
+
}
|
|
94
|
+
else {
|
|
95
|
+
batched.ops.push(op);
|
|
96
|
+
}
|
|
97
|
+
write(batched.tick, batched.tick.value + 1);
|
|
98
|
+
}
|
|
99
|
+
unbatch() {
|
|
100
|
+
let batched = this.batched;
|
|
101
|
+
if (batched !== null) {
|
|
102
|
+
this.batched = null;
|
|
103
|
+
batched.stop();
|
|
104
|
+
}
|
|
105
|
+
}
|
|
46
106
|
clear() {
|
|
47
107
|
this.dispose();
|
|
48
108
|
write(this._length, 0);
|
|
49
109
|
this.dispatch('clear');
|
|
110
|
+
this.record({ op: 'clear' });
|
|
50
111
|
}
|
|
51
112
|
concat(...items) {
|
|
52
113
|
let added = [];
|
|
@@ -66,6 +127,7 @@ class ReactiveArray extends Array {
|
|
|
66
127
|
if (added.length) {
|
|
67
128
|
write(this._length, this.length);
|
|
68
129
|
this.dispatch('concat', { items: added });
|
|
130
|
+
this.record({ items: added, op: 'concat' });
|
|
69
131
|
}
|
|
70
132
|
return this;
|
|
71
133
|
}
|
|
@@ -96,6 +158,9 @@ class ReactiveArray extends Array {
|
|
|
96
158
|
if (dirty) {
|
|
97
159
|
this.prune(event, listeners);
|
|
98
160
|
}
|
|
161
|
+
if (event === 'batch' && !this.delivering && this.listeners.batch === undefined) {
|
|
162
|
+
this.unbatch();
|
|
163
|
+
}
|
|
99
164
|
if (errors !== null) {
|
|
100
165
|
let error = errors.length === 1
|
|
101
166
|
? errors[0]
|
|
@@ -113,6 +178,9 @@ class ReactiveArray extends Array {
|
|
|
113
178
|
}
|
|
114
179
|
on(event, listener) {
|
|
115
180
|
let entry = listener, listeners = this.listeners[event];
|
|
181
|
+
if (event === 'batch' && this.batched === null) {
|
|
182
|
+
this.batch();
|
|
183
|
+
}
|
|
116
184
|
if (listeners === undefined) {
|
|
117
185
|
this.listeners[event] = [entry];
|
|
118
186
|
}
|
|
@@ -143,20 +211,15 @@ class ReactiveArray extends Array {
|
|
|
143
211
|
current[i] = null;
|
|
144
212
|
this.prune(event, current);
|
|
145
213
|
}
|
|
214
|
+
if (event === 'batch' && !this.delivering && this.listeners.batch === undefined) {
|
|
215
|
+
this.unbatch();
|
|
216
|
+
}
|
|
146
217
|
};
|
|
147
218
|
}
|
|
148
219
|
once(event, listener) {
|
|
149
220
|
listener.once = true;
|
|
150
221
|
return this.on(event, listener);
|
|
151
222
|
}
|
|
152
|
-
prune(event, listeners) {
|
|
153
|
-
while (listeners.length && listeners[listeners.length - 1] === null) {
|
|
154
|
-
listeners.pop();
|
|
155
|
-
}
|
|
156
|
-
if (listeners.length === 0 && this.listeners[event] === listeners) {
|
|
157
|
-
delete this.listeners[event];
|
|
158
|
-
}
|
|
159
|
-
}
|
|
160
223
|
pop() {
|
|
161
224
|
if (this.length === 0) {
|
|
162
225
|
return undefined;
|
|
@@ -165,6 +228,7 @@ class ReactiveArray extends Array {
|
|
|
165
228
|
dispose(item);
|
|
166
229
|
write(this._length, this.length);
|
|
167
230
|
this.dispatch('pop', { item });
|
|
231
|
+
this.record({ item, op: 'pop' });
|
|
168
232
|
return item;
|
|
169
233
|
}
|
|
170
234
|
push(...items) {
|
|
@@ -174,11 +238,13 @@ class ReactiveArray extends Array {
|
|
|
174
238
|
let length = super.push(...items);
|
|
175
239
|
write(this._length, length);
|
|
176
240
|
this.dispatch('push', { items });
|
|
241
|
+
this.record({ items, op: 'push' });
|
|
177
242
|
return length;
|
|
178
243
|
}
|
|
179
244
|
reverse() {
|
|
180
245
|
super.reverse();
|
|
181
246
|
this.dispatch('reverse');
|
|
247
|
+
this.record({ op: 'reverse' });
|
|
182
248
|
return this;
|
|
183
249
|
}
|
|
184
250
|
shift() {
|
|
@@ -189,10 +255,11 @@ class ReactiveArray extends Array {
|
|
|
189
255
|
dispose(item);
|
|
190
256
|
write(this._length, this.length);
|
|
191
257
|
this.dispatch('shift', { item });
|
|
258
|
+
this.record({ item, op: 'shift' });
|
|
192
259
|
return item;
|
|
193
260
|
}
|
|
194
261
|
sort(fn) {
|
|
195
|
-
if (this.listeners.sort === undefined) {
|
|
262
|
+
if (this.listeners.sort === undefined && this.batched === null) {
|
|
196
263
|
super.sort(fn);
|
|
197
264
|
}
|
|
198
265
|
else {
|
|
@@ -216,6 +283,7 @@ class ReactiveArray extends Array {
|
|
|
216
283
|
order[i] = bucket.indices[bucket.next++];
|
|
217
284
|
}
|
|
218
285
|
this.dispatch('sort', { order });
|
|
286
|
+
this.record({ op: 'sort', order });
|
|
219
287
|
}
|
|
220
288
|
return this;
|
|
221
289
|
}
|
|
@@ -226,12 +294,9 @@ class ReactiveArray extends Array {
|
|
|
226
294
|
for (let i = 0, n = removed.length; i < n; i++) {
|
|
227
295
|
dispose(removed[i]);
|
|
228
296
|
}
|
|
229
|
-
let index = Math.trunc(start) || 0;
|
|
230
|
-
this.dispatch('splice', {
|
|
231
|
-
|
|
232
|
-
items,
|
|
233
|
-
start: index < 0 ? Math.max(length + index, 0) : Math.min(index, length)
|
|
234
|
-
});
|
|
297
|
+
let index = Math.trunc(start) || 0, resolved = index < 0 ? Math.max(length + index, 0) : Math.min(index, length);
|
|
298
|
+
this.dispatch('splice', { deleteCount: removed.length, items, start: resolved });
|
|
299
|
+
this.record({ deleteCount: removed.length, items, op: 'splice', start: resolved });
|
|
235
300
|
}
|
|
236
301
|
return removed;
|
|
237
302
|
}
|
|
@@ -242,6 +307,7 @@ class ReactiveArray extends Array {
|
|
|
242
307
|
let length = super.unshift(...items);
|
|
243
308
|
write(this._length, length);
|
|
244
309
|
this.dispatch('unshift', { items });
|
|
310
|
+
this.record({ items, op: 'unshift' });
|
|
245
311
|
return length;
|
|
246
312
|
}
|
|
247
313
|
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import type { Reactive } from '../types.js';
|
|
2
1
|
import { ReactiveArray } from './array.js';
|
|
3
2
|
import { ReactiveObject } from './object.js';
|
|
3
|
+
import type { Reactive } from '../types.js';
|
|
4
4
|
type Guard<T> = T extends Record<PropertyKey, unknown> ? T extends {
|
|
5
5
|
dispose: unknown;
|
|
6
6
|
} ? {
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
import { COMPUTED, REACTIVE_ARRAY, SIGNAL } from '../constants.js';
|
|
2
|
-
import { Computed } from '../types.js';
|
|
3
2
|
import { ReactiveArray } from './array.js';
|
|
3
|
+
import type { Computed } from '../types.js';
|
|
4
4
|
declare class ReactiveObject<T extends Record<PropertyKey, unknown>> {
|
|
5
5
|
protected disposers: VoidFunction[] | null;
|
|
6
6
|
constructor(data: T | null);
|
|
7
|
-
protected [COMPUTED]<T extends Computed<ReturnType<T>>['fn']>(value: T): import("../
|
|
7
|
+
protected [COMPUTED]<T extends Computed<ReturnType<T>>['fn']>(value: T): import("../index.js").ComputedResult<ReturnType<T>>;
|
|
8
8
|
protected [REACTIVE_ARRAY]<U>(value: U[]): ReactiveArray<U>;
|
|
9
|
-
protected [SIGNAL]<T>(value: T): import("../
|
|
9
|
+
protected [SIGNAL]<T>(value: T): import("../index.js").Signal<T>;
|
|
10
10
|
dispose(): void;
|
|
11
11
|
}
|
|
12
12
|
declare const isReactiveObject: (value: unknown) => value is ReactiveObject<Record<PropertyKey, unknown>>;
|
package/build/reactive/object.js
CHANGED
package/build/system.d.ts
CHANGED
|
@@ -13,12 +13,13 @@ declare const isSignal: (value: unknown) => value is Signal<unknown>;
|
|
|
13
13
|
declare const onCleanup: (fn: VoidFunction) => typeof fn;
|
|
14
14
|
declare const peek: <T>(node: Signal<T> | Computed<T>) => T;
|
|
15
15
|
declare const read: <T>(node: Signal<T> | Computed<T>) => T;
|
|
16
|
-
declare const
|
|
16
|
+
declare const owner: () => Computed<unknown> | null;
|
|
17
|
+
declare const root: <T>(fn: ((dispose: VoidFunction) => T) | (() => T), parent?: Computed<unknown> | null) => T;
|
|
17
18
|
declare function signal<T>(value: T, equals?: ((a: T, b: T) => boolean) | null): Signal<T>;
|
|
18
19
|
declare namespace signal {
|
|
19
20
|
var selector: <T>(node: Signal<T>, key: T) => boolean;
|
|
20
21
|
}
|
|
21
22
|
declare const untrack: <T>(fn: () => T) => T;
|
|
22
23
|
declare const write: <T>(signal: Signal<T>, value: T) => void;
|
|
23
|
-
export { batch, computed, dispose, effect, flush, hasOwner, isComputed, isSignal, onCleanup, peek, read, root, signal, untrack, write };
|
|
24
|
+
export { batch, computed, dispose, effect, flush, hasOwner, isComputed, isSignal, onCleanup, owner, peek, read, root, signal, untrack, write };
|
|
24
25
|
export type { Computed, Signal };
|
package/build/system.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { PACKAGE_NAME, SIGNAL, STABILIZER_DEFERRED, STABILIZER_IDLE, STABILIZER_RESCHEDULE, STABILIZER_RUNNING, STABILIZER_SCHEDULED, STATE_CHECK, STATE_COMPUTED, STATE_DIRTY, STATE_EFFECT, STATE_ERROR, STATE_IN_HEAP, STATE_NOTIFY_MASK, STATE_RECOMPUTING } from './constants.js';
|
|
1
|
+
import { PACKAGE_NAME, SIGNAL, STABILIZER_DEFERRED, STABILIZER_IDLE, STABILIZER_RESCHEDULE, STABILIZER_RUNNING, STABILIZER_SCHEDULED, STATE_CHECK, STATE_COMPUTED, STATE_DIRTY, STATE_DISPOSED, STATE_EFFECT, STATE_ERROR, STATE_IN_HEAP, STATE_NOTIFY_MASK, STATE_RECOMPUTING } from './constants.js';
|
|
2
2
|
import { isObject, isPromise } from '@esportsplus/utilities';
|
|
3
3
|
let asyncFactory = new WeakMap(), depth = 0, disposeHead = null, draining = false, heap = new Array(64), heap_i = 0, heap_n = 0, linkPoolHead = null, microtask = queueMicrotask, notified = false, observer = null, pendingHead = null, pulled = null, puller = null, scope = null, stabilizer = STABILIZER_IDLE, version = 0, walkPoolHead = null, writes = 0;
|
|
4
4
|
function noop() {
|
|
@@ -623,6 +623,7 @@ const dispose = (computed) => {
|
|
|
623
623
|
try {
|
|
624
624
|
for (;;) {
|
|
625
625
|
deleteFromHeap(node);
|
|
626
|
+
node.state |= STATE_DISPOSED;
|
|
626
627
|
let dep = node.deps;
|
|
627
628
|
while (dep) {
|
|
628
629
|
dep = unlink(dep);
|
|
@@ -751,12 +752,16 @@ const read = (node) => {
|
|
|
751
752
|
}
|
|
752
753
|
return node.value;
|
|
753
754
|
};
|
|
754
|
-
const
|
|
755
|
+
const owner = () => observer || scope;
|
|
756
|
+
const root = (fn, parent) => {
|
|
757
|
+
if (parent && parent.state & STATE_DISPOSED) {
|
|
758
|
+
throw new Error(`${PACKAGE_NAME}: root() was given a disposed owner`);
|
|
759
|
+
}
|
|
755
760
|
let o = observer, s = scope, tracking = fn.length, value;
|
|
756
761
|
observer = null;
|
|
757
762
|
try {
|
|
758
763
|
if (tracking) {
|
|
759
|
-
let self = makeNode(noop), owner = o || s;
|
|
764
|
+
let self = makeNode(noop), owner = parent === undefined ? o || s : parent;
|
|
760
765
|
if (owner !== null) {
|
|
761
766
|
own(owner, self);
|
|
762
767
|
}
|
|
@@ -764,7 +769,7 @@ const root = (fn) => {
|
|
|
764
769
|
value = fn(() => dispose(self));
|
|
765
770
|
}
|
|
766
771
|
else {
|
|
767
|
-
scope = null;
|
|
772
|
+
scope = parent ?? null;
|
|
768
773
|
value = fn();
|
|
769
774
|
}
|
|
770
775
|
}
|
|
@@ -854,4 +859,4 @@ const write = (signal, value) => {
|
|
|
854
859
|
}
|
|
855
860
|
schedule();
|
|
856
861
|
};
|
|
857
|
-
export { batch, computed, dispose, effect, flush, hasOwner, isComputed, isSignal, onCleanup, peek, read, root, signal, untrack, write };
|
|
862
|
+
export { batch, computed, dispose, effect, flush, hasOwner, isComputed, isSignal, onCleanup, owner, peek, read, root, signal, untrack, write };
|
package/oxlintrc.json
ADDED
package/package.json
CHANGED
|
@@ -1,13 +1,18 @@
|
|
|
1
1
|
{
|
|
2
2
|
"author": "ICJR",
|
|
3
3
|
"dependencies": {
|
|
4
|
+
"@esportsplus/typescript": "^0.39.0",
|
|
4
5
|
"@esportsplus/utilities": "^0.28.6"
|
|
5
6
|
},
|
|
6
7
|
"devDependencies": {
|
|
7
|
-
"@
|
|
8
|
-
"
|
|
9
|
-
"
|
|
10
|
-
"
|
|
8
|
+
"@types/node": "^26.6.6",
|
|
9
|
+
"oxlint": "^1.90.0",
|
|
10
|
+
"oxlint-tsgolint": "^7.0.2005",
|
|
11
|
+
"vite": "^8.3.5",
|
|
12
|
+
"vitest": "^5.1.0"
|
|
13
|
+
},
|
|
14
|
+
"engines": {
|
|
15
|
+
"node": ">=26"
|
|
11
16
|
},
|
|
12
17
|
"exports": {
|
|
13
18
|
".": {
|
|
@@ -36,13 +41,16 @@
|
|
|
36
41
|
},
|
|
37
42
|
"type": "module",
|
|
38
43
|
"types": "build/index.d.ts",
|
|
39
|
-
"version": "0.
|
|
44
|
+
"version": "0.41.0",
|
|
40
45
|
"scripts": {
|
|
41
46
|
"agent:bench": "vitest bench --run",
|
|
42
|
-
"agent:test": "
|
|
47
|
+
"agent:test": "pnpm typecheck && vitest run",
|
|
43
48
|
"bench": "vitest bench --run",
|
|
44
|
-
"build": "tsc",
|
|
49
|
+
"build": "tsc -p tsconfig.build.json",
|
|
50
|
+
"lint": "oxlint -c oxlintrc.json --type-aware --deny-warnings",
|
|
45
51
|
"test": "vitest run",
|
|
52
|
+
"typecheck": "tsc --noEmit",
|
|
53
|
+
"verify": "pnpm build && pnpm typecheck && pnpm lint && pnpm test",
|
|
46
54
|
"-": "-"
|
|
47
55
|
}
|
|
48
56
|
}
|
package/pnpm-workspace.yaml
CHANGED
|
@@ -1,10 +1,7 @@
|
|
|
1
1
|
allowBuilds:
|
|
2
2
|
esbuild: true
|
|
3
3
|
minimumReleaseAgeExclude:
|
|
4
|
-
- '@esportsplus
|
|
5
|
-
- '@
|
|
6
|
-
-
|
|
7
|
-
-
|
|
8
|
-
- '@vitest/spy@5.0.0'
|
|
9
|
-
- vitest@5.0.0
|
|
10
|
-
- '@esportsplus/cli-passthrough@0.0.18'
|
|
4
|
+
- '@esportsplus/*'
|
|
5
|
+
- '@vitest/mocker@5.1.0'
|
|
6
|
+
- '@vitest/spy@5.1.0'
|
|
7
|
+
- vitest@5.1.0
|
package/src/compiler/array.ts
CHANGED
|
@@ -68,10 +68,10 @@ function declare(ctx: { isReactiveCall: IsReactiveCall; replacements: Replacemen
|
|
|
68
68
|
let typeParam = elementType ? `<${elementType}>` : '';
|
|
69
69
|
|
|
70
70
|
ctx.replacements.push({
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
71
|
+
generate: (_sourceFile, text) => expression.elements.length > 0
|
|
72
|
+
? ` new ${NAMESPACE}.ReactiveArray${typeParam}(${text(expression)})`
|
|
73
|
+
: ` new ${NAMESPACE}.ReactiveArray${typeParam}()`,
|
|
74
|
+
node
|
|
75
75
|
});
|
|
76
76
|
}
|
|
77
77
|
}
|
|
@@ -126,10 +126,10 @@ function length(access: ts.PropertyAccessExpression, replacements: ReplacementIn
|
|
|
126
126
|
let op = COMPOUND_OPERATORS.get(parent.operatorToken.kind);
|
|
127
127
|
|
|
128
128
|
replacements.push({
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
129
|
+
generate: (_sourceFile, text) => op === undefined
|
|
130
|
+
? `${text(expr)}.$length = ${text(parent.right)}`
|
|
131
|
+
: `${text(expr)}.$length = ${text(expr)}.length ${op} ${text(parent.right)}`,
|
|
132
|
+
node: parent
|
|
133
133
|
});
|
|
134
134
|
}
|
|
135
135
|
// arr.length++ / arr.length-- / ++arr.length / --arr.length
|
|
@@ -141,24 +141,22 @@ function length(access: ts.PropertyAccessExpression, replacements: ReplacementIn
|
|
|
141
141
|
let op = parent.operator === ts.SyntaxKind.PlusPlusToken ? '+' : '-';
|
|
142
142
|
|
|
143
143
|
replacements.push({
|
|
144
|
-
|
|
145
|
-
|
|
144
|
+
generate: (_sourceFile, text) => `${text(expr)}.$length = ${text(expr)}.length ${op} 1`,
|
|
145
|
+
node: parent
|
|
146
146
|
});
|
|
147
147
|
}
|
|
148
148
|
// Read-only: arr.length -> arr.$length
|
|
149
149
|
else {
|
|
150
150
|
replacements.push({
|
|
151
|
-
|
|
152
|
-
|
|
151
|
+
generate: (_sourceFile, text) => `${text(expr)}.$length`,
|
|
152
|
+
node: access
|
|
153
153
|
});
|
|
154
154
|
}
|
|
155
155
|
}
|
|
156
156
|
|
|
157
157
|
// An element write must notify, so it goes through $set; operands are evaluated once
|
|
158
158
|
function write(access: ts.ElementAccessExpression, ctx: ArrayTransformResult): void {
|
|
159
|
-
let parent = access.parent
|
|
160
|
-
array = (sf: ts.SourceFile) => access.expression.getText(sf),
|
|
161
|
-
index = (sf: ts.SourceFile) => access.argumentExpression.getText(sf);
|
|
159
|
+
let parent = access.parent!;
|
|
162
160
|
|
|
163
161
|
if (ts.isDeleteExpression(parent)) {
|
|
164
162
|
ctx.failures.push({ message: 'an element of a reactive array cannot be deleted; use splice()', node: parent });
|
|
@@ -170,10 +168,10 @@ function write(access: ts.ElementAccessExpression, ctx: ArrayTransformResult): v
|
|
|
170
168
|
right = parent.right;
|
|
171
169
|
|
|
172
170
|
ctx.replacements.push({
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
171
|
+
generate: (_sourceFile, text) => op === undefined
|
|
172
|
+
? `${text(access.expression)}.$set(${text(access.argumentExpression)}, ${text(right)})`
|
|
173
|
+
: `((_a, _k) => _a.$set(_k, _a[_k] ${op} (${text(right)})))(${text(access.expression)}, ${text(access.argumentExpression)})`,
|
|
174
|
+
node: parent
|
|
177
175
|
});
|
|
178
176
|
|
|
179
177
|
return;
|
|
@@ -183,10 +181,15 @@ function write(access: ts.ElementAccessExpression, ctx: ArrayTransformResult): v
|
|
|
183
181
|
op = unary.operator === ts.SyntaxKind.PlusPlusToken ? '+' : '-';
|
|
184
182
|
|
|
185
183
|
ctx.replacements.push({
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
184
|
+
generate: (_sourceFile, text) => {
|
|
185
|
+
let array = text(access.expression),
|
|
186
|
+
index = text(access.argumentExpression);
|
|
187
|
+
|
|
188
|
+
return ts.isPrefixUnaryExpression(unary)
|
|
189
|
+
? `((_a, _k) => (_a.$set(_k, _a[_k] ${op} 1), _a[_k]))(${array}, ${index})`
|
|
190
|
+
: `((_a, _k, _v) => (_a.$set(_k, _v ${op} 1), _v))(${array}, ${index}, ${array}[${index}])`;
|
|
191
|
+
},
|
|
192
|
+
node: unary
|
|
190
193
|
});
|
|
191
194
|
}
|
|
192
195
|
|
|
@@ -234,10 +237,10 @@ export default (sourceFile: ts.SourceFile, bindings: Bindings, isReactiveCall: I
|
|
|
234
237
|
elements = scope.unwrap(assignment.right) as ts.ArrayLiteralExpression;
|
|
235
238
|
|
|
236
239
|
result.replacements.push({
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
240
|
+
generate: (_sourceFile, text) => elements.elements.length > 0
|
|
241
|
+
? `${name}.splice(0, ${name}.length, ...${text(elements)})`
|
|
242
|
+
: `${name}.splice(0, ${name}.length)`,
|
|
243
|
+
node: assignment
|
|
241
244
|
});
|
|
242
245
|
}
|
|
243
246
|
|
package/src/compiler/bindings.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { ts } from '@esportsplus/typescript';
|
|
2
|
-
import { references } from '@esportsplus/typescript/compiler';
|
|
2
|
+
import { checker, references } from '@esportsplus/typescript/compiler';
|
|
3
3
|
import type { Origin } from '@esportsplus/typescript/compiler';
|
|
4
4
|
import { ENTRYPOINT, PACKAGE_NAME, TYPES } from './constants';
|
|
5
5
|
import type { Bindings } from './types';
|
|
@@ -7,7 +7,9 @@ import type { Bindings } from './types';
|
|
|
7
7
|
|
|
8
8
|
type Assignment = { call: ts.CallExpression; target: ts.Identifier };
|
|
9
9
|
|
|
10
|
-
// Per program snapshot: answers about declarations and types hold for the snapshot's lifetime
|
|
10
|
+
// Per program snapshot: answers about declarations and types hold for the snapshot's lifetime. `arrays`
|
|
11
|
+
// and `kinds` hold answers built from queries the shared checker does not memoize (getPropertyOfType,
|
|
12
|
+
// getNonNullableType, isArrayType), so dropping them costs a round-trip per array candidate or read
|
|
11
13
|
type Caches = {
|
|
12
14
|
arrays: Map<number, boolean>;
|
|
13
15
|
assignments: Map<string, Map<string, Assignment[]>>;
|
|
@@ -204,13 +206,19 @@ const classify = (bindings: Bindings, call: ts.CallExpression): TYPES | null =>
|
|
|
204
206
|
return type ? kindOf(bindings.checker, type) : null;
|
|
205
207
|
};
|
|
206
208
|
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
209
|
+
// Memoized per program (an already memoized checker is kept as is), so batched and repeated queries
|
|
210
|
+
// share one cache whoever hands the checker in
|
|
211
|
+
const create = (queries: ts.Checker, program: ts.Program, sourceFile: ts.SourceFile): Bindings => {
|
|
212
|
+
let memoized = checker.cached(queries, program);
|
|
213
|
+
|
|
214
|
+
return {
|
|
215
|
+
checker: memoized,
|
|
216
|
+
entries: new Set(references.exported(memoized, program, PACKAGE_NAME, ENTRYPOINT).map(references.key)),
|
|
217
|
+
origins: references.origins(memoized, program, sourceFile),
|
|
218
|
+
program,
|
|
219
|
+
sourceFile
|
|
220
|
+
};
|
|
221
|
+
};
|
|
214
222
|
|
|
215
223
|
// Whether a callee expression denotes `reactive`: `reactive`, an alias, `ns.reactive`, `ns['reactive']`
|
|
216
224
|
const denotes = (bindings: Bindings, expression: ts.Expression): boolean => {
|
|
@@ -293,6 +301,37 @@ const origin = (bindings: Bindings, node: ts.Node): Origin | null => {
|
|
|
293
301
|
return bindings.origins.get(node) ?? references.origin(bindings.checker, bindings.program, node);
|
|
294
302
|
};
|
|
295
303
|
|
|
304
|
+
// Types of the arguments classify() reads from the checker, requested in one round-trip; the
|
|
305
|
+
// memoized checker then answers each classify() from that request
|
|
306
|
+
const prefetch = (bindings: Bindings, calls: ts.CallExpression[]): void => {
|
|
307
|
+
let nodes: ts.Node[] = [];
|
|
308
|
+
|
|
309
|
+
for (let i = 0, n = calls.length; i < n; i++) {
|
|
310
|
+
let arg = calls[i].arguments[0];
|
|
311
|
+
|
|
312
|
+
if (!arg || calls[i].arguments.length !== 1 || ts.isSpreadElement(arg)) {
|
|
313
|
+
continue;
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
let value = unwrap(arg);
|
|
317
|
+
|
|
318
|
+
if (
|
|
319
|
+
ts.isArrayLiteralExpression(value) ||
|
|
320
|
+
ts.isArrowFunction(value) ||
|
|
321
|
+
ts.isFunctionExpression(value) ||
|
|
322
|
+
ts.isObjectLiteralExpression(value)
|
|
323
|
+
) {
|
|
324
|
+
continue;
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
nodes.push(arg);
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
if (nodes.length > 0) {
|
|
331
|
+
bindings.checker.getTypeAtLocation(nodes);
|
|
332
|
+
}
|
|
333
|
+
};
|
|
334
|
+
|
|
296
335
|
|
|
297
336
|
// The expression a value comes from, without parentheses, `!` and type assertions
|
|
298
337
|
const unwrap = (expression: ts.Expression): ts.Expression => {
|
|
@@ -300,4 +339,4 @@ const unwrap = (expression: ts.Expression): ts.Expression => {
|
|
|
300
339
|
};
|
|
301
340
|
|
|
302
341
|
|
|
303
|
-
export default { arrays, classify, create, denotes, holds, isReactiveCall, kind, origin, unwrap };
|
|
342
|
+
export default { arrays, classify, create, denotes, holds, isReactiveCall, kind, origin, prefetch, unwrap };
|