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
|
```ts
|
||||||
import { makeReducer } from "@valkyr/event-store";
|
import { makeReducer } from "@valkyr/event-store";
|
||||||
|
|
||||||
import type { EventRecord } from "./generated/events.ts";
|
import type { Events } from "./events.ts";
|
||||||
|
|
||||||
const reducer = makeReducer<
|
export const userReducer = makeReducer<Events, UserState>(
|
||||||
{
|
|
||||||
name: string;
|
|
||||||
email: string;
|
|
||||||
},
|
|
||||||
EventRecord
|
|
||||||
>(
|
|
||||||
(state, event) => {
|
(state, event) => {
|
||||||
switch (event.type) {
|
switch (event.type) {
|
||||||
case "user:created": {
|
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;
|
state.email = event.data.email;
|
||||||
break;
|
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": {
|
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;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return state;
|
return state;
|
||||||
},
|
},
|
||||||
"user",
|
|
||||||
() => ({
|
() => ({
|
||||||
name: "",
|
name: {
|
||||||
|
given: "",
|
||||||
|
family: "",
|
||||||
|
},
|
||||||
email: "",
|
email: "",
|
||||||
|
active: true,
|
||||||
|
posts: {
|
||||||
|
list: [],
|
||||||
|
count: 0,
|
||||||
|
},
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
type UserState = {
|
||||||
|
name: {
|
||||||
|
given: string;
|
||||||
|
family: string;
|
||||||
|
};
|
||||||
|
email: string;
|
||||||
|
active: boolean;
|
||||||
|
posts: {
|
||||||
|
list: string[];
|
||||||
|
count: number;
|
||||||
|
};
|
||||||
|
};
|
||||||
```
|
```
|
||||||
|
|
||||||
### Aggreates
|
### Aggreates
|
||||||
@@ -70,48 +101,89 @@ The benefit of this is that we can create various helper methods on the aggregat
|
|||||||
query the aggregated state.
|
query the aggregated state.
|
||||||
|
|
||||||
```ts
|
```ts
|
||||||
import { AggregateRoot, makeAggregateReducer } from "@valkyr/event-store";
|
import { AggregateRoot } from "@valkyr/event-store";
|
||||||
|
|
||||||
import type { EventRecord } from "./generated/events.ts";
|
import type { Events } from "./events.ts";
|
||||||
import { eventStore } from "./event-store.ts";
|
|
||||||
|
|
||||||
export class User extends AggregateRoot<EventRecord> {
|
export class User extends AggregateRoot<Events> {
|
||||||
name!: Name;
|
static override readonly name = "user";
|
||||||
email!: string;
|
|
||||||
|
name: Name = {
|
||||||
|
given: "",
|
||||||
|
family: "",
|
||||||
|
};
|
||||||
|
email: string = "";
|
||||||
|
active: boolean = true;
|
||||||
|
posts: UserPosts = {
|
||||||
|
list: [],
|
||||||
|
count: 0,
|
||||||
|
};
|
||||||
|
|
||||||
// -------------------------------------------------------------------------
|
// -------------------------------------------------------------------------
|
||||||
// Factories
|
// Reducer
|
||||||
// -------------------------------------------------------------------------
|
// -------------------------------------------------------------------------
|
||||||
|
|
||||||
static #reducer = makeAggregateReducer(User, "user");
|
with(event: Events["$events"][number]["$record"]) {
|
||||||
|
|
||||||
static async getById(userId: string): Promise<User | undefined> {
|
|
||||||
return eventStore.reduce({ stream: userId, reducer: this.#reducer });
|
|
||||||
}
|
|
||||||
|
|
||||||
// -------------------------------------------------------------------------
|
|
||||||
// Folder
|
|
||||||
// -------------------------------------------------------------------------
|
|
||||||
|
|
||||||
with(event: EventRecord) {
|
|
||||||
switch (event.type) {
|
switch (event.type) {
|
||||||
case "user:created": {
|
case "user:name:given-set": {
|
||||||
this.name = event.data.name;
|
this.name.given = event.data;
|
||||||
this.email = event.data.email;
|
break;
|
||||||
|
}
|
||||||
|
case "user:name:family-set": {
|
||||||
|
this.name.family = event.data;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case "user:email-set": {
|
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;
|
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}`;
|
return `${this.name.given} ${this.name.family}`;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -120,6 +192,11 @@ type Name = {
|
|||||||
given: string;
|
given: string;
|
||||||
family: string;
|
family: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
type UserPosts = {
|
||||||
|
list: string[];
|
||||||
|
count: number;
|
||||||
|
};
|
||||||
```
|
```
|
||||||
|
|
||||||
### Projectors
|
### 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`.
|
types of listeners, `once`, `on`, and `all`.
|
||||||
|
|
||||||
```ts
|
```ts
|
||||||
import { projector } from "./event-store.ts";
|
import { Projector } from "@valkyr/event-store";
|
||||||
|
|
||||||
projector.on("user:created", async (record) => {
|
import store from "./event-store.ts";
|
||||||
// do something with the event record ...
|
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 { makePostgresEventStore } from "@valkyr/event-store/postgres";
|
||||||
import postgres from "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>({
|
export const eventStore = new EventStore({
|
||||||
connection: () => postgres("postgres://${string}:${string}@${string}:${number}/${string}"), // lazy loaded connection
|
adapter: new PostgresAdapter(connection, { schema: "event_store" }),
|
||||||
schema: "event_store",
|
|
||||||
events,
|
events,
|
||||||
validators,
|
hooks,
|
||||||
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.
|
|
||||||
},
|
|
||||||
},
|
|
||||||
});
|
});
|
||||||
|
|
||||||
const projector = new Projector<EventRecord>();
|
const projector = new Projector<Events>();
|
||||||
|
|
||||||
eventStore.onEventsInserted(async (records, { batch }) => {
|
eventStore.onEventsInserted(async (records, { batch }) => {
|
||||||
// trigger event side effects here such as sending the records through
|
// trigger event side effects here such as sending the records through
|
||||||
|
|||||||
Reference in New Issue
Block a user