@nanobpm/bojtos-kit 0.8.0 → 0.9.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/README.md +22 -0
- package/dist/index.d.ts +1 -0
- package/dist/session.d.ts +13 -0
- package/dist/session.js +40 -0
- package/package.json +6 -2
- package/src/index.ts +8 -0
- package/src/session.ts +67 -0
package/README.md
CHANGED
|
@@ -61,6 +61,28 @@ The read methods — `searchUserTasks`, `searchProcessInstances`,
|
|
|
61
61
|
from `@nanobpm/engine-wasm/readmodel-types`, which are **derived** from the
|
|
62
62
|
Camunda-parity REST OpenAPI (one source of truth, not a hand-copy).
|
|
63
63
|
|
|
64
|
+
### `readModel()` — the `@nanobpm/engine-testkit` assertion handle
|
|
65
|
+
|
|
66
|
+
A `readmodel`-variant session also exposes `readModel()`, which returns the
|
|
67
|
+
session's engine read model as `@nanobpm/engine-testkit`'s structural
|
|
68
|
+
[`EngineReadModel`](https://www.npmjs.com/package/@nanobpm/engine-testkit) port
|
|
69
|
+
(`snapshot()` + the user-task read channel). Hand it straight to the `assertThat*`
|
|
70
|
+
DSL — it reads the engine's canonical snapshot off *this* session, so there is no
|
|
71
|
+
re-derived copy of the state:
|
|
72
|
+
|
|
73
|
+
```ts
|
|
74
|
+
import { assertThatInstance, assertThatUserTask, byProcessId } from "@nanobpm/engine-testkit";
|
|
75
|
+
|
|
76
|
+
const rm = session.readModel(); // typed EngineReadModel — a compile-time guarantee
|
|
77
|
+
|
|
78
|
+
assertThatInstance(rm, byProcessId("review")).isActive().hasActiveElement("review-task");
|
|
79
|
+
await assertThatUserTask(rm, { elementId: "review-task" }).isCreated();
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
The React binding surfaces the same handle as `useBojtos({ variant: "readmodel" }).readModel()`
|
|
83
|
+
(`EngineReadModel | null` until the engine is ready).
|
|
84
|
+
|
|
85
|
+
|
|
64
86
|
## Trace model
|
|
65
87
|
|
|
66
88
|
The kit also holds the framework-agnostic **trace model** the shared
|
package/dist/index.d.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
export { ensureWasm, ensureReadModelWasm, createBojtosSession, type BojtosSession, type ReadModelBojtosSession, type EngineVariant, type WasmSource, } from "./session.js";
|
|
2
|
+
export type { EngineReadModel, UserTaskQuery, UserTaskRow, } from "@nanobpm/engine-testkit";
|
|
2
3
|
export { dispatchWorkers, dispatchRound, settleReason, unhandledJobTypes, JobFailure, type JobHandler, type JobResult, type AgentHandler, type DispatchOptions, type DispatchResult, type RoundResult, type SettleReason, } from "./worker.js";
|
|
3
4
|
export { buildTraceItems, isTraceTurnGroup, foldEngineEvents, traceEntriesToRows, } from "./trace.js";
|
|
4
5
|
export type { TraceRowKind, TraceEntry, TraceRow, TraceTurnGroup, TraceItem, TraceAdapter, } from "./trace.js";
|
package/dist/session.d.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { type InitInput } from "@nanobpm/engine-wasm";
|
|
2
2
|
import type { FormResult, ProcessInstanceSearchQueryResult, ResourceResult, UserTaskSearchQueryResult, VariableSearchQueryResult } from "@nanobpm/engine-wasm/readmodel-types";
|
|
3
|
+
import type { EngineReadModel } from "@nanobpm/engine-testkit";
|
|
3
4
|
import type { ActivatedJob, ActivateInstruction, AgentResult, Snapshot, WasmEvent } from "./types.js";
|
|
4
5
|
/**
|
|
5
6
|
* Which engine binary backs a session. The two are separate wasm builds
|
|
@@ -204,6 +205,18 @@ export interface ReadModelBojtosSession extends BojtosSession {
|
|
|
204
205
|
* `GET /resources/{resourceKey}`.
|
|
205
206
|
*/
|
|
206
207
|
getResourceByKey(resourceKey: string): ResourceResult | null;
|
|
208
|
+
/**
|
|
209
|
+
* This session's engine read model as the structural {@link EngineReadModel}
|
|
210
|
+
* port that `@nanobpm/engine-testkit`'s `assertThat*` DSL asserts over. Hand it
|
|
211
|
+
* straight to `assertThatInstance` / `assertThatUserTask`: it reads the
|
|
212
|
+
* canonical engine snapshot (`snapshot()`) and the user-task read channel
|
|
213
|
+
* (`searchUserTasks` / `openUserTasks`, the CREATED set) off *this* session, so
|
|
214
|
+
* a consumer (e.g. `camunda/web-demo-framework`) asserts against the engine's
|
|
215
|
+
* single source of truth rather than re-deriving a snapshot. The returned
|
|
216
|
+
* object is typed `EngineReadModel`, so its structural conformance to the port
|
|
217
|
+
* is a compile-time guarantee, not a runtime cast.
|
|
218
|
+
*/
|
|
219
|
+
readModel(): EngineReadModel;
|
|
207
220
|
}
|
|
208
221
|
/**
|
|
209
222
|
* Create a fresh headless engine session. Ensures the chosen wasm variant is
|
package/dist/session.js
CHANGED
|
@@ -154,6 +154,46 @@ class WasmReadModelSession extends WasmBojtosSession {
|
|
|
154
154
|
getResourceByKey(resourceKey) {
|
|
155
155
|
return JSON.parse(this.rm.getResourceByKey(resourceKey));
|
|
156
156
|
}
|
|
157
|
+
readModel() {
|
|
158
|
+
// Reads flow straight off this session's engine — the same parsed snapshot
|
|
159
|
+
// and the same user-task read channel the rest of the session exposes — so
|
|
160
|
+
// there is a single source of truth and no re-derived copy of the state
|
|
161
|
+
// (nanobpm/bojtos#15). Typing the returned object as `EngineReadModel` makes
|
|
162
|
+
// structural conformance to engine-testkit's port a compile-time assignment.
|
|
163
|
+
const searchRows = (query) => this.searchUserTasks(JSON.stringify(query.state === undefined ? {} : { state: query.state }))
|
|
164
|
+
.items.filter((task) => {
|
|
165
|
+
// The wasm read model honours the lifecycle `state` filter itself; the
|
|
166
|
+
// non-lifecycle narrowings the DSL relies on (processInstanceKey /
|
|
167
|
+
// assignee / candidateGroup) are applied here so `assertThatUserTask`'s
|
|
168
|
+
// `hasAssignee` / `hasCandidateGroup` / instance-scoped selects hold.
|
|
169
|
+
if (query.processInstanceKey !== undefined &&
|
|
170
|
+
task.processInstanceKey !== query.processInstanceKey) {
|
|
171
|
+
return false;
|
|
172
|
+
}
|
|
173
|
+
if (query.assignee !== undefined && task.assignee !== query.assignee) {
|
|
174
|
+
return false;
|
|
175
|
+
}
|
|
176
|
+
if (query.candidateGroup !== undefined &&
|
|
177
|
+
!task.candidateGroups.includes(query.candidateGroup)) {
|
|
178
|
+
return false;
|
|
179
|
+
}
|
|
180
|
+
return true;
|
|
181
|
+
})
|
|
182
|
+
.map((task) => ({
|
|
183
|
+
userTaskKey: task.userTaskKey,
|
|
184
|
+
elementId: task.elementId ?? undefined,
|
|
185
|
+
}));
|
|
186
|
+
return {
|
|
187
|
+
// The engine snapshot is already the DSL's expected structural shape
|
|
188
|
+
// (`instances`/`activeElements`/`elementStats`/`incidents`/per-instance
|
|
189
|
+
// `variables`); the assertion only retypes the same object as the port's
|
|
190
|
+
// open `Record<string, unknown>` — it does not re-derive it.
|
|
191
|
+
snapshot: () => this.snapshot(),
|
|
192
|
+
searchUserTasks: (query) => Promise.resolve(searchRows(query)),
|
|
193
|
+
// openUserTasks is searchUserTasks pinned to the open (CREATED) set.
|
|
194
|
+
openUserTasks: (query) => Promise.resolve(searchRows({ ...query, state: "CREATED" })),
|
|
195
|
+
};
|
|
196
|
+
}
|
|
157
197
|
}
|
|
158
198
|
export async function createBojtosSession(opts) {
|
|
159
199
|
if (opts?.variant === "readmodel") {
|
package/package.json
CHANGED
|
@@ -1,9 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@nanobpm/bojtos-kit",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.9.1",
|
|
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",
|
|
7
|
+
"engines": {
|
|
8
|
+
"node": ">=22.6"
|
|
9
|
+
},
|
|
7
10
|
"repository": {
|
|
8
11
|
"type": "git",
|
|
9
12
|
"url": "git+https://github.com/nanobpm/bojtos.git",
|
|
@@ -32,7 +35,8 @@
|
|
|
32
35
|
"prepack": "npm run build"
|
|
33
36
|
},
|
|
34
37
|
"dependencies": {
|
|
35
|
-
"@nanobpm/engine-
|
|
38
|
+
"@nanobpm/engine-testkit": "^0.1.0",
|
|
39
|
+
"@nanobpm/engine-wasm": "^0.8.1"
|
|
36
40
|
},
|
|
37
41
|
"devDependencies": {
|
|
38
42
|
"typescript": "^5.6.3"
|
package/src/index.ts
CHANGED
|
@@ -11,6 +11,14 @@ export {
|
|
|
11
11
|
type EngineVariant,
|
|
12
12
|
type WasmSource,
|
|
13
13
|
} from "./session.js";
|
|
14
|
+
// The engine-testkit read-model port a `ReadModelBojtosSession.readModel()`
|
|
15
|
+
// satisfies, re-exported so a consumer can name the handle's type (e.g. to store
|
|
16
|
+
// it) without a direct import of `@nanobpm/engine-testkit`.
|
|
17
|
+
export type {
|
|
18
|
+
EngineReadModel,
|
|
19
|
+
UserTaskQuery,
|
|
20
|
+
UserTaskRow,
|
|
21
|
+
} from "@nanobpm/engine-testkit";
|
|
14
22
|
export {
|
|
15
23
|
dispatchWorkers,
|
|
16
24
|
dispatchRound,
|
package/src/session.ts
CHANGED
|
@@ -6,6 +6,11 @@ import type {
|
|
|
6
6
|
UserTaskSearchQueryResult,
|
|
7
7
|
VariableSearchQueryResult,
|
|
8
8
|
} from "@nanobpm/engine-wasm/readmodel-types";
|
|
9
|
+
import type {
|
|
10
|
+
EngineReadModel,
|
|
11
|
+
UserTaskQuery,
|
|
12
|
+
UserTaskRow,
|
|
13
|
+
} from "@nanobpm/engine-testkit";
|
|
9
14
|
import type {
|
|
10
15
|
ActivatedJob,
|
|
11
16
|
ActivateInstruction,
|
|
@@ -282,6 +287,18 @@ export interface ReadModelBojtosSession extends BojtosSession {
|
|
|
282
287
|
* `GET /resources/{resourceKey}`.
|
|
283
288
|
*/
|
|
284
289
|
getResourceByKey(resourceKey: string): ResourceResult | null;
|
|
290
|
+
/**
|
|
291
|
+
* This session's engine read model as the structural {@link EngineReadModel}
|
|
292
|
+
* port that `@nanobpm/engine-testkit`'s `assertThat*` DSL asserts over. Hand it
|
|
293
|
+
* straight to `assertThatInstance` / `assertThatUserTask`: it reads the
|
|
294
|
+
* canonical engine snapshot (`snapshot()`) and the user-task read channel
|
|
295
|
+
* (`searchUserTasks` / `openUserTasks`, the CREATED set) off *this* session, so
|
|
296
|
+
* a consumer (e.g. `camunda/web-demo-framework`) asserts against the engine's
|
|
297
|
+
* single source of truth rather than re-deriving a snapshot. The returned
|
|
298
|
+
* object is typed `EngineReadModel`, so its structural conformance to the port
|
|
299
|
+
* is a compile-time guarantee, not a runtime cast.
|
|
300
|
+
*/
|
|
301
|
+
readModel(): EngineReadModel;
|
|
285
302
|
}
|
|
286
303
|
|
|
287
304
|
function parseSnapshot(json: string): Snapshot {
|
|
@@ -492,6 +509,56 @@ class WasmReadModelSession
|
|
|
492
509
|
this.rm.getResourceByKey(resourceKey),
|
|
493
510
|
) as ResourceResult | null;
|
|
494
511
|
}
|
|
512
|
+
|
|
513
|
+
readModel(): EngineReadModel {
|
|
514
|
+
// Reads flow straight off this session's engine — the same parsed snapshot
|
|
515
|
+
// and the same user-task read channel the rest of the session exposes — so
|
|
516
|
+
// there is a single source of truth and no re-derived copy of the state
|
|
517
|
+
// (nanobpm/bojtos#15). Typing the returned object as `EngineReadModel` makes
|
|
518
|
+
// structural conformance to engine-testkit's port a compile-time assignment.
|
|
519
|
+
const searchRows = (query: UserTaskQuery): UserTaskRow[] =>
|
|
520
|
+
this.searchUserTasks(
|
|
521
|
+
JSON.stringify(query.state === undefined ? {} : { state: query.state }),
|
|
522
|
+
)
|
|
523
|
+
.items.filter((task) => {
|
|
524
|
+
// The wasm read model honours the lifecycle `state` filter itself; the
|
|
525
|
+
// non-lifecycle narrowings the DSL relies on (processInstanceKey /
|
|
526
|
+
// assignee / candidateGroup) are applied here so `assertThatUserTask`'s
|
|
527
|
+
// `hasAssignee` / `hasCandidateGroup` / instance-scoped selects hold.
|
|
528
|
+
if (
|
|
529
|
+
query.processInstanceKey !== undefined &&
|
|
530
|
+
task.processInstanceKey !== query.processInstanceKey
|
|
531
|
+
) {
|
|
532
|
+
return false;
|
|
533
|
+
}
|
|
534
|
+
if (query.assignee !== undefined && task.assignee !== query.assignee) {
|
|
535
|
+
return false;
|
|
536
|
+
}
|
|
537
|
+
if (
|
|
538
|
+
query.candidateGroup !== undefined &&
|
|
539
|
+
!task.candidateGroups.includes(query.candidateGroup)
|
|
540
|
+
) {
|
|
541
|
+
return false;
|
|
542
|
+
}
|
|
543
|
+
return true;
|
|
544
|
+
})
|
|
545
|
+
.map((task) => ({
|
|
546
|
+
userTaskKey: task.userTaskKey,
|
|
547
|
+
elementId: task.elementId ?? undefined,
|
|
548
|
+
}));
|
|
549
|
+
|
|
550
|
+
return {
|
|
551
|
+
// The engine snapshot is already the DSL's expected structural shape
|
|
552
|
+
// (`instances`/`activeElements`/`elementStats`/`incidents`/per-instance
|
|
553
|
+
// `variables`); the assertion only retypes the same object as the port's
|
|
554
|
+
// open `Record<string, unknown>` — it does not re-derive it.
|
|
555
|
+
snapshot: () => this.snapshot() as unknown as Record<string, unknown>,
|
|
556
|
+
searchUserTasks: (query) => Promise.resolve(searchRows(query)),
|
|
557
|
+
// openUserTasks is searchUserTasks pinned to the open (CREATED) set.
|
|
558
|
+
openUserTasks: (query) =>
|
|
559
|
+
Promise.resolve(searchRows({ ...query, state: "CREATED" })),
|
|
560
|
+
};
|
|
561
|
+
}
|
|
495
562
|
}
|
|
496
563
|
|
|
497
564
|
/**
|