@esportsplus/reactivity 0.24.4 → 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,14 +113,41 @@ 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
152
  if (ts.isImportDeclaration(node) &&
134
153
  ts.isStringLiteral(node.moduleSpecifier) &&
@@ -151,132 +170,166 @@ function visit(ctx, node) {
151
170
  let arg = node.arguments[0], classification = classifyReactiveArg(arg);
152
171
  if (classification) {
153
172
  let varName = null;
154
- if (node.parent && ts.isVariableDeclaration(node.parent) && ts.isIdentifier(node.parent.name)) {
173
+ if (ts.isVariableDeclaration(node.parent) && ts.isIdentifier(node.parent.name)) {
155
174
  varName = node.parent.name.text;
156
175
  }
157
- else if (node.parent &&
158
- ts.isBinaryExpression(node.parent) &&
176
+ else if (ts.isBinaryExpression(node.parent) &&
159
177
  node.parent.operatorToken.kind === ts.SyntaxKind.EqualsToken &&
160
178
  ts.isIdentifier(node.parent.left)) {
161
179
  varName = node.parent.left.text;
162
180
  }
163
181
  if (varName) {
164
182
  let scope = findEnclosingScope(node);
165
- ctx.bindings.set(varName, classification);
166
183
  ctx.scopedBindings.push({ name: varName, scope, type: classification });
184
+ ctx.bindings.set(varName, classification);
167
185
  }
168
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
+ });
169
205
  ctx.neededImports.add('computed');
170
- let transformedArg = ts.visitEachChild(arg, n => visitComputedArg(ctx, n), ctx.context);
171
- return createComputedCall(ctx.factory, ctx.ns.reactivity, transformedArg);
172
206
  }
173
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
+ });
174
214
  ctx.neededImports.add('signal');
175
- return createSignalCall(ctx.factory, ctx.ns.reactivity, arg);
176
215
  }
177
216
  }
178
217
  }
179
- if (ts.isBinaryExpression(node) && ts.isIdentifier(node.left)) {
180
- let assignType = isAssignmentOperator(node.operatorToken.kind);
181
- if (assignType) {
182
- let binding = findBinding(ctx.scopedBindings, node.left.text, node.left);
183
- if (binding && binding.type !== 'computed' && !isReactiveReassignment(node)) {
184
- ctx.neededImports.add('set');
185
- let factory = ctx.factory, name = node.left.text, signalIdent = factory.createIdentifier(name), transformedRight = ts.visitEachChild(node.right, n => visit(ctx, n), ctx.context);
186
- if (assignType === 'simple') {
187
- return createSetCall(factory, ctx.ns.reactivity, signalIdent, transformedRight);
188
- }
189
- else {
190
- let op = getCompoundOperator(node.operatorToken.kind), valueAccess = factory.createPropertyAccessExpression(signalIdent, 'value');
191
- return createSetCall(factory, ctx.ns.reactivity, signalIdent, factory.createBinaryExpression(valueAccess, op, transformedRight));
192
- }
193
- }
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;
194
222
  }
195
- }
196
- if (ts.isPrefixUnaryExpression(node) && ts.isIdentifier(node.operand)) {
197
- let op = node.operator;
198
- if (op === ts.SyntaxKind.PlusPlusToken || op === ts.SyntaxKind.MinusMinusToken) {
199
- let binding = findBinding(ctx.scopedBindings, node.operand.text, node.operand);
200
- if (binding && binding.type !== 'computed') {
201
- ctx.neededImports.add('set');
202
- 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');
203
- if (node.parent && ts.isExpressionStatement(node.parent)) {
204
- return createSetCall(factory, ctx.ns.reactivity, signalIdent, factory.createBinaryExpression(valueAccess, delta, factory.createNumericLiteral(1)));
205
- }
206
- else {
207
- return createCommaExpr(factory, createSetCall(factory, ctx.ns.reactivity, signalIdent, factory.createBinaryExpression(valueAccess, delta, factory.createNumericLiteral(1))), factory.createPropertyAccessExpression(factory.createIdentifier(name), 'value'));
208
- }
209
- }
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;
210
227
  }
211
- }
212
- if (ts.isPostfixUnaryExpression(node) && ts.isIdentifier(node.operand)) {
213
- let op = node.operator;
214
- if (op === ts.SyntaxKind.PlusPlusToken || op === ts.SyntaxKind.MinusMinusToken) {
215
- let binding = findBinding(ctx.scopedBindings, node.operand.text, node.operand);
216
- if (binding && binding.type !== 'computed') {
217
- ctx.neededImports.add('set');
218
- 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');
219
- if (node.parent && ts.isExpressionStatement(node.parent)) {
220
- 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
+ }
221
279
  }
222
280
  else {
223
- 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
+ });
224
287
  }
