2.2.1 • Published 9 days ago

@coderspirit/lambda-ioc v2.2.1

Weekly downloads
-
License
MIT
Repository
github
Last release
9 days ago

@coderspirit/lambda-ioc

NPM version TypeScript License npm downloads Known Vulnerabilities Security Score

Super type safe dependency injection 💉 for TypeScript

Install instructions

Node

# With PNPM
pnpm add @coderspirit/lambda-ioc

# With NPM
npm install @coderspirit/lambda-ioc

# With Yarn:
yarn add @coderspirit/lambda-ioc

Example

import { createContainer } from '@coderspirit/lambda-ioc'
import { pino } from 'pino'

import { getDb } from './infra/db.ts'
import {
  buildService,
  buildServiceA,
  buildServiceB
} from './services.ts'
import { buildServer } from './server.ts'

const container = createContainer()
  // We can register already instantiated values
  .registerValue('logger', pino())
  // We can register factories as well
  .registerFactory('logger2', pino)
  // Factories don't guarantee returning the same instance every time,
  // which can be necessary sometimes, so we provide a solution:
  .registerSingleton('logger3', pino)
  .registerSingleton('db', getDb)
  // We can also pass dependencies to factories and singleton factories.
  // What follows is "equivalent" to:
  // .registerValue('service', buildService(pino(), getDb()))
  .registerSingleton('service', buildService, 'logger', 'db')
  // The type checker will raise an error if we try to pass dependencies
  // that we didn't specify before, protecting us from having errors at
  // runtime:
  .registerSingleton('brokenService', buildService, 'logger', 'db2')
  // We might want to register some things within "groups". This is done
  // by specifying a prefix. We'll see later how this can be useful:
  .registerSingleton('svc:a', buildServiceA, 'logger', 'db')
  .registerSingleton('svc:b', buildServiceB, 'logger', 'db')
  // Sometimes our factories are asynchronous
  .registerAsyncFactory('asyncStuff', async () => Promise.resolve(42))
  .registerAsyncSingleton(
    'aSingleton',
    async () => Promise.resolve({ v: 42 })
  )
  // We can inject groups into other registered dependencies by using
  // the `:*` suffix
  .registerAsync('server', buildServer, 'svc:*')
  // The next call is not strictly necessary, but it helps to "clean up"
  // the container's type for faster type checking.
  // Although we don't do it in this example, we can parametrize the
  // type parameters of `close` if we want to expose less dependencies
  // than the ones we registered.
  .close()

// Once we have the container, we can start resolving its registered
// values in a type-safe way:

// The type checker will know that `logger` is an instance of `Logger`
const logger = container.resolve('logger')

// The type checker will raise an error because it knows we didn't
// register anything under the key 'wrong'.
const wrong = container.resolve('wrong')

// To resolve what we registered asynchronously, we have to use the
// asynchronous resolver
const asyncStuff = await container.resolveAsync('asyncStuff')

// We can't resolve synchronously what was registered asynchronously,
// what follows will raise a type checking error:
const asyncAsSync = container.resolve('asyncStuff')

// To resolve "groups", we also have to do it asynchronously, even if
// they were registered synchronously. This is because groups can have
// synchronous and asynchronous dependencies.
// `svcGroup` will be an array (with arbitrary order) containing the
// dependencies registered under the 'svc:` prefix.
const svcGroup = await container.resolveGroup('svc')

// Having a specific method to resolve groups is fine, but it does not
// fit well in dependency resolution pipelines. For this reason, we also
// provide a way to asynchronously resolve groups by relying on the `:*`
// suffix, so we can pass whole groups as dependencies.
const svcGroup = await container.resolveAsync('svc:*')

// In case we wanted to keep the labels of our resolved group
// dependencies, we can rely on the `:#` suffix. This will return us a
// list where its values are [label, dependency] pairs.
const svcLabelledGroup = await container.resolveAsync('svc:#')

// We can also resolve the labels in a group without having to resolve
// their associated dependencies (using the `:@` suffix).
const svcLabels = container.resolve('svc:@')

Other considerations

While this library is intended to provide compile-time safety, it also provides runtime safety to ensure that there are no surprises when it's used in pure JS projects.

Benefits

  • 100% type safe:
    • The type checker will complain if we try to resolve unregistered dependencies.
    • The type checker will complain if we try to register new dependencies that depend on unregistered dependencies, or if there is any kind of type mismatch.
  • Purely functional
  • Immutable
  • Circular dependencies are impossible

Drawbacks

  • All dependencies must be declared "in order".
    • This implies that this IoC container cannot be used in combination with some auto-wiring solutions, such as IoC decorators.
  • The involved types are a bit convoluted:
    • They might cause the type checker to be slow.
    • In some situations, the type checker might be unable to infer the involved types due to excessive "nested types" depth.
2.2.1

9 days ago

2.2.0

16 days ago

2.1.0

19 days ago

2.0.3

20 days ago

2.0.2

20 days ago

2.0.4

20 days ago

2.0.1

20 days ago

2.0.0

20 days ago

1.0.0

2 years ago

0.8.0

2 years ago

0.7.1

2 years ago

0.7.0

2 years ago

0.6.0

2 years ago

0.5.1

2 years ago

0.5.0

2 years ago

0.4.0

2 years ago

0.3.0

2 years ago

0.2.0

2 years ago

0.1.1

2 years ago

0.1.0

2 years ago