@esportsplus/reactivity 0.26.1 → 0.27.2

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.
@@ -0,0 +1,126 @@
1
+ import { defineProperty, isArray, isPromise } from '@esportsplus/utilities';
2
+ import { computed, dispose, effect, read, root, signal, write } from '~/system';
3
+ import { Computed, Signal } from '~/types';
4
+ import { COMPUTED, REACTIVE_ARRAY, REACTIVE_OBJECT, SIGNAL } from '~/constants';
5
+ import { ReactiveArray } from './array';
6
+
7
+
8
+ class ReactiveObject<T extends Record<PropertyKey, unknown>> {
9
+ protected disposers: VoidFunction[] | null = null;
10
+
11
+
12
+ constructor(data: T | null) {
13
+ if (data == null) {
14
+ return;
15
+ }
16
+
17
+ for (let key in data) {
18
+ let value = data[key as keyof T],
19
+ type = typeof value;
20
+
21
+ if (type === 'function') {
22
+ let node = this[COMPUTED]( value as () => T[keyof T] );
23
+
24
+ defineProperty(this, key, {
25
+ enumerable: true,
26
+ get: () => read(node)
27
+ });
28
+
29
+ continue;
30
+ }
31
+
32
+ if (value == null || type !== 'object') {
33
+ // Skip isArray when possible
34
+ }
35
+ else if (isArray(value)) {
36
+ defineProperty(this, key, {
37
+ enumerable: true,
38
+ value: this[REACTIVE_ARRAY](value)
39
+ });
40
+
41
+ continue;
42
+ }
43
+
44
+ let node = signal(value);
45
+
46
+ defineProperty(this, key, {
47
+ enumerable: true,
48
+ get() {
49
+ return read(node);
50
+ },
51
+ set(v: typeof value) {
52
+ write(node, v);
53
+ }
54
+ });
55
+ }
56
+ }
57
+
58
+
59
+ protected [REACTIVE_ARRAY]<U>(value: U[]): ReactiveArray<U> {
60
+ let node = new ReactiveArray(...value);
61
+
62
+ (this.disposers ??= []).push( () => node.dispose() );
63
+
64
+ return node;
65
+ }
66
+
67
+ protected [COMPUTED]<T extends Computed<ReturnType<T>>['fn']>(value: T) {
68
+ return root(() => {
69
+ let node: Computed<ReturnType<T>> | Signal<ReturnType<T> | undefined> = computed(value);
70
+
71
+ if (isPromise(node.value)) {
72
+ let factory = node,
73
+ version = 0;
74
+
75
+ node = signal<ReturnType<T> | undefined>(undefined);
76
+
77
+ (this.disposers ??= []).push(
78
+ effect(() => {
79
+ let id = ++version;
80
+
81
+ (read(factory) as Promise<ReturnType<T>>).then((v) => {
82
+ if (id !== version) {
83
+ return;
84
+ }
85
+
86
+ write(node as Signal<typeof v>, v);
87
+ });
88
+ })
89
+ )
90
+ }
91
+ else {
92
+ (this.disposers ??= []).push(() => dispose(node as Computed<ReturnType<T>>));
93
+ }
94
+
95
+ return node;
96
+ });
97
+ }
98
+
99
+ protected [SIGNAL]<T>(value: T) {
100
+ return signal(value);
101
+ }
102
+
103
+
104
+ dispose() {
105
+ let disposers = this.disposers,
106
+ disposer;
107
+
108
+ if (!disposers) {
109
+ return;
110
+ }
111
+
112
+ while (disposer = disposers.pop()) {
113
+ disposer();
114
+ }
115
+ }
116
+ }
117
+
118
+ Object.defineProperty(ReactiveObject.prototype, REACTIVE_OBJECT, { value: true });
119
+
120
+
121
+ const isReactiveObject = (value: any): value is ReactiveObject<any> => {
122
+ return typeof value === 'object' && value !== null && value[REACTIVE_OBJECT] === true;
123
+ };
124
+
125
+
126
+ export { isReactiveObject, ReactiveObject };
package/src/types.ts CHANGED
@@ -43,9 +43,9 @@ type Reactive<T> = T extends (...args: unknown[]) => Promise<infer R>
43
43
  : T extends (...args: any[]) => infer R
44
44
  ? R & { readonly [READONLY]: true }
45
45
  : T extends (infer U)[]
46
- ? U[] & Pick<ReactiveArray<U>, 'clear' | 'on' | 'once'>
46
+ ? U[] & Pick<ReactiveArray<U>, 'clear' | 'dispose' | 'on' | 'once'>
47
47
  : T extends Record<PropertyKey, unknown>
