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

injector-next

v1.1.1

Published

--- Next gen Dependency Injector. Very similar to Angular injector. Extremely small ~1Kb

Downloads

3

Readme

Injector Next


Next gen Dependency Injector. Very similar to Angular injector. Extremely small ~1Kb

WARNING: It was meant to be used with Typescript. While it is possible to use with plain JS it is not advised and will be hard to use.

For Typescript, you need to have these two options turned on in tsconfig.json:

{
	"compilerOptions": {
		"emitDecoratorMetadata": true,
		"experimentalDecorators": true
	}
}

Depends on reflect-metadata.

npm

WARNING: API is not yet final

Requirements

Technically it should have no requirements.


Installation

For yarn:

yarn add injector-next

For npm:

npm i injector-next

Docs


Imports:

// ES6 JS/Typescript style
import { Injector } from 'injector-next';

// require
const { Injector } = require('injector-next');

Class Decorators:

Put this before the class you wish to be automatically injected as singleton

@Injectable()
// Alias to Injectable, does the same thing
@Service()

Parameter decorators:

If you wish to provide your own factory, put this before the parameter, this is mainly used for injecting dynamic values, however can be used to inject.

const dateFactory = () => { return new Map(); };
dateFactory.diFactory = true;

@Injectable()
class MyService {
	constructor(
		@Token({ factory: dateFactory }) protected createdAt: Date
	) {}
}

Importing named tokens:

// Somwhere before in your application
Injector.register('appStartTime', new Date());

@Injectable()
class MyService {
	constructor(
		@Token('appStartTime') protected appStartTime: Date
	) {}
}

Basic usage:

@Injectable()
class ServiceA {
	a = 1;
}

@Injectable()
class ServiceB {
	constructor(
		// injector automatically figures out the class 
		// based on parameter type
		protected sa: ServiceA
	) {}
}

const sb = Injector.resolve(ServiceB);

Manual registration:

class ExternalClass {
	a = 1;
}
// Notice that registration requires actual instance
Injector.register('unique-name', new ExternalClass());

// Now you can get it directly by name:
Injector.get('unique-name');

// Or by token itself, beware that it will return the first registered instance
Injector.resolve(ExternalClass);

// You can also override it, calling register twice will result in error
// IMPORTANT: this will get rid of original instance
Injector.override('unique-name', new ExternalClass());

Custom resolver:

// External class that you cannot modify
class ServiceExternal {
	a = 1;
}

@Injectable()
class ServiceB {
	constructor(
		protected sa: any,
		protected cons: typeof console,
		protected dt: Date,
	) {}
}

const customResolve = (token: any, idx: string) => {
	// Either check by index of parameter
	if (+idx === 0) {
		// this will make it basically factory that would spawn instance 
		// each time is resolved
		return new ServiceExternal();
	}
	if (+idx === 1) {
		return console;
	}
	// Or by class itself
	if (token === Date) {
		return new Date();
	}

	return null;
};

const sb = Injector.resolve(ServiceB, customResolve);

Result:

ServiceB {
  sa: ServiceExternal { a: 1 },
  cons: Object [console] {
    log: [Function: log],
    warn: [Function: warn],
    dir: [Function: dir],
    time: [Function: time],
    timeEnd: [Function: timeEnd],
    timeLog: [Function: timeLog],
    trace: [Function: trace],
    assert: [Function: assert],
    clear: [Function: clear],
    count: [Function: count],
    countReset: [Function: countReset],
    group: [Function: group],
    groupEnd: [Function: groupEnd],
    table: [Function: table],
    debug: [Function: debug],
    info: [Function: info],
    dirxml: [Function: dirxml],
    error: [Function: error],
    groupCollapsed: [Function: groupCollapsed],
    Console: [Function: Console],
    profile: [Function: profile],
    profileEnd: [Function: profileEnd],
    timeStamp: [Function: timeStamp],
    context: [Function: context]
  },
  dt: 2021-07-07T18:35:05.968Z
}

Custom tokens:

// create a factory class
const mapFactory = () => { return new Map(); }
// mark it as di factory (otherwise injector cannot distinguish 
// between actual class and factory
mapFactory.diFactory = true;

@Injectable()
class ServiceB {
	constructor(
		// Mark parameter directly
		@Token({ factory: mapFactory }) protected map: Map<any, any>
	) {}
}

const sb = Injector.resolve(ServiceB);

Result (map is a factory):

ServiceB { map: Map(0) {} }

Custom token to provide non classes:

// create a factory class
const singletonInstance = {
	unnamed: 'object',
	that: 'has no constructor'
}
const configFactory = () => { return singletonInstance; }
configFactory.diFactory = true;

@Injectable()
class ServiceB {
	constructor(
		// Mark parameter directly
		@Token({ factory: configFactory }) 
		protected config: any
	) {}
}

const sb = Injector.resolve(ServiceB);

Result (config is singleton):

ServiceB { config: { unnamed: 'object', that: 'has no constructor' } }

