@nanobpm/bojtos-kit 0.6.0 → 0.8.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/README.md CHANGED
@@ -27,6 +27,40 @@ Every command returns the post-run `Snapshot`: `activeElementIds` /
27
27
  For React, use [`@nanobpm/bojtos-react`](../bojtos-react), which owns the session
28
28
  lifecycle and reactive state on top of this kit.
29
29
 
30
+ ## Engine variants — `lean` (default) and `readmodel`
31
+
32
+ `@nanobpm/engine-wasm` ships two binaries; a session picks one via `variant`:
33
+
34
+ - **`lean`** (default) — primary state only. Read it through `snapshot()` /
35
+ `events()`. Loaded statically, so every consumer bundles it.
36
+ - **`readmodel`** — the lean surface **plus** the gateway's Camunda-parity REST
37
+ read channel. Loaded via a **dynamic import**, so a lean-only page never
38
+ downloads the heavier read-model binary (wasm can't be tree-shaken out of a
39
+ single build — code-splitting is the only lever).
40
+
41
+ ```ts
42
+ import {
43
+ createBojtosSession,
44
+ type UserTaskSearchQueryResult,
45
+ } from "@nanobpm/bojtos-kit";
46
+
47
+ // `variant: "readmodel"` widens the return type to `ReadModelBojtosSession`:
48
+ const session = await createBojtosSession({ variant: "readmodel" });
49
+ session.deploy(bpmnXml);
50
+ session.createInstance("review", "{}");
51
+
52
+ // Typed against @nanobpm/engine-wasm/readmodel-types (re-exported here):
53
+ const open: UserTaskSearchQueryResult = session.searchUserTasks(
54
+ JSON.stringify({ state: "CREATED" }),
55
+ );
56
+ const form = session.getFormByKey("2251799813685250"); // FormResult | null
57
+ ```
58
+
59
+ The read methods — `searchUserTasks`, `searchProcessInstances`,
60
+ `searchVariables`, `getFormByKey`, `getResourceByKey` — return DTOs re-exported
61
+ from `@nanobpm/engine-wasm/readmodel-types`, which are **derived** from the
62
+ Camunda-parity REST OpenAPI (one source of truth, not a hand-copy).
63
+
30
64
  ## Trace model
31
65
 
32
66
  The kit also holds the framework-agnostic **trace model** the shared
package/dist/index.d.ts CHANGED
@@ -1,5 +1,6 @@
1
- export { ensureWasm, createBojtosSession, type BojtosSession, type WasmSource, } from "./session.js";
1
+ export { ensureWasm, ensureReadModelWasm, createBojtosSession, type BojtosSession, type ReadModelBojtosSession, type EngineVariant, type WasmSource, } from "./session.js";
2
2
  export { dispatchWorkers, dispatchRound, settleReason, unhandledJobTypes, JobFailure, type JobHandler, type JobResult, type AgentHandler, type DispatchOptions, type DispatchResult, type RoundResult, type SettleReason, } from "./worker.js";
3
3
  export { buildTraceItems, isTraceTurnGroup, foldEngineEvents, traceEntriesToRows, } from "./trace.js";
4
4
  export type { TraceRowKind, TraceEntry, TraceRow, TraceTurnGroup, TraceItem, TraceAdapter, } from "./trace.js";
5
5
  export type { Snapshot, InstanceDto, JobDto, ActivatedJob, IncidentDto, TimerDto, UserTaskDto, MessageSubscriptionDto, SignalSubscriptionDto, ElementStatDto, SequenceFlowDto, DecisionInstanceDto, ActiveEl, ActivateInstruction, AgentActivation, AgentResult, WasmEvent, } from "./types.js";
6
+ export type { UserTaskSearchQueryResult, UserTaskResult, ProcessInstanceSearchQueryResult, ProcessInstanceResult, VariableSearchQueryResult, VariableResult, FormResult, ResourceResult, SearchQueryResponse, SearchQueryPageResponse, } from "@nanobpm/engine-wasm/readmodel-types";
package/dist/index.js CHANGED
@@ -1,7 +1,7 @@
1
1
  // @nanobpm/bojtos-kit — the framework-agnostic core of the Bojtos demo
