@esportsplus/reactivity 0.33.0 → 0.36.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 (45) 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 +12 -54
  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 +10 -21
  10. package/build/compiler/plugins/vite.d.ts +4 -2
  11. package/build/compiler/primitives.d.ts +6 -2
  12. package/build/compiler/primitives.js +35 -42
  13. package/build/compiler/types.d.ts +3 -1
  14. package/build/constants.d.ts +2 -2
  15. package/build/constants.js +2 -2
  16. package/build/reactive/array.d.ts +1 -1
  17. package/build/reactive/array.js +26 -15
  18. package/build/reactive/index.js +5 -14
  19. package/build/reactive/object.js +1 -1
  20. package/build/system.d.ts +10 -13
  21. package/build/system.js +105 -83
  22. package/build/types.d.ts +2 -7
  23. package/package.json +5 -5
  24. package/pnpm-workspace.yaml +3 -0
  25. package/src/compiler/array.ts +14 -64
  26. package/src/compiler/constants.ts +21 -5
  27. package/src/compiler/index.ts +39 -70
  28. package/src/compiler/object.ts +12 -29
  29. package/src/compiler/primitives.ts +55 -53
  30. package/src/compiler/types.ts +4 -1
  31. package/src/constants.ts +4 -4
  32. package/src/reactive/array.ts +33 -17
  33. package/src/reactive/index.ts +5 -17
  34. package/src/reactive/object.ts +1 -1
  35. package/src/system.ts +138 -101
  36. package/src/types.ts +2 -9
  37. package/test/async-computed.test.ts +2 -2
  38. package/test/compiler/compiler.test.ts +40 -23
  39. package/test/effects.test.ts +37 -1
  40. package/test/lib/uncaught.ts +26 -0
  41. package/test/reactive/array.test.ts +169 -99
  42. package/test/reactive/nested.test.ts +14 -14
  43. package/test/reactive/objects.test.ts +1 -1
  44. package/test/reactive/reactive.test.ts +49 -0
  45. package/test/system.test.ts +130 -22
package/README.md CHANGED
@@ -1,7 +1,5 @@
1
1
  # @esportsplus/reactivity
2
2
 
3
- Project doc bundle: [docs/index.md](./docs/index.md) — rejected dead-ends, skip reasons, completed-work index.
4
-
5
3
  A fine-grained reactivity system with compile-time transformations. Write reactive code with natural JavaScript syntax while the compiler generates optimized signal-based code.
6
4
 
7
5
  ## Installation
@@ -223,7 +221,7 @@ let user = new ReactiveObject_1();
223
221
  | `reactive(() => expr)` | Creates a computed value (compile-time only) |
224
222
  | `reactive({...})` | Creates a reactive object with signals and computeds |
225
223
  | `reactive([...])` | Creates a reactive array |
226
- | `effect(fn, onError?)` | Runs a function that re-executes when dependencies change. Returns a dispose function. Optional `onError` receives thrown or rejected errors |
224
+ | `effect(fn, apply?)` | Runs a function that re-executes when dependencies change. Returns a dispose function. Optional `apply(value, prev)` runs untracked after each run with `fn`'s return value |
227
225
  | `root(fn)` | Creates an untracked scope. If `fn` accepts an argument, a dispose function is provided |
228
226
  | `onCleanup(fn)` | Registers a cleanup function for the current effect/computed |
229
227
 
@@ -259,7 +257,7 @@ For advanced use cases, the underlying classes are exported:
259
257
 
260
258
  | Class | Description |
261
259
  |-------|-------------|
262
- | `ReactiveArray<T>` | Array subclass with reactivity and event dispatching |
260
+ | `ReactiveArray<T>` | Array subclass with reactivity and event dispatching. `new ReactiveArray(items?: T[])` takes the initial items as one array |
263
261
  | `ReactiveObject<T>` | Base class for reactive objects |
264
262
 
265
263
  ### Constants
@@ -283,7 +281,6 @@ Symbol constants for type identification:
283
281
  | `SelectorSignal<T>` | Signal variant backing `signal.selector` per-key subscriptions |
284
282
  | `Link` | Dependency graph link between nodes |
