@nocobase/flow-engine 2.3.0-beta.8 → 2.3.0-beta.9
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/lib/FlowDefinition.d.ts +1 -0
- package/lib/FlowDefinition.js +3 -0
- package/lib/flow-registry/BaseFlowRegistry.d.ts +2 -0
- package/lib/flow-registry/InstanceFlowRegistry.d.ts +1 -1
- package/lib/flowContext.d.ts +1 -0
- package/lib/flowContext.js +6 -1
- package/lib/models/CollectionFieldModel.d.ts +1 -1
- package/lib/models/CollectionFieldModel.js +6 -3
- package/package.json +4 -4
- package/src/FlowDefinition.ts +4 -0
- package/src/__tests__/objectVariable.test.ts +37 -1
- package/src/flow-registry/BaseFlowRegistry.ts +2 -0
- package/src/flow-registry/InstanceFlowRegistry.ts +1 -1
- package/src/flowContext.ts +7 -1
- package/src/models/CollectionFieldModel.tsx +7 -4
- package/src/models/__tests__/CollectionFieldModel.test.ts +5 -0
package/lib/FlowDefinition.d.ts
CHANGED
|
@@ -13,6 +13,7 @@ export declare class FlowDefinition {
|
|
|
13
13
|
_steps: Map<string, FlowStep>;
|
|
14
14
|
protected options: Omit<FlowDefinitionOptions, 'steps'>;
|
|
15
15
|
constructor(options: FlowDefinitionOptions, flowRegistry: IFlowRepository);
|
|
16
|
+
get model(): import(".").FlowModel<import("./types").DefaultStructure>;
|
|
16
17
|
get key(): string;
|
|
17
18
|
get title(): string;
|
|
18
19
|
set title(title: string);
|
package/lib/FlowDefinition.js
CHANGED
|
@@ -8,8 +8,10 @@
|
|
|
8
8
|
*/
|
|
9
9
|
import { FlowDefinitionOptions } from '../types';
|
|
10
10
|
import { FlowDefinition } from '../FlowDefinition';
|
|
11
|
+
import type { FlowModel } from '../models';
|
|
11
12
|
type FlowKey = string;
|
|
12
13
|
export interface IFlowRepository {
|
|
14
|
+
readonly model?: FlowModel;
|
|
13
15
|
addFlows(flowDefs: Record<string, Omit<FlowDefinitionOptions, 'key'>>): void;
|
|
14
16
|
addFlow(flowKey: string, flowOptions: Omit<FlowDefinitionOptions, 'key'>): FlowDefinition | void;
|
|
15
17
|
removeFlow(flowKey: string): void;
|
|
@@ -11,7 +11,7 @@ import { FlowModel } from '../models';
|
|
|
11
11
|
import { BaseFlowRegistry } from './BaseFlowRegistry';
|
|
12
12
|
type FlowKey = string;
|
|
13
13
|
export declare class InstanceFlowRegistry extends BaseFlowRegistry {
|
|
14
|
-
|
|
14
|
+
readonly model: FlowModel;
|
|
15
15
|
static readonly _type: "instance";
|
|
16
16
|
constructor(model: FlowModel);
|
|
17
17
|
save(): Promise<void>;
|
package/lib/flowContext.d.ts
CHANGED
|
@@ -349,6 +349,7 @@ declare class BaseFlowEngineContext extends FlowContext {
|
|
|
349
349
|
*/
|
|
350
350
|
renderJson: (template: JSONValue) => Promise<any>;
|
|
351
351
|
resolveJsonTemplate: (template: JSONValue, options?: ResolveJsonTemplateOptions) => Promise<any>;
|
|
352
|
+
variableContractModelUid?: string;
|
|
352
353
|
getVar: (path: string) => Promise<any>;
|
|
353
354
|
request: (options: RequestOptions) => Promise<any>;
|
|
354
355
|
runjs: (code: string, variables?: Record<string, any>, options?: JSRunnerOptions) => Promise<any>;
|
package/lib/flowContext.js
CHANGED
|
@@ -2518,7 +2518,7 @@ const _FlowEngineContext = class _FlowEngineContext extends BaseFlowEngineContex
|
|
|
2518
2518
|
try {
|
|
2519
2519
|
const contractRd = (0, import_params_resolvers.buildFlowModelResolveDescriptor)(
|
|
2520
2520
|
this,
|
|
2521
|
-
options == null ? void 0 : options.contractModelUid
|
|
2521
|
+
(options == null ? void 0 : options.contractModelUid) ?? this.variableContractModelUid
|
|
2522
2522
|
);
|
|
2523
2523
|
serverResolved = await (0, import_params_resolvers.enqueueVariablesResolve)(this, {
|
|
2524
2524
|
...contractRd ? { contractRd } : {},
|
|
@@ -2986,11 +2986,16 @@ __name(_FlowForkModelContext, "FlowForkModelContext");
|
|
|
2986
2986
|
let FlowForkModelContext = _FlowForkModelContext;
|
|
2987
2987
|
const _FlowRuntimeContext = class _FlowRuntimeContext extends BaseFlowModelContext {
|
|
2988
2988
|
constructor(model, flowKey, _mode = "runtime") {
|
|
2989
|
+
var _a, _b;
|
|
2989
2990
|
super();
|
|
2990
2991
|
this.model = model;
|
|
2991
2992
|
this.flowKey = flowKey;
|
|
2992
2993
|
this._mode = _mode;
|
|
2993
2994
|
this.addDelegate(this.model.context);
|
|
2995
|
+
const owner = (_b = (_a = model.getFlow) == null ? void 0 : _a.call(model, flowKey)) == null ? void 0 : _b.model;
|
|
2996
|
+
if (owner && owner.uid !== model.uid) {
|
|
2997
|
+
this.defineProperty("variableContractModelUid", { value: owner.uid });
|
|
2998
|
+
}
|
|
2994
2999
|
this.defineMethod("getStepParams", (stepKey) => {
|
|
2995
3000
|
return model.getStepParams(flowKey, stepKey) || {};
|
|
2996
3001
|
});
|
|
@@ -38,7 +38,7 @@ export declare class CollectionFieldModel<T extends DefaultStructure = DefaultSt
|
|
|
38
38
|
get collectionField(): CollectionField;
|
|
39
39
|
afterAddAsSubModel(): Promise<void>;
|
|
40
40
|
static getBindingsByField(ctx: FlowEngineContext, collectionField: CollectionField): BindingOptions[];
|
|
41
|
-
static getDefaultBindingByField(ctx: FlowEngineContext, collectionField: CollectionField, options?: {
|
|
41
|
+
static getDefaultBindingByField(ctx: FlowEngineContext, collectionField: CollectionField | null | undefined, options?: {
|
|
42
42
|
useStrict?: boolean;
|
|
43
43
|
fallbackToTargetTitleField?: boolean;
|
|
44
44
|
targetCollectionTitleField?: CollectionField;
|
|
@@ -83,7 +83,7 @@ function FieldDeletePlaceholder() {
|
|
|
83
83
|
const dataSourcePrefix = dataSource ? `${t(dataSource.displayName || dataSource.key)} > ` : "";
|
|
84
84
|
const collectionPrefix = collection ? `${t(collection.title) || collection.name || collection.tableName} > ` : "";
|
|
85
85
|
return `${dataSourcePrefix}${collectionPrefix}${name}`;
|
|
86
|
-
}, []);
|
|
86
|
+
}, [collection, dataSource, name, t]);
|
|
87
87
|
return /* @__PURE__ */ import_react.default.createElement(import_antd.Form.Item, null, /* @__PURE__ */ import_react.default.createElement(
|
|
88
88
|
"div",
|
|
89
89
|
{
|
|
@@ -111,7 +111,7 @@ function FieldWithoutPermissionPlaceholder() {
|
|
|
111
111
|
const dataSourcePrefix = `${t(dataSource.displayName || dataSource.key)} > `;
|
|
112
112
|
const collectionPrefix = collection ? `${t(collection.title) || collection.name || collection.tableName} > ` : "";
|
|
113
113
|
return `${dataSourcePrefix}${collectionPrefix}${name}`;
|
|
114
|
-
}, []);
|
|
114
|
+
}, [collection, dataSource.displayName, dataSource.key, name, t]);
|
|
115
115
|
const { actionName } = model.forbidden;
|
|
116
116
|
const messageValue = (0, import_react.useMemo)(() => {
|
|
117
117
|
return t(
|
|
@@ -121,7 +121,7 @@ function FieldWithoutPermissionPlaceholder() {
|
|
|
121
121
|
actionName: t(import_lodash.default.capitalize(actionName))
|
|
122
122
|
}
|
|
123
123
|
).replaceAll(">", ">");
|
|
124
|
-
}, [nameValue, t]);
|
|
124
|
+
}, [actionName, nameValue, t]);
|
|
125
125
|
return /* @__PURE__ */ import_react.default.createElement(import_antd.Tooltip, { title: messageValue }, /* @__PURE__ */ import_react.default.createElement(import_FormItem.FormItem, { showLabel: true, label: model.context.collectionField.title || name, style: { opacity: "0.4" } }, /* @__PURE__ */ import_react.default.createElement(import_icons.LockOutlined, null)));
|
|
126
126
|
}
|
|
127
127
|
__name(FieldWithoutPermissionPlaceholder, "FieldWithoutPermissionPlaceholder");
|
|
@@ -188,6 +188,9 @@ const _CollectionFieldModel = class _CollectionFieldModel extends import_flowMod
|
|
|
188
188
|
);
|
|
189
189
|
}
|
|
190
190
|
static getDefaultBindingByField(ctx, collectionField, options = {}) {
|
|
191
|
+
if (!collectionField) {
|
|
192
|
+
return null;
|
|
193
|
+
}
|
|
191
194
|
if (options.fallbackToTargetTitleField) {
|
|
192
195
|
const binding = this.getDefaultBindingByField(ctx, collectionField, { useStrict: true });
|
|
193
196
|
if (!binding) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@nocobase/flow-engine",
|
|
3
|
-
"version": "2.3.0-beta.
|
|
3
|
+
"version": "2.3.0-beta.9",
|
|
4
4
|
"private": false,
|
|
5
5
|
"description": "A standalone flow engine for NocoBase, managing workflows, models, and actions.",
|
|
6
6
|
"main": "lib/index.js",
|
|
@@ -8,8 +8,8 @@
|
|
|
8
8
|
"dependencies": {
|
|
9
9
|
"@formily/antd-v5": "1.x",
|
|
10
10
|
"@formily/reactive": "2.x",
|
|
11
|
-
"@nocobase/sdk": "2.3.0-beta.
|
|
12
|
-
"@nocobase/shared": "2.3.0-beta.
|
|
11
|
+
"@nocobase/sdk": "2.3.0-beta.9",
|
|
12
|
+
"@nocobase/shared": "2.3.0-beta.9",
|
|
13
13
|
"ahooks": "^3.7.2",
|
|
14
14
|
"axios": "^1.7.0",
|
|
15
15
|
"dayjs": "^1.11.9",
|
|
@@ -37,5 +37,5 @@
|
|
|
37
37
|
],
|
|
38
38
|
"author": "NocoBase Team",
|
|
39
39
|
"license": "Apache-2.0",
|
|
40
|
-
"gitHead": "
|
|
40
|
+
"gitHead": "c215e60ed704afe6c086bd96304f4b0d3b46d02e"
|
|
41
41
|
}
|
package/src/FlowDefinition.ts
CHANGED
|
@@ -9,7 +9,8 @@
|
|
|
9
9
|
|
|
10
10
|
import { describe, expect, it, vi } from 'vitest';
|
|
11
11
|
import { generateFlowModelRdFromToken } from '@nocobase/utils/client';
|
|
12
|
-
import { FlowContext } from '../flowContext';
|
|
12
|
+
import { FlowContext, FlowRuntimeContext } from '../flowContext';
|
|
13
|
+
import { FlowModel } from '../models';
|
|
13
14
|
import { FlowEngine } from '../flowEngine';
|
|
14
15
|
import {
|
|
15
16
|
createAssociationAwareObjectMetaFactory,
|
|
@@ -490,4 +491,39 @@ describe('objectVariable utilities', () => {
|
|
|
490
491
|
expect(resolved).toEqual({ x: 'T1' });
|
|
491
492
|
expect((ctx as any).api.request).not.toHaveBeenCalled();
|
|
492
493
|
});
|
|
494
|
+
|
|
495
|
+
it('keeps runtime and configuration descriptors separate for a forwarded instance flow', async () => {
|
|
496
|
+
const engine = new FlowEngine();
|
|
497
|
+
const owner = new FlowModel({ uid: 'reference-owner', flowEngine: engine });
|
|
498
|
+
const target = new FlowModel({ uid: 'reference-target', flowEngine: engine });
|
|
499
|
+
owner.registerFlow({ key: 'forwarded', steps: {} });
|
|
500
|
+
const getFlow = vi.spyOn(target, 'getFlow').mockReturnValue(owner.getFlow('forwarded'));
|
|
501
|
+
const context = new FlowRuntimeContext(target, 'forwarded');
|
|
502
|
+
getFlow.mockRestore(); // Delayed getVar calls must retain the owner after the event bridge is restored.
|
|
503
|
+
|
|
504
|
+
type ResolveItem = { id: string; template: unknown; rd?: string; contractRd?: string };
|
|
505
|
+
const calls: ResolveItem[] = [];
|
|
506
|
+
const payload = Buffer.from(JSON.stringify({ userId: 1, signInTime: 'forwarded-flow' })).toString('base64url');
|
|
507
|
+
const token = `test.${payload}.sig`;
|
|
508
|
+
engine.context.defineProperty('api', {
|
|
509
|
+
value: {
|
|
510
|
+
auth: { token },
|
|
511
|
+
request: vi.fn(async ({ data }: { data: { values: { batch: ResolveItem[] } } }) => {
|
|
512
|
+
const batch = data.values.batch;
|
|
513
|
+
calls.push(...batch);
|
|
514
|
+
return { data: { data: { results: batch.map((item) => ({ id: item.id, data: 'resolved' })) } } };
|
|
515
|
+
}),
|
|
516
|
+
},
|
|
517
|
+
});
|
|
518
|
+
target.context.defineProperty('remote', { value: {}, resolveOnServer: true });
|
|
519
|
+
|
|
520
|
+
expect(await context.getVar('ctx.remote.name')).toBe('resolved');
|
|
521
|
+
expect(calls[0].rd).toBe(generateFlowModelRdFromToken(target.uid, token));
|
|
522
|
+
expect(calls[0].contractRd).toBe(generateFlowModelRdFromToken(owner.uid, token));
|
|
523
|
+
await context.resolveJsonTemplate('{{ ctx.remote.name }}', { contractModelUid: 'explicit-owner' });
|
|
524
|
+
expect(calls[1].contractRd).toBe(generateFlowModelRdFromToken('explicit-owner', token));
|
|
525
|
+
await new FlowRuntimeContext(target, 'local').resolveJsonTemplate('{{ ctx.remote.name }}');
|
|
526
|
+
expect(calls[2].contractRd).toBeUndefined();
|
|
527
|
+
expect(owner.getFlow('forwarded')?.serialize()).not.toHaveProperty('model');
|
|
528
|
+
});
|
|
493
529
|
});
|
|
@@ -10,10 +10,12 @@
|
|
|
10
10
|
import { FlowDefinitionOptions } from '../types';
|
|
11
11
|
import { FlowDefinition } from '../FlowDefinition';
|
|
12
12
|
import { observable } from '@formily/reactive';
|
|
13
|
+
import type { FlowModel } from '../models';
|
|
13
14
|
|
|
14
15
|
type FlowKey = string;
|
|
15
16
|
|
|
16
17
|
export interface IFlowRepository {
|
|
18
|
+
readonly model?: FlowModel;
|
|
17
19
|
addFlows(flowDefs: Record<string, Omit<FlowDefinitionOptions, 'key'>>): void;
|
|
18
20
|
addFlow(flowKey: string, flowOptions: Omit<FlowDefinitionOptions, 'key'>): FlowDefinition | void;
|
|
19
21
|
removeFlow(flowKey: string): void;
|
package/src/flowContext.ts
CHANGED
|
@@ -3051,6 +3051,7 @@ class BaseFlowEngineContext extends FlowContext {
|
|
|
3051
3051
|
*/
|
|
3052
3052
|
declare renderJson: (template: JSONValue) => Promise<any>;
|
|
3053
3053
|
declare resolveJsonTemplate: (template: JSONValue, options?: ResolveJsonTemplateOptions) => Promise<any>;
|
|
3054
|
+
declare variableContractModelUid?: string;
|
|
3054
3055
|
declare getVar: (path: string) => Promise<any>;
|
|
3055
3056
|
declare request: (options: RequestOptions) => Promise<any>;
|
|
3056
3057
|
declare runjs: (code: string, variables?: Record<string, any>, options?: JSRunnerOptions) => Promise<any>;
|
|
@@ -3408,7 +3409,7 @@ export class FlowEngineContext extends BaseFlowEngineContext {
|
|
|
3408
3409
|
try {
|
|
3409
3410
|
const contractRd = buildFlowModelResolveDescriptor(
|
|
3410
3411
|
this as FlowRuntimeContext<FlowModel>,
|
|
3411
|
-
options?.contractModelUid,
|
|
3412
|
+
options?.contractModelUid ?? this.variableContractModelUid,
|
|
3412
3413
|
);
|
|
3413
3414
|
serverResolved = await enqueueVariablesResolve(this as FlowRuntimeContext<FlowModel>, {
|
|
3414
3415
|
...(contractRd ? { contractRd } : {}),
|
|
@@ -3943,6 +3944,11 @@ export class FlowRuntimeContext<
|
|
|
3943
3944
|
) {
|
|
3944
3945
|
super();
|
|
3945
3946
|
this.addDelegate(this.model.context);
|
|
3947
|
+
const owner = model.getFlow?.(flowKey)?.model;
|
|
3948
|
+
if (owner && owner.uid !== model.uid) {
|
|
3949
|
+
// A forwarded instance flow keeps its configuration owner, without changing the runtime model.
|
|
3950
|
+
this.defineProperty('variableContractModelUid', { value: owner.uid });
|
|
3951
|
+
}
|
|
3946
3952
|
this.defineMethod('getStepParams', (stepKey: string) => {
|
|
3947
3953
|
return model.getStepParams(flowKey, stepKey) || {};
|
|
3948
3954
|
});
|
|
@@ -51,7 +51,7 @@ export function FieldDeletePlaceholder() {
|
|
|
51
51
|
const dataSourcePrefix = dataSource ? `${t(dataSource.displayName || dataSource.key)} > ` : '';
|
|
52
52
|
const collectionPrefix = collection ? `${t(collection.title) || collection.name || collection.tableName} > ` : '';
|
|
53
53
|
return `${dataSourcePrefix}${collectionPrefix}${name}`;
|
|
54
|
-
}, []);
|
|
54
|
+
}, [collection, dataSource, name, t]);
|
|
55
55
|
return (
|
|
56
56
|
<Form.Item>
|
|
57
57
|
<div
|
|
@@ -80,7 +80,7 @@ function FieldWithoutPermissionPlaceholder() {
|
|
|
80
80
|
const dataSourcePrefix = `${t(dataSource.displayName || dataSource.key)} > `;
|
|
81
81
|
const collectionPrefix = collection ? `${t(collection.title) || collection.name || collection.tableName} > ` : '';
|
|
82
82
|
return `${dataSourcePrefix}${collectionPrefix}${name}`;
|
|
83
|
-
}, []);
|
|
83
|
+
}, [collection, dataSource.displayName, dataSource.key, name, t]);
|
|
84
84
|
const { actionName } = model.forbidden;
|
|
85
85
|
const messageValue = useMemo(() => {
|
|
86
86
|
return t(
|
|
@@ -90,7 +90,7 @@ function FieldWithoutPermissionPlaceholder() {
|
|
|
90
90
|
actionName: t(_.capitalize(actionName)),
|
|
91
91
|
},
|
|
92
92
|
).replaceAll('>', '>');
|
|
93
|
-
}, [nameValue, t]);
|
|
93
|
+
}, [actionName, nameValue, t]);
|
|
94
94
|
|
|
95
95
|
return (
|
|
96
96
|
<Tooltip title={messageValue}>
|
|
@@ -198,13 +198,16 @@ export class CollectionFieldModel<T extends DefaultStructure = DefaultStructure>
|
|
|
198
198
|
|
|
199
199
|
static getDefaultBindingByField(
|
|
200
200
|
ctx: FlowEngineContext,
|
|
201
|
-
collectionField: CollectionField,
|
|
201
|
+
collectionField: CollectionField | null | undefined,
|
|
202
202
|
options: {
|
|
203
203
|
useStrict?: boolean;
|
|
204
204
|
fallbackToTargetTitleField?: boolean;
|
|
205
205
|
targetCollectionTitleField?: CollectionField;
|
|
206
206
|
} = {},
|
|
207
207
|
): BindingOptions | null {
|
|
208
|
+
if (!collectionField) {
|
|
209
|
+
return null;
|
|
210
|
+
}
|
|
208
211
|
if (options.fallbackToTargetTitleField) {
|
|
209
212
|
const binding = this.getDefaultBindingByField(ctx, collectionField, { useStrict: true });
|
|
210
213
|
if (!binding) {
|
|
@@ -108,6 +108,11 @@ describe('CollectionFieldModel', () => {
|
|
|
108
108
|
expect(defaultBinding).toBeNull();
|
|
109
109
|
});
|
|
110
110
|
|
|
111
|
+
it('should return null when the collection field has been deleted', () => {
|
|
112
|
+
const defaultBinding = TestModel.getDefaultBindingByField(mockContext, undefined);
|
|
113
|
+
expect(defaultBinding).toBeNull();
|
|
114
|
+
});
|
|
115
|
+
|
|
111
116
|
it('should return null if no bindings exist for the interface', () => {
|
|
112
117
|
class Test1Model extends CollectionFieldModel {}
|
|
113
118
|
class Test2Model extends Test1Model {}
|