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 namespaces 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.
When to use Rec
Section titled “When to use Rec”Use Rec when:
Section titled “Use Rec when:”- Operating inside pipelines: You are transforming, filtering, or merging records point-free
inside
pipechains. - You require type-safe picks or omits: You want the compiler to statically track exactly which properties exist after keys are picked or omitted.
- Safe key retrieval is required: You want to avoid accidental
undefinedruntime crashes by capturing key absence as aMaybecontainer.
Keep using standard object notation when:
Section titled “Keep using standard object notation when:”- The operation is a simple, local one-liner: Inside a narrow function body where standard
dot-notation
obj.keyor spreads{ ...obj }are already clear and require no composition.