typescript-style
npx skills add https://github.com/hqarroum/writing-typescript --skill typescript-style
Agent 安装分布
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(), preferconst 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
unknownand narrow via type guards. - Omit explicit return types when TypeScript can infer them unambiguously. Add them for public API boundaries.
- Prefer
satisfiesoveras, validates the type at the assignment site while preserving the narrower inferred type. - Use
readonlyfor properties that are never reassigned after construction. - Use
as constfor literal objects and arrays that should not change. - Use
import typewhen 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
constby default. Useletonly when reassignment is required. NEVER usevar. - 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:
- Built-in modules (e.g.
fs,path) - External libraries (e.g.
react,lodash) - Internal modules (e.g.
./utils,../components/Button) - Type imports (e.g.
import type { User } from './types') - Multiple imports.
- Built-in modules (e.g.
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/awaitover.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...ofwithObject.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
ZodorDrizzleschemas, 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 ownsrc/directory,tsconfig.json, andpackage.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
@typein 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
MapandSetover 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
AbortControllerto control cancellation of long-running async operations when possible.
Anti-Patterns
- No
ObjectorArrayconstructors â use literals{}and[]. - No
eval()orFunctionconstructor. - No
withstatements. - 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
vitestfor 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 owntests/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:
- Install dependencies (
pnpm install --frozen-lockfile). - Type-check (
tsc --noEmit). - Lint (
biome check .). - Test (
vitest run). - Build (
tscor 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 withstrict: true.