| 1 | /** |
| 2 | * Works with plain JavaScript records as immutable key/value dictionaries. |
| 3 | * |
| 4 | * A record is an object whose keys are strings or symbols. This module includes |
| 5 | * helpers for construction, lookup, updates, mapping, filtering, folding, |
| 6 | * set-like combination, and typed conversions between records and iterable |
| 7 | * entries. Helpers that change values return new records instead of mutating the |
| 8 | * input. |
| 9 | * |
| 10 | * @since 2.0.0 |
| 11 | */ |
| 12 | |
| 13 | import type * as Combiner from "./Combiner.ts" |
| 14 | import * as Equal from "./Equal.ts" |
| 15 | import type { Equivalence } from "./Equivalence.ts" |
| 16 | import { dual, identity } from "./Function.ts" |
| 17 | import type { TypeLambda } from "./HKT.ts" |
| 18 | import * as InternalRecord from "./internal/record.ts" |
| 19 | import * as Option from "./Option.ts" |
| 20 | import * as Reducer from "./Reducer.ts" |
| 21 | import type { Result } from "./Result.ts" |
| 22 | import * as R from "./Result.ts" |
| 23 | import type { NoInfer } from "./Types.ts" |
| 24 | |
| 25 | /** |
| 26 | * Represents a readonly record with keys of type `K` and values of type `A`. |
| 27 | * This is the foundational type for immutable key-value mappings in Effect. |
| 28 | * |
| 29 | * **Example** (Defining a readonly record type) |
| 30 | * |
| 31 | * ```ts import.meta.vitest |
| 32 | * import type { Record } from "effect" |
| 33 | * |
| 34 | * // Creating a readonly record type |
| 35 | * type UserRecord = Record.ReadonlyRecord<"name" | "age", string | number> |
| 36 | * |
| 37 | * const user: UserRecord = { |
| 38 | * name: "John", |
| 39 | * age: 30 |
| 40 | * } |
| 41 | * user // => { name: "John", age: 30 } |
| 42 | * ``` |
| 43 | * |
| 44 | * @category models |
| 45 | * @since 2.0.0 |
| 46 | */ |
| 47 | export type ReadonlyRecord<in out K extends string | symbol, out A> = { |
| 48 | readonly [P in K]: A |
| 49 | } |
| 50 | |
| 51 | /** |
| 52 | * Namespace containing utility types for working with readonly records. |
| 53 | * These types help with type-level operations on record keys and values. |
| 54 | * |
| 55 | * **Example** (Using readonly record helper types) |
| 56 | * |
| 57 | * ```ts import.meta.vitest |
| 58 | * import type { Record } from "effect" |
| 59 | * |
| 60 | * // Using NonLiteralKey to convert literal keys to generic types |
| 61 | * type GenericKey = Record.ReadonlyRecord.NonLiteralKey<"foo" | "bar"> // string |
| 62 | * |
| 63 | * // Using IntersectKeys to find common keys between record types |
| 64 | * type CommonKeys = Record.ReadonlyRecord.IntersectKeys<"a" | "b", "b" | "c"> // "b" |
| 65 | * |
| 66 | * "key" satisfies GenericKey |
| 67 | * "b" satisfies CommonKeys |
| 68 | * ``` |
| 69 | * |
| 70 | * @since 2.0.0 |
| 71 | */ |
| 72 | export declare namespace ReadonlyRecord { |
| 73 | type IsFiniteString<T extends string> = T extends "" ? true : |
| 74 | [T] extends [`${infer Head}${infer Rest}`] |
| 75 | ? string extends Head ? false : `${number}` extends Head ? false : Rest extends "" ? true : IsFiniteString<Rest> |
| 76 | : false |
| 77 | |
| 78 | /** |
| 79 | * Represents a type that converts literal string keys to generic string type and symbol keys to generic symbol type. |
| 80 | * This is useful for maintaining type safety while allowing flexible key types in record operations. |
| 81 | * |
| 82 | * **Example** (Converting literal keys to non-literal keys) |
| 83 | * |
| 84 | * ```ts import.meta.vitest |
| 85 | * import type { Record } from "effect" |
| 86 | * |
| 87 | * // For literal string keys, this becomes 'string' |
| 88 | * type Example1 = Record.ReadonlyRecord.NonLiteralKey<"foo" | "bar"> // string |
| 89 | * |
| 90 | * // For symbol keys, this becomes 'symbol' |
| 91 | * type Example2 = Record.ReadonlyRecord.NonLiteralKey<symbol> // symbol |
| 92 | * |
| 93 | * const symbol: Example2 = Symbol.for("key") |
| 94 | * "key" satisfies Example1 |
| 95 | * symbol |
| 96 | * ``` |
| 97 | * |
| 98 | * @category models |
| 99 | * @since 2.0.0 |
| 100 | */ |
| 101 | export type NonLiteralKey<K extends string | symbol> = K extends string ? IsFiniteString<K> extends true ? string : K |
| 102 | : symbol |
| 103 | |
| 104 | /** |
| 105 | * Represents the intersection of two key types, handling both literal and non-literal string keys. |
| 106 | * This type is used in record operations that need to compute overlapping keys. |
| 107 | * |
| 108 | * **Example** (Intersecting record keys) |
| 109 | * |
| 110 | * ```ts import.meta.vitest |
| 111 | * import type { Record } from "effect" |
| 112 | * |
| 113 | * // Intersection of literal keys |
| 114 | * type Example1 = Record.ReadonlyRecord.IntersectKeys<"a" | "b", "b" | "c"> // "b" |
| 115 | * |
| 116 | * // Intersection with generic string |
| 117 | * type Example2 = Record.ReadonlyRecord.IntersectKeys<string, "a" | "b"> // string |
| 118 | * |
| 119 | * "b" satisfies Example1 |
| 120 | * "a" satisfies Example2 |
| 121 | * ``` |
| 122 | * |
| 123 | * @category models |
| 124 | * @since 2.0.0 |
| 125 | */ |
| 126 | export type IntersectKeys<K1 extends string, K2 extends string> = [string] extends [K1 | K2] ? |
| 127 | NonLiteralKey<K1> & NonLiteralKey<K2> |
| 128 | : K1 & K2 |
| 129 | |
| 130 | /** |
| 131 | * Represents a record produced by grouping values under keys of type `K`. |
| 132 | * |
| 133 | * **Details** |
| 134 | * |
| 135 | * Finite string or symbol key types become optional because an input may not |
| 136 | * produce every possible key. Open `string` and `symbol` key types retain their |
| 137 | * record index signatures. |
| 138 | * |
| 139 | * @category utility types |
| 140 | * @since 4.0.0 |
| 141 | */ |
| 142 | export type GroupByResult<K extends string | symbol, V> = [NonLiteralKey<K>] extends [K] ? Record<K, V> |
| 143 | : Partial<Record<K, V>> |
| 144 | } |
| 145 | |
| 146 | /** |
| 147 | * Type lambda for readonly records, used in higher-kinded type operations. |
| 148 | * This enables records to work with generic type constructors and functors. |
| 149 | * |
| 150 | * **Example** (Applying a readonly record type lambda) |
| 151 | * |
| 152 | * ```ts import.meta.vitest |
| 153 | * import type { HKT, Record } from "effect" |
| 154 | * |
| 155 | * type Settings = HKT.Kind< |
| 156 | * Record.ReadonlyRecordTypeLambda<"port" | "retries">, |
| 157 | * never, |
| 158 | * never, |
| 159 | * never, |
| 160 | * number |
| 161 | * > |
| 162 | * |
| 163 | * const defaults: Settings = { |
| 164 | * port: 3000, |
| 165 | * retries: 3 |
| 166 | * } |
| 167 | * defaults // => { port: 3000, retries: 3 } |
| 168 | * ``` |
| 169 | * |
| 170 | * @category utility types |
| 171 | * @since 2.0.0 |
| 172 | */ |
| 173 | export interface ReadonlyRecordTypeLambda<K extends string = string> extends TypeLambda { |
| 174 | readonly type: ReadonlyRecord<K, this["Target"]> |
| 175 | } |
| 176 | |
| 177 | /** |
| 178 | * Creates a new, empty record. |
| 179 | * |
| 180 | * **Example** (Creating an empty record) |
| 181 | * |
| 182 | * ```ts import.meta.vitest |
| 183 | * import { Record } from "effect" |
| 184 | * |
| 185 | * // Create an empty record |
| 186 | * const emptyRecord = Record.empty<string, number>() |
| 187 | * emptyRecord // => {} |
| 188 | * |
| 189 | * // The type ensures type safety for future operations |
| 190 | * Record.set(emptyRecord, "count", 42) // => { count: 42 } |
| 191 | * ``` |
| 192 | * |
| 193 | * @category constructors |
| 194 | * @since 2.0.0 |
| 195 | */ |
| 196 | export const empty = <K extends string | symbol = never, V = never>(): Record< |
| 197 | ReadonlyRecord.NonLiteralKey<K>, |
| 198 | V |
| 199 | > => ({} as any) |
| 200 | |
| 201 | /** |
| 202 | * Determines if a mutable record is empty. |
| 203 | * |
| 204 | * **Example** (Checking for an empty record) |
| 205 | * |
| 206 | * ```ts import.meta.vitest |
| 207 | * import { Record } from "effect" |
| 208 | * |
| 209 | * Record.isEmptyRecord({}) // => true |
| 210 | * Record.isEmptyRecord({ a: 3 }) // => false |
| 211 | * ``` |
| 212 | * |
| 213 | * @category guards |
| 214 | * @since 2.0.0 |
| 215 | */ |
| 216 | export const isEmptyRecord = <K extends string, A>(self: Record<K, A>): self is Record<K, never> => |
| 217 | Object.keys(self).length === 0 |
| 218 | |
| 219 | /** |
| 220 | * Determines if a readonly record is empty. |
| 221 | * |
| 222 | * **Example** (Checking for an empty readonly record) |
| 223 | * |
| 224 | * ```ts import.meta.vitest |
| 225 | * import { Record } from "effect" |
| 226 | * |
| 227 | * Record.isEmptyReadonlyRecord({}) // => true |
| 228 | * Record.isEmptyReadonlyRecord({ a: 3 }) // => false |
| 229 | * ``` |
| 230 | * |
| 231 | * @category guards |
| 232 | * @since 2.0.0 |
| 233 | */ |
| 234 | export const isEmptyReadonlyRecord: <K extends string, A>( |
| 235 | self: ReadonlyRecord<K, A> |
| 236 | ) => self is ReadonlyRecord<K, never> = isEmptyRecord |
| 237 | |
| 238 | /** |
| 239 | * Takes an iterable and a projection function and returns a record. |
| 240 | * The projection function maps each value of the iterable to a tuple of a key and a value, which is then added to the resulting record. |
| 241 | * |
| 242 | * **Example** (Building a record from mapped iterable values) |
| 243 | * |
| 244 | * ```ts import.meta.vitest |
| 245 | * import { Record } from "effect" |
| 246 | * |
| 247 | * Record.fromIterableWith([1, 2, 3, 4], (a) => [String(a), a * 2]) // => { "1": 2, "2": 4, "3": 6, "4": 8 } |
| 248 | * ``` |
| 249 | * |
| 250 | * @category constructors |
| 251 | * @since 2.0.0 |
| 252 | */ |
| 253 | export const fromIterableWith: { |
| 254 | <A, K extends string | symbol, B>( |
| 255 | f: (a: A) => readonly [K, B] |
| 256 | ): (self: Iterable<A>) => Record<ReadonlyRecord.NonLiteralKey<K>, B> |
| 257 | <A, K extends string | symbol, B>( |
| 258 | self: Iterable<A>, |
| 259 | f: (a: A) => readonly [K, B] |
| 260 | ): Record<ReadonlyRecord.NonLiteralKey<K>, B> |
| 261 | } = dual( |
| 262 | 2, |
| 263 | <A, K extends string, B>( |
| 264 | self: Iterable<A>, |
| 265 | f: (a: A) => readonly [K, B] |
| 266 | ): Record<ReadonlyRecord.NonLiteralKey<K>, B> => { |
| 267 | const out: Record<string, B> = empty() |
| 268 | for (const a of self) { |
| 269 | const [k, b] = f(a) |
| 270 | InternalRecord.assignProperty(out, k, b) |
| 271 | } |
| 272 | return out |
| 273 | } |
| 274 | ) |
| 275 | |
| 276 | /** |
| 277 | * Creates a new record from an iterable, utilizing the provided function to determine the key for each element. |
| 278 | * |
| 279 | * **Example** (Building a record keyed by iterable values) |
| 280 | * |
| 281 | * ```ts import.meta.vitest |
| 282 | * import { Record } from "effect" |
| 283 | * |
| 284 | * const users = [ |
| 285 | * { id: "2", name: "name2" }, |
| 286 | * { id: "1", name: "name1" } |
| 287 | * ] |
| 288 | * |
| 289 | * Record.fromIterableBy( |
| 290 | * users, |
| 291 | * (user) => user.id |
| 292 | * ) // => { "1": { id: "1", name: "name1" }, "2": { id: "2", name: "name2" } } |
| 293 | * ``` |
| 294 | * |
| 295 | * @category constructors |
| 296 | * @since 2.0.0 |
| 297 | */ |
| 298 | export const fromIterableBy: { |
| 299 | <A, K extends string | symbol>( |
| 300 | f: (a: A) => K |
| 301 | ): (items: Iterable<A>) => Record<ReadonlyRecord.NonLiteralKey<K>, A> |
| 302 | <A, K extends string | symbol>( |
| 303 | items: Iterable<A>, |
| 304 | f: (a: A) => K |
| 305 | ): Record<ReadonlyRecord.NonLiteralKey<K>, A> |
| 306 | } = dual( |
| 307 | 2, |
| 308 | <A, K extends string | symbol>( |
| 309 | items: Iterable<A>, |
| 310 | f: (a: A) => K |
| 311 | ): Record<ReadonlyRecord.NonLiteralKey<K>, A> => fromIterableWith(items, (a) => [f(a), a]) |
| 312 | ) |
| 313 | |
| 314 | /** |
| 315 | * Builds a record from an iterable of key-value pairs. |
| 316 | * |
| 317 | * **Details** |
| 318 | * |
| 319 | * If there are conflicting keys when using `fromEntries`, the last occurrence of the key/value pair will overwrite the |
| 320 | * previous ones. So the resulting record will only have the value of the last occurrence of each key. |
| 321 | * |
| 322 | * **Example** (Building a record from entries) |
| 323 | * |
| 324 | * ```ts import.meta.vitest |
| 325 | * import { Record } from "effect" |
| 326 | * |
| 327 | * Record.fromEntries([["a", 1], ["b", 2]]) // => { a: 1, b: 2 } |
| 328 | * ``` |
| 329 | * |
| 330 | * @category constructors |
| 331 | * @since 2.0.0 |
| 332 | */ |
| 333 | export const fromEntries: <Entry extends readonly [string | symbol, any]>( |
| 334 | entries: Iterable<Entry> |
| 335 | ) => Record<ReadonlyRecord.NonLiteralKey<Entry[0]>, Entry[1]> = Object.fromEntries |
| 336 | |
| 337 | /** |
| 338 | * Transforms the values of a record into an `Array` with a custom mapping function. |
| 339 | * |
| 340 | * **Example** (Collecting mapped record values) |
| 341 | * |
| 342 | * ```ts import.meta.vitest |
| 343 | * import { Record } from "effect" |
| 344 | * |
| 345 | * const x = { a: 1, b: 2, c: 3 } |
| 346 | * Record.collect(x, (key, n) => [key, n]) // => [["a", 1], ["b", 2], ["c", 3]] |
| 347 | * ``` |
| 348 | * |
| 349 | * @category converting |
| 350 | * @since 2.0.0 |
| 351 | */ |
| 352 | export const collect: { |
| 353 | <K extends string, A, B>(f: (key: K, a: A) => B): (self: ReadonlyRecord<K, A>) => Array<B> |
| 354 | <K extends string, A, B>(self: ReadonlyRecord<K, A>, f: (key: K, a: A) => B): Array<B> |
| 355 | } = dual( |
| 356 | 2, |
| 357 | <K extends string, A, B>(self: ReadonlyRecord<K, A>, f: (key: K, a: A) => B): Array<B> => { |
| 358 | const out: Array<B> = [] |
| 359 | for (const key of keys(self)) { |
| 360 | out.push(f(key, self[key])) |
| 361 | } |
| 362 | return out |
| 363 | } |
| 364 | ) |
| 365 | |
| 366 | /** |
| 367 | * Takes a record and returns an array of tuples containing its keys and values. |
| 368 | * |
| 369 | * **Example** (Converting a record to entries) |
| 370 | * |
| 371 | * ```ts import.meta.vitest |
| 372 | * import { Record } from "effect" |
| 373 | * |
| 374 | * const x = { a: 1, b: 2, c: 3 } |
| 375 | * Record.toEntries(x) // => [["a", 1], ["b", 2], ["c", 3]] |
| 376 | * ``` |
| 377 | * |
| 378 | * @category converting |
| 379 | * @since 2.0.0 |
| 380 | */ |
| 381 | export const toEntries: <K extends string, A>(self: ReadonlyRecord<K, A>) => Array<[K, A]> = collect(( |
| 382 | key, |
| 383 | value |
| 384 | ) => [key, value]) |
| 385 | |
| 386 | /** |
| 387 | * Returns the number of key/value pairs in a record. |
| 388 | * |
| 389 | * **Example** (Getting the record size) |
| 390 | * |
| 391 | * ```ts import.meta.vitest |
| 392 | * import { Record } from "effect" |
| 393 | * |
| 394 | * Record.size({ a: "a", b: 1, c: true }) // => 3 |
| 395 | * ``` |
| 396 | * |
| 397 | * @category getters |
| 398 | * @since 2.0.0 |
| 399 | */ |
| 400 | export const size = <K extends string, A>(self: ReadonlyRecord<K, A>): number => keys(self).length |
| 401 | |
| 402 | /** |
| 403 | * Checks whether a given `key` exists in a record. |
| 404 | * |
| 405 | * **Example** (Checking key membership) |
| 406 | * |
| 407 | * ```ts import.meta.vitest |
| 408 | * import { Record } from "effect" |
| 409 | * |
| 410 | * Record.has({ a: 1, b: 2 }, "a") // => true |
| 411 | * Record.has(Record.empty<string>(), "c") // => false |
| 412 | * ``` |
| 413 | * |
| 414 | * @category predicates |
| 415 | * @since 2.0.0 |
| 416 | */ |
| 417 | export const has: { |
| 418 | <K extends string | symbol>( |
| 419 | key: NoInfer<K> |
| 420 | ): <A>(self: ReadonlyRecord<K, A>) => boolean |
| 421 | <K extends string | symbol, A>( |
| 422 | self: ReadonlyRecord<K, A>, |
| 423 | key: NoInfer<K> |
| 424 | ): boolean |
| 425 | } = dual( |
| 426 | 2, |
| 427 | <K extends string | symbol, A>( |
| 428 | self: ReadonlyRecord<K, A>, |
| 429 | key: NoInfer<K> |
| 430 | ): boolean => Object.hasOwn(self, key) |
| 431 | ) |
| 432 | |
| 433 | /** |
| 434 | * Retrieves a value at a particular key from a record safely, returning it wrapped in an `Option`. |
| 435 | * |
| 436 | * **Example** (Getting a value as an Option) |
| 437 | * |
| 438 | * ```ts import.meta.vitest |
| 439 | * import { Option, Record as R } from "effect" |
| 440 | * |
| 441 | * const person: Record<string, unknown> = { name: "John Doe", age: 35 } |
| 442 | * |
| 443 | * R.get(person, "name") // => Option.some("John Doe") |
| 444 | * R.get(person, "email") // => Option.none() |
| 445 | * ``` |
| 446 | * |
| 447 | * @category getters |
| 448 | * @since 2.0.0 |
| 449 | */ |
| 450 | export const get: { |
| 451 | <K extends string | symbol>(key: NoInfer<K>): <A>(self: ReadonlyRecord<K, A>) => Option.Option<A> |
| 452 | <K extends string | symbol, A>(self: ReadonlyRecord<K, A>, key: NoInfer<K>): Option.Option<A> |
| 453 | } = dual( |
| 454 | 2, |
| 455 | <K extends string | symbol, A>(self: ReadonlyRecord<K, A>, key: NoInfer<K>): Option.Option<A> => |
| 456 | Object.hasOwn(self, key) ? Option.some(self[key]) : Option.none() |
| 457 | ) |
| 458 | |
| 459 | /** |
| 460 | * Applies a function to the element at the specified key safely, creating a new record, |
| 461 | * or return `Option.none()` if the key doesn't exist. |
| 462 | * |
| 463 | * **Example** (Modifying a value at a key) |
| 464 | * |
| 465 | * ```ts import.meta.vitest |
| 466 | * import { Option, Record } from "effect" |
| 467 | * |
| 468 | * const f = (x: number) => x * 2 |
| 469 | * |
| 470 | * const input: Record<string, number> = { a: 3 } |
| 471 | * |
| 472 | * Record.modify(input, "a", f) // => Option.some({ a: 6 }) |
| 473 | * Record.modify(input, "b", f) // => Option.none() |
| 474 | * ``` |
| 475 | * |
| 476 | * @category mutations |
| 477 | * @since 2.0.0 |
| 478 | */ |
| 479 | export const modify: { |
| 480 | <K extends string | symbol, A, B>( |
| 481 | key: NoInfer<K>, |
| 482 | f: (a: A) => B |
| 483 | ): (self: ReadonlyRecord<K, A>) => Option.Option<Record<K, A | B>> |
| 484 | <K extends string | symbol, A, B>( |
| 485 | self: ReadonlyRecord<K, A>, |
| 486 | key: NoInfer<K>, |
| 487 | f: (a: A) => B |
| 488 | ): Option.Option<Record<K, A | B>> |
| 489 | } = dual( |
| 490 | 3, |
| 491 | <K extends string | symbol, A, B>( |
| 492 | self: ReadonlyRecord<K, A>, |
| 493 | key: NoInfer<K>, |
| 494 | f: (a: A) => B |
| 495 | ): Option.Option<Record<K, A | B>> => { |
| 496 | if (!has(self, key)) return Option.none() |
| 497 | return Option.some({ ...self, [key]: f(self[key]) }) |
| 498 | } |
| 499 | ) |
| 500 | |
| 501 | /** |
| 502 | * Replaces the value at an existing key safely and returns the updated record in |
| 503 | * `Option.some`. |
| 504 | * |
| 505 | * **Details** |
| 506 | * |
| 507 | * If the key is not present, returns `Option.none()` and leaves the record |
| 508 | * unchanged. |
| 509 | * |
| 510 | * **Example** (Replacing a value at a key) |
| 511 | * |
| 512 | * ```ts import.meta.vitest |
| 513 | * import { Option, Record } from "effect" |
| 514 | * |
| 515 | * Record.replace({ a: 1, b: 2, c: 3 }, "a", 10) // => Option.some({ a: 10, b: 2, c: 3 }) |
| 516 | * Record.replace(Record.empty<string>(), "a", 10) // => Option.none() |
| 517 | * ``` |
| 518 | * |
| 519 | * @category mutations |
| 520 | * @since 2.0.0 |
| 521 | */ |
| 522 | export const replace: { |
| 523 | <K extends string | symbol, B>( |
| 524 | key: NoInfer<K>, |
| 525 | b: B |
| 526 | ): <A>(self: ReadonlyRecord<K, A>) => Option.Option<Record<K, A | B>> |
| 527 | <K extends string | symbol, A, B>( |
| 528 | self: ReadonlyRecord<K, A>, |
| 529 | key: NoInfer<K>, |
| 530 | b: B |
| 531 | ): Option.Option<Record<K, A | B>> |
| 532 | } = dual( |
| 533 | 3, |
| 534 | <K extends string | symbol, A, B>( |
| 535 | self: ReadonlyRecord<K, A>, |
| 536 | key: NoInfer<K>, |
| 537 | b: B |
| 538 | ): Option.Option<Record<K, A | B>> => modify(self, key, () => b) |
| 539 | ) |
| 540 | |
| 541 | /** |
| 542 | * Removes a key from a record. |
| 543 | * |
| 544 | * **When to use** |
| 545 | * |
| 546 | * Use to create a shallow copy of a record without one property. |
| 547 | * |
| 548 | * **Details** |
| 549 | * |
| 550 | * If the key is not present, the result is still a shallow copy of the original |
| 551 | * record. |
| 552 | * |
| 553 | * **Example** (Removing a key) |
| 554 | * |
| 555 | * ```ts import.meta.vitest |
| 556 | * import { Record } from "effect" |
| 557 | * |
| 558 | * Record.remove({ a: 1, b: 2 }, "a") // => { b: 2 } |
| 559 | * ``` |
| 560 | * |
| 561 | * @category mutations |
| 562 | * @since 2.0.0 |
| 563 | */ |
| 564 | export const remove: { |
| 565 | <K extends string | symbol, X extends K>(key: X): <A>(self: ReadonlyRecord<K, A>) => Record<Exclude<K, X>, A> |
| 566 | <K extends string | symbol, A, X extends K>(self: ReadonlyRecord<K, A>, key: X): Record<Exclude<K, X>, A> |
| 567 | } = dual( |
| 568 | 2, |
| 569 | <K extends string | symbol, A, X extends K>(self: ReadonlyRecord<K, A>, key: X): Record<Exclude<K, X>, A> => { |
| 570 | if (!has(self, key)) { |
| 571 | return { ...self } |
| 572 | } |
| 573 | const out = { ...self } |
| 574 | delete out[key] |
| 575 | return out |
| 576 | } |
| 577 | ) |
| 578 | |
| 579 | /** |
| 580 | * Retrieves the value of the property with the given `key` from a record safely and returns an `Option` |
| 581 | * of a tuple with the value and the record with the removed property. |
| 582 | * If the key is not present, returns `Option.none()`. |
| 583 | * |
| 584 | * **Example** (Popping a value and removing its key) |
| 585 | * |
| 586 | * ```ts import.meta.vitest |
| 587 | * import { Option, Record } from "effect" |
| 588 | * |
| 589 | * const input: Record<string, number> = { a: 1, b: 2 } |
| 590 | * |
| 591 | * Record.pop(input, "a") // => Option.some([1, { b: 2 }]) |
| 592 | * Record.pop(input, "c") // => Option.none() |
| 593 | * ``` |
| 594 | * |
| 595 | * @category mutations |
| 596 | * @since 2.0.0 |
| 597 | */ |
| 598 | export const pop: { |
| 599 | <K extends string | symbol, X extends K>( |
| 600 | key: X |
| 601 | ): <A>(self: ReadonlyRecord<K, A>) => Option.Option<[A, Record<Exclude<K, X>, A>]> |
| 602 | <K extends string | symbol, A, X extends K>( |
| 603 | self: ReadonlyRecord<K, A>, |
| 604 | key: X |
| 605 | ): Option.Option<[A, Record<Exclude<K, X>, A>]> |
| 606 | } = dual(2, <K extends string | symbol, A, X extends K>( |
| 607 | self: ReadonlyRecord<K, A>, |
| 608 | key: X |
| 609 | ): Option.Option<[A, Record<Exclude<K, X>, A>]> => |
| 610 | has(self, key) ? Option.some([self[key], remove(self, key)]) : Option.none()) |
| 611 | |
| 612 | /** |
| 613 | * Maps a record into another record by applying a transformation function to each of its values. |
| 614 | * |
| 615 | * **Example** (Mapping record values) |
| 616 | * |
| 617 | * ```ts import.meta.vitest |
| 618 | * import { Record } from "effect" |
| 619 | * |
| 620 | * const f = (n: number) => `-${n}` |
| 621 | * |
| 622 | * Record.map({ a: 3, b: 5 }, f) // => { a: "-3", b: "-5" } |
| 623 | * |
| 624 | * const g = (n: number, key: string) => `${key.toUpperCase()}-${n}` |
| 625 | * |
| 626 | * Record.map({ a: 3, b: 5 }, g) // => { a: "A-3", b: "B-5" } |
| 627 | * ``` |
| 628 | * |
| 629 | * @category mapping |
| 630 | * @since 2.0.0 |
| 631 | */ |
| 632 | export const map: { |
| 633 | <K extends string, A, B>(f: (a: A, key: NoInfer<K>) => B): (self: ReadonlyRecord<K, A>) => Record<K, B> |
| 634 | <K extends string, A, B>(self: ReadonlyRecord<K, A>, f: (a: A, key: NoInfer<K>) => B): Record<K, B> |
| 635 | } = dual( |
| 636 | 2, |
| 637 | <K extends string, A, B>(self: ReadonlyRecord<K, A>, f: (a: A, key: NoInfer<K>) => B): Record<K, B> => { |
| 638 | const out: Record<K, B> = { ...self } as any |
| 639 | for (const key of keys(self)) { |
| 640 | InternalRecord.assignProperty(out, key, f(self[key], key)) |
| 641 | } |
| 642 | return out |
| 643 | } |
| 644 | ) |
| 645 | |
| 646 | /** |
| 647 | * Maps the keys of a `ReadonlyRecord` while preserving the corresponding values. |
| 648 | * |
| 649 | * **Example** (Mapping record keys) |
| 650 | * |
| 651 | * ```ts import.meta.vitest |
| 652 | * import { Record } from "effect" |
| 653 | * |
| 654 | * Record.mapKeys({ a: 3, b: 5 }, (key) => key.toUpperCase()) // => { A: 3, B: 5 } |
| 655 | * ``` |
| 656 | * |
| 657 | * @category mapping |
| 658 | * @since 2.0.0 |
| 659 | */ |
| 660 | export const mapKeys: { |
| 661 | <K extends string, A, K2 extends string>( |
| 662 | f: (key: K, a: A) => K2 |
| 663 | ): (self: ReadonlyRecord<K, A>) => Record<K2, A> |
| 664 | <K extends string, A, K2 extends string>( |
| 665 | self: ReadonlyRecord<K, A>, |
| 666 | f: (key: K, a: A) => K2 |
| 667 | ): Record<K2, A> |
| 668 | } = dual( |
| 669 | 2, |
| 670 | <K extends string, A, K2 extends string>( |
| 671 | self: ReadonlyRecord<K, A>, |
| 672 | f: (key: K, a: A) => K2 |
| 673 | ): Record<K2, A> => { |
| 674 | const out: Record<K2, A> = {} as any |
| 675 | for (const key of keys(self)) { |
| 676 | const a = self[key] |
| 677 | InternalRecord.assignProperty(out, f(key, a), a) |
| 678 | } |
| 679 | return out |
| 680 | } |
| 681 | ) |
| 682 | |
| 683 | /** |
| 684 | * Maps entries of a `ReadonlyRecord` using the provided function, allowing modification of both keys and corresponding values. |
| 685 | * |
| 686 | * **Example** (Mapping record entries) |
| 687 | * |
| 688 | * ```ts import.meta.vitest |
| 689 | * import { Record } from "effect" |
| 690 | * |
| 691 | * Record.mapEntries({ a: 3, b: 5 }, (a, key) => [key.toUpperCase(), a + 1]) // => { A: 4, B: 6 } |
| 692 | * ``` |
| 693 | * |
| 694 | * @category mapping |
| 695 | * @since 2.0.0 |
| 696 | */ |
| 697 | export const mapEntries: { |
| 698 | <K extends string, A, K2 extends string, B>( |
| 699 | f: (a: A, key: K) => readonly [K2, B] |
| 700 | ): (self: ReadonlyRecord<K, A>) => Record<K2, B> |
| 701 | <K extends string, A, K2 extends string, B>( |
| 702 | self: ReadonlyRecord<K, A>, |
| 703 | f: (a: A, key: K) => [K2, B] |
| 704 | ): Record<K2, B> |
| 705 | } = dual( |
| 706 | 2, |
| 707 | <K extends string, A, K2 extends string, B>( |
| 708 | self: ReadonlyRecord<K, A>, |
| 709 | f: (a: A, key: K) => [K2, B] |
| 710 | ): Record<K2, B> => { |
| 711 | const out = {} as Record<K2, B> |
| 712 | for (const key of keys(self)) { |
| 713 | const [k, b] = f(self[key], key) |
| 714 | InternalRecord.assignProperty(out, k, b) |
| 715 | } |
| 716 | return out |
| 717 | } |
| 718 | ) |
| 719 | |
| 720 | /** |
| 721 | * Transforms a record by applying the function `f` to each key and value in the original record. |
| 722 | * If the function succeeds, the key-value pair is included in the output record. |
| 723 | * |
| 724 | * **Example** (Filtering and mapping with Result) |
| 725 | * |
| 726 | * ```ts import.meta.vitest |
| 727 | * import { Record, Result } from "effect" |
| 728 | * |
| 729 | * const x = { a: 1, b: 2, c: 3 } |
| 730 | * const f = (a: number, key: string) => a > 2 ? Result.succeed(a * 2) : Result.failVoid |
| 731 | * Record.filterMap(x, f) // => { c: 6 } |
| 732 | * ``` |
| 733 | * |
| 734 | * @category filtering |
| 735 | * @since 2.0.0 |
| 736 | */ |
| 737 | export const filterMap: { |
| 738 | <K extends string, A, B, X>( |
| 739 | f: (input: A, key: K) => Result<B, X> |
| 740 | ): (self: ReadonlyRecord<K, A>) => Record<ReadonlyRecord.NonLiteralKey<K>, B> |
| 741 | <K extends string, A, B, X>( |
| 742 | self: ReadonlyRecord<K, A>, |
| 743 | f: (input: A, key: K) => Result<B, X> |
| 744 | ): Record<ReadonlyRecord.NonLiteralKey<K>, B> |
| 745 | } = dual( |
| 746 | 2, |
| 747 | <K extends string, A, B, X>( |
| 748 | self: ReadonlyRecord<K, A>, |
| 749 | f: (input: A, key: K) => Result<B, X> |
| 750 | ): Record<ReadonlyRecord.NonLiteralKey<K>, B> => { |
| 751 | const out: Record<string, B> = empty() |
| 752 | for (const key of keys(self)) { |
| 753 | const result = f(self[key], key) |
| 754 | if (R.isSuccess(result)) { |
| 755 | InternalRecord.assignProperty(out, key, result.success) |
| 756 | } |
| 757 | } |
| 758 | return out |
| 759 | } |
| 760 | ) |
| 761 | |
| 762 | /** |
| 763 | * Selects properties from a record whose values match the given predicate. |
| 764 | * |
| 765 | * **Example** (Filtering record values) |
| 766 | * |
| 767 | * ```ts import.meta.vitest |
| 768 | * import { Record } from "effect" |
| 769 | * |
| 770 | * const x = { a: 1, b: 2, c: 3, d: 4 } |
| 771 | * Record.filter(x, (n) => n > 2) // => { c: 3, d: 4 } |
| 772 | * ``` |
| 773 | * |
| 774 | * @category filtering |
| 775 | * @since 2.0.0 |
| 776 | */ |
| 777 | export const filter: { |
| 778 | <K extends string, A, B extends A>( |
| 779 | refinement: (a: NoInfer<A>, key: K) => a is B |
| 780 | ): (self: ReadonlyRecord<K, A>) => Record<ReadonlyRecord.NonLiteralKey<K>, B> |
| 781 | <K extends string, A>( |
| 782 | predicate: (A: NoInfer<A>, key: K) => boolean |
| 783 | ): (self: ReadonlyRecord<K, A>) => Record<ReadonlyRecord.NonLiteralKey<K>, A> |
| 784 | <K extends string, A, B extends A>( |
| 785 | self: ReadonlyRecord<K, A>, |
| 786 | refinement: (a: A, key: K) => a is B |
| 787 | ): Record<ReadonlyRecord.NonLiteralKey<K>, B> |
| 788 | <K extends string, A>( |
| 789 | self: ReadonlyRecord<K, A>, |
| 790 | predicate: (a: A, key: K) => boolean |
| 791 | ): Record<ReadonlyRecord.NonLiteralKey<K>, A> |
| 792 | } = dual( |
| 793 | 2, |
| 794 | <K extends string, A>( |
| 795 | self: ReadonlyRecord<K, A>, |
| 796 | predicate: (a: A, key: K) => boolean |
| 797 | ): Record<ReadonlyRecord.NonLiteralKey<K>, A> => { |
| 798 | const out: Record<string, A> = empty() |
| 799 | for (const key of keys(self)) { |
| 800 | if (predicate(self[key], key)) { |
| 801 | InternalRecord.assignProperty(out, key, self[key]) |
| 802 | } |
| 803 | } |
| 804 | return out |
| 805 | } |
| 806 | ) |
| 807 | |
| 808 | /** |
| 809 | * Returns a new record containing only the `Some` values from a record of |
| 810 | * `Option` values, preserving the original keys. |
| 811 | * |
| 812 | * **Example** (Extracting Some values) |
| 813 | * |
| 814 | * ```ts import.meta.vitest |
| 815 | * import { Option, Record } from "effect" |
| 816 | * |
| 817 | * Record.getSomes({ a: Option.some(1), b: Option.none(), c: Option.some(2) }) // => { a: 1, c: 2 } |
| 818 | * ``` |
| 819 | * |
| 820 | * @category filtering |
| 821 | * @since 2.0.0 |
| 822 | */ |
| 823 | export const getSomes: <K extends string, A>( |
| 824 | self: ReadonlyRecord<K, Option.Option<A>> |
| 825 | ) => Record<ReadonlyRecord.NonLiteralKey<K>, A> = <K extends string, A>( |
| 826 | self: ReadonlyRecord<K, Option.Option<A>> |
| 827 | ): Record<ReadonlyRecord.NonLiteralKey<K>, A> => { |
| 828 | const out: Record<string, A> = empty() |
| 829 | for (const key of keys(self)) { |
| 830 | const option = self[key] |
| 831 | if (Option.isSome(option)) { |
| 832 | InternalRecord.assignProperty(out, key, option.value) |
| 833 | } |
| 834 | } |
| 835 | return out |
| 836 | } |
| 837 | |
| 838 | /** |
| 839 | * Returns a new record containing only the `Err` values from a record of |
| 840 | * `Result` values, preserving the original keys. |
| 841 | * |
| 842 | * **Example** (Extracting Result failures) |
| 843 | * |
| 844 | * ```ts import.meta.vitest |
| 845 | * import { Record, Result } from "effect" |
| 846 | * |
| 847 | * Record.getFailures({ |
| 848 | * a: Result.succeed(1), |
| 849 | * b: Result.fail("err"), |
| 850 | * c: Result.succeed(2) |
| 851 | * }) // => { b: "err" } |
| 852 | * ``` |
| 853 | * |
| 854 | * @category filtering |
| 855 | * @since 4.0.0 |
| 856 | */ |
| 857 | export const getFailures = <K extends string, A, E>( |
| 858 | self: ReadonlyRecord<K, Result<A, E>> |
| 859 | ): Record<ReadonlyRecord.NonLiteralKey<K>, E> => { |
| 860 | const out: Record<string, E> = empty() |
| 861 | for (const key of keys(self)) { |
| 862 | const value = self[key] |
| 863 | if (R.isFailure(value)) { |
| 864 | InternalRecord.assignProperty(out, key, value.failure) |
| 865 | } |
| 866 | } |
| 867 | |
| 868 | return out |
| 869 | } |
| 870 | |
| 871 | /** |
| 872 | * Returns a new record containing only the `Ok` values from a record of |
| 873 | * `Result` values, preserving the original keys. |
| 874 | * |
| 875 | * **Example** (Extracting Result successes) |
| 876 | * |
| 877 | * ```ts import.meta.vitest |
| 878 | * import { Record, Result } from "effect" |
| 879 | * |
| 880 | * Record.getSuccesses({ |
| 881 | * a: Result.succeed(1), |
| 882 | * b: Result.fail("err"), |
| 883 | * c: Result.succeed(2) |
| 884 | * }) // => { a: 1, c: 2 } |
| 885 | * ``` |
| 886 | * |
| 887 | * @category filtering |
| 888 | * @since 4.0.0 |
| 889 | */ |
| 890 | export const getSuccesses = <K extends string, A, E>( |
| 891 | self: ReadonlyRecord<K, Result<A, E>> |
| 892 | ): Record<string, A> => { |
| 893 | const out: Record<string, A> = empty() |
| 894 | for (const key of keys(self)) { |
| 895 | const value = self[key] |
| 896 | if (R.isSuccess(value)) { |
| 897 | InternalRecord.assignProperty(out, key, value.success) |
| 898 | } |
| 899 | } |
| 900 | |
| 901 | return out |
| 902 | } |
| 903 | |
| 904 | /** |
| 905 | * Applies a function to each record entry and partitions the returned `Result` |
| 906 | * values into two records. |
| 907 | * |
| 908 | * **Details** |
| 909 | * |
| 910 | * Success values are collected in the first record, and failure values are |
| 911 | * collected in the second record, preserving the original keys. |
| 912 | * |
| 913 | * **Example** (Partitioning with Result) |
| 914 | * |
| 915 | * ```ts import.meta.vitest |
| 916 | * import { Record, Result } from "effect" |
| 917 | * |
| 918 | * const x = { a: 1, b: 2, c: 3 } |
| 919 | * const f = (n: number) => (n % 2 === 0 ? Result.succeed(n) : Result.fail(n)) |
| 920 | * Record.partition(x, f) // => [{ b: 2 }, { a: 1, c: 3 }] |
| 921 | * ``` |
| 922 | * |
| 923 | * @category filtering |
| 924 | * @since 2.0.0 |
| 925 | */ |
| 926 | export const partition: { |
| 927 | <K extends string, A, B, C>( |
| 928 | f: (input: A, key: K) => Result<C, B> |
| 929 | ): ( |
| 930 | self: ReadonlyRecord<K, A> |
| 931 | ) => [passes: Record<ReadonlyRecord.NonLiteralKey<K>, C>, fails: Record<ReadonlyRecord.NonLiteralKey<K>, B>] |
| 932 | <K extends string, A, B, C>( |
| 933 | self: ReadonlyRecord<K, A>, |
| 934 | f: (input: A, key: K) => Result<C, B> |
| 935 | ): [passes: Record<ReadonlyRecord.NonLiteralKey<K>, C>, fails: Record<ReadonlyRecord.NonLiteralKey<K>, B>] |
| 936 | } = dual( |
| 937 | 2, |
| 938 | <K extends string, A, B, C>( |
| 939 | self: ReadonlyRecord<K, A>, |
| 940 | f: (input: A, key: K) => Result<C, B> |
| 941 | ): [passes: Record<ReadonlyRecord.NonLiteralKey<K>, C>, fails: Record<ReadonlyRecord.NonLiteralKey<K>, B>] => { |
| 942 | const passes: Record<string, C> = empty() |
| 943 | const fails: Record<string, B> = empty() |
| 944 | for (const key of keys(self)) { |
| 945 | const e = f(self[key], key) |
| 946 | if (R.isFailure(e)) { |
| 947 | InternalRecord.assignProperty(fails, key, e.failure) |
| 948 | } else { |
| 949 | InternalRecord.assignProperty(passes, key, e.success) |
| 950 | } |
| 951 | } |
| 952 | return [passes, fails] |
| 953 | } |
| 954 | ) |
| 955 | |
| 956 | /** |
| 957 | * Partitions a record of `Result` values into two separate records, |
| 958 | * one with the success values and one with the failure values. |
| 959 | * |
| 960 | * **Example** (Separating Result values) |
| 961 | * |
| 962 | * ```ts import.meta.vitest |
| 963 | * import { Record, Result } from "effect" |
| 964 | * |
| 965 | * Record.separate({ a: Result.fail("e"), b: Result.succeed(1) }) // => [{ b: 1 }, { a: "e" }] |
| 966 | * ``` |
| 967 | * |
| 968 | * @category filtering |
| 969 | * @since 2.0.0 |
| 970 | */ |
| 971 | export const separate: <K extends string, A, B>( |
| 972 | self: ReadonlyRecord<K, Result<B, A>> |
| 973 | ) => [Record<ReadonlyRecord.NonLiteralKey<K>, B>, Record<ReadonlyRecord.NonLiteralKey<K>, A>] = partition(identity) |
| 974 | |
| 975 | /** |
| 976 | * Retrieves the keys of a given record as an array. |
| 977 | * |
| 978 | * **Example** (Getting record keys) |
| 979 | * |
| 980 | * ```ts import.meta.vitest |
| 981 | * import { Record } from "effect" |
| 982 | * |
| 983 | * Record.keys({ a: 1, b: 2, c: 3 }) // => ["a", "b", "c"] |
| 984 | * ``` |
| 985 | * |
| 986 | * @category getters |
| 987 | * @since 2.0.0 |
| 988 | */ |
| 989 | export const keys = <K extends string | symbol, A>(self: ReadonlyRecord<K, A>): Array<K & string> => |
| 990 | Object.keys(self) as Array<K & string> |
| 991 | |
| 992 | /** |
| 993 | * Retrieves the values of a given record as an array. |
| 994 | * |
| 995 | * **Example** (Getting record values) |
| 996 | * |
| 997 | * ```ts import.meta.vitest |
| 998 | * import { Record } from "effect" |
| 999 | * |
| 1000 | * Record.values({ a: 1, b: 2, c: 3 }) // => [1, 2, 3] |
| 1001 | * ``` |
| 1002 | * |
| 1003 | * @category getters |
| 1004 | * @since 2.0.0 |
| 1005 | */ |
| 1006 | export const values = <K extends string, A>(self: ReadonlyRecord<K, A>): Array<A> => collect(self, (_, a) => a) |
| 1007 | |
| 1008 | /** |
| 1009 | * Adds a new key-value pair or update an existing key's value in a record. |
| 1010 | * |
| 1011 | * **Example** (Setting a record value) |
| 1012 | * |
| 1013 | * ```ts import.meta.vitest |
| 1014 | * import { Record } from "effect" |
| 1015 | * |
| 1016 | * Record.set("a", 5)({ a: 1, b: 2 }) // => { a: 5, b: 2 } |
| 1017 | * Record.set("c", 5)({ a: 1, b: 2 }) // => { a: 1, b: 2, c: 5 } |
| 1018 | * ``` |
| 1019 | * |
| 1020 | * @category mutations |
| 1021 | * @since 2.0.0 |
| 1022 | */ |
| 1023 | export const set: { |
| 1024 | <K extends string | symbol, K1 extends K | ((string | symbol) & {}), B>( |
| 1025 | key: K1, |
| 1026 | value: B |
| 1027 | ): <A>(self: ReadonlyRecord<K, A>) => Record<K | K1, A | B> |
| 1028 | <K extends string | symbol, A, K1 extends K | ((string | symbol) & {}), B>( |
| 1029 | self: ReadonlyRecord<K, A>, |
| 1030 | key: K1, |
| 1031 | value: B |
| 1032 | ): Record<K | K1, A | B> |
| 1033 | } = dual( |
| 1034 | 3, |
| 1035 | <K extends string | symbol, A, K1 extends K | ((string | symbol) & {}), B>( |
| 1036 | self: ReadonlyRecord<K, A>, |
| 1037 | key: K1, |
| 1038 | value: B |
| 1039 | ): Record<K | K1, A | B> => { |
| 1040 | return { ...self, [key]: value } as any |
| 1041 | } |
| 1042 | ) |
| 1043 | |
| 1044 | /** |
| 1045 | * Mutates a record by assigning a value to a property. |
| 1046 | * |
| 1047 | * **When to use** |
| 1048 | * |
| 1049 | * Use when incrementally constructing a new record and copying it for every |
| 1050 | * property would be unnecessary. |
| 1051 | * |
| 1052 | * **Gotchas** |
| 1053 | * |
| 1054 | * This function mutates `self`. When `key` is `"__proto__"`, it creates an |
| 1055 | * own data property instead of changing the object's prototype. |
| 1056 | * |
| 1057 | * **Example** (Assigning an external key safely) |
| 1058 | * |
| 1059 | * ```ts import.meta.vitest |
| 1060 | * import { Record } from "effect" |
| 1061 | * |
| 1062 | * const key: string = "__proto__" // Assume this comes from external input |
| 1063 | * const value = { polluted: true } |
| 1064 | * |
| 1065 | * const unsafe: Record<string, unknown> = {} |
| 1066 | * unsafe[key] = value |
| 1067 | * Object.getPrototypeOf(unsafe) === value // => true |
| 1068 | * |
| 1069 | * const safe: Record<string, unknown> = {} |
| 1070 | * Record.assignProperty(safe, key, value) |
| 1071 | * Object.getPrototypeOf(safe) === Object.prototype // => true |
| 1072 | * safe[key] === value // => true |
| 1073 | * ``` |
| 1074 | * |
| 1075 | * @see {@link set} for an immutable update |
| 1076 | * @category mutations |
| 1077 | * @since 4.0.0 |
| 1078 | */ |
| 1079 | export const assignProperty: (self: object, key: PropertyKey, value: unknown) => void = InternalRecord.assignProperty |
| 1080 | |
| 1081 | /** |
| 1082 | * Checks whether all the keys and values in one record are also found in another record. |
| 1083 | * Uses the provided equivalence function to compare values. |
| 1084 | * |
| 1085 | * **Example** (Checking subrecords with a custom equivalence) |
| 1086 | * |
| 1087 | * ```ts import.meta.vitest |
| 1088 | * import { Equivalence, Record } from "effect" |
| 1089 | * |
| 1090 | * const isSubrecord = Record.isSubrecordBy( |
| 1091 | * Equivalence.make<string>((self, that) => self.toLowerCase() === that.toLowerCase()) |
| 1092 | * ) |
| 1093 | * |
| 1094 | * const required: Record.ReadonlyRecord<string, string> = { role: "Admin" } |
| 1095 | * const available: Record.ReadonlyRecord<string, string> = { |
| 1096 | * role: "admin", |
| 1097 | * status: "active" |
| 1098 | * } |
| 1099 | * |
| 1100 | * isSubrecord(required, available) // => true |
| 1101 | * isSubrecord({ role: "Admin", status: "inactive" }, available) // => false |
| 1102 | * isSubrecord(required, { role: "editor", status: "active" }) // => false |
| 1103 | * ``` |
| 1104 | * |
| 1105 | * @category predicates |
| 1106 | * @since 2.0.0 |
| 1107 | */ |
| 1108 | export const isSubrecordBy = <A>(equivalence: Equivalence<A>): { |
| 1109 | <K extends string>(that: ReadonlyRecord<K, A>): (self: ReadonlyRecord<K, A>) => boolean |
| 1110 | <K extends string>(self: ReadonlyRecord<K, A>, that: ReadonlyRecord<K, A>): boolean |
| 1111 | } => |
| 1112 | dual(2, <K extends string>(self: ReadonlyRecord<K, A>, that: ReadonlyRecord<K, A>): boolean => { |
| 1113 | for (const key of keys(self)) { |
| 1114 | if (!has(that, key) || !equivalence(self[key], that[key])) { |
| 1115 | return false |
| 1116 | } |
| 1117 | } |
| 1118 | return true |
| 1119 | }) |
| 1120 | |
| 1121 | /** |
| 1122 | * Checks whether the first record is a subrecord of the second record. |
| 1123 | * |
| 1124 | * **Details** |
| 1125 | * |
| 1126 | * Returns `true` when every key and value in `self` is also present in `that`. |
| 1127 | * Values are compared with Effect equality via `Equal.asEquivalence()`. |
| 1128 | * |
| 1129 | * **Example** (Checking subrecords) |
| 1130 | * |
| 1131 | * ```ts import.meta.vitest |
| 1132 | * import { Record } from "effect" |
| 1133 | * |
| 1134 | * Record.isSubrecord({ a: 1 } as Record<string, number>, { a: 1, b: 2 }) // => true |
| 1135 | * Record.isSubrecord({ a: 1, b: 2 }, { a: 1 } as Record<string, number>) // => false |
| 1136 | * ``` |
| 1137 | * |
| 1138 | * @category predicates |
| 1139 | * @since 2.0.0 |
| 1140 | */ |
| 1141 | export const isSubrecord: { |
| 1142 | <K extends string, A>(that: ReadonlyRecord<K, A>): (self: ReadonlyRecord<K, A>) => boolean |
| 1143 | <K extends string, A>(self: ReadonlyRecord<K, A>, that: ReadonlyRecord<K, A>): boolean |
| 1144 | } = isSubrecordBy(Equal.asEquivalence()) |
| 1145 | |
| 1146 | /** |
| 1147 | * Reduces a record to a single value by combining its entries with a specified function. |
| 1148 | * |
| 1149 | * **Example** (Reducing record values) |
| 1150 | * |
| 1151 | * ```ts import.meta.vitest |
| 1152 | * import { Record } from "effect" |
| 1153 | * |
| 1154 | * Record.reduce({ a: 1, b: 2, c: 3 }, 0, (acc, value) => acc + value) // => 6 |
| 1155 | * ``` |
| 1156 | * |
| 1157 | * @category folding |
| 1158 | * @since 2.0.0 |
| 1159 | */ |
| 1160 | export const reduce: { |
| 1161 | <Z, V, K extends string>( |
| 1162 | zero: Z, |
| 1163 | f: (accumulator: Z, value: V, key: K) => Z |
| 1164 | ): (self: ReadonlyRecord<K, V>) => Z |
| 1165 | <K extends string, V, Z>(self: ReadonlyRecord<K, V>, zero: Z, f: (accumulator: Z, value: V, key: K) => Z): Z |
| 1166 | } = dual( |
| 1167 | 3, |
| 1168 | <K extends string, V, Z>( |
| 1169 | self: ReadonlyRecord<K, V>, |
| 1170 | zero: Z, |
| 1171 | f: (accumulator: Z, value: V, key: K) => Z |
| 1172 | ): Z => { |
| 1173 | let out: Z = zero |
| 1174 | for (const key of keys(self)) { |
| 1175 | out = f(out, self[key], key) |
| 1176 | } |
| 1177 | return out |
| 1178 | } |
| 1179 | ) |
| 1180 | |
| 1181 | /** |
| 1182 | * Checks whether all entries in a record meet a specific condition. |
| 1183 | * |
| 1184 | * **Example** (Checking every record value) |
| 1185 | * |
| 1186 | * ```ts import.meta.vitest |
| 1187 | * import { Record } from "effect" |
| 1188 | * |
| 1189 | * Record.every({ a: 1, b: 2 }, (n) => n > 0) // => true |
| 1190 | * Record.every({ a: 1, b: -1 }, (n) => n > 0) // => false |
| 1191 | * ``` |
| 1192 | * |
| 1193 | * @category guards |
| 1194 | * @since 2.0.0 |
| 1195 | */ |
| 1196 | export const every: { |
| 1197 | <A, K extends string, B extends A>( |
| 1198 | refinement: (value: A, key: K) => value is B |
| 1199 | ): (self: ReadonlyRecord<K, A>) => self is ReadonlyRecord<K, B> |
| 1200 | <A, K extends string>(predicate: (value: A, key: K) => boolean): (self: ReadonlyRecord<K, A>) => boolean |
| 1201 | <A, K extends string, B extends A>( |
| 1202 | self: ReadonlyRecord<K, A>, |
| 1203 | refinement: (value: A, key: K) => value is B |
| 1204 | ): self is ReadonlyRecord<K, B> |
| 1205 | <K extends string, A>(self: ReadonlyRecord<K, A>, predicate: (value: A, key: K) => boolean): boolean |
| 1206 | } = dual( |
| 1207 | 2, |
| 1208 | <A, K extends string, B extends A>( |
| 1209 | self: ReadonlyRecord<K, A>, |
| 1210 | refinement: (value: A, key: K) => value is B |
| 1211 | ): self is ReadonlyRecord<K, B> => { |
| 1212 | for (const key of keys(self)) { |
| 1213 | if (!refinement(self[key], key)) { |
| 1214 | return false |
| 1215 | } |
| 1216 | } |
| 1217 | return true |
| 1218 | } |
| 1219 | ) |
| 1220 | |
| 1221 | /** |
| 1222 | * Checks whether any entry in a record meets a specific condition. |
| 1223 | * |
| 1224 | * **Example** (Checking for any matching value) |
| 1225 | * |
| 1226 | * ```ts import.meta.vitest |
| 1227 | * import { Record } from "effect" |
| 1228 | * |
| 1229 | * Record.some({ a: 1, b: 2 }, (n) => n > 1) // => true |
| 1230 | * Record.some({ a: 1, b: 2 }, (n) => n > 2) // => false |
| 1231 | * ``` |
| 1232 | * |
| 1233 | * @category predicates |
| 1234 | * @since 2.0.0 |
| 1235 | */ |
| 1236 | export const some: { |
| 1237 | <A, K extends string>(predicate: (value: A, key: K) => boolean): (self: ReadonlyRecord<K, A>) => boolean |
| 1238 | <K extends string, A>(self: ReadonlyRecord<K, A>, predicate: (value: A, key: K) => boolean): boolean |
| 1239 | } = dual( |
| 1240 | 2, |
| 1241 | <K extends string, A>(self: ReadonlyRecord<K, A>, predicate: (value: A, key: K) => boolean): boolean => { |
| 1242 | for (const key of keys(self)) { |
| 1243 | if (predicate(self[key], key)) { |
| 1244 | return true |
| 1245 | } |
| 1246 | } |
| 1247 | return false |
| 1248 | } |
| 1249 | ) |
| 1250 | |
| 1251 | /** |
| 1252 | * Merges two records, preserving entries that exist in either of the records. |
| 1253 | * For keys that exist in both records, the provided combine function is used to merge the values. |
| 1254 | * |
| 1255 | * **Example** (Merging records with union) |
| 1256 | * |
| 1257 | * ```ts import.meta.vitest |
| 1258 | * import { Record } from "effect" |
| 1259 | * |
| 1260 | * Record.union({ a: 1, b: 2 }, { b: 3, c: 4 }, (a, b) => a + b) // => { a: 1, b: 5, c: 4 } |
| 1261 | * ``` |
| 1262 | * |
| 1263 | * @category combining |
| 1264 | * @since 2.0.0 |
| 1265 | */ |
| 1266 | export const union: { |
| 1267 | <K1 extends string, A, B, C>( |
| 1268 | that: ReadonlyRecord<K1, B>, |
| 1269 | combine: (selfValue: A, thatValue: B) => C |
| 1270 | ): <K0 extends string>(self: ReadonlyRecord<K0, A>) => Record<K0 | K1, A | B | C> |
| 1271 | <K0 extends string, A, K1 extends string, B, C>( |
| 1272 | self: ReadonlyRecord<K0, A>, |
| 1273 | that: ReadonlyRecord<K1, B>, |
| 1274 | combine: (selfValue: A, thatValue: B) => C |
| 1275 | ): Record<K0 | K1, A | B | C> |
| 1276 | } = dual( |
| 1277 | 3, |
| 1278 | <K0 extends string, A, K1 extends string, B, C>( |
| 1279 | self: ReadonlyRecord<K0, A>, |
| 1280 | that: ReadonlyRecord<K1, B>, |
| 1281 | combine: (selfValue: A, thatValue: B) => C |
| 1282 | ): Record<K0 | K1, A | B | C> => { |
| 1283 | if (isEmptyRecord(self)) { |
| 1284 | return { ...that } as any |
| 1285 | } |
| 1286 | if (isEmptyRecord(that)) { |
| 1287 | return { ...self } as any |
| 1288 | } |
| 1289 | const out: Record<string, A | B | C> = empty() |
| 1290 | for (const key of keys(self)) { |
| 1291 | if (has(that, key as any)) { |
| 1292 | InternalRecord.assignProperty(out, key, combine(self[key], that[key as unknown as K1])) |
| 1293 | } else { |
| 1294 | InternalRecord.assignProperty(out, key, self[key]) |
| 1295 | } |
| 1296 | } |
| 1297 | for (const key of keys(that)) { |
| 1298 | if (!has(out, key)) { |
| 1299 | InternalRecord.assignProperty(out, key, that[key]) |
| 1300 | } |
| 1301 | } |
| 1302 | return out |
| 1303 | } |
| 1304 | ) |
| 1305 | |
| 1306 | /** |
| 1307 | * Merges two records, retaining only the entries that exist in both records. |
| 1308 | * For intersecting keys, the provided combine function is used to merge the values. |
| 1309 | * |
| 1310 | * **Example** (Merging intersecting keys) |
| 1311 | * |
| 1312 | * ```ts import.meta.vitest |
| 1313 | * import { Record } from "effect" |
| 1314 | * |
| 1315 | * Record.intersection({ a: 1, b: 2 }, { b: 3, c: 4 }, (a, b) => a + b) // => { b: 5 } |
| 1316 | * ``` |
| 1317 | * |
| 1318 | * @category combining |
| 1319 | * @since 2.0.0 |
| 1320 | */ |
| 1321 | export const intersection: { |
| 1322 | <K1 extends string, A, B, C>( |
| 1323 | that: ReadonlyRecord<K1, B>, |
| 1324 | combine: (selfValue: A, thatValue: B) => C |
| 1325 | ): <K0 extends string>(self: ReadonlyRecord<K0, A>) => Record<ReadonlyRecord.IntersectKeys<K0, K1>, C> |
| 1326 | <K0 extends string, A, K1 extends string, B, C>( |
| 1327 | self: ReadonlyRecord<K0, A>, |
| 1328 | that: ReadonlyRecord<K1, B>, |
| 1329 | combine: (selfValue: A, thatValue: B) => C |
| 1330 | ): Record<ReadonlyRecord.IntersectKeys<K0, K1>, C> |
| 1331 | } = dual( |
| 1332 | 3, |
| 1333 | <K0 extends string, A, K1 extends string, B, C>( |
| 1334 | self: ReadonlyRecord<K0, A>, |
| 1335 | that: ReadonlyRecord<K1, B>, |
| 1336 | combine: (selfValue: A, thatValue: B) => C |
| 1337 | ): Record<ReadonlyRecord.IntersectKeys<K0, K1>, C> => { |
| 1338 | const out: Record<string, C> = empty() |
| 1339 | if (isEmptyRecord(self) || isEmptyRecord(that)) { |
| 1340 | return out |
| 1341 | } |
| 1342 | for (const key of keys(self)) { |
| 1343 | if (has(that, key as any)) { |
| 1344 | InternalRecord.assignProperty(out, key, combine(self[key], that[key as unknown as K1])) |
| 1345 | } |
| 1346 | } |
| 1347 | return out |
| 1348 | } |
| 1349 | ) |
| 1350 | |
| 1351 | /** |
| 1352 | * Merges two records, preserving only the entries that are unique to each record. |
| 1353 | * Keys that exist in both records are excluded from the result. |
| 1354 | * |
| 1355 | * **Example** (Keeping keys unique to each record) |
| 1356 | * |
| 1357 | * ```ts import.meta.vitest |
| 1358 | * import { Record } from "effect" |
| 1359 | * |
| 1360 | * Record.difference({ a: 1, b: 2 }, { b: 3, c: 4 }) // => { a: 1, c: 4 } |
| 1361 | * ``` |
| 1362 | * |
| 1363 | * @category combining |
| 1364 | * @since 2.0.0 |
| 1365 | */ |
| 1366 | export const difference: { |
| 1367 | <K1 extends string, B>( |
| 1368 | that: ReadonlyRecord<K1, B> |
| 1369 | ): <K0 extends string, A>(self: ReadonlyRecord<K0, A>) => Record<K0 | K1, A | B> |
| 1370 | <K0 extends string, A, K1 extends string, B>( |
| 1371 | self: ReadonlyRecord<K0, A>, |
| 1372 | that: ReadonlyRecord<K1, B> |
| 1373 | ): Record<K0 | K1, A | B> |
| 1374 | } = dual(2, <K0 extends string, A, K1 extends string, B>( |
| 1375 | self: ReadonlyRecord<K0, A>, |
| 1376 | that: ReadonlyRecord<K1, B> |
| 1377 | ): Record<K0 | K1, A | B> => { |
| 1378 | if (isEmptyRecord(self)) { |
| 1379 | return { ...that } as any |
| 1380 | } |
| 1381 | if (isEmptyRecord(that)) { |
| 1382 | return { ...self } as any |
| 1383 | } |
| 1384 | const out = {} as Record<K0 | K1, A | B> |
| 1385 | for (const key of keys(self)) { |
| 1386 | if (!has(that, key as any)) { |
| 1387 | InternalRecord.assignProperty(out, key, self[key]) |
| 1388 | } |
| 1389 | } |
| 1390 | for (const key of keys(that)) { |
| 1391 | if (!has(self, key as any)) { |
| 1392 | InternalRecord.assignProperty(out, key, that[key]) |
| 1393 | } |
| 1394 | } |
| 1395 | return out |
| 1396 | }) |
| 1397 | |
| 1398 | /** |
| 1399 | * Create an `Equivalence` for records using the provided `Equivalence` for values. |
| 1400 | * Two records are considered equivalent if they have the same keys and their corresponding values are equivalent. |
| 1401 | * |
| 1402 | * **Example** (Comparing records with a value equivalence) |
| 1403 | * |
| 1404 | * ```ts import.meta.vitest |
| 1405 | * import { Equal, Record } from "effect" |
| 1406 | * |
| 1407 | * const recordEquivalence = Record.makeEquivalence(Equal.asEquivalence<number>()) |
| 1408 | * |
| 1409 | * recordEquivalence({ a: 1, b: 2 }, { a: 1, b: 2 }) // => true |
| 1410 | * recordEquivalence({ a: 1, b: 2 }, { a: 1, b: 3 }) // => false |
| 1411 | * ``` |
| 1412 | * |
| 1413 | * @category instances |
| 1414 | * @since 4.0.0 |
| 1415 | */ |
| 1416 | export const makeEquivalence = <K extends string, A>( |
| 1417 | equivalence: Equivalence<A> |
| 1418 | ): Equivalence<ReadonlyRecord<K, A>> => { |
| 1419 | const is = isSubrecordBy(equivalence) |
| 1420 | return (self, that) => is(self, that) && is(that, self) |
| 1421 | } |
| 1422 | |
| 1423 | /** |
| 1424 | * Create a non-empty record from a single element. |
| 1425 | * |
| 1426 | * **Example** (Creating a singleton record) |
| 1427 | * |
| 1428 | * ```ts import.meta.vitest |
| 1429 | * import { Record } from "effect" |
| 1430 | * |
| 1431 | * Record.singleton("a", 1) // => { a: 1 } |
| 1432 | * ``` |
| 1433 | * |
| 1434 | * @category constructors |
| 1435 | * @since 2.0.0 |
| 1436 | */ |
| 1437 | export const singleton = <K extends string | symbol, A>(key: K, value: A): Record<K, A> => ({ |
| 1438 | [key]: value |
| 1439 | } as any) |
| 1440 | |
| 1441 | /** |
| 1442 | * Creates a `Reducer` for combining `Record`s using union, with values for keys that exist in both records combined |
| 1443 | * using the provided `Combiner`. |
| 1444 | * |
| 1445 | * **When to use** |
| 1446 | * |
| 1447 | * Use to build a reusable reducer for accumulating many records into one |
| 1448 | * union-shaped record, preserving keys from every input and combining |
| 1449 | * overlapping values with the supplied combiner. |
| 1450 | * |
| 1451 | * **Details** |
| 1452 | * |
| 1453 | * The returned reducer uses `Record.union` for combine and an empty record as |
| 1454 | * `initialValue`, so the default `combineAll` folds from `{}` and accumulates |
| 1455 | * keys from each input record. |
| 1456 | * |
| 1457 | * @see {@link union} for one-off record merging with the same union semantics |
| 1458 | * @see {@link makeReducerIntersection} for a reducer that keeps only keys present on both sides |
| 1459 | * |
| 1460 | * @category combining |
| 1461 | * @since 4.0.0 |
| 1462 | */ |
| 1463 | export function makeReducerUnion<K extends string, A>(combiner: Combiner.Combiner<A>): Reducer.Reducer<Record<K, A>> { |
| 1464 | return Reducer.make<Record<K, A>>( |
| 1465 | (self, that) => union(self, that, combiner.combine), |
| 1466 | {} as Record<K, A> |
| 1467 | ) |
| 1468 | } |
| 1469 | |
| 1470 | /** |
| 1471 | * Creates a `Reducer` whose `combine` operation intersects two records and |
| 1472 | * combines values for keys present in both records. |
| 1473 | * |
| 1474 | * **When to use** |
| 1475 | * |
| 1476 | * Use to build a `Reducer` that combines records by retaining only keys shared |
| 1477 | * by both inputs and combining matching values with a `Combiner`. |
| 1478 | * |
| 1479 | * **Gotchas** |
| 1480 | * |
| 1481 | * The reducer's `initialValue` is an empty record. Because intersection with |
| 1482 | * an empty record is empty, the default `combineAll` folds from `{}` and |
| 1483 | * therefore produces `{}` for ordinary non-empty inputs. |
| 1484 | * |
| 1485 | * @see {@link makeReducerUnion} for a reducer that preserves keys from either input record |
| 1486 | * @see {@link intersection} for applying the shared-key merge to one pair of records |
| 1487 | * |
| 1488 | * @category combining |
| 1489 | * @since 4.0.0 |
| 1490 | */ |
| 1491 | export function makeReducerIntersection<K extends string, A>( |
| 1492 | combiner: Combiner.Combiner<A> |
| 1493 | ): Reducer.Reducer<Record<K, A>> { |
| 1494 | return Reducer.make( |
| 1495 | (self, that) => intersection(self, that, combiner.combine) as any, |
| 1496 | {} as Record<K, A> |
| 1497 | ) |
| 1498 | } |
| 1499 | |
| 1500 | /** |
| 1501 | * Returns the first entry that satisfies the specified |
| 1502 | * predicate, or `None` if no such entry exists. |
| 1503 | * |
| 1504 | * **Example** (Finding the first matching entry) |
| 1505 | * |
| 1506 | * ```ts import.meta.vitest |
| 1507 | * import { Option, Record } from "effect" |
| 1508 | * |
| 1509 | * const record = { a: 1, b: 2, c: 3 } |
| 1510 | * Record.findFirst( |
| 1511 | * record, |
| 1512 | * (value, key) => value > 1 && key !== "b" |
| 1513 | * ) // => Option.some(["c", 3]) |
| 1514 | * ``` |
| 1515 | * |
| 1516 | * @category searching |
| 1517 | * @since 3.14.0 |
| 1518 | */ |
| 1519 | export const findFirst: { |
| 1520 | <K extends string | symbol, V, V2 extends V>( |
| 1521 | refinement: (value: NoInfer<V>, key: NoInfer<K>) => value is V2 |
| 1522 | ): (self: ReadonlyRecord<K, V>) => Option.Option<[K, V2]> |
| 1523 | <K extends string | symbol, V>( |
| 1524 | predicate: (value: NoInfer<V>, key: NoInfer<K>) => boolean |
| 1525 | ): (self: ReadonlyRecord<K, V>) => Option.Option<[K, V]> |
| 1526 | <K extends string | symbol, V, V2 extends V>( |
| 1527 | self: ReadonlyRecord<K, V>, |
| 1528 | refinement: (value: NoInfer<V>, key: NoInfer<K>) => value is V2 |
| 1529 | ): Option.Option<[K, V2]> |
| 1530 | <K extends string | symbol, V>( |
| 1531 | self: ReadonlyRecord<K, V>, |
| 1532 | predicate: (value: NoInfer<V>, key: NoInfer<K>) => boolean |
| 1533 | ): Option.Option<[K, V]> |
| 1534 | } = dual( |
| 1535 | 2, |
| 1536 | <K extends string | symbol, V>( |
| 1537 | self: ReadonlyRecord<K, V>, |
| 1538 | f: (value: V, key: K) => boolean |
| 1539 | ): Option.Option<[K, V]> => { |
| 1540 | const k = keys(self) |
| 1541 | for (let i = 0; i < k.length; i++) { |
| 1542 | const key = k[i] |
| 1543 | if (f(self[key], key)) { |
| 1544 | return Option.some([key, self[key]]) |
| 1545 | } |
| 1546 | } |
| 1547 | return Option.none() |
| 1548 | } |
| 1549 | ) |