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

orqis

v0.2.2

Published

Lightweight structured async task orchestration for Node.js - concurrency control, cancellation, retries, task groups, and observability hooks.

Downloads

632

Readme

orqis

Structured async task orchestration for Node.js

The gap between p-limit and Bull — without Redis, without boilerplate.

npm version npm downloads Bundle Size License: MIT TypeScript Node


Install

npm install orqis
# or
pnpm add orqis
# or
yarn add orqis

Requirements: Node.js ≥ 16. Works with ESM (import) and CommonJS (require) — zero configuration. Zero production dependencies.


Quick Start

import { TaskQueue } from 'orqis';

const queue = new TaskQueue({ concurrency: 5 });

const urls = [
  'https://api.example.com/users/1',
  'https://api.example.com/users/2',
  // ... hundreds more
];

const users = await queue.addAll(
  urls.map(url => async ({ signal }) => {
    const res = await fetch(url, { signal });
    if (!res.ok) throw new Error(`HTTP ${res.status}`);
    return res.json();
  })
);

console.log(`Fetched ${users.length} users`);

That's it. Concurrency capped at 5. Cancellation-ready. TypeScript-inferred.


Quick Decision Matrix

| Your Need | Use This | |-----------|----------| | Cap concurrency, nothing else | p-limit (~1 KB) | | Concurrency + priorities + timeouts | p-queue | | Concurrency + retries + cancellation + events + plugins | orqis (~5 KB) | | Redis-backed persistent queue across servers | BullMQ | | Cron / recurring jobs | Bree or node-cron | | API rate limiting | Bottleneck |

Orqis is for in-process async orchestration. If your tasks live in a single Node.js process and you need more than a concurrency cap but less than a full Redis-backed job queue, Orqis is the right tool.


The Problem Orqis Solves

You're building a CLI tool, a build script, or a background task runner. You have async work — lots of it — and you need more than raw Promise.all.

Promise.all starts everything at once. Feed it 10,000 URLs and you'll saturate the CPU, exhaust file handles, and get rate-limited by every API in sight. You need:

  • Concurrency control — process 8 at a time, not 10,000
  • Retries — transient failures happen; tasks should recover without boilerplate
  • Timeouts — a hung network call shouldn't stall your entire pipeline
  • Cancellation — Ctrl-C should clean up, not leak connections
  • Observability — which tasks ran, which failed, how long they took

The existing solutions force a compromise: reach for p-limit and build everything else yourself, or adopt Bull/BullMQ and bring in Redis middleware infrastructure. Orqis fills the gap — production-grade orchestration with zero infrastructure.


Why Orqis

Resilience

  • Built-in retry + backoff — exponential, fixed, with jitter. No p-retry wrapper needed.
  • Per-task timeouts — hung requests get aborted, not awaited forever.
  • stopOnError — halt the pipeline on first failure for build scripts and CI.

Control

  • AbortSignal-native cancellationctx.signal on every task. Pass it to fetch, streams, database clients. Cancellation propagates cleanly through the call chain.
  • TaskGroup scoped cancellation — cancel only the compile phase without touching lint. The missing primitive for structured concurrency.
  • Priority queue — binary max-heap; critical tasks jump the line.

Observability

  • Lifecycle eventsstart, success, error, retry, timeout, cancel, idle, empty, active. Every event has typed payloads.
  • Plugin systemonBefore, onAfter, onError, onCancel hooks. Built-in logging, metrics, and retry observer. Write custom plugins for Sentry, OpenTelemetry, caching, etc.
  • onIdle() / onEmpty() promises — await completion without polling.

Simplicity

  • Zero dependencies — 5 KB minzipped. No node_modules baggage, no CVE surface.
  • TypeScript-first — strict generics, typed events, no any leakage.
  • Dual ESM + CJS — one package, both module systems, no config.
  • No infrastructure — runs in-process. No Redis, no Docker, no worker processes.

When Should You Use Orqis

Use Orqis when your answer to one or more of these is "yes":

  • You're writing a CLI, build script, test runner, or data pipeline in Node.js
  • You need to run N async tasks but cap parallelism below some limit
  • Some of those tasks call unreliable external services that need retry logic
  • You need to handle Ctrl-C / graceful shutdown cleanly
  • You want to log or monitor task progress without wiring up a full observability stack
  • You have batches of related tasks that should be cancellable as a unit
  • You need different tasks to run with different priorities (critical path vs. background)

Consider alternatives when:

  • Tasks must survive process restarts → BullMQ (Redis-backed)
  • Work distributes across machines → BullMQ or Agenda
  • Recurring cron-style jobs needed → Bree or node-cron
  • Only need concurrency controlp-limit
  • Reactive stream processing → RxJS

Core Concepts

Tasks

A task is any async function. It receives { signal: AbortSignal } and returns a value. That's the entire interface.

const task = async ({ signal }: { signal: AbortSignal }) => {
  const data = await fetch('/api', { signal });
  return data.json();
};

const result = await queue.add(task);

Pending vs Active

Tasks exist in one of two states. Pending tasks are queued and waiting for a concurrency slot. Active tasks are running. queue.size is pending; queue.pending is active.

