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

promise-for-wrapper

v3.2.7

Published

A wrapper for handling promises with ease.

Readme

Promise For Wrapper

A powerful TypeScript utility library that revolutionizes asynchronous JavaScript by providing robust error handling, elegant data transformation pipelines, and predictable control flow for complex asynchronous operations.

Why Promise For Wrapper?

Working with asynchronous operations in JavaScript often leads to:

  • Deeply nested try/catch blocks
  • Inconsistent error handling patterns
  • Difficult-to-follow transformation chains
  • Unpredictable null/undefined behaviors
  • Hard-to-debug promise chains

Promise For Wrapper solves these problems with a declarative, functional approach that makes async code more:

  • Readable: Clear, linear flow of operations
  • Maintainable: Modular, testable transformation functions
  • Robust: Consistent error handling throughout
  • Predictable: Standardized [result, error] tuple pattern
  • Type-safe: Full TypeScript support with accurate type inference

Core Utilities

The library provides two main utilities:

  • promiseFor: Single-step promise resolution with built-in error handling
  • pipeFor: Multi-step transformation pipeline with automatic error propagation

Traditional vs Promise For Approach

| Feature | Traditional Promises | Promise For Wrapper | |---------|---------------------|---------------------| | Error Handling | Try/catch blocks | Automatic with [result, error] tuples | | Null Checking | Manual checks throughout code | Automatic in transformations | | Composition | Nested .then() chains | Linear transform/pipe chains | | Error Context | Lost in catch blocks | Preserved with detailed context | | HTTP Errors | Manual response.ok checks | Automatic detection and handling | | Type Safety | Limited inference in chains | Full type inference throughout | | Debugging | Complex stack traces | Clear error contexts with step info |

Installation

npm install promise-for-wrapper

Error Handling Philosophy

Traditional promise error handling relies on try/catch blocks that often lose context and make error propagation difficult. Promise For Wrapper takes a fundamentally different approach:

  1. Explicit Error Returns: All operations return a tuple of [result, error] instead of throwing exceptions
  2. Rich Error Context: Errors maintain their original information plus additional context
  3. Automatic Propagation: Errors automatically flow through pipelines without manual handling
  4. Type Safety: TypeScript ensures you handle both success and error cases

Error Context Structure

All operations use a standardized error context that preserves and enhances error information:

interface ErrorContext {
    message: string;        // Human-readable error message
    name: string;           // Error type name (e.g., "HTTPError", "TypeError")
    stack?: string | null;  // Original stack trace when available
    status?: number | null; // HTTP status code for network requests
    url?: string | null;    // URL for network requests
    method?: string | null; // HTTP method for network requests
    code?: string | null;   // Error code (e.g., "ECONNREFUSED")
    context: string;        // Description of the operation that failed
    responseData:any //contains the original response data
    stepInfo?: {            // For pipeline operations
        index: number;      // Position in the pipeline
        type: 'initialization' | 'transform' | 'pipe'; // Step type
        pipelineContext?: string; // Pipeline segment context
    };
}

Special Error Handling

The library includes specialized error handling for common scenarios:

  1. HTTP Errors: Automatically detects and normalizes HTTP errors from fetch responses
  2. Null/Undefined: Automatically handles null/undefined values in transformations
  3. Throwable Errors: Provides PipelineError and PromiseForError classes for when you need to throw

promiseFor: Single-Step Operations

promiseFor is the foundation of the library, providing a clean way to handle a promise and its potential errors without try/catch blocks.

API Overview

// Modern API (recommended)
const [result, error] = await promiseFor(
    promiseOrFunction,
    {
        postProcessor?: (value: T) => R | Promise<R>,
        context?: string
    }
);

// Legacy API (still supported)
const [result, error] = await promiseFor(
    promiseOrFunction,
    postProcessor,
    context
);

Key Features

  • Flexible Input: Accepts either a Promise or a function that returns a Promise
  • Post-Processing: Optional transformation of the resolved value
  • Error Context: Custom context string for better error messages
  • HTTP Detection: Automatic handling of HTTP error responses
  • Type Safety: Full TypeScript support with accurate type inference

