From 393afd58d61205416226fbb86d8ce0998e99285c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Christoffer=20R=C3=B8dvik?= Date: Tue, 12 Aug 2025 05:07:16 +0200 Subject: [PATCH] feat: add epoch event stream to aggregate id --- deno.json | 2 +- deno.lock | 8 +------- libraries/aggregate.ts | 21 ++++----------------- libraries/event.ts | 11 ++++++----- libraries/nanoid.ts | 10 ---------- libraries/reducer.ts | 1 + mod.ts | 1 - package.json | 1 - tests/store/add-event.ts | 9 ++++----- tests/store/add-many-events.ts | 5 ++--- tests/store/create-snapshot.ts | 3 +-- tests/store/make-reducer.ts | 14 ++++++-------- tests/store/once-projection.ts | 5 ++--- tests/store/providers/relations.ts | 5 ++--- tests/store/reduce.ts | 7 +++---- tests/store/replay-events.ts | 3 +-- 16 files changed, 34 insertions(+), 72 deletions(-) delete mode 100644 libraries/nanoid.ts diff --git a/deno.json b/deno.json index 3433af3..1350dfc 100644 --- a/deno.json +++ b/deno.json @@ -1,6 +1,6 @@ { "name": "@valkyr/event-store", - "version": "2.0.0-beta.5", + "version": "2.0.0-beta.6", "exports": { ".": "./mod.ts", "./browser": "./adapters/browser/adapter.ts", diff --git a/deno.lock b/deno.lock index 81a4804..46c3698 100644 --- a/deno.lock +++ b/deno.lock @@ -10,7 +10,6 @@ "npm:eslint@9": "9.33.0", "npm:fake-indexeddb@6": "6.1.0", "npm:mongodb@6": "6.18.0", - "npm:nanoid@5": "5.1.5", "npm:postgres@3": "3.4.7", "npm:prettier@3": "3.6.2", "npm:typescript-eslint@8": "8.39.0_eslint@9.33.0_typescript@5.9.2_@typescript-eslint+parser@8.39.0__eslint@9.33.0__typescript@5.9.2", @@ -299,7 +298,7 @@ "fast-equals", "idb", "mingo", - "nanoid@5.0.2", + "nanoid", "rfdc", "rxjs" ] @@ -694,10 +693,6 @@ "integrity": "sha512-2ustYUX1R2rL/Br5B/FMhi8d5/QzvkJ912rBYxskcpu0myTHzSZfTr1LAS2Sm7jxRUObRrSBFoyzwAhL49aVSg==", "bin": true }, - "nanoid@5.1.5": { - "integrity": "sha512-Ir/+ZpE9fDsNH0hQ3C68uyThDXzYcim2EqcZ8zn8Chtt1iylPT9xXJB0kPCnqzgcEGikO9RxSrh63MsmVCU7Fw==", - "bin": true - }, "natural-compare@1.4.0": { "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==" }, @@ -894,7 +889,6 @@ "npm:eslint@9", "npm:fake-indexeddb@6", "npm:mongodb@6", - "npm:nanoid@5", "npm:postgres@3", "npm:prettier@3", "npm:typescript-eslint@8", diff --git a/libraries/aggregate.ts b/libraries/aggregate.ts index 2d14b12..5a962dd 100644 --- a/libraries/aggregate.ts +++ b/libraries/aggregate.ts @@ -1,6 +1,6 @@ import type { AnyEventStore, EventsInsertSettings } from "../libraries/event-store.ts"; import type { Unknown } from "../types/common.ts"; -import { AggregateSnapshotViolation, AggregateStreamViolation } from "./errors.ts"; +import { AggregateSnapshotViolation } from "./errors.ts"; import { EventFactory } from "./event-factory.ts"; /** @@ -27,7 +27,7 @@ export abstract class AggregateRoot { /** * Primary unique identifier for the stream the aggregate belongs to. */ - #stream?: string; + id: string; /** * List of pending records to push to the parent event store. @@ -40,6 +40,7 @@ export abstract class AggregateRoot { * @param store - Store this aggregate instance acts against. */ constructor(store: AnyEventStore) { + this.id = crypto.randomUUID(); this.#store = store; } @@ -47,20 +48,6 @@ export abstract class AggregateRoot { // Accessors // ------------------------------------------------------------------------- - set id(value: string) { - if (this.#stream !== undefined) { - throw new AggregateStreamViolation(this.constructor.name); - } - this.#stream = value; - } - - get id(): string { - if (this.#stream === undefined) { - this.#stream = crypto.randomUUID(); - } - return this.#stream; - } - get #self(): typeof AggregateRoot { return this.constructor as typeof AggregateRoot; } @@ -147,7 +134,7 @@ export abstract class AggregateRoot { * any pending events, make sure to `.save()` before snapshotting. */ async snapshot() { - const stream = this.#stream; + const stream = this.id; if (stream === undefined) { throw new AggregateSnapshotViolation(this.#self.name); } diff --git a/libraries/event.ts b/libraries/event.ts index 7068116..a0fd23c 100644 --- a/libraries/event.ts +++ b/libraries/event.ts @@ -1,7 +1,6 @@ import z, { ZodType } from "zod"; import { EventValidationError } from "./errors.ts"; -import { makeId } from "./nanoid.ts"; import { getLogicalTimestamp } from "./time.ts"; import { toPrettyErrorLines } from "./zod.ts"; @@ -41,8 +40,8 @@ export class Event { const timestamp = getLogicalTimestamp(); const record = { - id: makeId(), - stream: payload.stream ?? makeId(), + id: crypto.randomUUID(), + stream: payload.stream ?? crypto.randomUUID(), type: this.state.type, data: "data" in payload ? payload.data : null, meta: "meta" in payload ? payload.meta : null, @@ -134,13 +133,13 @@ export type EventRecord = { /** * A unique event identifier. */ - id: string; + id: UUID; /** * Event streams are used to group related events together. This identifier * is used to identify the stream to which the event belongs. */ - stream: string; + stream: UUID; /** * Type refers to the purpose of the event in a past tense descibing something @@ -203,3 +202,5 @@ export type EventStatus = { */ outdated: boolean; }; + +export type UUID = `${string}-${string}-${string}-${string}-${string}`; diff --git a/libraries/nanoid.ts b/libraries/nanoid.ts deleted file mode 100644 index bc14a69..0000000 --- a/libraries/nanoid.ts +++ /dev/null @@ -1,10 +0,0 @@ -import { nanoid } from "nanoid"; - -/** - * Generate a new nanoid. - * - * @param size - Size of the id. Default: 11 - */ -export function makeId(size: number = 11): string { - return nanoid(size); -} diff --git a/libraries/reducer.ts b/libraries/reducer.ts index 4716b2c..e2f95bd 100644 --- a/libraries/reducer.ts +++ b/libraries/reducer.ts @@ -19,6 +19,7 @@ export function makeAggregateReducer< }, reduce(events: TEventFactory["$events"][number]["$record"][], snapshot?: Unknown) { const instance = aggregate.from(store, snapshot); + instance.id = events[0].stream; for (const event of events) { instance.with(event); } diff --git a/mod.ts b/mod.ts index 81d15d3..1594959 100644 --- a/mod.ts +++ b/mod.ts @@ -3,7 +3,6 @@ export * from "./libraries/errors.ts"; export * from "./libraries/event.ts"; export * from "./libraries/event-factory.ts"; export * from "./libraries/event-store.ts"; -export * from "./libraries/nanoid.ts"; export * from "./libraries/projector.ts"; export * from "./libraries/queue.ts"; export * from "./libraries/reducer.ts"; diff --git a/package.json b/package.json index 96046f3..4ac4021 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,6 @@ "dependencies": { "@valkyr/db": "1.0.1", "mongodb": "6", - "nanoid": "5", "postgres": "3", "zod": "4" }, diff --git a/tests/store/add-event.ts b/tests/store/add-event.ts index c79d035..5fc4721 100644 --- a/tests/store/add-event.ts +++ b/tests/store/add-event.ts @@ -2,7 +2,6 @@ import { assertEquals, assertObjectMatch, assertRejects } from "@std/assert"; import { it } from "@std/testing/bdd"; import { EventInsertionError, EventValidationError } from "../../libraries/errors.ts"; -import { makeId } from "../../libraries/nanoid.ts"; import type { Events } from "../mocks/events.ts"; import { describe } from "../utilities/describe.ts"; @@ -58,7 +57,7 @@ export default describe(".addEvent", (getEventStore) => { it("should insert and project 'user:created' event", async () => { const { store, projector } = await getEventStore(); - const stream = makeId(); + const stream = crypto.randomUUID(); const event = store.event({ stream, type: "user:created", @@ -93,7 +92,7 @@ export default describe(".addEvent", (getEventStore) => { }, }); - const stream = makeId(); + const stream = crypto.randomUUID(); const event = store.event({ stream, type: "user:created", @@ -121,7 +120,7 @@ export default describe(".addEvent", (getEventStore) => { it("should insert 'user:created' and add it to 'tenant:xyz' relation", async () => { const { store, projector } = await getEventStore(); - const key = `tenant:${makeId()}`; + const key = `tenant:${crypto.randomUUID()}`; projector.on("user:created", async ({ stream }) => { await store.relations.insert(key, stream); @@ -171,7 +170,7 @@ export default describe(".addEvent", (getEventStore) => { it("should insert 'user:email-set' and remove it from 'tenant:xyz' relations", async () => { const { store, projector } = await getEventStore(); - const key = `tenant:${makeId()}`; + const key = `tenant:${crypto.randomUUID()}`; projector.on("user:created", async ({ stream }) => { await store.relations.insert(key, stream); diff --git a/tests/store/add-many-events.ts b/tests/store/add-many-events.ts index 8508ff2..7052995 100644 --- a/tests/store/add-many-events.ts +++ b/tests/store/add-many-events.ts @@ -1,6 +1,5 @@ import { assertEquals, assertObjectMatch, assertRejects } from "@std/assert"; import { it } from "@std/testing/bdd"; -import { nanoid } from "nanoid"; import { EventValidationError } from "../../mod.ts"; import type { Events } from "../mocks/events.ts"; @@ -10,7 +9,7 @@ import { describe } from "../utilities/describe.ts"; export default describe(".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(); + const stream = crypto.randomUUID(); const events = [ store.event({ @@ -63,7 +62,7 @@ export default describe(".addSequence", (getEventStore) => { it("should not commit any events when insert fails", async () => { const { store } = await getEventStore(); - const stream = nanoid(); + const stream = crypto.randomUUID(); await assertRejects( async () => diff --git a/tests/store/create-snapshot.ts b/tests/store/create-snapshot.ts index 0de245f..af44464 100644 --- a/tests/store/create-snapshot.ts +++ b/tests/store/create-snapshot.ts @@ -1,6 +1,5 @@ import { assertEquals, assertNotEquals, assertObjectMatch } from "@std/assert"; import { it } from "@std/testing/bdd"; -import { nanoid } from "nanoid"; import type { Events } from "../mocks/events.ts"; import { userReducer } from "../mocks/user-reducer.ts"; @@ -9,7 +8,7 @@ import { describe } from "../utilities/describe.ts"; export default describe(".createSnapshot", (getEventStore) => { it("should create a new snapshot", async () => { const { store } = await getEventStore(); - const stream = nanoid(); + const stream = crypto.randomUUID(); await store.pushEvent( store.event({ diff --git a/tests/store/make-reducer.ts b/tests/store/make-reducer.ts index 0b0c616..8ac31f6 100644 --- a/tests/store/make-reducer.ts +++ b/tests/store/make-reducer.ts @@ -1,8 +1,6 @@ import { assertEquals } from "@std/assert"; import { it } from "@std/testing/bdd"; -import { nanoid } from "nanoid"; -import { makeId } from "../../libraries/nanoid.ts"; import type { Events } from "../mocks/events.ts"; import { userPostReducer } from "../mocks/user-posts-reducer.ts"; import { userReducer } from "../mocks/user-reducer.ts"; @@ -12,8 +10,8 @@ export default describe(".makeReducer", (getEventStore) => { it("should create a 'user' reducer and only reduce filtered events", async () => { const { store } = await getEventStore(); - const streamA = nanoid(); - const streamB = nanoid(); + const streamA = crypto.randomUUID(); + const streamB = crypto.randomUUID(); await store.pushEvent( store.event({ @@ -95,15 +93,15 @@ export default describe(".makeReducer", (getEventStore) => { it("should create a 'post:count' reducer and retrieve post correct post count", async () => { const { store, projector } = await getEventStore(); - const auditor = nanoid(); + const auditor = crypto.randomUUID(); projector.on("post:created", async ({ stream, meta: { auditor } }) => { await store.relations.insert(`user:${auditor}:posts`, stream); }); - const post1 = makeId(); - const post2 = makeId(); - const post3 = makeId(); + const post1 = crypto.randomUUID(); + const post2 = crypto.randomUUID(); + const post3 = crypto.randomUUID(); await store.pushEvent( store.event({ diff --git a/tests/store/once-projection.ts b/tests/store/once-projection.ts index c36484d..a51a364 100644 --- a/tests/store/once-projection.ts +++ b/tests/store/once-projection.ts @@ -1,7 +1,6 @@ import { assertEquals, assertObjectMatch } from "@std/assert"; import { it } from "@std/testing/bdd"; -import { makeId } from "../../libraries/nanoid.ts"; import type { Events } from "../mocks/events.ts"; import { describe } from "../utilities/describe.ts"; @@ -9,7 +8,7 @@ export default describe("projector.once", (getEventStore) => { it("should handle successfull projection", async () => { const { store, projector } = await getEventStore(); - const stream = makeId(); + const stream = crypto.randomUUID(); const event = store.event({ stream, type: "user:created", @@ -51,7 +50,7 @@ export default describe("projector.once", (getEventStore) => { it("should handle failed projection", async () => { const { store, projector } = await getEventStore(); - const stream = makeId(); + const stream = crypto.randomUUID(); const event = store.event({ stream, type: "user:created", diff --git a/tests/store/providers/relations.ts b/tests/store/providers/relations.ts index 522d9bd..c260912 100644 --- a/tests/store/providers/relations.ts +++ b/tests/store/providers/relations.ts @@ -1,6 +1,5 @@ import { assertEquals } from "@std/assert"; import { it } from "@std/testing/bdd"; -import { nanoid } from "nanoid"; import type { Events } from "../../mocks/events.ts"; import { describe } from "../../utilities/describe.ts"; @@ -10,7 +9,7 @@ export default describe("relations", (getEventStore) => { const { store } = await getEventStore(); const key = "sample"; - const stream = nanoid(); + const stream = crypto.randomUUID(); await store.relations.insert(key, stream); @@ -21,7 +20,7 @@ export default describe("relations", (getEventStore) => { const { store } = await getEventStore(); const key = "sample"; - const stream = nanoid(); + const stream = crypto.randomUUID(); await store.relations.insertMany([ { key, stream }, diff --git a/tests/store/reduce.ts b/tests/store/reduce.ts index a834311..c2be25c 100644 --- a/tests/store/reduce.ts +++ b/tests/store/reduce.ts @@ -1,6 +1,5 @@ import { assertEquals } from "@std/assert"; import { it } from "@std/testing/bdd"; -import { nanoid } from "nanoid"; import type { Events } from "../mocks/events.ts"; import { userReducer } from "../mocks/user-reducer.ts"; @@ -9,7 +8,7 @@ import { describe } from "../utilities/describe.ts"; export default describe(".reduce", (getEventStore) => { it("should return reduced state", async () => { const { store } = await getEventStore(); - const stream = nanoid(); + const stream = crypto.randomUUID(); await store.pushEvent( store.event({ @@ -51,7 +50,7 @@ export default describe(".reduce", (getEventStore) => { it("should return snapshot if it exists and no new events were found", async () => { const { store } = await getEventStore(); - const stream = nanoid(); + const stream = crypto.randomUUID(); await store.pushEvent( store.event({ @@ -94,7 +93,7 @@ export default describe(".reduce", (getEventStore) => { }); it("should return undefined if stream does not have events", async () => { - const stream = nanoid(); + const stream = crypto.randomUUID(); const { store } = await getEventStore(); const state = await store.reduce({ name: "user", stream, reducer: userReducer }); diff --git a/tests/store/replay-events.ts b/tests/store/replay-events.ts index 333cc1e..30590b4 100644 --- a/tests/store/replay-events.ts +++ b/tests/store/replay-events.ts @@ -1,6 +1,5 @@ import { assertObjectMatch } from "@std/assert"; import { it } from "@std/testing/bdd"; -import { nanoid } from "nanoid"; import type { Events } from "../mocks/events.ts"; import { describe } from "../utilities/describe.ts"; @@ -8,7 +7,7 @@ import { describe } from "../utilities/describe.ts"; export default describe(".replayEvents", (getEventStore) => { it("should replay events", async () => { const { store, projector } = await getEventStore(); - const stream = nanoid(); + const stream = crypto.randomUUID(); const record: Record = {};