@flusys/nestjs-entity-builder 9.1.0-rc.1 → 9.1.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/README.md +4 -2
- package/config/message-keys.d.ts +9 -1
- package/controllers/flow-runtime.controller.d.ts +16 -8
- package/dtos/flow.dto.d.ts +1 -0
- package/fesm/21.js +9 -1
- package/fesm/362.js +6 -6
- package/fesm/606.js +58 -7
- package/fesm/719.js +158 -152
- package/fesm/996.js +175 -52
- package/fesm/guards/index.js +6 -1
- package/fesm/index.js +16 -5
- package/fesm/modules/index.js +3 -2
- package/fesm/services/index.js +5 -3
- package/flow-engine/flow-graph.validator.d.ts +2 -3
- package/flow-engine/flow-run.types.d.ts +2 -1
- package/flow-engine/flow.types.d.ts +16 -0
- package/package.json +3 -3
- package/services/entity-flow-scaffold.d.ts +3 -2
- package/services/flow-definition.service.d.ts +1 -0
- package/services/flow-executor.service.d.ts +1 -0
- package/services/flow-runtime.service.d.ts +2 -1
- package/services/schema-sync.service.d.ts +1 -1
package/fesm/996.js
CHANGED
|
@@ -1034,7 +1034,7 @@ async function insertChildren(node, child, parentId, env, state) {
|
|
|
1034
1034
|
const cfg = c;
|
|
1035
1035
|
const input = cfg.inputList !== undefined ? await env.ruleEngine.resolveExpression(cfg.inputList, evaluationContext(state, env)) : await mapValues(cfg.input, env, state);
|
|
1036
1036
|
return {
|
|
1037
|
-
output: await env.callFlow(cfg.flowSlug, input, state)
|
|
1037
|
+
output: await env.callFlow(cfg.flowSlug, input, state, cfg.endpoint || undefined)
|
|
1038
1038
|
};
|
|
1039
1039
|
}
|
|
1040
1040
|
case 'http_request':
|
|
@@ -1128,7 +1128,8 @@ async function insertChildren(node, child, parentId, env, state) {
|
|
|
1128
1128
|
|
|
1129
1129
|
const errorMessage = (error)=>error instanceof Error ? error.message : String(error);
|
|
1130
1130
|
/**
|
|
1131
|
-
* Walks a flow graph from
|
|
1131
|
+
* Walks a flow graph from the Request step the call came in on (`state.startAt`; left out, the one
|
|
1132
|
+
* on the flow's own URL): runs a node, follows the output port it chose, repeats.
|
|
1132
1133
|
* `foreach` runs its `each` branch once per item and then continues on `done`; inside a nested
|
|
1133
1134
|
* loop the outer loop's scope stays reachable as `loop.parent`. A `respond` node
|
|
1134
1135
|
* ends the whole run. Limits (steps, time) are checked before every node.
|
|
@@ -1147,7 +1148,7 @@ const errorMessage = (error)=>error instanceof Error ? error.message : String(er
|
|
|
1147
1148
|
/** Runs to completion or throws (FlowNodeError wraps whatever a node threw). */ async walk(state) {
|
|
1148
1149
|
const trigger = [
|
|
1149
1150
|
...this.nodes.values()
|
|
1150
|
-
].find((n)=>n.type === 'trigger');
|
|
1151
|
+
].find((n)=>n.type === 'trigger' && (state.startAt ? n.id === state.startAt : !(0,flow_types/* .triggerPath */.um)(n)));
|
|
1151
1152
|
if (!trigger) throw new flow_errors/* .FlowLimitError */.IM(message_keys/* .FLOW_ERROR_MESSAGES.NO_TRIGGER */.zr.NO_TRIGGER);
|
|
1152
1153
|
await this.run(trigger.id, state);
|
|
1153
1154
|
}
|
|
@@ -1741,7 +1742,7 @@ function conditionProblem(node, options) {
|
|
|
1741
1742
|
return null;
|
|
1742
1743
|
}
|
|
1743
1744
|
/**
|
|
1744
|
-
* Save-time check of a whole flow: structure (
|
|
1745
|
+
* Save-time check of a whole flow: structure (Request steps with distinct URLs, valid ids/ports, no cycles),
|
|
1745
1746
|
* every node's configuration, every reference to another node, and the combination of
|
|
1746
1747
|
* settings (run-as / auth / transaction). Pure: no I/O, so the same result is what the
|
|
1747
1748
|
* designer shows and what the server enforces.
|
|
@@ -1791,9 +1792,7 @@ function conditionProblem(node, options) {
|
|
|
1791
1792
|
}));
|
|
1792
1793
|
}
|
|
1793
1794
|
const triggers = nodes.filter((n)=>n.type === 'trigger');
|
|
1794
|
-
|
|
1795
|
-
count: triggers.length
|
|
1796
|
-
}));
|
|
1795
|
+
checkTriggers(triggers, err);
|
|
1797
1796
|
for (const node of nodes){
|
|
1798
1797
|
if (byId.get(node.id) === node) validateNodeConfig(node, flow, context, err, warnings);
|
|
1799
1798
|
}
|
|
@@ -1841,7 +1840,9 @@ function conditionProblem(node, options) {
|
|
|
1841
1840
|
}
|
|
1842
1841
|
const cyclic = hasCycle(nodes, outgoing);
|
|
1843
1842
|
if (cyclic) err((0,_flusys_nestjs_shared_utils__rspack_import_0.messageRef)(_config_message_keys_js__rspack_import_7/* .FLOW_VALIDATION_MESSAGES.CYCLE */.qj.CYCLE));
|
|
1844
|
-
const reachable = triggers.length
|
|
1843
|
+
const reachable = triggers.length ? new Set(triggers.flatMap((trigger)=>[
|
|
1844
|
+
...reach(trigger.id, outgoing)
|
|
1845
|
+
])) : null;
|
|
1845
1846
|
for (const node of nodes){
|
|
1846
1847
|
if (reachable && !reachable.has(node.id)) warnings.push((0,_flusys_nestjs_shared_utils__rspack_import_0.messageRef)(_config_message_keys_js__rspack_import_7/* .FLOW_VALIDATION_MESSAGES.NODE_UNREACHABLE */.qj.NODE_UNREACHABLE, {
|
|
1847
1848
|
node: (0,_flow_types_js__rspack_import_6/* .nodeLabel */.dA)(node)
|
|
@@ -1865,11 +1866,27 @@ function conditionProblem(node, options) {
|
|
|
1865
1866
|
warnings
|
|
1866
1867
|
};
|
|
1867
1868
|
}
|
|
1869
|
+
/** At least one Request step; at most one on the flow's own URL, the others on distinct paths. */ function checkTriggers(triggers, err) {
|
|
1870
|
+
if (!triggers.length) return err((0,_flusys_nestjs_shared_utils__rspack_import_0.messageRef)(_config_message_keys_js__rspack_import_7/* .FLOW_VALIDATION_MESSAGES.TRIGGER_MISSING */.qj.TRIGGER_MISSING));
|
|
1871
|
+
const main = triggers.filter((trigger)=>!(0,_flow_types_js__rspack_import_6/* .triggerPath */.um)(trigger));
|
|
1872
|
+
if (main.length > 1) err((0,_flusys_nestjs_shared_utils__rspack_import_0.messageRef)(_config_message_keys_js__rspack_import_7/* .FLOW_VALIDATION_MESSAGES.TRIGGER_MAIN_DUPLICATE */.qj.TRIGGER_MAIN_DUPLICATE, {
|
|
1873
|
+
count: main.length
|
|
1874
|
+
}));
|
|
1875
|
+
const paths = new Set();
|
|
1876
|
+
for (const trigger of triggers){
|
|
1877
|
+
const path = (0,_flow_types_js__rspack_import_6/* .triggerPath */.um)(trigger);
|
|
1878
|
+
if (!path) continue;
|
|
1879
|
+
if (paths.has(path)) err((0,_flusys_nestjs_shared_utils__rspack_import_0.messageRef)(_config_message_keys_js__rspack_import_7/* .FLOW_VALIDATION_MESSAGES.TRIGGER_PATH_DUPLICATE */.qj.TRIGGER_PATH_DUPLICATE, {
|
|
1880
|
+
path
|
|
1881
|
+
}));
|
|
1882
|
+
paths.add(path);
|
|
1883
|
+
}
|
|
1884
|
+
}
|
|
1868
1885
|
/**
|
|
1869
1886
|
* What the runtime would always refuse, caught on save: an API-key flow called by a flow running
|
|
1870
1887
|
* as the caller, a required input left out, a chain that comes back to this flow or nests deeper
|
|
1871
1888
|
* than the limit. An inactive flow and an input the called flow does not declare are warnings.
|
|
1872
|
-
*/ function checkCalledFlow(slug, input, inputList, flow, known, at, err, warn) {
|
|
1889
|
+
*/ function checkCalledFlow(slug, endpoint, input, inputList, flow, known, at, err, warn) {
|
|
1873
1890
|
const target = known.get(slug);
|
|
1874
1891
|
const vars = {
|
|
1875
1892
|
...at,
|
|
@@ -1881,12 +1898,19 @@ function conditionProblem(node, options) {
|
|
|
1881
1898
|
if (flow.authMode === 'internal') warn.push((0,_flusys_nestjs_shared_utils__rspack_import_0.messageRef)(_config_message_keys_js__rspack_import_7/* .FLOW_VALIDATION_MESSAGES.NODE_FLOW_API_KEY */.qj.NODE_FLOW_API_KEY, vars));
|
|
1882
1899
|
else if (flow.runAs === 'caller') err((0,_flusys_nestjs_shared_utils__rspack_import_0.messageRef)(_config_message_keys_js__rspack_import_7/* .FLOW_VALIDATION_MESSAGES.NODE_FLOW_API_KEY */.qj.NODE_FLOW_API_KEY, vars));
|
|
1883
1900
|
}
|
|
1884
|
-
|
|
1901
|
+
const path = typeof endpoint === 'string' && endpoint ? endpoint : null;
|
|
1902
|
+
const entry = target.endpoints.find((e)=>e.path === path);
|
|
1903
|
+
if (!entry) {
|
|
1904
|
+
err((0,_flusys_nestjs_shared_utils__rspack_import_0.messageRef)(path ? _config_message_keys_js__rspack_import_7/* .FLOW_VALIDATION_MESSAGES.NODE_FLOW_ENDPOINT_UNKNOWN */.qj.NODE_FLOW_ENDPOINT_UNKNOWN : _config_message_keys_js__rspack_import_7/* .FLOW_VALIDATION_MESSAGES.NODE_FLOW_ENDPOINT_REQUIRED */.qj.NODE_FLOW_ENDPOINT_REQUIRED, {
|
|
1905
|
+
...vars,
|
|
1906
|
+
endpoint: path ?? ''
|
|
1907
|
+
}));
|
|
1908
|
+
} else if (entry.bodyType === 'list') {
|
|
1885
1909
|
if (inputList === undefined) err((0,_flusys_nestjs_shared_utils__rspack_import_0.messageRef)(_config_message_keys_js__rspack_import_7/* .FLOW_VALIDATION_MESSAGES.NODE_FLOW_INPUT_LIST_REQUIRED */.qj.NODE_FLOW_INPUT_LIST_REQUIRED, vars));
|
|
1886
1910
|
} else if (inputList !== undefined) {
|
|
1887
1911
|
err((0,_flusys_nestjs_shared_utils__rspack_import_0.messageRef)(_config_message_keys_js__rspack_import_7/* .FLOW_VALIDATION_MESSAGES.NODE_FLOW_INPUT_LIST_UNEXPECTED */.qj.NODE_FLOW_INPUT_LIST_UNEXPECTED, vars));
|
|
1888
1912
|
} else {
|
|
1889
|
-
checkCalledInputs(input,
|
|
1913
|
+
checkCalledInputs(input, entry.inputSchema, vars, err, warn);
|
|
1890
1914
|
}
|
|
1891
1915
|
const loop = callPathTo(slug, flow.slug, known, new Set());
|
|
1892
1916
|
if (loop) return err((0,_flusys_nestjs_shared_utils__rspack_import_0.messageRef)(_config_message_keys_js__rspack_import_7/* .FLOW_VALIDATION_MESSAGES.NODE_FLOW_LOOP */.qj.NODE_FLOW_LOOP, {
|
|
@@ -1901,9 +1925,8 @@ function conditionProblem(node, options) {
|
|
|
1901
1925
|
max: _flow_types_js__rspack_import_6/* .FLOW_LIMITS.MAX_CALL_DEPTH */.Or.MAX_CALL_DEPTH
|
|
1902
1926
|
}));
|
|
1903
1927
|
}
|
|
1904
|
-
/** The named inputs sent to a flow whose body is an object: every required one given, the rest declared. */ function checkCalledInputs(input,
|
|
1928
|
+
/** The named inputs sent to a flow whose body is an object: every required one given, the rest declared. */ function checkCalledInputs(input, schema, vars, err, warn) {
|
|
1905
1929
|
const given = input && typeof input === 'object' && !Array.isArray(input) ? Object.keys(input) : [];
|
|
1906
|
-
const schema = target.inputSchema ?? [];
|
|
1907
1930
|
if (schema.length) {
|
|
1908
1931
|
const declared = new Set(schema.map((field)=>field.name));
|
|
1909
1932
|
for (const name of given)if (!declared.has(name)) warn.push((0,_flusys_nestjs_shared_utils__rspack_import_0.messageRef)(_config_message_keys_js__rspack_import_7/* .FLOW_VALIDATION_MESSAGES.NODE_FLOW_INPUT_UNDECLARED */.qj.NODE_FLOW_INPUT_UNDECLARED, {
|
|
@@ -2081,7 +2104,16 @@ function validateNodeConfig(node, flow, context, err, warn) {
|
|
|
2081
2104
|
};
|
|
2082
2105
|
switch(node.type){
|
|
2083
2106
|
case 'trigger':
|
|
2084
|
-
|
|
2107
|
+
{
|
|
2108
|
+
if (c.path !== undefined && c.path !== '' && (typeof c.path !== 'string' || !_flow_types_js__rspack_import_6/* .FLOW_ENDPOINT_PATH_PATTERN.test */.YO.test(c.path))) err((0,_flusys_nestjs_shared_utils__rspack_import_0.messageRef)(_config_message_keys_js__rspack_import_7/* .FLOW_VALIDATION_MESSAGES.TRIGGER_PATH_INVALID */.qj.TRIGGER_PATH_INVALID, at));
|
|
2109
|
+
const path = (0,_flow_types_js__rspack_import_6/* .triggerPath */.um)(node);
|
|
2110
|
+
if (!path) break;
|
|
2111
|
+
if (c.bodyType !== undefined && !_flow_types_js__rspack_import_6/* .FLOW_BODY_TYPES.includes */.Y3.includes(c.bodyType)) err((0,_flusys_nestjs_shared_utils__rspack_import_0.messageRef)(_config_message_keys_js__rspack_import_7/* .FLOW_VALIDATION_MESSAGES.BODY_TYPE_UNKNOWN */.qj.BODY_TYPE_UNKNOWN, {
|
|
2112
|
+
type: String(c.bodyType)
|
|
2113
|
+
}));
|
|
2114
|
+
validateInputFields(Array.isArray(c.inputSchema) ? c.inputSchema : [], `${path}: `, 1, err);
|
|
2115
|
+
break;
|
|
2116
|
+
}
|
|
2085
2117
|
case 'validate':
|
|
2086
2118
|
validateChecks(c, at, rule, err);
|
|
2087
2119
|
if (c.mode !== undefined && c.mode !== 'all' && c.mode !== 'first') err((0,_flusys_nestjs_shared_utils__rspack_import_0.messageRef)(_config_message_keys_js__rspack_import_7/* .FLOW_VALIDATION_MESSAGES.NODE_MODE_INVALID */.qj.NODE_MODE_INVALID, at));
|
|
@@ -2216,7 +2248,8 @@ function validateNodeConfig(node, flow, context, err, warn) {
|
|
|
2216
2248
|
...at,
|
|
2217
2249
|
flow: c.flowSlug
|
|
2218
2250
|
}));
|
|
2219
|
-
else if (context.knownFlows) checkCalledFlow(c.flowSlug, c.input, c.inputList, flow, context.knownFlows, at, err, warn);
|
|
2251
|
+
else if (context.knownFlows) checkCalledFlow(c.flowSlug, c.endpoint, c.input, c.inputList, flow, context.knownFlows, at, err, warn);
|
|
2252
|
+
if (c.endpoint !== undefined && c.endpoint !== '' && (typeof c.endpoint !== 'string' || !_flow_types_js__rspack_import_6/* .FLOW_ENDPOINT_PATH_PATTERN.test */.YO.test(c.endpoint))) err((0,_flusys_nestjs_shared_utils__rspack_import_0.messageRef)(_config_message_keys_js__rspack_import_7/* .FLOW_VALIDATION_MESSAGES.TRIGGER_PATH_INVALID */.qj.TRIGGER_PATH_INVALID, at));
|
|
2220
2253
|
exprMap(c.input, KIND.input);
|
|
2221
2254
|
if (c.inputList !== undefined) expr(c.inputList, (0,_flusys_nestjs_shared_utils__rspack_import_0.messageRef)(_config_message_keys_js__rspack_import_7/* .FLOW_SUBJECT_MESSAGES.THE_INPUT_LIST */.bc.THE_INPUT_LIST));
|
|
2222
2255
|
break;
|
|
@@ -2427,8 +2460,11 @@ function validateChecks(c, at, rule, err) {
|
|
|
2427
2460
|
}));
|
|
2428
2461
|
}
|
|
2429
2462
|
}
|
|
2430
|
-
/** Every `context.<node>` must name a node of this flow; `input.<name>` should be
|
|
2431
|
-
const
|
|
2463
|
+
/** Every `context.<node>` must name a node of this flow; `input.<name>` should be an input some Request step declares. */ function checkNodeReferences(nodes, byId, flow, err, warnings) {
|
|
2464
|
+
const endpoints = (0,_flow_types_js__rspack_import_6/* .flowEndpoints */.Xr)(flow);
|
|
2465
|
+
const declaredInputs = new Set(endpoints.flatMap((endpoint)=>endpoint.inputSchema.map((f)=>f.name)));
|
|
2466
|
+
// A list body is read by index, and an object body without fields takes anything: neither has names to check.
|
|
2467
|
+
const namesKnown = endpoints.length > 0 && endpoints.every((endpoint)=>endpoint.bodyType !== 'list' && endpoint.inputSchema.length > 0);
|
|
2432
2468
|
for (const node of nodes){
|
|
2433
2469
|
let refs;
|
|
2434
2470
|
try {
|
|
@@ -2452,7 +2488,7 @@ function validateChecks(c, at, rule, err) {
|
|
|
2452
2488
|
err((0,_flusys_nestjs_shared_utils__rspack_import_0.messageRef)(_config_message_keys_js__rspack_import_7/* .FLOW_VALIDATION_MESSAGES.REF_SELF */.qj.REF_SELF, {
|
|
2453
2489
|
node: (0,_flow_types_js__rspack_import_6/* .nodeLabel */.dA)(node)
|
|
2454
2490
|
}));
|
|
2455
|
-
} else if (scope === 'input' && second &&
|
|
2491
|
+
} else if (scope === 'input' && second && namesKnown && !declaredInputs.has(second)) {
|
|
2456
2492
|
warnings.push((0,_flusys_nestjs_shared_utils__rspack_import_0.messageRef)(_config_message_keys_js__rspack_import_7/* .FLOW_VALIDATION_MESSAGES.REF_INPUT_UNDECLARED */.qj.REF_INPUT_UNDECLARED, {
|
|
2457
2493
|
node: (0,_flow_types_js__rspack_import_6/* .nodeLabel */.dA)(node),
|
|
2458
2494
|
input: second
|
|
@@ -2662,8 +2698,7 @@ function reach(start, outgoing) {
|
|
|
2662
2698
|
nesting.set(id, max);
|
|
2663
2699
|
return max;
|
|
2664
2700
|
};
|
|
2665
|
-
|
|
2666
|
-
if (start && nestingFrom(start.id) > _flow_types_js__rspack_import_6/* .FLOW_LIMITS.MAX_LOOP_DEPTH */.Or.MAX_LOOP_DEPTH) err((0,_flusys_nestjs_shared_utils__rspack_import_0.messageRef)(_config_message_keys_js__rspack_import_7/* .FLOW_VALIDATION_MESSAGES.LOOP_TOO_DEEP */.qj.LOOP_TOO_DEEP, {
|
|
2701
|
+
if (nodes.some((n)=>n.type === 'trigger' && nestingFrom(n.id) > _flow_types_js__rspack_import_6/* .FLOW_LIMITS.MAX_LOOP_DEPTH */.Or.MAX_LOOP_DEPTH)) err((0,_flusys_nestjs_shared_utils__rspack_import_0.messageRef)(_config_message_keys_js__rspack_import_7/* .FLOW_VALIDATION_MESSAGES.LOOP_TOO_DEEP */.qj.LOOP_TOO_DEEP, {
|
|
2667
2702
|
max: _flow_types_js__rspack_import_6/* .FLOW_LIMITS.MAX_LOOP_DEPTH */.Or.MAX_LOOP_DEPTH
|
|
2668
2703
|
}));
|
|
2669
2704
|
}
|
|
@@ -3055,6 +3090,33 @@ const FLOW_LIMITS = {
|
|
|
3055
3090
|
const FLOW_NODE_ID_PATTERN = /^[A-Za-z][A-Za-z0-9_]{0,39}$/;
|
|
3056
3091
|
const FLOW_SLUG_PATTERN = /^[a-z][a-z0-9-]{2,62}$/;
|
|
3057
3092
|
const FLOW_INPUT_NAME_PATTERN = /^[A-Za-z][A-Za-z0-9_]{0,59}$/;
|
|
3093
|
+
const FLOW_ENDPOINT_PATH_PATTERN = /^[a-z][a-z0-9-]{0,62}$/;
|
|
3094
|
+
/** A Request step's path, or null when it answers on the flow's own URL. */ function triggerPath(node) {
|
|
3095
|
+
const path = node.config?.path;
|
|
3096
|
+
return typeof path === 'string' && path.trim() ? path.trim() : null;
|
|
3097
|
+
}
|
|
3098
|
+
/** Every Request step of a flow as a URL. */ function flowEndpoints(flow) {
|
|
3099
|
+
return (flow.nodes ?? []).filter((node)=>node?.type === 'trigger').map((node)=>{
|
|
3100
|
+
const path = triggerPath(node);
|
|
3101
|
+
if (!path) return {
|
|
3102
|
+
nodeId: node.id,
|
|
3103
|
+
path,
|
|
3104
|
+
bodyType: flow.bodyType ?? 'object',
|
|
3105
|
+
inputSchema: flow.inputSchema ?? []
|
|
3106
|
+
};
|
|
3107
|
+
const config = node.config;
|
|
3108
|
+
return {
|
|
3109
|
+
nodeId: node.id,
|
|
3110
|
+
path,
|
|
3111
|
+
bodyType: config.bodyType ?? 'object',
|
|
3112
|
+
inputSchema: Array.isArray(config.inputSchema) ? config.inputSchema : []
|
|
3113
|
+
};
|
|
3114
|
+
});
|
|
3115
|
+
}
|
|
3116
|
+
/** The URL a call came in on: `path` of a Request step, or null / empty for the flow's own URL. */ function findEndpoint(flow, path) {
|
|
3117
|
+
const wanted = path?.trim() || null;
|
|
3118
|
+
return flowEndpoints(flow).find((endpoint)=>endpoint.path === wanted);
|
|
3119
|
+
}
|
|
3058
3120
|
/** Output ports a node of this type can have (a `respond` node ends the flow: none). */ function portsOf(node) {
|
|
3059
3121
|
switch(node.type){
|
|
3060
3122
|
case 'condition':
|
|
@@ -3097,7 +3159,10 @@ const FLOW_INPUT_NAME_PATTERN = /^[A-Za-z][A-Za-z0-9_]{0,59}$/;
|
|
|
3097
3159
|
}
|
|
3098
3160
|
|
|
3099
3161
|
__webpack_require__.d(__webpack_exports__, {
|
|
3100
|
-
|
|
3162
|
+
Xr: () => (flowEndpoints),
|
|
3163
|
+
a5: () => (portsOf),
|
|
3164
|
+
um: () => (triggerPath),
|
|
3165
|
+
vS: () => (findEndpoint)
|
|
3101
3166
|
}, {
|
|
3102
3167
|
At: NON_CONTINUABLE_NODE_TYPES,
|
|
3103
3168
|
Dn: RECORD_WRITE_TARGETS,
|
|
@@ -3105,6 +3170,7 @@ __webpack_require__.d(__webpack_exports__, {
|
|
|
3105
3170
|
Q7: FLOW_INPUT_TYPES,
|
|
3106
3171
|
Y3: FLOW_BODY_TYPES,
|
|
3107
3172
|
YD: FLOW_SLUG_PATTERN,
|
|
3173
|
+
YO: FLOW_ENDPOINT_PATH_PATTERN,
|
|
3108
3174
|
dA: nodeLabel,
|
|
3109
3175
|
fO: FLOW_AUTH_MODES,
|
|
3110
3176
|
fZ: FLOW_INPUT_NAME_PATTERN,
|
|
@@ -4100,8 +4166,7 @@ class FlowDefinitionService extends _flusys_nestjs_shared_classes__rspack_import
|
|
|
4100
4166
|
{
|
|
4101
4167
|
isActive: flow.isActive,
|
|
4102
4168
|
authMode: flow.authMode,
|
|
4103
|
-
|
|
4104
|
-
inputSchema: flow.inputSchema ?? [],
|
|
4169
|
+
endpoints: (0,_flow_engine_flow_types_js__rspack_import_8/* .flowEndpoints */.Xr)(flow),
|
|
4105
4170
|
calls: calledSlugs(flow.nodes)
|
|
4106
4171
|
}
|
|
4107
4172
|
]))
|
|
@@ -4129,6 +4194,31 @@ class FlowDefinitionService extends _flusys_nestjs_shared_classes__rspack_import
|
|
|
4129
4194
|
}
|
|
4130
4195
|
});
|
|
4131
4196
|
}
|
|
4197
|
+
/** A URL other flows' Call flow steps start at cannot be removed or renamed from under them. */ async assertEndpointsNotCalled(before, after) {
|
|
4198
|
+
const kept = new Set((0,_flow_engine_flow_types_js__rspack_import_8/* .flowEndpoints */.Xr)(after).map((e)=>e.path));
|
|
4199
|
+
const removed = (0,_flow_engine_flow_types_js__rspack_import_8/* .flowEndpoints */.Xr)(before).map((e)=>e.path).filter((path)=>!kept.has(path));
|
|
4200
|
+
if (!removed.length) return;
|
|
4201
|
+
const flows = await (await this.provider.getRepository(_entities_flow_definition_entity_js__rspack_import_6/* .FlowDefinition */.G)).find({
|
|
4202
|
+
select: {
|
|
4203
|
+
id: true,
|
|
4204
|
+
name: true,
|
|
4205
|
+
nodes: true
|
|
4206
|
+
}
|
|
4207
|
+
});
|
|
4208
|
+
for (const path of removed){
|
|
4209
|
+
const callers = flows.filter((flow)=>flow.id !== before.id && calledTargets(flow.nodes).some((t)=>t.slug === before.slug && t.endpoint === path)).map((flow)=>flow.name);
|
|
4210
|
+
if (!callers.length) continue;
|
|
4211
|
+
const url = `api-flows/${before.slug}${path ? `/${path}` : ''}`;
|
|
4212
|
+
throw new _nestjs_common__rspack_import_3.ConflictException({
|
|
4213
|
+
message: `The URL ${url} is called by: ${callers.join(', ')}. Change those flows first.`,
|
|
4214
|
+
messageKey: _config_message_keys_js__rspack_import_10/* .FLOW_MESSAGES.ENDPOINT_CALLED_BY_OTHER_FLOWS */.wu.ENDPOINT_CALLED_BY_OTHER_FLOWS,
|
|
4215
|
+
messageVariables: {
|
|
4216
|
+
url,
|
|
4217
|
+
flows: callers.join(', ')
|
|
4218
|
+
}
|
|
4219
|
+
});
|
|
4220
|
+
}
|
|
4221
|
+
}
|
|
4132
4222
|
async assertSaveable(settings, user, selfId) {
|
|
4133
4223
|
const { errors, warnings } = await this.validate(settings);
|
|
4134
4224
|
if (errors.length) {
|
|
@@ -4204,6 +4294,7 @@ class FlowDefinitionService extends _flusys_nestjs_shared_classes__rspack_import
|
|
|
4204
4294
|
if (merged.slug !== existing.slug) await this.assertNotCalled(existing.slug, [
|
|
4205
4295
|
existing.id
|
|
4206
4296
|
]);
|
|
4297
|
+
else await this.assertEndpointsNotCalled(existing, merged);
|
|
4207
4298
|
d.version = existing.version + 1;
|
|
4208
4299
|
}
|
|
4209
4300
|
}
|
|
@@ -4256,8 +4347,17 @@ FlowDefinitionService = _ts_decorate([
|
|
|
4256
4347
|
typeof IPermissionActionRegistry === "undefined" ? Object : IPermissionActionRegistry
|
|
4257
4348
|
])
|
|
4258
4349
|
], FlowDefinitionService);
|
|
4350
|
+
/** What the `call_flow` nodes of a flow run: a slug, and the path of the Request step they start at (null: the flow's own URL). */ function calledTargets(nodes) {
|
|
4351
|
+
return (nodes ?? []).filter((node)=>node?.type === 'call_flow' && typeof node.config?.flowSlug === 'string').map((node)=>{
|
|
4352
|
+
const config = node.config;
|
|
4353
|
+
return {
|
|
4354
|
+
slug: config.flowSlug,
|
|
4355
|
+
endpoint: typeof config.endpoint === 'string' && config.endpoint ? config.endpoint : null
|
|
4356
|
+
};
|
|
4357
|
+
});
|
|
4358
|
+
}
|
|
4259
4359
|
/** Slugs the `call_flow` nodes of a flow run. */ function calledSlugs(nodes) {
|
|
4260
|
-
return (nodes
|
|
4360
|
+
return calledTargets(nodes).map((target)=>target.slug);
|
|
4261
4361
|
}
|
|
4262
4362
|
function stripUndefined(value) {
|
|
4263
4363
|
return Object.fromEntries(Object.entries(value).filter(([, v])=>v !== undefined));
|
|
@@ -4775,6 +4875,7 @@ class FlowExecutorService {
|
|
|
4775
4875
|
timeoutMs: flowTimeout(definition.timeoutMs)
|
|
4776
4876
|
};
|
|
4777
4877
|
const state = {
|
|
4878
|
+
startAt: options.startAt,
|
|
4778
4879
|
input: options.input,
|
|
4779
4880
|
vars: {},
|
|
4780
4881
|
context: {},
|
|
@@ -4898,10 +4999,10 @@ class FlowExecutorService {
|
|
|
4898
4999
|
}
|
|
4899
5000
|
/**
|
|
4900
5001
|
* `call_flow`: runs another active flow as part of this run - same caller,
|
|
4901
|
-
* transaction, test mode and step/HTTP/time budget. Returns what it answered: its `respond`
|
|
5002
|
+
* transaction, test mode and step/HTTP/time budget, from the Request step at `endpoint` (left out, its own URL). Returns what it answered: its `respond`
|
|
4902
5003
|
* body, or the output of its last step. A 4xx/5xx answer or a rejection is passed on as the
|
|
4903
5004
|
* caller's own; any other failure fails the calling node.
|
|
4904
|
-
*/ async callFlow(slug, input, parent, env) {
|
|
5005
|
+
*/ async callFlow(slug, input, parent, env, endpoint) {
|
|
4905
5006
|
if (parent.callStack.includes(slug)) {
|
|
4906
5007
|
throw new flow_errors/* .FlowLimitError */.IM(message_keys/* .FLOW_ERROR_MESSAGES.CALLED_FLOW_RECURSION */.zr.CALLED_FLOW_RECURSION, {
|
|
4907
5008
|
flow: slug,
|
|
@@ -4916,7 +5017,15 @@ class FlowExecutorService {
|
|
|
4916
5017
|
});
|
|
4917
5018
|
const flow = await this.findCallable(slug, parent.qr);
|
|
4918
5019
|
await this.assertMayCall(flow, parent, env);
|
|
4919
|
-
const
|
|
5020
|
+
const settings = (0,flow_definition_service/* .toFlowSettings */.RK)(flow);
|
|
5021
|
+
const start = (0,flow_types/* .findEndpoint */.vS)(settings, endpoint);
|
|
5022
|
+
if (!start) {
|
|
5023
|
+
throw new flow_errors/* .FlowKeyedError */.Hp(message_keys/* .FLOW_ERROR_MESSAGES.CALLED_FLOW_ENDPOINT_NOT_FOUND */.zr.CALLED_FLOW_ENDPOINT_NOT_FOUND, {
|
|
5024
|
+
flow: slug,
|
|
5025
|
+
endpoint: endpoint ?? ''
|
|
5026
|
+
}, `Flow '${slug}' has no URL '${endpoint ?? ''}'.`);
|
|
5027
|
+
}
|
|
5028
|
+
const checked = (0,flow_input_validator/* .validateFlowInput */.v)(start.inputSchema, input, start.bodyType);
|
|
4920
5029
|
if (checked.errors.length) {
|
|
4921
5030
|
throw new flow_errors/* .FlowHttpError */.hk(400, 'Validation failed', checked.errors, {
|
|
4922
5031
|
messageKey: message_keys/* .FLOW_MESSAGES.INPUT_INVALID */.wu.INPUT_INVALID,
|
|
@@ -4925,8 +5034,8 @@ class FlowExecutorService {
|
|
|
4925
5034
|
}
|
|
4926
5035
|
});
|
|
4927
5036
|
}
|
|
4928
|
-
const settings = (0,flow_definition_service/* .toFlowSettings */.RK)(flow);
|
|
4929
5037
|
const child = {
|
|
5038
|
+
startAt: start.nodeId,
|
|
4930
5039
|
input: checked.values,
|
|
4931
5040
|
vars: {},
|
|
4932
5041
|
context: {},
|
|
@@ -5022,7 +5131,7 @@ class FlowExecutorService {
|
|
|
5022
5131
|
}
|
|
5023
5132
|
return entityServices.get(entityCode);
|
|
5024
5133
|
},
|
|
5025
|
-
callFlow: (slug, input, state)=>this.callFlow(slug, input, state, env),
|
|
5134
|
+
callFlow: (slug, input, state, endpoint)=>this.callFlow(slug, input, state, env, endpoint),
|
|
5026
5135
|
atomic: (state, work)=>this.atomic(state, work),
|
|
5027
5136
|
commit: (state)=>this.commitProgress(state),
|
|
5028
5137
|
http: flowHttpRequest,
|
|
@@ -5122,13 +5231,14 @@ FlowExecutorService = _ts_decorate([
|
|
|
5122
5231
|
2300(__unused_rspack_module, __webpack_exports__, __webpack_require__) {
|
|
5123
5232
|
/* import */ var _nestjs_common__rspack_import_0 = __webpack_require__(6157);
|
|
5124
5233
|
/* import */ var typeorm__rspack_import_1 = __webpack_require__(7158);
|
|
5125
|
-
/* import */ var
|
|
5234
|
+
/* import */ var _config_message_keys_js__rspack_import_9 = __webpack_require__(5732);
|
|
5126
5235
|
/* import */ var _entities_flow_definition_entity_js__rspack_import_2 = __webpack_require__(3770);
|
|
5127
5236
|
/* import */ var _flow_engine_flow_input_validator_js__rspack_import_3 = __webpack_require__(6390);
|
|
5128
|
-
/* import */ var
|
|
5129
|
-
/* import */ var
|
|
5130
|
-
/* import */ var
|
|
5131
|
-
/* import */ var
|
|
5237
|
+
/* import */ var _flow_engine_flow_types_js__rspack_import_4 = __webpack_require__(6256);
|
|
5238
|
+
/* import */ var _entity_builder_datasource_provider_js__rspack_import_5 = __webpack_require__(424);
|
|
5239
|
+
/* import */ var _flow_definition_service_js__rspack_import_6 = __webpack_require__(5229);
|
|
5240
|
+
/* import */ var _flow_execution_service_js__rspack_import_7 = __webpack_require__(7810);
|
|
5241
|
+
/* import */ var _flow_executor_service_js__rspack_import_8 = __webpack_require__(4698);
|
|
5132
5242
|
function _ts_decorate(decorators, target, key, desc) {
|
|
5133
5243
|
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
5134
5244
|
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") {
|
|
@@ -5161,6 +5271,7 @@ function _ts_param(paramIndex, decorator) {
|
|
|
5161
5271
|
|
|
5162
5272
|
|
|
5163
5273
|
|
|
5274
|
+
|
|
5164
5275
|
const HIDDEN_HEADERS = new Set([
|
|
5165
5276
|
'authorization',
|
|
5166
5277
|
'cookie',
|
|
@@ -5219,7 +5330,7 @@ const inputRejected = (errors)=>({
|
|
|
5219
5330
|
body: {
|
|
5220
5331
|
success: false,
|
|
5221
5332
|
message: 'Validation failed',
|
|
5222
|
-
messageKey:
|
|
5333
|
+
messageKey: _config_message_keys_js__rspack_import_9/* .FLOW_MESSAGES.INPUT_INVALID */.wu.INPUT_INVALID,
|
|
5223
5334
|
errors
|
|
5224
5335
|
},
|
|
5225
5336
|
trace: [],
|
|
@@ -5246,19 +5357,20 @@ class FlowRuntimeService {
|
|
|
5246
5357
|
}
|
|
5247
5358
|
});
|
|
5248
5359
|
}
|
|
5249
|
-
/** The public entry: a caller hit `POST api-flows/:slug`. */ async handle(flow, body, caller, user) {
|
|
5360
|
+
/** The public entry: a caller hit `POST api-flows/:slug` or `POST api-flows/:slug/:endpoint`. */ async handle(flow, endpoint, body, caller, user) {
|
|
5250
5361
|
const startedAt = new Date();
|
|
5251
|
-
const checked = (0,_flow_engine_flow_input_validator_js__rspack_import_3/* .validateFlowInput */.v)(
|
|
5362
|
+
const checked = (0,_flow_engine_flow_input_validator_js__rspack_import_3/* .validateFlowInput */.v)(endpoint.inputSchema, body, endpoint.bodyType);
|
|
5252
5363
|
if (checked.errors.length) {
|
|
5253
5364
|
const result = inputRejected(checked.errors);
|
|
5254
5365
|
await this.logWebhook(flow, body, result, startedAt, user, caller);
|
|
5255
5366
|
return result;
|
|
5256
5367
|
}
|
|
5257
5368
|
// A saved flow: saving it as `runAs: system` needed the system permission.
|
|
5258
|
-
const result = await this.executor.run((0,
|
|
5369
|
+
const result = await this.executor.run((0,_flow_definition_service_js__rspack_import_6/* .toFlowSettings */.RK)(flow), {
|
|
5259
5370
|
mode: 'live',
|
|
5260
5371
|
user,
|
|
5261
5372
|
input: checked.values,
|
|
5373
|
+
startAt: endpoint.nodeId,
|
|
5262
5374
|
request: requestInfo(caller),
|
|
5263
5375
|
exposeDetails: false,
|
|
5264
5376
|
systemAuthorized: true
|
|
@@ -5275,15 +5387,15 @@ class FlowRuntimeService {
|
|
|
5275
5387
|
}
|
|
5276
5388
|
if (dto.definition) {
|
|
5277
5389
|
// The designer's current draft, even for a saved flow: `flow` above is kept only to attribute the run.
|
|
5278
|
-
settings = (0,
|
|
5390
|
+
settings = (0,_flow_definition_service_js__rspack_import_6/* .toFlowSettings */.RK)(dto.definition);
|
|
5279
5391
|
permissionCode = dto.definition.permissionCode;
|
|
5280
5392
|
} else if (flow) {
|
|
5281
|
-
settings = (0,
|
|
5393
|
+
settings = (0,_flow_definition_service_js__rspack_import_6/* .toFlowSettings */.RK)(flow);
|
|
5282
5394
|
permissionCode = flow.permissionCode;
|
|
5283
5395
|
} else {
|
|
5284
5396
|
throw new _nestjs_common__rspack_import_0.BadRequestException({
|
|
5285
5397
|
message: 'Send a flowId or a definition to test.',
|
|
5286
|
-
messageKey:
|
|
5398
|
+
messageKey: _config_message_keys_js__rspack_import_9/* .FLOW_MESSAGES.NOTHING_TO_TEST */.wu.NOTHING_TO_TEST
|
|
5287
5399
|
});
|
|
5288
5400
|
}
|
|
5289
5401
|
const mode = dto.mode ?? 'dry_run';
|
|
@@ -5300,21 +5412,32 @@ class FlowRuntimeService {
|
|
|
5300
5412
|
if (validation.errors.length) {
|
|
5301
5413
|
throw new _nestjs_common__rspack_import_0.BadRequestException({
|
|
5302
5414
|
message: 'The flow has problems.',
|
|
5303
|
-
messageKey:
|
|
5415
|
+
messageKey: _config_message_keys_js__rspack_import_9/* .FLOW_MESSAGES.INVALID */.wu.INVALID,
|
|
5304
5416
|
errors: validation.errors.map((error)=>({
|
|
5305
5417
|
field: 'flow',
|
|
5306
5418
|
...error
|
|
5307
5419
|
}))
|
|
5308
5420
|
});
|
|
5309
5421
|
}
|
|
5422
|
+
const endpoint = (0,_flow_engine_flow_types_js__rspack_import_4/* .findEndpoint */.vS)(settings, dto.endpoint);
|
|
5423
|
+
if (!endpoint) {
|
|
5424
|
+
throw new _nestjs_common__rspack_import_0.BadRequestException({
|
|
5425
|
+
message: `This flow has no URL '${dto.endpoint ?? ''}'.`,
|
|
5426
|
+
messageKey: _config_message_keys_js__rspack_import_9/* .FLOW_MESSAGES.ENDPOINT_NOT_FOUND */.wu.ENDPOINT_NOT_FOUND,
|
|
5427
|
+
messageVariables: {
|
|
5428
|
+
endpoint: dto.endpoint ?? ''
|
|
5429
|
+
}
|
|
5430
|
+
});
|
|
5431
|
+
}
|
|
5310
5432
|
const startedAt = new Date();
|
|
5311
5433
|
const simulated = simulatedCaller(caller, dto.request);
|
|
5312
|
-
const body = dto.input ?? (
|
|
5313
|
-
const checked = (0,_flow_engine_flow_input_validator_js__rspack_import_3/* .validateFlowInput */.v)(
|
|
5434
|
+
const body = dto.input ?? (endpoint.bodyType === 'list' ? [] : {});
|
|
5435
|
+
const checked = (0,_flow_engine_flow_input_validator_js__rspack_import_3/* .validateFlowInput */.v)(endpoint.inputSchema, body, endpoint.bodyType);
|
|
5314
5436
|
const result = checked.errors.length ? inputRejected(checked.errors) : await this.executor.run(settings, {
|
|
5315
5437
|
mode,
|
|
5316
5438
|
user,
|
|
5317
5439
|
input: checked.values,
|
|
5440
|
+
startAt: endpoint.nodeId,
|
|
5318
5441
|
request: requestInfo(simulated),
|
|
5319
5442
|
exposeDetails: true,
|
|
5320
5443
|
systemAuthorized: true
|
|
@@ -5355,16 +5478,16 @@ FlowRuntimeService = _ts_decorate([
|
|
|
5355
5478
|
(0,_nestjs_common__rspack_import_0.Injectable)({
|
|
5356
5479
|
scope: _nestjs_common__rspack_import_0.Scope.REQUEST
|
|
5357
5480
|
}),
|
|
5358
|
-
_ts_param(0, (0,_nestjs_common__rspack_import_0.Inject)(
|
|
5359
|
-
_ts_param(1, (0,_nestjs_common__rspack_import_0.Inject)(
|
|
5360
|
-
_ts_param(2, (0,_nestjs_common__rspack_import_0.Inject)(
|
|
5361
|
-
_ts_param(3, (0,_nestjs_common__rspack_import_0.Inject)(
|
|
5481
|
+
_ts_param(0, (0,_nestjs_common__rspack_import_0.Inject)(_entity_builder_datasource_provider_js__rspack_import_5/* .EntityBuilderDataSourceProvider */.D)),
|
|
5482
|
+
_ts_param(1, (0,_nestjs_common__rspack_import_0.Inject)(_flow_executor_service_js__rspack_import_8/* .FlowExecutorService */.F)),
|
|
5483
|
+
_ts_param(2, (0,_nestjs_common__rspack_import_0.Inject)(_flow_execution_service_js__rspack_import_7/* .FlowExecutionService */.A)),
|
|
5484
|
+
_ts_param(3, (0,_nestjs_common__rspack_import_0.Inject)(_flow_definition_service_js__rspack_import_6/* .FlowDefinitionService */.Tk)),
|
|
5362
5485
|
_ts_metadata("design:type", Function),
|
|
5363
5486
|
_ts_metadata("design:paramtypes", [
|
|
5364
|
-
typeof
|
|
5365
|
-
typeof
|
|
5366
|
-
typeof
|
|
5367
|
-
typeof
|
|
5487
|
+
typeof _entity_builder_datasource_provider_js__rspack_import_5/* .EntityBuilderDataSourceProvider */.D === "undefined" ? Object : _entity_builder_datasource_provider_js__rspack_import_5/* .EntityBuilderDataSourceProvider */.D,
|
|
5488
|
+
typeof _flow_executor_service_js__rspack_import_8/* .FlowExecutorService */.F === "undefined" ? Object : _flow_executor_service_js__rspack_import_8/* .FlowExecutorService */.F,
|
|
5489
|
+
typeof _flow_execution_service_js__rspack_import_7/* .FlowExecutionService */.A === "undefined" ? Object : _flow_execution_service_js__rspack_import_7/* .FlowExecutionService */.A,
|
|
5490
|
+
typeof _flow_definition_service_js__rspack_import_6/* .FlowDefinitionService */.Tk === "undefined" ? Object : _flow_definition_service_js__rspack_import_6/* .FlowDefinitionService */.Tk
|
|
5368
5491
|
])
|
|
5369
5492
|
], FlowRuntimeService);
|
|
5370
5493
|
|
package/fesm/guards/index.js
CHANGED
|
@@ -71,6 +71,8 @@ const DEFAULT_MAX_KEYS = 10_000;
|
|
|
71
71
|
}
|
|
72
72
|
}
|
|
73
73
|
|
|
74
|
+
// EXTERNAL MODULE: ./projects/nestjs-entity-builder/src/flow-engine/flow.types.ts
|
|
75
|
+
var flow_types = __webpack_require__(6256);
|
|
74
76
|
;// CONCATENATED MODULE: ./projects/nestjs-entity-builder/src/guards/flow-access.guard.ts
|
|
75
77
|
function _ts_decorate(decorators, target, key, desc) {
|
|
76
78
|
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
@@ -106,6 +108,7 @@ function _ts_param(paramIndex, decorator) {
|
|
|
106
108
|
|
|
107
109
|
|
|
108
110
|
|
|
111
|
+
|
|
109
112
|
const limiter = new FlowRateLimiter();
|
|
110
113
|
class FlowAccessGuard {
|
|
111
114
|
moduleRef;
|
|
@@ -123,11 +126,13 @@ class FlowAccessGuard {
|
|
|
123
126
|
strict: false
|
|
124
127
|
});
|
|
125
128
|
const flow = slug ? await runtime.findActive(slug) : null;
|
|
126
|
-
|
|
129
|
+
const endpoint = flow ? (0,flow_types/* .findEndpoint */.vS)(flow, request.params?.endpoint) : undefined;
|
|
130
|
+
if (!flow || !endpoint) throw new common_.NotFoundException({
|
|
127
131
|
message: 'Not found',
|
|
128
132
|
messageKey: message_keys/* .FLOW_MESSAGES.NOT_FOUND */.wu.NOT_FOUND
|
|
129
133
|
});
|
|
130
134
|
request.flowDefinition = flow;
|
|
135
|
+
request.flowEndpoint = endpoint;
|
|
131
136
|
const ip = request.ip ?? request.socket?.remoteAddress ?? 'unknown';
|
|
132
137
|
if (!limiter.hit(`${flow.id}|${ip}`, flow.rateLimitPerMinute)) {
|
|
133
138
|
throw new common_.HttpException({
|