225
288
  }
226
289
  }
227
290
  }
228
- if (ts.isIdentifier(node) && node.parent && !isInDeclarationInit(node.parent)) {
229
- if (ts.isPropertyAccessExpression(node.parent) && node.parent.name === node) {
230
- return ts.visitEachChild(node, n => visit(ctx, n), ctx.context);
231
- }
232
- if (ts.isBinaryExpression(node.parent) && node.parent.left === node) {
233
- return ts.visitEachChild(node, n => visit(ctx, n), ctx.context);
234
- }
235
- if ((ts.isPrefixUnaryExpression(node.parent) || ts.isPostfixUnaryExpression(node.parent)) &&
236
- node.parent.operand === node) {
237
- return ts.visitEachChild(node, n => visit(ctx, n), ctx.context);
238
- }
239
- if (ts.isTypeOfExpression(node.parent) && node.parent.expression === node) {
240
- return ts.visitEachChild(node, n => visit(ctx, n), ctx.context);
241
- }
242
- let binding = findBinding(ctx.scopedBindings, node.text, node);
243
- if (binding) {
244
- ctx.neededImports.add('read');
245
- return createReadCall(ctx.factory, ctx.ns.reactivity, ctx.factory.createIdentifier(node.text));
246
- }
247
- }
248
- return ts.visitEachChild(node, n => visit(ctx, n), ctx.context);
291
+ ts.forEachChild(node, n => visit(ctx, n));
249
292
  }
250
- function visitComputedArg(ctx, node) {
251
- if (ts.isIdentifier(node) && node.parent) {
293
+ function visitArg(ctx, node) {
294
+ if (ts.isIdentifier(node)) {
252
295
  if (ts.isPropertyAccessExpression(node.parent) && node.parent.name === node) {
253
- return ts.visitEachChild(node, n => visitComputedArg(ctx, n), ctx.context);
296
+ ts.forEachChild(node, n => visitArg(ctx, n));
297
+ return;
254
298
  }
255
299
  if (ts.isCallExpression(node.parent) && node.parent.expression === node) {
256
- return ts.visitEachChild(node, n => visitComputedArg(ctx, n), ctx.context);
300
+ ts.forEachChild(node, n => visitArg(ctx, n));
301
+ return;
257
302
  }
258
303
  let binding = findBinding(ctx.scopedBindings, node.text, node);
259
304
  if (binding) {
260
305
  ctx.neededImports.add('read');
261
- 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
+ });
262
311
  }
263
312
  }
264
- return ts.visitEachChild(node, n => visitComputedArg(ctx, n), ctx.context);
313
+ ts.forEachChild(node, n => visitArg(ctx, n));
265
314
  }
266
- const createPrimitivesTransformer = (bindings, neededImports, ns) => {
267
- return (context) => {
268
- return (sourceFile) => {
269
- let ctx = {
270
- bindings,
271
- context,
272
- factory: context.factory,
273
- hasReactiveImport: false,
274
- neededImports,
275
- ns,
276
- scopedBindings: []
277
- };
278
- return ts.visitNode(sourceFile, n => visit(ctx, n));
279
- };
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
280
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;
281
334
  };
282
- 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.4",
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.'