@esportsplus/reactivity 0.22.3 → 0.23.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 (56) hide show
  1. package/build/index.d.ts +1 -1
  2. package/build/index.js +1 -1
  3. package/build/reactive/array.d.ts +3 -0
  4. package/build/reactive/array.js +32 -2
  5. package/build/reactive/index.d.ts +17 -14
  6. package/build/reactive/index.js +7 -25
  7. package/build/system.js +1 -1
  8. package/build/transformer/detector.d.ts +2 -0
  9. package/build/transformer/detector.js +38 -0
  10. package/build/transformer/index.d.ts +10 -0
  11. package/build/transformer/index.js +55 -0
  12. package/build/transformer/plugins/esbuild.d.ts +5 -0
  13. package/build/transformer/plugins/esbuild.js +31 -0
  14. package/build/transformer/plugins/tsc.d.ts +3 -0
  15. package/build/transformer/plugins/tsc.js +4 -0
  16. package/build/transformer/plugins/vite.d.ts +5 -0
  17. package/build/transformer/plugins/vite.js +28 -0
  18. package/build/transformer/transforms/auto-dispose.d.ts +3 -0
  19. package/build/transformer/transforms/auto-dispose.js +119 -0
  20. package/build/transformer/transforms/reactive-array.d.ts +4 -0
  21. package/build/transformer/transforms/reactive-array.js +93 -0
  22. package/build/transformer/transforms/reactive-object.d.ts +4 -0
  23. package/build/transformer/transforms/reactive-object.js +164 -0
  24. package/build/transformer/transforms/reactive-primitives.d.ts +4 -0
  25. package/build/transformer/transforms/reactive-primitives.js +335 -0
  26. package/build/transformer/transforms/utilities.d.ts +8 -0
  27. package/build/transformer/transforms/utilities.js +73 -0
  28. package/build/types.d.ts +14 -4
  29. package/package.json +30 -3
  30. package/readme.md +276 -2
  31. package/src/constants.ts +1 -1
  32. package/src/index.ts +1 -1
  33. package/src/reactive/array.ts +49 -2
  34. package/src/reactive/index.ts +33 -57
  35. package/src/system.ts +14 -5
  36. package/src/transformer/detector.ts +65 -0
  37. package/src/transformer/index.ts +78 -0
  38. package/src/transformer/plugins/esbuild.ts +47 -0
  39. package/src/transformer/plugins/tsc.ts +8 -0
  40. package/src/transformer/plugins/vite.ts +39 -0
  41. package/src/transformer/transforms/auto-dispose.ts +191 -0
  42. package/src/transformer/transforms/reactive-array.ts +143 -0
  43. package/src/transformer/transforms/reactive-object.ts +253 -0
  44. package/src/transformer/transforms/reactive-primitives.ts +461 -0
  45. package/src/transformer/transforms/utilities.ts +119 -0
  46. package/src/types.ts +24 -5
  47. package/test/arrays.ts +146 -0
  48. package/test/effects.ts +168 -0
  49. package/test/index.ts +8 -0
  50. package/test/nested.ts +201 -0
  51. package/test/objects.ts +106 -0
  52. package/test/primitives.ts +171 -0
  53. package/test/vite.config.ts +40 -0
  54. package/build/reactive/object.d.ts +0 -7
  55. package/build/reactive/object.js +0 -79
  56. package/src/reactive/object.ts +0 -116
