@nocobase/flow-engine 2.0.31 → 2.0.32
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/flowContext.js +27 -0
- package/package.json +5 -4
- package/src/__tests__/flowContext.test.ts +65 -1
- package/src/flowContext.ts +30 -0
package/lib/flowContext.js
CHANGED
|
@@ -57,6 +57,7 @@ __export(flowContext_exports, {
|
|
|
57
57
|
});
|
|
58
58
|
module.exports = __toCommonJS(flowContext_exports);
|
|
59
59
|
var import_reactive = require("@formily/reactive");
|
|
60
|
+
var import_axios = __toESM(require("axios"));
|
|
60
61
|
var antd = __toESM(require("antd"));
|
|
61
62
|
var import_lodash = __toESM(require("lodash"));
|
|
62
63
|
var import_qs = __toESM(require("qs"));
|
|
@@ -81,6 +82,28 @@ var import_dayjs = __toESM(require("dayjs"));
|
|
|
81
82
|
var import_runjsLibs = require("./runjsLibs");
|
|
82
83
|
var import_runjsModuleLoader = require("./utils/runjsModuleLoader");
|
|
83
84
|
var _proxy, _FlowContext_instances, createChildNodes_fn, findMetaByPath_fn, findMetaInDelegatesDeep_fn, findMetaInProperty_fn, resolvePathInMeta_fn, resolvePathInMetaAsync_fn, buildParentTitles_fn, toTreeNode_fn;
|
|
85
|
+
function normalizePathname(pathname) {
|
|
86
|
+
return pathname.endsWith("/") ? pathname : `${pathname}/`;
|
|
87
|
+
}
|
|
88
|
+
__name(normalizePathname, "normalizePathname");
|
|
89
|
+
function shouldBypassApiClient(url, app) {
|
|
90
|
+
try {
|
|
91
|
+
const requestUrl = new URL(url);
|
|
92
|
+
if (!["http:", "https:"].includes(requestUrl.protocol)) {
|
|
93
|
+
return false;
|
|
94
|
+
}
|
|
95
|
+
if (!(app == null ? void 0 : app.getApiUrl)) {
|
|
96
|
+
return true;
|
|
97
|
+
}
|
|
98
|
+
const apiUrl = new URL(app.getApiUrl());
|
|
99
|
+
const apiPath = normalizePathname(apiUrl.pathname);
|
|
100
|
+
const requestPath = normalizePathname(requestUrl.pathname);
|
|
101
|
+
return requestUrl.origin !== apiUrl.origin || !requestPath.startsWith(apiPath);
|
|
102
|
+
} catch {
|
|
103
|
+
return false;
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
__name(shouldBypassApiClient, "shouldBypassApiClient");
|
|
84
107
|
function isRecordRefLike(val) {
|
|
85
108
|
return !!(val && typeof val === "object" && "collection" in val && "filterByTk" in val);
|
|
86
109
|
}
|
|
@@ -2211,6 +2234,10 @@ const _BaseFlowEngineContext = class _BaseFlowEngineContext extends FlowContext
|
|
|
2211
2234
|
return this.engine.getModel(modelName, searchInPreviousEngines);
|
|
2212
2235
|
});
|
|
2213
2236
|
this.defineMethod("request", (options) => {
|
|
2237
|
+
const app = this.app;
|
|
2238
|
+
if (typeof (options == null ? void 0 : options.url) === "string" && shouldBypassApiClient(options.url, app)) {
|
|
2239
|
+
return import_axios.default.request(options);
|
|
2240
|
+
}
|
|
2214
2241
|
return this.api.request(options);
|
|
2215
2242
|
});
|
|
2216
2243
|
this.defineMethod(
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@nocobase/flow-engine",
|
|
3
|
-
"version": "2.0.
|
|
3
|
+
"version": "2.0.32",
|
|
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,9 +8,10 @@
|
|
|
8
8
|
"dependencies": {
|
|
9
9
|
"@formily/antd-v5": "1.x",
|
|
10
10
|
"@formily/reactive": "2.x",
|
|
11
|
-
"@nocobase/sdk": "2.0.
|
|
12
|
-
"@nocobase/shared": "2.0.
|
|
11
|
+
"@nocobase/sdk": "2.0.32",
|
|
12
|
+
"@nocobase/shared": "2.0.32",
|
|
13
13
|
"ahooks": "^3.7.2",
|
|
14
|
+
"axios": "^1.7.0",
|
|
14
15
|
"dayjs": "^1.11.9",
|
|
15
16
|
"dompurify": "^3.0.2",
|
|
16
17
|
"lodash": "^4.x",
|
|
@@ -36,5 +37,5 @@
|
|
|
36
37
|
],
|
|
37
38
|
"author": "NocoBase Team",
|
|
38
39
|
"license": "Apache-2.0",
|
|
39
|
-
"gitHead": "
|
|
40
|
+
"gitHead": "ad454bc0bc27dee7e931644200317d034c816e3e"
|
|
40
41
|
}
|
|
@@ -7,7 +7,8 @@
|
|
|
7
7
|
* For more information, please refer to: https://www.nocobase.com/agreement.
|
|
8
8
|
*/
|
|
9
9
|
|
|
10
|
-
import
|
|
10
|
+
import axios from 'axios';
|
|
11
|
+
import { describe, expect, it, vi, afterEach } from 'vitest';
|
|
11
12
|
import { FlowContext, FlowRuntimeContext, FlowRunJSContext, type PropertyMetaFactory } from '../flowContext';
|
|
12
13
|
import { FlowEngine } from '../flowEngine';
|
|
13
14
|
import { FlowModel } from '../models/flowModel';
|
|
@@ -1630,6 +1631,69 @@ describe('runAction delegation from runtime context', () => {
|
|
|
1630
1631
|
});
|
|
1631
1632
|
});
|
|
1632
1633
|
|
|
1634
|
+
describe('FlowContext request defaults', () => {
|
|
1635
|
+
class RequestModel extends FlowModel {}
|
|
1636
|
+
|
|
1637
|
+
afterEach(() => {
|
|
1638
|
+
vi.restoreAllMocks();
|
|
1639
|
+
});
|
|
1640
|
+
|
|
1641
|
+
const createRequestContext = () => {
|
|
1642
|
+
const engine = new FlowEngine();
|
|
1643
|
+
engine.registerModels({ RequestModel });
|
|
1644
|
+
|
|
1645
|
+
const apiRequest = vi.fn(async (options) => options);
|
|
1646
|
+
const app = {
|
|
1647
|
+
getApiUrl(pathname = '') {
|
|
1648
|
+
return 'https://app.example.com/api/'.replace(/\/$/g, '') + '/' + pathname.replace(/^\//g, '');
|
|
1649
|
+
},
|
|
1650
|
+
};
|
|
1651
|
+
|
|
1652
|
+
engine.context.defineProperty('api', { value: { request: apiRequest } as any });
|
|
1653
|
+
engine.context.defineProperty('app', { value: app });
|
|
1654
|
+
|
|
1655
|
+
const model = engine.createModel({ use: 'RequestModel' });
|
|
1656
|
+
const ctx = new FlowRuntimeContext(model, 'flow');
|
|
1657
|
+
const directAxiosRequest = vi.spyOn(axios, 'request').mockResolvedValue({ data: {} } as any);
|
|
1658
|
+
|
|
1659
|
+
return { ctx, apiRequest, directAxiosRequest };
|
|
1660
|
+
};
|
|
1661
|
+
|
|
1662
|
+
it.each([
|
|
1663
|
+
['apiClient', 'users:list', 'api'],
|
|
1664
|
+
['apiClient', '/api/users:list', 'api'],
|
|
1665
|
+
['apiClient', 'https://app.example.com/api/users:list', 'api'],
|
|
1666
|
+
['direct axios', 'https://app.example.com/custom-api/users', 'axios'],
|
|
1667
|
+
])('should use %s for %s', async (_target, url, expected) => {
|
|
1668
|
+
const { ctx, apiRequest, directAxiosRequest } = createRequestContext();
|
|
1669
|
+
|
|
1670
|
+
await ctx.request({ url, method: 'get' });
|
|
1671
|
+
|
|
1672
|
+
if (expected === 'api') {
|
|
1673
|
+
expect(apiRequest).toHaveBeenCalledTimes(1);
|
|
1674
|
+
expect(directAxiosRequest).not.toHaveBeenCalled();
|
|
1675
|
+
return;
|
|
1676
|
+
}
|
|
1677
|
+
|
|
1678
|
+
expect(directAxiosRequest).toHaveBeenCalledTimes(1);
|
|
1679
|
+
expect(apiRequest).not.toHaveBeenCalled();
|
|
1680
|
+
});
|
|
1681
|
+
|
|
1682
|
+
it('should use direct axios for cross-origin absolute urls', async () => {
|
|
1683
|
+
const { ctx, apiRequest, directAxiosRequest } = createRequestContext();
|
|
1684
|
+
|
|
1685
|
+
await ctx.request({ url: 'https://api.example.com/users', method: 'get', skipAuth: false });
|
|
1686
|
+
|
|
1687
|
+
expect(directAxiosRequest).toHaveBeenCalledTimes(1);
|
|
1688
|
+
expect(apiRequest).not.toHaveBeenCalled();
|
|
1689
|
+
expect(directAxiosRequest.mock.calls[0][0]).toMatchObject({
|
|
1690
|
+
url: 'https://api.example.com/users',
|
|
1691
|
+
method: 'get',
|
|
1692
|
+
skipAuth: false,
|
|
1693
|
+
});
|
|
1694
|
+
});
|
|
1695
|
+
});
|
|
1696
|
+
|
|
1633
1697
|
describe('FlowContext delayed meta loading', () => {
|
|
1634
1698
|
// 测试场景:属性定义时 meta 为异步函数,首次访问时延迟加载
|
|
1635
1699
|
// 输入:属性带有异步 meta 函数
|
package/src/flowContext.ts
CHANGED
|
@@ -11,6 +11,7 @@ import { ISchema } from '@formily/json-schema';
|
|
|
11
11
|
import { observable } from '@formily/reactive';
|
|
12
12
|
import { APIClient, RequestOptions } from '@nocobase/sdk';
|
|
13
13
|
import type { Router } from '@remix-run/router';
|
|
14
|
+
import axios from 'axios';
|
|
14
15
|
import { MessageInstance } from 'antd/es/message/interface';
|
|
15
16
|
import * as antd from 'antd';
|
|
16
17
|
import type { HookAPI } from 'antd/es/modal/useModal';
|
|
@@ -58,6 +59,31 @@ import dayjs from 'dayjs';
|
|
|
58
59
|
import { externalReactRender, setupRunJSLibs } from './runjsLibs';
|
|
59
60
|
import { runjsImportAsync, runjsImportModule, runjsRequireAsync } from './utils/runjsModuleLoader';
|
|
60
61
|
|
|
62
|
+
function normalizePathname(pathname: string) {
|
|
63
|
+
return pathname.endsWith('/') ? pathname : `${pathname}/`;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
function shouldBypassApiClient(url: string, app?: { getApiUrl?: (pathname?: string) => string }) {
|
|
67
|
+
try {
|
|
68
|
+
const requestUrl = new URL(url);
|
|
69
|
+
if (!['http:', 'https:'].includes(requestUrl.protocol)) {
|
|
70
|
+
return false;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
if (!app?.getApiUrl) {
|
|
74
|
+
return true;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
const apiUrl = new URL(app.getApiUrl());
|
|
78
|
+
const apiPath = normalizePathname(apiUrl.pathname);
|
|
79
|
+
const requestPath = normalizePathname(requestUrl.pathname);
|
|
80
|
+
|
|
81
|
+
return requestUrl.origin !== apiUrl.origin || !requestPath.startsWith(apiPath);
|
|
82
|
+
} catch {
|
|
83
|
+
return false;
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
61
87
|
// Helper: detect a RecordRef-like object
|
|
62
88
|
function isRecordRefLike(val: any): boolean {
|
|
63
89
|
return !!(val && typeof val === 'object' && 'collection' in val && 'filterByTk' in val);
|
|
@@ -3024,6 +3050,10 @@ class BaseFlowEngineContext extends FlowContext {
|
|
|
3024
3050
|
return this.engine.getModel(modelName, searchInPreviousEngines);
|
|
3025
3051
|
});
|
|
3026
3052
|
this.defineMethod('request', (options: RequestOptions) => {
|
|
3053
|
+
const app = this.app as { getApiUrl?: (pathname?: string) => string } | undefined;
|
|
3054
|
+
if (typeof options?.url === 'string' && shouldBypassApiClient(options.url, app)) {
|
|
3055
|
+
return axios.request(options);
|
|
3056
|
+
}
|
|
3027
3057
|
return this.api.request(options);
|
|
3028
3058
|
});
|
|
3029
3059
|
this.defineMethod(
|