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

serial-task

v1.1.0

Published

Put a list of functions in and get a composed task function. Similar to functional programming's compose (function composition), but with more fine-grained and precise control, and the generated task incurs almost no runtime overhead. Supports both synchr

Readme

Serial Task

License: MIT

Put a list of functions in and get a composed task function. Similar to functional programming's compose (function composition), but with more fine-grained and precise control, and the generated task incurs almost no runtime overhead. ✨

Note: For async functions, use createSerialTaskAsync instead of createSerialTask. Both functions have the same API, but createSerialTaskAsync properly handles async/await and Promise-based functions.

For more awesome packages, check out my homepage💛

📦 Installation

npm install serial-task
pnpm add serial-task

🎯 Quick Start

Note: For async functions(tasks/resultWrapper/conditions), use createSerialTaskAsync instead.

import { createSerialTask } from 'serial-task';

// Create a serial task with multiple functions
const mathTask = createSerialTask({
  tasks: [
    (x: number) => x + 1, // Step 1: add 1
    (x: number) => x * 2, // Step 2: multiply by 2
    (x: number) => x - 1, // Step 3: subtract 1
  ],
});

const result = mathTask(5);
console.log(result.value); // 11 -> ((5 + 1) * 2) - 1 = 11
console.log(result.results); // [6, 12, 11]

🔄 Execution Flow

The following diagram shows how functions are called in each iteration of the loop:

📖 API Reference

createSerialTask(options) / createSerialTaskAsync(options)

Creates a sync/async serial task function.

Parameters

  • options: SerialTaskOptions<F>
    • name?: string - Name of the generated task function (default: 'kskbTask')
    • tasks: F[] - Array of functions to be executed in order
    • breakCondition?: function - Function that determines when to break the loop (default: () => false)
    • skipCondition?: function - Function that determines when to skip a task (default: () => false)
    • resultWrapper?: function - Function that transforms input between tasks, default(means the first task gets original args, subsequent tasks get the last return value):
    (_task: Fn, index: number, _tasks: Fn[], args: unknown[], lastReturn: unknown) =>
      index === 0 ? args : [lastReturn];

Returns

A function that executes the tasks in order and returns a TaskReturn<R> object:

interface TaskReturn<R> {
  value: R; // Result of the last executed task
  results: R[]; // All results (skipped tasks are undefined)
  trivial: boolean; // True if tasks array was empty
  breakAt: number; // Index where loop broke (-1 if not broken)
  skipped: number[]; // Indices of skipped tasks
}

🎨 Usage Scenarios

Scenario 1: Function Composition Pipeline

Perfect for data transformation pipelines where you need to apply multiple transformations in sequence:

import { createSerialTask } from 'serial-task';

// Data processing pipeline
interface UserData {
  name: string;
  email: string;
  age: number;
}

const processUser = createSerialTask({
  name: 'userProcessor',
  tasks: [
    // Step 1: Validate input
    (user: UserData) => {
      if (!user.email.includes('@')) {
        throw new Error('Invalid email');
      }
      return user;
    },

    // Step 2: Normalize data
    (user: UserData) => ({
      ...user,
      name: user.name.trim().toLowerCase(),
      email: user.email.toLowerCase(),
    }),

    // Step 3: Add computed fields
    (user: UserData) => ({
      ...user,
      isAdult: user.age >= 18,
      displayName: user.name.charAt(0).toUpperCase() + user.name.slice(1),
    }),

    // Step 4: Generate summary
    (user: any) => ({
      ...user,
      summary: `${user.displayName} (${user.email}) - ${user.isAdult ? 'Adult' : 'Minor'}`,
    }),
  ],
});

const result = processUser({
  name: '  John Doe  ',
  email: '[email protected]',
  age: 25,
});

console.log(result.value);
// Output: {
//   name: 'john doe',
//   email: '[email protected]',
//   age: 25,
//   isAdult: true,
//   displayName: 'John doe',
//   summary: 'John doe ([email protected]) - Adult'
// }

Scenario 2: Event Handler Chain with Conditional Logic

Great for building middleware-like handler chains with skip and break logic:

import { createSerialTask } from 'serial-task';

interface Request {
  path: string;
  method: string;
  headers: Record<string, string>;
  body?: any;
  metadata?: Record<string, any>;
}

