@nanobpm/urban 0.88.0 → 0.89.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 +105 -1
- package/dist/runtime/core/host.js +3 -0
- package/dist/runtime/core/modules/mcp.js +68 -0
- package/dist/runtime/core/modules/runtime.gen.js +1 -1
- package/dist/runtime/engine/nanosdk.d.ts +30 -1
- package/dist/runtime/engine/nanosdk.js +163 -1
- package/dist/runtime/index.d.ts +1 -1
- package/package.json +1 -1
- package/src/runtime/browser/runtime.browser.js +77 -1
- package/src/runtime/browser/runtime.browser.test.ts +126 -1
- package/src/runtime/core/host.ts +111 -0
- package/src/runtime/core/modules/actions.test.ts +3 -0
- package/src/runtime/core/modules/agent.test.ts +3 -0
- package/src/runtime/core/modules/api.test.ts +3 -0
- package/src/runtime/core/modules/cancel.test.ts +3 -0
- package/src/runtime/core/modules/connectors.test.ts +3 -0
- package/src/runtime/core/modules/datasource.migrations.test.ts +3 -0
- package/src/runtime/core/modules/instance-tracking.test.ts +3 -0
- package/src/runtime/core/modules/mcp.test.ts +97 -0
- package/src/runtime/core/modules/mcp.ts +66 -0
- package/src/runtime/core/modules/pages.test.ts +3 -0
- package/src/runtime/core/modules/runtime.gen.ts +1 -1
- package/src/runtime/core/modules/surfaces.test.ts +6 -0
- package/src/runtime/core/modules/triggers.test.ts +3 -0
- package/src/runtime/core/modules/workers.test.ts +3 -0
- package/src/runtime/core/runtime.extensions.test.ts +9 -0
- package/src/runtime/core/runtime.test.ts +9 -0
- package/src/runtime/engine/nanosdk.test.ts +12 -0
- package/src/runtime/engine/nanosdk.ts +194 -1
- package/src/runtime/index.ts +4 -0
|
@@ -38,6 +38,9 @@ function ctxWith(surfaces: Record<string, unknown>): Parameters<typeof mountSurf
|
|
|
38
38
|
searchElementInstanceWaitStates: async () => [],
|
|
39
39
|
getElementInstance: async () => null,
|
|
40
40
|
searchIncidents: async () => [],
|
|
41
|
+
searchVariables: async () => [],
|
|
42
|
+
searchJobs: async () => [],
|
|
43
|
+
getProcessDefinitionXml: async () => null,
|
|
41
44
|
resolveIncident: async () => {},
|
|
42
45
|
updateJobRetries: async () => {},
|
|
43
46
|
setVariables: async () => {},
|
|
@@ -61,6 +64,9 @@ const fakeEngine: EngineClient = {
|
|
|
61
64
|
searchElementInstanceWaitStates: async () => [],
|
|
62
65
|
getElementInstance: async () => null,
|
|
63
66
|
searchIncidents: async () => [],
|
|
67
|
+
searchVariables: async () => [],
|
|
68
|
+
searchJobs: async () => [],
|
|
69
|
+
getProcessDefinitionXml: async () => null,
|
|
64
70
|
resolveIncident: async () => {},
|
|
65
71
|
updateJobRetries: async () => {},
|
|
66
72
|
setVariables: async () => {},
|
|
@@ -39,6 +39,9 @@ class MiniEngine implements EngineClient {
|
|
|
39
39
|
async searchElementInstanceWaitStates(): Promise<[]> { return []; }
|
|
40
40
|
async getElementInstance(): Promise<null> { return null; }
|
|
41
41
|
async searchIncidents(): Promise<never[]> { return []; }
|
|
42
|
+
async searchVariables(): Promise<never[]> { return []; }
|
|
43
|
+
async searchJobs(): Promise<never[]> { return []; }
|
|
44
|
+
async getProcessDefinitionXml(): Promise<null> { return null; }
|
|
42
45
|
async resolveIncident(): Promise<void> {}
|
|
43
46
|
async updateJobRetries(): Promise<void> {}
|
|
44
47
|
async setVariables(): Promise<void> {}
|
|
@@ -36,6 +36,9 @@ class MiniEngine implements EngineClient {
|
|
|
36
36
|
async searchElementInstanceWaitStates(): Promise<[]> { return []; }
|
|
37
37
|
async getElementInstance(): Promise<null> { return null; }
|
|
38
38
|
async searchIncidents(): Promise<never[]> { return []; }
|
|
39
|
+
async searchVariables(): Promise<never[]> { return []; }
|
|
40
|
+
async searchJobs(): Promise<never[]> { return []; }
|
|
41
|
+
async getProcessDefinitionXml(): Promise<null> { return null; }
|
|
39
42
|
async resolveIncident(): Promise<void> {}
|
|
40
43
|
async updateJobRetries(): Promise<void> {}
|
|
41
44
|
async setVariables(): Promise<void> {}
|
|
@@ -44,6 +44,15 @@ class NoopEngine implements EngineClient {
|
|
|
44
44
|
async searchIncidents() {
|
|
45
45
|
return [];
|
|
46
46
|
}
|
|
47
|
+
async searchVariables() {
|
|
48
|
+
return [];
|
|
49
|
+
}
|
|
50
|
+
async searchJobs() {
|
|
51
|
+
return [];
|
|
52
|
+
}
|
|
53
|
+
async getProcessDefinitionXml() {
|
|
54
|
+
return null;
|
|
55
|
+
}
|
|
47
56
|
async resolveIncident() {}
|
|
48
57
|
async updateJobRetries() {}
|
|
49
58
|
async setVariables() {}
|
|
@@ -65,6 +65,15 @@ class FakeEngine implements EngineClient {
|
|
|
65
65
|
async searchIncidents() {
|
|
66
66
|
return [];
|
|
67
67
|
}
|
|
68
|
+
async searchVariables() {
|
|
69
|
+
return [];
|
|
70
|
+
}
|
|
71
|
+
async searchJobs() {
|
|
72
|
+
return [];
|
|
73
|
+
}
|
|
74
|
+
async getProcessDefinitionXml() {
|
|
75
|
+
return null;
|
|
76
|
+
}
|
|
68
77
|
async resolveIncident() {}
|
|
69
78
|
async updateJobRetries() {}
|
|
70
79
|
async setVariables() {}
|
|
@@ -97,6 +97,18 @@ function fakeSdkClient(overrides: Partial<NanoSdkClient> = {}): NanoSdkClient &
|
|
|
97
97
|
calls.push("searchIncidents");
|
|
98
98
|
return { items: [] };
|
|
99
99
|
},
|
|
100
|
+
async searchVariables() {
|
|
101
|
+
calls.push("searchVariables");
|
|
102
|
+
return { items: [] };
|
|
103
|
+
},
|
|
104
|
+
async searchJobs() {
|
|
105
|
+
calls.push("searchJobs");
|
|
106
|
+
return { items: [] };
|
|
107
|
+
},
|
|
108
|
+
async getProcessDefinitionXml() {
|
|
109
|
+
calls.push("getProcessDefinitionXml");
|
|
110
|
+
return "";
|
|
111
|
+
},
|
|
100
112
|
async resolveIncident(input) {
|
|
101
113
|
calls.push("resolveIncident");
|
|
102
114
|
return { ...input };
|
|
@@ -24,12 +24,16 @@ import type {
|
|
|
24
24
|
IncidentFilter,
|
|
25
25
|
IncidentState,
|
|
26
26
|
IncidentSummary,
|
|
27
|
+
JobFilter,
|
|
27
28
|
JobHandler,
|
|
29
|
+
JobSummary,
|
|
28
30
|
ProcessInstanceSnapshot,
|
|
29
31
|
ProcessInstanceState,
|
|
30
32
|
UserTaskState,
|
|
31
33
|
UserTaskSummary,
|
|
32
34
|
UserTaskFilter,
|
|
35
|
+
VariableFilter,
|
|
36
|
+
VariableSummary,
|
|
33
37
|
WorkerSubscription,
|
|
34
38
|
} from "../core/host.ts";
|
|
35
39
|
import { assertDeployedWaitStateType, isBpmnError } from "../core/host.ts";
|
|
@@ -360,6 +364,99 @@ export function mapIncidentRow(
|
|
|
360
364
|
};
|
|
361
365
|
}
|
|
362
366
|
|
|
367
|
+
/** Map one engine variable row onto a {@link VariableSummary}, or `undefined` when the row is
|
|
368
|
+
* malformed (missing the `variableKey`, owning `processInstanceKey`, or `name` — the identity a
|
|
369
|
+
* caller needs). `value` is the engine's serialized JSON value (a string); a non-string value is
|
|
370
|
+
* JSON-encoded so the field is always a string a caller can `JSON.parse`. `scopeKey` falls back to
|
|
371
|
+
* the process-instance key for a process-level variable that omits it. `isTruncated` is the
|
|
372
|
+
* engine's large-value-clipped flag, defaulting to `false`. The single source of truth
|
|
373
|
+
* `searchVariables` extracts through. */
|
|
374
|
+
export function mapVariableRow(
|
|
375
|
+
row: Record<string, unknown>,
|
|
376
|
+
log: Log,
|
|
377
|
+
): VariableSummary | undefined {
|
|
378
|
+
const variableKey = presentEngineKey(row.variableKey);
|
|
379
|
+
if (variableKey === undefined) {
|
|
380
|
+
log("warn", "skipping variable with no variableKey in engine response");
|
|
381
|
+
return undefined;
|
|
382
|
+
}
|
|
383
|
+
const processInstanceKey = presentEngineKey(row.processInstanceKey);
|
|
384
|
+
if (processInstanceKey === undefined) {
|
|
385
|
+
log("warn", "skipping variable with no processInstanceKey in engine response", {
|
|
386
|
+
variableKey,
|
|
387
|
+
});
|
|
388
|
+
return undefined;
|
|
389
|
+
}
|
|
390
|
+
const name = presentText(row.name);
|
|
391
|
+
if (name === undefined) {
|
|
392
|
+
log("warn", "skipping variable with no name in engine response", { variableKey });
|
|
393
|
+
return undefined;
|
|
394
|
+
}
|
|
395
|
+
// A process-level variable is scoped to the process instance itself; the engine may omit
|
|
396
|
+
// `scopeKey` for it, so fall back to the process-instance key rather than dropping the row.
|
|
397
|
+
const scopeKey = presentEngineKey(row.scopeKey) ?? processInstanceKey;
|
|
398
|
+
// The engine serializes `value` as a JSON string. Keep a string as-is; encode any other type
|
|
399
|
+
// (an object/array/number a permissive engine might inline) so the field is always a string the
|
|
400
|
+
// caller can `JSON.parse`.
|
|
401
|
+
const value = typeof row.value === "string" ? row.value : JSON.stringify(row.value ?? null);
|
|
402
|
+
return {
|
|
403
|
+
variableKey,
|
|
404
|
+
name,
|
|
405
|
+
value,
|
|
406
|
+
scopeKey,
|
|
407
|
+
processInstanceKey,
|
|
408
|
+
isTruncated: row.isTruncated === true,
|
|
409
|
+
};
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
/** Map one engine job row onto a {@link JobSummary}, or `undefined` when the row is malformed
|
|
413
|
+
* (missing the `jobKey`, `type`, or owning `processInstanceKey`). `worker`/`retries`/`elementId`/
|
|
414
|
+
* `deadline` are best-effort diagnostics: present when the engine reports them, omitted otherwise
|
|
415
|
+
* — an unset `worker` is the "queued, not leased" signal, so it must be absent rather than `""`.
|
|
416
|
+
* `state` is passed through as a bare string (the engine's job-state enum is broad). The single
|
|
417
|
+
* source of truth `searchJobs` extracts through. */
|
|
418
|
+
export function mapJobRow(
|
|
419
|
+
row: Record<string, unknown>,
|
|
420
|
+
log: Log,
|
|
421
|
+
): JobSummary | undefined {
|
|
422
|
+
const jobKey = presentEngineKey(row.jobKey);
|
|
423
|
+
if (jobKey === undefined) {
|
|
424
|
+
log("warn", "skipping job with no jobKey in engine response");
|
|
425
|
+
return undefined;
|
|
426
|
+
}
|
|
427
|
+
const type = presentText(row.type);
|
|
428
|
+
if (type === undefined) {
|
|
429
|
+
log("warn", "skipping job with no type in engine response", { jobKey });
|
|
430
|
+
return undefined;
|
|
431
|
+
}
|
|
432
|
+
const processInstanceKey = presentEngineKey(row.processInstanceKey);
|
|
433
|
+
if (processInstanceKey === undefined) {
|
|
434
|
+
log("warn", "skipping job with no processInstanceKey in engine response", { jobKey });
|
|
435
|
+
return undefined;
|
|
436
|
+
}
|
|
437
|
+
const state = presentText(row.state);
|
|
438
|
+
if (state === undefined) {
|
|
439
|
+
log("warn", "skipping job with no state in engine response", { jobKey });
|
|
440
|
+
return undefined;
|
|
441
|
+
}
|
|
442
|
+
const worker = presentText(row.worker);
|
|
443
|
+
const retries = typeof row.retries === "number" && Number.isFinite(row.retries)
|
|
444
|
+
? row.retries
|
|
445
|
+
: undefined;
|
|
446
|
+
const elementId = presentText(row.elementId);
|
|
447
|
+
const deadline = presentText(row.deadline);
|
|
448
|
+
return {
|
|
449
|
+
jobKey,
|
|
450
|
+
type,
|
|
451
|
+
state,
|
|
452
|
+
processInstanceKey,
|
|
453
|
+
...(worker ? { worker } : {}),
|
|
454
|
+
...(retries !== undefined ? { retries } : {}),
|
|
455
|
+
...(elementId ? { elementId } : {}),
|
|
456
|
+
...(deadline ? { deadline } : {}),
|
|
457
|
+
};
|
|
458
|
+
}
|
|
459
|
+
|
|
363
460
|
/** A job as delivered to a nano-sdk job handler: the frame fields plus the
|
|
364
461
|
* acknowledgement actions the handler must call. */
|
|
365
462
|
export interface NanoSdkActivatedJob {
|
|
@@ -463,6 +560,21 @@ export interface NanoSdkClient {
|
|
|
463
560
|
consistency?: unknown,
|
|
464
561
|
options?: unknown,
|
|
465
562
|
): Promise<Record<string, unknown>>;
|
|
563
|
+
searchVariables(
|
|
564
|
+
input: { filter?: Record<string, unknown>; page?: Record<string, unknown> },
|
|
565
|
+
consistency?: unknown,
|
|
566
|
+
options?: unknown,
|
|
567
|
+
): Promise<Record<string, unknown>>;
|
|
568
|
+
searchJobs(
|
|
569
|
+
input: { filter?: Record<string, unknown>; page?: Record<string, unknown> },
|
|
570
|
+
consistency?: unknown,
|
|
571
|
+
options?: unknown,
|
|
572
|
+
): Promise<Record<string, unknown>>;
|
|
573
|
+
getProcessDefinitionXml(
|
|
574
|
+
input: { processDefinitionKey: string },
|
|
575
|
+
consistency?: unknown,
|
|
576
|
+
options?: unknown,
|
|
577
|
+
): Promise<unknown>;
|
|
466
578
|
resolveIncident(
|
|
467
579
|
input: { incidentKey: string },
|
|
468
580
|
options?: unknown,
|
|
@@ -680,7 +792,12 @@ export class SdkEngineClient implements EngineClient {
|
|
|
680
792
|
});
|
|
681
793
|
return [];
|
|
682
794
|
}
|
|
683
|
-
|
|
795
|
+
const processDefinitionKey = presentEngineKey(it.processDefinitionKey);
|
|
796
|
+
return [{
|
|
797
|
+
processInstanceKey: String(key),
|
|
798
|
+
state,
|
|
799
|
+
...(processDefinitionKey ? { processDefinitionKey } : {}),
|
|
800
|
+
}];
|
|
684
801
|
});
|
|
685
802
|
}
|
|
686
803
|
|
|
@@ -784,6 +901,82 @@ export class SdkEngineClient implements EngineClient {
|
|
|
784
901
|
});
|
|
785
902
|
}
|
|
786
903
|
|
|
904
|
+
async searchVariables(filter?: VariableFilter): Promise<VariableSummary[]> {
|
|
905
|
+
// Same shape as `searchIncidents`: build the engine filter from the transport-agnostic
|
|
906
|
+
// selectors, read at zero-wait consistency (a variable search is eventually consistent), and
|
|
907
|
+
// map each row through the single `mapVariableRow` gate, dropping malformed rows.
|
|
908
|
+
const f: Record<string, unknown> = {};
|
|
909
|
+
const processInstanceKey = presentEngineKey(filter?.processInstanceKey);
|
|
910
|
+
if (processInstanceKey) f.processInstanceKey = processInstanceKey;
|
|
911
|
+
const scopeKey = presentEngineKey(filter?.scopeKey);
|
|
912
|
+
if (scopeKey) f.scopeKey = scopeKey;
|
|
913
|
+
const name = filter?.name?.trim();
|
|
914
|
+
if (name) f.name = name;
|
|
915
|
+
const body = await this.client.searchVariables(
|
|
916
|
+
{ filter: f },
|
|
917
|
+
{ consistency: { waitUpToMs: 0 } },
|
|
918
|
+
);
|
|
919
|
+
const items = Array.isArray(body.items) ? body.items.filter(isRecord) : [];
|
|
920
|
+
return items.flatMap((it) => {
|
|
921
|
+
const mapped = mapVariableRow(it, this.log);
|
|
922
|
+
return mapped ? [mapped] : [];
|
|
923
|
+
});
|
|
924
|
+
}
|
|
925
|
+
|
|
926
|
+
async searchJobs(filter?: JobFilter): Promise<JobSummary[]> {
|
|
927
|
+
// Same shape as `searchIncidents`: build the engine filter from the transport-agnostic
|
|
928
|
+
// selectors, read at zero-wait consistency (a job search is eventually consistent), and map
|
|
929
|
+
// each row through the single `mapJobRow` gate, dropping malformed rows.
|
|
930
|
+
const f: Record<string, unknown> = {};
|
|
931
|
+
const processInstanceKey = presentEngineKey(filter?.processInstanceKey);
|
|
932
|
+
if (processInstanceKey) f.processInstanceKey = processInstanceKey;
|
|
933
|
+
const state = filter?.state?.trim();
|
|
934
|
+
if (state) f.state = state;
|
|
935
|
+
const type = filter?.type?.trim();
|
|
936
|
+
if (type) f.type = type;
|
|
937
|
+
const elementId = filter?.elementId?.trim();
|
|
938
|
+
if (elementId) f.elementId = elementId;
|
|
939
|
+
const worker = filter?.worker?.trim();
|
|
940
|
+
if (worker) f.worker = worker;
|
|
941
|
+
const body = await this.client.searchJobs(
|
|
942
|
+
{ filter: f },
|
|
943
|
+
{ consistency: { waitUpToMs: 0 } },
|
|
944
|
+
);
|
|
945
|
+
const items = Array.isArray(body.items) ? body.items.filter(isRecord) : [];
|
|
946
|
+
return items.flatMap((it) => {
|
|
947
|
+
const mapped = mapJobRow(it, this.log);
|
|
948
|
+
return mapped ? [mapped] : [];
|
|
949
|
+
});
|
|
950
|
+
}
|
|
951
|
+
|
|
952
|
+
async getProcessDefinitionXml(
|
|
953
|
+
processDefinitionKey: string,
|
|
954
|
+
): Promise<string | null> {
|
|
955
|
+
// A blank key can never address a definition — short-circuit to null rather than issue a
|
|
956
|
+
// `GET /v2/process-definitions//xml` with an empty segment. Normalize a padded-but-valid key so
|
|
957
|
+
// `" 5 "` addresses the same definition, mirroring `getElementInstance`.
|
|
958
|
+
const key = presentEngineKey(processDefinitionKey);
|
|
959
|
+
if (key === undefined) return null;
|
|
960
|
+
let xml: unknown;
|
|
961
|
+
try {
|
|
962
|
+
xml = await this.client.getProcessDefinitionXml(
|
|
963
|
+
{ processDefinitionKey: key },
|
|
964
|
+
{ consistency: { waitUpToMs: 0 } },
|
|
965
|
+
);
|
|
966
|
+
} catch (err) {
|
|
967
|
+
// A 404 (no such definition) is an expected "not found", not a fault — mirror `getForm`/
|
|
968
|
+
// `getElementInstance`, which treat a failed fetch as absence (null) rather than propagating.
|
|
969
|
+
this.log("warn", "getProcessDefinitionXml: engine fetch failed", {
|
|
970
|
+
processDefinitionKey: key,
|
|
971
|
+
err: err instanceof Error ? err.message : String(err),
|
|
972
|
+
});
|
|
973
|
+
return null;
|
|
974
|
+
}
|
|
975
|
+
// The endpoint returns the raw BPMN XML as a string (204 → empty). Treat a blank/absent body
|
|
976
|
+
// as "no XML" (null) so a caller distinguishes "have it" from "don't".
|
|
977
|
+
return typeof xml === "string" && xml.trim() !== "" ? xml : null;
|
|
978
|
+
}
|
|
979
|
+
|
|
787
980
|
async resolveIncident(input: { incidentKey: string }): Promise<void> {
|
|
788
981
|
await this.client.resolveIncident({
|
|
789
982
|
incidentKey: requireEngineKey(input.incidentKey, "incidentKey"),
|
package/src/runtime/index.ts
CHANGED
|
@@ -100,13 +100,17 @@ export type {
|
|
|
100
100
|
EngineIncidentState,
|
|
101
101
|
IncidentFilter,
|
|
102
102
|
IncidentSummary,
|
|
103
|
+
JobFilter,
|
|
103
104
|
JobHandler,
|
|
105
|
+
JobSummary,
|
|
104
106
|
ProcessInstanceSnapshot,
|
|
105
107
|
ProcessInstanceState,
|
|
106
108
|
SqliteDb,
|
|
107
109
|
UserTaskState,
|
|
108
110
|
UserTaskSummary,
|
|
109
111
|
UserTaskFilter,
|
|
112
|
+
VariableFilter,
|
|
113
|
+
VariableSummary,
|
|
110
114
|
WatchHandle,
|
|
111
115
|
WorkerSubscription,
|
|
112
116
|
} from "./core/host.ts";
|