Repository navigation
ImmutableMutable
Immutable<T> and Mutable<T> are type-only helpers (no runtime export) that
flip the readonly modifiers of a type, recursively. They are exported from the
package root and from the ./immutable subpath.
Recursively makes every property readonly and converts mutable collections to
their readonly counterparts:
| Source | Becomes |
|---|---|
Array<T> |
ReadonlyArray<Immutable<T>> |
Map<K, V> |
ReadonlyMap<Immutable<K>, Immutable<V>> |
Set<T> |
ReadonlySet<Immutable<T>> |
| plain object | { readonly [K in keyof T]: Immutable<T[K]> } |
| anything else | itself (see Pass-through types) |
import type { Immutable } from "@ghaerdi/rustify";
type User = {
name: string;
tags: string[];
metadata: { role: string };
};
type FrozenUser = Immutable<User>;
// {
// readonly name: string;
// readonly tags: readonly string[];
// readonly metadata: { readonly role: string };
// }
const user: Immutable<User> = {
name: "Alice",
tags: ["admin"],
metadata: { role: "admin" },
};
// Compile errors: user.name = "Bob" and user.tags.push("x") are both rejected.The deep inverse: recursively removes readonly modifiers and converts readonly
collections back to their mutable counterparts (ReadonlyArray → Array,
ReadonlyMap → Map, ReadonlySet → Set). Use it as an escape hatch when
mutation is intentional — for example inside a builder that needs to fill in a
frozen input.
import type { Immutable, Mutable } from "@ghaerdi/rustify";
type Frozen = Immutable<{ readonly name: string; readonly tags: readonly string[] }>;
type MutableUser = Mutable<Frozen>;
// { name: string; tags: string[] }
const user: MutableUser = { name: "Alice", tags: ["admin"] };
user.tags.push("editor"); // allowed — Mutable removed the readonlyMutable<Frozen> restores the original shape, so Mutable<Immutable<T>> is
assignable both ways with T.
Mutable<T>changes the type, not the value. If you already have a deeply frozen instance, converting the type does not make it mutable at runtime.
Some types cannot be made immutable through readonly alone — readonly on a
property of a Date, a RegExp, a Promise, a WeakMap or a WeakSet still
allows the object's own mutating methods (setTime, exec, …). These, plus
functions, ReadonlyMap/ReadonlySet/ReadonlyArray (for Immutable) and
primitives, pass through unchanged.
import type { Immutable } from "@ghaerdi/rustify";
type D = Immutable<Date>; // still Date — getTime(), setTime() still available
type F = Immutable<(x: number) => string>; // still the same function type
type S = Immutable<string>; // still string- Annotate a public API's return type with
Immutable<T>so callers cannot mutate your internal structures. - Keep
Mutable<T>internal (e.g. in a builder) so the immutability guarantee holds everywhere else. -
Immutable<T>is idempotent: applying it twice changes nothing.