woltage
Version:
A CQRS and Event-Sourcing Framework
82 lines (81 loc) • 3.08 kB
JavaScript
import { randomUUID } from 'crypto';
import { z } from 'zod/v4';
import { projectionStorage } from "./localStorages.js";
import { getEventClass } from "./eventMap.js";
class Event {
static toString() {
return this.name.replace(/\W+/g, ' ')
.split(/ |\B(?=[A-Z])/)
.map(word => word.toLowerCase())
.join('.');
}
static get type() {
return this.toString();
}
static get identity() {
return JSON.stringify({
type: this.type,
version: this.version,
});
}
static validate(payload) {
return this.schema.parse(payload);
}
static fromJSON(data, shouldValidate = false) {
return new (getEventClass(data.type, data.version))(data, shouldValidate);
}
constructor(data, shouldValidate = true) {
if (typeof this.version !== 'number' || this.version <= 0)
throw new Error('Event\'s version property must be a number > 0.');
if (typeof this.constructor.schema?.parse !== 'function')
throw new Error(`Missing schema for event class '${this.type}@${this.version}'.`);
if ('type' in data && this.type !== data.type)
throw new Error(`Event type does not match event data ("${this.type}" not equal "${data.type}").`);
if ('version' in data && this.version !== data.version)
throw new Error(`Event version does not match event data ("${this.version}" not equal "${data.version}").`);
const { id = randomUUID(), timestamp = Date.now(), aggregateId, payload, correlationId, causationId, meta = {}, position = -1n } = data;
this.id = id;
this.timestamp = timestamp;
this.aggregateId = aggregateId;
this.payload = payload;
const currentEvent = projectionStorage.getStore()?.currentEvent;
this.correlationId = correlationId ?? currentEvent?.correlationId ?? id;
this.causationId = causationId ?? currentEvent?.id ?? null;
this.meta = meta;
this.position = position;
this.payload = shouldValidate
? this.constructor.validate(data.payload)
: data.payload;
}
toString() {
return this.constructor.toString();
}
get type() {
return this.toString();
}
get version() {
return this.constructor.version;
}
get identity() {
return this.constructor.identity;
}
toJSON() {
if (!this.aggregateId)
throw new Error(`Invalid event state. Missing aggregate ID for '${this.type}@${this.version}' event.`);
return {
id: this.id,
type: this.type,
version: this.version,
timestamp: this.timestamp,
aggregateId: this.aggregateId,
payload: structuredClone(this.payload),
correlationId: this.correlationId,
causationId: this.causationId,
meta: this.meta,
position: this.position
};
}
}
Event.schema = z.any();
Event.version = -1;
export default Event;