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

@marcusrettig/box

v0.2.7

Published

Minimalistic type-safe service container

Downloads

86

Readme

Box

Box is a minimalistic, type-safe, zero-dependency, service container.

Note: This library is still in it's early stages and breaking changes are expected.

Breaking change: In release 0.2.0, the inject() function was changed to inject a single service instead of the entire container: inject(name). To inject the entire service container you should not use the injectAll() function instead (added in 0.2.1). See example below.

Install

With npm:

npm install @marcusrettig/box

With pnpm:

pnpm add @marcusrettig/box

With yarn:

yarn add @marcusrettig/box

Simple Example

import {Box} from '@marcusrettig/box';

class Database {
  findAll<T>(table: string): T[] {
    if (table === 'employees') {
      return [{name: 'Michael Scott'}] as T[];
    }

    throw new Error(`Table ${table} does not exist`);
  }
}

class EmployeeService {
  // Services can inject other services using the inject function.
  // The type is automatically inferred, in this case to Database.
  database = container.inject('database');

  findAll() {
    return this.database.findAll<{name: string}>('employees');
  }
}

const container = new Box({
  database: Box.class(Database),
  employeeService: Box.class(EmployeeService),
});

function start() {
  // The type for $ is automatically inferred to:
  // { database: Database; employeeService: EmployeeService }
  const $ = container.init({});

  const employees = $.employeeService.findAll();

  console.log(employees);
}

start();
npx tsx examples/simple.ts

Injecting all dependencies

Release 0.2.0 introduced a breaking change where the inject() function now injects a single named dependency instead of the entire service container. Release 0.2.1 adds a function called injectAll which injects the full container. Please note that some dependencies may not be initialized when injecting the container, so it is unsafe to access it during the injection context.

import {Box} from '@marcusrettig/box';

class EmployeeService {
  $ = container.injectAll();

  greet(name: string) {
    return this.$.greeting + ' ' + name;
  }
}

const container = new Box({
  greeting: Box.value('Hello'),
  employeeService: Box.class(EmployeeService),
});

const $ = container.init({});

console.log($.employeeService.greet('Michael Scott'));
npx tsx examples/inject-all.ts

Design Goals

  • Type-Safe: Detect all type errors at compile time (given that you follow the two rules described in the next section). Say goodbye to "No provider found" and similar errors.
  • Minimalistic: The library consists of fewer than 100 lines of code (excluding types and comments). It should be easy for anyone to understand what goes on under the hood.
  • Zero-dependency: No external dependencies makes it easy to use the library without a package manager such as npm, for example in the Deno runtime.

Injection Context

A service can access another service by injecting it using the inject() function. The benefit of this approach is that the type of the injected service is automatically inferred, and that the service is guaranteed to have been provided, eliminating the "No provider found" errors which you've probably seen in more dynamic dependency injection systems. This however comes with two caveats:

  1. The inject() function can only be called inside an injection context, such as a constructor, property initializer, or inside a factory function.
  2. Circular dependencies are not allowed. For example, if service A injects service B which injects service C which injects service A an error will be thrown.

The concept of injection context is similar to that in the Dependency Injection system of Angular 16 and you can read more about it in the Angular Documentation.

TLDR: Only call inject() in a constructor, property initializer, or factory function. Avoid circular dependencies.

The following examples demonstrates the injection context concept:

// Property initializer:
class EmployeeService {
  // Inside injection context:
  database = container.inject('database');

  findAll() {
    // Outside injection context (after initialization):
    return this.database.findAll('employees');
  }
}

// Factory function:
class createEmployeeService() {
  // Inside injection context:
  const database = container.inject('database');

  return {
    findAll() {
      // Outside injection context (after initialization):
      return database.findAll('employees');
    }
  }
}