Socket
Socket
Sign inDemoInstall

ts-glue

Package Overview
Dependencies
Maintainers
0
Versions
16
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

ts-glue

TypeScript opinionated dependency injection library. Glue is a tiny lightweight library made for FP codebases making extensive use of partial application for dependency injection.


Version published
Weekly downloads
663
increased by15.71%
Maintainers
0
Weekly downloads
 
Created
Source
ts-glue amazing logo

GitHub Actions Workflow Status NPM Version NPM License

ts-glue

TypeScript opinionated dependency injection library.
Why another dependency injection library?
Most dependency injection libraries target object oriented codebases.
Glue ([/ɡluː/], should you need to say it out loud) is a tiny lightweight library made for FP codebases making extensive use of partial application for dependency injection.
Main benefits of Glue include:

  • TypeScript type level configuration checks: Glue allows to check at buidtime that all dependencies have been registered.
  • Lazyness: A function or a component managed by Glue can be used anywhere in your codebase without worrying when and how dependencies will be set up at runtime.
  • Modularity: Glue is monorepo friendly, configuration can be splitted into several modules without losing buildtime checking nor lazyness.

If you are in a hurry you can start playing with ts-glue right away with this demo sandbox

Getting started

ts-glue is a TypeScript library that can be installed with any package manager such as npm or yarn:

  # with npm
  npm i ts-glue
  # or yarn
  yarn add ts-glue

Usage

In order to use ts-glue, you need to do 3 things:

  • Build a Glue object that will hold a descriptions of all the components and functions that might need to be injected
  • Register implementations
  • Use the glue to inject the implementations previously registered

So let's begin by building a "Glue" object and a description of all the functions and components that it will handle.
Let's say we have a clock function we want to inject in our codebase:

type Clock = () => Date;

const systemClock: Clock = () => new Date();

Our Glue could be set up as shown below:

import { Glue, is } from "ts-glue";

const glue = Glue.buildFrom({
  clock: is<Clock>,
}).registerService("clock", systemClock);

Our glue is now ready for use!

It can be used like a regular glue:

const clock: Clock = glue.getService("clock");

But the ts-glue sweet spot comes with functions that can be partially applied as shown below:

const doHelloWorld = (clock: Clock) => (name: string) =>
  `Hello world ${name} (${clock()})`;

const helloWorld = glue.inject(doHelloWorld, ["clock"]);

helloWorld("Glue");

Our helloWorld() is now ready for use. If we want to inject dependencies into an object instead of a function, we need to use Glue.build() instead of Glue.inject():

const buildHelloWorld = (clock: Clock) => ({
  sayHello: (name: string) => `Hello world ${name} (${clock()})`;
}):

const helloWorld = glue.build(buildHelloWorld, ['clock']);

helloWorld.sayHello('Glue');

TypeScript type level checks

Glue functions such as registerService() or inject(). This means that if you do a typo, TypeScript will yell at you!

import { Glue, is } from "ts-glue";

const glue =
  Glue.buildFrom(
    {
      clock: is<Clock>,
    }
  )
const doHelloWorld = (clock: Clock) => (name: string) => `Hello world ${name} (${clock()})`;


glue.registerService('cloq', systemClock); // Compilation error
glue.registerService('clock', () => 'string')); // Compilation error

glue.inject(doHelloWorld, ['cloq']); // compilation error
glue.inject(doHelloWorld, []); // compilation error

You can also ask ts-glue to check that your configuration is complete:

import { Glue, is } from "ts-glue";

const glue = Glue.buildFrom({
  clock: is<Clock>,
  dbConfiguration: is<DbConfiguration>,
}).registerService("clock", systemClock);

// compilation error, dbConfiguration is missing
glue.checkAllServicesAreRegistered();

const glue2 = glue.registerService("dbConfiguration", someDbConfiguration);
// compilation OK
glue2.checkAllServicesAreRegistered();

Feel free to experiment with ts-glue in this playground

Lazyness

ts-glue is very lazy :-)
Function dependencies are resolved at the very last moment, which is when they get executed. This means that you do not have to worry too much ot the sequence order of injections and registrations:

import { Glue, is } from "ts-glue";

type Random = () => number;
const doGiveMeANumber = (random: Random) => `A random number ${random()}`;

const glue = Glue.buildFrom({
  randomGenerator: is<Random>,
}).registerService("randomGenerator", Math.random);

const giveMeANumber = glue.inject(doGiveMeANumber, ["randomGenerator"]);

glue.registerService("randomGenerator", () => 42);
giveMeANumber(); // A random number 42

In the example above, we first build a glue, we register a first random number generator and we retrieve an injected version of the giveMeANumber() function. Then we override the registered random number generator. Since dependency injection is lazy, since dependencies are resolved each time an injected function get executed, giveMeANumber() calls the very last registered random generator.

ts-glue lazyness is very handy when one part of your codebase is managed by ts-glue but not everything. We have included in the example folder an Express express example app that demonstrate how to use components managed by ts-glue from Express routes that are out of the scope of ts-glue.

Modularity

Any significant codebase is splitted into several modules, packages... well it should be ;)
A big monolythic dependency injection configuration file quickly becomes hard to maintain. Hence ts-glue allows you to split your configuration in several files, composing your Glue from several sub Glue instances:


// Let's say we have a booking package in our codebase
// booking.ts
export const bookingGlue =
  Glue.buildFrom(
    {
      bookingService: is<BookingService>,
      dbConfiguration: is<DbConfiguration>,
      ...
    }
  ).registerService(
    'bookingService',
    someBookingServiceImplementation
  );

// Let's say we have also a billing package in our codebase
// billing.ts
export const billingGlue =
  Glue.buildFrom(
    {
      billingService: is<BillingService>,
      dbConfiguration: is<DbConfiguration>,
      ...
    }
  ).registerService(
    'billingService',
    someBillingServiceImplementation
  );

// Then at the entry point of our application
// we can gather our two previous glues
const appGlue = Glue.compose(
  bookingGlue,
  billingGlue
);

// appGlue can inject 'bookingService' and 'billingService'
const megaSagaService =
  appGlue.inject(
    ...,
    ['bookingService', 'billingService']
  )

// at that point line below fails to compile because
// dbConfiguration has not been registered
appGlue.checkAllServicesAreRegistered();

// Now it's ok and the dbConfiguration is registered
// into both bookingGlue and billingGlue
appGlue
  .registerService('dbConfiguration', SomeDbConfig)
  .checkAllServicesAreRegistered();

See for yourself how ts-glue leverages on TypeScript type checking with this playground

Under the cover

TBC

FAQs

Package last updated on 01 Aug 2024

Did you know?

Socket

Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.

Install

Related posts

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc