Skip to content

0.17.0

Compare
Choose a tag to compare
@oskardudycz oskardudycz released this 13 Aug 09:27
· 238 commits to main since this release

🚀 What's New

1. Added idempotence check tests for PostgreSQL projections. Now you can simulate retries, ensuring that your projection handling is idempotent. You can do it by specifying when options, passing the number of times, e.g. { numberOfTimes: 2 }

See full example:

void test('with idempotency check', () => {
  const couponId = uuid();

  return given(
    eventsInStream<ProductItemAdded>(shoppingCartId, [
      {
        type: 'ProductItemAdded',
        data: {
          productItem: { price: 100, productId: 'shoes', quantity: 100 },
        },
      },
    ]),
  )
    .when(
      newEventsInStream(shoppingCartId, [
        {
          type: 'DiscountApplied',
          data: { percent: 10, couponId },
        },
      ]),
      { numberOfTimes: 2 },
    )
    .then(
      expectPongoDocuments
        .fromCollection<ShoppingCartShortInfo>(
          shoppingCartShortInfoCollectionName,
        )
        .withId(shoppingCartId)
        .toBeEqual({
          productItemsCount: 100,
          totalAmount: 9000,
          appliedDiscounts: [couponId],
        }),
    );
})

Note: This is not needed for inline projections as they will be either applied or not, but it will be more apt for async projections.

by @oskardudycz in 106

2. Added initialState option to Pongo projections to allow getting a non-nullable state

It's optional, but you can do it by:

const shoppingCartShortInfoProjection = pongoSingleStreamProjection({
  collectionName: shoppingCartShortInfoCollectionName,
  evolve,
  canHandle: ['ProductItemAdded', 'DiscountApplied'],
  initialState: () => ({
    productItemsCount: 0,
    totalAmount: 0,
    appliedDiscounts: [],
  }),
});

Then your evolve function will require to have the signature taking non-nullable state, e.g.:

const evolve = (
  document: ShoppingCartShortInfo,
  { type, data: event }: ProductItemAdded | DiscountApplied,
): ShoppingCartShortInfo => {
  switch (type) {
    case 'ProductItemAdded':
      return {
        ...document,
        totalAmount:
          document.totalAmount +
          event.productItem.price * event.productItem.quantity,
        productItemsCount:
          document.productItemsCount + event.productItem.quantity,
      };
    case 'DiscountApplied':
      // idempotence check
      if (document.appliedDiscounts.includes(event.couponId)) return document;

      return {
        ...document,
        totalAmount: (document.totalAmount * (100 - event.percent)) / 100,
        appliedDiscounts: [...document.appliedDiscounts, event.couponId],
      };
  }
};

by @oskardudycz in 106

3. Extended typing for PostgreSQL projections to include global store position in event metadata

PostgreSQL event store maintains the global position, so it's worth reflecting that through the metadata.

Full Changelog: 0.16.0...0.17.0