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

nfkit

v1.0.37

Published

Common kits

Readme

nfkit

nfkit is a TypeScript utility package focused on practical backend/frontend engineering helpers: middleware dispatching, i18n pipeline, lightweight app context (DI-like), task scheduling, abortable proxies, and more.

Feature Count (public exports)

Based on index.ts, the package currently exposes 14 public entries:

  • 13 functional modules
  • 1 shared types module (types)

| # | Entry | Purpose | | --- | --- | --- | | 1 | workflow | Lazy chained calls for property/method/function invocation | | 2 | dual-object | Dual-state object (sync object + PromiseLike) | | 3 | workflow-dispatcher | Multi-worker task scheduler with retry/backoff and dynamic worker ops | | 4 | round-robin | Round-robin selector | | 5 | abortable | AbortSignal-aware proxy wrapper | | 6 | middleware-dispatcher | Generic middleware dispatching (dynamic/proto variants included) | | 7 | i18n | i18n pipeline and dictionary lookup middleware | | 8 | patch-string-in-object | Deep string patching inside object graphs | | 9 | observe-diff | Object mutation diff observer (add/update/delete) | | 10 | memorize | Per-instance memoization decorator for methods/getters | | 11 | may-be-array | T | T[] type helper + normalization utility | | 12 | app-context | Lightweight app context (provide/use/start/get) | | 13 | configurer | Config loading/parsing helpers | | 14 | types | Shared type exports (Awaitable, etc.) |

Installation

npm i nfkit

Quick Start

import {
  createAppContext,
  MiddlewareDispatcher,
  I18n,
  I18nLookupMiddleware,
  WorkflowDispatcher,
} from 'nfkit';

API and Usage

1) workflow

Builds a lazy chain. Execution starts only when you await (or call .then/.catch/.finally).

API

workflow<T>(source: T | Promise<T>): Chain<T>

Example

import { workflow } from 'nfkit';

class Client {
  count = 0;
  async connect() {
    return this;
  }
  inc(n: number) {
    this.count += n;
    return this;
  }
}

const client = new Client();
const value = await workflow(client).connect().inc(2).count;
// value === 2

2) dual-object

Creates a dual-state object:

  • sync access path for properties/methods
  • async object path via await obj
  • pending-safe deferred calls for selected async methods (asyncMethods)

API

type Dual<T> = T & PromiseLike<T>;
const DUAL_PENDING: unique symbol;
throwDualPending(): never;

dualizeAny<T>(
  sync: () => T,
  asyncFn: () => Promise<T>,
  options?: { asyncMethods?: readonly (keyof T)[] },
): Dual<T>

Example

import { dualizeAny } from 'nfkit';

type UserClient = {
  id: string;
  ping(): Promise<number>;
};

const c = dualizeAny<UserClient>(
  () => ({ id: 'cached', ping: async () => 1 }),
  async () => ({ id: 'remote', ping: async () => 42 }),
  { asyncMethods: ['ping'] as const },
);

const v = await c.ping(); // works even while object is pending
const obj = await c;

3) workflow-dispatcher

Asynchronous multi-worker dispatcher with:

  • global dispatch (auto worker selection)
  • worker-specific dispatch
  • retry + exponential backoff
  • runtime worker replace/add/remove

API

new WorkflowDispatcher<F extends (...args: any[]) => Promise<any>>(
  workersOrPromises: Array<F | Promise<F>>,
  options?: { maxAttempts?: number; backoffBaseMs?: number },
)

dispatch(...args: Parameters<F>): Promise<Awaited<ReturnType<F>>>
dispatchSpecific(index: number, ...args: Parameters<F>): Promise<Awaited<ReturnType<F>>>
replaceWorker(index: number, fn: F): void
addWorker(fn: F): number
removeWorker(index: number): Promise<void>
snapshot(): WorkerSnapshot<F>[]
get pending(): number

Example

import { WorkflowDispatcher } from 'nfkit';

type Worker = (x: number) => Promise<string>;

const d = new WorkflowDispatcher<Worker>([
  async (x) => `A:${x}`,
  async (x) => `B:${x}`,
]);

const r1 = await d.dispatch(1);
const r2 = await d.dispatchSpecific(0, 2);

4) round-robin

Simple round-robin picker.

API

class RoundRobin<T> {
  constructor(items: T[]);
  next(): T;
}