2
2
  // framework (ADR 0043). Wraps the in-browser wasm engine as a single scenario
3
3
  // runner and re-exports the engine's snapshot/event contract types.
4
- export { ensureWasm, createBojtosSession, } from "./session.js";
4
+ export { ensureWasm, ensureReadModelWasm, createBojtosSession, } from "./session.js";
5
5
  export { dispatchWorkers, dispatchRound, settleReason, unhandledJobTypes, JobFailure, } from "./worker.js";
6
6
  // The shared trace model + both adapters (engine-event fold and handler-emitted
7
7
  // `TraceEntry`) that retired the two forked `TraceTimeline` copies (#9). Pure and
package/dist/session.d.ts CHANGED
@@ -1,5 +1,23 @@
1
1
  import { type InitInput } from "@nanobpm/engine-wasm";
2
+ import type { FormResult, ProcessInstanceSearchQueryResult, ResourceResult, UserTaskSearchQueryResult, VariableSearchQueryResult } from "@nanobpm/engine-wasm/readmodel-types";
2
3
  import type { ActivatedJob, ActivateInstruction, AgentResult, Snapshot, WasmEvent } from "./types.js";
4
+ /**
5
+ * Which engine binary backs a session. The two are separate wasm builds
6
+ * (engine-wasm ships them at distinct subpaths, ADR 0043 §3 / engine-wasm
7
+ * README):
8
+ *
9
+ * - `"lean"` (default) — primary state only; the binary demos/the modeler use.
10
+ * Loaded via the static `@nanobpm/engine-wasm` import, so a bundler emits it
11
+ * for every bojtos-kit consumer.
12
+ * - `"readmodel"` — the lean surface **plus** the gateway's Camunda-parity REST
13
+ * read channel (`searchUserTasks`/`searchProcessInstances`/`searchVariables`/
14
+ * `getFormByKey`/`getResourceByKey`). It carries an in-memory wasm SQLite read
15
+ * model (~2× the wire size), so it is loaded via a **dynamic import** — a
16
+ * lean-only page never bundles it (wasm can't be tree-shaken out of a fat
17
+ * build; code-splitting is the only lever).
18
+ */
19
+ export type EngineVariant = "lean" | "readmodel";
20
+ type ReadModelModule = typeof import("@nanobpm/engine-wasm/readmodel");
3
21
  /**
4
22
  * The source of the engine wasm binary. Under a bundler that understands
5
23
  * `new URL(..., import.meta.url)` (e.g. Vite) the default loader needs no
@@ -20,6 +38,15 @@ export type WasmSource = InitInput;
20
38
  * the binary — can retry rather than being stuck on the first rejection.
21
39
  */
22
40
  export declare function ensureWasm(source?: WasmSource): Promise<void>;
