npm.io
1.7.0 • Published 19h ago

deku-swarm

Licence
MIT
Version
1.7.0
Deps
0
Size
68 kB
Vulns
0
Weekly
0

deku-swarm

Queue-based worker pool for Node.js, with cooldown-based automatic retry and support for job chaining.

deku-swarm is the generic queue-and-pool engine originally built for Navi's cache-warming job processing, extracted into a standalone, reusable package. It has no domain knowledge of HTTP requests or caching — you subclass Job with your own perform() logic, and deku-swarm takes care of queuing, worker allocation, cooldown-based retry, and dead-lettering.


Installation

npm install deku-swarm

Library usage

import { Job, JobFactory, JobRegistry, WorkerFactory, WorkersRegistry, Engine } from 'deku-swarm';

// 1. Define a Job subclass with your own work
class GreetJob extends Job {
  constructor({ id, name }) {
    super({ id });
    this.name = name;
  }

  async perform(logger) {
    logger.info(`Hello, ${this.name}!`);
    // A job's perform() can enqueue further jobs — this is how job chaining works.
    // JobRegistry.enqueue('greet', { name: 'again' });
  }
}

// 2. Register a factory that builds GreetJob instances under a lookup key
JobFactory.build('greet', { klass: GreetJob });

// 3. Build the singleton registries (once per process)
JobRegistry.build({ cooldown: 2000, maxRetries: 3 });
WorkersRegistry.build({
  quantity: 5,
  factory: new WorkerFactory({ jobRegistry: JobRegistry, workersRegistry: WorkersRegistry }),
});
WorkersRegistry.initWorkers();

// 4. Enqueue work
JobRegistry.enqueue('greet', { name: 'World' });

// 5. Run the engine until every job is finished/dead and every worker is idle
const engine = new Engine({ jobRegistry: JobRegistry, workersRegistry: WorkersRegistry, sleepMs: 500 });
await engine.start();

JobRegistry and WorkersRegistry are static singleton facades: build() once at startup, reset() between test examples. Both Engine and WorkerFactory accept either the static facade classes (as above) or plain instances exposing the same method names — pass whichever fits your application's dependency-injection style.

Job

Abstract base class for every unit of work. Subclass it and implement perform.

Member Description
constructor({ id }) id is the job's unique identifier.
async perform(logContext) Must be overridden. Throwing marks the job as failed; resolving marks it finished.
applyCooldown(ms) Marks the job not-ready-for-retry until ms milliseconds from now. Called internally on failure.
isReadyBy(currentTime) Whether the job's cooldown has elapsed as of currentTime.
get maxRetries() Maximum retry count for this job class. Defaults to 3; override to customize per subclass.
exhausted(maxRetries) Whether the job has used up its retry attempts.
Worker

Pulls one assigned Job at a time and runs it, reporting the outcome back to the registries.

Member Description
constructor({ id, jobRegistry, workersRegistry, loggerFactory }) loggerFactory is a ({ workerId, jobId }) => logger function; the returned logger must expose debug/info/warn/error.
assign(job) Assigns a Job to this worker.
async perform() Runs the assigned job, then reports finish/fail to jobRegistry and sets itself idle on workersRegistry.
JobFactory / WorkerFactory

Build instances of Job/Worker subclasses with generated unique IDs.

Member Description
new JobFactory({ klass, attributesGenerator, attributes }) klass defaults to the base Job class; attributes are merged into every built job (e.g. shared dependencies).
JobFactory.build(name, options) Creates a JobFactory and registers it under name for lookup by JobRegistry.enqueue(name, params).
JobFactory.get(name) Retrieves a previously registered factory.
JobFactory.reset() Clears all registered factories. Intended for test teardown.
new WorkerFactory({ klass, attributesGenerator, jobRegistry, workersRegistry, loggerFactory }) klass defaults to the base Worker class. Injects the given registries/logger factory into every built worker.
JobRegistry

Static singleton facade managing the job queues: enqueued, processing, failed (cooling down), retry, finished, and dead.