Example

import { RoundRobin } from 'nfkit';

const rr = new RoundRobin(['a', 'b', 'c']);
rr.next(); // a
rr.next(); // b
rr.next(); // c
rr.next(); // a

5) abortable

Wraps an object/function with an AbortSignal-aware proxy. After abort, access/calls throw (or reject for Promise flows).

API

class AbortedError extends Error {}

type AbortableOpts = {
  boxPrimitives?: boolean;
  noRecursive?: boolean;
};

abortable<T>(obj: T, signal: AbortSignal, opts?: AbortableOpts): T

Example

import { abortable } from 'nfkit';

const ac = new AbortController();
const obj = abortable({ n: 1 }, ac.signal);

obj.n; // 1
ac.abort('stop');
// later access throws AbortedError

6) middleware-dispatcher

Generic middleware execution model: (args..., next).

Includes 3 dispatchers:

  • DynamicMiddlewareDispatcher<F>: override buildMiddlewares
  • MiddlewareDispatcher<F>: direct array registration
  • ProtoMiddlewareDispatcher<A>: middleware by instance prototype chain

Core types/options

type Middleware<F>
type MiddlewareNext<F>
type MiddlewareDispatcherOptions<F> = {
  acceptResult?: (res) => boolean | Promise<boolean>;
  errorHandler?: (e, args, next) => any;
};

Example

import { MiddlewareDispatcher } from 'nfkit';

type Handler = (x: number) => number;
const d = new MiddlewareDispatcher<Handler>();

d.middleware(async (x, next) => {
  const r = await next();
  return (r ?? 0) + 1;
});

d.middleware(async (x) => x * 10);

const out = await d.dispatch(2); // 21

7) i18n

Middleware-based translation pipeline with built-in dictionary lookup middleware.

API

class I18n<Ex extends any[] = []> {
  constructor(options: { locales: string[]; defaultLocale?: string });
  middleware(mw: I18nMiddleware<Ex>, prior?: boolean): this;
  removeMiddleware(mw: I18nMiddleware<Ex>): this;
  getExactLocale(locale: string): string;
  translateString(locale: string, text: string, ...ex: Ex): Promise<string>;
  translate<T>(locale: string, obj: T, ...ex: Ex): Promise<T>;
}

type I18nDictionary = Record<string, Record<string, string>>;
I18nLookupMiddleware(
  dictOrFactory,
  options?: { matchType?: 'exact' | 'hierarchy' | 'startsWith' },
)
createI18nLookupMiddleware<Ex>()

Example

import { I18n, I18nLookupMiddleware } from 'nfkit';

const i18n = new I18n({ locales: ['en', 'zh', 'zh-Hans'], defaultLocale: 'en' });

i18n.middleware(
  I18nLookupMiddleware(
    {
      en: { hello: 'Hello' },
      'zh-Hans': { hello: '你好' },
    },
    { matchType: 'hierarchy' },
  ),
);

const s = await i18n.translateString('zh-Hans-CN', 'Say #{hello}');
// Say 你好

8) patch-string-in-object

Deeply traverses an object graph and transforms all string values (async callback supported).

API

patchStringInObject<T>(
  obj: T,
  cb: (s: string) => string | Promise<string>,
): Promise<T>

Example

import { patchStringInObject } from 'nfkit';

const out = await patchStringInObject(
  { a: 'hello', nested: ['x', 'y'] },
  (s) => s.toUpperCase(),
);
// { a: 'HELLO', nested: ['X', 'Y'] }

9) observe-diff

Creates a proxy that reports add/update/delete changes on property writes/deletes.

API

observeDiff<T>(
  obj: T,
  cb: (change: {
    type: 'add' | 'update' | 'delete';
    key: keyof T;
    oldValue: T[keyof T] | undefined;
    newValue: T[keyof T] | undefined;
  }) => any,
): T

Example

import { observeDiff } from 'nfkit';

const state = observeDiff<{ count?: number }>({}, (c) => {
  console.log(c.type, c.key, c.oldValue, c.newValue);
});

state.count = 1; // add
state.count = 2; // update
delete state.count; // delete

10) memorize

Per-instance memoization decorator for methods/getters.

API

Memorize(): MethodDecorator & PropertyDecorator

Example

import { Memorize } from 'nfkit';

class Svc {
  calls = 0;

