关于 typescript-project
该技能以严格类型、ESM、分层代码、验证和测试为核心,指导 TypeScript 项目的设计,适合新建或维护应用的开发者,并明确倡导避免兼容层、检查实际行为。
主要功能
- 01使用 Zod 严格类型与验证
- 02采用 Node 22+ 或 Bun 的 ESM
- 03划分 lib、services 和 adapters
- 04提供 Vitest 或 Bun test 指引
- 05不保留向后兼容层
- 06提供 LLM 的 LiteLLM 代理指引
适用场景
- 01搭建 TypeScript 项目结构
- 02分离业务逻辑与外部集成
- 03验证输入和错误行为
- 04选择服务代码测试方式
安装
复制下面的命令,在终端中运行。
$
npx skills add https:/ / github.com/ majiayu000/ spellbook --skill typescript-project安装后在 Claude Code 中输入/typescript-project确认已加载。
包含的文件
除 SKILL.md 外,安装时一起下载的参考资料和脚本。
reference/architecture.mdpatterns.mdtech-stack.mdtemplates/project/biome.jsonpackage.jsontsconfig.jsontemplates/project/src/index.tstemplates/project/src/adapters/in-memory.repository.tsllm.adapter.tstemplates/project/src/lib/config.tserrors.tsindex.tslogger.tstypes.tstemplates/project/src/services/example.service.tstemplates/project/tests/example.service.test.ts详细介绍
TypeScript 项目架构
核心原则
- 类型安全优先 — 严格模式,不使用
any,使用 Zod 进行运行时验证 - 原生 ESM — 默认使用 ES Modules,Node 22+ / Bun
- 分层架构 — 分离 lib/services/adapters
- 200 行限制 — 单个文件不得超过 200 行(参见 elegant-architecture skill)
- 测试现实情况 — 使用 Vitest/Bun test,尽量少用模拟
- 不考虑向后兼容 — 删除而不是弃用。直接修改,不使用垫片
- 使用 LiteLLM 调用 LLM API — 除非必须使用特定 SDK,否则所有 LLM 集成都使用 LiteLLM 代理
注意事项
- Bun、Zod、目录布局和 LiteLLM 的选择属于启用时的观察结果,并不能证明任务成功。检查有效输入、无效输入、状态变化和实际错误约定。
- 模板通过服务测试后仍可能无法通过
typecheck;在 2026 年 10 月的案例中,维护中的模板的noUnusedLocals捕获了一个未使用的配置辅助函数。两者都要运行。 - 声明依赖为
latest并不能锁定已复现的运行。保留生成的锁文件及其证据,并记录实际安装的版本。不要声称其他运行时已通过测试。 - 配置的代理 URL 并不代表模型已实际返回结果。如果没有服务提供商,请验证独立的应用行为,并明确说明 LLM 检查仍待完成。
不考虑向后兼容
删除未使用的代码。直接修改。不添加兼容层。
原因
- 死代码会成为技术债务
- 兼容垫片会增加复杂性
- 旧模式会通过复制粘贴不断传播
- “临时”变通方案会变成永久方案
应避免的反模式
// ❌ BAD: Renaming but keeping old export
export { newName };
export { newName as oldName }; // "for backwards compatibility"
// ❌ BAD: Unused parameter with underscore
function process(_legacyParam: string, data: Data) { ... }
// ❌ BAD: Deprecated comments instead of deletion
/** @deprecated Use newMethod instead */
export function oldMethod() { ... }
// ❌ BAD: Re-exporting removed functionality
export { removed } from './legacy'; // Keep for existing consumers
// ❌ BAD: Feature flags for old behavior
if (config.useLegacyMode) { ... }
正确做法
// ✅ GOOD: Just delete and update all usages
// Old: export { fetchData as getData }
// New: export { fetchData }
// Then: Find & replace all getData → fetchData
// ✅ GOOD: Remove unused parameters entirely
function process(data: Data) { ... }
// ✅ GOOD: Delete deprecated code, update callers
// Don't mark as deprecated, just remove it
// ✅ GOOD: Breaking changes are fine in active development
// Semantic versioning handles this for libraries
修改接口时
// ❌ BAD: Adding optional fields "for compatibility"
interface User {
id: string;
name: string;
firstName?: string; // New field, name kept for compatibility
lastName?: string;
}
// ✅ GOOD: Clean break, update all usages
interface User {
id: string;
firstName: string;
lastName: string;
}
// Then update ALL code that uses User.name
迁移策略
- 查找所有用法 —
grep -r "oldName" src/ - 一次性全部更新 — 使用单个提交,不设过渡期
- 删除旧代码 — 不添加弃用警告,直接移除
- 运行测试 — 确保没有问题
使用 LiteLLM 调用 LLM API
所有 LLM 集成都使用 LiteLLM 代理。不要直接调用服务提供商的 API。
为什么选择 LiteLLM
- 统一接口 — 一个 API 支持 100 多家 LLM 服务提供商(OpenAI、Anthropic、Azure、Bedrock 等)
- 与服务提供商无关 — 无需修改代码即可切换模型
- 成本跟踪 — 内置用量和成本监控
- 负载均衡 — 在不同服务提供商之间自动故障转移
- 速率限制 — 防止配额耗尽
设置
# Run LiteLLM proxy (Docker)
docker run -p 4000:4000 ghcr.io/berriai/litellm:main-stable
# Or install locally
pip install litellm[proxy]
litellm --model gpt-4o
TypeScript 用法
// adapters/llm.adapter.ts
import { OpenAI } from 'openai';
// Connect to LiteLLM proxy using OpenAI SDK
const llm = new OpenAI({
baseURL: process.env.LITELLM_URL || 'http://localhost:4000',
apiKey: process.env.LITELLM_API_KEY || 'sk-1234', // Proxy API key
});
export async function complete(prompt: string, model = 'gpt-4o'): Promise<string> {
const response = await llm.chat.completions.create({
model, // Can be any model: gpt-4o, claude-3-opus, gemini-pro, etc.
messages: [{ role: 'user', content: prompt }],
});
return response.choices[0]?.message?.content ?? '';
}
不应使用 LiteLLM 的情况
- 使用服务提供商特有功能进行流式传输(例如,Anthropic 的工具调用流式传输)
- OpenAI 格式不支持的服务提供商专属 API(带元数据的嵌入等)
- 出于合规或安全原因必须使用直接 SDK
反模式
// ❌ BAD: Direct provider SDKs everywhere
import Anthropic from '@anthropic-ai/sdk';
import OpenAI from 'openai';
import { GoogleGenerativeAI } from '@google/generative-ai';
// ❌ BAD: Provider-specific code scattered across codebase
if (provider === 'anthropic') { ... }
else if (provider === 'openai') { ... }
// ✅ GOOD: Single LiteLLM adapter, switch models via config
const response = await llm.chat.completions.create({
model: config.llmModel, // "gpt-4o" or "claude-3-opus" or "gemini-pro"
messages,
});
快速开始
1. 初始化项目
# Using Bun (recommended)
bun init
bun add zod
bun add -d typescript @types/bun @biomejs/biome
# Using Node.js
npm init -y
npm i zod
npm i -D typescript @types/node tsx @biomejs/biome
2. 应用技术栈
| 层 | 建议 |
|---|---|
| 运行时 | Bun / Node 22+ |
| 语言 | TypeScript(最新版) |
| 验证 | Zod(最新版) |
| 测试 | Bun test / Vitest |
| 构建 | bun build / tsup |
| 代码检查 | Biome(最新版) |
版本策略
始终使用最新版。模板中不要锁定版本。
{
"dependencies": {
"zod": "latest"
},
"devDependencies": {
"@biomejs/biome": "latest",
"typescript": "latest"
}
}
bun add/npm i会自动获取最新版- 使用
bun update --latest升级所有依赖 - 锁文件(
bun.lockb、package-lock.json)可确保构建可复现 - 通过阅读变更日志来处理重大变更,不要通过避免更新来规避
3. 使用标准结构
project/
├── src/
│ ├── index.ts # Entry point
│ ├── lib/ # Core utilities
│ │ ├── config.ts # Configuration management
│ │ ├── errors.ts # Custom error classes
│ │ ├── logger.ts # Logging infrastructure
│ │ └── types.ts # Shared type definitions
│ ├── services/ # Business logic
│ │ └── *.service.ts
│ └── adapters/ # External integrations
│ └── *.adapter.ts
├── tests/ # Test files
│ └── *.test.ts
├── tsconfig.json
├── package.json
└── biome.json # or eslint.config.js
架构分层
lib/ — 核心基础设施
在整个应用中使用的基础代码:
// lib/types.ts — Shared type definitions
export interface Result<T, E = Error> {
ok: boolean;
data?: T;
error?: E;
}
// lib/errors.ts — Custom errors
export class AppError extends Error {
constructor(
message: string,
public code: string,
public statusCode: number = 500
) {
super(message);
this.name = 'AppError';
}
}
// lib/config.ts — Configuration
export const config = {
env: process.env.NODE_ENV || 'development',
port: Number(process.env.PORT) || 3000,
db: {
url: process.env.DATABASE_URL!,
},
} as const;
// lib/logger.ts — Logging (see structured-logging-lite skill)
services/ — 业务逻辑
注入依赖的纯业务逻辑:
// services/user.service.ts
export class UserService {
constructor(private readonly userRepo: UserRepository) {}
async create(input: CreateUserInput): Promise<User> {
const existing = await this.userRepo.findByEmail(input.email);
if (existing) throw new AppError('Email exists', 'USER_EXISTS', 409);
return this.userRepo.save(User.create(input));
}
}
adapters/ — 外部集成
与外部系统(数据库、API、文件系统)交互:
// adapters/postgres.adapter.ts
export class PostgresUserRepository implements UserRepository {
constructor(private readonly db: Database) {}
async findByEmail(email: string): Promise<User | null> {
const row = await this.db.query('SELECT * FROM users WHERE email = $1', [email]);
return row ? User.fromRow(row) : null;
}
}
配置文件
tsconfig.json(2025)
{
"compilerOptions": {
"target": "ES2022",
"module": "NodeNext",
"moduleResolution": "NodeNext",
"lib": ["ES2022"],
"outDir": "dist",
"rootDir": "src",
"strict": true,
"noUncheckedIndexedAccess": true,
"noUnusedLocals": true,
"noUnusedParameters": true,
"esModuleInterop": true,
"skipLibCheck": true,
"declaration": true,
"sourceMap": true
},
"include": ["src/**/*"],
"exclude": ["node_modules", "dist"]
}
package.json
{
"name": "my-project",
"version": "1.0.0",
"type": "module",
"main": "dist/index.js",
"scripts": {
"dev": "bun run --watch src/index.ts",
"build": "bun build src/index.ts --outdir dist --target bun",
"start": "bun dist/index.js",
"test": "bun test",
"typecheck": "tsc --noEmit"
}
}
使用 Zod 进行验证
import { z } from 'zod';
// Define schemas
export const CreateUserSchema = z.object({
email: z.string().email(),
name: z.string().min(2).max(100),
age: z.number().int().positive().optional(),
});
// Infer types from schemas
export type CreateUserInput = z.infer<typeof CreateUserSchema>;
// Validate at boundaries
export function validateInput<T>(schema: z.ZodType<T>, data: unknown): T {
return schema.parse(data);
}
错误处理模式
// lib/errors.ts
export class AppError extends Error {
constructor(
message: string,
public readonly code: string,
public readonly statusCode: number = 500,
public readonly context?: Record<string, unknown>
) {
super(message);
this.name = 'AppError';
Error.captureStackTrace(this, this.constructor);
}
static notFound(resource: string, id: string) {
return new AppError(`${resource} not found: ${id}`, 'NOT_FOUND', 404);
}
static validation(message: string, context?: Record<string, unknown>) {
return new AppError(message, 'VALIDATION_ERROR', 400, context);
}
}
// Usage
throw AppError.notFound('User', userId);
测试策略
// tests/user.service.test.ts
import { describe, it, expect, beforeEach } from 'bun:test';
import { UserService } from '../src/services/user.service';
import { InMemoryUserRepository } from './helpers/in-memory-repo';
describe('UserService', () => {
let service: UserService;
let repo: InMemoryUserRepository;
beforeEach(() => {
repo = new InMemoryUserRepository();
service = new UserService(repo);
});
it('creates user with valid input', async () => {
const user = await service.create({
email: '[email protected]',
name: 'Test User',
});
expect(user.email).toBe('[email protected]');
expect(await repo.findByEmail('[email protected]')).toEqual(user);
});
it('rejects duplicate email', async () => {
await service.create({ email: '[email protected]', name: 'User 1' });
expect(
service.create({ email: '[email protected]', name: 'User 2' })
).rejects.toThrow('Email exists');
});
});
检查清单
## Project Setup
- [ ] TypeScript strict mode enabled
- [ ] ESM modules configured
- [ ] Biome/ESLint configured
- [ ] Testing framework ready
## Architecture
- [ ] lib/ for core utilities
- [ ] services/ for business logic
- [ ] adapters/ for external integrations
- [ ] Clear module boundaries
## Quality
- [ ] Zod schemas for validation
- [ ] Custom error classes
- [ ] Structured logging
- [ ] Tests for critical paths
## Build
- [ ] Build script configured
- [ ] Type checking in CI
- [ ] Tests in CI
See Also
- reference/architecture.md — Detailed architecture patterns
- reference/tech-stack.md — Tech stack comparison
- reference/patterns.md — Design patterns
- elegant-architecture skill — 200-line file limit
- structured-logging-lite skill — Logging setup
评论
支持 Markdown