Basic Example

// Using promiseFor with a direct promise
const [data, error] = await promiseFor(fetch('/api/users'));

if (error) {
    console.error('Failed to fetch users:', error.message);
    // Access additional error information
    if (error.status === 404) {
        return { users: [] };
    }
    return null;
}

// Process the successful result
console.log(`Fetched ${data.length} users`);

With Post-Processing

// Helper function for JSON parsing
const parseJson = async (response) => await response.json();

// Using promiseFor with post-processing (modern API)
const [userData, error] = await promiseFor(
    fetch('/api/users'),
    {
        postProcessor: parseJson,
        context: 'Fetching user data'
    }
);

// Alternative with legacy API
const [userData, error] = await promiseFor(
    fetch('/api/users'),
    parseJson,
    'Fetching user data'
);

if (error) {
    console.error(`${error.context} failed:`, error.message);
    return null;
}

return userData;

Function-Based Input

// Function that returns a promise
const fetchUserData = (userId) => fetch(`/api/users/${userId}`);

// Using promiseFor with a function
const [response, error] = await promiseFor(
    () => fetchUserData(123),
    {
        context: 'User data retrieval'
    }
);

if (error) {
    console.error('User data retrieval failed:', error);
    return null;
}

// Process the response
const userData = await response.json();

pipeFor: Multi-Step Operations

pipeFor is the powerful pipeline builder that enables clean, functional transformation chains for complex asynchronous operations.

API Overview

// Create a pipeline
const pipeline = pipeFor(promiseOrFunction, context?);

// Add transformation steps
pipeline.transform(transformFn, stepContext?);

// Add new promise-based operations
pipeline.pipe(promiseFn, stepContext?);

// Execute the pipeline
const [result, error] = await pipeline.execute();

Key Concepts

  1. transform(fn, context?):

    • Purpose: Transform data without creating new promises
    • Input: Takes exactly one argument (previous step's result)
    • Output: Returns transformed data (synchronously or asynchronously)
    • Null Safety: Automatically handles null/undefined values
    • Context: Optional context string for error reporting
  2. pipe(fn, context?):

    • Purpose: Introduce new promise-based operations
    • Input: Takes previous result as input
    • Output: Must return a Promise
    • Use Case: For making new API calls or other async operations
    • Context: Optional context string for error reporting
  3. execute():

    • Purpose: Run the pipeline and get the final result
    • Required: Must be called as the final step
    • Returns: [result, error] tuple
    • Behavior: Stops at the first error encountered

Pipeline Flow Visualization

Initial Promise → transform → transform → pipe → transform → [result, error]
                    ↓           ↓        ↓        ↓
                 Step 1       Step 2   Step 3   Step 4
                    ↓           ↓        ↓        ↓
                Data transform → New Promise → Final transform

Practical Example: Chained API Calls

This example demonstrates how pipeFor elegantly handles a complex workflow involving multiple API calls, data transformations, and error handling:

// Helper functions - small, focused, and reusable
const parseJson = (response: Response) => response.json();
const extractUserId = (todo: any) => todo.userId;
const fetchUserById = (userId: string) => fetch(`/api/users/${userId}`);
const extractUserProfile = (user: any) => user.profile;
const formatUserData = (profile: any) => ({
    name: profile.name,
    email: profile.email,
    lastUpdated: new Date().toISOString()
});

// Using pipeFor with clean, descriptive transformations
const [userData, error] = await pipeFor(fetch('/api/todos/1'), 'Todo retrieval')
    // Data extraction group
    .transform(parseJson, 'Parse todo JSON')                  
    .transform(extractUserId, 'Extract user ID')              

    // User data retrieval group
    .pipe(fetchUserById, 'Fetch user data')                   
    .transform(parseJson, 'Parse user JSON')                  
    .transform(extractUserProfile, 'Extract user profile')    

    // Final formatting
    .transform(formatUserData, 'Format user data')            
    .execute();                                              

if (error) {
    console.error(`Error in ${error.context}:`, error.message);

    // Detailed error handling based on step and status
    if (error.stepInfo?.type === 'pipe' && error.status === 404) {
        return { error: 'User not found', code: 'USER_NOT_FOUND' };
    }

    return { error: 'Failed to process user data', code: 'PROCESSING_ERROR' };
}

return { success: true, data: userData };

Automatic Null Checking Example

One of the most powerful features of pipeFor is automatic null/undefined handling, which eliminates a common source of runtime errors:

interface ApiData {
    items?: {
        results?: {
            mainData?: string;
        }[];
    };
}

// Helper functions
const parseJson = (response: Response): Promise<ApiData> => response.json();

// These would normally require extensive null checking
const extractItems = (data: ApiData) => data.items?.results;
const getFirstItem = (items: any[]) => items[0];
const getMainData = (item: any) => item.mainData;

// pipeFor handles all null checks automatically
const [result, error] = await pipeFor(fetch('/api/deeply/nested/data'))
    .transform(parseJson, 'Parse API response')
    .transform(extractItems, 'Extract results array')  // No explicit null check needed
    .transform(getFirstItem, 'Get first result')       // Safely handles empty arrays
    .transform(getMainData, 'Extract main data')       // Safely handles undefined properties
    .execute();

if (error) {
    console.error('Data extraction failed:', error);
    // Error will include which exact transform failed and why
    return null;
}

return result; // Only contains the value if all steps succeeded

Real-World Example: User Authentication Flow

This example shows how pipeFor can handle a complex authentication flow with validation, API calls, and data transformation:

// Helper functions
const parseJson = (res: Response) => res.json();
const validateCredentials = (credentials: any) => {
    if (!credentials.username || !credentials.password) {
        throw new Error('Invalid credentials');
    }
    return credentials;
};

const authenticateUser = async (credentials: any) => {
    const response = await fetch('/api/auth/login', {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify(credentials)
    });
    return response;
};

const extractAuthToken = (data: any) => data.token;
const fetchUserProfile = (token: string) => 
    fetch('/api/users/profile', {
        headers: { 'Authorization': `Bearer ${token}` }
    });

// Complete authentication flow
const [session, error] = await pipeFor(userInput)
    // Validation group
    .transform(validateCredentials, 'Validate user input')

    // Authentication group
    .pipe(authenticateUser, 'Authenticate user')
    .transform(parseJson, 'Parse authentication response')
    .transform(extractAuthToken, 'Extract auth token')

    // Profile retrieval group
    .pipe(fetchUserProfile, 'Fetch user profile')
    .transform(parseJson, 'Parse profile data')

    // Session creation
    .transform(profile => ({ 
        user: profile,
        authenticated: true,
        loginTime: new Date().toISOString()
    }), 'Create user session')
    .execute();

if (error) {
    // Detailed error handling based on which step failed
    if (error.context === 'Validate user input') {
        return { error: 'Please provide valid credentials' };
    }

    if (error.status === 401) {
        return { error: 'Invalid username or password' };
    }

    return { error: 'Authentication failed', details: error.message };
}

return { success: true, session };

Comparative Examples: Traditional vs Promise For Approach

This section compares traditional promise handling with the Promise For Wrapper approach across different scenarios.

Scenario 1: Error Handling in API Calls

Traditional Approach

async function fetchUserData(userId: string) {
    try {
        const response = await fetch(`/api/users/${userId}`);

        if (!response.ok) {
            throw new Error(`HTTP error! status: ${response.status}`);
        }

        const data = await response.json();
        return data;
    } catch (error) {
        console.error("Failed to fetch user:", error);
        // Error information is limited and context is lost
        return null;
    }
}

Promise For Approach

async function fetchUserData(userId: string) {
    const [data, error] = await promiseFor(
        fetch(`/api/users/${userId}`),
        { 
            postProcessor: res => res.json(),
            context: 'User data retrieval'
        }
    );

    if (error) {
        // Rich error information with context
        console.error(`${error.context} failed:`, error.message);

        // Detailed error handling based on status
        if (error.status === 404) {
            return { error: 'User not found' };
        }

        return { error: 'Service unavailable', details: error };
    }

    return { data };
}

Scenario 2: Complex Data Processing Chain

Traditional Approach

async function processOrderData(orderId: string) {
    try {
        // Fetch order details
        const orderResponse = await fetch(`/api/orders/${orderId}`);
        if (!orderResponse.ok) throw new Error(`HTTP error! status: ${orderResponse.status}`);
        const order = await orderResponse.json();

        // Fetch customer details
        const customerResponse = await fetch(`/api/customers/${order.customerId}`);
        if (!customerResponse.ok) throw new Error(`HTTP error! status: ${customerResponse.status}`);
        const customer = await customerResponse.json();

        // Calculate tax based on customer location
        const taxResponse = await fetch(`/api/tax-rates?region=${customer.region}`);
        if (!taxResponse.ok) throw new Error(`HTTP error! status: ${taxResponse.status}`);
        const taxData = await taxResponse.json();

        // Apply tax calculation
        const taxRate = taxData.rate || 0;
        const totalWithTax = order.total * (1 + taxRate);

        return {
            order,
            customer,
            tax: { rate: taxRate, amount: order.total * taxRate },
            totalWithTax
        };
    } catch (error) {
        console.error("Order processing failed:", error);
        // Hard to determine which step failed
        return null;
    }
}

Promise For Approach

// Clean, focused helper functions
const parseJson = (res: Response) => res.json();
const fetchCustomer = (order: any) => fetch(`/api/customers/${order.customerId}`);
const fetchTaxRate = (customer: any) => fetch(`/api/tax-rates?region=${customer.region}`);
const calculateTax = (data: { order: any, customer: any, taxData: any }) => {
    const taxRate = data.taxData.rate || 0;
    return {
        order: data.order,
        customer: data.customer,
        tax: { 
            rate: taxRate, 
            amount: data.order.total * taxRate 
        },
        totalWithTax: data.order.total * (1 + taxRate)
    };
};

async function processOrderData(orderId: string) {
    const [result, error] = await pipeFor(fetch(`/api/orders/${orderId}`), 'Order processing')
        // Order data retrieval
        .transform(parseJson, 'Parse order data')

        // Customer data retrieval
        .pipe(fetchCustomer, 'Fetch customer data')
        .transform(parseJson, 'Parse customer data')
        .transform(customer => ({ order, customer }), 'Combine order and customer')

        // Tax calculation
        .pipe(data => fetchTaxRate(data.customer), 'Fetch tax rate')
        .transform(parseJson, 'Parse tax data')
        .transform(taxData => ({ order, customer, taxData }), 'Prepare tax calculation')
        .transform(calculateTax, 'Calculate final price with tax')
        .execute();

    if (error) {
        // Precise error information with context
        console.error(`Error in ${error.context}:`, error.message);

        // Can determine exactly which step failed
        if (error.context === 'Fetch customer data' && error.status === 404) {
            return { error: 'Customer not found', code: 'CUSTOMER_NOT_FOUND' };
        }

        return { error: 'Order processing failed', details: error };
    }

    return { success: true, data: result };
}

Scenario 3: Handling Conditional Logic

Traditional Approach

async function processPayment(paymentData) {
    try {
        // Validate payment data
        if (!paymentData.amount || paymentData.amount <= 0) {
            throw new Error('Invalid payment amount');
        }

        // Process based on payment method
        let paymentResult;
        if (paymentData.method === 'credit') {
            const response = await fetch('/api/payments/credit', {
                method: 'POST',
                body: JSON.stringify(paymentData)
            });
            if (!response.ok) throw new Error(`Payment failed: ${response.status}`);
            paymentResult = await response.json();
        } else if (paymentData.method === 'paypal') {
            const response = await fetch('/api/payments/paypal', {
                method: 'POST',
                body: JSON.stringify(paymentData)
            });
            if (!response.ok) throw new Error(`Payment failed: ${response.status}`);
            paymentResult = await response.json();
        } else {
            throw new Error('Unsupported payment method');
        }

        // Record transaction
        const transactionResponse = await fetch('/api/transactions', {
            method: 'POST',
            body: JSON.stringify({
                paymentId: paymentResult.id,
                amount: paymentData.amount,
                status: paymentResult.status
            })
        });
        if (!transactionResponse.ok) throw new Error('Failed to record transaction');

        return { success: true, transaction: await transactionResponse.json() };
    } catch (error) {
        console.error('Payment processing error:', error);
        return { success: false, error: error.message };
    }
}

Promise For Approach

// Helper functions
const validatePayment = (data: any) => {
    if (!data.amount || data.amount <= 0) {
        throw new Error('Invalid payment amount');
    }
    return data;
};

const processPaymentMethod = (data: any) => {
    // Determine the correct payment processor based on method
    const endpoint = data.method === 'credit' 
        ? '/api/payments/credit' 
        : data.method === 'paypal'
            ? '/api/payments/paypal'
            : null;

    if (!endpoint) {
        throw new Error('Unsupported payment method');
    }

    return fetch(endpoint, {
        method: 'POST',
        body: JSON.stringify(data)
    });
};

const recordTransaction = (paymentResult: any) => {
    return fetch('/api/transactions', {
        method: 'POST',
        body: JSON.stringify({
            paymentId: paymentResult.id,
            amount: paymentResult.amount,
            status: paymentResult.status
        })
    });
};

async function processPayment(paymentData: any) {
    const [result, error] = await pipeFor(Promise.resolve(paymentData), 'Payment processing')
        // Validation
        .transform(validatePayment, 'Validate payment data')

        // Payment processing
        .pipe(processPaymentMethod, 'Process payment method')
        .transform(response => response.json(), 'Parse payment response')

        // Transaction recording
        .pipe(recordTransaction, 'Record transaction')
        .transform(response => response.json(), 'Parse transaction response')
        .transform(transaction => ({ 
            success: true, 
            transaction,
            timestamp: new Date().toISOString()
        }), 'Format final result')
        .execute();

    if (error) {
        // Detailed error handling with context
        console.error(`Payment error in ${error.context}:`, error.message);

        // User-friendly error messages based on context
        if (error.context === 'Validate payment data') {
            return { success: false, error: 'Please provide a valid payment amount' };
        }

        if (error.context === 'Process payment method') {
            if (error.message.includes('Unsupported')) {
                return { success: false, error: 'Please select a supported payment method' };
            }
            return { success: false, error: 'Payment processing failed' };
        }

        return { success: false, error: 'Transaction could not be completed' };
    }

    return result;
}

API Documentation

promiseFor

function promiseFor<T, R = T>(
    promiseOrFunction: (() => Promise<T>) | Promise<T>,
    optionsOrPostProcessor?: PromiseForOptions<T, R> | PostProcessor<T, R>,
    legacyContext?: string
): Promise<[R | null, ErrorContext | null]>

interface PromiseForOptions<T, R> {
    postProcessor?: (value: T) => R | Promise<R>;
    context?: string;
}

Parameters:

  • promiseOrFunction: A Promise or a function that returns a Promise
  • optionsOrPostProcessor: Either an options object or a post-processor function
  • legacyContext: Optional context string (legacy API)

Returns:

  • Promise resolving to a tuple: [result, error]

Example:

const [data, error] = await promiseFor(fetch('/api/data'), {
    postProcessor: res => res.json(),
    context: 'Data fetching'
});

pipeFor

function pipeFor<T>(
    initial: Promise<T> | (() => Promise<T>), 
    context?: string
): Pipeline<T>

Parameters:

  • initial: A Promise or a function that returns a Promise
  • context: Optional context string for the pipeline

Returns:

  • A Pipeline instance with transform, pipe, and execute methods

Example:

const pipeline = pipeFor(fetch('/api/data'), 'Data pipeline');
const [result, error] = await pipeline.execute();

Pipeline Methods

transform

transform<R>(
    fn: (input: T) => R | Promise<R>, 
    stepContext?: string
): Pipeline<R>
  • fn: Function to transform the input data
  • stepContext: Optional context string for this step
  • Returns: A new Pipeline instance with the transformed type

pipe

pipe<R>(
    fn: (input: NonNullable<T>) => Promise<R>, 
    stepContext?: string
): Pipeline<R>
  • fn: Function that takes the previous result and returns a new Promise
  • stepContext: Optional context string for this step
  • Returns: A new Pipeline instance with the new Promise's type

execute

execute(): Promise<[T | null, ErrorContext | null]>
  • Returns: Promise resolving to a tuple: [result, error]

Error Types

ErrorContext

interface ErrorContext {
    message: string;
    name: string;
    stack?: string | null;
    status?: number | null;
    url?: string | null;
    method?: string | null;
    code?: string | null;
    context: string;
    stepInfo?: {
        index: number;
        type: 'initialization' | 'transform' | 'pipe';
        pipelineContext?: string;
    };
}

HTTPError

class HTTPError extends Error {
    status: number;
    url: string;
}

PipelineError & PromiseForError

class PipelineError extends Error {
    errorContext: ErrorContext;
    statusCode: number;
}

class PromiseForError extends Error {
    errorContext: ErrorContext;
    statusCode: number;
}

Best Practices

1. Design for Composability

// Create reusable transformation functions
const parseJson = (res: Response) => res.json();
const extractItems = (data: any) => data.items;
const filterActive = (items: any[]) => items.filter(item => item.active);
const sortByDate = (items: any[]) => [...items].sort((a, b) => 
    new Date(b.date).getTime() - new Date(a.date).getTime()
);

// Compose them in different ways as needed
const getActiveItems = async (url: string) => {
    return pipeFor(fetch(url))
        .transform(parseJson)
        .transform(extractItems)
        .transform(filterActive)
        .execute();
};

const getRecentActiveItems = async (url: string) => {
    return pipeFor(fetch(url))
        .transform(parseJson)
        .transform(extractItems)
        .transform(filterActive)
        .transform(sortByDate)
        .execute();
};

2. Use Descriptive Context Strings

// Add context to both pipeline and individual steps
const [result, error] = await pipeFor(fetch('/api/users'), 'User data pipeline')
    .transform(parseJson, 'Parse user JSON')
    .transform(validateUserData, 'Validate user data')
    .transform(formatUserProfile, 'Format user profile')
    .execute();

// Makes error messages much more helpful
if (error) {
    console.error(`Error in ${error.context}: ${error.message}`);
    // Example: "Error in Validate user data: Missing required field: email"
}

3. Group Related Operations with Comments

const [result, error] = await pipeFor(fetch('/api/orders/123'))
    // Data retrieval and parsing
    .transform(parseJson, 'Parse order data')
    .transform(validateOrderData, 'Validate order data')

    // Payment processing
    .pipe(order => processPayment(order.payment), 'Process payment')
    .transform(validatePaymentResult, 'Validate payment result')

    // Inventory management
    .pipe(data => updateInventory(data.order.items), 'Update inventory')

    // Notification
    .pipe(data => sendOrderConfirmation(data), 'Send confirmation')
    .execute();

4. Keep Transforms Focused and Simple

// Don't do this:
.transform(data => {
    // Complex logic with multiple responsibilities
    const processed = complexOperation(data);
    const validated = validateData(processed);
    return formatOutput(validated).someField;
})

// Do this instead:
.transform(complexOperation, 'Process data')
.transform(validateData, 'Validate processed data')
.transform(formatOutput, 'Format for output')
.transform(result => result.someField, 'Extract required field')

5. Leverage TypeScript for Better Type Safety

interface User {
    id: string;
    name: string;
    email: string;
}

interface Order {
    id: string;
    items: OrderItem[];
    total: number;
}

// Type-safe transformations
const fetchUser = (userId: string) => fetch(`/api/users/${userId}`);
const parseUser = (res: Response): Promise<User> => res.json();
const fetchUserOrders = (user: User) => fetch(`/api/users/${user.id}/orders`);
const parseOrders = (res: Response): Promise<Order[]> => res.json();

// Pipeline with proper typing
const [orders, error] = await pipeFor(fetchUser('123'))
    .transform(parseUser)
    .pipe(fetchUserOrders)
    .transform(parseOrders)
    .execute();

// TypeScript knows that orders is Order[] | null

License

MIT License