@esportsplus/reactivity 0.34.0 → 0.36.1
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 +9 -7
- package/bench/reactive/array.bench.ts +17 -17
- package/build/compiler/array.d.ts +2 -2
- package/build/compiler/array.js +10 -52
- package/build/compiler/constants.d.ts +4 -4
- package/build/compiler/constants.js +20 -4
- package/build/compiler/index.js +25 -38
- package/build/compiler/object.d.ts +2 -2
- package/build/compiler/object.js +5 -16
- package/build/compiler/primitives.d.ts +6 -2
- package/build/compiler/primitives.js +31 -38
- package/build/compiler/types.d.ts +3 -1
- package/build/constants.d.ts +2 -2
- package/build/constants.js +2 -2
- package/build/reactive/array.d.ts +1 -1
- package/build/reactive/array.js +26 -15
- package/build/reactive/index.js +5 -14
- package/build/reactive/object.js +1 -1
- package/build/system.d.ts +1 -4
- package/build/system.js +105 -83
- package/build/types.d.ts +2 -7
- package/package.json +5 -5
- package/pnpm-workspace.yaml +2 -1
- package/src/compiler/array.ts +12 -62
- package/src/compiler/constants.ts +21 -5
- package/src/compiler/index.ts +38 -69
- package/src/compiler/object.ts +7 -24
- package/src/compiler/primitives.ts +51 -49
- package/src/compiler/types.ts +4 -1
- package/src/constants.ts +4 -4
- package/src/reactive/array.ts +33 -17
- package/src/reactive/index.ts +5 -17
- package/src/reactive/object.ts +1 -1
- package/src/system.ts +138 -101
- package/src/types.ts +2 -9
- package/test/async-computed.test.ts +2 -2
- package/test/compiler/compiler.test.ts +38 -22
- package/test/effects.test.ts +37 -1
- package/test/lib/uncaught.ts +26 -0
- package/test/reactive/array.test.ts +169 -99
- package/test/reactive/nested.test.ts +14 -14
- package/test/reactive/objects.test.ts +1 -1
- package/test/reactive/reactive.test.ts +49 -0
- package/test/system.test.ts +130 -22
|
@@ -2,7 +2,7 @@ import { describe, expect, it } from 'vitest';
|
|
|
2
2
|
import { ts } from '@esportsplus/typescript';
|
|
3
3
|
import { languageService } from '@esportsplus/typescript/compiler';
|
|
4
4
|
import type { ReplacementIntent } from '@esportsplus/typescript/compiler';
|
|
5
|
-
import { NAMESPACE } from '~/compiler/constants';
|
|
5
|
+
import { NAMESPACE, TYPES } from '~/compiler/constants';
|
|
6
6
|
import type { Bindings } from '~/compiler/types';
|
|
7
7
|
import array from '~/compiler/array';
|
|
8
8
|
import object from '~/compiler/object';
|
|
@@ -26,7 +26,7 @@ function applyIntents(code: string, sourceFile: ts.SourceFile, intents: Replacem
|
|
|
26
26
|
return code;
|
|
27
27
|
}
|
|
28
28
|
|
|
29
|
-
function isReactiveCall(node: ts.Node):
|
|
29
|
+
function isReactiveCall(node: ts.Node): node is ts.CallExpression {
|
|
30
30
|
return ts.isCallExpression(node) && ts.isIdentifier(node.expression) && node.expression.text === 'reactive';
|
|
31
31
|
}
|
|
32
32
|
|
|
@@ -37,15 +37,15 @@ function parse(code: string): ts.SourceFile {
|
|
|
37
37
|
function transformPrimitives(code: string): { bindings: Bindings; output: string } {
|
|
38
38
|
let bindings: Bindings = new Map(),
|
|
39
39
|
sourceFile = parse(code),
|
|
40
|
-
|
|
40
|
+
{ replacements } = primitives(sourceFile, bindings, isReactiveCall);
|
|
41
41
|
|
|
42
|
-
return { bindings, output: applyIntents(code, sourceFile,
|
|
42
|
+
return { bindings, output: applyIntents(code, sourceFile, replacements) };
|
|
43
43
|
}
|
|
44
44
|
|
|
45
45
|
function transformArray(code: string, bindings?: Bindings): { bindings: Bindings; output: string } {
|
|
46
46
|
let b: Bindings = bindings ?? new Map(),
|
|
47
47
|
sourceFile = parse(code),
|
|
48
|
-
intents = array(sourceFile, b,
|
|
48
|
+
intents = array(sourceFile, b, isReactiveCall);
|
|
49
49
|
|
|
50
50
|
return { bindings: b, output: applyIntents(code, sourceFile, intents) };
|
|
51
51
|
}
|
|
@@ -53,7 +53,7 @@ function transformArray(code: string, bindings?: Bindings): { bindings: Bindings
|
|
|
53
53
|
function transformObject(code: string): { bindings: Bindings; output: string; prepend: string[] } {
|
|
54
54
|
let bindings: Bindings = new Map(),
|
|
55
55
|
sourceFile = parse(code),
|
|
56
|
-
result = object(sourceFile, bindings,
|
|
56
|
+
result = object(sourceFile, bindings, isReactiveCall);
|
|
57
57
|
|
|
58
58
|
return {
|
|
59
59
|
bindings,
|
|
@@ -128,15 +128,28 @@ describe('primitives transform', () => {
|
|
|
128
128
|
it('tracks bindings for signal type', () => {
|
|
129
129
|
let { bindings } = transformPrimitives('let x = reactive(0);');
|
|
130
130
|
|
|
131
|
-
|
|
132
|
-
expect(bindings.get('x')).toBe(3);
|
|
131
|
+
expect(bindings.get('x')).toBe(TYPES.Signal);
|
|
133
132
|
});
|
|
134
133
|
|
|
135
134
|
it('tracks bindings for computed type', () => {
|
|
136
135
|
let { bindings } = transformPrimitives('let x = reactive(0); let d = reactive(() => x * 2);');
|
|
137
136
|
|
|
138
|
-
|
|
139
|
-
|
|
137
|
+
expect(bindings.get('d')).toBe(TYPES.Computed);
|
|
138
|
+
});
|
|
139
|
+
|
|
140
|
+
it('leaves a same-named plain variable in a sibling function untouched', () => {
|
|
141
|
+
let { output } = transformPrimitives('function a() { let x = reactive(0); return x; }\nfunction b() { let x = 1; return x; }');
|
|
142
|
+
|
|
143
|
+
expect(output).toContain(`function a() { let x = ${NAMESPACE}.signal(0); return ${NAMESPACE}.read(x); }`);
|
|
144
|
+
expect(output).toContain('function b() { let x = 1; return x; }');
|
|
145
|
+
});
|
|
146
|
+
|
|
147
|
+
it('resolves a shadowing inner binding to the innermost declaration', () => {
|
|
148
|
+
let { output } = transformPrimitives('let x = reactive(0); function f() { let x = reactive(() => 1); x = 2; }');
|
|
149
|
+
|
|
150
|
+
expect(output).toContain(`let x = ${NAMESPACE}.signal(0)`);
|
|
151
|
+
expect(output).toContain(`let x = ${NAMESPACE}.computed(() => 1)`);
|
|
152
|
+
expect(output).not.toContain(`${NAMESPACE}.write(x, 2)`);
|
|
140
153
|
});
|
|
141
154
|
|
|
142
155
|
it('transforms prefix --x in statement', () => {
|
|
@@ -206,18 +219,10 @@ describe('object transform', () => {
|
|
|
206
219
|
expect(output).toContain('<MyType>');
|
|
207
220
|
});
|
|
208
221
|
|
|
209
|
-
it('tracks object binding', () => {
|
|
210
|
-
let { bindings } = transformObject('let obj = reactive({ count: 0 });');
|
|
211
|
-
|
|
212
|
-
// TYPES.Object = 2
|
|
213
|
-
expect(bindings.get('obj')).toBe(2);
|
|
214
|
-
});
|
|
215
|
-
|
|
216
222
|
it('tracks nested array bindings', () => {
|
|
217
223
|
let { bindings } = transformObject('let obj = reactive({ items: [1, 2, 3] });');
|
|
218
224
|
|
|
219
|
-
|
|
220
|
-
expect(bindings.get('obj.items')).toBe(0);
|
|
225
|
+
expect(bindings.get('obj.items')).toBe(TYPES.Array);
|
|
221
226
|
});
|
|
222
227
|
});
|
|
223
228
|
|
|
@@ -227,7 +232,7 @@ describe('array transform', () => {
|
|
|
227
232
|
let { output } = transformArray('let arr = reactive([1, 2, 3]);');
|
|
228
233
|
|
|
229
234
|
expect(output).toContain(`new ${NAMESPACE}.ReactiveArray`);
|
|
230
|
-
expect(output).toContain('
|
|
235
|
+
expect(output).toContain('([1, 2, 3])');
|
|
231
236
|
});
|
|
232
237
|
|
|
233
238
|
it('transforms reactive([] as Type[]) to typed ReactiveArray', () => {
|
|
@@ -263,6 +268,18 @@ describe('array transform', () => {
|
|
|
263
268
|
expect(output).toContain('arr.$length = arr.length + 3');
|
|
264
269
|
});
|
|
265
270
|
|
|
271
|
+
it('transforms every compound operator on arr.length with its own token', () => {
|
|
272
|
+
let bindings: Bindings = new Map();
|
|
273
|
+
|
|
274
|
+
bindings.set('arr', 0);
|
|
275
|
+
|
|
276
|
+
for (let op of ['<<', '>>', '>>>', '??', '||', '&&', '-', '*', '/', '%', '**', '&', '|', '^']) {
|
|
277
|
+
let { output } = transformArray(`arr.length ${op}= 2;`, bindings);
|
|
278
|
+
|
|
279
|
+
expect(output).toContain(`arr.$length = arr.length ${op} 2`);
|
|
280
|
+
}
|
|
281
|
+
});
|
|
282
|
+
|
|
266
283
|
it('transforms arr[i] = value to arr.$set(i, value)', () => {
|
|
267
284
|
let bindings: Bindings = new Map();
|
|
268
285
|
|
|
@@ -276,8 +293,7 @@ describe('array transform', () => {
|
|
|
276
293
|
it('tracks reactive array binding from reactive call', () => {
|
|
277
294
|
let { bindings } = transformArray('let arr = reactive([1, 2, 3]);');
|
|
278
295
|
|
|
279
|
-
|
|
280
|
-
expect(bindings.get('arr')).toBe(0);
|
|
296
|
+
expect(bindings.get('arr')).toBe(TYPES.Array);
|
|
281
297
|
});
|
|
282
298
|
|
|
283
299
|
it('tracks alias binding from reactive array', () => {
|
package/test/effects.test.ts
CHANGED
|
@@ -182,7 +182,7 @@ describe('effect patterns', () => {
|
|
|
182
182
|
|
|
183
183
|
describe('effect with reactive array', () => {
|
|
184
184
|
it('effect tracks array length changes', async () => {
|
|
185
|
-
let arr = new ReactiveArray(1, 2, 3),
|
|
185
|
+
let arr = new ReactiveArray([1, 2, 3]),
|
|
186
186
|
runs = 0,
|
|
187
187
|
lengths: number[] = [];
|
|
188
188
|
|
|
@@ -331,3 +331,39 @@ describe('effect apply phase', () => {
|
|
|
331
331
|
expect(applied).toEqual([101, 202]);
|
|
332
332
|
});
|
|
333
333
|
});
|
|
334
|
+
|
|
335
|
+
|
|
336
|
+
describe('scheduling from a top-level creation run', () => {
|
|
337
|
+
it('a write issued by a top-level effect creation propagates', async () => {
|
|
338
|
+
let runs = 0,
|
|
339
|
+
s = signal(0);
|
|
340
|
+
|
|
341
|
+
effect(() => {
|
|
342
|
+
read(s);
|
|
343
|
+
runs++;
|
|
344
|
+
});
|
|
345
|
+
|
|
346
|
+
effect(() => {
|
|
347
|
+
write(s, 5);
|
|
348
|
+
});
|
|
349
|
+
|
|
350
|
+
await waitFor(() => runs === 2, 'the reader re-runs');
|
|
351
|
+
|
|
352
|
+
expect(read(s)).toBe(5);
|
|
353
|
+
});
|
|
354
|
+
|
|
355
|
+
it('a deferred inner effect created by a top-level effect creation runs', async () => {
|
|
356
|
+
let inner = 0,
|
|
357
|
+
s = signal(0);
|
|
358
|
+
|
|
359
|
+
effect(() => {
|
|
360
|
+
read(s);
|
|
361
|
+
|
|
362
|
+
effect(() => {
|
|
363
|
+
inner++;
|
|
364
|
+
});
|
|
365
|
+
});
|
|
366
|
+
|
|
367
|
+
await waitFor(() => inner === 1, 'the inner effect runs');
|
|
368
|
+
});
|
|
369
|
+
});
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
const captureUncaught = async (fn: () => void): Promise<unknown[]> => {
|
|
2
|
+
let captured: unknown[] = [],
|
|
3
|
+
previous = process.listeners('uncaughtException');
|
|
4
|
+
|
|
5
|
+
process.removeAllListeners('uncaughtException');
|
|
6
|
+
process.on('uncaughtException', (e) => { captured.push(e); });
|
|
7
|
+
|
|
8
|
+
try {
|
|
9
|
+
fn();
|
|
10
|
+
|
|
11
|
+
await Promise.resolve();
|
|
12
|
+
await Promise.resolve();
|
|
13
|
+
}
|
|
14
|
+
finally {
|
|
15
|
+
process.removeAllListeners('uncaughtException');
|
|
16
|
+
|
|
17
|
+
for (let i = 0, n = previous.length; i < n; i++) {
|
|
18
|
+
process.on('uncaughtException', previous[i]);
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
return captured;
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
export { captureUncaught };
|