@nanobpm/urban 0.82.1 → 0.84.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/dist/runtime/core/host.d.ts +207 -1
- package/dist/runtime/core/host.js +7 -0
- package/dist/runtime/engine/nanosdk.d.ts +76 -1
- package/dist/runtime/engine/nanosdk.js +387 -0
- package/dist/runtime/index.d.ts +1 -1
- package/package.json +1 -1
- package/src/runtime/core/host.ts +225 -0
- package/src/runtime/core/modules/actions.test.ts +7 -0
- package/src/runtime/core/modules/agent.test.ts +7 -0
- package/src/runtime/core/modules/api.test.ts +7 -0
- package/src/runtime/core/modules/cancel.test.ts +7 -0
- package/src/runtime/core/modules/connectors.test.ts +7 -0
- package/src/runtime/core/modules/datasource.migrations.test.ts +7 -0
- package/src/runtime/core/modules/instance-tracking.test.ts +7 -0
- package/src/runtime/core/modules/pages.test.ts +7 -0
- package/src/runtime/core/modules/surfaces.test.ts +14 -0
- package/src/runtime/core/modules/triggers.test.ts +7 -0
- package/src/runtime/core/modules/workers.test.ts +7 -0
- package/src/runtime/core/runtime.extensions.test.ts +15 -0
- package/src/runtime/core/runtime.test.ts +15 -0
- package/src/runtime/engine/nanosdk.test.ts +384 -0
- package/src/runtime/engine/nanosdk.ts +463 -0
- package/src/runtime/index.ts +10 -0
|
@@ -33,6 +33,13 @@ const engine: EngineClient = {
|
|
|
33
33
|
getForm: async () => null,
|
|
34
34
|
completeUserTask: async () => {},
|
|
35
35
|
searchProcessInstances: async () => [],
|
|
36
|
+
searchElementInstances: async () => [],
|
|
37
|
+
searchElementInstanceWaitStates: async () => [],
|
|
38
|
+
getElementInstance: async () => null,
|
|
39
|
+
searchIncidents: async () => [],
|
|
40
|
+
resolveIncident: async () => {},
|
|
41
|
+
updateJobRetries: async () => {},
|
|
42
|
+
setVariables: async () => {},
|
|
36
43
|
registerWorker: async (jobType) => ({ jobType, unsubscribe: async () => {} }),
|
|
37
44
|
close: async () => {},
|
|
38
45
|
};
|
|
@@ -18,6 +18,13 @@ const fakeEngine: EngineClient = {
|
|
|
18
18
|
getForm: async () => null,
|
|
19
19
|
completeUserTask: async () => {},
|
|
20
20
|
searchProcessInstances: async () => [],
|
|
21
|
+
searchElementInstances: async () => [],
|
|
22
|
+
searchElementInstanceWaitStates: async () => [],
|
|
23
|
+
getElementInstance: async () => null,
|
|
24
|
+
searchIncidents: async () => [],
|
|
25
|
+
resolveIncident: async () => {},
|
|
26
|
+
updateJobRetries: async () => {},
|
|
27
|
+
setVariables: async () => {},
|
|
21
28
|
registerWorker: async (jobType) => ({ jobType, unsubscribe: async () => {} }),
|
|
22
29
|
close: async () => {},
|
|
23
30
|
};
|
|
@@ -31,6 +31,13 @@ const engine: EngineClient = {
|
|
|
31
31
|
openUserTasks: async () => [],
|
|
32
32
|
getForm: async () => null,
|
|
33
33
|
searchProcessInstances: async () => [],
|
|
34
|
+
searchElementInstances: async () => [],
|
|
35
|
+
searchElementInstanceWaitStates: async () => [],
|
|
36
|
+
getElementInstance: async () => null,
|
|
37
|
+
searchIncidents: async () => [],
|
|
38
|
+
resolveIncident: async () => {},
|
|
39
|
+
updateJobRetries: async () => {},
|
|
40
|
+
setVariables: async () => {},
|
|
34
41
|
completeUserTask: async () => {},
|
|
35
42
|
registerWorker: async (jobType) => ({ jobType, unsubscribe: async () => {} }),
|
|
36
43
|
close: async () => {},
|
|
@@ -85,6 +85,13 @@ function cancelEngine(opts: {
|
|
|
85
85
|
openUserTasks: notUsed("openUserTasks"),
|
|
86
86
|
getForm: notUsed("getForm"),
|
|
87
87
|
completeUserTask: notUsed("completeUserTask"),
|
|
88
|
+
searchElementInstances: notUsed("searchElementInstances"),
|
|
89
|
+
searchElementInstanceWaitStates: notUsed("searchElementInstanceWaitStates"),
|
|
90
|
+
getElementInstance: notUsed("getElementInstance"),
|
|
91
|
+
searchIncidents: notUsed("searchIncidents"),
|
|
92
|
+
resolveIncident: notUsed("resolveIncident"),
|
|
93
|
+
updateJobRetries: notUsed("updateJobRetries"),
|
|
94
|
+
setVariables: notUsed("setVariables"),
|
|
88
95
|
registerWorker: notUsed("registerWorker"),
|
|
89
96
|
close: notUsed("close"),
|
|
90
97
|
};
|
|
@@ -21,6 +21,13 @@ class MiniEngine implements EngineClient {
|
|
|
21
21
|
async getForm(): Promise<null> { return null; }
|
|
22
22
|
async completeUserTask(): Promise<void> {}
|
|
23
23
|
async searchProcessInstances(): Promise<[]> { return []; }
|
|
24
|
+
async searchElementInstances(): Promise<[]> { return []; }
|
|
25
|
+
async searchElementInstanceWaitStates(): Promise<[]> { return []; }
|
|
26
|
+
async getElementInstance(): Promise<null> { return null; }
|
|
27
|
+
async searchIncidents(): Promise<never[]> { return []; }
|
|
28
|
+
async resolveIncident(): Promise<void> {}
|
|
29
|
+
async updateJobRetries(): Promise<void> {}
|
|
30
|
+
async setVariables(): Promise<void> {}
|
|
24
31
|
async registerWorker(jobType: string, handler: JobHandler): Promise<WorkerSubscription> {
|
|
25
32
|
this.workers.set(jobType, handler);
|
|
26
33
|
return { jobType, unsubscribe: async () => void this.workers.delete(jobType) };
|
|
@@ -20,6 +20,13 @@ const noEngine: EngineClient = {
|
|
|
20
20
|
getForm: async () => null,
|
|
21
21
|
completeUserTask: async () => {},
|
|
22
22
|
searchProcessInstances: async () => [],
|
|
23
|
+
searchElementInstances: async () => [],
|
|
24
|
+
searchElementInstanceWaitStates: async () => [],
|
|
25
|
+
getElementInstance: async () => null,
|
|
26
|
+
searchIncidents: async () => [],
|
|
27
|
+
resolveIncident: async () => {},
|
|
28
|
+
updateJobRetries: async () => {},
|
|
29
|
+
setVariables: async () => {},
|
|
23
30
|
registerWorker: async (jobType) => ({ jobType, unsubscribe: async () => {} }),
|
|
24
31
|
close: async () => {},
|
|
25
32
|
};
|
|
@@ -90,6 +90,13 @@ function fakeEngine(
|
|
|
90
90
|
searchUserTasks: notUsed("searchUserTasks"),
|
|
91
91
|
getForm: notUsed("getForm"),
|
|
92
92
|
completeUserTask: notUsed("completeUserTask"),
|
|
93
|
+
searchElementInstances: notUsed("searchElementInstances"),
|
|
94
|
+
searchElementInstanceWaitStates: notUsed("searchElementInstanceWaitStates"),
|
|
95
|
+
getElementInstance: notUsed("getElementInstance"),
|
|
96
|
+
searchIncidents: notUsed("searchIncidents"),
|
|
97
|
+
resolveIncident: notUsed("resolveIncident"),
|
|
98
|
+
updateJobRetries: notUsed("updateJobRetries"),
|
|
99
|
+
setVariables: notUsed("setVariables"),
|
|
93
100
|
registerWorker: notUsed("registerWorker"),
|
|
94
101
|
close: notUsed("close"),
|
|
95
102
|
};
|
|
@@ -48,6 +48,13 @@ function fakeEngine(): { engine: EngineClient; calls: FakeEngineCalls } {
|
|
|
48
48
|
async getForm() { return null; },
|
|
49
49
|
async completeUserTask() {},
|
|
50
50
|
async searchProcessInstances() { return []; },
|
|
51
|
+
async searchElementInstances() { return []; },
|
|
52
|
+
async searchElementInstanceWaitStates() { return []; },
|
|
53
|
+
async getElementInstance() { return null; },
|
|
54
|
+
async searchIncidents() { return []; },
|
|
55
|
+
async resolveIncident() {},
|
|
56
|
+
async updateJobRetries() {},
|
|
57
|
+
async setVariables() {},
|
|
51
58
|
async registerWorker(jobType) { return { jobType, unsubscribe: async () => {} }; },
|
|
52
59
|
async close() {},
|
|
53
60
|
};
|
|
@@ -34,6 +34,13 @@ function ctxWith(surfaces: Record<string, unknown>): Parameters<typeof mountSurf
|
|
|
34
34
|
getForm: async () => null,
|
|
35
35
|
completeUserTask: async () => {},
|
|
36
36
|
searchProcessInstances: async () => [],
|
|
37
|
+
searchElementInstances: async () => [],
|
|
38
|
+
searchElementInstanceWaitStates: async () => [],
|
|
39
|
+
getElementInstance: async () => null,
|
|
40
|
+
searchIncidents: async () => [],
|
|
41
|
+
resolveIncident: async () => {},
|
|
42
|
+
updateJobRetries: async () => {},
|
|
43
|
+
setVariables: async () => {},
|
|
37
44
|
registerWorker: async (jobType) => ({ jobType, unsubscribe: async () => {} }),
|
|
38
45
|
close: async () => {},
|
|
39
46
|
};
|
|
@@ -50,6 +57,13 @@ const fakeEngine: EngineClient = {
|
|
|
50
57
|
getForm: async () => null,
|
|
51
58
|
completeUserTask: async () => {},
|
|
52
59
|
searchProcessInstances: async () => [],
|
|
60
|
+
searchElementInstances: async () => [],
|
|
61
|
+
searchElementInstanceWaitStates: async () => [],
|
|
62
|
+
getElementInstance: async () => null,
|
|
63
|
+
searchIncidents: async () => [],
|
|
64
|
+
resolveIncident: async () => {},
|
|
65
|
+
updateJobRetries: async () => {},
|
|
66
|
+
setVariables: async () => {},
|
|
53
67
|
registerWorker: async (jobType) => ({ jobType, unsubscribe: async () => {} }),
|
|
54
68
|
close: async () => {},
|
|
55
69
|
};
|
|
@@ -35,6 +35,13 @@ class MiniEngine implements EngineClient {
|
|
|
35
35
|
async getForm(): Promise<null> { return null; }
|
|
36
36
|
async completeUserTask(): Promise<void> {}
|
|
37
37
|
async searchProcessInstances(): Promise<[]> { return []; }
|
|
38
|
+
async searchElementInstances(): Promise<[]> { return []; }
|
|
39
|
+
async searchElementInstanceWaitStates(): Promise<[]> { return []; }
|
|
40
|
+
async getElementInstance(): Promise<null> { return null; }
|
|
41
|
+
async searchIncidents(): Promise<never[]> { return []; }
|
|
42
|
+
async resolveIncident(): Promise<void> {}
|
|
43
|
+
async updateJobRetries(): Promise<void> {}
|
|
44
|
+
async setVariables(): Promise<void> {}
|
|
38
45
|
async registerWorker(jobType: string): Promise<{ jobType: string; unsubscribe(): Promise<void> }> {
|
|
39
46
|
return { jobType, unsubscribe: async () => {} };
|
|
40
47
|
}
|
|
@@ -32,6 +32,13 @@ class MiniEngine implements EngineClient {
|
|
|
32
32
|
async getForm(): Promise<null> { return null; }
|
|
33
33
|
async completeUserTask(): Promise<void> {}
|
|
34
34
|
async searchProcessInstances(): Promise<[]> { return []; }
|
|
35
|
+
async searchElementInstances(): Promise<[]> { return []; }
|
|
36
|
+
async searchElementInstanceWaitStates(): Promise<[]> { return []; }
|
|
37
|
+
async getElementInstance(): Promise<null> { return null; }
|
|
38
|
+
async searchIncidents(): Promise<never[]> { return []; }
|
|
39
|
+
async resolveIncident(): Promise<void> {}
|
|
40
|
+
async updateJobRetries(): Promise<void> {}
|
|
41
|
+
async setVariables(): Promise<void> {}
|
|
35
42
|
async close(): Promise<void> {}
|
|
36
43
|
async registerWorker(jobType: string, handler: JobHandler): Promise<WorkerSubscription> {
|
|
37
44
|
this.workers.set(jobType, handler);
|
|
@@ -32,6 +32,21 @@ class NoopEngine implements EngineClient {
|
|
|
32
32
|
async searchProcessInstances() {
|
|
33
33
|
return [];
|
|
34
34
|
}
|
|
35
|
+
async searchElementInstances() {
|
|
36
|
+
return [];
|
|
37
|
+
}
|
|
38
|
+
async searchElementInstanceWaitStates() {
|
|
39
|
+
return [];
|
|
40
|
+
}
|
|
41
|
+
async getElementInstance() {
|
|
42
|
+
return null;
|
|
43
|
+
}
|
|
44
|
+
async searchIncidents() {
|
|
45
|
+
return [];
|
|
46
|
+
}
|
|
47
|
+
async resolveIncident() {}
|
|
48
|
+
async updateJobRetries() {}
|
|
49
|
+
async setVariables() {}
|
|
35
50
|
async registerWorker(jobType: string): Promise<WorkerSubscription> {
|
|
36
51
|
return { jobType, unsubscribe: async () => {} };
|
|
37
52
|
}
|
|
@@ -53,6 +53,21 @@ class FakeEngine implements EngineClient {
|
|
|
53
53
|
async searchProcessInstances() {
|
|
54
54
|
return [];
|
|
55
55
|
}
|
|
56
|
+
async searchElementInstances() {
|
|
57
|
+
return [];
|
|
58
|
+
}
|
|
59
|
+
async searchElementInstanceWaitStates() {
|
|
60
|
+
return [];
|
|
61
|
+
}
|
|
62
|
+
async getElementInstance() {
|
|
63
|
+
return null;
|
|
64
|
+
}
|
|
65
|
+
async searchIncidents() {
|
|
66
|
+
return [];
|
|
67
|
+
}
|
|
68
|
+
async resolveIncident() {}
|
|
69
|
+
async updateJobRetries() {}
|
|
70
|
+
async setVariables() {}
|
|
56
71
|
async registerWorker(jobType: string, handler: JobHandler): Promise<WorkerSubscription> {
|
|
57
72
|
this.workers.set(jobType, handler);
|
|
58
73
|
return { jobType, unsubscribe: async () => void this.workers.delete(jobType) };
|
|
@@ -3,6 +3,8 @@ import assert from "node:assert/strict";
|
|
|
3
3
|
import {
|
|
4
4
|
createNanoSdkEngineClient,
|
|
5
5
|
normalizeProcessInstanceState,
|
|
6
|
+
normalizeElementInstanceState,
|
|
7
|
+
normalizeWaitStateType,
|
|
6
8
|
requireProcessInstanceKey,
|
|
7
9
|
SdkEngineClient,
|
|
8
10
|
type NanoSdkActivatedJob,
|
|
@@ -74,6 +76,34 @@ function fakeSdkClient(overrides: Partial<NanoSdkClient> = {}): NanoSdkClient &
|
|
|
74
76
|
calls.push("searchProcessInstances");
|
|
75
77
|
return { items: [] };
|
|
76
78
|
},
|
|
79
|
+
async searchElementInstances() {
|
|
80
|
+
calls.push("searchElementInstances");
|
|
81
|
+
return { items: [] };
|
|
82
|
+
},
|
|
83
|
+
async searchElementInstanceWaitStates() {
|
|
84
|
+
calls.push("searchElementInstanceWaitStates");
|
|
85
|
+
return { items: [] };
|
|
86
|
+
},
|
|
87
|
+
async getElementInstance() {
|
|
88
|
+
calls.push("getElementInstance");
|
|
89
|
+
return {};
|
|
90
|
+
},
|
|
91
|
+
async searchIncidents() {
|
|
92
|
+
calls.push("searchIncidents");
|
|
93
|
+
return { items: [] };
|
|
94
|
+
},
|
|
95
|
+
async resolveIncident(input) {
|
|
96
|
+
calls.push("resolveIncident");
|
|
97
|
+
return { ...input };
|
|
98
|
+
},
|
|
99
|
+
async updateJob(input) {
|
|
100
|
+
calls.push("updateJob");
|
|
101
|
+
return { ...input };
|
|
102
|
+
},
|
|
103
|
+
async createElementInstanceVariables(input) {
|
|
104
|
+
calls.push("createElementInstanceVariables");
|
|
105
|
+
return { ...input };
|
|
106
|
+
},
|
|
77
107
|
async completeUserTask() {
|
|
78
108
|
calls.push("completeUserTask");
|
|
79
109
|
return {};
|
|
@@ -448,6 +478,360 @@ test("normalizeProcessInstanceState maps the terminal set and rejects others", (
|
|
|
448
478
|
assert.equal(normalizeProcessInstanceState(undefined), undefined);
|
|
449
479
|
});
|
|
450
480
|
|
|
481
|
+
test("normalizeElementInstanceState maps the recognized states (case-insensitive) and rejects others", () => {
|
|
482
|
+
assert.equal(normalizeElementInstanceState("ACTIVE"), "ACTIVE");
|
|
483
|
+
assert.equal(normalizeElementInstanceState("completed"), "COMPLETED");
|
|
484
|
+
assert.equal(normalizeElementInstanceState("Terminated"), "TERMINATED");
|
|
485
|
+
assert.equal(normalizeElementInstanceState("SKIPPED"), undefined);
|
|
486
|
+
assert.equal(normalizeElementInstanceState(7), undefined);
|
|
487
|
+
assert.equal(normalizeElementInstanceState(undefined), undefined);
|
|
488
|
+
});
|
|
489
|
+
|
|
490
|
+
test("normalizeWaitStateType maps the wait-state kinds (case-insensitive) and rejects others", () => {
|
|
491
|
+
assert.equal(normalizeWaitStateType("JOB"), "JOB");
|
|
492
|
+
assert.equal(normalizeWaitStateType("message"), "MESSAGE");
|
|
493
|
+
assert.equal(normalizeWaitStateType("User_Task"), "USER_TASK");
|
|
494
|
+
assert.equal(normalizeWaitStateType("TIMER"), "TIMER");
|
|
495
|
+
assert.equal(normalizeWaitStateType("signal"), "SIGNAL");
|
|
496
|
+
assert.equal(normalizeWaitStateType("CONDITION"), "CONDITION");
|
|
497
|
+
assert.equal(normalizeWaitStateType("ESCALATION"), undefined);
|
|
498
|
+
assert.equal(normalizeWaitStateType(3), undefined);
|
|
499
|
+
});
|
|
500
|
+
|
|
501
|
+
test("searchElementInstances passes zero-wait consistency, maps rows, and forwards selectors", async () => {
|
|
502
|
+
let seenInput: unknown;
|
|
503
|
+
let seenConsistency: unknown;
|
|
504
|
+
const client = fakeSdkClient({
|
|
505
|
+
searchElementInstances: async (input, c) => {
|
|
506
|
+
seenInput = input;
|
|
507
|
+
seenConsistency = c;
|
|
508
|
+
return {
|
|
509
|
+
items: [
|
|
510
|
+
{
|
|
511
|
+
elementInstanceKey: 5,
|
|
512
|
+
processInstanceKey: 3,
|
|
513
|
+
elementId: "task",
|
|
514
|
+
type: "SERVICE_TASK",
|
|
515
|
+
state: "active",
|
|
516
|
+
},
|
|
517
|
+
// elementType may arrive as `elementType` on a plain REST body.
|
|
518
|
+
{
|
|
519
|
+
elementInstanceKey: "6",
|
|
520
|
+
processInstanceKey: "3",
|
|
521
|
+
elementId: "gw",
|
|
522
|
+
elementType: "PARALLEL_GATEWAY",
|
|
523
|
+
state: "COMPLETED",
|
|
524
|
+
},
|
|
525
|
+
],
|
|
526
|
+
};
|
|
527
|
+
},
|
|
528
|
+
});
|
|
529
|
+
const engine = new SdkEngineClient(client);
|
|
530
|
+
const out = await engine.searchElementInstances({
|
|
531
|
+
processInstanceKey: "3",
|
|
532
|
+
elementId: "task",
|
|
533
|
+
state: "ACTIVE",
|
|
534
|
+
});
|
|
535
|
+
assert.deepEqual(seenInput, {
|
|
536
|
+
filter: { processInstanceKey: "3", elementId: "task", state: "ACTIVE" },
|
|
537
|
+
});
|
|
538
|
+
assert.deepEqual(seenConsistency, { consistency: { waitUpToMs: 0 } });
|
|
539
|
+
assert.deepEqual(out, [
|
|
540
|
+
{
|
|
541
|
+
elementInstanceKey: "5",
|
|
542
|
+
processInstanceKey: "3",
|
|
543
|
+
elementId: "task",
|
|
544
|
+
elementType: "SERVICE_TASK",
|
|
545
|
+
state: "ACTIVE",
|
|
546
|
+
},
|
|
547
|
+
{
|
|
548
|
+
elementInstanceKey: "6",
|
|
549
|
+
processInstanceKey: "3",
|
|
550
|
+
elementId: "gw",
|
|
551
|
+
elementType: "PARALLEL_GATEWAY",
|
|
552
|
+
state: "COMPLETED",
|
|
553
|
+
},
|
|
554
|
+
]);
|
|
555
|
+
});
|
|
556
|
+
|
|
557
|
+
test("searchElementInstances with no filter sends an empty filter", async () => {
|
|
558
|
+
let seenInput: unknown;
|
|
559
|
+
const client = fakeSdkClient({
|
|
560
|
+
searchElementInstances: async (input) => {
|
|
561
|
+
seenInput = input;
|
|
562
|
+
return { items: [] };
|
|
563
|
+
},
|
|
564
|
+
});
|
|
565
|
+
const engine = new SdkEngineClient(client);
|
|
566
|
+
await engine.searchElementInstances();
|
|
567
|
+
assert.deepEqual(seenInput, { filter: {} });
|
|
568
|
+
});
|
|
569
|
+
|
|
570
|
+
test("searchElementInstances skips malformed rows (missing key/elementId/state)", async () => {
|
|
571
|
+
const client = fakeSdkClient({
|
|
572
|
+
searchElementInstances: async () => ({
|
|
573
|
+
items: [
|
|
574
|
+
{ elementInstanceKey: "", processInstanceKey: "3", elementId: "a", state: "ACTIVE" }, // keyless
|
|
575
|
+
{ elementInstanceKey: " ", processInstanceKey: "3", elementId: "a", state: "ACTIVE" }, // whitespace key
|
|
576
|
+
{ elementInstanceKey: "5", processInstanceKey: "", elementId: "a", state: "ACTIVE" }, // no PI key
|
|
577
|
+
{ elementInstanceKey: "6", processInstanceKey: "3", elementId: "", state: "ACTIVE" }, // no elementId
|
|
578
|
+
{ elementInstanceKey: "9", processInstanceKey: "3", elementId: " ", state: "ACTIVE" }, // whitespace elementId
|
|
579
|
+
{ elementInstanceKey: "7", processInstanceKey: "3", elementId: "a", state: "SKIPPED" }, // bad state
|
|
580
|
+
{ elementInstanceKey: "8", processInstanceKey: "3", elementId: "ok", state: "ACTIVE" }, // kept
|
|
581
|
+
],
|
|
582
|
+
}),
|
|
583
|
+
});
|
|
584
|
+
const engine = new SdkEngineClient(client);
|
|
585
|
+
const out = await engine.searchElementInstances();
|
|
586
|
+
assert.deepEqual(out, [
|
|
587
|
+
{ elementInstanceKey: "8", processInstanceKey: "3", elementId: "ok", state: "ACTIVE" },
|
|
588
|
+
]);
|
|
589
|
+
});
|
|
590
|
+
|
|
591
|
+
test("searchElementInstanceWaitStates maps JOB/MESSAGE parks and unwraps SDK `details`", async () => {
|
|
592
|
+
let seenInput: unknown;
|
|
593
|
+
let seenConsistency: unknown;
|
|
594
|
+
const client = fakeSdkClient({
|
|
595
|
+
searchElementInstanceWaitStates: async (input, c) => {
|
|
596
|
+
seenInput = input;
|
|
597
|
+
seenConsistency = c;
|
|
598
|
+
return {
|
|
599
|
+
items: [
|
|
600
|
+
{
|
|
601
|
+
elementInstanceKey: 5,
|
|
602
|
+
processInstanceKey: 3,
|
|
603
|
+
elementId: "svc",
|
|
604
|
+
type: "SERVICE_TASK",
|
|
605
|
+
details: { waitStateType: "JOB", jobType: "work", jobKey: 6 },
|
|
606
|
+
},
|
|
607
|
+
{
|
|
608
|
+
elementInstanceKey: 8,
|
|
609
|
+
processInstanceKey: 3,
|
|
610
|
+
elementId: "catch",
|
|
611
|
+
details: { waitStateType: "MESSAGE", messageName: "Go", correlationKey: "K1" },
|
|
612
|
+
},
|
|
613
|
+
],
|
|
614
|
+
};
|
|
615
|
+
},
|
|
616
|
+
});
|
|
617
|
+
const engine = new SdkEngineClient(client);
|
|
618
|
+
const out = await engine.searchElementInstanceWaitStates({ processInstanceKey: "3" });
|
|
619
|
+
assert.deepEqual(seenInput, { filter: { processInstanceKey: "3" } });
|
|
620
|
+
assert.deepEqual(seenConsistency, { consistency: { waitUpToMs: 0 } });
|
|
621
|
+
assert.deepEqual(out, [
|
|
622
|
+
{
|
|
623
|
+
elementInstanceKey: "5",
|
|
624
|
+
processInstanceKey: "3",
|
|
625
|
+
elementId: "svc",
|
|
626
|
+
elementType: "SERVICE_TASK",
|
|
627
|
+
waitStateType: "JOB",
|
|
628
|
+
jobType: "work",
|
|
629
|
+
jobKey: "6",
|
|
630
|
+
},
|
|
631
|
+
{
|
|
632
|
+
elementInstanceKey: "8",
|
|
633
|
+
processInstanceKey: "3",
|
|
634
|
+
elementId: "catch",
|
|
635
|
+
waitStateType: "MESSAGE",
|
|
636
|
+
messageName: "Go",
|
|
637
|
+
correlationKey: "K1",
|
|
638
|
+
},
|
|
639
|
+
]);
|
|
640
|
+
});
|
|
641
|
+
|
|
642
|
+
test("searchElementInstanceWaitStates forwards a waitStateType selector and reads inlined details", async () => {
|
|
643
|
+
let seenInput: unknown;
|
|
644
|
+
const client = fakeSdkClient({
|
|
645
|
+
searchElementInstanceWaitStates: async (input) => {
|
|
646
|
+
seenInput = input;
|
|
647
|
+
// A plain REST body may inline the park fields rather than nesting them under `details`.
|
|
648
|
+
return {
|
|
649
|
+
items: [
|
|
650
|
+
{
|
|
651
|
+
elementInstanceKey: "9",
|
|
652
|
+
processInstanceKey: "3",
|
|
653
|
+
elementId: "ut",
|
|
654
|
+
waitStateType: "USER_TASK",
|
|
655
|
+
taskKey: "42",
|
|
656
|
+
},
|
|
657
|
+
{
|
|
658
|
+
elementInstanceKey: "11",
|
|
659
|
+
processInstanceKey: "3",
|
|
660
|
+
elementId: "ut2",
|
|
661
|
+
waitStateType: "USER_TASK",
|
|
662
|
+
}, // USER_TASK with no taskKey/userTaskKey → dropped (invalid identity)
|
|
663
|
+
{ elementInstanceKey: "10", processInstanceKey: "3", elementId: "?" }, // no waitStateType → dropped
|
|
664
|
+
],
|
|
665
|
+
};
|
|
666
|
+
},
|
|
667
|
+
});
|
|
668
|
+
const engine = new SdkEngineClient(client);
|
|
669
|
+
const out = await engine.searchElementInstanceWaitStates({ waitStateType: "USER_TASK" });
|
|
670
|
+
assert.deepEqual(seenInput, { filter: { waitStateType: "USER_TASK" } });
|
|
671
|
+
assert.deepEqual(out, [
|
|
672
|
+
{
|
|
673
|
+
elementInstanceKey: "9",
|
|
674
|
+
processInstanceKey: "3",
|
|
675
|
+
elementId: "ut",
|
|
676
|
+
waitStateType: "USER_TASK",
|
|
677
|
+
userTaskKey: "42",
|
|
678
|
+
},
|
|
679
|
+
]);
|
|
680
|
+
});
|
|
681
|
+
|
|
682
|
+
test("searchElementInstanceWaitStates drops parks missing their required discriminator field", async () => {
|
|
683
|
+
const client = fakeSdkClient({
|
|
684
|
+
searchElementInstanceWaitStates: async () => ({
|
|
685
|
+
items: [
|
|
686
|
+
{ elementInstanceKey: "5", processInstanceKey: "3", elementId: "j", waitStateType: "JOB" }, // no jobType
|
|
687
|
+
{ elementInstanceKey: "6", processInstanceKey: "3", elementId: "m", waitStateType: "MESSAGE" }, // no messageName
|
|
688
|
+
{ elementInstanceKey: "7", processInstanceKey: "3", elementId: "s", waitStateType: "SIGNAL" }, // no signalName
|
|
689
|
+
{
|
|
690
|
+
elementInstanceKey: "8",
|
|
691
|
+
processInstanceKey: "3",
|
|
692
|
+
elementId: "j2",
|
|
693
|
+
waitStateType: "JOB",
|
|
694
|
+
jobType: " ",
|
|
695
|
+
}, // blank jobType → dropped
|
|
696
|
+
{
|
|
697
|
+
elementInstanceKey: "9",
|
|
698
|
+
processInstanceKey: "3",
|
|
699
|
+
elementId: "t",
|
|
700
|
+
waitStateType: "TIMER",
|
|
701
|
+
}, // TIMER has no required field → kept
|
|
702
|
+
],
|
|
703
|
+
}),
|
|
704
|
+
});
|
|
705
|
+
const engine = new SdkEngineClient(client);
|
|
706
|
+
const out = await engine.searchElementInstanceWaitStates();
|
|
707
|
+
assert.deepEqual(out, [
|
|
708
|
+
{ elementInstanceKey: "9", processInstanceKey: "3", elementId: "t", waitStateType: "TIMER" },
|
|
709
|
+
]);
|
|
710
|
+
});
|
|
711
|
+
|
|
712
|
+
test("getElementInstance returns a mapped summary, and null for a blank key or a 404", async () => {
|
|
713
|
+
const client = fakeSdkClient({
|
|
714
|
+
getElementInstance: async (input) => {
|
|
715
|
+
if (input.elementInstanceKey === "5") {
|
|
716
|
+
return {
|
|
717
|
+
elementInstanceKey: 5,
|
|
718
|
+
processInstanceKey: 3,
|
|
719
|
+
elementId: "task",
|
|
720
|
+
type: "SERVICE_TASK",
|
|
721
|
+
state: "ACTIVE",
|
|
722
|
+
};
|
|
723
|
+
}
|
|
724
|
+
throw new Error("404 not found");
|
|
725
|
+
},
|
|
726
|
+
});
|
|
727
|
+
const engine = new SdkEngineClient(client);
|
|
728
|
+
assert.deepEqual(await engine.getElementInstance("5"), {
|
|
729
|
+
elementInstanceKey: "5",
|
|
730
|
+
processInstanceKey: "3",
|
|
731
|
+
elementId: "task",
|
|
732
|
+
elementType: "SERVICE_TASK",
|
|
733
|
+
state: "ACTIVE",
|
|
734
|
+
});
|
|
735
|
+
// A padded-but-valid key is normalized before the fetch, so it addresses the same instance.
|
|
736
|
+
assert.deepEqual(await engine.getElementInstance(" 5 "), {
|
|
737
|
+
elementInstanceKey: "5",
|
|
738
|
+
processInstanceKey: "3",
|
|
739
|
+
elementId: "task",
|
|
740
|
+
elementType: "SERVICE_TASK",
|
|
741
|
+
state: "ACTIVE",
|
|
742
|
+
});
|
|
743
|
+
// A blank key short-circuits without hitting the engine; a 404 is treated as absence.
|
|
744
|
+
assert.equal(await engine.getElementInstance(" "), null);
|
|
745
|
+
assert.equal(await engine.getElementInstance("missing"), null);
|
|
746
|
+
});
|
|
747
|
+
|
|
748
|
+
test("searchIncidents normalizes the processInstanceKey selector (trims, drops blank)", async () => {
|
|
749
|
+
let seenInput: { filter?: Record<string, unknown> } | undefined;
|
|
750
|
+
const client = fakeSdkClient({
|
|
751
|
+
async searchIncidents(input) {
|
|
752
|
+
seenInput = input;
|
|
753
|
+
return { items: [] };
|
|
754
|
+
},
|
|
755
|
+
});
|
|
756
|
+
const engine = new SdkEngineClient(client);
|
|
757
|
+
|
|
758
|
+
// A padded key is trimmed before it reaches the engine filter.
|
|
759
|
+
await engine.searchIncidents({ processInstanceKey: " 7 " });
|
|
760
|
+
assert.deepEqual(seenInput?.filter, { processInstanceKey: "7" });
|
|
761
|
+
|
|
762
|
+
// A whitespace-only key is treated as absent, not forwarded as a garbage filter segment.
|
|
763
|
+
await engine.searchIncidents({ processInstanceKey: " " });
|
|
764
|
+
assert.deepEqual(seenInput?.filter, {});
|
|
765
|
+
|
|
766
|
+
// The state selector still passes through untouched.
|
|
767
|
+
await engine.searchIncidents({ state: "ACTIVE" });
|
|
768
|
+
assert.deepEqual(seenInput?.filter, { state: "ACTIVE" });
|
|
769
|
+
});
|
|
770
|
+
|
|
771
|
+
test("resolveIncident normalizes the incidentKey and rejects a blank one", async () => {
|
|
772
|
+
let seen: { incidentKey: string } | undefined;
|
|
773
|
+
const client = fakeSdkClient({
|
|
774
|
+
async resolveIncident(input) {
|
|
775
|
+
seen = input;
|
|
776
|
+
return { ...input };
|
|
777
|
+
},
|
|
778
|
+
});
|
|
779
|
+
const engine = new SdkEngineClient(client);
|
|
780
|
+
|
|
781
|
+
await engine.resolveIncident({ incidentKey: " inc-1 " });
|
|
782
|
+
assert.deepEqual(seen, { incidentKey: "inc-1" });
|
|
783
|
+
|
|
784
|
+
await assert.rejects(
|
|
785
|
+
engine.resolveIncident({ incidentKey: " " }),
|
|
786
|
+
/incidentKey must be a non-empty key/,
|
|
787
|
+
);
|
|
788
|
+
});
|
|
789
|
+
|
|
790
|
+
test("setVariables normalizes the scopeKey and rejects a blank one", async () => {
|
|
791
|
+
let seen: Record<string, unknown> | undefined;
|
|
792
|
+
const client = fakeSdkClient({
|
|
793
|
+
async createElementInstanceVariables(input) {
|
|
794
|
+
seen = input;
|
|
795
|
+
return { ...input };
|
|
796
|
+
},
|
|
797
|
+
});
|
|
798
|
+
const engine = new SdkEngineClient(client);
|
|
799
|
+
|
|
800
|
+
await engine.setVariables({ scopeKey: " ei-9 ", variables: { fixed: true } });
|
|
801
|
+
assert.deepEqual(seen, { elementInstanceKey: "ei-9", variables: { fixed: true } });
|
|
802
|
+
|
|
803
|
+
await assert.rejects(
|
|
804
|
+
engine.setVariables({ scopeKey: " ", variables: { fixed: true } }),
|
|
805
|
+
/scopeKey must be a non-empty key/,
|
|
806
|
+
);
|
|
807
|
+
});
|
|
808
|
+
|
|
809
|
+
test("updateJobRetries normalizes the jobKey and rejects a blank key or an invalid retry count", async () => {
|
|
810
|
+
let seen: { jobKey: string; changeset: Record<string, unknown> } | undefined;
|
|
811
|
+
const client = fakeSdkClient({
|
|
812
|
+
async updateJob(input) {
|
|
813
|
+
seen = input;
|
|
814
|
+
return { ...input };
|
|
815
|
+
},
|
|
816
|
+
});
|
|
817
|
+
const engine = new SdkEngineClient(client);
|
|
818
|
+
|
|
819
|
+
await engine.updateJobRetries({ jobKey: " j-1 ", retries: 3 });
|
|
820
|
+
assert.deepEqual(seen, { jobKey: "j-1", changeset: { retries: 3 } });
|
|
821
|
+
|
|
822
|
+
await assert.rejects(
|
|
823
|
+
engine.updateJobRetries({ jobKey: " ", retries: 3 }),
|
|
824
|
+
/jobKey must be a non-empty key/,
|
|
825
|
+
);
|
|
826
|
+
for (const retries of [-1, 1.5, Number.NaN]) {
|
|
827
|
+
await assert.rejects(
|
|
828
|
+
engine.updateJobRetries({ jobKey: "j-1", retries }),
|
|
829
|
+
/retries must be a non-negative integer/,
|
|
830
|
+
`retries=${retries} must be rejected`,
|
|
831
|
+
);
|
|
832
|
+
}
|
|
833
|
+
});
|
|
834
|
+
|
|
451
835
|
test("registerWorker creates a worker the SDK auto-starts, and dispatches + completes", async () => {
|
|
452
836
|
const client = fakeSdkClient();
|
|
453
837
|
const engine = new SdkEngineClient(client);
|