@nanobpm/urban 0.85.0 → 0.86.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/openapi/spec.d.ts +72 -0
- package/dist/openapi/spec.js +130 -0
- package/dist/runtime/core/host.d.ts +67 -9
- package/dist/runtime/core/host.js +48 -0
- package/dist/runtime/core/modules/mcp.d.ts +23 -5
- package/dist/runtime/core/modules/mcp.js +346 -61
- package/dist/runtime/engine/nanosdk.js +9 -5
- package/dist/runtime/index.d.ts +1 -0
- package/dist/runtime/index.js +4 -0
- package/package.json +3 -1
- package/src/openapi/mcp-parity.fixture.yaml +49 -0
- package/src/openapi/mcp-tools.snapshot.json +20 -0
- package/src/openapi/spec.test.ts +92 -0
- package/src/openapi/spec.ts +161 -0
- package/src/runtime/core/host.ts +83 -9
- package/src/runtime/core/modules/mcp.test.ts +526 -14
- package/src/runtime/core/modules/mcp.ts +391 -61
- package/src/runtime/engine/nanosdk.test.ts +69 -11
- package/src/runtime/engine/nanosdk.ts +9 -5
- package/src/runtime/index.ts +8 -0
|
@@ -13,6 +13,11 @@ import {
|
|
|
13
13
|
type NanoSdkJobWorkerConfig,
|
|
14
14
|
} from "./nanosdk.ts";
|
|
15
15
|
import { BpmnError } from "../core/host.ts";
|
|
16
|
+
import {
|
|
17
|
+
assertDeployedWaitStateType,
|
|
18
|
+
DEPLOYED_WAIT_STATE_TYPES,
|
|
19
|
+
UnsupportedWaitStateTypeError,
|
|
20
|
+
} from "../core/host.ts";
|
|
16
21
|
import { __resetExecStoreForTests, installExecStore, runInJobContext } from "../core/execContext.ts";
|
|
17
22
|
import { readLineage } from "../core/lineage.ts";
|
|
18
23
|
import { createNodeHost } from "../adapters/node.ts";
|
|
@@ -498,6 +503,27 @@ test("normalizeWaitStateType maps the wait-state kinds (case-insensitive) and re
|
|
|
498
503
|
assert.equal(normalizeWaitStateType(3), undefined);
|
|
499
504
|
});
|
|
500
505
|
|
|
506
|
+
test("assertDeployedWaitStateType: allows the floor and an absent selector, rejects the rest", () => {
|
|
507
|
+
// The deployed floor is exactly JOB|MESSAGE — the single source of truth both adapters gate on.
|
|
508
|
+
assert.deepEqual([...DEPLOYED_WAIT_STATE_TYPES], ["JOB", "MESSAGE"]);
|
|
509
|
+
// In-floor and absent selectors are no-ops (no throw).
|
|
510
|
+
assert.doesNotThrow(() => assertDeployedWaitStateType(undefined));
|
|
511
|
+
assert.doesNotThrow(() => assertDeployedWaitStateType("JOB"));
|
|
512
|
+
assert.doesNotThrow(() => assertDeployedWaitStateType("MESSAGE"));
|
|
513
|
+
// Every canonical type outside the floor is rejected with a typed, descriptive error.
|
|
514
|
+
for (const waitStateType of ["USER_TASK", "TIMER", "SIGNAL", "CONDITION"] as const) {
|
|
515
|
+
assert.throws(
|
|
516
|
+
() => assertDeployedWaitStateType(waitStateType),
|
|
517
|
+
(err: unknown) => {
|
|
518
|
+
assert.ok(err instanceof UnsupportedWaitStateTypeError);
|
|
519
|
+
assert.equal(err.waitStateType, waitStateType);
|
|
520
|
+
assert.match(err.message, /HTTP 422/);
|
|
521
|
+
return true;
|
|
522
|
+
},
|
|
523
|
+
);
|
|
524
|
+
}
|
|
525
|
+
});
|
|
526
|
+
|
|
501
527
|
test("searchElementInstances passes zero-wait consistency, maps rows, and forwards selectors", async () => {
|
|
502
528
|
let seenInput: unknown;
|
|
503
529
|
let seenConsistency: unknown;
|
|
@@ -650,35 +676,67 @@ test("searchElementInstanceWaitStates forwards a waitStateType selector and read
|
|
|
650
676
|
{
|
|
651
677
|
elementInstanceKey: "9",
|
|
652
678
|
processInstanceKey: "3",
|
|
653
|
-
elementId: "
|
|
654
|
-
waitStateType: "
|
|
655
|
-
|
|
679
|
+
elementId: "catch",
|
|
680
|
+
waitStateType: "MESSAGE",
|
|
681
|
+
messageName: "Go",
|
|
682
|
+
correlationKey: "K1",
|
|
656
683
|
},
|
|
657
684
|
{
|
|
658
685
|
elementInstanceKey: "11",
|
|
659
686
|
processInstanceKey: "3",
|
|
660
|
-
elementId: "
|
|
661
|
-
waitStateType: "
|
|
662
|
-
}, //
|
|
687
|
+
elementId: "catch2",
|
|
688
|
+
waitStateType: "MESSAGE",
|
|
689
|
+
}, // MESSAGE with no messageName → dropped (invalid identity)
|
|
663
690
|
{ elementInstanceKey: "10", processInstanceKey: "3", elementId: "?" }, // no waitStateType → dropped
|
|
664
691
|
],
|
|
665
692
|
};
|
|
666
693
|
},
|
|
667
694
|
});
|
|
668
695
|
const engine = new SdkEngineClient(client);
|
|
669
|
-
const out = await engine.searchElementInstanceWaitStates({ waitStateType: "
|
|
670
|
-
assert.deepEqual(seenInput, { filter: { waitStateType: "
|
|
696
|
+
const out = await engine.searchElementInstanceWaitStates({ waitStateType: "MESSAGE" });
|
|
697
|
+
assert.deepEqual(seenInput, { filter: { waitStateType: "MESSAGE" } });
|
|
671
698
|
assert.deepEqual(out, [
|
|
672
699
|
{
|
|
673
700
|
elementInstanceKey: "9",
|
|
674
701
|
processInstanceKey: "3",
|
|
675
|
-
elementId: "
|
|
676
|
-
waitStateType: "
|
|
677
|
-
|
|
702
|
+
elementId: "catch",
|
|
703
|
+
waitStateType: "MESSAGE",
|
|
704
|
+
messageName: "Go",
|
|
705
|
+
correlationKey: "K1",
|
|
678
706
|
},
|
|
679
707
|
]);
|
|
680
708
|
});
|
|
681
709
|
|
|
710
|
+
test("searchElementInstanceWaitStates rejects a waitStateType outside the deployed floor without calling the gateway", async () => {
|
|
711
|
+
// The deployed gateway's read model implements only JOB|MESSAGE and 422s any other filter
|
|
712
|
+
// value. The SDK adapter must fail fast client-side (not issue the call), so an app authoring
|
|
713
|
+
// `waitStateType: "USER_TASK"` cannot ship green then blow up at runtime (issue
|
|
714
|
+
// nanobpm/nano-ide#497).
|
|
715
|
+
let called = false;
|
|
716
|
+
const client = fakeSdkClient({
|
|
717
|
+
searchElementInstanceWaitStates: async () => {
|
|
718
|
+
called = true;
|
|
719
|
+
return { items: [] };
|
|
720
|
+
},
|
|
721
|
+
});
|
|
722
|
+
const engine = new SdkEngineClient(client);
|
|
723
|
+
for (const waitStateType of ["USER_TASK", "TIMER", "SIGNAL", "CONDITION"] as const) {
|
|
724
|
+
await assert.rejects(
|
|
725
|
+
engine.searchElementInstanceWaitStates({ waitStateType }),
|
|
726
|
+
(err: unknown) => {
|
|
727
|
+
assert.ok(err instanceof Error);
|
|
728
|
+
assert.match(err.message, /unsupported waitStateType/);
|
|
729
|
+
assert.match(err.message, new RegExp(waitStateType));
|
|
730
|
+
return true;
|
|
731
|
+
},
|
|
732
|
+
);
|
|
733
|
+
}
|
|
734
|
+
assert.equal(called, false, "the gateway must not be called for an out-of-floor filter");
|
|
735
|
+
// An in-floor filter still reaches the gateway.
|
|
736
|
+
await engine.searchElementInstanceWaitStates({ waitStateType: "JOB" });
|
|
737
|
+
assert.equal(called, true);
|
|
738
|
+
});
|
|
739
|
+
|
|
682
740
|
test("searchElementInstanceWaitStates drops parks missing their required discriminator field", async () => {
|
|
683
741
|
const client = fakeSdkClient({
|
|
684
742
|
searchElementInstanceWaitStates: async () => ({
|
|
@@ -32,7 +32,7 @@ import type {
|
|
|
32
32
|
UserTaskFilter,
|
|
33
33
|
WorkerSubscription,
|
|
34
34
|
} from "../core/host.ts";
|
|
35
|
-
import { isBpmnError } from "../core/host.ts";
|
|
35
|
+
import { assertDeployedWaitStateType, isBpmnError } from "../core/host.ts";
|
|
36
36
|
import {
|
|
37
37
|
buildFormSchema,
|
|
38
38
|
parseFormSchema,
|
|
@@ -709,10 +709,14 @@ export class SdkEngineClient implements EngineClient {
|
|
|
709
709
|
async searchElementInstanceWaitStates(
|
|
710
710
|
filter?: ElementInstanceWaitStateFilter,
|
|
711
711
|
): Promise<ElementInstanceWaitState[]> {
|
|
712
|
-
// The wait-states search surfaces
|
|
713
|
-
//
|
|
714
|
-
//
|
|
715
|
-
//
|
|
712
|
+
// The wait-states search surfaces the parks the deployed engine's read model serves.
|
|
713
|
+
// A `waitStateType` selector outside the deployed floor (`JOB | MESSAGE`) is rejected by
|
|
714
|
+
// the gateway with HTTP 422; fail fast client-side with a clear error instead, so the
|
|
715
|
+
// divergence is caught offline and identically to the WASM adapter (No Drift Surfaces).
|
|
716
|
+
assertDeployedWaitStateType(filter?.waitStateType);
|
|
717
|
+
// Same zero-wait read + per-row mapping-gate shape as `searchElementInstances`; the SDK
|
|
718
|
+
// nests park-specific fields under `details`, which `mapElementInstanceWaitStateRow`
|
|
719
|
+
// unwraps.
|
|
716
720
|
const f: Record<string, unknown> = {};
|
|
717
721
|
if (filter?.processInstanceKey) f.processInstanceKey = filter.processInstanceKey;
|
|
718
722
|
if (filter?.elementId) f.elementId = filter.elementId;
|
package/src/runtime/index.ts
CHANGED
|
@@ -133,6 +133,14 @@ export { BpmnError, isBpmnError } from "./core/host.ts";
|
|
|
133
133
|
// double / adapter iterates it to prove at runtime that it implements the full interface,
|
|
134
134
|
// catching the "fake lags the SDK" seam-lag class categorically.
|
|
135
135
|
export { ENGINE_CLIENT_METHODS } from "./core/host.ts";
|
|
136
|
+
// Deployed wait-state floor (issue nanobpm/nano-ide#497): the `waitStateType` values the
|
|
137
|
+
// deployed gateway actually serves, plus the shared guard both EngineClient adapters use so
|
|
138
|
+
// a filter outside the floor fails identically in emulation and against a live engine.
|
|
139
|
+
export {
|
|
140
|
+
assertDeployedWaitStateType,
|
|
141
|
+
DEPLOYED_WAIT_STATE_TYPES,
|
|
142
|
+
UnsupportedWaitStateTypeError,
|
|
143
|
+
} from "./core/host.ts";
|
|
136
144
|
|
|
137
145
|
// Connector packs (ADR 0050, in-process port)
|
|
138
146
|
export {
|