Skip to main content

Higher-Order Functions

Transform and manipulate builder state with powerful functional programming utilities.

What Are Higher-Order Functions?

Higher-order functions are functions that either:

  1. Take functions as arguments, or
  2. Return functions as results

They're essential for functional programming and enable powerful composition patterns.

Filter

Keep only properties that match a condition.

Syntax

filterBuilder<T>(predicate: Predicate<T>): Setter<T>

Example

import { filterBuilder } from '@noony-serverless/type-builder';

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

// Keep only safe properties (exclude password)
const sanitizeUser = filterBuilder<User>((key) => key !== 'password');

const fullUser = {
id: 1,
name: 'Alice',
email: 'alice@example.com',
password: 'secret123',
};

const safeUser = userBuilder.build(sanitizeUser(fullUser));
// { id: 1, name: 'Alice', email: 'alice@example.com' }

Use Cases

1. Data Sanitization

// Remove sensitive data before sending to client
const removeSecrets = filterBuilder<User>(
(key) => !['password', 'ssn', 'creditCard'].includes(key as string)
);

2. Partial Updates

// Keep only changed fields
const changedOnly = filterBuilder<User>((key, value) => value !== undefined);

Map

Transform values while preserving structure.

Syntax

mapBuilder<T, U>(transformer: Transformer<T, U>): Setter<T>

Example

import { mapBuilder } from '@noony-serverless/type-builder';

// Double all numeric values
const doubleNumbers = mapBuilder<User, number>((key, value) => {
if (typeof value === 'number') {
return (value as number) * 2;
}
return value as number;
});

const user = { id: 1, age: 30, name: 'Alice' };
const transformed = userBuilder.build(doubleNumbers(user));
// { id: 2, age: 60, name: 'Alice' }

Use Cases

1. Data Normalization

// Lowercase all strings
const lowercaseStrings = mapBuilder<User, string>((key, value) => {
if (typeof value === 'string') {
return (value as string).toLowerCase();
}
return value as string;
});

2. Unit Conversion

// Convert prices from cents to dollars
const centsToDollars = mapBuilder<Product, number>((key, value) => {
if (key === 'price' && typeof value === 'number') {
return (value as number) / 100;
}
return value as number;
});

Fold (Reduce)

Reduce builder state to a single value.

Syntax

foldBuilder<T, R>(
reducer: Reducer<T, R>,
initial: R
): (state: BuilderState<T>) => R

Example

import { foldBuilder } from '@noony-serverless/type-builder';

// Count number of properties
const countFields = foldBuilder<User, number>(
(acc, key, value) => acc + 1,
0 // Initial value
);

const user = { id: 1, name: 'Alice', email: 'alice@example.com' };
const fieldCount = countFields(user); // 3

Use Cases

1. Statistics

// Sum all numeric values
const sumNumbers = foldBuilder<User, number>((acc, key, value) => {
if (typeof value === 'number') {
return acc + (value as number);
}
return acc;
}, 0);

2. Data Aggregation

// Collect all string values
const collectStrings = foldBuilder<User, string[]>((acc, key, value) => {
if (typeof value === 'string') {
return [...acc, value as string];
}
return acc;
}, []);

Pick

Select specific properties from state.

Syntax

pick<T>(keys: (keyof T)[]): Setter<T>

Example

import { pick } from '@noony-serverless/type-builder';

interface User {
id: number;
name: string;
email: string;
password: string;
ssn: string;
}

// Pick only public fields
const publicFields = pick<User>(['id', 'name', 'email']);

const fullUser = {
id: 1,
name: 'Alice',
email: 'alice@example.com',
password: 'secret',
ssn: '123-45-6789',
};

const publicUser = publicFields(fullUser);
// { id: 1, name: 'Alice', email: 'alice@example.com' }

Use Cases

1. API Responses

// Return only necessary fields to client
const userResponse = pick<User>(['id', 'name', 'email']);

2. Database Queries

// Select specific columns
const userIdentity = pick<User>(['id', 'email']);

Omit

Exclude specific properties from state.

Syntax

omit<T>(keys: (keyof T)[]): Setter<T>

Example

import { omit } from '@noony-serverless/type-builder';

// Omit sensitive fields
const removeSensitive = omit<User>(['password', 'ssn', 'creditCard']);

const fullUser = {
id: 1,
name: 'Alice',
email: 'alice@example.com',
password: 'secret',
ssn: '123-45-6789',
};

const safeUser = removeSensitive(fullUser);
// { id: 1, name: 'Alice', email: 'alice@example.com' }

Pick vs Omit

// Pick: Specify what to KEEP
const keep = pick<User>(['id', 'name']);

// Omit: Specify what to REMOVE
const remove = omit<User>(['password', 'ssn']);

Use pick when you know exactly what you want. Use omit when it's easier to list what to exclude.


Partition

