Skip to content

Commit

Permalink
v0.2.1 (#26)
Browse files Browse the repository at this point in the history
* Change behaviour of `IContainerBuilder.define` so that already-defined services are not overwritten

* Refactor Container.get

* Update README
  • Loading branch information
mgdigital authored Oct 22, 2021
1 parent 83893af commit ae83ecb
Show file tree
Hide file tree
Showing 13 changed files with 125 additions and 26 deletions.
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
# Changelog

## [0.2.1]

- Change behaviour of `IContainerBuilder.define` so that already-defined services are not overwritten

## [0.1.16]

- Fix type signature of `IContainerBuilder.use` method
Expand Down
71 changes: 69 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,8 @@ These capabilities are implemented by components of the application, with some c

## Usage

### Creating a container module and defining services

Take the example of a logging component, that defines services in a container using the following keys (see [./examples/container/loggingModule/keys.ts](https://github.com/mgdigital/tsinject/blob/main/examples/container/loggingModule/keys.ts)):

```typescript
Expand Down Expand Up @@ -138,12 +140,77 @@ const logger = container.get(loggingModule.keys.logger)
logger.info('Logging something!')
```

Note that we should only call [IContainer.get](https://mgdigital.github.io/tsinject/interfaces/IContainer.html#get) from within a factory function or from the [composition root](https://freecontent.manning.com/dependency-injection-in-net-2nd-edition-understanding-the-composition-root/), avoiding the [service locator anti-pattern](https://freecontent.manning.com/the-service-locator-anti-pattern/).
**Note:** We should only call [IContainer.get](https://mgdigital.github.io/tsinject/interfaces/IContainer.html#get) from within a factory function or from the [composition root](https://freecontent.manning.com/dependency-injection-in-net-2nd-edition-understanding-the-composition-root/), avoiding the [service locator anti-pattern](https://freecontent.manning.com/the-service-locator-anti-pattern/).

**Note:** When defining a service in the container, if that service key is already defined then the key will **not** be overwritten. This allows modules to be used multiple times without introducing unpredictable behaviour when using decorators. For example of module A and B both depend on module C they can both use module C and then be used together in the same container. If an already defined service needs to be overwritten, this can be done with a decorator.

## Decorators

Decorators allow us to modify an already-defined service. Let's create a custom logging module that decorates some of the services in the base module defined above:


```typescript
import type { ContainerModule } from '@mgdigital/tsinject'
import loggingModule from './examples/container/loggingModule'

const myCustomLoggingModule: ContainerModule<
loggingModule.services
> = builder => builder
.use(loggingModule.default)
// Decorate the logger config so that output is always pretty
.decorate(
loggingModule.keys.loggerConfig,
factory => container => ({
...factory(container),
pretty: true
})
)
// Overwrite the log writer with some other implementation
.decorate(
loggingModule.keys.logWriter,
() => () => myCustomLogWriter
)
```

We can also use decorators to achieve features that aren't explicitly implemented in this library, such as service tagging, which we can do by defining a service as an array:

```typescript
import type { ContainerModule } from '@mgdigital/tsinject'

type TaggedServiceType = { foo: string }

const serviceTag = Symbol('serviceTag')

type ServiceMap = {
[serviceTag]: TaggedServiceType[]
}

const myModule: ContainerModule<
ServiceMap
> = builder => builder
.define(
serviceTag,
() => []
)

const myOtherModule: ContainerModule<
ServiceMap
> = builder => builder
.use(myModule)
.decorate(
serviceTag,
// Add a service to the array of already defined services
factory => container => [
...factory(container),
{ foo: 'bar' }
]
)
```

And that's it - unlike some other DI containers that claim to be lightweight, tsinject really is tiny and has a simple API, allowing large and complex but loosely coupled applications to be built from small, simple and easily testable components.

See the [examples](https://github.com/mgdigital/tsinject/tree/main/examples) folder for a more complete application. It includes a simple tasks service with a REST API that can be started by cloning this repository and running `yarn install`, `yarn build` then `yarn example:start`.

---

Copyright (c) 2021 Mike Gibson, https://github.com/mgdigital.
Copyright (c) 2021 Mike Gibson, https://github.com/mgdigital
2 changes: 1 addition & 1 deletion docs/classes/ServiceNotFoundError.html
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
<!DOCTYPE html><html class="default no-js"><head><meta charSet="utf-8"/><meta http-equiv="x-ua-compatible" content="IE=edge"/><title>ServiceNotFoundError | @mgdigital/tsinject - v0.1.16</title><meta name="description" content="Documentation for @mgdigital/tsinject - v0.1.16"/><meta name="viewport" content="width=device-width, initial-scale=1"/><link rel="stylesheet" href="../assets/style.css"/><link rel="stylesheet" href="../assets/highlight.css"/><script async src="../assets/search.js" id="search-script"></script></head><body><script>document.body.classList.add(localStorage.getItem(&quot;tsd-theme&quot;) || &quot;os&quot;)</script><header><div class="tsd-page-toolbar"><div class="container"><div class="table-wrap"><div class="table-cell" id="tsd-search" data-base=".."><div class="field"><label for="tsd-search-field" class="tsd-widget search no-caption">Search</label><input type="text" id="tsd-search-field"/></div><ul class="results"><li class="state loading">Preparing search index...</li><li class="state failure">The search index is not available</li></ul><a href="../index.html" class="title">@mgdigital/tsinject - v0.1.16</a></div><div class="table-cell" id="tsd-widgets"><div id="tsd-filter"><a href="#" class="tsd-widget options no-caption" data-toggle="options">Options</a><div class="tsd-filter-group"><div class="tsd-select" id="tsd-filter-visibility"><span class="tsd-select-label">All</span><ul class="tsd-select-list"><li data-value="public">Public</li><li data-value="protected">Public/Protected</li><li data-value="private" class="selected">All</li></ul></div> <input type="checkbox" id="tsd-filter-inherited" checked/><label class="tsd-widget" for="tsd-filter-inherited">Inherited</label><input type="checkbox" id="tsd-filter-externals" checked/><label class="tsd-widget" for="tsd-filter-externals">Externals</label></div></div><a href="#" class="tsd-widget menu no-caption" data-toggle="menu">Menu</a></div></div></div></div><div class="tsd-page-title"><div class="container"><ul class="tsd-breadcrumb"><li><a href="../modules.html">@mgdigital/tsinject - v0.1.16</a></li><li><a href="ServiceNotFoundError.html">ServiceNotFoundError</a></li></ul><h1>Class ServiceNotFoundError</h1></div></div></header><div class="container container-main"><div class="row"><div class="col-8 col-content"><section class="tsd-panel tsd-comment"><div class="tsd-comment tsd-typography"><div class="lead">
<!DOCTYPE html><html class="default no-js"><head><meta charSet="utf-8"/><meta http-equiv="x-ua-compatible" content="IE=edge"/><title>ServiceNotFoundError | @mgdigital/tsinject - v0.2.1</title><meta name="description" content="Documentation for @mgdigital/tsinject - v0.2.1"/><meta name="viewport" content="width=device-width, initial-scale=1"/><link rel="stylesheet" href="../assets/style.css"/><link rel="stylesheet" href="../assets/highlight.css"/><script async src="../assets/search.js" id="search-script"></script></head><body><script>document.body.classList.add(localStorage.getItem(&quot;tsd-theme&quot;) || &quot;os&quot;)</script><header><div class="tsd-page-toolbar"><div class="container"><div class="table-wrap"><div class="table-cell" id="tsd-search" data-base=".."><div class="field"><label for="tsd-search-field" class="tsd-widget search no-caption">Search</label><input type="text" id="tsd-search-field"/></div><ul class="results"><li class="state loading">Preparing search index...</li><li class="state failure">The search index is not available</li></ul><a href="../index.html" class="title">@mgdigital/tsinject - v0.2.1</a></div><div class="table-cell" id="tsd-widgets"><div id="tsd-filter"><a href="#" class="tsd-widget options no-caption" data-toggle="options">Options</a><div class="tsd-filter-group"><div class="tsd-select" id="tsd-filter-visibility"><span class="tsd-select-label">All</span><ul class="tsd-select-list"><li data-value="public">Public</li><li data-value="protected">Public/Protected</li><li data-value="private" class="selected">All</li></ul></div> <input type="checkbox" id="tsd-filter-inherited" checked/><label class="tsd-widget" for="tsd-filter-inherited">Inherited</label><input type="checkbox" id="tsd-filter-externals" checked/><label class="tsd-widget" for="tsd-filter-externals">Externals</label></div></div><a href="#" class="tsd-widget menu no-caption" data-toggle="menu">Menu</a></div></div></div></div><div class="tsd-page-title"><div class="container"><ul class="tsd-breadcrumb"><li><a href="../modules.html">@mgdigital/tsinject - v0.2.1</a></li><li><a href="ServiceNotFoundError.html">ServiceNotFoundError</a></li></ul><h1>Class ServiceNotFoundError</h1></div></div></header><div class="container container-main"><div class="row"><div class="col-8 col-content"><section class="tsd-panel tsd-comment"><div class="tsd-comment tsd-typography"><div class="lead">
<p>Error thrown when a non-existent key is requested from the container.</p>
</div></div></section><section class="tsd-panel tsd-hierarchy"><h3>Hierarchy</h3><ul class="tsd-hierarchy"><li><a href="TSInjectError.html" class="tsd-signature-type" data-tsd-kind="Class">TSInjectError</a><ul class="tsd-hierarchy"><li><span class="target">ServiceNotFoundError</span></li></ul></li></ul></section><section class="tsd-panel-group tsd-index-group"><h2>Index</h2><section class="tsd-panel tsd-index-panel"><div class="tsd-index-content"><section class="tsd-index-section "><h3>Constructors</h3><ul class="tsd-index-list"><li class="tsd-kind-constructor tsd-parent-kind-class tsd-is-overwrite"><a href="ServiceNotFoundError.html#constructor" class="tsd-kind-icon">constructor</a></li></ul></section><section class="tsd-index-section "><h3>Properties</h3><ul class="tsd-index-list"><li class="tsd-kind-property tsd-parent-kind-class"><a href="ServiceNotFoundError.html#key" class="tsd-kind-icon">key</a></li><li class="tsd-kind-property tsd-parent-kind-class tsd-is-inherited tsd-is-external"><a href="ServiceNotFoundError.html#message" class="tsd-kind-icon">message</a></li><li class="tsd-kind-property tsd-parent-kind-class tsd-is-inherited tsd-is-external"><a href="ServiceNotFoundError.html#name" class="tsd-kind-icon">name</a></li><li class="tsd-kind-property tsd-parent-kind-class tsd-is-inherited tsd-is-external"><a href="ServiceNotFoundError.html#stack" class="tsd-kind-icon">stack</a></li><li class="tsd-kind-property tsd-parent-kind-class tsd-is-inherited tsd-is-static tsd-is-external"><a href="ServiceNotFoundError.html#prepareStackTrace" class="tsd-kind-icon">prepare<wbr/>Stack<wbr/>Trace</a></li><li class="tsd-kind-property tsd-parent-kind-class tsd-is-inherited tsd-is-static tsd-is-external"><a href="ServiceNotFoundError.html#stackTraceLimit" class="tsd-kind-icon">stack<wbr/>Trace<wbr/>Limit</a></li></ul></section><section class="tsd-index-section tsd-is-inherited tsd-is-external"><h3>Methods</h3><ul class="tsd-index-list"><li class="tsd-kind-method tsd-parent-kind-class tsd-is-inherited tsd-is-static tsd-is-external"><a href="ServiceNotFoundError.html#captureStackTrace" class="tsd-kind-icon">capture<wbr/>Stack<wbr/>Trace</a></li></ul></section></div></section></section><section class="tsd-panel-group tsd-member-group "><h2>Constructors</h2><section class="tsd-panel tsd-member tsd-kind-constructor tsd-parent-kind-class tsd-is-overwrite"><a id="constructor" class="tsd-anchor"></a><h3>constructor</h3><ul class="tsd-signatures tsd-kind-constructor tsd-parent-kind-class tsd-is-overwrite"><li class="tsd-signature tsd-kind-icon">new <wbr/>Service<wbr/>Not<wbr/>Found<wbr/>Error<span class="tsd-signature-symbol">(</span>key<span class="tsd-signature-symbol">: </span><a href="../modules.html#ContainerKey" class="tsd-signature-type" data-tsd-kind="Type alias">ContainerKey</a><span class="tsd-signature-symbol">)</span><span class="tsd-signature-symbol">: </span><a href="ServiceNotFoundError.html" class="tsd-signature-type" data-tsd-kind="Class">ServiceNotFoundError</a></li></ul><ul class="tsd-descriptions"><li class="tsd-description"><aside class="tsd-sources"><p>Overrides <a href="TSInjectError.html">TSInjectError</a>.<a href="TSInjectError.html#constructor">constructor</a></p><ul><li>Defined in <a href="https://github.com/mgdigital/tsinject/blob/main/src/errors.ts#L12">Code/tsinject/src/errors.ts:12</a></li></ul></aside><h4 class="tsd-parameters-title">Parameters</h4><ul class="tsd-parameters"><li><h5>key: <a href="../modules.html#ContainerKey" class="tsd-signature-type" data-tsd-kind="Type alias">ContainerKey</a></h5></li></ul><h4 class="tsd-returns-title">Returns <a href="ServiceNotFoundError.html" class="tsd-signature-type" data-tsd-kind="Class">ServiceNotFoundError</a></h4></li></ul></section></section><section class="tsd-panel-group tsd-member-group "><h2>Properties</h2><section class="tsd-panel tsd-member tsd-kind-property tsd-parent-kind-class"><a id="key" class="tsd-anchor"></a><h3><span class="tsd-flag ts-flagReadonly">Readonly</span> key</h3><div class="tsd-signature tsd-kind-icon">key<span class="tsd-signature-symbol">:</span> <a href="../modules.html#ContainerKey" class="tsd-signature-type" data-tsd-kind="Type alias">ContainerKey</a></div><aside class="tsd-sources"></aside></section><section class="tsd-panel tsd-member tsd-kind-property tsd-parent-kind-class tsd-is-inherited tsd-is-external"><a id="message" class="tsd-anchor"></a><h3>message</h3><div class="tsd-signature tsd-kind-icon">message<span class="tsd-signature-symbol">:</span> <span class="tsd-signature-type">string</span></div><aside class="tsd-sources"><p>Inherited from <a href="TSInjectError.html">TSInjectError</a>.<a href="TSInjectError.html#message">message</a></p><ul><li>Defined in .yarn/berry/cache/typescript-patch-29eb8bf885-8.zip/node_modules/typescript/lib/lib.es5.d.ts:974</li></ul></aside></section><section class="tsd-panel tsd-member tsd-kind-property tsd-parent-kind-class tsd-is-inherited tsd-is-external"><a id="name" class="tsd-anchor"></a><h3>name</h3><div class="tsd-signature tsd-kind-icon">name<span class="tsd-signature-symbol">:</span> <span class="tsd-signature-type">string</span></div><aside class="tsd-sources"><p>Inherited from <a href="TSInjectError.html">TSInjectError</a>.<a href="TSInjectError.html#name">name</a></p><ul><li>Defined in .yarn/berry/cache/typescript-patch-29eb8bf885-8.zip/node_modules/typescript/lib/lib.es5.d.ts:973</li></ul></aside></section><section class="tsd-panel tsd-member tsd-kind-property tsd-parent-kind-class tsd-is-inherited tsd-is-external"><a id="stack" class="tsd-anchor"></a><h3><span class="tsd-flag ts-flagOptional">Optional</span> stack</h3><div class="tsd-signature tsd-kind-icon">stack<span class="tsd-signature-symbol">?:</span> <span class="tsd-signature-type">string</span></div><aside class="tsd-sources"><p>Inherited from <a href="TSInjectError.html">TSInjectError</a>.<a href="TSInjectError.html#stack">stack</a></p><ul><li>Defined in .yarn/berry/cache/typescript-patch-29eb8bf885-8.zip/node_modules/typescript/lib/lib.es5.d.ts:975</li></ul></aside></section><section class="tsd-panel tsd-member tsd-kind-property tsd-parent-kind-class tsd-is-inherited tsd-is-static tsd-is-external"><a id="prepareStackTrace" class="tsd-anchor"></a><h3><span class="tsd-flag ts-flagStatic">Static</span> <span class="tsd-flag ts-flagOptional">Optional</span> prepare<wbr/>Stack<wbr/>Trace</h3><div class="tsd-signature tsd-kind-icon">prepare<wbr/>Stack<wbr/>Trace<span class="tsd-signature-symbol">?:</span> <span class="tsd-signature-symbol">(</span>err<span class="tsd-signature-symbol">: </span><span class="tsd-signature-type">Error</span>, stackTraces<span class="tsd-signature-symbol">: </span><span class="tsd-signature-type">CallSite</span><span class="tsd-signature-symbol">[]</span><span class="tsd-signature-symbol">)</span><span class="tsd-signature-symbol"> =&gt; </span><span class="tsd-signature-type">any</span></div><aside class="tsd-sources"><p>Inherited from <a href="TSInjectError.html">TSInjectError</a>.<a href="TSInjectError.html#prepareStackTrace">prepareStackTrace</a></p><ul><li>Defined in .yarn/berry/cache/@types-node-npm-16.11.0-75617d0fee-8.zip/node_modules/@types/node/globals.d.ts:11</li></ul></aside><div class="tsd-type-declaration"><h4>Type declaration</h4><ul class="tsd-parameters"><li class="tsd-parameter-signature"><ul class="tsd-signatures tsd-kind-type-literal tsd-parent-kind-class"><li class="tsd-signature tsd-kind-icon"><span class="tsd-signature-symbol">(</span>err<span class="tsd-signature-symbol">: </span><span class="tsd-signature-type">Error</span>, stackTraces<span class="tsd-signature-symbol">: </span><span class="tsd-signature-type">CallSite</span><span class="tsd-signature-symbol">[]</span><span class="tsd-signature-symbol">)</span><span class="tsd-signature-symbol">: </span><span class="tsd-signature-type">any</span></li></ul><ul class="tsd-descriptions"><li class="tsd-description"><div class="tsd-comment tsd-typography"><div class="lead">
<p>Optional override for formatting stack traces</p>
Expand Down
Loading

0 comments on commit ae83ecb

Please sign in to comment.