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/Aggregate.ts
CHANGED
|
@@ -5,7 +5,8 @@
|
|
|
5
5
|
* An aggregate is plain data. {@link AggregateService} gives it a store to run
|
|
6
6
|
* against, and `Fixture` runs it in tests.
|
|
7
7
|
*/
|
|
8
|
-
import type
|
|
8
|
+
import { type DateTime, type Effect, Predicate, Schema } from "effect"
|
|
9
|
+
import * as EventVersions from "./EventVersions.ts"
|
|
9
10
|
|
|
10
11
|
export interface Tagged {
|
|
11
12
|
readonly _tag: string
|
|
@@ -29,6 +30,12 @@ export interface EventMeta {
|
|
|
29
30
|
readonly version: number
|
|
30
31
|
readonly position: number
|
|
31
32
|
readonly timestamp: DateTime.Utc
|
|
33
|
+
/** Shared by everything that follows from one original request, see `Causation`. */
|
|
34
|
+
readonly correlationId: string | null
|
|
35
|
+
/** What directly caused the command that recorded the event. */
|
|
36
|
+
readonly causationId: string | null
|
|
37
|
+
/** What the command carried beside its payload, such as the software version, see `Metadata`. */
|
|
38
|
+
readonly metadata: Schema.JsonObject
|
|
32
39
|
}
|
|
33
40
|
|
|
34
41
|
export interface CommandContext {
|
|
@@ -62,17 +69,27 @@ export interface AnyCommand {
|
|
|
62
69
|
readonly handler: CommandHandler<any, any, any, any, any>
|
|
63
70
|
}
|
|
64
71
|
|
|
72
|
+
/**
|
|
73
|
+
* The older shapes of some events, keyed by tag. Each history has to end in the
|
|
74
|
+
* event's current shape.
|
|
75
|
+
*/
|
|
76
|
+
export type Versions<Event extends Tagged> = {
|
|
77
|
+
readonly [Tag in Event["_tag"]]?: EventVersions.Any<Extract<Event, { readonly _tag: Tag }>>
|
|
78
|
+
}
|
|
79
|
+
|
|
65
80
|
export interface Aggregate<
|
|
66
81
|
Name extends string,
|
|
67
82
|
State,
|
|
68
83
|
Events extends EventSchema<any>,
|
|
69
|
-
Commands extends Record<string, AnyCommand
|
|
84
|
+
Commands extends Record<string, AnyCommand>,
|
|
85
|
+
V extends Versions<Events["Type"]> = {}
|
|
70
86
|
> {
|
|
71
87
|
readonly name: Name
|
|
72
88
|
readonly events: Events
|
|
73
89
|
readonly initial: State
|
|
74
90
|
readonly projection: Projection<State, Events["Type"]>
|
|
75
91
|
readonly commands: Commands
|
|
92
|
+
readonly versions: V
|
|
76
93
|
/** Returns a new aggregate that also accepts this command. */
|
|
77
94
|
command<const CommandName extends string, Payload extends PayloadSchema, E = never, R = never>(
|
|
78
95
|
name: CommandName,
|
|
@@ -80,10 +97,10 @@ export interface Aggregate<
|
|
|
80
97
|
readonly payload: Payload
|
|
81
98
|
readonly handler: CommandHandler<State, Payload["Type"], Events["Type"], E, R>
|
|
82
99
|
}
|
|
83
|
-
): Aggregate<Name, State, Events, Commands & Record<CommandName, Command<Payload, State, Events["Type"], E, R
|
|
100
|
+
): Aggregate<Name, State, Events, Commands & Record<CommandName, Command<Payload, State, Events["Type"], E, R>>, V>
|
|
84
101
|
}
|
|
85
102
|
|
|
86
|
-
export type Any = Aggregate<string, any, EventSchema<any>, Record<string, AnyCommand
|
|
103
|
+
export type Any = Aggregate<string, any, EventSchema<any>, Record<string, AnyCommand>, any>
|
|
87
104
|
|
|
88
105
|
export type Name<A extends Any> = A["name"]
|
|
89
106
|
export type State<A extends Any> = A["initial"]
|
|
@@ -100,10 +117,14 @@ const build = <
|
|
|
100
117
|
Name extends string,
|
|
101
118
|
State,
|
|
102
119
|
Events extends EventSchema<any>,
|
|
103
|
-
Commands extends Record<string, AnyCommand
|
|
120
|
+
Commands extends Record<string, AnyCommand>,
|
|
121
|
+
V extends Versions<Events["Type"]>
|
|
104
122
|
>(
|
|
105
|
-
definition: Pick<
|
|
106
|
-
|
|
123
|
+
definition: Pick<
|
|
124
|
+
Aggregate<Name, State, Events, Commands, V>,
|
|
125
|
+
"name" | "events" | "initial" | "projection" | "commands" | "versions"
|
|
126
|
+
>
|
|
127
|
+
): Aggregate<Name, State, Events, Commands, V> => ({
|
|
107
128
|
...definition,
|
|
108
129
|
command: (name, options) =>
|
|
109
130
|
build({
|
|
@@ -117,12 +138,15 @@ const build = <
|
|
|
117
138
|
* projection's return values are checked against it rather than widened.
|
|
118
139
|
*/
|
|
119
140
|
export const make = <State>() =>
|
|
120
|
-
<const Name extends string, Events extends EventSchema<any
|
|
141
|
+
<const Name extends string, Events extends EventSchema<any>, const V extends Versions<Events["Type"]> = {}>(options: {
|
|
121
142
|
readonly name: Name
|
|
122
143
|
readonly events: Events
|
|
123
144
|
readonly initial: State
|
|
124
145
|
readonly projection: Projection<State, Events["Type"]>
|
|
125
|
-
|
|
146
|
+
/** Older shapes of events already in the store, see `EventVersions`. */
|
|
147
|
+
readonly versions?: V | undefined
|
|
148
|
+
}): Aggregate<Name, State, Events, {}, V> =>
|
|
149
|
+
build({ ...options, versions: options.versions ?? ({} as V), commands: {} })
|
|
126
150
|
|
|
127
151
|
/** Folds one event into the state. */
|
|
128
152
|
export const evolve = <A extends Any>(aggregate: A, state: State<A>, event: Event<A>, meta: EventMeta): State<A> =>
|
|
@@ -131,3 +155,51 @@ export const evolve = <A extends Any>(aggregate: A, state: State<A>, event: Even
|
|
|
131
155
|
event,
|
|
132
156
|
meta
|
|
133
157
|
)
|
|
158
|
+
|
|
159
|
+
/** The schema version new events with this tag are written with. */
|
|
160
|
+
export const schemaVersion = (aggregate: Any, tag: string): number =>
|
|
161
|
+
EventVersions.current((aggregate.versions as Record<string, EventVersions.Any | undefined>)[tag])
|
|
162
|
+
|
|
163
|
+
/**
|
|
164
|
+
* An event as it was stored in the past, in an older version of its schema.
|
|
165
|
+
* Construct one directly to replay JSON taken from a database.
|
|
166
|
+
*/
|
|
167
|
+
export class Historical {
|
|
168
|
+
readonly type: string
|
|
169
|
+
readonly schemaVersion: number
|
|
170
|
+
readonly payload: Schema.Json
|
|
171
|
+
constructor(options: { readonly type: string; readonly schemaVersion: number; readonly payload: Schema.Json }) {
|
|
172
|
+
this.type = options.type
|
|
173
|
+
this.schemaVersion = options.schemaVersion
|
|
174
|
+
this.payload = options.payload
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
/**
|
|
179
|
+
* Encodes an event in an older version of its schema, as code from that time
|
|
180
|
+
* would have stored it. The value is checked against that version, so a test
|
|
181
|
+
* can't seed a shape that never existed.
|
|
182
|
+
*/
|
|
183
|
+
export const historical = <
|
|
184
|
+
A extends Any,
|
|
185
|
+
const Tag extends keyof A["versions"] & string,
|
|
186
|
+
const Version extends number
|
|
187
|
+
>(
|
|
188
|
+
aggregate: A,
|
|
189
|
+
tag: Tag,
|
|
190
|
+
version: Version,
|
|
191
|
+
value: EventVersions.SchemaAt<NonNullable<A["versions"][Tag]>, Version>["Type"]
|
|
192
|
+
): Historical => {
|
|
193
|
+
const versions = (aggregate.versions as Record<string, EventVersions.Any | undefined>)[tag]
|
|
194
|
+
const schema = versions?.schemas[version - 1]
|
|
195
|
+
if (schema === undefined) {
|
|
196
|
+
throw new Error(`${aggregate.name} has no version ${version} of ${tag}`)
|
|
197
|
+
}
|
|
198
|
+
const payload = Schema.encodeSync(Schema.toCodecJson(schema))(value) as Schema.Json
|
|
199
|
+
return new Historical({
|
|
200
|
+
type: tag,
|
|
201
|
+
schemaVersion: version,
|
|
202
|
+
// Events were always stored with their tag, whatever the schema of the time said.
|
|
203
|
+
payload: Predicate.isObject(payload) ? { ...(payload as object), _tag: tag } as Schema.Json : payload
|
|
204
|
+
})
|
|
205
|
+
}
|