{"id":22525533,"url":"https://github.com/sjohnsonaz/headwater","last_synced_at":"2025-08-03T21:32:29.298Z","repository":{"id":31565121,"uuid":"128300371","full_name":"sjohnsonaz/headwater","owner":"sjohnsonaz","description":"Dependency Injection and Mediator for TypeScript","archived":false,"fork":false,"pushed_at":"2023-03-04T02:37:41.000Z","size":1113,"stargazers_count":3,"open_issues_count":2,"forks_count":0,"subscribers_count":4,"default_branch":"master","last_synced_at":"2024-10-14T15:07:06.426Z","etag":null,"topics":["cqrs","cqrs-pattern","dependency","dependency-injection","di","injection","inversion-of-control","ioc","javascript","mediator","mediator-pattern","typescript"],"latest_commit_sha":null,"homepage":"","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/sjohnsonaz.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":"2018-04-06T03:19:51.000Z","updated_at":"2024-09-12T14:56:57.000Z","dependencies_parsed_at":"2023-01-14T19:19:29.057Z","dependency_job_id":null,"html_url":"https://github.com/sjohnsonaz/headwater","commit_stats":null,"previous_names":[],"tags_count":3,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/sjohnsonaz%2Fheadwater","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/sjohnsonaz%2Fheadwater/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/sjohnsonaz%2Fheadwater/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/sjohnsonaz%2Fheadwater/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/sjohnsonaz","download_url":"https://codeload.github.com/sjohnsonaz/headwater/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":228567013,"owners_count":17937986,"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":["cqrs","cqrs-pattern","dependency","dependency-injection","di","injection","inversion-of-control","ioc","javascript","mediator","mediator-pattern","typescript"],"created_at":"2024-12-07T06:10:30.711Z","updated_at":"2024-12-07T06:10:31.230Z","avatar_url":"https://github.com/sjohnsonaz.png","language":"TypeScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Headwater\n\n[![Node.js CI](https://github.com/sjohnsonaz/headwater/workflows/Node.js%20CI/badge.svg)](https://github.com/sjohnsonaz/headwater/actions?query=workflow%3A%22Node.js+CI%22) [![npm version](https://badge.fury.io/js/headwater.svg)](https://badge.fury.io/js/headwater)\n\n**Dependency Injection and Mediator for TypeScript and JavaScript**\n\n**Headwater** is a simple and fast Inversion of Control and Mediator implementation.  These implementations work together or separately.\n\n# Example\n\nWe can combine our Dependency Injection and Mediator patterns together!\n\nDeclare a Types `enum`.\n\n``` TypeScript\nenum Types {\n    Mediator = 'Mediator',\n    PostDataAccess = 'PostDataAccess',\n}\n```\n\nCreate a `Mediator`.\n\n``` TypeScript\nconst mediator = new Mediator();\n```\n\nCreate a `Request`.\n\n``` TypeScript\ninterface Post {\n    id: string;\n    subject: string;\n    body: string;\n}\n\nclass GetPostRequest\u003cPost\u003e {\n    constructor(public id: string) {\n        super();\n    }\n}\n```\n\nAdd a `RequestHandler` to the `Mediator`.\n\n\u003e **Note** the use of `inject()` anywhere we want to use **Dependency Injection**.\n\u003e\n\u003e Assuming we have a `PostDataAccess` class defined somewhere, we can inject it here!\n\n``` TypeScript\nmediator.add({\n    type: GetPostRequest,\n    handler: async (\n        { id },\n        postDataAccess = inject(Types.PostDataAccess)\n    ) =\u003e {\n        const post = await postDataAccess.get(id);\n        return post;\n    }\n});\n```\n\nBind the values to a `Container`.\n\n``` TypeScript\nconst container = new Container({\n    [Types.Mediator]: {\n        value: mediator\n    },\n    [Types.PostDataAccess]: {\n        value: PostDataAccess\n    }\n});\n\nContainer.setDefault(container);\n\ntype Bindings = typeof container['bindings'];\n\ndeclare module 'headwater' {\n    interface DefaultBindings extends Bindings {}\n}\n```\n\nInject the `Mediator`, send a `Request`, and **Headwater** will do the rest!\n\n``` TypeScript\nasync function main(mediator = inject(Types.Mediator)) {\n    const post = await mediator.send(new GetPostRequest(1234));\n    return post;\n}\n\nmain();\n\n// returns a Post\n```\n\n# Dependency Injection\n\nFor Inversion of Control, we need to bind values to a `Container`, so we can retrieve them later.  We can bind three types of values:\n* Value\n* Constructor\n* Factory\n\n## Create or use Default Container\n\nWe need a `Container` for binding values.  We can either create and manage this container directly, or use the **default** `Container`.\n\nWe first must import `Container`.\n\n``` TypeScript\nimport { Container } from 'headwater';\n```\n\n### Create Container\n\n``` TypeScript\nconst container = new Container();\n```\n\n### Use Default Container\n\n``` TypeScript\nconst container = Container.getDefault();\n```\n\nWe can also set the Default Container\n\n``` TypeScript\nconst container = new Container();\n\nContainer.setDefault(container);\n```\n\n## TypeScript Integration\n\nThe types for the Default Container can be injected as ambient typings.\n\n\u003e **Note**: It is highly recommended declare ambient typings.  This will allow simpler calls to `inject()` later.\n\n``` TypeScript\ntype Bindings = typeof container['bindings'];\n\ndeclare module 'headwater' {\n    interface DefaultBindings extends Bindings {}\n}\n```\n\n## Binding Values\n\nWe can bind any value to a `Container`.  We associate each binding with a unique `Type`.  The `Type` can be any `string`, `number`, or `symbol`.\n\n\u003e **Note**: It is highly recommended to use TypeScript `string enum` values:\n\n``` TypeScript\nenum Types {\n    UserDataAccess = 'UserDataAccess',\n    PostDataAccess = 'PostDataAccess'\n}\n```\n\nIt is also possible to use `const string` values:\n\n``` TypeScript\nconst USER_DATA_ACCESS = 'UserDataAccess';\nconst POST_DATA_ACCESS = 'PostDataAccess';\n```\n\n### Binding to the Container\n\n\u003e **Note**: It is highly recommended to bind in the constructor.  This provides typings automatically.\n\n``` TypeScript\nconst container = new Container({\n    [Types.UserDataAccess]: {\n        value: new UserDataAccess()\n    },\n    [Types.PostDataAccess]: {\n        value: new PostDataAccess()\n    }\n});\n```\n\nIt is also possible to bind later via:\n\n* `Container.prototype.bindValue()`\n* `Container.prototype.bindConstructor()`\n* `Container.prototype.bindFactory()`.\n\n### Bind a Value\n\nWe can bind a singleton value to a `Type`.\n\nThis can be done in the constructor via:\n\n``` TypeScript\nenum Types {\n    Value = 'Value'\n}\n\nconst container = new Container({\n    [Types.Value]: {\n        value: 'Some singleton value'\n    }\n});\n```\n\nIt can also be done later via:\n\n``` TypeScript\ncontainer.bindValue(Types.Value, 'Some singleton value');\n```\n\n### Bind a Constructor\n\nWe can bind a constructor to a `Type`.  This constructor will be called later to create instances.\n\n\u003e **Note**: Constructor parameters should have default values.  However, these can be specified upon injection.\n\n``` TypeScript\nclass ExampleClass {\n    constructor(public value = 0) {\n    }\n}\n\nenum Types {\n    ExampleClass: 'ExampleClass'\n}\n\nconst container = new Container({\n    [Types.ExampleClass]: {\n        type: 'constructor',\n        value: ExampleClass\n    }\n});\n```\n\nIt can also be done later via:\n\n``` TypeScript\ncontainer.bindConstructor(Types.ExampleClass, ExampleClass);\n```\n\n### Bind a Factory\n\nWe can also bind a factory to a `Type`.  This factory will be called later.\n\n\u003e **Note**: Factory parameters should have default values.  However, these can be specified upon injection.\n\n``` TypeScript\nfunction ExampleFactory(value = 0) {\n    return {\n        value\n    };\n}\n\nenum Types {\n    ExampleFactory = 'ExampleFactory';\n}\n\nconst container = new Container({\n    [Types.ExampleFactory]: {\n        type: 'factory',\n        value: ExampleFactory\n    }\n});\n```\n\nIt can also be done later via:\n\n``` TypeScript\ncontainer.bindFactory(Types.ExampleFactory, factory);\n```\n\n### Type Property\n\nThe **optional** `type` property in the constructor can be specified via `string` or `BindingType`.  Possible string values are:\n\n* `\"value\"`\n* `\"constructor\"`\n* `\"factory\"`\n\nIf unspecified, it is assumed to be a **Value Binding**.\n\n``` TypeScript\nconst container = new Container({\n    [Types.Value]: {\n        type: BindingType.Value,\n        value: 'Some singleton value'\n    },\n    [Types.ExampleClass]: {\n        type: BindingType.Constructor,\n        value: ExampleClass\n    },\n    [Types.ExampleFactory]: {\n        type: BindingType.Factory,\n        value: ExampleFactory\n    }\n});\n```\n\n## Retrieving Values\n\nWe can get any bound `Type` with the function `inject()`.\n\n``` TypeScript\nconst value = inject(Types.Value);\nconst example = inject(Types.ExampleClass);\nconst factory = inject(Types.FactoryExample);\n```\n\nWe can also get them directly from a `Container`.\n\n``` TypeScript\nconst value = container.get(Types.Value);\nconst example = container.get(Types.ExampleClass);\nconst factory = container.get(Types.FactoryExample);\n```\n\nIf a Constructor or Factory use parameters, we may specify them.\n\n``` TypeScript\nfunction ExampleFactory(value) {\n    return value;\n}\n\n...\n\nconst factory = inject(Types.ExampleFactory, 1);\n\n// result will be 1\n```\n\n## Injecting Values\n\nWe inject into a function by **default parameter** values.  For any function, we can specify default parameters.  If undefined is passed into that parameter, the default value is used instead.\n\n\u003e **Note**: It is highly recommended to inject via **default parameter** values.\n\nFor example:\n\n``` TypeScript\nfunction ExampleFactory(value = 0) {\n    return value;\n}\n\nconst result = ExampleFactory();\n\n// result will equal 0\n```\n\nIn this example, when we call factory with no parameters, `value` will be `0`.\n\nSo, we can use a bound Container value for the default value.\n\n``` TypeScript\nfunction ExampleFactory(value = inject(Types.Value)) {\n    return value;\n}\n\nconst result = ExampleFactory();\n\n// result will be the value bound to Types.Value.\n```\n\nIn this example, when factory is called with no parameters, we will use whatever is bound to `Types.Value`.\n\n## Specifying Parameters\n\nIf the bound value is a constructor or factory, we can also pass parameters into the `Container.get()` method.\n\nFor exmaple:\n\n``` TypeScript\nfunction factory(value = container.get('constructor', 1, 2, 3)) {\n    return value;\n}\n```\n\n## Specifying Containers\n\nWe can also use `inject()`, which uses the default `Container`.\n\n``` TypeScript\nimport { inject } from 'headwater';\n\nfunction factory(value = inject('value')) {\n    return value;\n}\n```\n\nWe can also specify a `Container` for `inject()`.\n\n``` TypeScript\nfunction factory(value = inject('value', container)) {\n    return value;\n}\n```\n\n\n# Mediator\n\nFor the Mediator pattern, we bind Handlers to Request types.\n\n## Create a Mediator\n\n``` TypeScript\nimport { Mediator } from 'headwater';\n\nconst mediator = new Mediator();\n```\n\n\u003e NOTE:  For simplicity, the Mediator can be injected via IOC.\n\n## Add Handler\n\n### Defining Requests\n\nWe must create a new `class` that extends `Request\u003cT\u003e`.  We specify into the generic `\u003cT\u003e` the **return type** of the `Request`.\n\n``` TypeScript\nimport { Request } from 'headwater';\n\nclass CreateRequest extends Request\u003cstring\u003e {\n    data: Data;\n\n    constructor(data: Data) {\n        super();\n        this.data = Data;\n    }\n}\n```\n\n\u003e NOTE:  The `super()` must be called.\n\n### Binding Handlers\n\nThe Handler must return a `Promise` with the type specified in the `Request`.\n\n``` TypeScript\nmediator.addHandler(async (request: CreateRequest) =\u003e {\n    // This function is async\n    // The return type must match the CreateRequest\n    return '';\n});\n```\n\n## Send\n\nWe must now create a new `Request` object, and pass it into the `Mediator`.  It will match the `Request` with a `Handler` and return a `Promise` with the value.\n\n``` TypeScript\nconst request = new CreateRequest({ ... });\n\nconst result = await mediator.send(request);\n```\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fsjohnsonaz%2Fheadwater","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fsjohnsonaz%2Fheadwater","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fsjohnsonaz%2Fheadwater/lists"}