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

@shellicar/core-di

v1.0.0

Published

A basic dependency injection library

Downloads

203

Readme

@shellicar/core-di

A basic dependency injection library.

Motivation

Coming from .NET I am used to DI frameworks/libraries such as Autofac, Ninject, StructureMap, Unity, and Microsoft's own DependencyInjection.

I started using InversifyJS, and tried out some others along the way, such as diod.

With TypeScript 5.0 generally available with non-experimental decorators, most DI libraries have not been updated, so I decided to create my own.

Features

My set of features is simple, based on my current usage

  • Type-safe registration.
const services = createServiceCollection();
abstract class IAbstract { abstract method(): void; }
abstract class Concrete {}
services.register(IAbstract).to(Concrete);
//                              ^ Error
  • Type-safe resolution.
const provider = services.buildProvider();
const svc = provider.resolve(IMyService);
//    ^ IMyService
  • Provide factory methods for instantiating classes.
services.register(Redis).to(Redis, x => {
  const options = x.resolve(IRedisOptions);
  return new Redis({
    port: options.port,
    host: options.host,
  });
});
  • Use property injection with decorators for simple dependency definition.
abstract class IDependency {}
class Service implements IService {
  @dependsOn(IDependency) private readonly dependency!: IDependency;
}
  • Provide multiple implementations for identifiers and provide a resolveAll method.
  • Define instance lifetime with simple builder pattern.
services.register(IAbstract).to(Concrete).singleton();
  • Create scopes to allow "per-request" lifetimes.
const services = createServiceCollection();
const provider = services.buildProvider();
using scope = provider.createScope();
  • Register classes during a scope
using scope = provider.createScope();
scope.Services.register(IContext).to(Context);
  • Override registrations (e.g.: for testing)
import { ok } from 'node:assert/strict';
const services = createServiceCollection({ registrationMode: ResolveMultipleMode.LastRegistered });
services.register(IOptions).to(Options);
// Later
services.register(IOptions).to(MockOptions);
const provider = services.buildProvider();
const options = provider.resolve(IOptions);
ok(options instanceof MockOptions);
  • Logging options
class CustomLogger extends ILogger {
  public override debug(message?: any, ...optionalParams: any[]): void {
    // custom implementation  
  }
}
// Override default logger
const services1 = createServiceCollection({ logger: new CustomLogger() });
// Override default log level
const services2 = createServiceCollection({ logLevel: LogLevel.Debug });
  • Service modules
class IAbstract {}
class Concrete extends IAbstract {}

class MyModule implements IServiceModule {
  public registerServices(services: IServiceCollection): void {
    services.register(IAbstract).to(Concrete);
  }
}

const services = createServiceCollection();
services.registerModules(MyModule);
const provider = services.buildProvider();
const svc = provider.resolve(IAbstract);

Usage

Check the test files for different usage scenarios.

import { dependsOn, createServiceCollection, IServiceModule, type IServiceCollection } from '@shellicar/core-di';

// Define the dependency interface
abstract class IClock {
  abstract now(): Date;
}
// And implementation
class DefaultClock implements IClock {
  now(): Date {
    return new Date();
  }
}

// Define your interface
abstract class IDatePrinter {
  abstract handle(): string;
}
// And implementation
class DatePrinter implements IDatePrinter {
  @dependsOn(IClock) public readonly clock!: IClock;

  handle(): string {
    return `The time is: ${this.clock.now().toISOString()}`;
  }  
}

class TimeModule extends IServiceModule {
  public registerServices(services: IServiceCollection): void {
    services.register(IClock).to(DefaultClock).singleton();
    services.register(IDatePrinter).to(DatePrinter).scoped();
  }
}

// Register and build provider
const services = createServiceCollection();
services.registerModules([TimeModule]);
const sp = services.buildProvider();

// Optionally create a scope
using scope = sp.createScope();

// Resolve the interface
const svc = scope.resolve(IDatePrinter);
console.log(svc.handle());

Inspired by