Method Description
JobRegistry.build(options) Builds the singleton. options.cooldown (ms, default 5000) and options.maxRetries (default 3) control retry behavior.
JobRegistry.reset() Destroys the singleton. Use in test teardown.
JobRegistry.enqueue(factoryKey, params) Builds a job via the named JobFactory and pushes it onto the enqueued queue.
JobRegistry.pick() Removes and returns the next ready job, moving it to processing. Used internally by WorkersAllocator.
JobRegistry.finish(job) / JobRegistry.fail(job) Reports a job outcome. A failed job is either re-queued with cooldown applied or moved to dead once exhausted().
JobRegistry.requeue(job) Moves a job from processing back to enqueued (e.g. no idle worker was available).
JobRegistry.retryJob(id) Manually moves a failed or dead job straight into the retry queue, bypassing cooldown.
JobRegistry.promoteReadyJobs() Moves jobs whose cooldown has elapsed from failed into the retry queue. Called each Engine tick.
JobRegistry.clearQueues() Clears enqueued/retry/failed/finished/dead, leaving processing untouched.
JobRegistry.hasJob() / JobRegistry.hasReadyJob() Whether any jobs exist at all / are immediately pickable.
JobRegistry.jobsByStatus(status) / JobRegistry.jobById(id) Introspection helpers, e.g. for a monitoring UI.
JobRegistry.stats() { enqueued, processing, failed, retryQueue, finished, dead, total } counts.
WorkersRegistry

Static singleton facade managing the worker pool.

Method Description
WorkersRegistry.build(options) Builds the singleton. options.quantity sets the pool size; options.factory optionally injects a custom WorkerFactory.
WorkersRegistry.reset() Destroys the singleton. Use in test teardown.
WorkersRegistry.initWorkers() Builds quantity workers and marks them idle.
WorkersRegistry.getIdleWorker() Picks (and marks busy) an idle worker, or returns null.
WorkersRegistry.setBusy(id) / WorkersRegistry.setIdle(id) Moves a worker between the busy/idle collections.
WorkersRegistry.hasBusyWorker() / WorkersRegistry.hasIdleWorker() Pool-state checks.
WorkersRegistry.stats() { idle, busy } counts.
WorkersAllocator

Matches ready jobs to idle workers. allocate() loops pick()/getIdleWorker() until either queue is exhausted, re-queuing a picked job if no worker is free. Built automatically by Engine unless you inject your own via allocator.

Engine

Drives the main loop: promotes cooling-down jobs, allocates ready jobs to idle workers, and (optionally) watches for sustained idleness.

Option Description
jobRegistry / workersRegistry Required. Any object exposing the same method names as JobRegistry/WorkersRegistry (the static facades work directly).
allocator Optional WorkersAllocator. Defaults to one built from jobRegistry/workersRegistry.
sleepMs Milliseconds to wait between loop ticks. Defaults to 500; a negative value disables sleeping (useful in tests).
keepAlive When true, the loop never exits on its own — only stop() ends it. Use this when a long-lived process (e.g. a web UI) needs the engine to wait for new work. Defaults to false, meaning the loop exits once there are no jobs and no busy workers.
idleTimeoutMs Milliseconds of sustained idleness (no jobs, no busy workers) before onIdleTimeout fires. 0 (default) disables tracking.
onIdleTimeout Callback invoked at most once per idle window once idleTimeoutMs is crossed.
Method Description
async start() Runs the loop until it should stop (see keepAlive above) or stop() is called.
stop() Requests the loop to exit after the current iteration.
pause() / resume() Suspend/resume job allocation without exiting the loop.
Collections

IdentifyableCollection, Queue, and SortedCollection are the small building blocks used internally by the registries (lookup-by-id storage, FIFO push/pick, and cooldown-ordered storage, respectively). They're exported for advanced use cases (e.g. building a custom registry), but most consumers only need Job, JobFactory, JobRegistry, WorkerFactory, WorkersRegistry, and Engine.


Source & Documentation

GitHub repository: darthjee/navideku-swarm lives under worker/.