Refactor event interface to use VariantTag for event types

This commit is contained in:
Pablo Baleztena 2024-10-15 07:45:11 -03:00
parent 4ea00f515b
commit 38e23ba095
5 changed files with 42 additions and 29 deletions

View File

@ -7,7 +7,7 @@ export interface TaggedVariant<TTag extends string> {
export type VariantFromTag<
TVariant extends TaggedVariant<string>,
TTag extends TVariant[typeof VariantTag],
TTag extends TVariant[VariantTag],
> = Extract<TVariant, { [VariantTag]: TTag }>;
export namespace Variant {

View File

@ -1,7 +1,13 @@
import { AsyncResult, MaybePromise, PosixDate } from "@fabric/core";
import {
AsyncResult,
MaybePromise,
PosixDate,
VariantFromTag,
VariantTag,
} from "@fabric/core";
import { StoreQueryError } from "../errors/query-error.js";
import { UUID } from "../types/uuid.js";
import { Event, EventFromKey } from "./event.js";
import { Event } from "./event.js";
import { StoredEvent } from "./stored-event.js";
export interface EventStore<TEvents extends Event> {
@ -16,9 +22,9 @@ export interface EventStore<TEvents extends Event> {
streamId: UUID,
): AsyncResult<StoredEvent<TEvents>[], StoreQueryError>;
subscribe<TEventKey extends TEvents["type"]>(
subscribe<TEventKey extends TEvents[VariantTag]>(
events: TEventKey[],
subscriber: EventSubscriber<EventFromKey<TEvents, TEventKey>>,
subscriber: EventSubscriber<VariantFromTag<TEvents, TEventKey>>,
): void;
}

View File

@ -1,11 +1,12 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
import { VariantTag } from "@fabric/core";
import { UUID } from "../types/uuid.js";
/**
* An event is a tagged variant with a payload and a timestamp.
*/
export interface Event<TTag extends string = string, TPayload = any> {
readonly type: TTag;
readonly [VariantTag]: TTag;
readonly id: UUID;
readonly streamId: UUID;
readonly payload: TPayload;
@ -13,5 +14,5 @@ export interface Event<TTag extends string = string, TPayload = any> {
export type EventFromKey<
TEvents extends Event,
TKey extends TEvents["type"],
> = Extract<TEvents, { type: TKey }>;
TKey extends TEvents[VariantTag],
> = Extract<TEvents, { [VariantTag]: TKey }>;

View File

@ -26,7 +26,7 @@ describe("Event Store", () => {
const newUUID = UUIDGeneratorMock.generate();
const userCreated: UserCreated = {
type: "UserCreated",
_tag: "UserCreated",
id: newUUID,
streamId: newUUID,
payload: { name: "test" },
@ -41,7 +41,7 @@ describe("Event Store", () => {
expect(events[0]).toEqual({
id: newUUID,
streamId: newUUID,
type: "UserCreated",
_tag: "UserCreated",
version: BigInt(1),
timestamp: expect.any(PosixDate),
payload: { name: "test" },
@ -52,7 +52,7 @@ describe("Event Store", () => {
const newUUID = UUIDGeneratorMock.generate();
const userCreated: UserCreated = {
type: "UserCreated",
_tag: "UserCreated",
id: newUUID,
streamId: newUUID,
payload: { name: "test" },
@ -68,7 +68,7 @@ describe("Event Store", () => {
expect(subscriber).toHaveBeenCalledWith({
id: newUUID,
streamId: newUUID,
type: "UserCreated",
_tag: "UserCreated",
version: BigInt(1),
timestamp: expect.any(PosixDate),
payload: { name: "test" },

View File

@ -1,4 +1,10 @@
import { AsyncResult, MaybePromise, PosixDate, Run } from "@fabric/core";
import {
AsyncResult,
MaybePromise,
PosixDate,
Run,
VariantTag,
} from "@fabric/core";
import {
Event,
EventFromKey,
@ -19,7 +25,7 @@ export class SQLiteEventStore<TEvents extends Event>
private streamVersions = new Map<UUID, bigint>();
private eventSubscribers = new Map<
TEvents["type"],
TEvents[VariantTag],
EventSubscriber<TEvents>[]
>();
@ -34,7 +40,7 @@ export class SQLiteEventStore<TEvents extends Event>
await this.db.run(
`CREATE TABLE IF NOT EXISTS events (
id TEXT PRIMARY KEY,
type TEXT NOT NULL,
_tag TEXT NOT NULL,
streamId TEXT NOT NULL,
version INTEGER NOT NULL,
timestamp NUMERIC NOT NULL,
@ -57,13 +63,13 @@ export class SQLiteEventStore<TEvents extends Event>
{
$id: streamId,
},
(event) => ({
id: event.id,
streamId: event.streamId,
type: event.type,
version: BigInt(event.version),
timestamp: new PosixDate(event.timestamp),
payload: JSONUtils.parse(event.payload),
(e) => ({
id: e.id,
streamId: e.streamId,
_tag: e._tag,
version: BigInt(e.version),
timestamp: new PosixDate(e.timestamp),
payload: JSONUtils.parse(e.payload),
}),
);
return events;
@ -95,7 +101,7 @@ export class SQLiteEventStore<TEvents extends Event>
event: StoredEvent<TEvents>,
): AsyncResult<void> {
return AsyncResult.from(async () => {
const subscribers = this.eventSubscribers.get(event.type) || [];
const subscribers = this.eventSubscribers.get(event[VariantTag]) || [];
await Promise.all(subscribers.map((subscriber) => subscriber(event)));
});
}
@ -121,13 +127,13 @@ export class SQLiteEventStore<TEvents extends Event>
);
}
subscribe<TEventKey extends TEvents["type"]>(
events: TEventKey[],
subscribe<TEventKey extends TEvents[VariantTag]>(
eventNames: TEventKey[],
subscriber: (
event: StoredEvent<EventFromKey<TEvents, TEventKey>>,
) => MaybePromise<void>,
): void {
events.forEach((event) => {
eventNames.forEach((event) => {
const subscribers = this.eventSubscribers.get(event) || [];
const newSubscribers = [
...subscribers,
@ -157,12 +163,12 @@ export class SQLiteEventStore<TEvents extends Event>
timestamp: new PosixDate(),
};
await this.db.runPrepared(
`INSERT INTO events (id, streamId, type, version, timestamp, payload)
VALUES ($id, $streamId, $type, $version, $timestamp, $payload)`,
`INSERT INTO events (id, streamId, _tag, version, timestamp, payload)
VALUES ($id, $streamId, $_tag, $version, $timestamp, $payload)`,
{
$id: storedEvent.id,
$streamId: streamId,
$type: storedEvent.type,
$_tag: storedEvent[VariantTag],
$version: storedEvent.version.toString(),
$timestamp: storedEvent.timestamp.timestamp,
$payload: JSON.stringify(storedEvent.payload),