effect-cqrs 0.0.0-stage → 0.2.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 +326 -2
- package/dist/Aggregate.d.ts +127 -0
- package/dist/Aggregate.d.ts.map +1 -0
- package/dist/Aggregate.js +59 -0
- package/dist/Aggregate.js.map +1 -0
- package/dist/AggregateService.d.ts +154 -0
- package/dist/AggregateService.d.ts.map +1 -0
- package/dist/AggregateService.js +407 -0
- package/dist/AggregateService.js.map +1 -0
- package/dist/Causation.d.ts +34 -0
- package/dist/Causation.d.ts.map +1 -0
- package/dist/Causation.js +32 -0
- package/dist/Causation.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 +132 -0
- package/dist/EventStore.d.ts.map +1 -0
- package/dist/EventStore.js +156 -0
- package/dist/EventStore.js.map +1 -0
- package/dist/EventVersions.d.ts +64 -0
- package/dist/EventVersions.d.ts.map +1 -0
- package/dist/EventVersions.js +55 -0
- package/dist/EventVersions.js.map +1 -0
- package/dist/Fixture.d.ts +106 -0
- package/dist/Fixture.d.ts.map +1 -0
- package/dist/Fixture.js +149 -0
- package/dist/Fixture.js.map +1 -0
- package/dist/Metadata.d.ts +42 -0
- package/dist/Metadata.d.ts.map +1 -0
- package/dist/Metadata.js +39 -0
- package/dist/Metadata.js.map +1 -0
- package/dist/PostgresEventStore.d.ts +30 -0
- package/dist/PostgresEventStore.d.ts.map +1 -0
- package/dist/PostgresEventStore.js +153 -0
- package/dist/PostgresEventStore.js.map +1 -0
- package/dist/Saga.d.ts +105 -0
- package/dist/Saga.d.ts.map +1 -0
- package/dist/Saga.js +97 -0
- package/dist/Saga.js.map +1 -0
- package/dist/SqliteEventStore.d.ts +25 -0
- package/dist/SqliteEventStore.d.ts.map +1 -0
- package/dist/SqliteEventStore.js +142 -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 +172 -0
- package/dist/ViewModel.js.map +1 -0
- package/dist/index.d.ts +13 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +13 -0
- package/dist/index.js.map +1 -0
- package/package.json +89 -3
- package/src/Aggregate.ts +205 -0
- package/src/AggregateService.ts +705 -0
- package/src/Causation.ts +48 -0
- package/src/Checkpoints.ts +23 -0
- package/src/EventStore.ts +270 -0
- package/src/EventVersions.ts +104 -0
- package/src/Fixture.ts +298 -0
- package/src/Metadata.ts +52 -0
- package/src/PostgresEventStore.ts +199 -0
- package/src/Saga.ts +176 -0
- package/src/SqliteEventStore.ts +196 -0
- package/src/ViewModel.ts +350 -0
- package/src/index.ts +12 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"Fixture.d.ts","sourceRoot":"","sources":["../src/Fixture.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;GAmBG;AACH,OAAO,EAAE,SAAS,EAAE,MAAM,EAAc,MAAM,EAAE,MAAM,QAAQ,CAAA;AAC9D,OAAO,KAAK,SAAS,MAAM,gBAAgB,CAAA;AAC3C,OAAO,KAAK,gBAAgB,MAAM,uBAAuB,CAAA;;;;;;AAGzD,2FAA2F;AAC3F,qBAAa,iBAAkB,SAAQ,sBAIrC;CAAG;AAEL,MAAM,WAAW,OAAO,CAAC,CAAC,SAAS,SAAS,CAAC,GAAG;IAC9C,QAAQ,CAAC,MAAM,EAAE,aAAa,CAAC,SAAS,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAA;IAClD,QAAQ,CAAC,KAAK,EAAE,SAAS,CAAC,KAAK,CAAC,CAAC,CAAC,CAAA;IAClC,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAA;IACxB,0EAA0E;IAC1E,QAAQ,CAAC,OAAO,EAAE,OAAO,CAAC,CAAC,CAAC,CAAA;CAC7B;AAED,8EAA8E;AAC9E,MAAM,MAAM,aAAa,CAAC,CAAC,SAAS,SAAS,CAAC,GAAG,IAAI,SAAS,CAAC,KAAK,CAAC,CAAC,CAAC,GAAG,SAAS,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,CAAA;AAEpG,kFAAkF;AAClF,MAAM,MAAM,UAAU,CAAC,CAAC,SAAS,SAAS,CAAC,GAAG,IAAI,SAAS,CAAC,KAAK,CAAC,CAAC,CAAC,GAAG,SAAS,CAAC,UAAU,CAAA;AAE3F,6DAA6D;AAC7D,MAAM,MAAM,IAAI,CAAC,CAAC,SAAS,SAAS,CAAC,GAAG,IAAI;IAC1C,QAAQ,EAAE,CAAC,IAAI,SAAS,CAAC,WAAW,CAAC,CAAC,CAAC,GAAG;QAAE,QAAQ,CAAC,OAAO,EAAE,CAAC,CAAC;QAAC,QAAQ,CAAC,OAAO,EAAE,SAAS,CAAC,OAAO,CAAC,CAAC,EAAE,CAAC,CAAC,CAAA;KAAE;CAC7G,CAAC,SAAS,CAAC,WAAW,CAAC,CAAC,CAAC,CAAC,CAAA;AAE3B,MAAM,WAAW,gBAAgB,CAAC,CAAC,SAAS,SAAS,CAAC,GAAG;IACvD,2EAA2E;IAC3E,QAAQ,CAAC,SAAS,EAAE,CAAC,KAAK,EAAE,SAAS,CAAC,KAAK,CAAC,CAAC,CAAC,KAAK,OAAO,CAAA;IAC1D;;;OAGG;IACH,QAAQ,CAAC,QAAQ,CAAC,EAAE;QAClB,QAAQ,EAAE,CAAC,IAAI,SAAS,CAAC,WAAW,CAAC,CAAC,CAAC,CAAC,CAAC,EAAE,SAAS,CAAC,SAAS,CAAC,SAAS,CAAC,OAAO,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,GAAG,KAAK;KAChG,GAAG,SAAS,CAAA;IACb,+DAA+D;IAC/D,QAAQ,CAAC,WAAW,CAAC,EAAE,MAAM,GAAG,SAAS,CAAA;IACzC,8EAA8E;IAC9E,QAAQ,CAAC,KAAK,CAAC,EAAE,SAAS,CAAC,YAAY,GAAG,SAAS,CAAA;CACpD;AAED;;;GAGG;AACH,MAAM,WAAW,QAAQ,CAAC,CAAC,SAAS,SAAS,CAAC,GAAG,EAAE,CAAC,EAAE,CAAC,CAAE,SAAQ,MAAM,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC;IAC9F,uDAAuD;IACvD,IAAI,CAAC,CAAC,SAAS,SAAS,CAAC,WAAW,CAAC,CAAC,CAAC,EACrC,OAAO,EAAE,CAAC,EACV,OAAO,EAAE,SAAS,CAAC,OAAO,CAAC,CAAC,EAAE,CAAC,CAAC,GAC/B,QAAQ,CAAC,CAAC,EAAE,CAAC,GAAG,SAAS,CAAC,YAAY,CAAC,CAAC,EAAE,CAAC,CAAC,GAAG,gBAAgB,CAAC,cAAc,EAAE,CAAC,GAAG,SAAS,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,CAAA;IAC5G,0FAA0F;IAC1F,SAAS,CAAC,CAAC,SAAS,SAAS,CAAC,WAAW,CAAC,CAAC,CAAC,EAC1C,OAAO,EAAE,CAAC,EACV,OAAO,EAAE,SAAS,CAAC,OAAO,CAAC,CAAC,EAAE,CAAC,CAAC,GAC/B,MAAM,CAAC,MAAM,CAAC,SAAS,CAAC,YAAY,CAAC,CAAC,EAAE,CAAC,CAAC,GAAG,gBAAgB,CAAC,cAAc,EAAE,CAAC,EAAE,CAAC,GAAG,SAAS,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,CAAA;IAC7G,mEAAmE;IACnE,WAAW,CAAC,KAAK,EAAE,aAAa,CAAC,CAAC,CAAC,GAAG,QAAQ,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC,CAAA;IACvD,+EAA+E;IAC/E,YAAY,CAAC,MAAM,EAAE,aAAa,CAAC,aAAa,CAAC,CAAC,CAAC,CAAC,GAAG,QAAQ,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC,CAAA;IACxE,6EAA6E;IAC7E,YAAY,CAAC,KAAK,EAAE,CAAC,KAAK,EAAE,SAAS,CAAC,KAAK,CAAC,CAAC,CAAC,KAAK,IAAI,GAAG,QAAQ,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC,CAAA;CAC5E;AAED,MAAM,WAAW,OAAO,CAAC,CAAC,SAAS,SAAS,CAAC,GAAG;IAC9C,0DAA0D;IAC1D,KAAK,CAAC,MAAM,EAAE,aAAa,CAAC,UAAU,CAAC,CAAC,CAAC,CAAC,GAAG,OAAO,CAAC,CAAC,CAAC,CAAA;IACvD,qGAAqG;IACrG,WAAW,CAAC,MAAM,CAAC,EAAE,aAAa,CAAC,UAAU,CAAC,CAAC,CAAC,CAAC,EAAE,WAAW,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,CAAC,CAAC,CAAA;IACpF,mEAAmE;IACnE,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC,MAAM,CAAC,SAAS,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,gBAAgB,CAAC,SAAS,EAAE,SAAS,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,CAAA;IACnG,4FAA4F;IAC5F,YAAY,CACV,KAAK,EAAE,CAAC,KAAK,EAAE,SAAS,CAAC,KAAK,CAAC,CAAC,CAAC,KAAK,IAAI,GACzC,QAAQ,CAAC,CAAC,EAAE,gBAAgB,CAAC,SAAS,EAAE,SAAS,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,CAAA;IAChE,IAAI,CAAC,CAAC,SAAS,SAAS,CAAC,WAAW,CAAC,CAAC,CAAC,EACrC,OAAO,EAAE,CAAC,EACV,OAAO,EAAE,SAAS,CAAC,OAAO,CAAC,CAAC,EAAE,CAAC,CAAC,GAC/B,QAAQ,CAAC,CAAC,EAAE,SAAS,CAAC,YAAY,CAAC,CAAC,EAAE,CAAC,CAAC,GAAG,gBAAgB,CAAC,cAAc,EAAE,SAAS,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,CAAA;IACpG,0FAA0F;IAC1F,SAAS,CAAC,CAAC,SAAS,SAAS,CAAC,WAAW,CAAC,CAAC,CAAC,EAC1C,OAAO,EAAE,CAAC,EACV,OAAO,EAAE,SAAS,CAAC,OAAO,CAAC,CAAC,EAAE,CAAC,CAAC,GAC/B,MAAM,CAAC,MAAM,CAAC,SAAS,CAAC,YAAY,CAAC,CAAC,EAAE,CAAC,CAAC,GAAG,gBAAgB,CAAC,cAAc,EAAE,KAAK,EAAE,SAAS,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,CAAA;IAC7G;;;;;OAKG;IACH,eAAe,CAAC,OAAO,EAAE,gBAAgB,CAAC,CAAC,CAAC,GAAG,MAAM,CAAC,MAAM,CAAC,IAAI,EAAE,KAAK,EAAE,SAAS,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,CAAA;CAChG;AA6DD,eAAO,MAAM,IAAI,GAAI,CAAC,SAAS,SAAS,CAAC,GAAG,aAC/B,CAAC,YACF;IACR,QAAQ,CAAC,WAAW,CAAC,EAAE,MAAM,CAAA;IAC7B,QAAQ,CAAC,MAAM,CAAC,EAAE,aAAa,CAAC,UAAU,CAAC,CAAC,CAAC,CAAC,CAAA;CAC/C,KACA,OAAO,CAAC,CAAC,CAiHX,CAAA"}
|
package/dist/Fixture.js
ADDED
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tests an aggregate in isolation: given these events, when this command runs,
|
|
3
|
+
* what happens? The moleculer-cqrs `CQRSFixture`, as a chain that runs when it
|
|
4
|
+
* is yielded:
|
|
5
|
+
*
|
|
6
|
+
* ```ts
|
|
7
|
+
* yield* Fixture.make(Todo)
|
|
8
|
+
* .givenEvents([created])
|
|
9
|
+
* .when("deleteTodo", { message: "Done" })
|
|
10
|
+
* .expectEvent("TodoDeleted")
|
|
11
|
+
* .inspectState((state) => assert.strictEqual(state.status, "deleted"))
|
|
12
|
+
* ```
|
|
13
|
+
*
|
|
14
|
+
* Commands run through the real {@link AggregateService} on an in-memory store,
|
|
15
|
+
* so payload validation and event encoding are exercised too.
|
|
16
|
+
*
|
|
17
|
+
* The history can include events stored in older versions of their schema, made
|
|
18
|
+
* with `Aggregate.historical`, which then go through the same upcasting as events
|
|
19
|
+
* read from a real store.
|
|
20
|
+
*/
|
|
21
|
+
import { Arbitrary, Effect, Effectable, Schema } from "effect";
|
|
22
|
+
import * as Aggregate from "./Aggregate.js";
|
|
23
|
+
import * as AggregateService from "./AggregateService.js";
|
|
24
|
+
import { EventStore, makeMemory } from "./EventStore.js";
|
|
25
|
+
/** Raised as a defect, so a command's error channel only holds what the command can do. */
|
|
26
|
+
export class ExpectationFailed extends Schema.TaggedError()("ExpectationFailed", {
|
|
27
|
+
message: Schema.String,
|
|
28
|
+
expected: Schema.Unknown,
|
|
29
|
+
actual: Schema.Unknown
|
|
30
|
+
}) {
|
|
31
|
+
}
|
|
32
|
+
class ScenarioImpl extends Effectable.Class {
|
|
33
|
+
aggregate;
|
|
34
|
+
run;
|
|
35
|
+
constructor(aggregate, run) {
|
|
36
|
+
super();
|
|
37
|
+
this.aggregate = aggregate;
|
|
38
|
+
this.run = run;
|
|
39
|
+
}
|
|
40
|
+
asEffect() {
|
|
41
|
+
return this.run;
|
|
42
|
+
}
|
|
43
|
+
when(command, payload) {
|
|
44
|
+
return new ScenarioImpl(this.aggregate, Effect.flatMap(this.run, (outcome) => outcome.fixture.when(command, payload)));
|
|
45
|
+
}
|
|
46
|
+
whenFails(command, payload) {
|
|
47
|
+
return Effect.flatMap(this.run, (outcome) => outcome.fixture.whenFails(command, payload));
|
|
48
|
+
}
|
|
49
|
+
expectEvent(event) {
|
|
50
|
+
return this.expectEvents([event]);
|
|
51
|
+
}
|
|
52
|
+
expectEvents(expected) {
|
|
53
|
+
const isSameEvent = Schema.toEquivalence(this.aggregate.events);
|
|
54
|
+
const matches = (want, event) => typeof want === "string" ? event._tag === want : isSameEvent(want, event);
|
|
55
|
+
return new ScenarioImpl(this.aggregate, Effect.tap(this.run, ({ events }) => events.length === expected.length && expected.every((want, index) => matches(want, events[index]))
|
|
56
|
+
? Effect.void
|
|
57
|
+
: Effect.die(new ExpectationFailed({
|
|
58
|
+
message: "The command did not emit the expected events",
|
|
59
|
+
expected,
|
|
60
|
+
actual: events
|
|
61
|
+
}))));
|
|
62
|
+
}
|
|
63
|
+
inspectState(check) {
|
|
64
|
+
return new ScenarioImpl(this.aggregate, Effect.tap(this.run, ({ state }) => Effect.sync(() => check(state))));
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
export const make = (aggregate, options) => {
|
|
68
|
+
const aggregateId = options?.aggregateId ?? "fixture-id";
|
|
69
|
+
const history = options?.events ?? [];
|
|
70
|
+
const encodeEvent = AggregateService.makeEventEncoder(aggregate);
|
|
71
|
+
const encodeGiven = (event) => event instanceof Aggregate.Historical ? Effect.succeed(event) : encodeEvent(event);
|
|
72
|
+
const seeded = Effect.gen(function* () {
|
|
73
|
+
const store = yield* makeMemory;
|
|
74
|
+
if (history.length > 0) {
|
|
75
|
+
yield* store.append({
|
|
76
|
+
aggregateName: aggregate.name,
|
|
77
|
+
aggregateId,
|
|
78
|
+
expectedVersion: 0,
|
|
79
|
+
events: yield* Effect.forEach(history, encodeGiven)
|
|
80
|
+
});
|
|
81
|
+
}
|
|
82
|
+
const service = yield* AggregateService.make(aggregate).pipe(Effect.provideService(EventStore, store));
|
|
83
|
+
return service;
|
|
84
|
+
}).pipe(
|
|
85
|
+
// A store with nothing but memory behind it can't fail, and a history that
|
|
86
|
+
// doesn't fit a fresh stream is a mistake in the test.
|
|
87
|
+
Effect.orDie);
|
|
88
|
+
const state = Effect.flatMap(seeded, (service) => service.readModel(aggregateId));
|
|
89
|
+
const when = (command, payload) => new ScenarioImpl(aggregate, Effect.gen(function* () {
|
|
90
|
+
const service = yield* seeded;
|
|
91
|
+
const result = yield* service.commands[command](aggregateId, payload);
|
|
92
|
+
return {
|
|
93
|
+
events: result.events,
|
|
94
|
+
state: yield* service.readModel(aggregateId),
|
|
95
|
+
version: result.version,
|
|
96
|
+
fixture: make(aggregate, { aggregateId, events: [...history, ...result.events] })
|
|
97
|
+
};
|
|
98
|
+
}));
|
|
99
|
+
const checkInvariants = (options) => Effect.gen(function* () {
|
|
100
|
+
const commands = Object.entries(aggregate.commands).flatMap(([name, command]) => {
|
|
101
|
+
const given = options.commands?.[name];
|
|
102
|
+
if (given === false)
|
|
103
|
+
return [];
|
|
104
|
+
const payload = given ?? Arbitrary.schema(Schema.toType(command.payload));
|
|
105
|
+
return [[name, Arbitrary.map(payload, (payload) => ({ command: name, payload }))]];
|
|
106
|
+
});
|
|
107
|
+
if (commands.length === 0) {
|
|
108
|
+
return yield* Effect.die(new Error("checkInvariants needs at least one command to run"));
|
|
109
|
+
}
|
|
110
|
+
const byName = new Map(commands);
|
|
111
|
+
const steps = Arbitrary.array(Arbitrary.flatMap(Arbitrary.schema(Schema.Literals(commands.map(([name]) => name))), (name) => byName.get(name)), { maxLength: options.maxCommands ?? 20 });
|
|
112
|
+
// Each run starts from a fresh store with the given history.
|
|
113
|
+
const holds = (sequence) => Effect.gen(function* () {
|
|
114
|
+
const service = yield* seeded;
|
|
115
|
+
if (!options.invariant(yield* service.readModel(aggregateId)))
|
|
116
|
+
return false;
|
|
117
|
+
for (const step of sequence) {
|
|
118
|
+
const succeeded = yield* service.commands[step.command](aggregateId, step.payload).pipe(Effect.as(true), Effect.catch(() => Effect.succeed(false)));
|
|
119
|
+
if (succeeded && !options.invariant(yield* service.readModel(aggregateId)))
|
|
120
|
+
return false;
|
|
121
|
+
}
|
|
122
|
+
return true;
|
|
123
|
+
}).pipe(Effect.orDie);
|
|
124
|
+
const result = yield* Arbitrary.checkEffect(steps, holds, options.check);
|
|
125
|
+
if (result._tag !== "Passed") {
|
|
126
|
+
return yield* Effect.die(new ExpectationFailed({
|
|
127
|
+
message: Arbitrary.formatCheckFailure(result) ?? "The invariant does not hold",
|
|
128
|
+
expected: "the invariant to hold after every command",
|
|
129
|
+
actual: result._tag === "Falsified" ? result.shrunkInput : result
|
|
130
|
+
}));
|
|
131
|
+
}
|
|
132
|
+
});
|
|
133
|
+
const fixture = {
|
|
134
|
+
given: (events) => make(aggregate, { aggregateId, events }),
|
|
135
|
+
givenEvents: (events = [], id = aggregateId) => make(aggregate, { aggregateId: id, events }),
|
|
136
|
+
state,
|
|
137
|
+
inspectState: (check) => new ScenarioImpl(aggregate, Effect.map(state, (current) => ({
|
|
138
|
+
events: [],
|
|
139
|
+
state: current,
|
|
140
|
+
version: history.length,
|
|
141
|
+
fixture
|
|
142
|
+
}))).inspectState(check),
|
|
143
|
+
when,
|
|
144
|
+
whenFails: (command, payload) => when(command, payload).pipe(Effect.flip, Effect.catch(() => Effect.die(new Error(`Expected command "${command}" to fail, but it succeeded`)))),
|
|
145
|
+
checkInvariants
|
|
146
|
+
};
|
|
147
|
+
return fixture;
|
|
148
|
+
};
|
|
149
|
+
//# sourceMappingURL=Fixture.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"Fixture.js","sourceRoot":"","sources":["../src/Fixture.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Metadata: what a command carries beside its payload that the domain doesn't
|
|
3
|
+
* decide on, such as the version of the software that ran it, the user who sent
|
|
4
|
+
* it or the address it came from. It is a JSON object recorded on every event
|
|
5
|
+
* the command writes, and projections, view models and `history` can read it.
|
|
6
|
+
*
|
|
7
|
+
* Like `Causation`, it travels in the fiber's context rather than in payloads.
|
|
8
|
+
* A command records, key by key, from the lowest precedence:
|
|
9
|
+
*
|
|
10
|
+
* 1. the metadata where its aggregate service was built, such as the software
|
|
11
|
+
* version provided once for the whole application with {@link layer};
|
|
12
|
+
* 2. the metadata where it runs, added with {@link withMetadata};
|
|
13
|
+
* 3. the `metadata` of a `dispatch` envelope.
|
|
14
|
+
*
|
|
15
|
+
* A view model or saga reacting to an event doesn't inherit that event's
|
|
16
|
+
* metadata: its commands record their own. The `causationId` leads back to the
|
|
17
|
+
* event, and from there to its metadata.
|
|
18
|
+
*/
|
|
19
|
+
import { Context, Effect, Layer, Schema } from "effect";
|
|
20
|
+
/** A JSON object. */
|
|
21
|
+
export type Metadata = Schema.JsonObject;
|
|
22
|
+
export declare const Metadata: Schema.Codec<Metadata>;
|
|
23
|
+
/** The metadata commands run in this fiber record, `{}` outside any. */
|
|
24
|
+
export declare const CurrentMetadata: Context.Reference<Schema.JsonObject>;
|
|
25
|
+
/** Runs an effect whose commands record this metadata on top of the current one. */
|
|
26
|
+
export declare const withMetadata: {
|
|
27
|
+
(metadata: Metadata): <A, E, R>(effect: Effect.Effect<A, E, R>) => Effect.Effect<A, E, R>;
|
|
28
|
+
<A, E, R>(effect: Effect.Effect<A, E, R>, metadata: Metadata): Effect.Effect<A, E, R>;
|
|
29
|
+
};
|
|
30
|
+
/**
|
|
31
|
+
* Provides metadata to everything built on top of this layer, for values that
|
|
32
|
+
* hold for the whole process such as the software version:
|
|
33
|
+
*
|
|
34
|
+
* ```ts
|
|
35
|
+
* AppLayer.pipe(Layer.provide(Metadata.layer({ appVersion: "1.4.0" })))
|
|
36
|
+
* ```
|
|
37
|
+
*
|
|
38
|
+
* Aggregate services remember it, so it is recorded even by commands run where
|
|
39
|
+
* the layer isn't visible.
|
|
40
|
+
*/
|
|
41
|
+
export declare const layer: (metadata: Metadata) => Layer.Layer<never>;
|
|
42
|
+
//# sourceMappingURL=Metadata.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"Metadata.d.ts","sourceRoot":"","sources":["../src/Metadata.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AACH,OAAO,EAAE,OAAO,EAAE,MAAM,EAAY,KAAK,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAA;AAEjE,qBAAqB;AACrB,MAAM,MAAM,QAAQ,GAAG,MAAM,CAAC,UAAU,CAAA;AAExC,eAAO,MAAM,QAAQ,EAAE,MAAM,CAAC,KAAK,CAAC,QAAQ,CAAqB,CAAA;AAEjE,wEAAwE;AACxE,eAAO,MAAM,eAAe,sCAE1B,CAAA;AAEF,oFAAoF;AACpF,eAAO,MAAM,YAAY,EAAE;IACzB,CAAC,QAAQ,EAAE,QAAQ,GAAG,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,MAAM,EAAE,MAAM,CAAC,MAAM,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC,KAAK,MAAM,CAAC,MAAM,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC,CAAA;IACzF,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,MAAM,EAAE,MAAM,CAAC,MAAM,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC,EAAE,QAAQ,EAAE,QAAQ,GAAG,MAAM,CAAC,MAAM,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC,CAAA;CAKtF,CAAA;AAED;;;;;;;;;;GAUG;AACH,eAAO,MAAM,KAAK,aAAc,QAAQ,KAAG,KAAK,CAAC,KAAK,CAAC,KAAK,CAA6C,CAAA"}
|
package/dist/Metadata.js
ADDED
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Metadata: what a command carries beside its payload that the domain doesn't
|
|
3
|
+
* decide on, such as the version of the software that ran it, the user who sent
|
|
4
|
+
* it or the address it came from. It is a JSON object recorded on every event
|
|
5
|
+
* the command writes, and projections, view models and `history` can read it.
|
|
6
|
+
*
|
|
7
|
+
* Like `Causation`, it travels in the fiber's context rather than in payloads.
|
|
8
|
+
* A command records, key by key, from the lowest precedence:
|
|
9
|
+
*
|
|
10
|
+
* 1. the metadata where its aggregate service was built, such as the software
|
|
11
|
+
* version provided once for the whole application with {@link layer};
|
|
12
|
+
* 2. the metadata where it runs, added with {@link withMetadata};
|
|
13
|
+
* 3. the `metadata` of a `dispatch` envelope.
|
|
14
|
+
*
|
|
15
|
+
* A view model or saga reacting to an event doesn't inherit that event's
|
|
16
|
+
* metadata: its commands record their own. The `causationId` leads back to the
|
|
17
|
+
* event, and from there to its metadata.
|
|
18
|
+
*/
|
|
19
|
+
import { Context, Effect, Function, Layer, Schema } from "effect";
|
|
20
|
+
export const Metadata = Schema.JsonObject;
|
|
21
|
+
/** The metadata commands run in this fiber record, `{}` outside any. */
|
|
22
|
+
export const CurrentMetadata = Context.Reference("effect-cqrs/Metadata", {
|
|
23
|
+
defaultValue: () => ({})
|
|
24
|
+
});
|
|
25
|
+
/** Runs an effect whose commands record this metadata on top of the current one. */
|
|
26
|
+
export const withMetadata = Function.dual(2, (effect, metadata) => CurrentMetadata.use((current) => Effect.provideService(effect, CurrentMetadata, { ...current, ...metadata })));
|
|
27
|
+
/**
|
|
28
|
+
* Provides metadata to everything built on top of this layer, for values that
|
|
29
|
+
* hold for the whole process such as the software version:
|
|
30
|
+
*
|
|
31
|
+
* ```ts
|
|
32
|
+
* AppLayer.pipe(Layer.provide(Metadata.layer({ appVersion: "1.4.0" })))
|
|
33
|
+
* ```
|
|
34
|
+
*
|
|
35
|
+
* Aggregate services remember it, so it is recorded even by commands run where
|
|
36
|
+
* the layer isn't visible.
|
|
37
|
+
*/
|
|
38
|
+
export const layer = (metadata) => Layer.succeed(CurrentMetadata, metadata);
|
|
39
|
+
//# sourceMappingURL=Metadata.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"Metadata.js","sourceRoot":"","sources":["../src/Metadata.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AACH,OAAO,EAAE,OAAO,EAAE,MAAM,EAAE,QAAQ,EAAE,KAAK,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAA;AAKjE,MAAM,CAAC,MAAM,QAAQ,GAA2B,MAAM,CAAC,UAAU,CAAA;AAEjE,wEAAwE;AACxE,MAAM,CAAC,MAAM,eAAe,GAAG,OAAO,CAAC,SAAS,CAAW,sBAAsB,EAAE;IACjF,YAAY,EAAE,GAAG,EAAE,CAAC,CAAC,EAAE,CAAC;CACzB,CAAC,CAAA;AAEF,oFAAoF;AACpF,MAAM,CAAC,MAAM,YAAY,GAGrB,QAAQ,CAAC,IAAI,CACf,CAAC,EACD,CAAU,MAA8B,EAAE,QAAkB,EAAE,EAAE,CAC9D,eAAe,CAAC,GAAG,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC,MAAM,CAAC,cAAc,CAAC,MAAM,EAAE,eAAe,EAAE,EAAE,GAAG,OAAO,EAAE,GAAG,QAAQ,EAAE,CAAC,CAAC,CAChH,CAAA;AAED;;;;;;;;;;GAUG;AACH,MAAM,CAAC,MAAM,KAAK,GAAG,CAAC,QAAkB,EAAsB,EAAE,CAAC,KAAK,CAAC,OAAO,CAAC,eAAe,EAAE,QAAQ,CAAC,CAAA"}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Durable storage on PostgreSQL, through any `effect/sql` PostgreSQL driver.
|
|
3
|
+
*
|
|
4
|
+
* A sequence can't number events here. Transactions commit in a different order from
|
|
5
|
+
* the one they drew their numbers in, so a view model that has read up to 6 would
|
|
6
|
+
* never come back for a 5 that commits later. Positions come from a counter row
|
|
7
|
+
* instead, which an append holds until it commits: writers wait for each other at
|
|
8
|
+
* that one step, and positions follow commit order with no gaps.
|
|
9
|
+
*
|
|
10
|
+
* The tables are created on startup.
|
|
11
|
+
*/
|
|
12
|
+
import { Effect, Layer } from "effect";
|
|
13
|
+
import { SqlClient } from "effect/sql";
|
|
14
|
+
import { Checkpoints } from "./Checkpoints.ts";
|
|
15
|
+
import * as EventStore from "./EventStore.ts";
|
|
16
|
+
export interface Options {
|
|
17
|
+
readonly eventsTable?: string;
|
|
18
|
+
readonly checkpointsTable?: string;
|
|
19
|
+
}
|
|
20
|
+
export declare const make: (options?: Options | undefined) => Effect.Effect<{
|
|
21
|
+
append(input: EventStore.AppendInput): Effect.Effect<ReadonlyArray<EventStore.StoredEvent>, EventStore.ConcurrencyConflict | EventStore.EventStoreError>;
|
|
22
|
+
read(filter?: EventStore.EventFilter): import("effect/Stream").Stream<EventStore.StoredEvent, EventStore.EventStoreError>;
|
|
23
|
+
subscribe(filter?: EventStore.EventFilter): import("effect/Stream").Stream<EventStore.StoredEvent, EventStore.EventStoreError>;
|
|
24
|
+
readonly head: Effect.Effect<number, EventStore.EventStoreError>;
|
|
25
|
+
readonly changes: import("effect/Stream").Stream<number>;
|
|
26
|
+
}, EventStore.EventStoreError, SqlClient.SqlClient>;
|
|
27
|
+
export declare const layer: (options?: Options) => Layer.Layer<EventStore.EventStore, EventStore.EventStoreError, SqlClient.SqlClient>;
|
|
28
|
+
/** Keeps checkpoints in the same database, for view models that are durable too. */
|
|
29
|
+
export declare const layerCheckpoints: (options?: Options) => Layer.Layer<Checkpoints, EventStore.EventStoreError, SqlClient.SqlClient>;
|
|
30
|
+
//# sourceMappingURL=PostgresEventStore.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"PostgresEventStore.d.ts","sourceRoot":"","sources":["../src/PostgresEventStore.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AACH,OAAO,EAAiC,MAAM,EAAE,KAAK,EAAU,MAAM,QAAQ,CAAA;AAC7E,OAAO,EAAE,SAAS,EAAiB,MAAM,YAAY,CAAA;AACrD,OAAO,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAA;AAC9C,OAAO,KAAK,UAAU,MAAM,iBAAiB,CAAA;AAE7C,MAAM,WAAW,OAAO;IACtB,QAAQ,CAAC,WAAW,CAAC,EAAE,MAAM,CAAA;IAC7B,QAAQ,CAAC,gBAAgB,CAAC,EAAE,MAAM,CAAA;CACnC;AAWD,eAAO,MAAM,IAAI;;;;;;mDAkIf,CAAA;AAEF,eAAO,MAAM,KAAK,aAAc,OAAO,KAAG,KAAK,CAAC,KAAK,CAAC,UAAU,CAAC,UAAU,EAAE,UAAU,CAAC,eAAe,EAAE,SAAS,CAAC,SAAS,CACxE,CAAA;AAEpD,oFAAoF;AACpF,eAAO,MAAM,gBAAgB,aAAc,OAAO,KAAG,KAAK,CAAC,KAAK,CAAC,WAAW,EAAE,UAAU,CAAC,eAAe,EAAE,SAAS,CAAC,SAAS,CAgC1H,CAAA"}
|
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Durable storage on PostgreSQL, through any `effect/sql` PostgreSQL driver.
|
|
3
|
+
*
|
|
4
|
+
* A sequence can't number events here. Transactions commit in a different order from
|
|
5
|
+
* the one they drew their numbers in, so a view model that has read up to 6 would
|
|
6
|
+
* never come back for a 5 that commits later. Positions come from a counter row
|
|
7
|
+
* instead, which an append holds until it commits: writers wait for each other at
|
|
8
|
+
* that one step, and positions follow commit order with no gaps.
|
|
9
|
+
*
|
|
10
|
+
* The tables are created on startup.
|
|
11
|
+
*/
|
|
12
|
+
import { Array as Arr, Clock, DateTime, Effect, Layer, Schema } from "effect";
|
|
13
|
+
import { SqlClient } from "effect/sql";
|
|
14
|
+
import { Checkpoints } from "./Checkpoints.js";
|
|
15
|
+
import * as EventStore from "./EventStore.js";
|
|
16
|
+
const decodeRows = Schema.decodeUnknownEffect(Schema.Array(EventStore.StoredEvent));
|
|
17
|
+
const storeError = (operation) => (cause) => new EventStore.EventStoreError({ operation, cause });
|
|
18
|
+
const isUniqueViolation = (error) => error.reason._tag === "UniqueViolation";
|
|
19
|
+
// The client binds at most 32,767 parameters to a statement, and each row uses 11.
|
|
20
|
+
const rowsPerInsert = 2_900;
|
|
21
|
+
export const make = Effect.fnUntraced(function* (options) {
|
|
22
|
+
const sql = yield* SqlClient.SqlClient;
|
|
23
|
+
const prefix = options?.eventsTable ?? "cqrs_events";
|
|
24
|
+
const events = sql(prefix);
|
|
25
|
+
const counter = sql(`${prefix}_head`);
|
|
26
|
+
const initialize = Effect.gen(function* () {
|
|
27
|
+
yield* sql `
|
|
28
|
+
CREATE TABLE IF NOT EXISTS ${events} (
|
|
29
|
+
position BIGINT PRIMARY KEY,
|
|
30
|
+
aggregate_name TEXT NOT NULL,
|
|
31
|
+
aggregate_id TEXT NOT NULL,
|
|
32
|
+
version INTEGER NOT NULL,
|
|
33
|
+
type TEXT NOT NULL,
|
|
34
|
+
schema_version INTEGER NOT NULL DEFAULT 1,
|
|
35
|
+
payload JSONB NOT NULL,
|
|
36
|
+
"timestamp" BIGINT NOT NULL,
|
|
37
|
+
correlation_id TEXT,
|
|
38
|
+
causation_id TEXT,
|
|
39
|
+
metadata JSONB NOT NULL DEFAULT '{}',
|
|
40
|
+
UNIQUE (aggregate_name, aggregate_id, version)
|
|
41
|
+
)
|
|
42
|
+
`;
|
|
43
|
+
// Tables created before these columns existed get them here.
|
|
44
|
+
yield* sql `ALTER TABLE ${events} ADD COLUMN IF NOT EXISTS schema_version INTEGER NOT NULL DEFAULT 1`;
|
|
45
|
+
yield* sql `ALTER TABLE ${events} ADD COLUMN IF NOT EXISTS correlation_id TEXT`;
|
|
46
|
+
yield* sql `ALTER TABLE ${events} ADD COLUMN IF NOT EXISTS causation_id TEXT`;
|
|
47
|
+
yield* sql `ALTER TABLE ${events} ADD COLUMN IF NOT EXISTS metadata JSONB NOT NULL DEFAULT '{}'`;
|
|
48
|
+
yield* sql `CREATE INDEX IF NOT EXISTS ${sql(`${prefix}_type_idx`)} ON ${events} (type, position)`;
|
|
49
|
+
yield* sql `
|
|
50
|
+
CREATE TABLE IF NOT EXISTS ${counter} (
|
|
51
|
+
only_row BOOLEAN PRIMARY KEY DEFAULT TRUE CHECK (only_row),
|
|
52
|
+
position BIGINT NOT NULL
|
|
53
|
+
)
|
|
54
|
+
`;
|
|
55
|
+
yield* sql `INSERT INTO ${counter} (position) VALUES (0) ON CONFLICT DO NOTHING`;
|
|
56
|
+
});
|
|
57
|
+
yield* initialize.pipe(Effect.mapError(storeError("initialize")));
|
|
58
|
+
const columns = sql.literal(`
|
|
59
|
+
position::float8 AS position,
|
|
60
|
+
aggregate_name AS "aggregateName",
|
|
61
|
+
aggregate_id AS "aggregateId",
|
|
62
|
+
version,
|
|
63
|
+
type,
|
|
64
|
+
schema_version AS "schemaVersion",
|
|
65
|
+
payload,
|
|
66
|
+
"timestamp"::float8 AS "timestamp",
|
|
67
|
+
correlation_id AS "correlationId",
|
|
68
|
+
causation_id AS "causationId",
|
|
69
|
+
metadata
|
|
70
|
+
`);
|
|
71
|
+
const append = (input) => {
|
|
72
|
+
const conflict = new EventStore.ConcurrencyConflict({
|
|
73
|
+
aggregateName: input.aggregateName,
|
|
74
|
+
aggregateId: input.aggregateId,
|
|
75
|
+
expectedVersion: input.expectedVersion
|
|
76
|
+
});
|
|
77
|
+
return Effect.gen(function* () {
|
|
78
|
+
const [current] = yield* sql `
|
|
79
|
+
SELECT COALESCE(MAX(version), 0) AS version FROM ${events}
|
|
80
|
+
WHERE aggregate_name = ${input.aggregateName} AND aggregate_id = ${input.aggregateId}
|
|
81
|
+
`;
|
|
82
|
+
if (current?.version !== input.expectedVersion) {
|
|
83
|
+
return yield* conflict;
|
|
84
|
+
}
|
|
85
|
+
const timestamp = yield* Clock.currentTimeMillis;
|
|
86
|
+
const [head] = yield* sql `
|
|
87
|
+
UPDATE ${counter} SET position = position + ${input.events.length} RETURNING position::float8 AS position
|
|
88
|
+
`;
|
|
89
|
+
const first = head.position - input.events.length + 1;
|
|
90
|
+
const stored = input.events.map((event, index) => EventStore.toStored(input, event, index, first + index, DateTime.makeUnsafe(timestamp)));
|
|
91
|
+
const rows = stored.map((event) => ({
|
|
92
|
+
position: event.position,
|
|
93
|
+
aggregate_name: event.aggregateName,
|
|
94
|
+
aggregate_id: event.aggregateId,
|
|
95
|
+
version: event.version,
|
|
96
|
+
type: event.type,
|
|
97
|
+
schema_version: event.schemaVersion,
|
|
98
|
+
payload: JSON.stringify(event.payload),
|
|
99
|
+
timestamp,
|
|
100
|
+
correlation_id: event.correlationId,
|
|
101
|
+
causation_id: event.causationId,
|
|
102
|
+
metadata: JSON.stringify(event.metadata)
|
|
103
|
+
}));
|
|
104
|
+
yield* Effect.forEach(Arr.chunksOf(rows, rowsPerInsert), (chunk) => sql `INSERT INTO ${events} ${sql.insert(chunk)}`, { discard: true });
|
|
105
|
+
return stored;
|
|
106
|
+
}).pipe(sql.withTransaction,
|
|
107
|
+
// The version check can't see a writer that hasn't committed, but the UNIQUE
|
|
108
|
+
// constraint can. Rolling back gives the position back, so that leaves no gap.
|
|
109
|
+
Effect.catchTag("SqlError", (error) => Effect.fail(isUniqueViolation(error) ? conflict : storeError("append")(error))));
|
|
110
|
+
};
|
|
111
|
+
const readBatch = (filter, afterPosition, limit) => {
|
|
112
|
+
const oneStream = EventStore.isOneStream(filter);
|
|
113
|
+
const clauses = [
|
|
114
|
+
sql `${events}.position > ${afterPosition}`,
|
|
115
|
+
...(oneStream ? [sql `version > ${filter.afterVersion ?? 0}`] : []),
|
|
116
|
+
...(filter.aggregateNames === undefined ? [] : [sql.in("aggregate_name", filter.aggregateNames)]),
|
|
117
|
+
...(filter.aggregateIds === undefined ? [] : [sql.in("aggregate_id", filter.aggregateIds)]),
|
|
118
|
+
...(filter.eventTypes === undefined ? [] : [sql.in("type", filter.eventTypes)]),
|
|
119
|
+
...(filter.startTime === undefined ? [] : [sql `"timestamp" >= ${DateTime.toEpochMillis(filter.startTime)}`]),
|
|
120
|
+
...(filter.finishTime === undefined ? [] : [sql `"timestamp" < ${DateTime.toEpochMillis(filter.finishTime)}`])
|
|
121
|
+
];
|
|
122
|
+
// Inside a stream versions follow positions, and ordering by them keeps to the
|
|
123
|
+
// stream's index. With `LIMIT` the planner would otherwise walk the primary key,
|
|
124
|
+
// through every later event of the log, looking for a stream with nothing new.
|
|
125
|
+
// `position` alone would mean the converted column above, which has no index.
|
|
126
|
+
const order = oneStream ? sql `version` : sql `${events}.position`;
|
|
127
|
+
return sql `SELECT ${columns} FROM ${events} WHERE ${sql.and(clauses)} ORDER BY ${order} LIMIT ${limit}`.pipe(Effect.flatMap(decodeRows), Effect.mapError(storeError("read")));
|
|
128
|
+
};
|
|
129
|
+
const head = sql `SELECT position::float8 AS position FROM ${counter}`.pipe(Effect.map(([row]) => row?.position ?? 0), Effect.mapError(storeError("head")));
|
|
130
|
+
return yield* EventStore.make({ append, readBatch, head });
|
|
131
|
+
});
|
|
132
|
+
export const layer = (options) => Layer.effect(EventStore.EventStore, make(options));
|
|
133
|
+
/** Keeps checkpoints in the same database, for view models that are durable too. */
|
|
134
|
+
export const layerCheckpoints = (options) => Layer.effect(Checkpoints, Effect.gen(function* () {
|
|
135
|
+
const sql = yield* SqlClient.SqlClient;
|
|
136
|
+
const table = sql(options?.checkpointsTable ?? "cqrs_checkpoints");
|
|
137
|
+
yield* sql `
|
|
138
|
+
CREATE TABLE IF NOT EXISTS ${table} (
|
|
139
|
+
view_model TEXT PRIMARY KEY,
|
|
140
|
+
position BIGINT NOT NULL
|
|
141
|
+
)
|
|
142
|
+
`.pipe(Effect.mapError(storeError("initialize")));
|
|
143
|
+
return Checkpoints.of({
|
|
144
|
+
get: (viewModel) => sql `
|
|
145
|
+
SELECT position::float8 AS position FROM ${table} WHERE view_model = ${viewModel}
|
|
146
|
+
`.pipe(Effect.map(([row]) => row?.position ?? 0), Effect.mapError(storeError("checkpoint"))),
|
|
147
|
+
set: (viewModel, position) => sql `
|
|
148
|
+
INSERT INTO ${table} (view_model, position) VALUES (${viewModel}, ${position})
|
|
149
|
+
ON CONFLICT (view_model) DO UPDATE SET position = excluded.position
|
|
150
|
+
`.pipe(Effect.asVoid, Effect.mapError(storeError("checkpoint")))
|
|
151
|
+
});
|
|
152
|
+
}));
|
|
153
|
+
//# sourceMappingURL=PostgresEventStore.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"PostgresEventStore.js","sourceRoot":"","sources":["../src/PostgresEventStore.ts"],"names":[],"mappings":"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"}
|
package/dist/Saga.d.ts
ADDED
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Sagas: reactions that span several aggregates and outlive one command, run as
|
|
3
|
+
* durable workflows from `effect/workflow`.
|
|
4
|
+
*
|
|
5
|
+
* A saga has two halves. Its trigger is a view model that follows the log from a
|
|
6
|
+
* checkpoint and starts a workflow for each event it cares about. The workflow is
|
|
7
|
+
* the process itself: each command it runs is an `Activity`, so when the engine
|
|
8
|
+
* replays the workflow after a restart it skips the commands that already
|
|
9
|
+
* finished, and `withCompensation` undoes earlier steps when a later one fails.
|
|
10
|
+
*
|
|
11
|
+
* Triggers see an event at least once, and the workflow's idempotency key turns
|
|
12
|
+
* a second start into a join of the first execution. That only holds while the
|
|
13
|
+
* engine remembers the execution, so pair `WorkflowEngine.layerMemory` with
|
|
14
|
+
* in-memory checkpoints, as for view models, and a durable engine such as
|
|
15
|
+
* `ClusterWorkflowEngine` from `effect/cluster` with durable ones.
|
|
16
|
+
*
|
|
17
|
+
* An activity that is cut off before its result is saved runs again, so the
|
|
18
|
+
* commands a saga runs should be idempotent, for instance by recording the id of
|
|
19
|
+
* the transfer they belong to.
|
|
20
|
+
*/
|
|
21
|
+
import { Effect, Layer, Schedule, Schema } from "effect";
|
|
22
|
+
import { Activity, Workflow } from "effect/workflow";
|
|
23
|
+
import type { WorkflowEngine } from "effect/workflow";
|
|
24
|
+
import type * as Aggregate from "./Aggregate.ts";
|
|
25
|
+
import type { CommandResult } from "./AggregateService.ts";
|
|
26
|
+
import type { Checkpoints } from "./Checkpoints.ts";
|
|
27
|
+
import type { EventStore, EventStoreError } from "./EventStore.ts";
|
|
28
|
+
import * as ViewModel from "./ViewModel.ts";
|
|
29
|
+
/**
|
|
30
|
+
* The causation a workflow carries in its payload. A workflow body runs in the
|
|
31
|
+
* engine's fibers, not in the trigger's, so this is how commands it runs learn
|
|
32
|
+
* which event caused them.
|
|
33
|
+
*/
|
|
34
|
+
export declare const CausationSchema: Schema.Struct<{
|
|
35
|
+
readonly correlationId: Schema.String;
|
|
36
|
+
readonly causationId: Schema.String;
|
|
37
|
+
}>;
|
|
38
|
+
/** A workflow whose payload carries the causation of the event that started it. */
|
|
39
|
+
export type SagaWorkflow<Tag extends string = string, Fields extends Schema.Struct.Fields = Schema.Struct.Fields, Success extends Schema.Top = Schema.Top, Error extends Schema.Top = Schema.Top> = Workflow.Workflow<Tag, Schema.Struct<Fields & {
|
|
40
|
+
readonly causation: typeof CausationSchema;
|
|
41
|
+
}>, Success, Error>;
|
|
42
|
+
/**
|
|
43
|
+
* Defines the workflow of a saga. It is `Workflow.make` with a `causation` field
|
|
44
|
+
* added to the payload, which {@link start} fills in and {@link toLayer} applies.
|
|
45
|
+
*/
|
|
46
|
+
export declare const workflow: <const Tag extends string, const Fields extends Schema.Struct.Fields, Success extends Schema.Top = Schema.Void, Error extends Schema.Top = Schema.Never>(tag: Tag, options: {
|
|
47
|
+
readonly payload: Fields;
|
|
48
|
+
/** Identifies one run of the saga, such as a transfer id. Starting it again joins that run. */
|
|
49
|
+
readonly idempotencyKey: (payload: Schema.Struct.Type<Fields>) => string;
|
|
50
|
+
readonly success?: Success;
|
|
51
|
+
readonly error?: Error;
|
|
52
|
+
readonly suspendedRetrySchedule?: Schedule.Schedule<any, unknown> | undefined;
|
|
53
|
+
}) => SagaWorkflow<Tag, Fields, Success, Error>;
|
|
54
|
+
/**
|
|
55
|
+
* Registers the body of a saga's workflow with the engine. Commands the body
|
|
56
|
+
* runs record the event that started the saga as their cause.
|
|
57
|
+
*/
|
|
58
|
+
export declare const toLayer: <Tag extends string, Fields extends Schema.Struct.Fields, Success extends Schema.Top, Error extends Schema.Top, R>(self: SagaWorkflow<Tag, Fields, Success, Error>, execute: (payload: Schema.Struct.Type<Fields>, executionId: string) => Effect.Effect<Success["Type"], Error["Type"], R>) => Layer.Layer<never, never, WorkflowEngine.WorkflowEngine | Schema.Struct.DecodingServices<Fields & {
|
|
59
|
+
readonly causation: typeof CausationSchema;
|
|
60
|
+
}> | Schema.Struct.EncodingServices<Fields & {
|
|
61
|
+
readonly causation: typeof CausationSchema;
|
|
62
|
+
}> | Error["DecodingServices"] | Error["EncodingServices"] | Success["DecodingServices"] | Success["EncodingServices"] | Exclude<R, Workflow.Execution<Tag> | import("effect/Scope").Scope | WorkflowEngine.WorkflowEngine | WorkflowEngine.WorkflowInstance>>;
|
|
63
|
+
/**
|
|
64
|
+
* Starts a saga's workflow without waiting for it, and returns its execution id.
|
|
65
|
+
* Inside a trigger the event being handled becomes the saga's cause; elsewhere
|
|
66
|
+
* the current causation does, or a new correlation when there is none.
|
|
67
|
+
*/
|
|
68
|
+
export declare const start: <Tag extends string, Fields extends Schema.Struct.Fields, Success extends Schema.Top, Error extends Schema.Top>(self: SagaWorkflow<Tag, Fields, Success, Error>, payload: Schema.Struct.MakeIn<Fields>) => Effect.Effect<string, never, WorkflowEngine.WorkflowEngine>;
|
|
69
|
+
/**
|
|
70
|
+
* The trigger of a saga: a view model whose handlers start workflows with
|
|
71
|
+
* {@link start}, so it keeps a checkpoint and retries like view models do. Run it
|
|
72
|
+
* with {@link layer}, or with `ViewModel.layer` next to the other view models,
|
|
73
|
+
* where replaying it starts every saga again and joins the executions the engine
|
|
74
|
+
* still remembers.
|
|
75
|
+
*/
|
|
76
|
+
export declare const make: <const Name extends string, const Aggregates extends ReadonlyArray<Aggregate.Any>, const H extends ViewModel.Handlers<Aggregate.Event<Aggregates[number]>>>(options: {
|
|
77
|
+
readonly name: Name;
|
|
78
|
+
readonly aggregates: Aggregates;
|
|
79
|
+
readonly handlers: H;
|
|
80
|
+
}) => ViewModel.ViewModel<Name, { readonly [K in keyof H]-?: NonNullable<H[K]> extends (...args: any) => Effect.Effect<any, infer E, any> ? E : never; }[keyof H], { readonly [K in keyof H]-?: NonNullable<H[K]> extends (...args: any) => Effect.Effect<any, any, infer R> ? R : never; }[keyof H]>;
|
|
81
|
+
/**
|
|
82
|
+
* Runs triggers in the background for as long as the layer lives, apart from
|
|
83
|
+
* the view models. Workflows usually run commands, so they sit on top of the
|
|
84
|
+
* command side, which view models are often provided to.
|
|
85
|
+
*/
|
|
86
|
+
export declare const layer: <const Sagas extends ReadonlyArray<ViewModel.Any>>(sagas: Sagas) => Layer.Layer<never, EventStoreError, EventStore | Checkpoints | ViewModel.Context<Sagas[number]>>;
|
|
87
|
+
/** What a saga keeps of a command it ran: enough to wait for view models to see it. */
|
|
88
|
+
export declare const CommandOutcome: Schema.Struct<{
|
|
89
|
+
readonly aggregateId: Schema.String;
|
|
90
|
+
readonly version: Schema.Int;
|
|
91
|
+
readonly position: Schema.Int;
|
|
92
|
+
}>;
|
|
93
|
+
/**
|
|
94
|
+
* Runs a command as an activity of a saga's workflow, so a replay of the
|
|
95
|
+
* workflow returns the saved outcome instead of running it again.
|
|
96
|
+
*
|
|
97
|
+
* Failures matching `error`, such as a domain error the saga reacts to, are
|
|
98
|
+
* saved like a success. A lost race or a store error is retried a few times.
|
|
99
|
+
* Anything else ends the workflow as a defect, which runs its compensations.
|
|
100
|
+
*/
|
|
101
|
+
export declare const command: <Err extends Schema.Top = typeof Schema.Never, E = never, R = never>(name: string, options: {
|
|
102
|
+
readonly execute: Effect.Effect<CommandResult<unknown>, E, R>;
|
|
103
|
+
readonly error?: Err | undefined;
|
|
104
|
+
}) => Activity.Activity<typeof CommandOutcome, Err, R>;
|
|
105
|
+
//# sourceMappingURL=Saga.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"Saga.d.ts","sourceRoot":"","sources":["../src/Saga.ts"],"names":[],"mappings":"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"}
|