Injector as factory:

// create a factory class
const randFactory = () => { return Math.random(); }
randFactory.diFactory = true;

@Injectable()
class ServiceX {
	stable = Math.random();
}

@Injectable()
class Entity {
	constructor(
			protected serv: ServiceX,
			@Token({ factory: randFactory })
			protected rand: number
	) {}
}

// Those will be just instanciated but not kept in registry
const e1 = Injector.resolve(Entity, null, true);
const e2 = Injector.resolve(Entity, null, true);
const e3 = Injector.resolve(Entity, null, true);

Result (serv is still a singleton but factory spawned random numbers each time):

Entity {
	serv: ServiceX { stable: 0.5692771742563438 },
	rand: 0.7034761836358194
}
Entity {
	serv: ServiceX { stable: 0.5692771742563438 },
	rand: 0.20460451477948371
}
Entity {
	serv: ServiceX { stable: 0.5692771742563438 },
	rand: 0.22173878210817932
}

Advanced usage:

WARNING modifying design:paramtypes directly may result in some odd behaviour if not done right.

interface CustomOptions {
	min: number;
	max: number;
	amount: number;
}

const CustomToken = (options: CustomOptions): ParameterDecorator => {
	return (target: Object, propertyKey: string | symbol, parameterIndex: number) => {
		// collect existing param types
		const tokens = Reflect.getMetadata('design:paramtypes', target) || [];

		// make new factory
		const factory = () => {
			const out = [];
			for (let i = 0; i < options.amount; i++) {
				out.push(Math.random() * (options.max - options.min) + options.min)
			}

			return out;
		};
		// mark it as factory
		factory.diFactory = true;
		
		tokens[parameterIndex] = factory;
		// redefine param types so we can check where is our namespace suppose to be injected
		Reflect.defineMetadata('design:paramtypes', tokens, target);
	};
};

@Injectable()
class ServiceB {
	constructor(
		@CustomToken({ min: 1, max: 10, amount: 3 }) 
		protected arr: number[],
		
		@CustomToken({ min: 10, max: 20, amount: 5 }) 
		protected arr2: number[],
		
		@CustomToken({ min: 100, max: 200, amount: 7 }) 
		protected arr3: number[],
	) {}
}

const sb = Injector.resolve(ServiceB);

Result:

ServiceB {
  arr: [ 6.682922113497945, 2.1919589707056892, 3.3588555893813377 ],
  arr2: [
    15.002033575190106,
    11.684026606562002,
    16.84351565375917,
    16.820905407384693,
    16.210885789832872
  ],
  arr3: [
    166.39310836720702,
    130.8052358138108,
    185.8191399183811,
    164.22233809832989,
    131.44972544841204,
    186.65161324868083,
    110.1801704311695
  ]
}

Registering custom resolvers

Sometimes you wish to have a custom resolver entire application.

// Dummy generic class
class PostgresRepository<T> {
	constructor(public type: any) {
	}
}

// Dummy model
class SomeModel {}

export class OmniRepositoryToken {
	constructor(
		public repoClass: any,
		public modelClass?: any,
		public ormName: string = 'orm',
	) {
	}
}

export class OmniOrmToken {
	constructor(
		public ormName: string = 'orm',
	) {
	}
}

export function OmniRepository(repoClass: any, modelClass?: any, ormName?: string) {
	return (target: any, propertyKey: string, parameterIndex: number) =>{
		const tokens = Reflect.getMetadata('design:paramtypes', target) || [];

		tokens[parameterIndex] = new OmniRepositoryToken(repoClass, modelClass, ormName);

		// redefine param types so we can check where is our namespace suppose to be injected
		Reflect.defineMetadata('design:paramtypes', tokens, target);
	}
}

export function Omni(ormName?: string) {
	return (target: any, propertyKey: string, parameterIndex: number) =>{
		const tokens = Reflect.getMetadata('design:paramtypes', target) || [];

		tokens[parameterIndex] = new OmniOrmToken(ormName);

		// redefine param types so we can check where is our namespace suppose to be injected
		Reflect.defineMetadata('design:paramtypes', tokens, target);
	}
}

const customResolve = (token: any, idx: string) => {
	if (token instanceof OmniRepositoryToken) {
		return new token.repoClass(token.modelClass);
	}

	if (token instanceof OmniOrmToken) {
		return Injector.get(token.ormName);
	}

	// If your resolver cannot resolve token, just return null
    // Injector will try to resolve token by itself 
	return null;
};

// Registering custom global resolver
Injector.registerResolver(customResolve);

@Injectable()
class ServiceB {
	constructor(
		@Omni() public orm: any,
		@OmniRepository(PostgresRepository, SomeModel) protected rep: PostgresRepository<SomeModel>,
	) {}
}

const sb = Injector.resolve(ServiceB);
console.log(sb);

Result

ServiceB {
  orm: OmniORM {},
  rep: PostgresRepository { type: [class SomeModel] }
}