@rytass/bpm-core-nestjs-module 0.20.0 → 0.20.1
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/CHANGELOG.md +19 -0
- package/package.json +2 -2
- package/src/lib/condition/expression-errors.js +17 -1
- package/src/lib/condition/expression-errors.js.map +1 -1
- package/src/lib/workflow-engine/workflow-condition-evaluator.js +12 -12
- package/src/lib/workflow-engine/workflow-condition-evaluator.js.map +1 -1
- package/src/lib/workflow-engine/workflow-engine.service.js +76 -28
- package/src/lib/workflow-engine/workflow-engine.service.js.map +1 -1
- package/src/lib/workflow-engine/workflow-form-value.d.ts +11 -0
- package/src/lib/workflow-engine/workflow-form-value.js +33 -0
- package/src/lib/workflow-engine/workflow-form-value.js.map +1 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,22 @@
|
|
|
1
|
+
## 0.20.1 (2026-11-10)
|
|
2
|
+
|
|
3
|
+
### 🩹 Fixes
|
|
4
|
+
|
|
5
|
+
- **workflow-graph:** read a multiple-selection "" as unfilled ([298753a](https://github.com/Rytass/BPMCore/commit/298753a))
|
|
6
|
+
- **workflow-engine:** fall back on a missing key only when it is the edge's field ([d2c5589](https://github.com/Rytass/BPMCore/commit/d2c5589))
|
|
7
|
+
- **workflow-engine:** fall back to structured fields only for an unfilled field ([1f626e0](https://github.com/Rytass/BPMCore/commit/1f626e0))
|
|
8
|
+
- **condition:** drop missing-key names after a computed index ([74d8081](https://github.com/Rytass/BPMCore/commit/74d8081))
|
|
9
|
+
- **workflow-engine:** cover empty strings and own keys in the structured fallback ([6371342](https://github.com/Rytass/BPMCore/commit/6371342))
|
|
10
|
+
- **condition:** echo only names written in the expression in CEL error messages ([098f7d9](https://github.com/Rytass/BPMCore/commit/098f7d9))
|
|
11
|
+
- **condition:** keep form and initiator values out of CEL error messages ([36757f4](https://github.com/Rytass/BPMCore/commit/36757f4))
|
|
12
|
+
- **workflow-engine:** route designer edges with unfilled fields through the structured evaluator ([a57f10f](https://github.com/Rytass/BPMCore/commit/a57f10f))
|
|
13
|
+
- **workflow-graph:** guard designer conditions against absent form keys ([a3d2cb6](https://github.com/Rytass/BPMCore/commit/a3d2cb6))
|
|
14
|
+
|
|
15
|
+
### ❤️ Thank You
|
|
16
|
+
|
|
17
|
+
- Chia Yu Pai @fantasywind
|
|
18
|
+
- Kai-Chieh Yang
|
|
19
|
+
|
|
1
20
|
## 0.20.0 (2026-11-08)
|
|
2
21
|
|
|
3
22
|
### 🚀 Features
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@rytass/bpm-core-nestjs-module",
|
|
3
|
-
"version": "0.20.
|
|
3
|
+
"version": "0.20.1",
|
|
4
4
|
"description": "Embeddable NestJS BPM approval workflow module: workflow engine, approval templates, forms, organization/member contracts, attachments, signatures, notifications, delegation, and TypeORM migrations.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"bpm",
|
|
@@ -162,7 +162,7 @@
|
|
|
162
162
|
"@nestjs/core": ">=10.0.0",
|
|
163
163
|
"@nestjs/graphql": ">=12.0.0",
|
|
164
164
|
"@nestjs/typeorm": ">=10.0.0",
|
|
165
|
-
"@rytass/bpm-core-shared": "^0.20.
|
|
165
|
+
"@rytass/bpm-core-shared": "^0.20.1",
|
|
166
166
|
"@rytass/secret-adapter-vault-nestjs": ">=0.4.5",
|
|
167
167
|
"express": ">=4.0.0",
|
|
168
168
|
"graphql": ">=16.0.0",
|
|
@@ -51,6 +51,11 @@ const MISSING_KEY_PATTERNS = [
|
|
|
51
51
|
* the source than it should, which only removes candidate names, never adds one.
|
|
52
52
|
*/
|
|
53
53
|
const STRING_LITERAL_PATTERN = /(?:(?<![A-Za-z0-9_])[rRbB]{1,2})?(?:"""[\s\S]*?"""|'''[\s\S]*?'''|"(?:\\.|[^"\\\n])*"|'(?:\\.|[^'\\\n])*')/gu;
|
|
54
|
+
/*
|
|
55
|
+
* A `[...]` whose content, string literals removed, still holds an identifier:
|
|
56
|
+
* an index or list element read from the context.
|
|
57
|
+
*/
|
|
58
|
+
const COMPUTED_INDEX_PATTERN = /\[[^\]]*[A-Za-z_]/u;
|
|
54
59
|
/**
|
|
55
60
|
* Whether `name` is written in the expression as an identifier, outside every
|
|
56
61
|
* string literal. The engine reports the key it failed to find, and with a
|
|
@@ -58,7 +63,18 @@ const STRING_LITERAL_PATTERN = /(?:(?<![A-Za-z0-9_])[rRbB]{1,2})?(?:"""[\s\S]*?"
|
|
|
58
63
|
* not something the author wrote. Only a name the author wrote may be shown.
|
|
59
64
|
* `name` has already passed {@link IDENTIFIER_PATTERN}, so it is safe in a regex.
|
|
60
65
|
*/
|
|
61
|
-
const isWrittenIdentifier = (expression, name) =>
|
|
66
|
+
const isWrittenIdentifier = (expression, name) => {
|
|
67
|
+
const source = expression.replace(STRING_LITERAL_PATTERN, ' ');
|
|
68
|
+
// A computed index (`form.rates[form.currency]`) can make the engine report a
|
|
69
|
+
// context value as the missing key, and that value may happen to equal a name
|
|
70
|
+
// the author wrote (`rates`). No name is shown for such an expression. An
|
|
71
|
+
// index that holds only literals (`form["a-b"]`, `["a", "b"]`, `[0]`) is
|
|
72
|
+
// written by the author and does not count.
|
|
73
|
+
if (COMPUTED_INDEX_PATTERN.test(source)) {
|
|
74
|
+
return false;
|
|
75
|
+
}
|
|
76
|
+
return new RegExp(`(?<![A-Za-z0-9_])${name}(?![A-Za-z0-9_])`, 'u').test(source);
|
|
77
|
+
};
|
|
62
78
|
/**
|
|
63
79
|
* Converts anything an evaluation threw into an {@link ExpressionRuntimeError}.
|
|
64
80
|
* Unknown failures become `EXPRESSION_TYPE_ERROR`: the message is dropped rather
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"expression-errors.js","sourceRoot":"","sources":["../../../../../../libs/bpm-core/src/lib/condition/expression-errors.ts"],"names":[],"mappings":";;;
|
|
1
|
+
{"version":3,"file":"expression-errors.js","sourceRoot":"","sources":["../../../../../../libs/bpm-core/src/lib/condition/expression-errors.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,18 +1,19 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.evaluateWorkflowEdgeCondition = evaluateWorkflowEdgeCondition;
|
|
4
|
+
const workflow_form_value_1 = require("./workflow-form-value");
|
|
4
5
|
function evaluateWorkflowEdgeCondition(edge, context) {
|
|
5
6
|
const fieldKey = edge.data.conditionFieldKey;
|
|
6
7
|
const operator = edge.data.conditionOperator;
|
|
7
8
|
if (!fieldKey || !operator) {
|
|
8
9
|
return false;
|
|
9
10
|
}
|
|
10
|
-
const actualValue = context.formData
|
|
11
|
+
const actualValue = (0, workflow_form_value_1.readOwnFormValue)(context.formData, fieldKey);
|
|
11
12
|
if (operator === 'IS_FILLED') {
|
|
12
|
-
return !isEmptyWorkflowValue(actualValue);
|
|
13
|
+
return !(0, workflow_form_value_1.isEmptyWorkflowValue)(actualValue);
|
|
13
14
|
}
|
|
14
15
|
if (operator === 'IS_EMPTY') {
|
|
15
|
-
return isEmptyWorkflowValue(actualValue);
|
|
16
|
+
return (0, workflow_form_value_1.isEmptyWorkflowValue)(actualValue);
|
|
16
17
|
}
|
|
17
18
|
const expectedValue = edge.data.conditionValue;
|
|
18
19
|
if (typeof expectedValue === 'undefined') {
|
|
@@ -21,6 +22,14 @@ function evaluateWorkflowEdgeCondition(edge, context) {
|
|
|
21
22
|
return compareWorkflowValues(actualValue, operator, expectedValue);
|
|
22
23
|
}
|
|
23
24
|
function compareWorkflowValues(actualValue, operator, expectedValue) {
|
|
25
|
+
// An unfilled field (absent, null, "", whitespace only or an empty list — the
|
|
26
|
+
// same set IS_EMPTY reports) holds no value to compare. Normalising it to ""
|
|
27
|
+
// would make `"" < "5"` true for an untouched number or date and would let
|
|
28
|
+
// `EQUALS ""` match, so every value and ordering comparison is false except
|
|
29
|
+
// NOT_EQUALS, which is true: nothing was filled, so it is not that value.
|
|
30
|
+
if ((0, workflow_form_value_1.isEmptyWorkflowValue)(actualValue)) {
|
|
31
|
+
return operator === 'NOT_EQUALS';
|
|
32
|
+
}
|
|
24
33
|
// A table value is a list of rows. Only IS_FILLED / IS_EMPTY (handled above,
|
|
25
34
|
// by row count) are defined for it; the designer offers nothing else, but a
|
|
26
35
|
// hand-written or imported definition can still carry a value comparison.
|
|
@@ -85,15 +94,6 @@ function isNonPrimitiveValue(value) {
|
|
|
85
94
|
}
|
|
86
95
|
return typeof value === 'object' && value !== null;
|
|
87
96
|
}
|
|
88
|
-
function isEmptyWorkflowValue(value) {
|
|
89
|
-
if (value === null || typeof value === 'undefined') {
|
|
90
|
-
return true;
|
|
91
|
-
}
|
|
92
|
-
if (Array.isArray(value)) {
|
|
93
|
-
return value.length === 0;
|
|
94
|
-
}
|
|
95
|
-
return typeof value === 'string' ? value.trim() === '' : false;
|
|
96
|
-
}
|
|
97
97
|
function normalizeWorkflowValue(value) {
|
|
98
98
|
if (typeof value === 'string' ||
|
|
99
99
|
typeof value === 'number' ||
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"workflow-condition-evaluator.js","sourceRoot":"","sources":["../../../../../../libs/bpm-core/src/lib/workflow-engine/workflow-condition-evaluator.ts"],"names":[],"mappings":";;
|
|
1
|
+
{"version":3,"file":"workflow-condition-evaluator.js","sourceRoot":"","sources":["../../../../../../libs/bpm-core/src/lib/workflow-engine/workflow-condition-evaluator.ts"],"names":[],"mappings":"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"}
|
|
@@ -47,6 +47,7 @@ const task_candidate_entity_1 = require("./task-candidate.entity");
|
|
|
47
47
|
const task_entity_1 = require("./task.entity");
|
|
48
48
|
const workflow_engine_enums_1 = require("./workflow-engine.enums");
|
|
49
49
|
const workflow_condition_evaluator_1 = require("./workflow-condition-evaluator");
|
|
50
|
+
const workflow_form_value_1 = require("./workflow-form-value");
|
|
50
51
|
const workflow_dry_run_object_1 = require("./workflow-dry-run.object");
|
|
51
52
|
const time_budget_1 = require("./time-budget");
|
|
52
53
|
const expression_budget_1 = require("../condition/expression-budget");
|
|
@@ -1950,14 +1951,14 @@ let WorkflowEngineService = WorkflowEngineService_1 = class WorkflowEngineServic
|
|
|
1950
1951
|
};
|
|
1951
1952
|
}
|
|
1952
1953
|
evaluateDryRunEdge(input, edge, lastDecision) {
|
|
1953
|
-
const expression = edge.
|
|
1954
|
-
if (expression
|
|
1955
|
-
return
|
|
1956
|
-
allowedRootIdentifiers: DRY_RUN_EDGE_ROOT_IDENTIFIERS,
|
|
1957
|
-
dialect: (0, expression_dialect_1.readWorkflowExpressionDialect)(input.workflowDefinition),
|
|
1958
|
-
});
|
|
1954
|
+
const expression = readEvaluableEdgeExpression(edge, input.formData);
|
|
1955
|
+
if (!expression) {
|
|
1956
|
+
return (0, workflow_condition_evaluator_1.evaluateWorkflowEdgeCondition)(edge, { formData: input.formData });
|
|
1959
1957
|
}
|
|
1960
|
-
return (
|
|
1958
|
+
return evaluateWithStructuredFallback(edge, input.formData, (message) => this.logger.warn(`dry-run ${message}`), () => this.conditionService.evaluateBoolean(expression, buildDryRunExpressionContext(input, lastDecision), `workflow.edges.${edge.id}.data.condition`, {
|
|
1959
|
+
allowedRootIdentifiers: DRY_RUN_EDGE_ROOT_IDENTIFIERS,
|
|
1960
|
+
dialect: (0, expression_dialect_1.readWorkflowExpressionDialect)(input.workflowDefinition),
|
|
1961
|
+
}));
|
|
1961
1962
|
}
|
|
1962
1963
|
/**
|
|
1963
1964
|
* The dry-run assignee of a user task. Only resolvers whose dry run yields
|
|
@@ -2802,15 +2803,17 @@ let WorkflowEngineService = WorkflowEngineService_1 = class WorkflowEngineServic
|
|
|
2802
2803
|
throw new common_1.ConflictException(`Workflow node ${node.id} has no matching outgoing edge`);
|
|
2803
2804
|
}
|
|
2804
2805
|
async evaluateWorkflowEdge(manager, instance, node, edge) {
|
|
2805
|
-
const expression = edge.
|
|
2806
|
-
if (expression
|
|
2807
|
-
|
|
2808
|
-
|
|
2809
|
-
|
|
2810
|
-
lastDecision: await this.readLastDecisionContext(manager, instance.id, node.id),
|
|
2811
|
-
}, `workflow.edges.${edge.id}.data.condition`, { dialect: (0, expression_dialect_1.readWorkflowExpressionDialect)(instance.workflowSnapshot) });
|
|
2806
|
+
const expression = readEvaluableEdgeExpression(edge, instance.formData);
|
|
2807
|
+
if (!expression) {
|
|
2808
|
+
return (0, workflow_condition_evaluator_1.evaluateWorkflowEdgeCondition)(edge, {
|
|
2809
|
+
formData: instance.formData,
|
|
2810
|
+
});
|
|
2812
2811
|
}
|
|
2813
|
-
|
|
2812
|
+
const context = {
|
|
2813
|
+
...buildWorkflowExpressionContext(instance),
|
|
2814
|
+
lastDecision: await this.readLastDecisionContext(manager, instance.id, node.id),
|
|
2815
|
+
};
|
|
2816
|
+
return evaluateWithStructuredFallback(edge, instance.formData, (message) => this.logger.warn(`instance=${instance.id} ${message}`), () => this.conditionService.evaluateBoolean(expression, context, `workflow.edges.${edge.id}.data.condition`, { dialect: (0, expression_dialect_1.readWorkflowExpressionDialect)(instance.workflowSnapshot) }));
|
|
2814
2817
|
}
|
|
2815
2818
|
async readLastDecisionContext(manager, instanceId, nodeId) {
|
|
2816
2819
|
const tasks = await manager.getRepository(task_entity_1.TaskEntity).find({
|
|
@@ -5445,22 +5448,67 @@ function readNodeEntryCondition(node) {
|
|
|
5445
5448
|
return null;
|
|
5446
5449
|
}
|
|
5447
5450
|
/**
|
|
5448
|
-
*
|
|
5449
|
-
*
|
|
5450
|
-
*
|
|
5451
|
-
*
|
|
5452
|
-
*
|
|
5453
|
-
* edges never routed as designed. The structured fields the designer stores
|
|
5454
|
-
* beside the CEL are evaluated instead: `evaluateWorkflowEdgeCondition`
|
|
5455
|
-
* already reads a list as contains / does not contain and an empty list as
|
|
5456
|
-
* empty, the meaning the current designer compiles too. A hand-written CEL
|
|
5457
|
-
* carries no structured fields and is never overridden.
|
|
5451
|
+
* The stored CEL an edge is evaluated by, or `null` when its structured fields
|
|
5452
|
+
* answer directly: no CEL, or a designer-built edge whose value is a list
|
|
5453
|
+
* (`evaluateWorkflowEdgeCondition` reads a list as contains / does not contain
|
|
5454
|
+
* and an empty list as empty, which the stored CEL of older definitions did
|
|
5455
|
+
* not).
|
|
5458
5456
|
*/
|
|
5459
|
-
function
|
|
5457
|
+
function readEvaluableEdgeExpression(edge, formData) {
|
|
5458
|
+
const expression = edge.data.condition?.trim();
|
|
5460
5459
|
const fieldKey = edge.data.conditionFieldKey;
|
|
5461
|
-
|
|
5460
|
+
const listValued = Boolean(fieldKey &&
|
|
5462
5461
|
edge.data.conditionOperator &&
|
|
5463
|
-
Array.isArray(formData
|
|
5462
|
+
Array.isArray((0, workflow_form_value_1.readOwnFormValue)(formData, fieldKey)));
|
|
5463
|
+
return expression && !listValued ? expression : null;
|
|
5464
|
+
}
|
|
5465
|
+
/**
|
|
5466
|
+
* Evaluates an edge's stored CEL and, only when it fails because the edge's
|
|
5467
|
+
* own field is unfilled, answers from the structured fields instead. The one
|
|
5468
|
+
* place both the actual run and the designer dry run decide that, so the two
|
|
5469
|
+
* cannot drift apart.
|
|
5470
|
+
*
|
|
5471
|
+
* Published definitions and in-flight instances keep the CEL they were
|
|
5472
|
+
* stored with, and that CEL throws for an optional field left untouched: the
|
|
5473
|
+
* key is absent (`Identifier "agent" not found`), or an ordered comparison
|
|
5474
|
+
* meets `null` or `""` (`null > 5`, `"" > 5`). Only then does the structured
|
|
5475
|
+
* evaluator answer, reading the value as unfilled (`EQUALS` and the ordered
|
|
5476
|
+
* comparisons false, `NOT_EQUALS` and `IS_EMPTY` true, `IS_FILLED` false).
|
|
5477
|
+
*
|
|
5478
|
+
* - `EXPRESSION_MISSING_KEY` falls back only when the missing key *is* the
|
|
5479
|
+
* edge's field. A CEL edited by hand to also read another key
|
|
5480
|
+
* (`form.agent != null && initiator.department == "X"`) fails as written
|
|
5481
|
+
* when that other key is missing, even if the edge's field is unfilled.
|
|
5482
|
+
* - `EXPRESSION_TYPE_ERROR` carries no key, so it falls back whenever the
|
|
5483
|
+
* edge's field is unfilled, and each such fallback is logged (`warn`, no
|
|
5484
|
+
* values) so an unrelated type error answered this way stays traceable.
|
|
5485
|
+
* - Everything else is rethrown: a stored CEL that evaluates keeps its
|
|
5486
|
+
* result, so no route that worked before changes; an error while the
|
|
5487
|
+
* edge's field is filled points at the CEL itself; a hand-written CEL has
|
|
5488
|
+
* no structured fields; a budget overrun or a function failure is never
|
|
5489
|
+
* absorbed.
|
|
5490
|
+
*/
|
|
5491
|
+
function evaluateWithStructuredFallback(edge, formData, warn, evaluate) {
|
|
5492
|
+
try {
|
|
5493
|
+
return evaluate();
|
|
5494
|
+
}
|
|
5495
|
+
catch (error) {
|
|
5496
|
+
const fieldKey = edge.data.conditionFieldKey;
|
|
5497
|
+
if (!fieldKey ||
|
|
5498
|
+
!edge.data.conditionOperator ||
|
|
5499
|
+
!(error instanceof expression_errors_1.ExpressionRuntimeError) ||
|
|
5500
|
+
!(0, workflow_form_value_1.isEmptyWorkflowValue)((0, workflow_form_value_1.readOwnFormValue)(formData, fieldKey))) {
|
|
5501
|
+
throw error;
|
|
5502
|
+
}
|
|
5503
|
+
if (error.code === 'EXPRESSION_MISSING_KEY' && error.keyName === fieldKey) {
|
|
5504
|
+
return (0, workflow_condition_evaluator_1.evaluateWorkflowEdgeCondition)(edge, { formData });
|
|
5505
|
+
}
|
|
5506
|
+
if (error.code === 'EXPRESSION_TYPE_ERROR') {
|
|
5507
|
+
warn(`code=EDGE_STRUCTURED_FALLBACK edge=${edge.id} field=${fieldKey} reason=EXPRESSION_TYPE_ERROR`);
|
|
5508
|
+
return (0, workflow_condition_evaluator_1.evaluateWorkflowEdgeCondition)(edge, { formData });
|
|
5509
|
+
}
|
|
5510
|
+
throw error;
|
|
5511
|
+
}
|
|
5464
5512
|
}
|
|
5465
5513
|
function edgeHasCondition(edge) {
|
|
5466
5514
|
return Boolean(edge.data.condition?.trim() ||
|