41
+ /**
42
+ * Load **and** initialise the read-model engine variant (idempotent; once per
43
+ * page). Unlike {@link ensureWasm} this also code-splits the binary in via a
44
+ * dynamic `import("@nanobpm/engine-wasm/readmodel")`, so a page that only ever
45
+ * calls {@link ensureWasm} never downloads the heavier read-model wasm. Same
46
+ * first-call-wins / retry-on-failure semantics as {@link ensureWasm}. Returns
47
+ * the module namespace so the caller can construct its `TestEngine`.
48
+ */
49
+ export declare function ensureReadModelWasm(source?: WasmSource): Promise<ReadModelModule>;
23
50
  /**
24
51
  * A headless handle to one in-browser engine instance: deploy a diagram, start
25
52
  * instances, complete/fail jobs, advance the virtual clock, and read the event
@@ -141,12 +168,66 @@ export interface BojtosSession {
141
168
  free(): void;
142
169
  }
143
170
  /**
144
- * Create a fresh headless engine session. Ensures the wasm module is loaded
145
- * (once per page), then constructs a new {@link TestEngine}. The virtual clock
171
+ * A {@link BojtosSession} backed by the **read-model** engine variant: the full
172
+ * lean command surface **plus** the gateway's Camunda-parity REST read channel.
173
+ * Each read method delegates to the in-memory read model (kept current after
174
+ * every command, cleared by {@link BojtosSession.reset}) and returns the parsed
175
+ * DTO — typed against `@nanobpm/engine-wasm/readmodel-types`, which is derived
176
+ * from the same Camunda REST OpenAPI the wasm mirrors, so these stay in lockstep
177
+ * with the engine instead of being hand-copied. Obtain one via
178
+ * `createBojtosSession({ variant: "readmodel" })`.
179
+ */
180
+ export interface ReadModelBojtosSession extends BojtosSession {
181
+ /**
182
+ * Search user tasks through the read model. Honours an optional `{ state? }`
183
+ * filter (e.g. `"CREATED"`). Mirrors `POST /user-tasks/search`.
184
+ */
185
+ searchUserTasks(filterJson?: string): UserTaskSearchQueryResult;
186
+ /**
187
+ * Search process instances through the read model. Body is shape-validated;
188
+ * filter/sort/page fields are not yet honoured (returns every instance).
189
+ * Mirrors `POST /process-instances/search`.
190
+ */
191
+ searchProcessInstances(filterJson?: string): ProcessInstanceSearchQueryResult;
192
+ /**
193
+ * Search variables through the read model. Long values are truncated with
194
+ * `isTruncated: true`. Mirrors `POST /variables/search`.
195
+ */
196
+ searchVariables(filterJson?: string): VariableSearchQueryResult;
197
+ /**
198
+ * The latest deployed form for `formKey`, or `null` if none. Mirrors
199
+ * `GET /forms/{formKey}`.
200
+ */
201
+ getFormByKey(formKey: string): FormResult | null;
202
+ /**
203
+ * The generic resource for `resourceKey`, or `null` if none. Mirrors
204
+ * `GET /resources/{resourceKey}`.
205
+ */
206
+ getResourceByKey(resourceKey: string): ResourceResult | null;
207
+ }
208
+ /**
209
+ * Create a fresh headless engine session. Ensures the chosen wasm variant is
210
+ * loaded (once per page), then constructs a new `TestEngine`. The virtual clock
146
211
  * starts at 0; deploy a diagram before starting instances. Pass a `wasm` source
147
212
  * in environments where the default `import.meta.url` loader can't resolve the
148
213
  * binary (Node/Jest, or the external-`.wasm` mode — ADR 0043 §3).
214
+ *
215
+ * With `variant: "readmodel"` the returned session also exposes the gateway's
216
+ * REST read channel (typed {@link ReadModelBojtosSession}); the default `"lean"`
217
+ * variant is state-only and never downloads the heavier read-model binary. A
218
+ * statically-`"readmodel"` variant widens the return type; a value only known as
219
+ * the `EngineVariant` union resolves to the base {@link BojtosSession}.
149
220
  */
150
221
  export declare function createBojtosSession(opts?: {
151
222
  wasm?: WasmSource;
223
+ variant?: "lean";
224
+ }): Promise<BojtosSession>;
225
+ export declare function createBojtosSession(opts: {
226
+ wasm?: WasmSource;
227
+ variant: "readmodel";
228
+ }): Promise<ReadModelBojtosSession>;
229
+ export declare function createBojtosSession(opts: {
230
+ wasm?: WasmSource;
231
+ variant: EngineVariant;
152
232
  }): Promise<BojtosSession>;
233
+ export {};
package/dist/session.js CHANGED
@@ -1,7 +1,9 @@
1
1
  import init, { TestEngine } from "@nanobpm/engine-wasm";
2
2
  // Lazily initialise the wasm module exactly once per page, no matter how many
3
- // sessions are created. Mirrors the console's original `ensureWasm`.
3
+ // sessions are created. Mirrors the console's original `ensureWasm`. The two
4
+ // variants init independently (a page may use either or both).
4
5
  let wasmReady = null;
