effect-cqrs 0.0.0-stage → 0.1.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/LICENSE +21 -0
- package/README.md +298 -2
- package/dist/Aggregate.d.ts +86 -0
- package/dist/Aggregate.d.ts.map +1 -0
- package/dist/Aggregate.js +15 -0
- package/dist/Aggregate.js.map +1 -0
- package/dist/AggregateService.d.ts +82 -0
- package/dist/AggregateService.d.ts.map +1 -0
- package/dist/AggregateService.js +181 -0
- package/dist/AggregateService.js.map +1 -0
- package/dist/Checkpoints.d.ts +19 -0
- package/dist/Checkpoints.d.ts.map +1 -0
- package/dist/Checkpoints.js +18 -0
- package/dist/Checkpoints.js.map +1 -0
- package/dist/EventStore.d.ts +110 -0
- package/dist/EventStore.d.ts.map +1 -0
- package/dist/EventStore.js +136 -0
- package/dist/EventStore.js.map +1 -0
- package/dist/Fixture.d.ts +71 -0
- package/dist/Fixture.d.ts.map +1 -0
- package/dist/Fixture.js +108 -0
- package/dist/Fixture.js.map +1 -0
- package/dist/PostgresEventStore.d.ts +30 -0
- package/dist/PostgresEventStore.d.ts.map +1 -0
- package/dist/PostgresEventStore.js +144 -0
- package/dist/PostgresEventStore.js.map +1 -0
- package/dist/SqliteEventStore.d.ts +25 -0
- package/dist/SqliteEventStore.d.ts.map +1 -0
- package/dist/SqliteEventStore.js +123 -0
- package/dist/SqliteEventStore.js.map +1 -0
- package/dist/ViewModel.d.ts +90 -0
- package/dist/ViewModel.d.ts.map +1 -0
- package/dist/ViewModel.js +170 -0
- package/dist/ViewModel.js.map +1 -0
- package/dist/index.d.ts +9 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +9 -0
- package/dist/index.js.map +1 -0
- package/package.json +87 -3
- package/src/Aggregate.ts +133 -0
- package/src/AggregateService.ts +342 -0
- package/src/Checkpoints.ts +23 -0
- package/src/EventStore.ts +233 -0
- package/src/Fixture.ts +213 -0
- package/src/PostgresEventStore.ts +188 -0
- package/src/SqliteEventStore.ts +166 -0
- package/src/ViewModel.ts +348 -0
- package/src/index.ts +8 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2019-2026 David Nussio
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
CHANGED
|
@@ -1,3 +1,299 @@
|
|
|
1
|
-
#
|
|
1
|
+
# effect-cqrs
|
|
2
2
|
|
|
3
|
-
This
|
|
3
|
+
This is a trial port of an old project,
|
|
4
|
+
[moleculer-cqrs](https://github.com/davidnussio/moleculer-cqrs), to
|
|
5
|
+
[Effect](https://effect.website) v4. The original is a CQRS and event sourcing module
|
|
6
|
+
for [Moleculer](https://moleculer.services), written in 2019. This repository
|
|
7
|
+
rebuilds the same ideas on Effect to find out how well they carry over.
|
|
8
|
+
|
|
9
|
+
It's an experiment, not a maintained library: the API may change, and
|
|
10
|
+
[Limitations](#limitations) lists what the port leaves out.
|
|
11
|
+
|
|
12
|
+
The documentation, with a walkthrough of the bank demo, is at
|
|
13
|
+
https://davidnussio.github.io/effect-cqrs/.
|
|
14
|
+
|
|
15
|
+
Commands decide on events, events are the only source of truth, and read models
|
|
16
|
+
are projections you can throw away and rebuild.
|
|
17
|
+
|
|
18
|
+
## Install
|
|
19
|
+
|
|
20
|
+
```bash
|
|
21
|
+
pnpm add effect-cqrs effect
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
It needs Node 24 or later and Effect 4. To store events in SQLite or PostgreSQL, add
|
|
25
|
+
that database's `effect/sql` driver too, `@effect/sql-sqlite-node` or `@effect/sql-pg`.
|
|
26
|
+
|
|
27
|
+
## Develop
|
|
28
|
+
|
|
29
|
+
```bash
|
|
30
|
+
pnpm install
|
|
31
|
+
pnpm test
|
|
32
|
+
pnpm example
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
The example needs `node:sqlite` and runs TypeScript directly. `pnpm build` compiles
|
|
36
|
+
`src` into `dist`, which is what npm gets.
|
|
37
|
+
|
|
38
|
+
`pnpm ui` serves an interactive demo at http://localhost:5173, also published at
|
|
39
|
+
https://davidnussio.github.io/effect-cqrs/demo/. It runs the framework
|
|
40
|
+
in the browser on the in-memory store: a bank whose accounts are aggregates, three
|
|
41
|
+
view models, and a saga for transfers. Its devtools slow commands and projections
|
|
42
|
+
down, pause and step the command bus, crash a projection, scrub through the log and
|
|
43
|
+
replay view models up to a chosen event.
|
|
44
|
+
|
|
45
|
+
## Define an aggregate
|
|
46
|
+
|
|
47
|
+
An aggregate is a schema of events, a projection folding them into state, and
|
|
48
|
+
commands deciding on new events.
|
|
49
|
+
|
|
50
|
+
```ts
|
|
51
|
+
import { DateTime, Effect, Schema } from "effect"
|
|
52
|
+
import { Aggregate } from "effect-cqrs"
|
|
53
|
+
|
|
54
|
+
const TodoEvent = Schema.TaggedUnion({
|
|
55
|
+
TodoCreated: { title: Schema.NonEmptyString, createdAt: Schema.DateTimeUtc },
|
|
56
|
+
TodoDeleted: { deletedAt: Schema.DateTimeUtc }
|
|
57
|
+
})
|
|
58
|
+
|
|
59
|
+
class TodoAlreadyDeleted extends Schema.TaggedError<TodoAlreadyDeleted>()("TodoAlreadyDeleted", {}) {}
|
|
60
|
+
|
|
61
|
+
type TodoState = { readonly title: string; readonly deleted: boolean }
|
|
62
|
+
|
|
63
|
+
const Todo = Aggregate.make<TodoState>()({
|
|
64
|
+
name: "todo",
|
|
65
|
+
events: TodoEvent,
|
|
66
|
+
initial: { title: "", deleted: false },
|
|
67
|
+
projection: {
|
|
68
|
+
TodoCreated: (state, { title }) => ({ ...state, title }),
|
|
69
|
+
TodoDeleted: (state) => ({ ...state, deleted: true })
|
|
70
|
+
}
|
|
71
|
+
})
|
|
72
|
+
.command("createTodo", {
|
|
73
|
+
payload: Schema.Struct({ title: Schema.NonEmptyString }),
|
|
74
|
+
handler: Effect.fnUntraced(function*(_, { title }) {
|
|
75
|
+
return TodoEvent.cases.TodoCreated.make({ title, createdAt: yield* DateTime.now })
|
|
76
|
+
})
|
|
77
|
+
})
|
|
78
|
+
.command("deleteTodo", {
|
|
79
|
+
payload: Schema.Struct({}),
|
|
80
|
+
handler: Effect.fnUntraced(function*(state) {
|
|
81
|
+
if (state.deleted) return yield* new TodoAlreadyDeleted()
|
|
82
|
+
return TodoEvent.cases.TodoDeleted.make({ deletedAt: yield* DateTime.now })
|
|
83
|
+
})
|
|
84
|
+
})
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
The projection must cover every event or it doesn't compile. A handler returns one
|
|
88
|
+
event, several, or `[]` to record nothing. It can use services, fail with typed
|
|
89
|
+
errors, and may run more than once, so it should only decide. Projections and
|
|
90
|
+
handlers must not mutate the state, which is kept between commands.
|
|
91
|
+
|
|
92
|
+
## Run commands
|
|
93
|
+
|
|
94
|
+
```ts
|
|
95
|
+
import { AggregateService, EventStore } from "effect-cqrs"
|
|
96
|
+
|
|
97
|
+
const program = Effect.gen(function*() {
|
|
98
|
+
const todos = yield* AggregateService.make(Todo)
|
|
99
|
+
|
|
100
|
+
// Typed call
|
|
101
|
+
yield* todos.commands.createTodo("todo-1", { title: "Buy Milk" })
|
|
102
|
+
|
|
103
|
+
// Untyped JSON from a transport, validated before running
|
|
104
|
+
yield* todos.dispatch({ aggregateId: "todo-2", type: "createTodo", payload: { title: "Buy Eggs" } })
|
|
105
|
+
|
|
106
|
+
yield* todos.readModel("todo-1")
|
|
107
|
+
yield* todos.readModel("todo-1", { finishTime }) // state as it was before finishTime
|
|
108
|
+
yield* todos.history("todo-1", { payload: true })
|
|
109
|
+
}).pipe(Effect.provide(EventStore.layerMemory))
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
Commands on one stream take turns inside a service, so they can't race. A handler must
|
|
113
|
+
not run a command on its own stream, which would wait for itself. Across processes
|
|
114
|
+
writers use optimistic concurrency: a command that loses a race backs off, reloads the
|
|
115
|
+
state and decides again, up to `maxRetries` times (5 by default), then fails with
|
|
116
|
+
`ConcurrencyConflict`.
|
|
117
|
+
|
|
118
|
+
The service remembers the state of recent aggregates (`stateCacheSize`, 10,000 by
|
|
119
|
+
default), so a command only reads the events it hasn't seen.
|
|
120
|
+
|
|
121
|
+
## Project into view models
|
|
122
|
+
|
|
123
|
+
A view model reads the event store from its own checkpoint, so it catches up after
|
|
124
|
+
downtime and can't miss an event. Delivery is at least once, so handlers should be
|
|
125
|
+
idempotent.
|
|
126
|
+
|
|
127
|
+
```ts
|
|
128
|
+
import { Checkpoints, ViewModel } from "effect-cqrs"
|
|
129
|
+
|
|
130
|
+
const TodoList = ViewModel.make({
|
|
131
|
+
name: "todo-list",
|
|
132
|
+
aggregates: [Todo],
|
|
133
|
+
dispose: clearList, // runs before a replay
|
|
134
|
+
handlers: {
|
|
135
|
+
TodoCreated: ({ title }, { aggregateId }) => addToList(aggregateId, title),
|
|
136
|
+
TodoDeleted: (_, { aggregateId }) => removeFromList(aggregateId)
|
|
137
|
+
}
|
|
138
|
+
})
|
|
139
|
+
|
|
140
|
+
const program = Effect.gen(function*() {
|
|
141
|
+
const todos = yield* AggregateService.make(Todo)
|
|
142
|
+
const viewModels = yield* ViewModel.ViewModels
|
|
143
|
+
|
|
144
|
+
const { position } = yield* todos.commands.createTodo("todo-1", { title: "Buy Milk" })
|
|
145
|
+
yield* viewModels.caughtUp(position) // read your own write
|
|
146
|
+
|
|
147
|
+
yield* viewModels.replay({ viewModels: ["todo-list"] })
|
|
148
|
+
yield* viewModels.status
|
|
149
|
+
}).pipe(
|
|
150
|
+
Effect.provide(ViewModel.layer([TodoList]).pipe(
|
|
151
|
+
Layer.provideMerge(Layer.merge(EventStore.layerMemory, Checkpoints.layerMemory))
|
|
152
|
+
))
|
|
153
|
+
)
|
|
154
|
+
```
|
|
155
|
+
|
|
156
|
+
A failing handler stalls its view model on that event and retries with backoff, so
|
|
157
|
+
events are never skipped or reordered. `viewModels.status` exposes the failure.
|
|
158
|
+
|
|
159
|
+
Anything else that reacts to events can use `store.subscribe(filter)`, a stream of
|
|
160
|
+
past events followed by live ones.
|
|
161
|
+
|
|
162
|
+
## Test an aggregate
|
|
163
|
+
|
|
164
|
+
`Fixture` runs commands through the real service on an in-memory store, so payload
|
|
165
|
+
validation and event encoding are tested too. It reads like `CQRSFixture`, and the
|
|
166
|
+
chain runs when you yield it or hand it to `it.effect`:
|
|
167
|
+
|
|
168
|
+
```ts
|
|
169
|
+
import { assert, it } from "@effect/vitest"
|
|
170
|
+
import { Fixture } from "effect-cqrs"
|
|
171
|
+
|
|
172
|
+
it.effect("creates a todo", () =>
|
|
173
|
+
Fixture.make(Todo)
|
|
174
|
+
.givenEvents()
|
|
175
|
+
.when("createTodo", { title: "Buy Milk" })
|
|
176
|
+
.expectEvent(TodoEvent.cases.TodoCreated.make({ title: "Buy Milk", createdAt: DateTime.makeUnsafe(0) }))
|
|
177
|
+
.inspectState((state) => assert.strictEqual(state.title, "Buy Milk")))
|
|
178
|
+
|
|
179
|
+
it.effect("runs commands one after another", () =>
|
|
180
|
+
Fixture.make(Todo)
|
|
181
|
+
.givenEvents([created])
|
|
182
|
+
.when("deleteTodo", {})
|
|
183
|
+
.expectEvent("TodoDeleted")
|
|
184
|
+
.when("createTodo", { title: "Buy Eggs" })
|
|
185
|
+
.expectEvents(["TodoCreated"]))
|
|
186
|
+
|
|
187
|
+
it.effect("rejects deleting twice", () =>
|
|
188
|
+
Effect.gen(function*() {
|
|
189
|
+
const error = yield* Fixture.make(Todo)
|
|
190
|
+
.givenEvents([created, deleted])
|
|
191
|
+
.whenFails("deleteTodo", {})
|
|
192
|
+
|
|
193
|
+
assert.strictEqual(error._tag, "TodoAlreadyDeleted")
|
|
194
|
+
}))
|
|
195
|
+
```
|
|
196
|
+
|
|
197
|
+
- `when` runs a command by name, typed against the aggregate. Commands that fail
|
|
198
|
+
stop the chain with their typed error.
|
|
199
|
+
- `expectEvent` expects exactly one event and `expectEvents` an exact, ordered list,
|
|
200
|
+
so `expectEvents([])` asserts that nothing was recorded. Each entry is a whole
|
|
201
|
+
event, compared with the aggregate's schema, or only its tag.
|
|
202
|
+
- `inspectState` takes a callback that throws to fail the test, so any assertion
|
|
203
|
+
library works.
|
|
204
|
+
- Yielding the chain gives `{ events, state, version, fixture }` for the last
|
|
205
|
+
command, if you prefer plain assertions.
|
|
206
|
+
|
|
207
|
+
`it.effect` runs on a test clock stopped at zero, so timestamps need no mocking: an
|
|
208
|
+
event created by `DateTime.now` has `DateTime.makeUnsafe(0)` until
|
|
209
|
+
`TestClock.adjust` moves time.
|
|
210
|
+
|
|
211
|
+
## Storage
|
|
212
|
+
|
|
213
|
+
`EventStore.layerMemory` keeps events in memory. For SQLite, provide any
|
|
214
|
+
`effect/sql` SQLite client:
|
|
215
|
+
|
|
216
|
+
```ts
|
|
217
|
+
import { SqliteClient } from "@effect/sql-sqlite-node"
|
|
218
|
+
import { SqliteEventStore } from "effect-cqrs"
|
|
219
|
+
|
|
220
|
+
const Storage = Layer.merge(SqliteEventStore.layer(), SqliteEventStore.layerCheckpoints()).pipe(
|
|
221
|
+
Layer.provide(SqliteClient.layer({ filename: "./data/events.sqlite" }))
|
|
222
|
+
)
|
|
223
|
+
```
|
|
224
|
+
|
|
225
|
+
Tables are created on startup. Use durable checkpoints only with a durable view
|
|
226
|
+
model: an in-memory view model paired with `Checkpoints.layerMemory` rebuilds itself
|
|
227
|
+
on every start, which is usually what you want.
|
|
228
|
+
|
|
229
|
+
For PostgreSQL, provide an `effect/sql` PostgreSQL client:
|
|
230
|
+
|
|
231
|
+
```ts
|
|
232
|
+
import { PgClient } from "@effect/sql-pg"
|
|
233
|
+
import { PostgresEventStore } from "effect-cqrs"
|
|
234
|
+
|
|
235
|
+
const Storage = Layer.merge(PostgresEventStore.layer(), PostgresEventStore.layerCheckpoints()).pipe(
|
|
236
|
+
Layer.provide(PgClient.layer({ url: Redacted.make("postgres://localhost:5432/cqrs") }))
|
|
237
|
+
)
|
|
238
|
+
```
|
|
239
|
+
|
|
240
|
+
Writers wait for each other at one step of each commit, which keeps positions in commit
|
|
241
|
+
order. Several processes can write to one database, but a view model only wakes on
|
|
242
|
+
appends from its own process. After a bulk load, run `ANALYZE`, or reads for view models
|
|
243
|
+
are slow until autovacuum gets to the table.
|
|
244
|
+
|
|
245
|
+
To run the tests on Postgres, `docker compose up -d --wait` starts one, and setting
|
|
246
|
+
`POSTGRES_URL=postgres://postgres:postgres@localhost:5432/cqrs` includes it in
|
|
247
|
+
`pnpm test`.
|
|
248
|
+
|
|
249
|
+
Another database needs an `EventStore.Backend`: `append`, `readBatch` and `head`.
|
|
250
|
+
Positions must follow commit order, so a backend on a database with concurrent
|
|
251
|
+
writers has to guarantee that itself.
|
|
252
|
+
|
|
253
|
+
## Performance
|
|
254
|
+
|
|
255
|
+
On a SQLite file a command takes about 0.1 ms, however long its stream is and however
|
|
256
|
+
many events the store holds, and a million events replay in under 5 seconds. On Postgres
|
|
257
|
+
a command takes about 1 ms. The first use of a stream after a restart replays it, at
|
|
258
|
+
about 3 µs per event. See the [performance and stress report](bench/REPORT.md) for the
|
|
259
|
+
numbers and the remaining limits.
|
|
260
|
+
|
|
261
|
+
`pnpm bench` measures each operation and `pnpm stress` loads the framework while
|
|
262
|
+
checking that nothing is lost.
|
|
263
|
+
|
|
264
|
+
## Coming from moleculer-cqrs
|
|
265
|
+
|
|
266
|
+
| moleculer-cqrs | effect-cqrs |
|
|
267
|
+
| --- | --- |
|
|
268
|
+
| `CQRSEventSourcing({ aggregate })` mixin | `AggregateService.make(aggregate)` |
|
|
269
|
+
| `call todo.command` | `todos.commands.x(id, payload)` or `todos.dispatch(json)` |
|
|
270
|
+
| `call todo.read-model` | `todos.readModel(id, { finishTime })` |
|
|
271
|
+
| `call todo.history` | `todos.history(id, { payload, startTime, finishTime })` |
|
|
272
|
+
| `call todo.replay` | `viewModels.replay({ viewModels, startTime, finishTime })` |
|
|
273
|
+
| view model service with `events` | `ViewModel.make({ handlers, dispose })` |
|
|
274
|
+
| `new CQRSFixture(aggregate)` | `Fixture.make(aggregate)` |
|
|
275
|
+
| `.givenEvents([...])` | `.givenEvents([...])` |
|
|
276
|
+
| `.when(createNews, payload)` | `.when("createNews", payload)` |
|
|
277
|
+
| `.expectEvent(event)` | `.expectEvent(event)` or `.expectEvent("Tag")` |
|
|
278
|
+
| `.inspectState(cb)` | `.inspectState(cb)` |
|
|
279
|
+
| `.whenThrow(command).toThrow(...)` | `.whenFails(command, payload)`, which returns the error |
|
|
280
|
+
| `fastest-validator` | `Schema` |
|
|
281
|
+
| `throw` in a command | typed errors |
|
|
282
|
+
| `resolve-storage-*` adapters | `EventStore.Backend` |
|
|
283
|
+
|
|
284
|
+
The largest change is on the read side. moleculer-cqrs broadcasts events over the
|
|
285
|
+
broker and only replay recovers one that was lost; here view models pull from the
|
|
286
|
+
store, so `replay` is for rebuilding, not for recovery.
|
|
287
|
+
|
|
288
|
+
## Limitations
|
|
289
|
+
|
|
290
|
+
- **New events stay in their process.** They wake subscribers in the same process only.
|
|
291
|
+
There is no transport equivalent to the Moleculer broker, so `dispatch` is the entry
|
|
292
|
+
point to wire into your own HTTP or RPC layer.
|
|
293
|
+
- **SQLite, PostgreSQL and memory only.** There are no adapters for MongoDB or MySQL.
|
|
294
|
+
- **No code generator.** There is no equivalent of `cqrs generate`.
|
|
295
|
+
- **No persistent snapshots.** Recent state is only kept in memory, so after a restart
|
|
296
|
+
each stream is replayed the first time it is used, at a cost that grows with its
|
|
297
|
+
length.
|
|
298
|
+
- **No schema migration.** A stored event that no longer decodes fails with
|
|
299
|
+
`EventDecodeError`, so evolve event schemas compatibly.
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The write side of the domain: a schema of events, a projection that folds them
|
|
3
|
+
* into state, and commands that inspect that state and decide on new events.
|
|
4
|
+
*
|
|
5
|
+
* An aggregate is plain data. {@link AggregateService} gives it a store to run
|
|
6
|
+
* against, and `Fixture` runs it in tests.
|
|
7
|
+
*/
|
|
8
|
+
import type { DateTime, Effect, Schema } from "effect";
|
|
9
|
+
export interface Tagged {
|
|
10
|
+
readonly _tag: string;
|
|
11
|
+
}
|
|
12
|
+
export type EventSchema<Event extends Tagged = Tagged> = Schema.Top & {
|
|
13
|
+
readonly Type: Event;
|
|
14
|
+
readonly DecodingServices: never;
|
|
15
|
+
readonly EncodingServices: never;
|
|
16
|
+
};
|
|
17
|
+
export type PayloadSchema = Schema.Top & {
|
|
18
|
+
readonly DecodingServices: never;
|
|
19
|
+
readonly EncodingServices: never;
|
|
20
|
+
};
|
|
21
|
+
/** Where an event sits in its stream and in the store. */
|
|
22
|
+
export interface EventMeta {
|
|
23
|
+
readonly aggregateName: string;
|
|
24
|
+
readonly aggregateId: string;
|
|
25
|
+
readonly version: number;
|
|
26
|
+
readonly position: number;
|
|
27
|
+
readonly timestamp: DateTime.Utc;
|
|
28
|
+
}
|
|
29
|
+
export interface CommandContext {
|
|
30
|
+
readonly aggregateId: string;
|
|
31
|
+
readonly version: number;
|
|
32
|
+
}
|
|
33
|
+
/** Exhaustive by event tag, so adding an event to the schema without folding it does not compile. */
|
|
34
|
+
export type Projection<State, Event extends Tagged> = {
|
|
35
|
+
readonly [Tag in Event["_tag"]]: (state: State, event: Extract<Event, {
|
|
36
|
+
readonly _tag: Tag;
|
|
37
|
+
}>, meta: EventMeta) => State;
|
|
38
|
+
};
|
|
39
|
+
/**
|
|
40
|
+
* Decides on the events a command produces. It may run several times when
|
|
41
|
+
* concurrent writers collide, so it should only decide and leave side effects to
|
|
42
|
+
* view models. Returning an empty array records nothing.
|
|
43
|
+
*/
|
|
44
|
+
export type CommandHandler<State, Payload, Event, E, R> = (state: State, payload: Payload, context: CommandContext) => Effect.Effect<Event | ReadonlyArray<Event>, E, R>;
|
|
45
|
+
export interface Command<Payload extends PayloadSchema, State, Event, E, R> {
|
|
46
|
+
readonly payload: Payload;
|
|
47
|
+
readonly handler: CommandHandler<State, Payload["Type"], Event, E, R>;
|
|
48
|
+
}
|
|
49
|
+
export interface AnyCommand {
|
|
50
|
+
readonly payload: PayloadSchema;
|
|
51
|
+
readonly handler: CommandHandler<any, any, any, any, any>;
|
|
52
|
+
}
|
|
53
|
+
export interface Aggregate<Name extends string, State, Events extends EventSchema<any>, Commands extends Record<string, AnyCommand>> {
|
|
54
|
+
readonly name: Name;
|
|
55
|
+
readonly events: Events;
|
|
56
|
+
readonly initial: State;
|
|
57
|
+
readonly projection: Projection<State, Events["Type"]>;
|
|
58
|
+
readonly commands: Commands;
|
|
59
|
+
/** Returns a new aggregate that also accepts this command. */
|
|
60
|
+
command<const CommandName extends string, Payload extends PayloadSchema, E = never, R = never>(name: CommandName, options: {
|
|
61
|
+
readonly payload: Payload;
|
|
62
|
+
readonly handler: CommandHandler<State, Payload["Type"], Events["Type"], E, R>;
|
|
63
|
+
}): Aggregate<Name, State, Events, Commands & Record<CommandName, Command<Payload, State, Events["Type"], E, R>>>;
|
|
64
|
+
}
|
|
65
|
+
export type Any = Aggregate<string, any, EventSchema<any>, Record<string, AnyCommand>>;
|
|
66
|
+
export type Name<A extends Any> = A["name"];
|
|
67
|
+
export type State<A extends Any> = A["initial"];
|
|
68
|
+
export type Event<A extends Any> = A["events"]["Type"];
|
|
69
|
+
export type Commands<A extends Any> = A["commands"];
|
|
70
|
+
export type CommandName<A extends Any> = keyof A["commands"] & string;
|
|
71
|
+
export type Payload<A extends Any, K extends CommandName<A>> = A["commands"][K]["payload"]["Type"];
|
|
72
|
+
export type CommandError<A extends Any, K extends CommandName<A> = CommandName<A>> = A["commands"][K] extends Command<any, any, any, infer E, any> ? E : never;
|
|
73
|
+
export type Context<A extends Any> = A["commands"][CommandName<A>] extends Command<any, any, any, any, infer R> ? R : never;
|
|
74
|
+
/**
|
|
75
|
+
* The state is given explicitly, `Aggregate.make<State>()({ ... })`, so that the
|
|
76
|
+
* projection's return values are checked against it rather than widened.
|
|
77
|
+
*/
|
|
78
|
+
export declare const make: <State>() => <const Name extends string, Events extends EventSchema<any>>(options: {
|
|
79
|
+
readonly name: Name;
|
|
80
|
+
readonly events: Events;
|
|
81
|
+
readonly initial: State;
|
|
82
|
+
readonly projection: Projection<State, Events["Type"]>;
|
|
83
|
+
}) => Aggregate<Name, State, Events, {}>;
|
|
84
|
+
/** Folds one event into the state. */
|
|
85
|
+
export declare const evolve: <A extends Any>(aggregate: A, state: State<A>, event: Event<A>, meta: EventMeta) => State<A>;
|
|
86
|
+
//# sourceMappingURL=Aggregate.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"Aggregate.d.ts","sourceRoot":"","sources":["../src/Aggregate.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AACH,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAA;AAEtD,MAAM,WAAW,MAAM;IACrB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;CACtB;AAED,MAAM,MAAM,WAAW,CAAC,KAAK,SAAS,MAAM,GAAG,MAAM,IAAI,MAAM,CAAC,GAAG,GAAG;IACpE,QAAQ,CAAC,IAAI,EAAE,KAAK,CAAA;IACpB,QAAQ,CAAC,gBAAgB,EAAE,KAAK,CAAA;IAChC,QAAQ,CAAC,gBAAgB,EAAE,KAAK,CAAA;CACjC,CAAA;AAED,MAAM,MAAM,aAAa,GAAG,MAAM,CAAC,GAAG,GAAG;IACvC,QAAQ,CAAC,gBAAgB,EAAE,KAAK,CAAA;IAChC,QAAQ,CAAC,gBAAgB,EAAE,KAAK,CAAA;CACjC,CAAA;AAED,0DAA0D;AAC1D,MAAM,WAAW,SAAS;IACxB,QAAQ,CAAC,aAAa,EAAE,MAAM,CAAA;IAC9B,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAA;IAC5B,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAA;IACxB,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAA;IACzB,QAAQ,CAAC,SAAS,EAAE,QAAQ,CAAC,GAAG,CAAA;CACjC;AAED,MAAM,WAAW,cAAc;IAC7B,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAA;IAC5B,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAA;CACzB;AAED,qGAAqG;AACrG,MAAM,MAAM,UAAU,CAAC,KAAK,EAAE,KAAK,SAAS,MAAM,IAAI;IACpD,QAAQ,EAAE,GAAG,IAAI,KAAK,CAAC,MAAM,CAAC,GAAG,CAAC,KAAK,EAAE,KAAK,EAAE,KAAK,EAAE,OAAO,CAAC,KAAK,EAAE;QAAE,QAAQ,CAAC,IAAI,EAAE,GAAG,CAAA;KAAE,CAAC,EAAE,IAAI,EAAE,SAAS,KAAK,KAAK;CACzH,CAAA;AAED;;;;GAIG;AACH,MAAM,MAAM,cAAc,CAAC,KAAK,EAAE,OAAO,EAAE,KAAK,EAAE,CAAC,EAAE,CAAC,IAAI,CACxD,KAAK,EAAE,KAAK,EACZ,OAAO,EAAE,OAAO,EAChB,OAAO,EAAE,cAAc,KACpB,MAAM,CAAC,MAAM,CAAC,KAAK,GAAG,aAAa,CAAC,KAAK,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC,CAAA;AAEtD,MAAM,WAAW,OAAO,CAAC,OAAO,SAAS,aAAa,EAAE,KAAK,EAAE,KAAK,EAAE,CAAC,EAAE,CAAC;IACxE,QAAQ,CAAC,OAAO,EAAE,OAAO,CAAA;IACzB,QAAQ,CAAC,OAAO,EAAE,cAAc,CAAC,KAAK,EAAE,OAAO,CAAC,MAAM,CAAC,EAAE,KAAK,EAAE,CAAC,EAAE,CAAC,CAAC,CAAA;CACtE;AAED,MAAM,WAAW,UAAU;IACzB,QAAQ,CAAC,OAAO,EAAE,aAAa,CAAA;IAC/B,QAAQ,CAAC,OAAO,EAAE,cAAc,CAAC,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,CAAC,CAAA;CAC1D;AAED,MAAM,WAAW,SAAS,CACxB,IAAI,SAAS,MAAM,EACnB,KAAK,EACL,MAAM,SAAS,WAAW,CAAC,GAAG,CAAC,EAC/B,QAAQ,SAAS,MAAM,CAAC,MAAM,EAAE,UAAU,CAAC;IAE3C,QAAQ,CAAC,IAAI,EAAE,IAAI,CAAA;IACnB,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAA;IACvB,QAAQ,CAAC,OAAO,EAAE,KAAK,CAAA;IACvB,QAAQ,CAAC,UAAU,EAAE,UAAU,CAAC,KAAK,EAAE,MAAM,CAAC,MAAM,CAAC,CAAC,CAAA;IACtD,QAAQ,CAAC,QAAQ,EAAE,QAAQ,CAAA;IAC3B,8DAA8D;IAC9D,OAAO,CAAC,KAAK,CAAC,WAAW,SAAS,MAAM,EAAE,OAAO,SAAS,aAAa,EAAE,CAAC,GAAG,KAAK,EAAE,CAAC,GAAG,KAAK,EAC3F,IAAI,EAAE,WAAW,EACjB,OAAO,EAAE;QACP,QAAQ,CAAC,OAAO,EAAE,OAAO,CAAA;QACzB,QAAQ,CAAC,OAAO,EAAE,cAAc,CAAC,KAAK,EAAE,OAAO,CAAC,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC,CAAA;KAC/E,GACA,SAAS,CAAC,IAAI,EAAE,KAAK,EAAE,MAAM,EAAE,QAAQ,GAAG,MAAM,CAAC,WAAW,EAAE,OAAO,CAAC,OAAO,EAAE,KAAK,EAAE,MAAM,CAAC,MAAM,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,CAAA;CACjH;AAED,MAAM,MAAM,GAAG,GAAG,SAAS,CAAC,MAAM,EAAE,GAAG,EAAE,WAAW,CAAC,GAAG,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,UAAU,CAAC,CAAC,CAAA;AAEtF,MAAM,MAAM,IAAI,CAAC,CAAC,SAAS,GAAG,IAAI,CAAC,CAAC,MAAM,CAAC,CAAA;AAC3C,MAAM,MAAM,KAAK,CAAC,CAAC,SAAS,GAAG,IAAI,CAAC,CAAC,SAAS,CAAC,CAAA;AAC/C,MAAM,MAAM,KAAK,CAAC,CAAC,SAAS,GAAG,IAAI,CAAC,CAAC,QAAQ,CAAC,CAAC,MAAM,CAAC,CAAA;AACtD,MAAM,MAAM,QAAQ,CAAC,CAAC,SAAS,GAAG,IAAI,CAAC,CAAC,UAAU,CAAC,CAAA;AACnD,MAAM,MAAM,WAAW,CAAC,CAAC,SAAS,GAAG,IAAI,MAAM,CAAC,CAAC,UAAU,CAAC,GAAG,MAAM,CAAA;AACrE,MAAM,MAAM,OAAO,CAAC,CAAC,SAAS,GAAG,EAAE,CAAC,SAAS,WAAW,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,UAAU,CAAC,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,MAAM,CAAC,CAAA;AAClG,MAAM,MAAM,YAAY,CAAC,CAAC,SAAS,GAAG,EAAE,CAAC,SAAS,WAAW,CAAC,CAAC,CAAC,GAAG,WAAW,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,UAAU,CAAC,CAAC,CAAC,CAAC,SACnG,OAAO,CAAC,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,MAAM,CAAC,EAAE,GAAG,CAAC,GAAG,CAAC,GAAG,KAAK,CAAA;AAClD,MAAM,MAAM,OAAO,CAAC,CAAC,SAAS,GAAG,IAAI,CAAC,CAAC,UAAU,CAAC,CAAC,WAAW,CAAC,CAAC,CAAC,CAAC,SAAS,OAAO,CAAC,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,MAAM,CAAC,CAAC,GAAG,CAAC,GAC/G,KAAK,CAAA;AAkBT;;;GAGG;AACH,eAAO,MAAM,IAAI,GAAI,KAAK,QACzB,KAAK,CAAC,IAAI,SAAS,MAAM,EAAE,MAAM,SAAS,WAAW,CAAC,GAAG,CAAC,WAAW;IACpE,QAAQ,CAAC,IAAI,EAAE,IAAI,CAAA;IACnB,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAA;IACvB,QAAQ,CAAC,OAAO,EAAE,KAAK,CAAA;IACvB,QAAQ,CAAC,UAAU,EAAE,UAAU,CAAC,KAAK,EAAE,MAAM,CAAC,MAAM,CAAC,CAAC,CAAA;CACvD,KAAG,SAAS,CAAC,IAAI,EAAE,KAAK,EAAE,MAAM,EAAE,EAAE,CAAwC,CAAA;AAE7E,sCAAsC;AACtC,eAAO,MAAM,MAAM,GAAI,CAAC,SAAS,GAAG,aAAa,CAAC,SAAS,KAAK,CAAC,CAAC,CAAC,SAAS,KAAK,CAAC,CAAC,CAAC,QAAQ,SAAS,KAAG,KAAK,CAAC,CAAC,CAK5G,CAAA"}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
const build = (definition) => ({
|
|
2
|
+
...definition,
|
|
3
|
+
command: (name, options) => build({
|
|
4
|
+
...definition,
|
|
5
|
+
commands: { ...definition.commands, [name]: options }
|
|
6
|
+
})
|
|
7
|
+
});
|
|
8
|
+
/**
|
|
9
|
+
* The state is given explicitly, `Aggregate.make<State>()({ ... })`, so that the
|
|
10
|
+
* projection's return values are checked against it rather than widened.
|
|
11
|
+
*/
|
|
12
|
+
export const make = () => (options) => build({ ...options, commands: {} });
|
|
13
|
+
/** Folds one event into the state. */
|
|
14
|
+
export const evolve = (aggregate, state, event, meta) => aggregate.projection[event._tag](state, event, meta);
|
|
15
|
+
//# sourceMappingURL=Aggregate.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"Aggregate.js","sourceRoot":"","sources":["../src/Aggregate.ts"],"names":[],"mappings":"AAkGA,MAAM,KAAK,GAAG,CAMZ,UAAqH,EAC3E,EAAE,CAAC,CAAC;IAC9C,GAAG,UAAU;IACb,OAAO,EAAE,CAAC,IAAI,EAAE,OAAO,EAAE,EAAE,CACzB,KAAK,CAAC;QACJ,GAAG,UAAU;QACb,QAAQ,EAAE,EAAE,GAAG,UAAU,CAAC,QAAQ,EAAE,CAAC,IAAI,CAAC,EAAE,OAAO,EAAS;KAC7D,CAAC;CACL,CAAC,CAAA;AAEF;;;GAGG;AACH,MAAM,CAAC,MAAM,IAAI,GAAG,GAAU,EAAE,CAChC,CAA6D,OAK5D,EAAsC,EAAE,CAAC,KAAK,CAAC,EAAE,GAAG,OAAO,EAAE,QAAQ,EAAE,EAAE,EAAE,CAAC,CAAA;AAE7E,sCAAsC;AACtC,MAAM,CAAC,MAAM,MAAM,GAAG,CAAgB,SAAY,EAAE,KAAe,EAAE,KAAe,EAAE,IAAe,EAAY,EAAE,CAChH,SAAS,CAAC,UAA8F,CAAC,KAAK,CAAC,IAAI,CAAE,CACpH,KAAK,EACL,KAAK,EACL,IAAI,CACL,CAAA"}
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Runs an aggregate against the {@link EventStore}: the `command`, `read-model`
|
|
3
|
+
* and `history` actions of moleculer-cqrs, as a typed service.
|
|
4
|
+
*/
|
|
5
|
+
import { type DateTime, Effect, Schema } from "effect";
|
|
6
|
+
import * as Aggregate from "./Aggregate.ts";
|
|
7
|
+
import { ConcurrencyConflict, EventStore, type EventStoreError, type StoredEvent } from "./EventStore.ts";
|
|
8
|
+
declare const UnknownCommand_base: Schema.Class<UnknownCommand, Schema.TaggedStruct<"UnknownCommand", {
|
|
9
|
+
readonly aggregateName: Schema.String;
|
|
10
|
+
readonly type: Schema.String;
|
|
11
|
+
}>, import("effect/Cause").YieldableError>;
|
|
12
|
+
export declare class UnknownCommand extends UnknownCommand_base {
|
|
13
|
+
}
|
|
14
|
+
declare const InvalidCommand_base: Schema.Class<InvalidCommand, Schema.TaggedStruct<"InvalidCommand", {
|
|
15
|
+
readonly aggregateName: Schema.String;
|
|
16
|
+
readonly type: Schema.String;
|
|
17
|
+
readonly message: Schema.String;
|
|
18
|
+
}>, import("effect/Cause").YieldableError>;
|
|
19
|
+
export declare class InvalidCommand extends InvalidCommand_base {
|
|
20
|
+
}
|
|
21
|
+
declare const EventDecodeError_base: Schema.Class<EventDecodeError, Schema.TaggedStruct<"EventDecodeError", {
|
|
22
|
+
readonly aggregateName: Schema.String;
|
|
23
|
+
readonly aggregateId: Schema.String;
|
|
24
|
+
readonly version: Schema.Int;
|
|
25
|
+
readonly type: Schema.String;
|
|
26
|
+
readonly message: Schema.String;
|
|
27
|
+
}>, import("effect/Cause").YieldableError>;
|
|
28
|
+
/** A stored event no longer matches the schema, which means the data needs a migration. */
|
|
29
|
+
export declare class EventDecodeError extends EventDecodeError_base {
|
|
30
|
+
}
|
|
31
|
+
export type ReadError = EventStoreError | EventDecodeError;
|
|
32
|
+
/** How a command can fail, on top of the errors its own handler declares. */
|
|
33
|
+
export type CommandFailure = ConcurrencyConflict | InvalidCommand | ReadError;
|
|
34
|
+
export interface CommandResult<Event> {
|
|
35
|
+
readonly aggregateName: string;
|
|
36
|
+
readonly aggregateId: string;
|
|
37
|
+
/** The stream version after the command. */
|
|
38
|
+
readonly version: number;
|
|
39
|
+
/** The store position of the last event, to pass to `ViewModels.caughtUp`. */
|
|
40
|
+
readonly position: number;
|
|
41
|
+
readonly events: ReadonlyArray<Event>;
|
|
42
|
+
}
|
|
43
|
+
export interface HistoryEntry<Event> {
|
|
44
|
+
readonly version: number;
|
|
45
|
+
readonly timestamp: DateTime.Utc;
|
|
46
|
+
readonly type: string;
|
|
47
|
+
readonly payload?: Event;
|
|
48
|
+
}
|
|
49
|
+
export interface AggregateService<A extends Aggregate.Any> {
|
|
50
|
+
readonly aggregate: A;
|
|
51
|
+
/** One typed function per command. */
|
|
52
|
+
readonly commands: {
|
|
53
|
+
readonly [K in Aggregate.CommandName<A>]: (aggregateId: string, payload: Aggregate.Payload<A, K>) => Effect.Effect<CommandResult<Aggregate.Event<A>>, Aggregate.CommandError<A, K> | CommandFailure>;
|
|
54
|
+
};
|
|
55
|
+
/** Runs a `{ aggregateId, type, payload }` envelope received as JSON from outside the process. */
|
|
56
|
+
readonly dispatch: (input: unknown) => Effect.Effect<CommandResult<Aggregate.Event<A>>, Aggregate.CommandError<A> | CommandFailure | UnknownCommand>;
|
|
57
|
+
/** Materializes the state on the fly, optionally as it was before `finishTime`. */
|
|
58
|
+
readonly readModel: (aggregateId: string, options?: {
|
|
59
|
+
readonly finishTime?: DateTime.Utc | undefined;
|
|
60
|
+
}) => Effect.Effect<Aggregate.State<A>, ReadError>;
|
|
61
|
+
/** Lists the events of one aggregate, with their decoded payload when `payload` is set. */
|
|
62
|
+
readonly history: (aggregateId: string, options?: {
|
|
63
|
+
readonly payload?: boolean | undefined;
|
|
64
|
+
readonly startTime?: DateTime.Utc | undefined;
|
|
65
|
+
readonly finishTime?: DateTime.Utc | undefined;
|
|
66
|
+
}) => Effect.Effect<ReadonlyArray<HistoryEntry<Aggregate.Event<A>>>, ReadError>;
|
|
67
|
+
}
|
|
68
|
+
/** Decodes a stored event with the schema of the aggregate it belongs to. */
|
|
69
|
+
export declare const makeEventDecoder: <A extends Aggregate.Any>(aggregate: A) => (stored: StoredEvent) => Effect.Effect<Aggregate.Event<A>, EventDecodeError>;
|
|
70
|
+
/** Encodes events into the JSON form the store keeps. */
|
|
71
|
+
export declare const makeEventEncoder: <A extends Aggregate.Any>(aggregate: A) => (event: Aggregate.Event<A>) => Effect.Effect<{
|
|
72
|
+
type: any;
|
|
73
|
+
payload: Schema.Json;
|
|
74
|
+
}, never, never>;
|
|
75
|
+
export declare const make: <A extends Aggregate.Any>(aggregate: A, options?: {
|
|
76
|
+
/** How many times a command is re-decided after losing a race. Defaults to 5. */
|
|
77
|
+
readonly maxRetries?: number;
|
|
78
|
+
/** How many aggregates keep their state in memory between commands. Defaults to 10,000. */
|
|
79
|
+
readonly stateCacheSize?: number;
|
|
80
|
+
} | undefined) => Effect.Effect<AggregateService<A>, never, EventStore | Aggregate.Context<A>>;
|
|
81
|
+
export {};
|
|
82
|
+
//# sourceMappingURL=AggregateService.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"AggregateService.d.ts","sourceRoot":"","sources":["../src/AggregateService.ts"],"names":[],"mappings":"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"}
|