@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 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.0",
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.0",
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) => new RegExp(`(?<![A-Za-z0-9_])${name}(?![A-Za-z0-9_])`, 'u').test(expression.replace(STRING_LITERAL_PATTERN, ' '));
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":"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"}
1
+ {"version":3,"file":"expression-errors.js","sourceRoot":"","sources":["../../../../../../libs/bpm-core/src/lib/condition/expression-errors.ts"],"names":[],"mappings":";;;AAmHA,4DA+CC;AAlKD,2CAAqD;AACrD,gEAAyE;AACzE,2EAAsE;AAEtE;;;;GAIG;AACU,QAAA,sBAAsB,GAAG;IACpC,yBAAyB;IACzB,wBAAwB;IACxB,uBAAuB;IACvB,4BAA4B;IAC5B,2BAA2B;IAC3B,4BAA4B;CACpB,CAAC;AAcX;;;;;GAKG;AACH,MAAa,sBAAuB,SAAQ,4BAAmB;IAK7D,YACE,IAAyB,EACzB,KAAa,EACb,UAAyC,EAAE;QAE3C,KAAK,CAAC,GAAG,KAAK,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC,CAAC,IAAI,OAAO,CAAC,MAAM,EAAE,CAAC,CAAC,CAAC,EAAE,KAAK,IAAI,EAAE,CAAC,CAAC;QAExE,IAAI,CAAC,IAAI,GAAG,IAAI,CAAC;QACjB,IAAI,CAAC,KAAK,GAAG,KAAK,CAAC;QACnB,IAAI,CAAC,OAAO,GAAG,OAAO,CAAC,OAAO,IAAI,IAAI,CAAC;IACzC,CAAC;CACF;AAhBD,wDAgBC;AAED,MAAM,kBAAkB,GAAG,2BAA2B,CAAC;AAEvD;;;;GAIG;AACH,MAAM,oBAAoB,GAAsB;IAC9C,+CAA+C;IAC/C,mDAAmD;CACpD,CAAC;AAEF;;;;;;GAMG;AACH,MAAM,sBAAsB,GAC1B,8GAA8G,CAAC;AAEjH;;;GAGG;AACH,MAAM,sBAAsB,GAAG,oBAAoB,CAAC;AAEpD;;;;;;GAMG;AACH,MAAM,mBAAmB,GAAG,CAAC,UAAkB,EAAE,IAAY,EAAW,EAAE;IACxE,MAAM,MAAM,GAAG,UAAU,CAAC,OAAO,CAAC,sBAAsB,EAAE,GAAG,CAAC,CAAC;IAE/D,8EAA8E;IAC9E,8EAA8E;IAC9E,0EAA0E;IAC1E,yEAAyE;IACzE,4CAA4C;IAC5C,IAAI,sBAAsB,CAAC,IAAI,CAAC,MAAM,CAAC,EAAE,CAAC;QACxC,OAAO,KAAK,CAAC;IACf,CAAC;IAED,OAAO,IAAI,MAAM,CAAC,oBAAoB,IAAI,kBAAkB,EAAE,GAAG,CAAC,CAAC,IAAI,CACrE,MAAM,CACP,CAAC;AACJ,CAAC,CAAC;AAEF;;;;;;;;GAQG;AACH,SAAgB,wBAAwB,CACtC,KAAc,EACd,KAAa,EACb,UAAmB;IAEnB,IAAI,KAAK,YAAY,sBAAsB,EAAE,CAAC;QAC5C,OAAO,KAAK,CAAC;IACf,CAAC;IAED,IAAI,KAAK,YAAY,qCAAuB,EAAE,CAAC;QAC7C,OAAO,IAAI,sBAAsB,CAAC,4BAA4B,EAAE,KAAK,CAAC,CAAC;IACzE,CAAC;IAED,IAAI,KAAK,YAAY,mDAAuB,EAAE,CAAC;QAC7C,MAAM,IAAI,GAAG,KAAK,CAAC,gBAAgB,IAAI,EAAE,CAAC;QAE1C,OAAO,IAAI,sBAAsB,CAAC,KAAK,CAAC,IAAI,EAAE,KAAK,EAAE;YACnD,OAAO,EACL,KAAK,CAAC,IAAI,KAAK,wBAAwB;gBACvC,UAAU,KAAK,SAAS;gBACxB,kBAAkB,CAAC,IAAI,CAAC,IAAI,CAAC;gBAC7B,mBAAmB,CAAC,UAAU,EAAE,IAAI,CAAC;gBACnC,CAAC,CAAC,IAAI;gBACN,CAAC,CAAC,IAAI;SACX,CAAC,CAAC;IACL,CAAC;IAED,MAAM,OAAO,GAAG,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE,CAAC;IAE5D,KAAK,MAAM,OAAO,IAAI,oBAAoB,EAAE,CAAC;QAC3C,MAAM,KAAK,GAAG,OAAO,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;QAEpC,IAAI,KAAK,EAAE,CAAC;YACV,MAAM,IAAI,GAAG,KAAK,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC;YAE5B,OAAO,IAAI,sBAAsB,CAAC,wBAAwB,EAAE,KAAK,EAAE;gBACjE,OAAO,EACL,UAAU,KAAK,SAAS;oBACxB,kBAAkB,CAAC,IAAI,CAAC,IAAI,CAAC;oBAC7B,mBAAmB,CAAC,UAAU,EAAE,IAAI,CAAC;oBACnC,CAAC,CAAC,IAAI;oBACN,CAAC,CAAC,IAAI;aACX,CAAC,CAAC;QACL,CAAC;IACH,CAAC;IAED,OAAO,IAAI,sBAAsB,CAAC,uBAAuB,EAAE,KAAK,CAAC,CAAC;AACpE,CAAC"}
@@ -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[fieldKey];
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":"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"}
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.data.condition?.trim();
1954
- if (expression && !isListValuedStructuredCondition(edge, input.formData)) {
1955
- return this.conditionService.evaluateBoolean(expression, buildDryRunExpressionContext(input, lastDecision), `workflow.edges.${edge.id}.data.condition`, {
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 (0, workflow_condition_evaluator_1.evaluateWorkflowEdgeCondition)(edge, { formData: input.formData });
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.data.condition?.trim();
2806
- if (expression &&
2807
- !isListValuedStructuredCondition(edge, instance.formData)) {
2808
- return this.conditionService.evaluateBoolean(expression, {
2809
- ...buildWorkflowExpressionContext(instance),
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
- return (0, workflow_condition_evaluator_1.evaluateWorkflowEdgeCondition)(edge, { formData: instance.formData });
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
- * A designer-built condition on a multiple-selection field (checkbox, or a
5449
- * select / autocomplete in `multiple` mode), whose submitted value is a list.
5450
- * Definitions saved before the designer compiled such fields with list
5451
- * semantics carry a single-value CEL (`form.tags == "A"`), which is false for
5452
- * `["A"]` — `!=` is always true and `!= ""` counts `[]` as filled — so those
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 isListValuedStructuredCondition(edge, formData) {
5457
+ function readEvaluableEdgeExpression(edge, formData) {
5458
+ const expression = edge.data.condition?.trim();
5460
5459
  const fieldKey = edge.data.conditionFieldKey;
5461
- return Boolean(fieldKey &&
5460
+ const listValued = Boolean(fieldKey &&
5462
5461
  edge.data.conditionOperator &&
5463
- Array.isArray(formData[fieldKey]));
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() ||