@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.
Files changed (44) hide show
  1. package/README.md +9 -7
  2. package/bench/reactive/array.bench.ts +17 -17
  3. package/build/compiler/array.d.ts +2 -2
  4. package/build/compiler/array.js +10 -52
  5. package/build/compiler/constants.d.ts +4 -4
  6. package/build/compiler/constants.js +20 -4
  7. package/build/compiler/index.js +25 -38
  8. package/build/compiler/object.d.ts +2 -2
  9. package/build/compiler/object.js +5 -16
  10. package/build/compiler/primitives.d.ts +6 -2
  11. package/build/compiler/primitives.js +31 -38
  12. package/build/compiler/types.d.ts +3 -1
  13. package/build/constants.d.ts +2 -2
  14. package/build/constants.js +2 -2
  15. package/build/reactive/array.d.ts +1 -1
  16. package/build/reactive/array.js +26 -15
  17. package/build/reactive/index.js +5 -14
  18. package/build/reactive/object.js +1 -1
  19. package/build/system.d.ts +1 -4
  20. package/build/system.js +105 -83
  21. package/build/types.d.ts +2 -7
  22. package/package.json +5 -5
  23. package/pnpm-workspace.yaml +2 -1
  24. package/src/compiler/array.ts +12 -62
  25. package/src/compiler/constants.ts +21 -5
  26. package/src/compiler/index.ts +38 -69
  27. package/src/compiler/object.ts +7 -24
  28. package/src/compiler/primitives.ts +51 -49
  29. package/src/compiler/types.ts +4 -1
  30. package/src/constants.ts +4 -4
  31. package/src/reactive/array.ts +33 -17
  32. package/src/reactive/index.ts +5 -17
  33. package/src/reactive/object.ts +1 -1
  34. package/src/system.ts +138 -101
  35. package/src/types.ts +2 -9
  36. package/test/async-computed.test.ts +2 -2
  37. package/test/compiler/compiler.test.ts +38 -22
  38. package/test/effects.test.ts +37 -1
  39. package/test/lib/uncaught.ts +26 -0
  40. package/test/reactive/array.test.ts +169 -99
  41. package/test/reactive/nested.test.ts +14 -14
  42. package/test/reactive/objects.test.ts +1 -1
  43. package/test/reactive/reactive.test.ts +49 -0
  44. 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): boolean {
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
- intents = primitives(sourceFile, bindings, isReactiveCall);
40
+ { replacements } = primitives(sourceFile, bindings, isReactiveCall);
41
41
 
42
- return { bindings, output: applyIntents(code, sourceFile, intents) };
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, undefined);
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, undefined);
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
- // TYPES.Signal = 3
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
- // TYPES.Computed = 1
139
- expect(bindings.get('d')).toBe(1);
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
- // TYPES.Array = 0
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('...[1, 2, 3]');
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
- // TYPES.Array = 0
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', () => {
@@ -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 };