Split state into two groups based on a predicate.

Syntax

partition<T>(
predicate: (key: keyof T, value: T[keyof T]) => boolean,
state: BuilderState<T>
): [BuilderState<T>, BuilderState<T>]

Example

import { partition } from '@noony-serverless/type-builder';

// Split into numbers and strings
const [numbers, strings] = partition<User>((key, value) => typeof value === 'number', {
id: 1,
name: 'Alice',
age: 30,
email: 'alice@example.com',
});

console.log(numbers); // { id: 1, age: 30 }
console.log(strings); // { name: 'Alice', email: 'alice@example.com' }

Use Cases

1. Data Validation

// Split valid and invalid fields
const [valid, invalid] = partition<User>((key, value) => validateField(key, value), userData);

2. Feature Flags

// Split enabled and disabled features
const [enabled, disabled] = partition<Features>((key, value) => value === true, featureFlags);

Compact

Remove undefined and null values.

Syntax

compact<T>(state: BuilderState<T>): BuilderState<T>

Example

import { compact } from '@noony-serverless/type-builder';

const messy = {
id: 1,
name: 'Alice',
email: undefined,
age: null,
active: true,
};

const clean = compact(messy);
// { id: 1, name: 'Alice', active: true }

Use Cases

1. API Cleanup

// Remove null/undefined before sending to API
const cleanData = pipe<User>(compact, removeEmptyStrings);

2. Form Data

// Remove empty form fields
const validFormData = compact(formState);

Composing Higher-Order Functions

The real power comes from combining these functions:

Example: Data Sanitization Pipeline

import { pipe, pick, omit, compact, mapBuilder } from '@noony-serverless/type-builder';

// Complex sanitization pipeline
const sanitizeUser = pipe<User>(
// 1. Remove sensitive fields
omit(['password', 'ssn']),

// 2. Remove null/undefined
compact,

// 3. Normalize strings
mapBuilder((key, value) => {
if (typeof value === 'string') {
return value.trim().toLowerCase();
}
return value;
}),

// 4. Pick only allowed fields
pick(['id', 'name', 'email'])
);

const user = sanitizeUser({
id: 1,
name: ' ALICE ',
email: ' ALICE@EXAMPLE.COM ',
password: 'secret',
ssn: '123-45-6789',
deletedAt: null,
});

// Result:
// {
// id: 1,
// name: 'alice',
// email: 'alice@example.com'
// }

Real-World Examples

Example 1: User Privacy Filter

// Remove PII before logging
const removePII = pipe<User>(
omit(['email', 'phone', 'address', 'ssn']),
mapBuilder((key, value) => {
// Hash user ID for privacy
if (key === 'id') {
return hashId(value as number);
}
return value;
})
);

const safeForLogging = removePII(user);
logger.info('User action', safeForLogging);

Example 2: Form Validation

// Validate form data
const validateForm = (formData: Partial<User>) => {
// Partition into valid and invalid fields
const [valid, invalid] = partition<User>((key, value) => validateField(key, value), formData);

if (Object.keys(invalid).length > 0) {
throw new ValidationError('Invalid fields', invalid);
}

return valid;
};

Example 3: API Response Transformer

// Transform API response
const transformAPIResponse = pipe<User>(
// Map API fields to internal format
mapBuilder((key, value) => {
const mapping: Record<string, string> = {
user_id: 'id',
user_name: 'name',
user_email: 'email',
};
return mapping[key as string] || key;
}),

// Remove null values
compact,

// Pick only needed fields
pick(['id', 'name', 'email', 'active'])
);

Performance Considerations

Filter vs Pick/Omit

// ✅ Fast: Use pick/omit for known keys
const fast = pick<User>(['id', 'name']);

// ⚠️ Slower: Use filter for dynamic conditions
const slower = filterBuilder<User>((key, value) => someComplexCondition(key, value));

Map Performance

// ✅ Fast: Check type once
const fast = mapBuilder<User, string>((key, value) => {
const str = value as string;
return typeof value === 'string' ? str.toLowerCase() : str;
});

// ❌ Slow: Multiple operations per field
const slow = mapBuilder<User, string>((key, value) => {
const str = value as string;
return str.toLowerCase().trim().replace(/\s+/g, ' '); // Many operations
});

Summary

Key Functions

FunctionPurposeReturns
filterBuilderKeep matching propertiesNew state
mapBuilderTransform valuesNew state
foldBuilderReduce to single valueAny type
pickSelect specific keysNew state
omitExclude specific keysNew state
partitionSplit into two groupsTuple of states
compactRemove null/undefinedNew state

When to Use

  • Filter: Dynamic property selection
  • Map: Value transformations
  • Fold: Aggregations and statistics
  • Pick: Known keys to keep
  • Omit: Known keys to remove
  • Partition: Split data into categories
  • Compact: Clean up optional fields

Next Steps