feat: add epoch event stream to aggregate id
This commit is contained in:
@@ -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",
|
||||
|
||||
8
deno.lock
generated
8
deno.lock
generated
@@ -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",
|
||||
|
||||
@@ -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<TEventFactory extends EventFactory> {
|
||||
/**
|
||||
* 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<TEventFactory extends EventFactory> {
|
||||
* @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<TEventFactory extends EventFactory> {
|
||||
// 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<TEventFactory> {
|
||||
return this.constructor as typeof AggregateRoot<TEventFactory>;
|
||||
}
|
||||
@@ -147,7 +134,7 @@ export abstract class AggregateRoot<TEventFactory extends EventFactory> {
|
||||
* 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);
|
||||
}
|
||||
|
||||
@@ -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<TEventState extends EventState = EventState> {
|
||||
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<TEvent extends EventState = EventState> = {
|
||||
/**
|
||||
* 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}`;
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
1
mod.ts
1
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";
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
"dependencies": {
|
||||
"@valkyr/db": "1.0.1",
|
||||
"mongodb": "6",
|
||||
"nanoid": "5",
|
||||
"postgres": "3",
|
||||
"zod": "4"
|
||||
},
|
||||
|
||||
@@ -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<Events>(".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<Events>(".addEvent", (getEventStore) => {
|
||||
},
|
||||
});
|
||||
|
||||
const stream = makeId();
|
||||
const stream = crypto.randomUUID();
|
||||
const event = store.event({
|
||||
stream,
|
||||
type: "user:created",
|
||||
@@ -121,7 +120,7 @@ export default describe<Events>(".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<Events>(".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);
|
||||
|
||||
@@ -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<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();
|
||||
const stream = crypto.randomUUID();
|
||||
|
||||
const events = [
|
||||
store.event({
|
||||
@@ -63,7 +62,7 @@ export default describe<Events>(".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 () =>
|
||||
|
||||
@@ -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<Events>(".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({
|
||||
|
||||
@@ -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<Events>(".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<Events>(".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({
|
||||
|
||||
@@ -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<Events>("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<Events>("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",
|
||||
|
||||
@@ -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<Events>("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<Events>("relations", (getEventStore) => {
|
||||
const { store } = await getEventStore();
|
||||
|
||||
const key = "sample";
|
||||
const stream = nanoid();
|
||||
const stream = crypto.randomUUID();
|
||||
|
||||
await store.relations.insertMany([
|
||||
{ key, stream },
|
||||
|
||||
@@ -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<Events>(".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<Events>(".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<Events>(".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 });
|
||||
|
||||
|
||||
@@ -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<Events>(".replayEvents", (getEventStore) => {
|
||||
it("should replay events", async () => {
|
||||
const { store, projector } = await getEventStore();
|
||||
const stream = nanoid();
|
||||
const stream = crypto.randomUUID();
|
||||
|
||||
const record: Record<string, any> = {};
|
||||
|
||||
|
||||
Reference in New Issue
Block a user