Skip to content

feat(generator): generate TypeScript SDK from OpenAPI spec #17

Description

@dev-queiroz

Summary

Generate a first-class TypeScript SDK package that provides type-safe client for the generated OpenAPI API.

Motivation

OpenAPI spec exists, but consumers must write HTTP clients manually or use generic tools. Generated SDK provides:

  • Typed methods for all endpoints
  • Automatic request/response validation with Zod
  • Error handling with typed exceptions
  • Built-in authentication support

Scope

In Scope

  • Generate SDK as separate npm-installable module
  • Client class with all CRUD operations
  • Typed request/response methods
  • Zod schema validation for responses
  • Bearer token authentication support
  • Error handling with custom exception types
  • Usage examples in generated README

Out of Scope

  • Swagger code generation from third-party libs
  • GraphQL client
  • gRPC client
  • Custom HTTP interceptors

Acceptance Criteria

  • SDK generated in generated/sdk/ directory
  • ForgeClient class with typed methods
  • Methods match OpenAPI paths (GET /users → client.users.list())
  • Request typing from OpenAPI request schemas
  • Response typing from OpenAPI response schemas
  • Zod validation on response objects
  • Bearer token support with constructor parameter
  • Query parameter support for list endpoints
  • Path parameter support for detail endpoints
  • Error responses with typed exceptions
  • Generated package.json with dependencies
  • TypeScript declaration files (.d.ts) generated
  • Usage examples included

Files to Create/Modify

  • packages/generators/src/sdk/typescript-sdk-generator.ts (NEW)
  • packages/generators/src/index.ts (MODIFY)
  • tests/sdk-generation.test.mjs (NEW)

Example Output

// generated/sdk/client.ts
import { z } from 'zod';

const UserSchema = z.object({
  id: z.string().uuid(),
  name: z.string(),
  email: z.string().email(),
});

type User = z.infer<typeof UserSchema>;

export interface ClientConfig {
  baseUrl?: string;
  token?: string;
}

export class ForgeClient {
  private baseUrl: string;
  private token?: string;

  constructor(config: ClientConfig = {}) {
    this.baseUrl = config.baseUrl || 'http://localhost:3000';
    this.token = config.token;
  }

  setToken(token: string): void {
    this.token = token;
  }

  async #request<T>(
    path: string,
    method: string,
    data?: unknown,
    schema?: z.ZodSchema<T>
  ): Promise<T> {
    const response = await fetch(`${this.baseUrl}${path}`, {
      method,
      headers: {
        'Content-Type': 'application/json',
        ...(this.token && { Authorization: `Bearer ${this.token}` }),
      },
      body: data ? JSON.stringify(data) : undefined,
    });

    if (!response.ok) {
      throw new Error(`HTTP ${response.status}: ${response.statusText}`);
    }

    const json = await response.json();
    return schema ? schema.parse(json) : json;
  }

  users = {
    list: async () => {
      return this.#request<User[]>(
        '/users',
        'GET',
        undefined,
        z.array(UserSchema)
      );
    },

    get: async (id: string) => {
      return this.#request<User>(
        `/users/${id}`,
        'GET',
        undefined,
        UserSchema
      );
    },

    create: async (data: Omit<User, 'id'>) => {
      return this.#request<User>(
        '/users',
        'POST',
        data,
        UserSchema
      );
    },

    update: async (id: string, data: Partial<User>) => {
      return this.#request<User>(
        `/users/${id}`,
        'PATCH',
        data,
        UserSchema
      );
    },

    delete: async (id: string) => {
      await this.#request(`/users/${id}`, 'DELETE');
    },
  };
}

// Usage
const client = new ForgeClient({
  baseUrl: 'https://api.example.com',
  token: process.env.API_TOKEN,
});

const users = await client.users.list();
const user = await client.users.get(userId);
const newUser = await client.users.create({ name: 'John', email: 'john@example.com' });

Test Strategy

  • Generate SDK from OpenAPI spec
  • Import SDK in test project
  • Test type inference works correctly
  • Test all CRUD methods with mock server
  • Verify error types are correct
  • Verify Zod validation catches bad responses
  • Verify Bearer token sent in Authorization header
  • Verify query parameters passed correctly

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions