typescript-style

📁 hqarroum/writing-typescript 📅 7 days ago
2
总安装量
2
周安装量
#68063
全站排名
安装命令
npx skills add https://github.com/hqarroum/writing-typescript --skill typescript-style

Agent 安装分布

opencode 2
gemini-cli 2
antigravity 2
claude-code 2
github-copilot 2
codex 2

Skill 文档

TypeScript Code Guidelines

Naming Conventions

  • Files and directories: kebab-case.ts.
  • Classes, interfaces, types, enums: PascalCase.
  • Functions, variables, methods: camelCase.
  • Constants: UPPER_SNAKE_CASE.
  • Booleans: prefix with is, has.
  • Functions: start with, or consist of, a verb (get, resolve, create).
  • Use simple variable and function names, e.g. instead of const userInformation = getUserInformation(), prefer const user = getUser().
  • Use context-appropriate short names within narrow scopes — e.g., order inside processOrders() is clearer than currentOrderBeingProcessed.

Type Safety

  • No any. Use type narrowing, type guards, or generics instead.
  • When the type is genuinely unknown at compile time, use unknown and narrow via type guards.
  • Omit explicit return types when TypeScript can infer them unambiguously. Add them for public API boundaries.
  • Prefer satisfies over as, validates the type at the assignment site while preserving the narrower inferred type.
  • Use readonly for properties that are never reassigned after construction.
  • Use as const for literal objects and arrays that should not change.
  • Use import type when importing symbols used only as types.

Code Style

Syntax

  • Use 2 spaces for indentation.
  • Always use single quotes for strings.
  • Prefer template literals for multi-line strings and string interpolation.
  • No trailing commas in objects, arrays, or function parameters.
  • Prefer arrow functions for anonymous functions and callbacks.
  • Use const by default. Use let only when reassignment is required. NEVER use var.
  • Use === and !== for comparisons, never == or !=.
  • Use parentheses around return values (Opinionated):
// Wrong.
return x + y;

// Right.
return (x + y);
  • Place variable declarations at the top of their scope when possible. Separate declarations from the rest of the code with a blank line for readability.
const work = () => {
  const x = 1;
  const y = 2;

  return (x + y);
};

Imports

  • Always use ES modules in packages.
  • Group imports in the following order:
    1. Built-in modules (e.g. fs, path)
    2. External libraries (e.g. react, lodash)
    3. Internal modules (e.g. ./utils, ../components/Button)
    4. Type imports (e.g. import type { User } from './types')
    5. Multiple imports.

Multiple imports must be separated by a blank line from the other imports and have each imported symbol defined on its own line.

import { readFile } from 'node:fs';
import { useState } from 'react';
import { debounce } from 'lodash';
import type { Request, Response } from 'express';
import type { Client } from './sdk';

import {
  orderByDate,
  orderByPrice
} from './components/orders';
import {
  User,
  Product
} from './models';
  • Use engine-specific protocols for built-in module imports, for example:

Node

import { readFile } from 'node:fs';

Structure

  • Prefer async/await over .then() chains.
  • Use early returns to reduce nesting.
  • Keep functions small and focused on a single task.
  • Use short descriptive function and variable names that convey intent.
  • Use higher-order functions (map, filter, reduce) over imperative loops where readability is preserved.
  • One variable per declaration statement. Avoid comma-separated variable declarations.
  • Prefer for...of with Object.entries() when iterating objects.
  • Prefer simple, concise code.
  • No default exports, always use named exports for better clarity and maintainability.
  • Use discriminated unions over optional properties for more complex types.
  • Break statements across multiple lines when they exceed 80 characters. For example:
// Wrong.
this.emit('agent:status', { state: 'thinking', message: 'Processing your request…' });

// Right.
this.emit('agent:status', {
  state: 'thinking',
  message: 'Processing your request…'
});
  • When using declarative code, for example, using Zod or Drizzle schemas, write the declarative code in an expressive way by formatting it with each property or method on a new line. For example:
/**
 * Schema for validating user data.
 */
