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

undecorated-di

v2.0.2

Published

Type-safe, straightforward dependency injection for TypeScript without annotations or reflect-metadata.

Downloads

125

Readme

undecorated-di

Type-safe, straightforward dependency injection TypeScript without annotations or reflect-metadata.

test

Installation

npm i undecorated-di

What's new in V2

  • Containers can now provide functions and constants in addition to classes
  • Simpler function signatures for attaching dependencies to classes
  • Keys are type-safe
  • Usage is now very similar to other libraries but with greater type-safety

Use

1. Define Interfaces

//interfaces.ts
export interface Greet {
  (name: string) : void;
}

export interface Logger {
  log(message: string) : void;
}

2. Create Keys

// keys.ts
import { Keys } from 'undecorated-di';
import type { Logger, Greet } from './interfaces';

export const { keys } = Keys.createKeys()
  .addKey('Logger')
  .forType<Logger>()
  .addKey('Greet')
  .forType<Greet>()
  .addKey('Greeting')
  .forType<string>();

3. Bind dependencies to functions

// greet.ts
import { bind } from 'undecorated-di';
import { keys } from './keys';
import type { Logger } from './interfaces';

function greet(logger: Logger, greeting: string, name: string) {
  const message = `${greeting}, ${name}!`;
  logger.log(message);
}

export default bind(greet, [keys.Logger, keys.Greeting]);

4. Inject dependencies into classes

// console-logger.ts
import { inject } from "undecorated-di";
import type { Logger } from "./interfaces";

class ConsoleLogger implements Logger {
  log(message: string) {
    console.log(message);
  }
}

/*
  If the constructor for ConsoleLogger required parameters,
  the corresponding keys would go in the second argument of inject.

  Unlike bind which can accept a partial list of keys, inject
  requires that you provide a key for every parameter of the
  constructor.
*/
export default inject(ConsoleLogger, []);

5. Create a Container

// container.ts
import { ContainerBuilder, inject } from "undecorated-di";
import { keys } from "./keys";
import greet from "./greet";
import ConsoleLogger from "./console-logger";

export const container = ContainerBuilder.createBuilder()
  .registerConstant(keys.Greeting, "Hello")
  .registerClass(keys.Logger, ConsoleLogger)
  .registerFunction(keys.Greet, greet)
  .build();

6. Enjoy

// consumer.ts
import { container } from "./container";
import { keys } from "./keys";

const greet = container.get(keys.Greet);

greet("World");

Circular Dependencies

As of version 1.1.0, circular dependencies CAN be resolved in very specific cases. First, all members of the cycle must be registered as singletons. One singleton will be replaced by a proxy, which will be "filled in" with an actual instance of that singleton once it is instantiated.

Each time a transient service is retrieved, the expected behavior is that that instance is unique. Therefore, to avoid the potentially infinite chains that this guarantee of uniqueness could cause, only singletons may be part of a resolvable circular dependency. If any members are registered in transient scope, a CircularDependencyError will be thrown. For similar reasons, functions detected in a dependency cycle will also trigger a CircularDependencyError.

If a property of one member of a cycle is accessed by another member in that member's constructor, an UninitializedPropertyAccessError will be thrown. Once all of the constructors have been called, all singletons will have been fully instantiated, and their properties can be considered valid and may be accessed without causing errors.

Though circular dependencies can be resolved, they must be handled with care. For example, the dependency below will resolve if both classes are registered as singletons:

  class A {
    b : B;

    get myValue() {
      return this.b.myValue;
    }

    constructor(b : B) {
      this.b = b;
    }
  }

  class B {
    a : A;

    get myValue() {
      return this.a.myValue;
    }

    constructor(a : A) {
      this.a = a;
    }
  }

However, it will cause stack overflow if myValue is actually accessed from either class. Therefore, if you choose to include a circular dependency, first ask yourself if it is actually necessary. If it happens to be an appropriate solution to describe a certain problem (perhaps a problem whose optimal answer involves nesting or recursion), ensure that an internal cycle like the one above does not exist, or that there is a terminal condition when members of the cycle invoke each other's methods or access each others' properties. Additionally, ensuring that methods are pure can help keep these types of interactions clean.

Checking for circular dependencies

When an attempt to resolve a dependency is made, a tree structure is created and a branch of the tree is traversed from child node to parent node in order to check for circular dependencies. For efficiency, this check is only performed once. In the case of singletons, if they have previously been resolved, they are returned immediately. In the case of transient services, once a dependency has been resolved, its key is added to a set of previously resolved dependencies. Each time the service is requested, before performing the circular dependency check again, this set will be checked for the key of the service in question. If it exists in the set, the dependency is considered trusted and the tree traversal will not be performed.

License

MIT License

Copyright (c) 2023 Joseph Dvorak

Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.