Skip to content

Commit 01339d5

Browse files
authored
v0.3.1 (#34)
1 parent 320a2d3 commit 01339d5

24 files changed

+389
-316
lines changed

CHANGELOG.md

+7-2
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,21 @@
11
# Changelog
22

3+
## [0.3.1]
4+
5+
- Change the `ContainerModule` type to include a key, allowing modules to be used multiple times without rebuilding
6+
- Simplify interface naming
7+
38
## [0.2.2]
49

510
- Add `containerKeyValues` function
611

712
## [0.2.1]
813

9-
- Change behaviour of `IContainerBuilder.define` so that already-defined services are not overwritten
14+
- Change behaviour of `ContainerBuilder.define` so that already-defined services are not overwritten
1015

1116
## [0.1.16]
1217

13-
- Fix type signature of `IContainerBuilder.use` method
18+
- Fix type signature of `ContainerBuilder.use` method
1419

1520
## [0.1.15]
1621

README.md

+83-72
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ Several dependency injection solutions exist for TypeScript. Most use either dec
2828

2929
<span style="color:green">&check;</span> Achieving loose coupling in large applications
3030

31-
tsinject works by defining named factory functions in a container builder, with unique symbols mapping services available in the container to their type. These factory functions can return anything, allowing configuration objects, class instances, functions or any other type of value to be defined as a container service. Any code can be containerized without need for modifications such as annotations or static properties.
31+
**tsinject** works by defining named factory functions in a container builder, with unique symbols mapping services available in the container to their type. These factory functions can return anything, allowing configuration objects, class instances, functions or any other type of value to be defined as a container service. Any code can be containerized without need for modifications such as annotations or static properties.
3232

3333
Any application that does something useful needs to cause side effects. These might include:
3434

@@ -37,7 +37,7 @@ Any application that does something useful needs to cause side effects. These mi
3737
- Asking the user for input
3838
- Logging a message to the console
3939

40-
These capabilities are implemented by components of the application, with some components depending on others, and with the implementation or configuration of components often depending on values read from the environment. The quickest way to allow components to communicate with each other is often via globally defined singleton instances. This increases complexity, making code more difficult to debug, test and maintain. Instead, by building components that have their dependencies injected, we can create complex but decoupled applications.
40+
These capabilities are implemented by components of the application, with some components depending on others, and with the implementation or configuration of components often depending on values read from the environment. The quickest way to allow components to communicate with each other is often via globally defined singleton instances. Importing these global side effects throughout an application can increase complexity, making code more difficult to debug, test and maintain. Instead, by building components that have their dependencies injected, we can create complex but decoupled applications.
4141

4242
## Usage
4343

@@ -84,35 +84,39 @@ import simpleLogFormatter from '../../logging/simpleLogFormatter'
8484
const loggingModule: ContainerModule<
8585
processEnvModule.services &
8686
LoggingServices
87-
> = builder => builder
88-
// Use another container module that provides services required by this one
89-
.use(processEnvModule.default)
90-
// Define a config object based on environment variables
91-
.define(
92-
keys.loggerConfig,
93-
container => loggerConfigFromEnv(
94-
container.get(processEnvModule.keys.processEnv)
87+
> = {
88+
// Specify a unique key for the module
89+
key: Symbol('loggingModule'),
90+
build: builder => builder
91+
// Use another container module that provides services required by this one
92+
.use(processEnvModule.default)
93+
// Define a config object based on environment variables
94+
.define(
95+
keys.loggerConfig,
96+
container => loggerConfigFromEnv(
97+
container.get(processEnvModule.keys.processEnv)
98+
)
9599
)
96-
)
97-
// Provide a different implementation depending on environment variable configuration
98-
.define(
99-
keys.logFormatter,
100-
container => container.get(keys.loggerConfig).pretty
101-
? prettyLogFormatter
102-
: simpleLogFormatter
103-
)
104-
.define(
105-
keys.logWriter,
106-
() => consoleLogWriter
107-
)
108-
.define(
109-
keys.logger,
110-
container => new Logger(
111-
container.get(keys.logFormatter),
112-
container.get(keys.logWriter),
113-
container.get(keys.loggerConfig).level
100+
// Provide a different implementation depending on environment variable configuration
101+
.define(
102+
keys.logFormatter,
103+
container => container.get(keys.loggerConfig).pretty
104+
? prettyLogFormatter
105+
: simpleLogFormatter
114106
)
115-
)
107+
.define(
108+
keys.logWriter,
109+
() => consoleLogWriter
110+
)
111+
.define(
112+
keys.logger,
113+
container => new Logger(
114+
container.get(keys.logFormatter),
115+
container.get(keys.logWriter),
116+
container.get(keys.loggerConfig).level
117+
)
118+
)
119+
}
116120

117121
export default loggingModule
118122
```
@@ -132,9 +136,7 @@ const logger = container.get(loggingModule.keys.logger)
132136
logger.info('Logging something!')
133137
```
134138

135-
**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/).
136-
137-
**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, if modules A and B both depend on module C, they can both use module C, and then be used together module D, where they will share the same dependency instance from module C. If an already defined service needs to be overwritten, this can be done with a decorator.
139+
**Note:** We should only call [Container.get](https://mgdigital.github.io/tsinject/interfaces/Container.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/).
138140

139141
### Decorators
140142

@@ -145,32 +147,35 @@ Decorators allow us to modify an already-defined service. Let's create a custom
145147
import type { ContainerModule } from '@mgdigital/tsinject'
146148
import * as loggingModule from './examples/container/loggingModule'
147149

148-
const myCustomLoggingModule: ContainerModule<
150+
const customLoggingModule: ContainerModule<
149151
loggingModule.services
150-
> = builder => builder
151-
.use(loggingModule.default)
152-
// Decorate the logger config so that output is always pretty
153-
.decorate(
154-
loggingModule.keys.loggerConfig,
155-
factory => container => ({
156-
...factory(container),
157-
pretty: true
158-
})
159-
)
160-
// Decorate the log formatter to append an exclamation mark to all log entries
161-
.decorate(
162-
loggingModule.keys.logFormatter,
163-
factory => container => {
164-
const baseFormatter = factory(container)
165-
return (level, message, data) =>
166-
baseFormatter(level, message, data) + '!'
167-
}
168-
)
169-
// Overwrite the log writer with some other implementation
170-
.decorate(
171-
loggingModule.keys.logWriter,
172-
() => () => myCustomLogWriter
173-
)
152+
> = {
153+
key: Symbol('customLoggingModule'),
154+
build: builder => builder
155+
.use(loggingModule.default)
156+
// Decorate the logger config so that output is always pretty
157+
.decorate(
158+
loggingModule.keys.loggerConfig,
159+
factory => container => ({
160+
...factory(container),
161+
pretty: true
162+
})
163+
)
164+
// Decorate the log formatter to append an exclamation mark to all log entries
165+
.decorate(
166+
loggingModule.keys.logFormatter,
167+
factory => container => {
168+
const baseFormatter = factory(container)
169+
return (level, message, data) =>
170+
baseFormatter(level, message, data) + '!'
171+
}
172+
)
173+
// Overwrite the log writer with some other implementation
174+
.decorate(
175+
loggingModule.keys.logWriter,
176+
() => () => myCustomLogWriter
177+
)
178+
}
174179
```
175180

176181
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:
@@ -188,24 +193,30 @@ type ServiceMap = {
188193

189194
const myModule: ContainerModule<
190195
ServiceMap
191-
> = builder => builder
192-
.define(
193-
serviceTag,
194-
() => []
195-
)
196+
> = {
197+
key: Symbol('myModule'),
198+
build: builder => builder
199+
.define(
200+
serviceTag,
201+
() => []
202+
)
203+
}
196204

197205
const myOtherModule: ContainerModule<
198206
ServiceMap
199-
> = builder => builder
200-
.use(myModule)
201-
.decorate(
202-
serviceTag,
203-
// Add a service to the array of already defined services
204-
factory => container => [
205-
...factory(container),
206-
{ foo: 'bar' }
207-
]
208-
)
207+
> = {
208+
key: Symbol('myOtherModule'),
209+
build: builder => builder
210+
.use(myModule)
211+
.decorate(
212+
serviceTag,
213+
// Add a service to the array of already defined services
214+
factory => container => [
215+
...factory(container),
216+
{ foo: 'bar' }
217+
]
218+
)
219+
}
209220
```
210221

211222
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.

0 commit comments

Comments
 (0)