48
- ? { [K in keyof T]: T[K] } & { dispose: VoidFunction }
48
+ ? { [K in keyof T]: Reactive<T[K]>; } & { dispose: VoidFunction }
49
49
  : T;
50
50
 
51
51
  type Signal<T> = {
package/test/debug.ts ADDED
@@ -0,0 +1,7 @@
1
+ import { reactive } from '@esportsplus/reactivity';
2
+
3
+ let value = reactive({
4
+ hey: () => 'sadasd'
5
+ });
6
+
7
+ console.log(value.hey);
@@ -1,6 +1,6 @@
1
1
  import { resolve } from 'path';
2
2
  import { defineConfig } from 'vite';
3
- import reactivity from '../build/transformer/plugins/vite.js';
3
+ import reactivity from '../build/compiler/plugins/vite.js';
4
4
 
5
5
 
6
6
  export default defineConfig({
@@ -8,6 +8,7 @@ export default defineConfig({
8
8
  lib: {
9
9
  entry: {
10
10
  arrays: resolve(__dirname, 'arrays.ts'),
11
+ debug: resolve(__dirname, 'debug.ts'),
11
12
  effects: resolve(__dirname, 'effects.ts'),
12
13
  index: resolve(__dirname, 'index.ts'),
13
14
  nested: resolve(__dirname, 'nested.ts'),
@@ -1,4 +0,0 @@
1
- import { ts } from '@esportsplus/typescript';
2
- import type { Bindings } from '../types.js';
3
- declare const _default: (sourceFile: ts.SourceFile, bindings: Bindings, ns: string) => string;
4
- export default _default;
@@ -1,266 +0,0 @@
1
- import { ts } from '@esportsplus/typescript';
2
- import { ast, code as c } from '@esportsplus/typescript/compiler';
3
- import { COMPILER_ENTRYPOINT, COMPILER_TYPES, PACKAGE } from '../constants.js';
4
- const COMPOUND_OPERATORS = new Map([
5
- [ts.SyntaxKind.AmpersandAmpersandEqualsToken, '&&'],
6
- [ts.SyntaxKind.AmpersandEqualsToken, '&'],
7
- [ts.SyntaxKind.AsteriskAsteriskEqualsToken, '**'],
8
- [ts.SyntaxKind.AsteriskEqualsToken, '*'],
9
- [ts.SyntaxKind.BarBarEqualsToken, '||'],
10
- [ts.SyntaxKind.BarEqualsToken, '|'],
11
- [ts.SyntaxKind.CaretEqualsToken, '^'],
12
- [ts.SyntaxKind.GreaterThanGreaterThanEqualsToken, '>>'],
13
- [ts.SyntaxKind.GreaterThanGreaterThanGreaterThanEqualsToken, '>>>'],
14
- [ts.SyntaxKind.LessThanLessThanEqualsToken, '<<'],
15
- [ts.SyntaxKind.MinusEqualsToken, '-'],
16
- [ts.SyntaxKind.PercentEqualsToken, '%'],
17
- [ts.SyntaxKind.PlusEqualsToken, '+'],
18
- [ts.SyntaxKind.QuestionQuestionEqualsToken, '??'],
19
- [ts.SyntaxKind.SlashEqualsToken, '/']
20
- ]);
21
- function findBinding(bindings, name, node) {
22
- for (let i = 0, n = bindings.length; i < n; i++) {
23
- let b = bindings[i];
24
- if (b.name === name && isInScope(node, b)) {
25
- return b;
26
- }
27
- }
28
- return undefined;
29
- }
30
- function findEnclosingScope(node) {
31
- let current = node.parent;
32
- while (current) {
33
- if (ts.isBlock(current) ||
34
- ts.isSourceFile(current) ||
35
- ts.isFunctionDeclaration(current) ||
36
- ts.isFunctionExpression(current) ||
37
- ts.isArrowFunction(current) ||
38
- ts.isForStatement(current) ||
39
- ts.isForInStatement(current) ||
40
- ts.isForOfStatement(current)) {
41
- return current;
42
- }
43
- current = current.parent;
44
- }
45
- return node.getSourceFile();
46
- }
47
- function isInDeclarationInit(node) {
48
- let parent = node.parent;
49
- return ts.isVariableDeclaration(parent) && parent.initializer === node;
50
- }
51
- function isInScope(reference, binding) {
52
- let current = reference;
53
- while (current) {
54
- if (current === binding.scope) {
55
- return true;
56
- }
57
- current = current.parent;
58
- }
59
- return false;
60
- }
61
- function isReactiveReassignment(node) {
62
- let parent = node.parent;
63
- if (ts.isBinaryExpression(parent) &&
64
- parent.operatorToken.kind === ts.SyntaxKind.EqualsToken &&
65
- parent.right === node &&
66
- ts.isCallExpression(node) &&
67
- ts.isIdentifier(node.expression) &&
68
- node.expression.text === COMPILER_ENTRYPOINT) {
69
- return true;
70
- }
71
- return false;
72
- }
73
- function isWriteContext(node) {
74
- let parent = node.parent;
75
- if (ts.isBinaryExpression(parent) && parent.left === node) {
76
- let op = parent.operatorToken.kind;
77
- if (op === ts.SyntaxKind.EqualsToken) {
78
- return 'simple';
79
- }
80
- if (COMPOUND_OPERATORS.has(op)) {
81
- return 'compound';
82
- }
83
- }
84
- if (ts.isPostfixUnaryExpression(parent) || ts.isPrefixUnaryExpression(parent)) {
85
- let op = parent.operator;
86
- if (op === ts.SyntaxKind.PlusPlusToken || op === ts.SyntaxKind.MinusMinusToken) {
87
- return 'increment';
88
- }
89
- }
90
- return false;
91
- }
92
- function visit(ctx, node) {
93
- if (ts.isImportDeclaration(node) &&
94
- ts.isStringLiteral(node.moduleSpecifier) &&
95
- node.moduleSpecifier.text.includes(PACKAGE)) {
96
- let clause = node.importClause;
97
- if (clause?.namedBindings && ts.isNamedImports(clause.namedBindings)) {
98
- for (let i = 0, n = clause.namedBindings.elements.length; i < n; i++) {
99
- if (clause.namedBindings.elements[i].name.text === COMPILER_ENTRYPOINT) {
100
- ctx.hasReactiveImport = true;
101
- break;
102
- }
103
- }
104
- }
105
- }
106
- if (ctx.hasReactiveImport &&
107
- ts.isCallExpression(node) &&
108
- ts.isIdentifier(node.expression) &&
109
- node.expression.text === COMPILER_ENTRYPOINT &&
110
- node.arguments.length > 0) {
111
- let arg = node.arguments[0], classification = COMPILER_TYPES.Signal;
112
- if (ts.isArrowFunction(arg) || ts.isFunctionExpression(arg)) {
113
- classification = COMPILER_TYPES.Computed;
114
- }
115
- else if (ts.isObjectLiteralExpression(arg) || ts.isArrayLiteralExpression(arg)) {
116
- classification = null;
117
- }
118
- if (classification) {
119
- let varName = null;
120
- if (node.parent && ts.isVariableDeclaration(node.parent) && ts.isIdentifier(node.parent.name)) {
121
- varName = node.parent.name.text;
122
- }
123
- else if (node.parent &&
124
- ts.isBinaryExpression(node.parent) &&
125
- node.parent.operatorToken.kind === ts.SyntaxKind.EqualsToken &&
126
- ts.isIdentifier(node.parent.left)) {
127
- varName = node.parent.left.text;
128
- }
129
- if (varName) {
130
- let scope = findEnclosingScope(node);
131
- ctx.scopedBindings.push({ name: varName, scope, type: classification });
132
- ctx.bindings.set(varName, classification);
133
- }
134
- if (classification === COMPILER_TYPES.Computed) {
135
- let argStart = arg.getStart(ctx.sourceFile);
136
- ctx.computedArgRanges.push({ end: arg.end, start: argStart });
137
- let argCtx = {
138
- argStart,
139
- innerReplacements: [],
140
- ns: ctx.ns,
141
- scopedBindings: ctx.scopedBindings,
142
- sourceFile: ctx.sourceFile
143
- };
144
- visitArg(argCtx, arg);
145
- let argText = c.replace(arg.getText(ctx.sourceFile), argCtx.innerReplacements);
146
- ctx.replacements.push({
147
- end: node.end,
148
- newText: `${ctx.ns}.computed(${argText})`,
149
- start: node.pos
150
- });
151
- }
152
- else {
153
- ctx.replacements.push({
154
- end: node.end,
155
- newText: `${ctx.ns}.signal(${arg.getText(ctx.sourceFile)})`,
156
- start: node.pos
157
- });
158
- }
159
- }
160
- }
161
- if (ts.isIdentifier(node) && node.parent && !isInDeclarationInit(node.parent)) {
162
- if (ts.isPropertyAccessExpression(node.parent) && node.parent.name === node) {
163
- ts.forEachChild(node, n => visit(ctx, n));
164
- return;
165
- }
166
- let nodeStart = node.getStart(ctx.sourceFile);
167
- if (ast.inRange(ctx.computedArgRanges, nodeStart, node.end)) {
168
- ts.forEachChild(node, n => visit(ctx, n));
169
- return;
170
- }
171
- let binding = findBinding(ctx.scopedBindings, node.text, node), name = node.text;
172
- if (binding && node.parent) {
173
- if (!isReactiveReassignment(node.parent) &&
174
- !(ts.isTypeOfExpression(node.parent) && node.parent.expression === node)) {
175
- let writeCtx = isWriteContext(node);
176
- if (writeCtx) {
177
- if (binding.type !== COMPILER_TYPES.Computed) {
178
- let parent = node.parent;
179
- if (writeCtx === 'simple' && ts.isBinaryExpression(parent)) {
180
- ctx.replacements.push({
181
- end: parent.end,
182
- newText: `${ctx.ns}.write(${name}, ${parent.right.getText(ctx.sourceFile)})`,
183
- start: parent.pos
184
- });
185
- }
186
- else if (writeCtx === 'compound' && ts.isBinaryExpression(parent)) {
187
- let op = COMPOUND_OPERATORS.get(parent.operatorToken.kind) ?? '+';
188
- ctx.replacements.push({
189
- end: parent.end,
190
- newText: `${ctx.ns}.write(${name}, ${name}.value ${op} ${parent.right.getText(ctx.sourceFile)})`,
191
- start: parent.pos
192
- });
193
- }
194
- else if (writeCtx === 'increment') {
195
- let delta = parent.operator === ts.SyntaxKind.PlusPlusToken ? '+ 1' : '- 1', isPrefix = ts.isPrefixUnaryExpression(parent);
196
- if (ts.isExpressionStatement(parent.parent)) {
197
- ctx.replacements.push({
198
- end: parent.end,
199
- newText: `${ctx.ns}.write(${name}, ${name}.value ${delta})`,
200
- start: parent.pos
201
- });
202
- }
203
- else if (isPrefix) {
204
- ctx.replacements.push({
205
- end: parent.end,
206
- newText: `(${ctx.ns}.write(${name}, ${name}.value ${delta}), ${name}.value)`,
207
- start: parent.pos
208
- });
209
- }
210
- else {
211
- let tmp = `_t${ctx.tmpCounter++}`;
212
- ctx.replacements.push({
213
- end: parent.end,
214
- newText: `((${tmp}) => (${ctx.ns}.write(${name}, ${tmp} ${delta}), ${tmp}))(${name}.value)`,
215
- start: parent.pos
216
- });
217
- }
218
- }
219
- }
220
- }
221
- else {
222
- ctx.replacements.push({
223
- end: node.end,
224
- newText: `${ctx.ns}.read(${name})`,
225
- start: node.pos
226
- });
227
- }
228
- }
229
- }
230
- }
231
- ts.forEachChild(node, n => visit(ctx, n));
232
- }
233
- function visitArg(ctx, node) {
234
- if (ts.isIdentifier(node) && node.parent) {
235
- if ((ts.isPropertyAccessExpression(node.parent) && node.parent.name === node) ||
236
- (ts.isCallExpression(node.parent) && node.parent.expression === node)) {
237
- ts.forEachChild(node, n => visitArg(ctx, n));
238
- return;
239
- }
240
- if (findBinding(ctx.scopedBindings, node.text, node)) {
241
- ctx.innerReplacements.push({
242
- end: node.end - ctx.argStart,
243
- newText: `${ctx.ns}.read(${node.text})`,
244
- start: node.getStart(ctx.sourceFile) - ctx.argStart
245
- });
246
- }
247
- }
248
- ts.forEachChild(node, n => visitArg(ctx, n));
249
- }
250
- export default (sourceFile, bindings, ns) => {
251
- let code = sourceFile.getFullText(), ctx = {
252
- bindings,
253
- computedArgRanges: [],
254
- hasReactiveImport: false,
255
- ns,
256
- replacements: [],
257
- scopedBindings: [],
258
- sourceFile,
259
- tmpCounter: 0
260
- };
261
- visit(ctx, sourceFile);
262
- if (ctx.replacements.length === 0) {
263
- return code;
264
- }
265
- return c.replace(code, ctx.replacements);
266
- };