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

fncore

v0.1.11

Published

A modern functional utility library with high performance and strong type safety

Readme

FNcore MIT License NPM badge

한국어 | English

A modern functional library with high performance and strong type safety

Features

  • High Performance: Optimized execution through lazy evaluation
  • Type Safety: Written in TypeScript with perfect type inference
  • Async Processing: Efficient parallel processing with concurrent and concurrentPool
  • Functional Programming: Declarative and readable code with pipe
  • Tree Shaking: Minimal bundle size with ESM support

Installation

npm install fncore

API

Composition

  • pipe - Function pipeline composition
  • curry - Function currying
  • identity - Identity function

Iterable

  • map, filter, reduce - Basic transformation functions
  • flatMap, flat - Flattening
  • take, takeWhile, takeUntilInclusive - Conditional extraction
  • chunk, slice - Splitting
  • concat, zip - Combining
  • compact, filterNonNil - Remove null/undefined
  • uniq, uniqBy - Remove duplicates
  • difference, differenceBy - Set difference
  • intersection, intersectionBy - Set intersection
  • partition - Conditional separation
  • sort, sortBy - Sorting
  • reverse - Reverse order
  • head, last - First/last element
  • pluck - Property extraction
  • range - Generate number range
  • cycle - Infinite repetition
  • scan - Cumulative transformation
  • some, every - Condition checking
  • each - Iteration

Async

  • concurrent - Unlimited parallel processing
  • concurrentPool - Limited concurrency control
  • toAsync - Sync → Async conversion

Object

  • entries - Object entries conversion
  • groupBy - Grouping
  • indexBy - Indexing
  • pickBy, omitBy - Conditional select/exclude
  • compactObject - Remove falsy values
  • prop - Property access

Predicate

  • isString, isNumber, isBoolean - Basic type checks
  • isArray, isObject - Complex type checks
  • isNil, isUndefined, isEmpty - null/undefined/empty checks
  • negate - Condition negation
  • unless - Conditional execution

Number

  • clamp - Range limiting

Promise

  • delay - Delayed execution
  • retry - Retry logic
  • withTimeout - Timeout setting

Side Effect

  • tap - Execute side effects while preserving value
  • peek - Side effects during iteration
  • noop - No-op function

Error

  • throwError - Throw error
  • throwIf - Conditional error throwing

Conversion

  • toArray - Convert iterable to array
  • toAsync - Convert sync iterable to async

Examples

1. Basic Data Transformation

import { pipe, map, filter, take, toArray } from 'fncore';

// Simple pipeline: transform → filter → limit
const result = pipe(
  [1, 2, 3, 4, 5, 6, 7, 8, 9, 10],
  map((x) => x * 2), // Double each number
  filter((x) => x > 10), // Keep only numbers > 10
  take(3), // Take first 3 results
  toArray
);
// Result: [12, 14, 16]

2. Parallel API Calls with Retry

import { pipe, toAsync, map, concurrent, retry, toArray } from 'fncore';

// Pure fetch function
const fetchUser = async (id: number) => {
  const res = await fetch(`/api/users/${id}`);
  if (!res.ok) throw new Error('Failed');
  return res.json();
};

// Fetch multiple users in parallel with retry logic
async function fetchUsers(userIds: number[]) {
  return pipe(
    userIds,
    toAsync,
    // Apply retry to each fetch operation
    map((id) => retry(() => fetchUser(id), { retries: 3, delay: 1000 })),
    // Execute 3 requests in parallel
    concurrent(3),
    toArray
  );
}

3. Data Aggregation and Grouping

import { pipe, groupBy, entries, map, sortBy, filter, toArray } from 'fncore';

interface Order {
  userId: string;
  amount: number;
  status: 'pending' | 'completed' | 'cancelled';
}

