effect-cqrs 0.1.0 → 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/README.md +34 -6
- package/dist/Aggregate.d.ts +47 -6
- package/dist/Aggregate.d.ts.map +1 -1
- package/dist/Aggregate.js +45 -1
- package/dist/Aggregate.js.map +1 -1
- package/dist/AggregateService.d.ts +83 -11
- package/dist/AggregateService.d.ts.map +1 -1
- package/dist/AggregateService.js +261 -35
- package/dist/AggregateService.js.map +1 -1
- 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/EventStore.d.ts +22 -0
- package/dist/EventStore.d.ts.map +1 -1
- package/dist/EventStore.js +30 -10
- package/dist/EventStore.js.map +1 -1
- 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 +40 -5
- package/dist/Fixture.d.ts.map +1 -1
- package/dist/Fixture.js +44 -3
- package/dist/Fixture.js.map +1 -1
- 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.map +1 -1
- package/dist/PostgresEventStore.js +22 -13
- package/dist/PostgresEventStore.js.map +1 -1
- 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.map +1 -1
- package/dist/SqliteEventStore.js +32 -13
- package/dist/SqliteEventStore.js.map +1 -1
- package/dist/ViewModel.d.ts.map +1 -1
- package/dist/ViewModel.js +3 -1
- package/dist/ViewModel.js.map +1 -1
- package/dist/index.d.ts +4 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +4 -0
- package/dist/index.js.map +1 -1
- package/package.json +5 -3
- package/src/Aggregate.ts +81 -9
- package/src/AggregateService.ts +418 -55
- package/src/Causation.ts +48 -0
- package/src/EventStore.ts +47 -10
- package/src/EventVersions.ts +104 -0
- package/src/Fixture.ts +92 -7
- package/src/Metadata.ts +52 -0
- package/src/PostgresEventStore.ts +24 -13
- package/src/Saga.ts +176 -0
- package/src/SqliteEventStore.ts +43 -13
- package/src/ViewModel.ts +3 -1
- package/src/index.ts +4 -0
package/src/SqliteEventStore.ts
CHANGED
|
@@ -16,7 +16,8 @@ export interface Options {
|
|
|
16
16
|
|
|
17
17
|
const EventRow = Schema.Struct({
|
|
18
18
|
...EventStore.StoredEvent.fields,
|
|
19
|
-
payload: Schema.fromJsonString(Schema.Json)
|
|
19
|
+
payload: Schema.fromJsonString(Schema.Json),
|
|
20
|
+
metadata: Schema.fromJsonString(Schema.JsonObject)
|
|
20
21
|
})
|
|
21
22
|
|
|
22
23
|
const decodeRows = Schema.decodeUnknownEffect(Schema.Array(EventRow))
|
|
@@ -25,6 +26,13 @@ const storeError = (operation: string) => (cause: unknown) => new EventStore.Eve
|
|
|
25
26
|
|
|
26
27
|
const isUniqueViolation = (error: SqlError.SqlError) => error.reason._tag === "UniqueViolation"
|
|
27
28
|
|
|
29
|
+
const addedColumns: ReadonlyArray<readonly [string, string]> = [
|
|
30
|
+
["schema_version", "INTEGER NOT NULL DEFAULT 1"],
|
|
31
|
+
["correlation_id", "TEXT"],
|
|
32
|
+
["causation_id", "TEXT"],
|
|
33
|
+
["metadata", "TEXT NOT NULL DEFAULT '{}'"]
|
|
34
|
+
]
|
|
35
|
+
|
|
28
36
|
export const make = Effect.fnUntraced(function*(options?: Options) {
|
|
29
37
|
const sql = yield* SqlClient.SqlClient
|
|
30
38
|
const events = sql(options?.eventsTable ?? "cqrs_events")
|
|
@@ -37,11 +45,29 @@ export const make = Effect.fnUntraced(function*(options?: Options) {
|
|
|
37
45
|
aggregate_id TEXT NOT NULL,
|
|
38
46
|
version INTEGER NOT NULL,
|
|
39
47
|
type TEXT NOT NULL,
|
|
48
|
+
schema_version INTEGER NOT NULL DEFAULT 1,
|
|
40
49
|
payload TEXT NOT NULL,
|
|
41
50
|
timestamp INTEGER NOT NULL,
|
|
51
|
+
correlation_id TEXT,
|
|
52
|
+
causation_id TEXT,
|
|
53
|
+
metadata TEXT NOT NULL DEFAULT '{}',
|
|
42
54
|
UNIQUE (aggregate_name, aggregate_id, version)
|
|
43
55
|
)
|
|
44
56
|
`.pipe(Effect.mapError(storeError("initialize")))
|
|
57
|
+
// Tables created before these columns existed get them here. SQLite has no
|
|
58
|
+
// `ADD COLUMN IF NOT EXISTS`, so the existing columns are listed first.
|
|
59
|
+
const existing = yield* sql<{ readonly name: string }>`PRAGMA table_info(${events})`.pipe(
|
|
60
|
+
Effect.map((rows) => new Set(rows.map(({ name }) => name))),
|
|
61
|
+
Effect.mapError(storeError("initialize"))
|
|
62
|
+
)
|
|
63
|
+
yield* Effect.forEach(
|
|
64
|
+
addedColumns.filter(([name]) => !existing.has(name)),
|
|
65
|
+
([name, definition]) =>
|
|
66
|
+
sql`ALTER TABLE ${events} ADD COLUMN ${sql(name)} ${sql.literal(definition)}`.pipe(
|
|
67
|
+
Effect.mapError(storeError("initialize"))
|
|
68
|
+
),
|
|
69
|
+
{ discard: true }
|
|
70
|
+
)
|
|
45
71
|
yield* sql`
|
|
46
72
|
CREATE INDEX IF NOT EXISTS ${sql(`${indexPrefix}_type_idx`)} ON ${events} (type, position)
|
|
47
73
|
`.pipe(Effect.mapError(storeError("initialize")))
|
|
@@ -52,8 +78,12 @@ export const make = Effect.fnUntraced(function*(options?: Options) {
|
|
|
52
78
|
aggregate_id AS "aggregateId",
|
|
53
79
|
version,
|
|
54
80
|
type,
|
|
81
|
+
schema_version AS "schemaVersion",
|
|
55
82
|
payload,
|
|
56
|
-
timestamp
|
|
83
|
+
timestamp,
|
|
84
|
+
correlation_id AS "correlationId",
|
|
85
|
+
causation_id AS "causationId",
|
|
86
|
+
metadata
|
|
57
87
|
`)
|
|
58
88
|
|
|
59
89
|
const append = (input: EventStore.AppendInput) => {
|
|
@@ -71,28 +101,28 @@ export const make = Effect.fnUntraced(function*(options?: Options) {
|
|
|
71
101
|
return yield* conflict
|
|
72
102
|
}
|
|
73
103
|
const timestamp = yield* Clock.currentTimeMillis
|
|
104
|
+
const metadata = JSON.stringify(input.metadata ?? {})
|
|
74
105
|
return yield* Effect.forEach(input.events, (event, index) =>
|
|
75
106
|
sql<{ readonly position: number }>`
|
|
76
|
-
INSERT INTO ${events} (
|
|
107
|
+
INSERT INTO ${events} (
|
|
108
|
+
aggregate_name, aggregate_id, version, type, schema_version, payload, timestamp, correlation_id, causation_id,
|
|
109
|
+
metadata
|
|
110
|
+
)
|
|
77
111
|
VALUES (
|
|
78
112
|
${input.aggregateName},
|
|
79
113
|
${input.aggregateId},
|
|
80
114
|
${input.expectedVersion + index + 1},
|
|
81
115
|
${event.type},
|
|
116
|
+
${event.schemaVersion ?? 1},
|
|
82
117
|
${JSON.stringify(event.payload)},
|
|
83
|
-
${timestamp}
|
|
118
|
+
${timestamp},
|
|
119
|
+
${input.correlationId ?? null},
|
|
120
|
+
${input.causationId ?? null},
|
|
121
|
+
${metadata}
|
|
84
122
|
)
|
|
85
123
|
RETURNING position
|
|
86
124
|
`.pipe(
|
|
87
|
-
Effect.map(([row])
|
|
88
|
-
position: row!.position,
|
|
89
|
-
aggregateName: input.aggregateName,
|
|
90
|
-
aggregateId: input.aggregateId,
|
|
91
|
-
version: input.expectedVersion + index + 1,
|
|
92
|
-
type: event.type,
|
|
93
|
-
payload: event.payload,
|
|
94
|
-
timestamp: DateTime.makeUnsafe(timestamp)
|
|
95
|
-
}))
|
|
125
|
+
Effect.map(([row]) => EventStore.toStored(input, event, index, row!.position, DateTime.makeUnsafe(timestamp)))
|
|
96
126
|
))
|
|
97
127
|
}).pipe(
|
|
98
128
|
sql.withTransaction,
|
package/src/ViewModel.ts
CHANGED
|
@@ -23,6 +23,7 @@ import {
|
|
|
23
23
|
import type { DateTime } from "effect"
|
|
24
24
|
import type * as Aggregate from "./Aggregate.ts"
|
|
25
25
|
import { makeEventDecoder } from "./AggregateService.ts"
|
|
26
|
+
import * as Causation from "./Causation.ts"
|
|
26
27
|
import { Checkpoints } from "./Checkpoints.ts"
|
|
27
28
|
import { type EventFilter, EventStore, type EventStoreError, type StoredEvent } from "./EventStore.ts"
|
|
28
29
|
|
|
@@ -188,7 +189,8 @@ const makeRunner = Effect.fnUntraced(function*<V extends Any>(viewModel: V, tail
|
|
|
188
189
|
const handle = (decodeEvent: (stored: StoredEvent) => Effect.Effect<unknown, unknown>) => (stored: StoredEvent) =>
|
|
189
190
|
Effect.gen(function*() {
|
|
190
191
|
const event = yield* decodeEvent(stored)
|
|
191
|
-
|
|
192
|
+
// Commands the handler runs record this event as their cause.
|
|
193
|
+
yield* viewModel.handlers[stored.type]!(event as never, stored).pipe(Causation.causedBy(stored))
|
|
192
194
|
}).pipe(
|
|
193
195
|
Effect.mapError(fail(stored.position)),
|
|
194
196
|
Effect.catchDefect((defect) => Effect.fail(fail(stored.position)(defect)))
|
package/src/index.ts
CHANGED
|
@@ -1,8 +1,12 @@
|
|
|
1
1
|
export * as Aggregate from "./Aggregate.ts"
|
|
2
2
|
export * as AggregateService from "./AggregateService.ts"
|
|
3
|
+
export * as Causation from "./Causation.ts"
|
|
3
4
|
export * as Checkpoints from "./Checkpoints.ts"
|
|
4
5
|
export * as EventStore from "./EventStore.ts"
|
|
6
|
+
export * as EventVersions from "./EventVersions.ts"
|
|
5
7
|
export * as Fixture from "./Fixture.ts"
|
|
8
|
+
export * as Metadata from "./Metadata.ts"
|
|
6
9
|
export * as PostgresEventStore from "./PostgresEventStore.ts"
|
|
10
|
+
export * as Saga from "./Saga.ts"
|
|
7
11
|
export * as SqliteEventStore from "./SqliteEventStore.ts"
|
|
8
12
|
export * as ViewModel from "./ViewModel.ts"
|