@nanobpm/urban 0.86.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/runtime/core/host.d.ts +67 -9
- package/dist/runtime/core/host.js +48 -0
- 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 +1 -1
- package/src/runtime/core/host.ts +83 -9
- package/src/runtime/engine/nanosdk.test.ts +69 -11
- package/src/runtime/engine/nanosdk.ts +9 -5
- package/src/runtime/index.ts +8 -0
|
@@ -260,12 +260,58 @@ export interface ElementInstanceSummary {
|
|
|
260
260
|
}
|
|
261
261
|
/**
|
|
262
262
|
* The kind of wait an element instance is parked in, as a
|
|
263
|
-
* `/v2/element-instances/wait-states/search` result reports it.
|
|
264
|
-
*
|
|
265
|
-
* (an event awaiting correlation), a `TIMER`, a `SIGNAL`, a `CONDITION`, as well
|
|
266
|
-
*
|
|
263
|
+
* `/v2/element-instances/wait-states/search` result reports it. This is the *canonical*
|
|
264
|
+
* six-type contract (Zeebe's, as of 2026): a `JOB` (a service task awaiting a worker), a
|
|
265
|
+
* `MESSAGE` (an event awaiting correlation), a `TIMER`, a `SIGNAL`, a `CONDITION`, as well
|
|
266
|
+
* as a `USER_TASK`.
|
|
267
|
+
*
|
|
268
|
+
* **Not every deployed engine implements the whole union.** It is forward-looking on
|
|
269
|
+
* purpose (the response mapper and {@link normalizeWaitStateType} tolerate all six so a
|
|
270
|
+
* newer engine's rows map cleanly), but the *currently deployed* nanobpmn gateway's
|
|
271
|
+
* read-model floor is narrower — see {@link DEPLOYED_WAIT_STATE_TYPES}. A
|
|
272
|
+
* {@link ElementInstanceWaitStateFilter} whose `waitStateType` falls outside that floor is
|
|
273
|
+
* rejected (the gateway answers HTTP 422); {@link assertDeployedWaitStateType} enforces the
|
|
274
|
+
* same floor client-side so the divergence surfaces as a clear error offline, in tests, and
|
|
275
|
+
* against a live engine alike. Ref: Magikcraft/nano-bpm#1042.
|
|
267
276
|
*/
|
|
268
277
|
export type WaitStateType = "JOB" | "MESSAGE" | "USER_TASK" | "TIMER" | "SIGNAL" | "CONDITION";
|
|
278
|
+
/**
|
|
279
|
+
* The `waitStateType` values the *currently deployed* nanobpmn gateway's wait-state read
|
|
280
|
+
* model actually implements — the **deployed floor**, a strict subset of the canonical
|
|
281
|
+
* {@link WaitStateType} union. The gateway's read model is a snapshot of the Zeebe contract
|
|
282
|
+
* taken 2026-06-09 (the same day Zeebe extended it with `USER_TASK`, then `TIMER`/`SIGNAL`/
|
|
283
|
+
* `CONDITION`), so it recognizes only `JOB | MESSAGE` and rejects any other filter value
|
|
284
|
+
* with HTTP 422.
|
|
285
|
+
*
|
|
286
|
+
* This constant is the **single source of truth** for that floor: the SDK-backed and
|
|
287
|
+
* WASM-backed {@link EngineClient} adapters both gate through {@link assertDeployedWaitStateType},
|
|
288
|
+
* and the WASM emulation synthesizes only these park kinds — so a test cannot pass against
|
|
289
|
+
* the emulation while a real gateway would 422 (No Drift Surfaces). Widen it (adding a leg
|
|
290
|
+
* backed by the real gateway) only once the engine actually serves the richer type — the
|
|
291
|
+
* tracking gap is Magikcraft/nano-bpm#1042.
|
|
292
|
+
*/
|
|
293
|
+
export declare const DEPLOYED_WAIT_STATE_TYPES: readonly WaitStateType[];
|
|
294
|
+
/**
|
|
295
|
+
* Thrown when a wait-state search is asked to filter on a {@link WaitStateType} the deployed
|
|
296
|
+
* gateway's read model does not implement (outside {@link DEPLOYED_WAIT_STATE_TYPES}). It
|
|
297
|
+
* fails fast client-side with a clear, offline-detectable error rather than letting the call
|
|
298
|
+
* reach the gateway and surface as an opaque HTTP 422 "did not match any variant of untagged
|
|
299
|
+
* enum WaitStateTypeFilterProperty". Ref: Magikcraft/nano-bpm#1042.
|
|
300
|
+
*/
|
|
301
|
+
export declare class UnsupportedWaitStateTypeError extends Error {
|
|
302
|
+
readonly waitStateType: string;
|
|
303
|
+
readonly supported: readonly WaitStateType[];
|
|
304
|
+
constructor(waitStateType: string);
|
|
305
|
+
}
|
|
306
|
+
/**
|
|
307
|
+
* Assert a wait-state filter's `waitStateType` is within the deployed floor
|
|
308
|
+
* ({@link DEPLOYED_WAIT_STATE_TYPES}), throwing {@link UnsupportedWaitStateTypeError}
|
|
309
|
+
* otherwise. An absent selector is always allowed (the search is unfiltered by type). Both
|
|
310
|
+
* {@link EngineClient} adapters call this before issuing a wait-state search, so the floor is
|
|
311
|
+
* enforced identically whether the query runs against a live gateway or the in-process
|
|
312
|
+
* emulation. No-ops for an in-floor type.
|
|
313
|
+
*/
|
|
314
|
+
export declare function assertDeployedWaitStateType(waitStateType?: WaitStateType): void;
|
|
269
315
|
/**
|
|
270
316
|
* A single parked (waiting) element instance as
|
|
271
317
|
* {@link EngineClient.searchElementInstanceWaitStates} reports it. Every variant carries the
|
|
@@ -312,6 +358,10 @@ export interface ElementInstanceFilter {
|
|
|
312
358
|
* Selectors for an element-instance *wait-state* search: which process instance, element,
|
|
313
359
|
* and/or kind of wait to match. Used by {@link EngineClient.searchElementInstanceWaitStates}.
|
|
314
360
|
* There is deliberately no lifecycle-`state` selector — a wait state is always an active park.
|
|
361
|
+
*
|
|
362
|
+
* A `waitStateType` selector must fall within the deployed floor
|
|
363
|
+
* ({@link DEPLOYED_WAIT_STATE_TYPES}); a value outside it is rejected with
|
|
364
|
+
* {@link UnsupportedWaitStateTypeError} (the live gateway answers HTTP 422).
|
|
315
365
|
*/
|
|
316
366
|
export interface ElementInstanceWaitStateFilter {
|
|
317
367
|
processInstanceKey?: string;
|
|
@@ -476,11 +526,19 @@ export interface EngineClient {
|
|
|
476
526
|
searchElementInstances(filter?: ElementInstanceFilter): Promise<ElementInstanceSummary[]>;
|
|
477
527
|
/**
|
|
478
528
|
* Search the *wait states* of element instances
|
|
479
|
-
* (`POST /v2/element-instances/wait-states/search`) —
|
|
480
|
-
*
|
|
481
|
-
*
|
|
482
|
-
*
|
|
483
|
-
*
|
|
529
|
+
* (`POST /v2/element-instances/wait-states/search`) — the parks the deployed engine's
|
|
530
|
+
* read model serves. A zero-wait read; each result is discriminated by `waitStateType`
|
|
531
|
+
* (see {@link ElementInstanceWaitState}), so a consumer can read the job/message parks a
|
|
532
|
+
* {@link searchUserTasks} cannot surface.
|
|
533
|
+
*
|
|
534
|
+
* **Deployed floor.** The currently deployed nanobpmn gateway implements only
|
|
535
|
+
* `JOB | MESSAGE` ({@link DEPLOYED_WAIT_STATE_TYPES}) — a `USER_TASK` park is read via
|
|
536
|
+
* {@link searchUserTasks}, not here. A `waitStateType` filter outside the floor is rejected
|
|
537
|
+
* with {@link UnsupportedWaitStateTypeError} (the live gateway answers HTTP 422); an
|
|
538
|
+
* unfiltered search returns only in-floor parks. The `waitStateType` union is
|
|
539
|
+
* forward-looking (richer engines may serve more), but this adapter contract pins the
|
|
540
|
+
* floor so a query cannot pass in emulation while a real engine rejects it. Ref:
|
|
541
|
+
* Magikcraft/nano-bpm#1042.
|
|
484
542
|
*/
|
|
485
543
|
searchElementInstanceWaitStates(filter?: ElementInstanceWaitStateFilter): Promise<ElementInstanceWaitState[]>;
|
|
486
544
|
/**
|
|
@@ -42,6 +42,54 @@ export function presentFormIdentifier(value) {
|
|
|
42
42
|
const trimmed = value?.trim();
|
|
43
43
|
return trimmed ? trimmed : undefined;
|
|
44
44
|
}
|
|
45
|
+
/**
|
|
46
|
+
* The `waitStateType` values the *currently deployed* nanobpmn gateway's wait-state read
|
|
47
|
+
* model actually implements — the **deployed floor**, a strict subset of the canonical
|
|
48
|
+
* {@link WaitStateType} union. The gateway's read model is a snapshot of the Zeebe contract
|
|
49
|
+
* taken 2026-06-09 (the same day Zeebe extended it with `USER_TASK`, then `TIMER`/`SIGNAL`/
|
|
50
|
+
* `CONDITION`), so it recognizes only `JOB | MESSAGE` and rejects any other filter value
|
|
51
|
+
* with HTTP 422.
|
|
52
|
+
*
|
|
53
|
+
* This constant is the **single source of truth** for that floor: the SDK-backed and
|
|
54
|
+
* WASM-backed {@link EngineClient} adapters both gate through {@link assertDeployedWaitStateType},
|
|
55
|
+
* and the WASM emulation synthesizes only these park kinds — so a test cannot pass against
|
|
56
|
+
* the emulation while a real gateway would 422 (No Drift Surfaces). Widen it (adding a leg
|
|
57
|
+
* backed by the real gateway) only once the engine actually serves the richer type — the
|
|
58
|
+
* tracking gap is Magikcraft/nano-bpm#1042.
|
|
59
|
+
*/
|
|
60
|
+
export const DEPLOYED_WAIT_STATE_TYPES = ["JOB", "MESSAGE"];
|
|
61
|
+
/**
|
|
62
|
+
* Thrown when a wait-state search is asked to filter on a {@link WaitStateType} the deployed
|
|
63
|
+
* gateway's read model does not implement (outside {@link DEPLOYED_WAIT_STATE_TYPES}). It
|
|
64
|
+
* fails fast client-side with a clear, offline-detectable error rather than letting the call
|
|
65
|
+
* reach the gateway and surface as an opaque HTTP 422 "did not match any variant of untagged
|
|
66
|
+
* enum WaitStateTypeFilterProperty". Ref: Magikcraft/nano-bpm#1042.
|
|
67
|
+
*/
|
|
68
|
+
export class UnsupportedWaitStateTypeError extends Error {
|
|
69
|
+
waitStateType;
|
|
70
|
+
supported;
|
|
71
|
+
constructor(waitStateType) {
|
|
72
|
+
super(`unsupported waitStateType "${waitStateType}": the deployed nanobpmn gateway's ` +
|
|
73
|
+
`wait-state read model implements only ${DEPLOYED_WAIT_STATE_TYPES.join(" | ")} and ` +
|
|
74
|
+
`rejects any other value with HTTP 422. Ref: Magikcraft/nano-bpm#1042.`);
|
|
75
|
+
this.name = "UnsupportedWaitStateTypeError";
|
|
76
|
+
this.waitStateType = waitStateType;
|
|
77
|
+
this.supported = DEPLOYED_WAIT_STATE_TYPES;
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
/**
|
|
81
|
+
* Assert a wait-state filter's `waitStateType` is within the deployed floor
|
|
82
|
+
* ({@link DEPLOYED_WAIT_STATE_TYPES}), throwing {@link UnsupportedWaitStateTypeError}
|
|
83
|
+
* otherwise. An absent selector is always allowed (the search is unfiltered by type). Both
|
|
84
|
+
* {@link EngineClient} adapters call this before issuing a wait-state search, so the floor is
|
|
85
|
+
* enforced identically whether the query runs against a live gateway or the in-process
|
|
86
|
+
* emulation. No-ops for an in-floor type.
|
|
87
|
+
*/
|
|
88
|
+
export function assertDeployedWaitStateType(waitStateType) {
|
|
89
|
+
if (waitStateType !== undefined && !DEPLOYED_WAIT_STATE_TYPES.includes(waitStateType)) {
|
|
90
|
+
throw new UnsupportedWaitStateTypeError(waitStateType);
|
|
91
|
+
}
|
|
92
|
+
}
|
|
45
93
|
/**
|
|
46
94
|
* The names of every method on {@link EngineClient} — the single *runtime* source of
|
|
47
95
|
* truth for the interface's surface. It is compile-time-pinned to `keyof EngineClient`
|
|
@@ -18,7 +18,7 @@ var __rewriteRelativeImportExtension = (this && this.__rewriteRelativeImportExte
|
|
|
18
18
|
}
|
|
19
19
|
return path;
|
|
20
20
|
};
|
|
21
|
-
import { isBpmnError } from "../core/host.js";
|
|
21
|
+
import { assertDeployedWaitStateType, isBpmnError } from "../core/host.js";
|
|
22
22
|
import { buildFormSchema, parseFormSchema, pickFormLinkage, resolveFormIdentifier, } from "../core/form-contract.js";
|
|
23
23
|
import { isRecord } from "../core/guards.js";
|
|
24
24
|
import { applyAmbientLineage } from "../core/lineage.js";
|
|
@@ -507,10 +507,14 @@ export class SdkEngineClient {
|
|
|
507
507
|
});
|
|
508
508
|
}
|
|
509
509
|
async searchElementInstanceWaitStates(filter) {
|
|
510
|
-
// The wait-states search surfaces
|
|
511
|
-
//
|
|
512
|
-
//
|
|
513
|
-
//
|
|
510
|
+
// The wait-states search surfaces the parks the deployed engine's read model serves.
|
|
511
|
+
// A `waitStateType` selector outside the deployed floor (`JOB | MESSAGE`) is rejected by
|
|
512
|
+
// the gateway with HTTP 422; fail fast client-side with a clear error instead, so the
|
|
513
|
+
// divergence is caught offline and identically to the WASM adapter (No Drift Surfaces).
|
|
514
|
+
assertDeployedWaitStateType(filter?.waitStateType);
|
|
515
|
+
// Same zero-wait read + per-row mapping-gate shape as `searchElementInstances`; the SDK
|
|
516
|
+
// nests park-specific fields under `details`, which `mapElementInstanceWaitStateRow`
|
|
517
|
+
// unwraps.
|
|
514
518
|
const f = {};
|
|
515
519
|
if (filter?.processInstanceKey)
|
|
516
520
|
f.processInstanceKey = filter.processInstanceKey;
|
package/dist/runtime/index.d.ts
CHANGED
|
@@ -15,6 +15,7 @@ export { createLogger } from "./core/logger.ts";
|
|
|
15
15
|
export type { Logger, LogLevel, LogFields, LogSink } from "./core/logger.ts";
|
|
16
16
|
export { BpmnError, isBpmnError } from "./core/host.ts";
|
|
17
17
|
export { ENGINE_CLIENT_METHODS } from "./core/host.ts";
|
|
18
|
+
export { assertDeployedWaitStateType, DEPLOYED_WAIT_STATE_TYPES, UnsupportedWaitStateTypeError, } from "./core/host.ts";
|
|
18
19
|
export { adaptConnectorHandler, mountConnectors, resolveInstalledConnectors, } from "./core/modules/connectors.ts";
|
|
19
20
|
export type { ConnectorsHandle } from "./core/modules/connectors.ts";
|
|
20
21
|
export { DataLayer, TypeRepo } from "./core/modules/datasource.ts";
|
package/dist/runtime/index.js
CHANGED
|
@@ -22,6 +22,10 @@ export { BpmnError, isBpmnError } from "./core/host.js";
|
|
|
22
22
|
// double / adapter iterates it to prove at runtime that it implements the full interface,
|
|
23
23
|
// catching the "fake lags the SDK" seam-lag class categorically.
|
|
24
24
|
export { ENGINE_CLIENT_METHODS } from "./core/host.js";
|
|
25
|
+
// Deployed wait-state floor (issue nanobpm/nano-ide#497): the `waitStateType` values the
|
|
26
|
+
// deployed gateway actually serves, plus the shared guard both EngineClient adapters use so
|
|
27
|
+
// a filter outside the floor fails identically in emulation and against a live engine.
|
|
28
|
+
export { assertDeployedWaitStateType, DEPLOYED_WAIT_STATE_TYPES, UnsupportedWaitStateTypeError, } from "./core/host.js";
|
|
25
29
|
// Connector packs (ADR 0050, in-process port)
|
|
26
30
|
export { adaptConnectorHandler, mountConnectors, resolveInstalledConnectors, } from "./core/modules/connectors.js";
|
|
27
31
|
// Data layer
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@nanobpm/urban",
|
|
3
|
-
"version": "0.86.
|
|
3
|
+
"version": "0.86.1",
|
|
4
4
|
"description": "Urban: build and run code-first apps on Nano — runtime, derivation toolkit, and CLI in one. Author durable processes with @nanobpm/workflow (defineFlow), a typed datasource, forms, triggers and surfaces from a nano.app.json manifest, on Node or Deno.",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"type": "module",
|
package/src/runtime/core/host.ts
CHANGED
|
@@ -296,10 +296,19 @@ export interface ElementInstanceSummary {
|
|
|
296
296
|
|
|
297
297
|
/**
|
|
298
298
|
* The kind of wait an element instance is parked in, as a
|
|
299
|
-
* `/v2/element-instances/wait-states/search` result reports it.
|
|
300
|
-
*
|
|
301
|
-
* (an event awaiting correlation), a `TIMER`, a `SIGNAL`, a `CONDITION`, as well
|
|
302
|
-
*
|
|
299
|
+
* `/v2/element-instances/wait-states/search` result reports it. This is the *canonical*
|
|
300
|
+
* six-type contract (Zeebe's, as of 2026): a `JOB` (a service task awaiting a worker), a
|
|
301
|
+
* `MESSAGE` (an event awaiting correlation), a `TIMER`, a `SIGNAL`, a `CONDITION`, as well
|
|
302
|
+
* as a `USER_TASK`.
|
|
303
|
+
*
|
|
304
|
+
* **Not every deployed engine implements the whole union.** It is forward-looking on
|
|
305
|
+
* purpose (the response mapper and {@link normalizeWaitStateType} tolerate all six so a
|
|
306
|
+
* newer engine's rows map cleanly), but the *currently deployed* nanobpmn gateway's
|
|
307
|
+
* read-model floor is narrower — see {@link DEPLOYED_WAIT_STATE_TYPES}. A
|
|
308
|
+
* {@link ElementInstanceWaitStateFilter} whose `waitStateType` falls outside that floor is
|
|
309
|
+
* rejected (the gateway answers HTTP 422); {@link assertDeployedWaitStateType} enforces the
|
|
310
|
+
* same floor client-side so the divergence surfaces as a clear error offline, in tests, and
|
|
311
|
+
* against a live engine alike. Ref: Magikcraft/nano-bpm#1042.
|
|
303
312
|
*/
|
|
304
313
|
export type WaitStateType =
|
|
305
314
|
| "JOB"
|
|
@@ -309,6 +318,59 @@ export type WaitStateType =
|
|
|
309
318
|
| "SIGNAL"
|
|
310
319
|
| "CONDITION";
|
|
311
320
|
|
|
321
|
+
/**
|
|
322
|
+
* The `waitStateType` values the *currently deployed* nanobpmn gateway's wait-state read
|
|
323
|
+
* model actually implements — the **deployed floor**, a strict subset of the canonical
|
|
324
|
+
* {@link WaitStateType} union. The gateway's read model is a snapshot of the Zeebe contract
|
|
325
|
+
* taken 2026-06-09 (the same day Zeebe extended it with `USER_TASK`, then `TIMER`/`SIGNAL`/
|
|
326
|
+
* `CONDITION`), so it recognizes only `JOB | MESSAGE` and rejects any other filter value
|
|
327
|
+
* with HTTP 422.
|
|
328
|
+
*
|
|
329
|
+
* This constant is the **single source of truth** for that floor: the SDK-backed and
|
|
330
|
+
* WASM-backed {@link EngineClient} adapters both gate through {@link assertDeployedWaitStateType},
|
|
331
|
+
* and the WASM emulation synthesizes only these park kinds — so a test cannot pass against
|
|
332
|
+
* the emulation while a real gateway would 422 (No Drift Surfaces). Widen it (adding a leg
|
|
333
|
+
* backed by the real gateway) only once the engine actually serves the richer type — the
|
|
334
|
+
* tracking gap is Magikcraft/nano-bpm#1042.
|
|
335
|
+
*/
|
|
336
|
+
export const DEPLOYED_WAIT_STATE_TYPES: readonly WaitStateType[] = ["JOB", "MESSAGE"];
|
|
337
|
+
|
|
338
|
+
/**
|
|
339
|
+
* Thrown when a wait-state search is asked to filter on a {@link WaitStateType} the deployed
|
|
340
|
+
* gateway's read model does not implement (outside {@link DEPLOYED_WAIT_STATE_TYPES}). It
|
|
341
|
+
* fails fast client-side with a clear, offline-detectable error rather than letting the call
|
|
342
|
+
* reach the gateway and surface as an opaque HTTP 422 "did not match any variant of untagged
|
|
343
|
+
* enum WaitStateTypeFilterProperty". Ref: Magikcraft/nano-bpm#1042.
|
|
344
|
+
*/
|
|
345
|
+
export class UnsupportedWaitStateTypeError extends Error {
|
|
346
|
+
readonly waitStateType: string;
|
|
347
|
+
readonly supported: readonly WaitStateType[];
|
|
348
|
+
constructor(waitStateType: string) {
|
|
349
|
+
super(
|
|
350
|
+
`unsupported waitStateType "${waitStateType}": the deployed nanobpmn gateway's ` +
|
|
351
|
+
`wait-state read model implements only ${DEPLOYED_WAIT_STATE_TYPES.join(" | ")} and ` +
|
|
352
|
+
`rejects any other value with HTTP 422. Ref: Magikcraft/nano-bpm#1042.`,
|
|
353
|
+
);
|
|
354
|
+
this.name = "UnsupportedWaitStateTypeError";
|
|
355
|
+
this.waitStateType = waitStateType;
|
|
356
|
+
this.supported = DEPLOYED_WAIT_STATE_TYPES;
|
|
357
|
+
}
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
/**
|
|
361
|
+
* Assert a wait-state filter's `waitStateType` is within the deployed floor
|
|
362
|
+
* ({@link DEPLOYED_WAIT_STATE_TYPES}), throwing {@link UnsupportedWaitStateTypeError}
|
|
363
|
+
* otherwise. An absent selector is always allowed (the search is unfiltered by type). Both
|
|
364
|
+
* {@link EngineClient} adapters call this before issuing a wait-state search, so the floor is
|
|
365
|
+
* enforced identically whether the query runs against a live gateway or the in-process
|
|
366
|
+
* emulation. No-ops for an in-floor type.
|
|
367
|
+
*/
|
|
368
|
+
export function assertDeployedWaitStateType(waitStateType?: WaitStateType): void {
|
|
369
|
+
if (waitStateType !== undefined && !DEPLOYED_WAIT_STATE_TYPES.includes(waitStateType)) {
|
|
370
|
+
throw new UnsupportedWaitStateTypeError(waitStateType);
|
|
371
|
+
}
|
|
372
|
+
}
|
|
373
|
+
|
|
312
374
|
/**
|
|
313
375
|
* A single parked (waiting) element instance as
|
|
314
376
|
* {@link EngineClient.searchElementInstanceWaitStates} reports it. Every variant carries the
|
|
@@ -352,6 +414,10 @@ export interface ElementInstanceFilter {
|
|
|
352
414
|
* Selectors for an element-instance *wait-state* search: which process instance, element,
|
|
353
415
|
* and/or kind of wait to match. Used by {@link EngineClient.searchElementInstanceWaitStates}.
|
|
354
416
|
* There is deliberately no lifecycle-`state` selector — a wait state is always an active park.
|
|
417
|
+
*
|
|
418
|
+
* A `waitStateType` selector must fall within the deployed floor
|
|
419
|
+
* ({@link DEPLOYED_WAIT_STATE_TYPES}); a value outside it is rejected with
|
|
420
|
+
* {@link UnsupportedWaitStateTypeError} (the live gateway answers HTTP 422).
|
|
355
421
|
*/
|
|
356
422
|
export interface ElementInstanceWaitStateFilter {
|
|
357
423
|
processInstanceKey?: string;
|
|
@@ -512,11 +578,19 @@ export interface EngineClient {
|
|
|
512
578
|
): Promise<ElementInstanceSummary[]>;
|
|
513
579
|
/**
|
|
514
580
|
* Search the *wait states* of element instances
|
|
515
|
-
* (`POST /v2/element-instances/wait-states/search`) —
|
|
516
|
-
*
|
|
517
|
-
*
|
|
518
|
-
*
|
|
519
|
-
*
|
|
581
|
+
* (`POST /v2/element-instances/wait-states/search`) — the parks the deployed engine's
|
|
582
|
+
* read model serves. A zero-wait read; each result is discriminated by `waitStateType`
|
|
583
|
+
* (see {@link ElementInstanceWaitState}), so a consumer can read the job/message parks a
|
|
584
|
+
* {@link searchUserTasks} cannot surface.
|
|
585
|
+
*
|
|
586
|
+
* **Deployed floor.** The currently deployed nanobpmn gateway implements only
|
|
587
|
+
* `JOB | MESSAGE` ({@link DEPLOYED_WAIT_STATE_TYPES}) — a `USER_TASK` park is read via
|
|
588
|
+
* {@link searchUserTasks}, not here. A `waitStateType` filter outside the floor is rejected
|
|
589
|
+
* with {@link UnsupportedWaitStateTypeError} (the live gateway answers HTTP 422); an
|
|
590
|
+
* unfiltered search returns only in-floor parks. The `waitStateType` union is
|
|
591
|
+
* forward-looking (richer engines may serve more), but this adapter contract pins the
|
|
592
|
+
* floor so a query cannot pass in emulation while a real engine rejects it. Ref:
|
|
593
|
+
* Magikcraft/nano-bpm#1042.
|
|
520
594
|
*/
|
|
521
595
|
searchElementInstanceWaitStates(
|
|
522
596
|
filter?: ElementInstanceWaitStateFilter,
|
|
@@ -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 {
|