--- name: typescript-rules description: "TypeScript/JavaScript coding rules: style, patterns, security, testing. Triggers: .ts, .tsx, .js, .jsx, package.json, tsconfig.json, React, Next.js, Vue, Vite, Vitest, Jest, ESLint." effort: medium user-invocable: false allowed-tools: Read --- # 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: true` in tsconfig.json. - Never use `any` -- use `unknown` + type guards instead. - Prefer `interface` over `type` for object shapes (extendable). - Use `as const` for 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 `I` only if project convention requires it. ## Functions - Prefer arrow functions for callbacks and inline. - Use `function` declarations 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 `type` imports: `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 `readonly` for arrays and objects that should not be mutated. - Use `satisfies` operator to validate types without widening. - Prefer `unknown` over `any` at API boundaries. - Use template literal types for string patterns. ## Avoid - `enum` -- use `as const` objects or union types. - `namespace` -- use ES modules. - `private` keyword -- use `#` private fields. - Non-null assertion `!` -- use proper type narrowing. - `as` type casting -- use type guards and narrowing. ## Configuration - Enable `noUncheckedIndexedAccess` for safer array/object access. - Enable `exactOptionalPropertyTypes` to distinguish `undefined` from missing. - Use `moduleResolution: "bundler"` for modern projects. - Set `isolatedModules: true` for 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 `useCallback` and `useMemo` only 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.tsx` and `error.tsx` for streaming and error boundaries. - Fetch data in Server Components, not in useEffect on client. - Use `revalidatePath` / `revalidateTag` for 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 `Error` with 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 `type` or `kind` literal field as discriminant. - Use `switch` with exhaustive checking (`never` in default) on unions. - Prefer unions over optional fields for mutually exclusive states. ## Async Patterns - Use `async/await` everywhere. Never use raw `.then()` chains. - Use `Promise.all()` for independent concurrent operations. - Use `Promise.allSettled()` when some failures are acceptable. - Implement cancellation with `AbortController` for 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`. - 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 `readonly` on interface properties by default. - Use `Readonly`, `ReadonlyArray` 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 `is` return type for custom type guards: `(x: unknown): x is User`. - Use `in` operator for discriminating object shapes. - Prefer `satisfies` over `as` for 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 `dangerouslySetInnerHTML` without 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 audit` in CI. Fail on high/critical vulnerabilities. - Use `npm audit signatures` to verify package provenance. - Pin exact versions with lockfile. Review lockfile changes in PRs. - Avoid packages with postinstall scripts unless trusted. ## Secrets - Use `process.env` with Zod validation for env vars. - Never import `.env` files in production -- use platform env injection. - Never log `req.headers.authorization` or session tokens. - Use `crypto.timingSafeEqual()` for comparing secrets. ## Server Hardening - Set security headers: HSTS, X-Content-Type-Options, X-Frame-Options. - Use `helmet` middleware in Express, built-in security in Fastify. - Implement rate limiting on all endpoints (`express-rate-limit`, `@fastify/rate-limit`). - Disable `X-Powered-By` header. 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.ts` or `*.spec.ts` colocated with source. - Test utilities: `tests/helpers/` or `tests/utils/`. - Fixtures: `tests/fixtures/` with typed factory functions. ## Structure - Use `describe` for grouping by function/class/feature. - Use `it` with behavior descriptions: `it('returns 404 when user not found')`. - Avoid deeply nested `describe` blocks (max 2 levels). - Use `beforeEach` for setup, avoid `beforeAll` for mutable state. ## Type-Safe Mocking - Use `vi.fn()` with type parameters: `vi.fn<[string], Promise>()`. - 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 `userEvent` over `fireEvent` for realistic user interactions. - Test behavior and rendered output, not component internals. - Use `renderHook` for 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 `await` async operations. Never use `done` callback. - Use `vi.useFakeTimers()` for timer-dependent code. - Use `waitFor` from 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.