{"id":21584971,"url":"https://github.com/snatalenko/node-cqrs","last_synced_at":"2025-08-20T23:32:42.088Z","repository":{"id":14015096,"uuid":"75549697","full_name":"snatalenko/node-cqrs","owner":"snatalenko","description":"CQRS backbone with event sourcing for Node.js","archived":false,"fork":false,"pushed_at":"2023-08-29T15:05:40.000Z","size":1050,"stargazers_count":72,"open_issues_count":7,"forks_count":20,"subscribers_count":6,"default_branch":"master","last_synced_at":"2024-04-14T12:30:37.901Z","etag":null,"topics":["aggregate","cqrs","es6","event-sourcing","eventstore","nodejs","saga"],"latest_commit_sha":null,"homepage":"https://www.node-cqrs.org","language":"JavaScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/snatalenko.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":null,"license":"LICENSE","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":"2016-12-04T16:32:29.000Z","updated_at":"2024-06-19T03:04:38.502Z","dependencies_parsed_at":"2024-06-19T03:04:33.053Z","dependency_job_id":"227ed050-5348-4312-889b-2f7561bf23ea","html_url":"https://github.com/snatalenko/node-cqrs","commit_stats":{"total_commits":343,"total_committers":5,"mean_commits":68.6,"dds":"0.023323615160349864","last_synced_commit":"a94b6b248e4007e30c5ec17dfb066571e4f2ce31"},"previous_names":[],"tags_count":73,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/snatalenko%2Fnode-cqrs","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/snatalenko%2Fnode-cqrs/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/snatalenko%2Fnode-cqrs/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/snatalenko%2Fnode-cqrs/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/snatalenko","download_url":"https://codeload.github.com/snatalenko/node-cqrs/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":230471175,"owners_count":18231193,"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":["aggregate","cqrs","es6","event-sourcing","eventstore","nodejs","saga"],"created_at":"2024-11-24T15:08:36.707Z","updated_at":"2024-12-19T17:08:32.422Z","avatar_url":"https://github.com/snatalenko.png","language":"JavaScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"node-cqrs\n=========\n\n[![NPM Version](https://img.shields.io/npm/v/node-cqrs.svg)](https://www.npmjs.com/package/node-cqrs)\n[![Audit Status](https://github.com/snatalenko/node-cqrs/actions/workflows/audit.yml/badge.svg)](https://github.com/snatalenko/node-cqrs/actions/workflows/audit.yml)\n[![Tests Status](https://github.com/snatalenko/node-cqrs/actions/workflows/tests.yml/badge.svg)](https://github.com/snatalenko/node-cqrs/actions/workflows/tests.yml)\n[![Coverage Status](https://coveralls.io/repos/github/snatalenko/node-cqrs/badge.svg?branch=master)](https://coveralls.io/github/snatalenko/node-cqrs?branch=master)\n[![NPM Downloads](https://img.shields.io/npm/dm/node-cqrs.svg)](https://www.npmjs.com/package/node-cqrs)\n\n## Overview\n\nThe package provides building blocks for making a CQRS-ES application. It was inspired by Lokad.CQRS, but not tied to a specific storage implementation or infrastructure. It favors ES6 classes and dependency injection, so any components can be modified or replaced with your own implementations without hacks to the package codebase.\n\n[Documentation at node-cqrs.org](https://www.node-cqrs.org)\n\n\nYour app is expected to operate with loosely typed commands and events that match the following interface: \n\n```ts\ndeclare interface IMessage {\n    type: string,\n\n    aggregateId?: string|number,\n    aggregateVersion?: number,\n\n    sagaId?: string|number,\n    sagaVersion?: number,\n\n    payload?: any,\n    context?: any\n}\n```\n\nDomain business logic should be placed in Aggregate, Saga and Projection classes:\n\n- [Aggregates](entities/Aggregate/README.MD) handle commands and emit events\n- [Sagas](entities/Saga/README.MD) handle events and enqueue commands\n- [Projections](entities/Projection/README.md) listen to events and update views\n\n\nMessage delivery is being handled by the following services (in order of appearance):\n\n- **Command Bus** delivers commands to command handlers\n- [Aggregate Command Handler](middleware/AggregateCommandHandler.md) restores an aggregate state, executes a command\n- **Event Store** persists events and deliver them to event handlers (saga event handlers, projections or any other custom services)\n- **Saga Event Handler** restores saga state and applies event\n\n\nFrom a high level, this is how the command/event flow looks like:\n\n![Overview](docs/images/node-cqrs-components.png)\n\n\n## Getting Started\n\nYou can find sample code of a User domain in the **/examples** folder.\n\n\n### Your App → Command → Aggregate\n\nDescribe an aggregate that handles a command:\n\n```js\nconst { AbstractAggregate } = require('node-cqrs');\n\nclass UserAggregate extends AbstractAggregate {\n  static get handles() {\n    return ['createUser'];\n  }\n  \n  createUser(commandPayload) {\n    // ...\n  }\n}\n```\n\nThen register aggregate in the [DI container](middleware/DIContainer.md). \nAll the wiring can be done manually, without a DI container (you can find it in samples), but with container it’s just easier:\n\n```js\nconst { ContainerBuilder, InMemoryEventStorage } = require('node-cqrs');\n\nconst builder = new ContainerBuilder();\nbuilder.register(InMemoryEventStorage).as('storage');\nbuilder.registerAggregate(UserAggregate);\n\nconst container = builder.container();\n```\n\nThen send a command:\n\n```js\nconst userAggregateId = undefined;\nconst payload = {\n  username: 'john',\n  password: 'test'\n};\n\ncontainer.commandBus.send('createUser', userAggregateId, { payload });\n```\n\nBehind the scene, an AggregateCommandHandler will catch the command, \ntry to load an aggregate event stream and project it to aggregate state, \nthen it will pass the command payload to the `createUser` handler we’ve defined earlier.\n\nThe `createUser` implementation can look like this: \n\n```js\ncreateUser(commandPayload) {\n  const { username, password } = commandPayload;\n\n  this.emit('userCreated', {\n    username,\n    passwordHash: md5Hash(password)\n  });\n}  \n```\n\nOnce the above method is executed, the emitted userCreated event will be persisted and delivered to event handlers (sagas, projections or any other custom event receptors).\n\n\n### Aggregate → Event → Projection → View\n\nNow it’s time to work on a read model. We’ll need a projection that will handle our events. Projection must implement 2 methods: `subscribe(eventStore)` and `project(event)` . \nTo make it easier, you can extend an `AbstractProjection`:\n\n```js\nconst { AbstractProjection } = require('node-cqrs');\n\nclass UsersProjection extends AbstractProjection {\n  static get handles() {\n    return ['userCreated'];\n  }\n  \n  userCreated(event) {\n    // ...\n  }\n}\n```\n\nBy default, projection uses async `InMemoryView` for inner view, but we’ll use `Map` to make it more familiar:\n\n```js\nclass UsersProjection extends AbstractProjection {\n  get view() {\n    return this._view || (this._view = new Map());\n  }\n\n  // ...\n}\n```\n\nWith `Map` view, our event handler can look this way:\n\n```js\nclass UsersProjection extends AbstractProjection {\n  // ...\n\n  userCreated(event) {\n    this.view.set(event.aggregateId, {\n      username: event.payload.username\n    });\n  }\n}\n```\n\nOnce the projection is ready, it can be registered in the DI container: \n\n```js\nbuilder.registerProjection(UsersProjection, 'users');\n```\n\nAnd accessed from anywhere in your app:\n\n```js\ncontainer.users\n// Map { 1 =\u003e { username: 'John' } }\n```\n\n## Contribution\n\n* [editorconfig](http://editorconfig.org)\n* [eslint](http://eslint.org)\n* `npm test -- --watch`\n\n## License\n\n* [MIT License](https://github.com/snatalenko/node-cqrs/blob/master/LICENSE)\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fsnatalenko%2Fnode-cqrs","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fsnatalenko%2Fnode-cqrs","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fsnatalenko%2Fnode-cqrs/lists"}