const UserSchema = z.object({
  
  /**
   * The unique identifier for the user.
   * @uuid
   */
  id: z
    .string()
    .uuid()
    .describe('The unique identifier for the user.'),

  /**
   * The name of the user.
   * @min 1 character
   * @max 100 characters
   */
  name: z
    .string()
    .min(1)
    .max(100)
    .describe('The name of the user.'),

  /**
   * The email of the user.
   */
  email: z
    .string()
    .email()
    .describe('The email of the user.')
});

Enums vs. Types

Prefer TypeScript type over enum for most cases. TypeScript enum compile to runtime objects and introduce non-standard syntax. Use union types for simple value sets and as const objects when you need both runtime values and type inference.

// Simple.
type Status = 'active' | 'inactive';

// Multi-line.
type UserRole = 'admin'
  | 'editor'
  | 'viewer'
  | 'guest';

Function Chaining

When chaining multiple function calls using a fluent interface, format the code with each call on a new line for better readability. For example:

const result = object
  .map(arg1, arg2)
  .reduce(arg3)
  .pipe(arg4);

Object Literals

When defining object literals, especially those with multiple properties, format the code with each property on a new line.

For example:

const obj = {
  prop1: value1,
  prop2: value2,
  prop3: value3
};

Curly Braces

Always use curly braces for blocks. This improves readability.

For example:

// Wrong.
if (condition) order();

// Wrong.

if (condition)
  order();

// Right.
if (condition) {
  order();
}

Organization

  • Types are always co-located with their implementation and define a single type, or multiple closely related types. Avoid “types-only” files that export unrelated types.
  • Organize code by feature or module under specific directories.
  • Avoid global type augmentation when possible.
  • Store code in a src/ directory, with subdirectories for features or modules. For monorepos, each package should have its own src/ directory, tsconfig.json, and package.json/bun.json.

Comments

  • Use JSDoc comments for functions, types, enums, classes, methods, and class properties. Class properties are always defined at the top of the class, before the constructor. Example:
/**
 * Calculates the area of a rectangle.
 * @param width - The width of the rectangle.
 * @param height - The height of the rectangle.
 * @returns The area of the rectangle.
 */
const calculateArea = (width: number, height: number) => {
  return (width * height);
};
/**
 * The `User` class represents a user in the system.
 */
export class User {

  /**
   * Creates a new user.
   * @param name - The name of the user.
   * @param email - The email of the user.
   * @constructor
   */
  constructor(public name: string, public email: string) {}
}
/**
 * Properties for a custom order.
 */
export type CustomOrderProps = {

  /**
   * The unique identifier for the order.
   */
  id: string;

  /**
   * The product being ordered.
   */
  product: string;

  /**
   * The quantity to be ordered.
   */
  quantity: number;
};

/**
 * The `CustomOrder` class represents a custom order in the system.
 */
export class CustomOrder {

  /**
   * Creates a new custom order.
   * @param props - The properties of the custom order.
   * @constructor
   */
  constructor(private props: CustomOrderProps) {}

  /**
   * Computes the total price of the order.
   * @returns The total price of the order.
   */
  getTotalPrice() {
    // Implementation goes here.
  }
}
  • Only use @type in JSDoc comments when the type or unit is non-obvious.
/**
 * The default timeout for API requests.
 * @type {number} in milliseconds
 */
