{"id":25310027,"url":"https://github.com/ebyte23/neviles.js","last_synced_at":"2025-04-07T11:11:56.017Z","repository":{"id":95679933,"uuid":"394241951","full_name":"eByte23/NEvilES.js","owner":"eByte23","description":"NEvilES.js","archived":false,"fork":false,"pushed_at":"2021-08-11T15:46:48.000Z","size":115,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":1,"default_branch":"main","last_synced_at":"2025-02-13T13:42:46.332Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"TypeScript","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/eByte23.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":".github/CONTRIBUTING.md","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":"2021-08-09T10:09:41.000Z","updated_at":"2021-08-11T15:46:51.000Z","dependencies_parsed_at":"2023-05-09T10:31:35.784Z","dependency_job_id":null,"html_url":"https://github.com/eByte23/NEvilES.js","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/eByte23%2FNEvilES.js","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/eByte23%2FNEvilES.js/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/eByte23%2FNEvilES.js/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/eByte23%2FNEvilES.js/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/eByte23","download_url":"https://codeload.github.com/eByte23/NEvilES.js/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":247640466,"owners_count":20971558,"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":[],"created_at":"2025-02-13T13:36:20.758Z","updated_at":"2025-04-07T11:11:56.009Z","avatar_url":"https://github.com/eByte23.png","language":"TypeScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# NEvilES.js\nAn Event Sourcing library for javascript base on the principles and data models used in https://github.com/CRGCode/NEvilES\n\n## Get Started\n\nHere are the main things you need to know\n\nComing from c# and NEvilES there are a few differences and I have had to work around type system limitations in typescript\n\n\nHere is how we define our aggregates, commands and events\n```ts\n// import what we need from the library\nimport { BaseAggregate, Event, ICommand } from './Aggregate';\n// We must also pass all the Event Types we raise/emit from the aggregate.\n\n// Event Type Definition\nexport type CustomerEventTypes =\n  | 'customer.customer_created'\n  | 'customer.mobile_number_changed';\n\n// Define our command as a type or interface.\n// take note we are using readonly here from properties but this isn't required\ntype CreateCustomerCommand = ICommand \u0026 {\n  readonly firstName: string;\n  readonly lastName: string;\n  readonly email: string;\n  readonly mobile: string;\n};\n\n\n// Defining our event\nexport class CustomerCreated extends Event\u003cCustomerEventTypes\u003e {\n  constructor(\n    streamId: string,\n    firstName: string,\n    lastName: string,\n    email: string,\n    mobile: string\n  ) {\n    // This first parameter is our event type from our `CustomerEventTypes` definition\n    // this is required for every event\n    super('customer.customer_created', streamId);\n\n    this.FirstName = firstName;\n    this.LastName = lastName;\n    this.Email = email;\n    this.Mobile = mobile;\n  }\n\n  public readonly FirstName: string;\n  public readonly LastName: string;\n  public readonly Email: string;\n  public readonly Mobile: string;\n}\n\n\n// Our Aggrgeates should extend(inherit) BaseAggregate\u003cT\u003e\n\nexport class CustomerAggregate extends BaseAggregate\u003cCustomerEventTypes\u003e {\n  constructor() {\n    super();\n\n    // We setup or `apply` functions to apply internal state\n    // manipulations to our aggregate.\n    // These get re-run on fetch of aggregate.\n    this.on\u003cCustomerCreated\u003e('customer.customer_created', (e) =\u003e\n      this.customerCreated(e)\n    );\n  }\n  \n  // Our aggregate internal state.\n  // We may use this later to onfirm a customers current email address when changing it\n  private email: string;\n\n  // We define our handler method here.\n  public createCustomer(msg: CreateCustomerCommand): void {\n    if (this.Exists) {\n      throw new Error(`A customer with Id: ${msg.StreamId} already exists`);\n    }\n\n    // We raise our event here!\n    this.raise(\n      new CustomerCreated(\n        msg.StreamId,\n        msg.firstName,\n        msg.lastName,\n        msg.email,\n        msg.mobile\n      )\n    );\n  }\n\n  // Apply method.\n  private customerCreated(evt: CustomerCreated): void {\n    this.email = evt.Email;\n  }\n\n}\n\n```\n\n\nGetting up and running.\n\n```ts\n// First we need an event store instance\n// we will start with no events in the store\nconst es = new InMemoryEventStore();\n\nconst streamId = '04e9aba2-ac45-4b9d-b7b7-5f0163faa0d7'; // we create some form of id most liekly a guid/uuid\n\n// Now we could skip this step if we are sure it is a new aggregate/stream\n// and handle the check when saving in the eventstore\n// That option would look like\nconst agg = new CustomerAggreate();\nagg.setState(streamId);\n\n// otherwise I would lean towards this always for a consistent pattern\nconst agg = es.Get(streamId, new CustomerAggregate());\n\n// As we use an interface or type def for the command we can\n// just pass an object with a matching signature\nagg.createCustomer({\n    StreamId: streamId,\n    FirstName: 'john',\n    LastName: 'doe',\n    Email: 'email@domain.com',\n    Mobile: '0421123123'\n});\n\n// The above would run the logic in the handler, run apply methods for the event and save the event in the aggrate to be commited to the store.\n\n// something like this\nes.Save(agg);\n```\n\n\n\n### Commands \n\n\n### Events\n\n\n### Event Store\nThis is where we read and write and store our events with abstractions for different storage methods e.g. MySQL, DynamoDB, PostgreSQL etc...\n\n### Aggregates\nThis is where our business logic should lie and where we arange our commands and events.\n\n\nsudo code for our example\n\n```ts\nconst repository = new InMemoryEventRepository();\nconst eventStore = new EventStore(repository);\n\nconst aggregate = (new Aggregate(eventStore)).Get(\"abcde\");\n\nconst aggregateCommit = aggreate.handle({\n  type: 'create_customer',\n  data:{\n    id:'aaaa',\n    name: 'Elijah Bate'\n  }\n});\n\neventStore.commit(aggregateCommit);\n\n\n```\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Febyte23%2Fneviles.js","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Febyte23%2Fneviles.js","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Febyte23%2Fneviles.js/lists"}