# Type Safety

> Type-safety discipline for all TypeScript work — prove unknown values, avoid any/as/non-null assertions, prefer inference and exhaustive unions. Applies whenever editing or creating TypeScript files.

- Skill: `g-bastianelli/type-safety` (Agent Skill)
- Install (CLI): `npx skillmds@latest add g-bastianelli/type-safety`
- Raw SKILL.md: https://api.skillmd.com/api/skills/g-bastianelli/type-safety/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: AI & ML
- Author: g-bastianelli (https://skillmd.com/u/g-bastianelli)
- Updated: 2026-09-17
- Page: https://skillmd.com/skills/g-bastianelli/type-safety

---


# subroutine — type-safety discipline

Apply this to every TypeScript file. Read the nearest `AGENTS.md` first; the
repo wins when it defines a stricter or different convention.

## Prove types; do not silence the checker

- Never use `any`, `as T`, or non-null `!`. Narrow `unknown`, parse structured
  external data with the repo's schema library, and handle absence explicitly.
- Do not add lint disables to bypass these rules. A difficult type usually
  exposes a missing boundary or an imprecise model.
- `as const` is allowed for literals. Use `satisfies` when checking an object's
  shape without widening its inferred values.

```ts
const config = {
  mode: "strict",
  retries: 3,
} satisfies WorkerConfig;

const first = rows[0];
if (!first) return err({ code: "NOT_FOUND" });
return ok(first);
```

## Let inference work

- Annotate exported API signatures and intentional boundaries; let local
  variables and callbacks infer.
- Derive types from their source (`z.infer`, `Awaited<ReturnType<...>>`, indexed
  access) instead of redeclaring the same shape.

## Model finite states explicitly

- Prefer string-literal unions to TypeScript `enum`.
- Expose named value sets as object-as-const when callers need symbolic access.
- Match discriminated unions with more than two variants using `ts-pattern`
  `.exhaustive()`.

```ts
export const JOB_STATUS = {
  idle: "idle",
  running: "running",
  failed: "failed",
} as const;
export type JobStatus = (typeof JOB_STATUS)[keyof typeof JOB_STATUS];

const label = match(status)
  .with("idle", () => "Idle")
  .with("running", () => "Running")
  .with("failed", () => "Failed")
  .exhaustive();
```

When using Drizzle/Postgres, prefer a typed text column plus boundary validation
to `pgEnum`; database enum migrations make removal and renaming unnecessarily
rigid. Skip this rule when the repository deliberately standardizes otherwise.

