Struct overview
Extension to the Effect Struct module providing prototype-safe struct merging, field overriding, one-key construction, derived-field enrichment, and a refined evolve.
Mental model
- This module must be used for objects whose property names are known. When only the property types are known, use
MRecord. - Compared to the spread operator, the helpers here intentionally do not copy the prototype: the result is always a fresh
Data-shaped object. This makes the loss of prototype explicit instead of hidden behind syntax sugar. - Mutating variants ({@link mutableSet}, {@link mutableEnrichWith}) write in place and should be reserved for hot paths where allocation cost is unacceptable.
Common tasks
- Merge / append: {@link prepend}, {@link append}
- Override existing fields: {@link set}, {@link mutableSet}
- Construct: {@link make}
- Add derived fields: {@link enrichWith}, {@link mutableEnrichWith}
- Transform fields: {@link evolve}
Quickstart
Example (Append a field then derive another from self)
import { pipe } from "effect"
import * as MStruct from "@parischap/effect-lib/MStruct"
const built = pipe(
{ firstName: "Ada" } as const,
MStruct.append({ lastName: "Lovelace" } as const),
MStruct.enrichWith({ fullName: ({ firstName, lastName }) => `${firstName} ${lastName}` })
)
console.log(built) // { firstName: 'Ada', lastName: 'Lovelace', fullName: 'Ada Lovelace' }
Table of contents
Constructors
make
Builds a single-field struct { [key]: value }.
Example (Single-key constructor)
import { pipe } from "effect"
import * as MStruct from "@parischap/effect-lib/MStruct"
console.log(pipe(42, MStruct.make("answer"))) // { answer: 42 }
Signature
export declare const make: <K extends PropertyKey>(key: K) => <V>(value: V) => { readonly [key in K]: V }
Models
Type (type alias)
Type of a struct. Ideally, we would have chosen MTypes.Object. But it does not cover class instances on which we want this module to operate
Signature
export type Type = MTypes.NonPrimitive
Utility types
Append (type alias)
Utility type computing the result of { ...first, ...second }.
- When a field exists in both
FirstandSecond, the resulting type follows JavaScript’s spread semantics:Second’s value wins, except when it is optional, in which case the result is the union ofSecond[k](withoutundefined) andFirst[k].
Signature
export type Append<First extends Type, Second extends Type> = {
readonly [k in keyof First | keyof Second]: k extends keyof Second
? k extends keyof First
? // Is Second[k] not optional
Extract<Second[k], undefined> extends never
? Second[k]
: Exclude<Second[k], undefined> | First[k]
: Second[k]
: First[k]
}
Utils
append
Builds { ...self, ...that } — fields in that override fields in self.
- Use instead of the spread operator when you want to make explicit that the prototype of the left-hand side is dropped.
- Same as Effect’s Struct.assign but handles optional values more cleanly.
Example (New fields override)
import { pipe } from "effect"
import * as MStruct from "@parischap/effect-lib/MStruct"
console.log(pipe({ a: 1, b: 2 } as const, MStruct.append({ b: 3, c: 4 } as const)))
// { a: 1, b: 3, c: 4 }
Signature
export declare const append: <O1 extends Type>(that: O1) => <O extends Type>(self: O) => MTypes.Data<Append<O, O1>>
enrichWith
Computes a record of new fields where each value is f(self) for the corresponding key, then appends them to self.
- Use when several derived fields all depend on the same source struct.
- Existing fields with the same key are overwritten.
Example (Adding derived fields)
import { pipe } from "effect"
import * as MStruct from "@parischap/effect-lib/MStruct"
console.log(
pipe(
{ firstName: "Ada", lastName: "Lovelace" } as const,
MStruct.enrichWith({
fullName: ({ firstName, lastName }) => `${firstName} ${lastName}`,
initials: ({ firstName, lastName }) => `${firstName[0]}.${lastName[0]}.`
})
)
)
// { firstName: 'Ada', lastName: 'Lovelace', fullName: 'Ada Lovelace', initials: 'A.L.' }
Signature
export declare const enrichWith: <O extends Type, O1 extends { [x: PropertyKey]: MTypes.OneArgFunction<O, unknown> }>(
fields: O1
) => (self: O) => MTypes.Data<Omit<O, keyof O1> & { readonly [key in keyof O1]: ReturnType<O1[key]> }>
mutableEnrichWith
Same as {@link enrichWith} but writes the new fields into self in place using Object.assign.
- Use only when allocation cost matters and
selfis not shared.
Signature
export declare const mutableEnrichWith: <
O extends MTypes.NonPrimitive,
O1 extends { [x: PropertyKey]: MTypes.OneArgFunction<O, unknown> }
>(
fields: O1
) => (self: O) => Omit<O, keyof O1> & { readonly [key in keyof O1]: ReturnType<O1[key]> }
mutableSet
Same as {@link set} but writes the new fields into self in place using Object.assign.
- Use only when allocation cost matters and
selfis not shared.
Signature
export declare const mutableSet: <O extends Type, O1 extends Partial<O>>(
that: O1
) => (self: O) => Omit<O, keyof O1> & O1
prepend
Builds { ...that, ...self } — fields in self override fields in that.
- Use instead of the spread operator when you want to make explicit that the prototype of the left-hand side is dropped.
- Same as Effect’s Struct.assign but handles optional values more cleanly.
Example (Self overrides defaults)
import { pipe } from "effect"
import * as MStruct from "@parischap/effect-lib/MStruct"
const defaults = { theme: "light", font: "serif" } as const
console.log(pipe({ font: "mono" } as const, MStruct.prepend(defaults)))
// { theme: 'light', font: 'mono' }
Signature
export declare const prepend: <O1 extends Type>(that: O1) => <O extends Type>(self: O) => MTypes.Data<Append<O1, O>>
set
Like {@link append} but that may only override fields that already exist in self. New keys are rejected at the type level.
Example (Override only declared fields)
import { pipe } from "effect"
import * as MStruct from "@parischap/effect-lib/MStruct"
type User = { readonly name: string; readonly age: number }
const user: User = { name: "Ada", age: 30 }
console.log(pipe(user, MStruct.set({ age: 31 }))) // { name: 'Ada', age: 31 }
Signature
export declare const set: <O extends Type, O1 extends Partial<O>>(
that: O1
) => (self: O) => MTypes.Data<Omit<O, keyof O1> & O1>
{
evolve, }
Same as Struct.evolve but the return type drops any property carried by the prototype, since those are not copied by spreading. Transformations whose key is missing in self are ignored.
Example (Evolving selected fields)
import { pipe } from "effect"
import * as MStruct from "@parischap/effect-lib/MStruct"
console.log(
pipe({ firstName: "ada", lastName: "lovelace" } as const, MStruct.evolve({ firstName: (s) => s.toUpperCase() }))
)
// { firstName: 'ADA', lastName: 'lovelace' }
Signature
export declare const { evolve }: any