{"id":18158132,"url":"https://github.com/seikho/evtstore","last_synced_at":"2025-07-15T03:40:41.799Z","repository":{"id":35162683,"uuid":"214466062","full_name":"Seikho/evtstore","owner":"Seikho","description":"Event Sourcing and CQRS with Node.js and TypeScript","archived":false,"fork":false,"pushed_at":"2023-09-27T20:53:29.000Z","size":660,"stargazers_count":68,"open_issues_count":5,"forks_count":6,"subscribers_count":4,"default_branch":"master","last_synced_at":"2025-06-24T10:50:14.797Z","etag":null,"topics":["cqrs","event-sourcing","mongodb","nodejs","postgres","typescript"],"latest_commit_sha":null,"homepage":"https://seikho.github.io/evtstore","language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/Seikho.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2019-10-11T15:07:26.000Z","updated_at":"2025-04-18T18:03:24.000Z","dependencies_parsed_at":"2024-06-19T05:25:43.619Z","dependency_job_id":"e0d6cbdd-b982-479d-922d-b6e5fb90e34d","html_url":"https://github.com/Seikho/evtstore","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/Seikho/evtstore","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Seikho%2Fevtstore","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Seikho%2Fevtstore/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Seikho%2Fevtstore/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Seikho%2Fevtstore/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/Seikho","download_url":"https://codeload.github.com/Seikho/evtstore/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Seikho%2Fevtstore/sbom","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":265398302,"owners_count":23758469,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["cqrs","event-sourcing","mongodb","nodejs","postgres","typescript"],"created_at":"2024-11-02T07:05:39.505Z","updated_at":"2025-07-15T03:40:41.776Z","avatar_url":"https://github.com/Seikho.png","language":"TypeScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# EvtStore\n\n\u003e Type-safe Event Sourcing and CQRS with Node.JS and TypeScript\n\n- [Documentation](https://seikho.github.io/evtstore)\n- [Supported Databases](https://seikho.github.io/evtstore/#/docs/providers)\n- [API](https://seikho.github.io/evtstore/#/docs/api)\n- [Event Handlers](https://seikho.github.io/evtstore/#/docs/event-handlers)\n- [Command Handlers](https://seikho.github.io/evtstore/#/docs/commands)\n- Examples\n  - [the example folder](https://github.com/Seikho/evtstore/tree/master/example)\n  - [My fullstack starter](https://github.com/Seikho/fullstack-starter)\n\n**Note: `createDomain` will be migrating to `createDomainV2` in version 11.x**\nThe `createDomainV2` API solves circular reference issues when importing aggregates.\nThe original `createDomain` will be available as `createDomainV1` from 11.x onwards.\n\n## Why\n\nI reguarly use event sourcing and wanted to lower the barrier for entry and increase productivity for colleagues.  \nThe design goals were:\n\n- Provide as much type safety and inference as possible\n- Make creating domains quick and intuitive\n- Be easy to test\n- Allow developers to focus on application/business problems instead of Event Sourcing and CQRS problems\n\nTo obtain these goals the design is highly opinionated, but still flexible.\n\n## Supported Databases\n\nSee [Providers](https://seikho.github.io/evtstore/#/docs/providers) for more details and examples\n\n- Postgres using [Postgres.js](https://www.npmjs.com/package/postgres)\n- Postgres using [node-postgres](https://node-postgres.com)\n- SQLite, MySQL, Postgres using [Knex](https://knexjs.org)\n- In-memory\n- MongoDB\n- Neo4j v3.5\n- Neo4j v4\n\n## Aggregate Persistence\n\nSee [the documentation](https://seikho.github.io/evtstore/#/docs/api?id=aggregate-persistence) regarding information about aggregate persistence. This refers to persisting a copy of the aggregate on events for performant retrieval.\n\n## Examples\n\nEvtStore is type-driven to take advantage of type safety and auto completion. We front-load the creation of our `Event`, `Aggregate`, and `Command` types to avoid having to repeatedly import and pass them as generic argument. EvtStore makes use for TypeScript's [mapped types and conditional types](https://www.typescriptlang.org/docs/handbook/2/mapped-types.html) to achieve this.\n\n```ts\ntype UserEvt =\n  | { type: 'created', name: string }\n  | { type: 'disabled' }\n  | { type: 'enabled' }\ntype UserAgg = { name: string, enabled: boolean }\ntype UserCmd =\n  | { type: 'create': name: string }\n  | { type: 'enable' }\n  | { type: 'disable' }\n\ntype PostEvt =\n  | { type: 'postCreated', userId: string, content: string }\n  | { type: 'postArchived' }\n\ntype PostAgg = { userId: string, content: string, archived: boolean }\ntype PostCmd =\n  | { type: 'createPost', userId: string, content: string }\n  | { type: 'archivedPost', userId: string }\n\nconst user = createAggregate\u003cUserEvt, UserAgg, 'users'\u003e({\n  stream: 'users',\n  create: () =\u003e ({ name: '', enabled: false }),\n  fold: (evt) =\u003e {\n    switch (evt.type) {\n      case 'created':\n        return { name: evt.name, enabled: true }\n      case 'disabled':\n        return { enabled: false }\n      case 'enabled':\n        return { enabled: true }\n    }\n  }\n})\n\nconst post = createAggregate\u003cPostEvt, PostAgg, 'posts'\u003e({\n  stream: 'posts',\n  create: () =\u003e ({ content: '', userId: '', archived: false }),\n  fold: (evt) =\u003e {\n    switch (evt.type) {\n      case 'postCreated':\n        return { userId: evt.userId, content: evt.content }\n      case 'postArchived':\n        return { archived: true }\n    },\n  }\n})\n\nconst provider = createProvider()\n\nexport const { domain, createHandler } = createDomain({ provider }, { user, post })\n\nexport const userCmd = createCommands\u003cUserEvt, UserEvt, UserCmd\u003e(domain.user, {\n  async create(cmd, agg) { ... },\n  async disable(cmd, agg) { ... },\n  async enable(cmd, agg) { ... },\n})\n\nexport const postCmd = createCommands\u003cPostEvt, PostAgg, PostCmd\u003e(domain.post, {\n  async createPost(cmd, agg) {\n    if (agg.version) throw new CommandError('Post already exists')\n    const user = await domain.user.getAggregate(cmd.userId)\n    if (!user.version) throw new CommandError('Unauthorized')\n\n    return { type: 'postCreated', content: cmd.content, userId: cmd.userId }\n  },\n  async archivePost(cmd, agg) {\n    if (cmd.userId !== agg.userId) throw new CommandError('Not allowed')\n    if (agg.archived) return\n\n    return { type: 'postArchived' }\n  }\n})\n\nconst postModel = createHandler('posts-model', ['posts'], {\n  // When the event handler is started for the first time, the handler will begin at the end of the stream(s) history\n  tailStream: false,\n\n  // Every time the event handler is started, the handler will begin at the end of the stream(s) history\n  alwaysTailStream: false,\n\n  // Skip events that throw an error when being handled\n  continueOnError: false,\n})\n\npostModel.handle('posts', 'postCreated', async (id, event, meta) =\u003e {\n  // Insert into database\n})\npostModel.start()\n\n```\n\nSee [the example folder](https://github.com/Seikho/evtstore/tree/master/example)\n\n## API\n\nSee [API](https://seikho.github.io/evtstore/#/docs/api)\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fseikho%2Fevtstore","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fseikho%2Fevtstore","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fseikho%2Fevtstore/lists"}