| Method | Effect | |--------|--------| | queue.clear() | Removes pending tasks (running continue) | | queue.cancel() | Removes pending + aborts running | | queue.pause() | Stops dequeuing (running continue) |

queue.onIdle() vs queue.onEmpty()

onIdle() resolves when pending = 0 AND running = 0 — everything is done. onEmpty() resolves when the pending queue drains, even if tasks are still running.

TaskGroup

A group is a named batch of tasks that can be awaited or cancelled as a unit, without affecting other work on the same queue.

import { TaskGroup } from 'orqis/group';

const queue  = new TaskQueue({ concurrency: 8 });
const build  = new TaskGroup(queue, { id: 'build' });
const test   = new TaskGroup(queue, { id: 'test' });

build.cancel();            // only build tasks abort
await test.onComplete();   // wait for only test tasks

Comparison

| | orqis | p-limit | p-queue | Bottleneck | Bull | |-|-----------|---------|---------|------------|------| | Size | ~5 KB | ~1 KB | ~25 KB publish | ~15 KB | ~229 KB | | Dependencies | 0 | 0 | 1 | 0 | 8+ | | ESM + CJS | ✅ | ✅ | ESM only | ✅ | ✅ | | TypeScript-first | ✅ | ✅ | ✅ | ❌ | ✅ | | Concurrency cap | ✅ | ✅ | ✅ | ✅ | ✅ | | Priority queue | ✅ | ❌ | ✅ | ✅ | ✅ | | AbortSignal cancellation | ✅ | ❌ | partial | ❌ | ❌ | | Structured concurrency (TaskGroup) | ✅ | ❌ | ❌ | ❌ | ❌ | | Built-in retry + backoff | ✅ | ❌ | ❌ | ❌ | ✅ | | Per-task timeouts | ✅ | ❌ | ✅ | ❌ | ✅ | | Lifecycle events | ✅ | ❌ | ✅ | ❌ | ✅ | | Plugin system | ✅ | ❌ | ❌ | ❌ | ❌ | | Infrastructure required | none | none | none | none | Redis | | Maintenance status | Active | Active | Active | Stale (2019) | Active |

The only things Orqis deliberately omits are persistence, clustering, and cron scheduling — all of which require external infrastructure that most in-process use cases don't need.


Graceful Shutdown

import { TaskQueue } from 'orqis';

const queue = new TaskQueue({ concurrency: 8 });

process.on('SIGINT', async () => {
  console.log('\nShutting down gracefully...');
  queue.pause();
  await queue.onIdle();
  process.exit(0);
});

process.on('SIGTERM', () => {
  queue.cancel();
  process.exit(1);
});

API at a Glance

new TaskQueue(options?)

// Methods
queue.add(task, opts?)      // → Promise<T>
queue.addAll(tasks, opts?)  // → Promise<T[]>
queue.onIdle()              // → Promise<void>
queue.onEmpty()             // → Promise<void>
queue.pause() / resume()
queue.clear() / cancel()
queue.on/off/once(event, fn)

// Properties
queue.size      // pending count
queue.pending   // active count
queue.isPaused
queue.signal    // AbortSignal

// Events
'on start'     → { id }
'on success'   → { id, result, durationMs }
'on error'     → { id, error, attempt }
'on retry'     → { id, attempt, delay }
'on timeout'   → { id }
'on cancel'    → { id }
'on idle' / 'on empty' / 'on active'

Full docs: API Reference


Plugin System

import { loggingPlugin, metricsPlugin, retryObserverPlugin } from 'orqis/plugins';

queue.use(loggingPlugin({ prefix: '[build]', verbose: true }));

const { plugin, snapshot } = metricsPlugin();
queue.use(plugin);
await queue.onIdle();
console.log(snapshot());

queue.use(retryObserverPlugin((id, attempt, delay) => {
  console.warn(`Task ${id} retrying (${attempt}) in ${delay}ms`);
}));

See the Plugin Guide for custom plugin examples (cache, Sentry, OpenTelemetry).


What's Next (Roadmap)

| Version | Focus | |---------|-------| | v1.0 | queue.use() on TaskQueue, rate limiting, 100% coverage | | v1.x | OpenTelemetry plugin, queue.stats(), progress events | | v2.0 | Browser/Worker support, Deno/Bun, optional persistence |

See the full roadmap.


Documentation

| Page | Description | |------|-------------| | Getting Started | Step-by-step from install to first queue | | Core Concepts | How Orqis works internally | | Recipes | Copy-paste patterns for common scenarios | | Plugin System | Logging, metrics, tracing, and custom middleware | | API Reference | Complete type signatures and option tables | | Migration Guide | Moving from p-limit, p-queue, async, fastq | | Testing Guide | How to test queue-based code reliably | | Comparison | Detailed survey of the ecosystem | | Roadmap | Planned features and release milestones | | Wiki | Troubleshooting, FAQs, common errors |


Contributing

git clone https://github.com/AkkilMG/orqis.git
cd orqis && npm install
npm test          # run test suite
npm run build     # compile dist/
npm run typecheck # type-check src/
npm run lint      # ESLint

Read CONTRIBUTING.md before opening a PR. Bug reports go in Issues. Questions go in Discussions.


License

MIT © Akkil M G