A discriminated command type keeps each action branch visible.

command dispatch Dispatching by command kind is a traceable way to apply several updates in order.

Command Dispatch Case

useReset
commands.ts
Replay: real traced execution (multi-file project)
type Command =
    | { kind: "add"; amount: number }
    | { kind: "subtract"; amount: number }
    | { kind: "reset" };

function applyCommand(total: number, command: Command): number {
    if (command.kind === "add") {
        return total + command.amount;
    }

    if (command.kind === "subtract") {
        return total - command.amount;
    }

    return 0;
}

const useReset: boolean = false;
const commands: Command[] = [
    { kind: "add", amount: 5 },
    useReset ? { kind: "reset" } : { kind: "subtract", amount: 2 },
    { kind: "add", amount: 3 }
];

let total: number = 0;
for (const command of commands) {
    total = applyCommand(total, command);
}

console.log(`total=${total}`);
type Command =
    | { kind: "add"; amount: number }
    | { kind: "subtract"; amount: number }
    | { kind: "reset" };

function applyCommand(total: number, command: Command): number {
    if (command.kind === "add") {
        return total + command.amount;
    }

    if (command.kind === "subtract") {
        return total - command.amount;
    }

    return 0;
}

const useReset: boolean = true;
const commands: Command[] = [
    { kind: "add", amount: 5 },
    useReset ? { kind: "reset" } : { kind: "subtract", amount: 2 },
    { kind: "add", amount: 3 }
];

let total: number = 0;
for (const command of commands) {
    total = applyCommand(total, command);
}

console.log(`total=${total}`);
  1. useReset ← false, commands ← [object Object],[object Object],[object Object]

    15    return 0;16}1718const useReset→ false: boolean = false;  //@useReset=true19const commands→ [object Object],[object Object],[object Object]: Command[] = [20    { kind: "add", amount: 5 },21    useReset ? { kind: "reset" } : { kind: "subtract", amount: 2 },22    { kind: "add", amount: 3 }23];2425let total→ 0: number = 0;26for (const command of commands) {27    total = applyCommand(total0, command[object Object]);28}
  2. function applyCommand(total: number, command: Command): number

    pass 1 of 3
    3    | { kind: "subtract"; amount: number }4    | { kind: "reset" };56function applyCommand(total0: number, command[object Object]: Command): number {7    if (command.kind === "add") {
    All 3 passes — pass 1 is the card above
    passtotalcommand.kindcommand.amount
    10add5
    25subtract2
    33add3
  3. if (command.kind === "add")

    pass 1 of 2
    6function applyCommand(total: number, command: Command): number {7    if (command.kindadd === "add") {8        return total + command.amount5;9    }
  4. total ← 5

    25let total: number = 0;26for (const command of commands) {27    total = applyCommand(total5, command[object Object]);28}
  5. if (command.kind === "subtract")

    8    return total + command.amount;9}1011if (command.kindsubtract === "subtract") {12    return total - command.amount2;13}
  6. total ← 3

    25let total: number = 0;26for (const command of commands) {27    total = applyCommand(total3, command[object Object]);28}
  7. if (command.kind === "add")

    pass 2 of 2
    6function applyCommand(total: number, command: Command): number {7    if (command.kindadd === "add") {8        return total + command.amount3;9    }
  8. total ← 6

    25let total: number = 0;26for (const command of commands) {27    total = applyCommand(total3, command[object Object]);28}2930console.log(`total=${total6}`);
    outputtotal=6
  1. useReset ← true, commands ← [object Object],[object Object],[object Object]

    15    return 0;16}1718const useReset→ true: boolean = true;19const commands→ [object Object],[object Object],[object Object]: Command[] = [20    { kind: "add", amount: 5 },21    useReset ? { kind: "reset" } : { kind: "subtract", amount: 2 },22    { kind: "add", amount: 3 }23];2425let total→ 0: number = 0;26for (const command of commands) {27    total = applyCommand(total0, command[object Object]);28}
  2. function applyCommand(total: number, command: Command): number

    pass 1 of 3
    3    | { kind: "subtract"; amount: number }4    | { kind: "reset" };56function applyCommand(total0: number, command[object Object]: Command): number {7    if (command.kind === "add") {
    All 3 passes — pass 1 is the card above
    passtotalcommand.kindcommand.amount
    10add5
    25
    30add3
  3. if (command.kind === "add")

    pass 1 of 2
    6function applyCommand(total: number, command: Command): number {7    if (command.kindadd === "add") {8        return total + command.amount5;9    }
  4. total ← 5

    25let total: number = 0;26for (const command of commands) {27    total = applyCommand(total5, command[object Object]);28}
  5. total ← 0

    25let total: number = 0;26for (const command of commands) {27    total = applyCommand(total0, command[object Object]);28}
  6. if (command.kind === "add")

    pass 2 of 2
    6function applyCommand(total: number, command: Command): number {7    if (command.kindadd === "add") {8        return total + command.amount3;9    }
  7. total ← 3

    25let total: number = 0;26for (const command of commands) {27    total = applyCommand(total0, command[object Object]);28}2930console.log(`total=${total3}`);
    outputtotal=3