---
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<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 `readonly` on 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 `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<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 `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.
