Loading...
Loading...
Modern TypeScript patterns your AI agent should use. Strict mode, discriminated unions, satisfies operator, const assertions, and type-safe patterns for TypeScript 5.x.
npx skill4agent add ofershap/typescript-best-practices typescript-best-practicesanyunknownsatisfies{
"compilerOptions": {
"strict": false,
"target": "ES2020"
}
}{
"compilerOptions": {
"strict": true,
"noUncheckedIndexedAccess": true,
"exactOptionalPropertyTypes": true,
"noImplicitOverride": true,
"target": "ES2022"
}
}noUncheckedIndexedAccessconst config = {
port: 3000,
host: "localhost",
} as Config;
config.port.toFixed(); // No error even if port could be stringconst config = {
port: 3000,
host: "localhost",
} satisfies Config;
config.port.toFixed(); // TypeScript knows port is numbersatisfiesassatisfiesasinterface ApiResponse {
data?: User;
error?: string;
loading?: boolean;
}type ApiResponse =
| { status: "loading" }
| { status: "success"; data: User }
| { status: "error"; error: string };const ROUTES = {
home: "/",
about: "/about",
contact: "/contact",
};
// Type: { home: string; about: string; contact: string }const ROUTES = {
home: "/",
about: "/about",
contact: "/contact",
} as const;
// Type: { readonly home: "/"; readonly about: "/about"; readonly contact: "/contact" }as conststringfunction parseJson(text: string): any {
return JSON.parse(text);
}
const data = parseJson('{"name": "test"}');
data.nonExistent.method(); // No error - runtime crashfunction parseJson(text: string): unknown {
return JSON.parse(text);
}
const data = parseJson('{"name": "test"}');
if (isUser(data)) {
data.name; // Safe - type narrowed
}anyunknownfunction getLocaleMessage(id: string): string { ... }type Locale = 'en' | 'ja' | 'pt';
type MessageKey = 'welcome' | 'goodbye';
type LocaleMessageId = `${Locale}_${MessageKey}`;
function getLocaleMessage(id: LocaleMessageId): string { ... }function createLight<C extends string>(colors: C[], defaultColor?: C) { ... }
createLight(['red', 'green', 'blue'], 'purple'); // No error - purple widens Cfunction createLight<C extends string>(colors: C[], defaultColor?: NoInfer<C>) { ... }
createLight(['red', 'green', 'blue'], 'purple'); // Error - 'purple' not in CNoInfer<T>function getUser(id: string): User { ... }
function getOrder(id: string): Order { ... }
const userId = getUserId();
getOrder(userId); // No error - but wrong!type UserId = string & { readonly __brand: 'UserId' };
type OrderId = string & { readonly __brand: 'OrderId' };
function getUser(id: UserId): User { ... }
function getOrder(id: OrderId): Order { ... }
const userId = getUserId();
getOrder(userId); // Error - UserId is not OrderIdfunction handleStatus(status: "active" | "inactive" | "pending") {
switch (status) {
case "active":
return "Active";
case "inactive":
return "Inactive";
// 'pending' silently falls through
}
}function handleStatus(status: "active" | "inactive" | "pending") {
switch (status) {
case "active":
return "Active";
case "inactive":
return "Inactive";
case "pending":
return "Pending";
default: {
const _exhaustive: never = status;
throw new Error(`Unhandled status: ${_exhaustive}`);
}
}
}neverfunction processItem(item: unknown) {
const user = item as User;
console.log(user.name);
}function isUser(item: unknown): item is User {
return typeof item === "object" && item !== null && "name" in item && "email" in item;
}
function processItem(item: unknown) {
if (isUser(item)) {
console.log(item.name); // Safe - narrowed to User
}
}item is Useras Userimport { User, UserService } from "./user";
// User is only used as a type, but gets included in the bundleimport type { User } from "./user";
import { UserService } from "./user";import typeinterface Config {
[key: string]: string;
}type Config = Record<string, string>;
// Or better - use a specific union for keys:
type Config = Record<"host" | "port" | "env", string>;Record<K, V>const file = openFile("data.txt");
try {
processFile(file);
} finally {
file.close();
}using file = openFile("data.txt");
processFile(file);
// file.close() called automatically via Symbol.disposeusingwithusingstrict: truenoUncheckedIndexedAccess: truesatisfiestypekindas constimport typeswitchneveranyunknownas// @ts-ignore// @ts-expect-errorenumas constFunction