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.
Files changed (48) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +298 -2
  3. package/dist/Aggregate.d.ts +86 -0
  4. package/dist/Aggregate.d.ts.map +1 -0
  5. package/dist/Aggregate.js +15 -0
  6. package/dist/Aggregate.js.map +1 -0
  7. package/dist/AggregateService.d.ts +82 -0
  8. package/dist/AggregateService.d.ts.map +1 -0
  9. package/dist/AggregateService.js +181 -0
  10. package/dist/AggregateService.js.map +1 -0
  11. package/dist/Checkpoints.d.ts +19 -0
  12. package/dist/Checkpoints.d.ts.map +1 -0
  13. package/dist/Checkpoints.js +18 -0
  14. package/dist/Checkpoints.js.map +1 -0
  15. package/dist/EventStore.d.ts +110 -0
  16. package/dist/EventStore.d.ts.map +1 -0
  17. package/dist/EventStore.js +136 -0
  18. package/dist/EventStore.js.map +1 -0
  19. package/dist/Fixture.d.ts +71 -0
  20. package/dist/Fixture.d.ts.map +1 -0
  21. package/dist/Fixture.js +108 -0
  22. package/dist/Fixture.js.map +1 -0
  23. package/dist/PostgresEventStore.d.ts +30 -0
  24. package/dist/PostgresEventStore.d.ts.map +1 -0
  25. package/dist/PostgresEventStore.js +144 -0
  26. package/dist/PostgresEventStore.js.map +1 -0
  27. package/dist/SqliteEventStore.d.ts +25 -0
  28. package/dist/SqliteEventStore.d.ts.map +1 -0
  29. package/dist/SqliteEventStore.js +123 -0
  30. package/dist/SqliteEventStore.js.map +1 -0
  31. package/dist/ViewModel.d.ts +90 -0
  32. package/dist/ViewModel.d.ts.map +1 -0
  33. package/dist/ViewModel.js +170 -0
  34. package/dist/ViewModel.js.map +1 -0
  35. package/dist/index.d.ts +9 -0
  36. package/dist/index.d.ts.map +1 -0
  37. package/dist/index.js +9 -0
  38. package/dist/index.js.map +1 -0
  39. package/package.json +87 -3
  40. package/src/Aggregate.ts +133 -0
  41. package/src/AggregateService.ts +342 -0
  42. package/src/Checkpoints.ts +23 -0
  43. package/src/EventStore.ts +233 -0
  44. package/src/Fixture.ts +213 -0
  45. package/src/PostgresEventStore.ts +188 -0
  46. package/src/SqliteEventStore.ts +166 -0
  47. package/src/ViewModel.ts +348 -0
  48. package/src/index.ts +8 -0
