castore-dev/castore

Handle arrays of event spreading in EventStore.pushEventGroup

jeremycare opened this issue · 0 comments

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
    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_TYPES>,
      $EVENT_DETAILS extends EventDetail = $Contravariant<EVENT_DETAILS, EventDetail>,
      REDUCER extends Reducer<Aggregate, $EVENT_DETAILS> = Reducer<Aggregate, $EVENT_DETAILS>,
      AGGREGATE extends Aggregate = ReturnType<REDUCER>,
      $AGGREGATE extends Aggregate = $Contravariant<AGGREGATE, Aggregate>,
      AGGREGATE_CLASS extends AggregateRoot<AGGREGATE, EVENT_DETAILS> = AggregateRoot<AGGREGATE, EVENT_DETAILS>,
    > implements EventStore<AGGREGATE, EVENT_DETAILS, AGGREGATE_CLASS> {
      private castoreEventStore: CastoreEventStoreImpl<EVENT_STORE_ID, EVENT_TYPES, EVENT_DETAILS, $EVENT_DETAILS, REDUCER, AGGREGATE, $AGGREGATE>;
    
      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<AGGREGATE | undefined> {
        const state = await this.castoreEventStore.getAggregate(id);
        return state.aggregate;
      }
    
      public async getAggregate(id: string): Promise<AGGREGATE_CLASS | undefined> {
        const state = await this.castoreEventStore.getAggregate(id);
        if (!state.aggregate) {
          return undefined;
        }
        return this.factory(state.aggregate);
      }
    
      public async getEvents(id: string): Promise<EVENT_DETAILS[]> {
        const aggregate = await this.castoreEventStore.getAggregate(id);
        return aggregate.events;
      }
    
      public async saveEvents(events: OptionalTimestamp<EVENT_DETAILS>[]): Promise<void> {
        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<EVENT_DETAILS>
        const groupedEvents = eventsWithTimestamp.map((event) => this.castoreEventStore.groupEvent(event as EVENT_DETAILS));
        await CastoreEventStoreImpl.pushEventGroup({}, ...groupedEvents);
      }
    
      public async save(aggregate: AGGREGATE_CLASS): Promise<void> {
        const events = aggregate.popChanges();
        await this.saveEvents(events);
      }
    
      private assignTimestamp(event: OptionalTimestamp<EVENT_DETAILS>): 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.