Thanks to visit codestin.com
Credit goes to github.com

Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 2 additions & 9 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,12 +22,10 @@ Event Sourcing is a data storage paradigm that saves **changes in your applicati

After years of using it at [Kumo](https://dev.to/kumo), we have grown to love it, but also experienced first-hand the lack of consensus and tooling around it. That's where Castore comes from!


<p align="center">
Castore is a TypeScript library that <b>makes Event Sourcing easy</b> 😎
</p>


With Castore, you'll be able to:

- Define your [event stores](#eventstore)
Expand Down Expand Up @@ -80,7 +78,6 @@ Castore is opiniated. It comes with a collection of best practices and documente
- [🎁 Event Store](#%EF%B8%8F-reducers)
- [💾 Event Storage Adapter](#-eventstorageadapter)
- [📨 Command](#-command)
- [📸 Snapshots](#-snapshots)
- [Resources](#resources)
- [🎯 Test Tools](#-test-tools)
- [🔗 Packages List](#-packages-list)
Expand Down Expand Up @@ -296,7 +293,7 @@ export const usersReducer: Reducer<UserAggregate, UserEventsDetails> = (
const johnDowAggregate: UserAggregate = johnDowEvents.reduce(usersReducer);
```

> ☝️ Note that aggregates are always **computed on the fly**, and NOT stored. Changing them does not require any data migration whatsoever (except if you use snapshots, an invalidation is needed first).
> ☝️ Note that aggregates are always **computed on the fly**, and NOT stored. Changing them does not require any data migration whatsoever.

### 🎁 `EventStore`

Expand All @@ -305,6 +302,7 @@ Once you've defined your [event types](#-eventtype) and how to [aggregate](#%EF%
Each event store in your application represents a business entity. Think of event stores as _"what tables would be in CRUD"_, except that instead of directly updating data, you just append new events to it!

In Castore, `EventStore` classes are NOT responsible for actually storing data (this will come with [event storage adapters](#-eventstorageadapter)). But rather to provide a boilerplate-free and type-safe interface to perform many actions such as:

- Listing aggregate ids
- Accessing events of an aggregate
- Building an aggregate with the reducer
Expand Down Expand Up @@ -397,17 +395,12 @@ So far, castore supports 2 Storage Adapters ✨:

_...coming soon_

### 📸 `Snapshots`

_...coming soon_

## Resources

### 🎯 Test Tools

_...coming soon_


### 🔗 Packages List

_...coming soon_
Expand Down
1 change: 0 additions & 1 deletion demo/blueprint/src/counters/eventStore.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,6 @@ export const counterEventStore = new EventStore({
counterIncrementedEvent,
counterRemovedEvent,
],
snapshotInterval: 2,
reduce: (counterAggregate: CounterAggregate, event): CounterAggregate => {
const { version, aggregateId } = event;

Expand Down
1 change: 0 additions & 1 deletion demo/blueprint/src/users/eventStore.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@ import { userCreatedEvent, userRemovedEvent } from './events';
export const userEventStore = new EventStore({
eventStoreId: 'USER',
eventStoreEvents: [userCreatedEvent, userRemovedEvent],
snapshotInterval: 2,
reduce: (counterAggregate: UserAggregate, event): UserAggregate => {
const { version, aggregateId } = event;

Expand Down
42 changes: 3 additions & 39 deletions packages/core/src/eventStore/eventStore.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,6 @@ import {
AggregateSimulator,
Reducer,
} from './types';
import { validateSnapshotInterval } from './utils/validateSnapshotInterval';

export class EventStore<
I extends string = string,
Expand All @@ -43,7 +42,6 @@ export class EventStore<
*/
reduce: R;
simulateSideEffect: SideEffectsSimulator<D, $D>;
snapshotInterval: number;

getEvents: EventsGetter<D>;
pushEvent: EventPusher<$D>;
Expand All @@ -69,7 +67,6 @@ export class EventStore<
[event.version]: event,
}),
storageAdapter: $storageAdapter,
snapshotInterval = Infinity,
}: {
eventStoreId: I;
/**
Expand All @@ -82,7 +79,6 @@ export class EventStore<
reduce: R;
simulateSideEffect?: SideEffectsSimulator<D, $D>;
storageAdapter?: StorageAdapter;
snapshotInterval?: number;
}) {
this.eventStoreId = eventStoreId;
this.eventStoreEvents = eventStoreEvents;
Expand All @@ -93,8 +89,6 @@ export class EventStore<
*/
this.storageAdapter = $storageAdapter;

this.snapshotInterval = validateSnapshotInterval(snapshotInterval);

this.getStorageAdapter = () => {
if (!this.storageAdapter) {
throw new UndefinedStorageAdapterError({
Expand All @@ -120,22 +114,6 @@ export class EventStore<
await storageAdapter.pushEvent(eventDetail, {
eventStoreId: this.eventStoreId,
});

const { version, aggregateId } = eventDetail;
if (version % this.snapshotInterval === 0) {
/**
* @debt performances "In theory, events should already have been fetched. Find a way to not have to refetch aggregate (caching or input)"
*/
const { aggregate } = await this.getAggregate(aggregateId);

if (!aggregate) {
console.error('Unable to create snapshot: Aggregate not found');

return;
}

await storageAdapter.putSnapshot(aggregate);
}
};

this.listAggregateIds = async options =>
Expand All @@ -145,30 +123,16 @@ export class EventStore<
eventDetails.reduce(this.reduce, aggregate) as A | undefined;

this.getAggregate = async (aggregateId, options = {}) => {
const { maxVersion } = options;

let snapshot: A | undefined;
if (maxVersion === undefined || maxVersion >= this.snapshotInterval) {
snapshot = (
await this.getStorageAdapter().getLastSnapshot(aggregateId, {
maxVersion,
})
).snapshot as A | undefined;
}

const { events } = await this.getEvents(aggregateId, {
...options,
minVersion: snapshot ? snapshot.version + 1 : undefined,
});
const { events } = await this.getEvents(aggregateId, options);

const aggregate = this.buildAggregate(
events as unknown as $D[],
snapshot as unknown as $A,
undefined,
);

const lastEvent = events[events.length - 1];

return { aggregate, events, lastEvent, snapshot };
return { aggregate, events, lastEvent };
};

this.getExistingAggregate = async (aggregateId, options) => {
Expand Down
1 change: 0 additions & 1 deletion packages/core/src/eventStore/eventStore.type.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,6 @@ const assertGetAggregateOutput: A.Equals<
aggregate: CounterAggregate | undefined;
events: CounterEventsDetails[];
lastEvent: CounterEventsDetails | undefined;
snapshot: CounterAggregate | undefined;
}>
> = 1;
assertGetAggregateOutput;
Expand Down
73 changes: 0 additions & 73 deletions packages/core/src/eventStore/eventStore.unit.test.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
/* eslint-disable max-lines */
import { AggregateNotFoundError } from '~/errors/aggregateNotFound';

import {
Expand All @@ -16,7 +15,6 @@ import {
getLastSnapshotMock,
putSnapshotMock,
counterCreatedEventMock,
counterIncrementedEventMock,
} from './eventStore.util.test';

describe('event store', () => {
Expand Down Expand Up @@ -47,7 +45,6 @@ describe('event store', () => {
'getAggregate',
'getExistingAggregate',
'simulateAggregate',
'snapshotInterval',
]),
);

Expand All @@ -74,11 +71,6 @@ describe('event store', () => {
it('gets aggregate correctly', async () => {
const response = await counterEventStore.getAggregate(counterIdMock);

expect(getLastSnapshotMock).toHaveBeenCalledTimes(1);
expect(getLastSnapshotMock).toHaveBeenCalledWith(counterIdMock, {
maxVersion: undefined,
});

expect(getEventsMock).toHaveBeenCalledTimes(1);
expect(getEventsMock).toHaveBeenCalledWith(counterIdMock, {});
expect(response).toStrictEqual({
Expand All @@ -88,60 +80,6 @@ describe('event store', () => {
),
events: counterEventsMocks,
lastEvent: counterEventsMocks[counterEventsMocks.length - 1],
snapshot: undefined,
});
});

it('gets and use last snapshot if possible', async () => {
const eventsAfterSnapshot = [counterIncrementedEventMock];
const snapshot = [counterCreatedEventMock].reduce(
countersReducer,
undefined as unknown as CounterAggregate,
);
getLastSnapshotMock.mockResolvedValue({ snapshot });
getEventsMock.mockResolvedValue({ events: eventsAfterSnapshot });

const response = await counterEventStore.getAggregate(counterIdMock);

expect(getLastSnapshotMock).toHaveBeenCalledTimes(1);
expect(getLastSnapshotMock).toHaveBeenCalledWith(counterIdMock, {
maxVersion: undefined,
});

expect(getEventsMock).toHaveBeenCalledTimes(1);
expect(getEventsMock).toHaveBeenCalledWith(counterIdMock, {
minVersion: 2,
});
expect(response).toStrictEqual({
aggregate: eventsAfterSnapshot.reduce(countersReducer, snapshot),
events: eventsAfterSnapshot,
lastEvent: eventsAfterSnapshot[eventsAfterSnapshot.length - 1],
snapshot,
});
});

it('skips fetching last snapshot if maxVersion is below snapshotInterval', async () => {
const events = [counterCreatedEventMock];
getEventsMock.mockResolvedValue({ events });

const response = await counterEventStore.getAggregate(counterIdMock, {
maxVersion: 1,
});

expect(getLastSnapshotMock).not.toHaveBeenCalled();

expect(getEventsMock).toHaveBeenCalledTimes(1);
expect(getEventsMock).toHaveBeenCalledWith(counterIdMock, {
maxVersion: 1,
});
expect(response).toStrictEqual({
aggregate: events.reduce(
countersReducer,
undefined as unknown as CounterAggregate,
),
events,
lastEvent: events[events.length - 1],
snapshot: undefined,
});
});
});
Expand All @@ -162,7 +100,6 @@ describe('event store', () => {
),
events: counterEventsMocks,
lastEvent: counterEventsMocks[counterEventsMocks.length - 1],
snapshot: undefined,
});
});

Expand All @@ -189,16 +126,6 @@ describe('event store', () => {
eventStoreId: counterEventStore.eventStoreId,
});
});

it('puts snapshot on second event pushed (because snapshot interval is 2)', async () => {
await counterEventStore.pushEvent(counterIncrementedEventMock);

expect(pushEventMock).toHaveBeenCalledTimes(1);
expect(pushEventMock).toHaveBeenCalledWith(counterIncrementedEventMock, {
eventStoreId: counterEventStore.eventStoreId,
});
expect(putSnapshotMock).toHaveBeenCalledTimes(1);
});
});

describe('listAggregateIds', () => {
Expand Down
1 change: 0 additions & 1 deletion packages/core/src/eventStore/eventStore.util.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -105,7 +105,6 @@ export const counterEventStore = new EventStore({
],
reduce: countersReducer,
storageAdapter: mockStorageAdapter,
snapshotInterval: 2,
});

// Users
Expand Down
1 change: 0 additions & 1 deletion packages/core/src/eventStore/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,6 @@ export type AggregateGetter<
aggregate: R extends true ? A : A | undefined;
events: D[];
lastEvent: R extends true ? D : D | undefined;
snapshot: A | undefined;
}>;

export type SimulationOptions = { simulationDate?: string };
Expand Down
22 changes: 0 additions & 22 deletions packages/core/src/eventStore/utils/validateSnapshotInterval.ts

This file was deleted.

2 changes: 0 additions & 2 deletions packages/core/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,6 @@ export type {
PushEventContext,
ListAggregateIdsOptions,
ListAggregateIdsOutput,
GetLastSnapshotOptions,
ListSnapshotsOptions,
} from './storageAdapter';
export { EventStore } from './eventStore';
export type {
Expand Down
2 changes: 1 addition & 1 deletion packages/json-schema-event/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -52,8 +52,8 @@
"jest": "^27.5.1",
"json-schema-to-ts": "^2.5.4",
"prettier": "^2.6.2",
"ts-toolbelt": "^9.6.0",
"ts-node": "^10.7.0",
"ts-toolbelt": "^9.6.0",
"ttypescript": "^1.5.13",
"typescript": "^4.6.3"
},
Expand Down