{"id":27689691,"url":"https://github.com/almin/ddd-base","last_synced_at":"2025-06-22T09:38:31.354Z","repository":{"id":51928909,"uuid":"101475695","full_name":"almin/ddd-base","owner":"almin","description":"DDD base class library for JavaScript application.","archived":false,"fork":false,"pushed_at":"2021-05-12T22:59:33.000Z","size":2283,"stargazers_count":77,"open_issues_count":8,"forks_count":7,"subscribers_count":1,"default_branch":"master","last_synced_at":"2025-04-09T07:47:21.040Z","etag":null,"topics":["class","ddd","javascript","typescript"],"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/almin.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":null,"security":null,"support":null}},"created_at":"2017-08-26T09:17:43.000Z","updated_at":"2025-02-23T20:48:01.000Z","dependencies_parsed_at":"2022-08-23T18:40:23.311Z","dependency_job_id":null,"html_url":"https://github.com/almin/ddd-base","commit_stats":null,"previous_names":[],"tags_count":17,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/almin%2Fddd-base","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/almin%2Fddd-base/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/almin%2Fddd-base/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/almin%2Fddd-base/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/almin","download_url":"https://codeload.github.com/almin/ddd-base/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":249845515,"owners_count":21333728,"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":["class","ddd","javascript","typescript"],"created_at":"2025-04-25T10:39:22.915Z","updated_at":"2025-04-25T10:39:23.469Z","avatar_url":"https://github.com/almin.png","language":"TypeScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# ddd-base [![Build Status](https://travis-ci.org/almin/ddd-base.svg?branch=master)](https://travis-ci.org/almin/ddd-base)\n\n**Status**: Experimental\n\nDDD base class library for JavaScript client-side application.\n\n**Notes**:\n\nThis library does not depend on [Almin](https://github.com/almin/almin).\nYou can use it with other JavaScript framework.\n\n## Features\n\nThis library provide basic DDD base classes.\n\n- [`Entity`](#entity): Entity is domain concept that have a unique identity\n- [`Identifier`](#identifier): Identifier is unique identifier for an Entity\n- [`ValueObject`](#valueobject): Value Object is an entity’s state, describing something about the entity\n- [`Repository`](#repository): Repository is used to manage aggregate persistence\n- [`Converter`](#converter): Converter convert between Entity \u003c-\u003e Props(Entity's props) \u003c-\u003e JSON(Serialized data object)\n\n## Install\n\nInstall with [npm](https://www.npmjs.com/):\n\n    npm install ddd-base\n\n## Usage\n\n### Entity\n\n\u003e Entities are domain concepts that have a unique identity in the problem domain.\n\nEntity's equability is Identifier.\n\n### Identifier\n\nIdentifier is a unique object for each entity.\n\n`Entity#equals` check that the Entity's identifier is equaled to other entity's identifier.\n\n#### Entity Props\n\nEntity Props have to `id` props that is instance of `Identifier`.\n\n1. Define `XProps` type\n    - `XProps` should include `id: Identifier\u003cstring|number\u003e` property.\n\n```ts\nclass XIdentifier extends Identifier\u003cstring\u003e {}\ninterface XProps {\n    id: XIdentifier; // \u003c= required\n}\n```\n\n2. Pass `XProps` to `Entity\u003cXProps\u003e`\n\n```ts\nclass XEntity extends Entity\u003cXProps\u003e {\n    // implement\n}\n```\n\nYou can get the props via `entity.props`.\n\n```ts\nconst xEntity = new XEntity({\n    id: new XIdentifier(\"x\");\n});\nconsole.log(xEntity.props.id);\n```\n\n\n**Example:**\n\n```ts\n// Entity A\nclass AIdentifier extends Identifier\u003cstring\u003e {}\ninterface AProps {\n    id: AIdentifier;\n}\nclass AEntity extends Entity\u003cAProps\u003e {}\n// Entity B\nclass BIdentifier extends Identifier\u003cstring\u003e {}\ninterface BProps {\n    id: BIdentifier;\n}\nclass BEntity extends Entity\u003cBProps\u003e {}\n// A is not B\nconst a = new AEntity({\n    id: new AIdentifier(\"1\"))\n});\nconst b = new BEntity({\n    id: new BIdentifier(\"1\")\n});\nassert.ok(!a.equals(b), \"A is not B\");\n```\n\nProps can includes other property.\n\n```ts\n// Entity A\nclass AIdentifier extends Identifier\u003cstring\u003e {}\n\ninterface AProps {\n    id: AIdentifier;\n    a: number;\n    b: string;\n}\n\nclass AEntity extends Entity\u003cAProps\u003e {\n    constructor(props: AProps) {\n        super(props);\n    }\n}\n\nconst entity = new AEntity({\n    id: new AIdentifier(\"a\"),\n    a: 42,\n    b: \"string\"\n});\n```\n\n### ValueObject\n\n\u003e Value object is an entity’s state, describing something about the entity or the things it owns.\n\nValueObject's equability is values.\n\n```ts\nimport {ValueObject} from \"ddd-base\";\n\n// X ValueObject\ntype XProps = { value: number };\n\nclass XValue extends ValueObject\u003cXProps\u003e {\n    constructor(props: XProps) {\n        super(props);\n    }\n}\n// x1's value equal to x2's value\nconst x1 = new XValue({ value: 42 });\nconst x2 = new XValue({ value: 42 });\nconsole.log(x1.props.value); // =\u003e 42\nconsole.log(x2.props.value); // =\u003e 42\nconsole.log(x1.equals(x2));// =\u003e true\n// x3's value not equal both\nconst x3 = new XValue({ value: 1 });\nconsole.log(x1.equals(x3));// =\u003e false\nconsole.log(x2.equals(x3));// =\u003e false\n```\n\n:memo: ValueObject's props have not a limitation like Entity.\nBecause, ValueObject's equability is not identifier.\n\n### Repository\n\n\u003e A repository is used to manage aggregate persistence and retrieval while ensuring that there is a separation between the domain model and the data model.\n\n`Repository` collect entity.\n\nCurrently, `Repository` implementation is in-memory database like Map.\nThis library provide following types of repository.\n\n- `NonNullableBaseRepository`\n- `NullableBaseRepository`\n\n#### NonNullableBaseRepository\n\n`NonNullableRepository` has initial value.\nIn other words, NonNullableRepository#get always return a value.\n\n```ts\n/**\n * NonNullableRepository has initial value.\n * In other words, NonNullableRepository#get always return a value.\n */\nexport declare class NonNullableRepository\u003cEntity extends EntityLike\u003cany\u003e, Props extends Entity[\"props\"], Id extends Props[\"id\"]\u003e {\n    protected initialEntity: Entity;\n    private core;\n    constructor(initialEntity: Entity);\n    readonly map: MapLike\u003cstring, Entity\u003e;\n    readonly events: RepositoryEventEmitter\u003cEntity\u003e;\n    get(): Entity;\n    getAll(): Entity[];\n    findById(entityId?: Id): Entity | undefined;\n    save(entity: Entity): void;\n    delete(entity: Entity): void;\n    clear(): void;\n}\n\n```\n\n#### NullableBaseRepository\n\n`NullableRepository` has not initial value.\nIn other word, NullableRepository#get may return undefined.\n\n```ts\n/**\n * NullableRepository has not initial value.\n * In other word, NullableRepository#get may return undefined.\n */\nexport declare class NullableRepository\u003cEntity extends EntityLike\u003cany\u003e, Props extends Entity[\"props\"], Id extends Props[\"id\"]\u003e {\n    private core;\n    constructor();\n    readonly map: MapLike\u003cstring, Entity\u003e;\n    readonly events: RepositoryEventEmitter\u003cEntity\u003e;\n    get(): Entity | undefined;\n    getAll(): Entity[];\n    findById(entityId?: Id): Entity | undefined;\n    save(entity: Entity): void;\n    delete(entity: Entity): void;\n    clear(): void;\n}\n```\n\n### Converter\n\n\u003e JSON \u003c-\u003e Props \u003c-\u003e Entity\n\nConverter is that convert JSON \u003c-\u003e Props \u003c-\u003e Entity.\n\n`createConverter` create `Converter` instance from `Props` and `JSON` types and converting definition.\n\n```ts\n// Pass Props type and JSON types as generics\n// 1st argument is that a Constructor of entity that is required for creating entity from JSON\n// 2nd argument is that a mapping object\n// mapping object has tuple array for each property.\n// tuple is [Props to JSON, JSON to Props]  \ncreateConverter\u003cPropsType, JSONType\u003e(EntityConstructor, mappingObject): Converter\u003cPropsType, JSONType\u003e;\n```\n\n`mappingObject` has tuple array for each property.\n\n```ts\nconst converter = createConverter\u003cEntityProps, EntityJSON\u003e(Entity, {\n    id: [propsToJSON function, jsonToProps function],\n    // [(prop value) =\u003e json value, (json value) =\u003e prop value]\n});\n```\n\nExample of `createConveter`.\n\n```ts\n\n// Entity A\nclass AIdentifier extends Identifier\u003cstring\u003e {\n}\n\ninterface AProps {\n    id: AIdentifier;\n    a1: number;\n    a2: string;\n}\n\nclass AEntity extends Entity\u003cAProps\u003e {\n    constructor(args: AProps) {\n        super(args);\n    }\n}\n\ninterface AEntityJSON {\n    id: string;\n    a1: number;\n    a2: string;\n}\n\n// Create converter\n// Tuple has two convert function that Props -\u003e JSON and JSON -\u003e Props\nconst AConverter = createConverter\u003cAProps, AEntityJSON\u003e(AEntity, {\n    id: [prop =\u003e prop.toValue(), json =\u003e new AIdentifier(json)],\n    a1: [prop =\u003e prop, json =\u003e json],\n    a2: [prop =\u003e prop, json =\u003e json]\n});\nconst entity = new AEntity({\n    id: new AIdentifier(\"a\"),\n    a: 42,\n    b: \"b prop\"\n});\n// Entity to JSON\nconst json = AConverter.toJSON(entity);\nassert.deepStrictEqual(json, {\n    id: \"a\",\n    a: 42,\n    b: \"b prop\"\n});\n// JSON to Entity\nconst entity_conveterted = converter.fromJSON(json);\nassert.deepStrictEqual(entity, entity_conveterted);\n```\n\n:memo: Limitation: \n\nConvert can be possible one for one converting.\n\n```\n// Can not do convert following pattern\n// JSON -\u003e Entity\n// a -\u003e b, c properties\n```\n\n#### Nesting Converter\n\nYou can set `Converter` instead of mapping functions.\nThis pattern called **Nesting Converter**.\n\n```ts\n// Parent has A and B\nclass ParentIdentifier extends Identifier\u003cstring\u003e {\n}\n\ninterface ParentJSON {\n    id: string;\n    a: AEntityJSON;\n    b: BValueJSON;\n}\n\ninterface ParentProps {\n    id: ParentIdentifier;\n    a: AEntity;\n    b: BValue;\n}\n\nclass ParentEntity extends Entity\u003cParentProps\u003e {\n}\n\nconst ParentConverter = createConverter\u003cParentProps, ParentJSON\u003e(ParentEntity, {\n    id: [prop =\u003e prop.toValue(), json =\u003e new ParentIdentifier(json)],\n    a: AConverter, // Set Conveter instead of mapping functions\n    b: BConverter\n});\n```\n\nFor more details, see [test/Converter-test.ts](./test/Converter-test.ts).\n\n### [Deprecated] Serializer\n\n\u003e JSON \u003c-\u003e Entity\n\nDDD-base just define the interface of `Serializer` that does following converting.\n\n- Convert from JSON to Entity\n- Convert from Entity to JSON\n\nYou can implement `Serializer` interface and use it.\n\n```ts\nexport interface Serializer\u003cEntity, JSON\u003e {\n    /**\n     * Convert Entity to JSON format\n     */\n    toJSON(entity: Entity): JSON;\n\n    /**\n     * Convert JSON to Entity\n     */\n    fromJSON(json: JSON): Entity;\n}\n```\n\n\nImplementation:\n\n```ts\n// Entity A\nclass AIdentifier extends Identifier\u003cstring\u003e {}\n\ninterface AEntityArgs {\n    id: AIdentifier;\n    a: number;\n    b: string;\n}\n\nclass AEntity extends Entity\u003cAIdentifier\u003e {\n    private a: number;\n    private b: string;\n\n    constructor(args: AEntityArgs) {\n        super(args.id);\n        this.a = args.a;\n        this.b = args.b;\n    }\n\n    toJSON(): AEntityJSON {\n        return {\n            id: this.id.toValue(),\n            a: this.a,\n            b: this.b\n        };\n    }\n}\n// JSON\ninterface AEntityJSON {\n    id: string;\n    a: number;\n    b: string;\n}\n\n// Serializer\nconst ASerializer: Serializer\u003cAEntity, AEntityJSON\u003e = {\n    fromJSON(json) {\n        return new AEntity({\n            id: new AIdentifier(json.id),\n            a: json.a,\n            b: json.b\n        });\n    },\n    toJSON(entity) {\n        return entity.toJSON();\n    }\n};\n\nit(\"toJSON: Entity -\u003e JSON\", () =\u003e {\n    const entity = new AEntity({\n        id: new AIdentifier(\"a\"),\n        a: 42,\n        b: \"b prop\"\n    });\n    const json = ASerializer.toJSON(entity);\n    assert.deepStrictEqual(json, {\n        id: \"a\",\n        a: 42,\n        b: \"b prop\"\n    });\n});\n\nit(\"fromJSON: JSON -\u003e Entity\", () =\u003e {\n    const entity = ASerializer.fromJSON({\n        id: \"a\",\n        a: 42,\n        b: \"b prop\"\n    });\n    assert.ok(entity instanceof AEntity, \"entity should be instanceof AEntity\");\n    assert.deepStrictEqual(\n        ASerializer.toJSON(entity),\n        {\n            id: \"a\",\n            a: 42,\n            b: \"b prop\"\n        },\n        \"JSON \u003c-\u003e Entity\"\n    );\n});\n```\n\n## :memo: Design Note\n\n### Why entity and value object has `props`?\n\nIt come from TypeScript limitation.\nTypeScript can not define type of class's properties.\n\n```ts\n// A limitation of generics interface\ntype AEntityProps = {\n  key: string;\n}\nclass AEntity extends Entity\u003cAEntityProps\u003e {}\n\nconst aEntity = new AEntity({\n  key: \"value\"\n});\n// can not type\naEntity.key; // type is any?\n```\n\nWe can resolve this issue by introducing `props` property.\n\n```ts\n// `props` make realize typing\ntype AEntityProps = {\n  key: string;\n}\nclass AEntity extends Entity\u003cAEntityProps\u003e {}\n\nconst aEntity = new AEntity({\n  key: \"value\"\n});\n// can not type\naEntity.props; // props is AEntityProps\n```\n\nThis approach is similar with [React](https://reactjs.org/).\n\n- [Why did React use 'props' as an abbreviation for property/properties? - Quora](https://www.quora.com/Why-did-React-use-props-as-an-abbreviation-for-property-properties)\n\n### Nesting props is ugly\n\nIf you want to access nested propery via `props`, you have written `a.props.b.props.c`.\nIt is ugly syntax.\n\nInstead of this, you can assign `props` values to entity's properties directly.\n\n```ts\nclass ShoppingCartItemIdentifier extends Identifier\u003cstring\u003e {\n}\n\ninterface ShoppingCartItemProps {\n    id: ShoppingCartItemIdentifier;\n    name: string;\n    price: number;\n}\n\nclass ShoppingCartItem extends Entity\u003cShoppingCartItemProps\u003e implements ShoppingCartItemProps {\n    id: ShoppingCartItemIdentifier;\n    name: string;\n    price: number;\n\n    constructor(props: ShoppingCartItemProps) {\n        // pass to props\n        super(props);\n        // assign own property\n        this.id = props.id;\n        this.name = props.name;\n        this.price = props.price;\n    }\n}\n\nconst item = new ShoppingCartItem({\n    id: new ShoppingCartItemIdentifier(\"item 1\");\n    name: \"bag\";\n    price: 1000\n});\n\nconsole.log(item.props.price === item.price); // =\u003e true\n```\n\n### `props` is readonly by default\n\n\u003e This is related with \"Nesting props is ugly\"\n\n`props` is `readonly` and `Object.freeze(props)` by default.\n\n**props**:\n\nIt is clear that `props` are a Entity's **configureation**.\nThey are **received** from above and immutable as far as the Entity receiving them is concerned.\n\n**state**:\n\nddd-base does not define `state` type.\nBut, `state` is own properties of Entity.\nIt is mutable value and it can be modified by default.\n\nFor example, `this.id`, `this.name`, and `this.price` are state of `ShoppingCartItem`.\nYou can modify this state.\n\n```ts\nclass ShoppingCartItemIdentifier extends Identifier\u003cstring\u003e {\n}\n\ninterface ShoppingCartItemProps {\n    id: ShoppingCartItemIdentifier;\n    name: string;\n    price: number;\n}\n\nclass ShoppingCartItem extends Entity\u003cShoppingCartItemProps\u003e implements ShoppingCartItemProps {\n    id: ShoppingCartItemIdentifier;\n    name: string;\n    price: number;\n\n    constructor(props: ShoppingCartItemProps) {\n        // pass to props\n        super(props);\n        // assign own property\n        this.id = props.id;\n        this.name = props.name;\n        this.price = props.price;\n    }\n}\n```\n\n### Changing _props_ and _state_\n\n|                                           | *props* | *state* |\n| ----------------------------------------- | ------- | ------- |\n| Can get initial value from parent Entity? | Yes     | Yes     |\n| Can be changed by parent Entity?          | Yes     | No      |\n| Can set default values inside Entity?     | Yes     | Yes     |\n| Can change inside Entity?                 | No      | Yes     |\n| Can set initial value for child Entity?   | Yes     | Yes     |\n\n\n\nRelated concept:\n\n- [react-guide/props-vs-state.md at master · uberVU/react-guide](https://github.com/uberVU/react-guide/blob/master/props-vs-state.md)\n- [Thinking in React - React](https://reactjs.org/docs/thinking-in-react.html)\n\n## Real UseCase\n\n- [azu/irodr: RSS reader client like LDR for Inoreader.](https://github.com/azu/irodr \"azu/irodr: RSS reader client like LDR for Inoreader.\")\n- [azu/searchive: Search All My Documents{PDF}.](https://github.com/azu/searchive \"azu/searchive: Search All My Documents{PDF}.\")\n- [proofdict/editor: Proofdict editor.](https://github.com/proofdict/editor \"proofdict/editor: Proofdict editor.\")\n\n## Changelog\n\nSee [Releases page](https://github.com/almin/ddd-base/releases).\n\n## Running tests\n\nInstall devDependencies and Run `npm test`:\n\n    npm i -d \u0026\u0026 npm test\n\n## Contributing\n\nPull requests and stars are always welcome.\n\nFor bugs and feature requests, [please create an issue](https://github.com/almin/ddd-base/issues).\n\n1. Fork it!\n2. Create your feature branch: `git checkout -b my-new-feature`\n3. Commit your changes: `git commit -am 'Add some feature'`\n4. Push to the branch: `git push origin my-new-feature`\n5. Submit a pull request :D\n\n## Author\n\n- [github/azu](https://github.com/azu)\n- [twitter/azu_re](https://twitter.com/azu_re)\n\n## License\n\nMIT © azu\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Falmin%2Fddd-base","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Falmin%2Fddd-base","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Falmin%2Fddd-base/lists"}