typescript-patterns
Skill8 starsChanged 8 days agoMITView on GitHub
Writes and reviews type-safe TypeScript - strict compiler settings, unknown plus narrowing instead of any, discriminated unions with exhaustive checks, satisfies and as const, branded types, and runtime validation at trust boundaries with schema-inferred types. Use when writing or reviewing TypeScript, creating or tightening tsconfig, removing any, type assertions or non-null assertions, modeling domain types, or typing API, form, env or JSON data. Not for plain JavaScript projects.
8 other projects use a version of this.
Adopted by
Most recently taken up by
- MMACABRE/SKILLStheir own version, first seen Nov 8, 2026
- ku5ic/claude-kittheir own version, first seen Nov 4, 2026
- mikulgohil/claude-skillstheir own version, first seen Oct 13, 2026
- sabahattink/antigravity-fullstack-hqtheir own version, first seen Sep 20, 2026
- techmefr/mentistheir own version, first seen Sep 11, 2026
- bjornjee/skillstheir own version, first seen Sep 8, 2026
Used with
Projects with it also use fastapi-patterns and python-patterns.
Other files people use alongside this one.
Files found in the same public projects as a version of this one, more often than they turn up anywhere else. Each project counts once, large collections count for less, and this file's own repository is left out.
seikaikyoSkillfastapi-patterns
FastAPI + SQLModel + Neon PostgreSQL 後端開發最佳實踐。適用於 Render 部署的 API 服務。涵蓋路由設計、資料庫操作、錯誤處理、認證授權。
Also in 3 projects that use this
xenitV1Skillpython-patterns
Python development principles and decision-making. Framework selection, async patterns, type hints, project structure. Teaches thinking, not copying.
Also in 3 projects that use this
Other versions8 projects use a version of this.
Word for word · 0
None found.
Edited · 0
None found.
Wrote their own version · 8
- cohen-liel/hivemind★ 108same name
- sabahattink/antigravity-fullstack-hq★ 30same name
- peopleforrester/claude-dotfiles★ 3same name
- bjornjee/skills★ 2same name
- ku5ic/claude-kit★ 2same name
- MMACABRE/SKILLS★ 1same name
- mikulgohil/claude-skills★ 0same name
- techmefr/mentis★ 0same name
How matches are found. Word for word: the exact same text. Edited: shares passages with this file (“99% same” means 99% of its five-word phrases also appear here; 10% or more counts). Their own version: similar meaning, or, weakest, the same name. We can’t tell which project had it first. Projects are counted among the 200 most-starred candidates, and mentions are links to the repository we found, up to 50 per site, so both counts are a floor.
In this repository60 other files it gives its agents.
- api-designSkill
- auth-implementationSkill
- background-jobsSkill
- building-agentsSkill
- caching-strategiesSkill
- ci-cd-pipelinesSkill
- cloud-cost-optimizationSkill
- codebase-onboardingSkill
- code-review-changesSkill
- concurrency-patternsSkill
- database-migrationsSkill
- database-schema-designSkill
- database-testingSkill
- data-pipelinesSkill
- data-privacy-retentionSkill
- dependency-upgradesSkill
- design-docsSkill
- design-systemsSkill
- dockerfilesSkill
- domain-driven-designSkill
- event-driven-architectureSkill
- file-storage-uploadsSkill
- frontend-securitySkill
- git-workflowSkill
- graphql-apisSkill
- grpc-servicesSkill
- incident-responseSkill
- infrastructure-as-codeSkill
- internationalizationSkill
- kubernetes-deploymentsSkill
- large-scale-migrationsSkill
- legacy-refactoringSkill
- llm-evalsSkill
- llm-integrationSkill
- mcp-serversSkill
- micro-frontendsSkill
- ml-experimentsSkill
- monorepo-managementSkill
- nosql-designSkill
- observabilitySkill
- orm-patternsSkill
- performance-profilingSkill
- postgres-operationsSkill
- rag-systemsSkill
- react-patternsSkill
- realtime-apisSkill
- resilience-patternsSkill
- root-cause-debuggingSkill
- search-integrationSkill
- security-auditSkill
- seo-and-ssrSkill
- sql-optimizationSkill
- system-designSkill
- test-strategySkill
- threat-modelingSkill
- unit-testingSkill
- verify-before-doneSkill
- web-accessibilitySkill
- web-performanceSkill
- writing-skillsSkill
The file
Show its rules and text
We split this skill into the rules it gives an agent
13 rules. 0 are common, 3 are distinctive, and 10 are very specific to this project.
0
Common
Most of the 4 edited versions we read keep these.
3
Distinctive
This author's own choices. Worth a look.
10
Very specific
Tied to their project's names, files or tools. Probably skip.
- Common
- Distinctive
- Very specific
- Very specific: line 22
Type-check before and after: Run npx tsc --noEmit (or the project's typecheck script).
- Distinctive: line 23
Model the domain with unions and literal types: before writing logic (see below).
- Very specific: line 42
Do not flip strict on a large existing codebase in one PR.
- Very specific: line 114
Use satisfies instead of a type annotation when you want the value checked against a type but keep its precise inferred type.
- Distinctive: line 132
Use brands for IDs that are easy to mix up, money in minor units, sanitized HTML, and validated emails or URLs.
- Very specific: line 136
Write the shape once as a schema (zod, valibot, arktype, typebox, or whatever the project uses) and derive the type from it.
- Very specific: line 163
Use const type parameters (5.0+) to infer literal types from arguments without forcing callers to write as const.
- Very specific: line 164
Use NoInfer<T> (5.4+) when one argument should not drive inference, such as a default value.
- Very specific: line 178
Use import type { User } from "./user" and export type { User } for types.
- Very specific: line 188
@ts-ignore: If you truly cannot fix it, use // @ts-expect-error <reason>, which fails once the error is gone. - Very specific: line 191
Function,Objector{}as types: Use a real signature, Record<string, unknown> or unknown. - Distinctive: line 199
Every value that crosses a trust boundary is validated at runtime, and its type is inferred from the schema.
- Very specific: line 200
Every union switch has an exhaustive never check.
“Common” is measured: kept by at least 6 in 10 of the 4 edited versions of this file we could read. “Very specific” is our guess from the words: it names a file, a command, a setting or the project itself.
Files
TypeScript Patterns
Types are only as true as the data that reaches them. Make the compiler as strict as the codebase can bear, validate everything that crosses a trust boundary at runtime, and never silence an error you have not understood.
When to use
- Writing new TypeScript or reviewing a TypeScript diff.
- Creating
tsconfig.jsonor tightening an existing one. - Code with
any,as,!or@ts-ignorescattered around. - Typing data from HTTP APIs, forms, env vars,
localStorage, queues, webhooks orJSON.parse.
Workflow
- Read the config first. Open
tsconfig.jsonand itsextendschain. Runnpx tsc --showConfigto see the effective settings. Notestrict,noUncheckedIndexedAccess,module,moduleResolutionandverbatimModuleSyntax. - Check the TypeScript version (
npx tsc -v) before using newer syntax.satisfiesneeds 4.9+,consttype parameters 5.0+,NoInfer5.4+, inferred type predicates 5.5+. - Type-check before and after. Run
npx tsc --noEmit(or the project'stypecheckscript). A change that adds errors is not done. - Model the domain with unions and literal types before writing logic (see below).
- Put a runtime validator at every trust boundary and derive the static type from the schema.
- Remove escape hatches (
any,as,!,@ts-ignore) in code you touch and replace them with narrowing. If one must stay, add a comment saying why it is safe. - Re-run the type checker, the linter and the tests.
Compiler settings
For new code, strict: true is non-negotiable. Add the extra flags below by default; see references/tsconfig.md for full configs per project type and a migration plan for existing code.
| Option | What it buys | Cost |
strict | strictNullChecks, noImplicitAny, strictFunctionTypes, useUnknownInCatchVariables and more | Required |
noUncheckedIndexedAccess | arr[i] and record[key] become T | undefined | Extra checks in index-heavy code; worth it |
exactOptionalPropertyTypes | { a?: string } stops accepting an explicit a: undefined | Noisy with some libraries; adopt in new code |
noImplicitOverride | Subclass overrides must say override | Low |
noFallthroughCasesInSwitch | Catches missing break/return | Low |
verbatimModuleSyntax | Type-only imports must say import type; predictable output | Use with ESM; in CommonJS output it forces import x = require() |
isolatedModules | Each file can be compiled alone (esbuild, swc, Babel, Vite) | Bans a few constructs such as cross-file const enum |
Do not flip strict on a large existing codebase in one PR. Follow the incremental plan in the reference.
Core patterns
unknown over any, then narrow
function parseConfig(raw: string): Config {
const data: unknown = JSON.parse(raw);
return ConfigSchema.parse(data); // validated, typed
}
try {
await save();
} catch (err: unknown) {
const message = err instanceof Error ? err.message : String(err);
logger.error({ message }, "save failed");
}Narrowing toolkit: typeof, instanceof, in, Array.isArray, discriminant checks, type predicates ((x): x is User) and assertion functions (asserts x is User). From 5.5, .filter((x) => x !== undefined) narrows the array type on its own.
Make illegal states unrepresentable
Replace bags of optional fields with a discriminated union, and make the compiler prove every case is handled.
// Bad: loading, data and error can all be set at once
type State<T> = { loading: boolean; data?: T; error?: Error };
// Good
type State<T> =
| { status: "idle" }
| { status: "loading" }
| { status: "success"; data: T }
| { status: "error"; error: Error };
function assertNever(value: never): never {
throw new Error(`Unhandled case: ${JSON.stringify(value)}`);
}
function render<T>(state: State<T>): string {
switch (state.status) {
case "idle": return "Start";
case "loading": return "Loading...";
case "success": return `Got ${String(state.data)}`;
case "error": return state.error.message;
default: return assertNever(state); // compile error if a case is added and not handled
}
}Literal unions and as const objects instead of enum
export const Role = { Admin: "admin", Member: "member", Viewer: "viewer" } as const;
export type Role = (typeof Role)[keyof typeof Role]; // "admin" | "member" | "viewer"Plain string unions are erasable, tree-shake cleanly and work with Node's type stripping and erasableSyntaxOnly. Keep an existing enum if the codebase standardizes on it; don't mix both styles in one module.
satisfies to check without widening
const routes = {
home: "/",
userDetail: "/users/:id",
} as const satisfies Record<string, `/${string}`>;
routes.userDetail; // type is "/users/:id", not stringUse satisfies instead of a type annotation when you want the value checked against a type but keep its precise inferred type.
Branded types for IDs and validated values
type Brand<T, B extends string> = T & { readonly __brand: B };
export type UserId = Brand<string, "UserId">;
export type OrderId = Brand<string, "OrderId">;
export function toUserId(raw: string): UserId {
if (!raw.startsWith("usr_")) throw new Error(`Invalid user id: ${raw}`);
return raw as UserId; // the only place this assertion is allowed
}
declare function getOrder(id: OrderId): Promise<Order>;
// getOrder(userId) is now a compile errorUse brands for IDs that are easy to mix up, money in minor units, sanitized HTML, and validated emails or URLs.
Validate at trust boundaries, infer the type from the schema
Write the shape once as a schema (zod, valibot, arktype, typebox, or whatever the project uses) and derive the type from it. Never cast await res.json() to a type.
import { z } from "zod";
const User = z.object({
id: z.string().min(1),
name: z.string(),
role: z.enum(["admin", "member", "viewer"]),
});
export type User = z.infer<typeof User>;
export async function fetchUser(id: string): Promise<User> {
const res = await fetch(`/api/users/${encodeURIComponent(id)}`);
if (!res.ok) throw new Error(`GET /api/users failed: ${res.status}`);
const parsed = User.safeParse(await res.json());
if (!parsed.success) throw new Error(`Unexpected user payload: ${parsed.error.message}`);
return parsed.data;
}Boundaries that need validation: HTTP responses and request bodies, env vars (validate once at startup and export a typed env object), JSON.parse, localStorage, URL and query params, postMessage, webhooks, queue messages, and rows from loosely typed DB drivers.
Generics that earn their keep
- A type parameter should relate two or more things (an input to an output, two arguments). One that appears once is
anyin disguise; use the concrete type orunknown. - Constrain parameters:
function pick<T, K extends keyof T>(obj: T, keys: K[]): Pick<T, K>. - Use
consttype parameters (5.0+) to infer literal types from arguments without forcing callers to writeas const. - Use
NoInfer<T>(5.4+) when one argument should not drive inference, such as a default value.
Utility types worth knowing
| Type | Use |
Pick, Omit | Derive view types from a model. Omit is not distributive over unions; write a distributive helper if needed |
Partial, Required, Readonly | Patch objects, config with defaults, immutable inputs |
Record<K, V> | Maps with known key sets (Record<Role, Permission[]>) |
ReturnType, Parameters, Awaited | Derive types from functions you don't own |
Extract, Exclude, NonNullable | Filter unions |
Imports and exports
Use import type { User } from "./user" and export type { User } for types. It keeps runtime output predictable and is required under verbatimModuleSyntax.
Public API types
Annotate return types of exported functions and public methods so the API cannot change by accident. Let local variables and private helpers infer.
Anti-patterns
asto make an error go away. It hides the bug the compiler found. Narrow or validate instead.as unknown as Tis always a red flag.- Non-null
!. Replace with a check and an early return or a thrown error that says what was missing. @ts-ignore. If you truly cannot fix it, use// @ts-expect-error <reason>, which fails once the error is gone.- Trusting
JSON.parse,res.json()orany-typed library output. Validate it. - Bags of optional properties where a discriminated union fits.
Function,Objector{}as types. Use a real signature,Record<string, unknown>orunknown.- Duplicated types that drift from the schema, the DB model or the API contract. Derive them.
- Type-level cleverness that nobody on the team can read. Prefer a simple type plus a runtime check.
- Turning off strict flags to get a build green.
Done when
tsc --noEmitpasses with no new errors and no newany,as,!or@ts-ignorewithout a comment justifying it.- Every value that crosses a trust boundary is validated at runtime, and its type is inferred from the schema.
- Every union
switchhas an exhaustivenevercheck. - Exported functions have explicit return types.
References
- references/tsconfig.md — read when creating or changing
tsconfig.json, choosingmodule/moduleResolution, or migrating an existing codebase to stricter settings.
Does it work?
Reviews from people and from AI agents that ran it: what they used it for, and whether it worked.
Reviews can't be read right now.
Your trail
Files you open will collect here, so you can find them again.
Open two or more, and you can remix the rules you like from each into one list. Kept in this browser only.
Now do this for your own.
Bring your CLAUDE.md, AGENTS.md or skills. agentleFS shows which of your rules are common, which are yours and which only fit one project, and keeps one version your agents read everywhere.

