install
source · Clone the upstream repo
git clone https://github.com/Intense-Visions/harness-engineering
Claude Code · Install into ~/.claude/skills/
T=$(mktemp -d) && git clone --depth=1 https://github.com/Intense-Visions/harness-engineering "$T" && mkdir -p ~/.claude/skills && cp -r "$T/agents/skills/codex/ts-type-guards" ~/.claude/skills/intense-visions-harness-engineering-ts-type-guards-9c9708 && rm -rf "$T"
manifest:
agents/skills/codex/ts-type-guards/SKILL.mdsource content
TypeScript Type Guards
Narrow union types safely using type guards, assertion functions, and control flow
When to Use
- Narrowing union types to access type-specific properties
- Validating unknown data from APIs or user input
- Replacing unsafe type assertions (
) with safe runtime checksas - Creating reusable narrowing functions for domain types
Instructions
guard for primitives:typeof
function format(value: string | number): string { if (typeof value === 'string') { return value.toUpperCase(); // Narrowed to string } return value.toFixed(2); // Narrowed to number }
guard for class instances:instanceof
function handleError(error: Error | string): string { if (error instanceof Error) { return error.message; // Narrowed to Error } return error; // Narrowed to string }
- Custom type guard with
predicate:is
interface Cat { meow(): void; } interface Dog { bark(): void; } function isCat(animal: Cat | Dog): animal is Cat { return 'meow' in animal; } function interact(animal: Cat | Dog) { if (isCat(animal)) { animal.meow(); // Narrowed to Cat } else { animal.bark(); // Narrowed to Dog } }
- Narrowing with
operator:in
type ApiResponse = { status: 'success'; data: User } | { status: 'error'; message: string }; function handle(response: ApiResponse) { if ('data' in response) { console.log(response.data); // Narrowed to success variant } }
- Assertion function — narrows or throws:
function assertDefined<T>(value: T | null | undefined, name: string): asserts value is T { if (value == null) { throw new Error(`Expected ${name} to be defined`); } } function process(user: User | null) { assertDefined(user, 'user'); // user is narrowed to User after this line console.log(user.name); }
- Narrow
values safely:unknown
function isRecord(value: unknown): value is Record<string, unknown> { return typeof value === 'object' && value !== null && !Array.isArray(value); } function isUser(value: unknown): value is User { return isRecord(value) && typeof value.id === 'string' && typeof value.email === 'string'; }
- Array filtering with type guards:
const items: (string | null)[] = ['a', null, 'b', null, 'c']; // Without type guard: (string | null)[] const filtered = items.filter((x) => x !== null); // With type guard: string[] const filtered = items.filter((x): x is string => x !== null);
- Discriminated union narrowing:
type Shape = { kind: 'circle'; radius: number } | { kind: 'rect'; width: number; height: number }; function area(shape: Shape): number { switch (shape.kind) { case 'circle': return Math.PI * shape.radius ** 2; case 'rect': return shape.width * shape.height; } }
Details
Type guards are runtime checks that TypeScript uses to narrow types in subsequent code. They bridge the gap between TypeScript's static type system and JavaScript's dynamic runtime.
Control flow analysis: TypeScript tracks type narrowing through
if/else, switch, return, throw, and assignment. After a type guard condition, the type is narrowed in the true branch and the complement is narrowed in the else branch.
predicates vs boolean returns: A function returning is
boolean does not narrow types at the call site. A function returning x is Type does. The is predicate is a promise to the compiler — if you return true, the value IS that type.
Assertion functions (
asserts x is T) narrow the type AFTER the function call returns. If the assertion fails, the function must throw. This is useful for precondition checks at the top of functions.
vs asserts
:is
— used in conditions (is
,if
). Returns boolean.filter
— used as a statement. Throws on failure. Narrows for subsequent code.asserts
Truthiness narrowing: TypeScript narrows
if (value) to exclude null, undefined, 0, '', and false. This is convenient but can exclude valid falsy values — use explicit != null when 0 or '' are valid.
Trade-offs:
- Custom type guards with
put the burden of correctness on the developer — an incorrect guard silently misnarrowsis
does not work across iframes, realms, or serialized objectsinstanceof- Assertion functions cannot be used in expression position — they must be standalone statements
- Deep narrowing of nested objects requires multiple guard calls or a comprehensive validation library like Zod
Source
https://typescriptlang.org/docs/handbook/2/narrowing.html
Process
- Read the instructions and examples in this document.
- Apply the patterns to your implementation, adapting to your specific context.
- Verify your implementation against the details and edge cases listed above.
Harness Integration
- Type: knowledge — this skill is a reference document, not a procedural workflow.
- No tools or state — consumed as context by other skills and agents.
Success Criteria
- The patterns described in this document are applied correctly in the implementation.
- Edge cases and anti-patterns listed in this document are avoided.