@rytass/bpm-core-nestjs-module 0.19.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 +55 -0
- package/package.json +3 -2
- package/src/index.d.ts +1 -0
- package/src/index.js +1 -0
- package/src/index.js.map +1 -1
- package/src/lib/bpm/bpm-root-options.d.ts +13 -0
- package/src/lib/bpm/bpm-root-options.js.map +1 -1
- package/src/lib/bpm/bpm-root.module.d.ts +12 -0
- package/src/lib/bpm/bpm-root.module.js +5 -0
- package/src/lib/bpm/bpm-root.module.js.map +1 -1
- package/src/lib/condition/adapters/cel-legacy.adapter.d.ts +62 -0
- package/src/lib/condition/adapters/cel-legacy.adapter.js +141 -0
- package/src/lib/condition/adapters/cel-legacy.adapter.js.map +1 -0
- package/src/lib/condition/adapters/cel-v2.adapter.d.ts +76 -0
- package/src/lib/condition/adapters/cel-v2.adapter.js +371 -0
- package/src/lib/condition/adapters/cel-v2.adapter.js.map +1 -0
- package/src/lib/condition/condition.service.d.ts +90 -5
- package/src/lib/condition/condition.service.js +287 -71
- package/src/lib/condition/condition.service.js.map +1 -1
- package/src/lib/condition/expression-ast.d.ts +176 -0
- package/src/lib/condition/expression-ast.js +1254 -0
- package/src/lib/condition/expression-ast.js.map +1 -0
- package/src/lib/condition/expression-budget.d.ts +44 -0
- package/src/lib/condition/expression-budget.js +61 -0
- package/src/lib/condition/expression-budget.js.map +1 -0
- package/src/lib/condition/expression-dialect.d.ts +29 -0
- package/src/lib/condition/expression-dialect.js +44 -0
- package/src/lib/condition/expression-dialect.js.map +1 -0
- package/src/lib/condition/expression-differential.corpus.d.ts +34 -0
- package/src/lib/condition/expression-differential.corpus.js +995 -0
- package/src/lib/condition/expression-differential.corpus.js.map +1 -0
- package/src/lib/condition/expression-differential.d.ts +33 -0
- package/src/lib/condition/expression-differential.js +48 -0
- package/src/lib/condition/expression-differential.js.map +1 -0
- package/src/lib/condition/expression-engine-failure.d.ts +15 -0
- package/src/lib/condition/expression-engine-failure.js +22 -0
- package/src/lib/condition/expression-engine-failure.js.map +1 -0
- package/src/lib/condition/expression-errors.d.ts +40 -0
- package/src/lib/condition/expression-errors.js +121 -0
- package/src/lib/condition/expression-errors.js.map +1 -0
- package/src/lib/condition/expression-function-invocation.d.ts +50 -0
- package/src/lib/condition/expression-function-invocation.js +290 -0
- package/src/lib/condition/expression-function-invocation.js.map +1 -0
- package/src/lib/condition/expression-result.walker.d.ts +18 -0
- package/src/lib/condition/expression-result.walker.js +152 -0
- package/src/lib/condition/expression-result.walker.js.map +1 -0
- package/src/lib/condition/expression-runtime.d.ts +70 -0
- package/src/lib/condition/expression-runtime.js +134 -0
- package/src/lib/condition/expression-runtime.js.map +1 -0
- package/src/lib/condition/index.d.ts +4 -0
- package/src/lib/condition/index.js +8 -0
- package/src/lib/condition/index.js.map +1 -1
- package/src/lib/delegation/delegation-rule.entity.d.ts +7 -0
- package/src/lib/delegation/delegation-rule.entity.js +10 -0
- package/src/lib/delegation/delegation-rule.entity.js.map +1 -1
- package/src/lib/delegation/delegation.service.d.ts +7 -1
- package/src/lib/delegation/delegation.service.js +22 -5
- package/src/lib/delegation/delegation.service.js.map +1 -1
- package/src/lib/delegation/dto/delegation-rule.input.d.ts +2 -0
- package/src/lib/delegation/dto/delegation-rule.input.js +10 -0
- package/src/lib/delegation/dto/delegation-rule.input.js.map +1 -1
- package/src/lib/expression-function/expression-function.built-ins.d.ts +9 -0
- package/src/lib/expression-function/expression-function.built-ins.js +117 -0
- package/src/lib/expression-function/expression-function.built-ins.js.map +1 -0
- package/src/lib/expression-function/expression-function.module.d.ts +19 -0
- package/src/lib/expression-function/expression-function.module.js +34 -0
- package/src/lib/expression-function/expression-function.module.js.map +1 -0
- package/src/lib/expression-function/expression-function.provider.d.ts +17 -0
- package/src/lib/expression-function/expression-function.provider.js +39 -0
- package/src/lib/expression-function/expression-function.provider.js.map +1 -0
- package/src/lib/expression-function/expression-function.registry.d.ts +36 -0
- package/src/lib/expression-function/expression-function.registry.js +231 -0
- package/src/lib/expression-function/expression-function.registry.js.map +1 -0
- package/src/lib/expression-function/expression-function.types.d.ts +30 -0
- package/src/lib/expression-function/expression-function.types.js +8 -0
- package/src/lib/expression-function/expression-function.types.js.map +1 -0
- package/src/lib/expression-function/index.d.ts +5 -0
- package/src/lib/expression-function/index.js +9 -0
- package/src/lib/expression-function/index.js.map +1 -0
- package/src/lib/form/form-schema.validator.js +53 -6
- package/src/lib/form/form-schema.validator.js.map +1 -1
- package/src/lib/form-data-source/form-data-source-value-resolver.service.d.ts +12 -1
- package/src/lib/form-data-source/form-data-source-value-resolver.service.js +45 -26
- package/src/lib/form-data-source/form-data-source-value-resolver.service.js.map +1 -1
- package/src/lib/form-data-source/form-data-source.expression.d.ts +95 -0
- package/src/lib/form-data-source/form-data-source.expression.js +267 -0
- package/src/lib/form-data-source/form-data-source.expression.js.map +1 -0
- package/src/lib/form-data-source/form-data-source.module.js +4 -0
- package/src/lib/form-data-source/form-data-source.module.js.map +1 -1
- package/src/lib/form-data-source/form-data-source.service.d.ts +22 -3
- package/src/lib/form-data-source/form-data-source.service.js +78 -18
- package/src/lib/form-data-source/form-data-source.service.js.map +1 -1
- package/src/lib/form-data-source/form-data-source.validation.d.ts +47 -4
- package/src/lib/form-data-source/form-data-source.validation.js +151 -23
- package/src/lib/form-data-source/form-data-source.validation.js.map +1 -1
- package/src/lib/migrations/0000000030000-delegation-scope-condition-dialect.d.ts +22 -0
- package/src/lib/migrations/0000000030000-delegation-scope-condition-dialect.js +76 -0
- package/src/lib/migrations/0000000030000-delegation-scope-condition-dialect.js.map +1 -0
- package/src/lib/migrations/index.d.ts +1 -0
- package/src/lib/migrations/index.js +3 -0
- package/src/lib/migrations/index.js.map +1 -1
- package/src/lib/notification/notification.service.d.ts +1 -1
- package/src/lib/notification/notification.service.js +7 -3
- package/src/lib/notification/notification.service.js.map +1 -1
- package/src/lib/template/dto/expression-designer.dto.d.ts +107 -0
- package/src/lib/template/dto/expression-designer.dto.js +415 -0
- package/src/lib/template/dto/expression-designer.dto.js.map +1 -0
- package/src/lib/template/expression-designer.queries.d.ts +16 -0
- package/src/lib/template/expression-designer.queries.js +66 -0
- package/src/lib/template/expression-designer.queries.js.map +1 -0
- package/src/lib/template/expression-designer.service.d.ts +72 -0
- package/src/lib/template/expression-designer.service.js +543 -0
- package/src/lib/template/expression-designer.service.js.map +1 -0
- package/src/lib/template/index.d.ts +2 -0
- package/src/lib/template/index.js +2 -0
- package/src/lib/template/index.js.map +1 -1
- package/src/lib/template/template.module.js +4 -0
- package/src/lib/template/template.module.js.map +1 -1
- package/src/lib/template/template.service.d.ts +37 -1
- package/src/lib/template/template.service.js +317 -29
- package/src/lib/template/template.service.js.map +1 -1
- package/src/lib/template/workflow-definition.validator.d.ts +7 -0
- package/src/lib/template/workflow-definition.validator.js +26 -2
- package/src/lib/template/workflow-definition.validator.js.map +1 -1
- package/src/lib/workflow-engine/time-budget.d.ts +13 -0
- package/src/lib/workflow-engine/time-budget.js +58 -0
- package/src/lib/workflow-engine/time-budget.js.map +1 -0
- 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.d.ts +24 -0
- package/src/lib/workflow-engine/workflow-engine.service.js +311 -88
- 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/src/lib/workflow-engine/workflow-service-task-dispatcher.token.js +4 -0
- package/src/lib/workflow-engine/workflow-service-task-dispatcher.token.js.map +1 -1
- package/src/lib/workflow-webhook/workflow-webhook-delivery.service.d.ts +3 -1
- package/src/lib/workflow-webhook/workflow-webhook-delivery.service.js +11 -2
- package/src/lib/workflow-webhook/workflow-webhook-delivery.service.js.map +1 -1
- package/src/lib/workflow-webhook/workflow-webhook-enqueue.d.ts +8 -1
- package/src/lib/workflow-webhook/workflow-webhook-enqueue.js +70 -27
- package/src/lib/workflow-webhook/workflow-webhook-enqueue.js.map +1 -1
- package/src/lib/workflow-webhook/workflow-webhook-expression.d.ts +57 -0
- package/src/lib/workflow-webhook/workflow-webhook-expression.js +123 -0
- package/src/lib/workflow-webhook/workflow-webhook-expression.js.map +1 -0
- package/src/lib/workflow-webhook/workflow-webhook.module.js +4 -0
- package/src/lib/workflow-webhook/workflow-webhook.module.js.map +1 -1
- package/src/lib/workflow-webhook/workflow-webhook.validator.d.ts +7 -0
- package/src/lib/workflow-webhook/workflow-webhook.validator.js +29 -6
- package/src/lib/workflow-webhook/workflow-webhook.validator.js.map +1 -1
|
@@ -47,10 +47,19 @@ 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");
|
|
52
|
+
const time_budget_1 = require("./time-budget");
|
|
53
|
+
const expression_budget_1 = require("../condition/expression-budget");
|
|
54
|
+
const expression_errors_1 = require("../condition/expression-errors");
|
|
55
|
+
const expression_dialect_1 = require("../condition/expression-dialect");
|
|
51
56
|
const workflow_service_task_dispatcher_token_1 = require("./workflow-service-task-dispatcher.token");
|
|
52
57
|
const workflow_token_entity_1 = require("./workflow-token.entity");
|
|
53
58
|
const bpm_root_options_1 = require("../bpm/bpm-root-options");
|
|
59
|
+
const MAX_DRY_RUN_INPUT_LENGTH = 1_048_576;
|
|
60
|
+
const MAX_DRY_RUN_VISITS = 10_000;
|
|
61
|
+
const DRY_RUN_TIME_BUDGET_MS = 2_000;
|
|
62
|
+
const DRY_RUN_TOO_COMPLEX_MESSAGE = '流程或條件的運算量過大,無法完成試跑,請減少連續的閘道分支或簡化條件';
|
|
54
63
|
const MAX_PROCESSING_STEPS = 500;
|
|
55
64
|
exports.MAX_APPROVAL_INSTANCE_BUSINESS_KEY_LENGTH = 200;
|
|
56
65
|
const APPROVAL_INSTANCE_BUSINESS_KEY_INDEX = 'approval_instances_business_key_key';
|
|
@@ -58,6 +67,40 @@ const APPROVAL_INSTANCE_BUSINESS_KEY_INDEX = 'approval_instances_business_key_ke
|
|
|
58
67
|
const ORG_MANAGER_CHAIN_MAX_HOPS = 20;
|
|
59
68
|
const DEFAULT_APPROVAL_HISTORY_TASK_LIMIT = 50;
|
|
60
69
|
const SYSTEM_DRY_RUN_INSTANCE_ID = 'dry-run-instance';
|
|
70
|
+
// The dry run checks each expression against the roots its use point may read,
|
|
71
|
+
// as publish does. Keep these in step with `WORKFLOW_EXPRESSION_ROOT_IDENTIFIERS`
|
|
72
|
+
// in template.service.ts; P2 moves both to one place.
|
|
73
|
+
const DRY_RUN_WORKFLOW_ROOT_IDENTIFIERS = [
|
|
74
|
+
'env',
|
|
75
|
+
'form',
|
|
76
|
+
'formData',
|
|
77
|
+
'initiator',
|
|
78
|
+
'instance',
|
|
79
|
+
];
|
|
80
|
+
const DRY_RUN_EDGE_ROOT_IDENTIFIERS = [
|
|
81
|
+
...DRY_RUN_WORKFLOW_ROOT_IDENTIFIERS,
|
|
82
|
+
'lastDecision',
|
|
83
|
+
];
|
|
84
|
+
const hasExpressionText = (expression) => typeof expression === 'string' && expression.trim() !== '';
|
|
85
|
+
/**
|
|
86
|
+
* Whether a definition's dialect can be read: missing (`cel-legacy`) or one
|
|
87
|
+
* this version knows. Never throws, unlike `readWorkflowExpressionDialect`.
|
|
88
|
+
*/
|
|
89
|
+
const isReadableExpressionDialect = (definition) => {
|
|
90
|
+
const dialect = definition?.expressionDialect;
|
|
91
|
+
return (dialect === undefined || dialect === null || (0, expression_dialect_1.isExpressionDialect)(dialect));
|
|
92
|
+
};
|
|
93
|
+
/**
|
|
94
|
+
* Options for evaluating an expression that may be blank. The dialect is read
|
|
95
|
+
* only when there is an expression: an unknown dialect is refused because the
|
|
96
|
+
* expression cannot be evaluated as its author tested it, and a blank one is
|
|
97
|
+
* never evaluated at all. Publishing does not check the field yet (Phase 23
|
|
98
|
+
* P2), so reading it eagerly would turn a stray field on a version with no
|
|
99
|
+
* expressions into an error, where before dialects existed it was ignored.
|
|
100
|
+
*/
|
|
101
|
+
const readExpressionDialectOptions = (expression, definition) => hasExpressionText(expression)
|
|
102
|
+
? { dialect: (0, expression_dialect_1.readWorkflowExpressionDialect)(definition) }
|
|
103
|
+
: {};
|
|
61
104
|
/**
|
|
62
105
|
* Delegation-chain reason recorded for an approver-initiated transfer, as
|
|
63
106
|
* opposed to {@link SLA_ESCALATION_DELEGATION_REASON}.
|
|
@@ -152,6 +195,8 @@ let WorkflowEngineService = WorkflowEngineService_1 = class WorkflowEngineServic
|
|
|
152
195
|
this.formDataSourceValueResolver = formDataSourceValueResolver;
|
|
153
196
|
this.webhookDeliveryService = webhookDeliveryService;
|
|
154
197
|
this.logger = new common_1.Logger(WorkflowEngineService_1.name);
|
|
198
|
+
/** Webhooks each open engine transaction will send once it has committed. */
|
|
199
|
+
this.deferredWebhookDispatches = new WeakMap();
|
|
155
200
|
// A binding under the token wins: that is either the host's own provider
|
|
156
201
|
// or `workflowServiceTaskDispatcherProvider`, both of them deliberate. The
|
|
157
202
|
// runtime instance from `BPMRootModule`'s async factory comes next.
|
|
@@ -177,6 +222,59 @@ let WorkflowEngineService = WorkflowEngineService_1 = class WorkflowEngineServic
|
|
|
177
222
|
readOrganizationToday() {
|
|
178
223
|
return (0, organization_time_zone_1.formatDateOnlyInTimeZone)(new Date(), this.organizationTimeZone);
|
|
179
224
|
}
|
|
225
|
+
/**
|
|
226
|
+
* Opens an engine transaction whose webhooks wait for its commit. A webhook
|
|
227
|
+
* sent from inside would hold the case lock and a pooled connection for as
|
|
228
|
+
* long as the far end takes to answer, so a slow endpoint could stall the
|
|
229
|
+
* case and then, a connection at a time, the whole API. A transaction that
|
|
230
|
+
* rolls back sends nothing.
|
|
231
|
+
*
|
|
232
|
+
* The caller still waits for the sends, so the outcome is in the activity log
|
|
233
|
+
* by the time a mutation returns, as it always has been.
|
|
234
|
+
*/
|
|
235
|
+
async runEngineTransaction(work) {
|
|
236
|
+
// Mutable on purpose: every step of the transaction appends to it, and
|
|
237
|
+
// threading it through each of them would change a dozen signatures.
|
|
238
|
+
const dispatches = [];
|
|
239
|
+
// One transaction is one operation: its evaluations share one budget.
|
|
240
|
+
const result = await (0, expression_budget_1.runExpressionOperation)(() => this.approvalInstanceRepository.manager.transaction((manager) => {
|
|
241
|
+
this.deferredWebhookDispatches.set(manager, dispatches);
|
|
242
|
+
return work(manager);
|
|
243
|
+
}));
|
|
244
|
+
await dispatches.reduce(async (previous, dispatch) => {
|
|
245
|
+
await previous;
|
|
246
|
+
await this.sendCommittedWebhook(dispatch);
|
|
247
|
+
}, Promise.resolve());
|
|
248
|
+
return result;
|
|
249
|
+
}
|
|
250
|
+
/**
|
|
251
|
+
* Queues a webhook for after the commit. A manager this service did not open
|
|
252
|
+
* has no queue and no commit it can see, so that webhook goes out on the spot
|
|
253
|
+
* rather than being dropped.
|
|
254
|
+
*/
|
|
255
|
+
async dispatchWebhookAfterCommit(manager, dispatch) {
|
|
256
|
+
const queue = this.deferredWebhookDispatches.get(manager);
|
|
257
|
+
if (queue) {
|
|
258
|
+
queue.push(dispatch);
|
|
259
|
+
return;
|
|
260
|
+
}
|
|
261
|
+
const activityRepository = manager.getRepository(activity_log_entity_1.ActivityLogEntity);
|
|
262
|
+
const result = await dispatch.send();
|
|
263
|
+
await activityRepository.save(activityRepository.create(dispatch.readActivityLog(result)));
|
|
264
|
+
}
|
|
265
|
+
/**
|
|
266
|
+
* The transaction has committed and the mutation has succeeded, so nothing
|
|
267
|
+
* here may fail it: a result that cannot be recorded is only logged.
|
|
268
|
+
*/
|
|
269
|
+
async sendCommittedWebhook(dispatch) {
|
|
270
|
+
try {
|
|
271
|
+
const result = await dispatch.send();
|
|
272
|
+
await this.activityLogRepository.save(this.activityLogRepository.create(dispatch.readActivityLog(result)));
|
|
273
|
+
}
|
|
274
|
+
catch (error) {
|
|
275
|
+
this.logger.error(`Could not record a webhook result for instance ${dispatch.instanceId}: ${error instanceof Error ? error.message : String(error)}`);
|
|
276
|
+
}
|
|
277
|
+
}
|
|
180
278
|
async submitApprovalInstance(input, authContext) {
|
|
181
279
|
if (input.submittedByMemberId !== undefined &&
|
|
182
280
|
input.submittedByMemberId !== null &&
|
|
@@ -219,7 +317,9 @@ let WorkflowEngineService = WorkflowEngineService_1 = class WorkflowEngineServic
|
|
|
219
317
|
const initiatorMetadataSnapshot = input.initiatorMetadataSnapshotJson
|
|
220
318
|
? parseJsonObject(input.initiatorMetadataSnapshotJson, 'initiatorMetadataSnapshotJson')
|
|
221
319
|
: await this.readDefaultInitiatorMetadataSnapshot(input.initiatorMemberId);
|
|
222
|
-
if (!this.conditionService.evaluateBoolean(templateVersion.initiatorPolicyCel, buildInitiatorPolicyContext(initiatorMetadataSnapshot), 'initiatorPolicyCel'
|
|
320
|
+
if (!this.conditionService.evaluateBoolean(templateVersion.initiatorPolicyCel, buildInitiatorPolicyContext(initiatorMetadataSnapshot), 'initiatorPolicyCel',
|
|
321
|
+
// The policy is written in its version's dialect (ADR 22 §3.3).
|
|
322
|
+
readExpressionDialectOptions(templateVersion.initiatorPolicyCel, templateVersion.workflowDefinition))) {
|
|
223
323
|
throw new common_1.ConflictException('Current member is not allowed to start this approval template');
|
|
224
324
|
}
|
|
225
325
|
// Named here, before the transaction, so the completion notice that later
|
|
@@ -233,7 +333,7 @@ let WorkflowEngineService = WorkflowEngineService_1 = class WorkflowEngineServic
|
|
|
233
333
|
if (!startNode) {
|
|
234
334
|
throw new common_1.BadRequestException('Published workflow does not include a startEvent');
|
|
235
335
|
}
|
|
236
|
-
return this.
|
|
336
|
+
return this.runEngineTransaction(async (manager) => {
|
|
237
337
|
const instanceRepository = manager.getRepository(approval_instance_entity_1.ApprovalInstanceEntity);
|
|
238
338
|
const tokenRepository = manager.getRepository(workflow_token_entity_1.WorkflowTokenEntity);
|
|
239
339
|
const activityRepository = manager.getRepository(activity_log_entity_1.ActivityLogEntity);
|
|
@@ -370,7 +470,7 @@ let WorkflowEngineService = WorkflowEngineService_1 = class WorkflowEngineServic
|
|
|
370
470
|
return { formDataOptionSnapshot, instance };
|
|
371
471
|
}
|
|
372
472
|
async processInstance(instanceId) {
|
|
373
|
-
await this.
|
|
473
|
+
await this.runEngineTransaction(async (manager) => {
|
|
374
474
|
await manager.query('SELECT pg_advisory_xact_lock(hashtext($1))', [
|
|
375
475
|
instanceId,
|
|
376
476
|
]);
|
|
@@ -397,7 +497,7 @@ let WorkflowEngineService = WorkflowEngineService_1 = class WorkflowEngineServic
|
|
|
397
497
|
});
|
|
398
498
|
}
|
|
399
499
|
async decideTask(input, options = {}) {
|
|
400
|
-
return this.
|
|
500
|
+
return this.runEngineTransaction(async (manager) => {
|
|
401
501
|
const taskRepository = manager.getRepository(task_entity_1.TaskEntity);
|
|
402
502
|
const taskCandidateRepository = manager.getRepository(task_candidate_entity_1.TaskCandidateEntity);
|
|
403
503
|
const taskDecisionRepository = manager.getRepository(task_decision_entity_1.TaskDecisionEntity);
|
|
@@ -760,7 +860,7 @@ let WorkflowEngineService = WorkflowEngineService_1 = class WorkflowEngineServic
|
|
|
760
860
|
});
|
|
761
861
|
}
|
|
762
862
|
async cancelApprovalInstance(input) {
|
|
763
|
-
return this.
|
|
863
|
+
return this.runEngineTransaction(async (manager) => {
|
|
764
864
|
await manager.query('SELECT pg_advisory_xact_lock(hashtext($1))', [
|
|
765
865
|
input.instanceId,
|
|
766
866
|
]);
|
|
@@ -801,7 +901,7 @@ let WorkflowEngineService = WorkflowEngineService_1 = class WorkflowEngineServic
|
|
|
801
901
|
* returned unchanged.
|
|
802
902
|
*/
|
|
803
903
|
async expireApprovalInstance(input) {
|
|
804
|
-
return this.
|
|
904
|
+
return this.runEngineTransaction(async (manager) => {
|
|
805
905
|
await manager.query('SELECT pg_advisory_xact_lock(hashtext($1))', [
|
|
806
906
|
input.instanceId,
|
|
807
907
|
]);
|
|
@@ -858,7 +958,7 @@ let WorkflowEngineService = WorkflowEngineService_1 = class WorkflowEngineServic
|
|
|
858
958
|
const preparation = this.formDataSourceValueResolver
|
|
859
959
|
? await this.prepareResubmitFormDataOptions(input, submittedFormData, authContext ?? createWorkflowAuthContext(input.initiatorMemberId))
|
|
860
960
|
: null;
|
|
861
|
-
return this.
|
|
961
|
+
return this.runEngineTransaction(async (manager) => {
|
|
862
962
|
await manager.query('SELECT pg_advisory_xact_lock(hashtext($1))', [
|
|
863
963
|
input.instanceId,
|
|
864
964
|
]);
|
|
@@ -947,7 +1047,7 @@ let WorkflowEngineService = WorkflowEngineService_1 = class WorkflowEngineServic
|
|
|
947
1047
|
});
|
|
948
1048
|
}
|
|
949
1049
|
async requestAdhocCountersign({ comment, requestedByMemberId, target, taskId, }) {
|
|
950
|
-
return this.
|
|
1050
|
+
return this.runEngineTransaction(async (manager) => {
|
|
951
1051
|
const { instance, node, task } = await this.loadAdhocOperationContext(manager, taskId, requestedByMemberId, { requireAllowAddSigner: true });
|
|
952
1052
|
const targetValue = buildAdhocTargetValue(target);
|
|
953
1053
|
// Rejects WEBHOOK targets — countersigners must be members.
|
|
@@ -979,7 +1079,7 @@ let WorkflowEngineService = WorkflowEngineService_1 = class WorkflowEngineServic
|
|
|
979
1079
|
});
|
|
980
1080
|
}
|
|
981
1081
|
async requestAdhocPreApproval({ comment, onReject, requestedByMemberId, target, taskId, }) {
|
|
982
|
-
return this.
|
|
1082
|
+
return this.runEngineTransaction(async (manager) => {
|
|
983
1083
|
const { instance, node, task } = await this.loadAdhocOperationContext(manager, taskId, requestedByMemberId, { requireAllowAddSigner: true });
|
|
984
1084
|
const targetValue = buildAdhocTargetValue(target);
|
|
985
1085
|
// Rejects WEBHOOK targets — pre-approvers must be members.
|
|
@@ -1006,7 +1106,7 @@ let WorkflowEngineService = WorkflowEngineService_1 = class WorkflowEngineServic
|
|
|
1006
1106
|
});
|
|
1007
1107
|
}
|
|
1008
1108
|
async configureAdhocNotification({ channels, requestedByMemberId, target, taskId, type, }) {
|
|
1009
|
-
return this.
|
|
1109
|
+
return this.runEngineTransaction(async (manager) => {
|
|
1010
1110
|
const { instance, node, task } = await this.loadAdhocOperationContext(manager, taskId, requestedByMemberId, { requireAllowAddSigner: false });
|
|
1011
1111
|
const targetValue = buildAdhocTargetValue(target);
|
|
1012
1112
|
const directiveRepository = manager.getRepository(adhoc_directive_entity_1.AdhocDirectiveEntity);
|
|
@@ -1029,7 +1129,7 @@ let WorkflowEngineService = WorkflowEngineService_1 = class WorkflowEngineServic
|
|
|
1029
1129
|
});
|
|
1030
1130
|
}
|
|
1031
1131
|
async cancelAdhocDirective({ cancelledByMemberId, directiveId, }) {
|
|
1032
|
-
return this.
|
|
1132
|
+
return this.runEngineTransaction(async (manager) => {
|
|
1033
1133
|
const directiveRepository = manager.getRepository(adhoc_directive_entity_1.AdhocDirectiveEntity);
|
|
1034
1134
|
const directive = await directiveRepository.findOne({
|
|
1035
1135
|
where: { id: directiveId },
|
|
@@ -1593,30 +1693,34 @@ let WorkflowEngineService = WorkflowEngineService_1 = class WorkflowEngineServic
|
|
|
1593
1693
|
const targetValue = readAdhocTargetValue(directive);
|
|
1594
1694
|
const activityRepository = manager.getRepository(activity_log_entity_1.ActivityLogEntity);
|
|
1595
1695
|
if (targetValue.kind === adhoc_enums_1.AdhocTargetKindEnum.WEBHOOK) {
|
|
1596
|
-
const
|
|
1696
|
+
const webhookPayload = {
|
|
1597
1697
|
...payload,
|
|
1598
1698
|
instanceId: instance.id,
|
|
1599
1699
|
instanceTitle: instance.title,
|
|
1600
1700
|
message,
|
|
1601
1701
|
triggeredAt: new Date().toISOString(),
|
|
1602
|
-
}
|
|
1603
|
-
await
|
|
1604
|
-
actorMemberId: null,
|
|
1605
|
-
eventType: result.ok
|
|
1606
|
-
? workflow_engine_enums_1.ActivityLogEventTypeEnum.SERVICE_TASK_EXECUTED
|
|
1607
|
-
: workflow_engine_enums_1.ActivityLogEventTypeEnum.SERVICE_TASK_FAILED,
|
|
1702
|
+
};
|
|
1703
|
+
await this.dispatchWebhookAfterCommit(manager, {
|
|
1608
1704
|
instanceId: instance.id,
|
|
1609
|
-
|
|
1610
|
-
|
|
1611
|
-
|
|
1612
|
-
|
|
1613
|
-
|
|
1614
|
-
|
|
1615
|
-
|
|
1616
|
-
|
|
1617
|
-
|
|
1618
|
-
|
|
1619
|
-
|
|
1705
|
+
readActivityLog: (result) => ({
|
|
1706
|
+
actorMemberId: null,
|
|
1707
|
+
eventType: result.ok
|
|
1708
|
+
? workflow_engine_enums_1.ActivityLogEventTypeEnum.SERVICE_TASK_EXECUTED
|
|
1709
|
+
: workflow_engine_enums_1.ActivityLogEventTypeEnum.SERVICE_TASK_FAILED,
|
|
1710
|
+
instanceId: instance.id,
|
|
1711
|
+
nodeId: directive.originNodeId,
|
|
1712
|
+
payload: {
|
|
1713
|
+
action: 'ADHOC_WEBHOOK',
|
|
1714
|
+
directiveId: directive.id,
|
|
1715
|
+
error: result.error,
|
|
1716
|
+
ok: result.ok,
|
|
1717
|
+
status: result.status,
|
|
1718
|
+
url: targetValue.webhookUrl,
|
|
1719
|
+
},
|
|
1720
|
+
taskId: null,
|
|
1721
|
+
}),
|
|
1722
|
+
send: () => executeAdhocWebhookDispatch(this.serviceTaskDispatcher, targetValue, webhookPayload),
|
|
1723
|
+
});
|
|
1620
1724
|
return;
|
|
1621
1725
|
}
|
|
1622
1726
|
try {
|
|
@@ -1667,29 +1771,38 @@ let WorkflowEngineService = WorkflowEngineService_1 = class WorkflowEngineServic
|
|
|
1667
1771
|
})));
|
|
1668
1772
|
}
|
|
1669
1773
|
dryRunApprovalWorkflow(input) {
|
|
1774
|
+
// Checked first: parsing happens outside the time budget below.
|
|
1775
|
+
if ([
|
|
1776
|
+
input.workflowDefinitionJson,
|
|
1777
|
+
input.formDataJson,
|
|
1778
|
+
input.initiatorMetadataSnapshotJson ?? '',
|
|
1779
|
+
].some((json) => json.length > MAX_DRY_RUN_INPUT_LENGTH)) {
|
|
1780
|
+
return createFailedDryRunResult('試跑資料過大,請縮小流程或測試資料後再試');
|
|
1781
|
+
}
|
|
1670
1782
|
const workflowDefinition = parseWorkflowDefinition(input.workflowDefinitionJson);
|
|
1671
1783
|
const formData = parseJsonObject(input.formDataJson, 'formDataJson');
|
|
1672
1784
|
const initiatorMetadataSnapshot = input.initiatorMetadataSnapshotJson
|
|
1673
1785
|
? parseJsonObject(input.initiatorMetadataSnapshotJson, 'initiatorMetadataSnapshotJson')
|
|
1674
1786
|
: readDefaultInitiatorMetadataSnapshot(input.initiatorMemberId);
|
|
1675
1787
|
try {
|
|
1788
|
+
// The CEL engine loads outside the budget so an interrupt can never land
|
|
1789
|
+
// halfway through loading a module.
|
|
1790
|
+
this.conditionService.prepareEngine((0, expression_dialect_1.readWorkflowExpressionDialect)(workflowDefinition));
|
|
1791
|
+
// The outer budget bounds the whole walk, and the operation budget
|
|
1792
|
+
// bounds the expressions inside it. Both are charged to this dry run only.
|
|
1676
1793
|
return Object.assign(new workflow_dry_run_object_1.WorkflowDryRunResultObject(), {
|
|
1677
1794
|
errors: [],
|
|
1678
|
-
steps: this.simulateWorkflow({
|
|
1795
|
+
steps: (0, expression_budget_1.runExpressionOperation)(() => (0, time_budget_1.runWithinTimeBudget)(() => this.simulateWorkflow({
|
|
1679
1796
|
formData,
|
|
1680
1797
|
initiatorMemberId: input.initiatorMemberId,
|
|
1681
1798
|
initiatorMetadataSnapshot,
|
|
1682
1799
|
workflowDefinition,
|
|
1683
|
-
}),
|
|
1800
|
+
}), DRY_RUN_TIME_BUDGET_MS)),
|
|
1684
1801
|
valid: true,
|
|
1685
1802
|
});
|
|
1686
1803
|
}
|
|
1687
1804
|
catch (error) {
|
|
1688
|
-
return
|
|
1689
|
-
errors: [error instanceof Error ? error.message : 'Dry run failed'],
|
|
1690
|
-
steps: [],
|
|
1691
|
-
valid: false,
|
|
1692
|
-
});
|
|
1805
|
+
return createFailedDryRunResult(readDryRunFailureMessage(error));
|
|
1693
1806
|
}
|
|
1694
1807
|
}
|
|
1695
1808
|
simulateWorkflow(input) {
|
|
@@ -1697,8 +1810,18 @@ let WorkflowEngineService = WorkflowEngineService_1 = class WorkflowEngineServic
|
|
|
1697
1810
|
if (!startNode) {
|
|
1698
1811
|
throw new common_1.BadRequestException('Workflow does not include a startEvent');
|
|
1699
1812
|
}
|
|
1813
|
+
// Every branch re-walks whatever follows it, so a chain of gateways doubles
|
|
1814
|
+
// the work at each one while the depth limit never trips. Closure state
|
|
1815
|
+
// because all the branches of one dry run share this one count.
|
|
1816
|
+
let visits = 0;
|
|
1700
1817
|
return this.simulateWorkflowPath({
|
|
1701
1818
|
...input,
|
|
1819
|
+
chargeVisit: () => {
|
|
1820
|
+
visits += 1;
|
|
1821
|
+
if (visits > MAX_DRY_RUN_VISITS) {
|
|
1822
|
+
throw new common_1.ConflictException(DRY_RUN_TOO_COMPLEX_MESSAGE);
|
|
1823
|
+
}
|
|
1824
|
+
},
|
|
1702
1825
|
depth: 0,
|
|
1703
1826
|
incomingEdge: null,
|
|
1704
1827
|
lastDecision: null,
|
|
@@ -1711,6 +1834,7 @@ let WorkflowEngineService = WorkflowEngineService_1 = class WorkflowEngineServic
|
|
|
1711
1834
|
if (input.depth > MAX_PROCESSING_STEPS) {
|
|
1712
1835
|
throw new common_1.ConflictException('Dry run exceeded maximum processing steps');
|
|
1713
1836
|
}
|
|
1837
|
+
input.chargeVisit();
|
|
1714
1838
|
const node = readWorkflowNodeOrThrow(input.workflowDefinition, input.nodeId);
|
|
1715
1839
|
const visitKey = `${input.nodeId}:${input.incomingEdge?.id ?? 'start'}`;
|
|
1716
1840
|
const entryCondition = readNodeEntryCondition(node);
|
|
@@ -1732,7 +1856,10 @@ let WorkflowEngineService = WorkflowEngineService_1 = class WorkflowEngineServic
|
|
|
1732
1856
|
];
|
|
1733
1857
|
}
|
|
1734
1858
|
const nextVisitedNodeIds = new Set([...input.visitedNodeIds, visitKey]);
|
|
1735
|
-
const entryConditionMet = this.conditionService.evaluateBoolean(entryCondition, buildDryRunExpressionContext(input, input.lastDecision), `workflow.nodes.${node.id}.data.entryCondition
|
|
1859
|
+
const entryConditionMet = this.conditionService.evaluateBoolean(entryCondition, buildDryRunExpressionContext(input, input.lastDecision), `workflow.nodes.${node.id}.data.entryCondition`, {
|
|
1860
|
+
allowedRootIdentifiers: DRY_RUN_WORKFLOW_ROOT_IDENTIFIERS,
|
|
1861
|
+
dialect: (0, expression_dialect_1.readWorkflowExpressionDialect)(input.workflowDefinition),
|
|
1862
|
+
});
|
|
1736
1863
|
const assignment = entryConditionMet && node.type === 'userTask'
|
|
1737
1864
|
? this.resolveDryRunAssignment(input, node)
|
|
1738
1865
|
: null;
|
|
@@ -1824,11 +1951,14 @@ let WorkflowEngineService = WorkflowEngineService_1 = class WorkflowEngineServic
|
|
|
1824
1951
|
};
|
|
1825
1952
|
}
|
|
1826
1953
|
evaluateDryRunEdge(input, edge, lastDecision) {
|
|
1827
|
-
const expression = edge.
|
|
1828
|
-
if (expression
|
|
1829
|
-
return
|
|
1954
|
+
const expression = readEvaluableEdgeExpression(edge, input.formData);
|
|
1955
|
+
if (!expression) {
|
|
1956
|
+
return (0, workflow_condition_evaluator_1.evaluateWorkflowEdgeCondition)(edge, { formData: input.formData });
|
|
1830
1957
|
}
|
|
1831
|
-
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
|
+
}));
|
|
1832
1962
|
}
|
|
1833
1963
|
/**
|
|
1834
1964
|
* The dry-run assignee of a user task. Only resolvers whose dry run yields
|
|
@@ -1926,7 +2056,10 @@ let WorkflowEngineService = WorkflowEngineService_1 = class WorkflowEngineServic
|
|
|
1926
2056
|
return findMemberIdsInValue(readValueAtPath(input.formData, resolver.formPath));
|
|
1927
2057
|
}
|
|
1928
2058
|
if (resolver.type === 'EXPRESSION') {
|
|
1929
|
-
return findMemberIdsInValue(this.conditionService.evaluateValue(resolver.expression, buildDryRunExpressionContext(input, null), `${label}.expression
|
|
2059
|
+
return findMemberIdsInValue(this.conditionService.evaluateValue(resolver.expression, buildDryRunExpressionContext(input, null), `${label}.expression`, {
|
|
2060
|
+
allowedRootIdentifiers: DRY_RUN_WORKFLOW_ROOT_IDENTIFIERS,
|
|
2061
|
+
dialect: (0, expression_dialect_1.readWorkflowExpressionDialect)(input.workflowDefinition),
|
|
2062
|
+
}));
|
|
1930
2063
|
}
|
|
1931
2064
|
if (resolver.type === 'ORG_MANAGER') {
|
|
1932
2065
|
const managerMemberIds = findMemberIdsInValue(readManagerMemberIdFromInitiatorSnapshot(input.initiatorMetadataSnapshot, resolver.levelsUp));
|
|
@@ -2138,7 +2271,18 @@ let WorkflowEngineService = WorkflowEngineService_1 = class WorkflowEngineServic
|
|
|
2138
2271
|
if (!currentVersion?.formDefinitionVersionId) {
|
|
2139
2272
|
return false;
|
|
2140
2273
|
}
|
|
2141
|
-
|
|
2274
|
+
// A policy in a dialect this version does not know cannot be evaluated,
|
|
2275
|
+
// so the template is not launchable (fail closed), but only that one:
|
|
2276
|
+
// publishing does not check the field yet (Phase 23 P2), and one such
|
|
2277
|
+
// version must not break the list for every member.
|
|
2278
|
+
if (hasExpressionText(currentVersion.initiatorPolicyCel) &&
|
|
2279
|
+
!isReadableExpressionDialect(currentVersion.workflowDefinition)) {
|
|
2280
|
+
this.logger.warn(`template=${template.id} version=${currentVersion.id} left out of launchable templates: unknown expression dialect`);
|
|
2281
|
+
return false;
|
|
2282
|
+
}
|
|
2283
|
+
// Each template is its own operation, so one slow template cannot use
|
|
2284
|
+
// up the allowance of the rest of the list.
|
|
2285
|
+
return (0, expression_budget_1.runExpressionOperation)(() => this.conditionService.evaluateBoolean(currentVersion.initiatorPolicyCel, buildInitiatorPolicyContext(initiatorMetadataSnapshot), 'initiatorPolicyCel', readExpressionDialectOptions(currentVersion.initiatorPolicyCel, currentVersion.workflowDefinition)));
|
|
2142
2286
|
});
|
|
2143
2287
|
}
|
|
2144
2288
|
async listWorkflowTokens(instanceId, scope) {
|
|
@@ -2592,7 +2736,7 @@ let WorkflowEngineService = WorkflowEngineService_1 = class WorkflowEngineServic
|
|
|
2592
2736
|
}
|
|
2593
2737
|
isNodeEntryConditionMet(instance, node) {
|
|
2594
2738
|
const entryCondition = readNodeEntryCondition(node);
|
|
2595
|
-
return this.conditionService.evaluateBoolean(entryCondition, buildWorkflowExpressionContext(instance), `workflow.nodes.${node.id}.data.entryCondition
|
|
2739
|
+
return this.conditionService.evaluateBoolean(entryCondition, buildWorkflowExpressionContext(instance), `workflow.nodes.${node.id}.data.entryCondition`, readExpressionDialectOptions(entryCondition, instance.workflowSnapshot));
|
|
2596
2740
|
}
|
|
2597
2741
|
async skipNodeByEntryCondition(manager, instance, token, node) {
|
|
2598
2742
|
await this.skipNode(manager, instance, token, node, {
|
|
@@ -2659,15 +2803,17 @@ let WorkflowEngineService = WorkflowEngineService_1 = class WorkflowEngineServic
|
|
|
2659
2803
|
throw new common_1.ConflictException(`Workflow node ${node.id} has no matching outgoing edge`);
|
|
2660
2804
|
}
|
|
2661
2805
|
async evaluateWorkflowEdge(manager, instance, node, edge) {
|
|
2662
|
-
const expression = edge.
|
|
2663
|
-
if (expression
|
|
2664
|
-
|
|
2665
|
-
|
|
2666
|
-
|
|
2667
|
-
lastDecision: await this.readLastDecisionContext(manager, instance.id, node.id),
|
|
2668
|
-
}, `workflow.edges.${edge.id}.data.condition`);
|
|
2806
|
+
const expression = readEvaluableEdgeExpression(edge, instance.formData);
|
|
2807
|
+
if (!expression) {
|
|
2808
|
+
return (0, workflow_condition_evaluator_1.evaluateWorkflowEdgeCondition)(edge, {
|
|
2809
|
+
formData: instance.formData,
|
|
2810
|
+
});
|
|
2669
2811
|
}
|
|
2670
|
-
|
|
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) }));
|
|
2671
2817
|
}
|
|
2672
2818
|
async readLastDecisionContext(manager, instanceId, nodeId) {
|
|
2673
2819
|
const tasks = await manager.getRepository(task_entity_1.TaskEntity).find({
|
|
@@ -3491,7 +3637,7 @@ let WorkflowEngineService = WorkflowEngineService_1 = class WorkflowEngineServic
|
|
|
3491
3637
|
});
|
|
3492
3638
|
}
|
|
3493
3639
|
async executeTaskReassignment(preRead, options) {
|
|
3494
|
-
return this.
|
|
3640
|
+
return this.runEngineTransaction(async (manager) => {
|
|
3495
3641
|
try {
|
|
3496
3642
|
const plan = await this.planTaskReassignment(manager, preRead.id, options);
|
|
3497
3643
|
if (options.dryRun) {
|
|
@@ -3868,7 +4014,7 @@ let WorkflowEngineService = WorkflowEngineService_1 = class WorkflowEngineServic
|
|
|
3868
4014
|
async delegateResolvedCandidates(manager, instance, nodeId, resolvedCandidates, gradeGated) {
|
|
3869
4015
|
// One batch for the node, so the host directory is asked once for every
|
|
3870
4016
|
// candidate's delegation agents rather than once per candidate.
|
|
3871
|
-
const resolutions = await this.delegationService.resolveAssignees(resolvedCandidates.map((candidate) => candidate.memberId), this.buildDelegationContext(instance, nodeId));
|
|
4017
|
+
const resolutions = await this.delegationService.resolveAssignees(resolvedCandidates.map((candidate) => candidate.memberId), { ...this.buildDelegationContext(instance, nodeId), manager });
|
|
3872
4018
|
const resolved = resolvedCandidates.map((candidate, index) => ({
|
|
3873
4019
|
candidate,
|
|
3874
4020
|
resolution: resolutions[index],
|
|
@@ -3890,7 +4036,7 @@ let WorkflowEngineService = WorkflowEngineService_1 = class WorkflowEngineServic
|
|
|
3890
4036
|
}
|
|
3891
4037
|
/** One member's delegation, grade-gated as {@link applyGradeGate} says. */
|
|
3892
4038
|
async resolveDelegation(manager, principalMemberId, context, gradeGated) {
|
|
3893
|
-
const resolution = await this.delegationService.resolveAssignee(principalMemberId, context);
|
|
4039
|
+
const resolution = await this.delegationService.resolveAssignee(principalMemberId, { ...context, manager });
|
|
3894
4040
|
const memberIds = gradeGated
|
|
3895
4041
|
? readGradeGateMemberIds(principalMemberId, resolution)
|
|
3896
4042
|
: [];
|
|
@@ -3946,7 +4092,7 @@ let WorkflowEngineService = WorkflowEngineService_1 = class WorkflowEngineServic
|
|
|
3946
4092
|
return readMemberIdsFromValue(readValueAtPath(instance.formData, resolver.formPath), label, subject, `依表單欄位「${resolver.formPath}」`);
|
|
3947
4093
|
}
|
|
3948
4094
|
if (resolver.type === 'EXPRESSION') {
|
|
3949
|
-
return readMemberIdsFromValue(this.conditionService.evaluateValue(resolver.expression, buildWorkflowExpressionContext(instance), `${label}.expression
|
|
4095
|
+
return readMemberIdsFromValue(this.conditionService.evaluateValue(resolver.expression, buildWorkflowExpressionContext(instance), `${label}.expression`, { dialect: (0, expression_dialect_1.readWorkflowExpressionDialect)(instance.workflowSnapshot) }), label, subject, '依運算式');
|
|
3950
4096
|
}
|
|
3951
4097
|
if (resolver.type === 'POSITION') {
|
|
3952
4098
|
return this.resolvePositionCandidates(manager, resolver.positionId, label, subject);
|
|
@@ -4468,34 +4614,39 @@ let WorkflowEngineService = WorkflowEngineService_1 = class WorkflowEngineServic
|
|
|
4468
4614
|
const action = node.data.action;
|
|
4469
4615
|
if (action.type === 'WEBHOOK') {
|
|
4470
4616
|
const payload = typeof action.payload === 'string' && action.payload.trim()
|
|
4471
|
-
? this.conditionService.evaluateValue(action.payload, buildWorkflowExpressionContext(instance), `workflow.nodes.${node.id}.data.action.payload
|
|
4617
|
+
? this.conditionService.evaluateValue(action.payload, buildWorkflowExpressionContext(instance), `workflow.nodes.${node.id}.data.action.payload`, {
|
|
4618
|
+
dialect: (0, expression_dialect_1.readWorkflowExpressionDialect)(instance.workflowSnapshot),
|
|
4619
|
+
})
|
|
4472
4620
|
: {
|
|
4473
4621
|
instance: buildWorkflowExpressionContext(instance).instance,
|
|
4474
4622
|
triggeredAt: new Date().toISOString(),
|
|
4475
4623
|
};
|
|
4476
|
-
|
|
4477
|
-
await activityRepository.save(activityRepository.create({
|
|
4478
|
-
actorMemberId: null,
|
|
4479
|
-
eventType: result.ok
|
|
4480
|
-
? workflow_engine_enums_1.ActivityLogEventTypeEnum.SERVICE_TASK_EXECUTED
|
|
4481
|
-
: workflow_engine_enums_1.ActivityLogEventTypeEnum.SERVICE_TASK_FAILED,
|
|
4624
|
+
await this.dispatchWebhookAfterCommit(manager, {
|
|
4482
4625
|
instanceId: instance.id,
|
|
4483
|
-
|
|
4484
|
-
|
|
4485
|
-
|
|
4486
|
-
|
|
4487
|
-
|
|
4488
|
-
|
|
4489
|
-
|
|
4490
|
-
|
|
4491
|
-
|
|
4492
|
-
|
|
4493
|
-
|
|
4626
|
+
readActivityLog: (result) => ({
|
|
4627
|
+
actorMemberId: null,
|
|
4628
|
+
eventType: result.ok
|
|
4629
|
+
? workflow_engine_enums_1.ActivityLogEventTypeEnum.SERVICE_TASK_EXECUTED
|
|
4630
|
+
: workflow_engine_enums_1.ActivityLogEventTypeEnum.SERVICE_TASK_FAILED,
|
|
4631
|
+
instanceId: instance.id,
|
|
4632
|
+
nodeId: node.id,
|
|
4633
|
+
payload: {
|
|
4634
|
+
action: 'WEBHOOK',
|
|
4635
|
+
error: result.error,
|
|
4636
|
+
ok: result.ok,
|
|
4637
|
+
status: result.status,
|
|
4638
|
+
tokenId: token.id,
|
|
4639
|
+
url: action.url,
|
|
4640
|
+
},
|
|
4641
|
+
taskId: null,
|
|
4642
|
+
}),
|
|
4643
|
+
send: () => executeWebhookServiceAction(this.serviceTaskDispatcher, action, payload),
|
|
4644
|
+
});
|
|
4494
4645
|
await this.advanceTokenToOutgoingNodes(manager, instance, token, node);
|
|
4495
4646
|
return;
|
|
4496
4647
|
}
|
|
4497
4648
|
if (action.type === 'SET_FORM_FIELD') {
|
|
4498
|
-
const value = this.conditionService.evaluateValue(action.value, buildWorkflowExpressionContext(instance), `workflow.nodes.${node.id}.data.action.value
|
|
4649
|
+
const value = this.conditionService.evaluateValue(action.value, buildWorkflowExpressionContext(instance), `workflow.nodes.${node.id}.data.action.value`, { dialect: (0, expression_dialect_1.readWorkflowExpressionDialect)(instance.workflowSnapshot) });
|
|
4499
4650
|
assertServiceTaskTextValue(instance.formDefinitionSnapshot, action.fieldPath, value, node.data.label);
|
|
4500
4651
|
const updatedFormData = writeValueAtPath(instance.formData, action.fieldPath, value);
|
|
4501
4652
|
// A service task runs inside this transaction, so it cannot call a host
|
|
@@ -5297,22 +5448,67 @@ function readNodeEntryCondition(node) {
|
|
|
5297
5448
|
return null;
|
|
5298
5449
|
}
|
|
5299
5450
|
/**
|
|
5300
|
-
*
|
|
5301
|
-
*
|
|
5302
|
-
*
|
|
5303
|
-
*
|
|
5304
|
-
*
|
|
5305
|
-
* edges never routed as designed. The structured fields the designer stores
|
|
5306
|
-
* beside the CEL are evaluated instead: `evaluateWorkflowEdgeCondition`
|
|
5307
|
-
* already reads a list as contains / does not contain and an empty list as
|
|
5308
|
-
* empty, the meaning the current designer compiles too. A hand-written CEL
|
|
5309
|
-
* 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).
|
|
5310
5456
|
*/
|
|
5311
|
-
function
|
|
5457
|
+
function readEvaluableEdgeExpression(edge, formData) {
|
|
5458
|
+
const expression = edge.data.condition?.trim();
|
|
5312
5459
|
const fieldKey = edge.data.conditionFieldKey;
|
|
5313
|
-
|
|
5460
|
+
const listValued = Boolean(fieldKey &&
|
|
5314
5461
|
edge.data.conditionOperator &&
|
|
5315
|
-
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
|
+
}
|
|
5316
5512
|
}
|
|
5317
5513
|
function edgeHasCondition(edge) {
|
|
5318
5514
|
return Boolean(edge.data.condition?.trim() ||
|
|
@@ -5617,6 +5813,33 @@ function normalizeFormFieldPath(path) {
|
|
|
5617
5813
|
}
|
|
5618
5814
|
return segments;
|
|
5619
5815
|
}
|
|
5816
|
+
/**
|
|
5817
|
+
* The message a failed dry run shows its caller. Only the fixed budget text, an
|
|
5818
|
+
* HTTP exception's own message, or an expression error's label and code reach
|
|
5819
|
+
* this point. Any other error is replaced with a generic message, since its text
|
|
5820
|
+
* may carry engine or context details.
|
|
5821
|
+
*/
|
|
5822
|
+
function readDryRunFailureMessage(error) {
|
|
5823
|
+
if (error instanceof time_budget_1.TimeBudgetExceededError) {
|
|
5824
|
+
return DRY_RUN_TOO_COMPLEX_MESSAGE;
|
|
5825
|
+
}
|
|
5826
|
+
if (error instanceof expression_errors_1.ExpressionRuntimeError) {
|
|
5827
|
+
return error.keyName
|
|
5828
|
+
? `${error.message}(找不到「${error.keyName}」)`
|
|
5829
|
+
: error.message;
|
|
5830
|
+
}
|
|
5831
|
+
if (error instanceof common_1.HttpException) {
|
|
5832
|
+
return error.message;
|
|
5833
|
+
}
|
|
5834
|
+
return 'Dry run failed';
|
|
5835
|
+
}
|
|
5836
|
+
function createFailedDryRunResult(message) {
|
|
5837
|
+
return Object.assign(new workflow_dry_run_object_1.WorkflowDryRunResultObject(), {
|
|
5838
|
+
errors: [message],
|
|
5839
|
+
steps: [],
|
|
5840
|
+
valid: false,
|
|
5841
|
+
});
|
|
5842
|
+
}
|
|
5620
5843
|
async function executeWebhookServiceAction(serviceTaskDispatcher, action, payload) {
|
|
5621
5844
|
try {
|
|
5622
5845
|
return await serviceTaskDispatcher.dispatchWebhook({
|