feat: update documentation
This commit is contained in:
172
README.md
172
README.md
@@ -30,35 +30,66 @@ may not be up to date.
|
||||
```ts
|
||||
import { makeReducer } from "@valkyr/event-store";
|
||||
|
||||
import type { EventRecord } from "./generated/events.ts";
|
||||
import type { Events } from "./events.ts";
|
||||
|
||||
const reducer = makeReducer<
|
||||
{
|
||||
name: string;
|
||||
email: string;
|
||||
},
|
||||
EventRecord
|
||||
>(
|
||||
export const userReducer = makeReducer<Events, UserState>(
|
||||
(state, event) => {
|
||||
switch (event.type) {
|
||||
case "user:created": {
|
||||
state.name = `${event.data.name.given} ${event.data.name.family}`;
|
||||
state.name.given = event.data.name?.given ?? "";
|
||||
state.name.family = event.data.name?.family ?? "";
|
||||
state.email = event.data.email;
|
||||
break;
|
||||
}
|
||||
case "user:name:given-set": {
|
||||
state.name.given = event.data;
|
||||
break;
|
||||
}
|
||||
case "user:name:family-set": {
|
||||
state.name.family = event.data;
|
||||
break;
|
||||
}
|
||||
case "user:email-set": {
|
||||
state.email = event.data.email;
|
||||
state.email = event.data;
|
||||
break;
|
||||
}
|
||||
case "user:activated": {
|
||||
state.active = true;
|
||||
break;
|
||||
}
|
||||
case "user:deactivated": {
|
||||
state.active = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
return state;
|
||||
},
|
||||
"user",
|
||||
() => ({
|
||||
name: "",
|
||||
name: {
|
||||
given: "",
|
||||
family: "",
|
||||
},
|
||||
email: "",
|
||||
active: true,
|
||||
posts: {
|
||||
list: [],
|
||||
count: 0,
|
||||
},
|
||||
}),
|
||||
);
|
||||
|
||||
type UserState = {
|
||||
name: {
|
||||
given: string;
|
||||
family: string;
|
||||
};
|
||||
email: string;
|
||||
active: boolean;
|
||||
posts: {
|
||||
list: string[];
|
||||
count: number;
|
||||
};
|
||||
};
|
||||
```
|
||||
|
||||
### Aggreates
|
||||
@@ -70,48 +101,89 @@ The benefit of this is that we can create various helper methods on the aggregat
|
||||
query the aggregated state.
|
||||
|
||||
```ts
|
||||
import { AggregateRoot, makeAggregateReducer } from "@valkyr/event-store";
|
||||
import { AggregateRoot } from "@valkyr/event-store";
|
||||
|
||||
import type { EventRecord } from "./generated/events.ts";
|
||||
import { eventStore } from "./event-store.ts";
|
||||
import type { Events } from "./events.ts";
|
||||
|
||||
export class User extends AggregateRoot<EventRecord> {
|
||||
name!: Name;
|
||||
email!: string;
|
||||
export class User extends AggregateRoot<Events> {
|
||||
static override readonly name = "user";
|
||||
|
||||
name: Name = {
|
||||
given: "",
|
||||
family: "",
|
||||
};
|
||||
email: string = "";
|
||||
active: boolean = true;
|
||||
posts: UserPosts = {
|
||||
list: [],
|
||||
count: 0,
|
||||
};
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// Factories
|
||||
// Reducer
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
static #reducer = makeAggregateReducer(User, "user");
|
||||
|
||||
static async getById(userId: string): Promise<User | undefined> {
|
||||
return eventStore.reduce({ stream: userId, reducer: this.#reducer });
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// Folder
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
with(event: EventRecord) {
|
||||
with(event: Events["$events"][number]["$record"]) {
|
||||
switch (event.type) {
|
||||
case "user:created": {
|
||||
this.name = event.data.name;
|
||||
this.email = event.data.email;
|
||||
case "user:name:given-set": {
|
||||
this.name.given = event.data;
|
||||
break;
|
||||
}
|
||||
case "user:name:family-set": {
|
||||
this.name.family = event.data;
|
||||
break;
|
||||
}
|
||||
case "user:email-set": {
|
||||
this.email = event.data.email;
|
||||
this.email = event.data;
|
||||
break;
|
||||
}
|
||||
case "user:activated": {
|
||||
this.active = true;
|
||||
break;
|
||||
}
|
||||
case "user:deactivated": {
|
||||
this.active = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// Utilities
|
||||
// Actions
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
fullName() {
|
||||
setGivenName(given: string): this {
|
||||
return this.push({
|
||||
type: "user:name:given-set",
|
||||
stream: this.id,
|
||||
data: given,
|
||||
meta: { auditor: "foo" },
|
||||
});
|
||||
}
|
||||
|
||||
setFamilyName(family: string): this {
|
||||
return this.push({
|
||||
type: "user:name:family-set",
|
||||
stream: this.id,
|
||||
data: family,
|
||||
meta: { auditor: "foo" },
|
||||
});
|
||||
}
|
||||
|
||||
setEmail(email: string, auditor: string): this {
|
||||
return this.push({
|
||||
type: "user:email-set",
|
||||
stream: this.id,
|
||||
data: email,
|
||||
meta: { auditor },
|
||||
});
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// Helpers
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
fullName(): string {
|
||||
return `${this.name.given} ${this.name.family}`;
|
||||
}
|
||||
}
|
||||
@@ -120,6 +192,11 @@ type Name = {
|
||||
given: string;
|
||||
family: string;
|
||||
};
|
||||
|
||||
type UserPosts = {
|
||||
list: string[];
|
||||
count: number;
|
||||
};
|
||||
```
|
||||
|
||||
### Projectors
|
||||
@@ -132,10 +209,27 @@ A projector is registered for a specific event type, and can have multiple handl
|
||||
types of listeners, `once`, `on`, and `all`.
|
||||
|
||||
```ts
|
||||
import { projector } from "./event-store.ts";
|
||||
import { Projector } from "@valkyr/event-store";
|
||||
|
||||
projector.on("user:created", async (record) => {
|
||||
// do something with the event record ...
|
||||
import store from "./event-store.ts";
|
||||
import type { Events } from "./events.ts";
|
||||
|
||||
const projector = new Projector<Events>();
|
||||
|
||||
if (hooks.onEventsInserted === undefined) {
|
||||
store.onEventsInserted(async (records, { batch }) => {
|
||||
if (batch !== undefined) {
|
||||
await projector.pushMany(batch, records);
|
||||
} else {
|
||||
for (const record of records) {
|
||||
await projector.push(record, { hydrated: false, outdated: false });
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
projector.on("event", async (record) => {
|
||||
// handle event
|
||||
});
|
||||
```
|
||||
|
||||
|
||||
@@ -14,24 +14,15 @@ Once we have defined our configs and printed our events we create a new postgres
|
||||
import { makePostgresEventStore } from "@valkyr/event-store/postgres";
|
||||
import postgres from "postgres";
|
||||
|
||||
import { type Event, type EventRecord, events, validators } from "./generated/events.ts";
|
||||
import { events, type Events } from "./events.ts";
|
||||
|
||||
export const eventStore = makePostgresEventStore<Event>({
|
||||
connection: () => postgres("postgres://${string}:${string}@${string}:${number}/${string}"), // lazy loaded connection
|
||||
schema: "event_store",
|
||||
export const eventStore = new EventStore({
|
||||
adapter: new PostgresAdapter(connection, { schema: "event_store" }),
|
||||
events,
|
||||
validators,
|
||||
hooks: {
|
||||
async onError(error) {
|
||||
// when the event store throws unhandled errors they will end up in
|
||||
// this location that can be further logged in the systems own logger
|
||||
// if onError hook is not provided all unhandled errors are logged
|
||||
// through the `console.error` method.
|
||||
},
|
||||
},
|
||||
hooks,
|
||||
});
|
||||
|
||||
const projector = new Projector<EventRecord>();
|
||||
const projector = new Projector<Events>();
|
||||
|
||||
eventStore.onEventsInserted(async (records, { batch }) => {
|
||||
// trigger event side effects here such as sending the records through
|
||||
|
||||
Reference in New Issue
Block a user