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 🙏

© 2026 – Pkg Stats / Ryan Hefner

@fuiste/dependencies

v0.1.1

Published

Type-safe, functional dependency composition for TypeScript

Readme

@fuiste/dependencies

Type-safe, functional dependency composition for TypeScript.

Docs: https://fuiste.github.io/dependencies/

This library is inspired by Effect's Layer, but keeps the surface area small and focused around two ideas:

  • Context: an immutable collection of services addressed by typed tags.
  • Dependency: a recipe for building one or more tagged services, possibly from other tagged services.

The goal is to make application wiring feel like the rest of your functional code: explicit, composable, typed, and easy to test.

Installation

pnpm add @fuiste/dependencies

Core Ideas

  • Context.Tag<Service>('name') creates a typed service identifier.
  • Context stores concrete services.
  • Dependency.succeed, Dependency.sync, Dependency.async, and Dependency.scoped create dependency recipes.
  • compose(left, right) feeds left outputs into right requirements and keeps both outputs.
  • merge(a, b) builds independent dependencies concurrently.
  • provide(dep, source) satisfies requirements without exposing the source outputs.
  • override(live, test) swaps or supplements services for tests and local customization.
  • build(dep) builds a graph into a Result containing a Context.
  • use(dep, fn) is the simplest way to acquire dependencies, run some code, and automatically close scoped resources.

Quick Start

import { Context, Dependency, Result, compose, use } from '@fuiste/dependencies'

const Config = Context.Tag<{ appName: string }>('readme/quick/config')
const Greeter = Context.Tag<{ greet: () => string }>('readme/quick/greeter')

const config = Dependency.succeed(Config, { appName: 'Dependencies' })
const greeter = Dependency.sync(Greeter, [Config], (context) => ({
  greet: () => `hello from ${Context.get(context, Config).appName}`,
}))

const result = await use(compose(config, greeter), (context) => {
  return Context.get(context, Greeter).greet()
})

if (Result.isOk(result)) {
  console.log(result.value)
}

Composition

A realistic graph usually reads like a pipeline.

import {
  Context,
  Dependency,
  Result,
  build,
  compose,
} from '@fuiste/dependencies'

const Config = Context.Tag<{ prefix: string }>('readme/compose/config')
const Logger = Context.Tag<{ log: (message: string) => string }>(
  'readme/compose/logger',
)
const Database = Context.Tag<{ query: (sql: string) => string }>(
  'readme/compose/database',
)
const App = Context.Tag<{ start: () => string }>('readme/compose/app')

const config = Dependency.succeed(Config, { prefix: 'demo' })

const logger = Dependency.sync(Logger, [Config], (context) => ({
  log: (message: string) =>
    `[${Context.get(context, Config).prefix}] ${message}`,
}))

const database = Dependency.sync(Database, [Logger], (context) => ({
  query: (sql: string) => Context.get(context, Logger).log(`query:${sql}`),
}))

const app = Dependency.sync(App, [Database], (context) => ({
  start: () => Context.get(context, Database).query('select 1'),
}))

const program = compose(config, compose(logger, compose(database, app)))
const built = await build(program)

if (Result.isOk(built)) {
  console.log(Context.get(built.value, App).start())
}

A few useful rules of thumb:

  • Use compose when the right-hand dependency needs outputs from the left.
  • Use merge when the branches are independent and can be built in parallel.
  • Use provide when you want to satisfy requirements without carrying the provider's outputs forward.

Scoped Resources

Dependency.scoped is for resources that must be released. You can either manage the scope yourself with build, or let use do it for you.

Manual Scope

import { Context, Dependency, Result, Scope, build } from '@fuiste/dependencies'

const Database = Context.Tag<{ query: () => string }>('readme/scope/database')

const database = Dependency.scoped(Database, () => ({
  service: { query: () => 'ok' },
  release: () => {
    console.log('closing database')
  },
}))

const scope = Scope.make()
const built = await build(database, { scope })

if (Result.isOk(built)) {
  console.log(Context.get(built.value, Database).query())
}

await Scope.close(scope)

Automatic Scope With use

import { Context, Dependency, use } from '@fuiste/dependencies'

const Connection = Context.Tag<{ id: string }>('scope/use-connection')

const connection = Dependency.scoped(Connection, () => ({
  service: { id: 'conn-2' },
  release: () => {
    console.log('released')
  },
}))

await use(connection, async (context) => {
  console.log(Context.get(context, Connection).id)
})

Testing And Overrides

Because dependencies are plain values, testing is usually just graph substitution.

import {
  Context,
  Dependency,
  Result,
  build,
  override,
} from '@fuiste/dependencies'

const Repository = Context.Tag<{ source: string }>('readme/override/repository')
const App = Context.Tag<{ source: () => string }>('readme/override/app')

const app = Dependency.sync(App, [Repository], (context) => ({
  source: () => Context.get(context, Repository).source,
}))

const inMemoryRepository = Dependency.succeed(Repository, { source: 'memory' })
const built = await build(override(app, inMemoryRepository))

if (Result.isOk(built)) {
  console.log(Context.get(built.value, App).source())
}

override is especially useful when you want a test dependency to satisfy requirements and win over a live service with the same tag.

Result Shape

build and use return a small Result value:

type Result<A, E> = { _tag: 'ok'; value: A } | { _tag: 'err'; error: E }

Build failures use a normalized BuildError shape:

  • missing_service
  • duplicate_service
  • circular_dependency
  • construction_failed

construction_failed distinguishes between typed errors returned as Result.err(...) and unexpected defects such as thrown exceptions.

API Sketch

Context

const tag = Context.Tag<Service>('service/name')
const empty = Context.empty()
const single = Context.of(tag, service)
const next = Context.add(single, otherTag, otherService)
const hasTag = Context.has(next, tag)
const service = Context.get(next, tag)
const merged = Context.merge(left, right)

Dependency

const live = Dependency.succeed(tag, value)
const syncDep = Dependency.sync(tag, [OtherTag], (context) => service)
const asyncDep = Dependency.async(tag, [OtherTag], async (context) => service)
const scopedDep = Dependency.scoped(tag, [OtherTag], async (context) => ({
  service,
  release: async () => {},
}))

const graph = compose(a, b)
const parallel = merge(a, b)
const provided = provide(graph, source)
const swapped = override(live, test)

const built = await build(graph)
const result = await use(graph, (context) => run(context))

Notes

  • Tags are stable by key, so Context.Tag<Service>('logger') always refers to the same runtime tag for that key.
  • Ordinary context merges and ordinary dependency composition reject duplicate output services.
  • override is the explicit escape hatch when one service should win over another.
  • Scoped dependencies require an explicit scope when you call build. If you do not want to manage that yourself, use use.