Rec
Record utilities for working with plain JavaScript objects as dictionaries.
Provides type-safe operations for records (objects with PropertyKey indexes) including type guards, merging, creation, and index signature manipulation. Strictly validates plain objects, rejecting arrays and class instances.
Import
import { Rec } from '@wollybeard/kit'
import * as Rec from '@wollybeard/kit/rec'
Factories
[F]
create
<value>(): Record<PropertyKey, value>
Returns: An empty record typed to hold values of the specified type
Create an empty record with a specific value type. Useful for initializing typed record collections.
Examples:
const scores = create<number>()
scores['alice'] = 95
scores['bob'] = 87
// scores is typed as Record<PropertyKey, number>
// Creating typed lookups
interface User {
id: string
name: string
}
const userLookup = create<User>()
userLookup['u123'] = { id: 'u123', name: 'Alice' }
// Useful as accumulator in reduce operations
const grouped = items.reduce(
(acc, item) => {
acc[item.category] = item
return acc
},
create<Item>(),
)
Operations
[F]
merge
<rec1 extends Any, rec2 extends Any > (rec1: rec1, rec2: rec2): rec1 & rec2
Parameters:
rec1
- The base record to merge intorec2
- The record to merge from
Returns: A new record with properties from both records merged
Deep merge two records, with properties from the second record overwriting the first. This is an alias for Obj.merge that works specifically with record types.
Examples:
Rec.merge({ a: 1, b: 2 }, { b: 3, c: 4 })
// Returns: { a: 1, b: 3, c: 4 }
// Deep merging of nested records
Rec.merge(
{ user: { name: 'Alice', settings: { theme: 'dark' } } },
{ user: { settings: { fontSize: 16 } } },
)
// Returns: { user: { name: 'Alice', settings: { theme: 'dark', fontSize: 16 } } }
// Type-safe merging
type Config = { api: { url: string }; timeout?: number }
type Overrides = { api: { key: string }; timeout: number }
const config: Config = { api: { url: 'https://api.com' } }
const overrides: Overrides = { api: { key: 'secret' }, timeout: 5000 }
const merged = Rec.merge(config, overrides)
// merged is typed as Config & Overrides
Type Guards
[F]
is
(value: unknown): boolean
Parameters:
value
- The value to check
Returns: True if the value is a plain record object
Check if a value is a record (plain object only, not class instances or arrays). This is a strict check that only accepts plain objects with Object.prototype.
Examples:
Rec.is({ a: 1, b: 2 }) // true
Rec.is({}) // true
Rec.is([1, 2, 3]) // false - arrays are not records
Rec.is(null) // false
Rec.is(new Date()) // false - class instances are not plain records
Rec.is(Object.create(null)) // false - not plain Object.prototype
// Type guard usage
function processData(data: unknown) {
if (Rec.is(data)) {
// data is typed as Rec.Any
Object.keys(data).forEach(key => {
console.log(data[key])
})
}
}
Types
[T]
Any
type Any = AnyKeyTo<unknown>
[T]
AnyReadonly
type AnyReadonly = AnyReadonlyKeyTo<unknown>
[T]
AnyKeyTo
type AnyKeyTo<$Value> = {
[key: PropertyKey]: $Value
}
[T]
AnyReadonlyKeyTo
type AnyReadonlyKeyTo<$Value> = {
readonly [key: PropertyKey]: $Value
}
[T]
Value
type Value = {
[key: PropertyKey]: Lang.Value
}
[T]
Optional
type Optional<$Key extends PropertyKey, $Value> = {
[K in $Key]?: $Value
}
[T]
RemoveIndex
type RemoveIndex<$T> = {
[k in keyof $T as string extends k ? never : number extends k ? never : k]:
$T[k]
}
Remove index signatures from an object type. Useful for converting Record types to object types with only known keys.
Examples:
type WithIndex = { a: string; b: number; [key: string]: any }
type WithoutIndex = RemoveIndex<WithIndex> // { a: string; b: number }
[T]
IsHasIndex
type IsHasIndex<$T, $Key extends PropertyKey = string> = $Key extends keyof $T
? true
: false
Check if a type has an index signature.
Examples:
type T1 = IsHasIndex<{ [key: string]: any }> // true
type T2 = IsHasIndex<{ a: string }> // false
type T3 = IsHasIndex<{ [key: number]: any }, number> // true