// Calculate total amount per user from completed orders
function calculateUserTotals(orders: Order[]) {
  return pipe(
    orders,
    // Filter completed orders only
    filter((order) => order.status === 'completed'),
    // Group by user ID
    groupBy((order) => order.userId),
    // Convert to entries for processing
    entries,
    // Calculate total and count for each user
    map(([userId, userOrders]) => ({
      userId,
      total: userOrders.reduce((sum, order) => sum + order.amount, 0),
      count: userOrders.length,
    })),
    // Sort by total amount (descending)
    sortBy((user) => -user.total),
    toArray
  );
}

4. Large-scale Batch Processing

import { pipe, chunk, toAsync, map, concurrent, flat, toArray } from 'fncore';

// Process large dataset in batches to avoid memory issues
async function processBatchData(items: string[]) {
  return pipe(
    items,
    // Split into chunks of 1000 items
    chunk(1000),
    toAsync,
    // Send each batch to API
    map(async (batch) => {
      const response = await fetch('/api/batch', {
        method: 'POST',
        body: JSON.stringify(batch),
      });
      return response.json();
    }),
    // Process 2 batches in parallel
    concurrent(2),
    // Flatten results from all batches
    flat,
    toArray
  );
}

5. Shopping Cart with Running Total

import { pipe, scan, toArray } from 'fncore';

interface CartItem {
  name: string;
  price: number;
}

// Calculate running total as items are added to cart
const cartItems: CartItem[] = [
  { name: 'Laptop', price: 1000 },
  { name: 'Mouse', price: 50 },
  { name: 'Keyboard', price: 100 },
  { name: 'Monitor', price: 300 },
];

// Using with seed value
const runningTotals = toArray(
  scan((total, item) => total + item.price, 0, cartItems)
);
// Result: [0, 1000, 1050, 1150, 1450]

// Without seed - uses first item as initial value
const totals = pipe(
  [1, 2, 3, 4],
  scan((acc, cur) => acc * cur),
  toArray
);
// Result: [1, 2, 6, 24]

// Or use iterator directly
const iter = scan((total, item) => total + item.price, 0, cartItems);
iter.next(); // {value: 0, done: false}
iter.next(); // {value: 1000, done: false}
iter.next(); // {value: 1050, done: false}

5-1. Real-time Notification Counter

import { pipe, scan, toAsync, toArray } from 'fncore';

interface Notification {
  type: 'message' | 'like' | 'comment';
  count: number;
}

// Real-time notification stream
async function* notificationStream() {
  // Simulating real-time notifications
  yield { type: 'message' as const, count: 1 };
  yield { type: 'like' as const, count: 3 };
  yield { type: 'comment' as const, count: 2 };
  yield { type: 'message' as const, count: 1 };
}

// Track cumulative notification count (with seed)
const totalNotifications = scan(
  (total, notification) => total + notification.count,
  0,
  notificationStream()
);

for await (const total of totalNotifications) {
  updateBadge(total); // Update UI badge with total count
  // Yields: 0, 1, 4, 6, 7
}

// Or use pipe without seed (first item becomes initial value)
const counts = [1, 3, 2, 1];

const totals = await pipe(
  counts,
  toAsync,
  scan((total, count) => total + count),
  toArray
);
// Result: [1, 4, 6, 7]

6. Multi-step Data Pipeline with Retry

import { pipe, retry, toAsync, map, filter, toArray } from 'fncore';

// Pure fetch functions - no retry logic inside
const fetchOrder = async (id: number) => {
  const res = await fetch(`/api/orders/${id}`);
  if (!res.ok) throw new Error('Failed');
  return res.json();
};

const fetchOrderDetails = async (order: Order) => {
  const res = await fetch(`/api/orders/${order.id}/details`);
  if (!res.ok) throw new Error('Failed');
  const details = await res.json();
  return { ...order, details };
};