6
+ let readModelReady = null;
5
7
  /**
6
8
  * Initialise the wasm engine module (idempotent; safe to call repeatedly). The
7
9
  * first successful call wins: a `source` passed to a later call is ignored once
@@ -24,6 +26,28 @@ export function ensureWasm(source) {
24
26
  }
25
27
  return wasmReady;
26
28
  }
29
+ /**
30
+ * Load **and** initialise the read-model engine variant (idempotent; once per
31
+ * page). Unlike {@link ensureWasm} this also code-splits the binary in via a
32
+ * dynamic `import("@nanobpm/engine-wasm/readmodel")`, so a page that only ever
33
+ * calls {@link ensureWasm} never downloads the heavier read-model wasm. Same
34
+ * first-call-wins / retry-on-failure semantics as {@link ensureWasm}. Returns
35
+ * the module namespace so the caller can construct its `TestEngine`.
36
+ */
37
+ export function ensureReadModelWasm(source) {
38
+ if (!readModelReady) {
39
+ readModelReady = import("@nanobpm/engine-wasm/readmodel")
40
+ .then(async (mod) => {
41
+ await mod.default(source === undefined ? undefined : { module_or_path: source });
42
+ return mod;
43
+ })
44
+ .catch((e) => {
45
+ readModelReady = null;
46
+ throw e;
47
+ });
48
+ }
49
+ return readModelReady;
50
+ }
27
51
  function parseSnapshot(json) {
28
52
  // The wasm engine is the schema authority; its JSON is the contract boundary.
29
53
  return JSON.parse(json);
@@ -104,14 +128,38 @@ class WasmBojtosSession {
104
128
  this.engine.free();
105
129
  }
106
130
  }
107
- /**
108
- * Create a fresh headless engine session. Ensures the wasm module is loaded
109
- * (once per page), then constructs a new {@link TestEngine}. The virtual clock
110
- * starts at 0; deploy a diagram before starting instances. Pass a `wasm` source
111
- * in environments where the default `import.meta.url` loader can't resolve the
112
- * binary (Node/Jest, or the external-`.wasm` mode — ADR 0043 §3).
113
- */
131
+ class WasmReadModelSession extends WasmBojtosSession {
132
+ // The read-model engine is a structural superset of the lean `TestEngine`
133
+ // (identical command surface + the 5 read methods), so it satisfies the base
134
+ // constructor while we keep our own read-model-typed reference for the read
135
+ // channel no casts, so a future divergence in the shared surface is a
136
+ // compile error rather than a runtime one.
137
+ rm;
138
+ constructor(engine) {
139
+ super(engine);
140
+ this.rm = engine;
141
+ }
142
+ searchUserTasks(filterJson = "{}") {
143
+ return JSON.parse(this.rm.searchUserTasks(filterJson || "{}"));
144
+ }
145
+ searchProcessInstances(filterJson = "{}") {
146
+ return JSON.parse(this.rm.searchProcessInstances(filterJson || "{}"));
147
+ }
148
+ searchVariables(filterJson = "{}") {
149
+ return JSON.parse(this.rm.searchVariables(filterJson || "{}"));
150
+ }
151
+ getFormByKey(formKey) {
152
+ return JSON.parse(this.rm.getFormByKey(formKey));
153
+ }
154
+ getResourceByKey(resourceKey) {
155
+ return JSON.parse(this.rm.getResourceByKey(resourceKey));
156
+ }
157
+ }
114
158
  export async function createBojtosSession(opts) {
159
+ if (opts?.variant === "readmodel") {
160
+ const mod = await ensureReadModelWasm(opts.wasm);
161
+ return new WasmReadModelSession(new mod.TestEngine());
162
+ }
115
163
  await ensureWasm(opts?.wasm);
116
164
  return new WasmBojtosSession(new TestEngine());
117
165
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nanobpm/bojtos-kit",
3
- "version": "0.6.0",
3
+ "version": "0.8.0",
4
4
  "description": "Framework-agnostic core of the Bojtos in-browser BPMN demo framework (ADR 0043): a single scenario runner over the @nanobpm/engine-wasm engine (deploy, start instances, complete/fail jobs, advance the clock, read snapshots and the event log), plus the engine's snapshot/event contract types. Consumed by @nanobpm/bojtos-react and the console test-run panel.",
5
5
  "type": "module",
6
6
  "license": "Apache-2.0",
@@ -32,7 +32,7 @@
32
32
  "prepack": "npm run build"
33
33
  },
34
34
  "dependencies": {
35
- "@nanobpm/engine-wasm": "^0.3.0"
35
+ "@nanobpm/engine-wasm": "^0.7.0"
36
36
  },
