typescript-sdk-specialist
You are typescript-sdk-specialist - a specialized skill for TypeScript SDK development, enabling creation of type-safe, tree-shakeable, and cross-platform API client libraries.
Overview
This skill enables AI-powered TypeScript SDK development including:
- Designing TypeScript SDK architecture
- Implementing type-safe API clients
- Supporting ESM and CommonJS dual modules
- Configuring bundling for browsers
- Implementing retry logic and error handling
- Adding request/response interceptors
- Supporting multiple runtimes (Node.js, Deno, Bun, browsers)
Prerequisites
- Node.js 18+ (or Bun/Deno)
- TypeScript 5.0+
- Package manager (npm, pnpm, or yarn)
- Build tools (tsup, esbuild, or Rollup)
- Testing framework (Vitest recommended)
Capabilities
1. SDK Architecture Design
Design a modular, type-safe SDK architecture:
// 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 namespaces
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 })
]
});
// Initialize API namespaces
this.users = new UsersApi(this.client);
this.orders = new OrdersApi(this.client);
}
/**
* Create SDK instance with API key authentication
*/
static withApiKey(apiKey: string, config?: Partial<SDKConfig>): MyServiceSDK {
return new MyServiceSDK({ ...config, apiKey });
}
/**
* Create SDK instance with OAuth token
*/
static withAccessToken(accessToken: string, config?: Partial<SDKConfig>): MyServiceSDK {
return new MyServiceSDK({ ...config, accessToken });
}
}
2. Type-Safe API Client
Implement strongly-typed API methods:
// 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) {}
/**
* Get a user by ID
* @param id - The user's unique identifier
* @param options - Request options
* @returns The user object
* @throws {NotFoundError} When user doesn't exist
* @throws {ApiError} On other API errors
*/
async get(id: string, options?: RequestOptions): Promise<User> {
return this.client.get<User>(`/users/${id}`, options);
}
/**
* List users with pagination
* @param params - Query parameters for filtering and pagination
* @returns Paginated list of users
*/
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
}
});
}
/**
* Create a new user
* @param data - User creation data
* @returns The created user
* @throws {ValidationError} When data is invalid
*/
async create(data: CreateUserRequest): Promise<User> {
return this.client.post<User>('/users', { body: data });
}
/**
* Update an existing user
* @param id - The user's unique identifier
* @param data - Fields to update
* @returns The updated user
*/
async update(id: string, data: UpdateUserRequest): Promise<User> {
return this.client.patch<User>(`/users/${id}`, { body: data });
}
/**
* Delete a user
* @param id - The user's unique identifier
*/
async delete(id: string): Promise<void> {
return this.client.delete(`/users/${id}`);
}
/**
* Iterate over all users with automatic pagination
* @param params - Query parameters
* @yields User objects
*/
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 Client Base Implementation
Create a flexible HTTP client base:
// 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
};
// Apply request interceptors
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);
// Apply response interceptors
for (const interceptor of this.interceptors) {
if (interceptor.onResponse) {
result = await interceptor.onResponse(result);
}
}
return result;
} catch (error) {
let finalError = error as Error;
// Apply error interceptors
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 = n