Skip to main content

A dependency injection nano container

This package works with Node.js, Deno, BrowsersIt is unknown whether this package works with Cloudflare Workers, Bun
It is unknown whether this package works with Cloudflare Workers
This package works with Node.js
This package works with Deno
It is unknown whether this package works with Bun
This package works with Browsers
JSR Score
100%
Published
3 months ago (0.6.3)

Bottle XS 👕

JSR JSR Score codecov

A dependency injection micro nano container that draws inspiration from BottleJS (for concepts) and Zod (for type inference and composability).

Features

  • ✨ Type inference of the services just works
  • ⚡ < 500 bytes (when minified and gzipped)
  • 🍺 Terse lazy-loading access to the services as in BottleJS
  • 🏭 Supports (by default) the factory pattern from BottleJS
  • 🔧 Supports the service and serviceFactory patterns from BottleJS via additional pure helpers (tree-shakeable)
  • ♻️ Possiblity to reset the providers to re-instantiate a service
  • 🐾 Tracks dependencies: reseting a provider will reset all the dependents (opt-out possible)
  • 🔒 Favors immutability by taking the providers at construction time
  • 🔌 Favors composability by inheriting from other containers

Examples

Basic usage

import { Bottle } from 'jsr:@esroyo/bottlexs';

interface BarleyLike {
    water: Water;
}

interface HopsLike {
    water: Water;
}

class Water {}
class Nordal implements BarleyLike {
    constructor(public water: Water) {}
}
class Hallertau implements HopsLike {
    constructor(public water: Water) {}
}
class Beer {
    public brand = 'San Miguel';
    constructor(
        public barley: BarleyLike,
        public hops: HopsLike,
        public water: Water,
    ) {}
}

// Services are defined with a name `string | symbol` and a `Factory` function.
// A factory function receives an object as an argument (the container), and
// should return the constructed service.
//
// The accurate typing of the object expected by each Factory is important.
// The Factory declaration is used to typecheck that all expected dependencies
// are effectively available at the end of the day.
const providers = {
    barley: (container: { water: Water }) => new Nordal(container.water),
    hops: (container: { water: Water }) => new Hallertau(container.water),
    water: () => new Water(),
    beer: (
        container: { barley: BarleyLike; hops: HopsLike; water: Water },
    ) => new Beer(container.barley, container.hops, container.water),
};
const bottle = new Bottle(providers);

// inferred type
type Services = typeof bottle.container;
// type SomeServices = {
//     barley: Nordal;
//     hops: Hallertau;
//     water: Water;
//     beer: Beer;
// }

console.log(bottle.container.beer.brand);
// "San Miguel"

console.log(
    bottle.container.water ===
        bottle.container.beer.water,
);
// true

Inherit/compose in multiple bottle instances

import { Bottle } from 'jsr:@esroyo/bottlexs';

class Water {}
class Barley {
    constructor(public water: Water) {}
}
class Hops {
    constructor(public water: Water) {}
}
class Beer {
    public brand = 'San Miguel';
    constructor(
        public barley: Barley,
        public hops: Hops,
        public water: Water,
    ) {}
}

const someProviders = {
    barley: ({ water }: { water: Water }) => new Barley(water),
    hops: ({ water }: { water: Water }) => new Hops(water),
    water: () => new Water(),
};
const someBottle = new Bottle(someProviders);

// inferred type
type SomeServices = typeof someBottle.container;
// type SomeServices = {
//     barley: Barley;
//     hops: Hops;
//     water: Water;
// }

const someOtherProviders = {
    now: () => Date.now,
    beer: (
        { barley, hops, water }: { barley: Barley; hops: Hops; water: Water },
    ) => new Beer(barley, hops, water),
};
const otherBottle = new Bottle(someOtherProviders, someBottle);

// inferred type
type OtherServices = typeof otherBottle.container;
// type OtherServices = {
//     now: () => number;
//     beer: Beer;
//     barley: Barley;
//     hops: Hops;
//     water: Water;
// }

console.log(otherBottle.container.beer.brand);
// "San Miguel"

console.log(
    someBottle.container.water ===
        otherBottle.container.beer.water,
);
// true

Use the service helper with constructors

import { Bottle, service } from 'jsr:@esroyo/bottlexs';

class Water {}
class Barley {
    constructor(public water: Water) {}
}
class Hops {
    constructor(public water: Water) {}
}

const providers = {
    // The default provider pattern is the Factory pattern:
    // receives the container as parameter and returns an instance
    barley: (container: { water: Water }) => new Barley(container.water),
    water: () => new Water(),
    // It is possible to use the alternative `service` helper:
    // takes in a Constructor, and a list of services to be resolved
    // and passed as arguments to the [[Constructor]] call
    hops: service(Hops, ['water'] as const),
};
const bottle = new Bottle(providers);

// inferred type
type Services = typeof bottle.container;
// type SomeServices = {
//     barley: Barley;
//     hops: Hops;
//     water: Water;
// }

Add Package

deno add jsr:@esroyo/bottlexs

Import symbol

import * as _esroyo_bottlexs from "@esroyo/bottlexs";

---- OR ----

Import directly with a jsr specifier

import * as _esroyo_bottlexs from "jsr:@esroyo/bottlexs";

Add Package

npx jsr add @esroyo/bottlexs

Import symbol

import * as _esroyo_bottlexs from "@esroyo/bottlexs";

Add Package

yarn dlx jsr add @esroyo/bottlexs

Import symbol

import * as _esroyo_bottlexs from "@esroyo/bottlexs";

Add Package

pnpm dlx jsr add @esroyo/bottlexs

Import symbol

import * as _esroyo_bottlexs from "@esroyo/bottlexs";

Add Package

bunx jsr add @esroyo/bottlexs

Import symbol

import * as _esroyo_bottlexs from "@esroyo/bottlexs";