Rec — Record Utilities
Plain JavaScript objects serving as dictionaries — typed as Record<string, A> — are the most
ubiquitous data structures in any TypeScript application. We use them for configurations, lookup
tables, and serialized payloads.
However, working with records in functional pipelines introduces two common points of friction:
- They are data-first: Modifying objects natively forces us to write verbose, inline spreads
inside our
pipechains:(obj) => ({ ...obj, key: value }). - They are unsafe: Accessing a missing key via bracket notation (
obj[key]) silently returnsundefinedat runtime, bypassing the type system and causing errors downstream.
Rec solves both issues. It provides a small, highly optimized collection of data-last, curried
utilities designed to compose cleanly in pipelines, returning explicit Maybe values for safe,
crash-free key lookups.
Safe Key Lookup
Section titled “Safe Key Lookup”Rec.lookup retrieves the value associated with a key, wrapping it in a Maybe container to make
key absence explicit in your types:
This integrates naturally with other pipelines:
Transforming Values
Section titled “Transforming Values”Rec.map transforms every value inside a record, returning a new record with the original keys
preserved:
If the transformation requires the key as well as the value, Rec.mapWithKey passes both to your
callback:
Filtering Values
Section titled “Filtering Values”Rec.filterkeeps only the entries whose values satisfy a predicate.Rec.filterWithKeypasses both the key and the value to the predicate:
Picking and Omitting Keys
Section titled “Picking and Omitting Keys”Rec.pickreturns a new record containing only the specified keys.Rec.omitreturns a new record with the specified keys removed.
Both utilities are fully type-safe. pick returns a precise Pick<A, K> type and omit
returns a precise Omit<A, K> type, ensuring the compiler tracks exactly which properties survive
the pipeline:
Merging Records
Section titled “Merging Records”Rec.merge combines two records, returning a fresh object. Keys present in the second record
override those in the first record, behaving identically to standard object spreads:
Keys, Values, and Entries
Section titled “Keys, Values, and Entries”Rec provides utilities to extract arrays of keys, values, or entries:
Rec.from.entries is the inverse constructor, building a record from an array of key-value pairs:
You can pair entries and from.entries to easily perform structural record mappings:
Sizing and Verification
Section titled “Sizing and Verification”Traversing and Sequencing Records
Section titled “Traversing and Sequencing Records”When a record contains values that wrap effectful computations, you may want to aggregate those
individual effects into a single container holding the mapped record. Rec.traverse and
Rec.sequence provide drawers for traversing and sequencing records, short-circuiting on the first
failure.
Rec.traverse.Maybe maps a Maybe-returning function over the values of a record. If every value
produces a Some, it returns Some of the updated record. If any value maps to None, the entire
operation returns None:
Rec.sequence.Maybe takes a record where every value is already a Maybe and collapses it into a
single Maybe of a record:
Similarly, Rec.traverse.Result and Rec.sequence.Result aggregate fallible computations:
Non-Empty Records
Section titled “Non-Empty Records”Sometimes, you need compile-time assurance that a record contains at least one key-value pair. You
can represent this with the branded type Rec.NonEmpty<A> and refine standard records using the
type guard Rec.is.nonEmpty.
You can create a non-empty record directly using Rec.NonEmpty.singleton or parse a standard record
using Rec.NonEmpty.from.Record:
Operating on Rec.NonEmpty<A> ensures that operations which would normally return standard arrays
(which could be empty) instead return guaranteed non-empty arrays (Arr.NonEmpty). For example,
extracting keys, values, or entries from a non-empty record returns Arr.NonEmpty:
You can also reduce the values of a non-empty record without providing an initial accumulator seed, since there is guaranteed to be at least one value:
Additionally, Rec.map and Rec.mapWithKey preserve the non-empty status of the record in their
return types:
For more details on operating with guaranteed non-empty data structures, see the dedicated Non-Empty Collections Guide.
Problems it solves
Section titled “Problems it solves”- Type-safe payload sanitization: When preparing API responses, stripping private data (such as
password hashes, secret tokens, or internal flags) using
Rec.omitor retaining allowed fields viaRec.picknarrows the TypeScript type statically, preventing accidental access to omitted properties downstream. - Transforming record values in ingestion pipelines: When processing dynamic dictionaries (such
as converting string prices to numbers or status strings to enums),
Rec.mapandRec.filterapply transformations point-free withinpipeworkflows while preserving key structure. - Payload key migration and renaming (
Rec.mapKeys): When ingesting third-party webhook payloads or migrating legacy API schemas (such as convertingsnake_casekeys tocamelCase),Rec.mapKeystransforms object keys immutably across entire dictionaries. - Safe dynamic property retrieval: When looking up runtime keys in configuration objects, user
attributes, or localized string tables, direct index access (
obj[key]) returnsundefinedwithout static warnings.Rec.getreturns aMaybe<V>, requiring explicit presence handling before operating on values. - Merging multi-layer configuration records: Combining default theme tokens with user style overrides or merging localized message catalogs without mutating input objects.