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,108 @@
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
+ import { Effect, Effectable, Schema } from "effect";
18
+ import * as AggregateService from "./AggregateService.js";
19
+ import { EventStore, makeMemory } from "./EventStore.js";
20
+ /** Raised as a defect, so a command's error channel only holds what the command can do. */
21
+ export class ExpectationFailed extends Schema.TaggedError()("ExpectationFailed", {
22
+ message: Schema.String,
23
+ expected: Schema.Unknown,
24
+ actual: Schema.Unknown
25
+ }) {
26
+ }
27
+ class ScenarioImpl extends Effectable.Class {
28
+ aggregate;
29
+ run;
30
+ constructor(aggregate, run) {
31
+ super();
32
+ this.aggregate = aggregate;
33
+ this.run = run;
34
+ }
35
+ asEffect() {
36
+ return this.run;
37
+ }
38
+ when(command, payload) {
39
+ return new ScenarioImpl(this.aggregate, Effect.flatMap(this.run, (outcome) => outcome.fixture.when(command, payload)));
40
+ }
41
+ whenFails(command, payload) {
42
+ return Effect.flatMap(this.run, (outcome) => outcome.fixture.whenFails(command, payload));
43
+ }
44
+ expectEvent(event) {
45
+ return this.expectEvents([event]);
46
+ }
47
+ expectEvents(expected) {
48
+ const isSameEvent = Schema.toEquivalence(this.aggregate.events);
49
+ const matches = (want, event) => typeof want === "string" ? event._tag === want : isSameEvent(want, event);
50
+ return new ScenarioImpl(this.aggregate, Effect.tap(this.run, ({ events }) => events.length === expected.length && expected.every((want, index) => matches(want, events[index]))
51
+ ? Effect.void
52
+ : Effect.die(new ExpectationFailed({
53
+ message: "The command did not emit the expected events",
54
+ expected,
55
+ actual: events
56
+ }))));
57
+ }
58
+ inspectState(check) {
59
+ return new ScenarioImpl(this.aggregate, Effect.tap(this.run, ({ state }) => Effect.sync(() => check(state))));
60
+ }
61
+ }
62
+ export const make = (aggregate, options) => {
63
+ const aggregateId = options?.aggregateId ?? "fixture-id";
64
+ const history = options?.events ?? [];
65
+ const encodeEvent = AggregateService.makeEventEncoder(aggregate);
66
+ const seeded = Effect.gen(function* () {
67
+ const store = yield* makeMemory;
68
+ if (history.length > 0) {
69
+ yield* store.append({
70
+ aggregateName: aggregate.name,
71
+ aggregateId,
72
+ expectedVersion: 0,
73
+ events: yield* Effect.forEach(history, encodeEvent)
74
+ });
75
+ }
76
+ const service = yield* AggregateService.make(aggregate).pipe(Effect.provideService(EventStore, store));
77
+ return service;
78
+ }).pipe(
79
+ // A store with nothing but memory behind it can't fail, and a history that
80
+ // doesn't fit a fresh stream is a mistake in the test.
81
+ Effect.orDie);
82
+ const state = Effect.flatMap(seeded, (service) => service.readModel(aggregateId));
83
+ const when = (command, payload) => new ScenarioImpl(aggregate, Effect.gen(function* () {
84
+ const service = yield* seeded;
85
+ const result = yield* service.commands[command](aggregateId, payload);
86
+ return {
87
+ events: result.events,
88
+ state: yield* service.readModel(aggregateId),
89
+ version: result.version,
90
+ fixture: make(aggregate, { aggregateId, events: [...history, ...result.events] })
91
+ };
92
+ }));
93
+ const fixture = {
94
+ given: (events) => make(aggregate, { aggregateId, events }),
95
+ givenEvents: (events = [], id = aggregateId) => make(aggregate, { aggregateId: id, events }),
96
+ state,
97
+ inspectState: (check) => new ScenarioImpl(aggregate, Effect.map(state, (current) => ({
98
+ events: [],
99
+ state: current,
100
+ version: history.length,
101
+ fixture
102
+ }))).inspectState(check),
103
+ when,
104
+ whenFails: (command, payload) => when(command, payload).pipe(Effect.flip, Effect.catch(() => Effect.die(new Error(`Expected command "${command}" to fail, but it succeeded`))))
105
+ };
106
+ return fixture;
107
+ };
108
+ //# 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,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;;;;;;mDAuHf,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,144 @@
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 7.
20
+ const rowsPerInsert = 4_000;
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
+ payload JSONB NOT NULL,
35
+ "timestamp" BIGINT NOT NULL,
36
+ UNIQUE (aggregate_name, aggregate_id, version)
37
+ )
38
+ `;
39
+ yield* sql `CREATE INDEX IF NOT EXISTS ${sql(`${prefix}_type_idx`)} ON ${events} (type, position)`;
40
+ yield* sql `
41
+ CREATE TABLE IF NOT EXISTS ${counter} (
42
+ only_row BOOLEAN PRIMARY KEY DEFAULT TRUE CHECK (only_row),
43
+ position BIGINT NOT NULL
44
+ )
45
+ `;
46
+ yield* sql `INSERT INTO ${counter} (position) VALUES (0) ON CONFLICT DO NOTHING`;
47
+ });
48
+ yield* initialize.pipe(Effect.mapError(storeError("initialize")));
49
+ const columns = sql.literal(`
50
+ position::float8 AS position,
51
+ aggregate_name AS "aggregateName",
52
+ aggregate_id AS "aggregateId",
53
+ version,
54
+ type,
55
+ payload,
56
+ "timestamp"::float8 AS "timestamp"
57
+ `);
58
+ const append = (input) => {
59
+ const conflict = new EventStore.ConcurrencyConflict({
60
+ aggregateName: input.aggregateName,
61
+ aggregateId: input.aggregateId,
62
+ expectedVersion: input.expectedVersion
63
+ });
64
+ return Effect.gen(function* () {
65
+ const [current] = yield* sql `
66
+ SELECT COALESCE(MAX(version), 0) AS version FROM ${events}
67
+ WHERE aggregate_name = ${input.aggregateName} AND aggregate_id = ${input.aggregateId}
68
+ `;
69
+ if (current?.version !== input.expectedVersion) {
70
+ return yield* conflict;
71
+ }
72
+ const timestamp = yield* Clock.currentTimeMillis;
73
+ const [head] = yield* sql `
74
+ UPDATE ${counter} SET position = position + ${input.events.length} RETURNING position::float8 AS position
75
+ `;
76
+ const first = head.position - input.events.length + 1;
77
+ const stored = input.events.map((event, index) => ({
78
+ position: first + index,
79
+ aggregateName: input.aggregateName,
80
+ aggregateId: input.aggregateId,
81
+ version: input.expectedVersion + index + 1,
82
+ type: event.type,
83
+ payload: event.payload,
84
+ timestamp: DateTime.makeUnsafe(timestamp)
85
+ }));
86
+ const rows = stored.map((event) => ({
87
+ position: event.position,
88
+ aggregate_name: event.aggregateName,
89
+ aggregate_id: event.aggregateId,
90
+ version: event.version,
91
+ type: event.type,
92
+ payload: JSON.stringify(event.payload),
93
+ timestamp
94
+ }));
95
+ yield* Effect.forEach(Arr.chunksOf(rows, rowsPerInsert), (chunk) => sql `INSERT INTO ${events} ${sql.insert(chunk)}`, { discard: true });
96
+ return stored;
97
+ }).pipe(sql.withTransaction,
98
+ // The version check can't see a writer that hasn't committed, but the UNIQUE
99
+ // constraint can. Rolling back gives the position back, so that leaves no gap.
100
+ Effect.catchTag("SqlError", (error) => Effect.fail(isUniqueViolation(error) ? conflict : storeError("append")(error))));
101
+ };
102
+ const readBatch = (filter, afterPosition, limit) => {
103
+ const oneStream = EventStore.isOneStream(filter);
104
+ const clauses = [
105
+ sql `${events}.position > ${afterPosition}`,
106
+ ...(oneStream ? [sql `version > ${filter.afterVersion ?? 0}`] : []),
107
+ ...(filter.aggregateNames === undefined ? [] : [sql.in("aggregate_name", filter.aggregateNames)]),
108
+ ...(filter.aggregateIds === undefined ? [] : [sql.in("aggregate_id", filter.aggregateIds)]),
109
+ ...(filter.eventTypes === undefined ? [] : [sql.in("type", filter.eventTypes)]),
110
+ ...(filter.startTime === undefined ? [] : [sql `"timestamp" >= ${DateTime.toEpochMillis(filter.startTime)}`]),
111
+ ...(filter.finishTime === undefined ? [] : [sql `"timestamp" < ${DateTime.toEpochMillis(filter.finishTime)}`])
112
+ ];
113
+ // Inside a stream versions follow positions, and ordering by them keeps to the
114
+ // stream's index. With `LIMIT` the planner would otherwise walk the primary key,
115
+ // through every later event of the log, looking for a stream with nothing new.
116
+ // `position` alone would mean the converted column above, which has no index.
117
+ const order = oneStream ? sql `version` : sql `${events}.position`;
118
+ return sql `SELECT ${columns} FROM ${events} WHERE ${sql.and(clauses)} ORDER BY ${order} LIMIT ${limit}`.pipe(Effect.flatMap(decodeRows), Effect.mapError(storeError("read")));
119
+ };
120
+ const head = sql `SELECT position::float8 AS position FROM ${counter}`.pipe(Effect.map(([row]) => row?.position ?? 0), Effect.mapError(storeError("head")));
121
+ return yield* EventStore.make({ append, readBatch, head });
122
+ });
123
+ export const layer = (options) => Layer.effect(EventStore.EventStore, make(options));
124
+ /** Keeps checkpoints in the same database, for view models that are durable too. */
125
+ export const layerCheckpoints = (options) => Layer.effect(Checkpoints, Effect.gen(function* () {
126
+ const sql = yield* SqlClient.SqlClient;
127
+ const table = sql(options?.checkpointsTable ?? "cqrs_checkpoints");
128
+ yield* sql `
129
+ CREATE TABLE IF NOT EXISTS ${table} (
130
+ view_model TEXT PRIMARY KEY,
131
+ position BIGINT NOT NULL
132
+ )
133
+ `.pipe(Effect.mapError(storeError("initialize")));
134
+ return Checkpoints.of({
135
+ get: (viewModel) => sql `
136
+ SELECT position::float8 AS position FROM ${table} WHERE view_model = ${viewModel}
137
+ `.pipe(Effect.map(([row]) => row?.position ?? 0), Effect.mapError(storeError("checkpoint"))),
138
+ set: (viewModel, position) => sql `
139
+ INSERT INTO ${table} (view_model, position) VALUES (${viewModel}, ${position})
140
+ ON CONFLICT (view_model) DO UPDATE SET position = excluded.position
141
+ `.pipe(Effect.asVoid, Effect.mapError(storeError("checkpoint")))
142
+ });
143
+ }));
144
+ //# sourceMappingURL=PostgresEventStore.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"PostgresEventStore.js","sourceRoot":"","sources":["../src/PostgresEventStore.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,25 @@
1
+ /**
2
+ * Durable storage on SQLite, through any `effect/sql` SQLite driver.
3
+ *
4
+ * The tables are created on startup. The schema relies on SQLite's single writer
5
+ * for positions to follow commit order, so it is not portable to other databases.
6
+ */
7
+ import { Effect, Layer } from "effect";
8
+ import { SqlClient } from "effect/sql";
9
+ import { Checkpoints } from "./Checkpoints.ts";
10
+ import * as EventStore from "./EventStore.ts";
11
+ export interface Options {
12
+ readonly eventsTable?: string;
13
+ readonly checkpointsTable?: string;
14
+ }
15
+ export declare const make: (options?: Options | undefined) => Effect.Effect<{
16
+ append(input: EventStore.AppendInput): Effect.Effect<ReadonlyArray<EventStore.StoredEvent>, EventStore.ConcurrencyConflict | EventStore.EventStoreError>;
17
+ read(filter?: EventStore.EventFilter): import("effect/Stream").Stream<EventStore.StoredEvent, EventStore.EventStoreError>;
18
+ subscribe(filter?: EventStore.EventFilter): import("effect/Stream").Stream<EventStore.StoredEvent, EventStore.EventStoreError>;
19
+ readonly head: Effect.Effect<number, EventStore.EventStoreError>;
20
+ readonly changes: import("effect/Stream").Stream<number>;
21
+ }, EventStore.EventStoreError, SqlClient.SqlClient>;
22
+ export declare const layer: (options?: Options) => Layer.Layer<EventStore.EventStore, EventStore.EventStoreError, SqlClient.SqlClient>;
23
+ /** Keeps checkpoints in the same database, for view models that are durable too. */
24
+ export declare const layerCheckpoints: (options?: Options) => Layer.Layer<Checkpoints, EventStore.EventStoreError, SqlClient.SqlClient>;
25
+ //# sourceMappingURL=SqliteEventStore.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"SqliteEventStore.d.ts","sourceRoot":"","sources":["../src/SqliteEventStore.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AACH,OAAO,EAAmB,MAAM,EAAE,KAAK,EAAU,MAAM,QAAQ,CAAA;AAC/D,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;AAaD,eAAO,MAAM,IAAI;;;;;;mDAsGf,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,CA8B1H,CAAA"}
@@ -0,0 +1,123 @@
1
+ /**
2
+ * Durable storage on SQLite, through any `effect/sql` SQLite driver.
3
+ *
4
+ * The tables are created on startup. The schema relies on SQLite's single writer
5
+ * for positions to follow commit order, so it is not portable to other databases.
6
+ */
7
+ import { Clock, DateTime, Effect, Layer, Schema } from "effect";
8
+ import { SqlClient } from "effect/sql";
9
+ import { Checkpoints } from "./Checkpoints.js";
10
+ import * as EventStore from "./EventStore.js";
11
+ const EventRow = Schema.Struct({
12
+ ...EventStore.StoredEvent.fields,
13
+ payload: Schema.fromJsonString(Schema.Json)
14
+ });
15
+ const decodeRows = Schema.decodeUnknownEffect(Schema.Array(EventRow));
16
+ const storeError = (operation) => (cause) => new EventStore.EventStoreError({ operation, cause });
17
+ const isUniqueViolation = (error) => error.reason._tag === "UniqueViolation";
18
+ export const make = Effect.fnUntraced(function* (options) {
19
+ const sql = yield* SqlClient.SqlClient;
20
+ const events = sql(options?.eventsTable ?? "cqrs_events");
21
+ const indexPrefix = options?.eventsTable ?? "cqrs_events";
22
+ yield* sql `
23
+ CREATE TABLE IF NOT EXISTS ${events} (
24
+ position INTEGER PRIMARY KEY AUTOINCREMENT,
25
+ aggregate_name TEXT NOT NULL,
26
+ aggregate_id TEXT NOT NULL,
27
+ version INTEGER NOT NULL,
28
+ type TEXT NOT NULL,
29
+ payload TEXT NOT NULL,
30
+ timestamp INTEGER NOT NULL,
31
+ UNIQUE (aggregate_name, aggregate_id, version)
32
+ )
33
+ `.pipe(Effect.mapError(storeError("initialize")));
34
+ yield* sql `
35
+ CREATE INDEX IF NOT EXISTS ${sql(`${indexPrefix}_type_idx`)} ON ${events} (type, position)
36
+ `.pipe(Effect.mapError(storeError("initialize")));
37
+ const columns = sql.literal(`
38
+ position,
39
+ aggregate_name AS "aggregateName",
40
+ aggregate_id AS "aggregateId",
41
+ version,
42
+ type,
43
+ payload,
44
+ timestamp
45
+ `);
46
+ const append = (input) => {
47
+ const conflict = new EventStore.ConcurrencyConflict({
48
+ aggregateName: input.aggregateName,
49
+ aggregateId: input.aggregateId,
50
+ expectedVersion: input.expectedVersion
51
+ });
52
+ return Effect.gen(function* () {
53
+ const [current] = yield* sql `
54
+ SELECT COALESCE(MAX(version), 0) AS version FROM ${events}
55
+ WHERE aggregate_name = ${input.aggregateName} AND aggregate_id = ${input.aggregateId}
56
+ `;
57
+ if (current?.version !== input.expectedVersion) {
58
+ return yield* conflict;
59
+ }
60
+ const timestamp = yield* Clock.currentTimeMillis;
61
+ return yield* Effect.forEach(input.events, (event, index) => sql `
62
+ INSERT INTO ${events} (aggregate_name, aggregate_id, version, type, payload, timestamp)
63
+ VALUES (
64
+ ${input.aggregateName},
65
+ ${input.aggregateId},
66
+ ${input.expectedVersion + index + 1},
67
+ ${event.type},
68
+ ${JSON.stringify(event.payload)},
69
+ ${timestamp}
70
+ )
71
+ RETURNING position
72
+ `.pipe(Effect.map(([row]) => ({
73
+ position: row.position,
74
+ aggregateName: input.aggregateName,
75
+ aggregateId: input.aggregateId,
76
+ version: input.expectedVersion + index + 1,
77
+ type: event.type,
78
+ payload: event.payload,
79
+ timestamp: DateTime.makeUnsafe(timestamp)
80
+ }))));
81
+ }).pipe(sql.withTransaction,
82
+ // The version check can't see a writer racing it on a driver that opens
83
+ // deferred transactions, but the UNIQUE constraint can.
84
+ Effect.catchTag("SqlError", (error) => Effect.fail(isUniqueViolation(error) ? conflict : storeError("append")(error))));
85
+ };
86
+ const readBatch = (filter, afterPosition, limit) => {
87
+ const oneStream = EventStore.isOneStream(filter);
88
+ const clauses = [
89
+ sql `position > ${afterPosition}`,
90
+ ...(oneStream ? [sql `version > ${filter.afterVersion ?? 0}`] : []),
91
+ ...(filter.aggregateNames === undefined ? [] : [sql.in("aggregate_name", filter.aggregateNames)]),
92
+ ...(filter.aggregateIds === undefined ? [] : [sql.in("aggregate_id", filter.aggregateIds)]),
93
+ ...(filter.eventTypes === undefined ? [] : [sql.in("type", filter.eventTypes)]),
94
+ ...(filter.startTime === undefined ? [] : [sql `timestamp >= ${DateTime.toEpochMillis(filter.startTime)}`]),
95
+ ...(filter.finishTime === undefined ? [] : [sql `timestamp < ${DateTime.toEpochMillis(filter.finishTime)}`])
96
+ ];
97
+ // Inside a stream versions follow positions, and only versions are indexed.
98
+ const order = oneStream ? sql `version` : sql `position`;
99
+ return sql `SELECT ${columns} FROM ${events} WHERE ${sql.and(clauses)} ORDER BY ${order} LIMIT ${limit}`.pipe(Effect.flatMap(decodeRows), Effect.mapError(storeError("read")));
100
+ };
101
+ const head = sql `SELECT COALESCE(MAX(position), 0) AS position FROM ${events}`.pipe(Effect.map(([row]) => row?.position ?? 0), Effect.mapError(storeError("head")));
102
+ return yield* EventStore.make({ append, readBatch, head });
103
+ });
104
+ export const layer = (options) => Layer.effect(EventStore.EventStore, make(options));
105
+ /** Keeps checkpoints in the same database, for view models that are durable too. */
106
+ export const layerCheckpoints = (options) => Layer.effect(Checkpoints, Effect.gen(function* () {
107
+ const sql = yield* SqlClient.SqlClient;
108
+ const table = sql(options?.checkpointsTable ?? "cqrs_checkpoints");
109
+ yield* sql `
110
+ CREATE TABLE IF NOT EXISTS ${table} (
111
+ view_model TEXT PRIMARY KEY,
112
+ position INTEGER NOT NULL
113
+ )
114
+ `.pipe(Effect.mapError(storeError("initialize")));
115
+ return Checkpoints.of({
116
+ get: (viewModel) => sql `SELECT position FROM ${table} WHERE view_model = ${viewModel}`.pipe(Effect.map(([row]) => row?.position ?? 0), Effect.mapError(storeError("checkpoint"))),
117
+ set: (viewModel, position) => sql `
118
+ INSERT INTO ${table} (view_model, position) VALUES (${viewModel}, ${position})
119
+ ON CONFLICT (view_model) DO UPDATE SET position = excluded.position
120
+ `.pipe(Effect.asVoid, Effect.mapError(storeError("checkpoint")))
121
+ });
122
+ }));
123
+ //# sourceMappingURL=SqliteEventStore.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"SqliteEventStore.js","sourceRoot":"","sources":["../src/SqliteEventStore.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,90 @@
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, Effect, Layer, Option, Schema } from "effect";
11
+ import type { DateTime } from "effect";
12
+ import type * as Aggregate from "./Aggregate.ts";
13
+ import { Checkpoints } from "./Checkpoints.ts";
14
+ import { EventStore, type EventStoreError } from "./EventStore.ts";
15
+ declare const UnknownViewModel_base: Schema.Class<UnknownViewModel, Schema.TaggedStruct<"UnknownViewModel", {
16
+ readonly name: Schema.String;
17
+ }>, import("effect/Cause").YieldableError>;
18
+ export declare class UnknownViewModel extends UnknownViewModel_base {
19
+ }
20
+ declare const ViewModelError_base: Schema.Class<ViewModelError, Schema.TaggedStruct<"ViewModelError", {
21
+ readonly viewModel: Schema.String;
22
+ /** The event being processed, `0` when the failure happened outside any event. */
23
+ readonly position: Schema.Int;
24
+ readonly cause: Schema.Defect;
25
+ }>, import("effect/Cause").YieldableError>;
26
+ export declare class ViewModelError extends ViewModelError_base {
27
+ }
28
+ export type Handlers<Event extends Aggregate.Tagged> = {
29
+ readonly [Tag in Event["_tag"]]?: (event: Extract<Event, {
30
+ readonly _tag: Tag;
31
+ }>, meta: Aggregate.EventMeta) => Effect.Effect<unknown, any, any>;
32
+ };
33
+ type HandlerError<H> = {
34
+ readonly [K in keyof H]-?: NonNullable<H[K]> extends (...args: any) => Effect.Effect<any, infer E, any> ? E : never;
35
+ }[keyof H];
36
+ type HandlerContext<H> = {
37
+ readonly [K in keyof H]-?: NonNullable<H[K]> extends (...args: any) => Effect.Effect<any, any, infer R> ? R : never;
38
+ }[keyof H];
39
+ export interface ViewModel<out Name extends string, out E, out R> {
40
+ readonly name: Name;
41
+ readonly aggregates: ReadonlyArray<Aggregate.Any>;
42
+ readonly handlers: Record<string, (event: never, meta: Aggregate.EventMeta) => Effect.Effect<unknown, E, R>>;
43
+ /** Clears everything the view model stored, before a replay rebuilds it. */
44
+ readonly dispose: Effect.Effect<void, E, R>;
45
+ }
46
+ export type Any = ViewModel<string, any, any>;
47
+ export type Context<V extends Any> = V extends ViewModel<any, any, infer R> ? R : never;
48
+ /** Handlers are keyed by event tag, and events without one are skipped. */
49
+ export declare const make: <const Name extends string, const Aggregates extends ReadonlyArray<Aggregate.Any>, const H extends Handlers<Aggregate.Event<Aggregates[number]>>, E = never, R = never>(options: {
50
+ readonly name: Name;
51
+ readonly aggregates: Aggregates;
52
+ readonly handlers: H;
53
+ readonly dispose: Effect.Effect<void, E, R>;
54
+ }) => ViewModel<Name, E | HandlerError<H>, R | HandlerContext<H>>;
55
+ export interface ReplayOptions {
56
+ readonly viewModels: ReadonlyArray<string>;
57
+ readonly startTime?: DateTime.Utc | undefined;
58
+ readonly finishTime?: DateTime.Utc | undefined;
59
+ }
60
+ export interface ReplayResult {
61
+ readonly viewModels: ReadonlyArray<string>;
62
+ readonly eventCount: number;
63
+ }
64
+ export interface Status {
65
+ readonly name: string;
66
+ readonly position: number;
67
+ /** The failure the view model is retrying, while it is stalled on one. */
68
+ readonly lastError: Option.Option<ViewModelError>;
69
+ }
70
+ declare const ViewModels_base: Context.ServiceClass<ViewModels, "effect-cqrs/ViewModels", {
71
+ /**
72
+ * Disposes the named view models and rebuilds them from their events. A time
73
+ * window limits which events are replayed: the view model then resumes from
74
+ * the present and events outside the window stay out of it.
75
+ */
76
+ replay(options: ReplayOptions): Effect.Effect<ReplayResult, UnknownViewModel | ViewModelError>;
77
+ /**
78
+ * Waits until every view model has processed `position`, defaulting to the
79
+ * latest event. Pair it with `CommandResult.position` to read your own writes.
80
+ * A view model stalled on a failure doesn't catch up, so consider a timeout.
81
+ */
82
+ caughtUp(position?: number): Effect.Effect<void, EventStoreError>;
83
+ readonly status: Effect.Effect<ReadonlyArray<Status>>;
84
+ }>;
85
+ export declare class ViewModels extends ViewModels_base {
86
+ }
87
+ /** Starts the view models in the background for as long as the layer lives. */
88
+ export declare const layer: <const VMs extends ReadonlyArray<Any>>(viewModels: VMs) => Layer.Layer<ViewModels, EventStoreError, EventStore | Checkpoints | Context<VMs[number]>>;
89
+ export {};
90
+ //# sourceMappingURL=ViewModel.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ViewModel.d.ts","sourceRoot":"","sources":["../src/ViewModel.ts"],"names":[],"mappings":"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"}