{"id":25566523,"url":"https://github.com/pdmlab/sourced-ts","last_synced_at":"2026-03-16T08:30:17.110Z","repository":{"id":49226325,"uuid":"378466878","full_name":"PDMLab/sourced-ts","owner":"PDMLab","description":"Tiny TypeScript framework for building models with the event sourcing pattern (events and snapshots). ","archived":false,"fork":false,"pushed_at":"2021-06-22T19:34:02.000Z","size":231,"stargazers_count":0,"open_issues_count":0,"forks_count":1,"subscribers_count":3,"default_branch":"main","last_synced_at":"2025-02-11T12:41:12.273Z","etag":null,"topics":["event-sourcing","typescript"],"latest_commit_sha":null,"homepage":"https://www.npmjs.com/package/sourced-ts","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/PDMLab.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}},"created_at":"2021-06-19T17:19:13.000Z","updated_at":"2021-06-22T19:34:04.000Z","dependencies_parsed_at":"2022-08-23T19:40:45.976Z","dependency_job_id":null,"html_url":"https://github.com/PDMLab/sourced-ts","commit_stats":null,"previous_names":[],"tags_count":9,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/PDMLab%2Fsourced-ts","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/PDMLab%2Fsourced-ts/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/PDMLab%2Fsourced-ts/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/PDMLab%2Fsourced-ts/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/PDMLab","download_url":"https://codeload.github.com/PDMLab/sourced-ts/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":239927230,"owners_count":19719826,"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":["event-sourcing","typescript"],"created_at":"2025-02-20T22:33:01.184Z","updated_at":"2026-03-16T08:30:17.053Z","avatar_url":"https://github.com/PDMLab.png","language":"TypeScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"![Build Status](https://img.shields.io/github/workflow/status/pdmlab/sourced-ts/build)\n\n# sourced-ts\n\nTiny TypeScript framework forked from [sourced](https://github.com/mateodelnorte/sourced) for building models with the [event sourcing](http://cqrs.nu/Faq/event-sourcing) pattern (events and snapshots). Unlike Active Record where entity state is persisted on a one-model-per row database format, event sourcing stores all the changes (events) to the entity, rather than just its current state. The current state is derived by loading all events, or a latest snapshot plus subsequent events, and replaying them against the entity.\n\nOne large benefit of event sourcing: your data _*is*_ your audit trail. Zero discrepancies.\n\nFor example usage, see the [examples](./examples) and [tests](./test).\n\nSourced-ts makes no assumptions about how you _store_ your events and snapshots. The library is small and tight with only the required functionality to define entities and their logic, enqueue and emit events, and track event state to later be persisted. To actually persist, use one of the following libraries or implement your own:\n\n- [sourced-repo-mongo-ts](https://github.com/pdmlab/sourced-repo-mongo-ts)\n\n# TypeScript Example\n\n```typescript\nconst Entity = require('sourced').SourcedEntity\n\nclass Market extends Entity {\n  constructor(snapshot, events) {\n    super()\n    this.orders = []\n    this.price = 0\n\n    this.rehydrate(snapshot, events)\n  }\n\n  init(param) {\n    this.id = param.id\n    this.digest('init', param)\n    this.emit('initialized', param, this)\n  }\n\n  createOrder(param) {\n    this.orders.push(param)\n    var total = 0\n    this.orders.forEach(function (order) {\n      total += order.price\n    })\n    this.price = total / this.orders.length\n    this.digest('createOrder', param)\n    this.emit('done', param, this)\n  }\n}\n```\n\n# Reference\n\n## Classes\n\n\u003cdl\u003e\n\u003cdt\u003e\u003ca href=\"#Entity\"\u003eEntity\u003c/a\u003e\u003c/dt\u003e\n\u003cdd\u003e\u003cp\u003e{Function} Entity\u003c/p\u003e\n\u003c/dd\u003e\n\u003cdt\u003e\u003ca href=\"#EntityError\"\u003eEntityError\u003c/a\u003e\u003c/dt\u003e\n\u003cdd\u003e\u003cp\u003e{Function} EntityError\u003c/p\u003e\n\u003c/dd\u003e\n\u003c/dl\u003e\n\n\u003ca name=\"Entity\"\u003e\u003c/a\u003e\n\n## Entity\n\n{Function} Entity\n\n**Kind**: global class **Requires**: \u003ccode\u003emodule:events\u003c/code\u003e, \u003ccode\u003emodule:debug\u003c/code\u003e, \u003ccode\u003emodule:util\u003c/code\u003e, \u003ccode\u003emodule:lodash\u003c/code\u003e **License**: MIT\n\n- [Entity](#Entity)\n  - [new Entity([snapshot], [events])](#new_Entity_new)\n  - _instance_\n    - [.emit()](#Entity+emit)\n    - [.enqueue()](#Entity+enqueue)\n    - [.digest(method, data)](#Entity+digest)\n    - [.merge(snapshot)](#Entity+merge)\n    - [.mergeProperty(name, value)](#Entity+mergeProperty)\n    - [.replay(events)](#Entity+replay)\n    - [.snapshot()](#Entity+snapshot) ⇒ \u003ccode\u003eObject\u003c/code\u003e\n    - [.trimSnapshot(snapshot)](#Entity+trimSnapshot)\n  - _static_\n    - [.digestMethod(type, fn)](#Entity.digestMethod)\n    - [.mergeProperty(type, name, fn)](#Entity.mergeProperty)\n\n\u003ca name=\"new_Entity_new\"\u003e\u003c/a\u003e\n\n### new Entity([snapshot], [events])\n\nCreates an event-sourced Entity.\n\n| Param | Type | Description |\n| --- | --- | --- |\n| [snapshot] | \u003ccode\u003eObject\u003c/code\u003e | A previously saved snapshot of an entity. |\n| [events] | \u003ccode\u003eArray\u003c/code\u003e | An array of events to apply on instantiation. |\n\n\u003ca name=\"Entity+emit\"\u003e\u003c/a\u003e\n\n### entity.emit()\n\nWrapper around the EventEmitter.emit method that adds a condition so events are not fired during replay.\n\n**Kind**: instance method of \u003ccode\u003e[Entity](#Entity)\u003c/code\u003e \u003ca name=\"Entity+enqueue\"\u003e\u003c/a\u003e\n\n### entity.enqueue()\n\nAdd events to the queue of events to emit. If called during replay, this method does nothing.\n\n**Kind**: instance method of \u003ccode\u003e[Entity](#Entity)\u003c/code\u003e \u003ca name=\"Entity+digest\"\u003e\u003c/a\u003e\n\n### entity.digest(method, data)\n\nDigest a command with given data.This is called whenever you want to record a command into the events for the entity. If called during replay, this method does nothing.\n\n**Kind**: instance method of \u003ccode\u003e[Entity](#Entity)\u003c/code\u003e\n\n| Param | Type | Description |\n| --- | --- | --- |\n| method | \u003ccode\u003eString\u003c/code\u003e | the name of the method/command you want to digest. |\n| data | \u003ccode\u003eObject\u003c/code\u003e | the data that should be passed to the replay. |\n\n\u003ca name=\"Entity+merge\"\u003e\u003c/a\u003e\n\n### entity.merge(snapshot)\n\nMerge a snapshot onto the entity.\n\nFor every property passed in the snapshot, the value is deep-cloned and then merged into the instance through mergeProperty. See mergeProperty for details.\n\n**Kind**: instance method of \u003ccode\u003e[Entity](#Entity)\u003c/code\u003e **See**: Entity.prototype.mergeProperty\n\n| Param    | Type                | Description      |\n| -------- | ------------------- | ---------------- |\n| snapshot | \u003ccode\u003eObject\u003c/code\u003e | snapshot object. |\n\n\u003ca name=\"Entity+mergeProperty\"\u003e\u003c/a\u003e\n\n### entity.mergeProperty(name, value)\n\nMerge a property onto the instance.\n\nGiven a name and a value, mergeProperty checks first attempt to find the property in the mergeProperties map using the constructor name as key. If it is found and it is a function, the function is called. If it is NOT found we check if the property is an object. If so, we merge. If not, we simply assign the passed value to the instance.\n\n**Kind**: instance method of \u003ccode\u003e[Entity](#Entity)\u003c/code\u003e **See**\n\n- mergeProperties\n- Entity.mergeProperty\n\n| Param | Type                | Description                             |\n| ----- | ------------------- | --------------------------------------- |\n| name  | \u003ccode\u003eString\u003c/code\u003e | the name of the property being merged.  |\n| value | \u003ccode\u003eObject\u003c/code\u003e | the value of the property being merged. |\n\n\u003ca name=\"Entity+replay\"\u003e\u003c/a\u003e\n\n### entity.replay(events)\n\nReplay an array of events onto the instance.\n\nThe goal here is to allow application of events without emitting, enqueueing nor digesting the replayed events. This is done by setting this.replaying to true which emit, enqueue and digest check for.\n\nIf the method in the event being replayed exists in the instance, we call the mathod with the data in the event and set the version of the instance to the version of the event. If the method is not found, we attempt to parse the constructor to give a more descriptive error.\n\n**Kind**: instance method of \u003ccode\u003e[Entity](#Entity)\u003c/code\u003e\n\n| Param  | Type               | Description                        |\n| ------ | ------------------ | ---------------------------------- |\n| events | \u003ccode\u003eArray\u003c/code\u003e | an array of events to be replayed. |\n\n\u003ca name=\"Entity+snapshot\"\u003e\u003c/a\u003e\n\n### entity.snapshot() ⇒ \u003ccode\u003eObject\u003c/code\u003e\n\nCreate a snapshot of the current state of the entity instance.\n\nHere the instance's snapshotVersion property is set to the current version, then the instance is deep-cloned and the clone is trimmed of the internal sourced attributes using trimSnapshot and returned.\n\n**Kind**: instance method of \u003ccode\u003e[Entity](#Entity)\u003c/code\u003e \u003ca name=\"Entity+trimSnapshot\"\u003e\u003c/a\u003e\n\n### entity.trimSnapshot(snapshot)\n\nRemove the internal sourced properties from the passed snapshot.\n\nSnapshots are to contain only entity data properties. This trims all other properties from the snapshot.\n\n**Kind**: instance method of \u003ccode\u003e[Entity](#Entity)\u003c/code\u003e **See**: Entity.prototype.snapshot\n\n| Param    | Type                | Description                 |\n| -------- | ------------------- | --------------------------- |\n| snapshot | \u003ccode\u003eObject\u003c/code\u003e | the snapshot to be trimmed. |\n\n\u003ca name=\"Entity.digestMethod\"\u003e\u003c/a\u003e\n\n### Entity.digestMethod(type, fn)\n\nHelper function to automatically create a method that calls digest on the param provided. Use it to add methods that automatically call digest.\n\n**Kind**: static method of \u003ccode\u003e[Entity](#Entity)\u003c/code\u003e\n\n| Param | Type | Description |\n| --- | --- | --- |\n| type | \u003ccode\u003eObject\u003c/code\u003e | the entity class to which the method will be added. |\n| fn | \u003ccode\u003efunction\u003c/code\u003e | the actual function to be added. |\n\n**Example**\n\n```js\nEntity.digestMethod(Car, function clearSettings(param) {\n  const self = this\n\n  this.settings.get(param.name).forEach((name, config) =\u003e {\n    config.sources.forEach((source) =\u003e {\n      source.remove()\n    })\n  })\n\n  return this.settings\n})\n```\n\n\u003ca name=\"Entity.mergeProperty\"\u003e\u003c/a\u003e\n\n### Entity.mergeProperty(type, name, fn)\n\nConvenience function to store references to functions that should be run when mergin a particular property.\n\n**Kind**: static method of \u003ccode\u003e[Entity](#Entity)\u003c/code\u003e **See**: mergeProperties\n\n| Param | Type | Description |\n| --- | --- | --- |\n| type | \u003ccode\u003eObject\u003c/code\u003e | the entity class to which the property-\u003efn belongs to. |\n| name | \u003ccode\u003eString\u003c/code\u003e | the name of the property that holds the fn. |\n| fn | \u003ccode\u003efunction\u003c/code\u003e | the function to execute when merging the property. |\n\n**Example**\n\n```js\nfunction Wheel(status) {\n  this.status = status\n}\n\nWheel.prototype.go = function () {\n  this.status = 'going'\n}\n\nfunction Car() {\n  this.id = null\n  this.wheel = new Wheel() // for instantiating our default wheel, when we first 'new' up a Car\n\n  Entity.apply(this, arguments)\n}\n\nutil.inherits(Car, Entity)\n\nEntity.mergeProperty(Car, 'interests', function (obj) {\n  this.wheel = new Wheel() // for instantiating our wheel from saved values in a database\n})\n```\n\n\u003ca name=\"eventsToEmit\"\u003e\u003c/a\u003e\n\n## eventsToEmit : \u003ccode\u003eArray\u003c/code\u003e\n\n[Description]\n\n**Kind**: global variable **Todo**\n\n- [ ] discuss the use of this so it can be documented better.\n\n\u003ca name=\"newEvents\"\u003e\u003c/a\u003e\n\n## newEvents : \u003ccode\u003eArray\u003c/code\u003e\n\n[Description]\n\n**Kind**: global variable **Todo**\n\n- [ ] discuss the use of this so it can be documented better.\n\n\u003ca name=\"replaying\"\u003e\u003c/a\u003e\n\n## replaying : \u003ccode\u003eBoolean\u003c/code\u003e\n\nBoolean to prevent emit, enqueue and digest from running during replay.\n\n**Kind**: global variable \u003ca name=\"snapshotVersion\"\u003e\u003c/a\u003e\n\n## snapshotVersion : \u003ccode\u003eNumber\u003c/code\u003e\n\nHolds the version of the latest snapshot for the entity.\n\n**Kind**: global variable \u003ca name=\"timestamp\"\u003e\u003c/a\u003e\n\n## timestamp : \u003ccode\u003eNumber\u003c/code\u003e\n\nHolds the event's timestamp in the entity.\n\n**Kind**: global variable \u003ca name=\"version\"\u003e\u003c/a\u003e\n\n## version : \u003ccode\u003eNumber\u003c/code\u003e\n\nHolds the current version of the entity.\n\n**Kind**: global variable \u003ca name=\"mergeProperties\"\u003e\u003c/a\u003e\n\n## mergeProperties\n\nmergeProperties holds a map of entity types to properties.\n\n**Kind**: global variable **See**\n\n- Entity.mergeProperty\n- Entity.prototype.mergeProperty\n\n\u003ca name=\"EntityError\"\u003e\u003c/a\u003e\n\n## EntityError\n\n{Function} EntityError\n\n**Kind**: global class \u003ca name=\"new_EntityError_new\"\u003e\u003c/a\u003e\n\n### new EntityError(msg, [constr])\n\nExtending native Error.\n\n| Param | Type | Default | Description |\n| --- | --- | --- | --- |\n| msg | \u003ccode\u003eString\u003c/code\u003e |  | The error message. |\n| [constr] | \u003ccode\u003eObject\u003c/code\u003e | \u003ccode\u003ethis\u003c/code\u003e | The constructor or instance. |\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fpdmlab%2Fsourced-ts","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fpdmlab%2Fsourced-ts","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fpdmlab%2Fsourced-ts/lists"}