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 🙏

© 2025 – Pkg Stats / Ryan Hefner

job-planner

v2.0.0

Published

Simple and powerful execution flow control lib. Manage your processes' execution order with zero pain.

Readme

Job Planner

npm version Build Status License: MIT

Simple and powerful execution flow control library. Manage your processes' execution order with zero pain.

Table of Contents

Installation

npm install job-planner
# or
yarn add job-planner

Quick Start

import { createPlan } from 'job-planner';

const planner = createPlan(({ sync }) =>
  sync(connectToDB, runMigrations, seedData)
);

await planner();

API Reference

createPlan(planCreator)

Creates a plan executor from a plan definition.

import { createPlan } from 'job-planner';

const planner = createPlan(({ sync, parallel }) =>
  sync(
    initializeApp,
    parallel(loadUserData, loadSettings),
    startServer
  )
);

await planner({ stopOnError: true });

Parameters:

  • planCreator - Function that receives { sync, parallel } helpers and returns a Job

Returns: A function that executes the plan with optional configuration

sync(...processes)

Creates a synchronous job where processes execute sequentially.

import { sync, createPlan } from 'job-planner';

// Via createPlan callback
createPlan(({ sync }) => sync(step1, step2, step3));

// Direct import for composition
const setupDB = sync(connectToDB, runMigrations);

Throws:

  • EmptyJobError - If no processes are provided
  • InvalidProcessError - If any process is not a function or Job

parallel(...processes)

Creates a parallel job where processes execute concurrently.

import { parallel, createPlan } from 'job-planner';

createPlan(({ parallel }) =>
  parallel(fetchUserData, fetchProducts, fetchSettings)
);

Throws:

  • EmptyJobError - If no processes are provided
  • InvalidProcessError - If any process is not a function or Job

Type Guards

import { isJob, isProcess } from 'job-planner';

isJob(someValue);     // true if value is a Job object
isProcess(someValue); // true if value is a callable function

Configuration

The planner accepts a configuration object:

interface Config {
  executor?(process: Process): Promise<unknown> | unknown;
  stopOnError?: boolean;
  errorHandler?(error: unknown): void;
}

Default Configuration

const defaultConfig = {
  async executor(process) {
    return process();
  },
  stopOnError: false,
  errorHandler(error) {
    throw error;
  },
};

executor

Custom function that wraps process execution. Useful for logging, metrics, or dependency injection.

await planner({
  async executor(process) {
    console.log('Starting process...');
    const result = await process();
    console.log('Process complete');
    return result;
  },
});

stopOnError

When true, stops plan execution after the first error. The errorHandler is still called before stopping.

await planner({
  stopOnError: true,
  errorHandler(error) {
    console.error('Process failed:', error);
  },
});

errorHandler

Handler for errors thrown by processes. Called for each error before deciding whether to continue.

await planner({
  errorHandler(error) {
    logError(error);
    // Don't rethrow - continue execution
  },
});

Error Handling

Built-in Error Classes

import {
  EmptyJobError,
  InvalidProcessError,
  NotJobError,
  UnknownJobTypeError,
} from 'job-planner';

| Error | When Thrown | |-------|-------------| | EmptyJobError | sync() or parallel() called with no arguments | | InvalidProcessError | Non-function/non-Job passed to sync() or parallel() | | NotJobError | createPlan callback doesn't return a valid Job | | UnknownJobTypeError | Job has an unrecognized type |

Error Handling in Parallel Jobs

In parallel execution, errors are handled per-process:

const planner = createPlan(({ parallel }) =>
  parallel(
    () => { throw new Error('Error 1'); },
    () => { throw new Error('Error 2'); },
    () => console.log('Success')
  )
);

const errors: Error[] = [];
await planner({
  stopOnError: false,
  errorHandler(e) {
    errors.push(e as Error);
  },
});
// errors contains both Error 1 and Error 2
// 'Success' was still logged

TypeScript Support

Full TypeScript support with exported types:

import {
  createPlan,
  sync,
  parallel,
  // Types
  Job,
  SyncJob,
  ParallelJob,
  Process,
  Config,
  PlanCreator,
  // Type guards
  isJob,
  isProcess,
  // Errors
  EmptyJobError,
  InvalidProcessError,
  NotJobError,
  UnknownJobTypeError,
  // Enum
  JOB_TYPES,
} from 'job-planner';

Common Patterns

Database Setup

const planner = createPlan(({ sync }) =>
  sync(connectToDB, runMigrations, seedData)
);

await planner({ stopOnError: true });

Parallel Data Fetching

const planner = createPlan(({ parallel }) =>
  parallel(fetchUsers, fetchProducts, fetchOrders)
);

await planner();

Mixed Execution

const planner = createPlan(({ sync, parallel }) =>
  sync(
    initialize,
    parallel(
      loadConfig,
      loadTranslations,
      sync(connectDB, warmCache)
    ),
    startServer
  )
);

Job Composition

import { sync, parallel, createPlan } from 'job-planner';

const dbSetup = sync(connectToDB, runMigrations);
const cacheSetup = sync(connectRedis, warmCache);
const startup = parallel(dbSetup, cacheSetup);

const planner = createPlan(() => sync(startup, startServer));

Migration from v1

Breaking Changes in v2

  1. Empty jobs now throw EmptyJobError

    // v1: Silently did nothing
    sync();
    
    // v2: Throws EmptyJobError
    sync(); // Error: Cannot create sync job with no processes
  2. Invalid processes now throw InvalidProcessError

    // v1: Failed at runtime during execution
    sync('not a function');
    
    // v2: Throws immediately
    sync('not a function'); // Error: Invalid process at index 0
  3. errorHandler now receives unknown instead of Error

    // v1
    errorHandler(error: Error) { }
    
    // v2
    errorHandler(error: unknown) { }

License

MIT