1/**
2 * Works with plain TypeScript objects, also called structs.
3 *
4 * The runtime helpers in this module create new objects instead of mutating
5 * their inputs. They cover common object workflows such as reading properties,
6 * listing typed keys, picking or omitting fields, assigning and renaming keys,
7 * transforming values, deriving comparison helpers, and creating records from a
8 * list of keys. The module also includes type-level helpers for simplifying and
9 * merging object shapes.
10 *
11 * @since 2.0.0
12 */
13
14import * as Combiner from "./Combiner.ts"
15import * as Equivalence from "./Equivalence.ts"
16import { dual } from "./Function.ts"
17import * as InternalRecord from "./internal/record.ts"
18import * as order from "./Order.ts"
19import * as Reducer from "./Reducer.ts"
20
21/**
22 * Flattens intersection types into a single object type for readability.
23 *
24 * **When to use**
25 *
26 * Use when hovering over a type shows `A & B & C` instead of the merged shape.
27 *
28 * **Details**
29 *
30 * This helper is purely cosmetic at the type level and has no runtime effect.
31 * It preserves `readonly` modifiers; use {@link Mutable} to strip them.
32 *
33 * **Example** (Flattening an intersection)
34 *
35 * ```ts import.meta.vitest
36 * import type { Struct } from "effect"
37 *
38 * type Original = { a: string } & { b: number }
39 *
40 * // Without Simplify, the type displays as `{ a: string } & { b: number }`
41 * type Simplified = Struct.Simplify<Original>
42 * // { a: string; b: number }
43 *
44 * const witness: Simplified = { a: "value", b: 1 }
45 * ```
46 *
47 * @see {@link Mutable} – also flattens but removes `readonly`
48 * @see {@link Assign} – merges two types with right-side precedence
49 * @category utility types
50 * @since 4.0.0
51 */
52export type Simplify<T> = { [K in keyof T]: T[K] } & {}
53
54/**
55 * Removes `readonly` modifiers from all properties of an object type.
56 *
57 * **When to use**
58 *
59 * Use when you need a mutable version of a readonly interface.
60 *
61 * **Details**
62 *
63 * This helper is purely cosmetic at the type level and has no runtime effect.
64 * It also flattens intersections like {@link Simplify}.
65 *
66 * **Example** (Making a readonly type mutable)
67 *
68 * ```ts import.meta.vitest
69 * import type { Struct } from "effect"
70 *
71 * type ReadOnly = { readonly a: string; readonly b: number }
72 * type Writable = Struct.Mutable<ReadOnly>
73 * // { a: string; b: number }
74 *
75 * const witness: Writable = { a: "value", b: 1 }
76 * witness.b = 2
77 * witness // => { a: "value", b: 2 }
78 * ```
79 *
80 * @see {@link Simplify} – flattens intersections without removing `readonly`
81 * @category utility types
82 * @since 4.0.0
83 */
84export type Mutable<T> = { -readonly [K in keyof T]: T[K] } & {}
85
86/**
87 * Merges two object types with properties from `U` taking precedence over `T`
88 * on overlapping keys (like `Object.assign` at the type level).
89 *
90 * **When to use**
91 *
92 * Use when you need the type-level equivalent of `{ ...T, ...U }`.
93 *
94 * **Details**
95 *
96 * When no keys overlap, this returns a simple intersection for efficiency.
97 * When keys overlap, the type from `U` wins.
98 *
99 * **Example** (Merging two types with overlapping keys)
100 *
101 * ```ts import.meta.vitest
102 * import type { Struct } from "effect"
103 *
104 * type A = { a: string; b: number }
105 * type B = { b: boolean; c: string }
106 * type Merged = Struct.Assign<A, B>
107 * // { a: string; b: boolean; c: string }
108 *
109 * const witness: Merged = { a: "value", b: true, c: "other" }
110 * ```
111 *
112 * @see {@link assign} – the runtime equivalent
113 * @see {@link Simplify} – flatten the resulting intersection
114 * @category utility types
115 * @since 4.0.0
116 */
117export type Assign<T, U> = Simplify<keyof T & keyof U extends never ? T & U : Omit<T, keyof T & keyof U> & U>
118
119/**
120 * Retrieves the value at `key` from a struct.
121 *
122 * **When to use**
123 *
124 * Use to extract a single property from a struct in a pipeline.
125 *
126 * **Details**
127 *
128 * The return type is narrowed to `S[K]`.
129 *
130 * **Example** (Extracting a property in a pipeline)
131 *
132 * ```ts import.meta.vitest
133 * import { pipe, Struct } from "effect"
134 *
135 * pipe({ name: "Alice", age: 30 }, Struct.get("name")) // => "Alice"
136 * ```
137 *
138 * @see {@link keys} – list all string keys of a struct
139 * @see {@link pick} – extract multiple properties into a new struct
140 * @category getters
141 * @since 2.0.0
142 */
143export const get: {
144 <S extends object, const K extends keyof S>(key: K): (self: S) => S[K]
145 <S extends object, const K extends keyof S>(self: S, key: K): S[K]
146} = dual(2, <S extends object, const K extends keyof S>(self: S, key: K): S[K] => self[key])
147
148/**
149 * Returns the string keys of a struct as a properly typed `Array<keyof S & string>`.
150 *
151 * **When to use**
152 *
153 * Use when you want a typed replacement for `Object.keys` that narrows the result
154 * to the known string keys of the struct.
155 *
156 * **Gotchas**
157 *
158 * Symbol keys are excluded; only string keys are returned.
159 *
160 * **Example** (Reading typed keys)
161 *
162 * ```ts import.meta.vitest
163 * import { Struct } from "effect"
164 *
165 * const user = { name: "Alice", age: 30, [Symbol.for("id")]: 1 }
166 *
167 * const k: Array<"name" | "age"> = Struct.keys(user)
168 * k // => ["name", "age"]
169 * ```
170 *
171 * @see {@link get} – access a single key's value
172 * @see {@link pick} – select a subset of keys into a new struct
173 * @category getters
174 * @since 3.6.0
175 */
176export const keys = <S extends object>(self: S): Array<(keyof S) & string> =>
177 Object.keys(self) as Array<(keyof S) & string>
178
179/**
180 * Creates a new struct containing only the specified keys.
181 *
182 * **When to use**
183 *
184 * Use to narrow a struct down to a subset of its properties.
185 *
186 * **Gotchas**
187 *
188 * Keys not present in the struct are silently ignored.
189 *
190 * **Example** (Selecting specific properties)
191 *
192 * ```ts import.meta.vitest
193 * import { pipe, Struct } from "effect"
194 *
195 * const user = { name: "Alice", age: 30, admin: true }
196 * pipe(user, Struct.pick(["name", "age"])) // => { name: "Alice", age: 30 }
197 * ```
198 *
199 * @see {@link omit} – the inverse (exclude keys instead)
200 * @see {@link get} – extract a single value
201 * @category filtering
202 * @since 2.0.0
203 */
204export const pick: {
205 <S extends object, const Keys extends ReadonlyArray<keyof S>>(
206 keys: Keys
207 ): (self: S) => Simplify<Pick<S, Keys[number]>>
208 <S extends object, const Keys extends ReadonlyArray<keyof S>>(self: S, keys: Keys): Simplify<Pick<S, Keys[number]>>
209} = dual(
210 2,
211 <S extends object, const Keys extends ReadonlyArray<keyof S>>(self: S, keys: Keys) => {
212 return buildStruct(self, (k, v) => (hasPropertyKey(keys, k) ? [k, v] : undefined))
213 }
214)
215
216/**
217 * Creates a new struct with the specified keys removed.
218 *
219 * **When to use**
220 *
221 * Use to exclude sensitive or irrelevant fields from a struct.
222 *
223 * **Gotchas**
224 *
225 * Keys not present in the struct are silently ignored.
226 *
227 * **Example** (Removing a property)
228 *
229 * ```ts import.meta.vitest
230 * import { pipe, Struct } from "effect"
231 *
232 * const user = { name: "Alice", age: 30, password: "secret" }
233 * pipe(user, Struct.omit(["password"])) // => { name: "Alice", age: 30 }
234 * ```
235 *
236 * @see {@link pick} – the inverse (keep only specified keys)
237 * @category filtering
238 * @since 2.0.0
239 */
240export const omit: {
241 <S extends object, const Keys extends ReadonlyArray<keyof S>>(
242 keys: Keys
243 ): (self: S) => Simplify<Omit<S, Keys[number]>>
244 <S extends object, const Keys extends ReadonlyArray<keyof S>>(self: S, keys: Keys): Simplify<Omit<S, Keys[number]>>
245} = dual(
246 2,
247 <S extends object, Keys extends ReadonlyArray<keyof S>>(self: S, keys: Keys) => {
248 return buildStruct(self, (k, v) => (!hasPropertyKey(keys, k) ? [k, v] : undefined))
249 }
250)
251
252/**
253 * Merges two structs into a new struct. When both structs share a key, the
254 * value from `that` (the second struct) wins.
255 *
256 * **When to use**
257 *
258 * Use when you want `{ ...self, ...that }` with proper types.
259 *
260 * **Details**
261 *
262 * The result type is `Simplify<Assign<S, O>>`.
263 *
264 * **Example** (Merging structs with overlapping keys)
265 *
266 * ```ts import.meta.vitest
267 * import { pipe, Struct } from "effect"
268 *
269 * const defaults = { theme: "light", lang: "en" }
270 * const overrides = { theme: "dark", fontSize: 14 }
271 * pipe(defaults, Struct.assign(overrides)) // => { theme: "dark", lang: "en", fontSize: 14 }
272 * ```
273 *
274 * @see {@link Assign} – the type-level equivalent
275 * @see {@link evolve} – transform individual values instead of replacing them
276 * @category combining
277 * @since 4.0.0
278 */
279export const assign: {
280 <O extends object>(that: O): <S extends object>(self: S) => Assign<S, O>
281 <O extends object, S extends object>(self: S, that: O): Assign<S, O>
282} = dual(
283 2,
284 <O extends object, S extends object>(self: S, that: O) => {
285 return { ...self, ...that }
286 }
287)
288
289type Evolver<S> = { readonly [K in keyof S]?: (a: S[K]) => unknown }
290
291type Evolved<S, E> = Simplify<
292 { [K in keyof S]: K extends keyof E ? (E[K] extends (...a: any) => infer R ? R : S[K]) : S[K] }
293>
294
295/**
296 * Transforms values of a struct selectively using per-key functions. Keys
297 * without a corresponding function are copied unchanged.
298 *
299 * **When to use**
300 *
301 * Use when you want to update specific fields while keeping the rest intact.
302 *
303 * **Details**
304 *
305 * Each transform function receives the current value and returns the new value;
306 * the return type can differ from the input type.
307 *
308 * **Example** (Transforming selected values)
309 *
310 * ```ts import.meta.vitest
311 * import { pipe, Struct } from "effect"
312 *
313 * const result = pipe(
314 * { name: "alice", age: 30, active: true },
315 * Struct.evolve({
316 * name: (s) => s.toUpperCase(),
317 * age: (n) => n + 1
318 * })
319 * )
320 * result // => { name: "ALICE", age: 31, active: true }
321 * ```
322 *
323 * @see {@link evolveKeys} – transform keys instead of values
324 * @see {@link evolveEntries} – transform both keys and values
325 * @see {@link map} – apply the same transformation to all values
326 * @category transforming
327 * @since 2.0.0
328 */
329export const evolve: {
330 <S extends object, E extends Evolver<S>>(e: E): (self: S) => Evolved<S, E>
331 <S extends object, E extends Evolver<S>>(self: S, e: E): Evolved<S, E>
332} = dual(
333 2,
334 <S extends object, E extends Evolver<S>>(self: S, e: E): Evolved<S, E> => {
335 return buildStruct(self, (k, v) => [k, Object.hasOwn(e, k) ? (e as any)[k](v) : v])
336 }
337)
338
339type KeyEvolver<S> = { readonly [K in keyof S]?: (k: K) => PropertyKey }
340
341type KeyEvolved<S, E> = Simplify<
342 { [K in keyof S as K extends keyof E ? (E[K] extends ((k: K) => infer R extends PropertyKey) ? R : K) : K]: S[K] }
343>
344
345/**
346 * Transforms keys of a struct selectively using per-key functions. Keys without
347 * a corresponding function are copied unchanged.
348 *
349 * **When to use**
350 *
351 * Use when you need computed key names, such as uppercasing or prefixing.
352 *
353 * **Details**
354 *
355 * Each transform function receives the key name and must return a new
356 * `PropertyKey`.
357 *
358 * **Example** (Renaming keys with functions)
359 *
360 * ```ts import.meta.vitest
361 * import { pipe, Struct } from "effect"
362 *
363 * const result = pipe(
364 * { name: "Alice", age: 30 },
365 * Struct.evolveKeys({
366 * name: (k) => k.toUpperCase()
367 * })
368 * )
369 * result // => { NAME: "Alice", age: 30 }
370 * ```
371 *
372 * @see {@link renameKeys} – rename keys with a static mapping
373 * @see {@link evolve} – transform values instead of keys
374 * @see {@link evolveEntries} – transform both keys and values
375 * @category transforming
376 * @since 4.0.0
377 */
378export const evolveKeys: {
379 <S extends object, E extends KeyEvolver<S>>(e: E): (self: S) => KeyEvolved<S, E>
380 <S extends object, E extends KeyEvolver<S>>(self: S, e: E): KeyEvolved<S, E>
381} = dual(
382 2,
383 <S extends object, E extends KeyEvolver<S>>(self: S, e: E): KeyEvolved<S, E> => {
384 return buildStruct(self, (k, v) => [Object.hasOwn(e, k) ? (e as any)[k](k) : k, v])
385 }
386)
387
388type EntryEvolver<S> = { readonly [K in keyof S]?: (k: K, v: S[K]) => [PropertyKey, unknown] }
389
390type EntryEvolved<S, E> = {
391 [
392 K in keyof S as K extends keyof E ?
393 E[K] extends ((k: K, v: S[K]) => [infer NK extends PropertyKey, infer _V]) ? NK : K
394 : K
395 ]: K extends keyof E ? E[K] extends ((k: K, v: S[K]) => [infer _NK, infer V]) ? V
396 : S[K] :
397 S[K]
398}
399
400/**
401 * Transforms both keys and values of a struct selectively. Each per-key
402 * function receives `(key, value)` and must return a `[newKey, newValue]`
403 * tuple. Keys without a corresponding function are copied unchanged.
404 *
405 * **When to use**
406 *
407 * Use when you need to rename a key and change its value in one step.
408 *
409 * **Details**
410 *
411 * The return type is fully tracked at the type level.
412 *
413 * **Example** (Transforming keys and values together)
414 *
415 * ```ts import.meta.vitest
416 * import { pipe, Struct } from "effect"
417 *
418 * const result = pipe(
419 * { amount: 100, label: "total" },
420 * Struct.evolveEntries({
421 * amount: (k, v) => [`${k}Cents`, v * 100],
422 * label: (k, v) => [k, v.toUpperCase()]
423 * })
424 * )
425 * result // => { amountCents: 10000, label: "TOTAL" }
426 * ```
427 *
428 * @see {@link evolve} – transform values only
429 * @see {@link evolveKeys} – transform keys only
430 * @category transforming
431 * @since 4.0.0
432 */
433export const evolveEntries: {
434 <S extends object, E extends EntryEvolver<S>>(e: E): (self: S) => EntryEvolved<S, E>
435 <S extends object, E extends EntryEvolver<S>>(self: S, e: E): EntryEvolved<S, E>
436} = dual(
437 2,
438 <S extends object, E extends EntryEvolver<S>>(self: S, e: E): EntryEvolved<S, E> => {
439 return buildStruct(self, (k, v) => (Object.hasOwn(e, k) ? (e as any)[k](k, v) : [k, v]))
440 }
441)
442
443/**
444 * Renames keys in a struct using a static `{ oldKey: newKey }` mapping. Keys
445 * not mentioned in the mapping are copied unchanged.
446 *
447 * **When to use**
448 *
449 * Use when you need simple, declarative key renaming without custom logic.
450 *
451 * **Details**
452 *
453 * For computed key names, use {@link evolveKeys} instead.
454 *
455 * **Example** (Renaming keys)
456 *
457 * ```ts import.meta.vitest
458 * import { pipe, Struct } from "effect"
459 *
460 * const result = pipe(
461 * { firstName: "Alice", lastName: "Smith", age: 30 },
462 * Struct.renameKeys({ firstName: "first", lastName: "last" })
463 * )
464 * result // => { first: "Alice", last: "Smith", age: 30 }
465 * ```
466 *
467 * @see {@link evolveKeys} – rename keys using functions
468 * @see {@link evolveEntries} – rename keys and transform values
469 * @category transforming
470 * @since 4.0.0
471 */
472export const renameKeys: {
473 <S extends object, const M extends { readonly [K in keyof S]?: PropertyKey }>(
474 mapping: M
475 ): (self: S) => { [K in keyof S as K extends keyof M ? M[K] extends PropertyKey ? M[K] : K : K]: S[K] }
476 <S extends object, const M extends { readonly [K in keyof S]?: PropertyKey }>(
477 self: S,
478 mapping: M
479 ): { [K in keyof S as K extends keyof M ? M[K] extends PropertyKey ? M[K] : K : K]: S[K] }
480} = dual(2, <S extends object, const M extends { readonly [K in keyof S]?: PropertyKey }>(self: S, mapping: M) => {
481 return buildStruct(self, (k, v) => [Object.hasOwn(mapping, k) ? mapping[k]! : k, v])
482})
483
484/**
485 * Creates an `Equivalence` for a struct by providing an `Equivalence` for each
486 * property. Two structs are equivalent when all their corresponding properties
487 * are equivalent.
488 *
489 * **When to use**
490 *
491 * Use when you need equality for a record-like object to be decided field by
492 * field, with a custom equality rule for each property.
493 *
494 * **Details**
495 *
496 * This is an alias of `Equivalence.Struct`. Each property's equivalence is
497 * checked independently; all must return `true` for the overall result to be
498 * `true`.
499 *
500 * **Example** (Comparing structs for equivalence)
501 *
502 * ```ts import.meta.vitest
503 * import { Equivalence, Struct } from "effect"
504 *
505 * const PersonEquivalence = Struct.makeEquivalence({
506 * name: Equivalence.strictEqual<string>(),
507 * age: Equivalence.strictEqual<number>()
508 * })
509 *
510 * PersonEquivalence({ name: "Alice", age: 30 }, { name: "Alice", age: 30 }) // => true
511 * PersonEquivalence({ name: "Alice", age: 30 }, { name: "Bob", age: 30 }) // => false
512 * ```
513 *
514 * @see {@link makeOrder} – create an `Order` for structs
515 * @category instances
516 * @since 4.0.0
517 */
518export const makeEquivalence = Equivalence.Struct
519
520/**
521 * Creates an `Order` for a struct by providing an `Order` for each property.
522 * Properties are compared in the order they appear in the fields object; the
523 * first non-zero comparison determines the result.
524 *
525 * **When to use**
526 *
527 * Use when you need to sort record-like objects lexicographically by several
528 * fields, with each field using its own ordering rule.
529 *
530 * **Details**
531 *
532 * This is an alias of `Order.Struct`. The order of keys in the `fields` object
533 * determines comparison priority.
534 *
535 * **Example** (Ordering structs by name then age)
536 *
537 * ```ts import.meta.vitest
538 * import { Number, String, Struct } from "effect"
539 *
540 * const PersonOrder = Struct.makeOrder({
541 * name: String.Order,
542 * age: Number.Order
543 * })
544 *
545 * PersonOrder({ name: "Alice", age: 30 }, { name: "Bob", age: 25 }) // => -1
546 * ```
547 *
548 * @see {@link makeEquivalence} – create an `Equivalence` for structs
549 * @category ordering
550 * @since 4.0.0
551 */
552export const makeOrder = order.Struct
553
554/**
555 * Interface for type-level functions used by {@link map}, {@link mapPick}, and
556 * {@link mapOmit}.
557 *
558 * **When to use**
559 *
560 * Use when defining a typed function for {@link map}, {@link mapPick}, or
561 * {@link mapOmit}.
562 *
563 * **Details**
564 *
565 * Extend this interface with concrete `~lambda.in` and `~lambda.out` types to
566 * describe how a function transforms values at the type level. At runtime,
567 * create lambda values with {@link lambda}.
568 *
569 * **Example** (Defining a lambda type)
570 *
571 * ```ts import.meta.vitest
572 * import type { Struct } from "effect"
573 *
574 * interface ToString extends Struct.Lambda {
575 * readonly "~lambda.out": string
576 * }
577 *
578 * const witness: ToString = { "~lambda.in": 1, "~lambda.out": "1" }
579 * ```
580 *
581 * @see {@link Apply} – apply a Lambda to a concrete type
582 * @see {@link lambda} – create a runtime lambda value
583 * @see {@link map} – use a lambda to transform all struct values
584 * @category utility types
585 * @since 4.0.0
586 */
587export interface Lambda {
588 readonly "~lambda.in": unknown
589 readonly "~lambda.out": unknown
590}
591
592/**
593 * Applies a {@link Lambda} type-level function to a value type `V`, producing
594 * the output type.
595 *
596 * **When to use**
597 *
598 * Use when you need to compute what type a Lambda would produce for a
599 * given input.
600 *
601 * **Details**
602 *
603 * This works by intersecting the Lambda with `{ "~lambda.in": V }` and reading
604 * `"~lambda.out"`.
605 *
606 * **Example** (Computing the output type of a lambda)
607 *
608 * ```ts import.meta.vitest
609 * import type { Struct } from "effect"
610 *
611 * interface ToString extends Struct.Lambda {
612 * readonly "~lambda.out": string
613 * }
614 *
615 * // string
616 * type Result = Struct.Apply<ToString, number>
617 *
618 * const witness: Result = "value"
619 * ```
620 *
621 * @see {@link Lambda} – the base interface
622 * @category utility types
623 * @since 4.0.0
624 */
625export type Apply<L extends Lambda, V> = (L & { readonly "~lambda.in": V })["~lambda.out"]
626
627/**
628 * Wraps a plain function as a {@link Lambda} value so it can be used with
629 * {@link map}, {@link mapPick}, and {@link mapOmit}.
630 *
631 * **When to use**
632 *
633 * Use to create a typed lambda for struct mapping APIs that need type-level
634 * input and output tracking.
635 *
636 * **Details**
637 *
638 * The type parameter `L` encodes both the input and output types at the type
639 * level, allowing the compiler to track how struct value types change. At
640 * runtime, the returned value is the same function; `lambda` only adjusts the
641 * type.
642 *
643 * **Example** (Wrapping values in arrays)
644 *
645 * ```ts import.meta.vitest
646 * import { pipe, Struct } from "effect"
647 *
648 * interface AsArray extends Struct.Lambda {
649 * <A>(self: A): Array<A>
650 * readonly "~lambda.out": Array<this["~lambda.in"]>
651 * }
652 *
653 * const asArray = Struct.lambda<AsArray>((a) => [a])
654 * const result = pipe({ x: 1, y: "hello" }, Struct.map(asArray))
655 * result // => { x: [1], y: ["hello"] }
656 * ```
657 *
658 * @see {@link Lambda} – the type-level interface
659 * @see {@link map} – apply a lambda to all struct values
660 * @category constructors
661 * @since 4.0.0
662 */
663export const lambda = <L extends (a: any) => any>(
664 f: (a: Parameters<L>[0]) => ReturnType<L>
665): L => f as any
666
667/**
668 * Applies a {@link Lambda} transformation to every value in a struct.
669 *
670 * **When to use**
671 *
672 * Use when you want to apply the same function to every value in a struct.
673 *
674 * **Details**
675 *
676 * The lambda must be created with {@link lambda} so the compiler can track the
677 * output types.
678 *
679 * **Example** (Wrapping every value in an array)
680 *
681 * ```ts import.meta.vitest
682 * import { pipe, Struct } from "effect"
683 *
684 * interface AsArray extends Struct.Lambda {
685 * <A>(self: A): Array<A>
686 * readonly "~lambda.out": Array<this["~lambda.in"]>
687 * }
688 *
689 * const asArray = Struct.lambda<AsArray>((a) => [a])
690 * const result = pipe({ width: 10, height: 20 }, Struct.map(asArray))
691 * result // => { width: [10], height: [20] }
692 * ```
693 *
694 * @see {@link mapPick} – apply a lambda only to selected keys
695 * @see {@link mapOmit} – apply a lambda to all keys except selected ones
696 * @see {@link evolve} – apply different functions to different keys
697 * @category mapping
698 * @since 4.0.0
699 */
700export const map: {
701 <L extends Lambda>(
702 lambda: L
703 ): <S extends object>(self: S) => { [K in keyof S]: Apply<L, S[K]> }
704 <S extends object, L extends Lambda>(
705 self: S,
706 lambda: L
707 ): { [K in keyof S]: Apply<L, S[K]> }
708} = dual(
709 2,
710 <S extends object, L extends Function>(self: S, lambda: L) => {
711 return buildStruct(self, (k, v) => [k, lambda(v)])
712 }
713)
714
715/**
716 * Applies a {@link Lambda} transformation only to the specified keys; all
717 * other keys are copied unchanged.
718 *
719 * **When to use**
720 *
721 * Use when you want to apply the same transformation to a subset of properties.
722 *
723 * **Example** (Wrapping only selected values in arrays)
724 *
725 * ```ts import.meta.vitest
726 * import { pipe, Struct } from "effect"
727 *
728 * interface AsArray extends Struct.Lambda {
729 * <A>(self: A): Array<A>
730 * readonly "~lambda.out": Array<this["~lambda.in"]>
731 * }
732 *
733 * const asArray = Struct.lambda<AsArray>((a) => [a])
734 * const result = pipe(
735 * { x: 1, y: 2, z: 3 },
736 * Struct.mapPick(["x", "z"], asArray)
737 * )
738 * result // => { x: [1], y: 2, z: [3] }
739 * ```
740 *
741 * @see {@link map} – apply a lambda to all keys
742 * @see {@link mapOmit} – apply a lambda to all keys except selected ones
743 * @category mapping
744 * @since 4.0.0
745 */
746export const mapPick: {
747 <S extends object, const Keys extends ReadonlyArray<keyof S>, L extends Lambda>(
748 keys: Keys,
749 lambda: L
750 ): (
751 self: S
752 ) => { [K in keyof S]: K extends Keys[number] ? Apply<L, S[K]> : S[K] }
753 <S extends object, const Keys extends ReadonlyArray<keyof S>, L extends Lambda>(
754 self: S,
755 keys: Keys,
756 lambda: L
757 ): { [K in keyof S]: K extends Keys[number] ? Apply<L, S[K]> : S[K] }
758} = dual(
759 3,
760 <S extends object, const Keys extends ReadonlyArray<keyof S>, L extends Function>(
761 self: S,
762 keys: Keys,
763 lambda: L
764 ) => {
765 return buildStruct(self, (k, v) => [k, hasPropertyKey(keys, k) ? lambda(v) : v])
766 }
767)
768
769/**
770 * Applies a {@link Lambda} transformation to all keys except the specified
771 * ones; the excluded keys are copied unchanged.
772 *
773 * **When to use**
774 *
775 * Use when most keys should be transformed but a few should be preserved.
776 *
777 * **Example** (Wrapping all values except one in arrays)
778 *
779 * ```ts import.meta.vitest
780 * import { pipe, Struct } from "effect"
781 *
782 * interface AsArray extends Struct.Lambda {
783 * <A>(self: A): Array<A>
784 * readonly "~lambda.out": Array<this["~lambda.in"]>
785 * }
786 *
787 * const asArray = Struct.lambda<AsArray>((a) => [a])
788 * const result = pipe(
789 * { x: 1, y: 2, z: 3 },
790 * Struct.mapOmit(["y"], asArray)
791 * )
792 * result // => { x: [1], y: 2, z: [3] }
793 * ```
794 *
795 * @see {@link map} – apply a lambda to all keys
796 * @see {@link mapPick} – apply a lambda only to selected keys
797 * @category mapping
798 * @since 4.0.0
799 */
800export const mapOmit: {
801 <S extends object, const Keys extends ReadonlyArray<keyof S>, L extends Lambda>(
802 keys: Keys,
803 lambda: L
804 ): (
805 self: S
806 ) => { [K in keyof S]: K extends Keys[number] ? S[K] : Apply<L, S[K]> }
807 <S extends object, const Keys extends ReadonlyArray<keyof S>, L extends Lambda>(
808 self: S,
809 keys: Keys,
810 lambda: L
811 ): { [K in keyof S]: K extends Keys[number] ? S[K] : Apply<L, S[K]> }
812} = dual(
813 3,
814 <S extends object, const Keys extends ReadonlyArray<keyof S>, L extends Function>(
815 self: S,
816 keys: Keys,
817 lambda: L
818 ) => {
819 return buildStruct(self, (k, v) => [k, !hasPropertyKey(keys, k) ? lambda(v) : v])
820 }
821)
822
823function hasPropertyKey(keys: ReadonlyArray<PropertyKey>, key: PropertyKey): boolean {
824 return keys.some((candidate) => candidate === key || typeof candidate === "number" && String(candidate) === key)
825}
826
827/**
828 * Walk `source`; for each key decide what to emit via the small callback.
829 *
830 * The callback returns either
831 * • `undefined` → nothing is copied, or
832 * • `[newKey, newVal]`
833 *
834 * so every public API just supplies a different callback.
835 */
836function buildStruct<
837 S extends object,
838 f extends (k: keyof S, v: S[keyof S]) => [PropertyKey, unknown] | undefined
839>(
840 source: S,
841 f: f
842): any {
843 const out: Record<PropertyKey, unknown> = {}
844 for (const k of Reflect.ownKeys(source) as Array<keyof S>) {
845 if (!Object.prototype.propertyIsEnumerable.call(source, k)) continue
846 const res = f(k, source[k])
847 if (res) {
848 const [nk, nv] = res
849 InternalRecord.assignProperty(out, nk, nv)
850 }
851 }
852 return out
853}
854
855/**
856 * Creates a `Combiner` for a struct shape by providing a `Combiner` for each
857 * property. When two structs are combined, each property is merged using its
858 * corresponding combiner.
859 *
860 * **When to use**
861 *
862 * Use when you need to merge two same-shape records by combining each property
863 * independently, such as summing counters or concatenating strings.
864 *
865 * **Details**
866 *
867 * Pass `omitKeyWhen` to drop properties whose merged value matches a predicate,
868 * such as omitting zero counters.
869 *
870 * **Example** (Combining struct properties)
871 *
872 * ```ts import.meta.vitest
873 * import { Number, String, Struct } from "effect"
874 *
875 * const C = Struct.makeCombiner<{ readonly n: number; readonly s: string }>({
876 * n: Number.ReducerSum,
877 * s: String.ReducerConcat
878 * })
879 *
880 * C.combine({ n: 1, s: "hello" }, { n: 2, s: " world" }) // => { n: 3, s: "hello world" }
881 * ```
882 *
883 * @see {@link makeReducer} – like `makeCombiner` but with an initial value
884 * @category combining
885 * @since 4.0.0
886 */
887export function makeCombiner<A>(
888 combiners: { readonly [K in keyof A]: Combiner.Combiner<A[K]> },
889 options?: {
890 readonly omitKeyWhen?: ((a: A[keyof A]) => boolean) | undefined
891 }
892): Combiner.Combiner<A> {
893 const omitKeyWhen = options?.omitKeyWhen ?? (() => false)
894 return Combiner.make((self, that) => {
895 const keys = Reflect.ownKeys(combiners) as Array<keyof A>
896 const out = {} as A
897 for (const key of keys) {
898 const merge = combiners[key].combine(self[key], that[key])
899 if (omitKeyWhen(merge)) continue
900 InternalRecord.assignProperty(out as object, key, merge)
901 }
902 return out
903 })
904}
905
906/**
907 * Creates a `Reducer` for a struct shape by providing a `Reducer` for each
908 * property. The initial value is derived from each property's
909 * `Reducer.initialValue`. When reducing a collection of structs, each property
910 * is combined independently.
911 *
912 * **When to use**
913 *
914 * Use when you need to fold same-shape records by accumulating each property
915 * independently into one summary record.
916 *
917 * **Details**
918 *
919 * Pass `omitKeyWhen` to drop properties whose reduced value matches a
920 * predicate.
921 *
922 * **Example** (Reducing a collection of structs)
923 *
924 * ```ts import.meta.vitest
925 * import { Number, String, Struct } from "effect"
926 *
927 * const R = Struct.makeReducer<{ readonly n: number; readonly s: string }>({
928 * n: Number.ReducerSum,
929 * s: String.ReducerConcat
930 * })
931 *
932 * const result = R.combineAll([
933 * { n: 1, s: "a" },
934 * { n: 2, s: "b" },
935 * { n: 3, s: "c" }
936 * ])
937 * result // => { n: 6, s: "abc" }
938 * ```
939 *
940 * @see {@link makeCombiner} – like `makeReducer` but without an initial value
941 * @category folding
942 * @since 4.0.0
943 */
944export function makeReducer<A>(
945 reducers: { readonly [K in keyof A]: Reducer.Reducer<A[K]> },
946 options?: {
947 readonly omitKeyWhen?: ((a: A[keyof A]) => boolean) | undefined
948 }
949): Reducer.Reducer<A> {
950 const combine = makeCombiner(reducers, options).combine
951 const initialValue = {} as A
952 for (const key of Reflect.ownKeys(reducers) as Array<keyof A>) {
953 const iv = reducers[key].initialValue
954 if (options?.omitKeyWhen?.(iv)) continue
955 InternalRecord.assignProperty(initialValue as object, key, iv)
956 }
957 return Reducer.make(combine, initialValue)
958}
959
960/**
961 * Creates a record with the given keys and value.
962 *
963 * **When to use**
964 *
965 * Use to build an object where each provided key receives the same value.
966 *
967 * **Example** (Creating a record)
968 *
969 * ```ts import.meta.vitest
970 * import { Struct } from "effect"
971 *
972 * Struct.Record(["a", "b"], "value") // => { a: "value", b: "value" }
973 * ```
974 *
975 * @category constructors
976 * @since 4.0.0
977 */
978export function Record<const Keys extends ReadonlyArray<string | symbol>, Value>(
979 keys: Keys,
980 value: Value
981): Record<Keys[number], Value> {
982 const out: any = {}
983 for (const key of keys) {
984 InternalRecord.assignProperty(out, key, value)
985 }
986 return out
987}