feat: update aggregate implementation

This commit is contained in:
2025-08-11 13:34:28 +02:00
parent 9dddc4e79f
commit cc8c558db6
37 changed files with 361 additions and 511 deletions

View File

@@ -3,10 +3,10 @@ import { it } from "@std/testing/bdd";
import { EventInsertionError, EventValidationError } from "../../libraries/errors.ts";
import { makeId } from "../../libraries/nanoid.ts";
import type { EventStoreFactory } from "../mocks/events.ts";
import type { Events } from "../mocks/events.ts";
import { describe } from "../utilities/describe.ts";
export default describe<EventStoreFactory>(".addEvent", (getEventStore) => {
export default describe<Events>(".addEvent", (getEventStore) => {
it("should throw a 'EventValidationError' when providing bad event data", async () => {
const { store } = await getEventStore();

View File

@@ -3,11 +3,11 @@ import { it } from "@std/testing/bdd";
import { nanoid } from "nanoid";
import { EventValidationError } from "../../mod.ts";
import type { EventStoreFactory } from "../mocks/events.ts";
import type { Events } from "../mocks/events.ts";
import { userReducer } from "../mocks/user-reducer.ts";
import { describe } from "../utilities/describe.ts";
export default describe<EventStoreFactory>(".addSequence", (getEventStore) => {
export default describe<Events>(".addSequence", (getEventStore) => {
it("should insert 'user:created', 'user:name:given-set', and 'user:email-set' in a sequence of events", async () => {
const { store } = await getEventStore();
const stream = nanoid();

View File

@@ -2,11 +2,11 @@ import { assertEquals, assertNotEquals, assertObjectMatch } from "@std/assert";
import { it } from "@std/testing/bdd";
import { nanoid } from "nanoid";
import type { EventStoreFactory } from "../mocks/events.ts";
import type { Events } from "../mocks/events.ts";
import { userReducer } from "../mocks/user-reducer.ts";
import { describe } from "../utilities/describe.ts";
export default describe<EventStoreFactory>(".createSnapshot", (getEventStore) => {
export default describe<Events>(".createSnapshot", (getEventStore) => {
it("should create a new snapshot", async () => {
const { store } = await getEventStore();
const stream = nanoid();

View File

@@ -1,24 +1,26 @@
import { assertEquals } from "@std/assert";
import { it } from "@std/testing/bdd";
import type { EventStoreFactory } from "../mocks/events.ts";
import { User } from "../mocks/aggregates.ts";
import type { Events } from "../mocks/events.ts";
import { describe } from "../utilities/describe.ts";
export default describe<EventStoreFactory>(".makeAggregateReducer", (getEventStore) => {
export default describe<Events>(".makeAggregateReducer", (getEventStore) => {
it("should reduce a user", async () => {
const { store } = await getEventStore();
const userA = await store
.aggregate("user")
.create({ given: "John", family: "Doe" }, "john.doe@fixture.none")
const userA = await store.aggregate
.from(User)
.setGivenName("Jane")
.setFamilyName("Doe")
.setEmail("john.doe@fixture.none", "auditor")
.save();
await userA.snapshot();
await userA.setFamilyName("Smith").setEmail("jane.smith@fixture.none", "system").save();
const userB = await store.aggregate("user").getById(userA.id);
const userB = await store.aggregate.getByStream(User, userA.id);
if (userB === undefined) {
throw new Error("Expected user to exist");
}

View File

@@ -2,10 +2,10 @@ import { assertEquals, assertLess } from "@std/assert";
import { it } from "@std/testing/bdd";
import { RelationPayload } from "../../types/adapter.ts";
import type { EventStoreFactory } from "../mocks/events.ts";
import type { Events } from "../mocks/events.ts";
import { describe } from "../utilities/describe.ts";
export default describe<EventStoreFactory>(".makeEvent", (getEventStore) => {
export default describe<Events>(".makeEvent", (getEventStore) => {
it("should make and performantly batch insert a list of events directly", async () => {
const { store } = await getEventStore();

View File

@@ -3,12 +3,12 @@ import { it } from "@std/testing/bdd";
import { nanoid } from "nanoid";
import { makeId } from "../../libraries/nanoid.ts";
import type { EventStoreFactory } from "../mocks/events.ts";
import type { Events } from "../mocks/events.ts";
import { userPostReducer } from "../mocks/user-posts-reducer.ts";
import { userReducer } from "../mocks/user-reducer.ts";
import { describe } from "../utilities/describe.ts";
export default describe<EventStoreFactory>(".makeReducer", (getEventStore) => {
export default describe<Events>(".makeReducer", (getEventStore) => {
it("should create a 'user' reducer and only reduce filtered events", async () => {
const { store } = await getEventStore();

View File

@@ -2,10 +2,10 @@ import { assertEquals, assertObjectMatch } from "@std/assert";
import { it } from "@std/testing/bdd";
import { makeId } from "../../libraries/nanoid.ts";
import type { EventStoreFactory } from "../mocks/events.ts";
import type { Events } from "../mocks/events.ts";
import { describe } from "../utilities/describe.ts";
export default describe<EventStoreFactory>("projector.once", (getEventStore) => {
export default describe<Events>("projector.once", (getEventStore) => {
it("should handle successfull projection", async () => {
const { store, projector } = await getEventStore();

View File

@@ -2,10 +2,10 @@ import { assertEquals } from "@std/assert";
import { it } from "@std/testing/bdd";
import { nanoid } from "nanoid";
import type { EventStoreFactory } from "../../mocks/events.ts";
import type { Events } from "../../mocks/events.ts";
import { describe } from "../../utilities/describe.ts";
export default describe<EventStoreFactory>("relations", (getEventStore) => {
export default describe<Events>("relations", (getEventStore) => {
it("should create a new relation", async () => {
const { store } = await getEventStore();

View File

@@ -1,36 +1,38 @@
import { assertEquals, assertObjectMatch } from "@std/assert";
import { it } from "@std/testing/bdd";
import type { EventStoreFactory } from "../mocks/events.ts";
import { User } from "../mocks/aggregates.ts";
import type { Events } from "../mocks/events.ts";
import { userReducer } from "../mocks/user-reducer.ts";
import { describe } from "../utilities/describe.ts";
export default describe<EventStoreFactory>(".pushAggregate", (getEventStore) => {
export default describe<Events>(".pushAggregate", (getEventStore) => {
it("should successfully commit pending aggregate events to the event store", async () => {
const { store } = await getEventStore();
const user = store
.aggregate("user")
.create({ given: "Jane", family: "Doe" }, "jane.doe@fixture.none")
const user = store.aggregate
.from(User)
.setGivenName("Jane")
.setFamilyName("Doe")
.setEmail("jane.doe@fixture.none", "admin")
.setGivenName("John")
.setEmail("john.doe@fixture.none", "admin");
assertEquals(user.toPending().length, 3);
assertEquals(user.toPending().length, 5);
await store.pushAggregate(user);
await user.save();
assertEquals(user.toPending().length, 0);
const records = await store.getEventsByStreams([user.id]);
assertEquals(records.length, 3);
assertEquals(records.length, 5);
assertObjectMatch(records[0], {
stream: user.id,
data: { name: { given: "Jane", family: "Doe" }, email: "jane.doe@fixture.none" },
});
assertObjectMatch(records[1], { stream: user.id, data: "John" });
assertObjectMatch(records[2], { stream: user.id, data: "john.doe@fixture.none", meta: { auditor: "admin" } });
assertObjectMatch(records[0], { stream: user.id, data: "Jane" });
assertObjectMatch(records[1], { stream: user.id, data: "Doe" });
assertObjectMatch(records[2], { stream: user.id, data: "jane.doe@fixture.none", meta: { auditor: "admin" } });
assertObjectMatch(records[3], { stream: user.id, data: "John" });
assertObjectMatch(records[4], { stream: user.id, data: "john.doe@fixture.none", meta: { auditor: "admin" } });
const state = await store.reduce({ name: "user", stream: user.id, reducer: userReducer });

View File

@@ -1,50 +1,53 @@
import { assertEquals, assertObjectMatch } from "@std/assert";
import { it } from "@std/testing/bdd";
import type { EventStoreFactory } from "../mocks/events.ts";
import { User } from "../mocks/aggregates.ts";
import type { Events } from "../mocks/events.ts";
import { userReducer } from "../mocks/user-reducer.ts";
import { describe } from "../utilities/describe.ts";
export default describe<EventStoreFactory>(".pushManyAggregates", (getEventStore) => {
export default describe<Events>(".pushManyAggregates", (getEventStore) => {
it("should successfully commit pending aggregates events to the event store", async () => {
const { store } = await getEventStore();
const userA = store
.aggregate("user")
.create({ given: "Jane", family: "Doe" }, "jane.doe@fixture.none")
const userA = store.aggregate
.from(User)
.setGivenName("Jane")
.setFamilyName("Doe")
.setEmail("jane.doe@fixture.none", "admin")
.setGivenName("John")
.setEmail("john.doe@fixture.none", "admin");
const userB = store
.aggregate("user")
.create({ given: "Peter", family: "Doe" }, "peter.doe@fixture.none")
const userB = store.aggregate
.from(User)
.setGivenName("Peter")
.setFamilyName("Doe")
.setEmail("peter.doe@fixture.none", "admin")
.setGivenName("Barry")
.setEmail("barry.doe@fixture.none", "admin");
assertEquals(userA.toPending().length, 3);
assertEquals(userB.toPending().length, 3);
assertEquals(userA.toPending().length, 5);
assertEquals(userB.toPending().length, 5);
await store.pushManyAggregates([userA, userB]);
await store.aggregate.push([userA, userB]);
assertEquals(userA.toPending().length, 0);
assertEquals(userB.toPending().length, 0);
const records = await store.getEventsByStreams([userA.id, userB.id]);
assertEquals(records.length, 6);
assertEquals(records.length, 10);
assertObjectMatch(records[0], {
stream: userA.id,
data: { name: { given: "Jane", family: "Doe" }, email: "jane.doe@fixture.none" },
});
assertObjectMatch(records[1], { stream: userA.id, data: "John" });
assertObjectMatch(records[2], { stream: userA.id, data: "john.doe@fixture.none", meta: { auditor: "admin" } });
assertObjectMatch(records[3], {
stream: userB.id,
data: { name: { given: "Peter", family: "Doe" }, email: "peter.doe@fixture.none" },
});
assertObjectMatch(records[4], { stream: userB.id, data: "Barry" });
assertObjectMatch(records[5], { stream: userB.id, data: "barry.doe@fixture.none", meta: { auditor: "admin" } });
assertObjectMatch(records[0], { stream: userA.id, data: "Jane" });
assertObjectMatch(records[1], { stream: userA.id, data: "Doe" });
assertObjectMatch(records[2], { stream: userA.id, data: "jane.doe@fixture.none", meta: { auditor: "admin" } });
assertObjectMatch(records[3], { stream: userA.id, data: "John" });
assertObjectMatch(records[4], { stream: userA.id, data: "john.doe@fixture.none", meta: { auditor: "admin" } });
assertObjectMatch(records[5], { stream: userB.id, data: "Peter" });
assertObjectMatch(records[6], { stream: userB.id, data: "Doe" });
assertObjectMatch(records[7], { stream: userB.id, data: "peter.doe@fixture.none", meta: { auditor: "admin" } });
assertObjectMatch(records[8], { stream: userB.id, data: "Barry" });
assertObjectMatch(records[9], { stream: userB.id, data: "barry.doe@fixture.none", meta: { auditor: "admin" } });
const stateA = await store.reduce({ name: "user", stream: userA.id, reducer: userReducer });
const stateB = await store.reduce({ name: "user", stream: userB.id, reducer: userReducer });

View File

@@ -2,11 +2,11 @@ import { assertEquals } from "@std/assert";
import { it } from "@std/testing/bdd";
import { nanoid } from "nanoid";
import type { EventStoreFactory } from "../mocks/events.ts";
import type { Events } from "../mocks/events.ts";
import { userReducer } from "../mocks/user-reducer.ts";
import { describe } from "../utilities/describe.ts";
export default describe<EventStoreFactory>(".reduce", (getEventStore) => {
export default describe<Events>(".reduce", (getEventStore) => {
it("should return reduced state", async () => {
const { store } = await getEventStore();
const stream = nanoid();

View File

@@ -2,10 +2,10 @@ import { assertObjectMatch } from "@std/assert";
import { it } from "@std/testing/bdd";
import { nanoid } from "nanoid";
import { EventStoreFactory } from "../mocks/events.ts";
import type { Events } from "../mocks/events.ts";
import { describe } from "../utilities/describe.ts";
export default describe<EventStoreFactory>(".replayEvents", (getEventStore) => {
export default describe<Events>(".replayEvents", (getEventStore) => {
it("should replay events", async () => {
const { store, projector } = await getEventStore();
const stream = nanoid();