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/dist/EventStore.js
CHANGED
|
@@ -24,8 +24,36 @@ export const StoredEvent = Schema.Struct({
|
|
|
24
24
|
aggregateId: Schema.String,
|
|
25
25
|
version: Schema.Int,
|
|
26
26
|
type: Schema.String,
|
|
27
|
+
/** The version of the event's schema the payload was written with. Events from before versions existed are `1`. */
|
|
28
|
+
schemaVersion: Schema.Int,
|
|
27
29
|
payload: Schema.Json,
|
|
28
|
-
timestamp: Schema.DateTimeUtcFromMillis
|
|
30
|
+
timestamp: Schema.DateTimeUtcFromMillis,
|
|
31
|
+
/** Shared by everything that follows from one original request. */
|
|
32
|
+
correlationId: Schema.NullOr(Schema.String),
|
|
33
|
+
/** What directly caused the command that recorded the event: a request, a command or another event. */
|
|
34
|
+
causationId: Schema.NullOr(Schema.String),
|
|
35
|
+
/** What the command carried beside its payload, see `Metadata`. Events from before metadata existed have `{}`. */
|
|
36
|
+
metadata: Schema.JsonObject
|
|
37
|
+
});
|
|
38
|
+
/**
|
|
39
|
+
* Identifies an event wherever it is stored, as the `causationId` of what it causes:
|
|
40
|
+
* `account/alice@4`. Name and id are URI-encoded, so a `/` or `@` inside one can't
|
|
41
|
+
* make two events look alike.
|
|
42
|
+
*/
|
|
43
|
+
export const eventId = (event) => `${encodeURIComponent(event.aggregateName)}/${encodeURIComponent(event.aggregateId)}@${event.version}`;
|
|
44
|
+
/** The columns every adapter stores for a new event, with the defaults filled in. */
|
|
45
|
+
export const toStored = (input, event, index, position, timestamp) => ({
|
|
46
|
+
position,
|
|
47
|
+
aggregateName: input.aggregateName,
|
|
48
|
+
aggregateId: input.aggregateId,
|
|
49
|
+
version: input.expectedVersion + index + 1,
|
|
50
|
+
type: event.type,
|
|
51
|
+
schemaVersion: event.schemaVersion ?? 1,
|
|
52
|
+
payload: event.payload,
|
|
53
|
+
timestamp,
|
|
54
|
+
correlationId: input.correlationId ?? null,
|
|
55
|
+
causationId: input.causationId ?? null,
|
|
56
|
+
metadata: input.metadata ?? {}
|
|
29
57
|
});
|
|
30
58
|
/** A filter on exactly one stream, which a store can read through that stream's index. */
|
|
31
59
|
export const isOneStream = ({ aggregateIds, aggregateNames }) => aggregateNames?.length === 1 && aggregateIds?.length === 1;
|
|
@@ -102,15 +130,7 @@ export const makeMemory = Effect.gen(function* () {
|
|
|
102
130
|
expectedVersion: input.expectedVersion
|
|
103
131
|
});
|
|
104
132
|
}
|
|
105
|
-
const stored = input.events.map((event, index) => (
|
|
106
|
-
position: events.length + index + 1,
|
|
107
|
-
aggregateName: input.aggregateName,
|
|
108
|
-
aggregateId: input.aggregateId,
|
|
109
|
-
version: version + index + 1,
|
|
110
|
-
type: event.type,
|
|
111
|
-
payload: event.payload,
|
|
112
|
-
timestamp
|
|
113
|
-
}));
|
|
133
|
+
const stored = input.events.map((event, index) => toStored(input, event, index, events.length + index + 1, timestamp));
|
|
114
134
|
for (const event of stored) {
|
|
115
135
|
events.push(event);
|
|
116
136
|
stream.push(event);
|
package/dist/EventStore.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"EventStore.js","sourceRoot":"","sources":["../src/EventStore.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AACH,OAAO,EAAE,OAAO,EAAE,QAAQ,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,EAAE,MAAM,EAAE,eAAe,EAAE,MAAM,QAAQ,CAAA;AAEvG,MAAM,OAAO,eAAgB,SAAQ,MAAM,CAAC,WAAW,EAAmB,CAAC,iBAAiB,EAAE;IAC5F,SAAS,EAAE,MAAM,CAAC,MAAM;IACxB,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE;CACvB,CAAC;CAAG;AAEL,MAAM,OAAO,mBAAoB,SAAQ,MAAM,CAAC,WAAW,EAAuB,CAAC,qBAAqB,EAAE;IACxG,aAAa,EAAE,MAAM,CAAC,MAAM;IAC5B,WAAW,EAAE,MAAM,CAAC,MAAM;IAC1B,eAAe,EAAE,MAAM,CAAC,GAAG;CAC5B,CAAC;CAAG;AAEL,MAAM,CAAC,MAAM,WAAW,GAAG,MAAM,CAAC,MAAM,CAAC;IACvC,QAAQ,EAAE,MAAM,CAAC,GAAG;IACpB,aAAa,EAAE,MAAM,CAAC,MAAM;IAC5B,WAAW,EAAE,MAAM,CAAC,MAAM;IAC1B,OAAO,EAAE,MAAM,CAAC,GAAG;IACnB,IAAI,EAAE,MAAM,CAAC,MAAM;IACnB,OAAO,EAAE,MAAM,CAAC,IAAI;IACpB,SAAS,EAAE,MAAM,CAAC,qBAAqB;
|
|
1
|
+
{"version":3,"file":"EventStore.js","sourceRoot":"","sources":["../src/EventStore.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The older shapes of one event, so that events stored long ago still reach
|
|
3
|
+
* projections and view models in today's shape. The log is never rewritten:
|
|
4
|
+
* old payloads are upcast as they are read.
|
|
5
|
+
*
|
|
6
|
+
* ```ts
|
|
7
|
+
* const AccountOpenedV1 = Schema.Struct({ owner: Schema.String })
|
|
8
|
+
*
|
|
9
|
+
* const Account = Aggregate.make<State>()({
|
|
10
|
+
* name: "account",
|
|
11
|
+
* events: AccountEvent,
|
|
12
|
+
* initial,
|
|
13
|
+
* projection,
|
|
14
|
+
* versions: {
|
|
15
|
+
* AccountOpened: EventVersions.from(AccountOpenedV1).next(
|
|
16
|
+
* AccountEvent.cases.AccountOpened,
|
|
17
|
+
* ({ owner }) => ({ owner, currency: "CHF" })
|
|
18
|
+
* )
|
|
19
|
+
* }
|
|
20
|
+
* })
|
|
21
|
+
* ```
|
|
22
|
+
*
|
|
23
|
+
* The first schema is version 1, the shape events had before the history was
|
|
24
|
+
* declared, and every `next` adds a version. The last one must be the event's
|
|
25
|
+
* current schema, which is what new events are written with.
|
|
26
|
+
*/
|
|
27
|
+
import { Schema } from "effect";
|
|
28
|
+
/** A schema that decodes and encodes without services, as stored payloads need. */
|
|
29
|
+
export type VersionSchema = Schema.Top & {
|
|
30
|
+
readonly DecodingServices: never;
|
|
31
|
+
readonly EncodingServices: never;
|
|
32
|
+
};
|
|
33
|
+
/** What an upcast returns: the next version, without the `_tag` the event's name already gives. */
|
|
34
|
+
export type Untagged<T> = T extends {
|
|
35
|
+
readonly _tag: string;
|
|
36
|
+
} ? Omit<T, "_tag"> : T;
|
|
37
|
+
export interface EventVersions<Schemas extends ReadonlyArray<VersionSchema>, Latest> {
|
|
38
|
+
/** Oldest first: `schemas[0]` is version 1. */
|
|
39
|
+
readonly schemas: Schemas;
|
|
40
|
+
/** `upcasts[i]` turns version `i + 1` into version `i + 2`. */
|
|
41
|
+
readonly upcasts: ReadonlyArray<(previous: any) => unknown>;
|
|
42
|
+
/** The type of the last version, which an aggregate checks against its event. */
|
|
43
|
+
readonly "~latest"?: Latest;
|
|
44
|
+
/** Adds a version, with the function that turns the previous one into it. */
|
|
45
|
+
next<Next extends VersionSchema>(schema: Next, upcast: (previous: Latest) => Untagged<Next["Type"]>): EventVersions<readonly [...Schemas, Next], Next["Type"]>;
|
|
46
|
+
}
|
|
47
|
+
/** Any history whose last version is `Latest`. */
|
|
48
|
+
export type Any<Latest = any> = EventVersions<ReadonlyArray<VersionSchema>, Latest>;
|
|
49
|
+
/** The schema of one version, or `never` when the history has no such version. */
|
|
50
|
+
export type SchemaAt<H extends Any, V extends number> = H extends EventVersions<infer Schemas, any> ? [never, ...Schemas][V] extends infer S extends VersionSchema ? S : never : never;
|
|
51
|
+
/** Starts a history at version 1. */
|
|
52
|
+
export declare const from: <V1 extends VersionSchema>(schema: V1) => EventVersions<readonly [V1], V1["Type"]>;
|
|
53
|
+
/** The version new events of this kind are written with. */
|
|
54
|
+
export declare const current: (versions: Any | undefined) => number;
|
|
55
|
+
/**
|
|
56
|
+
* Builds one decoder per old version, each going all the way to the current one.
|
|
57
|
+
* The chains are composed once with `Schema.decodeTo`, so reading an old event
|
|
58
|
+
* costs one decode however many versions it is behind.
|
|
59
|
+
*
|
|
60
|
+
* `decoders[v - 1]` decodes a JSON payload stored at version `v`. The current
|
|
61
|
+
* version has none, as the aggregate's own schema reads it.
|
|
62
|
+
*/
|
|
63
|
+
export declare const decoders: (tag: string, versions: Any) => ReadonlyArray<Schema.Decoder<unknown>>;
|
|
64
|
+
//# sourceMappingURL=EventVersions.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"EventVersions.d.ts","sourceRoot":"","sources":["../src/EventVersions.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AACH,OAAO,EAAE,MAAM,EAAgB,MAAM,QAAQ,CAAA;AAE7C,mFAAmF;AACnF,MAAM,MAAM,aAAa,GAAG,MAAM,CAAC,GAAG,GAAG;IACvC,QAAQ,CAAC,gBAAgB,EAAE,KAAK,CAAA;IAChC,QAAQ,CAAC,gBAAgB,EAAE,KAAK,CAAA;CACjC,CAAA;AAED,mGAAmG;AACnG,MAAM,MAAM,QAAQ,CAAC,CAAC,IAAI,CAAC,SAAS;IAAE,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;CAAE,GAAG,IAAI,CAAC,CAAC,EAAE,MAAM,CAAC,GAAG,CAAC,CAAA;AAMnF,MAAM,WAAW,aAAa,CAAC,OAAO,SAAS,aAAa,CAAC,aAAa,CAAC,EAAE,MAAM;IACjF,+CAA+C;IAC/C,QAAQ,CAAC,OAAO,EAAE,OAAO,CAAA;IACzB,+DAA+D;IAC/D,QAAQ,CAAC,OAAO,EAAE,aAAa,CAAC,CAAC,QAAQ,EAAE,GAAG,KAAK,OAAO,CAAC,CAAA;IAC3D,iFAAiF;IACjF,QAAQ,CAAC,SAAS,CAAC,EAAE,MAAM,CAAA;IAC3B,6EAA6E;IAC7E,IAAI,CAAC,IAAI,SAAS,aAAa,EAC7B,MAAM,EAAE,IAAI,EACZ,MAAM,EAAE,CAAC,QAAQ,EAAE,MAAM,KAAK,QAAQ,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC,GACnD,aAAa,CAAC,SAAS,CAAC,GAAG,OAAO,EAAE,IAAI,CAAC,EAAE,IAAI,CAAC,MAAM,CAAC,CAAC,CAAA;CAC5D;AAED,kDAAkD;AAClD,MAAM,MAAM,GAAG,CAAC,MAAM,GAAG,GAAG,IAAI,aAAa,CAAC,aAAa,CAAC,aAAa,CAAC,EAAE,MAAM,CAAC,CAAA;AAEnF,kFAAkF;AAClF,MAAM,MAAM,QAAQ,CAAC,CAAC,SAAS,GAAG,EAAE,CAAC,SAAS,MAAM,IAAI,CAAC,SAAS,aAAa,CAAC,MAAM,OAAO,EAAE,GAAG,CAAC,GAC/F,CAAC,KAAK,EAAE,GAAG,OAAO,CAAC,CAAC,CAAC,CAAC,SAAS,MAAM,CAAC,SAAS,aAAa,GAAG,CAAC,GAAG,KAAK,GACxE,KAAK,CAAA;AAWT,qCAAqC;AACrC,eAAO,MAAM,IAAI,GAAI,EAAE,SAAS,aAAa,UAAU,EAAE,KAAG,aAAa,CAAC,SAAS,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,MAAM,CAAC,CACrE,CAAA;AAE9B,4DAA4D;AAC5D,eAAO,MAAM,OAAO,aAAc,GAAG,GAAG,SAAS,KAAG,MAAuC,CAAA;AAE3F;;;;;;;GAOG;AACH,eAAO,MAAM,QAAQ,QAAS,MAAM,YAAY,GAAG,KAAG,aAAa,CAAC,MAAM,CAAC,OAAO,CAAC,OAAO,CAAC,CAgBxF,CAAA"}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The older shapes of one event, so that events stored long ago still reach
|
|
3
|
+
* projections and view models in today's shape. The log is never rewritten:
|
|
4
|
+
* old payloads are upcast as they are read.
|
|
5
|
+
*
|
|
6
|
+
* ```ts
|
|
7
|
+
* const AccountOpenedV1 = Schema.Struct({ owner: Schema.String })
|
|
8
|
+
*
|
|
9
|
+
* const Account = Aggregate.make<State>()({
|
|
10
|
+
* name: "account",
|
|
11
|
+
* events: AccountEvent,
|
|
12
|
+
* initial,
|
|
13
|
+
* projection,
|
|
14
|
+
* versions: {
|
|
15
|
+
* AccountOpened: EventVersions.from(AccountOpenedV1).next(
|
|
16
|
+
* AccountEvent.cases.AccountOpened,
|
|
17
|
+
* ({ owner }) => ({ owner, currency: "CHF" })
|
|
18
|
+
* )
|
|
19
|
+
* }
|
|
20
|
+
* })
|
|
21
|
+
* ```
|
|
22
|
+
*
|
|
23
|
+
* The first schema is version 1, the shape events had before the history was
|
|
24
|
+
* declared, and every `next` adds a version. The last one must be the event's
|
|
25
|
+
* current schema, which is what new events are written with.
|
|
26
|
+
*/
|
|
27
|
+
import { Schema, SchemaGetter } from "effect";
|
|
28
|
+
const build = (schemas, upcasts) => ({
|
|
29
|
+
schemas,
|
|
30
|
+
upcasts,
|
|
31
|
+
next: (schema, upcast) => build([...schemas, schema], [...upcasts, upcast])
|
|
32
|
+
});
|
|
33
|
+
/** Starts a history at version 1. */
|
|
34
|
+
export const from = (schema) => build([schema], []);
|
|
35
|
+
/** The version new events of this kind are written with. */
|
|
36
|
+
export const current = (versions) => versions?.schemas.length ?? 1;
|
|
37
|
+
/**
|
|
38
|
+
* Builds one decoder per old version, each going all the way to the current one.
|
|
39
|
+
* The chains are composed once with `Schema.decodeTo`, so reading an old event
|
|
40
|
+
* costs one decode however many versions it is behind.
|
|
41
|
+
*
|
|
42
|
+
* `decoders[v - 1]` decodes a JSON payload stored at version `v`. The current
|
|
43
|
+
* version has none, as the aggregate's own schema reads it.
|
|
44
|
+
*/
|
|
45
|
+
export const decoders = (tag, versions) => versions.schemas.slice(0, -1).map((schema, index) => versions.schemas.slice(index + 1).reduce((chain, next, step) => chain.pipe(Schema.decodeTo(Schema.toType(next), {
|
|
46
|
+
// Adding the tag lets the last step decode into the tagged event, and a
|
|
47
|
+
// schema in between without one drops it again.
|
|
48
|
+
decode: SchemaGetter.transform((previous) => ({
|
|
49
|
+
...versions.upcasts[index + step](previous),
|
|
50
|
+
_tag: tag
|
|
51
|
+
})),
|
|
52
|
+
// A payload is only ever written in its current version.
|
|
53
|
+
encode: SchemaGetter.forbidden(() => `${tag} can only be written in its current version`)
|
|
54
|
+
})), Schema.toCodecJson(schema)));
|
|
55
|
+
//# sourceMappingURL=EventVersions.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"EventVersions.js","sourceRoot":"","sources":["../src/EventVersions.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AACH,OAAO,EAAE,MAAM,EAAE,YAAY,EAAE,MAAM,QAAQ,CAAA;AAqC7C,MAAM,KAAK,GAAG,CACZ,OAAgB,EAChB,OAAkD,EACH,EAAE,CAAC,CAAC;IACnD,OAAO;IACP,OAAO;IACP,IAAI,EAAE,CAAC,MAAM,EAAE,MAAM,EAAE,EAAE,CAAC,KAAK,CAAC,CAAC,GAAG,OAAO,EAAE,MAAM,CAAU,EAAE,CAAC,GAAG,OAAO,EAAE,MAAM,CAAC,CAAQ;CAC5F,CAAC,CAAA;AAEF,qCAAqC;AACrC,MAAM,CAAC,MAAM,IAAI,GAAG,CAA2B,MAAU,EAA4C,EAAE,CACrG,KAAK,CAAC,CAAC,MAAM,CAAU,EAAE,EAAE,CAAC,CAAA;AAE9B,4DAA4D;AAC5D,MAAM,CAAC,MAAM,OAAO,GAAG,CAAC,QAAyB,EAAU,EAAE,CAAC,QAAQ,EAAE,OAAO,CAAC,MAAM,IAAI,CAAC,CAAA;AAE3F;;;;;;;GAOG;AACH,MAAM,CAAC,MAAM,QAAQ,GAAG,CAAC,GAAW,EAAE,QAAa,EAA0C,EAAE,CAC7F,QAAQ,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,MAAM,EAAE,KAAK,EAAE,EAAE,CAClD,QAAQ,CAAC,OAAO,CAAC,KAAK,CAAC,KAAK,GAAG,CAAC,CAAC,CAAC,MAAM,CACtC,CAAC,KAAK,EAAE,IAAI,EAAE,IAAI,EAAE,EAAE,CACpB,KAAK,CAAC,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC,EAAE;IAC9C,wEAAwE;IACxE,gDAAgD;IAChD,MAAM,EAAE,YAAY,CAAC,SAAS,CAAC,CAAC,QAAQ,EAAE,EAAE,CAAC,CAAC;QAC5C,GAAI,QAAQ,CAAC,OAAO,CAAC,KAAK,GAAG,IAAI,CAAE,CAAC,QAAQ,CAAY;QACxD,IAAI,EAAE,GAAG;KACV,CAAC,CAAC;IACH,yDAAyD;IACzD,MAAM,EAAE,YAAY,CAAC,SAAS,CAAC,GAAG,EAAE,CAAC,GAAG,GAAG,6CAA6C,CAAC;CAC1F,CAAC,CAAC,EACL,MAAM,CAAC,WAAW,CAAC,MAAM,CAAC,CACA,CAC7B,CAAA"}
|
package/dist/Fixture.d.ts
CHANGED
|
@@ -13,9 +13,13 @@
|
|
|
13
13
|
*
|
|
14
14
|
* Commands run through the real {@link AggregateService} on an in-memory store,
|
|
15
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.
|
|
16
20
|
*/
|
|
17
|
-
import { Effect, Schema } from "effect";
|
|
18
|
-
import
|
|
21
|
+
import { Arbitrary, Effect, Schema } from "effect";
|
|
22
|
+
import * as Aggregate from "./Aggregate.ts";
|
|
19
23
|
import * as AggregateService from "./AggregateService.ts";
|
|
20
24
|
declare const ExpectationFailed_base: Schema.Class<ExpectationFailed, Schema.TaggedStruct<"ExpectationFailed", {
|
|
21
25
|
readonly message: Schema.String;
|
|
@@ -34,6 +38,30 @@ export interface Outcome<A extends Aggregate.Any> {
|
|
|
34
38
|
}
|
|
35
39
|
/** An event to compare with, or only its tag when the rest doesn't matter. */
|
|
36
40
|
export type ExpectedEvent<A extends Aggregate.Any> = Aggregate.Event<A> | Aggregate.Event<A>["_tag"];
|
|
41
|
+
/** An event in today's shape, or one stored in an older version of its schema. */
|
|
42
|
+
export type GivenEvent<A extends Aggregate.Any> = Aggregate.Event<A> | Aggregate.Historical;
|
|
43
|
+
/** One generated step of {@link Fixture.checkInvariants}. */
|
|
44
|
+
export type Step<A extends Aggregate.Any> = {
|
|
45
|
+
readonly [K in Aggregate.CommandName<A>]: {
|
|
46
|
+
readonly command: K;
|
|
47
|
+
readonly payload: Aggregate.Payload<A, K>;
|
|
48
|
+
};
|
|
49
|
+
}[Aggregate.CommandName<A>];
|
|
50
|
+
export interface InvariantOptions<A extends Aggregate.Any> {
|
|
51
|
+
/** Must hold for the given state and after every command that succeeds. */
|
|
52
|
+
readonly invariant: (state: Aggregate.State<A>) => boolean;
|
|
53
|
+
/**
|
|
54
|
+
* How to generate each command's payload. By default it comes from the payload
|
|
55
|
+
* schema. `false` leaves a command out.
|
|
56
|
+
*/
|
|
57
|
+
readonly commands?: {
|
|
58
|
+
readonly [K in Aggregate.CommandName<A>]?: Arbitrary.Arbitrary<Aggregate.Payload<A, K>> | false;
|
|
59
|
+
} | undefined;
|
|
60
|
+
/** The longest sequence of commands to try. Defaults to 20. */
|
|
61
|
+
readonly maxCommands?: number | undefined;
|
|
62
|
+
/** Passed on to `Arbitrary.checkEffect`, for the number of runs or a seed. */
|
|
63
|
+
readonly check?: Arbitrary.CheckOptions | undefined;
|
|
64
|
+
}
|
|
37
65
|
/**
|
|
38
66
|
* The steps so far. Nothing runs until it is yielded or run as an Effect, which
|
|
39
67
|
* produces the {@link Outcome} of the last command.
|
|
@@ -52,9 +80,9 @@ export interface Scenario<A extends Aggregate.Any, E, R> extends Effect.Effect<O
|
|
|
52
80
|
}
|
|
53
81
|
export interface Fixture<A extends Aggregate.Any> {
|
|
54
82
|
/** Replaces the history the next command runs against. */
|
|
55
|
-
given(events: ReadonlyArray<
|
|
83
|
+
given(events: ReadonlyArray<GivenEvent<A>>): Fixture<A>;
|
|
56
84
|
/** Like `given`, starting from an empty history by default and optionally switching aggregate id. */
|
|
57
|
-
givenEvents(events?: ReadonlyArray<
|
|
85
|
+
givenEvents(events?: ReadonlyArray<GivenEvent<A>>, aggregateId?: string): Fixture<A>;
|
|
58
86
|
/** The state after the given events, without running a command. */
|
|
59
87
|
readonly state: Effect.Effect<Aggregate.State<A>, AggregateService.ReadError, Aggregate.Context<A>>;
|
|
60
88
|
/** Passes the state after the given events to a callback, which throws to fail the test. */
|
|
@@ -62,10 +90,17 @@ export interface Fixture<A extends Aggregate.Any> {
|
|
|
62
90
|
when<K extends Aggregate.CommandName<A>>(command: K, payload: Aggregate.Payload<A, K>): Scenario<A, Aggregate.CommandError<A, K> | AggregateService.CommandFailure, Aggregate.Context<A>>;
|
|
63
91
|
/** Runs a command that is expected to fail and returns the error. Dies if it succeeds. */
|
|
64
92
|
whenFails<K extends Aggregate.CommandName<A>>(command: K, payload: Aggregate.Payload<A, K>): Effect.Effect<Aggregate.CommandError<A, K> | AggregateService.CommandFailure, never, Aggregate.Context<A>>;
|
|
93
|
+
/**
|
|
94
|
+
* Runs random sequences of commands from the given history and checks that the
|
|
95
|
+
* invariant holds after each one that succeeds. Commands that fail are part of
|
|
96
|
+
* the game and are skipped. A violation dies with {@link ExpectationFailed},
|
|
97
|
+
* holding the shortest sequence found that breaks it.
|
|
98
|
+
*/
|
|
99
|
+
checkInvariants(options: InvariantOptions<A>): Effect.Effect<void, never, Aggregate.Context<A>>;
|
|
65
100
|
}
|
|
66
101
|
export declare const make: <A extends Aggregate.Any>(aggregate: A, options?: {
|
|
67
102
|
readonly aggregateId?: string;
|
|
68
|
-
readonly events?: ReadonlyArray<
|
|
103
|
+
readonly events?: ReadonlyArray<GivenEvent<A>>;
|
|
69
104
|
}) => Fixture<A>;
|
|
70
105
|
export {};
|
|
71
106
|
//# sourceMappingURL=Fixture.d.ts.map
|
package/dist/Fixture.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Fixture.d.ts","sourceRoot":"","sources":["../src/Fixture.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"Fixture.d.ts","sourceRoot":"","sources":["../src/Fixture.ts"],"names":[],"mappings":"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"}
|
package/dist/Fixture.js
CHANGED
|
@@ -13,8 +13,13 @@
|
|
|
13
13
|
*
|
|
14
14
|
* Commands run through the real {@link AggregateService} on an in-memory store,
|
|
15
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.
|
|
16
20
|
*/
|
|
17
|
-
import { Effect, Effectable, Schema } from "effect";
|
|
21
|
+
import { Arbitrary, Effect, Effectable, Schema } from "effect";
|
|
22
|
+
import * as Aggregate from "./Aggregate.js";
|
|
18
23
|
import * as AggregateService from "./AggregateService.js";
|
|
19
24
|
import { EventStore, makeMemory } from "./EventStore.js";
|
|
20
25
|
/** Raised as a defect, so a command's error channel only holds what the command can do. */
|
|
@@ -63,6 +68,7 @@ export const make = (aggregate, options) => {
|
|
|
63
68
|
const aggregateId = options?.aggregateId ?? "fixture-id";
|
|
64
69
|
const history = options?.events ?? [];
|
|
65
70
|
const encodeEvent = AggregateService.makeEventEncoder(aggregate);
|
|
71
|
+
const encodeGiven = (event) => event instanceof Aggregate.Historical ? Effect.succeed(event) : encodeEvent(event);
|
|
66
72
|
const seeded = Effect.gen(function* () {
|
|
67
73
|
const store = yield* makeMemory;
|
|
68
74
|
if (history.length > 0) {
|
|
@@ -70,7 +76,7 @@ export const make = (aggregate, options) => {
|
|
|
70
76
|
aggregateName: aggregate.name,
|
|
71
77
|
aggregateId,
|
|
72
78
|
expectedVersion: 0,
|
|
73
|
-
events: yield* Effect.forEach(history,
|
|
79
|
+
events: yield* Effect.forEach(history, encodeGiven)
|
|
74
80
|
});
|
|
75
81
|
}
|
|
76
82
|
const service = yield* AggregateService.make(aggregate).pipe(Effect.provideService(EventStore, store));
|
|
@@ -90,6 +96,40 @@ export const make = (aggregate, options) => {
|
|
|
90
96
|
fixture: make(aggregate, { aggregateId, events: [...history, ...result.events] })
|
|
91
97
|
};
|
|
92
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
|
+
});
|
|
93
133
|
const fixture = {
|
|
94
134
|
given: (events) => make(aggregate, { aggregateId, events }),
|
|
95
135
|
givenEvents: (events = [], id = aggregateId) => make(aggregate, { aggregateId: id, events }),
|
|
@@ -101,7 +141,8 @@ export const make = (aggregate, options) => {
|
|
|
101
141
|
fixture
|
|
102
142
|
}))).inspectState(check),
|
|
103
143
|
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`))))
|
|
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
|
|
105
146
|
};
|
|
106
147
|
return fixture;
|
|
107
148
|
};
|
package/dist/Fixture.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Fixture.js","sourceRoot":"","sources":["../src/Fixture.ts"],"names":[],"mappings":"AAAA
|
|
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"}
|
|
@@ -1 +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;;;;;;
|
|
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"}
|
|
@@ -16,8 +16,8 @@ import * as EventStore from "./EventStore.js";
|
|
|
16
16
|
const decodeRows = Schema.decodeUnknownEffect(Schema.Array(EventStore.StoredEvent));
|
|
17
17
|
const storeError = (operation) => (cause) => new EventStore.EventStoreError({ operation, cause });
|
|
18
18
|
const isUniqueViolation = (error) => error.reason._tag === "UniqueViolation";
|
|
19
|
-
// The client binds at most 32,767 parameters to a statement, and each row uses
|
|
20
|
-
const rowsPerInsert =
|
|
19
|
+
// The client binds at most 32,767 parameters to a statement, and each row uses 11.
|
|
20
|
+
const rowsPerInsert = 2_900;
|
|
21
21
|
export const make = Effect.fnUntraced(function* (options) {
|
|
22
22
|
const sql = yield* SqlClient.SqlClient;
|
|
23
23
|
const prefix = options?.eventsTable ?? "cqrs_events";
|
|
@@ -31,11 +31,20 @@ export const make = Effect.fnUntraced(function* (options) {
|
|
|
31
31
|
aggregate_id TEXT NOT NULL,
|
|
32
32
|
version INTEGER NOT NULL,
|
|
33
33
|
type TEXT NOT NULL,
|
|
34
|
+
schema_version INTEGER NOT NULL DEFAULT 1,
|
|
34
35
|
payload JSONB NOT NULL,
|
|
35
36
|
"timestamp" BIGINT NOT NULL,
|
|
37
|
+
correlation_id TEXT,
|
|
38
|
+
causation_id TEXT,
|
|
39
|
+
metadata JSONB NOT NULL DEFAULT '{}',
|
|
36
40
|
UNIQUE (aggregate_name, aggregate_id, version)
|
|
37
41
|
)
|
|
38
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 '{}'`;
|
|
39
48
|
yield* sql `CREATE INDEX IF NOT EXISTS ${sql(`${prefix}_type_idx`)} ON ${events} (type, position)`;
|
|
40
49
|
yield* sql `
|
|
41
50
|
CREATE TABLE IF NOT EXISTS ${counter} (
|
|
@@ -52,8 +61,12 @@ export const make = Effect.fnUntraced(function* (options) {
|
|
|
52
61
|
aggregate_id AS "aggregateId",
|
|
53
62
|
version,
|
|
54
63
|
type,
|
|
64
|
+
schema_version AS "schemaVersion",
|
|
55
65
|
payload,
|
|
56
|
-
"timestamp"::float8 AS "timestamp"
|
|
66
|
+
"timestamp"::float8 AS "timestamp",
|
|
67
|
+
correlation_id AS "correlationId",
|
|
68
|
+
causation_id AS "causationId",
|
|
69
|
+
metadata
|
|
57
70
|
`);
|
|
58
71
|
const append = (input) => {
|
|
59
72
|
const conflict = new EventStore.ConcurrencyConflict({
|
|
@@ -74,23 +87,19 @@ export const make = Effect.fnUntraced(function* (options) {
|
|
|
74
87
|
UPDATE ${counter} SET position = position + ${input.events.length} RETURNING position::float8 AS position
|
|
75
88
|
`;
|
|
76
89
|
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
|
-
}));
|
|
90
|
+
const stored = input.events.map((event, index) => EventStore.toStored(input, event, index, first + index, DateTime.makeUnsafe(timestamp)));
|
|
86
91
|
const rows = stored.map((event) => ({
|
|
87
92
|
position: event.position,
|
|
88
93
|
aggregate_name: event.aggregateName,
|
|
89
94
|
aggregate_id: event.aggregateId,
|
|
90
95
|
version: event.version,
|
|
91
96
|
type: event.type,
|
|
97
|
+
schema_version: event.schemaVersion,
|
|
92
98
|
payload: JSON.stringify(event.payload),
|
|
93
|
-
timestamp
|
|
99
|
+
timestamp,
|
|
100
|
+
correlation_id: event.correlationId,
|
|
101
|
+
causation_id: event.causationId,
|
|
102
|
+
metadata: JSON.stringify(event.metadata)
|
|
94
103
|
}));
|
|
95
104
|
yield* Effect.forEach(Arr.chunksOf(rows, rowsPerInsert), (chunk) => sql `INSERT INTO ${events} ${sql.insert(chunk)}`, { discard: true });
|
|
96
105
|
return stored;
|