mcpField
T
typescript-project

typescript-project

github.com/majiayu000开发28885 次安装今天更新
代码仓库

关于 typescript-project

该技能以严格类型、ESM、分层代码、验证和测试为核心,指导 TypeScript 项目的设计,适合新建或维护应用的开发者,并明确倡导避免兼容层、检查实际行为。

主要功能

  1. 01使用 Zod 严格类型与验证
  2. 02采用 Node 22+ 或 Bun 的 ESM
  3. 03划分 lib、services 和 adapters
  4. 04提供 Vitest 或 Bun test 指引
  5. 05不保留向后兼容层
  6. 06提供 LLM 的 LiteLLM 代理指引

适用场景

  1. 01搭建 TypeScript 项目结构
  2. 02分离业务逻辑与外部集成
  3. 03验证输入和错误行为
  4. 04选择服务代码测试方式

安装

复制下面的命令,在终端中运行。

$npx skills add https://github.com/majiayu000/spellbook --skill typescript-project

安装后在 Claude Code 中输入/typescript-project确认已加载。

包含的文件

除 SKILL.md 外,安装时一起下载的参考资料和脚本。

reference/
architecture.md
patterns.md
tech-stack.md
templates/project/
biome.json
package.json
tsconfig.json
templates/project/src/
index.ts
templates/project/src/adapters/
in-memory.repository.ts
llm.adapter.ts
templates/project/src/lib/
config.ts
errors.ts
index.ts
logger.ts
types.ts
templates/project/src/services/
example.service.ts
templates/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
迁移策略
  1. 查找所有用法 — grep -r "oldName" src/
  2. 一次性全部更新 — 使用单个提交,不设过渡期
  3. 删除旧代码 — 不添加弃用警告,直接移除
  4. 运行测试 — 确保没有问题

使用 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

评论

支持 Markdown