@bpmnkit/engine 0.1.7
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 +130 -0
- package/dist/dmn.d.ts +7 -0
- package/dist/dmn.js +115 -0
- package/dist/engine.d.ts +36 -0
- package/dist/engine.js +66 -0
- package/dist/index.d.ts +10 -0
- package/dist/index.js +7 -0
- package/dist/instance.d.ts +72 -0
- package/dist/instance.js +654 -0
- package/dist/timers.d.ts +14 -0
- package/dist/timers.js +85 -0
- package/dist/types.d.ts +57 -0
- package/dist/types.js +3 -0
- package/dist/variables.d.ts +25 -0
- package/dist/variables.js +76 -0
- package/dist/zeebe.d.ts +35 -0
- package/dist/zeebe.js +75 -0
- package/package.json +31 -0
package/dist/timers.js
ADDED
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Schedule a timer from a BPMN timer event definition.
|
|
3
|
+
* Supports ISO 8601 durations (PT2M), dates (2025-06-01T00:00:00Z),
|
|
4
|
+
* and cycles (R3/PT5S — fires N times or indefinitely when R/...).
|
|
5
|
+
* Returns a cancel function.
|
|
6
|
+
*/
|
|
7
|
+
export function scheduleTimer(def, callback) {
|
|
8
|
+
if (def.timeDuration !== undefined) {
|
|
9
|
+
return scheduleAfterDuration(def.timeDuration, callback);
|
|
10
|
+
}
|
|
11
|
+
if (def.timeDate !== undefined) {
|
|
12
|
+
return scheduleAtDate(def.timeDate, callback);
|
|
13
|
+
}
|
|
14
|
+
if (def.timeCycle !== undefined) {
|
|
15
|
+
return scheduleCycle(def.timeCycle, callback);
|
|
16
|
+
}
|
|
17
|
+
// No timer definition — fire immediately
|
|
18
|
+
const id = setTimeout(callback, 0);
|
|
19
|
+
return () => clearTimeout(id);
|
|
20
|
+
}
|
|
21
|
+
function scheduleAfterDuration(duration, cb) {
|
|
22
|
+
const ms = parseDurationMs(duration);
|
|
23
|
+
const id = setTimeout(cb, ms);
|
|
24
|
+
return () => clearTimeout(id);
|
|
25
|
+
}
|
|
26
|
+
function scheduleAtDate(dateStr, cb) {
|
|
27
|
+
const target = new Date(dateStr).getTime();
|
|
28
|
+
const ms = Math.max(0, target - Date.now());
|
|
29
|
+
const id = setTimeout(cb, ms);
|
|
30
|
+
return () => clearTimeout(id);
|
|
31
|
+
}
|
|
32
|
+
function scheduleCycle(cycle, cb) {
|
|
33
|
+
// Format: R<n>/<duration> or R/<duration> (infinite) or just <duration>
|
|
34
|
+
const cycleMatch = /^R(\d*)\/(.+)$/.exec(cycle);
|
|
35
|
+
if (cycleMatch === null) {
|
|
36
|
+
return scheduleAfterDuration(cycle, cb);
|
|
37
|
+
}
|
|
38
|
+
const countStr = cycleMatch[1];
|
|
39
|
+
const durationStr = cycleMatch[2] ?? "";
|
|
40
|
+
const maxFires = countStr === "" || countStr === undefined ? Number.POSITIVE_INFINITY : Number(countStr);
|
|
41
|
+
const ms = parseDurationMs(durationStr);
|
|
42
|
+
let fired = 0;
|
|
43
|
+
let cancelled = false;
|
|
44
|
+
let timerId;
|
|
45
|
+
const fire = () => {
|
|
46
|
+
if (cancelled)
|
|
47
|
+
return;
|
|
48
|
+
cb();
|
|
49
|
+
fired++;
|
|
50
|
+
if (fired < maxFires) {
|
|
51
|
+
timerId = setTimeout(fire, ms);
|
|
52
|
+
}
|
|
53
|
+
};
|
|
54
|
+
timerId = setTimeout(fire, ms);
|
|
55
|
+
return () => {
|
|
56
|
+
cancelled = true;
|
|
57
|
+
if (timerId !== undefined)
|
|
58
|
+
clearTimeout(timerId);
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* Parse an ISO 8601 duration string into milliseconds.
|
|
63
|
+
* Handles: PT#S, PT#M, PT#H, P#D, P#W, and combinations.
|
|
64
|
+
*/
|
|
65
|
+
export function parseDurationMs(duration) {
|
|
66
|
+
const re = /^P(?:(\d+(?:\.\d+)?)Y)?(?:(\d+(?:\.\d+)?)M)?(?:(\d+(?:\.\d+)?)W)?(?:(\d+(?:\.\d+)?)D)?(?:T(?:(\d+(?:\.\d+)?)H)?(?:(\d+(?:\.\d+)?)M)?(?:(\d+(?:\.\d+)?)S)?)?$/;
|
|
67
|
+
const m = re.exec(duration.trim());
|
|
68
|
+
if (m === null)
|
|
69
|
+
return 0;
|
|
70
|
+
const years = Number(m[1] ?? 0);
|
|
71
|
+
const months = Number(m[2] ?? 0);
|
|
72
|
+
const weeks = Number(m[3] ?? 0);
|
|
73
|
+
const days = Number(m[4] ?? 0);
|
|
74
|
+
const hours = Number(m[5] ?? 0);
|
|
75
|
+
const minutes = Number(m[6] ?? 0);
|
|
76
|
+
const seconds = Number(m[7] ?? 0);
|
|
77
|
+
return (years * 365.25 * 24 * 3600 * 1000 +
|
|
78
|
+
months * 30.44 * 24 * 3600 * 1000 +
|
|
79
|
+
weeks * 7 * 24 * 3600 * 1000 +
|
|
80
|
+
days * 24 * 3600 * 1000 +
|
|
81
|
+
hours * 3600 * 1000 +
|
|
82
|
+
minutes * 60 * 1000 +
|
|
83
|
+
seconds * 1000);
|
|
84
|
+
}
|
|
85
|
+
//# sourceMappingURL=timers.js.map
|
package/dist/types.d.ts
ADDED
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
export type ProcessEvent = {
|
|
2
|
+
type: "element:entering";
|
|
3
|
+
elementId: string;
|
|
4
|
+
elementName?: string;
|
|
5
|
+
elementType: string;
|
|
6
|
+
} | {
|
|
7
|
+
type: "element:entered";
|
|
8
|
+
elementId: string;
|
|
9
|
+
elementName?: string;
|
|
10
|
+
elementType: string;
|
|
11
|
+
} | {
|
|
12
|
+
type: "element:leaving";
|
|
13
|
+
elementId: string;
|
|
14
|
+
elementName?: string;
|
|
15
|
+
elementType: string;
|
|
16
|
+
} | {
|
|
17
|
+
type: "element:left";
|
|
18
|
+
elementId: string;
|
|
19
|
+
elementName?: string;
|
|
20
|
+
elementType: string;
|
|
21
|
+
} | {
|
|
22
|
+
type: "variable:set";
|
|
23
|
+
name: string;
|
|
24
|
+
value: unknown;
|
|
25
|
+
scopeId: string;
|
|
26
|
+
} | {
|
|
27
|
+
type: "job:created";
|
|
28
|
+
job: Job;
|
|
29
|
+
} | {
|
|
30
|
+
type: "feel:evaluated";
|
|
31
|
+
elementId: string;
|
|
32
|
+
property: string;
|
|
33
|
+
expression: string;
|
|
34
|
+
result: unknown;
|
|
35
|
+
variables: Record<string, unknown>;
|
|
36
|
+
} | {
|
|
37
|
+
type: "element:failed";
|
|
38
|
+
elementId: string;
|
|
39
|
+
error: string;
|
|
40
|
+
} | {
|
|
41
|
+
type: "process:completed";
|
|
42
|
+
variables: Record<string, unknown>;
|
|
43
|
+
} | {
|
|
44
|
+
type: "process:failed";
|
|
45
|
+
error: string;
|
|
46
|
+
};
|
|
47
|
+
export interface Job {
|
|
48
|
+
readonly id: string;
|
|
49
|
+
readonly type: string;
|
|
50
|
+
readonly headers: Record<string, string>;
|
|
51
|
+
readonly variables: Record<string, unknown>;
|
|
52
|
+
complete(variables?: Record<string, unknown>): void;
|
|
53
|
+
fail(error: string): void;
|
|
54
|
+
throwError(code: string, message: string): void;
|
|
55
|
+
}
|
|
56
|
+
export type JobHandler = (job: Job) => void | Promise<void>;
|
|
57
|
+
//# sourceMappingURL=types.d.ts.map
|
package/dist/types.js
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Hierarchical variable scope store.
|
|
3
|
+
* Scopes form a parent chain; reads walk up, writes update the nearest
|
|
4
|
+
* scope that already owns the variable (or fall through to local).
|
|
5
|
+
*/
|
|
6
|
+
export declare class VariableStore {
|
|
7
|
+
private readonly scopes;
|
|
8
|
+
private readonly parents;
|
|
9
|
+
createScope(id: string, parentId?: string): void;
|
|
10
|
+
removeScope(id: string): void;
|
|
11
|
+
/** Walk up the chain and return the value, or undefined if not found. */
|
|
12
|
+
get(scopeId: string, name: string): unknown;
|
|
13
|
+
/**
|
|
14
|
+
* Set a variable. Walks up the chain and updates it in the nearest scope
|
|
15
|
+
* that already holds the variable. If not found anywhere, sets it locally.
|
|
16
|
+
*/
|
|
17
|
+
set(scopeId: string, name: string, value: unknown): void;
|
|
18
|
+
/** Set a variable in this scope only, regardless of parent state. */
|
|
19
|
+
setLocal(scopeId: string, name: string, value: unknown): void;
|
|
20
|
+
/** Return all variables merged from root → this scope (child wins). */
|
|
21
|
+
getAll(scopeId: string): Record<string, unknown>;
|
|
22
|
+
private hasOwn;
|
|
23
|
+
private ancestorHas;
|
|
24
|
+
}
|
|
25
|
+
//# sourceMappingURL=variables.d.ts.map
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Hierarchical variable scope store.
|
|
3
|
+
* Scopes form a parent chain; reads walk up, writes update the nearest
|
|
4
|
+
* scope that already owns the variable (or fall through to local).
|
|
5
|
+
*/
|
|
6
|
+
export class VariableStore {
|
|
7
|
+
scopes = new Map();
|
|
8
|
+
parents = new Map();
|
|
9
|
+
createScope(id, parentId) {
|
|
10
|
+
this.scopes.set(id, new Map());
|
|
11
|
+
if (parentId !== undefined) {
|
|
12
|
+
this.parents.set(id, parentId);
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
removeScope(id) {
|
|
16
|
+
this.scopes.delete(id);
|
|
17
|
+
this.parents.delete(id);
|
|
18
|
+
}
|
|
19
|
+
/** Walk up the chain and return the value, or undefined if not found. */
|
|
20
|
+
get(scopeId, name) {
|
|
21
|
+
const scope = this.scopes.get(scopeId);
|
|
22
|
+
if (scope === undefined)
|
|
23
|
+
return undefined;
|
|
24
|
+
if (scope.has(name))
|
|
25
|
+
return scope.get(name);
|
|
26
|
+
const parentId = this.parents.get(scopeId);
|
|
27
|
+
if (parentId !== undefined)
|
|
28
|
+
return this.get(parentId, name);
|
|
29
|
+
return undefined;
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Set a variable. Walks up the chain and updates it in the nearest scope
|
|
33
|
+
* that already holds the variable. If not found anywhere, sets it locally.
|
|
34
|
+
*/
|
|
35
|
+
set(scopeId, name, value) {
|
|
36
|
+
if (this.hasOwn(scopeId, name)) {
|
|
37
|
+
this.scopes.get(scopeId)?.set(name, value);
|
|
38
|
+
return;
|
|
39
|
+
}
|
|
40
|
+
const parentId = this.parents.get(scopeId);
|
|
41
|
+
if (parentId !== undefined && this.ancestorHas(parentId, name)) {
|
|
42
|
+
this.set(parentId, name, value);
|
|
43
|
+
return;
|
|
44
|
+
}
|
|
45
|
+
this.scopes.get(scopeId)?.set(name, value);
|
|
46
|
+
}
|
|
47
|
+
/** Set a variable in this scope only, regardless of parent state. */
|
|
48
|
+
setLocal(scopeId, name, value) {
|
|
49
|
+
this.scopes.get(scopeId)?.set(name, value);
|
|
50
|
+
}
|
|
51
|
+
/** Return all variables merged from root → this scope (child wins). */
|
|
52
|
+
getAll(scopeId) {
|
|
53
|
+
const parentId = this.parents.get(scopeId);
|
|
54
|
+
const parentVars = parentId !== undefined ? this.getAll(parentId) : {};
|
|
55
|
+
const scope = this.scopes.get(scopeId);
|
|
56
|
+
if (scope === undefined)
|
|
57
|
+
return parentVars;
|
|
58
|
+
const result = { ...parentVars };
|
|
59
|
+
for (const [k, v] of scope) {
|
|
60
|
+
result[k] = v;
|
|
61
|
+
}
|
|
62
|
+
return result;
|
|
63
|
+
}
|
|
64
|
+
hasOwn(scopeId, name) {
|
|
65
|
+
return this.scopes.get(scopeId)?.has(name) ?? false;
|
|
66
|
+
}
|
|
67
|
+
ancestorHas(scopeId, name) {
|
|
68
|
+
if (this.hasOwn(scopeId, name))
|
|
69
|
+
return true;
|
|
70
|
+
const parentId = this.parents.get(scopeId);
|
|
71
|
+
if (parentId !== undefined)
|
|
72
|
+
return this.ancestorHas(parentId, name);
|
|
73
|
+
return false;
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
//# sourceMappingURL=variables.js.map
|
package/dist/zeebe.d.ts
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import type { XmlElement } from "@bpmnkit/core";
|
|
2
|
+
/** Parsed Zeebe extension data for a BPMN element. */
|
|
3
|
+
export interface ParsedZeebeExt {
|
|
4
|
+
taskDefinition?: {
|
|
5
|
+
type: string;
|
|
6
|
+
retries: number;
|
|
7
|
+
};
|
|
8
|
+
ioMapping?: {
|
|
9
|
+
inputs: Array<{
|
|
10
|
+
source: string;
|
|
11
|
+
target: string;
|
|
12
|
+
}>;
|
|
13
|
+
outputs: Array<{
|
|
14
|
+
source: string;
|
|
15
|
+
target: string;
|
|
16
|
+
}>;
|
|
17
|
+
};
|
|
18
|
+
taskHeaders?: Record<string, string>;
|
|
19
|
+
calledDecision?: {
|
|
20
|
+
decisionId: string;
|
|
21
|
+
resultVariable: string;
|
|
22
|
+
};
|
|
23
|
+
formDefinition?: {
|
|
24
|
+
formId: string;
|
|
25
|
+
};
|
|
26
|
+
scriptTask?: {
|
|
27
|
+
expression: string;
|
|
28
|
+
resultVariable: string;
|
|
29
|
+
};
|
|
30
|
+
/** JSON string from `camundaModeler:exampleOutputJson` zeebe:property — used in play mode. */
|
|
31
|
+
exampleOutputJson?: string;
|
|
32
|
+
}
|
|
33
|
+
/** Parse extensionElements XmlElement array into a typed Zeebe extension object. */
|
|
34
|
+
export declare function parseZeebeExt(extensionElements: XmlElement[]): ParsedZeebeExt;
|
|
35
|
+
//# sourceMappingURL=zeebe.d.ts.map
|
package/dist/zeebe.js
ADDED
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
/** Parse extensionElements XmlElement array into a typed Zeebe extension object. */
|
|
2
|
+
export function parseZeebeExt(extensionElements) {
|
|
3
|
+
const result = {};
|
|
4
|
+
for (const el of extensionElements) {
|
|
5
|
+
switch (el.name) {
|
|
6
|
+
case "zeebe:taskDefinition": {
|
|
7
|
+
const type = el.attributes.type ?? "";
|
|
8
|
+
const retries = Number(el.attributes.retries ?? "3");
|
|
9
|
+
result.taskDefinition = { type, retries };
|
|
10
|
+
break;
|
|
11
|
+
}
|
|
12
|
+
case "zeebe:ioMapping": {
|
|
13
|
+
const inputs = [];
|
|
14
|
+
const outputs = [];
|
|
15
|
+
for (const child of el.children) {
|
|
16
|
+
if (child.name === "zeebe:input") {
|
|
17
|
+
inputs.push({
|
|
18
|
+
source: child.attributes.source ?? "",
|
|
19
|
+
target: child.attributes.target ?? "",
|
|
20
|
+
});
|
|
21
|
+
}
|
|
22
|
+
else if (child.name === "zeebe:output") {
|
|
23
|
+
outputs.push({
|
|
24
|
+
source: child.attributes.source ?? "",
|
|
25
|
+
target: child.attributes.target ?? "",
|
|
26
|
+
});
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
result.ioMapping = { inputs, outputs };
|
|
30
|
+
break;
|
|
31
|
+
}
|
|
32
|
+
case "zeebe:taskHeaders": {
|
|
33
|
+
const headers = {};
|
|
34
|
+
for (const child of el.children) {
|
|
35
|
+
if (child.name === "zeebe:header") {
|
|
36
|
+
const key = child.attributes.key;
|
|
37
|
+
const value = child.attributes.value;
|
|
38
|
+
if (key !== undefined)
|
|
39
|
+
headers[key] = value ?? "";
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
result.taskHeaders = headers;
|
|
43
|
+
break;
|
|
44
|
+
}
|
|
45
|
+
case "zeebe:calledDecision": {
|
|
46
|
+
const decisionId = el.attributes.decisionId ?? "";
|
|
47
|
+
const resultVariable = el.attributes.resultVariable ?? "";
|
|
48
|
+
result.calledDecision = { decisionId, resultVariable };
|
|
49
|
+
break;
|
|
50
|
+
}
|
|
51
|
+
case "zeebe:formDefinition": {
|
|
52
|
+
const formId = el.attributes.formId ?? "";
|
|
53
|
+
result.formDefinition = { formId };
|
|
54
|
+
break;
|
|
55
|
+
}
|
|
56
|
+
case "zeebe:script": {
|
|
57
|
+
const expression = el.attributes.expression ?? "";
|
|
58
|
+
const resultVariable = el.attributes.resultVariable ?? "";
|
|
59
|
+
result.scriptTask = { expression, resultVariable };
|
|
60
|
+
break;
|
|
61
|
+
}
|
|
62
|
+
case "zeebe:properties": {
|
|
63
|
+
for (const child of el.children) {
|
|
64
|
+
if (child.name === "zeebe:property" &&
|
|
65
|
+
child.attributes.name === "camundaModeler:exampleOutputJson") {
|
|
66
|
+
result.exampleOutputJson = child.attributes.value;
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
break;
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
return result;
|
|
74
|
+
}
|
|
75
|
+
//# sourceMappingURL=zeebe.js.map
|
package/package.json
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@bpmnkit/engine",
|
|
3
|
+
"version": "0.1.7",
|
|
4
|
+
"type": "module",
|
|
5
|
+
"main": "./dist/index.js",
|
|
6
|
+
"types": "./dist/index.d.ts",
|
|
7
|
+
"exports": {
|
|
8
|
+
".": {
|
|
9
|
+
"types": "./dist/index.d.ts",
|
|
10
|
+
"import": "./dist/index.js"
|
|
11
|
+
}
|
|
12
|
+
},
|
|
13
|
+
"files": ["dist/**/*.js", "dist/**/*.d.ts"],
|
|
14
|
+
"scripts": {
|
|
15
|
+
"build": "tsc",
|
|
16
|
+
"typecheck": "tsc --noEmit",
|
|
17
|
+
"test": "vitest run",
|
|
18
|
+
"check": "biome check ."
|
|
19
|
+
},
|
|
20
|
+
"dependencies": {
|
|
21
|
+
"@bpmnkit/core": "workspace:*",
|
|
22
|
+
"@bpmnkit/feel": "workspace:*"
|
|
23
|
+
},
|
|
24
|
+
"description": "Lightweight BPMN 2.0 process execution engine for browsers and Node.js — zero dependencies",
|
|
25
|
+
"keywords": ["bpmn", "engine", "simulation", "workflow", "typescript", "dmn"],
|
|
26
|
+
"license": "MIT",
|
|
27
|
+
"repository": {
|
|
28
|
+
"type": "git",
|
|
29
|
+
"url": "https://github.com/bpmnkit/monorepo"
|
|
30
|
+
}
|
|
31
|
+
}
|