TypeScript/JavaScript Rules
These rules come from app/rules/typescript/ in ai-toolkit. They cover
the project's standards for coding style, frameworks, patterns,
security, and testing in TypeScript/JavaScript. Apply them when writing or
reviewing TypeScript/JavaScript code.
TypeScript Coding Style
Strict Mode
- Always use
strict: truein tsconfig.json. - Never use
any-- useunknown+ type guards instead. - Prefer
interfaceovertypefor object shapes (extendable). - Use
as constfor literal types and readonly tuples.
Naming
- PascalCase: types, interfaces, enums, classes, components.
- camelCase: variables, functions, methods, properties.
- UPPER_SNAKE: constants, env vars.
- Prefix interfaces with
Ionly if project convention requires it.
Functions
- Prefer arrow functions for callbacks and inline.
- Use
functiondeclarations for hoisted, named functions. - Max 3 parameters -- use options object beyond that.
- Always type return values for public/exported functions.
Imports
- Group: node builtins, external, internal, relative.
- Use
typeimports:import type { Foo } from './foo'. - No barrel exports unless at package boundary.
- Prefer named exports over default exports.
Types
- Use discriminated unions over class hierarchies for state.
- Use
readonlyfor arrays and objects that should not be mutated. - Use
satisfiesoperator to validate types without widening. - Prefer
unknownoveranyat API boundaries. - Use template literal types for string patterns.
Avoid
enum-- useas constobjects or union types.namespace-- use ES modules.privatekeyword -- use#private fields.- Non-null assertion
!-- use proper type narrowing. astype casting -- use type guards and narrowing.
Configuration
- Enable
noUncheckedIndexedAccessfor safer array/object access. - Enable
exactOptionalPropertyTypesto distinguishundefinedfrom missing. - Use
moduleResolution: "bundler"for modern projects. - Set
isolatedModules: truefor bundler compatibility.
TypeScript Frameworks
React
- Use function components exclusively. No class components.
- Colocate state with the component that owns it. Lift only when needed.
- Use
useCallbackanduseMemoonly when profiling shows a need. - Use
React.lazy()+ Suspense for code-splitting routes. - Avoid prop drilling past 2 levels -- use Context or state management.
Next.js (App Router)
- Default to Server Components. Add
"use client"only when needed. - Use Server Actions for mutations. Never expose internal APIs to client.
- Use
loading.tsxanderror.tsxfor streaming and error boundaries. - Fetch data in Server Components, not in useEffect on client.
- Use
revalidatePath/revalidateTagfor cache invalidation.
Express / Fastify / Hono
- Use layered architecture: route -> controller -> service -> repository.
- Validate request body/params/query with Zod middleware.
- Centralize error handling in a single error middleware.
- Use async route handlers with proper error forwarding.
- Return consistent response shapes:
{ data }or{ error }.
State Management
- Use Zustand or Jotai for client state. Redux only for complex existing apps.
- Use TanStack Query (React Query) for server state.
- Separate server state (fetched data) from client state (UI state).
- Never duplicate server data in client state stores.
ORM / Database
- Use Drizzle for new projects (SQL-like, type-safe, lightweight).
- Use Prisma for rapid prototyping (schema-first, great DX).
- Always use migrations. Never modify schema manually in production.
- Use transactions for multi-table operations.
Node.js Runtime
- Use
node:prefix for built-in modules:import { readFile } from 'node:fs/promises'. - Prefer
fetch(built-in since Node 18) over axios/node-fetch. - Use
structuredClone()for deep cloning. - Set
"type": "module"in package.json for ESM.
Monorepo
- Use Turborepo or Nx for monorepo orchestration.
- Share types via internal packages, not copy-paste.
- Use workspace protocols:
"@org/shared": "workspace:*".
TypeScript Patterns
Error Handling
- Use Result type pattern:
{ success: true; data: T } | { success: false; error: E }. - Use Zod
.safeParse()for validation -- returns typed result, never throws. - Create domain-specific error classes extending
Errorwith error codes. - Centralize error handling in middleware, not in each handler.
- Never catch errors silently. Log or rethrow with context.
Discriminated Unions
- Use discriminated unions for state machines and polymorphic data.
- Always include a
typeorkindliteral field as discriminant. - Use
switchwith exhaustive checking (neverin default) on unions. - Prefer unions over optional fields for mutually exclusive states.
Async Patterns
- Use
async/awaiteverywhere. Never use raw.then()chains. - Use
Promise.all()for independent concurrent operations. - Use
Promise.allSettled()when some failures are acceptable. - Implement cancellation with
AbortControllerfor long operations. - Wrap callbacks in Promises at the boundary, then use async/await.
Validation
- Validate at API boundaries with Zod, Valibot, or ArkType.
- Derive TypeScript types from schemas:
z.infer<typeof Schema>. - Never trust runtime data to match TypeScript types without validation.
- Use branded types for domain primitives:
UserId,Email,Slug.
Dependency Injection
- Use constructor injection for services and repositories.
- Accept interfaces, not concrete classes, in constructors.
- Use factory functions for creating configured instances.
- Avoid service locator pattern and global singletons.
Immutability
- Use
readonlyon interface properties by default. - Use
Readonly<T>,ReadonlyArray<T>for function parameters. - Use
Object.freeze()only for runtime safety in config objects. - Prefer spread/map/filter over mutating methods (push, splice).
Type Guards
- Use
isreturn type for custom type guards:(x: unknown): x is User. - Use
inoperator for discriminating object shapes. - Prefer
satisfiesoverasfor type validation without casting. - Use assertion functions (
asserts x is T) for preconditions.
TypeScript Security
Input Validation
- Validate ALL request data with Zod/Valibot at API boundary.
- Use
.strip()/.strict()to reject unexpected fields. - Validate URL params and query strings, not just request bodies.
- Never pass raw user input to
eval(),Function(), or template literals in SQL.
XSS Prevention
- Use framework auto-escaping (React JSX, Angular templates).
- Never use
dangerouslySetInnerHTMLwithout DOMPurify sanitization. - Sanitize user content before storing, not just before rendering.
- Set CSP headers:
default-src 'self'; script-src 'self'.
Authentication
- Use
httpOnly,secure,sameSite: 'strict'for auth cookies. - Implement CSRF protection for cookie-based auth (double-submit or token).
- Use short-lived JWTs (15min) with refresh token rotation.
- Validate JWT signature, expiration, issuer, and audience on every request.
SQL / NoSQL Injection
- Use parameterized queries with Drizzle/Prisma/TypeORM. Never concatenate.
- For raw queries, use tagged template literals:
sql\SELECT * FROM users WHERE id = ${id}``. - Validate and cast IDs to expected types (UUID, integer) before queries.
- Use ORM query builders for dynamic filters.
Dependency Security
- Run
npm auditin CI. Fail on high/critical vulnerabilities. - Use
npm audit signaturesto verify package provenance. - Pin exact versions with lockfile. Review lockfile changes in PRs.
- Avoid packages with postinstall scripts unless trusted.
Secrets
- Use
process.envwith Zod validation for env vars. - Never import
.envfiles in production -- use platform env injection. - Never log
req.headers.authorizationor session tokens. - Use
crypto.timingSafeEqual()for comparing secrets.
Server Hardening
- Set security headers: HSTS, X-Content-Type-Options, X-Frame-Options.
- Use
helmetmiddleware in Express, built-in security in Fastify. - Implement rate limiting on all endpoints (
express-rate-limit,@fastify/rate-limit). - Disable
X-Powered-Byheader. Do not expose server technology.
File Uploads
- Validate file type by magic bytes, not just extension or MIME type.
- Set maximum file size limits on the server.
- Store uploads outside the web root. Serve through a proxy with CDN.
- Generate random filenames. Never use user-provided filenames for storage.
TypeScript Testing
Framework
- Use Vitest for new projects (faster, native ESM, TypeScript-first).
- Use Jest only for existing projects already using it.
- Use Playwright for E2E browser testing.
- Use Supertest or built-in fetch for API integration tests.
File Naming
- Test files:
*.test.tsor*.spec.tscolocated with source. - Test utilities:
tests/helpers/ortests/utils/. - Fixtures:
tests/fixtures/with typed factory functions.
Structure
- Use
describefor grouping by function/class/feature. - Use
itwith behavior descriptions:it('returns 404 when user not found'). - Avoid deeply nested
describeblocks (max 2 levels). - Use
beforeEachfor setup, avoidbeforeAllfor mutable state.
Type-Safe Mocking
- Use
vi.fn()with type parameters:vi.fn<[string], Promise<User>>(). - Use
vi.mock()for module-level mocking. - Prefer dependency injection over module mocking for testability.
- Use
vi.spyOn()for partial mocks on existing objects.
React/Component Testing
- Use React Testing Library. Query by role, label, text -- not test IDs.
- Use
userEventoverfireEventfor realistic user interactions. - Test behavior and rendered output, not component internals.
- Use
renderHookfor testing custom hooks in isolation.
Assertions
- Use
expect().toBe()for primitives,expect().toEqual()for objects. - Use
expect().toMatchInlineSnapshot()for complex output verification. - Avoid
toBeTruthy/toBeFalsy-- use specific matchers. - Use
expect().rejects.toThrow()for async error testing.
Async Testing
- Always
awaitasync operations. Never usedonecallback. - Use
vi.useFakeTimers()for timer-dependent code. - Use
waitForfrom Testing Library for async DOM updates.
Performance
- Run tests in parallel (Vitest default). Isolate state to enable this.
- Use
vi.mock()for heavy dependencies (DB, network) in unit tests. - Keep unit test suite under 30 seconds.