New Case Study:See how Anthropic automated 95% of dependency reviews with Socket.Learn More β†’
Socket
Sign inDemoInstall
Socket

@castore/core

Package Overview
Dependencies
Maintainers
4
Versions
64
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

@castore/core

Castore 🦫 <a aria-label="NPM version" href="https://www.npmjs.com/package/@c

  • 1.4.0
  • Source
  • npm
  • Socket score

Version published
Weekly downloads
1.9K
increased by13.35%
Maintainers
4
Weekly downloads
Β 
Created
Source

Castore 🦫



Making Event Sourcing easy 😎

Event Sourcing is a data storage paradigm that saves changes in your application state rather than the state itself. It is powerful but tricky to implement.

After years of using it at 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!

Castore is a TypeScript library that makes Event Sourcing easy 😎

With Castore, you'll be able to:

  • Define your event stores
  • Fetch and push new events seamlessly
  • Implement and test your commands
  • ...and much more!

All that with first-class developer experience and minimal boilerplate ✨

πŸ«€ Core Design

Some important decisions that we've made early on:

πŸ’­ Abstractions first

Castore has been designed with flexibility in mind. It gives you abstractions that are meant to be used anywhere: React apps, containers, Lambdas... you name it!

For instance, EventStore classes are stack agnostic: They need an EventStorageAdapter class to interact with actual data. You can code your own EventStorageAdapter (simply implement the interface), but it's much simpler to use an off-the-shelf adapter like DynamoDBEventStorageAdapter.

πŸ™…β€β™‚οΈ We do NOT deploy resources

While some packages like DynamoDBEventStorageAdapter require compatible infrastructure, Castore is not responsible for deploying it.

Though that is not something we exclude in the future, we are a small team and decided to focus on DevX first.

β›‘ Full type safety

Speaking of DevX, we absolutely love TypeScript! If you do too, you're in the right place: We push type-safety to the limit in everything we do!

If you don't, that's fine πŸ‘ Castore is still available in Node/JS. And you can still profit from some nice JSDocs!

πŸ“– Best practices

The Event Sourcing journey has many hidden pitfalls. We ran into them for you!

Castore is opiniated. It comes with a collection of best practices and documented anti-patterns that we hope will help you out!

Table of content

Getting Started

πŸ“₯ Installation

# npm
npm install @castore/core

# yarn
yarn add @castore/core

πŸ“¦ Packages structure

Castore is not a single package, but a collection of packages revolving around a core package. This is made so every line of code added to your project is opt-in, wether you use tree-shaking or not.

Castore packages are released together. Though different versions may be compatible, you are guaranteed to have working code as long as you use matching versions.

Here is an example of working package.json:

{
  ...
  "dependencies": {
    "@castore/core": "1.3.1",
    "@castore/dynamodb-event-storage-adapter": "1.3.1"
    ...
  },
  "devDependencies": {
    "@castore/test-tools": "1.3.1"
    ...
  }
}

The Basics

πŸ“š Events

Event Sourcing is all about saving changes in your application state. Such changes are represented by events, and needless to say, they are quite important πŸ™ƒ

Events that concern the same business entity (like a User) are aggregated through a common id called aggregateId (and vice versa, events that have the same aggregateId represent changes of the same business entity). The index of an event in such a serie of events is called its version.

In Castore, stored events (also called event details) always have exactly the following attributes:

  • aggregateId (string)
  • version (integer β‰₯ 1)
  • timestamp (string): A date in ISO 8601 format
  • type (string): A string identifying the business meaning of the event
  • payload (?any = never): A payload of any type
  • metadata (?any = never): Some metadata of any type
import type { EventDetail } from '@castore/core';

type UserCreatedEventDetail = EventDetail<
  'USER_CREATED',
  { name: string; age: number },
  { invitedBy?: string }
>;

// πŸ‘‡ Equivalent to:
type UserCreatedEventDetail = {
  aggregateId: string;
  version: number;
  timestamp: string;
  type: 'USER_CREATED';
  payload: { name: string; age: number };
  metadata: { invitedBy?: string };
};

🏷 EventType

Events are generally classified in events types (not to confuse with TS types). Castore lets you declare them via the EventType class:

import { EventType } from '@castore/core';

