From 91c3755533dd4728e8e24ef983bb81eb7e24b429 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Christoffer=20R=C3=B8dvik?= Date: Mon, 11 Aug 2025 13:52:29 +0200 Subject: [PATCH] feat: add reducer factory method --- libraries/aggregate.ts | 43 +++++++++++++++++++++++++++------------- libraries/event-store.ts | 11 ++++++++++ 2 files changed, 40 insertions(+), 14 deletions(-) diff --git a/libraries/aggregate.ts b/libraries/aggregate.ts index a203a82..a37eb00 100644 --- a/libraries/aggregate.ts +++ b/libraries/aggregate.ts @@ -2,7 +2,6 @@ import type { AnyEventStore, EventsInsertSettings } from "../libraries/event-sto import type { Unknown } from "../types/common.ts"; import { AggregateSnapshotViolation, AggregateStreamViolation } from "./errors.ts"; import { EventFactory } from "./event-factory.ts"; -import { makeAggregateReducer } from "./reducer.ts"; /** * Represents an aggregate root in an event-sourced system. @@ -20,6 +19,9 @@ export abstract class AggregateRoot { */ static readonly name: string; + /** + * Instance used for internal interaction with the originating event store. + */ readonly #store: AnyEventStore; /** @@ -52,13 +54,17 @@ export abstract class AggregateRoot { this.#stream = value; } - get id() { + get id(): string { if (this.#stream === undefined) { this.#stream = crypto.randomUUID(); } return this.#stream; } + get #self(): typeof AggregateRoot { + return this.constructor as typeof AggregateRoot; + } + /** * Does the aggregate have pending events to submit to the event store. */ @@ -133,6 +139,27 @@ export abstract class AggregateRoot { // Mutators // ------------------------------------------------------------------------- + /** + * Generates a new snapshot for the aggregate instance. + * + * If the instance has any pending events, they will be commited before + * the snapshot operation is executed. If there are special settings for + * any pending events, make sure to `.save()` before snapshotting. + */ + async snapshot() { + const stream = this.#stream; + if (stream === undefined) { + throw new AggregateSnapshotViolation(this.#self.name); + } + await this.save(); + const reducer = this.#store.aggregate.reducer(this.#self); + await this.#store.createSnapshot({ + name: this.#self.name, + stream, + reducer, + }); + } + /** * Saves all pending events to the attached event store. * @@ -150,18 +177,6 @@ export abstract class AggregateRoot { return this; } - async snapshot() { - const stream = this.#stream; - if (stream === undefined) { - throw new AggregateSnapshotViolation((this.constructor as typeof AggregateRoot).name); - } - await this.#store.createSnapshot({ - name: this.constructor.name, - stream, - reducer: makeAggregateReducer(this.#store, this.constructor as typeof AggregateRoot), - }); - } - /** * Removes all events from the aggregate #pending list. */ diff --git a/libraries/event-store.ts b/libraries/event-store.ts index 63f884d..fc3f5a2 100644 --- a/libraries/event-store.ts +++ b/libraries/event-store.ts @@ -184,6 +184,17 @@ export class EventStore => { return aggregate.from(this, snapshot); }, + + /** + * Create a new reducer instance for the given aggregate. + * + * @param aggregate - Aggregate to create a reducer for. + */ + reducer: >( + aggregate: TAggregate, + ): Reducer> => { + return makeAggregateReducer(this, aggregate); + }, }; /*