A mapped type can mark every property readonly for immutable snapshots.

readonly mapped property Adding `readonly` inside a mapped type creates a version whose properties cannot be reassigned.

Readonly Snapshot Shape

countValue
readonly_snapshot.ts
Replay: real traced execution (multi-file project)
type Counter = {
    count: number;
    label: string;
};

type Snapshot<T> = {
    readonly [K in keyof T]: T[K];
};

function describeSnapshot(snapshot: Snapshot<Counter>): string {
    return `${snapshot.label}=${snapshot.count}`;
}

const countValue: number = 4;
const snapshot: Snapshot<Counter> = {
    count: countValue,
    label: "runs",
};

console.log(describeSnapshot(snapshot));
type Counter = {
    count: number;
    label: string;
};

type Snapshot<T> = {
    readonly [K in keyof T]: T[K];
};

function describeSnapshot(snapshot: Snapshot<Counter>): string {
    return `${snapshot.label}=${snapshot.count}`;
}

const countValue: number = 9;
const snapshot: Snapshot<Counter> = {
    count: countValue,
    label: "runs",
};

console.log(describeSnapshot(snapshot));
  1. countValue ← 4, snapshot ← [object Object]

    11    return `${snapshot.label}=${snapshot.count}`;12}1314const countValue→ 4: number = 4;  //@countValue=915const snapshot→ [object Object]: Snapshot<Counter> = {16    count: countValue4,17    label: "runs",18};1920console.log(describeSnapshot(snapshot[object Object]));
  2. function describeSnapshot(snapshot: Snapshot<Counter>): string

    7    readonly [K in keyof T]: T[K];8};910function describeSnapshot(snapshot[object Object]: Snapshot<Counter>): string {11    return `${snapshot.labelruns}=${snapshot.count4}`;12}
  3. console.log(describeSnapshot(snapshot));

    17    label: "runs",18};1920console.log(describeSnapshot(snapshot[object Object]));
    outputruns=4
  1. countValue ← 9, snapshot ← [object Object]

    11    return `${snapshot.label}=${snapshot.count}`;12}1314const countValue→ 9: number = 9;15const snapshot→ [object Object]: Snapshot<Counter> = {16    count: countValue9,17    label: "runs",18};1920console.log(describeSnapshot(snapshot[object Object]));
  2. function describeSnapshot(snapshot: Snapshot<Counter>): string

    7    readonly [K in keyof T]: T[K];8};910function describeSnapshot(snapshot[object Object]: Snapshot<Counter>): string {11    return `${snapshot.labelruns}=${snapshot.count9}`;12}
  3. console.log(describeSnapshot(snapshot));

    17    label: "runs",18};1920console.log(describeSnapshot(snapshot[object Object]));
    outputruns=9