const DEFAULT_TIMEOUT = 5_000;
  • Do not use decorative separators (e.g., -, // --------, // ========) in comments.

  • Use inline comments to explain what the code is doing and why. Do not use inline comments for declarations at the beginning of a function.

/**
 * Issues an API request to the given URL and returns the response data.
 * @param url - The URL to send the request to.
 * @returns The response data from the API.
 */
const request = async (url: URL) => {
  const response = await fetch(url);
    
  // Check for successful response.
  if (!response.ok) {
    throw new Error(`HTTP error! status: ${response.status}`);
  }

  // Validate response with Zod.
  const result = ResponseSchema.safeParse(
    await response.json()
  );
  if (!result.success) {
    throw new Error(`Invalid response format: ${result.error.message}`);
  }
  
  return (result.data);
};
  • Do not comment the top of files with a description of the file’s purpose. The file name and structure should be self-explanatory.
  • Do not use single-line block comments (/** ... */) for function or type declarations. Use multi-line JSDoc comments instead.

Globals

Use multi-line comments for global variables or constants. Globals should be defined at the top of the file, after imports and before any other code.

/**
 * The default timeout for API requests.
 * @type {number} in milliseconds
 */
const DEFAULT_TIMEOUT = 5_000;

Performance

  • Prefer Map and Set over plain objects for frequent lookups/membership checks.
  • Avoid unnecessary spread copies in hot paths — mutate behind a controlled interface.
  • Use Promise.all() for independent async operations; Promise.allSettled() when dealing with partial failure.
  • Use AbortController to control cancellation of long-running async operations when possible.

Anti-Patterns

  • No Object or Array constructors — use literals {} and [].
  • No eval() or Function constructor.
  • No with statements.
  • No deep nesting of callbacks or conditionals, concise, readable code is required.
  • Avoid non-null assertions (!) — handle null/undefined cases explicitly.
  • No over-annotation of inferred types when not necessary.
  • Avoid Barrel files as they break tree-shaking, slow down bundlers and IDE indexing, create circular dependency traps, and inflate bundle sizes.
  • No static-only classes.
  • No nested ternaries — max one level deep.
  • No magic numbers / strings — extract to named constants.
  • No prototype manipulation.
  • No unbounded or user-supplied regular expressions — avoid catastrophic backtracking (ReDoS). Prefer simple patterns, use non-backtracking quantifiers, or validate regex complexity before execution.

Logging

See the reference guide for details.

Architecture

See the reference guide for details.

Security

See the reference guide for details.

Testing

  • Use vitest for unit and integration testing.
  • Use supertest for HTTP endpoint testing when a headless browser is not needed.
  • Write tests per feature or component in a dedicated tests/ directory within each associated package. For monorepos, each package should have its own tests/ directory and test configuration.
  • Under the tests/ directory, organize tests by type (e.g., unit/, integration/, e2e/).

Environment

For every project using environment variables, always keep an env.ts file that defines and exports all expected environment variables with proper types. Use zod to validate these variables at runtime, and export a validated configuration object for use throughout the codebase.

/**
 * Environment variable schema definition.
 */
const envSchema = z.object({

  /**
   * The Supabase region where the function is executing.
   * Automatically set by Supabase runtime.
   * @optional Available only in deployed environments
   */
  SB_REGION: z
    .string()
    .optional(),

  /**
   * Unique identifier for the current function execution.
   * Automatically set by Supabase runtime.
   * @optional Available only in deployed environments
   */
  SB_EXECUTION_ID: z
    .string()
    .optional(),

  /**
   * The primary site URL (marketing/landing page).
   * Used for CORS configuration and email templates.
   * @example "https://example.com"
   */
  SITE_URL: z
    .url(),

  /**
   * The site domain (without protocol).
   * Used in email templates for links.
   * @example "example.com"
   */
  SITE_DOMAIN: z
    .string(),

  /**
   * The current environment (development, staging, production).
   * Used for conditional logic and logging.
   * @default "development"
   */
  ENVIRONMENT: z
    .enum(['development', 'staging', 'production'])
    .default('development')
});

/**
 * Parse and validate environment variables.
 *
 * This function is called immediately at module load time to ensure
 * all required environment variables are present and valid.
 *
 * @throws {z.ZodError} If validation fails, with detailed error messages
 * @returns Validated and typed environment variables
 */
const parseEnv = (): Env => {
  try {
    return envSchema.parse({
      SB_REGION: process.env.SB_REGION,
      SB_EXECUTION_ID: process.env.SB_EXECUTION_ID,
      SITE_URL: process.env.SITE_URL,
      SITE_DOMAIN: process.env.SITE_DOMAIN,
      ENVIRONMENT: process.env.ENVIRONMENT
    });
  } catch (error) {
    // Log the error with details for debugging.
    throw error;
  }
};

/**
 * Validated environment variables.
 *
 * Import this constant to access typed, validated environment variables
 * throughout your Edge Functions.
 */
export const env = parseEnv();

Tooling

Below is a table of tools that can be used to enforce good practices.

Code Quality & Type Safety

Tool Purpose
Zod Runtime validation and type inference for complex data structures.
Biome Code formatter and linter that supports TypeScript.
tsc TypeScript compiler for type checking and transpilation.
tsx TypeScript execution environment for running TS files directly.

Tests

Tool Purpose
vitest Fast and lightweight testing framework with built-in TypeScript support.
supertest HTTP assertions for testing API endpoints without a headless browser.

HTTP Stress Testing

Tool Purpose
wrk High-performance HTTP benchmarking tool for load testing APIs.
k6 Modern load testing tool with scripting in JavaScript/TypeScript.
autocannon Fast HTTP/1.1 benchmarking tool for stress testing APIs.

Web Page Testing

Tool Purpose
playwright End-to-end testing framework for web applications with TypeScript support.
puppeteer Headless Chrome Node API for web page testing and automation.

Database & ORMs

Need Use Why
ORM Drizzle SQL-like API, fully type-safe, zero overhead, supports Postgres/MySQL/SQLite
Query builder Kysely Type-safe raw SQL builder, no magic, great for complex queries
Migrations Drizzle Kit Auto-generates migrations from schema changes

Zod-specific Guidelines

  • Define schemas in a dedicated file adjacent to the code that consumes them.
  • Use z.infer<typeof Schema> to derive types from Zod schemas, the schema is the source of truth, not a hand-written interface.
  • Use .describe() to add descriptions to schema properties for better documentation and error messages.

Runtime

Use one of the following runtimes based on project needs:

Runtime Use Case Notes
Node.js General-purpose server-side applications, CLI tools, scripts. Most widely supported, vast ecosystem. Use by default unless mandated otherwise, or technically impossible.
Bun High-performance runtime with built-in bundler and transpiler. Excellent for performance-critical applications, but newer and less stable.
Deno Modern runtime with built-in TypeScript support, secure by default. Use for Serverless runtimes that only support Deno.

Package Managers

Package Manager Use Case
pnpm Default for Node.js projects, widely supported.
bun Use when the project is Bun-based.
npm Use when the project is Node.js-based and there’s a specific reason to choose npm.

Files

Add the following files to your project.

File Purpose Action
biome.json Biome configuration for formatting and linting. Create and configure according to project needs.
tsconfig.json TypeScript configuration for compiler options and project structure. Create and configure according to project needs.
.gitignore Specifies files and directories to ignore in version control. Create and update according to project needs.
nx.json or turbo.json Configuration for monorepo management with Nx or Turborepo. Create and configure according to project needs if using a monorepo.
.nvmrc Specifies the Node.js version for the project. Create and set to the desired Node.js version when the project is Node based.
.bun-version Specifies the Bun version for the project. Create and set to the desired Bun version when the project is Bun based.

CI/CD

Tool Purpose
GitHub Actions CI/CD pipelines for automated testing, linting, and deployment.
tsc --noEmit Type-check step — run in CI to catch type errors before merge.
biome check Lint and format check — run in CI to enforce code style.
vitest run Run tests — run in CI to validate correctness.

Every CI pipeline should include at minimum these ordered steps:

  1. Install dependencies (pnpm install --frozen-lockfile).
  2. Type-check (tsc --noEmit).
  3. Lint (biome check .).
  4. Test (vitest run).
  5. Build (tsc or bundler).

Reference Files

The references/config directory contains starter configurations:

  • biome.json — Biome linter configuration. The formatter is intentionally disabled — use Biome’s CLI (biome format) or editor integration separately so that formatting and linting remain independently configurable.
  • tsconfig.json — TypeScript compiler options with strict: true.