285
283
  | `Reactive<T>` | Utility type for inferring reactive object/array types |
286
- | `TransformResult` | Compiler transform output metadata |
287
284
 
288
285
  ## ReactiveArray
289
286
 
@@ -341,8 +338,13 @@ pnpm bench # vitest bench --run
341
338
  `agent:test` (`tsc --noEmit && vitest run`) and `agent:bench` (`vitest bench --run`) are the
342
339
  CI-facing aliases of the same build/test/bench commands.
343
340
 
341
+ ## Changes in 0.35.0
342
+
343
+ - `new ReactiveArray(items?)` takes an array instead of variadic items. `new ReactiveArray(5)` previously produced a length-5 holey array, and spreading large arrays overflowed the call stack.
344
+ - `root.disposables` is removed. `reactive()` now always registers its disposal with the enclosing effect or root scope.
345
+ - `ReactiveArray` listener errors are still detached but are now rethrown on a microtask instead of swallowed.
346
+ - `TransformResult` is no longer exported.
347
+
344
348
  ## License
345
349
 
346
350
  MIT
347
-
348
- <!-- claude-code:readme-source-hash: 189167a17e9be8c6 -->
@@ -9,7 +9,7 @@ describe('ReactiveArray creation', () => {
9
9
  });
10
10
 
11
11
  bench('create with 10 items', () => {
12
- new ReactiveArray(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
12
+ new ReactiveArray([1, 2, 3, 4, 5, 6, 7, 8, 9, 10]);
13
13
  });
14
14
 
15
15
  bench('create with 100 items', () => {
@@ -19,7 +19,7 @@ describe('ReactiveArray creation', () => {
19
19
  items.push(i);
20
20
  }
21
21
 
22
- new ReactiveArray(...items);
22
+ new ReactiveArray(items);
23
23
  });
24
24
  });
25
25
 
