@eventmodelers/cli 1.0.44 → 1.0.45
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/package.json +1 -1
- package/stacks/modeling-kit/templates/.claude/skills/eventmodeling-core-rules/SKILL.md +4 -2
- package/stacks/modeling-kit/templates/.claude/skills/eventmodeling-identifying-outputs/SKILL.md +1 -1
- package/stacks/modeling-kit/templates/.claude/skills/eventmodeling-orchestrating-event-modeling/SKILL.md +1 -1
- package/stacks/modeling-kit/templates/.claude/skills/eventmodeling-slicing-event-models/SKILL.md +2 -0
- package/stacks/modeling-kit/templates/.claude/skills/place-element/SKILL.md +1 -1
- package/stacks/node/templates/root/README.md +67 -0
package/package.json
CHANGED
|
@@ -41,7 +41,7 @@ automation slice = EVENT(s) → AUTOMATION → COMMAND → EVENT(s)
|
|
|
41
41
|
|
|
42
42
|
A slice never mixes a COMMAND and a READMODEL — those are two different slice types (`state-change` and `state-view`), even when they belong to the same feature. Slices are independently deployable and only communicate with each other via events.
|
|
43
43
|
|
|
44
|
-
Slices are not placed directly — they're **derived** from a completed model. Every COMMAND on the board implies a `state-change` slice, every READMODEL implies a `state-view` slice, every AUTOMATION implies an `automation` slice
|
|
44
|
+
Slices are not placed directly — they're **derived** from a completed model. Every COMMAND on the board implies a `state-change` slice, every READMODEL implies a `state-view` slice, every AUTOMATION implies an `automation` slice — **except** a `linkedTo` copy (see Linked Copies below), which implies no slice of its own: it's the same underlying COMMAND/EVENT/READMODEL reused elsewhere on the board, not a second independently-deployable thing, so only the origin node's column gets a `SLICE_BORDER`. `eventmodeling-slicing-event-models` makes that implicit structure explicit on the board once modeling is done; it never invents a slice that isn't already implied by an element that's there, and skips columns whose only COMMAND/READMODEL is a linked copy.
|
|
45
45
|
|
|
46
46
|
## Elements
|
|
47
47
|
|
|
@@ -116,7 +116,7 @@ When an element's natural column is already occupied by something else, insert a
|
|
|
116
116
|
|
|
117
117
|
A later EVENT sometimes needs to change data an earlier READMODEL already shows on a SCREEN — e.g. a cancellation affecting an "active items" view placed several columns earlier. Wiring that later EVENT straight back into the existing READMODEL is never the fix: it's a backward connection, and the platform only accepts a backward `EVENT → READMODEL` edge when the READMODEL already has a `READMODEL → AUTOMATION` edge (the todo-list pattern — see Translation Chain below). A READMODEL that only feeds a SCREEN never qualifies for that exemption.
|
|
118
118
|
|
|
119
|
-
Instead, place a **new READMODEL node** in a new column **immediately after** the later event's column,
|
|
119
|
+
Instead, place a **new READMODEL node** in a new column **immediately after** the later event's column, then link it to the earlier READMODEL instance via the `linkedTo` copy mechanism below (`link_element`) — it *is* the same read model, carried forward to a later state, not an unrelated new one, so it uses the same copy mechanism as any other reused fact. Connect the later event forward into the linked copy, then update whichever field(s) that event actually changes (e.g. `status`) on the copy — the fields it inherited from the link that the new event doesn't touch stay as copied. Give it a matching SCREEN in that same new column — same screen name/title as the earlier one, showing the updated data, optionally re-marked/highlighted via `html-screen`'s Marks feature to call out what changed. A SCREEN can never itself be a `linkedTo` copy (the mechanism only supports COMMAND/EVENT/READMODEL), so the new SCREEN is placed as a plain node matching the earlier one's title, not linked. Leave the earlier READMODEL/SCREEN instance exactly as it is — it's still what that earlier point in the timeline correctly showed. Never delete it.
|
|
120
120
|
|
|
121
121
|
See `eventmodeling-identifying-outputs` Step 5g for the full placement/wiring mechanics, and `place-element` Step 6 for the column-insertion mechanics.
|
|
122
122
|
|
|
@@ -126,6 +126,8 @@ A **linked copy** is a COMMAND/EVENT/READMODEL node that mirrors another node el
|
|
|
126
126
|
|
|
127
127
|
To make one: place a normal new node of the same type (COMMAND/EVENT/READMODEL only) at the target spot, then link it to the origin (`link_element`, or the REST `.../nodes/:nodeId/link` fallback). This replaces the new node's `meta` with a full copy of the origin's and sets `meta.linkedTo` to the origin's node id — the authoritative pointer (a `data.linkedTo` also exists but is a rendering mirror only, not the source of truth). Wire the resulting copy to its own neighbors normally afterward.
|
|
128
128
|
|
|
129
|
+
A linked copy is allowed to diverge from its origin afterward in the specific field(s) a newly-connected event actually drives — e.g. the "Updating a Read Model That Already Feeds a Screen" copy above, where `status` is updated post-link to the event's new value. The link establishes "this is the same underlying fact, not a duplicate" at creation time; it doesn't freeze the copy's fields identical to the origin's forever.
|
|
130
|
+
|
|
129
131
|
Never delete the original (the node with no `linkedTo`) once copies of it exist — copies reference it, and removing it breaks every copy. `eventmodeling-checking-completeness` already treats any `linkedTo`-marked node as an intentional copy, never a duplicate or missing-slice gap to flag.
|
|
130
132
|
|
|
131
133
|
See `place-element` Step 6a for the mechanical linking steps.
|
package/stacks/modeling-kit/templates/.claude/skills/eventmodeling-identifying-outputs/SKILL.md
CHANGED
|
@@ -314,7 +314,7 @@ For each view screen S that queries this read model as its primary read model:
|
|
|
314
314
|
|
|
315
315
|
**View screens normally share the column of the (primary) read model they display** — either because they were placed there in Step 3, or because the read model was placed into the screen's own column just now (per the check above). A screen's own position is never moved to resolve a read model placement conflict — when the shared column isn't available, the **read model** gets a new column immediately before the screen's, not the other way around (this preserves the screen's narrative order from Step 3).
|
|
316
316
|
|
|
317
|
-
**The same rule applies to `EVENT → READMODEL`** (shared statement: `eventmodeling-core-rules`'s "Updating a Read Model That Already Feeds a Screen"). If a later event needs to update data a read model already feeds to a SCREEN, do not connect that later event back into the existing read model — the platform only accepts an `EVENT → READMODEL` backward connection when the read model already has a `READMODEL → AUTOMATION` edge (the todo-list pattern from Step 4b, `eventmodeling-designing-automation-chains`). For any read model feeding a SCREEN, resolve the update the same way Step 5c resolves multi-component screens: place a **new
|
|
317
|
+
**The same rule applies to `EVENT → READMODEL`** (shared statement: `eventmodeling-core-rules`'s "Updating a Read Model That Already Feeds a Screen"). If a later event needs to update data a read model already feeds to a SCREEN, do not connect that later event back into the existing read model — the platform only accepts an `EVENT → READMODEL` backward connection when the read model already has a `READMODEL → AUTOMATION` edge (the todo-list pattern from Step 4b, `eventmodeling-designing-automation-chains`). For any read model feeding a SCREEN, resolve the update the same way Step 5c resolves multi-component screens: place a **new READMODEL node** in a new column immediately after the later event's column, then link it to the earlier read model instance (`link_element` — it's the same read model carried forward, not an unrelated new one; see `eventmodeling-core-rules`'s Linked Copies section). Connect the later event forward into the linked copy, then update the field(s) that event actually changes. Place a matching SCREEN in that same new column — same title, updated data, optionally re-marked/highlighted via `html-screen`'s Marks feature; a SCREEN can't be a `linkedTo` copy itself (COMMAND/EVENT/READMODEL only), so place it as a plain node matching the earlier screen's title.
|
|
318
318
|
|
|
319
319
|
### Step 5h — Wire connections after placing each READMODEL (and its SCREEN)
|
|
320
320
|
|
|
@@ -122,7 +122,7 @@ The timeline must always progress left-to-right — this is the goal to design t
|
|
|
122
122
|
|
|
123
123
|
**`EVENT → READMODEL` has exactly one exception, and it is narrow.** A read model that already carries a `READMODEL → AUTOMATION` edge — i.e. a todo-list read model feeding an automation, per `eventmodeling-designing-automation-chains` (Step 4b) — may also be fed by a later-column event closing an item it opened earlier. That accumulator shape is what the todo-list pattern exists for, and it is confirmed against the platform API (`learn-eventmodelers-api` §3 — `POST .../connections`). **Outside that one case, the platform rejects the connection, for good reason:** without it, a read model would become a moving target for whatever screen or scenario later reaches back into it.
|
|
124
124
|
|
|
125
|
-
For every other read model — in particular one feeding a SCREEN rather than an AUTOMATION — never connect a later event back into it, no matter how convenient. If a later event needs to update what a screen already shows, resolve it the same way Step 5c resolves multi-component screens: place a **new
|
|
125
|
+
For every other read model — in particular one feeding a SCREEN rather than an AUTOMATION — never connect a later event back into it, no matter how convenient. If a later event needs to update what a screen already shows, resolve it the same way Step 5c resolves multi-component screens: place a **new READMODEL node** in a new column immediately after the later event's column, link it to the earlier instance (`link_element` — it's the same read model, not an unrelated new one; see `eventmodeling-core-rules`'s Linked Copies section), connect the later event forward into the linked copy, update the field(s) that event actually changes, and place a matching copy of the same screen there (same title, updated data, optionally re-marked/highlighted per `html-screen`'s Marks feature — a SCREEN can't itself be linked, so it's a plain node matching the earlier one's title).
|
|
126
126
|
|
|
127
127
|
A wide fan-in read model (many connected events, one column) is a different problem with a different fix — see "one read model per component" and the >3-events heuristic above. It is never a justification for a backward arrow. The one real signal to treat as a prunable connection regardless of column position is a **connected event that isn't actually used by any field** on the read model.
|
|
128
128
|
|
package/stacks/modeling-kit/templates/.claude/skills/eventmodeling-slicing-event-models/SKILL.md
CHANGED
|
@@ -39,6 +39,8 @@ Walk the timeline column by column:
|
|
|
39
39
|
- Every **READMODEL** → one `state-view` slice, named after the read model
|
|
40
40
|
- Every **AUTOMATION** → one `automation` slice, named after the automation (or the command it issues)
|
|
41
41
|
|
|
42
|
+
**Skip any column whose only COMMAND/READMODEL node has `meta.linkedTo` set** — it's a linked copy of a node elsewhere on the board (see `eventmodeling-core-rules`'s Linked Copies and Slices sections), not a second independently-deployable thing. Only the origin node's own column gets a slice.
|
|
43
|
+
|
|
42
44
|
---
|
|
43
45
|
|
|
44
46
|
## Step 1: Resolve the Timeline
|
|
@@ -226,7 +226,7 @@ Use this REST call (see the curl fallback below) when you don't already have fre
|
|
|
226
226
|
| Any | Same element type | Stop and tell the user — true conflict, no safe default. |
|
|
227
227
|
| Any | Different type but not a known pairing | Stop and tell the user. |
|
|
228
228
|
|
|
229
|
-
**A later EVENT that needs to update a READMODEL already feeding a SCREEN is not a Step 6 occupancy case — it's a backward-connection trap.** Do not `set_connection` that EVENT into the existing READMODEL, even if the existing READMODEL's column is reachable. See `eventmodeling-core-rules`'s "Updating a Read Model That Already Feeds a Screen": place a **new** READMODEL
|
|
229
|
+
**A later EVENT that needs to update a READMODEL already feeding a SCREEN is not a Step 6 occupancy case — it's a backward-connection trap.** Do not `set_connection` that EVENT into the existing READMODEL, even if the existing READMODEL's column is reachable. See `eventmodeling-core-rules`'s "Updating a Read Model That Already Feeds a Screen": place a **new** READMODEL in a new column immediately after the event's column instead, link it to the existing READMODEL as a `linkedTo` copy (Step 6a below — it's the same read model, not an unrelated new one), connect the event forward into the linked copy, and give it a matching SCREEN in the same column (a plain node, not linked — SCREEN isn't a linkable type).
|
|
230
230
|
|
|
231
231
|
**Insert immediately after** means: create the new column right after the current one, not by appending to the end. This keeps the read model visually adjacent to the event that drives it.
|
|
232
232
|
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
# Node.js / TypeScript Event-Sourced Service
|
|
2
|
+
|
|
3
|
+
An event-sourced backend scaffolded by [`@eventmodelers/cli`](https://www.npmjs.com/package/@eventmodelers/cli).
|
|
4
|
+
It uses [Emmett](https://event-driven-io.github.io/emmett/) over Postgres for the event store,
|
|
5
|
+
Express for the HTTP layer, and Flyway for schema migrations.
|
|
6
|
+
|
|
7
|
+
Features are built as **vertical slices** under `src/slices/`, generated from the slices on your
|
|
8
|
+
Eventmodelers board by the agent in `.build-kit/`.
|
|
9
|
+
|
|
10
|
+
## Prerequisites
|
|
11
|
+
|
|
12
|
+
- Node.js 20 or later (the dev/start scripts use `node --env-file`)
|
|
13
|
+
- Docker and Docker Compose (for local Postgres)
|
|
14
|
+
- Flyway CLI on your `PATH` (for `npm run flyway:migrate`)
|
|
15
|
+
- [Claude Code](https://claude.com/claude-code) if you want to run the build agent
|
|
16
|
+
|
|
17
|
+
## Getting started
|
|
18
|
+
|
|
19
|
+
1. Start Postgres:
|
|
20
|
+
|
|
21
|
+
```bash
|
|
22
|
+
docker compose up -d
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
2. Create your `.env`:
|
|
26
|
+
|
|
27
|
+
```bash
|
|
28
|
+
cp .env.example .env
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
Or run `./setup-env.sh` to be prompted for host, port, database, user and password.
|
|
32
|
+
|
|
33
|
+
3. Activate the baseline migration and apply it:
|
|
34
|
+
|
|
35
|
+
```bash
|
|
36
|
+
mv migrations/V1__schema.sql.example migrations/V1__schema.sql
|
|
37
|
+
npm install
|
|
38
|
+
npm run flyway:migrate
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
`V1__schema.sql` creates the processor dead-letter queue table the runtime expects. Add your own
|
|
42
|
+
`V2__*.sql`, `V3__*.sql` and so on as slices introduce projections.
|
|
43
|
+
|
|
44
|
+
4. Run the server:
|
|
45
|
+
|
|
46
|
+
```bash
|
|
47
|
+
npm run build # slice routes and processors are loaded from dist/
|
|
48
|
+
npm run dev
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
The API is on http://localhost:3000, with Swagger UI at http://localhost:3000/api-docs and the raw
|
|
52
|
+
OpenAPI document at http://localhost:3000/swagger.json.
|
|
53
|
+
|
|
54
|
+
## Scripts
|
|
55
|
+
|
|
56
|
+
| Script | What it does |
|
|
57
|
+
|---|---|
|
|
58
|
+
| `npm run dev` | Start the server locally with `.env` loaded |
|
|
59
|
+
| `npm run build` | Compile TypeScript to `dist/` |
|
|
60
|
+
| `npm start` | Start in production mode |
|
|
61
|
+
| `npm test` | Run `src/**/*.test.ts` via `tsx --test` |
|
|
62
|
+
| `npm run flyway:migrate` | Apply pending migrations from `migrations/` |
|
|
63
|
+
|
|
64
|
+
## Learn more
|
|
65
|
+
|
|
66
|
+
- [Eventmodelers](https://eventmodelers.ai)
|
|
67
|
+
- [Emmett documentation](https://event-driven-io.github.io/emmett/)
|