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

@alexmacarthur/power-plant

v0.0.7

Published

A dependency injection framework built on native decorators.

Downloads

25

Readme

Power Plant

npm bundle size

A dependency injection framework built on native decorators.

Why?

Native class decorators will inevitably be made available in ECMAScript, providing a simple API for enhancing classes, their fields, methods, and accessors.

Amongst the many use cases is container-managed dependency injection, an approach used by frameworks such as Laravel, Spring Boot, and Nest.js. This library offers a similar feature using native JavaScript decorators.

It's purpose is to help maintain clear inversion of control, remove instantiation responsibilty from your application, and enable simpler testing of your application code.

Installation

npm install @alexmacarthur/power-plant

Usage

There are two decorators available for using this library.

(Optionally) Register Classes w/ Constructor Parameters

By default, no class registration is necessary (they will be lazily instantiated when injected). However, if you'd like to customize the parameters passed to a class constructor, use the @register decorator to define them as an array:

@register(["arg1", "arg2", "arg3"])
class MyService {}

When the class is later instantiated, that array will be provided as distinct parameters:

new class MyService("arg1", "arg2", "arg3");

If, for whatever reason, you'd like to add or remove registered classes manually, you can import the registry Map directly:

import { registry } from "@alexmacarthur/power-plant";

// Register with constructor parameters.
registry.set(MyService, ["arg1", "arg2", "arg3"]);

// Unregister classes.
registry.delete(MyService);

Injecting Classes

The @inject() decorator is needed to construct and assign instances to class fields.

class MyClass {
  @inject(MyService);
  myService: MyService;

  constructor() {
    console.log(this.myService); // instance of MyService
  }
}

If needed, you can manipulate the container of instantiated classes directly:

import { container } from "@alexmacarthur/power-plant";

// Set instances.
const myServiceInstance = new MyService();
container.set(MyService, myServiceInstance);

// Remove instances.
container.delete(MyService);

Testing with Injected Dependencies

To mock an injected class during a test, you can use the aforementioned container to point the @inject decorator to a mock instance. For example:

it("Can mock instances.", () => {
  class MyTestApp {
    @inject(EmailService)
    emailService;

    go() {
      this.emailService.send("my message!");
    }
  }

  const mockEmailServiceInstance = {
    send: vi.fn(),
  };

  // Replace actual implementation with a mock.
  container.set(EmailService, mockEmailServiceInstance);

  new MyTestApp().go();

  // Make assertions on that mock.
  expect(mockEmailServiceInstance.send).toHaveBeenCalledTimes(1);
  expect(mockEmailServiceInstance.send).toHaveBeenCalledWith("my message!");
});

FAQ

What makes this special?

This library is built entirely on the offiial ECMAScript decorators proposal. It differs from most other libraries out there, which are built on a legacy decorator specification.

In what runtimes is this supported?

Currently, Deno appears to be the only major runtime that supports native ECMAScript decorators, and even it has a bug that'll prevent this library from working correctly.

So, in order to use this, you'll need to compile your code with TypeScript > v5.0 or with Babel's @babel/plugin-proposal-decorators plugin.

Can I use this across modules in the same application?

Yes. The same registry and container are used throughout an application, so you can safely inject dependencies without worring about classes being unnecessarily instantiated multiple times. Here's a more fleshed out, contrived example:

// my-dependency-class.ts

export class MyDependencyClass {}
// my-class.ts

import { inject } from "@alexmacarthur/power-plant";
import { MyDependencyClass } from "./my-dependency-class";

export class MyClass {
  @inject(MyDependencyClass)
  dependencyClass: MyDependencyClass;

  constructor() {}

  run() {}
}
// my-other-class.ts

import { inject } from "@alexmacarthur/power-plant";
import { MyDependencyClass } from "./my-dependency-class";

export class MyOtherClass {
  @inject(MyDependencyClass)
  dependencyClass: MyDependencyClass;

  constructor() {}

  run() {}
}
// index.ts

import { register } from "@alexmacarthur/power-plant";
import { MyDependencyClass } from "./my-dependency-class";
import { MyClass } from "./my-class";
import { MyOtherClass } from "./my-other-class";

register(MyDependencyClass);

new MyClass().run();
new MyOtherClass().run();

// MyDependencyClass was instantiated _once_.

Feedback or Contributions?

Make an issue or find me on X.