package/readme.md CHANGED
@@ -1,2 +1,276 @@
1
- https://github.com/milomg/r3
2
- https://github.com/stackblitz/alien-signals
1
+ # @esportsplus/reactivity
2
+
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.
4
+
5
+ ## Installation
6
+
7
+ ```bash
8
+ pnpm add @esportsplus/reactivity
9
+ ```
10
+
11
+ ## Core Concepts
12
+
13
+ The library provides a `reactive()` function that acts as a compile-time macro. At build time, transformer plugins convert `reactive()` calls into optimized signal/computed primitives.
14
+
15
+ ### Reactive Primitives
16
+
17
+ ```typescript
18
+ import { reactive, effect } from '@esportsplus/reactivity';
19
+
20
+ // Signals - reactive values
21
+ let count = reactive(0);
22
+ let name = reactive('John');
23
+
24
+ // Read values naturally
25
+ console.log(count); // 0
26
+ console.log(name); // 'John'
27
+
28
+ // Write with simple assignment
29
+ count = 10;
30
+ name = 'Jane';
31
+
32
+ // Compound assignments work
33
+ count += 5;
34
+ count++;
35
+
36
+ // Computed values - derived from other reactive values
37
+ let doubled = reactive(() => count * 2);
38
+ console.log(doubled); // 30
39
+ ```
40
+
41
+ ### Reactive Objects
42
+
43
+ ```typescript
44
+ import { reactive } from '@esportsplus/reactivity';
45
+
46
+ let user = reactive({
47
+ age: 25,
48
+ name: 'John',
49
+ // Computed properties are arrow functions
50
+ canVote: () => user.age >= 18
51
+ });
52
+
53
+ console.log(user.name); // 'John'
54
+ console.log(user.canVote); // true
55
+
56
+ user.age = 17;
57
+ console.log(user.canVote); // false
58
+
59
+ // Cleanup resources
60
+ user.dispose();
61
+ ```
62
+
63
+ ### Reactive Arrays
64
+
65
+ ```typescript
66
+ import { reactive } from '@esportsplus/reactivity';
67
+
68
+ let state = reactive({
69
+ items: [1, 2, 3],
70
+ total: () => state.items.reduce((a, b) => a + b, 0)
71
+ });
72
+
73
+ console.log(state.total); // 6
74
+
75
+ state.items.push(4, 5);
76
+ console.log(state.total); // 15
77
+
78
+ // Listen to array events
79
+ state.items.on('push', ({ items }) => {
80
+ console.log('Added:', items);
81
+ });
82
+ ```
83
+
84
+ ### Effects
85
+
86
+ ```typescript
87
+ import { effect, reactive } from '@esportsplus/reactivity';
88
+
89
+ let count = reactive(0);
90
+
91
+ let cleanup = effect(() => {
92
+ console.log('Count is:', count);
93
+ });
94
+
95
+ count = 1; // logs: Count is: 1
96
+ count = 2; // logs: Count is: 2
97
+
98
+ cleanup(); // stops the effect
99
+ ```
100
+
101
+ ## Transformer Plugins
102
+
103
+ The library requires a build-time transformer to convert `reactive()` calls into optimized code. Three plugins are available:
104
+
105
+ ### Vite Plugin
106
+
107
+ ```typescript
108
+ // vite.config.ts
109
+ import { defineConfig } from 'vite';
110
+ import { plugin as reactivity } from '@esportsplus/reactivity/plugins/vite';
111
+
112
+ export default defineConfig({
113
+ plugins: [
114
+ reactivity()
115
+ ]
116
+ });
117
+ ```
118
+
119
+ ### esbuild Plugin
120
+
121
+ ```typescript
122
+ import esbuild from 'esbuild';
123
+ import reactivity from '@esportsplus/reactivity/plugins/esbuild';
124
+
125
+ await esbuild.build({
126
+ entryPoints: ['src/index.ts'],
127
+ bundle: true,
128
+ outfile: 'dist/index.js',
129
+ plugins: [
130
+ reactivity()
131
+ ]
132
+ });
133
+ ```
134
+
135
+ ### TypeScript Custom Transformer
136
+
137
+ For direct TypeScript compilation using `ttsc` or `ts-patch`:
138
+
139
+ ```json
140
+ // tsconfig.json
141
+ {
142
+ "compilerOptions": {
143
+ "plugins": [
144
+ { "transform": "@esportsplus/reactivity/plugins/tsc" }
145
+ ]
146
+ }
147
+ }
148
+ ```
149
+
150
+ ## Plugin Options
151
+
152
+ All plugins accept the same options:
153
+
154
+ ```typescript
155
+ interface TransformOptions {
156
+ // Inject automatic disposal tracking (experimental)
157
+ autoDispose?: boolean;
158
+ }
159
+ ```
160
+
161
+ Example with options:
162
+
163
+ ```typescript
164
+ // vite.config.ts
165
+ import { plugin as reactivity } from '@esportsplus/reactivity/plugins/vite';
166
+
167
+ export default defineConfig({
168
+ plugins: [
169
+ reactivity({ autoDispose: true })
170
+ ]
171
+ });
172
+ ```
173
+
174
+ ## How It Works
175
+
176
+ The transformer converts your code at compile time:
177
+
178
+ **Input:**
179
+ ```typescript
180
+ let count = reactive(0);
181
+ let doubled = reactive(() => count * 2);
182
+
183
+ count = 5;
184
+ console.log(doubled);
185
+ ```
186
+
187
+ **Output:**
188
+ ```typescript
189
+ import { computed, read, set, signal } from '@esportsplus/reactivity';
190
+
191
+ let count = signal(0);
192
+ let doubled = computed(() => read(count) * 2);
193
+
194
+ set(count, 5);
195
+ console.log(read(doubled));
196
+ ```
197
+
198
+ Reactive objects are transformed into classes:
199
+
200
+ **Input:**
201
+ ```typescript
202
+ let user = reactive({
203
+ name: 'John',
204
+ greeting: () => `Hello, ${user.name}`
205
+ });
206
+ ```
207
+
208
+ **Output:**
209
+ ```typescript
210
+ class ReactiveObject_1 {
211
+ #name = signal('John');
212
+ #greeting = null;
213
+
214
+ get name() { return read(this.#name); }
215
+ set name(v) { set(this.#name, v); }
216
+ get greeting() { return read(this.#greeting ??= computed(() => `Hello, ${this.name}`)); }
217
+
218
+ dispose() {
219
+ if (this.#greeting) dispose(this.#greeting);
220
+ }
221
+ }
222
+
223
+ let user = new ReactiveObject_1();
224
+ ```
225
+
226
+ ## API Reference
227
+
228
+ ### Core Functions
229
+
230
+ | Function | Description |
231
+ |----------|-------------|
232
+ | `reactive(value)` | Creates a signal from a primitive value |
233
+ | `reactive(() => expr)` | Creates a computed value |
234
+ | `reactive({...})` | Creates a reactive object with signals and computeds |
235
+ | `reactive([...])` | Creates a reactive array |
236
+ | `effect(fn)` | Runs a function that re-executes when dependencies change |
237
+ | `root(fn)` | Creates an untracked scope for effects |
238
+ | `onCleanup(fn)` | Registers a cleanup function for the current effect |
239
+
240
+ ### Low-Level Functions
241
+
242
+ These are typically only used by the transformer output:
243
+
244
+ | Function | Description |
245
+ |----------|-------------|
246
+ | `signal(value)` | Creates a raw signal |
247
+ | `computed(fn)` | Creates a raw computed |
248
+ | `read(node)` | Reads a signal or computed value |
249
+ | `set(signal, value)` | Sets a signal value |
250
+ | `dispose(computed)` | Disposes a computed and its dependencies |
251
+
252
+ ### Type Guards
253
+
254
+ | Function | Description |
255
+ |----------|-------------|
256
+ | `isSignal(value)` | Checks if value is a Signal |
257
+ | `isComputed(value)` | Checks if value is a Computed |
258
+
259
+ ## ReactiveArray Events
260
+
261
+ | Event | Payload | Description |
262
+ |-------|---------|-------------|
263
+ | `clear` | `undefined` | Array was cleared |
264
+ | `concat` | `{ items: T[] }` | Items were concatenated |
265
+ | `pop` | `{ item: T }` | Item was popped |
266
+ | `push` | `{ items: T[] }` | Items were pushed |
267
+ | `reverse` | `undefined` | Array was reversed |
268
+ | `set` | `{ index, item }` | Item was set at index |
269
+ | `shift` | `{ item: T }` | Item was shifted |
270
+ | `sort` | `{ order: number[] }` | Array was sorted |
271
+ | `splice` | `{ start, deleteCount, items }` | Array was spliced |
272
+ | `unshift` | `{ items: T[] }` | Items were unshifted |
273
+
274
+ ## License
275
+
276
+ MIT
package/src/constants.ts CHANGED
@@ -44,4 +44,4 @@ export {
44
44
  STATE_IN_HEAP,
45
45
  STATE_NONE,
46
46
  STATE_RECOMPUTING
47
- };
47
+ };
package/src/index.ts CHANGED
@@ -1,4 +1,4 @@
1
- export { default as reactive } from './reactive';
2
1
  export { STABILIZER_IDLE, STABILIZER_RESCHEDULE, STABILIZER_RUNNING, STABILIZER_SCHEDULED } from './constants';
