Skip to content

[Feature] (no-op) DistributedData.decodeCrdt is already pattern-matched — reference example #231

@pathosDev

Description

@pathosDev

Size / Priority

  • Size: No change — reference example, no work required.
  • Category: C.1 Pattern-Matching — documentation / codebase-style item.

Affected files

  • src/crdt/DistributedData.ts:49-71decodeCrdt discriminated-union dispatch.

Status — no implementation needed

This issue is intentionally a no-op. decodeCrdt already uses a TypeScript-exhaustive switch:

export function decodeCrdt(json: CrdtJson): Crdt<any> {
  switch (json.kind) {
    case 'GCounter':    return GCounter.fromJSON(json);
    case 'PNCounter':   return PNCounter.fromJSON(json);
    case 'GSet':        return GSet.fromJSON<unknown>(json);
    case 'ORSet':       return ORSet.fromJSON<unknown>(json);
    case 'LWWRegister': return LWWRegister.fromJSON<unknown>(json);
    case 'GCounterMap': return GCounterMap.fromJSON<unknown>(json);
    case 'LWWMap':      return LWWMap.fromJSON<unknown, unknown>(json);
    case 'MVRegister':  return MVRegister.fromJSON<unknown>(json);
    case 'ORMap':       return ORMap.fromJSON<unknown, Crdt<any>>(json, /* ... */);
    default: {
      const _exhaustive: never = json;
      throw new Error(`unknown crdt kind: ${(_exhaustive as { kind: string }).kind}`);
    }
  }
}

The const _exhaustive: never = json assignment in the default branch is the compile-time exhaustiveness guard — if a new variant is added to CrdtJson without updating this switch, TypeScript reports an error here because json would no longer be assignable to never.

This is functionally equivalent to match().exhaustive() and arguably faster (native switch vs library function call).

Why this is the "reference example" in the catalog

Other items in C.1 (#230, #232#247) propose converting if-chains and instanceof-chains to match().exhaustive() for the same exhaustiveness property. This site demonstrates that vanilla switch + never-assignment achieves the same property when:

  • The discriminator is a string literal union (not nominal types — for those, match is still preferred).
  • The arms are simple call dispatches (not multi-clause guards).

For both forms, the exhaustiveness check is what matters. Either is acceptable; the codebase happens to use both.

Action

  • Document this pattern in CONTRIBUTING.md (or equivalent doc) as the codebase's reference for discriminator-union dispatch.
  • Note in the doc when to prefer switch (string-literal unions, simple dispatches) vs match (nominal types via P.instanceOf, guard predicates, complex patterns).
  • Close this issue as "verified — no change required" once the doc is in place.

Acceptance criteria

  • No code change in DistributedData.ts.
  • Documentation entry referencing this site as the reference pattern.
  • (Optional) Lint rule that warns on switch without a never-typed default branch in the codebase (would catch any new switch that forgot the guard).

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or requestpriority: lowNice-to-have / niche / demand-driven

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions