Releases: event-driven-io/emmett
0.20.2
📝 What's Changed
- Fixed browser-only usage of Emmett package introduced in 0.20.0 by @oskardudycz in #125
- Fixed Commonjs compatibility by @oskardudycz in #122
Full Changelog: 0.20.1...0.20.2
0.20.1
📝 What's Changed
- Fix
CommandHandler
type definition forhandleOptions
By accident the 0.20.0 provided regression for the event store customisation if you're building your own based on Emmett's abstractions by @alex-laycalvert in 123
Full Changelog: 0.20.0...0.20.1
0.20.0
🚀 What's New
- [BREAKING] Command handling idempotency improvements. This will allow automatic retries, e.g. for optimistic concurrency check, and also ignoring the command by returning an empty array. All of that should give easier handling for idempotent handlers. by @oskardudycz in 119
- Added Support for an async
decide
/handle
function forCommandHandler
Kids, be careful with that! by @alex-laycalvert in 99 - Added streaming capabilities. That's the foundational work for event subscriptions and event streaming through WebStreams. by @oskardudycz in 76
📝 What's Changed
- Bumped pongo 0.16.1 by @oskardudycz in 120
- Fixed Dockerfile configuration to use esmodules instead of common js by @oskardudycz in 121
Full Changelog: 0.19.1...0.20.0
0.19.1
📝 What's Changed
- Bumped Pongo to 0.15.3 version by @oskardudycz in #118
- Updated samples to Emmett 0.19.0 version by @oskardudycz in #118
Full Changelog: 0.19.0...0.19.1
0.19.0
🚀 What's New
- Bumped Pongo to 0.15.0 by @oskardudycz in 115, 117. Check detailed log of changes in Pongo 0.14.0 and 0.15.0 release notes.
- Added Initial Emmett CLI. The migration support will come tho in the follow up release. by @oskardudycz in 116
Full Changelog: 0.18.0...0.19.0
0.18.0
🚀 What's New
- Added default implementation of PostgreSQL event store session It automatically reuses a single connection inside the same command handler. That cut the time to at least a half for command handling processing. by @oskardudycz in #110
- Added also capability to migrate, return string and print schema for PostgreSQL event store. You can do it with:
// migrate
await eventStore.schema.migrate();
// print
eventStore.schema.print();
// return
const sql = evenStore.schema.sql();
There'll be a follow-up release with the command line utility. by @oskardudycz in #110
- Added benchmark configuration to generate schema upfront by @oskardudycz in #109, #110
##📝 What's Changed
- Updated to Pongo 0.13.0 and other peer dependencies This is technically a breaking change, as some dependencies were bumped by a major version. by @oskardudycz in #113
- Updated TS config to fix debugging by @oskardudycz in #114
Full Changelog: 0.17.0...0.18.0
0.17.0
🚀 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
0.16.0
🚀 What's New
1. Added PostgreSQL and Pongo Projection specification for integration testing
Now you can write your tests as:
const given = PostgreSQLProjectionSpec.for({
projection: shoppingCartShortInfoProjection,
connectionString,
});
test('with empty given and when eventsInStream',
given(
eventsInStream<ProductItemAdded>(shoppingCartId, [
{
type: 'ProductItemAdded',
data: {
productItem: { price: 100, productId: 'shoes', quantity: 100 },
},
},
]),
)
.when(
newEventsInStream(shoppingCartId, [
{
type: 'DiscountApplied',
data: { percent: 10 },
},
]),
)
.then(
expectPongoDocuments
.fromCollection(shoppingCartShortInfoCollectionName)
.withId(shoppingCartId)
.toBeEqual({
productItemsCount: 100,
totalAmount: 9000,
}),
);
});
There are also other helpers; you can also check raw projections using helpers like assertSQLQueryResultMatches
.
by @oskardudycz in #105
2. Refactored Postgres projections registrations
- Separated projection definition from registration. Thanks to that, projections will be able to switch their type in the future or be used in both (e.g., for rebuilds).
- Refactored projections to take params object instead of a list of parameters. That will cut the number of permutations and make it easier to define them
- Renamed
pongoSingleProjection
topongoSingleStreamProjection
to keep naming consistency.
Now, registration will look like this (notice projections.inline
helper):
import { projections } from '@event-driven-io/emmett';
const eventStore = getPostgreSQLEventStore(connectionString, {
projections: projections.inline(
[shoppingCartShortInfoProjection, customProjection]
),
});
And Pongo projection definition as:
const shoppingCartShortInfoProjection = pongoSingleStreamProjection({
collectionName: 'shoppingCartsShortInfo',
evolve,
canHandle: ['ProductItemAdded', 'DiscountApplied'],
});
by @oskardudycz in #104
📝 What’s Changed
- Updated Pongo to 0.12.5 by @oskardudycz in #103, #105
Full Changelog: 0.15.0...0.16.0
0.15.0
🚀 What's New
1. Refactored PostgreSQL event store to benefit from the latest Pongo and Dumbo improvements
Now, it's possible to inject ambient connections and use non-pooled event stores in the same way as in Pongo.
To run a non-pooled setup (e.g. for Cloudflare Hyperdrive or Supabase Supavisor) you need to do the following:
const eventStore = getPostgreSQLEventStore(connectionString, {
projections: [shoppingCartShortInfoProjection, customProjection],
connectionOptions: { pooled: false },
});
Now you can also use custom projection as:
import { sql } from '@event-driven-io/dumbo';
import { postgreSQLInlineProjection, sql } from '@event-driven-io/emmett-postgresql';
const customProjection = postgreSQLInlineProjection<ShoppingCartEvent>({
name: 'customProjection',
canHandle: ['ProductItemAdded'],
handle: (events, { execute }) =>
execute.batchCommand(
events.map(({ shoppingCartId, productId, quantity }) =>
sql('INSERT INTO product_items VALUES(%s, %L, %L)', productId, quantity));
)
),
});
by @oskardudycz in #101
2. Added raw sql projections to support raw SQL handling
Generalised also projection definition.
Made postgreSQLProjection
and inlineProjection
obsolete, use postgreSQLProjection
and postgreSQLInlineProjection
instead.
Now you can use the new projections raw projection as:
import { sql } from '@event-driven-io/dumbo';
import { postgreSQLRawSQLProjection } from '@event-driven-io/emmett-postgresql';
const rawSQLProjection = postgreSQLRawSQLProjection<ProductItemAdded>({
name: 'customProjection',
canHandle: ['ProductItemAdded'],
handle: (event, context) =>
sql(
'INSERT INTO product_items VALUES(%s, %s, %L)',
event.shoppingCartId, event.productId, event.quantity)
)
});
There's also postgreSQLRawBatchSQLProjection
that handles a batch of events accordingly. Both can be sync or async. You can access and query the database through the context
handler param.
by @oskardudycz in #102
3. Added a basic example of projection handling to PostgreSQL samples.
You can check it here in the source code. by @oskardudycz in 100
Full Changelog: 0.14.1...0.15.0
0.14.1
📝 What's Changed
- Exposed missing Pongo projection types and functions by @oskardudycz in #98
Full Changelog: 0.14.0...0.14.1