‹ 首页

designing-sdks

@ancoleman · 收录于 1 周前

Design production-ready SDKs with retry logic, error handling, pagination, and multi-language support. Use when building client libraries for APIs or creating developer-facing SDK interfaces.

适合你,如果你需要为 API 构建多语言客户端库

/ 下载安装
designing-sdks.skill双击,或拖进 Claude 桌面版 / Cowork,即完成安装↓ .skill↓ .zip
用别的 agent?下载 .zip 解压,把文件夹放进它的技能目录
Claude Code~/.claude/skills/(项目级 .claude/skills/)
Codex CLI~/.codex/skills/
Cursor自动读取上面两处目录
其他工具见其文档的「skills」目录;两个下载是同一份文件,只是名字不同
/ 通过 npx 安装 校验哈希
npx oh-my-skill add ancoleman/ai-design-components/designing-sdks
/ 通过 bash 安装
curl -fsSL https://oh-my-skill.com/install.sh | bash -s -- ancoleman/ai-design-components/designing-sdks
/ 已经装过?验证本机副本,不用重装
npx oh-my-skill verify ancoleman/ai-design-components/designing-sdks
安装目标可用 --agent / --scope 或 --to 明确指定;省略时只会在唯一已存在的 agent 目录上自动选择,零命中或多命中会停止并提示。content_hash 缺失或不一致均拒装。
384GitHub stars
~2.5K最小装载
~19.3K含声明引用
~20.4K文本包总量
镜像托管

怎么用

技能原文 SKILL.md作者撰写 · MIT · 76551b7

SDK Design

Design client libraries (SDKs) with excellent developer experience through intuitive APIs, robust error handling, automatic retries, and consistent patterns across programming languages.

When to Use This Skill

Use when building a client library for a REST API, creating internal service SDKs, implementing retry logic with exponential backoff, handling authentication patterns, creating typed error hierarchies, implementing pagination with async iterators, or designing streaming APIs for real-time data.

Core Architecture Patterns
Client → Resources → Methods

Organize SDK code hierarchically:

Client (config: API key, base URL, retries, timeout)
├─ Resources (users, payments, posts)
│   ├─ create(), retrieve(), update(), delete()
│   └─ list() (with pagination)
└─ Top-Level Methods (convenience)

Resource-Based (Stripe style):

const client = new APIClient({ apiKey: 'sk_test_...' })
const user = await client.users.create({ email: 'user@example.com' })

Use for APIs <100 methods. Prioritizes developer experience.

Command-Based (AWS SDK v3):

import { S3Client, PutObjectCommand } from '@aws-sdk/client-s3'
await client.send(new PutObjectCommand({ Bucket: '...' }))

Use for APIs >100 methods. Prioritizes bundle size and tree-shaking.

For detailed architectural guidance, see references/architecture-patterns.md.

Language-Specific Patterns
TypeScript: Async-Only
const user = await client.users.create({ email: 'user@example.com' })

All methods return Promises. Avoid callbacks.

Python: Dual Sync/Async
# Sync
client = APIClient(api_key='sk_test_...')
user = client.users.create(email='user@example.com')

# Async
async_client = AsyncAPIClient(api_key='sk_test_...')
user = await async_client.users.create(email='user@example.com')

Provide both clients. Users choose based on architecture.

Go: Sync with Context
client := apiclient.New("api_key")
user, err := client.Users().Create(ctx, req)

Use context.Context for timeout and cancellation.

Authentication
API Key (Most Common)
const client = new APIClient({ apiKey: process.env.API_KEY })

Store keys in environment variables, never hardcode.

OAuth Token Refresh
const client = new APIClient({
  clientId: 'id',
  clientSecret: 'secret',
  refreshToken: 'token',
  onTokenRefresh: (newToken) => saveToken(newToken)
})

SDK automatically refreshes tokens before expiry.

Bearer Token Per-Request
await client.users.list({
  headers: { Authorization: `Bearer ${userToken}` }
})

Use for multi-tenant applications.

See references/authentication.md for OAuth flows, JWT handling, and credential providers.

Retry and Backoff
Exponential Backoff with Jitter
async function retryWithBackoff<T>(fn: () => Promise<T>, maxRetries: number): Promise<T> {
  let attempt = 0

  while (attempt <= maxRetries) {
    try {
      return await fn()
    } catch (error) {
      attempt++
      if (attempt > maxRetries || !isRetryable(error)) throw error

      const exponential = Math.min(1000 * Math.pow(2, attempt - 1), 10000)
      const jitter = Math.random() * 500
      await sleep(exponential + jitter)
    }
  }
}