2
+ export { default as reactive } from './reactive';
3
3
  export * from './system';
4
4
  export * from './types';
@@ -1,6 +1,7 @@
1
1
  import { isArray } from '@esportsplus/utilities';
2
- import { REACTIVE_ARRAY } from '~/constants';
3
- import { isReactiveObject } from './object';
2
+ import { read, set, signal } from '~/system';
3
+ import { REACTIVE_ARRAY, REACTIVE_OBJECT } from '~/constants';
4
+ import type { Signal } from '~/types';
4
5
 
5
6
 
6
7
  type Events<T> = {
@@ -43,17 +44,46 @@ type Listener<V> = {
43
44
  type Listeners = Record<string, (Listener<any> | null)[]>;
44
45
 
45
46
 
47
+ function isReactiveObject(value: unknown): value is { dispose(): void } {
48
+ return value !== null && typeof value === 'object' && (value as any)[REACTIVE_OBJECT] === true;
49
+ }
50
+
51
+
46
52
  class ReactiveArray<T> extends Array<T> {
53
+ private _length: Signal<number>;
54
+
47
55
  listeners: Listeners = {};
48
56
 
49
57
 
50
58
  constructor(...items: T[]) {
51
59
  super(...items);
60
+ this._length = signal(items.length);
61
+ }
62
+
63
+
64
+ $length() {
65
+ return read(this._length);
52
66
  }
53
67
 
68
+ $set(i: number, value: T) {
69
+ let prev = this[i];
70
+
71
+ if (prev === value) {
72
+ return;
73
+ }
74
+
75
+ this[i] = value;
76
+
77
+ if (i >= super.length) {
78
+ set(this._length, i + 1);
79
+ }
80
+
81
+ this.dispatch('set', { index: i, item: value });
82
+ }
54
83
 
55
84
  clear() {
56
85
  this.dispose();
86
+ set(this._length, 0);
57
87
  this.dispatch('clear');
58
88
  }
59
89
 
@@ -78,6 +108,7 @@ class ReactiveArray<T> extends Array<T> {
78
108
  }
79
109
 
80
110
  if (added.length) {
111
+ set(this._length, super.length);
81
112
  this.dispatch('concat', { items: added });
82
113
  }
83
114
 
@@ -125,6 +156,8 @@ class ReactiveArray<T> extends Array<T> {
125
156
  item.dispose();
126
157
  }
127
158
  }
159
+
160
+ set(this._length, 0);
128
161
  }
129
162
 
130
163
  on<K extends keyof Events<T>>(event: K, listener: Listener<Events<T>[K]>) {
@@ -164,9 +197,12 @@ class ReactiveArray<T> extends Array<T> {
164
197
  let item = super.pop();
165
198
 
166
199
  if (item !== undefined) {
200
+ set(this._length, super.length);
201
+
167
202
  if (isReactiveObject(item)) {
168
203
  item.dispose();
169
204
  }
205
+
170
206
  this.dispatch('pop', { item });
171
207
  }
172
208
 
@@ -174,8 +210,13 @@ class ReactiveArray<T> extends Array<T> {
174
210
  }
175
211
 
176
212
  push(...items: T[]) {
213
+ if (!items.length) {
214
+ return super.length;
215
+ }
216
+
177
217
  let length = super.push(...items);
178
218
 
219
+ set(this._length, length);
179
220
  this.dispatch('push', { items });
180
221
 
181
222
  return length;
@@ -192,9 +233,12 @@ class ReactiveArray<T> extends Array<T> {
192
233
  let item = super.shift();
193
234
 
194
235
  if (item !== undefined) {
236
+ set(this._length, super.length);
237
+
195
238
  if (isReactiveObject(item)) {
196
239
  item.dispose();
197
240
  }
241
+
198
242
  this.dispatch('shift', { item });
199
243
  }
200
244
 
@@ -250,6 +294,8 @@ class ReactiveArray<T> extends Array<T> {
250
294
  let removed = super.splice(start, deleteCount, ...items);
251
295
 
252
296
  if (items.length > 0 || removed.length > 0) {
297
+ set(this._length, super.length);
298
+
253
299
  for (let i = 0, n = removed.length; i < n; i++) {
254
300
  let item = removed[i];
255
301
 
@@ -267,6 +313,7 @@ class ReactiveArray<T> extends Array<T> {
267
313
  unshift(...items: T[]) {
268
314
  let length = super.unshift(...items);
269
315
 
316
+ set(this._length, length);
270
317
  this.dispatch('unshift', { items });
271
318
 
272
319
  return length;
@@ -1,58 +1,34 @@
1
- import { isArray, isObject, Prettify } from '@esportsplus/utilities';
2
- import { onCleanup, root } from '~/system';
1
+ import { REACTIVE_OBJECT } from '~/constants';
3
2
  import { ReactiveArray } from './array';
4
- import { ReactiveObject } from './object';
5
-
6
-
7
- type API<T> =
8
- T extends Record<PropertyKey, unknown>
9
- ? Prettify<{ [K in keyof T]: Infer<T[K]> } & { dispose: VoidFunction } >
10
- : T extends (infer U)[]
11
- ? ReactiveArray<U>
12
- : never;
13
-
14
- type Guard<T> = T extends { dispose: any } ? { never: '[ dispose ] are reserved keys' } : T;
15
-
16
- type Infer<T> =
17
- T extends (...args: unknown[]) => Promise<infer R>
18
- ? R | undefined
19
- : T extends (...args: any[]) => infer R
20
- ? R
21
- : T extends (infer U)[]
22
- ? ReactiveArray<U>
23
- : T extends ReactiveObject<any>
24
- ? T
25
- : T extends Record<PropertyKey, unknown>
26
- ? { [K in keyof T]: T[K] }
27
- : T;
28
-
29
-
30
- export default <T extends Record<PropertyKey, any> | unknown[]>(input: Guard<T>): API<T> => {
31
- let dispose = false,
32
- value = root(() => {
33
- let response: API<T> | undefined;
34
-
35
- if (isObject(input)) {
36
- response = new ReactiveObject(input) as any as API<T>;
37
- }
38
- else if (isArray(input)) {
39
- response = new ReactiveArray(...input) as API<T>;
40
- }
41
-
42
- if (response) {
43
- if (root.disposables) {
44
- dispose = true;
45
- }
46
-
47
- return response;
48
- }
49
-
50
- throw new Error(`@esportsplus/reactivity: 'reactive' received invalid input - ${JSON.stringify(input)}`);
51
- });
52
-
53
- if (dispose) {
54
- onCleanup(() => value.dispose());
55
- }
56
-
57
- return value;
58
- };
3
+
4
+
5
+ // Branded type to prevent assignment to computed values
6
+ declare const READONLY: unique symbol;
7
+
8
+ type ReactiveObject<T extends Record<PropertyKey, unknown>> = T & {
9
+ [REACTIVE_OBJECT]: true;
10
+ dispose(): void;
11
+ };
12
+
13
+ type ReactiveObjectGuard<T> = T extends { dispose: any } ? { never: '[ dispose ] is a reserved key' } : T;
14
+
15
+
16
+ // Function input branded return type (prevents assignment)
17
+ function reactive<T extends () => unknown>(_input: T): ReturnType<T> & { readonly [READONLY]: true };
18
+ // Object literal existing ReactiveObject behavior
19
+ function reactive<T extends Record<PropertyKey, any>>(_input: ReactiveObjectGuard<T>): ReactiveObject<T>;
20
+ // Array literal existing ReactiveArray behavior
21
+ function reactive<T>(_input: T[]): ReactiveArray<T>;
22
+ // Everything else → passthrough type (allows assignment)
23
+ function reactive<T>(_input: T): T;
24
+ function reactive(_input: unknown): unknown {
25
+ throw new Error(
26
+ '@esportsplus/reactivity: reactive() called at runtime. ' +
27
+ 'Ensure vite-plugin-reactivity-compile is configured.'
28
+ );
29
+ }
30
+
31
+
32
+ export default reactive;
33
+ export { reactive, ReactiveArray };
34
+ export type { ReactiveObject };
package/src/system.ts CHANGED
@@ -1,10 +1,19 @@
1
- import { isObject } from '@esportsplus/utilities';
2
1
  import {
3
- COMPUTED, SIGNAL,
4
- STABILIZER_IDLE, STABILIZER_RESCHEDULE, STABILIZER_RUNNING, STABILIZER_SCHEDULED,
5
- STATE_CHECK, STATE_DIRTY, STATE_IN_HEAP, STATE_NONE, STATE_NOTIFY_MASK, STATE_RECOMPUTING
2
+ COMPUTED,
3
+ SIGNAL,
4
+ STABILIZER_IDLE,
5
+ STABILIZER_RESCHEDULE,
6
+ STABILIZER_RUNNING,
7
+ STABILIZER_SCHEDULED,
8
+ STATE_CHECK,
9
+ STATE_DIRTY,
10
+ STATE_IN_HEAP,
11
+ STATE_NONE,
12
+ STATE_NOTIFY_MASK,
13
+ STATE_RECOMPUTING
6
14
  } from './constants';
7
15
  import { Computed, Link, Signal } from './types';
16
+ import { isObject } from '@esportsplus/utilities';
8
17
 
9
18
 
10
19
  let depth = 0,
@@ -558,4 +567,4 @@ export {
558
567
  read, root,
559
568
  set, signal
560
569
  };
561
- export type { Computed, Signal };
570
+ export type { Computed, Signal };
@@ -0,0 +1,65 @@
1
+ import { mightNeedTransform as checkTransform } from '@esportsplus/typescript/transformer';
2
+ import ts from 'typescript';
3
+
4
+
5
+ interface DetectContext {
6
+ hasImport: boolean;
7
+ hasUsage: boolean;
8
+ }
9
+
10
+
11
+ const REACTIVE_REGEX = /\breactive\b/;
12
+
13
+
14
+ function visit(ctx: DetectContext, node: ts.Node): void {
15
+ if (ctx.hasImport && ctx.hasUsage) {
16
+ return;
17
+ }
18
+
19
+ if (
20
+ ts.isImportDeclaration(node) &&
21
+ node.importClause?.namedBindings &&
22
+ ts.isNamedImports(node.importClause.namedBindings)
23
+ ) {
24
+ let elements = node.importClause.namedBindings.elements;
25
+
26
+ for (let i = 0, n = elements.length; i < n; i++) {
27
+ let el = elements[i],
28
+ name = el.propertyName?.text ?? el.name.text;
29
+
30
+ if (name === 'reactive') {
31
+ ctx.hasImport = true;
32
+ break;
33
+ }
34
+ }
35
+ }
36
+
37
+ if (
38
+ ts.isCallExpression(node) &&
39
+ ts.isIdentifier(node.expression) &&
40
+ node.expression.text === 'reactive'
41
+ ) {
42
+ ctx.hasUsage = true;
43
+ }
44
+
45
+ ts.forEachChild(node, n => visit(ctx, n));
46
+ }
47
+
48
+
49
+ const mightNeedTransform = (code: string): boolean => {
50
+ if (!checkTransform(code, { regex: REACTIVE_REGEX })) {
51
+ return false;
52
+ }
53
+
54
+ let ctx: DetectContext = {
55
+ hasImport: false,
56
+ hasUsage: false
57
+ };
58
+
59
+ visit(ctx, ts.createSourceFile('detect.ts', code, ts.ScriptTarget.Latest, false));
60
+
61
+ return ctx.hasImport && ctx.hasUsage;
62
+ };
63
+
64
+
65
+ export { mightNeedTransform };
@@ -0,0 +1,78 @@
1
+ import type { Bindings, TransformOptions, TransformResult } from '~/types';
2
+ import { injectAutoDispose } from './transforms/auto-dispose';
3
+ import { mightNeedTransform } from './detector';
4
+ import { transformReactiveArrays } from './transforms/reactive-array';
5
+ import { transformReactiveObjects } from './transforms/reactive-object';
6
+ import { transformReactivePrimitives } from './transforms/reactive-primitives';
7
+ import ts from 'typescript';
8
+
9
+
10
+ const createTransformer = (options?: TransformOptions): ts.TransformerFactory<ts.SourceFile> => {
11
+ return () => {
12
+ return (sourceFile: ts.SourceFile): ts.SourceFile => {
13
+ let result = transform(sourceFile, options);
14
+
15
+ return result.transformed ? result.sourceFile : sourceFile;
16
+ };
17
+ };
18
+ };
19
+
20
+ const transform = (sourceFile: ts.SourceFile, options?: TransformOptions): TransformResult => {
21
+ let bindings: Bindings = new Map(),
22
+ code = sourceFile.getFullText(),
23
+ current = sourceFile,
24
+ original = code,
25
+ result: string;
26
+
27
+ if (!mightNeedTransform(code)) {
28
+ return { code, sourceFile, transformed: false };
29
+ }
30
+
31
+ // Run all transforms, only re-parse between transforms if code changed
32
+ result = transformReactiveObjects(current, bindings);
33
+
34
+ if (result !== code) {
35
+ current = ts.createSourceFile(sourceFile.fileName, result, sourceFile.languageVersion, true);
36
+ code = result;
37
+ }
38
+
39
+ result = transformReactiveArrays(current, bindings);
40
+
41
+ if (result !== code) {
42
+ current = ts.createSourceFile(sourceFile.fileName, result, sourceFile.languageVersion, true);
43
+ code = result;
44
+ }
45
+
46
+ result = transformReactivePrimitives(current, bindings);
47
+
48
+ if (result !== code) {
49
+ current = ts.createSourceFile(sourceFile.fileName, result, sourceFile.languageVersion, true);
50
+ code = result;
51
+ }
52
+
53
+ if (options?.autoDispose) {
54
+ result = injectAutoDispose(current);
55
+
56
+ if (result !== code) {
57
+ current = ts.createSourceFile(sourceFile.fileName, result, sourceFile.languageVersion, true);
58
+ code = result;
59
+ }
60
+ }
61
+
62
+ if (code === original) {
63
+ return { code, sourceFile, transformed: false };
64
+ }
65
+
66
+ return {
67
+ code,
68
+ sourceFile: current,
69
+ transformed: true
70
+ };
71
+ };
72
+
73
+
74
+ export { createTransformer, mightNeedTransform, transform };
75
+ export { injectAutoDispose } from './transforms/auto-dispose';
76
+ export { transformReactiveArrays } from './transforms/reactive-array';
77
+ export { transformReactiveObjects } from './transforms/reactive-object';
78
+ export { transformReactivePrimitives } from './transforms/reactive-primitives';