npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2024 – Pkg Stats / Ryan Hefner

siringa

v0.3.3

Published

Simple type-checking injection library for Typescript

Downloads

309

Readme

SIRINGA

"siringa" (italian): "syringe", a tool for administering injections.

SIRINGA is an extremely simple dependency injection framework focusing on type correctness and asynchronous injections, without relying on decorators and/or decorator metadata.

Quick Start

At its core, SIRINGA tries to make dependency injection as easy as possible.

Classes can be injected their dependencies by specifying a static $inject property (a tuple of injection keys):

// A class with no dependencies, and an implicit zero-args constructor
class Foo {
  foo() { /* ... your code... */ }
}

// Here "Bar" needs an instance of "Foo" to be injected
class Bar {
  // The _tuple_ of required injections, this must be declared `as const`
  // and the resolved types must match the constructor's arguments
  static $inject = [ Foo ] as const

  // Called by the injector with the correct dependencies
  constructor(private _foo: Foo) {}

  // Your code
  bar() {
    this._foo.bar()
  }
}

// Create a new injector
const injector = new Injector()
  .bind(Foo) // Bind "Foo" (no dependencies required)
  .bind(Bar) // Bind "Bar" (requires "Foo")

// Get the singleton instance of "Bar"
const bar = await injector.get(Bar)
// Get the singleton instance of "Foo" (injection magically happens!)
const foo = await injector.get(Foo)

One-Time Injection

We can use an Injector also to perform one-time injections (the instance injected won't be managed by the Injector itself).

Using the same Foo and Bar classes from above:

// Create a new injector and bind Foo
const injector = new Injector().bind(Foo)

// Create a new "Bar" instance injected with its required "Foo"
const bar = await injector.inject(Bar)

// Will return the same instance given to "Bar"'s constructor
const foo = await injector.get(Foo)

// The code below will miserably fail, as "Bar" is not managed by the injector
// const bar2 = await injector.get(Bar)

Name-based Injection

Injection keys must not necessarily be classes, they can also be strings:

class Foo {
  foo() { /* ... your code... */ }
}

class Bar {
  // The string `foo` is used to identify the binding
  static $inject = [ 'foo' ] as const

  // Called by the injector with the correct dependencies
  constructor(private _foo: Foo) {}

  // Your code
  bar() {
    this._foo.bar()
  }
}

// Create a new injector
const injector = new Injector()
  .bind('foo', Foo) // Bind "Foo" to the string 'foo'
  .bind('bar', Bar) // Bind "Bar" to the string 'bar'

// Get the singleton instance of "Bar"
const bar = await injector.get('bar')
// Get the singleton instance of "Foo"
const foo = await injector.get('foo')

Promises Injection

Sometimes it might be necessary to be injected a Promise of a component, rather than a component itself.

For example, in an Lambda Function we might want to asynchronously use other services (retrieve secrets, connect to databases, ...) while starting to process a request.

In this case we can request to be injected a (yet unresolved) Promise of a bound component:

import { promise } from 'siringa'

class Foo {
  foo() { /* ... your code... */ }
}

class Bar {
  // The `promise(...)` function declares the injection of a `Promise`
  static $inject = [ promise(Foo) ] as const

  // Called by the injector with the correct `Promise` for dependencies
  constructor(private _foo: Promise<Foo>) {}

  // Your code
  bar() {
    await (this._foo).bar()
  }
}

Using Factories

The injector's create(...) method allows to use a factory pattern to create instances to be injected:

class Foo {
  constructor(public _value: number)
}

class Bar {
  // The string `foo` is used to identify the binding
  static $inject = [ Foo ] as const

  // Called by the injector with the correct dependencies
  constructor(private _foo: Foo) {}

  // Your code
  bar() {
    console.log(this._foo._value)
  }
}

// Create a new injector
const injector = new Injector()
  .bind(Foo, (injections) => {
    // The "injections" object can be used to resolve dependencies
    // in the injector itself using "get(...)" or "inject(...)"

    return new Foo(12345)
  })

// Inject "Bar" with "Foo"
const bar = await injector.inject(Bar)

// This will print "12345"
bar.bar()

As in the example above, factory methods will be given an Injections instance which can be used to get instances, inject new objects, or create sub-injectors.

See the reference for Injections below.

Using Instances

Similar to factories (above) the injector's use(...) method allows to use pre-baked instances as dependencies for other objects:

class Foo {
  constructor(public _value: number)
}

class Bar {
  // The string `foo` is used to identify the binding
  static $inject = [ Foo ] as const

  // Called by the injector with the correct dependencies
  constructor(private _foo: Foo) {}

  // Your code
  bar() {
    console.log(this._foo._value)
  }
}

// Create a new injector
const injector = new Injector()
  .use(Foo, new Foo(12345))

// Inject "Bar" with "Foo"
const bar = await injector.inject(Bar)

// This will print "12345"
bar.bar()

Child Injectors

In some cases it is useful to create child injectors.

A child injector inherits all the bindings from its parent, but any extra binding declared in it won't affect its parent.

Also, a child injector can redeclare a binding without affecting its parent.

For example (using silly strings to simplify the inner workings!)

const parent = new Injector()
  .use('foo', 'parent foo')

const child = parent.injector()
  .create('parentFoo', async (injections) => {
    const s = await injections.get('foo') // this is the parent's value!!!
    return `${s} from a child`
  }
  .use('foo', 'child foo')

await parent.get('foo') // this will return "parent foo"
await child.get('foo') // this will return "child foo" (overrides parent)

await child.get('parentFoo') // this will return "parent foo from a child"
// await parent.get('parentFoo') // fails, as parent doesn't define "parentFoo"

API Reference

Quick-and-dirty reference for our types (for details, everything should be annotated with JSDoc).

class Injector

Binding

  • injector.bind(component: Constructor): Injector
    Bind the specified constructor (a class) to the injector

  • injector.bind(component: Constructor, implementation: Constructor): Injector
    Bind the specified component (a class) to the injector, and use the specified implementation (another class extending the component) to create instances

  • injector.bind(name: string, implementation: Constructor): Injector
    Bind the specified name (a _string) to the injector, and use the specified implementation (a class ) to create instances

Factories

  • injector.create(component: Constructor, factory: Factory): Injector
    Use the specified factory (a function) to create instances for the specified component (a class)

  • injector.create(name: string, factory: Factory): Injector
    Use the specified factory (a function) to create instances for the specified name (a string)

Instances

  • injector.use(component: Constructor, instance: any): Injector
    Bind the specified instance to a component (a class).

  • injector.create(name: string, instance: any): Injector
    Bind the specified instance to a name (a string).

Injections

  • injections.get(component: Constructor): Instance
    Returns the instance bound to the specified constructor (a class)

  • injections.get(name: string): Instance
    Returns the instance bound to the specified name (a string)

  • injections.inject(injectable: Constructor): Instance
    Create a new instance of the given injectable (a class) injecting all its required dependencies

  • injections.make(factory: Factory): ReturnType<Factory>
    Simple utility method to invoke the factory with the correct Injections and return its result. This can be used to alleviate issues when top-level await is not available.

Sub Injectors

  • injections.injector(): Injector
    Create a sub-injector (child) of the current one

interface Injections

  • injections.get(component: Constructor): Instance
    Returns the instance bound to the specified constructor (a class)

  • injections.get(name: string): Instance
    Returns the instance bound to the specified name (a string)

  • injections.inject(injectable: Constructor): Instance
    Create a new instance of the given injectable (a class) injecting all its required dependencies

  • injections.injector(): Injector
    Create a sub-injector (child) of the current one