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
);
}
}