@flowgram-vue/variable-core 0.2.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.
- package/LICENSE +22 -0
- package/dist/index.cjs +2630 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.ts +2293 -0
- package/dist/index.js +2592 -0
- package/dist/index.js.map +1 -0
- package/package.json +68 -0
- package/src/ast/ast-node.ts +374 -0
- package/src/ast/ast-registers.ts +129 -0
- package/src/ast/common/data-node.ts +63 -0
- package/src/ast/common/index.ts +8 -0
- package/src/ast/common/list-node.ts +70 -0
- package/src/ast/common/map-node.ts +89 -0
- package/src/ast/declaration/base-variable-field.ts +184 -0
- package/src/ast/declaration/index.ts +13 -0
- package/src/ast/declaration/property.ts +19 -0
- package/src/ast/declaration/variable-declaration-list.ts +112 -0
- package/src/ast/declaration/variable-declaration.ts +78 -0
- package/src/ast/expression/base-expression.ts +117 -0
- package/src/ast/expression/enumerate-expression.ts +77 -0
- package/src/ast/expression/index.ts +10 -0
- package/src/ast/expression/keypath-expression.ts +157 -0
- package/src/ast/expression/legacy-keypath-expression.ts +119 -0
- package/src/ast/expression/wrap-array-expression.ts +96 -0
- package/src/ast/factory.ts +163 -0
- package/src/ast/flags.ts +50 -0
- package/src/ast/index.ts +26 -0
- package/src/ast/match.ts +146 -0
- package/src/ast/type/array.ts +109 -0
- package/src/ast/type/base-type.ts +49 -0
- package/src/ast/type/boolean.ts +26 -0
- package/src/ast/type/custom-type.ts +70 -0
- package/src/ast/type/index.ts +19 -0
- package/src/ast/type/integer.ts +29 -0
- package/src/ast/type/map.ts +96 -0
- package/src/ast/type/number.ts +26 -0
- package/src/ast/type/object.ts +185 -0
- package/src/ast/type/string.ts +55 -0
- package/src/ast/type/union.ts +13 -0
- package/src/ast/types.ts +188 -0
- package/src/ast/utils/expression.ts +61 -0
- package/src/ast/utils/helpers.ts +73 -0
- package/src/ast/utils/inversify.ts +42 -0
- package/src/ast/utils/observable.ts +5 -0
- package/src/ast/utils/variable-field.ts +25 -0
- package/src/composables/index.ts +9 -0
- package/src/composables/scope-provider.ts +78 -0
- package/src/composables/use-available-variables.ts +39 -0
- package/src/composables/use-output-variables.ts +36 -0
- package/src/composables/use-scope-available.ts +32 -0
- package/src/index.ts +14 -0
- package/src/providers.ts +22 -0
- package/src/scope/datas/index.ts +8 -0
- package/src/scope/datas/scope-available-data.ts +234 -0
- package/src/scope/datas/scope-event-data.ts +67 -0
- package/src/scope/datas/scope-output-data.ts +151 -0
- package/src/scope/index.ts +9 -0
- package/src/scope/scope-chain.ts +69 -0
- package/src/scope/scope.ts +200 -0
- package/src/scope/types.ts +102 -0
- package/src/scope/variable-table.ts +203 -0
- package/src/services/index.ts +6 -0
- package/src/services/variable-field-key-rename-service.ts +131 -0
- package/src/utils/memo.ts +38 -0
- package/src/utils/toDisposable.ts +16 -0
- package/src/variable-container-module.ts +28 -0
- package/src/variable-engine.ts +197 -0
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Copyright (c) 2025 Bytedance Ltd. and/or its affiliates
|
|
3
|
+
* SPDX-License-Identifier: MIT
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { inject, injectable } from 'inversify';
|
|
7
|
+
import { DisposableCollection, type Event } from '@flowgram-vue/utils';
|
|
8
|
+
|
|
9
|
+
import { VariableEngineProvider } from '../providers';
|
|
10
|
+
import { type Scope } from './scope';
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Manages the dependency relationships between scopes.
|
|
14
|
+
* This is an abstract class, and specific implementations determine how the scope order is managed.
|
|
15
|
+
*/
|
|
16
|
+
@injectable()
|
|
17
|
+
export abstract class ScopeChain {
|
|
18
|
+
readonly toDispose: DisposableCollection = new DisposableCollection();
|
|
19
|
+
|
|
20
|
+
@inject(VariableEngineProvider) variableEngineProvider: VariableEngineProvider;
|
|
21
|
+
|
|
22
|
+
get variableEngine() {
|
|
23
|
+
return this.variableEngineProvider();
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
constructor() {}
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Refreshes the dependency and coverage relationships for all scopes.
|
|
30
|
+
*/
|
|
31
|
+
refreshAllChange(): void {
|
|
32
|
+
this.variableEngine.getAllScopes().forEach((_scope) => {
|
|
33
|
+
_scope.refreshCovers();
|
|
34
|
+
_scope.refreshDeps();
|
|
35
|
+
});
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Gets the dependency scopes for a given scope.
|
|
40
|
+
* @param scope The scope to get dependencies for.
|
|
41
|
+
* @returns An array of dependency scopes.
|
|
42
|
+
*/
|
|
43
|
+
abstract getDeps(scope: Scope): Scope[];
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Gets the covering scopes for a given scope.
|
|
47
|
+
* @param scope The scope to get covers for.
|
|
48
|
+
* @returns An array of covering scopes.
|
|
49
|
+
*/
|
|
50
|
+
abstract getCovers(scope: Scope): Scope[];
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Sorts all scopes based on their dependency relationships.
|
|
54
|
+
* @returns A sorted array of all scopes.
|
|
55
|
+
*/
|
|
56
|
+
abstract sortAll(): Scope[];
|
|
57
|
+
|
|
58
|
+
dispose(): void {
|
|
59
|
+
this.toDispose.dispose();
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
get disposed(): boolean {
|
|
63
|
+
return this.toDispose.disposed;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
get onDispose(): Event<void> {
|
|
67
|
+
return this.toDispose.onDispose;
|
|
68
|
+
}
|
|
69
|
+
}
|
|
@@ -0,0 +1,200 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Copyright (c) 2025 Bytedance Ltd. and/or its affiliates
|
|
3
|
+
* SPDX-License-Identifier: MIT
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { DisposableCollection } from '@flowgram-vue/utils';
|
|
7
|
+
|
|
8
|
+
import { type VariableEngine } from '../variable-engine';
|
|
9
|
+
import { createMemo } from '../utils/memo';
|
|
10
|
+
import { ASTKind, type ASTNode, type ASTNodeJSON, MapNode } from '../ast';
|
|
11
|
+
import { ScopeAvailableData, ScopeEventData, ScopeOutputData } from './datas';
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Interface for the Scope constructor.
|
|
15
|
+
*/
|
|
16
|
+
export interface IScopeConstructor {
|
|
17
|
+
new (options: {
|
|
18
|
+
id: string | symbol;
|
|
19
|
+
variableEngine: VariableEngine;
|
|
20
|
+
meta?: Record<string, any>;
|
|
21
|
+
}): Scope;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Represents a variable scope, which manages its own set of variables and their lifecycle.
|
|
26
|
+
* - `scope.output` represents the variables declared within this scope.
|
|
27
|
+
* - `scope.available` represents all variables accessible from this scope, including those from parent scopes.
|
|
28
|
+
*/
|
|
29
|
+
export class Scope<ScopeMeta extends Record<string, any> = Record<string, any>> {
|
|
30
|
+
/**
|
|
31
|
+
* A unique identifier for the scope.
|
|
32
|
+
*/
|
|
33
|
+
readonly id: string | symbol;
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* The variable engine instance this scope belongs to.
|
|
37
|
+
*/
|
|
38
|
+
readonly variableEngine: VariableEngine;
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Metadata associated with the scope, which can be extended by higher-level business logic.
|
|
42
|
+
*/
|
|
43
|
+
readonly meta: ScopeMeta;
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* The root AST node for this scope, which is a MapNode.
|
|
47
|
+
* It stores various data related to the scope, such as `outputs`.
|
|
48
|
+
*/
|
|
49
|
+
readonly ast: MapNode;
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Manages the available variables for this scope.
|
|
53
|
+
*/
|
|
54
|
+
readonly available: ScopeAvailableData;
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Manages the output variables for this scope.
|
|
58
|
+
*/
|
|
59
|
+
readonly output: ScopeOutputData;
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Manages event dispatching and handling for this scope.
|
|
63
|
+
*/
|
|
64
|
+
readonly event: ScopeEventData;
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* A memoization utility for caching computed values.
|
|
68
|
+
*/
|
|
69
|
+
protected memo = createMemo();
|
|
70
|
+
|
|
71
|
+
public toDispose: DisposableCollection = new DisposableCollection();
|
|
72
|
+
|
|
73
|
+
constructor(options: { id: string | symbol; variableEngine: VariableEngine; meta?: ScopeMeta }) {
|
|
74
|
+
this.id = options.id;
|
|
75
|
+
this.meta = options.meta || ({} as any);
|
|
76
|
+
this.variableEngine = options.variableEngine;
|
|
77
|
+
|
|
78
|
+
this.event = new ScopeEventData(this);
|
|
79
|
+
|
|
80
|
+
this.ast = this.variableEngine.astRegisters.createAST(
|
|
81
|
+
{
|
|
82
|
+
kind: ASTKind.MapNode,
|
|
83
|
+
key: String(this.id),
|
|
84
|
+
},
|
|
85
|
+
{
|
|
86
|
+
scope: this,
|
|
87
|
+
}
|
|
88
|
+
) as MapNode;
|
|
89
|
+
|
|
90
|
+
this.output = new ScopeOutputData(this);
|
|
91
|
+
this.available = new ScopeAvailableData(this);
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* Refreshes the covering scopes.
|
|
96
|
+
*/
|
|
97
|
+
refreshCovers(): void {
|
|
98
|
+
this.memo.clear('covers');
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* Refreshes the dependency scopes and the available variables.
|
|
103
|
+
*/
|
|
104
|
+
refreshDeps(): void {
|
|
105
|
+
this.memo.clear('deps');
|
|
106
|
+
this.available.refresh();
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/**
|
|
110
|
+
* Gets the scopes that this scope depends on.
|
|
111
|
+
*/
|
|
112
|
+
get depScopes(): Scope[] {
|
|
113
|
+
return this.memo('deps', () =>
|
|
114
|
+
this.variableEngine.chain
|
|
115
|
+
.getDeps(this)
|
|
116
|
+
.filter((_scope) => Boolean(_scope) && !_scope?.disposed)
|
|
117
|
+
);
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
/**
|
|
121
|
+
* Gets the scopes that are covered by this scope.
|
|
122
|
+
*/
|
|
123
|
+
get coverScopes(): Scope[] {
|
|
124
|
+
return this.memo('covers', () =>
|
|
125
|
+
this.variableEngine.chain
|
|
126
|
+
.getCovers(this)
|
|
127
|
+
.filter((_scope) => Boolean(_scope) && !_scope?.disposed)
|
|
128
|
+
);
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
/**
|
|
132
|
+
* Disposes of the scope and its resources.
|
|
133
|
+
* This will also trigger updates in dependent and covering scopes.
|
|
134
|
+
*/
|
|
135
|
+
dispose(): void {
|
|
136
|
+
this.ast.dispose();
|
|
137
|
+
this.toDispose.dispose();
|
|
138
|
+
|
|
139
|
+
// When a scope is disposed, update its dependent and covering scopes.
|
|
140
|
+
this.coverScopes.forEach((_scope) => _scope.refreshDeps());
|
|
141
|
+
this.depScopes.forEach((_scope) => _scope.refreshCovers());
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
onDispose = this.toDispose.onDispose;
|
|
145
|
+
|
|
146
|
+
get disposed(): boolean {
|
|
147
|
+
return this.toDispose.disposed;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
/**
|
|
151
|
+
* Sets a variable in the scope with the default key 'outputs'.
|
|
152
|
+
*
|
|
153
|
+
* @param json The JSON representation of the AST node to set.
|
|
154
|
+
* @returns The created or updated AST node.
|
|
155
|
+
*/
|
|
156
|
+
public setVar<Node extends ASTNode = ASTNode>(json: ASTNodeJSON): Node;
|
|
157
|
+
|
|
158
|
+
/**
|
|
159
|
+
* Sets a variable in the scope with a specified key.
|
|
160
|
+
*
|
|
161
|
+
* @param key The key of the variable to set.
|
|
162
|
+
* @param json The JSON representation of the AST node to set.
|
|
163
|
+
* @returns The created or updated AST node.
|
|
164
|
+
*/
|
|
165
|
+
public setVar<Node extends ASTNode = ASTNode>(key: string, json: ASTNodeJSON): Node;
|
|
166
|
+
|
|
167
|
+
public setVar<Node extends ASTNode = ASTNode>(
|
|
168
|
+
arg1: string | ASTNodeJSON,
|
|
169
|
+
arg2?: ASTNodeJSON
|
|
170
|
+
): Node {
|
|
171
|
+
if (typeof arg1 === 'string' && arg2 !== undefined) {
|
|
172
|
+
return this.ast.set(arg1, arg2);
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
if (typeof arg1 === 'object' && arg2 === undefined) {
|
|
176
|
+
return this.ast.set('outputs', arg1);
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
throw new Error('Invalid arguments');
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
/**
|
|
183
|
+
* Retrieves a variable from the scope by its key.
|
|
184
|
+
*
|
|
185
|
+
* @param key The key of the variable to retrieve. Defaults to 'outputs'.
|
|
186
|
+
* @returns The AST node for the variable, or `undefined` if not found.
|
|
187
|
+
*/
|
|
188
|
+
public getVar<Node extends ASTNode = ASTNode>(key: string = 'outputs') {
|
|
189
|
+
return this.ast.get<Node>(key);
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
/**
|
|
193
|
+
* Clears a variable from the scope by its key.
|
|
194
|
+
*
|
|
195
|
+
* @param key The key of the variable to clear. Defaults to 'outputs'.
|
|
196
|
+
*/
|
|
197
|
+
public clearVar(key: string = 'outputs') {
|
|
198
|
+
return this.ast.remove(key);
|
|
199
|
+
}
|
|
200
|
+
}
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Copyright (c) 2025 Bytedance Ltd. and/or its affiliates
|
|
3
|
+
* SPDX-License-Identifier: MIT
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { Event, Disposable } from '@flowgram-vue/utils';
|
|
7
|
+
|
|
8
|
+
import { BaseVariableField, VariableDeclaration } from '../ast';
|
|
9
|
+
import { type Scope } from './scope';
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Parameters for getting all scopes.
|
|
13
|
+
*/
|
|
14
|
+
export interface GetAllScopeParams {
|
|
15
|
+
/**
|
|
16
|
+
* Whether to sort the scopes.
|
|
17
|
+
*/
|
|
18
|
+
sort?: boolean;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Action type for scope changes.
|
|
23
|
+
*/
|
|
24
|
+
export interface ScopeChangeAction {
|
|
25
|
+
type: 'add' | 'delete' | 'update' | 'available';
|
|
26
|
+
scope: Scope;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Interface for a variable table.
|
|
31
|
+
*/
|
|
32
|
+
export interface IVariableTable extends Disposable {
|
|
33
|
+
/**
|
|
34
|
+
* The parent variable table.
|
|
35
|
+
*/
|
|
36
|
+
parentTable?: IVariableTable;
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* @deprecated Use `onVariableListChange` or `onAnyVariableChange` instead.
|
|
40
|
+
*/
|
|
41
|
+
onDataChange: Event<void>;
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* The current version of the variable table.
|
|
45
|
+
*/
|
|
46
|
+
version: number;
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* The list of variables in the table.
|
|
50
|
+
*/
|
|
51
|
+
variables: VariableDeclaration[];
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* The keys of the variables in the table.
|
|
55
|
+
*/
|
|
56
|
+
variableKeys: string[];
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Fires a change event.
|
|
60
|
+
*/
|
|
61
|
+
fireChange(): void;
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Gets a variable or property by its key path.
|
|
65
|
+
* @param keyPath The key path to the variable or property.
|
|
66
|
+
* @returns The found `BaseVariableField` or `undefined`.
|
|
67
|
+
*/
|
|
68
|
+
getByKeyPath(keyPath: string[]): BaseVariableField | undefined;
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Gets a variable by its key.
|
|
72
|
+
* @param key The key of the variable.
|
|
73
|
+
* @returns The found `VariableDeclaration` or `undefined`.
|
|
74
|
+
*/
|
|
75
|
+
getVariableByKey(key: string): VariableDeclaration | undefined;
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* Disposes the variable table.
|
|
79
|
+
*/
|
|
80
|
+
dispose(): void;
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* Subscribes to changes in the variable list.
|
|
84
|
+
* @param observer The observer function.
|
|
85
|
+
* @returns A disposable to unsubscribe.
|
|
86
|
+
*/
|
|
87
|
+
onVariableListChange(observer: (variables: VariableDeclaration[]) => void): Disposable;
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* Subscribes to changes in any variable's value.
|
|
91
|
+
* @param observer The observer function.
|
|
92
|
+
* @returns A disposable to unsubscribe.
|
|
93
|
+
*/
|
|
94
|
+
onAnyVariableChange(observer: (changedVariable: VariableDeclaration) => void): Disposable;
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* Subscribes to both variable list changes and any variable's value changes.
|
|
98
|
+
* @param observer The observer function.
|
|
99
|
+
* @returns A disposable to unsubscribe.
|
|
100
|
+
*/
|
|
101
|
+
onListOrAnyVarChange(observer: () => void): Disposable;
|
|
102
|
+
}
|
|
@@ -0,0 +1,203 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Copyright (c) 2025 Bytedance Ltd. and/or its affiliates
|
|
3
|
+
* SPDX-License-Identifier: MIT
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { Observable, Subject, merge, share, skip, switchMap } from 'rxjs';
|
|
7
|
+
import { DisposableCollection, Emitter } from '@flowgram-vue/utils';
|
|
8
|
+
|
|
9
|
+
import { subsToDisposable } from '../utils/toDisposable';
|
|
10
|
+
import { BaseVariableField } from '../ast/declaration/base-variable-field';
|
|
11
|
+
import { VariableDeclaration } from '../ast';
|
|
12
|
+
import { IVariableTable } from './types';
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* A class that stores and manages variables in a table-like structure.
|
|
16
|
+
* It provides methods for adding, removing, and retrieving variables, as well as
|
|
17
|
+
* observables for listening to changes in the variable list and individual variables.
|
|
18
|
+
*/
|
|
19
|
+
export class VariableTable implements IVariableTable {
|
|
20
|
+
protected table: Map<string, VariableDeclaration> = new Map();
|
|
21
|
+
|
|
22
|
+
toDispose = new DisposableCollection();
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* @deprecated
|
|
26
|
+
*/
|
|
27
|
+
protected onDataChangeEmitter = new Emitter<void>();
|
|
28
|
+
|
|
29
|
+
protected variables$: Subject<VariableDeclaration[]> = new Subject<VariableDeclaration[]>();
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* An observable that listens for value changes on any variable within the table.
|
|
33
|
+
*/
|
|
34
|
+
protected anyVariableChange$: Observable<VariableDeclaration> = this.variables$.pipe(
|
|
35
|
+
switchMap((_variables) =>
|
|
36
|
+
merge(
|
|
37
|
+
..._variables.map((_v) =>
|
|
38
|
+
_v.value$.pipe<any>(
|
|
39
|
+
// Skip the initial value of the BehaviorSubject
|
|
40
|
+
skip(1)
|
|
41
|
+
)
|
|
42
|
+
)
|
|
43
|
+
)
|
|
44
|
+
),
|
|
45
|
+
share()
|
|
46
|
+
);
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Subscribes to updates on any variable in the list.
|
|
50
|
+
* @param observer A function to be called when any variable's value changes.
|
|
51
|
+
* @returns A disposable object to unsubscribe from the updates.
|
|
52
|
+
*/
|
|
53
|
+
onAnyVariableChange(observer: (changedVariable: VariableDeclaration) => void) {
|
|
54
|
+
return subsToDisposable(this.anyVariableChange$.subscribe(observer));
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Subscribes to changes in the variable list (additions or removals).
|
|
59
|
+
* @param observer A function to be called when the list of variables changes.
|
|
60
|
+
* @returns A disposable object to unsubscribe from the updates.
|
|
61
|
+
*/
|
|
62
|
+
onVariableListChange(observer: (variables: VariableDeclaration[]) => void) {
|
|
63
|
+
return subsToDisposable(this.variables$.subscribe(observer));
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Subscribes to both variable list changes and updates to any variable in the list.
|
|
68
|
+
* @param observer A function to be called when either the list or a variable in it changes.
|
|
69
|
+
* @returns A disposable collection to unsubscribe from both events.
|
|
70
|
+
*/
|
|
71
|
+
onListOrAnyVarChange(observer: () => void) {
|
|
72
|
+
const disposables = new DisposableCollection();
|
|
73
|
+
disposables.pushAll([this.onVariableListChange(observer), this.onAnyVariableChange(observer)]);
|
|
74
|
+
return disposables;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* @deprecated Use onListOrAnyVarChange instead.
|
|
79
|
+
*/
|
|
80
|
+
public onDataChange = this.onDataChangeEmitter.event;
|
|
81
|
+
|
|
82
|
+
protected _version: number = 0;
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* Fires change events to notify listeners that the data has been updated.
|
|
86
|
+
*/
|
|
87
|
+
fireChange() {
|
|
88
|
+
this.bumpVersion();
|
|
89
|
+
this.onDataChangeEmitter.fire();
|
|
90
|
+
this.variables$.next(this.variables);
|
|
91
|
+
this.parentTable?.fireChange();
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* The current version of the variable table, incremented on each change.
|
|
96
|
+
*/
|
|
97
|
+
get version(): number {
|
|
98
|
+
return this._version;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* Increments the version number, resetting to 0 if it reaches MAX_SAFE_INTEGER.
|
|
103
|
+
*/
|
|
104
|
+
protected bumpVersion() {
|
|
105
|
+
this._version = this._version + 1;
|
|
106
|
+
if (this._version === Number.MAX_SAFE_INTEGER) {
|
|
107
|
+
this._version = 0;
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
constructor(
|
|
112
|
+
/**
|
|
113
|
+
* An optional parent table. If provided, this table will contain all variables
|
|
114
|
+
* from the current table.
|
|
115
|
+
*/
|
|
116
|
+
public parentTable?: IVariableTable
|
|
117
|
+
) {
|
|
118
|
+
this.toDispose.pushAll([
|
|
119
|
+
this.onDataChangeEmitter,
|
|
120
|
+
// Activate the share() operator
|
|
121
|
+
this.onAnyVariableChange(() => {
|
|
122
|
+
this.bumpVersion();
|
|
123
|
+
}),
|
|
124
|
+
]);
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
/**
|
|
128
|
+
* An array of all variables in the table.
|
|
129
|
+
*/
|
|
130
|
+
get variables(): VariableDeclaration[] {
|
|
131
|
+
return Array.from(this.table.values());
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
/**
|
|
135
|
+
* An array of all variable keys in the table.
|
|
136
|
+
*/
|
|
137
|
+
get variableKeys(): string[] {
|
|
138
|
+
return Array.from(this.table.keys());
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
/**
|
|
142
|
+
* Retrieves a variable or a nested property field by its key path.
|
|
143
|
+
* @param keyPath An array of keys representing the path to the desired field.
|
|
144
|
+
* @returns The found variable or property field, or undefined if not found.
|
|
145
|
+
*/
|
|
146
|
+
getByKeyPath(keyPath: string[]): BaseVariableField | undefined {
|
|
147
|
+
const [variableKey, ...propertyKeys] = keyPath || [];
|
|
148
|
+
|
|
149
|
+
if (!variableKey) {
|
|
150
|
+
return;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
const variable = this.getVariableByKey(variableKey);
|
|
154
|
+
|
|
155
|
+
return propertyKeys.length ? variable?.getByKeyPath(propertyKeys) : variable;
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
/**
|
|
159
|
+
* Retrieves a variable by its key.
|
|
160
|
+
* @param key The key of the variable to retrieve.
|
|
161
|
+
* @returns The variable declaration if found, otherwise undefined.
|
|
162
|
+
*/
|
|
163
|
+
getVariableByKey(key: string) {
|
|
164
|
+
return this.table.get(key);
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
/**
|
|
168
|
+
* Adds a variable to the table.
|
|
169
|
+
* If a parent table exists, the variable is also added to the parent.
|
|
170
|
+
* @param variable The variable declaration to add.
|
|
171
|
+
*/
|
|
172
|
+
addVariableToTable(variable: VariableDeclaration) {
|
|
173
|
+
this.table.set(variable.key, variable);
|
|
174
|
+
if (this.parentTable) {
|
|
175
|
+
(this.parentTable as VariableTable).addVariableToTable(variable);
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
/**
|
|
180
|
+
* Removes a variable from the table.
|
|
181
|
+
* If a parent table exists, the variable is also removed from the parent.
|
|
182
|
+
* @param key The key of the variable to remove.
|
|
183
|
+
*/
|
|
184
|
+
removeVariableFromTable(key: string) {
|
|
185
|
+
this.table.delete(key);
|
|
186
|
+
if (this.parentTable) {
|
|
187
|
+
(this.parentTable as VariableTable).removeVariableFromTable(key);
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
/**
|
|
192
|
+
* Disposes of all resources used by the variable table.
|
|
193
|
+
*/
|
|
194
|
+
dispose(): void {
|
|
195
|
+
this.variableKeys.forEach((_key) =>
|
|
196
|
+
(this.parentTable as VariableTable)?.removeVariableFromTable(_key)
|
|
197
|
+
);
|
|
198
|
+
this.parentTable?.fireChange();
|
|
199
|
+
this.variables$.complete();
|
|
200
|
+
this.variables$.unsubscribe();
|
|
201
|
+
this.toDispose.dispose();
|
|
202
|
+
}
|
|
203
|
+
}
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Copyright (c) 2025 Bytedance Ltd. and/or its affiliates
|
|
3
|
+
* SPDX-License-Identifier: MIT
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { difference } from 'lodash-es';
|
|
7
|
+
import { inject, injectable, postConstruct, preDestroy } from 'inversify';
|
|
8
|
+
import { DisposableCollection, Emitter } from '@flowgram-vue/utils';
|
|
9
|
+
|
|
10
|
+
import { VariableEngine } from '../variable-engine';
|
|
11
|
+
import {
|
|
12
|
+
ASTNode,
|
|
13
|
+
BaseVariableField,
|
|
14
|
+
ObjectPropertiesChangeAction,
|
|
15
|
+
VariableDeclarationListChangeAction,
|
|
16
|
+
} from '../ast';
|
|
17
|
+
|
|
18
|
+
interface RenameInfo {
|
|
19
|
+
before: BaseVariableField;
|
|
20
|
+
after: BaseVariableField;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* This service is responsible for detecting when a variable field's key is renamed.
|
|
25
|
+
* It listens for changes in variable declaration lists and object properties, and
|
|
26
|
+
* determines if a change constitutes a rename operation.
|
|
27
|
+
*/
|
|
28
|
+
@injectable()
|
|
29
|
+
export class VariableFieldKeyRenameService {
|
|
30
|
+
@inject(VariableEngine) variableEngine: VariableEngine;
|
|
31
|
+
|
|
32
|
+
toDispose = new DisposableCollection();
|
|
33
|
+
|
|
34
|
+
renameEmitter = new Emitter<RenameInfo>();
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Emits events for fields that are disposed of during a list change, but not renamed.
|
|
38
|
+
* This helps distinguish between a field that was truly removed and one that was renamed.
|
|
39
|
+
*/
|
|
40
|
+
disposeInListEmitter = new Emitter<BaseVariableField>();
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* An event that fires when a variable field key is successfully renamed.
|
|
44
|
+
*/
|
|
45
|
+
onRename = this.renameEmitter.event;
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* An event that fires when a field is removed from a list (and not part of a rename).
|
|
49
|
+
*/
|
|
50
|
+
onDisposeInList = this.disposeInListEmitter.event;
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Handles changes in a list of fields to detect rename operations.
|
|
54
|
+
* @param ast The AST node where the change occurred.
|
|
55
|
+
* @param prev The list of fields before the change.
|
|
56
|
+
* @param next The list of fields after the change.
|
|
57
|
+
*/
|
|
58
|
+
handleFieldListChange(ast?: ASTNode, prev?: BaseVariableField[], next?: BaseVariableField[]) {
|
|
59
|
+
// 1. Check if a rename is possible.
|
|
60
|
+
if (!ast || !prev?.length || !next?.length) {
|
|
61
|
+
this.notifyFieldsDispose(prev, next);
|
|
62
|
+
return;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
// 2. The lengths of the lists must be the same for a rename.
|
|
66
|
+
if (prev.length !== next.length) {
|
|
67
|
+
this.notifyFieldsDispose(prev, next);
|
|
68
|
+
return;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
let renameNodeInfo: RenameInfo | null = null;
|
|
72
|
+
let existFieldChanged = false;
|
|
73
|
+
|
|
74
|
+
for (const [index, prevField] of prev.entries()) {
|
|
75
|
+
const nextField = next[index];
|
|
76
|
+
|
|
77
|
+
if (prevField.key !== nextField.key) {
|
|
78
|
+
// Only one rename is allowed at a time.
|
|
79
|
+
if (existFieldChanged) {
|
|
80
|
+
this.notifyFieldsDispose(prev, next);
|
|
81
|
+
return;
|
|
82
|
+
}
|
|
83
|
+
existFieldChanged = true;
|
|
84
|
+
|
|
85
|
+
if (prevField.type?.kind === nextField.type?.kind) {
|
|
86
|
+
renameNodeInfo = { before: prevField, after: nextField };
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
if (!renameNodeInfo) {
|
|
92
|
+
this.notifyFieldsDispose(prev, next);
|
|
93
|
+
return;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
this.renameEmitter.fire(renameNodeInfo);
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
/**
|
|
100
|
+
* Notifies listeners about fields that were removed from a list.
|
|
101
|
+
* @param prev The list of fields before the change.
|
|
102
|
+
* @param next The list of fields after the change.
|
|
103
|
+
*/
|
|
104
|
+
notifyFieldsDispose(prev?: BaseVariableField[], next?: BaseVariableField[]) {
|
|
105
|
+
const removedFields = difference(prev || [], next || []);
|
|
106
|
+
removedFields.forEach((_field) => this.disposeInListEmitter.fire(_field));
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
@postConstruct()
|
|
110
|
+
init() {
|
|
111
|
+
this.toDispose.pushAll([
|
|
112
|
+
this.variableEngine.onGlobalEvent<VariableDeclarationListChangeAction>(
|
|
113
|
+
'VariableListChange',
|
|
114
|
+
(_action) => {
|
|
115
|
+
this.handleFieldListChange(_action.ast, _action.payload?.prev, _action.payload?.next);
|
|
116
|
+
}
|
|
117
|
+
),
|
|
118
|
+
this.variableEngine.onGlobalEvent<ObjectPropertiesChangeAction>(
|
|
119
|
+
'ObjectPropertiesChange',
|
|
120
|
+
(_action) => {
|
|
121
|
+
this.handleFieldListChange(_action.ast, _action.payload?.prev, _action.payload?.next);
|
|
122
|
+
}
|
|
123
|
+
),
|
|
124
|
+
]);
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
@preDestroy()
|
|
128
|
+
dispose() {
|
|
129
|
+
this.toDispose.dispose();
|
|
130
|
+
}
|
|
131
|
+
}
|