@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,78 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Copyright (c) 2025 Bytedance Ltd. and/or its affiliates
|
|
3
|
+
* SPDX-License-Identifier: MIT
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import {
|
|
7
|
+
computed,
|
|
8
|
+
defineComponent,
|
|
9
|
+
inject,
|
|
10
|
+
provide,
|
|
11
|
+
unref,
|
|
12
|
+
type ComputedRef,
|
|
13
|
+
type InjectionKey,
|
|
14
|
+
type PropType,
|
|
15
|
+
} from 'vue';
|
|
16
|
+
|
|
17
|
+
import { Scope } from '../scope';
|
|
18
|
+
|
|
19
|
+
interface ScopeContextProps {
|
|
20
|
+
scope: Scope;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export const ScopeKey: InjectionKey<Scope | ComputedRef<Scope>> = Symbol('Scope');
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* ScopeProvider provides the scope to its children via provide/inject.
|
|
27
|
+
*/
|
|
28
|
+
export const ScopeProvider = defineComponent({
|
|
29
|
+
name: 'ScopeProvider',
|
|
30
|
+
props: {
|
|
31
|
+
/**
|
|
32
|
+
* scope used in the context
|
|
33
|
+
*/
|
|
34
|
+
scope: {
|
|
35
|
+
type: Object as PropType<Scope>,
|
|
36
|
+
},
|
|
37
|
+
/**
|
|
38
|
+
* @deprecated use scope prop instead, this is kept for backward compatibility
|
|
39
|
+
*/
|
|
40
|
+
value: {
|
|
41
|
+
type: Object as PropType<ScopeContextProps>,
|
|
42
|
+
},
|
|
43
|
+
},
|
|
44
|
+
setup(props, { slots }) {
|
|
45
|
+
const scopeToUse = computed(() => props.scope || props.value?.scope);
|
|
46
|
+
|
|
47
|
+
if (!scopeToUse.value) {
|
|
48
|
+
throw new Error('[ScopeProvider] scope is required');
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
provide(ScopeKey, scopeToUse as ComputedRef<Scope>);
|
|
52
|
+
|
|
53
|
+
return () => slots.default?.();
|
|
54
|
+
},
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* useCurrentScope returns the scope provided by ScopeProvider.
|
|
59
|
+
*/
|
|
60
|
+
export const useCurrentScope = <Strict extends boolean = false>(params?: {
|
|
61
|
+
/**
|
|
62
|
+
* whether to throw error when no scope in ScopeProvider is found
|
|
63
|
+
*/
|
|
64
|
+
strict: Strict;
|
|
65
|
+
}): Strict extends true ? Scope : Scope | undefined => {
|
|
66
|
+
const { strict = false } = params || {};
|
|
67
|
+
|
|
68
|
+
const context = inject(ScopeKey, undefined);
|
|
69
|
+
|
|
70
|
+
if (!context) {
|
|
71
|
+
if (strict) {
|
|
72
|
+
throw new Error('useCurrentScope must be used within a <ScopeProvider scope={scope}>');
|
|
73
|
+
}
|
|
74
|
+
console.warn('useCurrentScope should be used within a <ScopeProvider scope={scope}>');
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
return unref(context) as Strict extends true ? Scope : Scope | undefined;
|
|
78
|
+
};
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Copyright (c) 2025 Bytedance Ltd. and/or its affiliates
|
|
3
|
+
* SPDX-License-Identifier: MIT
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { computed, onScopeDispose, shallowRef, type ComputedRef } from 'vue';
|
|
7
|
+
|
|
8
|
+
import { useService } from '@flowgram-vue/core';
|
|
9
|
+
|
|
10
|
+
import { VariableDeclaration } from '../ast';
|
|
11
|
+
import { VariableEngine } from '../variable-engine';
|
|
12
|
+
import { useCurrentScope } from './scope-provider';
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Get available variable list in the current scope.
|
|
16
|
+
*
|
|
17
|
+
* - If no scope, return global variable list.
|
|
18
|
+
* - The composable is reactive to variable list or any variables change.
|
|
19
|
+
*/
|
|
20
|
+
export function useAvailableVariables(): ComputedRef<VariableDeclaration[]> {
|
|
21
|
+
const scope = useCurrentScope();
|
|
22
|
+
const variableEngine: VariableEngine = useService(VariableEngine);
|
|
23
|
+
const tick = shallowRef(0);
|
|
24
|
+
|
|
25
|
+
const disposable = !scope
|
|
26
|
+
? variableEngine.globalVariableTable.onListOrAnyVarChange(() => {
|
|
27
|
+
tick.value += 1;
|
|
28
|
+
})
|
|
29
|
+
: scope.available.onDataChange(() => {
|
|
30
|
+
tick.value += 1;
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
onScopeDispose(() => disposable.dispose());
|
|
34
|
+
|
|
35
|
+
return computed(() => {
|
|
36
|
+
tick.value;
|
|
37
|
+
return scope ? scope.available.variables : variableEngine.globalVariableTable.variables;
|
|
38
|
+
});
|
|
39
|
+
}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Copyright (c) 2025 Bytedance Ltd. and/or its affiliates
|
|
3
|
+
* SPDX-License-Identifier: MIT
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { computed, onScopeDispose, shallowRef, type ComputedRef } from 'vue';
|
|
7
|
+
|
|
8
|
+
import { VariableDeclaration } from '../ast';
|
|
9
|
+
import { useCurrentScope } from './scope-provider';
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Get output variable list in the current scope.
|
|
13
|
+
*
|
|
14
|
+
* - The composable is reactive to variable list or any variables change.
|
|
15
|
+
*/
|
|
16
|
+
export function useOutputVariables(): ComputedRef<VariableDeclaration[]> {
|
|
17
|
+
const scope = useCurrentScope();
|
|
18
|
+
const tick = shallowRef(0);
|
|
19
|
+
|
|
20
|
+
if (!scope) {
|
|
21
|
+
throw new Error(
|
|
22
|
+
'[useOutputVariables]: No scope found, useOutputVariables must be used in <ScopeProvider>'
|
|
23
|
+
);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
const disposable = scope.output.onListOrAnyVarChange(() => {
|
|
27
|
+
tick.value += 1;
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
onScopeDispose(() => disposable.dispose());
|
|
31
|
+
|
|
32
|
+
return computed(() => {
|
|
33
|
+
tick.value;
|
|
34
|
+
return scope.output.variables;
|
|
35
|
+
});
|
|
36
|
+
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Copyright (c) 2025 Bytedance Ltd. and/or its affiliates
|
|
3
|
+
* SPDX-License-Identifier: MIT
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { computed, onScopeDispose, shallowRef, type ComputedRef } from 'vue';
|
|
7
|
+
|
|
8
|
+
import { ScopeAvailableData } from '../scope/datas';
|
|
9
|
+
import { useCurrentScope } from './scope-provider';
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Get the available variables in the current scope.
|
|
13
|
+
* 获取作用域的可访问变量
|
|
14
|
+
*/
|
|
15
|
+
export function useScopeAvailable(params?: { autoRefresh?: boolean }): ComputedRef<ScopeAvailableData> {
|
|
16
|
+
const { autoRefresh = true } = params || {};
|
|
17
|
+
|
|
18
|
+
const scope = useCurrentScope({ strict: true });
|
|
19
|
+
const tick = shallowRef(0);
|
|
20
|
+
|
|
21
|
+
if (autoRefresh) {
|
|
22
|
+
const disposable = scope.available.onListOrAnyVarChange(() => {
|
|
23
|
+
tick.value += 1;
|
|
24
|
+
});
|
|
25
|
+
onScopeDispose(() => disposable.dispose());
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
return computed(() => {
|
|
29
|
+
tick.value;
|
|
30
|
+
return scope.available;
|
|
31
|
+
});
|
|
32
|
+
}
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Copyright (c) 2025 Bytedance Ltd. and/or its affiliates
|
|
3
|
+
* SPDX-License-Identifier: MIT
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
export { VariableContainerModule } from './variable-container-module';
|
|
7
|
+
export { VariableEngine } from './variable-engine';
|
|
8
|
+
export { VariableEngineProvider } from './providers';
|
|
9
|
+
|
|
10
|
+
export * from './composables';
|
|
11
|
+
export * from './scope';
|
|
12
|
+
export * from './ast';
|
|
13
|
+
export * from './services';
|
|
14
|
+
export { type Observer } from 'rxjs';
|
package/src/providers.ts
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Copyright (c) 2025 Bytedance Ltd. and/or its affiliates
|
|
3
|
+
* SPDX-License-Identifier: MIT
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { interfaces } from 'inversify';
|
|
7
|
+
|
|
8
|
+
import { type VariableEngine } from './variable-engine';
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* A provider for dynamically obtaining the `VariableEngine` instance.
|
|
12
|
+
* This is used to prevent circular dependencies when injecting `VariableEngine`.
|
|
13
|
+
*/
|
|
14
|
+
export const VariableEngineProvider = Symbol('DynamicVariableEngine');
|
|
15
|
+
export type VariableEngineProvider = () => VariableEngine;
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* A provider for obtaining the Inversify container instance.
|
|
19
|
+
* This allows other parts of the application, like AST nodes, to access the container for dependency injection.
|
|
20
|
+
*/
|
|
21
|
+
export const ContainerProvider = Symbol('ContainerProvider');
|
|
22
|
+
export type ContainerProvider = () => interfaces.Container;
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Copyright (c) 2025 Bytedance Ltd. and/or its affiliates
|
|
3
|
+
* SPDX-License-Identifier: MIT
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
export { ScopeOutputData } from './scope-output-data';
|
|
7
|
+
export { ScopeAvailableData } from './scope-available-data';
|
|
8
|
+
export { ScopeEventData } from './scope-event-data';
|
|
@@ -0,0 +1,234 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Copyright (c) 2025 Bytedance Ltd. and/or its affiliates
|
|
3
|
+
* SPDX-License-Identifier: MIT
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import {
|
|
7
|
+
Observable,
|
|
8
|
+
Subject,
|
|
9
|
+
animationFrameScheduler,
|
|
10
|
+
debounceTime,
|
|
11
|
+
distinctUntilChanged,
|
|
12
|
+
map,
|
|
13
|
+
merge,
|
|
14
|
+
share,
|
|
15
|
+
skip,
|
|
16
|
+
startWith,
|
|
17
|
+
switchMap,
|
|
18
|
+
tap,
|
|
19
|
+
} from 'rxjs';
|
|
20
|
+
import { flatten } from 'lodash-es';
|
|
21
|
+
import { shallowEqual } from 'fast-equals';
|
|
22
|
+
import { Disposable } from '@flowgram-vue/utils';
|
|
23
|
+
import { Emitter } from '@flowgram-vue/utils';
|
|
24
|
+
|
|
25
|
+
import { IVariableTable } from '../types';
|
|
26
|
+
import { type Scope } from '../scope';
|
|
27
|
+
import { subsToDisposable } from '../../utils/toDisposable';
|
|
28
|
+
import { createMemo } from '../../utils/memo';
|
|
29
|
+
import { SubscribeConfig } from '../../ast/types';
|
|
30
|
+
import { ASTNode, BaseVariableField, VariableDeclaration } from '../../ast';
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Manages the available variables within a scope.
|
|
34
|
+
*/
|
|
35
|
+
export class ScopeAvailableData {
|
|
36
|
+
protected memo = createMemo();
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* The global variable table from the variable engine.
|
|
40
|
+
*/
|
|
41
|
+
get globalVariableTable(): IVariableTable {
|
|
42
|
+
return this.scope.variableEngine.globalVariableTable;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
protected _version: number = 0;
|
|
46
|
+
|
|
47
|
+
protected refresh$: Subject<void> = new Subject();
|
|
48
|
+
|
|
49
|
+
protected _variables: VariableDeclaration[] = [];
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* The current version of the available data, which increments on each change.
|
|
53
|
+
*/
|
|
54
|
+
get version() {
|
|
55
|
+
return this._version;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
protected bumpVersion() {
|
|
59
|
+
this._version = this._version + 1;
|
|
60
|
+
if (this._version === Number.MAX_SAFE_INTEGER) {
|
|
61
|
+
this._version = 0;
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Refreshes the list of available variables.
|
|
67
|
+
* This should be called when the dependencies of the scope change.
|
|
68
|
+
*/
|
|
69
|
+
refresh(): void {
|
|
70
|
+
// Do not trigger refresh for a disposed scope.
|
|
71
|
+
if (this.scope.disposed) {
|
|
72
|
+
return;
|
|
73
|
+
}
|
|
74
|
+
this.refresh$.next();
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* An observable that emits when the list of available variables changes.
|
|
79
|
+
*/
|
|
80
|
+
protected variables$: Observable<VariableDeclaration[]> = this.refresh$.pipe(
|
|
81
|
+
// Map to the flattened list of variables from all dependency scopes.
|
|
82
|
+
map(() => flatten(this.depScopes.map((scope) => scope.output.variables || []))),
|
|
83
|
+
// Use shallow equality to check if the variable list has changed.
|
|
84
|
+
distinctUntilChanged<VariableDeclaration[]>(shallowEqual),
|
|
85
|
+
share()
|
|
86
|
+
);
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* An observable that emits when any variable in the available list changes its value.
|
|
90
|
+
*/
|
|
91
|
+
protected anyVariableChange$: Observable<VariableDeclaration> = this.variables$.pipe(
|
|
92
|
+
switchMap((_variables) =>
|
|
93
|
+
merge(
|
|
94
|
+
..._variables.map((_v) =>
|
|
95
|
+
_v.value$.pipe<any>(
|
|
96
|
+
// Skip the initial value of the BehaviorSubject.
|
|
97
|
+
skip(1)
|
|
98
|
+
)
|
|
99
|
+
)
|
|
100
|
+
)
|
|
101
|
+
),
|
|
102
|
+
share()
|
|
103
|
+
);
|
|
104
|
+
|
|
105
|
+
/**
|
|
106
|
+
* Subscribes to changes in any variable's value in the available list.
|
|
107
|
+
* @param observer A function to be called with the changed variable.
|
|
108
|
+
* @returns A disposable to unsubscribe from the changes.
|
|
109
|
+
*/
|
|
110
|
+
onAnyVariableChange(observer: (changedVariable: VariableDeclaration) => void) {
|
|
111
|
+
return subsToDisposable(this.anyVariableChange$.subscribe(observer));
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/**
|
|
115
|
+
* Subscribes to changes in the list of available variables.
|
|
116
|
+
* @param observer A function to be called with the new list of variables.
|
|
117
|
+
* @returns A disposable to unsubscribe from the changes.
|
|
118
|
+
*/
|
|
119
|
+
onVariableListChange(observer: (variables: VariableDeclaration[]) => void) {
|
|
120
|
+
return subsToDisposable(this.variables$.subscribe(observer));
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
/**
|
|
124
|
+
* @deprecated
|
|
125
|
+
*/
|
|
126
|
+
protected onDataChangeEmitter = new Emitter<VariableDeclaration[]>();
|
|
127
|
+
|
|
128
|
+
protected onListOrAnyVarChangeEmitter = new Emitter<VariableDeclaration[]>();
|
|
129
|
+
|
|
130
|
+
/**
|
|
131
|
+
* @deprecated use available.onListOrAnyVarChange instead
|
|
132
|
+
*/
|
|
133
|
+
public onDataChange = this.onDataChangeEmitter.event;
|
|
134
|
+
|
|
135
|
+
/**
|
|
136
|
+
* An event that fires when the variable list changes or any variable's value is updated.
|
|
137
|
+
*/
|
|
138
|
+
public onListOrAnyVarChange = this.onListOrAnyVarChangeEmitter.event;
|
|
139
|
+
|
|
140
|
+
constructor(public readonly scope: Scope) {
|
|
141
|
+
this.scope.toDispose.pushAll([
|
|
142
|
+
this.onVariableListChange((_variables) => {
|
|
143
|
+
this._variables = _variables;
|
|
144
|
+
this.memo.clear();
|
|
145
|
+
this.onDataChangeEmitter.fire(this._variables);
|
|
146
|
+
this.bumpVersion();
|
|
147
|
+
this.onListOrAnyVarChangeEmitter.fire(this._variables);
|
|
148
|
+
}),
|
|
149
|
+
this.onAnyVariableChange(() => {
|
|
150
|
+
this.onDataChangeEmitter.fire(this._variables);
|
|
151
|
+
this.bumpVersion();
|
|
152
|
+
this.onListOrAnyVarChangeEmitter.fire(this._variables);
|
|
153
|
+
}),
|
|
154
|
+
Disposable.create(() => {
|
|
155
|
+
this.refresh$.complete();
|
|
156
|
+
this.refresh$.unsubscribe();
|
|
157
|
+
}),
|
|
158
|
+
]);
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
/**
|
|
162
|
+
* Gets the list of available variables.
|
|
163
|
+
*/
|
|
164
|
+
get variables(): VariableDeclaration[] {
|
|
165
|
+
return this._variables;
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
/**
|
|
169
|
+
* Gets the keys of the available variables.
|
|
170
|
+
*/
|
|
171
|
+
get variableKeys(): string[] {
|
|
172
|
+
return this.memo('availableKeys', () => this._variables.map((_v) => _v.key));
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
/**
|
|
176
|
+
* Gets the dependency scopes.
|
|
177
|
+
*/
|
|
178
|
+
get depScopes(): Scope[] {
|
|
179
|
+
return this.scope.depScopes;
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
/**
|
|
183
|
+
* Retrieves a variable field by its key path from the available variables.
|
|
184
|
+
* @param keyPath The key path to the variable field.
|
|
185
|
+
* @returns The found `BaseVariableField` or `undefined`.
|
|
186
|
+
*/
|
|
187
|
+
getByKeyPath(keyPath: string[] = []): BaseVariableField | undefined {
|
|
188
|
+
// Check if the variable is accessible in the current scope.
|
|
189
|
+
if (!this.variableKeys.includes(keyPath[0])) {
|
|
190
|
+
return;
|
|
191
|
+
}
|
|
192
|
+
return this.globalVariableTable.getByKeyPath(keyPath);
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
/**
|
|
196
|
+
* Tracks changes to a variable field by its key path.
|
|
197
|
+
* This includes changes to its type, value, or any nested properties.
|
|
198
|
+
* @param keyPath The key path to the variable field to track.
|
|
199
|
+
* @param cb The callback to execute when the variable changes.
|
|
200
|
+
* @param opts Configuration options for the subscription.
|
|
201
|
+
* @returns A disposable to unsubscribe from the tracking.
|
|
202
|
+
*/
|
|
203
|
+
trackByKeyPath<Data = BaseVariableField | undefined>(
|
|
204
|
+
keyPath: string[] = [],
|
|
205
|
+
cb: (variable?: Data) => void,
|
|
206
|
+
opts?: SubscribeConfig<BaseVariableField | undefined, Data>
|
|
207
|
+
): Disposable {
|
|
208
|
+
const { triggerOnInit = true, debounceAnimation, selector } = opts || {};
|
|
209
|
+
|
|
210
|
+
return subsToDisposable(
|
|
211
|
+
merge(this.anyVariableChange$, this.variables$)
|
|
212
|
+
.pipe(
|
|
213
|
+
triggerOnInit ? startWith() : tap(() => null),
|
|
214
|
+
map(() => {
|
|
215
|
+
const v = this.getByKeyPath(keyPath);
|
|
216
|
+
return selector ? selector(v) : (v as any);
|
|
217
|
+
}),
|
|
218
|
+
distinctUntilChanged(
|
|
219
|
+
(a, b) => shallowEqual(a, b),
|
|
220
|
+
(value) => {
|
|
221
|
+
if (value instanceof ASTNode) {
|
|
222
|
+
// If the value is an ASTNode, compare its hash for changes.
|
|
223
|
+
return value.hash;
|
|
224
|
+
}
|
|
225
|
+
return value;
|
|
226
|
+
}
|
|
227
|
+
),
|
|
228
|
+
// Debounce updates to a single emission per animation frame.
|
|
229
|
+
debounceAnimation ? debounceTime(0, animationFrameScheduler) : tap(() => null)
|
|
230
|
+
)
|
|
231
|
+
.subscribe(cb)
|
|
232
|
+
);
|
|
233
|
+
}
|
|
234
|
+
}
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Copyright (c) 2025 Bytedance Ltd. and/or its affiliates
|
|
3
|
+
* SPDX-License-Identifier: MIT
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { Subject, filter } from 'rxjs';
|
|
7
|
+
import { Disposable } from '@flowgram-vue/utils';
|
|
8
|
+
|
|
9
|
+
import { type Scope } from '../scope';
|
|
10
|
+
import { subsToDisposable } from '../../utils/toDisposable';
|
|
11
|
+
import { type GlobalEventActionType } from '../../ast';
|
|
12
|
+
|
|
13
|
+
type Observer<ActionType extends GlobalEventActionType = GlobalEventActionType> = (
|
|
14
|
+
action: ActionType
|
|
15
|
+
) => void;
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Manages global events within a scope.
|
|
19
|
+
*/
|
|
20
|
+
export class ScopeEventData {
|
|
21
|
+
event$: Subject<GlobalEventActionType> = new Subject<GlobalEventActionType>();
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Dispatches a global event.
|
|
25
|
+
* @param action The event action to dispatch.
|
|
26
|
+
*/
|
|
27
|
+
dispatch<ActionType extends GlobalEventActionType = GlobalEventActionType>(action: ActionType) {
|
|
28
|
+
if (this.scope.disposed) {
|
|
29
|
+
return;
|
|
30
|
+
}
|
|
31
|
+
this.event$.next(action);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Subscribes to all global events.
|
|
36
|
+
* @param observer The observer function to call with the event action.
|
|
37
|
+
* @returns A disposable to unsubscribe from the events.
|
|
38
|
+
*/
|
|
39
|
+
subscribe<ActionType extends GlobalEventActionType = GlobalEventActionType>(
|
|
40
|
+
observer: Observer<ActionType>
|
|
41
|
+
): Disposable {
|
|
42
|
+
return subsToDisposable(this.event$.subscribe(observer as Observer));
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Subscribes to a specific type of global event.
|
|
47
|
+
* @param type The type of the event to subscribe to.
|
|
48
|
+
* @param observer The observer function to call with the event action.
|
|
49
|
+
* @returns A disposable to unsubscribe from the event.
|
|
50
|
+
*/
|
|
51
|
+
on<ActionType extends GlobalEventActionType = GlobalEventActionType>(
|
|
52
|
+
type: ActionType['type'],
|
|
53
|
+
observer: Observer<ActionType>
|
|
54
|
+
): Disposable {
|
|
55
|
+
return subsToDisposable(
|
|
56
|
+
this.event$.pipe(filter((_action) => _action.type === type)).subscribe(observer as Observer)
|
|
57
|
+
);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
constructor(public readonly scope: Scope) {
|
|
61
|
+
scope.toDispose.pushAll([
|
|
62
|
+
this.subscribe((_action) => {
|
|
63
|
+
scope.variableEngine.fireGlobalEvent(_action);
|
|
64
|
+
}),
|
|
65
|
+
]);
|
|
66
|
+
}
|
|
67
|
+
}
|
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Copyright (c) 2025 Bytedance Ltd. and/or its affiliates
|
|
3
|
+
* SPDX-License-Identifier: MIT
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { VariableTable } from '../variable-table';
|
|
7
|
+
import { IVariableTable } from '../types';
|
|
8
|
+
import { type Scope } from '../scope';
|
|
9
|
+
import { type VariableEngine } from '../../variable-engine';
|
|
10
|
+
import { createMemo } from '../../utils/memo';
|
|
11
|
+
import { NewASTAction } from '../../ast/types';
|
|
12
|
+
import { DisposeASTAction } from '../../ast/types';
|
|
13
|
+
import { ReSortVariableDeclarationsAction } from '../../ast/declaration/variable-declaration';
|
|
14
|
+
import { ASTKind, type VariableDeclaration } from '../../ast';
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Manages the output variables of a scope.
|
|
18
|
+
*/
|
|
19
|
+
export class ScopeOutputData {
|
|
20
|
+
protected variableTable: IVariableTable;
|
|
21
|
+
|
|
22
|
+
protected memo = createMemo();
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* The variable engine instance.
|
|
26
|
+
*/
|
|
27
|
+
get variableEngine(): VariableEngine {
|
|
28
|
+
return this.scope.variableEngine;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* The global variable table from the variable engine.
|
|
33
|
+
*/
|
|
34
|
+
get globalVariableTable(): IVariableTable {
|
|
35
|
+
return this.scope.variableEngine.globalVariableTable;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* The current version of the output data, which increments on each change.
|
|
40
|
+
*/
|
|
41
|
+
get version() {
|
|
42
|
+
return this.variableTable.version;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* @deprecated use onListOrAnyVarChange instead
|
|
47
|
+
*/
|
|
48
|
+
get onDataChange() {
|
|
49
|
+
return this.variableTable.onDataChange.bind(this.variableTable);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* An event that fires when the list of output variables changes.
|
|
54
|
+
*/
|
|
55
|
+
get onVariableListChange() {
|
|
56
|
+
return this.variableTable.onVariableListChange.bind(this.variableTable);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* An event that fires when any output variable's value changes.
|
|
61
|
+
*/
|
|
62
|
+
get onAnyVariableChange() {
|
|
63
|
+
return this.variableTable.onAnyVariableChange.bind(this.variableTable);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* An event that fires when the output variable list changes or any variable's value is updated.
|
|
68
|
+
*/
|
|
69
|
+
get onListOrAnyVarChange() {
|
|
70
|
+
return this.variableTable.onListOrAnyVarChange.bind(this.variableTable);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
protected _hasChanges = false;
|
|
74
|
+
|
|
75
|
+
constructor(public readonly scope: Scope) {
|
|
76
|
+
// Setup scope variable table based on globalVariableTable
|
|
77
|
+
this.variableTable = new VariableTable(scope.variableEngine.globalVariableTable);
|
|
78
|
+
|
|
79
|
+
this.scope.toDispose.pushAll([
|
|
80
|
+
// When the root AST node is updated, check if there are any changes.
|
|
81
|
+
this.scope.ast.subscribe(() => {
|
|
82
|
+
if (this._hasChanges) {
|
|
83
|
+
this.memo.clear();
|
|
84
|
+
this.notifyCoversChange();
|
|
85
|
+
this.variableTable.fireChange();
|
|
86
|
+
this._hasChanges = false;
|
|
87
|
+
}
|
|
88
|
+
}),
|
|
89
|
+
this.scope.event.on<DisposeASTAction>('DisposeAST', (_action) => {
|
|
90
|
+
if (_action.ast?.kind === ASTKind.VariableDeclaration) {
|
|
91
|
+
this.removeVariableFromTable(_action.ast.key);
|
|
92
|
+
}
|
|
93
|
+
}),
|
|
94
|
+
this.scope.event.on<NewASTAction>('NewAST', (_action) => {
|
|
95
|
+
if (_action.ast?.kind === ASTKind.VariableDeclaration) {
|
|
96
|
+
this.addVariableToTable(_action.ast as VariableDeclaration);
|
|
97
|
+
}
|
|
98
|
+
}),
|
|
99
|
+
this.scope.event.on<ReSortVariableDeclarationsAction>('ReSortVariableDeclarations', () => {
|
|
100
|
+
this._hasChanges = true;
|
|
101
|
+
}),
|
|
102
|
+
this.variableTable,
|
|
103
|
+
]);
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* The output variable declarations of the scope, sorted by order.
|
|
108
|
+
*/
|
|
109
|
+
get variables(): VariableDeclaration[] {
|
|
110
|
+
return this.memo('variables', () =>
|
|
111
|
+
this.variableTable.variables.sort((a, b) => a.order - b.order)
|
|
112
|
+
);
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
/**
|
|
116
|
+
* The keys of the output variables.
|
|
117
|
+
*/
|
|
118
|
+
get variableKeys(): string[] {
|
|
119
|
+
return this.memo('variableKeys', () => this.variableTable.variableKeys);
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
protected addVariableToTable(variable: VariableDeclaration) {
|
|
123
|
+
if (variable.scope !== this.scope) {
|
|
124
|
+
throw Error('VariableDeclaration must be a ast node in scope');
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
(this.variableTable as VariableTable).addVariableToTable(variable);
|
|
128
|
+
this._hasChanges = true;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
protected removeVariableFromTable(key: string) {
|
|
132
|
+
(this.variableTable as VariableTable).removeVariableFromTable(key);
|
|
133
|
+
this._hasChanges = true;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
/**
|
|
137
|
+
* Retrieves a variable declaration by its key.
|
|
138
|
+
* @param key The key of the variable.
|
|
139
|
+
* @returns The `VariableDeclaration` or `undefined` if not found.
|
|
140
|
+
*/
|
|
141
|
+
getVariableByKey(key: string) {
|
|
142
|
+
return this.variableTable.getVariableByKey(key);
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
/**
|
|
146
|
+
* Notifies the covering scopes that the available variables have changed.
|
|
147
|
+
*/
|
|
148
|
+
notifyCoversChange(): void {
|
|
149
|
+
this.scope.coverScopes.forEach((scope) => scope.available.refresh());
|
|
150
|
+
}
|
|
151
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Copyright (c) 2025 Bytedance Ltd. and/or its affiliates
|
|
3
|
+
* SPDX-License-Identifier: MIT
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
export { ScopeChain } from './scope-chain';
|
|
7
|
+
export { Scope } from './scope';
|
|
8
|
+
export { ScopeOutputData } from './datas';
|
|
9
|
+
export { type IVariableTable } from './types';
|