Skip to main content

typescript-code-style

Use when writing TypeScript code in any project. Covers naming conventions, file organization, error handling, configuration patterns, TypeScript compiler settings, and general code approaches. Apply these conventions by default.

跳到安装

来源信息

仓库
subsquid/pipes-sdk
最近来源活动
2026年2月23日 18:34
检测到的 SKILL.md 语言
英语
星标
24
分支
3

安装方式

默认使用会先检查来源的 Prompt;你也可以切换为直接命令,或下载本地副本。

检查来源文件

决定是否安装前,请先阅读 SKILL.md,以及 SkillsMP 当前展示的配套文件。

正在显示 SKILL.md

SKILL.md
来源说明 · 只读预览
name
typescript-code-style
description
Use when writing TypeScript code in any project. Covers naming conventions, file organization, error handling, configuration patterns, TypeScript compiler settings, and general code approaches. Apply these conventions by default.
# TypeScript Code Style General conventions for all TypeScript projects. Apply these by default. ## TypeScript Configuration ### tsconfig.json ```json { "compilerOptions": { "target": "esnext", "module": "nodenext", "moduleResolution": "nodenext", "strict": true, "noImplicitReturns": true, "noImplicitOverride": true, "noFallthroughCasesInSwitch": true, "esModuleInterop": true, "skipLibCheck": true, "declaration": true, "sourceMap": true, "outDir": "dist", "stripInternal": true } } ``` Key points: - Always enable `strict: true` - Use `nodenext` module resolution for Node.js projects - Use `stripInternal: true` with `@internal` JSDoc to hide implementation details from declaration files ### Path Aliases ```json { "compilerOptions": { "paths": { "~/*": ["src/*"] } } } ``` Use `~/` prefix for internal imports. Never use relative paths that go up more than one level (`../../`). ## Naming Conventions | Element | Convention | Example | | -------------------- | ----------------- | ----------------------------- | | Files | kebab-case | `user-service.ts` | | Classes | PascalCase | `UserService` | | Interfaces/Types | PascalCase | `UserSession` | | Functions/Methods | camelCase | `findByEmail()` | | Constants | UPPER_SNAKE_CASE | `MAX_RETRY_COUNT` | | Private class fields | `#` prefix | `#connection` | | Enum values | PascalCase | `Status.Active` | | Boolean variables | is/has/can prefix | `isActive`, `hasPermission` | ### File Naming ``` user.service.ts # service user.controller.ts # controller user.repository.ts # repository user.entity.ts # database entity user.test.ts # test user.e2e.test.ts # e2e test create-user.request.ts # request DTO user.response.ts # response DTO index.ts # barrel exports ``` ## Exports ### Barrel Files Use `index.ts` for public API of a module: ```ts export * from './user.service' export * from './user.entity' export type { UserSession } from './user.types' ``` ### Export Patterns - Use `export type` for type-only exports - Mark internal implementation with `@internal` JSDoc - Prefer named exports over default exports ```ts /** @internal */ export function internalHelper() {} export type UserRole = 'admin' | 'user' | 'viewer' export class UserService {} ``` ## Error Handling ### Custom Error Classes Create specific error types with type guards: ```ts export class AppError extends Error { constructor( message: string, public readonly code: string, ) { super(message) this.name = 'AppError' } } export class ForkException extends Error { readonly name = 'ForkError' readonly previousBlocks: BlockCursor[] constructor(previousBlocks: BlockCursor[]) { super(`Fork detected at block ${previousBlocks[0]?.number}`) this.previousBlocks = previousBlocks } } export function isForkException(err: unknown): err is ForkException { return err instanceof ForkException || (err instanceof Error && err.name === 'ForkError') } ``` ### Error Handling Rules 1. **Use type guards** for error discrimination — check both `instanceof` and `name` for cross-boundary errors 2. **Serialize errors with cause chains** in logging using `pino.stdSerializers.errWithCause` 3. **Wrap unknown errors** with `ensureError()` before propagating: ```ts function ensureError(value: unknown): Error { if (value instanceof Error) return value return new Error(String(value)) } ``` 4. **Never silently swallow errors** — at minimum, log them 5. **Use specific error classes** — not generic `Error` for domain errors ## Configuration ### Environment Variables Use environment variables for all configuration. Validate at startup: ```ts const config = { port: Number(process.env.HTTP_PORT ?? 3000), logLevel: process.env.LOG_LEVEL ?? 'info', databaseUrl: requireEnv('DATABASE_URL'), } function requireEnv(name: string): string { const value = process.env[name] if (!value) throw new Error(`Missing required env var: ${name}`) return value } ``` ### Options Objects Use options objects for component configuration with sensible defaults: ```ts interface HttpClientOptions { baseUrl: string timeout?: number // default: 20_000 retryAttempts?: number // default: 3 retrySchedule?: number[] // default: [1000, 3000, 10_000] headers?: Record<string, string> } ``` ## Code Patterns ### Prefer `#private` Over `private` Use ES private fields for true encapsulation: ```ts class Connection { #client: Client #logger: Logger constructor(client: Client, logger: Logger) { this.#client = client this.#logger = logger } } ``` ### Numeric Literals Use underscores for readability: ```ts const MAX_SIZE = 10_485_760 // 10 MB const TIMEOUT = 5_000 // 5 seconds const IDLE_TIME = 300 // 300ms ``` ### Async Patterns - Always handle promises — use `@typescript-eslint/no-floating-promises` - Use `AsyncLocalStorage` for implicit context passing in pipelines: ```ts import { AsyncLocalStorage } from 'node:async_hooks' const asyncLocalStorage = new AsyncLocalStorage<RuntimeContext>() export function runWithContext<T>(ctx: RuntimeContext, fn: () => Promise<T>): Promise<T> { return asyncLocalStorage.run(ctx, fn) } export function useContext(): RuntimeContext { const ctx = asyncLocalStorage.getStore() if (!ctx) throw new Error('No runtime context') return ctx } ``` ### Composition Over Inheritance Use composable transformers/middleware instead of deep class hierarchies: ```ts // Good: composable pipeline const pipeline = source .pipe(decode()) .pipe(transform()) .pipe(filter()) // Bad: deep inheritance class SpecializedTransformer extends BaseTransformer extends AbstractTransformer {} ``` ### Builder Pattern for Complex Configuration ```ts const query = evmQueryBuilder() .addFields({ logs: { address: true, topics: true } }) .setRange({ from: 1_000_000 }) .build() ``` ### Factory Functions Over Constructors Prefer factory functions when the construction logic is complex or return types vary: ```ts // Good export function createLogger(options?: LoggerOptions): Logger { return pino({ ...defaults, ...options }) } // Instead of exposing constructor directly ``` ## Build Setup ### tsup (for Libraries) ```ts import { defineConfig } from 'tsup' export default defineConfig({ entry: ['src/index.ts'], outDir: 'dist', format: ['cjs', 'esm'], sourcemap: true, dts: true, clean: true, }) ``` ### Package.json Exports (Dual CJS/ESM) ```json { "exports": { ".": { "types": "./dist/index.d.ts", "import": "./dist/index.js", "require": "./dist/index.cjs" } } } ``` ## Things to Avoid 1. **No `namespace`** — use ES modules 2. **No `enum`** — use `as const` objects or union types: ```ts // Good const Status = { Active: 'active', Inactive: 'inactive' } as const type Status = (typeof Status)[keyof typeof Status] // Bad enum Status { Active = 'active', Inactive = 'inactive' } ``` 3. **No `any` in public APIs** — use `unknown` and narrow 4. **No `==`** — always `===` 5. **No default exports** — use named exports 6. **No relative imports beyond one level** — use path aliases (`~/`) 7. **No magic numbers** — extract to named constants 8. **No classes for simple data** — use plain objects and types
在 GitHub 查看