{"id":18729270,"url":"https://github.com/cosmicmind/domainjs","last_synced_at":"2025-04-12T16:33:36.721Z","repository":{"id":37618615,"uuid":"485852686","full_name":"CosmicMind/domainjs","owner":"CosmicMind","description":"A framework for building applications with Domain-driven design in TypeScript.","archived":false,"fork":false,"pushed_at":"2025-03-08T04:11:47.000Z","size":1437,"stargazers_count":3,"open_issues_count":0,"forks_count":0,"subscribers_count":2,"default_branch":"release","last_synced_at":"2025-04-12T03:41:34.788Z","etag":null,"topics":["domain-driven-design","typescript"],"latest_commit_sha":null,"homepage":"","language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"bsd-3-clause","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/CosmicMind.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":".github/CODEOWNERS","security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2022-04-26T15:51:25.000Z","updated_at":"2025-03-08T04:11:52.000Z","dependencies_parsed_at":"2024-01-20T05:25:37.244Z","dependency_job_id":"688ffe83-a7f9-4f3f-873a-054f741618b2","html_url":"https://github.com/CosmicMind/domainjs","commit_stats":null,"previous_names":["cosmicmind/domain"],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/CosmicMind%2Fdomainjs","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/CosmicMind%2Fdomainjs/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/CosmicMind%2Fdomainjs/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/CosmicMind%2Fdomainjs/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/CosmicMind","download_url":"https://codeload.github.com/CosmicMind/domainjs/tar.gz/refs/heads/release","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":248596743,"owners_count":21130756,"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":["domain-driven-design","typescript"],"created_at":"2024-11-07T14:26:23.803Z","updated_at":"2025-04-12T16:33:36.435Z","avatar_url":"https://github.com/CosmicMind.png","language":"TypeScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Welcome to DomainJS\n\nInspired by [domain-driven design](https://en.wikipedia.org/wiki/Domain-driven_design) (DDD), DomainJS is a domain-driven design framework for scalable systems.\n\n### Entities - What are they\n\nIn Domain-driven design (DDD) an entity is a representation of an object within a given domain, for example: a book, product order, and user would be \nentities within a domain that handles purchase orders for an e-commerce website that sold books. Let's take a look at an entity type definition for a user within our domain.\n\n```typescript\n// User.ts\n\nimport {\n  Entity,\n} from '@cosmicmind/domainjs'\n\nexport type User = Entity \u0026 {\n  id: string\n  name: string\n  age: number\n}\n```\n\nThe above example is a user entity type definition. It represents an object, in this case a user within our domain. The user is defined by the id, name, and age attribute values. When working with entities, \nDomainJS will immediately help within the following areas of concern:\n\n1. How to validate entities and reliably construct new entity instances?\n2. How to observe the entity lifecycle?\n\n#### How to validate entities and reliably construct new entity instances?\n\nLet's take a look at the following code example to understand entity validation in DomainJS.\n\n```typescript\nimport {\n  User,\n} from './User'\n\nfunction someFunction(user: User): void {\n  // ... do something\n}\n\nconst user: User = {\n  id: '123',\n  name: 'Sarah',\n  age: 29,\n}\n\n// ... \n\nsomeFunction(user)\n```\n\nIn the above code, we can see that the `user` passed to `someFunction` actually doesn't provide any guarantees of its validity. In order to guarantee validity within\n`someFunction`, validation logic would need to be executed within the function itself. An issue arises when we need to constantly validate our entities, causing validation \nlogic to exist in multiple places within our codebase. DomainJS defines the validation logic within the entity itself and calls the appropriate validators when creating and \nupdating entities, for example:\n\n```typescript\n// User.ts\n\n// ...\n\nexport const makeUser = defineEntity\u003cUser\u003e({\n  attributes: {\n    id: {\n      validator(value): boolean | never {\n        // id validation logic\n        // return true | false\n        // or throw an error\n      },\n    },\n    \n    name: {\n      validator(value): boolean | never {\n        // name validation logic\n        // return true | false\n        // or throw an error\n      },\n    },\n\n    age: {\n      validator(value): boolean | never {\n        // age validation logic\n        // return true | false\n        // or throw an error\n      },\n    },\n  },\n})\n```\n\n```typescript\nimport { \n  makeUser,\n} from './User'\n\nfunction someFunction(user: User): void {\n    // ... do something\n}\n\nconst user = makeUser({\n  id: '123',\n  name: 'Sarah',\n  age: 29,\n})\n\nconsole.log(user.id) // \"123\"\nconsole.log(user.name) // \"Sarah\"\nconsole.log(user.age) // 29\n\nsomeFunction(user)\n```\n\nBy using the constructor function returned by `defineEntity\u003cUser\u003e(...)`, each user entity is guaranteed to be valid.\nIt is impossible for the user entity to be created and reach the code at line `someFunction(user)` if it is invalid.\n\n#### How to observe the entity lifecycle?\n\nDomainJS organizes lifecycle hooks within the entity definition itself, like so: \n\n```typescript\nexport const makeUser = defineEntity\u003cUser\u003e({\n  created(user) {\n    // ... do something\n  },\n  \n  trace(user) {\n    // ... do something\n  },\n  \n  attributes: {\n    // ...\n    \n    age: {\n      validator(value): boolean | never {\n        // ... do something \n      }, \n      \n      udpated(newValue, oldValue, user): void {\n        // ... do something\n      },\n    },\n    \n    // ...\n  },\n})\n```\n\nThe above example shows the various lifecycle hooks available for entities. Let's take a look at each one of these\nhooks to understand when they are executed. \n\n##### created\n\nThe `created` lifecycle hook is executed only once when an instance is initially created. \n\n##### updated\n\nThe `updated` lifecycle hook is executed after an attribute has been updated.\n\n##### trace\n\nThe `trace` lifecycle hook is executed after the `created` and `updated` lifecycle hooks.\n\n### Value Objects\n\nA Value Object (VO) in Domain-driven design encapsulates a single value and its validity. Further to ensuring its validity, \na VO provides specific functionality that is relevant to the value itself, for example: \n\n```typescript\n// Email.ts\n\nimport {\n  Value,\n  defineValue,\n} from '@cosmicmind/domainjs'\n\nexport class Email extends Value\u003cstring\u003e {\n  get domainAddress(): string {\n    return this.value.split('@')[1]\n  }\n}\n\nexport const makeEmail = defineValue(Email, {\n  created(email): void {\n    // ... do something\n  },\n\n  trace(email) {\n    // ... do something\n  },\n  \n  validator(value): boolean | never {\n    // email validation logic\n    // return true | false\n    // or throw an error\n  },\n})\n```\n\n```typescript\nimport { \n  makeEmail,\n} from './Email'\n\nconst email = makeEmail('me@domain.com')\n\nconsole.log(email.value) // \"me@domain.com\"\nconsole.log(email.domainAddress) // \"domain.com\"\n```\n\n\n\n\n... more to come ...\n\n\n\n\n\n```typescript\n// User.ts\n\nimport {\n  Entity,\n  defineEntity,\n} from '@cosmicmind/domainjs'\n\nimport {\n  Email,\n} from './Email'\n\nexport type User = Entity \u0026 {\n  id: string\n  name: string\n  age: number\n  email: Email\n}\n\nexport const makeUser = defineEntity\u003cUser\u003e({\n  attributes: {\n    id: {\n      validator(value): boolean | never {\n        // id validation logic\n        // return true | false\n        // or throw an error\n      },\n    },\n    \n    name: {\n      validator(value): boolean | never {\n        // name validation logic\n        // return true | false\n        // or throw an error\n      },\n    },\n\n    age: {\n      validator(value): boolean | never {\n        // age validation logic\n        // return true | false\n        // or throw an error\n      },\n    },\n  },\n})\n```\n\n```typescript\nimport { \n  makeUser,\n} from './User'\n\nimport {\n  makeEmail,\n} from './Email'\n\nconst user = makeUser({\n  id: '123',\n  name: 'Daniel',\n  age: 29,\n  email: makeEmail('me@domain.com'),\n})\n\nconsole.log(user.id) // \"123\"\nconsole.log(user.name) // \"Daniel\"\nconsole.log(user.age) // 29\nconsole.log(user.email.value) // \"me@domain.com\"\nconsole.log(user.email.domainAddress) // \"domain.com\"\n```\n\nValue Objects are great for parameter passing and letting the function know that it is using a \nvalid value. For example:\n\n```typescript\nimport {\n  Email,\n} from './Email'\n\nfunction someFunction(email: Email): void {\n  if ('domain.com' === email.domainAddress) {\n    // ... do something\n  }\n}\n```\n\n### Aggregates - Defining possibilities while encapsulating the logic\n\n\n\n\n\n... more to come ...\n\n\n\n\n\n\n#### Data encapsulation and value accessibility\n\n\n\n\n... more to come ...\n\n\n\n\n\n```typescript\n// UserAggregate.ts\n\nimport {\n  Aggregate,\n  defineAggregate,\n} from '@cosmicmind/domainjs'\n\nimport {\n  User,\n} from './User'\n\nimport {\n  Email,\n} from './Email'\n\nexport class UserAggregate extends Aggregate\u003cUser\u003e {\n  get id(): string {\n    return this.root.id\n  }\n\n  get email(): Email {\n    return this.root.email\n  }\n\n  registerAccount(): void {\n    // ... do something\n  }\n}\n\nexport const makeUserAggregate = defineAggregate(UserAggregate, {\n  created(user) {\n    // ... do something\n  },\n\n  trace(user) {\n    // ... do something\n  },\n\n  attributes: {\n    // ...\n\n    age: {\n      validator(value): boolean | never {\n        // ... do something \n      },\n\n      udpated(newValue, oldValue, user): void {\n        // ... do something\n      },\n    },\n\n    // ...\n  },\n})\n```\n\n```typescript\nimport { \n  makeUserAggregate\n} from './UserAggregate'\n\nimport {\n  makeEmail,\n} from './Email'\n\nconst user = makeUserAggregate({\n  id: '123',\n  name: 'Daniel',\n  age: 29,\n  email: makeEmail('me@domain.com'),\n})\n\nconsole.log(user.id) // \"123\"\nconsole.log(user.name) // error cannot access (not exposed in UserAggragte)\nconsole.log(user.age) // error cannot access (not exposed in UserAggragte)\nconsole.log(user.email.value) // \"me@domain.com\"\nconsole.log(user.email.domainAddress) // \"domain.com\"\n\nuser.registerAccount() // ... account registration process\n```\n\n\n\n\n\n... more to come ...\n\n\n\n\n\n\n### Aggregate Events\n\n\n\n\n\n... more to come ...\n\n\n\n\n\n```typescript\n// RegisterAccountEvent.ts\n\nimport {\n  Event,\n  defineEvent,\n} from '@cosmicmind/domainjs'\n\nimport {\n  User,\n} from './User'\n\nexport type RegisterAccountEvent = Event \u0026 {\n  id: string\n  user: User\n}\n\nexport const createRegisterAccountEvent = defineEvent\u003cRegisterAccountEvent\u003e({\n  attributes: {\n    id: {\n      validator(value): boolean | never {\n        // id validation logic\n        // return true | false\n        // or throw an error\n      },\n    },\n  },\n})\n```\n\nNow that we have our `RegisterAccountEvent`, let's add it to the `UserAggregate` example. \n\n```typescript\n// UserAggregate.ts\n\nimport {\n  Aggregate,\n  defineAggregate,\n  EventTopics,\n} from '@cosmicmind/domainjs'\n\n// ...\n\nimport {\n  RegisterAccountEvent,\n  createRegisterAccountEvent,\n} from './RegisterAccountEvent'\n\nexport type UserAggregateEventTopics = EventTopics \u0026 {\n  'register-account': RegisterAccountEvent\n}\n\nexport class UserAggregate extends Aggregate\u003cUser, UserAggregateEventTopics\u003e {\n  // ...\n\n  registerAccount(): void {\n    // ... do something\n\n    this.publishSync('register-account', createRegisterAccountEvent({\n      id: '123',\n      user: this.root,\n    }))\n  }\n}\n\n// ...\n```\n\n```typescript\nimport { \n  makeUserAggregate\n} from './UserAggregate'\n\nimport {\n  makeEmail,\n} from './Email'\n\nconst user = makeUserAggregate({\n  id: '123',\n  name: 'Daniel',\n  age: 29,\n  email: makeEmail('me@domain.com'),\n})\n\nuser.subscribe('register-account', (event: RegisterAccountEvent) =\u003e {\n  // ... do something\n})\n\nconsole.log(user.id) // \"123\"\nconsole.log(user.name) // error cannot access (not exposed in UserAggragte)\nconsole.log(user.age) // error cannot access (not exposed in UserAggragte)\nconsole.log(user.email.value) // \"me@domain.com\"\nconsole.log(user.email.domainAddress) // \"domain.com\"\n\nuser.registerAccount() // ... account registration process and event is published\n```\n\n## What's Next\n\nAdditional documentation and examples will follow shortly. If you have any examples or use cases that \nyou are interested in exploring, please create a discussion. \n\nThank you! \n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fcosmicmind%2Fdomainjs","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fcosmicmind%2Fdomainjs","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fcosmicmind%2Fdomainjs/lists"}