{"id":38598808,"url":"https://github.com/fraktalio/fmodel-ts","last_synced_at":"2026-01-17T08:31:40.394Z","repository":{"id":36959957,"uuid":"402151134","full_name":"fraktalio/fmodel-ts","owner":"fraktalio","description":"Functional Domain Modeling with Typescript","archived":false,"fork":false,"pushed_at":"2025-12-17T21:09:13.000Z","size":29241,"stargazers_count":150,"open_issues_count":5,"forks_count":13,"subscribers_count":2,"default_branch":"main","last_synced_at":"2025-12-21T08:42:55.651Z","etag":null,"topics":["cqrs","event-sourcing","eventsourcing","typescript"],"latest_commit_sha":null,"homepage":"https://fraktalio.com/fmodel-ts","language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"other","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/fraktalio.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,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2021-09-01T17:42:29.000Z","updated_at":"2025-12-17T21:09:18.000Z","dependencies_parsed_at":"2023-10-14T20:05:21.723Z","dependency_job_id":"73f7a931-acac-455d-94fc-b11e5e9a6d82","html_url":"https://github.com/fraktalio/fmodel-ts","commit_stats":{"total_commits":594,"total_committers":5,"mean_commits":118.8,"dds":"0.24747474747474751","last_synced_commit":"21417d84ab09cd59ad9479a30b097d8fd2778423"},"previous_names":[],"tags_count":19,"template":false,"template_full_name":null,"purl":"pkg:github/fraktalio/fmodel-ts","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/fraktalio%2Ffmodel-ts","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/fraktalio%2Ffmodel-ts/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/fraktalio%2Ffmodel-ts/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/fraktalio%2Ffmodel-ts/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/fraktalio","download_url":"https://codeload.github.com/fraktalio/fmodel-ts/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/fraktalio%2Ffmodel-ts/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":28504364,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-01-17T06:57:29.758Z","status":"ssl_error","status_checked_at":"2026-01-17T06:56:03.931Z","response_time":85,"last_error":"SSL_read: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"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","eventsourcing","typescript"],"created_at":"2026-01-17T08:31:40.289Z","updated_at":"2026-01-17T08:31:40.373Z","avatar_url":"https://github.com/fraktalio.png","language":"TypeScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# **f`(`model`)`** - Functional Domain Modeling with TypeScript\n\n[![CI with Node/NPM - Test and Build](https://github.com/fraktalio/fmodel-ts/actions/workflows/node-test-build.yml/badge.svg)](https://github.com/fraktalio/fmodel-ts/actions/workflows/node-test-build.yml)\n\n\u003c!-- TOC --\u003e\n* [**f`(`model`)`** - Functional Domain Modeling with TypeScript](#fmodel---functional-domain-modeling-with-typescript)\n  * [`IOR\u003cLibrary, Inspiration\u003e`](#iorlibrary-inspiration)\n  * [`(command: C, state: S) =\u003e readonly E[]`](#command-c-state-s--readonly-e)\n  * [`(state: S, event: E) =\u003e S`](#state-s-event-e--s)\n  * [Event-sourced or State-stored systems](#event-sourced-or-state-stored-systems)\n  * [Decider](#decider)\n    * [Event-sourcing aggregate](#event-sourcing-aggregate)\n    * [State-stored aggregate](#state-stored-aggregate)\n  * [View](#view)\n    * [Materialized View](#materialized-view)\n  * [Saga](#saga)\n    * [Saga Manager](#saga-manager)\n  * [Event Modeling](#event-modeling)\n  * [Structuring the data](#structuring-the-data)\n    * [`C` / Command / Intent to change the state of the system](#c--command--intent-to-change-the-state-of-the-system)\n    * [`E` / Event / Fact](#e--event--fact)\n    * [`S` / State / Current state of the system/aggregate/entity](#s--state--current-state-of-the-systemaggregateentity)\n  * [Modeling the Behaviour](#modeling-the-behaviour)\n      * [Decider - data type that represents the main decision-making algorithm.](#decider---data-type-that-represents-the-main-decision-making-algorithm)\n      * [Event-sourcing aggregate](#event-sourcing-aggregate-1)\n  * [Install as a dependency of your project](#install-as-a-dependency-of-your-project)\n  * [Examples](#examples)\n  * [FModel in other languages](#fmodel-in-other-languages)\n  * [Resources](#resources)\n  * [Credits](#credits)\n\u003c!-- TOC --\u003e\n\n\n\u003e v2.0.0 of the library is introducing breaking changes. [Check the PR](https://github.com/fraktalio/fmodel-ts/pull/692)!\n\u003e Besides keeping the focus on separating data from behavior, we want to split the responsibilities between the domain and application/adapter layers better.\n\u003e For example, `metadata types` exist only on the application layer, not leaking into the domain, as these don't benefit core logic. Example: `traceId`, `correlationId`, ...\n\u003e To keep it simple, `v2.*.*` will use the main branch going forward. [v1.*.*](https://github.com/fraktalio/fmodel-ts/tree/v1) will continue to be supported (bugs only, no new features)\n\nWhen you’re developing an information system to automate the activities of the business, you are modeling the business.\nThe abstractions that you design, the behaviors that you implement, and the UI interactions that you build all reflect\nthe business — together, they constitute the model of the domain.\n\n![event-modeling](https://github.com/fraktalio/fmodel-ts/raw/main/.assets/event-modeling.png)\n\n## `IOR\u003cLibrary, Inspiration\u003e`\n\nThis project can be used as a library, or as an inspiration, or both. It provides just enough tactical Domain-Driven\nDesign patterns, optimised for Event Sourcing and CQRS.\n\nThe library is fully isolated from the application and infrastructure layers. It represents a pure \ndeclaration of the program logic. It is written in [TypeScript](https://www.typescriptlang.org/) programming language.\n  \n## `(command: C, state: S) =\u003e readonly E[]`\n\nOn a higher level of abstraction, any information system is responsible for handling the intent (`Command`) and based on\nthe current `State`, produce new facts (`Events`):\n\n- given the current `State/S` *on the input*,\n- when `Command/C` is handled *on the input*,\n- expect `list` of new `Events/E` to be published/emitted *on the output*\n\n## `(state: S, event: E) =\u003e S`\n\nThe new state is always evolved out of the current state `S` and the current event `E`:\n\n- given the current `State/S` *on the input*,\n- when `Event/E` is handled *on the input*,\n- expect new `State/S` to be published *on the output*\n\n## Event-sourced or State-stored systems\n\n- State-stored systems are traditional systems that are only storing the current State by overwriting the previous State\n  in the storage.\n- Event-sourced systems are storing the events in immutable storage by only appending.\n\n**Both types of systems can be designed by using only these two functions and three generic parameters**\n\n![event sourced vs state stored](https://github.com/fraktalio/fmodel-ts/raw/main/.assets/es-ss-system.png)\n\n`Decider` is the most important datatype, but it is not the only one. Let's discuss all of them, and visualize how they fit the Onion architecture.\nThe arrows in the image are showing the direction of the dependency. Notice that all dependencies point inwards, and that Domain is not depending on anything.\n\n![onion architecture image](https://github.com/fraktalio/fmodel-ts/raw/main/.assets/onion.png)\n\n## Decider\n\n`Decider` is a datatype that represents the main decision-making algorithm. It belongs to the Domain layer. It has three\ngeneric parameters `C`, `S`, `E` , representing the type of the values that `Decider` may contain or use.\n`Decider` can be specialized for any type `C` or `S` or `E` because these types do not affect its\nbehavior. `Decider` behaves the same for `C`=`Int` or `C`=`YourCustomType`, for example.\n\n`Decider` is a pure domain component.\n\n- `C` - Command\n- `S` - State\n- `E` - Event\n\nNotice that `Decider` implements an interface `IDecider` to communicate the contract.\n\n```typescript\nexport class Decider\u003cC, S, E\u003e implements IDecider\u003cC, S, E\u003e {\n  constructor(\n    readonly decide: (c: C, s: S) =\u003e readonly E[],\n    readonly evolve: (s: S, e: E) =\u003e S,\n    readonly initialState: S\n  ) {\n  }\n}\n```\n\nAdditionally, `initialState` of the Decider is introduced to gain more control over the initial state of the Decider.\n\n![decider image](https://github.com/fraktalio/fmodel-ts/raw/main/.assets/decider.png)\n\nWe can now construct event-sourcing or/and state-storing aggregate by using the same `decider`.\n\n### Event-sourcing aggregate\n\n`Event sourcing aggregate` is using/delegating a `Decider` to handle commands and produce events. It belongs to the Application layer.\nIn order to handle the command, aggregate needs to fetch the current state (represented as a list of events)\nvia `EventRepository.fetchEvents` function, and then delegate the command to the decider which can produce new events as a result.\nProduced events are then stored via `EventRepository.save` function.\n\n![event sourced aggregate](https://github.com/fraktalio/fmodel-ts/raw/main/.assets/es-aggregate.png)\n\n### State-stored aggregate\n\n`State stored aggregate` is using/delegating a `Decider` to handle commands and produce new state. It belongs to the Application layer.\nIn order to handle the command, aggregate needs to fetch the current state via `StateRepository.fetchState` function first, and then\ndelegate the command to the decider which can produce new state as a result.\nNew state is then stored via `StateRepository.save` function.\n\n![state storedaggregate](https://github.com/fraktalio/fmodel-ts/raw/main/.assets/ss-aggregate.png)\n\n\n## View\n\n`View`  is a datatype that represents the event handling algorithm, responsible for translating the events into\ndenormalized state, which is more adequate for querying. It belongs to the Domain layer. It is usually used to create\nthe view/query side of the CQRS pattern. Obviously, the command side of the CQRS is usually event-sourced aggregate.\n\nIt has two generic parameters `S`, `E`, representing the type of the values that `View` may contain or use.\n`View` can be specialized for any type of `S`, `E` because these types do not affect its behavior.\n`View` behaves the same for `E`=`Int` or `E`=`YourCustomType`, for example.\n\n`View` is a pure domain component.\n\n- `S` - State\n- `E` - Event\n\nNotice that `View` implements an interface `IView` to communicate the contract.\n\n\n```typescript\nexport class View\u003cS, E\u003e implements IView\u003cS, E\u003e {\n  constructor(readonly evolve: (s: S, e: E) =\u003e S, readonly initialState: S) {\n  }\n}\n```\n\n![view image](https://github.com/fraktalio/fmodel-ts/raw/main/.assets/view.png)\n\n### Materialized View\n\nA `Materialized view` is using/delegating a `View` to handle events of type `E` and to maintain a state of denormalized projection(s) as a\nresult. Essentially, it represents the query/view side of the CQRS pattern. It belongs to the Application layer.\n\nIn order to handle the event, materialized view needs to fetch the current state via `ViewStateRepository.fetchState` function first, and then delegate the event to the view, which can produce new state as a result. New state\nis then stored via `ViewStateRepository.save` function.\n\n\n## Saga\n\n`Saga` is a datatype that represents the central point of control, deciding what to execute next (`A`). It is\nresponsible for mapping different events from many aggregates into action results `AR` that the `Saga` then can use to\ncalculate the next actions `A` to be mapped to commands of other aggregates.\n\n`Saga` is stateless, it does not maintain the state.\n\nIt has two generic parameters `AR`, `A`, representing the type of the values that `Saga` may contain or use.\n`Saga` can be specialized for any type of `AR`, `A` because these types do not affect its behavior.\n`Saga` behaves the same for `AR`=`Int` or `AR`=`YourCustomType`, for example.\n\n`Saga` is a pure domain component.\n\n- `AR` - Action Result\n- `A`  - Action\n\nNotice that `Saga` implements an interface `ISaga` to communicate the contract.\n\n```typescript\nexport class Saga\u003cAR, A\u003e implements ISaga\u003cAR, A\u003e{\n    constructor(readonly react: (ar: AR) =\u003e readonly A[]) {}\n}\n```\n\n![saga image](https://github.com/fraktalio/fmodel-ts/raw/main/.assets/saga.png)\n\n### Saga Manager\n\n`Saga manager` is a stateless process orchestrator. It belongs to the Application layer.\nIt is reacting on Action Results of type `AR` and produces new actions `A` based on them.\n\nSaga manager is using/delegating a `Saga` to react on Action Results of type `AR` and produce new actions `A` which are\ngoing to be published via `ActionPublisher.publish` function.\n\n## Event Modeling\n\n[Event Modeling](https://eventmodeling.org/posts/what-is-event-modeling/) is:\n\n- a method of describing systems using an example of how information has changed within them over time.\n- a scenario-based and UX-driven approach to defining requirements.\n\n![restaurant model](https://github.com/fraktalio/fmodel-ts/raw/main/.assets/restaurant-model.jpg)\n\n## Structuring the data\n\n\u003e TypeScript adopts a structural type system which determines type compatibility and equivalence based on the type structure or definition rather than the declarative relationship between types and interfaces, which contrasts with nominal type system.\n\nIn TypeScript, we can use Algebraic Data Types (ADTs) to model our application's domain entities and relationships in a functional way, clearly defining the set of possible values and states.\nTypeScript has two main types of ADTs: union types (`\"|\"` operator), intersection types (`\"\u0026\"` operator), tuples and records\n\n- `union types` is used to define a type that can take on one of several possible variants - modeling a `sum/OR` type.\n- `intersection types`, `tuples` and `records` are used to combine several types into one - modeling a `product/AND` type.\n\nADTs will help with\n\n- representing the business domain in the code accurately\n- enforcing correctness\n- reducing the likelihood of bugs.\n\nIn FModel, we extensively use ADTs to model the data.\n\n### `C` / Command / Intent to change the state of the system\n\n```typescript\n// Be precise and explicit about the types\nexport type SchemaVersion = number;\nexport type RestaurantId = string;\nexport type OrderId = string;\nexport type MenuItemId = string;\nexport type RestaurantName = string;\nexport type RestaurantMenuId = string;\nexport type MenuItemName = string;\nexport type MenuItemPrice = string;\n```\n```typescript\nexport type Command = RestaurantCommand | OrderCommand;\n\nexport type RestaurantCommand = CreateRestaurantCommand | ChangeRestaurantMenuCommand | PlaceOrderCommand;\n\nexport type CreateRestaurantCommand = {\n  readonly decider: \"Restaurant\";\n  readonly kind: \"CreateRestaurantCommand\";\n  readonly id: RestaurantId;\n  readonly name: RestaurantName;\n  readonly menu: RestaurantMenu;\n};\n\nexport type ChangeRestaurantMenuCommand = {\n  readonly decider: \"Restaurant\";\n  readonly kind: \"ChangeRestaurantMenuCommand\";\n  readonly id: RestaurantId;\n  readonly menu: RestaurantMenu;\n};\n\nexport type PlaceOrderCommand = {\n  readonly decider: \"Restaurant\";\n  readonly kind: \"PlaceOrderCommand\";\n  readonly id: RestaurantId;\n  readonly orderId: OrderId;\n  readonly menuItems: MenuItem[];\n};\n```\n\n```typescript\nexport type OrderCommand = CreateOrderCommand | MarkOrderAsPreparedCommand;\n\nexport type CreateOrderCommand = {\n  decider: \"Order\";\n  kind: \"CreateOrderCommand\";\n  id: OrderId;\n  restaurantId: RestaurantId;\n  menuItems: MenuItem[];\n};\n\nexport type MarkOrderAsPreparedCommand = {\n  decider: \"Order\";\n  kind: \"MarkOrderAsPreparedCommand\";\n  id: OrderId;\n};\n```\n\n### `E` / Event / Fact\n\n```typescript\nexport type Event = RestaurantEvent | OrderEvent;\n\nexport type RestaurantEvent =\n  | RestaurantCreatedEvent\n  | RestaurantNotCreatedEvent\n  | RestaurantMenuChangedEvent\n  | RestaurantMenuNotChangedEvent\n  | RestaurantOrderPlacedEvent\n  | RestaurantOrderNotPlacedEvent;\n\nexport type RestaurantCreatedEvent = {\n  readonly version: SchemaVersion;\n  readonly decider: \"Restaurant\";\n  readonly kind: \"RestaurantCreatedEvent\";\n  readonly id: RestaurantId;\n  readonly name: RestaurantName;\n  readonly menu: RestaurantMenu;\n  readonly final: boolean;\n};\n\nexport type RestaurantNotCreatedEvent = {\n  readonly version: SchemaVersion;\n  readonly decider: \"Restaurant\";\n  readonly kind: \"RestaurantNotCreatedEvent\";\n  readonly id: RestaurantId;\n  readonly name: RestaurantName;\n  readonly menu: RestaurantMenu;\n  readonly reason: Reason;\n  readonly final: boolean;\n};\n\nexport type RestaurantMenuChangedEvent = {\n  readonly version: SchemaVersion;\n  readonly decider: \"Restaurant\";\n  readonly kind: \"RestaurantMenuChangedEvent\";\n  readonly id: RestaurantId;\n  readonly menu: RestaurantMenu;\n  readonly final: boolean;\n};\n\nexport type RestaurantMenuNotChangedEvent = {\n  readonly version: SchemaVersion;\n  readonly decider: \"Restaurant\";\n  readonly kind: \"RestaurantMenuNotChangedEvent\";\n  readonly id: RestaurantId;\n  readonly menu: RestaurantMenu;\n  readonly reason: Reason;\n  readonly final: boolean;\n};\n\nexport type RestaurantOrderPlacedEvent = {\n  readonly version: SchemaVersion;\n  readonly decider: \"Restaurant\";\n  readonly kind: \"RestaurantOrderPlacedEvent\";\n  readonly id: RestaurantId;\n  readonly orderId: OrderId;\n  readonly menuItems: MenuItem[];\n  readonly final: boolean;\n};\n\nexport type RestaurantOrderNotPlacedEvent = {\n  readonly version: SchemaVersion;\n  readonly decider: \"Restaurant\";\n  readonly kind: \"RestaurantOrderNotPlacedEvent\";\n  readonly id: RestaurantId;\n  readonly orderId: OrderId;\n  readonly menuItems: MenuItem[];\n  readonly reason: Reason;\n  readonly final: boolean;\n};\n```\n\n```typescript\nexport type OrderEvent =\n  | OrderCreatedEvent\n  | OrderNotCreatedEvent\n  | OrderPreparedEvent\n  | OrderNotPreparedEvent;\n\nexport type OrderCreatedEvent = {\n  version: SchemaVersion;\n  decider: \"Order\";\n  kind: \"OrderCreatedEvent\";\n  id: OrderId;\n  restaurantId: RestaurantId;\n  menuItems: MenuItem[];\n  final: boolean;\n};\n\nexport type OrderNotCreatedEvent = {\n  version: SchemaVersion;\n  decider: \"Order\";\n  kind: \"OrderNotCreatedEvent\";\n  id: OrderId;\n  restaurantId: RestaurantId;\n  reason: Reason;\n  menuItems: MenuItem[];\n  final: boolean;\n};\n\nexport type OrderPreparedEvent = {\n  version: SchemaVersion;\n  decider: \"Order\";\n  kind: \"OrderPreparedEvent\";\n  id: OrderId;\n  final: boolean;\n};\n\nexport type OrderNotPreparedEvent = {\n  version: SchemaVersion;\n  decider: \"Order\";\n  kind: \"OrderNotPreparedEvent\";\n  id: OrderId;\n  reason: Reason;\n  final: boolean;\n};\n```\n### `S` / State / Current state of the system/aggregate/entity\n\n```typescript\n/**\n * Restaurant state / a data type that represents the current state of the Restaurant\n */\nexport type Restaurant = {\n  readonly restaurantId: RestaurantId;\n  readonly name: RestaurantName;\n  readonly menu: RestaurantMenu;\n};\n```\n\n```typescript\n/**\n * Order state / a data type that represents the current state of the Order\n */\nexport type Order = {\n  readonly orderId: OrderId;\n  readonly restaurantId: RestaurantId;\n  readonly menuItems: MenuItem[];\n  readonly status: OrderStatus;\n};\n```\n\n## Modeling the Behaviour\n\n- algebraic data types form the structure of our entities (commands, state, and events).\n- functions/lambda offers the algebra of manipulating the entities in a compositional manner, effectively modeling the behavior.\n\nThis leads to modularity in design and a clear separation of the entity’s structure and functions/behaviour of the entity.\n\nFmodel library offers generic and abstract components to specialize in for your specific case/expected behavior:\n\n#### Decider - data type that represents the main decision-making algorithm.\n\n```typescript\n/**\n * Restaurant - `pure` command handler / a decision-making component\n * ___\n * A pure command handling algorithm, responsible for evolving the state of the restaurant.\n * It does not produce any side effects, such as I/O, logging, etc.\n * It utilizes type narrowing to make sure that the command is handled exhaustively.\n * https://www.typescriptlang.org/docs/handbook/2/narrowing.html#exhaustiveness-checking\n * ___\n * @param c - command type that is being handled - `RestaurantCommand`\n * @param s - state type that is being evolved - `Restaurant | null`\n * @param e - event type that is being produced / a fact / an outcome of the decision - `RestaurantEvent`\n */\nexport const restaurantDecider: Decider\u003c\n  RestaurantCommand,\n  Restaurant | null,\n  RestaurantEvent\n\u003e = new Decider\u003cRestaurantCommand, Restaurant | null, RestaurantEvent\u003e(\n  (command, currentState) =\u003e {\n    switch (command.kind) {\n      case \"CreateRestaurantCommand\":\n        return currentState == null\n          ? [\n            {\n              version: 1,\n              decider: \"Restaurant\",\n              kind: \"RestaurantCreatedEvent\",\n              id: command.id,\n              name: command.name,\n              menu: command.menu,\n              final: false,\n            },\n          ]\n          : [\n            {\n              version: 1,\n              decider: \"Restaurant\",\n              kind: \"RestaurantNotCreatedEvent\",\n              id: command.id,\n              name: command.name,\n              menu: command.menu,\n              reason: \"Restaurant already exist!\",\n              final: false,\n            },\n          ];\n      case \"ChangeRestaurantMenuCommand\":\n        return currentState !== null\n          ? [\n            {\n              version: 1,\n              decider: \"Restaurant\",\n              kind: \"RestaurantMenuChangedEvent\",\n              id: currentState.restaurantId,\n              menu: command.menu,\n              final: false,\n            },\n          ]\n          : [\n            {\n              version: 1,\n              decider: \"Restaurant\",\n              kind: \"RestaurantMenuNotChangedEvent\",\n              id: command.id,\n              menu: command.menu,\n              reason: \"Restaurant does not exist!\",\n              final: false,\n            },\n          ];\n      case \"PlaceOrderCommand\":\n        return currentState !== null\n          ? [\n            {\n              version: 1,\n              decider: \"Restaurant\",\n              kind: \"RestaurantOrderPlacedEvent\",\n              id: command.id,\n              orderId: command.orderId,\n              menuItems: command.menuItems,\n              final: false,\n            },\n          ]\n          : [\n            {\n              version: 1,\n              decider: \"Restaurant\",\n              kind: \"RestaurantOrderNotPlacedEvent\",\n              id: command.id,\n              orderId: command.orderId,\n              menuItems: command.menuItems,\n              reason: \"Restaurant does not exist!\",\n              final: false,\n            },\n          ];\n      default:\n        // Exhaustive matching of the command type\n        const _: never = command;\n        return [];\n    }\n  },\n  (currentState, event) =\u003e {\n    switch (event.kind) {\n      case \"RestaurantCreatedEvent\":\n        return { restaurantId: event.id, name: event.name, menu: event.menu };\n      case \"RestaurantNotCreatedEvent\":\n        return currentState;\n      case \"RestaurantMenuChangedEvent\":\n        return currentState !== null\n          ? {\n            restaurantId: currentState.restaurantId,\n            name: currentState.name,\n            menu: event.menu,\n          }\n          : currentState;\n      case \"RestaurantMenuNotChangedEvent\":\n        return currentState;\n      case \"RestaurantOrderPlacedEvent\":\n        return currentState;\n      case \"RestaurantOrderNotPlacedEvent\":\n        return currentState;\n      default:\n        // Exhaustive matching of the event type\n        const _: never = event;\n        return currentState;\n    }\n  },\n  null,\n);\n```\n\n```typescript\n/**\n * Order  - `pure` command handler / a decision-making component\n * ___\n * A pure command handling algorithm, responsible for evolving the state of the order.\n * It does not produce any side effects, such as I/O, logging, etc.\n * It utilizes type narrowing to make sure that the command is handled exhaustively.\n * https://www.typescriptlang.org/docs/handbook/2/narrowing.html#exhaustiveness-checking\n * ___\n * @param c - command type that is being handled - `OrderCommand`\n * @param s - state type that is being evolved - `Order | null`\n * @param e - event type that is being produced / a fact / an outcome of the decision - `Order`Event`\n */\nexport const orderDecider: Decider\u003cOrderCommand, Order | null, OrderEvent\u003e =\n  new Decider\u003cOrderCommand, Order | null, OrderEvent\u003e(\n    (command, currentState) =\u003e {\n      switch (command.kind) {\n        case \"CreateOrderCommand\":\n          return currentState == null\n            ? [\n              {\n                version: 1,\n                decider: \"Order\",\n                kind: \"OrderCreatedEvent\",\n                id: command.id,\n                restaurantId: command.restaurantId,\n                menuItems: command.menuItems,\n                final: false,\n              },\n            ]\n            : [\n              {\n                version: 1,\n                decider: \"Order\",\n                kind: \"OrderNotCreatedEvent\",\n                id: command.id,\n                restaurantId: command.restaurantId,\n                menuItems: command.menuItems,\n                final: false,\n                reason: \"Order already exist!\",\n              },\n            ];\n        case \"MarkOrderAsPreparedCommand\":\n          return currentState !== null\n            ? [\n              {\n                version: 1,\n                decider: \"Order\",\n                kind: \"OrderPreparedEvent\",\n                id: currentState.orderId,\n                final: false,\n              },\n            ]\n            : [\n              {\n                version: 1,\n                decider: \"Order\",\n                kind: \"OrderNotPreparedEvent\",\n                id: command.id,\n                reason: \"Order does not exist!\",\n                final: false,\n              },\n            ];\n        default:\n          // Exhaustive matching of the command type\n          const _: never = command;\n          return [];\n      }\n    },\n    (currentState, event) =\u003e {\n      switch (event.kind) {\n        case \"OrderCreatedEvent\":\n          return {\n            orderId: event.id,\n            restaurantId: event.restaurantId,\n            menuItems: event.menuItems,\n            status: \"CREATED\",\n          };\n        case \"OrderNotCreatedEvent\":\n          return currentState;\n        case \"OrderPreparedEvent\":\n          return currentState !== null\n            ? {\n              orderId: currentState.orderId,\n              restaurantId: currentState.restaurantId,\n              menuItems: currentState.menuItems,\n              status: \"PREPARED\",\n            }\n            : currentState;\n        case \"OrderNotPreparedEvent\":\n          return currentState;\n        default:\n          // Exhaustive matching of the event type\n          const _: never = event;\n          return currentState;\n      }\n    },\n    null,\n  );\n```\n\nThe logic execution will be orchestrated by the outside components that use the domain components (decider, view) to do the computations. These components will be responsible for fetching and saving the data (repositories).\n\n\nThe arrows in the image (adapters-\u003eapplication-\u003edomain) show the direction of the dependency. Notice that all dependencies point inward and that Domain does not depend on anybody or anything.\n\nPushing these decisions from the core domain model is very valuable. Being able to postpone them is a sign of good architecture.\n\n#### Event-sourcing aggregate\n```typescript\n// We are adding new types on this layer: Metadatada and Version. Observe how these types are not leaking into the Domain layer (decider), and not influencing the core logic.\nexport type StreamVersion = { event_id: string };\nexport type CommandMetadata = { tenant: string };\nexport type EventMetadata = {\n  tenant: string;\n  command_id: string;\n  event_id: string;\n};\n```\n```typescript\n/**\n * An aggregate that handles the command and produces new events / A convenient type alias for the Fmodel's `EventSourcingAggregate`\n * \n * This aggregate can handle all the commands of the system. Observe how two deciders (restaurant and order) are combined into one.\n */\nexport type ApplicationAggregate = EventSourcingAggregate\u003c\n  Command,\n  Restaurant \u0026 Order,\n  Event,\n  StreamVersion,\n  CommandMetadata,\n  EventMetadata\n\u003e;\n\n// Parse the command from the request\nconst command: Command = JSON.parse(await req.json());\nconsole.log(\"Handling command: \", command);\n// We can combine deciders to create a new decider that can handle both restaurant and order commands\nconst decider = restaurantDecider.combine(orderDecider);\n// Create a repository for the events of all types\nconst eventRepository = new EventRepository(supabaseClient);\n// Create an aggregate to handle the commands of all types!\nconst aggregate: ApplicationAggregate = new EventSourcingAggregate(\n  decider,\n  eventRepository,\n);\n\n// Handle the command\nconst result = await aggregate.handle(command);\n```\n## Install as a dependency of your project\n\n```shell\nnpm i @fraktalio/fmodel-ts\n```\n\u003e Available on [https://www.npmjs.com/package/@fraktalio/fmodel-ts](https://www.npmjs.com/package/@fraktalio/fmodel-ts)\n\n## Examples\n\n- Why don't you start by browsing [tests](https://github.com/fraktalio/fmodel-ts/blob/main/src/lib/domain/decider.spec.ts)?\n- [Event sourcing and event streaming with Axon - Gift Card domain](https://github.com/AxonIQ/giftcard-demo-ts)\n- [Event sourcing on Deno runtime, with Deno KV as an event store - Restaurant Management domain](https://github.com/fraktalio/fmodel-deno-demo)\n\n## FModel in other languages\n\n- [FModel in Kotlin](https://github.com/fraktalio/fmodel)\n- [FModel in Rust](https://github.com/fraktalio/fmodel-rust)\n\n\n## Resources\n\n- [The Blog - Domain modeling](https://fraktalio.com/blog/)\n- [Event Modeling - What is it?](https://eventmodeling.org/posts/what-is-event-modeling/)\n\n## Credits\n\nSpecial credits to `Jérémie Chassaing` for sharing his [research](https://www.youtube.com/watch?v=kgYGMVDHQHs)\nand `Adam Dymitruk` for hosting the meetup.\n\n---\nCreated with :heart: by [Fraktalio](https://fraktalio.com/)\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ffraktalio%2Ffmodel-ts","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Ffraktalio%2Ffmodel-ts","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ffraktalio%2Ffmodel-ts/lists"}