function isRetryable(error: any): boolean {
  return (
    error.code === 'ECONNRESET' ||
    error.code === 'ETIMEDOUT' ||
    (error.status >= 500 && error.status < 600) ||
    error.status === 429
  )
}

Retry Decision Matrix:

| Error Type | Retry? | Rationale | |------------|--------|-----------| | 5xx, 429, Network Timeout | ✅ Yes | Transient errors | | 4xx, 401, 403, 404 | ❌ No | Client errors won't fix themselves |

Rate Limit Handling
if (error.status === 429) {
  const retryAfter = parseInt(error.headers['retry-after'] || '60')
  await sleep(retryAfter * 1000)
}

Respect Retry-After header on 429 responses.

See references/retry-backoff.md for jitter strategies, circuit breakers, and idempotency keys.

Error Handling
Typed Error Hierarchy
class APIError extends Error {
  constructor(
    message: string,
    public status: number,
    public code: string,
    public requestId: string
  ) {
    super(message)
    this.name = 'APIError'
  }
}

class RateLimitError extends APIError {
  constructor(message: string, requestId: string, public retryAfter: number) {
    super(message, 429, 'rate_limit_error', requestId)
  }
}

class AuthenticationError extends APIError {
  constructor(message: string, requestId: string) {
    super(message, 401, 'authentication_error', requestId)
  }
}
Error Handling in Practice
try {
  const user = await client.users.create({ email: 'invalid' })
} catch (error) {
  if (error instanceof RateLimitError) {
    await sleep(error.retryAfter * 1000)
  } else if (error instanceof AuthenticationError) {
    console.error('Invalid API key')
  } else if (error instanceof APIError) {
    console.error(`${error.message} (Request ID: ${error.requestId})`)
  }
}

Include request ID in all errors for debugging.

See references/error-handling.md for user-friendly messages, validation errors, and debugging support.

Pagination
Async Iterators (Recommended)

TypeScript:

for await (const user of client.users.list({ limit: 100 })) {
  console.log(user.id, user.email)
}

Python:

async for user in client.users.list(limit=100):
    print(user.id, user.email)

SDK automatically fetches next page.

Implementation
class UsersResource {
  async *list(options?: { limit?: number }): AsyncGenerator<User> {
    let cursor: string | undefined = undefined

    while (true) {
      const response = await this.client.request('GET', '/users', {
        query: { limit: String(options?.limit || 100), ...(cursor ? { cursor } : {}) }
      })

      for (const user of response.data) yield user

      if (!response.has_more) break
      cursor = response.next_cursor
    }
  }
}
Manual Pagination
let cursor: string | undefined = undefined
while (true) {
  const response = await client.users.list({ limit: 100, cursor })
  for (const user of response.data) console.log(user.id)
  if (!response.has_more) break
  cursor = response.next_cursor
}

Provide both automatic and manual options.

See references/pagination.md for cursor vs. offset pagination and Go channel patterns.

Streaming
Server-Sent Events
async *stream(path: string, body?: any): AsyncGenerator<any> {
  const response = await fetch(url, {
    headers: { 'Accept': 'text/event-stream' },
    body: JSON.stringify(body)
  })

  const reader = response.body!.getReader()
  const decoder = new TextDecoder()

  while (true) {
    const { done, value } = await reader.read()
    if (done) break

    const chunk = decoder.decode(value)
    for (const line of chunk.split('\n')) {
      if (line.startsWith('data: ')) {
        const data = line.slice(6)
        if (data === '[DONE]') return
        yield JSON.parse(data)
      }
    }
  }
}

// Usage
for await (const chunk of client.posts.stream({ prompt: 'Write a story' })) {
  process.stdout.write(chunk.content)
}
Idempotency Keys

Prevent duplicate operations during retries:

import { randomUUID } from 'crypto'

if (['POST', 'PATCH', 'PUT'].includes(method)) {
  headers['Idempotency-Key'] = options?.idempotencyKey || randomUUID()
}

// Usage
await client.charges.create(
  { amount: 1000 },
  { idempotencyKey: 'charge_unique_123' }
)

Server deduplicates requests by key.

Versioning
Semantic Versioning
  • 1.0.01.1.0: New features (safe)
  • 1.1.02.0.0: Breaking changes (review)
  • 1.0.01.0.1: Bug fixes (safe)
