@nanobpm/nano-ide-app-embedded-nano 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/nano-ide.ext.json +10 -0
- package/package.json +10 -0
- package/templates/embedded-starter/README.md +27 -0
- package/templates/embedded-starter/deno.json +7 -0
- package/templates/embedded-starter/engine/host.ts +73 -0
- package/templates/embedded-starter/engine/unano.d.ts +70 -0
- package/templates/embedded-starter/engine/unano.js +456 -0
- package/templates/embedded-starter/engine/unano_bg.wasm +0 -0
- package/templates/embedded-starter/engine/unano_bg.wasm.d.ts +19 -0
- package/templates/embedded-starter/main.ts +30 -0
- package/templates/embedded-starter/resources/processes/throughput.bpmn +16 -0
package/package.json
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@nanobpm/nano-ide-app-embedded-nano",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "Embedded μ-nano app pack for the Nano RAD IDE: compile a self-contained Deno binary with the Nano BPMN engine running in-process (ADR 0005), no gateway required.",
|
|
5
|
+
"license": "Apache-2.0",
|
|
6
|
+
"keywords": ["nano-ide-ext", "nano-ide-app", "nanobpm", "embedded"],
|
|
7
|
+
"publishConfig": { "access": "public" },
|
|
8
|
+
"repository": { "type": "git", "url": "https://github.com/jwulf/nano-ide.git", "directory": "packages/app-embedded-nano" },
|
|
9
|
+
"files": ["nano-ide.ext.json", "templates"]
|
|
10
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
# Embedded μ-nano starter
|
|
2
|
+
|
|
3
|
+
A self-contained process app: the Nano BPMN engine runs **in this process** via
|
|
4
|
+
WebAssembly (ADR 0005). No gateway, no sockets. `deno compile` ships engine + app
|
|
5
|
+
as one binary.
|
|
6
|
+
|
|
7
|
+
## Run
|
|
8
|
+
|
|
9
|
+
```sh
|
|
10
|
+
deno task start # N=1000 by default; override with N=5000 deno task start
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
## Compile to a single binary
|
|
14
|
+
|
|
15
|
+
```sh
|
|
16
|
+
deno task compile # -> ./embedded-app (includes engine wasm + BPMN)
|
|
17
|
+
./embedded-app
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
## How it works
|
|
21
|
+
|
|
22
|
+
- `engine/unano_bg.wasm` is `engine-core` compiled to wasm; `engine/host.ts` wraps it
|
|
23
|
+
as an `EmbeddedHost` (deploy / createInstance / activateJobs / complete / tick).
|
|
24
|
+
- `@nanobpm/nano-sdk` with `CAMUNDA_TRANSPORT=embedded` binds the host directly, so the
|
|
25
|
+
same SDK code that talks to a real cluster drives the in-process engine.
|
|
26
|
+
- The host injects `Date.now()` via `tickNow`, so the clock-free engine-core runs as a
|
|
27
|
+
real wall-clock runtime.
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
// EmbeddedHost (ADR 0005, realization (a) "in-process direct"). Wraps μ-nano.wasm
|
|
2
|
+
// (engine-core compiled to wasm) and implements the EmbeddedHost contract the
|
|
3
|
+
// nano-sdk-js embedded transport binds to. engine-core stays clock-free; this host
|
|
4
|
+
// injects Date.now() via tickNow so the engine runs as a real, wall-clock runtime.
|
|
5
|
+
import { TestEngine } from "./unano.js";
|
|
6
|
+
|
|
7
|
+
export interface EmbeddedJob {
|
|
8
|
+
jobKey: string;
|
|
9
|
+
type: string;
|
|
10
|
+
processInstanceKey: string;
|
|
11
|
+
elementId: string;
|
|
12
|
+
retries: number;
|
|
13
|
+
variables: Record<string, unknown>;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export class EmbeddedHost {
|
|
17
|
+
private engine: TestEngine;
|
|
18
|
+
private constructor(engine: TestEngine) {
|
|
19
|
+
this.engine = engine;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/** Boot the wasm engine. The deno-target unano.js instantiates μ-nano.wasm at
|
|
23
|
+
* import time (top-level await), so there is nothing else to load. */
|
|
24
|
+
static async create(): Promise<EmbeddedHost> {
|
|
25
|
+
return new EmbeddedHost(new TestEngine());
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
async deploy(xml: string): Promise<{ processIds: string[] }> {
|
|
29
|
+
const r = JSON.parse(this.engine.deploy(xml));
|
|
30
|
+
return { processIds: r.processIds ?? [] };
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
async createInstance(input: { processDefinitionId?: string; variables?: Record<string, unknown> }): Promise<{ processInstanceKey: string }> {
|
|
34
|
+
const snap = JSON.parse(this.engine.createInstance(input.processDefinitionId ?? "", JSON.stringify(input.variables ?? {})));
|
|
35
|
+
return { processInstanceKey: String(snap.created) };
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
async activateJobs(type: string, max: number, timeoutMs: number, worker: string): Promise<EmbeddedJob[]> {
|
|
39
|
+
const jobs = JSON.parse(this.engine.activateJobs(type, max, timeoutMs, worker)) as any[];
|
|
40
|
+
return jobs.map((j) => ({
|
|
41
|
+
jobKey: String(j.key),
|
|
42
|
+
type: j.type,
|
|
43
|
+
processInstanceKey: String(j.instanceKey ?? ""),
|
|
44
|
+
elementId: j.elementId ?? "",
|
|
45
|
+
retries: j.retries ?? 3,
|
|
46
|
+
variables: j.variables ?? {},
|
|
47
|
+
}));
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
async completeJob(jobKey: string, variables?: Record<string, unknown>): Promise<void> {
|
|
51
|
+
this.engine.completeJob(jobKey, JSON.stringify(variables ?? {}));
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
async failJob(jobKey: string, retries: number, errorMessage?: string): Promise<void> {
|
|
55
|
+
this.engine.failJob(jobKey, retries, errorMessage ?? "");
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
instanceCompleted(key: string): boolean {
|
|
59
|
+
const snap = JSON.parse(this.engine.snapshot());
|
|
60
|
+
const inst = (snap.instances ?? []).find((i: any) => String(i.key) === key);
|
|
61
|
+
return !inst || inst.completed === true;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
instanceVariables(key: string): Record<string, unknown> {
|
|
65
|
+
const snap = JSON.parse(this.engine.snapshot());
|
|
66
|
+
const inst = (snap.instances ?? []).find((i: any) => String(i.key) === key);
|
|
67
|
+
return inst?.variables ?? {};
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
tick(): void {
|
|
71
|
+
this.engine.tickNow(Date.now());
|
|
72
|
+
}
|
|
73
|
+
}
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
/* tslint:disable */
|
|
2
|
+
/* eslint-disable */
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* A simulated engine instance bound to one modeler session.
|
|
6
|
+
*/
|
|
7
|
+
export class TestEngine {
|
|
8
|
+
free(): void;
|
|
9
|
+
[Symbol.dispose](): void;
|
|
10
|
+
/**
|
|
11
|
+
* Activate up to `max_jobs` `Created` jobs of `job_type`, locking them to
|
|
12
|
+
* `worker` until `now + timeout_ms`. Returns a JSON array of activated jobs
|
|
13
|
+
* (key, type, instance/element, retries, variables) for the dispatch loop to
|
|
14
|
+
* hand to worker handlers. The host owns the wall clock via `tickNow`.
|
|
15
|
+
*/
|
|
16
|
+
activateJobs(job_type: string, max_jobs: number, timeout_ms: number, worker: string): string;
|
|
17
|
+
/**
|
|
18
|
+
* Advance the virtual clock by `by_ms` milliseconds, firing any timers that
|
|
19
|
+
* become due and expiring any lapsed job locks.
|
|
20
|
+
*/
|
|
21
|
+
advanceTime(by_ms: number): string;
|
|
22
|
+
/**
|
|
23
|
+
* Complete a waiting job by key, merging `variables_json` (a JSON object
|
|
24
|
+
* string) into the instance. The job is activated first if it has not been
|
|
25
|
+
* already, so the UI can complete a freshly-created job directly.
|
|
26
|
+
*/
|
|
27
|
+
completeJob(job_key: string, variables_json: string): string;
|
|
28
|
+
/**
|
|
29
|
+
* Start a new instance of `process_id`, seeding it with the given variables
|
|
30
|
+
* (a JSON object string; pass `"{}"` or `""` for none). Returns the
|
|
31
|
+
* post-run [`Snapshot`] with a top-level `created` field holding the new
|
|
32
|
+
* instance key.
|
|
33
|
+
*/
|
|
34
|
+
createInstance(process_id: string, variables_json: string): string;
|
|
35
|
+
/**
|
|
36
|
+
* Parse and deploy a BPMN resource. Returns a JSON object
|
|
37
|
+
* `{ "processIds": [...], "snapshot": {...} }` on success, or throws a
|
|
38
|
+
* JS error carrying the parse/deploy failure message.
|
|
39
|
+
*/
|
|
40
|
+
deploy(xml: string): string;
|
|
41
|
+
/**
|
|
42
|
+
* The full ordered event log emitted so far, as a JSON array of
|
|
43
|
+
* `{ seq, now, type, ...payload }`. Useful for a step-through / trace view.
|
|
44
|
+
*/
|
|
45
|
+
events(): string;
|
|
46
|
+
/**
|
|
47
|
+
* Fail a waiting job by key with the given remaining `retries` and message.
|
|
48
|
+
* With no retries left this raises an incident (visible in the snapshot).
|
|
49
|
+
*/
|
|
50
|
+
failJob(job_key: string, retries: number, message: string): string;
|
|
51
|
+
/**
|
|
52
|
+
* Create a fresh, empty simulated engine. The virtual clock starts at 0.
|
|
53
|
+
*/
|
|
54
|
+
constructor();
|
|
55
|
+
/**
|
|
56
|
+
* The current simulation state as a JSON [`Snapshot`].
|
|
57
|
+
*/
|
|
58
|
+
snapshot(): string;
|
|
59
|
+
/**
|
|
60
|
+
* Set the engine clock to a wall-clock instant (ms), then trigger due timers
|
|
61
|
+
* and expire lapsed job locks. The embedded host calls this with `Date.now()`
|
|
62
|
+
* so `engine-core` stays clock-free while running as a real runtime. The
|
|
63
|
+
* clock never moves backwards. Returns the snapshot.
|
|
64
|
+
*/
|
|
65
|
+
tickNow(now_ms: number): string;
|
|
66
|
+
/**
|
|
67
|
+
* The current virtual clock (milliseconds).
|
|
68
|
+
*/
|
|
69
|
+
readonly now: number;
|
|
70
|
+
}
|
|
@@ -0,0 +1,456 @@
|
|
|
1
|
+
/* @ts-self-types="./unano.d.ts" */
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* A simulated engine instance bound to one modeler session.
|
|
5
|
+
*/
|
|
6
|
+
export class TestEngine {
|
|
7
|
+
__destroy_into_raw() {
|
|
8
|
+
const ptr = this.__wbg_ptr;
|
|
9
|
+
this.__wbg_ptr = 0;
|
|
10
|
+
TestEngineFinalization.unregister(this);
|
|
11
|
+
return ptr;
|
|
12
|
+
}
|
|
13
|
+
free() {
|
|
14
|
+
const ptr = this.__destroy_into_raw();
|
|
15
|
+
wasm.__wbg_testengine_free(ptr, 0);
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* Activate up to `max_jobs` `Created` jobs of `job_type`, locking them to
|
|
19
|
+
* `worker` until `now + timeout_ms`. Returns a JSON array of activated jobs
|
|
20
|
+
* (key, type, instance/element, retries, variables) for the dispatch loop to
|
|
21
|
+
* hand to worker handlers. The host owns the wall clock via `tickNow`.
|
|
22
|
+
* @param {string} job_type
|
|
23
|
+
* @param {number} max_jobs
|
|
24
|
+
* @param {number} timeout_ms
|
|
25
|
+
* @param {string} worker
|
|
26
|
+
* @returns {string}
|
|
27
|
+
*/
|
|
28
|
+
activateJobs(job_type, max_jobs, timeout_ms, worker) {
|
|
29
|
+
let deferred4_0;
|
|
30
|
+
let deferred4_1;
|
|
31
|
+
try {
|
|
32
|
+
const retptr = wasm.__wbindgen_add_to_stack_pointer(-16);
|
|
33
|
+
const ptr0 = passStringToWasm0(job_type, wasm.__wbindgen_export, wasm.__wbindgen_export2);
|
|
34
|
+
const len0 = WASM_VECTOR_LEN;
|
|
35
|
+
const ptr1 = passStringToWasm0(worker, wasm.__wbindgen_export, wasm.__wbindgen_export2);
|
|
36
|
+
const len1 = WASM_VECTOR_LEN;
|
|
37
|
+
wasm.testengine_activateJobs(retptr, this.__wbg_ptr, ptr0, len0, max_jobs, timeout_ms, ptr1, len1);
|
|
38
|
+
var r0 = getDataViewMemory0().getInt32(retptr + 4 * 0, true);
|
|
39
|
+
var r1 = getDataViewMemory0().getInt32(retptr + 4 * 1, true);
|
|
40
|
+
var r2 = getDataViewMemory0().getInt32(retptr + 4 * 2, true);
|
|
41
|
+
var r3 = getDataViewMemory0().getInt32(retptr + 4 * 3, true);
|
|
42
|
+
var ptr3 = r0;
|
|
43
|
+
var len3 = r1;
|
|
44
|
+
if (r3) {
|
|
45
|
+
ptr3 = 0; len3 = 0;
|
|
46
|
+
throw takeObject(r2);
|
|
47
|
+
}
|
|
48
|
+
deferred4_0 = ptr3;
|
|
49
|
+
deferred4_1 = len3;
|
|
50
|
+
return getStringFromWasm0(ptr3, len3);
|
|
51
|
+
} finally {
|
|
52
|
+
wasm.__wbindgen_add_to_stack_pointer(16);
|
|
53
|
+
wasm.__wbindgen_export3(deferred4_0, deferred4_1, 1);
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* Advance the virtual clock by `by_ms` milliseconds, firing any timers that
|
|
58
|
+
* become due and expiring any lapsed job locks.
|
|
59
|
+
* @param {number} by_ms
|
|
60
|
+
* @returns {string}
|
|
61
|
+
*/
|
|
62
|
+
advanceTime(by_ms) {
|
|
63
|
+
let deferred2_0;
|
|
64
|
+
let deferred2_1;
|
|
65
|
+
try {
|
|
66
|
+
const retptr = wasm.__wbindgen_add_to_stack_pointer(-16);
|
|
67
|
+
wasm.testengine_advanceTime(retptr, this.__wbg_ptr, by_ms);
|
|
68
|
+
var r0 = getDataViewMemory0().getInt32(retptr + 4 * 0, true);
|
|
69
|
+
var r1 = getDataViewMemory0().getInt32(retptr + 4 * 1, true);
|
|
70
|
+
var r2 = getDataViewMemory0().getInt32(retptr + 4 * 2, true);
|
|
71
|
+
var r3 = getDataViewMemory0().getInt32(retptr + 4 * 3, true);
|
|
72
|
+
var ptr1 = r0;
|
|
73
|
+
var len1 = r1;
|
|
74
|
+
if (r3) {
|
|
75
|
+
ptr1 = 0; len1 = 0;
|
|
76
|
+
throw takeObject(r2);
|
|
77
|
+
}
|
|
78
|
+
deferred2_0 = ptr1;
|
|
79
|
+
deferred2_1 = len1;
|
|
80
|
+
return getStringFromWasm0(ptr1, len1);
|
|
81
|
+
} finally {
|
|
82
|
+
wasm.__wbindgen_add_to_stack_pointer(16);
|
|
83
|
+
wasm.__wbindgen_export3(deferred2_0, deferred2_1, 1);
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
/**
|
|
87
|
+
* Complete a waiting job by key, merging `variables_json` (a JSON object
|
|
88
|
+
* string) into the instance. The job is activated first if it has not been
|
|
89
|
+
* already, so the UI can complete a freshly-created job directly.
|
|
90
|
+
* @param {string} job_key
|
|
91
|
+
* @param {string} variables_json
|
|
92
|
+
* @returns {string}
|
|
93
|
+
*/
|
|
94
|
+
completeJob(job_key, variables_json) {
|
|
95
|
+
let deferred4_0;
|
|
96
|
+
let deferred4_1;
|
|
97
|
+
try {
|
|
98
|
+
const retptr = wasm.__wbindgen_add_to_stack_pointer(-16);
|
|
99
|
+
const ptr0 = passStringToWasm0(job_key, wasm.__wbindgen_export, wasm.__wbindgen_export2);
|
|
100
|
+
const len0 = WASM_VECTOR_LEN;
|
|
101
|
+
const ptr1 = passStringToWasm0(variables_json, wasm.__wbindgen_export, wasm.__wbindgen_export2);
|
|
102
|
+
const len1 = WASM_VECTOR_LEN;
|
|
103
|
+
wasm.testengine_completeJob(retptr, this.__wbg_ptr, ptr0, len0, ptr1, len1);
|
|
104
|
+
var r0 = getDataViewMemory0().getInt32(retptr + 4 * 0, true);
|
|
105
|
+
var r1 = getDataViewMemory0().getInt32(retptr + 4 * 1, true);
|
|
106
|
+
var r2 = getDataViewMemory0().getInt32(retptr + 4 * 2, true);
|
|
107
|
+
var r3 = getDataViewMemory0().getInt32(retptr + 4 * 3, true);
|
|
108
|
+
var ptr3 = r0;
|
|
109
|
+
var len3 = r1;
|
|
110
|
+
if (r3) {
|
|
111
|
+
ptr3 = 0; len3 = 0;
|
|
112
|
+
throw takeObject(r2);
|
|
113
|
+
}
|
|
114
|
+
deferred4_0 = ptr3;
|
|
115
|
+
deferred4_1 = len3;
|
|
116
|
+
return getStringFromWasm0(ptr3, len3);
|
|
117
|
+
} finally {
|
|
118
|
+
wasm.__wbindgen_add_to_stack_pointer(16);
|
|
119
|
+
wasm.__wbindgen_export3(deferred4_0, deferred4_1, 1);
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
/**
|
|
123
|
+
* Start a new instance of `process_id`, seeding it with the given variables
|
|
124
|
+
* (a JSON object string; pass `"{}"` or `""` for none). Returns the
|
|
125
|
+
* post-run [`Snapshot`] with a top-level `created` field holding the new
|
|
126
|
+
* instance key.
|
|
127
|
+
* @param {string} process_id
|
|
128
|
+
* @param {string} variables_json
|
|
129
|
+
* @returns {string}
|
|
130
|
+
*/
|
|
131
|
+
createInstance(process_id, variables_json) {
|
|
132
|
+
let deferred4_0;
|
|
133
|
+
let deferred4_1;
|
|
134
|
+
try {
|
|
135
|
+
const retptr = wasm.__wbindgen_add_to_stack_pointer(-16);
|
|
136
|
+
const ptr0 = passStringToWasm0(process_id, wasm.__wbindgen_export, wasm.__wbindgen_export2);
|
|
137
|
+
const len0 = WASM_VECTOR_LEN;
|
|
138
|
+
const ptr1 = passStringToWasm0(variables_json, wasm.__wbindgen_export, wasm.__wbindgen_export2);
|
|
139
|
+
const len1 = WASM_VECTOR_LEN;
|
|
140
|
+
wasm.testengine_createInstance(retptr, this.__wbg_ptr, ptr0, len0, ptr1, len1);
|
|
141
|
+
var r0 = getDataViewMemory0().getInt32(retptr + 4 * 0, true);
|
|
142
|
+
var r1 = getDataViewMemory0().getInt32(retptr + 4 * 1, true);
|
|
143
|
+
var r2 = getDataViewMemory0().getInt32(retptr + 4 * 2, true);
|
|
144
|
+
var r3 = getDataViewMemory0().getInt32(retptr + 4 * 3, true);
|
|
145
|
+
var ptr3 = r0;
|
|
146
|
+
var len3 = r1;
|
|
147
|
+
if (r3) {
|
|
148
|
+
ptr3 = 0; len3 = 0;
|
|
149
|
+
throw takeObject(r2);
|
|
150
|
+
}
|
|
151
|
+
deferred4_0 = ptr3;
|
|
152
|
+
deferred4_1 = len3;
|
|
153
|
+
return getStringFromWasm0(ptr3, len3);
|
|
154
|
+
} finally {
|
|
155
|
+
wasm.__wbindgen_add_to_stack_pointer(16);
|
|
156
|
+
wasm.__wbindgen_export3(deferred4_0, deferred4_1, 1);
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
/**
|
|
160
|
+
* Parse and deploy a BPMN resource. Returns a JSON object
|
|
161
|
+
* `{ "processIds": [...], "snapshot": {...} }` on success, or throws a
|
|
162
|
+
* JS error carrying the parse/deploy failure message.
|
|
163
|
+
* @param {string} xml
|
|
164
|
+
* @returns {string}
|
|
165
|
+
*/
|
|
166
|
+
deploy(xml) {
|
|
167
|
+
let deferred3_0;
|
|
168
|
+
let deferred3_1;
|
|
169
|
+
try {
|
|
170
|
+
const retptr = wasm.__wbindgen_add_to_stack_pointer(-16);
|
|
171
|
+
const ptr0 = passStringToWasm0(xml, wasm.__wbindgen_export, wasm.__wbindgen_export2);
|
|
172
|
+
const len0 = WASM_VECTOR_LEN;
|
|
173
|
+
wasm.testengine_deploy(retptr, this.__wbg_ptr, ptr0, len0);
|
|
174
|
+
var r0 = getDataViewMemory0().getInt32(retptr + 4 * 0, true);
|
|
175
|
+
var r1 = getDataViewMemory0().getInt32(retptr + 4 * 1, true);
|
|
176
|
+
var r2 = getDataViewMemory0().getInt32(retptr + 4 * 2, true);
|
|
177
|
+
var r3 = getDataViewMemory0().getInt32(retptr + 4 * 3, true);
|
|
178
|
+
var ptr2 = r0;
|
|
179
|
+
var len2 = r1;
|
|
180
|
+
if (r3) {
|
|
181
|
+
ptr2 = 0; len2 = 0;
|
|
182
|
+
throw takeObject(r2);
|
|
183
|
+
}
|
|
184
|
+
deferred3_0 = ptr2;
|
|
185
|
+
deferred3_1 = len2;
|
|
186
|
+
return getStringFromWasm0(ptr2, len2);
|
|
187
|
+
} finally {
|
|
188
|
+
wasm.__wbindgen_add_to_stack_pointer(16);
|
|
189
|
+
wasm.__wbindgen_export3(deferred3_0, deferred3_1, 1);
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
/**
|
|
193
|
+
* The full ordered event log emitted so far, as a JSON array of
|
|
194
|
+
* `{ seq, now, type, ...payload }`. Useful for a step-through / trace view.
|
|
195
|
+
* @returns {string}
|
|
196
|
+
*/
|
|
197
|
+
events() {
|
|
198
|
+
let deferred2_0;
|
|
199
|
+
let deferred2_1;
|
|
200
|
+
try {
|
|
201
|
+
const retptr = wasm.__wbindgen_add_to_stack_pointer(-16);
|
|
202
|
+
wasm.testengine_events(retptr, this.__wbg_ptr);
|
|
203
|
+
var r0 = getDataViewMemory0().getInt32(retptr + 4 * 0, true);
|
|
204
|
+
var r1 = getDataViewMemory0().getInt32(retptr + 4 * 1, true);
|
|
205
|
+
var r2 = getDataViewMemory0().getInt32(retptr + 4 * 2, true);
|
|
206
|
+
var r3 = getDataViewMemory0().getInt32(retptr + 4 * 3, true);
|
|
207
|
+
var ptr1 = r0;
|
|
208
|
+
var len1 = r1;
|
|
209
|
+
if (r3) {
|
|
210
|
+
ptr1 = 0; len1 = 0;
|
|
211
|
+
throw takeObject(r2);
|
|
212
|
+
}
|
|
213
|
+
deferred2_0 = ptr1;
|
|
214
|
+
deferred2_1 = len1;
|
|
215
|
+
return getStringFromWasm0(ptr1, len1);
|
|
216
|
+
} finally {
|
|
217
|
+
wasm.__wbindgen_add_to_stack_pointer(16);
|
|
218
|
+
wasm.__wbindgen_export3(deferred2_0, deferred2_1, 1);
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
/**
|
|
222
|
+
* Fail a waiting job by key with the given remaining `retries` and message.
|
|
223
|
+
* With no retries left this raises an incident (visible in the snapshot).
|
|
224
|
+
* @param {string} job_key
|
|
225
|
+
* @param {number} retries
|
|
226
|
+
* @param {string} message
|
|
227
|
+
* @returns {string}
|
|
228
|
+
*/
|
|
229
|
+
failJob(job_key, retries, message) {
|
|
230
|
+
let deferred4_0;
|
|
231
|
+
let deferred4_1;
|
|
232
|
+
try {
|
|
233
|
+
const retptr = wasm.__wbindgen_add_to_stack_pointer(-16);
|
|
234
|
+
const ptr0 = passStringToWasm0(job_key, wasm.__wbindgen_export, wasm.__wbindgen_export2);
|
|
235
|
+
const len0 = WASM_VECTOR_LEN;
|
|
236
|
+
const ptr1 = passStringToWasm0(message, wasm.__wbindgen_export, wasm.__wbindgen_export2);
|
|
237
|
+
const len1 = WASM_VECTOR_LEN;
|
|
238
|
+
wasm.testengine_failJob(retptr, this.__wbg_ptr, ptr0, len0, retries, ptr1, len1);
|
|
239
|
+
var r0 = getDataViewMemory0().getInt32(retptr + 4 * 0, true);
|
|
240
|
+
var r1 = getDataViewMemory0().getInt32(retptr + 4 * 1, true);
|
|
241
|
+
var r2 = getDataViewMemory0().getInt32(retptr + 4 * 2, true);
|
|
242
|
+
var r3 = getDataViewMemory0().getInt32(retptr + 4 * 3, true);
|
|
243
|
+
var ptr3 = r0;
|
|
244
|
+
var len3 = r1;
|
|
245
|
+
if (r3) {
|
|
246
|
+
ptr3 = 0; len3 = 0;
|
|
247
|
+
throw takeObject(r2);
|
|
248
|
+
}
|
|
249
|
+
deferred4_0 = ptr3;
|
|
250
|
+
deferred4_1 = len3;
|
|
251
|
+
return getStringFromWasm0(ptr3, len3);
|
|
252
|
+
} finally {
|
|
253
|
+
wasm.__wbindgen_add_to_stack_pointer(16);
|
|
254
|
+
wasm.__wbindgen_export3(deferred4_0, deferred4_1, 1);
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
/**
|
|
258
|
+
* Create a fresh, empty simulated engine. The virtual clock starts at 0.
|
|
259
|
+
*/
|
|
260
|
+
constructor() {
|
|
261
|
+
const ret = wasm.testengine_new();
|
|
262
|
+
this.__wbg_ptr = ret;
|
|
263
|
+
TestEngineFinalization.register(this, this.__wbg_ptr, this);
|
|
264
|
+
return this;
|
|
265
|
+
}
|
|
266
|
+
/**
|
|
267
|
+
* The current virtual clock (milliseconds).
|
|
268
|
+
* @returns {number}
|
|
269
|
+
*/
|
|
270
|
+
get now() {
|
|
271
|
+
const ret = wasm.testengine_now(this.__wbg_ptr);
|
|
272
|
+
return ret;
|
|
273
|
+
}
|
|
274
|
+
/**
|
|
275
|
+
* The current simulation state as a JSON [`Snapshot`].
|
|
276
|
+
* @returns {string}
|
|
277
|
+
*/
|
|
278
|
+
snapshot() {
|
|
279
|
+
let deferred2_0;
|
|
280
|
+
let deferred2_1;
|
|
281
|
+
try {
|
|
282
|
+
const retptr = wasm.__wbindgen_add_to_stack_pointer(-16);
|
|
283
|
+
wasm.testengine_snapshot(retptr, this.__wbg_ptr);
|
|
284
|
+
var r0 = getDataViewMemory0().getInt32(retptr + 4 * 0, true);
|
|
285
|
+
var r1 = getDataViewMemory0().getInt32(retptr + 4 * 1, true);
|
|
286
|
+
var r2 = getDataViewMemory0().getInt32(retptr + 4 * 2, true);
|
|
287
|
+
var r3 = getDataViewMemory0().getInt32(retptr + 4 * 3, true);
|
|
288
|
+
var ptr1 = r0;
|
|
289
|
+
var len1 = r1;
|
|
290
|
+
if (r3) {
|
|
291
|
+
ptr1 = 0; len1 = 0;
|
|
292
|
+
throw takeObject(r2);
|
|
293
|
+
}
|
|
294
|
+
deferred2_0 = ptr1;
|
|
295
|
+
deferred2_1 = len1;
|
|
296
|
+
return getStringFromWasm0(ptr1, len1);
|
|
297
|
+
} finally {
|
|
298
|
+
wasm.__wbindgen_add_to_stack_pointer(16);
|
|
299
|
+
wasm.__wbindgen_export3(deferred2_0, deferred2_1, 1);
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
/**
|
|
303
|
+
* Set the engine clock to a wall-clock instant (ms), then trigger due timers
|
|
304
|
+
* and expire lapsed job locks. The embedded host calls this with `Date.now()`
|
|
305
|
+
* so `engine-core` stays clock-free while running as a real runtime. The
|
|
306
|
+
* clock never moves backwards. Returns the snapshot.
|
|
307
|
+
* @param {number} now_ms
|
|
308
|
+
* @returns {string}
|
|
309
|
+
*/
|
|
310
|
+
tickNow(now_ms) {
|
|
311
|
+
let deferred2_0;
|
|
312
|
+
let deferred2_1;
|
|
313
|
+
try {
|
|
314
|
+
const retptr = wasm.__wbindgen_add_to_stack_pointer(-16);
|
|
315
|
+
wasm.testengine_tickNow(retptr, this.__wbg_ptr, now_ms);
|
|
316
|
+
var r0 = getDataViewMemory0().getInt32(retptr + 4 * 0, true);
|
|
317
|
+
var r1 = getDataViewMemory0().getInt32(retptr + 4 * 1, true);
|
|
318
|
+
var r2 = getDataViewMemory0().getInt32(retptr + 4 * 2, true);
|
|
319
|
+
var r3 = getDataViewMemory0().getInt32(retptr + 4 * 3, true);
|
|
320
|
+
var ptr1 = r0;
|
|
321
|
+
var len1 = r1;
|
|
322
|
+
if (r3) {
|
|
323
|
+
ptr1 = 0; len1 = 0;
|
|
324
|
+
throw takeObject(r2);
|
|
325
|
+
}
|
|
326
|
+
deferred2_0 = ptr1;
|
|
327
|
+
deferred2_1 = len1;
|
|
328
|
+
return getStringFromWasm0(ptr1, len1);
|
|
329
|
+
} finally {
|
|
330
|
+
wasm.__wbindgen_add_to_stack_pointer(16);
|
|
331
|
+
wasm.__wbindgen_export3(deferred2_0, deferred2_1, 1);
|
|
332
|
+
}
|
|
333
|
+
}
|
|
334
|
+
}
|
|
335
|
+
if (Symbol.dispose) TestEngine.prototype[Symbol.dispose] = TestEngine.prototype.free;
|
|
336
|
+
function __wbg_get_imports() {
|
|
337
|
+
const import0 = {
|
|
338
|
+
__proto__: null,
|
|
339
|
+
__wbg___wbindgen_throw_ea4887a5f8f9a9db: function(arg0, arg1) {
|
|
340
|
+
throw new Error(getStringFromWasm0(arg0, arg1));
|
|
341
|
+
},
|
|
342
|
+
__wbindgen_cast_0000000000000001: function(arg0, arg1) {
|
|
343
|
+
// Cast intrinsic for `Ref(String) -> Externref`.
|
|
344
|
+
const ret = getStringFromWasm0(arg0, arg1);
|
|
345
|
+
return addHeapObject(ret);
|
|
346
|
+
},
|
|
347
|
+
};
|
|
348
|
+
return {
|
|
349
|
+
__proto__: null,
|
|
350
|
+
"./unano_bg.js": import0,
|
|
351
|
+
};
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
const TestEngineFinalization = (typeof FinalizationRegistry === 'undefined')
|
|
355
|
+
? { register: () => {}, unregister: () => {} }
|
|
356
|
+
: new FinalizationRegistry(ptr => wasm.__wbg_testengine_free(ptr, 1));
|
|
357
|
+
|
|
358
|
+
function addHeapObject(obj) {
|
|
359
|
+
if (heap_next === heap.length) heap.push(heap.length + 1);
|
|
360
|
+
const idx = heap_next;
|
|
361
|
+
heap_next = heap[idx];
|
|
362
|
+
|
|
363
|
+
heap[idx] = obj;
|
|
364
|
+
return idx;
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
function dropObject(idx) {
|
|
368
|
+
if (idx < 1028) return;
|
|
369
|
+
heap[idx] = heap_next;
|
|
370
|
+
heap_next = idx;
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
let cachedDataViewMemory0 = null;
|
|
374
|
+
function getDataViewMemory0() {
|
|
375
|
+
if (cachedDataViewMemory0 === null || cachedDataViewMemory0.buffer.detached === true || (cachedDataViewMemory0.buffer.detached === undefined && cachedDataViewMemory0.buffer !== wasm.memory.buffer)) {
|
|
376
|
+
cachedDataViewMemory0 = new DataView(wasm.memory.buffer);
|
|
377
|
+
}
|
|
378
|
+
return cachedDataViewMemory0;
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
function getStringFromWasm0(ptr, len) {
|
|
382
|
+
return decodeText(ptr >>> 0, len);
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
let cachedUint8ArrayMemory0 = null;
|
|
386
|
+
function getUint8ArrayMemory0() {
|
|
387
|
+
if (cachedUint8ArrayMemory0 === null || cachedUint8ArrayMemory0.byteLength === 0) {
|
|
388
|
+
cachedUint8ArrayMemory0 = new Uint8Array(wasm.memory.buffer);
|
|
389
|
+
}
|
|
390
|
+
return cachedUint8ArrayMemory0;
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
function getObject(idx) { return heap[idx]; }
|
|
394
|
+
|
|
395
|
+
let heap = new Array(1024).fill(undefined);
|
|
396
|
+
heap.push(undefined, null, true, false);
|
|
397
|
+
|
|
398
|
+
let heap_next = heap.length;
|
|
399
|
+
|
|
400
|
+
function passStringToWasm0(arg, malloc, realloc) {
|
|
401
|
+
if (realloc === undefined) {
|
|
402
|
+
const buf = cachedTextEncoder.encode(arg);
|
|
403
|
+
const ptr = malloc(buf.length, 1) >>> 0;
|
|
404
|
+
getUint8ArrayMemory0().subarray(ptr, ptr + buf.length).set(buf);
|
|
405
|
+
WASM_VECTOR_LEN = buf.length;
|
|
406
|
+
return ptr;
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
let len = arg.length;
|
|
410
|
+
let ptr = malloc(len, 1) >>> 0;
|
|
411
|
+
|
|
412
|
+
const mem = getUint8ArrayMemory0();
|
|
413
|
+
|
|
414
|
+
let offset = 0;
|
|
415
|
+
|
|
416
|
+
for (; offset < len; offset++) {
|
|
417
|
+
const code = arg.charCodeAt(offset);
|
|
418
|
+
if (code > 0x7F) break;
|
|
419
|
+
mem[ptr + offset] = code;
|
|
420
|
+
}
|
|
421
|
+
if (offset !== len) {
|
|
422
|
+
if (offset !== 0) {
|
|
423
|
+
arg = arg.slice(offset);
|
|
424
|
+
}
|
|
425
|
+
ptr = realloc(ptr, len, len = offset + arg.length * 3, 1) >>> 0;
|
|
426
|
+
const view = getUint8ArrayMemory0().subarray(ptr + offset, ptr + len);
|
|
427
|
+
const ret = cachedTextEncoder.encodeInto(arg, view);
|
|
428
|
+
|
|
429
|
+
offset += ret.written;
|
|
430
|
+
ptr = realloc(ptr, len, offset, 1) >>> 0;
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
WASM_VECTOR_LEN = offset;
|
|
434
|
+
return ptr;
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
function takeObject(idx) {
|
|
438
|
+
const ret = getObject(idx);
|
|
439
|
+
dropObject(idx);
|
|
440
|
+
return ret;
|
|
441
|
+
}
|
|
442
|
+
|
|
443
|
+
let cachedTextDecoder = new TextDecoder('utf-8', { ignoreBOM: true, fatal: true });
|
|
444
|
+
cachedTextDecoder.decode();
|
|
445
|
+
function decodeText(ptr, len) {
|
|
446
|
+
return cachedTextDecoder.decode(getUint8ArrayMemory0().subarray(ptr, ptr + len));
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
const cachedTextEncoder = new TextEncoder();
|
|
450
|
+
|
|
451
|
+
let WASM_VECTOR_LEN = 0;
|
|
452
|
+
|
|
453
|
+
const wasmUrl = new URL('unano_bg.wasm', import.meta.url);
|
|
454
|
+
const wasmInstantiated = await WebAssembly.instantiateStreaming(fetch(wasmUrl), __wbg_get_imports());
|
|
455
|
+
const wasmInstance = wasmInstantiated.instance;
|
|
456
|
+
const wasm = wasmInstance.exports;
|
|
Binary file
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/* tslint:disable */
|
|
2
|
+
/* eslint-disable */
|
|
3
|
+
export const memory: WebAssembly.Memory;
|
|
4
|
+
export const __wbg_testengine_free: (a: number, b: number) => void;
|
|
5
|
+
export const testengine_activateJobs: (a: number, b: number, c: number, d: number, e: number, f: number, g: number, h: number) => void;
|
|
6
|
+
export const testengine_advanceTime: (a: number, b: number, c: number) => void;
|
|
7
|
+
export const testengine_completeJob: (a: number, b: number, c: number, d: number, e: number, f: number) => void;
|
|
8
|
+
export const testengine_createInstance: (a: number, b: number, c: number, d: number, e: number, f: number) => void;
|
|
9
|
+
export const testengine_deploy: (a: number, b: number, c: number, d: number) => void;
|
|
10
|
+
export const testengine_events: (a: number, b: number) => void;
|
|
11
|
+
export const testengine_failJob: (a: number, b: number, c: number, d: number, e: number, f: number, g: number) => void;
|
|
12
|
+
export const testengine_new: () => number;
|
|
13
|
+
export const testengine_now: (a: number) => number;
|
|
14
|
+
export const testengine_snapshot: (a: number, b: number) => void;
|
|
15
|
+
export const testengine_tickNow: (a: number, b: number, c: number) => void;
|
|
16
|
+
export const __wbindgen_add_to_stack_pointer: (a: number) => number;
|
|
17
|
+
export const __wbindgen_export: (a: number, b: number) => number;
|
|
18
|
+
export const __wbindgen_export2: (a: number, b: number, c: number, d: number) => number;
|
|
19
|
+
export const __wbindgen_export3: (a: number, b: number, c: number) => void;
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
// Embedded μ-nano starter (ADR 0005). The Nano engine runs IN this process via
|
|
2
|
+
// wasm — no gateway, no sockets. The nano-sdk-js "embedded" transport binds to the
|
|
3
|
+
// host directly, so the same SDK code that talks to a real cluster also drives the
|
|
4
|
+
// in-process engine. `deno compile --include engine` ships engine + app as one binary.
|
|
5
|
+
import { createCamundaClient } from "@nanobpm/nano-sdk";
|
|
6
|
+
import { EmbeddedHost } from "./engine/host.ts";
|
|
7
|
+
|
|
8
|
+
const BPMN = await Deno.readTextFile(new URL("./resources/processes/throughput.bpmn", import.meta.url));
|
|
9
|
+
|
|
10
|
+
const host = await EmbeddedHost.create();
|
|
11
|
+
await host.deploy(BPMN);
|
|
12
|
+
|
|
13
|
+
const camunda = createCamundaClient({ config: { CAMUNDA_TRANSPORT: "embedded" }, embeddedHost: host });
|
|
14
|
+
|
|
15
|
+
camunda.createJobWorker({
|
|
16
|
+
jobType: "work",
|
|
17
|
+
workerName: "embedded-worker",
|
|
18
|
+
maxParallelJobs: 16,
|
|
19
|
+
jobHandler: async (job: { complete: (v?: Record<string, unknown>) => Promise<unknown> }) => job.complete({ done: true }),
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
const N = Number(Deno.env.get("N") ?? 1000);
|
|
23
|
+
const t0 = performance.now();
|
|
24
|
+
await Promise.all(
|
|
25
|
+
Array.from({ length: N }, () =>
|
|
26
|
+
camunda.createProcessInstance({ processDefinitionId: "throughput", variables: {}, awaitCompletion: true })),
|
|
27
|
+
);
|
|
28
|
+
const dt = (performance.now() - t0) / 1000;
|
|
29
|
+
console.log(`embedded: ${N} instances in ${dt.toFixed(2)}s — ${(N / dt).toFixed(0)}/s`);
|
|
30
|
+
camunda.stopAllWorkers?.();
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
<?xml version="1.0" encoding="UTF-8"?>
|
|
2
|
+
<definitions xmlns="http://www.omg.org/spec/BPMN/20100524/MODEL"
|
|
3
|
+
xmlns:zeebe="http://camunda.org/schema/zeebe/1.0"
|
|
4
|
+
targetNamespace="http://nanobpm/embedded">
|
|
5
|
+
<process id="throughput" isExecutable="true">
|
|
6
|
+
<startEvent id="start"/>
|
|
7
|
+
<sequenceFlow id="f1" sourceRef="start" targetRef="work"/>
|
|
8
|
+
<serviceTask id="work">
|
|
9
|
+
<extensionElements>
|
|
10
|
+
<zeebe:taskDefinition type="work"/>
|
|
11
|
+
</extensionElements>
|
|
12
|
+
</serviceTask>
|
|
13
|
+
<sequenceFlow id="f2" sourceRef="work" targetRef="end"/>
|
|
14
|
+
<endEvent id="end"/>
|
|
15
|
+
</process>
|
|
16
|
+
</definitions>
|