export const userCreatedEventType = new EventType<
  'USER_CREATED',
  { name: string; age: number },
  { invitedBy?: string }
>({ type: 'USER_CREATED' });

Note that we only provide TS types for payload and metadata properties. That is because, as stated in the core design, Castore is meant to be as flexible as possible, and that includes the validation library you want to use: The EventType class is not meant to be used directly, but rather extended by other classes which will add run-time validation methods to it πŸ‘

See the following packages for examples:

Constructor:

  • type (string): The event type
import { EventType } from '@castore/core';

export const userCreatedEventType = new EventType({ type: 'USER_CREATED' });

Properties:

  • type (string): The event type
const eventType = userCreatedEventType.type;
// => "USER_CREATED"

Type Helpers:

  • EventTypeDetail: Returns the event detail TS type of an EventType
import type { EventTypeDetail } from '@castore/core';

type UserCreatedEventTypeDetail = EventTypeDetail<typeof userCreatedEventType>;

// πŸ‘‡ Equivalent to:
type UserCreatedEventTypeDetail = {
  aggregateId: string;
  version: number;
  timestamp: string;
  type: 'USER_CREATED';
  payload: { name: string; age: number };
  metadata: { invitedBy?: string };
};
  • EventTypesDetails: Return the events details of a list of EventType
import type { EventTypesDetails } from '@castore/core';

type UserEventTypesDetails = EventTypesDetails<
  [typeof userCreatedEventType, typeof userRemovedEventType]
>;
// => EventTypeDetail<typeof userCreatedEventType>
// | EventTypeDetail<typeof userRemovedEventType>

πŸ— Aggregate

Eventhough entities are stored as series of events, we still want to use a stable interface to represent their states at a point in time rather than directly using events. In Castore, it is implemented by a TS type called Aggregate.

☝️ Think of aggregates as "what the data would look like in CRUD"

In Castore, aggregates necessarily contain an aggregateId and version attributes (the version of the latest event). But for the rest, it's up to you πŸ€·β€β™‚οΈ

For instance, we can include a name, age and status properties to our UserAggregate:

import type { Aggregate } from '@castore/core';

// Represents a User at a point in time
interface UserAggregate extends Aggregate {
  name: string;
  age: number;
  status: 'CREATED' | 'REMOVED';
}

// πŸ‘‡ Equivalent to:
interface UserAggregate {
  aggregateId: string;
  version: number;
  name: string;
  age: number;
  status: 'CREATED' | 'REMOVED';
}

βš™οΈ Reducer

Aggregates are derived from their events by reducing them through a reducer function. It defines how to update the aggregate when a new event is pushed:

import type { Reducer } from '@castore/core';

export const usersReducer: Reducer<UserAggregate, UserEventsDetails> = (
  userAggregate,
  newEvent,
) => {
  const { version, aggregateId } = newEvent;

  switch (newEvent.type) {
    case 'USER_CREATED': {
      const { name, age } = newEvent.payload;

      // πŸ‘‡ Return the next version of the aggregate
      return {
        aggregateId,
        version,
        name,
        age,
        status: 'CREATED',
      };
    }
    case 'USER_REMOVED':
      return { ...userAggregate, version, status: 'REMOVED' };
  }
};

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).

🎁 EventStore

Once you've defined your event types and how to aggregate them, you can bundle them together in an EventStore class.

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). 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
  • Pushing new events etc...
import { EventStore } from '@castore/core';

const userEventStore = new EventStore({
  eventStoreId: 'USERS',
  eventTypes: [
    userCreatedEventType,
    userRemovedEventType,
    ...
  ],
  reducer: usersReducer,
});
// ...and that's it πŸ₯³

☝️ The EventStore class is the heart of Castore, it even gave it its name!

Constructor:

...coming soon

Properties:

...coming soon

Type Helpers:

...coming soon

πŸ’Ύ EventStorageAdapter

...coming soon

πŸ“¨ Command

...coming soon

πŸ“Έ Snapshots

...coming soon

Resources

🎯 Test Tools

...coming soon

πŸ”— Packages List

...coming soon

πŸ“– Common Patterns

...coming soon

Keywords

FAQs

Package last updated on 28 Oct 2022

Did you know?

Socket

Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.

Install

Related posts

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚑️ by Socket Inc