typescript-satisfies-operator

Guides proper usage of TypeScript's satisfies operator vs type annotations. Use this skill when deciding between type annotations (colon) and satisfies, validating object shapes while preserving literal types, or troubleshooting type inference issues.

Safety Notice

This listing is imported from skills.sh public index metadata. Review upstream SKILL.md and repository scripts before running.

Copy this and send it to your AI assistant to learn

Install skill "typescript-satisfies-operator" with this command: npx skills add flpbalada/my-opencode-config/flpbalada-my-opencode-config-typescript-satisfies-operator

TypeScript: The satisfies Operator

Core Concept

The satisfies operator validates that an expression matches a type without changing the inferred type. This is different from type annotations (:) which widen the type.

Key insight from Matt Pocock:

  • "When you use a colon, the type BEATS the value"
  • "When you use satisfies, the value BEATS the type"

Type Annotation vs Satisfies

type RoutingPathname = "/products" | "/cart" | "/checkout";

// Type annotation - widens to union
const url1: RoutingPathname = "/products";
// url1 is typed as: RoutingPathname (wide)

// Satisfies - keeps literal
const url2 = "/products" satisfies RoutingPathname;
// url2 is typed as: '/products' (narrow)

// Why it matters:
const test1: "/products" = url1; // Error: RoutingPathname not assignable to '/products'
const test2: "/products" = url2; // Works

Classic Use Case: Object Validation with Preserved Types

type Colors = "red" | "green" | "blue";
type RGB = [red: number, green: number, blue: number];

// Type annotation loses specific property types
const palette1: Record<Colors, string | RGB> = {
  red: [255, 0, 0],
  green: "#00ff00",
  blue: [0, 0, 255],
};
palette1.green.toUpperCase(); // Error: 'toUpperCase' doesn't exist on string | RGB

// Satisfies validates AND preserves literal types
const palette2 = {
  red: [255, 0, 0],
  green: "#00ff00",
  bleu: [0, 0, 255], // Error: Typo caught!
} satisfies Record<Colors, string | RGB>;
palette2.green.toUpperCase(); // Works - green is inferred as string

When to Use What

Annotation StyleType vs ValueUse Case
: Type (colon)Type winsNeed wider type for reassignment
satisfies TypeValue winsNeed validation + narrow inference
as TypeLies to TSEscape hatch (use sparingly!)
No annotationInferenceMost common - let TS infer

Rule of Thumb

Use satisfies when:

  1. You want the EXACT type of the variable, not the wider type
  2. The type is complex enough that you want validation you didn't mess it up

Use colon annotation when:

  1. You need to reassign the variable later with different values of the union
  2. You explicitly want the wider type

Common Pattern: as const satisfies

Combine as const for immutability with satisfies for validation:

const routes = {
  home: "/",
  products: "/products",
  cart: "/cart",
} as const satisfies Record<string, string>;

// routes.home is typed as '/' (readonly literal)
// But validated against Record<string, string>

Prefer as const satisfies Over Type Annotation

When you need both validation AND literal type preservation:

// Bad - type annotation widens types, loses literals
const LANG_MAP: Record<string, string> = {
  en: '1',
  cs: '2',
} as const;
// LANG_MAP.en is just string, not '1'

// Good - satisfies validates while preserving literal types
const LANG_MAP = {
  en: '1',
  cs: '2',
} as const satisfies Record<string, string>;
// LANG_MAP.en is '1' (narrow literal type)

Real-World Example: Config Validation

type Locale = 'en' | 'cs';

// Validates all locales are present, preserves specific values
const SHOP_GRAPHQL_LOCALE_LANGUAGE_ID_MAP = {
  en: '1',
  cs: '2',
} as const satisfies Record<Locale, string>;

// TypeScript will error if you miss a locale:
const INCOMPLETE_MAP = {
  en: '1',
  // cs: '2',  // Error: Property 'cs' is missing
} as const satisfies Record<Locale, string>;

Real-World Examples

Configuration Objects

type Config = {
  api: string;
  timeout: number;
  retries: number;
};

// Validates shape, but keeps literal types for autocomplete
const config = {
  api: "https://api.example.com",
  timeout: 5000,
  retries: 3,
} satisfies Config;

// config.api is 'https://api.example.com', not string

Event Handlers Map

type EventMap = Record<string, (...args: unknown[]) => void>;

const handlers = {
  click: (x: number, y: number) => console.log(x, y),
  submit: (data: FormData) => console.log(data),
} satisfies EventMap;

// handlers.click is (x: number, y: number) => void
// Not (...args: unknown[]) => void

Exhaustive Checks with Records

type Status = "pending" | "approved" | "rejected";

const statusLabels = {
  pending: "Waiting for review",
  approved: "Approved",
  rejected: "Rejected",
} satisfies Record<Status, string>;

// If you add a new Status, TypeScript will error until you add it here

References

Source Transparency

This detail page is rendered from real SKILL.md content. Trust labels are metadata-based hints, not a safety guarantee.

Related Skills

Related by shared tags or category signals.

Coding

trust-psychology

No summary provided by upstream source.

Repository SourceNeeds Review
Coding

social-proof-psychology

No summary provided by upstream source.

Repository SourceNeeds Review
Coding

five-whys

No summary provided by upstream source.

Repository SourceNeeds Review
Coding

cognitive-fluency-psychology

No summary provided by upstream source.

Repository SourceNeeds Review