@esportsplus/reactivity 0.24.5 → 0.25.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.
@@ -1,5 +1,5 @@
1
1
  import { uid } from '@esportsplus/typescript/transformer';
2
- import { createCommaExpr, createComputedCall, createPostfixIncrementExpr, createReadCall, createSetCall, createSignalCall } from '../factory.js';
2
+ import { addMissingImports, applyReplacements } from './utilities.js';
3
3
  import { ts } from '@esportsplus/typescript';
4
4
  function classifyReactiveArg(arg) {
5
5
  if (ts.isArrowFunction(arg) || ts.isFunctionExpression(arg)) {
@@ -22,14 +22,14 @@ function findBinding(bindings, name, node) {
22
22
  function findEnclosingScope(node) {
23
23
  let current = node.parent;
24
24
  while (current) {
25
- if (ts.isArrowFunction(current) ||
26
- ts.isBlock(current) ||
27
- ts.isForInStatement(current) ||
28
- ts.isForOfStatement(current) ||
29
- ts.isForStatement(current) ||
25
+ if (ts.isBlock(current) ||
26
+ ts.isSourceFile(current) ||
30
27
  ts.isFunctionDeclaration(current) ||
31
28
  ts.isFunctionExpression(current) ||
32
- ts.isSourceFile(current)) {
29
+ ts.isArrowFunction(current) ||
30
+ ts.isForStatement(current) ||
31
+ ts.isForInStatement(current) ||
32
+ ts.isForOfStatement(current)) {
33
33
  return current;
34
34
  }
35
35
  current = current.parent;
@@ -38,72 +38,64 @@ function findEnclosingScope(node) {
38
38
  }
39
39
  function getCompoundOperator(kind) {
40
40
  if (kind === ts.SyntaxKind.PlusEqualsToken) {
41
- return ts.SyntaxKind.PlusToken;
41
+ return '+';
42
42
  }
43
43
  else if (kind === ts.SyntaxKind.MinusEqualsToken) {
44
- return ts.SyntaxKind.MinusToken;
44
+ return '-';
45
45
  }
46
46
  else if (kind === ts.SyntaxKind.AsteriskEqualsToken) {
47
- return ts.SyntaxKind.AsteriskToken;
47
+ return '*';
48
48
  }
49
49
  else if (kind === ts.SyntaxKind.SlashEqualsToken) {
50
- return ts.SyntaxKind.SlashToken;
50
+ return '/';
51
51
  }
52
52
  else if (kind === ts.SyntaxKind.PercentEqualsToken) {
53
- return ts.SyntaxKind.PercentToken;
53
+ return '%';
54
54
  }
55
55
  else if (kind === ts.SyntaxKind.AsteriskAsteriskEqualsToken) {
56
- return ts.SyntaxKind.AsteriskAsteriskToken;
56
+ return '**';
57
57
  }
58
58
  else if (kind === ts.SyntaxKind.AmpersandEqualsToken) {
59
- return ts.SyntaxKind.AmpersandToken;
59
+ return '&';
60
60
  }
61
61
  else if (kind === ts.SyntaxKind.BarEqualsToken) {
62
- return ts.SyntaxKind.BarToken;
62
+ return '|';
63
63
  }
64
64
  else if (kind === ts.SyntaxKind.CaretEqualsToken) {
65
- return ts.SyntaxKind.CaretToken;
65
+ return '^';
66
66
  }
67
67
  else if (kind === ts.SyntaxKind.LessThanLessThanEqualsToken) {
68
- return ts.SyntaxKind.LessThanLessThanToken;
68
+ return '<<';
69
69
  }
70
70
  else if (kind === ts.SyntaxKind.GreaterThanGreaterThanEqualsToken) {
71
- return ts.SyntaxKind.GreaterThanGreaterThanToken;
71
+ return '>>';
72
72
  }
73
73
  else if (kind === ts.SyntaxKind.GreaterThanGreaterThanGreaterThanEqualsToken) {
74
- return ts.SyntaxKind.GreaterThanGreaterThanGreaterThanToken;
74
+ return '>>>';
75
75
  }
76
76
  else if (kind === ts.SyntaxKind.AmpersandAmpersandEqualsToken) {
77
- return ts.SyntaxKind.AmpersandAmpersandToken;
77
+ return '&&';
78
78
  }
79
79
  else if (kind === ts.SyntaxKind.BarBarEqualsToken) {
80
- return ts.SyntaxKind.BarBarToken;
80
+ return '||';
81
81
  }
82
82
  else if (kind === ts.SyntaxKind.QuestionQuestionEqualsToken) {
83
- return ts.SyntaxKind.QuestionQuestionToken;
83
+ return '??';
84
84
  }
85
85
  else {
86
- return ts.SyntaxKind.PlusToken;
86
+ return '+';
87
87
  }
88
88
  }
89
- function isAssignmentOperator(kind) {
90
- if (kind === ts.SyntaxKind.EqualsToken) {
91
- return 'simple';
92
- }
93
- if (kind >= ts.SyntaxKind.PlusEqualsToken && kind <= ts.SyntaxKind.CaretEqualsToken) {
94
- return 'compound';
95
- }
96
- if (kind === ts.SyntaxKind.AmpersandAmpersandEqualsToken ||
97
- kind === ts.SyntaxKind.BarBarEqualsToken ||
98
- kind === ts.SyntaxKind.QuestionQuestionEqualsToken) {
99
- return 'compound';
89
+ function isInComputedRange(ranges, start, end) {
90
+ for (let i = 0, n = ranges.length; i < n; i++) {
91
+ let r = ranges[i];
92
+ if (start >= r.start && end <= r.end) {
93
+ return true;
94
+ }
100
95
  }
101
96
  return false;
102
97
  }
103
98
  function isInDeclarationInit(node) {
104
- if (!node || !node.parent) {
105
- return false;
106
- }
107
99
  let parent = node.parent;
108
100
  if (ts.isVariableDeclaration(parent) && parent.initializer === node) {
109
101
  return true;
@@ -121,29 +113,54 @@ function isInScope(reference, binding) {
121
113
  return false;
122
114
  }
123
115
  function isReactiveReassignment(node) {
124
- let right = node.right;
125
- if (ts.isCallExpression(right) &&
126
- ts.isIdentifier(right.expression) &&
127
- right.expression.text === 'reactive') {
116
+ let parent = node.parent;
117
+ if (ts.isBinaryExpression(parent) &&
118
+ parent.operatorToken.kind === ts.SyntaxKind.EqualsToken &&
119
+ parent.right === node &&
120
+ ts.isCallExpression(node) &&
121
+ ts.isIdentifier(node.expression) &&
122
+ node.expression.text === 'reactive') {
128
123
  return true;
129
124
  }
130
125
  return false;
131
126
  }
127
+ function isWriteContext(node) {
128
+ let parent = node.parent;
129
+ if (ts.isBinaryExpression(parent) && parent.left === node) {
130
+ let op = parent.operatorToken.kind;
131
+ if (op === ts.SyntaxKind.EqualsToken) {
132
+ return 'simple';
133
+ }
134
+ if (op >= ts.SyntaxKind.PlusEqualsToken && op <= ts.SyntaxKind.CaretEqualsToken) {
135
+ return 'compound';
136
+ }
137
+ if (op === ts.SyntaxKind.AmpersandAmpersandEqualsToken ||
138
+ op === ts.SyntaxKind.BarBarEqualsToken ||
139
+ op === ts.SyntaxKind.QuestionQuestionEqualsToken) {
140
+ return 'compound';
141
+ }
142
+ }
143
+ if (ts.isPostfixUnaryExpression(parent) || ts.isPrefixUnaryExpression(parent)) {
144
+ let op = parent.operator;
145
+ if (op === ts.SyntaxKind.PlusPlusToken || op === ts.SyntaxKind.MinusMinusToken) {
146
+ return 'increment';
147
+ }
148
+ }
149
+ return false;
150
+ }
132
151
  function visit(ctx, node) {
133
- if (ts.isImportDeclaration(node)) {
134
- if (ts.isStringLiteral(node.moduleSpecifier) &&
135
- node.moduleSpecifier.text.includes('@esportsplus/reactivity')) {
136
- let clause = node.importClause;
137
- if (clause?.namedBindings && ts.isNamedImports(clause.namedBindings)) {
138
- for (let i = 0, n = clause.namedBindings.elements.length; i < n; i++) {
139
- if (clause.namedBindings.elements[i].name.text === 'reactive') {
140
- ctx.hasReactiveImport = true;
141
- break;
142
- }
152
+ if (ts.isImportDeclaration(node) &&
153
+ ts.isStringLiteral(node.moduleSpecifier) &&
154
+ node.moduleSpecifier.text.includes('@esportsplus/reactivity')) {
155
+ let clause = node.importClause;
156
+ if (clause?.namedBindings && ts.isNamedImports(clause.namedBindings)) {
157
+ for (let i = 0, n = clause.namedBindings.elements.length; i < n; i++) {
158
+ if (clause.namedBindings.elements[i].name.text === 'reactive') {
159
+ ctx.hasReactiveImport = true;
160
+ break;
143
161
  }
144
162
  }
145
163
  }
146
- return node;
147
164
  }
148
165
  if (ctx.hasReactiveImport &&
149
166
  ts.isCallExpression(node) &&
@@ -153,132 +170,166 @@ function visit(ctx, node) {
153
170
  let arg = node.arguments[0], classification = classifyReactiveArg(arg);
154
171
  if (classification) {
155
172
  let varName = null;
156
- if (node.parent && ts.isVariableDeclaration(node.parent) && ts.isIdentifier(node.parent.name)) {
173
+ if (ts.isVariableDeclaration(node.parent) && ts.isIdentifier(node.parent.name)) {
157
174
  varName = node.parent.name.text;
158
175
  }
159
- else if (node.parent &&
160
- ts.isBinaryExpression(node.parent) &&
176
+ else if (ts.isBinaryExpression(node.parent) &&
161
177
  node.parent.operatorToken.kind === ts.SyntaxKind.EqualsToken &&
162
178
  ts.isIdentifier(node.parent.left)) {
163
179
  varName = node.parent.left.text;
164
180
  }
165
181
  if (varName) {
166
182
  let scope = findEnclosingScope(node);
167
- ctx.bindings.set(varName, classification);
168
183
  ctx.scopedBindings.push({ name: varName, scope, type: classification });
184
+ ctx.bindings.set(varName, classification);
169
185
  }
170
186
  if (classification === 'computed') {
187
+ ctx.computedArgRanges.push({
188
+ end: arg.end,
189
+ start: arg.getStart(ctx.sourceFile)
190
+ });
191
+ let argCtx = {
192
+ argStart: arg.getStart(ctx.sourceFile),
193
+ innerReplacements: [],
194
+ neededImports: ctx.neededImports,
195
+ scopedBindings: ctx.scopedBindings,
196
+ sourceFile: ctx.sourceFile
197
+ };
198
+ visitArg(argCtx, arg);
199
+ let argText = applyReplacements(arg.getText(ctx.sourceFile), argCtx.innerReplacements);
200
+ ctx.replacements.push({
201
+ end: node.end,
202
+ newText: `computed(${argText})`,
203
+ start: node.pos
204
+ });
171
205
  ctx.neededImports.add('computed');
172
- let transformedArg = ts.visitEachChild(arg, n => visitComputedArg(ctx, n), ctx.context);
173
- return createComputedCall(ctx.factory, ctx.ns.reactivity, transformedArg);
174
206
  }
175
207
  else {
208
+ let argText = arg.getText(ctx.sourceFile);
209
+ ctx.replacements.push({
210
+ end: node.end,
211
+ newText: `signal(${argText})`,
212
+ start: node.pos
213
+ });
176
214
  ctx.neededImports.add('signal');
177
- return createSignalCall(ctx.factory, ctx.ns.reactivity, arg);
178
215
  }
179
216
  }
180
217
  }
181
- if (ts.isBinaryExpression(node) && ts.isIdentifier(node.left)) {
182
- let assignType = isAssignmentOperator(node.operatorToken.kind);
183
- if (assignType) {
184
- let binding = findBinding(ctx.scopedBindings, node.left.text, node.left);
185
- if (binding && binding.type !== 'computed' && !isReactiveReassignment(node)) {
186
- ctx.neededImports.add('set');
187
- let factory = ctx.factory, name = node.left.text, signalIdent = factory.createIdentifier(name), transformedRight = ts.visitEachChild(node.right, n => visit(ctx, n), ctx.context);
188
- if (assignType === 'simple') {
189
- return createSetCall(factory, ctx.ns.reactivity, signalIdent, transformedRight);
190
- }
191
- else {
192
- let op = getCompoundOperator(node.operatorToken.kind), valueAccess = factory.createPropertyAccessExpression(signalIdent, 'value');
193
- return createSetCall(factory, ctx.ns.reactivity, signalIdent, factory.createBinaryExpression(valueAccess, op, transformedRight));
194
- }
195
- }
218
+ if (ts.isIdentifier(node) && !isInDeclarationInit(node.parent)) {
219
+ if (ts.isPropertyAccessExpression(node.parent) && node.parent.name === node) {
220
+ ts.forEachChild(node, n => visit(ctx, n));
221
+ return;
196
222
  }
197
- }
198
- if (ts.isPrefixUnaryExpression(node) && ts.isIdentifier(node.operand)) {
199
- let op = node.operator;
200
- if (op === ts.SyntaxKind.PlusPlusToken || op === ts.SyntaxKind.MinusMinusToken) {
201
- let binding = findBinding(ctx.scopedBindings, node.operand.text, node.operand);
202
- if (binding && binding.type !== 'computed') {
203
- ctx.neededImports.add('set');
204
- let delta = op === ts.SyntaxKind.PlusPlusToken ? ts.SyntaxKind.PlusToken : ts.SyntaxKind.MinusToken, factory = ctx.factory, name = node.operand.text, signalIdent = factory.createIdentifier(name), valueAccess = factory.createPropertyAccessExpression(signalIdent, 'value');
205
- if (node.parent && ts.isExpressionStatement(node.parent)) {
206
- return createSetCall(factory, ctx.ns.reactivity, signalIdent, factory.createBinaryExpression(valueAccess, delta, factory.createNumericLiteral(1)));
207
- }
208
- else {
209
- return createCommaExpr(factory, createSetCall(factory, ctx.ns.reactivity, signalIdent, factory.createBinaryExpression(valueAccess, delta, factory.createNumericLiteral(1))), factory.createPropertyAccessExpression(factory.createIdentifier(name), 'value'));
210
- }
211
- }
223
+ let nodeStart = node.getStart(ctx.sourceFile);
224
+ if (isInComputedRange(ctx.computedArgRanges, nodeStart, node.end)) {
225
+ ts.forEachChild(node, n => visit(ctx, n));
226
+ return;
212
227
  }
213
- }
214
- if (ts.isPostfixUnaryExpression(node) && ts.isIdentifier(node.operand)) {
215
- let op = node.operator;
216
- if (op === ts.SyntaxKind.PlusPlusToken || op === ts.SyntaxKind.MinusMinusToken) {
217
- let binding = findBinding(ctx.scopedBindings, node.operand.text, node.operand);
218
- if (binding && binding.type !== 'computed') {
219
- ctx.neededImports.add('set');
220
- let delta = op === ts.SyntaxKind.PlusPlusToken ? ts.SyntaxKind.PlusToken : ts.SyntaxKind.MinusToken, factory = ctx.factory, name = node.operand.text, signalIdent = factory.createIdentifier(name), valueAccess = factory.createPropertyAccessExpression(signalIdent, 'value');
221
- if (node.parent && ts.isExpressionStatement(node.parent)) {
222
- return createSetCall(factory, ctx.ns.reactivity, signalIdent, factory.createBinaryExpression(valueAccess, delta, factory.createNumericLiteral(1)));
228
+ let binding = findBinding(ctx.scopedBindings, node.text, node), name = node.text;
229
+ if (binding) {
230
+ if (!isReactiveReassignment(node.parent) &&
231
+ !(ts.isTypeOfExpression(node.parent) && node.parent.expression === node)) {
232
+ let writeCtx = isWriteContext(node);
233
+ if (writeCtx) {
234
+ if (binding.type !== 'computed') {
235
+ ctx.neededImports.add('set');
236
+ let parent = node.parent;
237
+ if (writeCtx === 'simple' && ts.isBinaryExpression(parent)) {
238
+ let valueText = parent.right.getText(ctx.sourceFile);
239
+ ctx.replacements.push({
240
+ end: parent.end,
241
+ newText: `set(${name}, ${valueText})`,
242
+ start: parent.pos
243
+ });
244
+ }
245
+ else if (writeCtx === 'compound' && ts.isBinaryExpression(parent)) {
246
+ let op = getCompoundOperator(parent.operatorToken.kind), valueText = parent.right.getText(ctx.sourceFile);
247
+ ctx.replacements.push({
248
+ end: parent.end,
249
+ newText: `set(${name}, ${name}.value ${op} ${valueText})`,
250
+ start: parent.pos
251
+ });
252
+ }
253
+ else if (writeCtx === 'increment') {
254
+ let isPrefix = ts.isPrefixUnaryExpression(parent), op = parent.operator, delta = op === ts.SyntaxKind.PlusPlusToken ? '+ 1' : '- 1';
255
+ if (ts.isExpressionStatement(parent.parent)) {
256
+ ctx.replacements.push({
257
+ end: parent.end,
258
+ newText: `set(${name}, ${name}.value ${delta})`,
259
+ start: parent.pos
260
+ });
261
+ }
262
+ else if (isPrefix) {
263
+ ctx.replacements.push({
264
+ end: parent.end,
265
+ newText: `(set(${name}, ${name}.value ${delta}), ${name}.value)`,
266
+ start: parent.pos
267
+ });
268
+ }
269
+ else {
270
+ let tmp = uid('tmp');
271
+ ctx.replacements.push({
272
+ end: parent.end,
273
+ newText: `((${tmp}) => (set(${name}, ${tmp} ${delta}), ${tmp}))(${name}.value)`,
274
+ start: parent.pos
275
+ });
276
+ }
277
+ }
278
+ }
223
279
  }
224
280
  else {
225
- return createPostfixIncrementExpr(factory, ctx.ns.reactivity, uid('tmp'), name, delta);
281
+ ctx.neededImports.add('read');
282
+ ctx.replacements.push({
283
+ end: node.end,
284
+ newText: `read(${name})`,
285
+ start: node.pos
286
+ });
226
287
  }
227
288
  }
228
289
  }
229
290
  }
230
- if (ts.isIdentifier(node) && node.parent && !isInDeclarationInit(node.parent)) {
231
- if (ts.isPropertyAccessExpression(node.parent) && node.parent.name === node) {
232
- return ts.visitEachChild(node, n => visit(ctx, n), ctx.context);
233
- }
234
- if (ts.isBinaryExpression(node.parent) && node.parent.left === node) {
235
- return ts.visitEachChild(node, n => visit(ctx, n), ctx.context);
236
- }
237
- if ((ts.isPrefixUnaryExpression(node.parent) || ts.isPostfixUnaryExpression(node.parent)) &&
238
- node.parent.operand === node) {
239
- return ts.visitEachChild(node, n => visit(ctx, n), ctx.context);
240
- }
241
- if (ts.isTypeOfExpression(node.parent) && node.parent.expression === node) {
242
- return ts.visitEachChild(node, n => visit(ctx, n), ctx.context);
243
- }
244
- let binding = findBinding(ctx.scopedBindings, node.text, node);
245
- if (binding) {
246
- ctx.neededImports.add('read');
247
- return createReadCall(ctx.factory, ctx.ns.reactivity, ctx.factory.createIdentifier(node.text));
248
- }
249
- }
250
- return ts.visitEachChild(node, n => visit(ctx, n), ctx.context);
291
+ ts.forEachChild(node, n => visit(ctx, n));
251
292
  }
252
- function visitComputedArg(ctx, node) {
253
- if (ts.isIdentifier(node) && node.parent) {
293
+ function visitArg(ctx, node) {
294
+ if (ts.isIdentifier(node)) {
254
295
  if (ts.isPropertyAccessExpression(node.parent) && node.parent.name === node) {
255
- return ts.visitEachChild(node, n => visitComputedArg(ctx, n), ctx.context);
296
+ ts.forEachChild(node, n => visitArg(ctx, n));
297
+ return;
256
298
  }
257
299
  if (ts.isCallExpression(node.parent) && node.parent.expression === node) {
258
- return ts.visitEachChild(node, n => visitComputedArg(ctx, n), ctx.context);
300
+ ts.forEachChild(node, n => visitArg(ctx, n));
301
+ return;
259
302
  }
260
303
  let binding = findBinding(ctx.scopedBindings, node.text, node);
261
304
  if (binding) {
262
305
  ctx.neededImports.add('read');
263
- return createReadCall(ctx.factory, ctx.ns.reactivity, ctx.factory.createIdentifier(node.text));
306
+ ctx.innerReplacements.push({
307
+ end: node.end - ctx.argStart,
308
+ newText: `read(${node.text})`,
309
+ start: node.getStart(ctx.sourceFile) - ctx.argStart
310
+ });
264
311
  }
265
312
  }
266
- return ts.visitEachChild(node, n => visitComputedArg(ctx, n), ctx.context);
313
+ ts.forEachChild(node, n => visitArg(ctx, n));
267
314
  }
268
- const createPrimitivesTransformer = (bindings, neededImports, ns) => {
269
- return (context) => {
270
- return (sourceFile) => {
271
- let ctx = {
272
- bindings,
273
- context,
274
- factory: context.factory,
275
- hasReactiveImport: false,
276
- neededImports,
277
- ns,
278
- scopedBindings: []
279
- };
280
- return ts.visitNode(sourceFile, n => visit(ctx, n));
281
- };
315
+ const transformReactivePrimitives = (sourceFile, bindings) => {
316
+ let code = sourceFile.getFullText(), ctx = {
317
+ bindings,
318
+ computedArgRanges: [],
319
+ hasReactiveImport: false,
320
+ neededImports: new Set(),
321
+ replacements: [],
322
+ scopedBindings: [],
323
+ sourceFile
282
324
  };
325
+ visit(ctx, sourceFile);
326
+ if (ctx.replacements.length === 0) {
327
+ return code;
328
+ }
329
+ let result = applyReplacements(code, ctx.replacements);
330
+ if (ctx.neededImports.size > 0) {
331
+ result = addMissingImports(result, ctx.neededImports);
332
+ }
333
+ return result;
283
334
  };
284
- export { createPrimitivesTransformer };
335
+ export { transformReactivePrimitives };
@@ -0,0 +1,8 @@
1
+ import { applyReplacements, type Replacement } from '@esportsplus/typescript/transformer';
2
+ type ExtraImport = {
3
+ module: string;
4
+ specifier: string;
5
+ };
6
+ declare const addMissingImports: (code: string, needed: Set<string>, extraImports?: ExtraImport[]) => string;
7
+ export { addMissingImports, applyReplacements };
8
+ export type { ExtraImport, Replacement };
@@ -0,0 +1,27 @@
1
+ import { addImport, applyReplacements } from '@esportsplus/typescript/transformer';
2
+ const addMissingImports = (code, needed, extraImports) => {
3
+ let extraSpecifiers = new Set(), reactivitySpecifiers = [];
4
+ if (extraImports) {
5
+ for (let i = 0, n = extraImports.length; i < n; i++) {
6
+ extraSpecifiers.add(extraImports[i].specifier);
7
+ }
8
+ }
9
+ for (let imp of needed) {
10
+ if (!extraSpecifiers.has(imp)) {
11
+ reactivitySpecifiers.push(imp);
12
+ }
13
+ }
14
+ if (reactivitySpecifiers.length > 0) {
15
+ code = addImport(code, '@esportsplus/reactivity', reactivitySpecifiers);
16
+ }
17
+ if (extraImports) {
18
+ for (let i = 0, n = extraImports.length; i < n; i++) {
19
+ let extra = extraImports[i];
20
+ if (needed.has(extra.specifier)) {
21
+ code = addImport(code, extra.module, [extra.specifier]);
22
+ }
23
+ }
24
+ }
25
+ return code;
26
+ };
27
+ export { addMissingImports, applyReplacements };
package/build/types.d.ts CHANGED
@@ -1,12 +1,8 @@
1
1
  import { COMPUTED, SIGNAL, STATE_CHECK, STATE_DIRTY, STATE_IN_HEAP, STATE_NONE, STATE_RECOMPUTING } from './constants.js';
2
2
  import { ReactiveArray, ReactiveObject } from './reactive/index.js';
3
+ import { ts } from '@esportsplus/typescript';
3
4
  type BindingType = 'array' | 'computed' | 'object' | 'signal';
4
5
  type Bindings = Map<string, BindingType>;
5
- interface Namespaces {
6
- array: string;
7
- constants: string;
8
- reactivity: string;
9
- }
10
6
  interface Computed<T> {
11
7
  cleanup: VoidFunction | VoidFunction[] | null;
12
8
  deps: Link | null;
@@ -35,4 +31,9 @@ type Signal<T> = {
35
31
  type: typeof SIGNAL;
36
32
  value: T;
37
33
  };
38
- export type { BindingType, Bindings, Computed, Link, Namespaces, ReactiveArray, ReactiveObject, Signal };
34
+ interface TransformResult {
35
+ code: string;
36
+ sourceFile: ts.SourceFile;
37
+ transformed: boolean;
38
+ }
39
+ export type { BindingType, Bindings, Computed, Link, ReactiveArray, ReactiveObject, Signal, TransformResult };
package/package.json CHANGED
@@ -36,7 +36,7 @@
36
36
  },
37
37
  "type": "module",
38
38
  "types": "build/index.d.ts",
39
- "version": "0.24.5",
39
+ "version": "0.25.0",
40
40
  "scripts": {
41
41
  "build": "tsc",
42
42
  "build:test": "pnpm build && vite build --config test/vite.config.ts",
@@ -1,3 +1,4 @@
1
+ import { Prettify } from '@esportsplus/utilities';
1
2
  import { REACTIVE_OBJECT } from '~/constants';
2
3
  import { ReactiveArray } from './array';
3
4
 
@@ -5,10 +6,28 @@ import { ReactiveArray } from './array';
5
6
  // Branded type to prevent assignment to computed values
6
7
  declare const READONLY: unique symbol;
7
8
 
8
- type ReactiveObject<T extends Record<PropertyKey, unknown>> = T & {
9
- [REACTIVE_OBJECT]: true;
10
- dispose(): void;
11
- };
9
+ type Infer<T> =
10
+ T extends (...args: unknown[]) => Promise<infer R>
11
+ ? R | undefined
12
+ : T extends (...args: any[]) => infer R
13
+ ? R
14
+ : T extends (infer U)[]
15
+ ? ReactiveArray<U>
16
+ : T extends ReactiveObject<any>
17
+ ? T
18
+ : T extends Record<PropertyKey, unknown>
19
+ ? { [K in keyof T]: T[K] }
20
+ : T;
21
+
22
+ type ReactiveObject<T> =
23
+ T extends Record<PropertyKey, unknown>
24
+ ? Prettify<{ [K in keyof T]: Infer<T[K]> } & {
25
+ [REACTIVE_OBJECT]: true;
26
+ dispose: VoidFunction
27
+ }>
28
+ : T extends (infer U)[]
29
+ ? ReactiveArray<U>
30
+ : never;
12
31
 
13
32
  type ReactiveObjectGuard<T> = T extends { dispose: any } ? { never: '[ dispose ] is a reserved key' } : T;
14
33
 
@@ -20,8 +39,7 @@ function reactive<T extends Record<PropertyKey, any>>(_input: ReactiveObjectGuar
20
39
  // Array literal → existing ReactiveArray behavior
21
40
  function reactive<T>(_input: T[]): ReactiveArray<T>;
22
41
  // Everything else → passthrough type (allows assignment)
23
- function reactive<T>(_input: T): T;
24
- function reactive(_input: unknown): unknown {
42
+ function reactive<T>(_input: T): T {
25
43
  throw new Error(
26
44
  '@esportsplus/reactivity: reactive() called at runtime. ' +
27
45
  'Ensure vite-plugin-reactivity-compile is configured.'