Struct.ts
Struct.ts overview
Section titled “Struct.ts overview”Works with plain TypeScript objects, also called structs.
The runtime helpers in this module create new objects instead of mutating their inputs. They cover common object workflows such as reading properties, listing typed keys, picking or omitting fields, assigning and renaming keys, transforming values, deriving comparison helpers, and creating records from a list of keys. The module also includes type-level helpers for simplifying and merging object shapes.
Since v2.0.0
Exports Grouped by Category
Section titled “Exports Grouped by Category”- Key utilities
- Lambda
- combining
- constructors
- filtering
- folding
- getters
- instances
- mapping
- ordering
- transforming
- utility types
Key utilities
Section titled “Key utilities”evolveKeys
Section titled “evolveKeys”Transforms keys of a struct selectively using per-key functions. Keys without a corresponding function are copied unchanged.
When to use
Use when you need computed key names, such as uppercasing or prefixing.
Details
Each transform function receives the key name and must return a new
PropertyKey.
Example (Renaming keys with functions)
import { pipe, Struct } from "effect"
const result = pipe( { name: "Alice", age: 30 }, Struct.evolveKeys({ name: (k) => k.toUpperCase() }))console.log(result) // { NAME: "Alice", age: 30 }See
renameKeys– rename keys with a static mappingevolve– transform values instead of keysevolveEntries– transform both keys and values
Signature
declare const evolveKeys: { <S extends object, E extends KeyEvolver<S>>(e: E): (self: S) => KeyEvolved<S, E> <S extends object, E extends KeyEvolver<S>>(self: S, e: E): KeyEvolved<S, E>}Since v4.0.0
Returns the string keys of a struct as a properly typed Array<keyof S & string>.
When to use
Use when you want a typed replacement for Object.keys that narrows the result
to the known string keys of the struct.
Gotchas
Symbol keys are excluded; only string keys are returned.
Example (Reading typed keys)
import { Struct } from "effect"
const user = { name: "Alice", age: 30, [Symbol.for("id")]: 1 }
const k: Array<"name" | "age"> = Struct.keys(user)console.log(k) // ["name", "age"]See
get– access a single key’s valuepick– select a subset of keys into a new struct
Signature
declare const keys: <S extends object>(self: S) => Array<keyof S & string>Since v3.6.0
renameKeys
Section titled “renameKeys”Renames keys in a struct using a static { oldKey: newKey } mapping. Keys
not mentioned in the mapping are copied unchanged.
When to use
Use when you need simple, declarative key renaming without custom logic.
Details
For computed key names, use evolveKeys instead.
Example (Renaming keys)
import { pipe, Struct } from "effect"
const result = pipe( { firstName: "Alice", lastName: "Smith", age: 30 }, Struct.renameKeys({ firstName: "first", lastName: "last" }))console.log(result) // { first: "Alice", last: "Smith", age: 30 }See
evolveKeys– rename keys using functionsevolveEntries– rename keys and transform values
Signature
declare const renameKeys: { <S extends object, const M extends { readonly [K in keyof S]?: PropertyKey }>( mapping: M ): (self: S) => { [K in keyof S as K extends keyof M ? (M[K] extends PropertyKey ? M[K] : K) : K]: S[K] } <S extends object, const M extends { readonly [K in keyof S]?: PropertyKey }>( self: S, mapping: M ): { [K in keyof S as K extends keyof M ? (M[K] extends PropertyKey ? M[K] : K) : K]: S[K] }}Since v4.0.0
Lambda
Section titled “Lambda”Apply (type alias)
Section titled “Apply (type alias)”Applies a Lambda type-level function to a value type V, producing
the output type.
When to use
Use when you need to compute what type a Lambda would produce for a given input.
Details
This works by intersecting the Lambda with { "~lambda.in": V } and reading
"~lambda.out".
Example (Computing the output type of a lambda)
import type { Struct } from "effect"
interface ToString extends Struct.Lambda { readonly "~lambda.out": string}
// Result is `string`type Result = Struct.Apply<ToString, number>See
Lambda– the base interface
Signature
type Apply<L, V> = (L & { readonly "~lambda.in": V })["~lambda.out"]Since v4.0.0
Lambda (interface)
Section titled “Lambda (interface)”Interface for type-level functions used by map, mapPick, and
mapOmit.
When to use
Use when defining a typed function for map, mapPick, or
mapOmit.
Details
Extend this interface with concrete ~lambda.in and ~lambda.out types to
describe how a function transforms values at the type level. At runtime,
create lambda values with lambda.
Example (Defining a lambda type)
import type { Struct } from "effect"
interface ToString extends Struct.Lambda { readonly "~lambda.out": string}See
Apply– apply a Lambda to a concrete typelambda– create a runtime lambda valuemap– use a lambda to transform all struct values
Signature
export interface Lambda { readonly "~lambda.in": unknown readonly "~lambda.out": unknown}Since v4.0.0
lambda
Section titled “lambda”Wraps a plain function as a Lambda value so it can be used with
map, mapPick, and mapOmit.
When to use
Use to create a typed lambda for struct mapping APIs that need type-level input and output tracking.
Details
The type parameter L encodes both the input and output types at the type
level, allowing the compiler to track how struct value types change. At
runtime, the returned value is the same function; lambda only adjusts the
type.
Example (Wrapping values in arrays)
import { pipe, Struct } from "effect"
interface AsArray extends Struct.Lambda { <A>(self: A): Array<A> readonly "~lambda.out": Array<this["~lambda.in"]>}
const asArray = Struct.lambda<AsArray>((a) => [a])const result = pipe({ x: 1, y: "hello" }, Struct.map(asArray))console.log(result) // { x: [1], y: ["hello"] }See
Lambda– the type-level interfacemap– apply a lambda to all struct values
Signature
declare const lambda: <L extends (a: any) => any>(f: (a: Parameters<L>[0]) => ReturnType<L>) => LSince v4.0.0
combining
Section titled “combining”assign
Section titled “assign”Merges two structs into a new struct. When both structs share a key, the
value from that (the second struct) wins.
When to use
Use when you want { ...self, ...that } with proper types.
Details
The result type is Simplify<Assign<S, O>>.
Example (Merging structs with overlapping keys)
import { pipe, Struct } from "effect"
const defaults = { theme: "light", lang: "en" }const overrides = { theme: "dark", fontSize: 14 }const config = pipe(defaults, Struct.assign(overrides))console.log(config) // { theme: "dark", lang: "en", fontSize: 14 }See
Assign– the type-level equivalentevolve– transform individual values instead of replacing them
Signature
declare const assign: { <O extends object>(that: O): <S extends object>(self: S) => Assign<S, O> <O extends object, S extends object>(self: S, that: O): Assign<S, O>}Since v4.0.0
makeCombiner
Section titled “makeCombiner”Creates a Combiner for a struct shape by providing a Combiner for each
property. When two structs are combined, each property is merged using its
corresponding combiner.
When to use
Use when you need to merge two same-shape records by combining each property independently, such as summing counters or concatenating strings.
Details
Pass omitKeyWhen to drop properties whose merged value matches a predicate,
such as omitting zero counters.
Example (Combining struct properties)
import { Number, String, Struct } from "effect"
const C = Struct.makeCombiner<{ readonly n: number; readonly s: string }>({ n: Number.ReducerSum, s: String.ReducerConcat})
const result = C.combine({ n: 1, s: "hello" }, { n: 2, s: " world" })console.log(result) // { n: 3, s: "hello world" }See
makeReducer– likemakeCombinerbut with an initial value
Signature
declare const makeCombiner: <A>( combiners: { readonly [K in keyof A]: Combiner.Combiner<A[K]> }, options?: { readonly omitKeyWhen?: ((a: A[keyof A]) => boolean) | undefined }) => Combiner.Combiner<A>Since v4.0.0
constructors
Section titled “constructors”Record
Section titled “Record”Creates a record with the given keys and value.
When to use
Use to build an object where each provided key receives the same value.
Example (Creating a record)
import { Struct } from "effect"
const record = Struct.Record(["a", "b"], "value")console.log(record) // { a: "value", b: "value" }Signature
declare const Record: <const Keys extends ReadonlyArray<string | symbol>, Value>( keys: Keys, value: Value) => Record<Keys[number], Value>Since v4.0.0
filtering
Section titled “filtering”Creates a new struct with the specified keys removed.
When to use
Use to exclude sensitive or irrelevant fields from a struct.
Gotchas
Keys not present in the struct are silently ignored.
Example (Removing a property)
import { pipe, Struct } from "effect"
const user = { name: "Alice", age: 30, password: "secret" }const safe = pipe(user, Struct.omit(["password"]))console.log(safe) // { name: "Alice", age: 30 }See
pick– the inverse (keep only specified keys)
Signature
declare const omit: { <S extends object, const Keys extends ReadonlyArray<keyof S>>( keys: Keys ): (self: S) => Simplify<Omit<S, Keys[number]>> <S extends object, const Keys extends ReadonlyArray<keyof S>>(self: S, keys: Keys): Simplify<Omit<S, Keys[number]>>}Since v2.0.0
Creates a new struct containing only the specified keys.
When to use
Use to narrow a struct down to a subset of its properties.
Gotchas
Keys not present in the struct are silently ignored.
Example (Selecting specific properties)
import { pipe, Struct } from "effect"
const user = { name: "Alice", age: 30, admin: true }const nameAndAge = pipe(user, Struct.pick(["name", "age"]))console.log(nameAndAge) // { name: "Alice", age: 30 }See
omit– the inverse (exclude keys instead)get– extract a single value
Signature
declare const pick: { <S extends object, const Keys extends ReadonlyArray<keyof S>>( keys: Keys ): (self: S) => Simplify<Pick<S, Keys[number]>> <S extends object, const Keys extends ReadonlyArray<keyof S>>(self: S, keys: Keys): Simplify<Pick<S, Keys[number]>>}Since v2.0.0
folding
Section titled “folding”makeReducer
Section titled “makeReducer”Creates a Reducer for a struct shape by providing a Reducer for each
property. The initial value is derived from each property’s
Reducer.initialValue. When reducing a collection of structs, each property
is combined independently.
When to use
Use when you need to fold same-shape records by accumulating each property independently into one summary record.
Details
Pass omitKeyWhen to drop properties whose reduced value matches a
predicate.
Example (Reducing a collection of structs)
import { Number, String, Struct } from "effect"
const R = Struct.makeReducer<{ readonly n: number; readonly s: string }>({ n: Number.ReducerSum, s: String.ReducerConcat})
const result = R.combineAll([ { n: 1, s: "a" }, { n: 2, s: "b" }, { n: 3, s: "c" }])console.log(result) // { n: 6, s: "abc" }See
makeCombiner– likemakeReducerbut without an initial value
Signature
declare const makeReducer: <A>( reducers: { readonly [K in keyof A]: Reducer.Reducer<A[K]> }, options?: { readonly omitKeyWhen?: ((a: A[keyof A]) => boolean) | undefined }) => Reducer.Reducer<A>Since v4.0.0
getters
Section titled “getters”Retrieves the value at key from a struct.
When to use
Use to extract a single property from a struct in a pipeline.
Details
The return type is narrowed to S[K].
Example (Extracting a property in a pipeline)
import { pipe, Struct } from "effect"
const name = pipe({ name: "Alice", age: 30 }, Struct.get("name"))console.log(name) // "Alice"See
keys– list all string keys of a structpick– extract multiple properties into a new struct
Signature
declare const get: { <S extends object, const K extends keyof S>(key: K): (self: S) => S[K] <S extends object, const K extends keyof S>(self: S, key: K): S[K]}Since v2.0.0
instances
Section titled “instances”makeEquivalence
Section titled “makeEquivalence”Creates an Equivalence for a struct by providing an Equivalence for each
property. Two structs are equivalent when all their corresponding properties
are equivalent.
When to use
Use when you need equality for a record-like object to be decided field by field, with a custom equality rule for each property.
Details
This is an alias of Equivalence.Struct. Each property’s equivalence is
checked independently; all must return true for the overall result to be
true.
Example (Comparing structs for equivalence)
import { Equivalence, Struct } from "effect"
const PersonEquivalence = Struct.makeEquivalence({ name: Equivalence.strictEqual<string>(), age: Equivalence.strictEqual<number>()})
console.log(PersonEquivalence({ name: "Alice", age: 30 }, { name: "Alice", age: 30 }))// trueconsole.log(PersonEquivalence({ name: "Alice", age: 30 }, { name: "Bob", age: 30 }))// falseSee
makeOrder– create anOrderfor structs
Signature
declare const makeEquivalence: <R extends Record<string, Equivalence.Equivalence<any>>>( fields: R) => Equivalence.Equivalence<{ readonly [K in keyof R]: [R[K]] extends [Equivalence.Equivalence<infer A>] ? A : never }>Since v4.0.0
mapping
Section titled “mapping”Applies a Lambda transformation to every value in a struct.
When to use
Use when you want to apply the same function to every value in a struct.
Details
The lambda must be created with lambda so the compiler can track the
output types.
Example (Wrapping every value in an array)
import { pipe, Struct } from "effect"
interface AsArray extends Struct.Lambda { <A>(self: A): Array<A> readonly "~lambda.out": Array<this["~lambda.in"]>}
const asArray = Struct.lambda<AsArray>((a) => [a])const result = pipe({ width: 10, height: 20 }, Struct.map(asArray))console.log(result) // { width: [10], height: [20] }See
mapPick– apply a lambda only to selected keysmapOmit– apply a lambda to all keys except selected onesevolve– apply different functions to different keys
Signature
declare const map: { <L extends Lambda>(lambda: L): <S extends object>(self: S) => { [K in keyof S]: Apply<L, S[K]> } <S extends object, L extends Lambda>(self: S, lambda: L): { [K in keyof S]: Apply<L, S[K]> }}Since v4.0.0
mapOmit
Section titled “mapOmit”Applies a Lambda transformation to all keys except the specified
ones; the excluded keys are copied unchanged.
When to use
Use when most keys should be transformed but a few should be preserved.
Example (Wrapping all values except one in arrays)
import { pipe, Struct } from "effect"
interface AsArray extends Struct.Lambda { <A>(self: A): Array<A> readonly "~lambda.out": Array<this["~lambda.in"]>}
const asArray = Struct.lambda<AsArray>((a) => [a])const result = pipe({ x: 1, y: 2, z: 3 }, Struct.mapOmit(["y"], asArray))console.log(result) // { x: [1], y: 2, z: [3] }See
map– apply a lambda to all keysmapPick– apply a lambda only to selected keys
Signature
declare const mapOmit: { <S extends object, const Keys extends ReadonlyArray<keyof S>, L extends Lambda>( keys: Keys, lambda: L ): (self: S) => { [K in keyof S]: K extends Keys[number] ? S[K] : Apply<L, S[K]> } <S extends object, const Keys extends ReadonlyArray<keyof S>, L extends Lambda>( self: S, keys: Keys, lambda: L ): { [K in keyof S]: K extends Keys[number] ? S[K] : Apply<L, S[K]> }}Since v4.0.0
mapPick
Section titled “mapPick”Applies a Lambda transformation only to the specified keys; all
other keys are copied unchanged.
When to use
Use when you want to apply the same transformation to a subset of properties.
Example (Wrapping only selected values in arrays)
import { pipe, Struct } from "effect"
interface AsArray extends Struct.Lambda { <A>(self: A): Array<A> readonly "~lambda.out": Array<this["~lambda.in"]>}
const asArray = Struct.lambda<AsArray>((a) => [a])const result = pipe({ x: 1, y: 2, z: 3 }, Struct.mapPick(["x", "z"], asArray))console.log(result) // { x: [1], y: 2, z: [3] }See
map– apply a lambda to all keysmapOmit– apply a lambda to all keys except selected ones
Signature
declare const mapPick: { <S extends object, const Keys extends ReadonlyArray<keyof S>, L extends Lambda>( keys: Keys, lambda: L ): (self: S) => { [K in keyof S]: K extends Keys[number] ? Apply<L, S[K]> : S[K] } <S extends object, const Keys extends ReadonlyArray<keyof S>, L extends Lambda>( self: S, keys: Keys, lambda: L ): { [K in keyof S]: K extends Keys[number] ? Apply<L, S[K]> : S[K] }}Since v4.0.0
ordering
Section titled “ordering”makeOrder
Section titled “makeOrder”Creates an Order for a struct by providing an Order for each property.
Properties are compared in the order they appear in the fields object; the
first non-zero comparison determines the result.
When to use
Use when you need to sort record-like objects lexicographically by several fields, with each field using its own ordering rule.
Details
This is an alias of Order.Struct. The order of keys in the fields object
determines comparison priority.
Example (Ordering structs by name then age)
import { Number, String, Struct } from "effect"
const PersonOrder = Struct.makeOrder({ name: String.Order, age: Number.Order})
console.log(PersonOrder({ name: "Alice", age: 30 }, { name: "Bob", age: 25 }))// -1 (Alice comes before Bob)See
makeEquivalence– create anEquivalencefor structs
Signature
declare const makeOrder: <const R extends { readonly [x: string]: order.Order<any> }>( fields: R) => order.Order<{ [K in keyof R]: [R[K]] extends [order.Order<infer A>] ? A : never }>Since v4.0.0
transforming
Section titled “transforming”evolve
Section titled “evolve”Transforms values of a struct selectively using per-key functions. Keys without a corresponding function are copied unchanged.
When to use
Use when you want to update specific fields while keeping the rest intact.
Details
Each transform function receives the current value and returns the new value; the return type can differ from the input type.
Example (Transforming selected values)
import { pipe, Struct } from "effect"
const result = pipe( { name: "alice", age: 30, active: true }, Struct.evolve({ name: (s) => s.toUpperCase(), age: (n) => n + 1 }))console.log(result) // { name: "ALICE", age: 31, active: true }See
evolveKeys– transform keys instead of valuesevolveEntries– transform both keys and valuesmap– apply the same transformation to all values
Signature
declare const evolve: { <S extends object, E extends Evolver<S>>(e: E): (self: S) => Evolved<S, E> <S extends object, E extends Evolver<S>>(self: S, e: E): Evolved<S, E>}Since v2.0.0
evolveEntries
Section titled “evolveEntries”Transforms both keys and values of a struct selectively. Each per-key
function receives (key, value) and must return a [newKey, newValue]
tuple. Keys without a corresponding function are copied unchanged.
When to use
Use when you need to rename a key and change its value in one step.
Details
The return type is fully tracked at the type level.
Example (Transforming keys and values together)
import { pipe, Struct } from "effect"
const result = pipe( { amount: 100, label: "total" }, Struct.evolveEntries({ amount: (k, v) => [`${k}Cents`, v * 100], label: (k, v) => [k, v.toUpperCase()] }))console.log(result) // { amountCents: 10000, label: "TOTAL" }See
evolve– transform values onlyevolveKeys– transform keys only
Signature
declare const evolveEntries: { <S extends object, E extends EntryEvolver<S>>(e: E): (self: S) => EntryEvolved<S, E> <S extends object, E extends EntryEvolver<S>>(self: S, e: E): EntryEvolved<S, E>}Since v4.0.0
utility types
Section titled “utility types”Assign (type alias)
Section titled “Assign (type alias)”Merges two object types with properties from U taking precedence over T
on overlapping keys (like Object.assign at the type level).
When to use
Use when you need the type-level equivalent of { ...T, ...U }.
Details
When no keys overlap, this returns a simple intersection for efficiency.
When keys overlap, the type from U wins.
Example (Merging two types with overlapping keys)
import type { Struct } from "effect"
type A = { a: string; b: number }type B = { b: boolean; c: string }type Merged = Struct.Assign<A, B>// { a: string; b: boolean; c: string }See
assign– the runtime equivalentSimplify– flatten the resulting intersection
Signature
type { [K in keyof (keyof T & keyof U extends never ? T & U : Omit<T, keyof T & keyof U> & U)]: (keyof T & keyof U extends never ? T & U : Omit<T, keyof T & keyof U> & U)[K]; } = Simplify<keyof T & keyof U extends never ? T & U : Omit<T, keyof T & keyof U> & U>Since v4.0.0
Mutable (type alias)
Section titled “Mutable (type alias)”Removes readonly modifiers from all properties of an object type.
When to use
Use when you need a mutable version of a readonly interface.
Details
This helper is purely cosmetic at the type level and has no runtime effect.
It also flattens intersections like Simplify.
Example (Making a readonly type mutable)
import type { Struct } from "effect"
type ReadOnly = { readonly a: string; readonly b: number }type Writable = Struct.Mutable<ReadOnly>// { a: string; b: number }See
Simplify– flattens intersections without removingreadonly
Signature
type { -readonly [K in keyof T]: T[K]; } = { -readonly [K in keyof T]: T[K] } & {}Since v4.0.0
Simplify (type alias)
Section titled “Simplify (type alias)”Flattens intersection types into a single object type for readability.
When to use
Use when hovering over a type shows A & B & C instead of the merged shape.
Details
This helper is purely cosmetic at the type level and has no runtime effect.
It preserves readonly modifiers; use Mutable to strip them.
Example (Flattening an intersection)
import type { Struct } from "effect"
type Original = { a: string } & { b: number }
// Without Simplify, the type displays as `{ a: string } & { b: number }`type Simplified = Struct.Simplify<Original>// { a: string; b: number }See
Mutable– also flattens but removesreadonlyAssign– merges two types with right-side precedence
Signature
type { [K in keyof T]: T[K]; } = { [K in keyof T]: T[K] } & {}Since v4.0.0