@@ -0,0 +1,170 @@
1
+ /**
2
+ * The read side: projections of events into whatever storage a query needs.
3
+ *
4
+ * Moleculer-cqrs pushes events to view models over the broker, so an event sent
5
+ * while a service is down is lost until someone replays. Here each view model
6
+ * pulls from the store from its own checkpoint, so it can't miss anything, and
7
+ * delivery is at least once: a handler can see an event again after a crash and
8
+ * should be idempotent.
9
+ */
10
+ import { Context, Duration, Effect, Layer, Option, Ref, Schedule, Schema, Semaphore, Stream, SubscriptionRef } from "effect";
11
+ import { makeEventDecoder } from "./AggregateService.js";
12
+ import { Checkpoints } from "./Checkpoints.js";
13
+ import { EventStore } from "./EventStore.js";
14
+ export class UnknownViewModel extends Schema.TaggedError()("UnknownViewModel", {
15
+ name: Schema.String
16
+ }) {
17
+ }
18
+ export class ViewModelError extends Schema.TaggedError()("ViewModelError", {
19
+ viewModel: Schema.String,
20
+ /** The event being processed, `0` when the failure happened outside any event. */
21
+ position: Schema.Int,
22
+ cause: Schema.Defect()
23
+ }) {
24
+ }
25
+ /** Handlers are keyed by event tag, and events without one are skipped. */
26
+ export const make = (options) => options;
27
+ export class ViewModels extends Context.Service()("effect-cqrs/ViewModels") {
28
+ }
29
+ // Stalled view models keep their place so events are never processed out of order.
30
+ const retryDelay = (attempt) => Duration.millis(Math.min(100 * 2 ** attempt, 30_000));
31
+ // Following the log moves a few events at a time, and a checkpoint for each of those
32
+ // moves is a commit per view model per event. Saving less often only means that a
33
+ // crash repeats more, which handlers already have to cope with.
34
+ const checkpointInterval = 500;
35
+ // How far behind a view model can be and still read the tail everyone shares. It is
36
+ // one page of the store, so a shared read never has to page.
37
+ const tailSize = 500;
38
+ /**
39
+ * Reads the end of the log once for all view models. When they are caught up they
40
+ * wake on the same append and want the same few events, so each reading them for
41
+ * itself is most of what a view model costs.
42
+ */
43
+ const makeTail = (store, viewModels) => {
44
+ const filter = {
45
+ aggregateNames: Array.from(new Set(viewModels.flatMap((viewModel) => viewModel.aggregates.map(({ name }) => name)))),
46
+ eventTypes: Array.from(new Set(viewModels.flatMap((viewModel) => Object.keys(viewModel.handlers))))
47
+ };
48
+ const decoded = new WeakMap();
49
+ let last;
50
+ return {
51
+ read: (afterPosition, latest) => Effect.suspend(() => last?.afterPosition === afterPosition && last.latest === latest
52
+ ? Effect.succeed(last.events)
53
+ : Stream.runCollect(store.read({ ...filter, afterPosition })).pipe(Effect.tap((events) => Effect.sync(() => void (last = { afterPosition, latest, events }))))),
54
+ decoded
55
+ };
56
+ };
57
+ const makeRunner = Effect.fnUntraced(function* (viewModel, tail) {
58
+ const store = yield* EventStore;
59
+ const checkpoints = yield* Checkpoints;
60
+ const context = yield* Effect.context();
61
+ const provide = (effect) => Effect.provideContext(effect, context);
62
+ const semaphore = yield* Semaphore.make(1);
63
+ const name = viewModel.name;
64
+ const fail = (position) => (cause) => new ViewModelError({ viewModel: name, position, cause });
65
+ const checkpoint = yield* checkpoints.get(name);
66
+ const position = yield* SubscriptionRef.make(checkpoint);
67
+ const saved = yield* Ref.make(checkpoint);
68
+ const lastError = yield* Ref.make(Option.none());
69
+ const stall = yield* Ref.make({ position: -1, attempts: 0 });
70
+ const decoders = new Map(viewModel.aggregates.map((aggregate) => [aggregate.name, makeEventDecoder(aggregate)]));
71
+ const filter = {
72
+ aggregateNames: Array.from(decoders.keys()),
73
+ eventTypes: Object.keys(viewModel.handlers)
74
+ };
75
+ const wants = (stored) => decoders.has(stored.aggregateName) && Object.hasOwn(viewModel.handlers, stored.type);
76
+ const decode = (stored) => decoders.get(stored.aggregateName)(stored);
77
+ // Events from the tail are the same objects for everyone, so the first to decode one
78
+ // does it for the rest.
79
+ const decodeShared = (stored) => tail.decoded.has(stored)
80
+ ? Effect.succeed(tail.decoded.get(stored))
81
+ : decode(stored).pipe(Effect.tap((event) => Effect.sync(() => void tail.decoded.set(stored, event))));
82
+ const handle = (decodeEvent) => (stored) => Effect.gen(function* () {
83
+ const event = yield* decodeEvent(stored);
84
+ yield* viewModel.handlers[stored.type](event, stored);
85
+ }).pipe(Effect.mapError(fail(stored.position)), Effect.catchDefect((defect) => Effect.fail(fail(stored.position)(defect))));
86
+ const save = (to) => checkpoints.set(name, to).pipe(Effect.mapError(fail(0)), Effect.andThen(Ref.set(saved, to)));
87
+ const advance = (to) => Ref.get(saved).pipe(Effect.flatMap((last) => to < last || to - last >= checkpointInterval ? save(to) : Effect.void), Effect.andThen(SubscriptionRef.set(position, to)));
88
+ // Finalizers run last to first, so this one sees the fiber below already stopped.
89
+ yield* Effect.addFinalizer(() => SubscriptionRef.get(position).pipe(Effect.flatMap(save), Effect.ignore));
90
+ // A crash repeats at most what came after the last saved checkpoint: two batches.
91
+ const processBatch = (batch, decodeEvent) => Effect.forEach(batch, handle(decodeEvent), { discard: true }).pipe(Effect.andThen(advance(batch[batch.length - 1].position)));
92
+ const consume = (eventFilter, onBatch) => store.read(eventFilter).pipe(Stream.mapError(fail(0)), Stream.runForEachArray((batch) => processBatch(batch, decode).pipe(Effect.andThen(onBatch(batch.length)))));
93
+ const follow = (afterPosition, latest) => tail.read(afterPosition, latest).pipe(Effect.mapError(fail(0)), Effect.map((events) => events.filter(wants)), Effect.flatMap((events) => events.length === 0 ? Effect.void : processBatch(events, decodeShared)));
94
+ // Moves past the end of the log even when the last events were not for this view model.
95
+ const settle = (head) => SubscriptionRef.get(position).pipe(Effect.flatMap((current) => current < head ? advance(head) : Effect.void));
96
+ const catchUp = (latest) => Effect.gen(function* () {
97
+ const afterPosition = yield* SubscriptionRef.get(position);
98
+ if (latest <= afterPosition)
99
+ return;
100
+ if (latest - afterPosition <= tailSize)
101
+ yield* follow(afterPosition, latest);
102
+ else
103
+ yield* consume({ ...filter, afterPosition }, () => Effect.void);
104
+ yield* settle(latest);
105
+ }).pipe(semaphore.withPermit, provide);
106
+ // Failing again at the same place doubles the delay. Having moved on means that
107
+ // stall is over, so the next one starts from the shortest delay.
108
+ const backOff = (error) => Effect.gen(function* () {
109
+ const current = yield* SubscriptionRef.get(position);
110
+ const attempt = yield* Ref.modify(stall, (last) => {
111
+ const attempts = last.position === current ? last.attempts + 1 : 0;
112
+ return [attempts, { position: current, attempts }];
113
+ });
114
+ yield* Ref.set(lastError, Option.some(error));
115
+ yield* Effect.logError(`View model ${name} failed, retrying`, error);
116
+ yield* Effect.sleep(retryDelay(attempt));
117
+ });
118
+ yield* store.changes.pipe(Stream.filterEffect((latest) => Effect.map(SubscriptionRef.get(position), (current) => latest > current)), Stream.runForEach((latest) => catchUp(latest).pipe(Effect.tapError(backOff), Effect.retry(Schedule.forever), Effect.andThen(Ref.set(lastError, Option.none())))), Effect.forkScoped);
119
+ const replay = (range) => Effect.gen(function* () {
120
+ // The checkpoint is cleared first: a crash in between then replays over
121
+ // old data, which is safe for idempotent handlers, instead of leaving a
122
+ // disposed view model that resumes from a stale position.
123
+ yield* advance(0);
124
+ yield* viewModel.dispose.pipe(Effect.mapError(fail(0)));
125
+ const head = yield* store.head.pipe(Effect.mapError(fail(0)));
126
+ const count = yield* Ref.make(0);
127
+ yield* consume({ ...filter, ...range }, (size) => Ref.update(count, (n) => n + size));
128
+ yield* settle(head);
129
+ return yield* Ref.get(count);
130
+ }).pipe(semaphore.withPermit, provide);
131
+ const caughtUp = (target) => SubscriptionRef.changes(position).pipe(Stream.filter((current) => current >= target), Stream.runHead, Effect.asVoid);
132
+ const status = Effect.gen(function* () {
133
+ return {
134
+ name,
135
+ position: yield* SubscriptionRef.get(position),
136
+ lastError: yield* Ref.get(lastError)
137
+ };
138
+ });
139
+ return { name, replay, caughtUp, status };
140
+ });
141
+ /** Starts the view models in the background for as long as the layer lives. */
142
+ export const layer = (viewModels) => Layer.effect(ViewModels, Effect.gen(function* () {
143
+ const store = yield* EventStore;
144
+ const tail = makeTail(store, viewModels);
145
+ const runners = yield* Effect.forEach(viewModels, (viewModel) => makeRunner(viewModel, tail));
146
+ const replay = Effect.fn("ViewModels.replay")(function* (options) {
147
+ const selected = yield* Effect.forEach(options.viewModels, (name) => {
148
+ const runner = runners.find((runner) => runner.name === name);
149
+ return runner === undefined ? new UnknownViewModel({ name }) : Effect.succeed(runner);
150
+ });
151
+ const counts = yield* Effect.forEach(selected, (runner) => runner.replay({ startTime: options.startTime, finishTime: options.finishTime }), { concurrency: "unbounded" });
152
+ return {
153
+ viewModels: options.viewModels,
154
+ eventCount: counts.reduce((total, count) => total + count, 0)
155
+ };
156
+ });
157
+ const caughtUp = Effect.fn("ViewModels.caughtUp")(function* (position) {
158
+ const target = position ?? (yield* store.head);
159
+ yield* Effect.forEach(runners, (runner) => runner.caughtUp(target), {
160
+ concurrency: "unbounded",
161
+ discard: true
162
+ });
163
+ });
164
+ return ViewModels.of({
165
+ replay,
166
+ caughtUp,
167
+ status: Effect.forEach(runners, (runner) => runner.status)
168
+ });
169
+ }));
170
+ //# sourceMappingURL=ViewModel.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ViewModel.js","sourceRoot":"","sources":["../src/ViewModel.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,9 @@
1
+ export * as Aggregate from "./Aggregate.ts";
2
+ export * as AggregateService from "./AggregateService.ts";
3
+ export * as Checkpoints from "./Checkpoints.ts";
4
+ export * as EventStore from "./EventStore.ts";
5
+ export * as Fixture from "./Fixture.ts";
6
+ export * as PostgresEventStore from "./PostgresEventStore.ts";
7
+ export * as SqliteEventStore from "./SqliteEventStore.ts";
8
+ export * as ViewModel from "./ViewModel.ts";
9
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,SAAS,MAAM,gBAAgB,CAAA;AAC3C,OAAO,KAAK,gBAAgB,MAAM,uBAAuB,CAAA;AACzD,OAAO,KAAK,WAAW,MAAM,kBAAkB,CAAA;AAC/C,OAAO,KAAK,UAAU,MAAM,iBAAiB,CAAA;AAC7C,OAAO,KAAK,OAAO,MAAM,cAAc,CAAA;AACvC,OAAO,KAAK,kBAAkB,MAAM,yBAAyB,CAAA;AAC7D,OAAO,KAAK,gBAAgB,MAAM,uBAAuB,CAAA;AACzD,OAAO,KAAK,SAAS,MAAM,gBAAgB,CAAA"}
package/dist/index.js ADDED
@@ -0,0 +1,9 @@
1
+ export * as Aggregate from "./Aggregate.js";
2
+ export * as AggregateService from "./AggregateService.js";
3
+ export * as Checkpoints from "./Checkpoints.js";
4
+ export * as EventStore from "./EventStore.js";
5
+ export * as Fixture from "./Fixture.js";
6
+ export * as PostgresEventStore from "./PostgresEventStore.js";
7
+ export * as SqliteEventStore from "./SqliteEventStore.js";
8
+ export * as ViewModel from "./ViewModel.js";
9
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,SAAS,MAAM,gBAAgB,CAAA;AAC3C,OAAO,KAAK,gBAAgB,MAAM,uBAAuB,CAAA;AACzD,OAAO,KAAK,WAAW,MAAM,kBAAkB,CAAA;AAC/C,OAAO,KAAK,UAAU,MAAM,iBAAiB,CAAA;AAC7C,OAAO,KAAK,OAAO,MAAM,cAAc,CAAA;AACvC,OAAO,KAAK,kBAAkB,MAAM,yBAAyB,CAAA;AAC7D,OAAO,KAAK,gBAAgB,MAAM,uBAAuB,CAAA;AACzD,OAAO,KAAK,SAAS,MAAM,gBAAgB,CAAA"}
package/package.json CHANGED
@@ -1,6 +1,90 @@
1
1
  {
2
2
  "name": "effect-cqrs",
3
- "version": "0.0.0-stage",
4
- "stub": true,
5
- "description": "Temporary package placeholder for staged publishing"
3
+ "version": "0.1.0",
4
+ "description": "CQRS and event sourcing for Effect v4, a port of moleculer-cqrs",
5
+ "keywords": [
6
+ "effect",
7
+ "cqrs",
8
+ "event-sourcing",
9
+ "event-store",
10
+ "aggregate",
11
+ "projection",
12
+ "sqlite",
13
+ "postgres"
14
+ ],
15
+ "homepage": "https://davidnussio.github.io/effect-cqrs/",
16
+ "bugs": {
17
+ "url": "https://github.com/davidnussio/effect-cqrs/issues"
18
+ },
19
+ "repository": {
20
+ "type": "git",
21
+ "url": "git+https://github.com/davidnussio/effect-cqrs.git"
22
+ },
23
+ "type": "module",
24
+ "license": "MIT",
25
+ "author": "David Nussio",
26
+ "engines": {
27
+ "node": ">=24"
28
+ },
29
+ "exports": {
30
+ ".": {
31
+ "types": "./dist/index.d.ts",
32
+ "default": "./dist/index.js"
33
+ },
34
+ "./*": {
35
+ "types": "./dist/*.d.ts",
36
+ "default": "./dist/*.js"
37
+ }
38
+ },
39
+ "files": [
40
+ "dist",
41
+ "src",
42
+ "!**/*.tsbuildinfo"
43
+ ],
44
+ "sideEffects": false,
45
+ "publishConfig": {
46
+ "access": "public"
47
+ },
48
+ "peerDependencies": {
49
+ "effect": "^4.0.6"
50
+ },
51
+ "devDependencies": {
52
+ "@astrojs/starlight": "^0.42.7",
53
+ "@changesets/cli": "^2.31.1",
54
+ "@effect/platform-node": "^4.0.6",
55
+ "@effect/sql-pg": "^4.0.6",
56
+ "@effect/sql-sqlite-node": "^4.0.6",
57
+ "@effect/vitest": "^4.0.6",
58
+ "@tailwindcss/vite": "^4.3.3",
59
+ "@types/node": "^24.0.0",
60
+ "@types/react": "^19.3.0",
61
+ "@types/react-dom": "^19.3.0",
62
+ "@vitejs/plugin-react": "^6.1.2",
63
+ "astro": "^7.4.4",
64
+ "effect": "^4.0.6",
65
+ "motion": "^14.7.1",
66
+ "react": "^19.3.1",
67
+ "react-dom": "^19.3.1",
68
+ "tailwindcss": "^4.3.3",
69
+ "typescript": "^7.0.2",
70
+ "vite": "^8.3.5",
71
+ "vitest": "^5.0.5"
72
+ },
73
+ "scripts": {
74
+ "check": "tsc --noEmit && tsc --noEmit -p examples/bank-ui",
75
+ "build": "rm -rf dist && tsc -p tsconfig.build.json",
76
+ "test": "vitest run",
77
+ "test:watch": "vitest",
78
+ "example": "node examples/todo/main.ts",
79
+ "ui": "vite examples/bank-ui",
80
+ "docs": "astro dev --root docs",
81
+ "docs:build": "vite build examples/bank-ui --base \"${DOCS_BASE:-/effect-cqrs}/demo/\" --outDir ../../docs/public/demo --emptyOutDir && astro build --root docs",
82
+ "docs:preview": "astro preview --root docs",
83
+ "bench": "node bench/performance.ts",
84
+ "stress": "node --expose-gc bench/stress.ts",
85
+ "changeset": "changeset",
86
+ "version-packages": "changeset version"
87
+ },
88
+ "main": "./dist/index.js",
89
+ "types": "./dist/index.d.ts"
6
90
  }
