@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
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { EngineClient, ElementInstanceState, ElementInstanceSummary, ElementInstanceFilter, ElementInstanceWaitState, ElementInstanceWaitStateFilter, WaitStateType, FormSchema, IncidentFilter, IncidentState, IncidentSummary, JobHandler, ProcessInstanceSnapshot, ProcessInstanceState, UserTaskState, UserTaskSummary, UserTaskFilter, WorkerSubscription } from "../core/host.ts";
|
|
1
|
+
import type { EngineClient, ElementInstanceState, ElementInstanceSummary, ElementInstanceFilter, ElementInstanceWaitState, ElementInstanceWaitStateFilter, WaitStateType, FormSchema, IncidentFilter, IncidentState, IncidentSummary, JobFilter, JobHandler, JobSummary, ProcessInstanceSnapshot, ProcessInstanceState, UserTaskState, UserTaskSummary, UserTaskFilter, VariableFilter, VariableSummary, WorkerSubscription } from "../core/host.ts";
|
|
2
2
|
import type { EngineSdkClient } from "./sdk.ts";
|
|
3
3
|
/** Coerce an engine response's process-instance key to a non-empty string, or
|
|
4
4
|
* throw — a missing key means a malformed/partial response, not a real instance. */
|
|
@@ -40,6 +40,21 @@ export declare function mapElementInstanceWaitStateRow(row: Record<string, unkno
|
|
|
40
40
|
* `errorMessage` are best-effort diagnostics: present when the engine reports them, omitted
|
|
41
41
|
* otherwise. The single source of truth `searchIncidents` extracts through. */
|
|
42
42
|
export declare function mapIncidentRow(row: Record<string, unknown>, log: Log): IncidentSummary | undefined;
|
|
43
|
+
/** Map one engine variable row onto a {@link VariableSummary}, or `undefined` when the row is
|
|
44
|
+
* malformed (missing the `variableKey`, owning `processInstanceKey`, or `name` — the identity a
|
|
45
|
+
* caller needs). `value` is the engine's serialized JSON value (a string); a non-string value is
|
|
46
|
+
* JSON-encoded so the field is always a string a caller can `JSON.parse`. `scopeKey` falls back to
|
|
47
|
+
* the process-instance key for a process-level variable that omits it. `isTruncated` is the
|
|
48
|
+
* engine's large-value-clipped flag, defaulting to `false`. The single source of truth
|
|
49
|
+
* `searchVariables` extracts through. */
|
|
50
|
+
export declare function mapVariableRow(row: Record<string, unknown>, log: Log): VariableSummary | undefined;
|
|
51
|
+
/** Map one engine job row onto a {@link JobSummary}, or `undefined` when the row is malformed
|
|
52
|
+
* (missing the `jobKey`, `type`, or owning `processInstanceKey`). `worker`/`retries`/`elementId`/
|
|
53
|
+
* `deadline` are best-effort diagnostics: present when the engine reports them, omitted otherwise
|
|
54
|
+
* — an unset `worker` is the "queued, not leased" signal, so it must be absent rather than `""`.
|
|
55
|
+
* `state` is passed through as a bare string (the engine's job-state enum is broad). The single
|
|
56
|
+
* source of truth `searchJobs` extracts through. */
|
|
57
|
+
export declare function mapJobRow(row: Record<string, unknown>, log: Log): JobSummary | undefined;
|
|
43
58
|
/** A job as delivered to a nano-sdk job handler: the frame fields plus the
|
|
44
59
|
* acknowledgement actions the handler must call. */
|
|
45
60
|
export interface NanoSdkActivatedJob {
|
|
@@ -135,6 +150,17 @@ export interface NanoSdkClient {
|
|
|
135
150
|
filter?: Record<string, unknown>;
|
|
136
151
|
page?: Record<string, unknown>;
|
|
137
152
|
}, consistency?: unknown, options?: unknown): Promise<Record<string, unknown>>;
|
|
153
|
+
searchVariables(input: {
|
|
154
|
+
filter?: Record<string, unknown>;
|
|
155
|
+
page?: Record<string, unknown>;
|
|
156
|
+
}, consistency?: unknown, options?: unknown): Promise<Record<string, unknown>>;
|
|
157
|
+
searchJobs(input: {
|
|
158
|
+
filter?: Record<string, unknown>;
|
|
159
|
+
page?: Record<string, unknown>;
|
|
160
|
+
}, consistency?: unknown, options?: unknown): Promise<Record<string, unknown>>;
|
|
161
|
+
getProcessDefinitionXml(input: {
|
|
162
|
+
processDefinitionKey: string;
|
|
163
|
+
}, consistency?: unknown, options?: unknown): Promise<unknown>;
|
|
138
164
|
resolveIncident(input: {
|
|
139
165
|
incidentKey: string;
|
|
140
166
|
}, options?: unknown): Promise<unknown>;
|
|
@@ -215,6 +241,9 @@ export declare class SdkEngineClient implements EngineClient {
|
|
|
215
241
|
searchElementInstanceWaitStates(filter?: ElementInstanceWaitStateFilter): Promise<ElementInstanceWaitState[]>;
|
|
216
242
|
getElementInstance(elementInstanceKey: string): Promise<ElementInstanceSummary | null>;
|
|
217
243
|
searchIncidents(filter?: IncidentFilter): Promise<IncidentSummary[]>;
|
|
244
|
+
searchVariables(filter?: VariableFilter): Promise<VariableSummary[]>;
|
|
245
|
+
searchJobs(filter?: JobFilter): Promise<JobSummary[]>;
|
|
246
|
+
getProcessDefinitionXml(processDefinitionKey: string): Promise<string | null>;
|
|
218
247
|
resolveIncident(input: {
|
|
219
248
|
incidentKey: string;
|
|
220
249
|
}): Promise<void>;
|
|
@@ -320,6 +320,91 @@ export function mapIncidentRow(row, log) {
|
|
|
320
320
|
state: normalizeIncidentState(row.state),
|
|
321
321
|
};
|
|
322
322
|
}
|
|
323
|
+
/** Map one engine variable row onto a {@link VariableSummary}, or `undefined` when the row is
|
|
324
|
+
* malformed (missing the `variableKey`, owning `processInstanceKey`, or `name` — the identity a
|
|
325
|
+
* caller needs). `value` is the engine's serialized JSON value (a string); a non-string value is
|
|
326
|
+
* JSON-encoded so the field is always a string a caller can `JSON.parse`. `scopeKey` falls back to
|
|
327
|
+
* the process-instance key for a process-level variable that omits it. `isTruncated` is the
|
|
328
|
+
* engine's large-value-clipped flag, defaulting to `false`. The single source of truth
|
|
329
|
+
* `searchVariables` extracts through. */
|
|
330
|
+
export function mapVariableRow(row, log) {
|
|
331
|
+
const variableKey = presentEngineKey(row.variableKey);
|
|
332
|
+
if (variableKey === undefined) {
|
|
333
|
+
log("warn", "skipping variable with no variableKey in engine response");
|
|
334
|
+
return undefined;
|
|
335
|
+
}
|
|
336
|
+
const processInstanceKey = presentEngineKey(row.processInstanceKey);
|
|
337
|
+
if (processInstanceKey === undefined) {
|
|
338
|
+
log("warn", "skipping variable with no processInstanceKey in engine response", {
|
|
339
|
+
variableKey,
|
|
340
|
+
});
|
|
341
|
+
return undefined;
|
|
342
|
+
}
|
|
343
|
+
const name = presentText(row.name);
|
|
344
|
+
if (name === undefined) {
|
|
345
|
+
log("warn", "skipping variable with no name in engine response", { variableKey });
|
|
346
|
+
return undefined;
|
|
347
|
+
}
|
|
348
|
+
// A process-level variable is scoped to the process instance itself; the engine may omit
|
|
349
|
+
// `scopeKey` for it, so fall back to the process-instance key rather than dropping the row.
|
|
350
|
+
const scopeKey = presentEngineKey(row.scopeKey) ?? processInstanceKey;
|
|
351
|
+
// The engine serializes `value` as a JSON string. Keep a string as-is; encode any other type
|
|
352
|
+
// (an object/array/number a permissive engine might inline) so the field is always a string the
|
|
353
|
+
// caller can `JSON.parse`.
|
|
354
|
+
const value = typeof row.value === "string" ? row.value : JSON.stringify(row.value ?? null);
|
|
355
|
+
return {
|
|
356
|
+
variableKey,
|
|
357
|
+
name,
|
|
358
|
+
value,
|
|
359
|
+
scopeKey,
|
|
360
|
+
processInstanceKey,
|
|
361
|
+
isTruncated: row.isTruncated === true,
|
|
362
|
+
};
|
|
363
|
+
}
|
|
364
|
+
/** Map one engine job row onto a {@link JobSummary}, or `undefined` when the row is malformed
|
|
365
|
+
* (missing the `jobKey`, `type`, or owning `processInstanceKey`). `worker`/`retries`/`elementId`/
|
|
366
|
+
* `deadline` are best-effort diagnostics: present when the engine reports them, omitted otherwise
|
|
367
|
+
* — an unset `worker` is the "queued, not leased" signal, so it must be absent rather than `""`.
|
|
368
|
+
* `state` is passed through as a bare string (the engine's job-state enum is broad). The single
|
|
369
|
+
* source of truth `searchJobs` extracts through. */
|
|
370
|
+
export function mapJobRow(row, log) {
|
|
371
|
+
const jobKey = presentEngineKey(row.jobKey);
|
|
372
|
+
if (jobKey === undefined) {
|
|
373
|
+
log("warn", "skipping job with no jobKey in engine response");
|
|
374
|
+
return undefined;
|
|
375
|
+
}
|
|
376
|
+
const type = presentText(row.type);
|
|
377
|
+
if (type === undefined) {
|
|
378
|
+
log("warn", "skipping job with no type in engine response", { jobKey });
|
|
379
|
+
return undefined;
|
|
380
|
+
}
|
|
381
|
+
const processInstanceKey = presentEngineKey(row.processInstanceKey);
|
|
382
|
+
if (processInstanceKey === undefined) {
|
|
383
|
+
log("warn", "skipping job with no processInstanceKey in engine response", { jobKey });
|
|
384
|
+
return undefined;
|
|
385
|
+
}
|
|
386
|
+
const state = presentText(row.state);
|
|
387
|
+
if (state === undefined) {
|
|
388
|
+
log("warn", "skipping job with no state in engine response", { jobKey });
|
|
389
|
+
return undefined;
|
|
390
|
+
}
|
|
391
|
+
const worker = presentText(row.worker);
|
|
392
|
+
const retries = typeof row.retries === "number" && Number.isFinite(row.retries)
|
|
393
|
+
? row.retries
|
|
394
|
+
: undefined;
|
|
395
|
+
const elementId = presentText(row.elementId);
|
|
396
|
+
const deadline = presentText(row.deadline);
|
|
397
|
+
return {
|
|
398
|
+
jobKey,
|
|
399
|
+
type,
|
|
400
|
+
state,
|
|
401
|
+
processInstanceKey,
|
|
402
|
+
...(worker ? { worker } : {}),
|
|
403
|
+
...(retries !== undefined ? { retries } : {}),
|
|
404
|
+
...(elementId ? { elementId } : {}),
|
|
405
|
+
...(deadline ? { deadline } : {}),
|
|
406
|
+
};
|
|
407
|
+
}
|
|
323
408
|
/**
|
|
324
409
|
* An `EngineClient` backed entirely by a `@nanobpm/nano-sdk` client. Deploy, create,
|
|
325
410
|
* message, and user-task calls map one-to-one onto the SDK's client methods; workers
|
|
@@ -484,7 +569,12 @@ export class SdkEngineClient {
|
|
|
484
569
|
});
|
|
485
570
|
return [];
|
|
486
571
|
}
|
|
487
|
-
|
|
572
|
+
const processDefinitionKey = presentEngineKey(it.processDefinitionKey);
|
|
573
|
+
return [{
|
|
574
|
+
processInstanceKey: String(key),
|
|
575
|
+
state,
|
|
576
|
+
...(processDefinitionKey ? { processDefinitionKey } : {}),
|
|
577
|
+
}];
|
|
488
578
|
});
|
|
489
579
|
}
|
|
490
580
|
async searchElementInstances(filter) {
|
|
@@ -575,6 +665,78 @@ export class SdkEngineClient {
|
|
|
575
665
|
return mapped ? [mapped] : [];
|
|
576
666
|
});
|
|
577
667
|
}
|
|
668
|
+
async searchVariables(filter) {
|
|
669
|
+
// Same shape as `searchIncidents`: build the engine filter from the transport-agnostic
|
|
670
|
+
// selectors, read at zero-wait consistency (a variable search is eventually consistent), and
|
|
671
|
+
// map each row through the single `mapVariableRow` gate, dropping malformed rows.
|
|
672
|
+
const f = {};
|
|
673
|
+
const processInstanceKey = presentEngineKey(filter?.processInstanceKey);
|
|
674
|
+
if (processInstanceKey)
|
|
675
|
+
f.processInstanceKey = processInstanceKey;
|
|
676
|
+
const scopeKey = presentEngineKey(filter?.scopeKey);
|
|
677
|
+
if (scopeKey)
|
|
678
|
+
f.scopeKey = scopeKey;
|
|
679
|
+
const name = filter?.name?.trim();
|
|
680
|
+
if (name)
|
|
681
|
+
f.name = name;
|
|
682
|
+
const body = await this.client.searchVariables({ filter: f }, { consistency: { waitUpToMs: 0 } });
|
|
683
|
+
const items = Array.isArray(body.items) ? body.items.filter(isRecord) : [];
|
|
684
|
+
return items.flatMap((it) => {
|
|
685
|
+
const mapped = mapVariableRow(it, this.log);
|
|
686
|
+
return mapped ? [mapped] : [];
|
|
687
|
+
});
|
|
688
|
+
}
|
|
689
|
+
async searchJobs(filter) {
|
|
690
|
+
// Same shape as `searchIncidents`: build the engine filter from the transport-agnostic
|
|
691
|
+
// selectors, read at zero-wait consistency (a job search is eventually consistent), and map
|
|
692
|
+
// each row through the single `mapJobRow` gate, dropping malformed rows.
|
|
693
|
+
const f = {};
|
|
694
|
+
const processInstanceKey = presentEngineKey(filter?.processInstanceKey);
|
|
695
|
+
if (processInstanceKey)
|
|
696
|
+
f.processInstanceKey = processInstanceKey;
|
|
697
|
+
const state = filter?.state?.trim();
|
|
698
|
+
if (state)
|
|
699
|
+
f.state = state;
|
|
700
|
+
const type = filter?.type?.trim();
|
|
701
|
+
if (type)
|
|
702
|
+
f.type = type;
|
|
703
|
+
const elementId = filter?.elementId?.trim();
|
|
704
|
+
if (elementId)
|
|
705
|
+
f.elementId = elementId;
|
|
706
|
+
const worker = filter?.worker?.trim();
|
|
707
|
+
if (worker)
|
|
708
|
+
f.worker = worker;
|
|
709
|
+
const body = await this.client.searchJobs({ filter: f }, { consistency: { waitUpToMs: 0 } });
|
|
710
|
+
const items = Array.isArray(body.items) ? body.items.filter(isRecord) : [];
|
|
711
|
+
return items.flatMap((it) => {
|
|
712
|
+
const mapped = mapJobRow(it, this.log);
|
|
713
|
+
return mapped ? [mapped] : [];
|
|
714
|
+
});
|
|
715
|
+
}
|
|
716
|
+
async getProcessDefinitionXml(processDefinitionKey) {
|
|
717
|
+
// A blank key can never address a definition — short-circuit to null rather than issue a
|
|
718
|
+
// `GET /v2/process-definitions//xml` with an empty segment. Normalize a padded-but-valid key so
|
|
719
|
+
// `" 5 "` addresses the same definition, mirroring `getElementInstance`.
|
|
720
|
+
const key = presentEngineKey(processDefinitionKey);
|
|
721
|
+
if (key === undefined)
|
|
722
|
+
return null;
|
|
723
|
+
let xml;
|
|
724
|
+
try {
|
|
725
|
+
xml = await this.client.getProcessDefinitionXml({ processDefinitionKey: key }, { consistency: { waitUpToMs: 0 } });
|
|
726
|
+
}
|
|
727
|
+
catch (err) {
|
|
728
|
+
// A 404 (no such definition) is an expected "not found", not a fault — mirror `getForm`/
|
|
729
|
+
// `getElementInstance`, which treat a failed fetch as absence (null) rather than propagating.
|
|
730
|
+
this.log("warn", "getProcessDefinitionXml: engine fetch failed", {
|
|
731
|
+
processDefinitionKey: key,
|
|
732
|
+
err: err instanceof Error ? err.message : String(err),
|
|
733
|
+
});
|
|
734
|
+
return null;
|
|
735
|
+
}
|
|
736
|
+
// The endpoint returns the raw BPMN XML as a string (204 → empty). Treat a blank/absent body
|
|
737
|
+
// as "no XML" (null) so a caller distinguishes "have it" from "don't".
|
|
738
|
+
return typeof xml === "string" && xml.trim() !== "" ? xml : null;
|
|
739
|
+
}
|
|
578
740
|
async resolveIncident(input) {
|
|
579
741
|
await this.client.resolveIncident({
|
|
580
742
|
incidentKey: requireEngineKey(input.incidentKey, "incidentKey"),
|
package/dist/runtime/index.d.ts
CHANGED
|
@@ -8,7 +8,7 @@ export { bindModeToHost, expandEnv, expandEnvString, isBindMode, loadManifest, p
|
|
|
8
8
|
export type { ActionDecl, AppManifest, BindMode, ChatSurface, DataSource, DomainField, DomainType, InstanceTracking, LlmBinding, NetworkConfig, PagesSurface, Surfaces, TaskInboxSurface, Trigger, Worker, } from "./core/manifest.ts";
|
|
9
9
|
export { collectManifestIssues, ManifestValidationError, validateManifest, } from "./core/validate.ts";
|
|
10
10
|
export type { ValidationIssue } from "./core/validate.ts";
|
|
11
|
-
export type { EngineClient, ElementInstanceState, ElementInstanceSummary, ElementInstanceFilter, ElementInstanceWaitState, ElementInstanceWaitStateFilter, WaitStateType, EngineJob, FormSchema, HostContext, HttpHandler, HttpRequest, HttpResponse, HttpServer, IncidentState, EngineIncidentState, IncidentFilter, IncidentSummary, JobHandler, ProcessInstanceSnapshot, ProcessInstanceState, SqliteDb, UserTaskState, UserTaskSummary, UserTaskFilter, WatchHandle, WorkerSubscription, } from "./core/host.ts";
|
|
11
|
+
export type { EngineClient, ElementInstanceState, ElementInstanceSummary, ElementInstanceFilter, ElementInstanceWaitState, ElementInstanceWaitStateFilter, WaitStateType, EngineJob, FormSchema, HostContext, HttpHandler, HttpRequest, HttpResponse, HttpServer, IncidentState, EngineIncidentState, IncidentFilter, IncidentSummary, JobFilter, JobHandler, JobSummary, ProcessInstanceSnapshot, ProcessInstanceState, SqliteDb, UserTaskState, UserTaskSummary, UserTaskFilter, VariableFilter, VariableSummary, WatchHandle, WorkerSubscription, } from "./core/host.ts";
|
|
12
12
|
export { buildFormSchema, parseFormSchema, pickFormLinkage, presentFormIdentifier, resolveFormIdentifier, } from "./core/form-contract.ts";
|
|
13
13
|
export type { AppApi, Mounted, RuntimeContext } from "./core/context.ts";
|
|
14
14
|
export { createLogger } from "./core/logger.ts";
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@nanobpm/urban",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.89.0",
|
|
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",
|
|
@@ -175,6 +175,74 @@ function installThemeBridge() {
|
|
|
175
175
|
window.parent.postMessage({ type: "nano-app-ready" }, window.location.origin);
|
|
176
176
|
}
|
|
177
177
|
|
|
178
|
+
// ── App-View message relay (#518) ─────────────────────────────────────────
|
|
179
|
+
// The App-View iframes this page currently mounts (renderAppView adds each one;
|
|
180
|
+
// teardown removes it so a switched-away page's frames leave the relay set). The
|
|
181
|
+
// bridge below uses this set as the trust membership: a message is only relayed
|
|
182
|
+
// if ev.source is one of THESE frames' contentWindows (a nested App-View of this
|
|
183
|
+
// page), never an arbitrary framed window.
|
|
184
|
+
/** @type {Set<HTMLIFrameElement>} */
|
|
185
|
+
const APP_VIEW_FRAMES = new Set();
|
|
186
|
+
|
|
187
|
+
// The relay primitive. A nested App-View posts to `window.parent` (this page),
|
|
188
|
+
// but this page installs no bridge, so the message is dropped: it never reaches
|
|
189
|
+
// the console host (forward-UP) nor the page's other App-Views (relay-ACROSS).
|
|
190
|
+
// This forwards a whitelisted `nano-navigate` UP to the host (chaining the same
|
|
191
|
+
// hostNavigate boundary the page's own links use, so a nested App-View can drive
|
|
192
|
+
// the console explorer — Preview DI, instance deep-links) and relays any other
|
|
193
|
+
// App-View message ACROSS to the page's sibling App-Views (e.g. Library "Reuse"
|
|
194
|
+
// → Compose fill). Deliberately generic (forward-up + broadcast-across), not a
|
|
195
|
+
// per-message special case, so the whole cross-frame failure class is closed at
|
|
196
|
+
// once rather than patched button-by-button.
|
|
197
|
+
//
|
|
198
|
+
// Trust boundary: the message must be same-origin AND from a known App-View
|
|
199
|
+
// iframe of THIS page. `ev.source` alone is insufficient — the origin pin plus
|
|
200
|
+
// the membership check are what actually gate the boundary, so a message from a
|
|
201
|
+
// non-App-View source or a cross-origin frame is never relayed.
|
|
202
|
+
/**
|
|
203
|
+
* @param {{ origin: string, source: any, data: any }} ev
|
|
204
|
+
* @param {Iterable<{ contentWindow: any }>} frames
|
|
205
|
+
* @param {{ origin: string, parent: any, embedded: boolean }} ctx
|
|
206
|
+
*/
|
|
207
|
+
function relayAppViewMessage(ev, frames, ctx) {
|
|
208
|
+
if (ev.origin !== ctx.origin) return;
|
|
209
|
+
const list = Array.isArray(frames) ? frames : [...frames];
|
|
210
|
+
let originFrame = null;
|
|
211
|
+
for (const f of list) {
|
|
212
|
+
if (f.contentWindow && f.contentWindow === ev.source) { originFrame = f; break; }
|
|
213
|
+
}
|
|
214
|
+
// Unknown source: not one of this page's App-Views — reject (trust boundary).
|
|
215
|
+
if (!originFrame) return;
|
|
216
|
+
const data = ev.data;
|
|
217
|
+
if (data && data.type === "nano-navigate") {
|
|
218
|
+
// Forward upward to the host/console, gated on the same same-origin embed
|
|
219
|
+
// boundary as hostNavigate: standalone (no same-origin console parent) there
|
|
220
|
+
// is nothing to forward to, so the message stays within the page.
|
|
221
|
+
if (ctx.embedded && ctx.parent) ctx.parent.postMessage(data, ctx.origin);
|
|
222
|
+
return;
|
|
223
|
+
}
|
|
224
|
+
// Relay across to the page's OTHER App-Views (never back to the originator).
|
|
225
|
+
for (const f of list) {
|
|
226
|
+
if (f === originFrame) continue;
|
|
227
|
+
const w = f.contentWindow;
|
|
228
|
+
if (w) w.postMessage(data, ctx.origin);
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
// Install the App-View relay as a single page-level `message` listener. Unlike
|
|
233
|
+
// the theme bridge this is NOT gated on NANO_EMBEDDED: the sibling relay-across
|
|
234
|
+
// works within a standalone page too; only the forward-up leg is embed-gated
|
|
235
|
+
// (via ctx.embedded in relayAppViewMessage).
|
|
236
|
+
function installAppViewBridge() {
|
|
237
|
+
window.addEventListener("message", (ev) => {
|
|
238
|
+
relayAppViewMessage(ev, APP_VIEW_FRAMES, {
|
|
239
|
+
origin: window.location.origin,
|
|
240
|
+
parent: window.parent,
|
|
241
|
+
embedded: NANO_EMBEDDED,
|
|
242
|
+
});
|
|
243
|
+
});
|
|
244
|
+
}
|
|
245
|
+
|
|
178
246
|
// Resolve every app endpoint against where THIS module was actually served from,
|
|
179
247
|
// so absolute-looking "/app/…" paths work both at the origin root (direct run,
|
|
180
248
|
// e.g. the CLI on :3000) and under a path-prefixed reverse proxy (the Nano
|
|
@@ -2769,6 +2837,13 @@ function renderAppView(node) {
|
|
|
2769
2837
|
});
|
|
2770
2838
|
|
|
2771
2839
|
const section = el("section", { class: "pc-appview" + (fill ? " pc-appview-fill" : "") });
|
|
2840
|
+
// Register this frame in the App-View relay set (#518) so the page-level bridge
|
|
2841
|
+
// can (a) recognise its outbound postMessages as coming from a known App-View of
|
|
2842
|
+
// this page and (b) address it as a sibling target for another App-View's relay.
|
|
2843
|
+
// A teardown disposer drops it again, so a switched-away page's frames don't
|
|
2844
|
+
// linger as phantom relay endpoints.
|
|
2845
|
+
APP_VIEW_FRAMES.add(frame);
|
|
2846
|
+
disposers.push(() => APP_VIEW_FRAMES.delete(frame));
|
|
2772
2847
|
// The title is an <h2> (the tag EVERY card renderer uses and makeCollapsible lifts into the
|
|
2773
2848
|
// collapse header), not a <div>: a collapsible appView's title must be lifted out of the body,
|
|
2774
2849
|
// otherwise it double-renders (header label + leftover in-body title). The .pc-appview-title CSS
|
|
@@ -2934,6 +3009,7 @@ function boot() {
|
|
|
2934
3009
|
PARAM = parseRoute().param;
|
|
2935
3010
|
NANO_EMBEDDED = computeEmbedded();
|
|
2936
3011
|
installThemeBridge();
|
|
3012
|
+
installAppViewBridge();
|
|
2937
3013
|
window.addEventListener("hashchange", renderPage);
|
|
2938
3014
|
// Re-render when the viewport crosses the mobile breakpoint so a Tier-2 'mobile'
|
|
2939
3015
|
// variant swaps in/out on rotation or a window resize (the grid card flip, nav
|
|
@@ -2953,4 +3029,4 @@ function boot() {
|
|
|
2953
3029
|
// renderer functions / RENDERERS registry can be imported directly.
|
|
2954
3030
|
if (typeof document !== "undefined" && document.getElementById("page")) boot();
|
|
2955
3031
|
|
|
2956
|
-
export { RENDERERS, renderText, renderButton, renderProse, renderNav, renderAppView, navLink, sameOriginPath, wireNavBadge, applyNavBadge, teardown, fmtCellValue, gridCell, buildDetailForm, buildEngineForm, evalDetailCondition, normalizeDetailOptions, chevronToggle, setChevronOpen };
|
|
3032
|
+
export { RENDERERS, renderText, renderButton, renderProse, renderNav, renderAppView, relayAppViewMessage, navLink, sameOriginPath, wireNavBadge, applyNavBadge, teardown, fmtCellValue, gridCell, buildDetailForm, buildEngineForm, evalDetailCondition, normalizeDetailOptions, chevronToggle, setChevronOpen };
|
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
import { test } from "node:test";
|
|
8
8
|
import assert from "node:assert/strict";
|
|
9
9
|
import { PAGE_NODE_TYPES } from "../core/page-nodes.ts";
|
|
10
|
-
import { RENDERERS, renderText, renderAppView, navLink, sameOriginPath, wireNavBadge, applyNavBadge, teardown, fmtCellValue, gridCell } from "./runtime.browser.js";
|
|
10
|
+
import { RENDERERS, renderText, renderAppView, relayAppViewMessage, navLink, sameOriginPath, wireNavBadge, applyNavBadge, teardown, fmtCellValue, gridCell } from "./runtime.browser.js";
|
|
11
11
|
|
|
12
12
|
// ── Minimal fake DOM ────────────────────────────────────────────────────────
|
|
13
13
|
// Just enough of the Element/Document surface that el()/renderText touch:
|
|
@@ -574,3 +574,128 @@ test("#338: a param-scoped nav badge with no route param degrades to 0 without q
|
|
|
574
574
|
assert.equal(pill.textContent, "0", "badge degrades to 0 rather than an empty-string-filtered count");
|
|
575
575
|
assert.equal(pill.hidden, false, "0 is shown (hideWhenZero is off) rather than a stale count");
|
|
576
576
|
});
|
|
577
|
+
|
|
578
|
+
// ── #518: App-View message relay (host-up + sibling-across bridge) ───────────
|
|
579
|
+
// A nested App-View posts to `window.parent` (the Urban page). Before this fix
|
|
580
|
+
// the page installed no bridge for those inbound postMessages, so a
|
|
581
|
+
// `nano-navigate` never chained up to the console host (DI Preview did nothing)
|
|
582
|
+
// and a sibling-directed message never reached the page's other App-Views
|
|
583
|
+
// (Library "Reuse" filled nothing). relayAppViewMessage is the primitive that
|
|
584
|
+
// closes that whole class: forward-up for nano-navigate, broadcast-across for
|
|
585
|
+
// everything else, both behind a strict same-origin + known-App-View-source
|
|
586
|
+
// trust boundary. These drive the primitive directly with fake frame windows.
|
|
587
|
+
|
|
588
|
+
// A minimal Window stand-in that records its postMessage calls, used both as an
|
|
589
|
+
// App-View iframe's contentWindow and as the host `window.parent`.
|
|
590
|
+
function fakeWindow(): { posts: Array<{ data: unknown; origin: string }> } & {
|
|
591
|
+
postMessage(data: unknown, origin: string): void;
|
|
592
|
+
} {
|
|
593
|
+
const posts: Array<{ data: unknown; origin: string }> = [];
|
|
594
|
+
return {
|
|
595
|
+
posts,
|
|
596
|
+
postMessage(data: unknown, origin: string): void {
|
|
597
|
+
posts.push({ data, origin });
|
|
598
|
+
},
|
|
599
|
+
};
|
|
600
|
+
}
|
|
601
|
+
|
|
602
|
+
// An App-View iframe stand-in exposing just the `.contentWindow` the relay reads.
|
|
603
|
+
function fakeFrame(win: unknown): { contentWindow: unknown } {
|
|
604
|
+
return { contentWindow: win };
|
|
605
|
+
}
|
|
606
|
+
|
|
607
|
+
const ORIGIN = "https://app.test";
|
|
608
|
+
|
|
609
|
+
test("#518: relayAppViewMessage forwards a child App-View's nano-navigate UP to the host", () => {
|
|
610
|
+
const parent = fakeWindow();
|
|
611
|
+
const composeWin = fakeWindow();
|
|
612
|
+
const compose = fakeFrame(composeWin);
|
|
613
|
+
const frames = [compose];
|
|
614
|
+
const data = { type: "nano-navigate", target: "definitionPreview", params: { xml: "<x/>" } };
|
|
615
|
+
|
|
616
|
+
relayAppViewMessage(
|
|
617
|
+
{ origin: ORIGIN, source: composeWin, data },
|
|
618
|
+
frames,
|
|
619
|
+
{ origin: ORIGIN, parent, embedded: true },
|
|
620
|
+
);
|
|
621
|
+
|
|
622
|
+
assert.equal(parent.posts.length, 1, "the host (window.parent) must receive the forwarded nano-navigate");
|
|
623
|
+
assert.deepEqual(parent.posts[0], { data, origin: ORIGIN });
|
|
624
|
+
// A forward-up is NOT also broadcast back into the page's App-Views.
|
|
625
|
+
assert.equal(composeWin.posts.length, 0, "nano-navigate is forwarded up, not relayed across");
|
|
626
|
+
});
|
|
627
|
+
|
|
628
|
+
test("#518: relayAppViewMessage relays a sibling-directed message ACROSS to the other App-Views only", () => {
|
|
629
|
+
const parent = fakeWindow();
|
|
630
|
+
const libraryWin = fakeWindow();
|
|
631
|
+
const composeWin = fakeWindow();
|
|
632
|
+
const stagedWin = fakeWindow();
|
|
633
|
+
const library = fakeFrame(libraryWin);
|
|
634
|
+
const compose = fakeFrame(composeWin);
|
|
635
|
+
const staged = fakeFrame(stagedWin);
|
|
636
|
+
const frames = [library, compose, staged];
|
|
637
|
+
const data = { type: "nano-delivery-graph-compose-fill", xml: "<definitions/>" };
|
|
638
|
+
|
|
639
|
+
relayAppViewMessage(
|
|
640
|
+
{ origin: ORIGIN, source: libraryWin, data },
|
|
641
|
+
frames,
|
|
642
|
+
{ origin: ORIGIN, parent, embedded: true },
|
|
643
|
+
);
|
|
644
|
+
|
|
645
|
+
// Delivered to BOTH siblings...
|
|
646
|
+
assert.deepEqual(composeWin.posts, [{ data, origin: ORIGIN }], "sibling Compose must receive the relayed message");
|
|
647
|
+
assert.deepEqual(stagedWin.posts, [{ data, origin: ORIGIN }], "sibling Staged must receive the relayed message");
|
|
648
|
+
// ...but never echoed back to the originator, nor forwarded up to the host.
|
|
649
|
+
assert.equal(libraryWin.posts.length, 0, "the originating App-View must not receive its own message back");
|
|
650
|
+
assert.equal(parent.posts.length, 0, "a sibling-directed (non-navigate) message must not be forwarded to the host");
|
|
651
|
+
});
|
|
652
|
+
|
|
653
|
+
test("#518: relayAppViewMessage does NOT forward up when the page is standalone (not embedded)", () => {
|
|
654
|
+
const parent = fakeWindow();
|
|
655
|
+
const composeWin = fakeWindow();
|
|
656
|
+
const compose = fakeFrame(composeWin);
|
|
657
|
+
const data = { type: "nano-navigate", target: "processExplorer", params: {} };
|
|
658
|
+
|
|
659
|
+
relayAppViewMessage(
|
|
660
|
+
{ origin: ORIGIN, source: composeWin, data },
|
|
661
|
+
[compose],
|
|
662
|
+
{ origin: ORIGIN, parent, embedded: false },
|
|
663
|
+
);
|
|
664
|
+
|
|
665
|
+
assert.equal(parent.posts.length, 0, "standalone: there is no same-origin console host to forward to");
|
|
666
|
+
});
|
|
667
|
+
|
|
668
|
+
test("#518: relayAppViewMessage rejects a message from a NON-App-View source (trust boundary)", () => {
|
|
669
|
+
const parent = fakeWindow();
|
|
670
|
+
const composeWin = fakeWindow();
|
|
671
|
+
const compose = fakeFrame(composeWin);
|
|
672
|
+
const strangerWin = fakeWindow(); // some other framed window, not one of our App-Views
|
|
673
|
+
const data = { type: "nano-navigate", target: "definitionPreview", params: {} };
|
|
674
|
+
|
|
675
|
+
relayAppViewMessage(
|
|
676
|
+
{ origin: ORIGIN, source: strangerWin, data },
|
|
677
|
+
[compose],
|
|
678
|
+
{ origin: ORIGIN, parent, embedded: true },
|
|
679
|
+
);
|
|
680
|
+
|
|
681
|
+
assert.equal(parent.posts.length, 0, "a message from an unknown source must not be forwarded up");
|
|
682
|
+
assert.equal(composeWin.posts.length, 0, "a message from an unknown source must not be relayed across");
|
|
683
|
+
});
|
|
684
|
+
|
|
685
|
+
test("#518: relayAppViewMessage rejects a CROSS-ORIGIN message even from a known App-View window", () => {
|
|
686
|
+
const parent = fakeWindow();
|
|
687
|
+
const composeWin = fakeWindow();
|
|
688
|
+
const stagedWin = fakeWindow();
|
|
689
|
+
const compose = fakeFrame(composeWin);
|
|
690
|
+
const staged = fakeFrame(stagedWin);
|
|
691
|
+
const data = { type: "nano-delivery-graph-compose-fill", xml: "<x/>" };
|
|
692
|
+
|
|
693
|
+
relayAppViewMessage(
|
|
694
|
+
{ origin: "https://evil.test", source: composeWin, data },
|
|
695
|
+
[compose, staged],
|
|
696
|
+
{ origin: ORIGIN, parent, embedded: true },
|
|
697
|
+
);
|
|
698
|
+
|
|
699
|
+
assert.equal(stagedWin.posts.length, 0, "a cross-origin message must not be relayed across");
|
|
700
|
+
assert.equal(parent.posts.length, 0, "a cross-origin message must not be forwarded up");
|
|
701
|
+
});
|
package/src/runtime/core/host.ts
CHANGED
|
@@ -229,6 +229,10 @@ export type UserTaskState = "CREATED" | "COMPLETED" | "CANCELED" | "FAILED";
|
|
|
229
229
|
export interface ProcessInstanceSnapshot {
|
|
230
230
|
readonly processInstanceKey: string;
|
|
231
231
|
readonly state: ProcessInstanceState;
|
|
232
|
+
/** The key of the deployed process definition this instance runs — the handle a caller feeds to
|
|
233
|
+
* {@link EngineClient.getProcessDefinitionXml} to fetch the deployed BPMN (the instance →
|
|
234
|
+
* `processDefinitionKey` → deployed XML path, no repo checkout). Absent when the engine omits it. */
|
|
235
|
+
readonly processDefinitionKey?: string;
|
|
232
236
|
}
|
|
233
237
|
|
|
234
238
|
/**
|
|
@@ -487,6 +491,76 @@ export interface FormSchema {
|
|
|
487
491
|
readonly schema: Record<string, unknown>;
|
|
488
492
|
}
|
|
489
493
|
|
|
494
|
+
/**
|
|
495
|
+
* Selectors for a variable search: which process instance and/or scope to match, optionally
|
|
496
|
+
* narrowed to a single variable `name`. Used by {@link EngineClient.searchVariables}. An
|
|
497
|
+
* eventually consistent (zero-wait) read; an unset selector matches every variable in scope.
|
|
498
|
+
*/
|
|
499
|
+
export interface VariableFilter {
|
|
500
|
+
processInstanceKey?: string;
|
|
501
|
+
scopeKey?: string;
|
|
502
|
+
name?: string;
|
|
503
|
+
}
|
|
504
|
+
|
|
505
|
+
/**
|
|
506
|
+
* A single process variable as {@link EngineClient.searchVariables} reports it — the read
|
|
507
|
+
* counterpart to the write-only {@link EngineClient.setVariables}. Mirrors the engine's v2
|
|
508
|
+
* `/variables/search` row: the variable's own `variableKey`, its `name`, its serialized JSON
|
|
509
|
+
* `value` (a string — an object/array value arrives JSON-encoded, so a caller `JSON.parse`s it),
|
|
510
|
+
* the `scopeKey` it is directly defined in (a process-instance key for a process-level variable,
|
|
511
|
+
* an element-instance key for a local one) and the owning `processInstanceKey`. `isTruncated` is
|
|
512
|
+
* `true` when the engine clipped a large `value` for the read model — the caller then knows the
|
|
513
|
+
* `value` is a preview, not the whole payload. This is the only way to read a parked token's
|
|
514
|
+
* business payload (`prKey`, `round`, an escalation `question`/`answer`, `resolution`,
|
|
515
|
+
* `directive`); the ADR 0065 projections carry lifecycle/position, not the payload.
|
|
516
|
+
*/
|
|
517
|
+
export interface VariableSummary {
|
|
518
|
+
readonly variableKey: string;
|
|
519
|
+
readonly name: string;
|
|
520
|
+
readonly value: string;
|
|
521
|
+
readonly scopeKey: string;
|
|
522
|
+
readonly processInstanceKey: string;
|
|
523
|
+
readonly isTruncated: boolean;
|
|
524
|
+
}
|
|
525
|
+
|
|
526
|
+
/**
|
|
527
|
+
* Selectors for a job search: which process instance, lifecycle `state`, job `type`, `elementId`,
|
|
528
|
+
* and/or leasing `worker` to match. Used by {@link EngineClient.searchJobs}. `state` is kept a
|
|
529
|
+
* transport-agnostic bare string (the engine's job-state enum is broad and forward-looking — e.g.
|
|
530
|
+
* `CREATED`/`COMPLETED`/`FAILED`/`TIMED_OUT`), so a new engine state flows through without having
|
|
531
|
+
* to be enumerated here. An eventually consistent (zero-wait) read; an unset selector matches
|
|
532
|
+
* every job.
|
|
533
|
+
*/
|
|
534
|
+
export interface JobFilter {
|
|
535
|
+
processInstanceKey?: string;
|
|
536
|
+
state?: string;
|
|
537
|
+
type?: string;
|
|
538
|
+
elementId?: string;
|
|
539
|
+
worker?: string;
|
|
540
|
+
}
|
|
541
|
+
|
|
542
|
+
/**
|
|
543
|
+
* A single job as {@link EngineClient.searchJobs} reports it — mirrors the engine's v2
|
|
544
|
+
* `/jobs/search` row. Carries the `jobKey` a caller passes to
|
|
545
|
+
* {@link EngineClient.updateJobRetries} (the only source of a `jobKey` for a job NOT already
|
|
546
|
+
* behind a `jobNoRetries` incident, which {@link searchIncidents} surfaces), the job `type` and
|
|
547
|
+
* lifecycle `state` (a bare string — see {@link JobFilter}), the owning `processInstanceKey`, and
|
|
548
|
+
* the best-effort diagnostics `worker`/`retries`/`elementId`/`deadline`. The `worker` field is the
|
|
549
|
+
* "is it actually stuck" signal: a `CREATED` job WITH a `worker` set has been leased by an agent,
|
|
550
|
+
* while one with NONE set is merely queued — so `worker` is omitted (absent) when unset. `retries`,
|
|
551
|
+
* `elementId`, and `deadline` are likewise present when the engine reports them, omitted otherwise.
|
|
552
|
+
*/
|
|
553
|
+
export interface JobSummary {
|
|
554
|
+
readonly jobKey: string;
|
|
555
|
+
readonly type: string;
|
|
556
|
+
readonly state: string;
|
|
557
|
+
readonly processInstanceKey: string;
|
|
558
|
+
readonly worker?: string;
|
|
559
|
+
readonly retries?: number;
|
|
560
|
+
readonly elementId?: string;
|
|
561
|
+
readonly deadline?: string;
|
|
562
|
+
}
|
|
563
|
+
|
|
490
564
|
/**
|
|
491
565
|
* EngineClient is the seam onto a Nano engine. The SDK/REST-backed adapter implements
|
|
492
566
|
* it against a live engine; tests implement it in-memory. Core modules depend only on this.
|
|
@@ -616,6 +690,40 @@ export interface EngineClient {
|
|
|
616
690
|
* they answer "how far has this instance progressed", this answers "why is it stuck".
|
|
617
691
|
*/
|
|
618
692
|
searchIncidents(filter?: IncidentFilter): Promise<IncidentSummary[]>;
|
|
693
|
+
/**
|
|
694
|
+
* Search process variables by owning process instance and/or scope, optionally narrowed to a
|
|
695
|
+
* single `name` (`POST /v2/variables/search`). The read counterpart to the write-only
|
|
696
|
+
* {@link setVariables}: it is the only way to see a parked token's business payload (`prKey`,
|
|
697
|
+
* `round`, an escalation `question`/`answer`, `resolution`, `directive`) — the ADR 0065
|
|
698
|
+
* projections carry lifecycle/position, not the payload. An eventually consistent (zero-wait)
|
|
699
|
+
* read; each {@link VariableSummary} carries the variable's serialized JSON `value` (a string)
|
|
700
|
+
* and an `isTruncated` flag set when the engine clipped a large value for the read model.
|
|
701
|
+
*/
|
|
702
|
+
searchVariables(filter?: VariableFilter): Promise<VariableSummary[]>;
|
|
703
|
+
/**
|
|
704
|
+
* Search jobs by owning process instance, lifecycle `state`, job `type`, `elementId`, and/or
|
|
705
|
+
* leasing `worker` (`POST /v2/jobs/search`). This is the "is it actually stuck" read: a
|
|
706
|
+
* `CREATED` job WITH a `worker` set has been leased by an agent, while one with NONE set is
|
|
707
|
+
* merely queued. It is also the on-tool source of a `jobKey` for {@link updateJobRetries} when
|
|
708
|
+
* the job is NOT behind an incident — {@link searchIncidents} only surfaces a `jobKey` for a
|
|
709
|
+
* job that already faulted (a `jobNoRetries` incident), so retrying an un-incidented job would
|
|
710
|
+
* otherwise be impossible without a direct engine-API call. An eventually consistent (zero-wait)
|
|
711
|
+
* read; each {@link JobSummary} carries the `jobKey` plus best-effort `worker`/`retries`/
|
|
712
|
+
* `elementId`/`deadline` diagnostics.
|
|
713
|
+
*/
|
|
714
|
+
searchJobs(filter?: JobFilter): Promise<JobSummary[]>;
|
|
715
|
+
/**
|
|
716
|
+
* Fetch the deployed BPMN XML of a process definition by its `processDefinitionKey`
|
|
717
|
+
* (`GET /v2/process-definitions/{processDefinitionKey}/xml`), or `null` when the key is blank,
|
|
718
|
+
* unknown (a 404), or the definition carries no XML. This is the *deployed* model — the routing
|
|
719
|
+
* source of truth, including its FEEL gateway conditions — so an agent can reason about WHY an
|
|
720
|
+
* instance routed where it did without checking out `resources/processes/*.bpmn` from the app
|
|
721
|
+
* repo. An instance already carries its `processDefinitionKey` (via {@link searchProcessInstances}),
|
|
722
|
+
* so the path is instance → `processDefinitionKey` → deployed XML. A read is treated as absence
|
|
723
|
+
* (`null`) rather than propagating (mirroring {@link getForm}/{@link getElementInstance}), so a
|
|
724
|
+
* caller distinguishes "have it" from "don't" without a try/catch. A zero-wait read.
|
|
725
|
+
*/
|
|
726
|
+
getProcessDefinitionXml(processDefinitionKey: string): Promise<string | null>;
|
|
619
727
|
/**
|
|
620
728
|
* Resolve an open incident by key (`POST /v2/incidents/{incidentKey}/resolution`), unblocking
|
|
621
729
|
* the parked token: a job incident returns the job to the activatable pool (so it must have
|
|
@@ -680,6 +788,9 @@ export const ENGINE_CLIENT_METHODS = [
|
|
|
680
788
|
"searchElementInstanceWaitStates",
|
|
681
789
|
"getElementInstance",
|
|
682
790
|
"searchIncidents",
|
|
791
|
+
"searchVariables",
|
|
792
|
+
"searchJobs",
|
|
793
|
+
"getProcessDefinitionXml",
|
|
683
794
|
"resolveIncident",
|
|
684
795
|
"updateJobRetries",
|
|
685
796
|
"setVariables",
|
|
@@ -37,6 +37,9 @@ const engine: EngineClient = {
|
|
|
37
37
|
searchElementInstanceWaitStates: async () => [],
|
|
38
38
|
getElementInstance: async () => null,
|
|
39
39
|
searchIncidents: async () => [],
|
|
40
|
+
searchVariables: async () => [],
|
|
41
|
+
searchJobs: async () => [],
|
|
42
|
+
getProcessDefinitionXml: async () => null,
|
|
40
43
|
resolveIncident: async () => {},
|
|
41
44
|
updateJobRetries: async () => {},
|
|
42
45
|
setVariables: async () => {},
|
|
@@ -22,6 +22,9 @@ const fakeEngine: EngineClient = {
|
|
|
22
22
|
searchElementInstanceWaitStates: async () => [],
|
|
23
23
|
getElementInstance: async () => null,
|
|
24
24
|
searchIncidents: async () => [],
|
|
25
|
+
searchVariables: async () => [],
|
|
26
|
+
searchJobs: async () => [],
|
|
27
|
+
getProcessDefinitionXml: async () => null,
|
|
25
28
|
resolveIncident: async () => {},
|
|
26
29
|
updateJobRetries: async () => {},
|
|
27
30
|
setVariables: async () => {},
|