Remeda is a TypeScript-first utility library with full type inference — every function is typed so the output type matches the transformation. pipe(data, fn1, fn2, fn3) composes data-first transformations while preserving types through each step. Functions are “purried”: they work data-first (map(array, fn)) or data-last (map(fn)(array)) for pipe composition. groupBy, sortBy, flatMap, uniq, zip, and chunk all infer output types. pick, omit, mapValues, mergeDeep, and mapKeys transform objects type-safely. isDefined, isString, isNonNullish, and isArray are type guard predicates usable as filter callbacks. createLazyInvocable enables lazy evaluation chains that only execute when consumed. Claude Code generates Remeda data transformation pipelines, groupBy+mapValues aggregations, type-safe filter chains, and object manipulation utilities for TypeScript applications.
CLAUDE.md for Remeda
## Remeda Stack
- Version: remeda >= 2.0
- Pipe: pipe(data, R.map(fn), R.filter(pred), R.sortBy([key, "asc"]))
- Group: R.groupBy(orders, o => o.status) — Record<Status, Order[]>
- Object: R.pick(obj, ["id", "name"]) / R.omit(obj, ["password"]) / R.mapValues(obj, fn)
- Guards: R.isDefined / R.isNonNullish / R.isString — use as filter(R.isDefined) predicates
- Sort: R.sortBy([key, "desc"], [key2, "asc"]) — multi-key sort
- Merge: R.mergeDeep(defaults, overrides) — deep merge preserving types
- Chunk: R.chunk(array, size) — split into batches
Data Transformation Pipelines
// lib/order-transforms.ts — Remeda data pipelines
import * as R from "remeda"
interface Order {
id: string
customerId: string
status: "pending" | "processing" | "shipped" | "delivered" | "cancelled"
totalCents: number
items: OrderItem[]
createdAt: Date
region: "us" | "eu" | "apac"
}
interface OrderItem {
productId: string
name: string
priceCents: number
quantity: number
}
// Group orders by status with type inference
export function groupOrdersByStatus(orders: Order[]) {
return R.groupBy(orders, o => o.status)
// Returns: Partial<Record<Order["status"], Order[]>>
}
// Aggregate revenue by region
export function revenueByRegion(orders: Order[]) {
return R.pipe(
orders,
R.filter(o => o.status !== "cancelled"),
R.groupBy(o => o.region),
R.mapValues(regionOrders =>
R.pipe(
regionOrders,
R.map(o => o.totalCents),
R.sum()
)
)
)
// Returns: Partial<Record<"us" | "eu" | "apac", number>>
}
// Top products by revenue across all orders
export function topProductsByRevenue(orders: Order[], limit = 10) {
return R.pipe(
orders,
R.flatMap(o => o.items),
R.groupBy(item => item.productId),
R.mapValues(items => ({
name: items[0]!.name,
totalRevenue: R.pipe(
items,
R.map(i => i.priceCents * i.quantity),
R.sum()
),
totalSold: R.sumBy(items, i => i.quantity),
})),
R.values(),
R.sortBy([v => v.totalRevenue, "desc"]),
R.take(limit)
)
}
// Orders summary with stats
export function ordersSummary(orders: Order[]) {
const activeOrders = R.filter(orders, o => o.status !== "cancelled")
return {
total: orders.length,
active: activeOrders.length,
cancelRate: orders.length > 0
? (orders.length - activeOrders.length) / orders.length
: 0,
avgOrderValue: activeOrders.length > 0
? R.sumBy(activeOrders, o => o.totalCents) / activeOrders.length
: 0,
byStatus: R.mapValues(
R.groupBy(orders, o => o.status),
group => group.length
),
}
}
Type Guard Predicates
// lib/data-utils.ts — type guards and filtering
import * as R from "remeda"
// Filter undefined from arrays — type narrows to T[]
export function compact<T>(arr: (T | undefined | null)[]): T[] {
return R.filter(arr, R.isNonNullish)
}
// Safe first/last with explicit undefined
export function safeHead<T>(arr: T[]): T | undefined {
return R.first(arr) // undefined if empty, typed T | undefined
}
// Parse and filter valid orders from API response
interface ApiOrder {
id?: string
status?: string
totalCents?: number
}
interface ValidOrder {
id: string
status: string
totalCents: number
}
export function parseOrders(raw: ApiOrder[]): ValidOrder[] {
return R.pipe(
raw,
R.filter((o): o is ValidOrder =>
R.isString(o.id) &&
R.isString(o.status) &&
R.isNumber(o.totalCents)
)
)
}
// Unique values with type inference
export function uniqueStatuses(orders: { status: string }[]): string[] {
return R.pipe(
orders,
R.map(o => o.status),
R.unique()
)
}
// Partition by predicate
export function partitionByStatus(orders: { status: string; id: string }[]) {
const [active, cancelled] = R.partition(
orders,
o => o.status !== "cancelled"
)
return { active, cancelled }
}
Object Utilities
// lib/object-utils.ts — pick, omit, mergeDeep
import * as R from "remeda"
interface UserProfile {
id: string
email: string
name: string
passwordHash: string
role: "admin" | "user"
preferences: {
theme: "light" | "dark"
notifications: boolean
emailFrequency: "daily" | "weekly" | "never"
}
createdAt: Date
}
// Safe public projection — omit sensitive fields
export function toPublicProfile(user: UserProfile) {
return R.omit(user, ["passwordHash", "createdAt"])
// Returns: Omit<UserProfile, "passwordHash" | "createdAt">
}
// Pick only needed fields for API response
export function toUserSummary(user: UserProfile) {
return R.pick(user, ["id", "name", "role"])
// Returns: Pick<UserProfile, "id" | "name" | "role">
}
// Deep merge defaults with user preferences
const DEFAULT_PREFERENCES: UserProfile["preferences"] = {
theme: "light",
notifications: true,
emailFrequency: "weekly",
}
export function applyPreferenceDefaults(
partial: Partial<UserProfile["preferences"]>
): UserProfile["preferences"] {
return R.mergeDeep(DEFAULT_PREFERENCES, partial)
}
// Transform object values
export function centsToDollars(prices: Record<string, number>): Record<string, number> {
return R.mapValues(prices, cents => cents / 100)
}
// Rename keys
export function toSnakeCase<T>(obj: Record<string, T>): Record<string, T> {
return R.mapKeys(obj, key =>
key.replace(/([A-Z])/g, "_$1").toLowerCase()
)
}
Sorting and Chunking
// lib/batch-utils.ts — sort, chunk, zip
import * as R from "remeda"
interface Product {
id: string
name: string
priceCents: number
stock: number
category: string
rating: number
}
// Multi-key sort: category asc, price desc
export function sortProducts(products: Product[]) {
return R.sortBy(
products,
[p => p.category, "asc"],
[p => p.priceCents, "desc"],
[p => p.rating, "desc"]
)
}
// Process in batches (for API rate limiting)
export async function processBatched<T, R>(
items: T[],
batchSize: number,
processor: (batch: T[]) => Promise<R[]>
): Promise<R[]> {
const batches = R.chunk(items, batchSize)
const results = await Promise.all(batches.map(processor))
return R.flat(results)
}
// Zip two arrays into tuples
export function zipWithIndex<T>(items: T[]): [T, number][] {
return R.zip(items, R.range(0, items.length))
}
// Find duplicates
export function findDuplicateIds(orders: { id: string }[]): string[] {
return R.pipe(
orders,
R.map(o => o.id),
R.groupBy(id => id),
R.entries(),
R.filter(([, group]) => group.length > 1),
R.map(([id]) => id)
)
}
// Rolling window aggregation
export function rollingAverage(values: number[], window: number): number[] {
return R.pipe(
R.range(window - 1, values.length),
R.map(i => {
const slice = values.slice(i - window + 1, i + 1)
return R.sum(slice) / window
})
)
}
For the Lodash alternative when utility functions for deep cloning (_.cloneDeep), debouncing (_.debounce), and template strings are needed alongside collection transforms — Lodash has a larger API but weaker TypeScript types than Remeda, see the utility library comparison. For the fp-ts alternative when algebraic data types (Option, Either, TaskEither) and full Haskell-style functional programming composition are needed beyond Remeda’s utilities — fp-ts adds monadic chaining but requires steeper learning curve, see the functional programming guide. The Claude Skills 360 bundle includes Remeda skill sets covering pipe composition, groupBy aggregations, and type-safe transformations. Start with the free tier to try Remeda pipeline generation.