  @Memorize()
  expensive() {
    this.calls += 1;
    return Math.random();
  }
}

Requires TypeScript decorator support (for example experimentalDecorators).

11) may-be-array

Utility for handling single value or array input consistently.

API

type MayBeArray<T> = T | T[];
makeArray<T>(value: MayBeArray<T>): T[];

Example

import { makeArray } from 'nfkit';

makeArray(1); // [1]
makeArray([1, 2]); // [1, 2]

12) app-context

Lightweight app context / DI-like container.

Supports:

  • provider registration via provide
  • context composition via use
  • lifecycle initialization via start
  • service lookup via get/getAsync

Core API

createAppContext<Req = Empty>(): AppContext<Empty, Req>

ctx.provide(ServiceClass, ...args?, options?)
ctx.use(otherCtx)
ctx.get(ServiceClass)
ctx.getAsync(ServiceClass)
ctx.define()
ctx.start()

Type-driven IoC contract

provide() is type constrained by AppServiceClass, whose constructor signature is:

new (ctx: AppContext<...>, ...args) => Service

That means the first constructor parameter must be ctx.

The type system also tracks context shape changes:

  • provide: 'name' adds ctx.name as that service instance type.
  • merge: ['memberA', 'memberB'] maps selected members from service onto ctx with correct member types.
  • use(otherCtx) merges provided/required context types across contexts.
  • start() resolves to never at type level if required context (Req) is not satisfied.

Common provide options:

{
  provide?: string; // expose service instance as ctx[prop]
  merge?: string[]; // map selected service members onto ctx
  useValue?: instance | Promise<instance>;
  useFactory?: (ctx, ...args) => instance | Promise<instance>;
  useClass?: Class;
}

provide vs merge:

  • provide: exposes the whole service instance on context (for example ctx.logger).
  • merge: exposes selected service members directly on context (for example ctx.info from ctx.logger.info).

Current runtime semantics:

  • ctx.provide/ctx.use inside a provider constructor: newly added providers are settled after that constructor ends.
  • ctx.provide/ctx.use inside init(): newly added providers are settled and initialized immediately after the current init ends.
  • calling provide/use after start(): providers are queued; call define() to trigger settlement.
  • use(startedCtx): merges existing instances directly; no re-construction and no re-init.

Example

import { AppContext, createAppContext } from 'nfkit';

class LoggerService {
  constructor(
    public ctx: AppContext,
    private prefix = '[app]',
  ) {}

  logs: string[] = [];

  info(s: string) {
    this.logs.push(`${this.prefix} ${s}`);
  }
}

const app = await createAppContext()
  // first constructor argument of provider class is always ctx
  .provide(LoggerService, '[core]', {
    provide: 'logger', // ctx.logger -> LoggerService
    merge: ['info'], // ctx.info -> bound logger.info
  })
  .define()
  .start();

app.logger.info('from instance');
app.info('from merged method');

13) configurer

Config helper for string-based configuration (env-style), with typed getters.

API

class Configurer<T extends Record<string, string>> {
  constructor(defaultConfig: T);
  loadConfig(options?: {
    env?: Record<string, string | undefined>;
    obj?: any;
  }): ConfigurerInstance<T>;
  generateExampleObject(): Record<string, unknown>;
}

class ConfigurerInstance<T extends Record<string, string>> {
  getString(key): string;
  getInt(key): number;
  getFloat(key): number;
  getBoolean(key): boolean;
  getStringArray(key): string[];
  getIntArray(key): number[];
  getFloatArray(key): number[];
  getBooleanArray(key): boolean[];
  getJSON<R>(key): R;
}

Merge priority:

defaultConfig < obj < env

Example

import { Configurer } from 'nfkit';

const cfg = new Configurer({ PORT: '3000', ENABLE_CACHE: '1' }).loadConfig({
  env: { PORT: '8080' },
});

cfg.getInt('PORT'); // 8080
cfg.getBoolean('ENABLE_CACHE'); // true

14) types

Shared type exports:

type Awaitable<T> = T | Promise<T>;
type AnyClass = new (...args: any[]) => any;
type ClassType<T = any> = new (...args: any[]) => T;
interface Empty {}
type Prettify<T> = ...

Notes

This README reflects the current implementation exported by index.ts. If exports change in future versions, treat source/types as the final reference.