TypeScript coding conventions for strict, type-safe projects. Use when: (1) writing or reviewing TypeScript code, (2) choosing between `any` and `unknown`, (3) structuring imports with verbatimModuleSyntax, (4) naming functions, booleans, queries, and commands, (5) handling errors with guard clauses and early returns, or (6) avoiding common anti-patterns like primitive obsession, magic strings, and premature abstraction.
Project-wide TypeScript standards that complement agent-specific instructions.
<rules>any: Use unknown if the type is truly dynamic, then narrow.strict: true, noUncheckedIndexedAccess, verbatimModuleSyntax.Readonly<T>, Pick, Omit, and Record for precise types.UserId, OrderId) to prevent mixing.z.infer<typeof schema> over hand-written types when a Zod schema exists.extends and declaration merging.interface for plain objects, type for everything else.// Interface: object shape, extensible
interface User {
id: string;
name: string;
}
interface Employee extends User {
company: string;
}
// Type: union, intersection, computed
type Result = Success | Failure;
type UserProfile = User & { bio: string };
type Nullable<T> = { [K in keyof T]: T[K] | null };
// Prefer this
type HttpMethod = "GET" | "POST" | "PUT" | "DELETE";
type Direction = "north" | "south" | "east" | "west";
// Over this (emits runtime JS)
enum HttpMethod { GET, POST, PUT, DELETE }
Add a type (or kind) literal field to each variant. Always handle exhaustiveness with assertNever.
interface Circle { type: "circle"; radius: number }
interface Square { type: "square"; side: number }
interface Triangle { type: "triangle"; base: number; height: number }
type Shape = Circle | Square | Triangle;
function assertNever(x: never): never {
throw new Error(`Unexpected value: ${x}`);
}
function area(shape: Shape): number {
switch (shape.type) {
case "circle": return Math.PI * shape.radius ** 2;
case "square": return shape.side ** 2;
case "triangle": return (shape.base * shape.height) / 2;
default: return assertNever(shape);
}
}
Always narrow before accessing type-specific properties.
typeof for primitives: typeof x === "string"in for object shapes: "swim" in petfunction isBook(item): item is Bookfunction format(input: string | number): string {
if (typeof input === "string") return input.toUpperCase();
return input.toFixed(2);
}
// Custom type guard
function isError(result: Result): result is ErrorResult {
return result.success === false;
}
extends — never assume properties exist on unconstrained T.T = unknown) when callers often use a single type.// Constrained generic
function getProperty<T, K extends keyof T>(obj: T, key: K): T[K] {
return obj[key];
}
// With default
type ApiResponse<T = unknown> = { data: T; status: number };
Use mapped types to derive variants from a base — never duplicate type definitions.
// All fields optional (equivalent to built-in Partial<T>)
type Optional<T> = { [K in keyof T]?: T[K] };
// All fields nullable
type Nullable<T> = { [K in keyof T]: T[K] | null };
// Key remapping with template literals
type Getters<T> = {
[K in keyof T as `get${Capitalize<string & K>}`]: () => T[K];
};
& to compose smaller interfaces into richer types.never — check for this.type Timestamped = { createdAt: Date; updatedAt: Date };
type UserRecord = User & Timestamped;
// Type-only imports (required by verbatimModuleSyntax)
import type { FastifyInstance } from "fastify";
// Mixed imports: separate values and types
import { z } from "zod/v4";
import type { ZodType } from "zod/v4";
// ioredis: always named import
import { Redis } from "ioredis";
httpErrors or use reply.status().send() — the centralized setErrorHandler formats the response.NotFoundError, ConflictError).getUserById, createReport, isActive, hasPermissionis/has/can/should prefixget, find, list, fetchcreate, update, delete, add, remove<anti_patterns>
RATE_LIMITS, PAGINATION, CACHE)..length on string | number fails at runtime if it's a number.readonly is shallow: readonly prevents reassignment but doesn't freeze nested objects.<T> with no extends loses type info — constrain or use a concrete type.{ status: string } & { status: number } silently collapses to never.default: return assertNever(x) in discriminated union switches.</anti_patterns>
npx skills add jgamaraalv/typescript-conventions下载完整 Skill 目录,包含 SKILL.md 及所有相关文件
Search for places (restaurants, cafes, etc.) via Google Places API proxy on localhost.
Interact with GitHub using the `gh` CLI. Use `gh issue`, `gh pr`, `gh run`, and `gh api` for issues, PRs, CI runs, and advanced queries.
Create or update AgentSkills. Use when designing, structuring, or packaging skills with scripts, references, and assets.
Start voice calls via the OpenClaw voice-call plugin.
Notion API for creating and managing pages, databases, and blocks.
Gemini CLI for one-shot Q&A, summaries, and generation.
Category:developer