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

@unional/async-context

v9.0.13

Published

Asynchronous context for functional style programming

Downloads

270

Readme

@unional/async-context

NPM version NPM downloads Bundle size

Codecov

Secure, type safe, asynchronous context for functional programming.

In functional programming, it is common to pass in a context object containing dependencies used by the function.

AsyncContext allows these dependencies to be loaded asynchronously.

This is useful in many cases. For example,

Installation

This is part of async-fp.

Typically, you will install that instead of installing @unional/async-context directly.

But if you want, you can:

# npm
npm install @unional/async-context

# yarn
yarn add @unional/async-context

# pnpm
pnpm install @unional/async-context

#rush
rush add -p @unional/async-context

Usage

Introduction Video

You can provide the initial context when calling the constructor. The context value must be an object (Record). You can extend and augment the context with the extend() later.

You can supply the context value in 4 different ways:

import { AsyncContext } from '@unional/async-context'

const context = new AsyncContext({ key: 'secret key' }) // or
const context = new AsyncContext(Promise.resolve({ key: 'secret key' })) // or
const context = new AsyncContext(() => ({ key: 'secret key' })) // or
const context = new AsyncContext(async () => ({ key: 'secret key' }))

await context.get() // => { key: 'secret key' }

If you provide an initialize function, it will not be executed until the context.get() is called. It allows you to wait for user input and change the values/dependencies loaded.

If you want to start loading the dependencies immediately, starts the loading and pass in the resulting Promise.

Initialize

You can create a AsyncContext and initialize it later. This allows you to create a context in one part of your system, and initialize it in another.

import { AsyncContext } from '@unional/async-context'

export const context = new AsyncContext<{ key: string }>() // optionally specify the init context type

// in another file
import { context } from './context'

context.initialize({ key: 'secret key' }) // or
context.initialize(Promise.resolve({ key: 'secret key' })) // or
context.initialize(() => ({ key: 'secret key' })) // or
context.initialize(() => Promise.resolve({ key: 'secret key' }))

initialize() can only be called when you create the AsyncContext with empty constructor. And it can only be called once. This prevents the context to be replaced.

Note that the data it contains are not frozen. If you want to protect them from tampering, you can use Object.freeze() or immutable library such as immutable.

Extend

You can extend or augment the context by calling extend().

import { AsyncContext } from '@unional/async-context'

const ctx = new AsyncContext({ a: 1, b: 'b' })

const newCtx = ctx.extend({ b: 2, c: 3 })
const newCtx = ctx.extend(Promise.resolve({ b: 2, c: 3 }))
const newCtx = ctx.extend(() => ({ b: 2, c: 3 }))
const newCtx = ctx.extend(async () => ({ b: 2, c: 3 }))

await newCtx.get() // => { a: 1, b: 2, c: 3 }

The extend() updates the AsyncContext in place, i.e. the newCtx and ctx above is the same instance.

The difference is that newCtx will have the context type updated while ctx doesn't.

If you want to use the ctx directly, you can use get<T>() to assert the context type.

const ctx = new AsyncContext({ a: 1, b: 'b' })
  .extend({ b: 2, c: 3 })

await ctx.get<{ a: number, b: number, c: number }>()

// it is really the same as this, but less verbose
(await ctx.get() as { a: number, b: number, c: number })

Chaining

As you can guess, the initialize() and extend() functions can be chained:

const ctx = new AsyncContext()

const newCtx = ctx.initialize({ a: 1 }).extend({ b: 2 }).extend({ c: 3 })

Use Cases

Just-in-time Dependency Loading

Since the handlers are delay executed, you can declare the dependencies you need, and load them only when the function is invoked.

import { AsyncContext } from '@unional/async-context'

function createSettingRoute(context: AsyncContext<IO>) {
  return async (request, response) => {
    const { io } = await context.get()
    response(io.loadSetting())
  }
}

addRoute('/setting', createSettingRoute(new AsyncContext(() => ({ io: createIO() }))))

Chained Dependency Loading

You can use extend() to chain asynchronous dependency loading.

import { AsyncContext } from '@unional/async-context'

const ctx = new AsyncContext(() => ({ io: createIO() }))
  .extend(loadConfig)
  .extend(loadPlugins)

async function loadConfig(ctx: AsyncContext<{ io: IO }>) {
  const { io } = await ctx.get()
  return { config: await io.getConfig() }
}

async function loadPlugins(ctx: AsyncContext<{ config: Config, io: IO }>) {
  const { config, io } = await ctx.get()
  return { plugins: await Promise.all(config.plugins.map(p => loadPlugin(io, p)) }
}

Configuration Injection

You can wait for user input.

import { AsyncContext } from '@unional/async-context'

let configure: (value: any | PromiseLike<any>) => void
const configuring = new Promise(a => configure = a)

const ctx = new AsyncContext(configuring)

async function doWork(ctx: AsyncContext) {
  const { config } = await ctx.get() // will wait for `configure()`
  ...
}

doWork(ctx)

// call by user after application starts
configure({ ... })