typescript-conventions
TypeScript Conventions
Project-wide TypeScript standards that complement agent-specific instructions.
Type Safety
- No
any: Useunknownif the type is truly dynamic, then narrow. - Strict config:
strict: true,noUncheckedIndexedAccess,verbatimModuleSyntax. - Use
Readonly<T>,Pick,Omit, andRecordfor precise types. - Use branded types for entity IDs (e.g.,
UserId,OrderId) to prevent mixing. - Prefer
z.infer<typeof schema>over hand-written types when a Zod schema exists.
Interface vs Type
- Interfaces for object shapes that may grow — they support
extendsand declaration merging. - Type aliases for unions, intersections, mapped types, and complex compositions.
- Simple rule:
interfacefor plain objects,typefor 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 };
Unions and Literal Types
- Prefer literal unions over enums — zero runtime cost, better tree-shaking, full autocomplete.
- Use enums only when you need a runtime object (iteration, reverse lookup).
// 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 }
Discriminated Unions
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);
}
}
Type Narrowing
Always narrow before accessing type-specific properties.
typeoffor primitives:typeof x === "string"infor object shapes:"swim" in pet- Custom type guards for reusable checks:
function isBook(item): item is Book
function 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;
}
Generics
- Constrain with
extends— never assume properties exist on unconstrainedT. - Use defaults (
T = unknown) when callers often use a single type. - Keep generics to one or two parameters; more suggests the function is too broad.
// 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 };
Mapped & Template Literal Types
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];
};
Intersection Types
- Use
&to compose smaller interfaces into richer types. - If two intersected types define the same key with incompatible types, the result collapses to
never— check for this.
type Timestamped = { createdAt: Date; updatedAt: Date };
type UserRecord = User & Timestamped;
Imports
// 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";
Error Handling
- Handle errors at the beginning of functions with early returns / guard clauses.
- Avoid deep nesting — use if-return pattern instead of else chains.
- In Fastify routes, throw
httpErrorsor usereply.status().send()— the centralizedsetErrorHandlerformats the response. - Custom error classes for domain-specific errors (e.g.,
NotFoundError,ConflictError).
Naming
- Functions:
getUserById,createReport,isActive,hasPermission - Booleans:
is/has/can/shouldprefix - Query (returns data):
get,find,list,fetch - Command (changes state):
create,update,delete,add,remove
<anti_patterns>
Anti-Patterns
- Primitive obsession: Use branded types or Zod enums, not raw strings for IDs and statuses.
- Magic numbers/strings: Use constants from a shared package (e.g.,
RATE_LIMITS,PAGINATION,CACHE). - Long parameter lists: Use an options object or a Zod schema.
- Premature abstraction: Three similar lines > one premature helper. Abstract on the third repetition.
- Using union values without narrowing: Accessing
.lengthonstring | numberfails at runtime if it's a number. - Unions too broad: A dozen options may suggest generics or a different pattern.
readonlyis shallow:readonlyprevents reassignment but doesn't freeze nested objects.- Enums for simple sets: Prefer literal unions when you don't need runtime iteration.
- Unconstrained generics:
<T>with noextendsloses type info — constrain or use a concrete type. - Conflicting intersections:
{ status: string } & { status: number }silently collapses tonever. - Forgetting exhaustiveness: Always add a
default: return assertNever(x)in discriminated union switches.
</anti_patterns>
More from jgamaraalv/ts-dev-kit
bullmq
BullMQ queue system reference for Redis-backed job queues, workers, flows, and schedulers. Use when: (1) creating queues and workers with BullMQ, (2) adding jobs (delayed, prioritized, repeatable, deduplicated), (3) setting up FlowProducer parent-child job hierarchies, (4) configuring retry strategies, rate limiting, or concurrency, (5) implementing job schedulers with cron/interval patterns, (6) preparing BullMQ for production (graceful shutdown, Redis config, monitoring), or (7) debugging stalled jobs or connection issues
46owasp-security-review
Review code and architectures against the OWASP Top 10:2025 — the ten most critical web application security risks. Use when: (1) reviewing code for security vulnerabilities, (2) auditing a feature or codebase against OWASP categories, (3) providing remediation guidance for identified vulnerabilities, (4) writing new code and needing secure coding patterns. Triggers: 'review for security', 'OWASP audit', 'check for vulnerabilities','security checklist', 'is this code secure', 'security review', 'fix vulnerability'.
42ioredis
ioredis v5 reference for Node.js Redis client — connection setup, RedisOptions, pipelines, transactions, Pub/Sub, Lua scripting, Cluster, and Sentinel. Use when: (1) creating or configuring Redis connections (standalone, cluster, sentinel), (2) writing Redis commands with ioredis (get/set, pipelines, multi/exec), (3) setting up Pub/Sub or Streams, (4) configuring retryStrategy, TLS, or auto-pipelining, (5) working with Redis Cluster options (scaleReads, NAT mapping), or (6) debugging ioredis connection issues. Important: use named import `import { Redis } from 'ioredis'` for correct TypeScript types with NodeNext.
35nextjs-best-practices
Next.js App Router best practices — file conventions, RSC boundaries, data patterns, async APIs, metadata, error handling, route handlers, image/font optimization, bundling. Use when writing, reviewing, or debugging Next.js App Router code.
29ui-ux-guidelines
Review UI code for Web Interface Guidelines compliance. Use when asked to review UI, check accessibility, audit design, review UX, or check against best practices.
26service-worker
Service Worker API implementation guide — registration, lifecycle management, caching strategies, push notifications, and background sync. Use when: (1) creating or modifying service worker files (sw.js), (2) implementing offline-first caching (cache-first, network-first, stale-while-revalidate), (3) setting up push notifications or background sync, (4) debugging service worker registration, scope, or update issues, (5) implementing navigation preload, (6) user mentions 'service worker', 'sw.js', 'offline support', 'cache strategy', 'push notification', 'background sync', 'workbox alternative', or 'PWA caching'.
25