Deprecation Warnings
function deprecated(message: string, since: string) {
  return function (target: any, propertyKey: string, descriptor: PropertyDescriptor) {
    const originalMethod = descriptor.value
    descriptor.value = function (...args: any[]) {
      console.warn(`[DEPRECATED] ${propertyKey} since ${since}. ${message}`)
      return originalMethod.apply(this, args)
    }
    return descriptor
  }
}

@deprecated('Use users.list() instead', 'v2.0.0')
async getAll() { return this.list() }
API Version Pinning
const client = new APIClient({
  apiKey: 'sk_test_...',
  apiVersion: '2025-01-01'
})

See references/versioning.md for migration strategies.

Configuration Best Practices
interface ClientConfig {
  apiKey: string
  baseURL?: string
  maxRetries?: number
  timeout?: number
  apiVersion?: string
  onTokenRefresh?: (token: string) => void
}

class APIClient {
  constructor(config: ClientConfig) {
    this.apiKey = config.apiKey
    this.baseURL = config.baseURL || 'https://api.example.com'
    this.maxRetries = config.maxRetries ?? 3
    this.timeout = config.timeout ?? 30000
  }
}

Provide sensible defaults, require only apiKey.

Quick Reference Tables
Authentication Patterns

| Pattern | Use Case | |---------|----------| | API Key | Service-to-service | | OAuth Refresh | User-based auth | | Bearer Per-Request | Multi-tenant |

Retry Strategies

| Strategy | Use Case | |----------|----------| | Exponential Backoff | Default retry | | Rate Limit | 429 responses | | Max Retries | Avoid infinite loops (3-5) |

Pagination Options

| Pattern | Language | Use Case | |---------|----------|----------| | Async Iterator | TypeScript, Python | Automatic pagination | | Generator | Python | Sync pagination | | Channels | Go | Concurrent iteration | | Manual | All | Explicit control |

Reference Documentation

Architecture:

  • references/architecture-patterns.md - Resource vs. command organization

Core Patterns:

  • references/authentication.md - OAuth, token refresh, credential providers
  • references/retry-backoff.md - Exponential backoff, jitter, circuit breakers
  • references/error-handling.md - Error hierarchies, debugging support
  • references/pagination.md - Cursor vs. offset, async iterators
  • references/versioning.md - SemVer, deprecation strategies
  • references/testing-sdks.md - Unit testing, mocking, integration tests
Code Examples

TypeScript:

  • examples/typescript/basic-client.ts - Simple async SDK
  • examples/typescript/advanced-client.ts - Retry, errors, streaming
  • examples/typescript/resource-based.ts - Stripe-style organization

Python:

  • examples/python/sync-client.py - Synchronous client
  • examples/python/async-client.py - Async client with asyncio
  • examples/python/dual-client.py - Both sync and async

Go:

  • examples/go/basic-client.go - Simple Go client
  • examples/go/context-client.go - Context patterns
  • examples/go/channel-pagination.go - Channel-based pagination
Best-in-Class SDK Examples

Study these production SDKs:

TypeScript/JavaScript:

  • AWS SDK v3 (@aws-sdk/client-*): Modular, tree-shakeable, middleware
  • Stripe Node (stripe): Resource-based, typed errors, excellent DX
  • OpenAI Node (openai): Streaming, async iterators, modern TypeScript

Python:

  • Boto3 (boto3): Resource vs. client patterns, paginators
  • Stripe Python (stripe): Dual sync/async, context managers

Go:

  • AWS SDK Go v2 (github.com/aws/aws-sdk-go-v2): Context, middleware
Common Pitfalls

Avoid these mistakes:

  1. No Retry Logic - All SDKs need automatic retries for transient errors
  2. Poor Error Messages - Include request ID, status code, error type
  3. No Pagination - Implement automatic pagination with async iterators
  4. Hardcoded Credentials - Use environment variables or config files
  5. Missing Idempotency - Add idempotency keys to prevent duplicate operations
  6. Ignoring Rate Limits - Respect Retry-After header on 429 responses
  7. Breaking Changes - Use SemVer, deprecate before removing
Integration with Other Skills
  • api-design-principles: API design complements SDK design (error codes → error classes)
  • building-clis: CLIs wrap SDKs for command-line access
  • testing-strategies: Test SDKs with mocked HTTP, retry scenarios
Next Steps

Review language-specific examples for implementation details. Study references for deep dives on specific patterns. Examine best-in-class SDKs (Stripe, AWS, OpenAI) for inspiration.

按 MIT 许可原样转载,未经改动 · 在 GitHub 查看 →

评论

登录即可评论;带「已验证安装」的,是发布者名下有本店的安装或持有记录。