// Multi-step pipeline: fetch orders → filter completed → fetch details
async function processOrders(orderIds: number[]) {
  return pipe(
    orderIds,
    toAsync,
    // Step 1: Fetch all orders with retry
    map((id) => retry(() => fetchOrder(id), { retries: 3, delay: 1000 })),
    // Step 2: Filter only completed orders
    filter((order) => order.status === 'completed'),
    // Step 3: Fetch details for each completed order with retry
    map((order) =>
      retry(() => fetchOrderDetails(order), { retries: 3, delay: 1000 })
    ),
    toArray
  );
}

7. Rate-limited API Calls with ConcurrentPool

import { pipe, toAsync, map, concurrentPool, retry, toArray } from 'fncore';

// API with rate limit (e.g., max 5 requests per second)
const fetchUserProfile = async (id: number) => {
  const res = await fetch(`/api/users/${id}/profile`);
  if (!res.ok) throw new Error('Failed');
  return res.json();
};

// Process 1000 users while respecting rate limits
async function fetchAllUserProfiles(userIds: number[]) {
  return pipe(
    userIds,
    toAsync,
    // Apply retry to each request
    map((id) => retry(() => fetchUserProfile(id), { retries: 3, delay: 1000 })),
    // Limit to 5 concurrent requests to respect rate limits
    concurrentPool(5),
    toArray
  );
}

// Example: Processing 1000 users
// - Without concurrentPool: All 1000 requests at once → Rate limit error
// - With concurrent(5): 5 at a time, but no control over timing
// - With concurrentPool(5): Exactly 5 concurrent requests, respects API limits

8. Validation and Error Handling

import {
  pipe,
  map,
  filter,
  throwIf,
  unless,
  negate,
  isEmpty,
  toArray,
} from 'fncore';

interface UserInput {
  email: string;
  age: number;
  name: string;
}

// Validate and process user inputs with clear error handling
function processUserInputs(inputs: UserInput[]) {
  return pipe(
    inputs,
    // Validate: throw error if email is empty
    map((user) =>
      throwIf(
        (u) => isEmpty(u.email),
        () => new Error('Email is required')
      )(user)
    ),
    // Validate: throw error if age is invalid
    map((user) =>
      throwIf(
        (u) => u.age < 18 || u.age > 100,
        () => new Error('Invalid age')
      )(user)
    ),
    // Filter: keep only valid names (skip empty names instead of throwing)
    filter((user) => !isEmpty(user.name)),
    // Transform: normalize email
    map((user) => ({ ...user, email: user.email.toLowerCase().trim() })),
    toArray
  );
}

// Conditional execution with unless
const processAdult = unless(
  (user: UserInput) => user.age < 18,
  (user) => {
    console.log(`Processing adult user: ${user.name}`);
    return user;
  }
);

// Using negate for cleaner conditions
const isNotEmpty = negate(isEmpty);
const validEmails = pipe(
  ['[email protected]', '', '[email protected]', null, '[email protected]'],
  filter(isNotEmpty),
  toArray
);
// Result: ['[email protected]', '[email protected]', '[email protected]']

9. Complex Object Transformations

import {
  pipe,
  entries,
  map,
  filter,
  pickBy,
  omitBy,
  compactObject,
  toArray,
} from 'fncore';

interface Product {
  id: string;
  name: string;
  price: number;
  stock: number;
  discount?: number;
  tags?: string[];
}

// Clean and transform product data
function cleanProductData(products: Product[]) {
  return pipe(
    products,
    // Remove products with invalid data
    map((product) => compactObject(product)),
    // Keep only products with positive stock
    map(
      pickBy(
        ([key, value]) =>
          key !== 'stock' || (typeof value === 'number' && value > 0)
      )
    ),
    // Remove internal fields
    map(omitBy(([key]) => key.startsWith('_'))),
    toArray
  );
}

// Dynamic filtering based on object properties
const activeProducts = pipe(
  products,
  map((p) => entries(p)),
  map((entries) =>
    entries.filter(([key, value]) => value !== null && value !== undefined)
  ),
  map((entries) => Object.fromEntries(entries)),
  toArray
);

10. Curry for Reusable Functions

import { pipe, map, filter, curry, toArray } from 'fncore';

// Create reusable curried functions
const add = curry((a: number, b: number) => a + b);
const multiply = curry((a: number, b: number) => a * b);
const greaterThan = curry(
  (threshold: number, value: number) => value > threshold
);

// Use in pipelines
const add10 = add(10);
const multiplyBy2 = multiply(2);
const greaterThan50 = greaterThan(50);

const result = pipe(
  [1, 2, 3, 4, 5],
  map(add10), // [11, 12, 13, 14, 15]
  map(multiplyBy2), // [22, 24, 26, 28, 30]
  filter(greaterThan50), // []
  toArray
);

// Practical example: Price calculations
const applyTax = curry((rate: number, price: number) => price * (1 + rate));
const applyDiscount = curry(
  (rate: number, price: number) => price * (1 - rate)
);

const calculateFinalPrice = pipe(
  [100, 200, 300],
  map(applyDiscount(0.1)), // 10% discount
  map(applyTax(0.2)), // 20% tax
  toArray
);
// Result: [108, 216, 324]

11. Type-safe Null Filtering with FilterNonNil

import { pipe, map, filterNonNil, toArray } from 'fncore';

interface User {
  id: number;
  name: string;
  email: string | null;
  profileImage: string | null;
  lastLoginAt: Date | null;
}

// Database query returns nullable fields even with WHERE clause
async function getUsersWithEmail() {
  // DB returns User[] but TypeScript still sees email as string | null
  const users: User[] = await db.query(
    'SELECT * FROM users WHERE email IS NOT NULL'
  );

  return pipe(
    users,
    // filterNonNil('email') filters AND narrows the type
    filterNonNil('email'),
    // Now TypeScript knows email is string (not string | null)
    map((user) => user.email.toLowerCase()), // No type error
    toArray
  );
}

// Practical example: Processing orders with required fields
interface Order {
  orderId: number;
  userId: number | null;
  productId: number | null;
  shippingAddress: string | null;
  paymentMethod: string | null;
}

async function getValidOrders(orders: Order[]) {
  return pipe(
    orders,
    // Filter orders with userId (type narrows to number)
    filterNonNil('userId'),
    // Filter orders with productId (type narrows to number)
    filterNonNil('productId'),
    // Filter orders with shipping address (type narrows to string)
    filterNonNil('shippingAddress'),
    // Now TypeScript knows all fields are non-null
    map((order) => ({
      orderId: order.orderId,
      userId: order.userId, // Type: number (not number | null)
      productId: order.productId, // Type: number (not number | null)
      shippingAddress: order.shippingAddress, // Type: string (not string | null)
    })),
    toArray
  );
}

// Real-world example: User profiles with optional data
interface UserProfile {
  userId: number;
  displayName: string | null;
  bio: string | null;
  avatarUrl: string | null;
  verifiedAt: Date | null;
}

async function getVerifiedProfiles(profiles: UserProfile[]) {
  return pipe(
    profiles,
    // Only verified users
    filterNonNil('verifiedAt'),
    // Only users with display name
    filterNonNil('displayName'),
    // Only users with avatar
    filterNonNil('avatarUrl'),
    // All fields are now guaranteed non-null
    map((profile) => ({
      userId: profile.userId,
      displayName: profile.displayName.trim(), // ✅ Safe string operation
      avatarUrl: profile.avatarUrl.replace('http://', 'https://'), // ✅ Safe
      verifiedAt: profile.verifiedAt.toISOString(), // ✅ Safe Date operation
    })),
    toArray
  );
}

License

MIT © Wisely Company

Acknowledgments

This library is inspired by:

  • es-toolkit - MIT License
    Copyright (c) 2024 Viva Republica, Inc
  • fxts - Apache License 2.0
    Copyright FxTS contributors