TypeScriptSDK专家Skill typescript-sdk-specialist

TypeScript SDK 专家是一个专业技能,专注于使用TypeScript开发安全、高效、跨平台的API客户端库,支持ESM和CommonJS模块,适用于Node.js和浏览器环境。

前端开发 0 次安装 0 次浏览 更新于 2/26/2026

typescript-sdk-specialist

你是 typescript-sdk-specialist - 一个专门针对TypeScript SDK开发的专长,能够创建类型安全、可摇树优化和跨平台的API客户端库。

概览

这项技能支持AI驱动的TypeScript SDK开发,包括:

  • 设计TypeScript SDK架构
  • 实现类型安全的API客户端
  • 支持ESM和CommonJS双模块
  • 为浏览器配置打包
  • 实施重试逻辑和错误处理
  • 添加请求/响应拦截器
  • 支持多个运行时(Node.js、Deno、Bun、浏览器)

前提条件

  • Node.js 18+(或Bun/Deno)
  • TypeScript 5.0+
  • 包管理器(npm、pnpm或yarn)
  • 构建工具(tsup、esbuild或Rollup)
  • 测试框架(推荐Vitest)

能力

1. SDK架构设计

设计一个模块化、类型安全的SDK架构:

// src/client.ts
import { BaseClient, ClientConfig } from './base';
import { UsersApi } from './api/users';
import { OrdersApi } from './api/orders';
import { AuthInterceptor } from './interceptors/auth';
import { RetryInterceptor } from './interceptors/retry';

export interface SDKConfig extends ClientConfig {
  apiKey?: string;
  accessToken?: string;
  timeout?: number;
  retries?: number;
  baseUrl?: string;
}

export class MyServiceSDK {
  private readonly client: BaseClient;

  // API命名空间
  public readonly users: UsersApi;
  public readonly orders: OrdersApi;

  constructor(config: SDKConfig) {
    this.client = new BaseClient({
      baseUrl: config.baseUrl ?? 'https://api.myservice.com',
      timeout: config.timeout ?? 30000,
      interceptors: [
        new AuthInterceptor(config),
        new RetryInterceptor({ maxRetries: config.retries ?? 3 })
      ]
    });

    // 初始化API命名空间
    this.users = new UsersApi(this.client);
    this.orders = new OrdersApi(this.client);
  }

  /**
   * 使用API密钥创建SDK实例
   */
  static withApiKey(apiKey: string, config?: Partial<SDKConfig>): MyServiceSDK {
    return new MyServiceSDK({ ...config, apiKey });
  }

  /**
   * 使用OAuth令牌创建SDK实例
   */
  static withAccessToken(accessToken: string, config?: Partial<SDKConfig>): MyServiceSDK {
    return new MyServiceSDK({ ...config, accessToken });
  }
}

2. 类型安全的API客户端

实现强类型API方法:

// src/api/users.ts
import { BaseClient, RequestOptions } from '../base';
import {
  User,
  CreateUserRequest,
  UpdateUserRequest,
  ListUsersParams,
  PaginatedResponse
} from '../models';

export class UsersApi {
  constructor(private readonly client: BaseClient) {}

  /**
   * 通过ID获取用户
   * @param id - 用户的唯一标识符
   * @param options - 请求选项
   * @returns 用户对象
   * @throws {NotFoundError} 当用户不存在时
   * @throws {ApiError} 在其他API错误时
   */
  async get(id: string, options?: RequestOptions): Promise<User> {
    return this.client.get<User>(`/users/${id}`, options);
  }

  /**
   * 带分页列出用户
   * @param params - 用于过滤和分页的查询参数
   * @returns 分页的用户列表
   */
  async list(params?: ListUsersParams): Promise<PaginatedResponse<User>> {
    return this.client.get<PaginatedResponse<User>>('/users', {
      params: {
        page: params?.page ?? 1,
        limit: params?.limit ?? 20,
        sort: params?.sort,
        filter: params?.filter
      }
    });
  }

  /**
   * 创建新用户
   * @param data - 用户创建数据
   * @returns 创建的用户
   * @throws {ValidationError} 当数据无效时
   */
  async create(data: CreateUserRequest): Promise<User> {
    return this.client.post<User>('/users', { body: data });
  }

  /**
   * 更新现有用户
   * @param id - 用户的唯一标识符
   * @param data - 要更新的字段
   * @returns 更新后的用户
   */
  async update(id: string, data: UpdateUserRequest): Promise<User> {
    return this.client.patch<User>(`/users/${id}`, { body: data });
  }

  /**
   * 删除用户
   * @param id - 用户的唯一标识符
   */
  async delete(id: string): Promise<void> {
    return this.client.delete(`/users/${id}`);
  }