37
37
  "devDependencies": {
38
38
  "typescript": "^5.6.3"
package/src/index.ts CHANGED
@@ -4,8 +4,11 @@
4
4
 
5
5
  export {
6
6
  ensureWasm,
7
+ ensureReadModelWasm,
7
8
  createBojtosSession,
8
9
  type BojtosSession,
10
+ type ReadModelBojtosSession,
11
+ type EngineVariant,
9
12
  type WasmSource,
10
13
  } from "./session.js";
11
14
  export {
@@ -61,3 +64,20 @@ export type {
61
64
  AgentResult,
62
65
  WasmEvent,
63
66
  } from "./types.js";
67
+ // The read-model query-result DTOs, re-exported from
68
+ // `@nanobpm/engine-wasm/readmodel-types` (derived from the Camunda-parity REST
69
+ // OpenAPI — a single source of truth, not a hand-copy). A consumer that reads a
70
+ // `ReadModelBojtosSession`'s `searchUserTasks()` / `getFormByKey()` return must
71
+ // be able to name these to write helpers over them.
72
+ export type {
73
+ UserTaskSearchQueryResult,
74
+ UserTaskResult,
75
+ ProcessInstanceSearchQueryResult,
76
+ ProcessInstanceResult,
77
+ VariableSearchQueryResult,
78
+ VariableResult,
79
+ FormResult,
80
+ ResourceResult,
81
+ SearchQueryResponse,
82
+ SearchQueryPageResponse,
83
+ } from "@nanobpm/engine-wasm/readmodel-types";
package/src/session.ts CHANGED
@@ -1,4 +1,11 @@
1
1
  import init, { type InitInput, TestEngine } from "@nanobpm/engine-wasm";
2
+ import type {
3
+ FormResult,
4
+ ProcessInstanceSearchQueryResult,
5
+ ResourceResult,
6
+ UserTaskSearchQueryResult,
7
+ VariableSearchQueryResult,
8
+ } from "@nanobpm/engine-wasm/readmodel-types";
2
9
  import type {
3
10
  ActivatedJob,
4
11
  ActivateInstruction,
@@ -7,9 +14,35 @@ import type {
7
14
  WasmEvent,
8
15
  } from "./types.js";
9
16
 
17
+ /**
18
+ * Which engine binary backs a session. The two are separate wasm builds
19
+ * (engine-wasm ships them at distinct subpaths, ADR 0043 §3 / engine-wasm
20
+ * README):
21
+ *
22
+ * - `"lean"` (default) — primary state only; the binary demos/the modeler use.
23
+ * Loaded via the static `@nanobpm/engine-wasm` import, so a bundler emits it
24
+ * for every bojtos-kit consumer.
25
+ * - `"readmodel"` — the lean surface **plus** the gateway's Camunda-parity REST
26
+ * read channel (`searchUserTasks`/`searchProcessInstances`/`searchVariables`/
27
+ * `getFormByKey`/`getResourceByKey`). It carries an in-memory wasm SQLite read
28
+ * model (~2× the wire size), so it is loaded via a **dynamic import** — a
29
+ * lean-only page never bundles it (wasm can't be tree-shaken out of a fat
30
+ * build; code-splitting is the only lever).
31
+ */
32
+ export type EngineVariant = "lean" | "readmodel";
33
+
34
+ // Type-only view of the read-model module so we can name its `TestEngine`
35
+ // (a distinct wasm-bindgen class from lean's, with the +5 read methods) without
36
+ // statically importing the heavy binary — the runtime handle is fetched lazily
37
+ // by `ensureReadModelWasm`'s dynamic `import()`.
38
+ type ReadModelModule = typeof import("@nanobpm/engine-wasm/readmodel");
39
+ type ReadModelEngine = InstanceType<ReadModelModule["TestEngine"]>;
40
+
10
41
  // Lazily initialise the wasm module exactly once per page, no matter how many
11
- // sessions are created. Mirrors the console's original `ensureWasm`.
42
+ // sessions are created. Mirrors the console's original `ensureWasm`. The two
43
+ // variants init independently (a page may use either or both).
12
44
  let wasmReady: Promise<void> | null = null;
45
+ let readModelReady: Promise<ReadModelModule> | null = null;
13
46
 
14
47
  /**
15
48
  * The source of the engine wasm binary. Under a bundler that understands
@@ -45,6 +78,33 @@ export function ensureWasm(source?: WasmSource): Promise<void> {
45
78
  return wasmReady;
46
79
  }
47
80
 
81
+ /**
82
+ * Load **and** initialise the read-model engine variant (idempotent; once per
83
+ * page). Unlike {@link ensureWasm} this also code-splits the binary in via a
84
+ * dynamic `import("@nanobpm/engine-wasm/readmodel")`, so a page that only ever
85
+ * calls {@link ensureWasm} never downloads the heavier read-model wasm. Same
86
+ * first-call-wins / retry-on-failure semantics as {@link ensureWasm}. Returns
87
+ * the module namespace so the caller can construct its `TestEngine`.
88
+ */
89
+ export function ensureReadModelWasm(
90
+ source?: WasmSource,
91
+ ): Promise<ReadModelModule> {
92
+ if (!readModelReady) {
93
+ readModelReady = import("@nanobpm/engine-wasm/readmodel")
94
+ .then(async (mod) => {
95
+ await mod.default(
96
+ source === undefined ? undefined : { module_or_path: source },
97
+ );
98
+ return mod;
99
+ })
100
+ .catch((e) => {
101
+ readModelReady = null;
102
+ throw e;
103
+ });
104
+ }
105
+ return readModelReady;
106
+ }
107
+
48
108
  /**
49
109
  * A headless handle to one in-browser engine instance: deploy a diagram, start
50
110
  * instances, complete/fail jobs, advance the virtual clock, and read the event
@@ -185,13 +245,52 @@ export interface BojtosSession {
185
245
  free(): void;
186
246
  }
187
247
 
248
+ /**
249
+ * A {@link BojtosSession} backed by the **read-model** engine variant: the full
250
+ * lean command surface **plus** the gateway's Camunda-parity REST read channel.
251
+ * Each read method delegates to the in-memory read model (kept current after
252
+ * every command, cleared by {@link BojtosSession.reset}) and returns the parsed
253
+ * DTO — typed against `@nanobpm/engine-wasm/readmodel-types`, which is derived
254
+ * from the same Camunda REST OpenAPI the wasm mirrors, so these stay in lockstep
255
+ * with the engine instead of being hand-copied. Obtain one via
256
+ * `createBojtosSession({ variant: "readmodel" })`.
257
+ */
258
+ export interface ReadModelBojtosSession extends BojtosSession {
259
+ /**
260
+ * Search user tasks through the read model. Honours an optional `{ state? }`
261
+ * filter (e.g. `"CREATED"`). Mirrors `POST /user-tasks/search`.
262
+ */
263
+ searchUserTasks(filterJson?: string): UserTaskSearchQueryResult;
264
+ /**
265
+ * Search process instances through the read model. Body is shape-validated;
266
+ * filter/sort/page fields are not yet honoured (returns every instance).
267
+ * Mirrors `POST /process-instances/search`.
268
+ */
269
+ searchProcessInstances(filterJson?: string): ProcessInstanceSearchQueryResult;
270
+ /**
271
+ * Search variables through the read model. Long values are truncated with
272
+ * `isTruncated: true`. Mirrors `POST /variables/search`.
273
+ */
274
+ searchVariables(filterJson?: string): VariableSearchQueryResult;
275
+ /**
276
+ * The latest deployed form for `formKey`, or `null` if none. Mirrors
277
+ * `GET /forms/{formKey}`.
278
+ */
279
+ getFormByKey(formKey: string): FormResult | null;
280
+ /**
281
+ * The generic resource for `resourceKey`, or `null` if none. Mirrors
282
+ * `GET /resources/{resourceKey}`.
283
+ */
284
+ getResourceByKey(resourceKey: string): ResourceResult | null;
285
+ }
286
+
188
287
  function parseSnapshot(json: string): Snapshot {
189
288
  // The wasm engine is the schema authority; its JSON is the contract boundary.
190
289
  return JSON.parse(json) as Snapshot;
191
290
  }
192
291
 
193
292
  class WasmBojtosSession implements BojtosSession {
194
- private readonly engine: TestEngine;
293
+ protected readonly engine: TestEngine;
195
294
 
196
295
  constructor(engine: TestEngine) {
197
296
  this.engine = engine;
@@ -350,16 +449,84 @@ class WasmBojtosSession implements BojtosSession {
350
449
  }
351
450
  }
352
451
 
452
+ class WasmReadModelSession
453
+ extends WasmBojtosSession
454
+ implements ReadModelBojtosSession
455
+ {
456
+ // The read-model engine is a structural superset of the lean `TestEngine`
457
+ // (identical command surface + the 5 read methods), so it satisfies the base
458
+ // constructor while we keep our own read-model-typed reference for the read
459
+ // channel — no casts, so a future divergence in the shared surface is a
460
+ // compile error rather than a runtime one.
461
+ private readonly rm: ReadModelEngine;
462
+
463
+ constructor(engine: ReadModelEngine) {
464
+ super(engine);
465
+ this.rm = engine;
466
+ }
467
+
468
+ searchUserTasks(filterJson = "{}"): UserTaskSearchQueryResult {
469
+ return JSON.parse(
470
+ this.rm.searchUserTasks(filterJson || "{}"),
471
+ ) as UserTaskSearchQueryResult;
472
+ }
473
+
474
+ searchProcessInstances(filterJson = "{}"): ProcessInstanceSearchQueryResult {
475
+ return JSON.parse(
476
+ this.rm.searchProcessInstances(filterJson || "{}"),
477
+ ) as ProcessInstanceSearchQueryResult;
478
+ }
479
+
480
+ searchVariables(filterJson = "{}"): VariableSearchQueryResult {
481
+ return JSON.parse(
482
+ this.rm.searchVariables(filterJson || "{}"),
483
+ ) as VariableSearchQueryResult;
484
+ }
485
+
486
+ getFormByKey(formKey: string): FormResult | null {
487
+ return JSON.parse(this.rm.getFormByKey(formKey)) as FormResult | null;
488
+ }
489
+
490
+ getResourceByKey(resourceKey: string): ResourceResult | null {
491
+ return JSON.parse(
492
+ this.rm.getResourceByKey(resourceKey),
493
+ ) as ResourceResult | null;
494
+ }
495
+ }
496
+
353
497
  /**
354
- * Create a fresh headless engine session. Ensures the wasm module is loaded
355
- * (once per page), then constructs a new {@link TestEngine}. The virtual clock
498
+ * Create a fresh headless engine session. Ensures the chosen wasm variant is
499
+ * loaded (once per page), then constructs a new `TestEngine`. The virtual clock
356
500
  * starts at 0; deploy a diagram before starting instances. Pass a `wasm` source
357
501
  * in environments where the default `import.meta.url` loader can't resolve the
358
502
  * binary (Node/Jest, or the external-`.wasm` mode — ADR 0043 §3).
503
+ *
504
+ * With `variant: "readmodel"` the returned session also exposes the gateway's
505
+ * REST read channel (typed {@link ReadModelBojtosSession}); the default `"lean"`
506
+ * variant is state-only and never downloads the heavier read-model binary. A
507
+ * statically-`"readmodel"` variant widens the return type; a value only known as
508
+ * the `EngineVariant` union resolves to the base {@link BojtosSession}.
359
509
  */
360
510
  export async function createBojtosSession(opts?: {
361
511
  wasm?: WasmSource;
512
+ variant?: "lean";
513
+ }): Promise<BojtosSession>;
514
+ export async function createBojtosSession(opts: {
515
+ wasm?: WasmSource;
516
+ variant: "readmodel";
517
+ }): Promise<ReadModelBojtosSession>;
518
+ export async function createBojtosSession(opts: {
519
+ wasm?: WasmSource;
520
+ variant: EngineVariant;
521
+ }): Promise<BojtosSession>;
522
+ export async function createBojtosSession(opts?: {
523
+ wasm?: WasmSource;
524
+ variant?: EngineVariant;
362
525
  }): Promise<BojtosSession> {
526
+ if (opts?.variant === "readmodel") {
527
+ const mod = await ensureReadModelWasm(opts.wasm);
528
+ return new WasmReadModelSession(new mod.TestEngine());
529
+ }
363
530
  await ensureWasm(opts?.wasm);
364
531
  return new WasmBojtosSession(new TestEngine());
365
532
  }