Skip to content

Conversation

Copy link

Copilot AI commented Dec 23, 2025

Implements a typed client that infers API specifications from server-side DefineApiSpec definitions, enabling compile-time validation and IDE autocomplete for API calls.

Type System

  • ExtractPathParams<Path>: Recursively extracts path parameters from URL patterns (/authors/{id}/books/{bookId} → typed params object)
  • ClientResponse<Responses>: Discriminated union where body type narrows based on status code
  • RequestOptions<Spec>: Infers params, query, body, headers, and cookies from endpoint spec
  • Client<ApiSpec>: Typed methods for all HTTP verbs (GET, POST, PUT, PATCH, DELETE, OPTIONS, HEAD)

Runtime Implementation

  • createClient<ApiSpec>(config): Factory returning typed client instance
  • Path parameter substitution ({id} → actual values)
  • Query string building with array support
  • Cookie header management
  • JSON/text response parsing
  • Header merging (base + request)

Usage

import { createClient, Tspec } from 'tspec';

// Server-side spec
type AuthorApiSpec = Tspec.DefineApiSpec<{
  paths: {
    '/authors/{id}': {
      get: {
        path: { id: number };
        responses: { 
          200: Author;
          404: { message: string };
        };
      };
    };
  }
}>;

// Client-side usage
const client = createClient<AuthorApiSpec>({
  baseUrl: 'https://api.example.com',
});

const result = await client.get('/authors/{id}', {
  params: { id: 1 }, // TypeScript enforces required params
});

if (result.status === 200) {
  console.log(result.body.name); // Typed as Author
} else if (result.status === 404) {
  console.error(result.body.message); // Typed as { message: string }
}

Package Exports

Added tspec/client export path for standalone client usage without importing server-side generation code.

Testing

16 new tests covering type inference, request building, response handling, and cookie support. All 121 tests passing.

Original prompt

Overview

Create a fully typed client layer that infers API specifications defined on the server using DefineApiSpec. This will enable end-to-end type safety between server API definitions and client-side usage.

Example Usage

Server-side (existing functionality)

import { Tspec } from 'tspec';

interface Author {
  id: number;
  name: string;
}

export type AuthorApiSpec = Tspec.DefineApiSpec<{
  paths: {
    '/authors/{id}': {
      get: {
        summary: 'Get author by id',
        path: { id: number },
        responses: { 
          200: Author,
          404: { message: string },
        },
      },
    },
  }
}>;

Client-side (NEW - to be implemented)

import { createClient } from 'tspec/client';
import { AuthorApiSpec } from './server';

const client = createClient<AuthorApiSpec>({
  baseUrl: 'https://api.example.com',
  baseHeaders: {
    'Content-Type': 'application/json',
  },
});

const result = await client.get('/authors/{id}', {
  params: { id: 1 },
});

if (result.status === 200) {
  console.log(result.body.name); // Fully typed as Author
} else if (result.status === 404) {
  console.error(result.body.message); // Fully typed as { message: string }
}

Requirements

1. Type Definitions (packages/tspec/src/types/client.ts)

Create type utilities that:

  • Extract all endpoints from a DefineApiSpec type
  • Infer path parameters from URL patterns (e.g., {id} in /authors/{id})
  • Infer query parameters, request body, and headers for each endpoint
  • Provide discriminated union responses based on status codes
  • Support all HTTP methods: get, post, put, patch, delete, options, head

Key types to implement:

// Extract response types by status code
type ExtractResponses<T> = ...

// Infer path parameters from URL string
type ExtractPathParams<Path extends string> = ...

// Client configuration
interface ClientConfig {
  baseUrl: string;
  baseHeaders?: Record<string, string>;
  fetch?: typeof fetch;
}

// Typed response with status discrimination
type TypedResponse<Responses> = {
  [Status in keyof Responses]: {
    status: Status;
    body: Responses[Status];
    headers: Headers;
  };
}[keyof Responses];

2. Client Implementation (packages/tspec/src/client/index.ts)

Implement createClient<T>(config: ClientConfig) that:

  • Returns an object with methods for each HTTP method (get, post, put, patch, delete, options, head)
  • Each method accepts: (path: string, options?: { params?, query?, body?, headers? })
  • Automatically substitutes path parameters (e.g., /authors/{id} with { id: 1 }/authors/1)
  • Serializes query parameters to URL search params
  • Handles JSON body serialization
  • Returns a typed response with status code discrimination
  • Allows custom fetch implementation for Node.js compatibility

3. Export Updates

Update packages/tspec/src/index.ts to export the new client functionality:

export { createClient } from './client';
export type { ClientConfig, TypedResponse } from './types/client';

4. Tests

Add tests in packages/tspec/src/client/__tests__/ that verify:

  • Type inference works correctly for path parameters
  • Type inference works correctly for query parameters
  • Type inference works correctly for request body
  • Response types are correctly discriminated by status code
  • Runtime behavior (path substitution, query serialization, etc.)

Technical Considerations

  • The implementation should leverage the existing types in packages/tspec/src/types/tspec.ts
  • Use TypeScript's template literal types for path parameter extraction
  • Ensure the client works in both browser and Node.js environments
  • Keep bundle size minimal by avoiding unnecessary runtime dependencies

This pull request was created from Copilot chat.


✨ Let Copilot coding agent set things up for you — coding agent works faster and does higher quality work when set up for your repo.

Copilot AI and others added 4 commits December 23, 2025 04:11
Co-authored-by: hanc2006 <4517251+hanc2006@users.noreply.github.com>
Co-authored-by: hanc2006 <4517251+hanc2006@users.noreply.github.com>
Co-authored-by: hanc2006 <4517251+hanc2006@users.noreply.github.com>
Co-authored-by: hanc2006 <4517251+hanc2006@users.noreply.github.com>
Copilot AI changed the title [WIP] Add fully typed client layer with API specifications Add fully typed client layer for end-to-end type safety Dec 23, 2025
Copilot AI requested a review from hanc2006 December 23, 2025 04:23
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants