@flowgram.ai/test-run-plugin 0.1.0-alpha.20
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/.eslintrc.cjs +17 -0
- package/.rush/temp/chunked-rush-logs/test-run-plugin.build.chunks.jsonl +21 -0
- package/.rush/temp/package-deps_build.json +47 -0
- package/.rush/temp/shrinkwrap-deps.json +161 -0
- package/dist/esm/index.js +731 -0
- package/dist/esm/index.js.map +1 -0
- package/dist/index.d.mts +314 -0
- package/dist/index.d.ts +314 -0
- package/dist/index.js +770 -0
- package/dist/index.js.map +1 -0
- package/package.json +56 -0
- package/rush-logs/test-run-plugin.build.log +21 -0
- package/src/create-test-run-plugin.ts +39 -0
- package/src/form-engine/contexts.ts +16 -0
- package/src/form-engine/fields/create-field.tsx +32 -0
- package/src/form-engine/fields/general-field.tsx +35 -0
- package/src/form-engine/fields/index.ts +9 -0
- package/src/form-engine/fields/object-field.tsx +21 -0
- package/src/form-engine/fields/reactive-field.tsx +57 -0
- package/src/form-engine/fields/recursion-field.tsx +31 -0
- package/src/form-engine/fields/schema-field.tsx +32 -0
- package/src/form-engine/form/form.tsx +38 -0
- package/src/form-engine/form/index.ts +6 -0
- package/src/form-engine/hooks/index.ts +8 -0
- package/src/form-engine/hooks/use-create-form.ts +69 -0
- package/src/form-engine/hooks/use-field.ts +13 -0
- package/src/form-engine/hooks/use-form.ts +13 -0
- package/src/form-engine/index.ts +19 -0
- package/src/form-engine/model/index.ts +89 -0
- package/src/form-engine/types.ts +56 -0
- package/src/form-engine/utils.ts +64 -0
- package/src/index.ts +22 -0
- package/src/reactive/hooks/index.ts +7 -0
- package/src/reactive/hooks/use-create-form.ts +90 -0
- package/src/reactive/hooks/use-test-run-service.ts +10 -0
- package/src/reactive/index.ts +6 -0
- package/src/services/config.ts +42 -0
- package/src/services/form/factory.ts +9 -0
- package/src/services/form/form.ts +78 -0
- package/src/services/form/index.ts +8 -0
- package/src/services/form/manager.ts +43 -0
- package/src/services/index.ts +14 -0
- package/src/services/pipeline/factory.ts +9 -0
- package/src/services/pipeline/index.ts +12 -0
- package/src/services/pipeline/pipeline.ts +143 -0
- package/src/services/pipeline/plugin.ts +11 -0
- package/src/services/pipeline/tap.ts +34 -0
- package/src/services/store.ts +27 -0
- package/src/services/test-run.ts +100 -0
- package/src/types.ts +29 -0
- package/tsconfig.json +12 -0
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Copyright (c) 2025 Bytedance Ltd. and/or its affiliates
|
|
3
|
+
* SPDX-License-Identifier: MIT
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import type { StoreApi } from 'zustand';
|
|
7
|
+
import { nanoid } from 'nanoid';
|
|
8
|
+
import { injectable, interfaces } from 'inversify';
|
|
9
|
+
import { Emitter } from '@flowgram.ai/utils';
|
|
10
|
+
|
|
11
|
+
import { Tap } from './tap';
|
|
12
|
+
import type { TestRunPipelinePlugin } from './plugin';
|
|
13
|
+
import { StoreService } from '../store';
|
|
14
|
+
export interface TestRunPipelineEntityOptions {
|
|
15
|
+
plugins: (new () => TestRunPipelinePlugin)[];
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
interface TestRunPipelineEntityState<T = any> {
|
|
19
|
+
status: 'idle' | 'preparing' | 'executing' | 'canceled' | 'finished';
|
|
20
|
+
data?: T;
|
|
21
|
+
result?: any;
|
|
22
|
+
getData: () => T;
|
|
23
|
+
setData: (next: any) => void;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export interface TestRunPipelineEntityCtx<T = any> {
|
|
27
|
+
id: string;
|
|
28
|
+
store: StoreApi<TestRunPipelineEntityState<T>>;
|
|
29
|
+
operate: {
|
|
30
|
+
update: (data: any) => void;
|
|
31
|
+
cancel: () => void;
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
const initialState: Omit<TestRunPipelineEntityState, 'getData' | 'setData'> = {
|
|
36
|
+
status: 'idle',
|
|
37
|
+
data: {},
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
@injectable()
|
|
41
|
+
export class TestRunPipelineEntity extends StoreService<TestRunPipelineEntityState> {
|
|
42
|
+
container: interfaces.Container | undefined;
|
|
43
|
+
|
|
44
|
+
id = nanoid();
|
|
45
|
+
|
|
46
|
+
prepare = new Tap<TestRunPipelineEntityCtx>();
|
|
47
|
+
|
|
48
|
+
private execute?: (ctx: TestRunPipelineEntityCtx) => Promise<void> | void;
|
|
49
|
+
|
|
50
|
+
private progress?: (ctx: TestRunPipelineEntityCtx) => Promise<void> | void;
|
|
51
|
+
|
|
52
|
+
get status() {
|
|
53
|
+
return this.getState().status;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
set status(next: TestRunPipelineEntityState['status']) {
|
|
57
|
+
this.setState({ status: next });
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
onProgressEmitter = new Emitter<any>();
|
|
61
|
+
|
|
62
|
+
onProgress = this.onProgressEmitter.event;
|
|
63
|
+
|
|
64
|
+
onFinishedEmitter = new Emitter();
|
|
65
|
+
|
|
66
|
+
onFinished = this.onFinishedEmitter.event;
|
|
67
|
+
|
|
68
|
+
constructor() {
|
|
69
|
+
super((set, get) => ({
|
|
70
|
+
...initialState,
|
|
71
|
+
getData: () => get().data || {},
|
|
72
|
+
setData: (next: any) => set((state) => ({ ...state, data: { ...state.data, ...next } })),
|
|
73
|
+
}));
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
public init(options: TestRunPipelineEntityOptions) {
|
|
77
|
+
if (!this.container) {
|
|
78
|
+
return;
|
|
79
|
+
}
|
|
80
|
+
const { plugins } = options;
|
|
81
|
+
for (const PluginClass of plugins) {
|
|
82
|
+
const plugin = this.container.resolve<TestRunPipelinePlugin>(PluginClass);
|
|
83
|
+
plugin.apply(this);
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
public registerExecute(fn: (ctx: TestRunPipelineEntityCtx) => Promise<void> | void) {
|
|
88
|
+
this.execute = fn;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
public registerProgress(fn: (ctx: TestRunPipelineEntityCtx) => Promise<void> | void) {
|
|
92
|
+
this.progress = fn;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
async start<T>(options?: { data: T }) {
|
|
96
|
+
const { data } = options || {};
|
|
97
|
+
if (this.status !== 'idle') {
|
|
98
|
+
return;
|
|
99
|
+
}
|
|
100
|
+
/** initialization data */
|
|
101
|
+
this.setState({ data });
|
|
102
|
+
const ctx: TestRunPipelineEntityCtx = {
|
|
103
|
+
id: this.id,
|
|
104
|
+
store: this.store,
|
|
105
|
+
operate: {
|
|
106
|
+
update: this.update.bind(this),
|
|
107
|
+
cancel: this.cancel.bind(this),
|
|
108
|
+
},
|
|
109
|
+
};
|
|
110
|
+
|
|
111
|
+
this.status = 'preparing';
|
|
112
|
+
await this.prepare.call(ctx);
|
|
113
|
+
if (this.status !== 'preparing') {
|
|
114
|
+
return;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
this.status = 'executing';
|
|
118
|
+
if (this.execute) {
|
|
119
|
+
await this.execute(ctx);
|
|
120
|
+
}
|
|
121
|
+
if (this.progress) {
|
|
122
|
+
await this.progress(ctx);
|
|
123
|
+
}
|
|
124
|
+
if (this.status === 'executing') {
|
|
125
|
+
this.status = 'finished';
|
|
126
|
+
this.onFinishedEmitter.fire(this.getState().result);
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
update(result: any) {
|
|
131
|
+
this.setState({ result });
|
|
132
|
+
this.onProgressEmitter.fire(result);
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
cancel() {
|
|
136
|
+
if ((this.status = 'preparing')) {
|
|
137
|
+
this.prepare.freeze();
|
|
138
|
+
}
|
|
139
|
+
this.status = 'canceled';
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
dispose() {}
|
|
143
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Copyright (c) 2025 Bytedance Ltd. and/or its affiliates
|
|
3
|
+
* SPDX-License-Identifier: MIT
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import type { TestRunPipelineEntity } from './pipeline';
|
|
7
|
+
|
|
8
|
+
export interface TestRunPipelinePlugin {
|
|
9
|
+
name: string;
|
|
10
|
+
apply(pipeline: TestRunPipelineEntity): void;
|
|
11
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Copyright (c) 2025 Bytedance Ltd. and/or its affiliates
|
|
3
|
+
* SPDX-License-Identifier: MIT
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { MaybePromise } from '../../types';
|
|
7
|
+
|
|
8
|
+
interface TapValue<T> {
|
|
9
|
+
name: string;
|
|
10
|
+
fn: (arg: T) => MaybePromise<void>;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export class Tap<T> {
|
|
14
|
+
private taps: TapValue<T>[] = [];
|
|
15
|
+
|
|
16
|
+
private frozen = false;
|
|
17
|
+
|
|
18
|
+
tap(name: string, fn: TapValue<T>['fn']) {
|
|
19
|
+
this.taps.push({ name, fn });
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
async call(ctx: T) {
|
|
23
|
+
for (const tap of this.taps) {
|
|
24
|
+
if (this.frozen) {
|
|
25
|
+
return;
|
|
26
|
+
}
|
|
27
|
+
await tap.fn(ctx);
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
freeze() {
|
|
32
|
+
this.frozen = true;
|
|
33
|
+
}
|
|
34
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Copyright (c) 2025 Bytedance Ltd. and/or its affiliates
|
|
3
|
+
* SPDX-License-Identifier: MIT
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { createStore } from 'zustand/vanilla';
|
|
7
|
+
import type { StoreApi, StateCreator } from 'zustand';
|
|
8
|
+
import { injectable, unmanaged } from 'inversify';
|
|
9
|
+
/**
|
|
10
|
+
* 包含 Store 的 Service
|
|
11
|
+
*/
|
|
12
|
+
@injectable()
|
|
13
|
+
export class StoreService<State> {
|
|
14
|
+
store: StoreApi<State>;
|
|
15
|
+
|
|
16
|
+
get getState() {
|
|
17
|
+
return this.store.getState.bind(this.store);
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
get setState() {
|
|
21
|
+
return this.store.setState.bind(this.store);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
constructor(@unmanaged() stateCreator: StateCreator<State>) {
|
|
25
|
+
this.store = createStore(stateCreator);
|
|
26
|
+
}
|
|
27
|
+
}
|
|
@@ -0,0 +1,100 @@
|
|
|
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 { Disposable, DisposableCollection, Emitter } from '@flowgram.ai/utils';
|
|
8
|
+
import type { FlowNodeEntity, FlowNodeType } from '@flowgram.ai/document';
|
|
9
|
+
|
|
10
|
+
import { TestRunPipelineFactory } from './pipeline/factory';
|
|
11
|
+
import { TestRunFormManager } from './form';
|
|
12
|
+
import { FormSchema } from '../form-engine';
|
|
13
|
+
import { TestRunPipelineEntity, type TestRunPipelineEntityOptions } from './pipeline';
|
|
14
|
+
import { TestRunConfig } from './config';
|
|
15
|
+
|
|
16
|
+
@injectable()
|
|
17
|
+
export class TestRunService {
|
|
18
|
+
@inject(TestRunConfig) private readonly config: TestRunConfig;
|
|
19
|
+
|
|
20
|
+
@inject(TestRunPipelineFactory) private readonly pipelineFactory: TestRunPipelineFactory;
|
|
21
|
+
|
|
22
|
+
@inject(TestRunFormManager) readonly formManager: TestRunFormManager;
|
|
23
|
+
|
|
24
|
+
pipelineEntities = new Map<string, TestRunPipelineEntity>();
|
|
25
|
+
|
|
26
|
+
pipelineBindings = new Map<string, Disposable>();
|
|
27
|
+
|
|
28
|
+
onPipelineProgressEmitter = new Emitter();
|
|
29
|
+
|
|
30
|
+
onPipelineProgress = this.onPipelineProgressEmitter.event;
|
|
31
|
+
|
|
32
|
+
onPipelineFinishedEmitter = new Emitter();
|
|
33
|
+
|
|
34
|
+
onPipelineFinished = this.onPipelineFinishedEmitter.event;
|
|
35
|
+
|
|
36
|
+
public isEnabled(nodeType: FlowNodeType) {
|
|
37
|
+
const config = this.config.nodes[nodeType];
|
|
38
|
+
return config && config?.enabled !== false;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
async toSchema(node: FlowNodeEntity) {
|
|
42
|
+
const nodeType = node.flowNodeType;
|
|
43
|
+
const config = this.config.nodes[nodeType];
|
|
44
|
+
if (!this.isEnabled(nodeType)) {
|
|
45
|
+
return {};
|
|
46
|
+
}
|
|
47
|
+
const properties =
|
|
48
|
+
typeof config.properties === 'function'
|
|
49
|
+
? await config.properties({ node })
|
|
50
|
+
: config.properties;
|
|
51
|
+
|
|
52
|
+
return {
|
|
53
|
+
type: 'object',
|
|
54
|
+
properties,
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
createFormWithSchema(schema: FormSchema) {
|
|
59
|
+
const form = this.formManager.createForm();
|
|
60
|
+
form.init({ schema });
|
|
61
|
+
return form;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
async createForm(node: FlowNodeEntity) {
|
|
65
|
+
const schema = await this.toSchema(node);
|
|
66
|
+
return this.createFormWithSchema(schema);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
createPipeline(options: TestRunPipelineEntityOptions) {
|
|
70
|
+
const pipeline = this.pipelineFactory();
|
|
71
|
+
this.pipelineEntities.set(pipeline.id, pipeline);
|
|
72
|
+
pipeline.init(options);
|
|
73
|
+
return pipeline;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
connectPipeline(pipeline: TestRunPipelineEntity) {
|
|
77
|
+
if (this.pipelineBindings.get(pipeline.id)) {
|
|
78
|
+
return;
|
|
79
|
+
}
|
|
80
|
+
const disposable = new DisposableCollection(
|
|
81
|
+
pipeline.onProgress(this.onPipelineProgressEmitter.fire.bind(this.onPipelineProgressEmitter)),
|
|
82
|
+
pipeline.onFinished(this.onPipelineFinishedEmitter.fire.bind(this.onPipelineFinishedEmitter))
|
|
83
|
+
);
|
|
84
|
+
this.pipelineBindings.set(pipeline.id, disposable);
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
disconnectPipeline(id: string) {
|
|
88
|
+
if (this.pipelineBindings.has(id)) {
|
|
89
|
+
const disposable = this.pipelineBindings.get(id);
|
|
90
|
+
disposable?.dispose();
|
|
91
|
+
this.pipelineBindings.delete(id);
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
disconnectAllPipeline() {
|
|
96
|
+
for (const id of this.pipelineBindings.keys()) {
|
|
97
|
+
this.disconnectPipeline(id);
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
}
|
package/src/types.ts
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Copyright (c) 2025 Bytedance Ltd. and/or its affiliates
|
|
3
|
+
* SPDX-License-Identifier: MIT
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import type { FlowNodeType, FlowNodeEntity } from '@flowgram.ai/document';
|
|
7
|
+
|
|
8
|
+
import type { FormSchema, FormComponents } from './form-engine';
|
|
9
|
+
|
|
10
|
+
export type MaybePromise<T> = T | Promise<T>;
|
|
11
|
+
|
|
12
|
+
type PropertiesFunctionParams = {
|
|
13
|
+
node: FlowNodeEntity;
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
export interface NodeTestConfig {
|
|
17
|
+
/** Enable node TestRun */
|
|
18
|
+
enabled?: boolean;
|
|
19
|
+
/** Input schema properties */
|
|
20
|
+
properties?:
|
|
21
|
+
| Record<string, FormSchema>
|
|
22
|
+
| ((params: PropertiesFunctionParams) => MaybePromise<Record<string, FormSchema>>);
|
|
23
|
+
}
|
|
24
|
+
export type NodeMap = Record<FlowNodeType, NodeTestConfig>;
|
|
25
|
+
|
|
26
|
+
export interface TestRunPluginConfig {
|
|
27
|
+
components?: FormComponents;
|
|
28
|
+
nodes?: NodeMap;
|
|
29
|
+
}
|