Thanks to visit codestin.com
Credit goes to github.com

Skip to content

carloitaben/di

Folders and files

NameName
Last commit message
Last commit date

Latest commit

Β 

History

10 Commits
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 

Repository files navigation

Barebones typesafe dependency injection thingy using Thenables and AsyncLocalStorage

Getting started

  1. Define your dependency
import { Dependency } from "@carloitaben/di-thingy"

type Random = {
  readonly next: () => number
}

const Random = new Dependency<Random>("Random")
  1. Use your dependency by awaiting it
import { Dependency } from "@carloitaben/di-thingy"

type Random = {
  readonly next: () => number
}

const Random = new Dependency<Random>("Random")

async function program() {
  const random = await Random
  const randomNumber = random.next()
  console.log(`random number: ${randomNumber}`)
}
  1. Provide a live implementation
import { Dependency } from "@carloitaben/di-thingy"

type Random = {
  readonly next: () => number
}

const Random = new Dependency<Random>("Random")

async function program() {
  const random = await Random
  const randomNumber = random.next()
  console.log(`random number: ${randomNumber}`)
}

const RandomLive = Random.make(() => ({
  next: () => Math.random(),
}))
  1. Build a runtime with the dependency implementation
import { Dependency } from "@carloitaben/di-thingy"

type Random = {
  readonly next: () => number
}

const Random = new Dependency<Random>("Random")

async function program() {
  const random = await Random
  const randomNumber = random.next()
  console.log(`random number: ${randomNumber}`)
}

const RandomLive = Random.make(() => ({
  next: () => Math.random(),
}))

const runtimeLive = new Runtime(RandomLive)

await runtimeLive.run(program) // stdout: random number: 0.8241872233134417
  1. Provide a test implementation
import { Dependency } from "@carloitaben/di-thingy"

type Random = {
  readonly next: () => number
}

const Random = new Dependency<Random>("Random")

async function program() {
  const random = await Random
  const randomNumber = random.next()
  console.log(`random number: ${randomNumber}`)
}

const RandomLive = Random.make(() => ({
  next: () => Math.random(),
}))

const runtimeLive = new Runtime(RandomLive)

await runtimeLive.run(program) // stdout: random number: 0.8241872233134417
await runtimeLive.run(program) // stdout: random number: 0.3176275913827688
await runtimeLive.run(program) // stdout: random number: 0.6740024767900261

const RandomTest = Random.make(() => ({
  next: () => 0.25,
}))

const runnableTest = new Runtime(RandomTest, program)

await runnableTest.run(program) // stdout: random number: 0.25
await runnableTest.run(program) // stdout: random number: 0.25
await runnableTest.run(program) // stdout: random number: 0.25
  1. Use the test implementation during tests

  2. Profit

Providing a default implementation

Instead of typing the dependency manually, you can provide a default implementation and the type will be inferred from it.

import { Dependency } from "@carloitaben/di-thingy"

export const Random = new Dependency("Random", () => ({
  next: () => Math.random(),
}))

export const RandomTest = Random.make(() => ({
  next: () => 0.25,
}))

Using dependencies to create other dependencies

Since dependencies are simply functions and implementations are thenables, you can use an async function to create dependencies and await other dependencies within it.

import { Dependency } from "@carloitaben/di-thingy"
import { Client, createClient } from "@libsql/client"
import { drizzle } from "drizzle-orm/libsql"

export const Database = new Dependency<Client>("Database")

// Local SQLite file
export const DatabaseTest = Database.make(() =>
  createClient({
    url: "file:sqlite.db",
  }),
)

// External database
export const DatabaseLive = Database.make(() =>
  createClient({
    url: process.env.DATABASE_URL,
    authToken: process.env.DATABASE_AUTH_TOKEN,
  }),
)

// Uses the provided Database to build the ORM
export const Drizzle = new Dependency("Drizzle", async () => {
  const database = await Database
  return drizzle(database)
})

Finalizers

In the example above, you can provide cleanup functions for dependency implementations. These functions are guaranteed to run independently of the runnable result.

import { Client, createClient } from "@libsql/client"
import { Dependency, Runtime } from "../../src"

export const Database = new Dependency<Client>("Database")

export const DatabaseTest = Database.make(
  () =>
    createClient({
      url: "file:sqlite.db",
    }),
  // Close the client after the runnable finishes
  (client) => {
    client.close()
    console.log("SQLite client closed")
  },
)

// ...

async function program() {
  throw Error("Oops")
}

const runtime = new Runtime(DatabaseTest)
await runtime.run(program) // stdout: SQLite client closed

You can also add finalizers to default dependency implementations.

import { Dependency } from "@carloitaben/di-thingy"
import { createClient } from "@libsql/client"

export const Database = new Dependency(
  "Database",
  () =>
    createClient({
      url: "file:sqlite.db",
    }),
  (client) => client.close(),
)

LICENSE

MIT

About

🍩 Minimal dependency injection

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published