castore-dev / castore

Making Event Sourcing easy 😎
MIT License
230 stars 19 forks source link

Handle arrays of event spreading in EventStore.pushEventGroup #177

Open jeremycare opened 10 months ago

jeremycare commented 10 months ago

Describe the bug A clear and concise description of what the bug is.

We are caching the events before sending them to the eventStore.pushGroupEvent

Our Encapsulation of Castore ```typescript import { Reducer, EventStore as CastoreEventStoreImpl, EventType, EventDetail, EventTypeDetails, $Contravariant, Aggregate, EventStorageAdapter, OptionalTimestamp, } from '@castore/core'; import { inject } from '@warehouse/di-container'; import { EventStore } from '../EventStore'; import { AggregateRoot } from '../AggregateRoot'; import DateAdapterImpl, { DateAdapter } from '../DateAdapter'; import { storageAdapterToken } from '../StorageAdapters/StorageAdapter'; export class EventStoreImpl< EVENT_STORE_ID extends string = string, EVENT_TYPES extends EventType[] = EventType[], EVENT_DETAILS extends EventDetail = EventTypeDetails, $EVENT_DETAILS extends EventDetail = $Contravariant, REDUCER extends Reducer = Reducer, AGGREGATE extends Aggregate = ReturnType, $AGGREGATE extends Aggregate = $Contravariant, AGGREGATE_CLASS extends AggregateRoot = AggregateRoot, > implements EventStore { private castoreEventStore: CastoreEventStoreImpl; private readonly factory: (state: AGGREGATE) => AGGREGATE_CLASS; private readonly dateAdapter: DateAdapter; constructor({ eventStoreId, eventTypes, mutate, eventStorageAdapter = inject(storageAdapterToken), factory, dateAdapter = new DateAdapterImpl(), }: { eventStoreId: EVENT_STORE_ID; eventTypes: EVENT_TYPES; mutate: REDUCER; eventStorageAdapter?: EventStorageAdapter; factory: (state: AGGREGATE) => AGGREGATE_CLASS; dateAdapter?: DateAdapter; }) { this.castoreEventStore = new CastoreEventStoreImpl({ eventStoreId, eventTypes, reducer: mutate, eventStorageAdapter, }); this.factory = factory; this.dateAdapter = dateAdapter; } public async getState(id: string): Promise { const state = await this.castoreEventStore.getAggregate(id); return state.aggregate; } public async getAggregate(id: string): Promise { const state = await this.castoreEventStore.getAggregate(id); if (!state.aggregate) { return undefined; } return this.factory(state.aggregate); } public async getEvents(id: string): Promise { const aggregate = await this.castoreEventStore.getAggregate(id); return aggregate.events; } public async saveEvents(events: OptionalTimestamp[]): Promise { if (events.length === 0) { return; } const eventsWithTimestamp = events.map((event) => this.assignTimestamp(event)); // as OptionalTimestamp cast because of a weird typescript issue where EVENT_DETAILS is not recognized as a valid OptionalTimestamp const groupedEvents = eventsWithTimestamp.map((event) => this.castoreEventStore.groupEvent(event as EVENT_DETAILS)); await CastoreEventStoreImpl.pushEventGroup({}, ...groupedEvents); } public async save(aggregate: AGGREGATE_CLASS): Promise { const events = aggregate.popChanges(); await this.saveEvents(events); } private assignTimestamp(event: OptionalTimestamp): EVENT_DETAILS { if (event.timestamp) { return event; } return { ...event, timestamp: this.dateAdapter.now() }; } } ```

To Reproduce

const pushArrayPokemonsEventGroupWithOptions = () => {
  const arrayOfPokemonsEvent = [
    pokemonsEventStore.groupEvent(pikachuAppearedEvent),
    pokemonsEventStore.groupEvent(pikachuCaughtEvent),
  ];

  return EventStore.pushEventGroup(
    { force: true },
    ...arrayOfPokemonsEvent,
  );
};

const assertPushArrayPokemonsEventGroupWithOptions: A.Equals<
  Awaited<ReturnType<typeof pushArrayPokemonsEventGroupWithOptions>>,
  {
    eventGroup: [
      { event: PokemonEventDetails; nextAggregate?: PokemonAggregate },
      { event: PokemonEventDetails; nextAggregate?: PokemonAggregate },
    ];
  }
> = 1;
assertPushArrayPokemonsEventGroupWithOptions;
Screenshot 2024-01-10 at 12 15 35

Expected behavior

Typescript should understand that we have a rest parameter, but it doesn't. It wasn't like that before upgrading to v2. I suspect it to be related to the addition of the option parameter.

Additional context Add any other context about the problem here.