// HTTP request handler chain
const requestHandler = createSerialTask({
  name: 'httpHandler',
  tasks: [
    // Handler 1: Authentication
    (req: Request) => {
      console.log('🔐 Authenticating request...');
      return {
        ...req,
        metadata: { ...req.metadata, authenticated: true, userId: 'user123' },
      };
    },

    // Handler 2: Rate limiting
    (req: Request) => {
      console.log('⏱️ Checking rate limits...');
      return {
        ...req,
        metadata: { ...req.metadata, rateLimited: false },
      };
    },

    // Handler 3: Input validation
    (req: Request) => {
      console.log('✅ Validating input...');
      if (req.method === 'POST' && !req.body) {
        throw new Error('Body required for POST requests');
      }
      return {
        ...req,
        metadata: { ...req.metadata, validated: true },
      };
    },

    // Handler 4: Business logic
    (req: Request) => {
      console.log('🔄 Processing business logic...');
      return {
        ...req,
        metadata: { ...req.metadata, processed: true, result: 'success' },
      };
    },

    // Handler 5: Response formatting
    (req: Request) => {
      console.log('📤 Formatting response...');
      return {
        ...req,
        metadata: { ...req.metadata, formatted: true },
      };
    },
  ],

  // Skip rate limiting for admin users
  skipCondition: (task, index, tasks, args, lastReturn) => {
    if (index === 1) {
      // rate limiting handler
      const req = lastReturn as Request;
      return req.metadata?.userId === 'admin';
    }
    return false;
  },

  // Break early if user is not authenticated
  breakCondition: (task, index, tasks, args, lastReturn) => {
    if (index > 0) {
      // after authentication
      const req = lastReturn as Request;
      return !req.metadata?.authenticated;
    }
    return false;
  },

  // Pass the result to the next handler
  resultWrapper: (task, index, tasks, args, lastReturn) => {
    if (index === 0) {
      return args; // First handler gets original args
    }
    return [...args, lastReturn]; // Subsequent handlers get the result from previous
  },
});

// Example usage
const request: Request = {
  path: '/api/users',
  method: 'GET',
  headers: { Authorization: 'Bearer token123' },
  metadata: {},
};

const result = requestHandler(request);

console.log('Final result:', result.value);
console.log('Skipped handlers:', result.skipped); // e.g., [1] if rate limiting was skipped
console.log('Broke at:', result.breakAt); // -1 if completed successfully

// Example output:
// 🔐 Authenticating request...
// ⏱️ Checking rate limits...
// ✅ Validating input...
// 🔄 Processing business logic...
// 📤 Formatting response...
// Final result: { ... processed request with all metadata ... }
// Skipped handlers: []
// Broke at: -1

🔧 Advanced Features

Conditional Execution

Control the flow of your task execution with powerful conditions:

const conditionalTask = createSerialTask({
  tasks: [taskA, taskB, taskC, taskD],

  // Skip tasks based on conditions
  skipCondition: (task, index, tasks, args, lastReturn) => {
    // Skip taskB if input is negative
    if (index === 1 && args[0] < 0) return true;
    return false;
  },

  // Break early if result exceeds threshold
  breakCondition: (task, index, tasks, args, lastReturn) => {
    return lastReturn > 100;
  },
});

Dynamic Task Arrays

Modify the task array during execution:

const dynamicTask = createSerialTask({
  tasks: [initialTask],
  resultWrapper: (task, index, taskArray, args, lastReturn) => {
    // Add more tasks dynamically
    if (index === 0 && someCondition) {
      taskArray.push(additionalTask);
    }
    return index === 0 ? args : [lastReturn];
  },
});

🔄 Async Support

For async functions, use createSerialTaskAsync:

import { createSerialTaskAsync } from 'serial-task';

const asyncTask = createSerialTaskAsync({
  tasks: [
    async (data) => await fetchUserData(data),
    async (user) => await validateUser(user),
    async (user) => await saveUser(user),
  ],
});

const result = await asyncTask(inputData);

🎪 Error Handling

Tasks can throw errors, which will propagate up and stop execution:

const taskWithErrors = createSerialTask({
  tasks: [
    (x) => x + 1,
    (x) => {
      if (x > 10) throw new Error('Value too large!');
      return x * 2;
    },
    (x) => x - 1,
  ],
});

try {
  const result = taskWithErrors(15);
} catch (error) {
  console.error('Task failed:', error.message);
}

📄 License

MIT