  /**
   * 自动分页遍历所有用户
   * @param params - 查询参数
   * @yields 用户对象
   */
  async *listAll(params?: Omit<ListUsersParams, 'page'>): AsyncGenerator<User> {
    let page = 1;
    let hasMore = true;

    while (hasMore) {
      const response = await this.list({ ...params, page });

      for (const user of response.data) {
        yield user;
      }

      hasMore = response.hasMore;
      page++;
    }
  }
}

3. HTTP客户端基础实现

创建一个灵活的HTTP客户端基础:

// src/base/client.ts
import { ApiError, NetworkError, TimeoutError } from '../errors';

export interface RequestOptions {
  params?: Record<string, string | number | boolean | undefined>;
  headers?: Record<string, string>;
  signal?: AbortSignal;
  timeout?: number;
}

export interface RequestInterceptor {
  onRequest?(config: RequestConfig): RequestConfig | Promise<RequestConfig>;
  onResponse?<T>(response: T): T | Promise<T>;
  onError?(error: Error): Error | Promise<Error>;
}

export class BaseClient {
  private baseUrl: string;
  private defaultTimeout: number;
  private interceptors: RequestInterceptor[];

  constructor(config: ClientConfig) {
    this.baseUrl = config.baseUrl;
    this.defaultTimeout = config.timeout ?? 30000;
    this.interceptors = config.interceptors ?? [];
  }

  async get<T>(path: string, options?: RequestOptions): Promise<T> {
    return this.request<T>('GET', path, options);
  }

  async post<T>(path: string, options?: RequestOptions & { body?: unknown }): Promise<T> {
    return this.request<T>('POST', path, options);
  }

  async put<T>(path: string, options?: RequestOptions & { body?: unknown }): Promise<T> {
    return this.request<T>('PUT', path, options);
  }

  async patch<T>(path: string, options?: RequestOptions & { body?: unknown }): Promise<T> {
    return this.request<T>('PATCH', path, options);
  }

  async delete<T = void>(path: string, options?: RequestOptions): Promise<T> {
    return this.request<T>('DELETE', path, options);
  }

  private async request<T>(
    method: string,
    path: string,
    options?: RequestOptions & { body?: unknown }
  ): Promise<T> {
    let config: RequestConfig = {
      method,
      url: `${this.baseUrl}${path}`,
      headers: {
        'Content-Type': 'application/json',
        'Accept': 'application/json',
        ...options?.headers
      },
      params: options?.params,
      body: options?.body,
      timeout: options?.timeout ?? this.defaultTimeout,
      signal: options?.signal
    };

    // 应用请求拦截器
    for (const interceptor of this.interceptors) {
      if (interceptor.onRequest) {
        config = await interceptor.onRequest(config);
      }
    }

    try {
      const response = await this.fetch(config);

      let result = await this.parseResponse<T>(response);

      // 应用响应拦截器
      for (const interceptor of this.interceptors) {
        if (interceptor.onResponse) {
          result = await interceptor.onResponse(result);
        }
      }

      return result;
    } catch (error) {
      let finalError = error as Error;

      // 应用错误拦截器
      for (const interceptor of this.interceptors) {
        if (interceptor.onError) {
          finalError = await interceptor.onError(finalError);
        }
      }

      throw finalError;
    }
  }

  private async fetch(config: RequestConfig): Promise<Response> {
    const url = new URL(config.url);

    if (config.params) {
      for (const [key, value] of Object.entries(config.params)) {
        if (value !== undefined) {
          url.searchParams.set(key, String(value));
        }
      }
    }

    const controller = new AbortController();
    const timeoutId = setTimeout(() => controller.abort(), config.timeout);

    try {
      const response = await fetch(url.toString(), {
        method: config.method,
        headers: config.headers,
        body: config.body ? JSON.stringify(config.body) : undefined,
        signal: config.signal ?? controller.signal
      });

      clearTimeout(timeoutId);

      if (!response.ok) {
        throw await this.createApiError(response);
      }

      return response;
    } catch (error) {
      clearTimeout(timeoutId);

      if (error instanceof DOMException && error.name === 'AbortError') {
        throw new TimeoutError(`请求超时 ${config.timeout}ms`);
      }

      if (error instanceof ApiError) {
        throw error;
      }

      throw new NetworkError('网络请求失败', { cause: error });
    }
  }

  private async parseResponse<T>(response: Response): Promise<T> {
    const contentType = response.headers.get('content-type');

    if (contentType?.includes('application/json')) {
      return response.json() as Promise<T>;
    }

    if (response.status === 204) {
      return undefined as T;
    }

    return response.text() as unknown as T;
  }

  private async createApiError(response: Response): Promise<ApiError> {
    let body: unknown;

    try {
      body = await response.json();
    } catch {
      body = await response.text();
    }

    return new ApiError(
      (body as any)?.message ?? `HTTP ${response.status}`,
      response.status,
      (body as any)?.code,
      body
    );
  }
}

4. 双ESM/