@@ -0,0 +1,133 @@
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
+
10
+ export interface Tagged {
11
+ readonly _tag: string
12
+ }
13
+
14
+ export type EventSchema<Event extends Tagged = Tagged> = Schema.Top & {
15
+ readonly Type: Event
16
+ readonly DecodingServices: never
17
+ readonly EncodingServices: never
18
+ }
19
+
20
+ export type PayloadSchema = Schema.Top & {
21
+ readonly DecodingServices: never
22
+ readonly EncodingServices: never
23
+ }
24
+
25
+ /** Where an event sits in its stream and in the store. */
26
+ export interface EventMeta {
27
+ readonly aggregateName: string
28
+ readonly aggregateId: string
29
+ readonly version: number
30
+ readonly position: number
31
+ readonly timestamp: DateTime.Utc
32
+ }
33
+
34
+ export interface CommandContext {
35
+ readonly aggregateId: string
36
+ readonly version: number
37
+ }
38
+
39
+ /** Exhaustive by event tag, so adding an event to the schema without folding it does not compile. */
40
+ export type Projection<State, Event extends Tagged> = {
41
+ readonly [Tag in Event["_tag"]]: (state: State, event: Extract<Event, { readonly _tag: Tag }>, meta: EventMeta) => State
42
+ }
43
+
44
+ /**
45
+ * Decides on the events a command produces. It may run several times when
46
+ * concurrent writers collide, so it should only decide and leave side effects to
47
+ * view models. Returning an empty array records nothing.
48
+ */
49
+ export type CommandHandler<State, Payload, Event, E, R> = (
50
+ state: State,
51
+ payload: Payload,
52
+ context: CommandContext
53
+ ) => Effect.Effect<Event | ReadonlyArray<Event>, E, R>
54
+
55
+ export interface Command<Payload extends PayloadSchema, State, Event, E, R> {
56
+ readonly payload: Payload
57
+ readonly handler: CommandHandler<State, Payload["Type"], Event, E, R>
58
+ }
59
+
60
+ export interface AnyCommand {
61
+ readonly payload: PayloadSchema
62
+ readonly handler: CommandHandler<any, any, any, any, any>
63
+ }
64
+
65
+ export interface Aggregate<
66
+ Name extends string,
67
+ State,
68
+ Events extends EventSchema<any>,
69
+ Commands extends Record<string, AnyCommand>
70
+ > {
71
+ readonly name: Name
72
+ readonly events: Events
73
+ readonly initial: State
74
+ readonly projection: Projection<State, Events["Type"]>
75
+ readonly commands: Commands
76
+ /** Returns a new aggregate that also accepts this command. */
77
+ command<const CommandName extends string, Payload extends PayloadSchema, E = never, R = never>(
78
+ name: CommandName,
79
+ options: {
80
+ readonly payload: Payload
81
+ readonly handler: CommandHandler<State, Payload["Type"], Events["Type"], E, R>
82
+ }
83
+ ): Aggregate<Name, State, Events, Commands & Record<CommandName, Command<Payload, State, Events["Type"], E, R>>>
84
+ }
85
+
86
+ export type Any = Aggregate<string, any, EventSchema<any>, Record<string, AnyCommand>>
87
+
88
+ export type Name<A extends Any> = A["name"]
89
+ export type State<A extends Any> = A["initial"]
90
+ export type Event<A extends Any> = A["events"]["Type"]
91
+ export type Commands<A extends Any> = A["commands"]
92
+ export type CommandName<A extends Any> = keyof A["commands"] & string
93
+ export type Payload<A extends Any, K extends CommandName<A>> = A["commands"][K]["payload"]["Type"]
94
+ export type CommandError<A extends Any, K extends CommandName<A> = CommandName<A>> = A["commands"][K] extends
95
+ Command<any, any, any, infer E, any> ? E : never
96
+ export type Context<A extends Any> = A["commands"][CommandName<A>] extends Command<any, any, any, any, infer R> ? R
97
+ : never
98
+
99
+ const build = <
100
+ Name extends string,
101
+ State,
102
+ Events extends EventSchema<any>,
103
+ Commands extends Record<string, AnyCommand>
104
+ >(
105
+ definition: Pick<Aggregate<Name, State, Events, Commands>, "name" | "events" | "initial" | "projection" | "commands">
106
+ ): Aggregate<Name, State, Events, Commands> => ({
107
+ ...definition,
108
+ command: (name, options) =>
109
+ build({
110
+ ...definition,
111
+ commands: { ...definition.commands, [name]: options } as any
112
+ })
113
+ })
114
+
115
+ /**
116
+ * The state is given explicitly, `Aggregate.make<State>()({ ... })`, so that the
117
+ * projection's return values are checked against it rather than widened.
118
+ */
119
+ export const make = <State>() =>
120
+ <const Name extends string, Events extends EventSchema<any>>(options: {
121
+ readonly name: Name
122
+ readonly events: Events
123
+ readonly initial: State
124
+ readonly projection: Projection<State, Events["Type"]>
125
+ }): Aggregate<Name, State, Events, {}> => build({ ...options, commands: {} })
126
+
127
+ /** Folds one event into the state. */
128
+ export const evolve = <A extends Any>(aggregate: A, state: State<A>, event: Event<A>, meta: EventMeta): State<A> =>
129
+ (aggregate.projection as Record<string, (state: State<A>, event: Event<A>, meta: EventMeta) => State<A>>)[event._tag]!(
130
+ state,
131
+ event,
132
+ meta
133
+ )