@@ -56,7 +56,7 @@ describe('ReactiveArray push', () => {
56
56
 
57
57
  describe('ReactiveArray pop', () => {
58
58
  bench('pop', () => {
59
- let arr = new ReactiveArray(1, 2, 3, 4, 5);
59
+ let arr = new ReactiveArray([1, 2, 3, 4, 5]);
60
60
 
61
61
  arr.pop();
62
62
  });
@@ -65,19 +65,19 @@ describe('ReactiveArray pop', () => {
65
65
 
66
66
  describe('ReactiveArray splice', () => {
67
67
  bench('splice remove 1', () => {
68
- let arr = new ReactiveArray(1, 2, 3, 4, 5);
68
+ let arr = new ReactiveArray([1, 2, 3, 4, 5]);
69
69
 
70
70
  arr.splice(2, 1);
71
71
  });
72
72
 
73
73
  bench('splice insert 1', () => {
74
- let arr = new ReactiveArray(1, 2, 3, 4, 5);
74
+ let arr = new ReactiveArray([1, 2, 3, 4, 5]);
75
75
 
76
76
  arr.splice(2, 0, 99);
77
77
  });
78
78
 
79
79
  bench('splice replace 1', () => {
80
- let arr = new ReactiveArray(1, 2, 3, 4, 5);
80
+ let arr = new ReactiveArray([1, 2, 3, 4, 5]);
81
81
 
82
82
  arr.splice(2, 1, 99);
83
83
  });
@@ -86,7 +86,7 @@ describe('ReactiveArray splice', () => {
86
86
 
87
87
  describe('ReactiveArray sort', () => {
88
88
  bench('sort 10 items', () => {
89
- let arr = new ReactiveArray(5, 3, 8, 1, 9, 2, 7, 4, 6, 10);
89
+ let arr = new ReactiveArray([5, 3, 8, 1, 9, 2, 7, 4, 6, 10]);
90
90
 
91
91
  arr.sort((a, b) => a - b);
92
92
  });
@@ -98,7 +98,7 @@ describe('ReactiveArray sort', () => {
98
98
  items.push(i);
99
99
  }
100
100
 
101
- let arr = new ReactiveArray(...items);
101
+ let arr = new ReactiveArray(items);
102
102
 
103
103
  arr.sort((a, b) => a - b);
104
104
  });
@@ -110,7 +110,7 @@ describe('ReactiveArray sort', () => {
110
110
  items.push(i);
111
111
  }
112
112
 
113
- let arr = new ReactiveArray(...items);
113
+ let arr = new ReactiveArray(items);
114
114
 
115
115
  arr.sort((a, b) => a - b);
116
116
  });
@@ -119,13 +119,13 @@ describe('ReactiveArray sort', () => {
119
119
 
120
120
  describe('ReactiveArray $set', () => {
121
121
  bench('$set', () => {
122
- let arr = new ReactiveArray(1, 2, 3, 4, 5);
122
+ let arr = new ReactiveArray([1, 2, 3, 4, 5]);
123
123
 
124
124
  arr.$set(2, 99);
125
125
  });
126
126
 
127
127
  bench('$set same value (no-op)', () => {
128
- let arr = new ReactiveArray(1, 2, 3, 4, 5);
128
+ let arr = new ReactiveArray([1, 2, 3, 4, 5]);
129
129
 
130
130
  arr.$set(2, 3);
131
131
  });
@@ -169,7 +169,7 @@ describe('ReactiveArray dispose/clear', () => {
169
169
  items.push(i);
170
170
  }
171
171
 
172
- let arr = new ReactiveArray(...items);
172
+ let arr = new ReactiveArray(items);
173
173
 
174
174
  arr.dispose();
175
175
  });
@@ -181,7 +181,7 @@ describe('ReactiveArray dispose/clear', () => {
181
181
  items.push(i);
182
182
  }
183
183
 
184
- let arr = new ReactiveArray(...items);
184
+ let arr = new ReactiveArray(items);
185
185
 
186
186
  arr.clear();
187
187
  });
@@ -201,13 +201,13 @@ describe('ReactiveArray concat/unshift/shift/reverse', () => {
201
201
  });
202
202
 
203
203
  bench('unshift 10 items', () => {
204
- let arr = new ReactiveArray(1, 2, 3, 4, 5);
204
+ let arr = new ReactiveArray([1, 2, 3, 4, 5]);
205
205
 
206
206
  arr.unshift(10, 20, 30, 40, 50, 60, 70, 80, 90, 100);
207
207
  });
208
208
 
209
209
  bench('shift', () => {
210
- let arr = new ReactiveArray(1, 2, 3, 4, 5);
210
+ let arr = new ReactiveArray([1, 2, 3, 4, 5]);
211
211
 
212
212
  arr.shift();
213
213
  });
@@ -219,7 +219,7 @@ describe('ReactiveArray concat/unshift/shift/reverse', () => {
219
219
  items.push(i);
220
220
  }
221
221
 
222
- let arr = new ReactiveArray(...items);
222
+ let arr = new ReactiveArray(items);
223
223
 
224
224
  arr.reverse();
225
225
  });
@@ -228,7 +228,7 @@ describe('ReactiveArray concat/unshift/shift/reverse', () => {
228
228
 
229
229
  describe('ReactiveArray reactive length', () => {
230
230
  bench('read $length in effect', () => {
231
- let arr = new ReactiveArray(1, 2, 3);
231
+ let arr = new ReactiveArray([1, 2, 3]);
232
232
 
233
233
  let stop = effect(() => {
234
234
  arr.$length;
@@ -1,5 +1,5 @@
1
1
  import { ts } from '@esportsplus/typescript';
2
2
  import type { ReplacementIntent } from '@esportsplus/typescript/compiler';
3
- import type { Bindings } from './types.js';
4
- declare const _default: (sourceFile: ts.SourceFile, bindings: Bindings, checker?: ts.TypeChecker) => ReplacementIntent[];
3
+ import type { Bindings, IsReactiveCall } from './types.js';
5
4
  export default _default;
5
+ declare function _default(sourceFile: ts.SourceFile, bindings: Bindings, isReactiveCall: IsReactiveCall): ReplacementIntent[];
@@ -1,16 +1,6 @@
1
1
  import { ts } from '@esportsplus/typescript';
2
- import { ast, imports } from '@esportsplus/typescript/compiler';
3
- import { ENTRYPOINT, NAMESPACE, PACKAGE_NAME, TYPES } from './constants.js';
4
- function isReactiveCall(checker, node) {
5
- if (!ts.isCallExpression(node) || !ts.isIdentifier(node.expression)) {
6
- return false;
7
- }
8
- let expr = node.expression;
9
- if (checker) {
10
- return imports.includes(checker, expr, PACKAGE_NAME, ENTRYPOINT);
11
- }
12
- return expr.text === ENTRYPOINT;
13
- }
2
+ import { ast } from '@esportsplus/typescript/compiler';
3
+ import { COMPOUND_OPERATORS, NAMESPACE, TYPES } from './constants.js';
14
4
  function getElementTypeText(typeNode, sourceFile) {
15
5
  if (ts.isArrayTypeNode(typeNode)) {
16
6
  return typeNode.elementType.getText(sourceFile);
@@ -24,40 +14,8 @@ function getElementTypeText(typeNode, sourceFile) {
24
14
  }
25
15
  return null;
26
16
  }
27
- function getOperator(kind) {
28
- switch (kind) {
29
- case ts.SyntaxKind.PlusEqualsToken: return '+';
30
- case ts.SyntaxKind.MinusEqualsToken: return '-';
31
- case ts.SyntaxKind.AsteriskEqualsToken: return '*';
32
- case ts.SyntaxKind.SlashEqualsToken: return '/';
33
- case ts.SyntaxKind.PercentEqualsToken: return '%';
34
- case ts.SyntaxKind.AsteriskAsteriskEqualsToken: return '**';
35
- case ts.SyntaxKind.AmpersandEqualsToken: return '&';
36
- case ts.SyntaxKind.BarEqualsToken: return '|';
37
- case ts.SyntaxKind.CaretEqualsToken: return '^';
38
- default: return '+';
39
- }
40
- }
41
- function isAssignmentOperator(kind) {
42
- return kind === ts.SyntaxKind.EqualsToken ||
43
- kind === ts.SyntaxKind.PlusEqualsToken ||
44
- kind === ts.SyntaxKind.MinusEqualsToken ||
45
- kind === ts.SyntaxKind.AsteriskEqualsToken ||
46
- kind === ts.SyntaxKind.SlashEqualsToken ||
47
- kind === ts.SyntaxKind.PercentEqualsToken ||
48
- kind === ts.SyntaxKind.AsteriskAsteriskEqualsToken ||
49
- kind === ts.SyntaxKind.LessThanLessThanEqualsToken ||
50
- kind === ts.SyntaxKind.GreaterThanGreaterThanEqualsToken ||
51
- kind === ts.SyntaxKind.GreaterThanGreaterThanGreaterThanEqualsToken ||
52
- kind === ts.SyntaxKind.AmpersandEqualsToken ||
53
- kind === ts.SyntaxKind.BarEqualsToken ||
54
- kind === ts.SyntaxKind.CaretEqualsToken ||
55
- kind === ts.SyntaxKind.BarBarEqualsToken ||
56
- kind === ts.SyntaxKind.AmpersandAmpersandEqualsToken ||
57
- kind === ts.SyntaxKind.QuestionQuestionEqualsToken;
58
- }
59
17
  function visit(ctx, node) {
60
- if (isReactiveCall(ctx.checker, node) && node.arguments.length > 0) {
18
+ if (ctx.isReactiveCall(node) && node.arguments.length > 0) {
61
19
  let arg = node.arguments[0], expression = ts.isAsExpression(arg) ? arg.expression : arg;
62
20
  if (ts.isArrayLiteralExpression(expression)) {
63
21
  let elementType = null;
@@ -74,7 +32,7 @@ function visit(ctx, node) {
74
32
  ctx.replacements.push({
75
33
  node,
76
34
  generate: (sf) => expression.elements.length > 0
77
- ? ` new ${NAMESPACE}.ReactiveArray${typeParam}(...${expression.getText(sf)})`
35
+ ? ` new ${NAMESPACE}.ReactiveArray${typeParam}(${expression.getText(sf)})`
78
36
  : ` new ${NAMESPACE}.ReactiveArray${typeParam}()`
79
37
  });
80
38
  }
@@ -105,9 +63,9 @@ function visit(ctx, node) {
105
63
  let name = ast.expression.name(node.expression);
106
64
  if (name && ctx.bindings.get(name) === TYPES.Array) {
107
65
  let expr = node.expression, parent = node.parent;
108
- if (parent && ts.isBinaryExpression(parent) && parent.left === node && isAssignmentOperator(parent.operatorToken.kind)) {
109
- let op = parent.operatorToken.kind;
110
- if (op === ts.SyntaxKind.EqualsToken) {
66
+ if (parent && ts.isBinaryExpression(parent) && parent.left === node && (parent.operatorToken.kind === ts.SyntaxKind.EqualsToken || COMPOUND_OPERATORS.has(parent.operatorToken.kind))) {
67
+ let op = COMPOUND_OPERATORS.get(parent.operatorToken.kind);
68
+ if (op === undefined) {
111
69
  ctx.replacements.push({
112
70
  node: parent,
113
71
  generate: (sf) => `${expr.getText(sf)}.$length = ${parent.right.getText(sf)}`
@@ -116,7 +74,7 @@ function visit(ctx, node) {
116
74
  else {
117
75
  ctx.replacements.push({
118
76
  node: parent,
119
- generate: (sf) => `${expr.getText(sf)}.$length = ${expr.getText(sf)}.length ${getOperator(op)} ${parent.right.getText(sf)}`
77
+ generate: (sf) => `${expr.getText(sf)}.$length = ${expr.getText(sf)}.length ${op} ${parent.right.getText(sf)}`
120
78
  });
121
79
  }
122
80
  }
@@ -160,7 +118,7 @@ function visit(ctx, node) {
160
118
  node.operatorToken.kind === ts.SyntaxKind.EqualsToken &&
161
119
  ts.isIdentifier(node.left)) {
162
120
  let name = node.left.text, right = node.right;
163
- while (ts.isAsExpression(right) || ts.isTypeAssertionExpression(right)) {
121
+ while (ts.isAsExpression(right) || ts.isTypeAssertion(right)) {
164
122
  right = right.expression;
165
123
  }
166
124
  if (ctx.bindings.get(name) === TYPES.Array && ts.isArrayLiteralExpression(right)) {
@@ -172,12 +130,12 @@ function visit(ctx, node) {
172
130
  });
173
131
  }
174
132
  }
175
- ts.forEachChild(node, n => visit(ctx, n));
133
+ node.forEachChild(n => visit(ctx, n));
176
134
  }
177
- export default (sourceFile, bindings, checker) => {
135
+ export default (sourceFile, bindings, isReactiveCall) => {
178
136
  let ctx = {
179
137
  bindings,
180
- checker,
138
+ isReactiveCall,
181
139
  replacements: [],
182
140
  sourceFile
183
141
  };
@@ -1,12 +1,12 @@
1
+ import { ts } from '@esportsplus/typescript';
2
+ declare const COMPOUND_OPERATORS: Map<ts.SyntaxKind, string>;
1
3
  declare const ENTRYPOINT = "reactive";
2
- declare const ENTRYPOINT_REGEX: RegExp;
3
4
  declare const NAMESPACE: string;
4
5
  declare const TYPES: {
5
6
  readonly Array: 0;
6
7
  readonly Computed: 1;
7
- readonly Object: 2;
8
- readonly Signal: 3;
8
+ readonly Signal: 2;
9
9
  };
10
10
  type TYPES = typeof TYPES[keyof typeof TYPES];
11
- export { ENTRYPOINT, ENTRYPOINT_REGEX, NAMESPACE, TYPES };
11
+ export { COMPOUND_OPERATORS, ENTRYPOINT, NAMESPACE, TYPES };
12
12
  export { PACKAGE_NAME } from '../constants.js';
@@ -1,12 +1,28 @@
1
+ import { ts } from '@esportsplus/typescript';
1
2
  import { uid } from '@esportsplus/typescript/compiler';
3
+ const COMPOUND_OPERATORS = new Map([
4
+ [ts.SyntaxKind.AmpersandAmpersandEqualsToken, '&&'],
5
+ [ts.SyntaxKind.AmpersandEqualsToken, '&'],
6
+ [ts.SyntaxKind.AsteriskAsteriskEqualsToken, '**'],
7
+ [ts.SyntaxKind.AsteriskEqualsToken, '*'],
8
+ [ts.SyntaxKind.BarBarEqualsToken, '||'],
9
+ [ts.SyntaxKind.BarEqualsToken, '|'],
10
+ [ts.SyntaxKind.CaretEqualsToken, '^'],
11
+ [ts.SyntaxKind.GreaterThanGreaterThanEqualsToken, '>>'],
12
+ [ts.SyntaxKind.GreaterThanGreaterThanGreaterThanEqualsToken, '>>>'],
13
+ [ts.SyntaxKind.LessThanLessThanEqualsToken, '<<'],
14
+ [ts.SyntaxKind.MinusEqualsToken, '-'],
15
+ [ts.SyntaxKind.PercentEqualsToken, '%'],
16
+ [ts.SyntaxKind.PlusEqualsToken, '+'],
17
+ [ts.SyntaxKind.QuestionQuestionEqualsToken, '??'],
18
+ [ts.SyntaxKind.SlashEqualsToken, '/']
19
+ ]);
2
20
  const ENTRYPOINT = 'reactive';
3
- const ENTRYPOINT_REGEX = /\breactive\b/;
4
21
  const NAMESPACE = uid('reactivity');
5
22
  const TYPES = {
6
23
  Array: 0,
7
24
  Computed: 1,
8
- Object: 2,
9
- Signal: 3
25
+ Signal: 2
10
26
  };
11
- export { ENTRYPOINT, ENTRYPOINT_REGEX, NAMESPACE, TYPES };
27
+ export { COMPOUND_OPERATORS, ENTRYPOINT, NAMESPACE, TYPES };
12
28
  export { PACKAGE_NAME } from '../constants.js';
@@ -4,11 +4,6 @@ import { ENTRYPOINT, NAMESPACE, PACKAGE_NAME } from './constants.js';
4
4
  import array from './array.js';
5
5
  import object from './object.js';
6
6
  import primitives from './primitives.js';
7
- function findRemainingCalls(checker, sourceFile, transformedNodes) {
8
- let ctx = { checker, replacements: [], sourceFile, transformedNodes };
9
- visit(ctx, sourceFile);
10
- return ctx.replacements;
11
- }
12
7
  function isReactiveCallExpression(checker, node) {
13
8
  if (!ts.isCallExpression(node)) {
14
9
  return false;
@@ -22,51 +17,43 @@ function isReactiveCallExpression(checker, node) {
22
17
  }
23
18
  return false;
24
19
  }
25
- function hasReactiveCalls(checker, node) {
26
- if (isReactiveCallExpression(checker, node)) {
27
- return true;
28
- }
29
- let found = false;
30
- ts.forEachChild(node, child => {
31
- if (!found && hasReactiveCalls(checker, child)) {
32
- found = true;
33
- }
34
- });
35
- return found;
36
- }
37
- function visit(ctx, node) {
38
- if (isReactiveCallExpression(ctx.checker, node) && !ctx.transformedNodes.has(node) && !ctx.transformedNodes.has(node.expression)) {
39
- ctx.replacements.push({
40
- generate: () => `${NAMESPACE}.reactive(${node.arguments.map(a => a.getText(ctx.sourceFile)).join(', ')})`,
41
- node
42
- });
43
- }
44
- ts.forEachChild(node, n => visit(ctx, n));
45
- }
46
20
  export default {
47
21
  patterns: ['reactive(', 'reactive<'],
48
22
  transform: (ctx) => {
49
- if (!ctx.checker || !hasReactiveCalls(ctx.checker, ctx.sourceFile)) {
23
+ let checker = ctx.checker;
24
+ if (!checker) {
50
25
  return {};
51
26
  }
52
27
  let bindings = new Map(), intents = {
53
28
  imports: [],
54
29
  prepend: [],
55
30
  replacements: []
56
- };
57
- intents.replacements.push(...primitives(ctx.sourceFile, bindings, (node) => isReactiveCallExpression(ctx.checker, node)));
58
- let { prepend, replacements } = object(ctx.sourceFile, bindings, ctx.checker);
59
- intents.prepend.push(...prepend);
31
+ }, isReactiveCall = (node) => isReactiveCallExpression(checker, node), sourceFile = ctx.sourceFile;
32
+ let { calls, replacements } = primitives(sourceFile, bindings, isReactiveCall);
33
+ if (calls.length === 0) {
34
+ return {};
35
+ }
60
36
  intents.replacements.push(...replacements);
61
- intents.replacements.push(...array(ctx.sourceFile, bindings, ctx.checker));
62
- intents.replacements.push(...findRemainingCalls(ctx.checker, ctx.sourceFile, new Set(intents.replacements.map(r => r.node))));
63
- if (intents.replacements.length > 0 || intents.prepend.length > 0) {
64
- intents.imports.push({
65
- namespace: NAMESPACE,
66
- package: PACKAGE_NAME,
67
- remove: [ENTRYPOINT]
37
+ let objects = object(sourceFile, bindings, isReactiveCall);
38
+ intents.prepend.push(...objects.prepend);
39
+ intents.replacements.push(...objects.replacements);
40
+ intents.replacements.push(...array(sourceFile, bindings, isReactiveCall));
41
+ let transformed = new Set(intents.replacements.map(r => r.node));
42
+ for (let i = 0, n = calls.length; i < n; i++) {
43
+ let call = calls[i];
44
+ if (transformed.has(call) || transformed.has(call.expression)) {
45
+ continue;
46
+ }
47
+ intents.replacements.push({
48
+ generate: () => `${NAMESPACE}.reactive(${call.arguments.map(a => a.getText(sourceFile)).join(', ')})`,
49
+ node: call
68
50
  });
69
51
  }
52
+ intents.imports.push({
53
+ namespace: NAMESPACE,
54
+ package: PACKAGE_NAME,
55
+ remove: [ENTRYPOINT]
56
+ });
70
57
  return intents;
71
58
  }
72
59
  };
@@ -1,9 +1,9 @@
1
1
  import { ts } from '@esportsplus/typescript';
2
2
  import type { ReplacementIntent } from '@esportsplus/typescript/compiler';
3
- import type { Bindings } from './types.js';
3
+ import type { Bindings, IsReactiveCall } from './types.js';
4
4
  type ObjectTransformResult = {
5
5
  prepend: string[];
6
6
  replacements: ReplacementIntent[];
7
7
  };
8
- declare const _default: (sourceFile: ts.SourceFile, bindings: Bindings, checker?: ts.TypeChecker) => ObjectTransformResult;
9
8
  export default _default;
9
+ declare function _default(sourceFile: ts.SourceFile, bindings: Bindings, isReactiveCall: IsReactiveCall): ObjectTransformResult;
@@ -1,6 +1,6 @@
1
1
  import { ts } from '@esportsplus/typescript';
2
- import { code, imports, uid } from '@esportsplus/typescript/compiler';
3
- import { ENTRYPOINT, NAMESPACE, PACKAGE_NAME, TYPES } from './constants.js';
2
+ import { code, uid } from '@esportsplus/typescript/compiler';
3
+ import { NAMESPACE, TYPES } from './constants.js';
4
4
  function analyzeProperty(prop, sourceFile) {
5
5
  if (!ts.isPropertyAssignment(prop)) {
6
6
  return null;
@@ -13,10 +13,10 @@ function analyzeProperty(prop, sourceFile) {
13
13
  return null;
14
14
  }
15
15
  let unwrapped = prop.initializer, value = unwrapped, valueText = value.getText(sourceFile);
16
- while (ts.isAsExpression(unwrapped) || ts.isTypeAssertionExpression(unwrapped) || ts.isParenthesizedExpression(unwrapped)) {
16
+ while (ts.isAsExpression(unwrapped) || ts.isTypeAssertion(unwrapped) || ts.isParenthesizedExpression(unwrapped)) {
17
17
  unwrapped = unwrapped.expression;
18
18
  }
19
- if (ts.isAsExpression(value) || ts.isTypeAssertionExpression(value)) {
19
+ if (ts.isAsExpression(value) || ts.isTypeAssertion(value)) {
20
20
  let type = value.type;
21
21
  if (ts.isArrayTypeNode(type) ||
22
22
  (ts.isTypeReferenceNode(type) &&
@@ -139,34 +139,23 @@ function isStaticValue(node) {
139
139
  node.kind === ts.SyntaxKind.UndefinedKeyword ||
140
140
  (ts.isPrefixUnaryExpression(node) && ts.isNumericLiteral(node.operand));
141
141
  }
142
- function isReactiveCall(checker, node) {
143
- if (!ts.isCallExpression(node) || !ts.isIdentifier(node.expression)) {
144
- return false;
145
- }
146
- let expr = node.expression;
147
- if (checker) {
148
- return imports.includes(checker, expr, PACKAGE_NAME, ENTRYPOINT);
149
- }
150
- return expr.text === ENTRYPOINT;
151
- }
152
142
  function visit(ctx, node) {
153
- if (isReactiveCall(ctx.checker, node)) {
143
+ if (ctx.isReactiveCall(node)) {
154
144
  let arg = node.arguments[0];
155
145
  if (arg && ts.isObjectLiteralExpression(arg)) {
156
146
  let properties = [], props = arg.properties, varname = null;
157
147
  if (node.parent && ts.isVariableDeclaration(node.parent) && ts.isIdentifier(node.parent.name)) {
158
148
  varname = node.parent.name.text;
159
- ctx.bindings.set(varname, TYPES.Object);
160
149
  }
161
150
  for (let i = 0, n = props.length; i < n; i++) {
162
151
  let prop = props[i];
163
152
  if (ts.isSpreadAssignment(prop)) {
164
- ts.forEachChild(node, n => visit(ctx, n));
153
+ node.forEachChild(n => visit(ctx, n));
165
154
  return;
166
155
  }
167
156
  let analyzed = analyzeProperty(prop, ctx.sourceFile);
168
157
  if (!analyzed) {
169
- ts.forEachChild(node, n => visit(ctx, n));
158
+ node.forEachChild(n => visit(ctx, n));
170
159
  return;
171
160
  }
172
161
  properties.push(analyzed);
@@ -183,13 +172,13 @@ function visit(ctx, node) {
183
172
  });
184
173
  }
185
174
  }
186
- ts.forEachChild(node, n => visit(ctx, n));
175
+ node.forEachChild(n => visit(ctx, n));
187
176
  }
188
- export default (sourceFile, bindings, checker) => {
177
+ export default (sourceFile, bindings, isReactiveCall) => {
189
178
  let ctx = {
190
179
  bindings,
191
180
  calls: [],
192
- checker,
181
+ isReactiveCall,
193
182
  sourceFile
194
183
  };
195
184
  visit(ctx, sourceFile);
@@ -1,12 +1,14 @@
1
1
  declare const _default: ({ root }?: {
2
2
  root?: string;
3
3
  }) => {
4
+ closeBundle: () => void;
5
+ closeWatcher: () => void;
4
6
  configResolved: (config: unknown) => void;
5
- enforce: "pre";
7
+ enforce: 'pre';
6
8
  name: string;
7
9
  transform: (code: string, id: string) => {
8
10
  code: string;
9
- map: null;
11
+ map: import("@esportsplus/typescript/compiler").SourceMapV3;
10
12
  } | null;
11
13
  watchChange: (id: string) => void;
12
14
  };
@@ -1,5 +1,9 @@
1
1
  import { ts } from '@esportsplus/typescript';
2
2
  import type { ReplacementIntent } from '@esportsplus/typescript/compiler';
3
- import type { Bindings } from './types.js';
4
- declare const _default: (sourceFile: ts.SourceFile, bindings: Bindings, isReactiveCall: (node: ts.Node) => boolean) => ReplacementIntent[];
3
+ import type { Bindings, IsReactiveCall } from './types.js';
4
+ type PrimitivesTransformResult = {
5
+ calls: ts.CallExpression[];
6
+ replacements: ReplacementIntent[];
7
+ };
5
8
  export default _default;
9
+ declare function _default(sourceFile: ts.SourceFile, bindings: Bindings, isReactiveCall: IsReactiveCall): PrimitivesTransformResult;