diff --git a/.changeset/true-lemons-think.md b/.changeset/true-lemons-think.md new file mode 100644 index 0000000..7b9c539 --- /dev/null +++ b/.changeset/true-lemons-think.md @@ -0,0 +1,24 @@ +--- +"typed-openapi": minor +--- + +Add comprehensive type-safe error handling and configurable status codes + +- **Type-safe error handling**: Added discriminated unions for API responses with `SafeApiResponse` and `TypedApiResponse` types that distinguish between success and error responses based on HTTP status codes +- **withResponse parameter**: Enhanced API clients to optionally return both the parsed data and the original Response object for advanced use cases +- **TanStack Query integration**: Added complete TanStack Query client generation with: + - Advanced mutation options supporting `withResponse` and `selectFn` parameters + - Automatic error type inference based on OpenAPI error schemas instead of generic Error type + - Type-safe error handling with discriminated unions for mutations + - Response-like error objects that extend Response with additional `data` property for consistency +- **Configurable status codes**: Made success and error status codes fully configurable: + - New `--success-status-codes` and `--error-status-codes` CLI options + - `GeneratorOptions` now accepts `successStatusCodes` and `errorStatusCodes` arrays + - Default error status codes cover comprehensive 4xx and 5xx ranges +- **Enhanced CLI options**: Added new command-line options for better control: + - `--include-client` to control whether to generate API client types and implementation + - `--include-client=false` to only generate the schemas and endpoints +- **Enhanced types**: Renamed `StatusCode` to `TStatusCode` and added reusable `ErrorStatusCode` type +- **Comprehensive documentation**: Added detailed examples and guides for error handling patterns + +This release significantly improves the type safety and flexibility of generated API clients, especially for error handling scenarios. diff --git a/.github/workflows/build-and-test.yaml b/.github/workflows/build-and-test.yaml index 3fda180..60f19ed 100644 --- a/.github/workflows/build-and-test.yaml +++ b/.github/workflows/build-and-test.yaml @@ -25,3 +25,6 @@ jobs: - name: Test run: pnpm test + + - name: Release package + run: pnpm dlx pkg-pr-new publish './packages/typed-openapi' diff --git a/README.md b/README.md index bb5b319..48b5ca2 100644 --- a/README.md +++ b/README.md @@ -6,10 +6,14 @@ See [the online playground](https://typed-openapi-astahmer.vercel.app/) ![Screenshot 2023-08-08 at 00 48 42](https://github.com/astahmer/typed-openapi/assets/47224540/3016fa92-e09a-41f3-a95f-32caa41325da) +[![pkg.pr.new](https://pkg.pr.new/badge/astahmer/typed-openapi)](https://pkg.pr.new/~/astahmer/typed-openapi) + ## Features - Headless API client, bring your own fetcher ! (fetch, axios, ky, etc...) - Generates a fully typesafe API client with just types by default (instant suggestions) +- **Type-safe error handling** with discriminated unions and configurable success status codes +- **TanStack Query integration** with `withResponse` and `selectFn` options for advanced error handling - Or you can also generate a client with runtime validation using one of the following runtimes: - [zod](https://zod.dev/) - [typebox](https://github.com/sinclairzx81/typebox) @@ -37,17 +41,26 @@ npx typed-openapi -h ``` ```sh -typed-openapi/0.1.3 +typed-openapi/1.5.0 -Usage: $ typed-openapi +Usage: + $ typed-openapi -Commands: Generate +Commands: + Generate -For more info, run any command with the `--help` flag: $ typed-openapi --help +For more info, run any command with the `--help` flag: + $ typed-openapi --help -Options: -o, --output Output path for the api client ts file (defaults to `..ts`) -r, --runtime - Runtime to use for validation; defaults to `none`; available: 'none' | 'arktype' | 'io-ts' | 'typebox' | -'valibot' | 'yup' | 'zod' (default: none) -h, --help Display this message -v, --version Display version number +Options: + -o, --output Output path for the api client ts file (defaults to `..ts`) + -r, --runtime Runtime to use for validation; defaults to `none`; available: Type<"arktype" | "io-ts" | "none" | "typebox" | "valibot" | "yup" | "zod"> (default: none) + --schemas-only Only generate schemas, skipping client generation (defaults to false) (default: false) + --include-client Include API client types and implementation (defaults to true) (default: true) + --success-status-codes Comma-separated list of success status codes for type-safe error handling (defaults to 2xx and 3xx ranges) + --tanstack [name] Generate tanstack client with withResponse support for error handling, defaults to false, can optionally specify a name for the generated file + -h, --help Display this message + -v, --version Display version number ``` ## Non-goals @@ -65,6 +78,247 @@ Options: -o, --output Output path for the api client ts file (defaults to Basically, let's focus on having a fast and typesafe API client generation instead. +## Usage Examples + +### API Client Setup + +The generated client is headless - you need to provide your own fetcher. Here are ready-to-use examples: + +- **[Basic API Client](packages/typed-openapi/API_CLIENT_EXAMPLES.md#basic-api-client-api-client-examplets)** - Simple, dependency-free wrapper +- **[Validating API Client](packages/typed-openapi/API_CLIENT_EXAMPLES.md#validating-api-client-api-client-with-validationts)** - With request/response validation + +### Type-Safe Error Handling + +The generated client supports two response modes: + +```typescript +// Default: Direct data return (simpler, but no error details) +const user = await api.get("/users/{id}", { + path: { id: "123" } +}); // user is directly typed as User object + +// WithResponse: Full Response object with typed ok/status and data +const result = await api.get("/users/{id}", { + path: { id: "123" }, + withResponse: true +}); + +// result is the actual Response object with typed ok/status overrides plus data access +if (result.ok) { + // Access data directly (already parsed) + const user = result.data; // Type: User + console.log("User:", user.name); + + // Or use json() method for compatibility + const userFromJson = await result.json(); // Same as result.data + console.log("User from json():", userFromJson.name); + + console.log("Status:", result.status); // Typed as success status codes + console.log("Headers:", result.headers); // Access to all Response properties +} else { + // Access error data directly + const error = result.data; // Type based on status code + if (result.status === 404) { + console.log("User not found:", error.message); + } else if (result.status === 401) { + console.log("Unauthorized:", error.details); + } +} +```### Success Response Type-Narrowing + +When endpoints have multiple success responses (200, 201, etc.), the type is automatically narrowed based on status: + +```typescript +const result = await api.post("/users", { + body: { name: "John" }, + withResponse: true +}); + +if (result.ok) { + if (result.status === 201) { + // result.data typed as CreateUserResponse (201) + console.log("Created user:", result.data.id); + } else if (result.status === 200) { + // result.data typed as ExistingUserResponse (200) + console.log("Existing user:", result.data.email); + } +} +``` + +### Generic Request Method + +For dynamic endpoint calls or when you need more control: + +```typescript +// Type-safe generic request method +const response = await api.request("GET", "/users/{id}", { + path: { id: "123" }, + query: { include: ["profile", "settings"] } +}); + +const user = await response.json(); // Fully typed based on endpoint +``` + +### TanStack Query Integration + +Generate TanStack Query wrappers for your endpoints: + +```bash +npx typed-openapi api.yaml --runtime zod --tanstack +``` + +## useQuery / fetchQuery / ensureQueryData + +```ts +// Basic query +const accessiblePagesQuery = useQuery( + tanstackApi.get('/authorization/accessible-pages').queryOptions +); + +// Query with query parameters +const membersQuery = useQuery( + tanstackApi.get('/authorization/organizations/:organizationId/members/search', { + path: { organizationId: 'org123' }, + query: { searchQuery: 'john' } + }).queryOptions +); + +// With additional query options +const departmentCostsQuery = useQuery({ + ...tanstackApi.get('/organizations/:organizationId/department-costs', { + path: { organizationId: params.orgId }, + query: { period: selectedPeriod }, + }).queryOptions, + staleTime: 30 * 1000, + // placeholderData: keepPreviousData, + // etc +}); +``` + +or if you need it in a router `beforeLoad` / `loader`: + +```ts +import { tanstackApi } from '#api'; + +await queryClient.fetchQuery( + tanstackApi.get('/:organizationId/remediation/accounting-lines/metrics', { + path: { organizationId: params.orgId }, + }).queryOptions, +); +``` + +## useMutation + +The mutation API supports both basic usage and advanced error handling with `withResponse` and custom transformations with `selectFn`. **Note**: All mutation errors are Response-like objects with type-safe error inference based on your OpenAPI error schemas. + +```ts +// Basic mutation (returns data only) +const basicMutation = useMutation({ + ...tanstackApi.mutation("post", '/authorization/organizations/:organizationId/invitations').mutationOptions, + onError: (error) => { + // error is a Response-like object with typed data based on OpenAPI spec + console.log(error instanceof Response); // true + console.log(error.status); // 400, 401, etc. (properly typed) + console.log(error.data); // Typed error response body + } +}); + +// With error handling using withResponse +const mutationWithErrorHandling = useMutation( + tanstackApi.mutation("post", '/users', { + withResponse: true + }).mutationOptions +); + +// With custom response transformation +const customMutation = useMutation( + tanstackApi.mutation("post", '/users', { + selectFn: (user) => ({ userId: user.id, userName: user.name }) + }).mutationOptions +); + +// Advanced: withResponse + selectFn for comprehensive error handling +const advancedMutation = useMutation( + tanstackApi.mutation("post", '/users', { + withResponse: true, + selectFn: (response) => ({ + success: response.ok, + user: response.ok ? response.data : null, + error: response.ok ? null : response.data, + statusCode: response.status + }) + }).mutationOptions +); +``` + +### Usage Examples: + +```ts +// Basic usage +basicMutation.mutate({ + body: { + emailAddress: 'user@example.com', + department: 'engineering', + roleName: 'admin' + } +}); + +// With error handling +mutationWithErrorHandling.mutate( + { body: userData }, + { + onSuccess: (response) => { + if (response.ok) { + toast.success(`User ${response.data.name} created!`); + } else { + if (response.status === 400) { + toast.error(`Validation error: ${response.data.message}`); + } else if (response.status === 409) { + toast.error('User already exists'); + } + } + } + } +); + +// Advanced usage with custom transformation +advancedMutation.mutate( + { body: userData }, + { + onSuccess: (result) => { + if (result.success) { + console.log('Created user:', result.user.name); + } else { + console.error(`Error ${result.statusCode}:`, result.error); + } + } + } +); +``` + +## useMutation without the tanstack api + +If you need to make a custom mutation you could use the `api` directly: + +```ts +const { mutate: login, isPending } = useMutation({ + mutationFn: async (type: 'google' | 'microsoft') => { + return api.post(`/authentication/${type}`, { body: { redirectUri: search.redirect } }); + }, + onSuccess: (data) => { + window.location.replace(data.url); + }, + onError: (error, type) => { + console.error(error); + toast({ + title: t(`toast.login.${type}.error`), + icon: 'warning', + variant: 'critical', + }); + }, +}); +``` + ## Alternatives [openapi-zod-client](https://github.com/astahmer/openapi-zod-client), which generates a diff --git a/packages/typed-openapi/API_CLIENT_EXAMPLES.md b/packages/typed-openapi/API_CLIENT_EXAMPLES.md new file mode 100644 index 0000000..699850d --- /dev/null +++ b/packages/typed-openapi/API_CLIENT_EXAMPLES.md @@ -0,0 +1,202 @@ +# API Client Examples + +These are production-ready API client wrappers for your generated typed-openapi code. Copy the one that fits your needs and customize it. + +## Basic API Client (`api-client-example.ts`) + +A simple, dependency-free client that handles: +- Path parameter replacement (`{id}` and `:id` formats) +- Query parameter serialization (including arrays) +- JSON request/response handling +- Custom headers +- Basic error handling + +### Setup + +1. Copy the file to your project +2. Update the import path to your generated API file: + ```typescript + import { type EndpointParameters, type Fetcher, createApiClient } from './generated/api'; + ``` +3. Set your API base URL: + ```typescript + const API_BASE_URL = process.env['API_BASE_URL'] || 'https://your-api.com'; + ``` +4. Uncomment the client creation: + ```typescript + export const api = createApiClient(fetcher, API_BASE_URL); + ``` + +### Usage + +```typescript +// GET request with query params +const users = await api.get('/users', { + query: { page: 1, limit: 10, tags: ['admin', 'user'] } +}); + +// POST request with body +const newUser = await api.post('/users', { + body: { name: 'John', email: 'john@example.com' } +}); + +// With path parameters +const user = await api.get('/users/{id}', { + path: { id: '123' } +}); + +// With custom headers +const result = await api.get('/protected', { + header: { Authorization: 'Bearer your-token' } +}); +``` + +## Validating API Client (`api-client-with-validation.ts`) + +Extends the basic client with schema validation for: +- Request body validation before sending +- Response validation after receiving +- Type-safe validation error handling + +### Setup + +1. Follow the basic client setup steps above +2. Import your validation library and schemas: + ```typescript + // For Zod + import { z } from 'zod'; + import { EndpointByMethod } from './generated/api'; + + // For Yup + import * as yup from 'yup'; + import { EndpointByMethod } from './generated/api'; + ``` +3. Implement the validation logic in the marked TODO sections +4. Configure validation settings: + ```typescript + const VALIDATE_REQUESTS = true; // Validate request bodies + const VALIDATE_RESPONSES = true; // Validate response data + ``` + +### Validation Implementation Example (Zod) + +```typescript +// Request validation +if (VALIDATE_REQUESTS && params?.body) { + const endpoint = EndpointByMethod[method as keyof typeof EndpointByMethod]; + const pathSchema = endpoint?.[actualUrl as keyof typeof endpoint]; + if (pathSchema?.body) { + pathSchema.body.parse(params.body); // Throws if invalid + } +} + +// Response validation +const responseData = await responseClone.json(); +const endpoint = EndpointByMethod[method as keyof typeof EndpointByMethod]; +const pathSchema = endpoint?.[actualUrl as keyof typeof endpoint]; +const statusSchema = pathSchema?.responses?.[response.status]; +if (statusSchema) { + statusSchema.parse(responseData); // Throws if invalid +} +``` + +### Error Handling + +```typescript +try { + const result = await api.post('/users', { + body: { name: 'John', email: 'invalid-email' } + }); +} catch (error) { + if (error instanceof ValidationError) { + if (error.type === 'request') { + console.error('Invalid request data:', error.validationErrors); + } else { + console.error('Invalid response data:', error.validationErrors); + } + } else { + console.error('Network or HTTP error:', error); + } +} +``` + +## Customization Ideas + +- **Authentication**: Add token handling, refresh logic, or auth headers +- **Retries**: Implement retry logic for failed requests +- **Caching**: Add response caching with TTL +- **Logging**: Add request/response logging for debugging +- **Rate limiting**: Implement client-side rate limiting +- **Metrics**: Add performance monitoring and error tracking +- **Base URL per environment**: Different URLs for dev/staging/prod + +## Error Handling Enhancement + +You can enhance error handling by creating custom error classes: + +```typescript +class ApiError extends Error { + constructor( + public readonly status: number, + public readonly statusText: string, + public readonly response: Response + ) { + super(`HTTP ${status}: ${statusText}`); + this.name = 'ApiError'; + } +} + +// In your fetcher: +if (!response.ok) { + throw new ApiError(response.status, response.statusText, response); +} +``` + +## Error Handling with withResponse + +For type-safe error handling without exceptions, use the `withResponse: true` option: + +```typescript +// Example with both data access methods +const result = await api.get("/users/{id}", { + path: { id: "123" }, + withResponse: true +}); + +if (result.ok) { + // Access data directly (already parsed) + const user = result.data; // Type: User + console.log("User:", user.name); + + // Or use json() method for compatibility + const userFromJson = await result.json(); // Same as result.data + console.log("Same user:", userFromJson.name); + + // Access other Response properties + console.log("Status:", result.status); + console.log("Headers:", result.headers.get("content-type")); +} else { + // Handle errors with proper typing + const error = result.data; // Type based on status code + + if (result.status === 404) { + console.error("Not found:", error.message); + } else if (result.status === 401) { + console.error("Unauthorized:", error.details); + } else { + console.error("Unknown error:", error); + } +} +``` + +## TanStack Query Integration + +For React applications using TanStack Query, see: +- [TANSTACK_QUERY_EXAMPLES.md](./TANSTACK_QUERY_EXAMPLES.md) for usage patterns with `withResponse` and `selectFn` +- [TANSTACK_QUERY_ERROR_HANDLING.md](./TANSTACK_QUERY_ERROR_HANDLING.md) for type-safe error handling based on OpenAPI error schemas + +Key features: +- Type-safe mutations with `withResponse` option +- Custom response transformation with `selectFn` +- Automatic error type inference from OpenAPI specs +- Full type inference for all scenarios diff --git a/packages/typed-openapi/TANSTACK_QUERY_ERROR_HANDLING.md b/packages/typed-openapi/TANSTACK_QUERY_ERROR_HANDLING.md new file mode 100644 index 0000000..a096da7 --- /dev/null +++ b/packages/typed-openapi/TANSTACK_QUERY_ERROR_HANDLING.md @@ -0,0 +1,252 @@ +# TanStack Query Error Handling Examples + +This document demonstrates how the generated TanStack Query client provides type-safe error handling based on OpenAPI error schemas. + +## Error Type Inference + +The TanStack Query client automatically infers error types from your OpenAPI spec's error responses (status codes 400-511). + +### OpenAPI Spec Example + +```yaml +paths: + /users: + post: + responses: + '201': + description: Created + content: + application/json: + schema: + $ref: '#/components/schemas/User' + '400': + description: Validation Error + content: + application/json: + schema: + $ref: '#/components/schemas/ValidationError' + '409': + description: Conflict + content: + application/json: + schema: + $ref: '#/components/schemas/ConflictError' + '500': + description: Server Error + content: + application/json: + schema: + $ref: '#/components/schemas/ServerError' + +components: + schemas: + User: + type: object + properties: + id: { type: string } + name: { type: string } + email: { type: string } + + ValidationError: + type: object + properties: + message: { type: string } + fields: + type: array + items: { type: string } + + ConflictError: + type: object + properties: + message: { type: string } + existingId: { type: string } + + ServerError: + type: object + properties: + message: { type: string } + code: { type: string } +``` + +## Generated Error Types + +For the above spec, the TanStack Query client generates: + +```typescript +// Error type is automatically inferred as: +type CreateUserError = + | Omit & { status: 400; data: ValidationError } + | Omit & { status: 409; data: ConflictError } + | Omit & { status: 500; data: ServerError } +``` + +## Usage Examples + +### Basic Usage with Type-Safe Error Handling + +```typescript +import { useMutation } from '@tanstack/react-query'; +import { tanstackApi } from './generated/tanstack-query-client'; + +function CreateUserForm() { + const createUser = useMutation({ + ...tanstackApi.mutation("post", "/users").mutationOptions, + onError: (error) => { + // error is fully typed based on your OpenAPI spec! + // error is also a Response instance with additional data property + console.log(error instanceof Response); // true + + if (error.status === 400) { + // error.data is typed as ValidationError + console.error('Validation failed:', error.data.message); + console.error('Invalid fields:', error.data.fields); + } else if (error.status === 409) { + // error.data is typed as ConflictError + console.error('User already exists:', error.data.existingId); + } else if (error.status === 500) { + // error.data is typed as ServerError + console.error('Server error:', error.data.code, error.data.message); + } + }, + onSuccess: (user) => { + // user is typed as User + console.log('Created user:', user.name); + } + }); + + return ( +
createUser.mutate({ + body: { name: 'John', email: 'john@example.com' } + })}> + {/* form content */} +
+ ); +} +``` + +### Advanced Usage with withResponse + +```typescript +function AdvancedCreateUserForm() { + const createUser = useMutation({ + ...tanstackApi.mutation("post", "/users", { + withResponse: true, + selectFn: (response) => ({ + success: response.ok, + user: response.ok ? response.data : null, + error: response.ok ? null : response.data, + statusCode: response.status, + headers: response.headers + }) + }).mutationOptions, + onError: (error) => { + // Same typed error handling as above + // error is also a Response instance + console.log(error.ok); // false + console.log(error.headers); // Response headers + + switch (error.status) { + case 400: + toast.error(`Validation: ${error.data.fields.join(', ')}`); + break; + case 409: + toast.error('Email already taken'); + break; + case 500: + toast.error(`Server error: ${error.data.code}`); + break; + } + }, + onSuccess: (result) => { + if (result.success) { + toast.success(`Welcome ${result.user!.name}!`); + // Access response headers + const rateLimit = result.headers.get('x-rate-limit-remaining'); + } + } + }); + + // ... rest of component +} +``` + +### Error Type Discrimination in Action + +```typescript +// The error parameter is automatically discriminated based on status +const handleError = (error: CreateUserError) => { + switch (error.status) { + case 400: + // TypeScript knows error.data is ValidationError + return { + title: 'Validation Failed', + message: error.data.message, + details: error.data.fields.map(field => `${field} is invalid`) + }; + + case 409: + // TypeScript knows error.data is ConflictError + return { + title: 'User Exists', + message: `User already exists with ID: ${error.data.existingId}`, + action: 'login' + }; + + case 500: + // TypeScript knows error.data is ServerError + return { + title: 'Server Error', + message: `Internal error (${error.data.code}): ${error.data.message}`, + action: 'retry' + }; + } +}; +``` + +## Benefits + +- **Full Type Safety**: Error types are automatically inferred from your OpenAPI spec +- **No Manual Type Definitions**: Types are generated, not hand-written +- **Discriminated Unions**: TypeScript can narrow error types based on status codes +- **IDE Support**: Full autocomplete and type checking for error properties +- **Runtime Safety**: Errors are thrown with consistent structure: `{ status, data }` + +## Error Structure + +All errors thrown by TanStack Query mutations are **Response-like objects** that extend the native Response with additional type safety: + +```typescript +interface ApiError extends Omit { + status: number; // HTTP status code (400-511) - properly typed + data: TData; // Typed error response body + // All other Response properties are available: + // ok: boolean + // headers: Headers + // url: string + // etc. +} +``` + +### Key Benefits of Response-like Errors + +- **instanceof Response**: Error objects are proper Response instances +- **Consistent API**: Both success and error responses follow the same Response pattern +- **Full Response Access**: Access to headers, URL, and other Response properties +- **Type Safety**: The `status` property is properly typed based on configured error status codes + +### Example Usage + +```typescript +try { + await mutation.mutationFn(params); +} catch (error) { + console.log(error instanceof Response); // true + console.log(error.ok); // false + console.log(error.status); // 400, 401, etc. (properly typed) + console.log(error.data); // Typed error response body + console.log(error.headers.get('content-type')); // Response headers + console.log(error.url); // Request URL +} +``` + +This makes error handling predictable and type-safe across your entire application while maintaining consistency with the Response API. diff --git a/packages/typed-openapi/TANSTACK_QUERY_EXAMPLES.md b/packages/typed-openapi/TANSTACK_QUERY_EXAMPLES.md new file mode 100644 index 0000000..9e73e3d --- /dev/null +++ b/packages/typed-openapi/TANSTACK_QUERY_EXAMPLES.md @@ -0,0 +1,178 @@ +# TanStack Query Integration Examples + +This document shows how to use the generated TanStack Query client with the new `withResponse` and `selectFn` options. + +## Basic Setup + +```typescript +import { TanstackQueryApiClient } from './generated/tanstack-query-client'; +import { createApiClient } from './generated/api-client'; + +// Create the API client and TanStack Query wrapper +const apiClient = createApiClient(fetch); +const queryClient = new TanstackQueryApiClient(apiClient); +``` + +## Usage Patterns + +### 1. Basic Usage (Data Only) + +```typescript +const basicMutation = queryClient.mutation("post", "/users"); +// Type: { mutationFn: (params) => Promise } + +// In React component +const createUser = useMutation(basicMutation.mutationOptions); +``` + +### 2. With Response Object for Error Handling + +```typescript +const withResponseMutation = queryClient.mutation("post", "/users", { + withResponse: true +}); +// Type: { mutationFn: (params) => Promise> } + +// Usage with error handling +const createUser = useMutation({ + ...withResponseMutation.mutationOptions, + onSuccess: (response) => { + if (response.ok) { + console.log('User created:', response.data); + console.log('Status:', response.status); + console.log('Headers:', response.headers.get('location')); + } else { + if (response.status === 400) { + console.error('Validation error:', response.data); + } else if (response.status === 409) { + console.error('User already exists:', response.data); + } + } + } +}); +``` + +### 3. Custom Response Transformation + +```typescript +// Transform response data without withResponse +const customSelectMutation = queryClient.mutation("post", "/users", { + selectFn: (user) => ({ + userId: user.id, + userName: user.name, + isActive: true + }) +}); +// Type: { mutationFn: (params) => Promise<{ userId: string, userName: string, isActive: boolean }> } +``` + +### 4. Advanced: Response Object + Custom Transformation + +```typescript +const advancedMutation = queryClient.mutation("post", "/users", { + withResponse: true, + selectFn: (response) => { + if (response.ok) { + return { + success: true, + user: response.data, + timestamp: new Date().toISOString() + }; + } else { + return { + success: false, + error: response.data, + statusCode: response.status + }; + } + } +}); +// Type: { mutationFn: (params) => Promise<{ success: boolean, user?: User, error?: ErrorType, statusCode?: number, timestamp?: string }> } +``` + +## Complete React Example + +```typescript +import { useMutation, useQueryClient } from '@tanstack/react-query'; +import { TanstackQueryApiClient } from './generated/tanstack-query-client'; + +function UserForm() { + const queryClient = useQueryClient(); + + // Mutation with error handling + const createUserMutation = queryClient.mutation("post", "/users", { + withResponse: true, + selectFn: (response) => ({ + success: response.ok, + user: response.ok ? response.data : null, + error: response.ok ? null : response.data, + statusCode: response.status + }) + }); + + const createUser = useMutation({ + ...createUserMutation.mutationOptions, + onSuccess: (result) => { + if (result.success) { + // Invalidate and refetch users list + queryClient.invalidateQueries(['users']); + toast.success(`User ${result.user.name} created successfully!`); + } else { + if (result.statusCode === 400) { + toast.error(`Validation error: ${result.error.message}`); + } else if (result.statusCode === 409) { + toast.error('A user with this email already exists'); + } else { + toast.error('An unexpected error occurred'); + } + } + }, + onError: (error) => { + // Type-safe error handling - error is a Response-like object with data property + console.log(error instanceof Response); // true + console.log(error.ok); // false + + if (error.status === 400) { + toast.error(`Validation failed: ${error.data.message}`); + } else if (error.status === 500) { + toast.error('Server error occurred'); + } else { + toast.error('Network error occurred'); + } + } + }); + + const handleSubmit = (userData: { name: string; email: string }) => { + createUser.mutate({ body: userData }); + }; + + return ( +
+ {/* form fields */} + +
+ ); +} +``` + +## Error Handling + +The TanStack Query client provides automatic error type inference based on your OpenAPI error schemas. For detailed examples, see [TANSTACK_QUERY_ERROR_HANDLING.md](./TANSTACK_QUERY_ERROR_HANDLING.md). + +Key features: +- **Type-safe errors**: Errors are typed as `{ status: number, data: ErrorSchemaType }` +- **Status code discrimination**: Different error types based on HTTP status codes +- **Full IDE support**: Autocomplete and type checking for error properties + +## Type Safety Benefits + +- **Full type inference**: All parameters and return types are automatically inferred +- **Error type discrimination**: Different error types based on status codes with full type safety +- **Response object access**: Headers, status, and other Response properties when needed +- **Custom transformations**: Type-safe data transformations with `selectFn` +- **Zero runtime overhead**: All type checking happens at compile time diff --git a/packages/typed-openapi/src/api-client-example.ts b/packages/typed-openapi/src/api-client-example.ts new file mode 100644 index 0000000..38e2667 --- /dev/null +++ b/packages/typed-openapi/src/api-client-example.ts @@ -0,0 +1,108 @@ +/** + * Generic API Client for typed-openapi generated code + * + * This is a simple, production-ready wrapper that you can copy and customize. + * It handles: + * - Path parameter replacement + * - Query parameter serialization + * - JSON request/response handling + * - Basic error handling + * + * Usage: + * 1. Replace './generated/api' with your actual generated file path + * 2. Set your API_BASE_URL + * 3. Customize error handling and headers as needed + */ + +// TODO: Replace with your generated API client imports +// import { type EndpointParameters, type Fetcher, createApiClient } from './generated/api'; + +// Basic configuration +const API_BASE_URL = process.env["API_BASE_URL"] || "https://api.example.com"; + +// Generic types for when you haven't imported the generated types yet +type EndpointParameters = { + body?: unknown; + query?: Record; + header?: Record; + path?: Record; +}; + +type Fetcher = (method: string, url: string, params?: EndpointParameters) => Promise; + +/** + * Simple fetcher implementation without external dependencies + */ +const fetcher: Fetcher = async (method, apiUrl, params) => { + const headers = new Headers(); + + // Replace path parameters (supports both {param} and :param formats) + const actualUrl = replacePathParams(apiUrl, (params?.path ?? {}) as Record); + const url = new URL(actualUrl); + + // Handle query parameters + if (params?.query) { + const searchParams = new URLSearchParams(); + Object.entries(params.query).forEach(([key, value]) => { + if (value != null) { + // Skip null/undefined values + if (Array.isArray(value)) { + value.forEach((val) => val != null && searchParams.append(key, String(val))); + } else { + searchParams.append(key, String(value)); + } + } + }); + url.search = searchParams.toString(); + } + + // Handle request body for mutation methods + const body = ["post", "put", "patch", "delete"].includes(method.toLowerCase()) + ? JSON.stringify(params?.body) + : undefined; + + if (body) { + headers.set("Content-Type", "application/json"); + } + + // Add custom headers + if (params?.header) { + Object.entries(params.header).forEach(([key, value]) => { + if (value != null) { + headers.set(key, String(value)); + } + }); + } + + const response = await fetch(url, { + method: method.toUpperCase(), + ...(body && { body }), + headers, + }); + + if (!response.ok) { + // You can customize error handling here + const error = new Error(`HTTP ${response.status}: ${response.statusText}`); + (error as any).response = response; + (error as any).status = response.status; + throw error; + } + + return response; +}; + +/** + * Replace path parameters in URL + * Supports both OpenAPI format {param} and Express format :param + */ +function replacePathParams(url: string, params: Record): string { + return url + .replace(/{(\w+)}/g, (_, key: string) => params[key] || `{${key}}`) + .replace(/:([a-zA-Z0-9_]+)/g, (_, key: string) => params[key] || `:${key}`); +} + +// TODO: Uncomment and replace with your generated createApiClient +// export const api = createApiClient(fetcher, API_BASE_URL); + +// Example of how to create the client once you have the generated code: +// export const api = createApiClient(fetcher, API_BASE_URL); diff --git a/packages/typed-openapi/src/api-client-with-validation.ts b/packages/typed-openapi/src/api-client-with-validation.ts new file mode 100644 index 0000000..f5dbbc7 --- /dev/null +++ b/packages/typed-openapi/src/api-client-with-validation.ts @@ -0,0 +1,184 @@ +/** + * Validating API Client for typed-openapi generated code + * + * This version includes input/output validation using the generated schemas. + * It validates: + * - Request body against schema before sending + * - Response data against schema after receiving + * - Provides type-safe error handling with schema validation errors + * + * Usage: + * 1. Replace './generated/api' with your actual generated file path + * 2. Set your API_BASE_URL + * 3. Choose your validation runtime (zod, yup, etc.) + * 4. Customize error handling as needed + */ + +// TODO: Replace with your generated API client imports +// import { type EndpointParameters, type Fetcher, createApiClient } from './generated/api'; + +// For validation - import your chosen runtime's types and schemas +// Example for Zod: +// import { z } from 'zod'; +// import { EndpointByMethod } from './generated/api'; + +// Basic configuration +const API_BASE_URL = process.env["API_BASE_URL"] || "https://api.example.com"; +const VALIDATE_REQUESTS = true; // Set to false to skip request validation +const VALIDATE_RESPONSES = true; // Set to false to skip response validation + +// Generic types for when you haven't imported the generated types yet +type EndpointParameters = { + body?: unknown; + query?: Record; + header?: Record; + path?: Record; +}; + +type Fetcher = (method: string, url: string, params?: EndpointParameters) => Promise; + +// Validation error class +class ValidationError extends Error { + constructor( + message: string, + public readonly type: "request" | "response", + public readonly validationErrors: unknown, + ) { + super(message); + this.name = "ValidationError"; + } +} + +/** + * Validating fetcher implementation + * + * This example shows the structure for validation. + * You'll need to adapt it based on your chosen validation library. + */ +const validatingFetcher: Fetcher = async (method, apiUrl, params) => { + const headers = new Headers(); + + // Replace path parameters (supports both {param} and :param formats) + const actualUrl = replacePathParams(apiUrl, (params?.path ?? {}) as Record); + const url = new URL(actualUrl); + + // Handle query parameters + if (params?.query) { + const searchParams = new URLSearchParams(); + Object.entries(params.query).forEach(([key, value]) => { + if (value != null) { + // Skip null/undefined values + if (Array.isArray(value)) { + value.forEach((val) => val != null && searchParams.append(key, String(val))); + } else { + searchParams.append(key, String(value)); + } + } + }); + url.search = searchParams.toString(); + } + + // Handle request body for mutation methods + let body: string | undefined; + if (["post", "put", "patch", "delete"].includes(method.toLowerCase()) && params?.body) { + // TODO: Add request validation here + if (VALIDATE_REQUESTS) { + try { + // Example for Zod validation: + // const endpoint = EndpointByMethod[method as keyof typeof EndpointByMethod]; + // const pathSchema = endpoint?.[actualUrl as keyof typeof endpoint]; + // if (pathSchema?.body) { + // pathSchema.body.parse(params.body); + // } + + // For now, just log that validation would happen here + console.debug("Request validation would happen here for:", method, actualUrl); + } catch (error) { + throw new ValidationError("Request body validation failed", "request", error); + } + } + + body = JSON.stringify(params.body); + headers.set("Content-Type", "application/json"); + } + + // Add custom headers + if (params?.header) { + Object.entries(params.header).forEach(([key, value]) => { + if (value != null) { + headers.set(key, String(value)); + } + }); + } + + const response = await fetch(url, { + method: method.toUpperCase(), + ...(body && { body }), + headers, + }); + + if (!response.ok) { + // You can customize error handling here + const error = new Error(`HTTP ${response.status}: ${response.statusText}`); + (error as any).response = response; + (error as any).status = response.status; + throw error; + } + + // TODO: Add response validation here + if (VALIDATE_RESPONSES) { + try { + // Clone response for validation (since response can only be read once) + const responseClone = response.clone(); + const responseData = await responseClone.json(); + + // Example for Zod validation: + // const endpoint = EndpointByMethod[method as keyof typeof EndpointByMethod]; + // const pathSchema = endpoint?.[actualUrl as keyof typeof endpoint]; + // const statusSchema = pathSchema?.responses?.[response.status as keyof typeof pathSchema.responses]; + // if (statusSchema) { + // statusSchema.parse(responseData); + // } + + // For now, just log that validation would happen here + console.debug("Response validation would happen here for:", method, actualUrl, response.status); + } catch (error) { + throw new ValidationError("Response validation failed", "response", error); + } + } + + return response; +}; + +/** + * Replace path parameters in URL + * Supports both OpenAPI format {param} and Express format :param + */ +function replacePathParams(url: string, params: Record): string { + return url + .replace(/{(\w+)}/g, (_, key: string) => params[key] || `{${key}}`) + .replace(/:([a-zA-Z0-9_]+)/g, (_, key: string) => params[key] || `:${key}`); +} + +// TODO: Uncomment and replace with your generated createApiClient +// export const api = createApiClient(validatingFetcher, API_BASE_URL); + +// Export the validation error for error handling +export { ValidationError }; + +// Example usage with error handling: +/* +try { + const result = await api.post('/users', { + body: { name: 'John', email: 'john@example.com' } + }); + const user = await result.json(); + console.log('Created user:', user); +} catch (error) { + if (error instanceof ValidationError) { + console.error(`${error.type} validation failed:`, error.validationErrors); + } else { + console.error('API error:', error); + } +} +*/ diff --git a/packages/typed-openapi/src/cli.ts b/packages/typed-openapi/src/cli.ts index a54be68..7ca8509 100644 --- a/packages/typed-openapi/src/cli.ts +++ b/packages/typed-openapi/src/cli.ts @@ -1,5 +1,4 @@ import { cac } from "cac"; - import { readFileSync } from "fs"; import { generateClientFiles } from "./generate-client-files.ts"; import { allowedRuntimes } from "./generator.ts"; @@ -11,16 +10,22 @@ cli .command("", "Generate") .option("-o, --output ", "Output path for the api client ts file (defaults to `..ts`)") .option( - "-r, --runtime ", + "-r, --runtime ", `Runtime to use for validation; defaults to \`none\`; available: ${allowedRuntimes.toString()}`, { default: "none" }, ) .option("--schemas-only", "Only generate schemas, skipping client generation (defaults to false)", { default: false }) + .option("--include-client", "Include API client types and implementation (defaults to true)", { default: true }) + .option( + "--success-status-codes ", + "Comma-separated list of success status codes (defaults to 2xx and 3xx ranges)", + ) + .option("--error-status-codes ", "Comma-separated list of error status codes (defaults to 4xx and 5xx ranges)") .option( "--tanstack [name]", "Generate tanstack client, defaults to false, can optionally specify a name for the generated file", ) - .action(async (input, _options) => { + .action(async (input: string, _options: any) => { return generateClientFiles(input, _options); }); diff --git a/packages/typed-openapi/src/generate-client-files.ts b/packages/typed-openapi/src/generate-client-files.ts index e8c8275..0900960 100644 --- a/packages/typed-openapi/src/generate-client-files.ts +++ b/packages/typed-openapi/src/generate-client-files.ts @@ -3,7 +3,12 @@ import type { OpenAPIObject } from "openapi3-ts/oas31"; import { basename, join, dirname } from "pathe"; import { type } from "arktype"; import { mkdir, writeFile } from "fs/promises"; -import { allowedRuntimes, generateFile } from "./generator.ts"; +import { + allowedRuntimes, + generateFile, + DEFAULT_SUCCESS_STATUS_CODES, + DEFAULT_ERROR_STATUS_CODES, +} from "./generator.ts"; import { mapOpenApiEndpoints } from "./map-openapi-endpoints.ts"; import { generateTanstackQueryFile } from "./tanstack-query.generator.ts"; import { prettify } from "./format.ts"; @@ -24,6 +29,9 @@ export const optionsSchema = type({ runtime: allowedRuntimes, tanstack: "boolean | string", schemasOnly: "boolean", + "includeClient?": "boolean | 'true' | 'false'", + "successStatusCodes?": "string", + "errorStatusCodes?": "string", }); export async function generateClientFiles(input: string, options: typeof optionsSchema.infer) { @@ -32,13 +40,30 @@ export async function generateClientFiles(input: string, options: typeof options const ctx = mapOpenApiEndpoints(openApiDoc); console.log(`Found ${ctx.endpointList.length} endpoints`); - const content = await prettify( - generateFile({ - ...ctx, - runtime: options.runtime, - schemasOnly: options.schemasOnly, - }), - ); + // Parse success status codes if provided + const successStatusCodes = options.successStatusCodes + ? (options.successStatusCodes.split(",").map((code) => parseInt(code.trim(), 10)) as readonly number[]) + : undefined; + + // Parse error status codes if provided + const errorStatusCodes = options.errorStatusCodes + ? (options.errorStatusCodes.split(",").map((code) => parseInt(code.trim(), 10)) as readonly number[]) + : undefined; + + // Convert string boolean to actual boolean + const includeClient = + options.includeClient === "false" ? false : options.includeClient === "true" ? true : options.includeClient; + + const generatorOptions = { + ...ctx, + runtime: options.runtime, + schemasOnly: options.schemasOnly, + includeClient: includeClient ?? true, + successStatusCodes: successStatusCodes ?? DEFAULT_SUCCESS_STATUS_CODES, + errorStatusCodes: errorStatusCodes ?? DEFAULT_ERROR_STATUS_CODES, + }; + + const content = await prettify(generateFile(generatorOptions)); const outputPath = join( cwd, options.output ?? input + `.${options.runtime === "none" ? "client" : options.runtime}.ts`, @@ -50,7 +75,7 @@ export async function generateClientFiles(input: string, options: typeof options if (options.tanstack) { const tanstackContent = await generateTanstackQueryFile({ - ...ctx, + ...generatorOptions, relativeApiClientPath: "./" + basename(outputPath), }); const tanstackOutputPath = join( diff --git a/packages/typed-openapi/src/generator.ts b/packages/typed-openapi/src/generator.ts index 7b1ede8..9491435 100644 --- a/packages/typed-openapi/src/generator.ts +++ b/packages/typed-openapi/src/generator.ts @@ -7,9 +7,25 @@ import { match } from "ts-pattern"; import { type } from "arktype"; import { wrapWithQuotesIfNeeded } from "./string-utils.ts"; +// Default success status codes (2xx and 3xx ranges) +export const DEFAULT_SUCCESS_STATUS_CODES = [ + 200, 201, 202, 203, 204, 205, 206, 207, 208, 226, 300, 301, 302, 303, 304, 305, 306, 307, 308, +] as const; + +// Default error status codes (4xx and 5xx ranges) +export const DEFAULT_ERROR_STATUS_CODES = [ + 400, 401, 402, 403, 404, 405, 406, 407, 408, 409, 410, 411, 412, 413, 414, 415, 416, 417, 418, 421, 422, 423, 424, + 425, 426, 428, 429, 431, 451, 500, 501, 502, 503, 504, 505, 506, 507, 508, 510, 511, +] as const; + +export type ErrorStatusCode = (typeof DEFAULT_ERROR_STATUS_CODES)[number]; + type GeneratorOptions = ReturnType & { runtime?: "none" | keyof typeof runtimeValidationGenerator; schemasOnly?: boolean; + successStatusCodes?: readonly number[]; + errorStatusCodes?: readonly number[]; + includeClient?: boolean; }; type GeneratorContext = Required; @@ -60,11 +76,18 @@ const replacerByRuntime = { }; export const generateFile = (options: GeneratorOptions) => { - const ctx = { ...options, runtime: options.runtime ?? "none" } as GeneratorContext; + const ctx = { + ...options, + runtime: options.runtime ?? "none", + successStatusCodes: options.successStatusCodes ?? DEFAULT_SUCCESS_STATUS_CODES, + errorStatusCodes: options.errorStatusCodes ?? DEFAULT_ERROR_STATUS_CODES, + includeClient: options.includeClient ?? true, + } as GeneratorContext; const schemaList = generateSchemaList(ctx); const endpointSchemaList = options.schemasOnly ? "" : generateEndpointSchemaList(ctx); - const apiClient = options.schemasOnly ? "" : generateApiClient(ctx); + const endpointByMethod = options.schemasOnly ? "" : generateEndpointByMethod(ctx); + const apiClient = options.schemasOnly || !ctx.includeClient ? "" : generateApiClient(ctx); const transform = ctx.runtime === "none" @@ -96,6 +119,7 @@ export const generateFile = (options: GeneratorOptions) => { const file = ` ${transform(schemaList + endpointSchemaList)} + ${endpointByMethod} ${apiClient} `; @@ -151,6 +175,24 @@ const responseHeadersObjectToString = (responseHeaders: Record, return str + "}"; }; +const generateResponsesObject = (responses: Record, ctx: GeneratorContext) => { + let str = "{"; + for (const [statusCode, responseType] of Object.entries(responses)) { + const value = + ctx.runtime === "none" + ? responseType.recompute((box) => { + if (Box.isReference(box) && !box.params.generics && box.value !== "null") { + box.value = `Schemas.${box.value}`; + } + + return box; + }).value + : responseType.value; + str += `${wrapWithQuotesIfNeeded(statusCode)}: ${value},\n`; + } + return str + "}"; +}; + const generateEndpointSchemaList = (ctx: GeneratorContext) => { let file = ` ${ctx.runtime === "none" ? "export namespace Endpoints {" : ""} @@ -197,6 +239,7 @@ const generateEndpointSchemaList = (ctx: GeneratorContext) => { }).value : endpoint.response.value }, + ${endpoint.responses ? `responses: ${generateResponsesObject(endpoint.responses, ctx)},` : ""} ${ endpoint.responseHeaders ? `responseHeaders: ${responseHeadersObjectToString(endpoint.responseHeaders, ctx)},` @@ -225,9 +268,11 @@ const generateEndpointByMethod = (ctx: GeneratorContext) => { ${Object.entries(byMethods) .map(([method, list]) => { return `${method}: { - ${list.map( - (endpoint) => `"${endpoint.path}": ${ctx.runtime === "none" ? "Endpoints." : ""}${endpoint.meta.alias}`, - )} + ${list + .map( + (endpoint) => `"${endpoint.path}": ${ctx.runtime === "none" ? "Endpoints." : ""}${endpoint.meta.alias}`, + ) + .join(",\n")} }`; }) .join(",\n")} @@ -249,9 +294,19 @@ const generateEndpointByMethod = (ctx: GeneratorContext) => { }; const generateApiClient = (ctx: GeneratorContext) => { + if (!ctx.includeClient) { + return ""; + } + const { endpointList } = ctx; const byMethods = groupBy(endpointList, "method"); - const endpointSchemaList = generateEndpointByMethod(ctx); + + // Generate the StatusCode type from the configured success status codes + const generateStatusCodeType = (statusCodes: readonly number[]) => { + return statusCodes.join(" | "); + }; + + const statusCodeType = generateStatusCodeType(ctx.successStatusCodes); const apiClientTypes = ` // @@ -270,6 +325,7 @@ type RequestFormat = "json" | "form-data" | "form-url" | "binary" | "text"; export type DefaultEndpoint = { parameters?: EndpointParameters | undefined; response: unknown; + responses?: Record; responseHeaders?: Record; }; @@ -285,11 +341,76 @@ export type Endpoint = { areParametersRequired: boolean; }; response: TConfig["response"]; + responses?: TConfig["responses"]; responseHeaders?: TConfig["responseHeaders"] }; export type Fetcher = (method: Method, url: string, parameters?: EndpointParameters | undefined) => Promise; +// Status code type for success responses +export type SuccessStatusCode = ${statusCodeType}; + +// Error handling types +export type TypedApiResponse = {}> = + (keyof TAllResponses extends never + ? Omit & { + ok: true; + status: number; + data: TSuccess; + json: () => Promise; + } + : { + [K in keyof TAllResponses]: K extends string + ? K extends \`\${infer TStatusCode extends number}\` + ? TStatusCode extends SuccessStatusCode + ? Omit & { + ok: true; + status: TStatusCode; + data: TSuccess; + json: () => Promise; + } + : Omit & { + ok: false; + status: TStatusCode; + data: TAllResponses[K]; + json: () => Promise; + } + : never + : K extends number + ? K extends SuccessStatusCode + ? Omit & { + ok: true; + status: K; + data: TSuccess; + json: () => Promise; + } + : Omit & { + ok: false; + status: K; + data: TAllResponses[K]; + json: () => Promise; + } + : never; + }[keyof TAllResponses]); + +export type SafeApiResponse = TEndpoint extends { response: infer TSuccess; responses: infer TResponses } + ? TResponses extends Record + ? TypedApiResponse + : Omit & { + ok: true; + status: number; + data: TSuccess; + json: () => Promise; + } + : TEndpoint extends { response: infer TSuccess } + ? Omit & { + ok: true; + status: number; + data: TSuccess; + json: () => Promise; + } + : never; + type RequiredKeys = { [P in keyof T]-?: undefined extends T[P] ? never : P; }[keyof T]; @@ -331,16 +452,58 @@ export class ApiClient { ...params: MaybeOptionalArg<${match(ctx.runtime) .with("zod", "yup", () => infer(`TEndpoint["parameters"]`)) .with("arktype", "io-ts", "typebox", "valibot", () => infer(`TEndpoint`) + `["parameters"]`) - .otherwise(() => `TEndpoint["parameters"]`)}> + .otherwise(() => `TEndpoint["parameters"]`)} & { withResponse?: false }> ): Promise<${match(ctx.runtime) .with("zod", "yup", () => infer(`TEndpoint["response"]`)) .with("arktype", "io-ts", "typebox", "valibot", () => infer(`TEndpoint`) + `["response"]`) - .otherwise(() => `TEndpoint["response"]`)}> { - return this.fetcher("${method}", this.baseUrl + path, params[0]) - .then(response => this.parseResponse(response))${match(ctx.runtime) - .with("zod", "yup", () => `as Promise<${infer(`TEndpoint["response"]`)}>`) - .with("arktype", "io-ts", "typebox", "valibot", () => `as Promise<${infer(`TEndpoint`) + `["response"]`}>`) - .otherwise(() => `as Promise`)}; + .otherwise(() => `TEndpoint["response"]`)}>; + + ${method}( + path: Path, + ...params: MaybeOptionalArg<${match(ctx.runtime) + .with("zod", "yup", () => infer(`TEndpoint["parameters"]`)) + .with("arktype", "io-ts", "typebox", "valibot", () => infer(`TEndpoint`) + `["parameters"]`) + .otherwise(() => `TEndpoint["parameters"]`)} & { withResponse: true }> + ): Promise>; + + ${method}( + path: Path, + ...params: MaybeOptionalArg + ): Promise { + const requestParams = params[0]; + const withResponse = requestParams?.withResponse; + + // Remove withResponse from params before passing to fetcher + const { withResponse: _, ...fetchParams } = requestParams || {}; + + if (withResponse) { + return this.fetcher("${method}", this.baseUrl + path, Object.keys(fetchParams).length ? fetchParams : undefined) + .then(async (response) => { + // Parse the response data + const data = await this.parseResponse(response); + + // Override properties while keeping the original Response object + const typedResponse = Object.assign(response, { + ok: response.ok, + status: response.status, + data: data, + json: () => Promise.resolve(data) + }); + return typedResponse; + }); + } else { + return this.fetcher("${method}", this.baseUrl + path, requestParams) + .then(response => this.parseResponse(response))${match(ctx.runtime) + .with("zod", "yup", () => `as Promise<${infer(`TEndpoint["response"]`)}>`) + .with( + "arktype", + "io-ts", + "typebox", + "valibot", + () => `as Promise<${infer(`TEndpoint`) + `["response"]`}>`, + ) + .otherwise(() => `as Promise`)}; + } } // ` @@ -393,10 +556,25 @@ export function createApiClient(fetcher: Fetcher, baseUrl?: string) { api.get("/users").then((users) => console.log(users)); api.post("/users", { body: { name: "John" } }).then((user) => console.log(user)); api.put("/users/:id", { path: { id: 1 }, body: { name: "John" } }).then((user) => console.log(user)); + + // With error handling + const result = await api.get("/users/{id}", { path: { id: "123" }, withResponse: true }); + if (result.ok) { + // Access data directly + const user = result.data; + console.log(user); + + // Or use the json() method for compatibility + const userFromJson = await result.json(); + console.log(userFromJson); + } else { + const error = result.data; + console.error(\`Error \${result.status}:\`, error); + } */ // { // Match the first 2xx-3xx response found, or fallback to default one otherwise let responseObject: ResponseObject | undefined; + const allResponses: Record = {}; + Object.entries(operation.responses ?? {}).map(([status, responseOrRef]) => { const statusCode = Number(status); - if (statusCode >= 200 && statusCode < 300) { - responseObject = refs.unwrap(responseOrRef); + const responseObj = refs.unwrap(responseOrRef); + + // Collect all responses for error handling + const content = responseObj?.content; + if (content) { + const matchingMediaType = Object.keys(content).find(isResponseMediaType); + if (matchingMediaType && content[matchingMediaType]) { + allResponses[status] = openApiSchemaToTs({ + schema: content[matchingMediaType]?.schema ?? {}, + ctx, + }); + } else { + // If no JSON content, use unknown type + allResponses[status] = openApiSchemaToTs({ schema: {}, ctx }); + } + } else { + // If no content defined, use unknown type + allResponses[status] = openApiSchemaToTs({ schema: {}, ctx }); + } + + // Keep the current logic for the main response (first 2xx-3xx) + if (statusCode >= 200 && statusCode < 300 && !responseObject) { + responseObject = responseObj; } }); + if (!responseObject && operation.responses?.default) { responseObject = refs.unwrap(operation.responses.default); + // Also add default to all responses if not already covered + if (!allResponses["default"]) { + const content = responseObject?.content; + if (content) { + const matchingMediaType = Object.keys(content).find(isResponseMediaType); + if (matchingMediaType && content[matchingMediaType]) { + allResponses["default"] = openApiSchemaToTs({ + schema: content[matchingMediaType]?.schema ?? {}, + ctx, + }); + } + } + } + } + + // Set the responses collection + if (Object.keys(allResponses).length > 0) { + endpoint.responses = allResponses; } const content = responseObject?.content; @@ -197,6 +239,7 @@ type RequestFormat = "json" | "form-data" | "form-url" | "binary" | "text"; type DefaultEndpoint = { parameters?: EndpointParameters | undefined; response: AnyBox; + responses?: Record; responseHeaders?: Record; }; @@ -212,5 +255,6 @@ export type Endpoint = { areParametersRequired: boolean; }; response: TConfig["response"]; + responses?: TConfig["responses"]; responseHeaders?: TConfig["responseHeaders"]; }; diff --git a/packages/typed-openapi/src/tanstack-query.generator.ts b/packages/typed-openapi/src/tanstack-query.generator.ts index 8c1436a..bdb930f 100644 --- a/packages/typed-openapi/src/tanstack-query.generator.ts +++ b/packages/typed-openapi/src/tanstack-query.generator.ts @@ -2,15 +2,28 @@ import { capitalize } from "pastable/server"; import { prettify } from "./format.ts"; import type { mapOpenApiEndpoints } from "./map-openapi-endpoints.ts"; +// Default error status codes (4xx and 5xx ranges) +export const DEFAULT_ERROR_STATUS_CODES = [ + 400, 401, 402, 403, 404, 405, 406, 407, 408, 409, 410, 411, 412, 413, 414, 415, 416, 417, 418, 421, 422, 423, 424, + 425, 426, 428, 429, 431, 451, 500, 501, 502, 503, 504, 505, 506, 507, 508, 510, 511, +] as const; + +export type ErrorStatusCode = (typeof DEFAULT_ERROR_STATUS_CODES)[number]; + type GeneratorOptions = ReturnType; -type GeneratorContext = Required; +type GeneratorContext = Required & { + errorStatusCodes?: readonly number[]; +}; export const generateTanstackQueryFile = async (ctx: GeneratorContext & { relativeApiClientPath: string }) => { const endpointMethods = new Set(ctx.endpointList.map((endpoint) => endpoint.method.toLowerCase())); + // Use configured error status codes or default + const errorStatusCodes = ctx.errorStatusCodes ?? DEFAULT_ERROR_STATUS_CODES; + const file = ` import { queryOptions } from "@tanstack/react-query" - import type { EndpointByMethod, ApiClient } from "${ctx.relativeApiClientPath}" + import type { EndpointByMethod, ApiClient, SafeApiResponse } from "${ctx.relativeApiClientPath}" type EndpointQueryKey = [ TOptions & { @@ -63,6 +76,8 @@ export const generateTanstackQueryFile = async (ctx: GeneratorContext & { relati type MaybeOptionalArg = RequiredKeys extends never ? [config?: T] : [config: T]; + type ErrorStatusCode = ${errorStatusCodes.join(" | ")}; + // // @@ -77,18 +92,20 @@ export const generateTanstackQueryFile = async (ctx: GeneratorContext & { relati path: Path, ...params: MaybeOptionalArg ) { - const queryKey = createQueryKey(path, params[0]); + const queryKey = createQueryKey(path as string, params[0]); const query = { /** type-only property if you need easy access to the endpoint params */ "~endpoint": {} as TEndpoint, queryKey, queryOptions: queryOptions({ queryFn: async ({ queryKey, signal, }) => { - const res = await this.client.${method}(path, { - ...params, - ...queryKey[0], + const requestParams = { + ...(params[0] || {}), + ...(queryKey[0] || {}), signal, - }); + withResponse: false as const + }; + const res = await this.client.${method}(path, requestParams); return res as TEndpoint["response"]; }, queryKey: queryKey @@ -96,11 +113,13 @@ export const generateTanstackQueryFile = async (ctx: GeneratorContext & { relati mutationOptions: { mutationKey: queryKey, mutationFn: async (localOptions: TEndpoint extends { parameters: infer Parameters} ? Parameters: never) => { - const res = await this.client.${method}(path, { - ...params, - ...queryKey[0], - ...localOptions, - }); + const requestParams = { + ...(params[0] || {}), + ...(queryKey[0] || {}), + ...(localOptions || {}), + withResponse: false as const + }; + const res = await this.client.${method}(path, requestParams); return res as TEndpoint["response"]; } } @@ -121,11 +140,34 @@ export const generateTanstackQueryFile = async (ctx: GeneratorContext & { relati TMethod extends keyof EndpointByMethod, TPath extends keyof EndpointByMethod[TMethod], TEndpoint extends EndpointByMethod[TMethod][TPath], - TSelection, - >(method: TMethod, path: TPath, selectFn?: (res: Omit & { - /** [MDN Reference](https://developer.mozilla.org/docs/Web/API/Request/json) */ - json: () => Promise; - }) => TSelection) { + TWithResponse extends boolean = false, + TSelection = TWithResponse extends true + ? SafeApiResponse + : TEndpoint extends { response: infer Res } ? Res : never, + TError = TEndpoint extends { responses: infer TResponses } + ? TResponses extends Record + ? { + [K in keyof TResponses]: K extends string + ? K extends \`\${infer TStatusCode extends number}\` + ? TStatusCode extends ErrorStatusCode + ? Omit & { status: TStatusCode; data: TResponses[K] } + : never + : never + : K extends number + ? K extends ErrorStatusCode + ? Omit & { status: K; data: TResponses[K] } + : never + : never; + }[keyof TResponses] + : Error + : Error + >(method: TMethod, path: TPath, options?: { + withResponse?: TWithResponse; + selectFn?: (res: TWithResponse extends true + ? SafeApiResponse + : TEndpoint extends { response: infer Res } ? Res : never + ) => TSelection; + }) { const mutationKey = [{ method, path }] as const; return { /** type-only property if you need easy access to the endpoint params */ @@ -133,14 +175,45 @@ export const generateTanstackQueryFile = async (ctx: GeneratorContext & { relati mutationKey: mutationKey, mutationOptions: { mutationKey: mutationKey, - mutationFn: async (params: TEndpoint extends { parameters: infer Parameters } ? Parameters : never) => { - const response = await this.client.request(method, path, params); - const res = selectFn ? selectFn(response) : response - return res as unknown extends TSelection ? typeof response : Awaited - }, - }, - }; + mutationFn: async (params: TEndpoint extends { parameters: infer Parameters } ? Parameters : never): Promise => { + const withResponse = options?.withResponse ?? false; + const selectFn = options?.selectFn; + + if (withResponse) { + // Type assertion is safe because we're handling the method dynamically + const response = await (this.client as any)[method](path, { ...params as any, withResponse: true }); + if (!response.ok) { + // Create a Response-like error object with additional data property + const error = Object.assign(Object.create(Response.prototype), { + ...response, + data: response.data + }) as TError; + throw error; + } + const res = selectFn ? selectFn(response as any) : response; + return res as TSelection; + } + + // Type assertion is safe because we're handling the method dynamically + // Always get the full response for error handling, even when withResponse is false + const response = await (this.client as any)[method](path, { ...params as any, withResponse: true }); + if (!response.ok) { + // Create a Response-like error object with additional data property + const error = Object.assign(Object.create(Response.prototype), { + ...response, + data: response.data + }) as TError; + throw error; + } + + // Return just the data if withResponse is false, otherwise return the full response + const finalResponse = withResponse ? response : response.data; + const res = selectFn ? selectFn(finalResponse as any) : finalResponse; + return res as TSelection; + } + } as import("@tanstack/react-query").UseMutationOptions, } + } // } `; diff --git a/packages/typed-openapi/tests/configurable-status-codes.test.ts b/packages/typed-openapi/tests/configurable-status-codes.test.ts new file mode 100644 index 0000000..58200ae --- /dev/null +++ b/packages/typed-openapi/tests/configurable-status-codes.test.ts @@ -0,0 +1,69 @@ +import { it, expect } from "vitest"; +import type { OpenAPIObject } from "openapi3-ts/oas31"; + +import { generateFile } from "../src/generator.ts"; +import { mapOpenApiEndpoints } from "../src/map-openapi-endpoints.ts"; +import { prettify } from "../src/format.ts"; + +it("should use custom success status codes", async () => { + const openApiDoc: OpenAPIObject = { + openapi: "3.0.0", + info: { title: "Test API", version: "1.0.0" }, + paths: { + "/test": { + get: { + operationId: "getTest", + responses: { + 200: { + description: "Success", + content: { + "application/json": { + schema: { type: "object", properties: { message: { type: "string" } } }, + }, + }, + }, + 201: { + description: "Created", + content: { + "application/json": { + schema: { type: "object", properties: { id: { type: "string" } } }, + }, + }, + }, + 400: { + description: "Bad Request", + content: { + "application/json": { + schema: { type: "object", properties: { error: { type: "string" } } }, + }, + }, + }, + }, + }, + }, + }, + }; + + const endpoints = mapOpenApiEndpoints(openApiDoc); + + // Test with default success status codes (should include 200 and 201) + const defaultGenerated = await prettify(generateFile(endpoints)); + expect(defaultGenerated).toContain("export type SuccessStatusCode ="); + expect(defaultGenerated).toContain("| 200"); + expect(defaultGenerated).toContain("| 201"); + + // Test with custom success status codes (only 200) + const customGenerated = await prettify( + generateFile({ + ...endpoints, + successStatusCodes: [200] as const, + }), + ); + + // Should only contain 200 in the StatusCode type + expect(customGenerated).toContain("export type SuccessStatusCode = 200;"); + expect(customGenerated).not.toContain("| 201"); + + // The ApiResponse type should use the custom StatusCode + expect(customGenerated).toContain("TStatusCode extends SuccessStatusCode"); +}); diff --git a/packages/typed-openapi/tests/generator.test.ts b/packages/typed-openapi/tests/generator.test.ts index b21f687..67b465f 100644 --- a/packages/typed-openapi/tests/generator.test.ts +++ b/packages/typed-openapi/tests/generator.test.ts @@ -57,6 +57,7 @@ describe("generator", () => { body: Schemas.Pet; }; response: Schemas.Pet; + responses: { 200: Schemas.Pet; 400: unknown; 404: unknown; 405: unknown }; }; export type post_AddPet = { method: "POST"; @@ -66,6 +67,7 @@ describe("generator", () => { body: Schemas.Pet; }; response: Schemas.Pet; + responses: { 200: Schemas.Pet; 405: unknown }; }; export type get_FindPetsByStatus = { method: "GET"; @@ -75,6 +77,7 @@ describe("generator", () => { query: Partial<{ status: "available" | "pending" | "sold" }>; }; response: Array; + responses: { 200: Array; 400: { code: number; message: string } }; }; export type get_FindPetsByTags = { method: "GET"; @@ -84,6 +87,7 @@ describe("generator", () => { query: Partial<{ tags: Array }>; }; response: Array; + responses: { 200: Array; 400: unknown }; }; export type get_GetPetById = { method: "GET"; @@ -93,6 +97,7 @@ describe("generator", () => { path: { petId: number }; }; response: Schemas.Pet; + responses: { 200: Schemas.Pet; 400: { code: number; message: string }; 404: { code: number; message: string } }; }; export type post_UpdatePetWithForm = { method: "POST"; @@ -103,6 +108,7 @@ describe("generator", () => { path: { petId: number }; }; response: unknown; + responses: { 405: unknown }; }; export type delete_DeletePet = { method: "DELETE"; @@ -113,6 +119,7 @@ describe("generator", () => { header: Partial<{ api_key: string }>; }; response: unknown; + responses: { 400: unknown }; }; export type post_UploadFile = { method: "POST"; @@ -125,6 +132,7 @@ describe("generator", () => { body: string; }; response: Schemas.ApiResponse; + responses: { 200: Schemas.ApiResponse }; }; export type get_GetInventory = { method: "GET"; @@ -132,6 +140,7 @@ describe("generator", () => { requestFormat: "json"; parameters: never; response: Record; + responses: { 200: Record }; }; export type post_PlaceOrder = { method: "POST"; @@ -141,6 +150,7 @@ describe("generator", () => { body: Schemas.Order; }; response: Schemas.Order; + responses: { 200: Schemas.Order; 405: unknown }; }; export type get_GetOrderById = { method: "GET"; @@ -150,6 +160,7 @@ describe("generator", () => { path: { orderId: number }; }; response: Schemas.Order; + responses: { 200: Schemas.Order; 400: unknown; 404: unknown }; }; export type delete_DeleteOrder = { method: "DELETE"; @@ -159,6 +170,7 @@ describe("generator", () => { path: { orderId: number }; }; response: unknown; + responses: { 400: unknown; 404: unknown }; }; export type post_CreateUser = { method: "POST"; @@ -168,6 +180,7 @@ describe("generator", () => { body: Schemas.User; }; response: Schemas.User; + responses: { default: Schemas.User }; }; export type post_CreateUsersWithListInput = { method: "POST"; @@ -177,6 +190,7 @@ describe("generator", () => { body: Array; }; response: Schemas.User; + responses: { 200: Schemas.User; default: unknown }; }; export type get_LoginUser = { method: "GET"; @@ -186,6 +200,7 @@ describe("generator", () => { query: Partial<{ username: string; password: string }>; }; response: string; + responses: { 200: string; 400: unknown }; responseHeaders: { "x-rate-limit": number; "x-expires-after": string }; }; export type get_LogoutUser = { @@ -194,6 +209,7 @@ describe("generator", () => { requestFormat: "json"; parameters: never; response: unknown; + responses: { default: unknown }; }; export type get_GetUserByName = { method: "GET"; @@ -203,6 +219,7 @@ describe("generator", () => { path: { username: string }; }; response: Schemas.User; + responses: { 200: Schemas.User; 400: unknown; 404: unknown }; }; export type put_UpdateUser = { method: "PUT"; @@ -214,6 +231,7 @@ describe("generator", () => { body: Schemas.User; }; response: unknown; + responses: { default: unknown }; }; export type delete_DeleteUser = { method: "DELETE"; @@ -223,6 +241,7 @@ describe("generator", () => { path: { username: string }; }; response: unknown; + responses: { 400: unknown; 404: unknown }; }; // @@ -284,6 +303,7 @@ describe("generator", () => { export type DefaultEndpoint = { parameters?: EndpointParameters | undefined; response: unknown; + responses?: Record; responseHeaders?: Record; }; @@ -299,11 +319,97 @@ describe("generator", () => { areParametersRequired: boolean; }; response: TConfig["response"]; + responses?: TConfig["responses"]; responseHeaders?: TConfig["responseHeaders"]; }; export type Fetcher = (method: Method, url: string, parameters?: EndpointParameters | undefined) => Promise; + // Status code type for success responses + export type SuccessStatusCode = + | 200 + | 201 + | 202 + | 203 + | 204 + | 205 + | 206 + | 207 + | 208 + | 226 + | 300 + | 301 + | 302 + | 303 + | 304 + | 305 + | 306 + | 307 + | 308; + + // Error handling types + export type TypedApiResponse< + TSuccess, + TAllResponses extends Record = {}, + > = keyof TAllResponses extends never + ? Omit & { + ok: true; + status: number; + data: TSuccess; + json: () => Promise; + } + : { + [K in keyof TAllResponses]: K extends string + ? K extends \`\${infer TStatusCode extends number}\` + ? TStatusCode extends SuccessStatusCode + ? Omit & { + ok: true; + status: TStatusCode; + data: TSuccess; + json: () => Promise; + } + : Omit & { + ok: false; + status: TStatusCode; + data: TAllResponses[K]; + json: () => Promise; + } + : never + : K extends number + ? K extends SuccessStatusCode + ? Omit & { + ok: true; + status: K; + data: TSuccess; + json: () => Promise; + } + : Omit & { + ok: false; + status: K; + data: TAllResponses[K]; + json: () => Promise; + } + : never; + }[keyof TAllResponses]; + + export type SafeApiResponse = TEndpoint extends { response: infer TSuccess; responses: infer TResponses } + ? TResponses extends Record + ? TypedApiResponse + : Omit & { + ok: true; + status: number; + data: TSuccess; + json: () => Promise; + } + : TEndpoint extends { response: infer TSuccess } + ? Omit & { + ok: true; + status: number; + data: TSuccess; + json: () => Promise; + } + : never; + type RequiredKeys = { [P in keyof T]-?: undefined extends T[P] ? never : P; }[keyof T]; @@ -334,44 +440,182 @@ describe("generator", () => { // put( path: Path, - ...params: MaybeOptionalArg - ): Promise { - return this.fetcher("put", this.baseUrl + path, params[0]).then((response) => - this.parseResponse(response), - ) as Promise; + ...params: MaybeOptionalArg + ): Promise; + + put( + path: Path, + ...params: MaybeOptionalArg + ): Promise>; + + put( + path: Path, + ...params: MaybeOptionalArg + ): Promise { + const requestParams = params[0]; + const withResponse = requestParams?.withResponse; + + // Remove withResponse from params before passing to fetcher + const { withResponse: _, ...fetchParams } = requestParams || {}; + + if (withResponse) { + return this.fetcher("put", this.baseUrl + path, Object.keys(fetchParams).length ? fetchParams : undefined).then( + async (response) => { + // Parse the response data + const data = await this.parseResponse(response); + + // Override properties while keeping the original Response object + const typedResponse = Object.assign(response, { + ok: response.ok, + status: response.status, + data: data, + json: () => Promise.resolve(data), + }); + return typedResponse; + }, + ); + } else { + return this.fetcher("put", this.baseUrl + path, requestParams).then((response) => + this.parseResponse(response), + ) as Promise; + } } // // post( path: Path, - ...params: MaybeOptionalArg - ): Promise { - return this.fetcher("post", this.baseUrl + path, params[0]).then((response) => - this.parseResponse(response), - ) as Promise; + ...params: MaybeOptionalArg + ): Promise; + + post( + path: Path, + ...params: MaybeOptionalArg + ): Promise>; + + post( + path: Path, + ...params: MaybeOptionalArg + ): Promise { + const requestParams = params[0]; + const withResponse = requestParams?.withResponse; + + // Remove withResponse from params before passing to fetcher + const { withResponse: _, ...fetchParams } = requestParams || {}; + + if (withResponse) { + return this.fetcher("post", this.baseUrl + path, Object.keys(fetchParams).length ? fetchParams : undefined).then( + async (response) => { + // Parse the response data + const data = await this.parseResponse(response); + + // Override properties while keeping the original Response object + const typedResponse = Object.assign(response, { + ok: response.ok, + status: response.status, + data: data, + json: () => Promise.resolve(data), + }); + return typedResponse; + }, + ); + } else { + return this.fetcher("post", this.baseUrl + path, requestParams).then((response) => + this.parseResponse(response), + ) as Promise; + } } // // get( path: Path, - ...params: MaybeOptionalArg - ): Promise { - return this.fetcher("get", this.baseUrl + path, params[0]).then((response) => - this.parseResponse(response), - ) as Promise; + ...params: MaybeOptionalArg + ): Promise; + + get( + path: Path, + ...params: MaybeOptionalArg + ): Promise>; + + get( + path: Path, + ...params: MaybeOptionalArg + ): Promise { + const requestParams = params[0]; + const withResponse = requestParams?.withResponse; + + // Remove withResponse from params before passing to fetcher + const { withResponse: _, ...fetchParams } = requestParams || {}; + + if (withResponse) { + return this.fetcher("get", this.baseUrl + path, Object.keys(fetchParams).length ? fetchParams : undefined).then( + async (response) => { + // Parse the response data + const data = await this.parseResponse(response); + + // Override properties while keeping the original Response object + const typedResponse = Object.assign(response, { + ok: response.ok, + status: response.status, + data: data, + json: () => Promise.resolve(data), + }); + return typedResponse; + }, + ); + } else { + return this.fetcher("get", this.baseUrl + path, requestParams).then((response) => + this.parseResponse(response), + ) as Promise; + } } // // delete( path: Path, - ...params: MaybeOptionalArg - ): Promise { - return this.fetcher("delete", this.baseUrl + path, params[0]).then((response) => - this.parseResponse(response), - ) as Promise; + ...params: MaybeOptionalArg + ): Promise; + + delete( + path: Path, + ...params: MaybeOptionalArg + ): Promise>; + + delete( + path: Path, + ...params: MaybeOptionalArg + ): Promise { + const requestParams = params[0]; + const withResponse = requestParams?.withResponse; + + // Remove withResponse from params before passing to fetcher + const { withResponse: _, ...fetchParams } = requestParams || {}; + + if (withResponse) { + return this.fetcher( + "delete", + this.baseUrl + path, + Object.keys(fetchParams).length ? fetchParams : undefined, + ).then(async (response) => { + // Parse the response data + const data = await this.parseResponse(response); + + // Override properties while keeping the original Response object + const typedResponse = Object.assign(response, { + ok: response.ok, + status: response.status, + data: data, + json: () => Promise.resolve(data), + }); + return typedResponse; + }); + } else { + return this.fetcher("delete", this.baseUrl + path, requestParams).then((response) => + this.parseResponse(response), + ) as Promise; + } } // @@ -410,6 +654,21 @@ describe("generator", () => { api.get("/users").then((users) => console.log(users)); api.post("/users", { body: { name: "John" } }).then((user) => console.log(user)); api.put("/users/:id", { path: { id: 1 }, body: { name: "John" } }).then((user) => console.log(user)); + + // With error handling + const result = await api.get("/users/{id}", { path: { id: "123" }, withResponse: true }); + if (result.ok) { + // Access data directly + const user = result.data; + console.log(user); + + // Or use the json() method for compatibility + const userFromJson = await result.json(); + console.log(userFromJson); + } else { + const error = result.data; + console.error(\`Error \${result.status}:\`, error); + } */ // { profilePictureURL?: (string | null) | undefined; }>; }; + responses: { + 200: { + members: Array<{ + id: string; + firstName?: (string | null) | undefined; + lastName?: (string | null) | undefined; + email: string; + profilePictureURL?: (string | null) | undefined; + }>; + }; + }; }; // @@ -721,6 +991,7 @@ describe("generator", () => { export type DefaultEndpoint = { parameters?: EndpointParameters | undefined; response: unknown; + responses?: Record; responseHeaders?: Record; }; @@ -736,11 +1007,97 @@ describe("generator", () => { areParametersRequired: boolean; }; response: TConfig["response"]; + responses?: TConfig["responses"]; responseHeaders?: TConfig["responseHeaders"]; }; export type Fetcher = (method: Method, url: string, parameters?: EndpointParameters | undefined) => Promise; + // Status code type for success responses + export type SuccessStatusCode = + | 200 + | 201 + | 202 + | 203 + | 204 + | 205 + | 206 + | 207 + | 208 + | 226 + | 300 + | 301 + | 302 + | 303 + | 304 + | 305 + | 306 + | 307 + | 308; + + // Error handling types + export type TypedApiResponse< + TSuccess, + TAllResponses extends Record = {}, + > = keyof TAllResponses extends never + ? Omit & { + ok: true; + status: number; + data: TSuccess; + json: () => Promise; + } + : { + [K in keyof TAllResponses]: K extends string + ? K extends \`\${infer TStatusCode extends number}\` + ? TStatusCode extends SuccessStatusCode + ? Omit & { + ok: true; + status: TStatusCode; + data: TSuccess; + json: () => Promise; + } + : Omit & { + ok: false; + status: TStatusCode; + data: TAllResponses[K]; + json: () => Promise; + } + : never + : K extends number + ? K extends SuccessStatusCode + ? Omit & { + ok: true; + status: K; + data: TSuccess; + json: () => Promise; + } + : Omit & { + ok: false; + status: K; + data: TAllResponses[K]; + json: () => Promise; + } + : never; + }[keyof TAllResponses]; + + export type SafeApiResponse = TEndpoint extends { response: infer TSuccess; responses: infer TResponses } + ? TResponses extends Record + ? TypedApiResponse + : Omit & { + ok: true; + status: number; + data: TSuccess; + json: () => Promise; + } + : TEndpoint extends { response: infer TSuccess } + ? Omit & { + ok: true; + status: number; + data: TSuccess; + json: () => Promise; + } + : never; + type RequiredKeys = { [P in keyof T]-?: undefined extends T[P] ? never : P; }[keyof T]; @@ -771,11 +1128,45 @@ describe("generator", () => { // get( path: Path, - ...params: MaybeOptionalArg - ): Promise { - return this.fetcher("get", this.baseUrl + path, params[0]).then((response) => - this.parseResponse(response), - ) as Promise; + ...params: MaybeOptionalArg + ): Promise; + + get( + path: Path, + ...params: MaybeOptionalArg + ): Promise>; + + get( + path: Path, + ...params: MaybeOptionalArg + ): Promise { + const requestParams = params[0]; + const withResponse = requestParams?.withResponse; + + // Remove withResponse from params before passing to fetcher + const { withResponse: _, ...fetchParams } = requestParams || {}; + + if (withResponse) { + return this.fetcher("get", this.baseUrl + path, Object.keys(fetchParams).length ? fetchParams : undefined).then( + async (response) => { + // Parse the response data + const data = await this.parseResponse(response); + + // Override properties while keeping the original Response object + const typedResponse = Object.assign(response, { + ok: response.ok, + status: response.status, + data: data, + json: () => Promise.resolve(data), + }); + return typedResponse; + }, + ); + } else { + return this.fetcher("get", this.baseUrl + path, requestParams).then((response) => + this.parseResponse(response), + ) as Promise; + } } // @@ -814,6 +1205,21 @@ describe("generator", () => { api.get("/users").then((users) => console.log(users)); api.post("/users", { body: { name: "John" } }).then((user) => console.log(user)); api.put("/users/:id", { path: { id: 1 }, body: { name: "John" } }).then((user) => console.log(user)); + + // With error handling + const result = await api.get("/users/{id}", { path: { id: "123" }, withResponse: true }); + if (result.ok) { + // Access data directly + const user = result.data; + console.log(user); + + // Or use the json() method for compatibility + const userFromJson = await result.json(); + console.log(userFromJson); + } else { + const error = result.data; + console.error(\`Error \${result.status}:\`, error); + } */ // { path: Partial<{ optionalInPath1: string; optionalInPath2: string }>; }; response: string; + responses: { 200: string }; }; // @@ -933,6 +1340,7 @@ describe("generator", () => { export type DefaultEndpoint = { parameters?: EndpointParameters | undefined; response: unknown; + responses?: Record; responseHeaders?: Record; }; @@ -948,11 +1356,97 @@ describe("generator", () => { areParametersRequired: boolean; }; response: TConfig["response"]; + responses?: TConfig["responses"]; responseHeaders?: TConfig["responseHeaders"]; }; export type Fetcher = (method: Method, url: string, parameters?: EndpointParameters | undefined) => Promise; + // Status code type for success responses + export type SuccessStatusCode = + | 200 + | 201 + | 202 + | 203 + | 204 + | 205 + | 206 + | 207 + | 208 + | 226 + | 300 + | 301 + | 302 + | 303 + | 304 + | 305 + | 306 + | 307 + | 308; + + // Error handling types + export type TypedApiResponse< + TSuccess, + TAllResponses extends Record = {}, + > = keyof TAllResponses extends never + ? Omit & { + ok: true; + status: number; + data: TSuccess; + json: () => Promise; + } + : { + [K in keyof TAllResponses]: K extends string + ? K extends \`\${infer TStatusCode extends number}\` + ? TStatusCode extends SuccessStatusCode + ? Omit & { + ok: true; + status: TStatusCode; + data: TSuccess; + json: () => Promise; + } + : Omit & { + ok: false; + status: TStatusCode; + data: TAllResponses[K]; + json: () => Promise; + } + : never + : K extends number + ? K extends SuccessStatusCode + ? Omit & { + ok: true; + status: K; + data: TSuccess; + json: () => Promise; + } + : Omit & { + ok: false; + status: K; + data: TAllResponses[K]; + json: () => Promise; + } + : never; + }[keyof TAllResponses]; + + export type SafeApiResponse = TEndpoint extends { response: infer TSuccess; responses: infer TResponses } + ? TResponses extends Record + ? TypedApiResponse + : Omit & { + ok: true; + status: number; + data: TSuccess; + json: () => Promise; + } + : TEndpoint extends { response: infer TSuccess } + ? Omit & { + ok: true; + status: number; + data: TSuccess; + json: () => Promise; + } + : never; + type RequiredKeys = { [P in keyof T]-?: undefined extends T[P] ? never : P; }[keyof T]; @@ -983,11 +1477,45 @@ describe("generator", () => { // get( path: Path, - ...params: MaybeOptionalArg - ): Promise { - return this.fetcher("get", this.baseUrl + path, params[0]).then((response) => - this.parseResponse(response), - ) as Promise; + ...params: MaybeOptionalArg + ): Promise; + + get( + path: Path, + ...params: MaybeOptionalArg + ): Promise>; + + get( + path: Path, + ...params: MaybeOptionalArg + ): Promise { + const requestParams = params[0]; + const withResponse = requestParams?.withResponse; + + // Remove withResponse from params before passing to fetcher + const { withResponse: _, ...fetchParams } = requestParams || {}; + + if (withResponse) { + return this.fetcher("get", this.baseUrl + path, Object.keys(fetchParams).length ? fetchParams : undefined).then( + async (response) => { + // Parse the response data + const data = await this.parseResponse(response); + + // Override properties while keeping the original Response object + const typedResponse = Object.assign(response, { + ok: response.ok, + status: response.status, + data: data, + json: () => Promise.resolve(data), + }); + return typedResponse; + }, + ); + } else { + return this.fetcher("get", this.baseUrl + path, requestParams).then((response) => + this.parseResponse(response), + ) as Promise; + } } // @@ -1026,10 +1554,48 @@ describe("generator", () => { api.get("/users").then((users) => console.log(users)); api.post("/users", { body: { name: "John" } }).then((user) => console.log(user)); api.put("/users/:id", { path: { id: 1 }, body: { name: "John" } }).then((user) => console.log(user)); + + // With error handling + const result = await api.get("/users/{id}", { path: { id: "123" }, withResponse: true }); + if (result.ok) { + // Access data directly + const user = result.data; + console.log(user); + + // Or use the json() method for compatibility + const userFromJson = await result.json(); + console.log(userFromJson); + } else { + const error = result.data; + console.error(\`Error \${result.status}:\`, error); + } */ // { + const openApiDoc = (await SwaggerParser.parse("./tests/samples/error-schemas.yaml")) as OpenAPIObject; + const generated = await prettify(generateFile(mapOpenApiEndpoints(openApiDoc))); + + // Verify error schemas are generated + expect(generated).toContain("export type AuthError"); + expect(generated).toContain("export type NotFoundError"); + expect(generated).toContain("export type ValidationError"); + expect(generated).toContain("export type ForbiddenError"); + expect(generated).toContain("export type ServerError"); + + // Verify error responses are included in endpoint types + expect(generated).toContain('responses: { 200: Schemas.User; 401: Schemas.AuthError; 404: Schemas.NotFoundError; 500: Schemas.ServerError }'); + expect(generated).toContain('responses: { 201: Schemas.Post; 400: Schemas.ValidationError; 403: Schemas.ForbiddenError }'); + + // Verify specific error schema structure + expect(generated).toContain("error: string"); + expect(generated).toContain("code: number"); + expect(generated).toContain("message: string"); + expect(generated).toContain("field: string"); + expect(generated).toContain("reason: string"); + }); }); diff --git a/packages/typed-openapi/tests/include-client.test.ts b/packages/typed-openapi/tests/include-client.test.ts new file mode 100644 index 0000000..f5ac1e7 --- /dev/null +++ b/packages/typed-openapi/tests/include-client.test.ts @@ -0,0 +1,69 @@ +import { it, expect } from "vitest"; +import type { OpenAPIObject } from "openapi3-ts/oas31"; + +import { generateFile } from "../src/generator.ts"; +import { mapOpenApiEndpoints } from "../src/map-openapi-endpoints.ts"; +import { prettify } from "../src/format.ts"; + +it("should exclude API client when includeClient is false", async () => { + const openApiDoc: OpenAPIObject = { + openapi: "3.0.0", + info: { title: "Test API", version: "1.0.0" }, + paths: { + "/test": { + get: { + operationId: "getTest", + responses: { + 200: { + description: "Success", + content: { + "application/json": { + schema: { type: "object", properties: { message: { type: "string" } } }, + }, + }, + }, + }, + }, + }, + }, + }; + + const endpoints = mapOpenApiEndpoints(openApiDoc); + + // Test with includeClient: false + const withoutClient = await prettify( + generateFile({ + ...endpoints, + includeClient: false, + }), + ); + + // Should not contain ApiClientTypes or ApiClient sections + expect(withoutClient).not.toContain("// "); + expect(withoutClient).not.toContain("// "); + expect(withoutClient).not.toContain("export class ApiClient"); + expect(withoutClient).not.toContain("export type EndpointParameters"); + expect(withoutClient).not.toContain("export type SuccessStatusCode"); + expect(withoutClient).not.toContain("export type TypedApiResponse"); + + // Should still contain schemas and endpoints + expect(withoutClient).toContain("export namespace Schemas"); + expect(withoutClient).toContain("export namespace Endpoints"); + expect(withoutClient).toContain("export type EndpointByMethod"); + + // Test with includeClient: true (default) + const withClient = await prettify( + generateFile({ + ...endpoints, + includeClient: true, + }), + ); + + // Should contain ApiClientTypes and ApiClient sections + expect(withClient).toContain("// "); + expect(withClient).toContain("// "); + expect(withClient).toContain("export class ApiClient"); + expect(withClient).toContain("export type EndpointParameters"); + expect(withClient).toContain("export type SuccessStatusCode"); + expect(withClient).toContain("export type TypedApiResponse"); +}); diff --git a/packages/typed-openapi/tests/map-openapi-endpoints.test.ts b/packages/typed-openapi/tests/map-openapi-endpoints.test.ts index f3bf3b2..430cf38 100644 --- a/packages/typed-openapi/tests/map-openapi-endpoints.test.ts +++ b/packages/typed-openapi/tests/map-openapi-endpoints.test.ts @@ -495,6 +495,27 @@ describe("map-openapi-endpoints", () => { "description": "successful operation", }, "400": { + "content": { + "application/json": { + "schema": { + "properties": { + "code": { + "example": 400, + "type": "integer", + }, + "message": { + "example": "Invalid status value", + "type": "string", + }, + }, + "required": [ + "code", + "message", + ], + "type": "object", + }, + }, + }, "description": "Invalid status value", }, }, @@ -646,9 +667,51 @@ describe("map-openapi-endpoints", () => { "description": "successful operation", }, "400": { + "content": { + "application/json": { + "schema": { + "properties": { + "code": { + "example": 400, + "type": "integer", + }, + "message": { + "example": "Invalid pet ID", + "type": "string", + }, + }, + "required": [ + "code", + "message", + ], + "type": "object", + }, + }, + }, "description": "Invalid ID supplied", }, "404": { + "content": { + "application/json": { + "schema": { + "properties": { + "code": { + "example": 404, + "type": "integer", + }, + "message": { + "example": "Pet not found", + "type": "string", + }, + }, + "required": [ + "code", + "message", + ], + "type": "object", + }, + }, + }, "description": "Pet not found", }, }, @@ -1325,6 +1388,24 @@ describe("map-openapi-endpoints", () => { "type": "ref", "value": "Pet", }, + "responses": { + "200": { + "type": "ref", + "value": "Pet", + }, + "400": { + "type": "keyword", + "value": "unknown", + }, + "404": { + "type": "keyword", + "value": "unknown", + }, + "405": { + "type": "keyword", + "value": "unknown", + }, + }, }, { "meta": { @@ -1402,6 +1483,16 @@ describe("map-openapi-endpoints", () => { "type": "ref", "value": "Pet", }, + "responses": { + "200": { + "type": "ref", + "value": "Pet", + }, + "405": { + "type": "keyword", + "value": "unknown", + }, + }, }, { "meta": { @@ -1454,6 +1545,27 @@ describe("map-openapi-endpoints", () => { "description": "successful operation", }, "400": { + "content": { + "application/json": { + "schema": { + "properties": { + "code": { + "example": 400, + "type": "integer", + }, + "message": { + "example": "Invalid status value", + "type": "string", + }, + }, + "required": [ + "code", + "message", + ], + "type": "object", + }, + }, + }, "description": "Invalid status value", }, }, @@ -1482,6 +1594,16 @@ describe("map-openapi-endpoints", () => { "type": "array", "value": "Array", }, + "responses": { + "200": { + "type": "array", + "value": "Array", + }, + "400": { + "type": "object", + "value": "{ code: number, message: string }", + }, + }, }, { "meta": { @@ -1559,6 +1681,16 @@ describe("map-openapi-endpoints", () => { "type": "array", "value": "Array", }, + "responses": { + "200": { + "type": "array", + "value": "Array", + }, + "400": { + "type": "keyword", + "value": "unknown", + }, + }, }, { "meta": { @@ -1599,9 +1731,51 @@ describe("map-openapi-endpoints", () => { "description": "successful operation", }, "400": { + "content": { + "application/json": { + "schema": { + "properties": { + "code": { + "example": 400, + "type": "integer", + }, + "message": { + "example": "Invalid pet ID", + "type": "string", + }, + }, + "required": [ + "code", + "message", + ], + "type": "object", + }, + }, + }, "description": "Invalid ID supplied", }, "404": { + "content": { + "application/json": { + "schema": { + "properties": { + "code": { + "example": 404, + "type": "integer", + }, + "message": { + "example": "Pet not found", + "type": "string", + }, + }, + "required": [ + "code", + "message", + ], + "type": "object", + }, + }, + }, "description": "Pet not found", }, }, @@ -1635,6 +1809,20 @@ describe("map-openapi-endpoints", () => { "type": "ref", "value": "Pet", }, + "responses": { + "200": { + "type": "ref", + "value": "Pet", + }, + "400": { + "type": "object", + "value": "{ code: number, message: string }", + }, + "404": { + "type": "object", + "value": "{ code: number, message: string }", + }, + }, }, { "meta": { @@ -1710,6 +1898,12 @@ describe("map-openapi-endpoints", () => { "type": "keyword", "value": "unknown", }, + "responses": { + "405": { + "type": "keyword", + "value": "unknown", + }, + }, }, { "meta": { @@ -1778,6 +1972,12 @@ describe("map-openapi-endpoints", () => { "type": "keyword", "value": "unknown", }, + "responses": { + "400": { + "type": "keyword", + "value": "unknown", + }, + }, }, { "meta": { @@ -1867,6 +2067,12 @@ describe("map-openapi-endpoints", () => { "type": "ref", "value": "ApiResponse", }, + "responses": { + "200": { + "type": "ref", + "value": "ApiResponse", + }, + }, }, { "meta": { @@ -1911,6 +2117,12 @@ describe("map-openapi-endpoints", () => { "type": "literal", "value": "Record", }, + "responses": { + "200": { + "type": "literal", + "value": "Record", + }, + }, }, { "meta": { @@ -1973,6 +2185,16 @@ describe("map-openapi-endpoints", () => { "type": "ref", "value": "Order", }, + "responses": { + "200": { + "type": "ref", + "value": "Order", + }, + "405": { + "type": "keyword", + "value": "unknown", + }, + }, }, { "meta": { @@ -2038,6 +2260,20 @@ describe("map-openapi-endpoints", () => { "type": "ref", "value": "Order", }, + "responses": { + "200": { + "type": "ref", + "value": "Order", + }, + "400": { + "type": "keyword", + "value": "unknown", + }, + "404": { + "type": "keyword", + "value": "unknown", + }, + }, }, { "meta": { @@ -2088,6 +2324,16 @@ describe("map-openapi-endpoints", () => { "type": "keyword", "value": "unknown", }, + "responses": { + "400": { + "type": "keyword", + "value": "unknown", + }, + "404": { + "type": "keyword", + "value": "unknown", + }, + }, }, { "meta": { @@ -2153,6 +2399,12 @@ describe("map-openapi-endpoints", () => { "type": "ref", "value": "User", }, + "responses": { + "default": { + "type": "ref", + "value": "User", + }, + }, }, { "meta": { @@ -2213,6 +2465,16 @@ describe("map-openapi-endpoints", () => { "type": "ref", "value": "User", }, + "responses": { + "200": { + "type": "ref", + "value": "User", + }, + "default": { + "type": "keyword", + "value": "unknown", + }, + }, }, { "meta": { @@ -2307,6 +2569,16 @@ describe("map-openapi-endpoints", () => { "value": "number", }, }, + "responses": { + "200": { + "type": "keyword", + "value": "string", + }, + "400": { + "type": "keyword", + "value": "unknown", + }, + }, }, { "meta": { @@ -2336,6 +2608,12 @@ describe("map-openapi-endpoints", () => { "type": "keyword", "value": "unknown", }, + "responses": { + "default": { + "type": "keyword", + "value": "unknown", + }, + }, }, { "meta": { @@ -2400,6 +2678,20 @@ describe("map-openapi-endpoints", () => { "type": "ref", "value": "User", }, + "responses": { + "200": { + "type": "ref", + "value": "User", + }, + "400": { + "type": "keyword", + "value": "unknown", + }, + "404": { + "type": "keyword", + "value": "unknown", + }, + }, }, { "meta": { @@ -2470,6 +2762,12 @@ describe("map-openapi-endpoints", () => { "type": "keyword", "value": "unknown", }, + "responses": { + "default": { + "type": "keyword", + "value": "unknown", + }, + }, }, { "meta": { @@ -2519,6 +2817,16 @@ describe("map-openapi-endpoints", () => { "type": "keyword", "value": "unknown", }, + "responses": { + "400": { + "type": "keyword", + "value": "unknown", + }, + "404": { + "type": "keyword", + "value": "unknown", + }, + }, }, ], "factory": { @@ -2690,6 +2998,12 @@ describe("map-openapi-endpoints", () => { "type": "keyword", "value": "unknown", }, + "responses": { + "200": { + "type": "keyword", + "value": "unknown", + }, + }, }, ] `); @@ -2907,8 +3221,78 @@ describe("map-openapi-endpoints", () => { "type": "ref", "value": "SerializedUserSession", }, + "responses": { + "200": { + "type": "ref", + "value": "SerializedUserSession", + }, + "401": { + "type": "keyword", + "value": "string", + }, + "500": { + "type": "keyword", + "value": "string", + }, + }, }, ] `); }); + + test("error schemas", async ({ expect }) => { + const openApiDoc = (await SwaggerParser.parse("./tests/samples/error-schemas.yaml")) as OpenAPIObject; + const result = mapOpenApiEndpoints(openApiDoc); + + // Find the getUserById endpoint + const getUserEndpoint = result.endpointList.find(e => e.meta.alias === "get_GetUserById"); + expect(getUserEndpoint).toBeDefined(); + expect(getUserEndpoint?.responses).toMatchInlineSnapshot(` + { + "200": { + "type": "ref", + "value": "User", + }, + "401": { + "type": "ref", + "value": "AuthError", + }, + "404": { + "type": "ref", + "value": "NotFoundError", + }, + "500": { + "type": "ref", + "value": "ServerError", + }, + } + `); + + // Find the createPost endpoint + const createPostEndpoint = result.endpointList.find(e => e.meta.alias === "post_CreatePost"); + expect(createPostEndpoint).toBeDefined(); + expect(createPostEndpoint?.responses).toMatchInlineSnapshot(` + { + "201": { + "type": "ref", + "value": "Post", + }, + "400": { + "type": "ref", + "value": "ValidationError", + }, + "403": { + "type": "ref", + "value": "ForbiddenError", + }, + } + `); + + // Verify that error schemas are properly resolved + const authErrorBox = result.refs.getInfosByRef("#/components/schemas/AuthError"); + expect(authErrorBox?.name).toBe("AuthError"); + + const validationErrorBox = result.refs.getInfosByRef("#/components/schemas/ValidationError"); + expect(validationErrorBox?.name).toBe("ValidationError"); + }); }); diff --git a/packages/typed-openapi/tests/multiple-success-responses.test.ts b/packages/typed-openapi/tests/multiple-success-responses.test.ts new file mode 100644 index 0000000..5f088a2 --- /dev/null +++ b/packages/typed-openapi/tests/multiple-success-responses.test.ts @@ -0,0 +1,399 @@ +import { describe, test } from "vitest"; +import type { OpenAPIObject } from "openapi3-ts/oas31"; +import { mapOpenApiEndpoints } from "../src/map-openapi-endpoints.js"; +import { generateFile } from "../src/generator.js"; +import { prettify } from "../src/format.js"; + +describe("multiple success responses", () => { + test("should handle 200 vs 201 responses with different schemas", async ({ expect }) => { + const openApiDoc: OpenAPIObject = { + openapi: "3.0.3", + info: { + title: "Multi Success API", + version: "1.0.0" + }, + paths: { + "/users": { + post: { + operationId: "createOrUpdateUser", + requestBody: { + content: { + "application/json": { + schema: { + type: "object", + properties: { + name: { type: "string" }, + email: { type: "string" } + }, + required: ["name", "email"] + } + } + } + }, + responses: { + "200": { + description: "User updated", + content: { + "application/json": { + schema: { + type: "object", + properties: { + id: { type: "string" }, + name: { type: "string" }, + email: { type: "string" }, + updated: { type: "boolean", const: true }, + updatedAt: { type: "string", format: "date-time" } + }, + required: ["id", "name", "email", "updated", "updatedAt"] + } + } + } + }, + "201": { + description: "User created", + content: { + "application/json": { + schema: { + type: "object", + properties: { + id: { type: "string" }, + name: { type: "string" }, + email: { type: "string" }, + created: { type: "boolean", const: true }, + createdAt: { type: "string", format: "date-time" } + }, + required: ["id", "name", "email", "created", "createdAt"] + } + } + } + }, + "400": { + description: "Validation error", + content: { + "application/json": { + schema: { + type: "object", + properties: { + message: { type: "string" }, + errors: { + type: "array", + items: { type: "string" } + } + }, + required: ["message", "errors"] + } + } + } + } + } + } + } + } + }; + + const mapped = mapOpenApiEndpoints(openApiDoc); + const generated = await prettify(generateFile(mapped)); + + // Check that the endpoint has proper response types + expect(generated).toContain("post_CreateOrUpdateUser"); + + // Check that different success responses have different schemas + expect(generated).toContain("updated: boolean"); + expect(generated).toContain("created: boolean"); + expect(generated).toContain("Array"); + + // Verify the SafeApiResponse type is present for error handling + expect(generated).toContain("SafeApiResponse"); + + expect(generated).toMatchInlineSnapshot(` + "export namespace Schemas { + // + // + } + + export namespace Endpoints { + // + + export type post_CreateOrUpdateUser = { + method: "POST"; + path: "/users"; + requestFormat: "json"; + parameters: { + body: { name: string; email: string }; + }; + response: { id: string; name: string; email: string; updated: boolean; updatedAt: string }; + responses: { + 200: { id: string; name: string; email: string; updated: boolean; updatedAt: string }; + 201: { id: string; name: string; email: string; created: boolean; createdAt: string }; + 400: { message: string; errors: Array }; + }; + }; + + // + } + + // + export type EndpointByMethod = { + post: { + "/users": Endpoints.post_CreateOrUpdateUser; + }; + }; + + // + + // + export type PostEndpoints = EndpointByMethod["post"]; + // + + // + export type EndpointParameters = { + body?: unknown; + query?: Record; + header?: Record; + path?: Record; + }; + + export type MutationMethod = "post" | "put" | "patch" | "delete"; + export type Method = "get" | "head" | "options" | MutationMethod; + + type RequestFormat = "json" | "form-data" | "form-url" | "binary" | "text"; + + export type DefaultEndpoint = { + parameters?: EndpointParameters | undefined; + response: unknown; + responses?: Record; + responseHeaders?: Record; + }; + + export type Endpoint = { + operationId: string; + method: Method; + path: string; + requestFormat: RequestFormat; + parameters?: TConfig["parameters"]; + meta: { + alias: string; + hasParameters: boolean; + areParametersRequired: boolean; + }; + response: TConfig["response"]; + responses?: TConfig["responses"]; + responseHeaders?: TConfig["responseHeaders"]; + }; + + export type Fetcher = (method: Method, url: string, parameters?: EndpointParameters | undefined) => Promise; + + // Status code type for success responses + export type SuccessStatusCode = + | 200 + | 201 + | 202 + | 203 + | 204 + | 205 + | 206 + | 207 + | 208 + | 226 + | 300 + | 301 + | 302 + | 303 + | 304 + | 305 + | 306 + | 307 + | 308; + + // Error handling types + export type TypedApiResponse< + TSuccess, + TAllResponses extends Record = {}, + > = keyof TAllResponses extends never + ? Omit & { + ok: true; + status: number; + data: TSuccess; + json: () => Promise; + } + : { + [K in keyof TAllResponses]: K extends string + ? K extends \`\${infer TStatusCode extends number}\` + ? TStatusCode extends SuccessStatusCode + ? Omit & { + ok: true; + status: TStatusCode; + data: TSuccess; + json: () => Promise; + } + : Omit & { + ok: false; + status: TStatusCode; + data: TAllResponses[K]; + json: () => Promise; + } + : never + : K extends number + ? K extends SuccessStatusCode + ? Omit & { + ok: true; + status: K; + data: TSuccess; + json: () => Promise; + } + : Omit & { + ok: false; + status: K; + data: TAllResponses[K]; + json: () => Promise; + } + : never; + }[keyof TAllResponses]; + + export type SafeApiResponse = TEndpoint extends { response: infer TSuccess; responses: infer TResponses } + ? TResponses extends Record + ? TypedApiResponse + : Omit & { + ok: true; + status: number; + data: TSuccess; + json: () => Promise; + } + : TEndpoint extends { response: infer TSuccess } + ? Omit & { + ok: true; + status: number; + data: TSuccess; + json: () => Promise; + } + : never; + + type RequiredKeys = { + [P in keyof T]-?: undefined extends T[P] ? never : P; + }[keyof T]; + + type MaybeOptionalArg = RequiredKeys extends never ? [config?: T] : [config: T]; + + // + + // + export class ApiClient { + baseUrl: string = ""; + + constructor(public fetcher: Fetcher) {} + + setBaseUrl(baseUrl: string) { + this.baseUrl = baseUrl; + return this; + } + + parseResponse = async (response: Response): Promise => { + const contentType = response.headers.get("content-type"); + if (contentType?.includes("application/json")) { + return response.json(); + } + return response.text() as unknown as T; + }; + + // + post( + path: Path, + ...params: MaybeOptionalArg + ): Promise; + + post( + path: Path, + ...params: MaybeOptionalArg + ): Promise>; + + post( + path: Path, + ...params: MaybeOptionalArg + ): Promise { + const requestParams = params[0]; + const withResponse = requestParams?.withResponse; + + // Remove withResponse from params before passing to fetcher + const { withResponse: _, ...fetchParams } = requestParams || {}; + + if (withResponse) { + return this.fetcher("post", this.baseUrl + path, Object.keys(fetchParams).length ? fetchParams : undefined).then( + async (response) => { + // Parse the response data + const data = await this.parseResponse(response); + + // Override properties while keeping the original Response object + const typedResponse = Object.assign(response, { + ok: response.ok, + status: response.status, + data: data, + json: () => Promise.resolve(data), + }); + return typedResponse; + }, + ); + } else { + return this.fetcher("post", this.baseUrl + path, requestParams).then((response) => + this.parseResponse(response), + ) as Promise; + } + } + // + + // + /** + * Generic request method with full type-safety for any endpoint + */ + request< + TMethod extends keyof EndpointByMethod, + TPath extends keyof EndpointByMethod[TMethod], + TEndpoint extends EndpointByMethod[TMethod][TPath], + >( + method: TMethod, + path: TPath, + ...params: MaybeOptionalArg + ): Promise< + Omit & { + /** [MDN Reference](https://developer.mozilla.org/docs/Web/API/Request/json) */ + json: () => Promise; + } + > { + return this.fetcher(method, this.baseUrl + (path as string), params[0] as EndpointParameters); + } + // + } + + export function createApiClient(fetcher: Fetcher, baseUrl?: string) { + return new ApiClient(fetcher).setBaseUrl(baseUrl ?? ""); + } + + /** + Example usage: + const api = createApiClient((method, url, params) => + fetch(url, { method, body: JSON.stringify(params) }).then((res) => res.json()), + ); + api.get("/users").then((users) => console.log(users)); + api.post("/users", { body: { name: "John" } }).then((user) => console.log(user)); + api.put("/users/:id", { path: { id: 1 }, body: { name: "John" } }).then((user) => console.log(user)); + + // With error handling + const result = await api.get("/users/{id}", { path: { id: "123" }, withResponse: true }); + if (result.ok) { + // Access data directly + const user = result.data; + console.log(user); + + // Or use the json() method for compatibility + const userFromJson = await result.json(); + console.log(userFromJson); + } else { + const error = result.data; + console.error(\`Error \${result.status}:\`, error); + } + */ + + // ; }; response: Array; + responses: { 200: Array; 400: Schemas.ErrorResponse; 500: Schemas.ErrorResponse }; }; export type post_ContainerCreate = { method: "POST"; @@ -885,6 +886,13 @@ export namespace Endpoints { Partial<{ HostConfig: Schemas.HostConfig; NetworkingConfig: Schemas.NetworkingConfig }>; }; response: Schemas.ContainerCreateResponse; + responses: { + 201: Schemas.ContainerCreateResponse; + 400: Schemas.ErrorResponse; + 404: Schemas.ErrorResponse; + 409: Schemas.ErrorResponse; + 500: Schemas.ErrorResponse; + }; }; export type get_ContainerInspect = { method: "GET"; @@ -921,6 +929,37 @@ export namespace Endpoints { Config: Schemas.ContainerConfig; NetworkSettings: Schemas.NetworkSettings; }>; + responses: { + 200: Partial<{ + Id: string; + Created: string; + Path: string; + Args: Array; + State: Schemas.ContainerState; + Image: string; + ResolvConfPath: string; + HostnamePath: string; + HostsPath: string; + LogPath: string; + Name: string; + RestartCount: number; + Driver: string; + Platform: string; + MountLabel: string; + ProcessLabel: string; + AppArmorProfile: string; + ExecIDs: Array | null; + HostConfig: Schemas.HostConfig; + GraphDriver: Schemas.GraphDriverData; + SizeRw: number; + SizeRootFs: number; + Mounts: Array; + Config: Schemas.ContainerConfig; + NetworkSettings: Schemas.NetworkSettings; + }>; + 404: Schemas.ErrorResponse; + 500: Schemas.ErrorResponse; + }; }; export type get_ContainerTop = { method: "GET"; @@ -931,6 +970,11 @@ export namespace Endpoints { path: { id: string }; }; response: Partial<{ Titles: Array; Processes: Array> }>; + responses: { + 200: Partial<{ Titles: Array; Processes: Array> }>; + 404: Schemas.ErrorResponse; + 500: Schemas.ErrorResponse; + }; }; export type get_ContainerLogs = { method: "GET"; @@ -949,6 +993,7 @@ export namespace Endpoints { path: { id: string }; }; response: unknown; + responses: { 200: unknown; 404: unknown; 500: unknown }; }; export type get_ContainerChanges = { method: "GET"; @@ -958,6 +1003,7 @@ export namespace Endpoints { path: { id: string }; }; response: Array; + responses: { 200: Array; 404: Schemas.ErrorResponse; 500: Schemas.ErrorResponse }; }; export type get_ContainerExport = { method: "GET"; @@ -967,6 +1013,7 @@ export namespace Endpoints { path: { id: string }; }; response: unknown; + responses: { 200: unknown; 404: unknown; 500: unknown }; }; export type get_ContainerStats = { method: "GET"; @@ -977,6 +1024,7 @@ export namespace Endpoints { path: { id: string }; }; response: Record; + responses: { 200: Record; 404: Schemas.ErrorResponse; 500: Schemas.ErrorResponse }; }; export type post_ContainerResize = { method: "POST"; @@ -987,6 +1035,7 @@ export namespace Endpoints { path: { id: string }; }; response: unknown; + responses: { 200: unknown; 404: unknown; 500: unknown }; }; export type post_ContainerStart = { method: "POST"; @@ -997,6 +1046,7 @@ export namespace Endpoints { path: { id: string }; }; response: unknown; + responses: { 204: unknown; 304: unknown; 404: Schemas.ErrorResponse; 500: Schemas.ErrorResponse }; }; export type post_ContainerStop = { method: "POST"; @@ -1007,6 +1057,7 @@ export namespace Endpoints { path: { id: string }; }; response: unknown; + responses: { 204: unknown; 304: unknown; 404: Schemas.ErrorResponse; 500: Schemas.ErrorResponse }; }; export type post_ContainerRestart = { method: "POST"; @@ -1017,6 +1068,7 @@ export namespace Endpoints { path: { id: string }; }; response: unknown; + responses: { 204: unknown; 404: Schemas.ErrorResponse; 500: Schemas.ErrorResponse }; }; export type post_ContainerKill = { method: "POST"; @@ -1027,6 +1079,7 @@ export namespace Endpoints { path: { id: string }; }; response: unknown; + responses: { 204: unknown; 404: Schemas.ErrorResponse; 409: Schemas.ErrorResponse; 500: Schemas.ErrorResponse }; }; export type post_ContainerUpdate = { method: "POST"; @@ -1038,6 +1091,7 @@ export namespace Endpoints { body: Schemas.Resources & Partial<{ RestartPolicy: Schemas.RestartPolicy }>; }; response: Partial<{ Warnings: Array }>; + responses: { 200: Partial<{ Warnings: Array }>; 404: Schemas.ErrorResponse; 500: Schemas.ErrorResponse }; }; export type post_ContainerRename = { method: "POST"; @@ -1048,6 +1102,7 @@ export namespace Endpoints { path: { id: string }; }; response: unknown; + responses: { 204: unknown; 404: Schemas.ErrorResponse; 409: Schemas.ErrorResponse; 500: Schemas.ErrorResponse }; }; export type post_ContainerPause = { method: "POST"; @@ -1057,6 +1112,7 @@ export namespace Endpoints { path: { id: string }; }; response: unknown; + responses: { 204: unknown; 404: Schemas.ErrorResponse; 500: Schemas.ErrorResponse }; }; export type post_ContainerUnpause = { method: "POST"; @@ -1066,6 +1122,7 @@ export namespace Endpoints { path: { id: string }; }; response: unknown; + responses: { 204: unknown; 404: Schemas.ErrorResponse; 500: Schemas.ErrorResponse }; }; export type post_ContainerAttach = { method: "POST"; @@ -1083,6 +1140,7 @@ export namespace Endpoints { path: { id: string }; }; response: unknown; + responses: { 101: unknown; 200: unknown; 400: unknown; 404: unknown; 500: unknown }; }; export type get_ContainerAttachWebsocket = { method: "GET"; @@ -1100,6 +1158,13 @@ export namespace Endpoints { path: { id: string }; }; response: unknown; + responses: { + 101: unknown; + 200: unknown; + 400: Schemas.ErrorResponse; + 404: Schemas.ErrorResponse; + 500: Schemas.ErrorResponse; + }; }; export type post_ContainerWait = { method: "POST"; @@ -1110,6 +1175,12 @@ export namespace Endpoints { path: { id: string }; }; response: Schemas.ContainerWaitResponse; + responses: { + 200: Schemas.ContainerWaitResponse; + 400: Schemas.ErrorResponse; + 404: Schemas.ErrorResponse; + 500: Schemas.ErrorResponse; + }; }; export type delete_ContainerDelete = { method: "DELETE"; @@ -1120,6 +1191,13 @@ export namespace Endpoints { path: { id: string }; }; response: unknown; + responses: { + 204: unknown; + 400: Schemas.ErrorResponse; + 404: Schemas.ErrorResponse; + 409: Schemas.ErrorResponse; + 500: Schemas.ErrorResponse; + }; }; export type get_ContainerArchive = { method: "GET"; @@ -1130,6 +1208,7 @@ export namespace Endpoints { path: { id: string }; }; response: unknown; + responses: { 200: unknown; 400: unknown; 404: unknown; 500: unknown }; }; export type put_PutContainerArchive = { method: "PUT"; @@ -1142,6 +1221,13 @@ export namespace Endpoints { body: string; }; response: unknown; + responses: { + 200: unknown; + 400: Schemas.ErrorResponse; + 403: Schemas.ErrorResponse; + 404: Schemas.ErrorResponse; + 500: Schemas.ErrorResponse; + }; }; export type head_ContainerArchiveInfo = { method: "HEAD"; @@ -1152,6 +1238,7 @@ export namespace Endpoints { path: { id: string }; }; response: unknown; + responses: { 200: unknown; 400: Schemas.ErrorResponse; 404: Schemas.ErrorResponse; 500: Schemas.ErrorResponse }; responseHeaders: { "x-docker-container-path-stat": string }; }; export type post_ContainerPrune = { @@ -1162,6 +1249,10 @@ export namespace Endpoints { query: Partial<{ filters: string }>; }; response: Partial<{ ContainersDeleted: Array; SpaceReclaimed: number }>; + responses: { + 200: Partial<{ ContainersDeleted: Array; SpaceReclaimed: number }>; + 500: Schemas.ErrorResponse; + }; }; export type get_ImageList = { method: "GET"; @@ -1171,6 +1262,7 @@ export namespace Endpoints { query: Partial<{ all: boolean; filters: string; "shared-size": boolean; digests: boolean }>; }; response: Array; + responses: { 200: Array; 500: Schemas.ErrorResponse }; }; export type post_ImageBuild = { method: "POST"; @@ -1208,6 +1300,7 @@ export namespace Endpoints { body: string; }; response: unknown; + responses: { 200: unknown; 400: Schemas.ErrorResponse; 500: Schemas.ErrorResponse }; }; export type post_BuildPrune = { method: "POST"; @@ -1217,6 +1310,7 @@ export namespace Endpoints { query: Partial<{ "keep-storage": number; all: boolean; filters: string }>; }; response: Partial<{ CachesDeleted: Array; SpaceReclaimed: number }>; + responses: { 200: Partial<{ CachesDeleted: Array; SpaceReclaimed: number }>; 500: Schemas.ErrorResponse }; }; export type post_ImageCreate = { method: "POST"; @@ -1237,6 +1331,7 @@ export namespace Endpoints { body: string; }; response: unknown; + responses: { 200: unknown; 404: Schemas.ErrorResponse; 500: Schemas.ErrorResponse }; }; export type get_ImageInspect = { method: "GET"; @@ -1246,6 +1341,7 @@ export namespace Endpoints { path: { name: string }; }; response: Schemas.ImageInspect; + responses: { 200: Schemas.ImageInspect; 404: Schemas.ErrorResponse; 500: Schemas.ErrorResponse }; }; export type get_ImageHistory = { method: "GET"; @@ -1262,6 +1358,18 @@ export namespace Endpoints { Size: number; Comment: string; }>; + responses: { + 200: Array<{ + Id: string; + Created: number; + CreatedBy: string; + Tags: Array; + Size: number; + Comment: string; + }>; + 404: Schemas.ErrorResponse; + 500: Schemas.ErrorResponse; + }; }; export type post_ImagePush = { method: "POST"; @@ -1273,6 +1381,7 @@ export namespace Endpoints { header: { "X-Registry-Auth": string }; }; response: unknown; + responses: { 200: unknown; 404: Schemas.ErrorResponse; 500: Schemas.ErrorResponse }; }; export type post_ImageTag = { method: "POST"; @@ -1283,6 +1392,13 @@ export namespace Endpoints { path: { name: string }; }; response: unknown; + responses: { + 201: unknown; + 400: Schemas.ErrorResponse; + 404: Schemas.ErrorResponse; + 409: Schemas.ErrorResponse; + 500: Schemas.ErrorResponse; + }; }; export type delete_ImageDelete = { method: "DELETE"; @@ -1293,6 +1409,12 @@ export namespace Endpoints { path: { name: string }; }; response: Array; + responses: { + 200: Array; + 404: Schemas.ErrorResponse; + 409: Schemas.ErrorResponse; + 500: Schemas.ErrorResponse; + }; }; export type get_ImageSearch = { method: "GET"; @@ -1304,6 +1426,12 @@ export namespace Endpoints { response: Array< Partial<{ description: string; is_official: boolean; is_automated: boolean; name: string; star_count: number }> >; + responses: { + 200: Array< + Partial<{ description: string; is_official: boolean; is_automated: boolean; name: string; star_count: number }> + >; + 500: Schemas.ErrorResponse; + }; }; export type post_ImagePrune = { method: "POST"; @@ -1313,6 +1441,10 @@ export namespace Endpoints { query: Partial<{ filters: string }>; }; response: Partial<{ ImagesDeleted: Array; SpaceReclaimed: number }>; + responses: { + 200: Partial<{ ImagesDeleted: Array; SpaceReclaimed: number }>; + 500: Schemas.ErrorResponse; + }; }; export type post_SystemAuth = { method: "POST"; @@ -1321,7 +1453,13 @@ export namespace Endpoints { parameters: { body: Schemas.AuthConfig; }; - response: unknown; + response: { Status: string; IdentityToken?: string | undefined }; + responses: { + 200: { Status: string; IdentityToken?: string | undefined }; + 204: unknown; + 401: Schemas.ErrorResponse; + 500: Schemas.ErrorResponse; + }; }; export type get_SystemInfo = { method: "GET"; @@ -1329,6 +1467,7 @@ export namespace Endpoints { requestFormat: "json"; parameters: never; response: Schemas.SystemInfo; + responses: { 200: Schemas.SystemInfo; 500: Schemas.ErrorResponse }; }; export type get_SystemVersion = { method: "GET"; @@ -1336,6 +1475,7 @@ export namespace Endpoints { requestFormat: "json"; parameters: never; response: Schemas.SystemVersion; + responses: { 200: Schemas.SystemVersion; 500: Schemas.ErrorResponse }; }; export type get_SystemPing = { method: "GET"; @@ -1343,6 +1483,7 @@ export namespace Endpoints { requestFormat: "json"; parameters: never; response: unknown; + responses: { 200: unknown; 500: unknown }; responseHeaders: { swarm: "inactive" | "pending" | "error" | "locked" | "active/worker" | "active/manager"; "docker-experimental": boolean; @@ -1358,6 +1499,7 @@ export namespace Endpoints { requestFormat: "json"; parameters: never; response: unknown; + responses: { 200: unknown; 500: unknown }; responseHeaders: { swarm: "inactive" | "pending" | "error" | "locked" | "active/worker" | "active/manager"; "docker-experimental": boolean; @@ -1385,6 +1527,7 @@ export namespace Endpoints { body: Schemas.ContainerConfig; }; response: Schemas.IdResponse; + responses: { 201: Schemas.IdResponse; 404: Schemas.ErrorResponse; 500: Schemas.ErrorResponse }; }; export type get_SystemEvents = { method: "GET"; @@ -1394,6 +1537,7 @@ export namespace Endpoints { query: Partial<{ since: string; until: string; filters: string }>; }; response: Schemas.EventMessage; + responses: { 200: Schemas.EventMessage; 400: Schemas.ErrorResponse; 500: Schemas.ErrorResponse }; }; export type get_SystemDataUsage = { method: "GET"; @@ -1409,6 +1553,16 @@ export namespace Endpoints { Volumes: Array; BuildCache: Array; }>; + responses: { + 200: Partial<{ + LayersSize: number; + Images: Array; + Containers: Array; + Volumes: Array; + BuildCache: Array; + }>; + 500: Schemas.ErrorResponse; + }; }; export type get_ImageGet = { method: "GET"; @@ -1418,6 +1572,7 @@ export namespace Endpoints { path: { name: string }; }; response: unknown; + responses: { 200: unknown; 500: unknown }; }; export type get_ImageGetAll = { method: "GET"; @@ -1427,6 +1582,7 @@ export namespace Endpoints { query: Partial<{ names: Array }>; }; response: unknown; + responses: { 200: unknown; 500: unknown }; }; export type post_ImageLoad = { method: "POST"; @@ -1436,6 +1592,7 @@ export namespace Endpoints { query: Partial<{ quiet: boolean }>; }; response: unknown; + responses: { 200: unknown; 500: Schemas.ErrorResponse }; }; export type post_ContainerExec = { method: "POST"; @@ -1459,6 +1616,12 @@ export namespace Endpoints { }>; }; response: Schemas.IdResponse; + responses: { + 201: Schemas.IdResponse; + 404: Schemas.ErrorResponse; + 409: Schemas.ErrorResponse; + 500: Schemas.ErrorResponse; + }; }; export type post_ExecStart = { method: "POST"; @@ -1470,6 +1633,7 @@ export namespace Endpoints { body: Partial<{ Detach: boolean; Tty: boolean; ConsoleSize: Array | null }>; }; response: unknown; + responses: { 200: unknown; 404: unknown; 409: unknown }; }; export type post_ExecResize = { method: "POST"; @@ -1480,6 +1644,7 @@ export namespace Endpoints { path: { id: string }; }; response: unknown; + responses: { 200: unknown; 400: Schemas.ErrorResponse; 404: Schemas.ErrorResponse; 500: Schemas.ErrorResponse }; }; export type get_ExecInspect = { method: "GET"; @@ -1501,6 +1666,23 @@ export namespace Endpoints { ContainerID: string; Pid: number; }>; + responses: { + 200: Partial<{ + CanRemove: boolean; + DetachKeys: string; + ID: string; + Running: boolean; + ExitCode: number; + ProcessConfig: Schemas.ProcessConfig; + OpenStdin: boolean; + OpenStderr: boolean; + OpenStdout: boolean; + ContainerID: string; + Pid: number; + }>; + 404: Schemas.ErrorResponse; + 500: Schemas.ErrorResponse; + }; }; export type get_VolumeList = { method: "GET"; @@ -1510,6 +1692,7 @@ export namespace Endpoints { query: Partial<{ filters: string }>; }; response: Schemas.VolumeListResponse; + responses: { 200: Schemas.VolumeListResponse; 500: Schemas.ErrorResponse }; }; export type post_VolumeCreate = { method: "POST"; @@ -1519,6 +1702,7 @@ export namespace Endpoints { body: Schemas.VolumeCreateOptions; }; response: Schemas.Volume; + responses: { 201: Schemas.Volume; 500: Schemas.ErrorResponse }; }; export type get_VolumeInspect = { method: "GET"; @@ -1528,6 +1712,7 @@ export namespace Endpoints { path: { name: string }; }; response: Schemas.Volume; + responses: { 200: Schemas.Volume; 404: Schemas.ErrorResponse; 500: Schemas.ErrorResponse }; }; export type put_VolumeUpdate = { method: "PUT"; @@ -1540,6 +1725,13 @@ export namespace Endpoints { body: Partial<{ Spec: Schemas.ClusterVolumeSpec }>; }; response: unknown; + responses: { + 200: unknown; + 400: Schemas.ErrorResponse; + 404: Schemas.ErrorResponse; + 500: Schemas.ErrorResponse; + 503: Schemas.ErrorResponse; + }; }; export type delete_VolumeDelete = { method: "DELETE"; @@ -1550,6 +1742,7 @@ export namespace Endpoints { path: { name: string }; }; response: unknown; + responses: { 204: unknown; 404: Schemas.ErrorResponse; 409: Schemas.ErrorResponse; 500: Schemas.ErrorResponse }; }; export type post_VolumePrune = { method: "POST"; @@ -1559,6 +1752,7 @@ export namespace Endpoints { query: Partial<{ filters: string }>; }; response: Partial<{ VolumesDeleted: Array; SpaceReclaimed: number }>; + responses: { 200: Partial<{ VolumesDeleted: Array; SpaceReclaimed: number }>; 500: Schemas.ErrorResponse }; }; export type get_NetworkList = { method: "GET"; @@ -1568,6 +1762,7 @@ export namespace Endpoints { query: Partial<{ filters: string }>; }; response: Array; + responses: { 200: Array; 500: Schemas.ErrorResponse }; }; export type get_NetworkInspect = { method: "GET"; @@ -1578,6 +1773,7 @@ export namespace Endpoints { path: { id: string }; }; response: Schemas.Network; + responses: { 200: Schemas.Network; 404: Schemas.ErrorResponse; 500: Schemas.ErrorResponse }; }; export type delete_NetworkDelete = { method: "DELETE"; @@ -1587,6 +1783,7 @@ export namespace Endpoints { path: { id: string }; }; response: unknown; + responses: { 204: unknown; 403: Schemas.ErrorResponse; 404: Schemas.ErrorResponse; 500: Schemas.ErrorResponse }; }; export type post_NetworkCreate = { method: "POST"; @@ -1607,6 +1804,12 @@ export namespace Endpoints { }; }; response: Partial<{ Id: string; Warning: string }>; + responses: { + 201: Partial<{ Id: string; Warning: string }>; + 403: Schemas.ErrorResponse; + 404: Schemas.ErrorResponse; + 500: Schemas.ErrorResponse; + }; }; export type post_NetworkConnect = { method: "POST"; @@ -1618,6 +1821,7 @@ export namespace Endpoints { body: Partial<{ Container: string; EndpointConfig: Schemas.EndpointSettings }>; }; response: unknown; + responses: { 200: unknown; 403: Schemas.ErrorResponse; 404: Schemas.ErrorResponse; 500: Schemas.ErrorResponse }; }; export type post_NetworkDisconnect = { method: "POST"; @@ -1629,6 +1833,7 @@ export namespace Endpoints { body: Partial<{ Container: string; Force: boolean }>; }; response: unknown; + responses: { 200: unknown; 403: Schemas.ErrorResponse; 404: Schemas.ErrorResponse; 500: Schemas.ErrorResponse }; }; export type post_NetworkPrune = { method: "POST"; @@ -1638,6 +1843,7 @@ export namespace Endpoints { query: Partial<{ filters: string }>; }; response: Partial<{ NetworksDeleted: Array }>; + responses: { 200: Partial<{ NetworksDeleted: Array }>; 500: Schemas.ErrorResponse }; }; export type get_PluginList = { method: "GET"; @@ -1647,6 +1853,7 @@ export namespace Endpoints { query: Partial<{ filters: string }>; }; response: Array; + responses: { 200: Array; 500: Schemas.ErrorResponse }; }; export type get_GetPluginPrivileges = { method: "GET"; @@ -1656,6 +1863,7 @@ export namespace Endpoints { query: { remote: string }; }; response: Array; + responses: { 200: Array; 500: Schemas.ErrorResponse }; }; export type post_PluginPull = { method: "POST"; @@ -1668,6 +1876,7 @@ export namespace Endpoints { body: Array; }; response: unknown; + responses: { 204: unknown; 500: Schemas.ErrorResponse }; }; export type get_PluginInspect = { method: "GET"; @@ -1677,6 +1886,7 @@ export namespace Endpoints { path: { name: string }; }; response: Schemas.Plugin; + responses: { 200: Schemas.Plugin; 404: Schemas.ErrorResponse; 500: Schemas.ErrorResponse }; }; export type delete_PluginDelete = { method: "DELETE"; @@ -1687,6 +1897,7 @@ export namespace Endpoints { path: { name: string }; }; response: Schemas.Plugin; + responses: { 200: Schemas.Plugin; 404: Schemas.ErrorResponse; 500: Schemas.ErrorResponse }; }; export type post_PluginEnable = { method: "POST"; @@ -1697,6 +1908,7 @@ export namespace Endpoints { path: { name: string }; }; response: unknown; + responses: { 200: unknown; 404: Schemas.ErrorResponse; 500: Schemas.ErrorResponse }; }; export type post_PluginDisable = { method: "POST"; @@ -1707,6 +1919,7 @@ export namespace Endpoints { path: { name: string }; }; response: unknown; + responses: { 200: unknown; 404: Schemas.ErrorResponse; 500: Schemas.ErrorResponse }; }; export type post_PluginUpgrade = { method: "POST"; @@ -1719,6 +1932,7 @@ export namespace Endpoints { body: Array; }; response: unknown; + responses: { 204: unknown; 404: Schemas.ErrorResponse; 500: Schemas.ErrorResponse }; }; export type post_PluginCreate = { method: "POST"; @@ -1728,6 +1942,7 @@ export namespace Endpoints { query: { name: string }; }; response: unknown; + responses: { 204: unknown; 500: Schemas.ErrorResponse }; }; export type post_PluginPush = { method: "POST"; @@ -1737,6 +1952,7 @@ export namespace Endpoints { path: { name: string }; }; response: unknown; + responses: { 200: unknown; 404: Schemas.ErrorResponse; 500: Schemas.ErrorResponse }; }; export type post_PluginSet = { method: "POST"; @@ -1748,6 +1964,7 @@ export namespace Endpoints { body: Array; }; response: unknown; + responses: { 204: unknown; 404: Schemas.ErrorResponse; 500: Schemas.ErrorResponse }; }; export type get_NodeList = { method: "GET"; @@ -1757,6 +1974,7 @@ export namespace Endpoints { query: Partial<{ filters: string }>; }; response: Array; + responses: { 200: Array; 500: Schemas.ErrorResponse; 503: Schemas.ErrorResponse }; }; export type get_NodeInspect = { method: "GET"; @@ -1766,6 +1984,12 @@ export namespace Endpoints { path: { id: string }; }; response: Schemas.Node; + responses: { + 200: Schemas.Node; + 404: Schemas.ErrorResponse; + 500: Schemas.ErrorResponse; + 503: Schemas.ErrorResponse; + }; }; export type delete_NodeDelete = { method: "DELETE"; @@ -1776,6 +2000,7 @@ export namespace Endpoints { path: { id: string }; }; response: unknown; + responses: { 200: unknown; 404: Schemas.ErrorResponse; 500: Schemas.ErrorResponse; 503: Schemas.ErrorResponse }; }; export type post_NodeUpdate = { method: "POST"; @@ -1788,6 +2013,13 @@ export namespace Endpoints { body: Schemas.NodeSpec; }; response: unknown; + responses: { + 200: unknown; + 400: Schemas.ErrorResponse; + 404: Schemas.ErrorResponse; + 500: Schemas.ErrorResponse; + 503: Schemas.ErrorResponse; + }; }; export type get_SwarmInspect = { method: "GET"; @@ -1795,6 +2027,12 @@ export namespace Endpoints { requestFormat: "json"; parameters: never; response: Schemas.Swarm; + responses: { + 200: Schemas.Swarm; + 404: Schemas.ErrorResponse; + 500: Schemas.ErrorResponse; + 503: Schemas.ErrorResponse; + }; }; export type post_SwarmInit = { method: "POST"; @@ -1813,6 +2051,7 @@ export namespace Endpoints { }>; }; response: string; + responses: { 200: string; 400: Schemas.ErrorResponse; 500: Schemas.ErrorResponse; 503: Schemas.ErrorResponse }; }; export type post_SwarmJoin = { method: "POST"; @@ -1828,6 +2067,7 @@ export namespace Endpoints { }>; }; response: unknown; + responses: { 200: unknown; 400: Schemas.ErrorResponse; 500: Schemas.ErrorResponse; 503: Schemas.ErrorResponse }; }; export type post_SwarmLeave = { method: "POST"; @@ -1837,6 +2077,7 @@ export namespace Endpoints { query: Partial<{ force: boolean }>; }; response: unknown; + responses: { 200: unknown; 500: Schemas.ErrorResponse; 503: Schemas.ErrorResponse }; }; export type post_SwarmUpdate = { method: "POST"; @@ -1853,6 +2094,7 @@ export namespace Endpoints { body: Schemas.SwarmSpec; }; response: unknown; + responses: { 200: unknown; 400: Schemas.ErrorResponse; 500: Schemas.ErrorResponse; 503: Schemas.ErrorResponse }; }; export type get_SwarmUnlockkey = { method: "GET"; @@ -1860,6 +2102,7 @@ export namespace Endpoints { requestFormat: "json"; parameters: never; response: Partial<{ UnlockKey: string }>; + responses: { 200: Partial<{ UnlockKey: string }>; 500: Schemas.ErrorResponse; 503: Schemas.ErrorResponse }; }; export type post_SwarmUnlock = { method: "POST"; @@ -1869,6 +2112,7 @@ export namespace Endpoints { body: Partial<{ UnlockKey: string }>; }; response: unknown; + responses: { 200: unknown; 500: Schemas.ErrorResponse; 503: Schemas.ErrorResponse }; }; export type get_ServiceList = { method: "GET"; @@ -1878,6 +2122,7 @@ export namespace Endpoints { query: Partial<{ filters: string; status: boolean }>; }; response: Array; + responses: { 200: Array; 500: Schemas.ErrorResponse; 503: Schemas.ErrorResponse }; }; export type post_ServiceCreate = { method: "POST"; @@ -1888,6 +2133,14 @@ export namespace Endpoints { body: Schemas.ServiceSpec & Record; }; response: Partial<{ ID: string; Warning: string }>; + responses: { + 201: Partial<{ ID: string; Warning: string }>; + 400: Schemas.ErrorResponse; + 403: Schemas.ErrorResponse; + 409: Schemas.ErrorResponse; + 500: Schemas.ErrorResponse; + 503: Schemas.ErrorResponse; + }; }; export type get_ServiceInspect = { method: "GET"; @@ -1898,6 +2151,12 @@ export namespace Endpoints { path: { id: string }; }; response: Schemas.Service; + responses: { + 200: Schemas.Service; + 404: Schemas.ErrorResponse; + 500: Schemas.ErrorResponse; + 503: Schemas.ErrorResponse; + }; }; export type delete_ServiceDelete = { method: "DELETE"; @@ -1907,6 +2166,7 @@ export namespace Endpoints { path: { id: string }; }; response: unknown; + responses: { 200: unknown; 404: Schemas.ErrorResponse; 500: Schemas.ErrorResponse; 503: Schemas.ErrorResponse }; }; export type post_ServiceUpdate = { method: "POST"; @@ -1923,6 +2183,13 @@ export namespace Endpoints { body: Schemas.ServiceSpec & Record; }; response: Schemas.ServiceUpdateResponse; + responses: { + 200: Schemas.ServiceUpdateResponse; + 400: Schemas.ErrorResponse; + 404: Schemas.ErrorResponse; + 500: Schemas.ErrorResponse; + 503: Schemas.ErrorResponse; + }; }; export type get_ServiceLogs = { method: "GET"; @@ -1941,6 +2208,7 @@ export namespace Endpoints { path: { id: string }; }; response: unknown; + responses: { 200: unknown; 404: unknown; 500: unknown; 503: unknown }; }; export type get_TaskList = { method: "GET"; @@ -1950,6 +2218,7 @@ export namespace Endpoints { query: Partial<{ filters: string }>; }; response: Array; + responses: { 200: Array; 500: Schemas.ErrorResponse; 503: Schemas.ErrorResponse }; }; export type get_TaskInspect = { method: "GET"; @@ -1959,6 +2228,12 @@ export namespace Endpoints { path: { id: string }; }; response: Schemas.Task; + responses: { + 200: Schemas.Task; + 404: Schemas.ErrorResponse; + 500: Schemas.ErrorResponse; + 503: Schemas.ErrorResponse; + }; }; export type get_TaskLogs = { method: "GET"; @@ -1977,6 +2252,7 @@ export namespace Endpoints { path: { id: string }; }; response: unknown; + responses: { 200: unknown; 404: unknown; 500: unknown; 503: unknown }; }; export type get_SecretList = { method: "GET"; @@ -1986,6 +2262,7 @@ export namespace Endpoints { query: Partial<{ filters: string }>; }; response: Array; + responses: { 200: Array; 500: Schemas.ErrorResponse; 503: Schemas.ErrorResponse }; }; export type post_SecretCreate = { method: "POST"; @@ -1995,6 +2272,12 @@ export namespace Endpoints { body: Schemas.SecretSpec & Record; }; response: Schemas.IdResponse; + responses: { + 201: Schemas.IdResponse; + 409: Schemas.ErrorResponse; + 500: Schemas.ErrorResponse; + 503: Schemas.ErrorResponse; + }; }; export type get_SecretInspect = { method: "GET"; @@ -2004,6 +2287,12 @@ export namespace Endpoints { path: { id: string }; }; response: Schemas.Secret; + responses: { + 200: Schemas.Secret; + 404: Schemas.ErrorResponse; + 500: Schemas.ErrorResponse; + 503: Schemas.ErrorResponse; + }; }; export type delete_SecretDelete = { method: "DELETE"; @@ -2013,6 +2302,7 @@ export namespace Endpoints { path: { id: string }; }; response: unknown; + responses: { 204: unknown; 404: Schemas.ErrorResponse; 500: Schemas.ErrorResponse; 503: Schemas.ErrorResponse }; }; export type post_SecretUpdate = { method: "POST"; @@ -2025,6 +2315,13 @@ export namespace Endpoints { body: Schemas.SecretSpec; }; response: unknown; + responses: { + 200: unknown; + 400: Schemas.ErrorResponse; + 404: Schemas.ErrorResponse; + 500: Schemas.ErrorResponse; + 503: Schemas.ErrorResponse; + }; }; export type get_ConfigList = { method: "GET"; @@ -2034,6 +2331,7 @@ export namespace Endpoints { query: Partial<{ filters: string }>; }; response: Array; + responses: { 200: Array; 500: Schemas.ErrorResponse; 503: Schemas.ErrorResponse }; }; export type post_ConfigCreate = { method: "POST"; @@ -2043,6 +2341,12 @@ export namespace Endpoints { body: Schemas.ConfigSpec & Record; }; response: Schemas.IdResponse; + responses: { + 201: Schemas.IdResponse; + 409: Schemas.ErrorResponse; + 500: Schemas.ErrorResponse; + 503: Schemas.ErrorResponse; + }; }; export type get_ConfigInspect = { method: "GET"; @@ -2052,6 +2356,12 @@ export namespace Endpoints { path: { id: string }; }; response: Schemas.Config; + responses: { + 200: Schemas.Config; + 404: Schemas.ErrorResponse; + 500: Schemas.ErrorResponse; + 503: Schemas.ErrorResponse; + }; }; export type delete_ConfigDelete = { method: "DELETE"; @@ -2061,6 +2371,7 @@ export namespace Endpoints { path: { id: string }; }; response: unknown; + responses: { 204: unknown; 404: Schemas.ErrorResponse; 500: Schemas.ErrorResponse; 503: Schemas.ErrorResponse }; }; export type post_ConfigUpdate = { method: "POST"; @@ -2073,6 +2384,13 @@ export namespace Endpoints { body: Schemas.ConfigSpec; }; response: unknown; + responses: { + 200: unknown; + 400: Schemas.ErrorResponse; + 404: Schemas.ErrorResponse; + 500: Schemas.ErrorResponse; + 503: Schemas.ErrorResponse; + }; }; export type get_DistributionInspect = { method: "GET"; @@ -2082,6 +2400,7 @@ export namespace Endpoints { path: { name: string }; }; response: Schemas.DistributionInspect; + responses: { 200: Schemas.DistributionInspect; 401: Schemas.ErrorResponse; 500: Schemas.ErrorResponse }; }; export type post_Session = { method: "POST"; @@ -2089,6 +2408,7 @@ export namespace Endpoints { requestFormat: "json"; parameters: never; response: unknown; + responses: { 101: unknown; 400: unknown; 500: unknown }; }; // @@ -2241,6 +2561,7 @@ type RequestFormat = "json" | "form-data" | "form-url" | "binary" | "text"; export type DefaultEndpoint = { parameters?: EndpointParameters | undefined; response: unknown; + responses?: Record; responseHeaders?: Record; }; @@ -2256,11 +2577,97 @@ export type Endpoint = { areParametersRequired: boolean; }; response: TConfig["response"]; + responses?: TConfig["responses"]; responseHeaders?: TConfig["responseHeaders"]; }; export type Fetcher = (method: Method, url: string, parameters?: EndpointParameters | undefined) => Promise; +// Status code type for success responses +export type SuccessStatusCode = + | 200 + | 201 + | 202 + | 203 + | 204 + | 205 + | 206 + | 207 + | 208 + | 226 + | 300 + | 301 + | 302 + | 303 + | 304 + | 305 + | 306 + | 307 + | 308; + +// Error handling types +export type TypedApiResponse< + TSuccess, + TAllResponses extends Record = {}, +> = keyof TAllResponses extends never + ? Omit & { + ok: true; + status: number; + data: TSuccess; + json: () => Promise; + } + : { + [K in keyof TAllResponses]: K extends string + ? K extends `${infer TStatusCode extends number}` + ? TStatusCode extends SuccessStatusCode + ? Omit & { + ok: true; + status: TStatusCode; + data: TSuccess; + json: () => Promise; + } + : Omit & { + ok: false; + status: TStatusCode; + data: TAllResponses[K]; + json: () => Promise; + } + : never + : K extends number + ? K extends SuccessStatusCode + ? Omit & { + ok: true; + status: K; + data: TSuccess; + json: () => Promise; + } + : Omit & { + ok: false; + status: K; + data: TAllResponses[K]; + json: () => Promise; + } + : never; + }[keyof TAllResponses]; + +export type SafeApiResponse = TEndpoint extends { response: infer TSuccess; responses: infer TResponses } + ? TResponses extends Record + ? TypedApiResponse + : Omit & { + ok: true; + status: number; + data: TSuccess; + json: () => Promise; + } + : TEndpoint extends { response: infer TSuccess } + ? Omit & { + ok: true; + status: number; + data: TSuccess; + json: () => Promise; + } + : never; + type RequiredKeys = { [P in keyof T]-?: undefined extends T[P] ? never : P; }[keyof T]; @@ -2291,55 +2698,227 @@ export class ApiClient { // get( path: Path, - ...params: MaybeOptionalArg - ): Promise { - return this.fetcher("get", this.baseUrl + path, params[0]).then((response) => - this.parseResponse(response), - ) as Promise; + ...params: MaybeOptionalArg + ): Promise; + + get( + path: Path, + ...params: MaybeOptionalArg + ): Promise>; + + get( + path: Path, + ...params: MaybeOptionalArg + ): Promise { + const requestParams = params[0]; + const withResponse = requestParams?.withResponse; + + // Remove withResponse from params before passing to fetcher + const { withResponse: _, ...fetchParams } = requestParams || {}; + + if (withResponse) { + return this.fetcher("get", this.baseUrl + path, Object.keys(fetchParams).length ? fetchParams : undefined).then( + async (response) => { + // Parse the response data + const data = await this.parseResponse(response); + + // Override properties while keeping the original Response object + const typedResponse = Object.assign(response, { + ok: response.ok, + status: response.status, + data: data, + json: () => Promise.resolve(data), + }); + return typedResponse; + }, + ); + } else { + return this.fetcher("get", this.baseUrl + path, requestParams).then((response) => + this.parseResponse(response), + ) as Promise; + } } // // post( path: Path, - ...params: MaybeOptionalArg - ): Promise { - return this.fetcher("post", this.baseUrl + path, params[0]).then((response) => - this.parseResponse(response), - ) as Promise; + ...params: MaybeOptionalArg + ): Promise; + + post( + path: Path, + ...params: MaybeOptionalArg + ): Promise>; + + post( + path: Path, + ...params: MaybeOptionalArg + ): Promise { + const requestParams = params[0]; + const withResponse = requestParams?.withResponse; + + // Remove withResponse from params before passing to fetcher + const { withResponse: _, ...fetchParams } = requestParams || {}; + + if (withResponse) { + return this.fetcher("post", this.baseUrl + path, Object.keys(fetchParams).length ? fetchParams : undefined).then( + async (response) => { + // Parse the response data + const data = await this.parseResponse(response); + + // Override properties while keeping the original Response object + const typedResponse = Object.assign(response, { + ok: response.ok, + status: response.status, + data: data, + json: () => Promise.resolve(data), + }); + return typedResponse; + }, + ); + } else { + return this.fetcher("post", this.baseUrl + path, requestParams).then((response) => + this.parseResponse(response), + ) as Promise; + } } // // delete( path: Path, - ...params: MaybeOptionalArg - ): Promise { - return this.fetcher("delete", this.baseUrl + path, params[0]).then((response) => - this.parseResponse(response), - ) as Promise; + ...params: MaybeOptionalArg + ): Promise; + + delete( + path: Path, + ...params: MaybeOptionalArg + ): Promise>; + + delete( + path: Path, + ...params: MaybeOptionalArg + ): Promise { + const requestParams = params[0]; + const withResponse = requestParams?.withResponse; + + // Remove withResponse from params before passing to fetcher + const { withResponse: _, ...fetchParams } = requestParams || {}; + + if (withResponse) { + return this.fetcher( + "delete", + this.baseUrl + path, + Object.keys(fetchParams).length ? fetchParams : undefined, + ).then(async (response) => { + // Parse the response data + const data = await this.parseResponse(response); + + // Override properties while keeping the original Response object + const typedResponse = Object.assign(response, { + ok: response.ok, + status: response.status, + data: data, + json: () => Promise.resolve(data), + }); + return typedResponse; + }); + } else { + return this.fetcher("delete", this.baseUrl + path, requestParams).then((response) => + this.parseResponse(response), + ) as Promise; + } } // // put( path: Path, - ...params: MaybeOptionalArg - ): Promise { - return this.fetcher("put", this.baseUrl + path, params[0]).then((response) => - this.parseResponse(response), - ) as Promise; + ...params: MaybeOptionalArg + ): Promise; + + put( + path: Path, + ...params: MaybeOptionalArg + ): Promise>; + + put( + path: Path, + ...params: MaybeOptionalArg + ): Promise { + const requestParams = params[0]; + const withResponse = requestParams?.withResponse; + + // Remove withResponse from params before passing to fetcher + const { withResponse: _, ...fetchParams } = requestParams || {}; + + if (withResponse) { + return this.fetcher("put", this.baseUrl + path, Object.keys(fetchParams).length ? fetchParams : undefined).then( + async (response) => { + // Parse the response data + const data = await this.parseResponse(response); + + // Override properties while keeping the original Response object + const typedResponse = Object.assign(response, { + ok: response.ok, + status: response.status, + data: data, + json: () => Promise.resolve(data), + }); + return typedResponse; + }, + ); + } else { + return this.fetcher("put", this.baseUrl + path, requestParams).then((response) => + this.parseResponse(response), + ) as Promise; + } } // // head( path: Path, - ...params: MaybeOptionalArg - ): Promise { - return this.fetcher("head", this.baseUrl + path, params[0]).then((response) => - this.parseResponse(response), - ) as Promise; + ...params: MaybeOptionalArg + ): Promise; + + head( + path: Path, + ...params: MaybeOptionalArg + ): Promise>; + + head( + path: Path, + ...params: MaybeOptionalArg + ): Promise { + const requestParams = params[0]; + const withResponse = requestParams?.withResponse; + + // Remove withResponse from params before passing to fetcher + const { withResponse: _, ...fetchParams } = requestParams || {}; + + if (withResponse) { + return this.fetcher("head", this.baseUrl + path, Object.keys(fetchParams).length ? fetchParams : undefined).then( + async (response) => { + // Parse the response data + const data = await this.parseResponse(response); + + // Override properties while keeping the original Response object + const typedResponse = Object.assign(response, { + ok: response.ok, + status: response.status, + data: data, + json: () => Promise.resolve(data), + }); + return typedResponse; + }, + ); + } else { + return this.fetcher("head", this.baseUrl + path, requestParams).then((response) => + this.parseResponse(response), + ) as Promise; + } } // @@ -2378,6 +2957,21 @@ export function createApiClient(fetcher: Fetcher, baseUrl?: string) { api.get("/users").then((users) => console.log(users)); api.post("/users", { body: { name: "John" } }).then((user) => console.log(user)); api.put("/users/:id", { path: { id: 1 }, body: { name: "John" } }).then((user) => console.log(user)); + + // With error handling + const result = await api.get("/users/{id}", { path: { id: "123" }, withResponse: true }); + if (result.ok) { + // Access data directly + const user = result.data; + console.log(user); + + // Or use the json() method for compatibility + const userFromJson = await result.json(); + console.log(userFromJson); + } else { + const error = result.data; + console.error(`Error ${result.status}:`, error); + } */ // ; @@ -1764,6 +1769,13 @@ export const post_ContainerCreate = t.type({ ]), }), response: ContainerCreateResponse, + responses: t.type({ + "201": ContainerCreateResponse, + "400": ErrorResponse, + "404": ErrorResponse, + "409": ErrorResponse, + "500": ErrorResponse, + }), }); export type get_ContainerInspect = t.TypeOf; @@ -1806,6 +1818,37 @@ export const get_ContainerInspect = t.type({ Config: t.union([t.undefined, ContainerConfig]), NetworkSettings: t.union([t.undefined, NetworkSettings]), }), + responses: t.type({ + "200": t.type({ + Id: t.union([t.undefined, t.string]), + Created: t.union([t.undefined, t.string]), + Path: t.union([t.undefined, t.string]), + Args: t.union([t.undefined, t.array(t.string)]), + State: t.union([t.undefined, ContainerState]), + Image: t.union([t.undefined, t.string]), + ResolvConfPath: t.union([t.undefined, t.string]), + HostnamePath: t.union([t.undefined, t.string]), + HostsPath: t.union([t.undefined, t.string]), + LogPath: t.union([t.undefined, t.string]), + Name: t.union([t.undefined, t.string]), + RestartCount: t.union([t.undefined, t.number]), + Driver: t.union([t.undefined, t.string]), + Platform: t.union([t.undefined, t.string]), + MountLabel: t.union([t.undefined, t.string]), + ProcessLabel: t.union([t.undefined, t.string]), + AppArmorProfile: t.union([t.undefined, t.string]), + ExecIDs: t.union([t.undefined, t.union([t.array(t.string), t.null])]), + HostConfig: t.union([t.undefined, HostConfig]), + GraphDriver: t.union([t.undefined, GraphDriverData]), + SizeRw: t.union([t.undefined, t.number]), + SizeRootFs: t.union([t.undefined, t.number]), + Mounts: t.union([t.undefined, t.array(MountPoint)]), + Config: t.union([t.undefined, ContainerConfig]), + NetworkSettings: t.union([t.undefined, NetworkSettings]), + }), + "404": ErrorResponse, + "500": ErrorResponse, + }), }); export type get_ContainerTop = t.TypeOf; @@ -1825,6 +1868,14 @@ export const get_ContainerTop = t.type({ Titles: t.union([t.undefined, t.array(t.string)]), Processes: t.union([t.undefined, t.array(t.array(t.string))]), }), + responses: t.type({ + "200": t.type({ + Titles: t.union([t.undefined, t.array(t.string)]), + Processes: t.union([t.undefined, t.array(t.array(t.string))]), + }), + "404": ErrorResponse, + "500": ErrorResponse, + }), }); export type get_ContainerLogs = t.TypeOf; @@ -1847,6 +1898,11 @@ export const get_ContainerLogs = t.type({ }), }), response: t.unknown, + responses: t.type({ + "200": t.unknown, + "404": t.unknown, + "500": t.unknown, + }), }); export type get_ContainerChanges = t.TypeOf; @@ -1860,6 +1916,11 @@ export const get_ContainerChanges = t.type({ }), }), response: t.array(FilesystemChange), + responses: t.type({ + "200": t.array(FilesystemChange), + "404": ErrorResponse, + "500": ErrorResponse, + }), }); export type get_ContainerExport = t.TypeOf; @@ -1873,6 +1934,11 @@ export const get_ContainerExport = t.type({ }), }), response: t.unknown, + responses: t.type({ + "200": t.unknown, + "404": t.unknown, + "500": t.unknown, + }), }); export type get_ContainerStats = t.TypeOf; @@ -1890,6 +1956,11 @@ export const get_ContainerStats = t.type({ }), }), response: t.record(t.string, t.unknown), + responses: t.type({ + "200": t.record(t.string, t.unknown), + "404": ErrorResponse, + "500": ErrorResponse, + }), }); export type post_ContainerResize = t.TypeOf; @@ -1907,6 +1978,11 @@ export const post_ContainerResize = t.type({ }), }), response: t.unknown, + responses: t.type({ + "200": t.unknown, + "404": t.unknown, + "500": t.unknown, + }), }); export type post_ContainerStart = t.TypeOf; @@ -1923,6 +1999,12 @@ export const post_ContainerStart = t.type({ }), }), response: t.unknown, + responses: t.type({ + "204": t.unknown, + "304": t.unknown, + "404": ErrorResponse, + "500": ErrorResponse, + }), }); export type post_ContainerStop = t.TypeOf; @@ -1940,6 +2022,12 @@ export const post_ContainerStop = t.type({ }), }), response: t.unknown, + responses: t.type({ + "204": t.unknown, + "304": t.unknown, + "404": ErrorResponse, + "500": ErrorResponse, + }), }); export type post_ContainerRestart = t.TypeOf; @@ -1957,6 +2045,11 @@ export const post_ContainerRestart = t.type({ }), }), response: t.unknown, + responses: t.type({ + "204": t.unknown, + "404": ErrorResponse, + "500": ErrorResponse, + }), }); export type post_ContainerKill = t.TypeOf; @@ -1973,6 +2066,12 @@ export const post_ContainerKill = t.type({ }), }), response: t.unknown, + responses: t.type({ + "204": t.unknown, + "404": ErrorResponse, + "409": ErrorResponse, + "500": ErrorResponse, + }), }); export type post_ContainerUpdate = t.TypeOf; @@ -1994,6 +2093,13 @@ export const post_ContainerUpdate = t.type({ response: t.type({ Warnings: t.union([t.undefined, t.array(t.string)]), }), + responses: t.type({ + "200": t.type({ + Warnings: t.union([t.undefined, t.array(t.string)]), + }), + "404": ErrorResponse, + "500": ErrorResponse, + }), }); export type post_ContainerRename = t.TypeOf; @@ -2010,6 +2116,12 @@ export const post_ContainerRename = t.type({ }), }), response: t.unknown, + responses: t.type({ + "204": t.unknown, + "404": ErrorResponse, + "409": ErrorResponse, + "500": ErrorResponse, + }), }); export type post_ContainerPause = t.TypeOf; @@ -2023,6 +2135,11 @@ export const post_ContainerPause = t.type({ }), }), response: t.unknown, + responses: t.type({ + "204": t.unknown, + "404": ErrorResponse, + "500": ErrorResponse, + }), }); export type post_ContainerUnpause = t.TypeOf; @@ -2036,6 +2153,11 @@ export const post_ContainerUnpause = t.type({ }), }), response: t.unknown, + responses: t.type({ + "204": t.unknown, + "404": ErrorResponse, + "500": ErrorResponse, + }), }); export type post_ContainerAttach = t.TypeOf; @@ -2057,6 +2179,13 @@ export const post_ContainerAttach = t.type({ }), }), response: t.unknown, + responses: t.type({ + "101": t.unknown, + "200": t.unknown, + "400": t.unknown, + "404": t.unknown, + "500": t.unknown, + }), }); export type get_ContainerAttachWebsocket = t.TypeOf; @@ -2078,6 +2207,13 @@ export const get_ContainerAttachWebsocket = t.type({ }), }), response: t.unknown, + responses: t.type({ + "101": t.unknown, + "200": t.unknown, + "400": ErrorResponse, + "404": ErrorResponse, + "500": ErrorResponse, + }), }); export type post_ContainerWait = t.TypeOf; @@ -2097,6 +2233,12 @@ export const post_ContainerWait = t.type({ }), }), response: ContainerWaitResponse, + responses: t.type({ + "200": ContainerWaitResponse, + "400": ErrorResponse, + "404": ErrorResponse, + "500": ErrorResponse, + }), }); export type delete_ContainerDelete = t.TypeOf; @@ -2115,6 +2257,13 @@ export const delete_ContainerDelete = t.type({ }), }), response: t.unknown, + responses: t.type({ + "204": t.unknown, + "400": ErrorResponse, + "404": ErrorResponse, + "409": ErrorResponse, + "500": ErrorResponse, + }), }); export type get_ContainerArchive = t.TypeOf; @@ -2131,6 +2280,12 @@ export const get_ContainerArchive = t.type({ }), }), response: t.unknown, + responses: t.type({ + "200": t.unknown, + "400": t.unknown, + "404": t.unknown, + "500": t.unknown, + }), }); export type put_PutContainerArchive = t.TypeOf; @@ -2150,6 +2305,13 @@ export const put_PutContainerArchive = t.type({ body: t.string, }), response: t.unknown, + responses: t.type({ + "200": t.unknown, + "400": ErrorResponse, + "403": ErrorResponse, + "404": ErrorResponse, + "500": ErrorResponse, + }), }); export type head_ContainerArchiveInfo = t.TypeOf; @@ -2166,6 +2328,12 @@ export const head_ContainerArchiveInfo = t.type({ }), }), response: t.unknown, + responses: t.type({ + "200": t.unknown, + "400": ErrorResponse, + "404": ErrorResponse, + "500": ErrorResponse, + }), responseHeaders: t.type({ "x-docker-container-path-stat": t.string, }), @@ -2185,6 +2353,13 @@ export const post_ContainerPrune = t.type({ ContainersDeleted: t.union([t.undefined, t.array(t.string)]), SpaceReclaimed: t.union([t.undefined, t.number]), }), + responses: t.type({ + "200": t.type({ + ContainersDeleted: t.union([t.undefined, t.array(t.string)]), + SpaceReclaimed: t.union([t.undefined, t.number]), + }), + "500": ErrorResponse, + }), }); export type get_ImageList = t.TypeOf; @@ -2201,6 +2376,10 @@ export const get_ImageList = t.type({ }), }), response: t.array(ImageSummary), + responses: t.type({ + "200": t.array(ImageSummary), + "500": ErrorResponse, + }), }); export type post_ImageBuild = t.TypeOf; @@ -2242,6 +2421,11 @@ export const post_ImageBuild = t.type({ body: t.string, }), response: t.unknown, + responses: t.type({ + "200": t.unknown, + "400": ErrorResponse, + "500": ErrorResponse, + }), }); export type post_BuildPrune = t.TypeOf; @@ -2260,6 +2444,13 @@ export const post_BuildPrune = t.type({ CachesDeleted: t.union([t.undefined, t.array(t.string)]), SpaceReclaimed: t.union([t.undefined, t.number]), }), + responses: t.type({ + "200": t.type({ + CachesDeleted: t.union([t.undefined, t.array(t.string)]), + SpaceReclaimed: t.union([t.undefined, t.number]), + }), + "500": ErrorResponse, + }), }); export type post_ImageCreate = t.TypeOf; @@ -2283,6 +2474,11 @@ export const post_ImageCreate = t.type({ body: t.string, }), response: t.unknown, + responses: t.type({ + "200": t.unknown, + "404": ErrorResponse, + "500": ErrorResponse, + }), }); export type get_ImageInspect = t.TypeOf; @@ -2296,6 +2492,11 @@ export const get_ImageInspect = t.type({ }), }), response: ImageInspect, + responses: t.type({ + "200": ImageInspect, + "404": ErrorResponse, + "500": ErrorResponse, + }), }); export type get_ImageHistory = t.TypeOf; @@ -2318,6 +2519,20 @@ export const get_ImageHistory = t.type({ Comment: t.string, }), ), + responses: t.type({ + "200": t.array( + t.type({ + Id: t.string, + Created: t.number, + CreatedBy: t.string, + Tags: t.array(t.string), + Size: t.number, + Comment: t.string, + }), + ), + "404": ErrorResponse, + "500": ErrorResponse, + }), }); export type post_ImagePush = t.TypeOf; @@ -2337,6 +2552,11 @@ export const post_ImagePush = t.type({ }), }), response: t.unknown, + responses: t.type({ + "200": t.unknown, + "404": ErrorResponse, + "500": ErrorResponse, + }), }); export type post_ImageTag = t.TypeOf; @@ -2354,6 +2574,13 @@ export const post_ImageTag = t.type({ }), }), response: t.unknown, + responses: t.type({ + "201": t.unknown, + "400": ErrorResponse, + "404": ErrorResponse, + "409": ErrorResponse, + "500": ErrorResponse, + }), }); export type delete_ImageDelete = t.TypeOf; @@ -2371,6 +2598,12 @@ export const delete_ImageDelete = t.type({ }), }), response: t.array(ImageDeleteResponseItem), + responses: t.type({ + "200": t.array(ImageDeleteResponseItem), + "404": ErrorResponse, + "409": ErrorResponse, + "500": ErrorResponse, + }), }); export type get_ImageSearch = t.TypeOf; @@ -2394,6 +2627,18 @@ export const get_ImageSearch = t.type({ star_count: t.union([t.undefined, t.number]), }), ), + responses: t.type({ + "200": t.array( + t.type({ + description: t.union([t.undefined, t.string]), + is_official: t.union([t.undefined, t.boolean]), + is_automated: t.union([t.undefined, t.boolean]), + name: t.union([t.undefined, t.string]), + star_count: t.union([t.undefined, t.number]), + }), + ), + "500": ErrorResponse, + }), }); export type post_ImagePrune = t.TypeOf; @@ -2410,6 +2655,13 @@ export const post_ImagePrune = t.type({ ImagesDeleted: t.union([t.undefined, t.array(ImageDeleteResponseItem)]), SpaceReclaimed: t.union([t.undefined, t.number]), }), + responses: t.type({ + "200": t.type({ + ImagesDeleted: t.union([t.undefined, t.array(ImageDeleteResponseItem)]), + SpaceReclaimed: t.union([t.undefined, t.number]), + }), + "500": ErrorResponse, + }), }); export type post_SystemAuth = t.TypeOf; @@ -2420,7 +2672,19 @@ export const post_SystemAuth = t.type({ parameters: t.type({ body: AuthConfig, }), - response: t.unknown, + response: t.type({ + Status: t.string, + IdentityToken: t.union([t.undefined, t.union([t.string, t.undefined])]), + }), + responses: t.type({ + "200": t.type({ + Status: t.string, + IdentityToken: t.union([t.undefined, t.union([t.string, t.undefined])]), + }), + "204": t.unknown, + "401": ErrorResponse, + "500": ErrorResponse, + }), }); export type get_SystemInfo = t.TypeOf; @@ -2430,6 +2694,10 @@ export const get_SystemInfo = t.type({ requestFormat: t.literal("json"), parameters: t.never, response: SystemInfo, + responses: t.type({ + "200": SystemInfo, + "500": ErrorResponse, + }), }); export type get_SystemVersion = t.TypeOf; @@ -2439,6 +2707,10 @@ export const get_SystemVersion = t.type({ requestFormat: t.literal("json"), parameters: t.never, response: SystemVersion, + responses: t.type({ + "200": SystemVersion, + "500": ErrorResponse, + }), }); export type get_SystemPing = t.TypeOf; @@ -2448,6 +2720,10 @@ export const get_SystemPing = t.type({ requestFormat: t.literal("json"), parameters: t.never, response: t.unknown, + responses: t.type({ + "200": t.unknown, + "500": t.unknown, + }), responseHeaders: t.type({ swarm: t.union([ t.literal("inactive"), @@ -2472,6 +2748,10 @@ export const head_SystemPingHead = t.type({ requestFormat: t.literal("json"), parameters: t.never, response: t.unknown, + responses: t.type({ + "200": t.unknown, + "500": t.unknown, + }), responseHeaders: t.type({ swarm: t.union([ t.literal("inactive"), @@ -2507,6 +2787,11 @@ export const post_ImageCommit = t.type({ body: ContainerConfig, }), response: IdResponse, + responses: t.type({ + "201": IdResponse, + "404": ErrorResponse, + "500": ErrorResponse, + }), }); export type get_SystemEvents = t.TypeOf; @@ -2522,6 +2807,11 @@ export const get_SystemEvents = t.type({ }), }), response: EventMessage, + responses: t.type({ + "200": EventMessage, + "400": ErrorResponse, + "500": ErrorResponse, + }), }); export type get_SystemDataUsage = t.TypeOf; @@ -2544,6 +2834,16 @@ export const get_SystemDataUsage = t.type({ Volumes: t.union([t.undefined, t.array(Volume)]), BuildCache: t.union([t.undefined, t.array(BuildCache)]), }), + responses: t.type({ + "200": t.type({ + LayersSize: t.union([t.undefined, t.number]), + Images: t.union([t.undefined, t.array(ImageSummary)]), + Containers: t.union([t.undefined, t.array(ContainerSummary)]), + Volumes: t.union([t.undefined, t.array(Volume)]), + BuildCache: t.union([t.undefined, t.array(BuildCache)]), + }), + "500": ErrorResponse, + }), }); export type get_ImageGet = t.TypeOf; @@ -2557,6 +2857,10 @@ export const get_ImageGet = t.type({ }), }), response: t.unknown, + responses: t.type({ + "200": t.unknown, + "500": t.unknown, + }), }); export type get_ImageGetAll = t.TypeOf; @@ -2570,6 +2874,10 @@ export const get_ImageGetAll = t.type({ }), }), response: t.unknown, + responses: t.type({ + "200": t.unknown, + "500": t.unknown, + }), }); export type post_ImageLoad = t.TypeOf; @@ -2583,6 +2891,10 @@ export const post_ImageLoad = t.type({ }), }), response: t.unknown, + responses: t.type({ + "200": t.unknown, + "500": ErrorResponse, + }), }); export type post_ContainerExec = t.TypeOf; @@ -2609,6 +2921,12 @@ export const post_ContainerExec = t.type({ }), }), response: IdResponse, + responses: t.type({ + "201": IdResponse, + "404": ErrorResponse, + "409": ErrorResponse, + "500": ErrorResponse, + }), }); export type post_ExecStart = t.TypeOf; @@ -2627,6 +2945,11 @@ export const post_ExecStart = t.type({ }), }), response: t.unknown, + responses: t.type({ + "200": t.unknown, + "404": t.unknown, + "409": t.unknown, + }), }); export type post_ExecResize = t.TypeOf; @@ -2644,6 +2967,12 @@ export const post_ExecResize = t.type({ }), }), response: t.unknown, + responses: t.type({ + "200": t.unknown, + "400": ErrorResponse, + "404": ErrorResponse, + "500": ErrorResponse, + }), }); export type get_ExecInspect = t.TypeOf; @@ -2669,6 +2998,23 @@ export const get_ExecInspect = t.type({ ContainerID: t.union([t.undefined, t.string]), Pid: t.union([t.undefined, t.number]), }), + responses: t.type({ + "200": t.type({ + CanRemove: t.union([t.undefined, t.boolean]), + DetachKeys: t.union([t.undefined, t.string]), + ID: t.union([t.undefined, t.string]), + Running: t.union([t.undefined, t.boolean]), + ExitCode: t.union([t.undefined, t.number]), + ProcessConfig: t.union([t.undefined, ProcessConfig]), + OpenStdin: t.union([t.undefined, t.boolean]), + OpenStderr: t.union([t.undefined, t.boolean]), + OpenStdout: t.union([t.undefined, t.boolean]), + ContainerID: t.union([t.undefined, t.string]), + Pid: t.union([t.undefined, t.number]), + }), + "404": ErrorResponse, + "500": ErrorResponse, + }), }); export type get_VolumeList = t.TypeOf; @@ -2682,6 +3028,10 @@ export const get_VolumeList = t.type({ }), }), response: VolumeListResponse, + responses: t.type({ + "200": VolumeListResponse, + "500": ErrorResponse, + }), }); export type post_VolumeCreate = t.TypeOf; @@ -2693,6 +3043,10 @@ export const post_VolumeCreate = t.type({ body: VolumeCreateOptions, }), response: Volume, + responses: t.type({ + "201": Volume, + "500": ErrorResponse, + }), }); export type get_VolumeInspect = t.TypeOf; @@ -2706,6 +3060,11 @@ export const get_VolumeInspect = t.type({ }), }), response: Volume, + responses: t.type({ + "200": Volume, + "404": ErrorResponse, + "500": ErrorResponse, + }), }); export type put_VolumeUpdate = t.TypeOf; @@ -2725,6 +3084,13 @@ export const put_VolumeUpdate = t.type({ }), }), response: t.unknown, + responses: t.type({ + "200": t.unknown, + "400": ErrorResponse, + "404": ErrorResponse, + "500": ErrorResponse, + "503": ErrorResponse, + }), }); export type delete_VolumeDelete = t.TypeOf; @@ -2741,6 +3107,12 @@ export const delete_VolumeDelete = t.type({ }), }), response: t.unknown, + responses: t.type({ + "204": t.unknown, + "404": ErrorResponse, + "409": ErrorResponse, + "500": ErrorResponse, + }), }); export type post_VolumePrune = t.TypeOf; @@ -2757,6 +3129,13 @@ export const post_VolumePrune = t.type({ VolumesDeleted: t.union([t.undefined, t.array(t.string)]), SpaceReclaimed: t.union([t.undefined, t.number]), }), + responses: t.type({ + "200": t.type({ + VolumesDeleted: t.union([t.undefined, t.array(t.string)]), + SpaceReclaimed: t.union([t.undefined, t.number]), + }), + "500": ErrorResponse, + }), }); export type get_NetworkList = t.TypeOf; @@ -2770,6 +3149,10 @@ export const get_NetworkList = t.type({ }), }), response: t.array(Network), + responses: t.type({ + "200": t.array(Network), + "500": ErrorResponse, + }), }); export type get_NetworkInspect = t.TypeOf; @@ -2787,6 +3170,11 @@ export const get_NetworkInspect = t.type({ }), }), response: Network, + responses: t.type({ + "200": Network, + "404": ErrorResponse, + "500": ErrorResponse, + }), }); export type delete_NetworkDelete = t.TypeOf; @@ -2800,6 +3188,12 @@ export const delete_NetworkDelete = t.type({ }), }), response: t.unknown, + responses: t.type({ + "204": t.unknown, + "403": ErrorResponse, + "404": ErrorResponse, + "500": ErrorResponse, + }), }); export type post_NetworkCreate = t.TypeOf; @@ -2825,6 +3219,15 @@ export const post_NetworkCreate = t.type({ Id: t.union([t.undefined, t.string]), Warning: t.union([t.undefined, t.string]), }), + responses: t.type({ + "201": t.type({ + Id: t.union([t.undefined, t.string]), + Warning: t.union([t.undefined, t.string]), + }), + "403": ErrorResponse, + "404": ErrorResponse, + "500": ErrorResponse, + }), }); export type post_NetworkConnect = t.TypeOf; @@ -2842,6 +3245,12 @@ export const post_NetworkConnect = t.type({ }), }), response: t.unknown, + responses: t.type({ + "200": t.unknown, + "403": ErrorResponse, + "404": ErrorResponse, + "500": ErrorResponse, + }), }); export type post_NetworkDisconnect = t.TypeOf; @@ -2859,6 +3268,12 @@ export const post_NetworkDisconnect = t.type({ }), }), response: t.unknown, + responses: t.type({ + "200": t.unknown, + "403": ErrorResponse, + "404": ErrorResponse, + "500": ErrorResponse, + }), }); export type post_NetworkPrune = t.TypeOf; @@ -2874,6 +3289,12 @@ export const post_NetworkPrune = t.type({ response: t.type({ NetworksDeleted: t.union([t.undefined, t.array(t.string)]), }), + responses: t.type({ + "200": t.type({ + NetworksDeleted: t.union([t.undefined, t.array(t.string)]), + }), + "500": ErrorResponse, + }), }); export type get_PluginList = t.TypeOf; @@ -2887,6 +3308,10 @@ export const get_PluginList = t.type({ }), }), response: t.array(Plugin), + responses: t.type({ + "200": t.array(Plugin), + "500": ErrorResponse, + }), }); export type get_GetPluginPrivileges = t.TypeOf; @@ -2900,6 +3325,10 @@ export const get_GetPluginPrivileges = t.type({ }), }), response: t.array(PluginPrivilege), + responses: t.type({ + "200": t.array(PluginPrivilege), + "500": ErrorResponse, + }), }); export type post_PluginPull = t.TypeOf; @@ -2918,6 +3347,10 @@ export const post_PluginPull = t.type({ body: t.array(PluginPrivilege), }), response: t.unknown, + responses: t.type({ + "204": t.unknown, + "500": ErrorResponse, + }), }); export type get_PluginInspect = t.TypeOf; @@ -2931,6 +3364,11 @@ export const get_PluginInspect = t.type({ }), }), response: Plugin, + responses: t.type({ + "200": Plugin, + "404": ErrorResponse, + "500": ErrorResponse, + }), }); export type delete_PluginDelete = t.TypeOf; @@ -2947,6 +3385,11 @@ export const delete_PluginDelete = t.type({ }), }), response: Plugin, + responses: t.type({ + "200": Plugin, + "404": ErrorResponse, + "500": ErrorResponse, + }), }); export type post_PluginEnable = t.TypeOf; @@ -2963,6 +3406,11 @@ export const post_PluginEnable = t.type({ }), }), response: t.unknown, + responses: t.type({ + "200": t.unknown, + "404": ErrorResponse, + "500": ErrorResponse, + }), }); export type post_PluginDisable = t.TypeOf; @@ -2979,6 +3427,11 @@ export const post_PluginDisable = t.type({ }), }), response: t.unknown, + responses: t.type({ + "200": t.unknown, + "404": ErrorResponse, + "500": ErrorResponse, + }), }); export type post_PluginUpgrade = t.TypeOf; @@ -2999,6 +3452,11 @@ export const post_PluginUpgrade = t.type({ body: t.array(PluginPrivilege), }), response: t.unknown, + responses: t.type({ + "204": t.unknown, + "404": ErrorResponse, + "500": ErrorResponse, + }), }); export type post_PluginCreate = t.TypeOf; @@ -3012,6 +3470,10 @@ export const post_PluginCreate = t.type({ }), }), response: t.unknown, + responses: t.type({ + "204": t.unknown, + "500": ErrorResponse, + }), }); export type post_PluginPush = t.TypeOf; @@ -3025,6 +3487,11 @@ export const post_PluginPush = t.type({ }), }), response: t.unknown, + responses: t.type({ + "200": t.unknown, + "404": ErrorResponse, + "500": ErrorResponse, + }), }); export type post_PluginSet = t.TypeOf; @@ -3039,6 +3506,11 @@ export const post_PluginSet = t.type({ body: t.array(t.string), }), response: t.unknown, + responses: t.type({ + "204": t.unknown, + "404": ErrorResponse, + "500": ErrorResponse, + }), }); export type get_NodeList = t.TypeOf; @@ -3052,6 +3524,11 @@ export const get_NodeList = t.type({ }), }), response: t.array(Node), + responses: t.type({ + "200": t.array(Node), + "500": ErrorResponse, + "503": ErrorResponse, + }), }); export type get_NodeInspect = t.TypeOf; @@ -3065,6 +3542,12 @@ export const get_NodeInspect = t.type({ }), }), response: Node, + responses: t.type({ + "200": Node, + "404": ErrorResponse, + "500": ErrorResponse, + "503": ErrorResponse, + }), }); export type delete_NodeDelete = t.TypeOf; @@ -3081,6 +3564,12 @@ export const delete_NodeDelete = t.type({ }), }), response: t.unknown, + responses: t.type({ + "200": t.unknown, + "404": ErrorResponse, + "500": ErrorResponse, + "503": ErrorResponse, + }), }); export type post_NodeUpdate = t.TypeOf; @@ -3098,6 +3587,13 @@ export const post_NodeUpdate = t.type({ body: NodeSpec, }), response: t.unknown, + responses: t.type({ + "200": t.unknown, + "400": ErrorResponse, + "404": ErrorResponse, + "500": ErrorResponse, + "503": ErrorResponse, + }), }); export type get_SwarmInspect = t.TypeOf; @@ -3107,6 +3603,12 @@ export const get_SwarmInspect = t.type({ requestFormat: t.literal("json"), parameters: t.never, response: Swarm, + responses: t.type({ + "200": Swarm, + "404": ErrorResponse, + "500": ErrorResponse, + "503": ErrorResponse, + }), }); export type post_SwarmInit = t.TypeOf; @@ -3127,6 +3629,12 @@ export const post_SwarmInit = t.type({ }), }), response: t.string, + responses: t.type({ + "200": t.string, + "400": ErrorResponse, + "500": ErrorResponse, + "503": ErrorResponse, + }), }); export type post_SwarmJoin = t.TypeOf; @@ -3144,6 +3652,12 @@ export const post_SwarmJoin = t.type({ }), }), response: t.unknown, + responses: t.type({ + "200": t.unknown, + "400": ErrorResponse, + "500": ErrorResponse, + "503": ErrorResponse, + }), }); export type post_SwarmLeave = t.TypeOf; @@ -3157,6 +3671,11 @@ export const post_SwarmLeave = t.type({ }), }), response: t.unknown, + responses: t.type({ + "200": t.unknown, + "500": ErrorResponse, + "503": ErrorResponse, + }), }); export type post_SwarmUpdate = t.TypeOf; @@ -3174,6 +3693,12 @@ export const post_SwarmUpdate = t.type({ body: SwarmSpec, }), response: t.unknown, + responses: t.type({ + "200": t.unknown, + "400": ErrorResponse, + "500": ErrorResponse, + "503": ErrorResponse, + }), }); export type get_SwarmUnlockkey = t.TypeOf; @@ -3185,6 +3710,13 @@ export const get_SwarmUnlockkey = t.type({ response: t.type({ UnlockKey: t.union([t.undefined, t.string]), }), + responses: t.type({ + "200": t.type({ + UnlockKey: t.union([t.undefined, t.string]), + }), + "500": ErrorResponse, + "503": ErrorResponse, + }), }); export type post_SwarmUnlock = t.TypeOf; @@ -3198,6 +3730,11 @@ export const post_SwarmUnlock = t.type({ }), }), response: t.unknown, + responses: t.type({ + "200": t.unknown, + "500": ErrorResponse, + "503": ErrorResponse, + }), }); export type get_ServiceList = t.TypeOf; @@ -3212,6 +3749,11 @@ export const get_ServiceList = t.type({ }), }), response: t.array(Service), + responses: t.type({ + "200": t.array(Service), + "500": ErrorResponse, + "503": ErrorResponse, + }), }); export type post_ServiceCreate = t.TypeOf; @@ -3229,6 +3771,17 @@ export const post_ServiceCreate = t.type({ ID: t.union([t.undefined, t.string]), Warning: t.union([t.undefined, t.string]), }), + responses: t.type({ + "201": t.type({ + ID: t.union([t.undefined, t.string]), + Warning: t.union([t.undefined, t.string]), + }), + "400": ErrorResponse, + "403": ErrorResponse, + "409": ErrorResponse, + "500": ErrorResponse, + "503": ErrorResponse, + }), }); export type get_ServiceInspect = t.TypeOf; @@ -3245,6 +3798,12 @@ export const get_ServiceInspect = t.type({ }), }), response: Service, + responses: t.type({ + "200": Service, + "404": ErrorResponse, + "500": ErrorResponse, + "503": ErrorResponse, + }), }); export type delete_ServiceDelete = t.TypeOf; @@ -3258,6 +3817,12 @@ export const delete_ServiceDelete = t.type({ }), }), response: t.unknown, + responses: t.type({ + "200": t.unknown, + "404": ErrorResponse, + "500": ErrorResponse, + "503": ErrorResponse, + }), }); export type post_ServiceUpdate = t.TypeOf; @@ -3283,6 +3848,13 @@ export const post_ServiceUpdate = t.type({ body: t.intersection([ServiceSpec, t.record(t.string, t.unknown)]), }), response: ServiceUpdateResponse, + responses: t.type({ + "200": ServiceUpdateResponse, + "400": ErrorResponse, + "404": ErrorResponse, + "500": ErrorResponse, + "503": ErrorResponse, + }), }); export type get_ServiceLogs = t.TypeOf; @@ -3305,6 +3877,12 @@ export const get_ServiceLogs = t.type({ }), }), response: t.unknown, + responses: t.type({ + "200": t.unknown, + "404": t.unknown, + "500": t.unknown, + "503": t.unknown, + }), }); export type get_TaskList = t.TypeOf; @@ -3318,6 +3896,11 @@ export const get_TaskList = t.type({ }), }), response: t.array(Task), + responses: t.type({ + "200": t.array(Task), + "500": ErrorResponse, + "503": ErrorResponse, + }), }); export type get_TaskInspect = t.TypeOf; @@ -3331,6 +3914,12 @@ export const get_TaskInspect = t.type({ }), }), response: Task, + responses: t.type({ + "200": Task, + "404": ErrorResponse, + "500": ErrorResponse, + "503": ErrorResponse, + }), }); export type get_TaskLogs = t.TypeOf; @@ -3353,6 +3942,12 @@ export const get_TaskLogs = t.type({ }), }), response: t.unknown, + responses: t.type({ + "200": t.unknown, + "404": t.unknown, + "500": t.unknown, + "503": t.unknown, + }), }); export type get_SecretList = t.TypeOf; @@ -3366,6 +3961,11 @@ export const get_SecretList = t.type({ }), }), response: t.array(Secret), + responses: t.type({ + "200": t.array(Secret), + "500": ErrorResponse, + "503": ErrorResponse, + }), }); export type post_SecretCreate = t.TypeOf; @@ -3377,6 +3977,12 @@ export const post_SecretCreate = t.type({ body: t.intersection([SecretSpec, t.record(t.string, t.unknown)]), }), response: IdResponse, + responses: t.type({ + "201": IdResponse, + "409": ErrorResponse, + "500": ErrorResponse, + "503": ErrorResponse, + }), }); export type get_SecretInspect = t.TypeOf; @@ -3390,6 +3996,12 @@ export const get_SecretInspect = t.type({ }), }), response: Secret, + responses: t.type({ + "200": Secret, + "404": ErrorResponse, + "500": ErrorResponse, + "503": ErrorResponse, + }), }); export type delete_SecretDelete = t.TypeOf; @@ -3403,6 +4015,12 @@ export const delete_SecretDelete = t.type({ }), }), response: t.unknown, + responses: t.type({ + "204": t.unknown, + "404": ErrorResponse, + "500": ErrorResponse, + "503": ErrorResponse, + }), }); export type post_SecretUpdate = t.TypeOf; @@ -3420,6 +4038,13 @@ export const post_SecretUpdate = t.type({ body: SecretSpec, }), response: t.unknown, + responses: t.type({ + "200": t.unknown, + "400": ErrorResponse, + "404": ErrorResponse, + "500": ErrorResponse, + "503": ErrorResponse, + }), }); export type get_ConfigList = t.TypeOf; @@ -3433,6 +4058,11 @@ export const get_ConfigList = t.type({ }), }), response: t.array(Config), + responses: t.type({ + "200": t.array(Config), + "500": ErrorResponse, + "503": ErrorResponse, + }), }); export type post_ConfigCreate = t.TypeOf; @@ -3444,6 +4074,12 @@ export const post_ConfigCreate = t.type({ body: t.intersection([ConfigSpec, t.record(t.string, t.unknown)]), }), response: IdResponse, + responses: t.type({ + "201": IdResponse, + "409": ErrorResponse, + "500": ErrorResponse, + "503": ErrorResponse, + }), }); export type get_ConfigInspect = t.TypeOf; @@ -3457,6 +4093,12 @@ export const get_ConfigInspect = t.type({ }), }), response: Config, + responses: t.type({ + "200": Config, + "404": ErrorResponse, + "500": ErrorResponse, + "503": ErrorResponse, + }), }); export type delete_ConfigDelete = t.TypeOf; @@ -3470,6 +4112,12 @@ export const delete_ConfigDelete = t.type({ }), }), response: t.unknown, + responses: t.type({ + "204": t.unknown, + "404": ErrorResponse, + "500": ErrorResponse, + "503": ErrorResponse, + }), }); export type post_ConfigUpdate = t.TypeOf; @@ -3487,6 +4135,13 @@ export const post_ConfigUpdate = t.type({ body: ConfigSpec, }), response: t.unknown, + responses: t.type({ + "200": t.unknown, + "400": ErrorResponse, + "404": ErrorResponse, + "500": ErrorResponse, + "503": ErrorResponse, + }), }); export type get_DistributionInspect = t.TypeOf; @@ -3500,6 +4155,11 @@ export const get_DistributionInspect = t.type({ }), }), response: DistributionInspect, + responses: t.type({ + "200": DistributionInspect, + "401": ErrorResponse, + "500": ErrorResponse, + }), }); export type post_Session = t.TypeOf; @@ -3509,6 +4169,11 @@ export const post_Session = t.type({ requestFormat: t.literal("json"), parameters: t.never, response: t.unknown, + responses: t.type({ + "101": t.unknown, + "400": t.unknown, + "500": t.unknown, + }), }); export type __ENDPOINTS_END__ = t.TypeOf; @@ -3661,6 +4326,7 @@ type RequestFormat = "json" | "form-data" | "form-url" | "binary" | "text"; export type DefaultEndpoint = { parameters?: EndpointParameters | undefined; response: unknown; + responses?: Record; responseHeaders?: Record; }; @@ -3676,11 +4342,97 @@ export type Endpoint = { areParametersRequired: boolean; }; response: TConfig["response"]; + responses?: TConfig["responses"]; responseHeaders?: TConfig["responseHeaders"]; }; export type Fetcher = (method: Method, url: string, parameters?: EndpointParameters | undefined) => Promise; +// Status code type for success responses +export type SuccessStatusCode = + | 200 + | 201 + | 202 + | 203 + | 204 + | 205 + | 206 + | 207 + | 208 + | 226 + | 300 + | 301 + | 302 + | 303 + | 304 + | 305 + | 306 + | 307 + | 308; + +// Error handling types +export type TypedApiResponse< + TSuccess, + TAllResponses extends Record = {}, +> = keyof TAllResponses extends never + ? Omit & { + ok: true; + status: number; + data: TSuccess; + json: () => Promise; + } + : { + [K in keyof TAllResponses]: K extends string + ? K extends `${infer TStatusCode extends number}` + ? TStatusCode extends SuccessStatusCode + ? Omit & { + ok: true; + status: TStatusCode; + data: TSuccess; + json: () => Promise; + } + : Omit & { + ok: false; + status: TStatusCode; + data: TAllResponses[K]; + json: () => Promise; + } + : never + : K extends number + ? K extends SuccessStatusCode + ? Omit & { + ok: true; + status: K; + data: TSuccess; + json: () => Promise; + } + : Omit & { + ok: false; + status: K; + data: TAllResponses[K]; + json: () => Promise; + } + : never; + }[keyof TAllResponses]; + +export type SafeApiResponse = TEndpoint extends { response: infer TSuccess; responses: infer TResponses } + ? TResponses extends Record + ? TypedApiResponse + : Omit & { + ok: true; + status: number; + data: TSuccess; + json: () => Promise; + } + : TEndpoint extends { response: infer TSuccess } + ? Omit & { + ok: true; + status: number; + data: TSuccess; + json: () => Promise; + } + : never; + type RequiredKeys = { [P in keyof T]-?: undefined extends T[P] ? never : P; }[keyof T]; @@ -3711,55 +4463,227 @@ export class ApiClient { // get( path: Path, - ...params: MaybeOptionalArg["parameters"]> - ): Promise["response"]> { - return this.fetcher("get", this.baseUrl + path, params[0]).then((response) => - this.parseResponse(response), - ) as Promise["response"]>; + ...params: MaybeOptionalArg["parameters"] & { withResponse?: false }> + ): Promise["response"]>; + + get( + path: Path, + ...params: MaybeOptionalArg["parameters"] & { withResponse: true }> + ): Promise>; + + get( + path: Path, + ...params: MaybeOptionalArg + ): Promise { + const requestParams = params[0]; + const withResponse = requestParams?.withResponse; + + // Remove withResponse from params before passing to fetcher + const { withResponse: _, ...fetchParams } = requestParams || {}; + + if (withResponse) { + return this.fetcher("get", this.baseUrl + path, Object.keys(fetchParams).length ? fetchParams : undefined).then( + async (response) => { + // Parse the response data + const data = await this.parseResponse(response); + + // Override properties while keeping the original Response object + const typedResponse = Object.assign(response, { + ok: response.ok, + status: response.status, + data: data, + json: () => Promise.resolve(data), + }); + return typedResponse; + }, + ); + } else { + return this.fetcher("get", this.baseUrl + path, requestParams).then((response) => + this.parseResponse(response), + ) as Promise["response"]>; + } } // // post( path: Path, - ...params: MaybeOptionalArg["parameters"]> - ): Promise["response"]> { - return this.fetcher("post", this.baseUrl + path, params[0]).then((response) => - this.parseResponse(response), - ) as Promise["response"]>; + ...params: MaybeOptionalArg["parameters"] & { withResponse?: false }> + ): Promise["response"]>; + + post( + path: Path, + ...params: MaybeOptionalArg["parameters"] & { withResponse: true }> + ): Promise>; + + post( + path: Path, + ...params: MaybeOptionalArg + ): Promise { + const requestParams = params[0]; + const withResponse = requestParams?.withResponse; + + // Remove withResponse from params before passing to fetcher + const { withResponse: _, ...fetchParams } = requestParams || {}; + + if (withResponse) { + return this.fetcher("post", this.baseUrl + path, Object.keys(fetchParams).length ? fetchParams : undefined).then( + async (response) => { + // Parse the response data + const data = await this.parseResponse(response); + + // Override properties while keeping the original Response object + const typedResponse = Object.assign(response, { + ok: response.ok, + status: response.status, + data: data, + json: () => Promise.resolve(data), + }); + return typedResponse; + }, + ); + } else { + return this.fetcher("post", this.baseUrl + path, requestParams).then((response) => + this.parseResponse(response), + ) as Promise["response"]>; + } } // // delete( path: Path, - ...params: MaybeOptionalArg["parameters"]> - ): Promise["response"]> { - return this.fetcher("delete", this.baseUrl + path, params[0]).then((response) => - this.parseResponse(response), - ) as Promise["response"]>; + ...params: MaybeOptionalArg["parameters"] & { withResponse?: false }> + ): Promise["response"]>; + + delete( + path: Path, + ...params: MaybeOptionalArg["parameters"] & { withResponse: true }> + ): Promise>; + + delete( + path: Path, + ...params: MaybeOptionalArg + ): Promise { + const requestParams = params[0]; + const withResponse = requestParams?.withResponse; + + // Remove withResponse from params before passing to fetcher + const { withResponse: _, ...fetchParams } = requestParams || {}; + + if (withResponse) { + return this.fetcher( + "delete", + this.baseUrl + path, + Object.keys(fetchParams).length ? fetchParams : undefined, + ).then(async (response) => { + // Parse the response data + const data = await this.parseResponse(response); + + // Override properties while keeping the original Response object + const typedResponse = Object.assign(response, { + ok: response.ok, + status: response.status, + data: data, + json: () => Promise.resolve(data), + }); + return typedResponse; + }); + } else { + return this.fetcher("delete", this.baseUrl + path, requestParams).then((response) => + this.parseResponse(response), + ) as Promise["response"]>; + } } // // put( path: Path, - ...params: MaybeOptionalArg["parameters"]> - ): Promise["response"]> { - return this.fetcher("put", this.baseUrl + path, params[0]).then((response) => - this.parseResponse(response), - ) as Promise["response"]>; + ...params: MaybeOptionalArg["parameters"] & { withResponse?: false }> + ): Promise["response"]>; + + put( + path: Path, + ...params: MaybeOptionalArg["parameters"] & { withResponse: true }> + ): Promise>; + + put( + path: Path, + ...params: MaybeOptionalArg + ): Promise { + const requestParams = params[0]; + const withResponse = requestParams?.withResponse; + + // Remove withResponse from params before passing to fetcher + const { withResponse: _, ...fetchParams } = requestParams || {}; + + if (withResponse) { + return this.fetcher("put", this.baseUrl + path, Object.keys(fetchParams).length ? fetchParams : undefined).then( + async (response) => { + // Parse the response data + const data = await this.parseResponse(response); + + // Override properties while keeping the original Response object + const typedResponse = Object.assign(response, { + ok: response.ok, + status: response.status, + data: data, + json: () => Promise.resolve(data), + }); + return typedResponse; + }, + ); + } else { + return this.fetcher("put", this.baseUrl + path, requestParams).then((response) => + this.parseResponse(response), + ) as Promise["response"]>; + } } // // head( path: Path, - ...params: MaybeOptionalArg["parameters"]> - ): Promise["response"]> { - return this.fetcher("head", this.baseUrl + path, params[0]).then((response) => - this.parseResponse(response), - ) as Promise["response"]>; + ...params: MaybeOptionalArg["parameters"] & { withResponse?: false }> + ): Promise["response"]>; + + head( + path: Path, + ...params: MaybeOptionalArg["parameters"] & { withResponse: true }> + ): Promise>; + + head( + path: Path, + ...params: MaybeOptionalArg + ): Promise { + const requestParams = params[0]; + const withResponse = requestParams?.withResponse; + + // Remove withResponse from params before passing to fetcher + const { withResponse: _, ...fetchParams } = requestParams || {}; + + if (withResponse) { + return this.fetcher("head", this.baseUrl + path, Object.keys(fetchParams).length ? fetchParams : undefined).then( + async (response) => { + // Parse the response data + const data = await this.parseResponse(response); + + // Override properties while keeping the original Response object + const typedResponse = Object.assign(response, { + ok: response.ok, + status: response.status, + data: data, + json: () => Promise.resolve(data), + }); + return typedResponse; + }, + ); + } else { + return this.fetcher("head", this.baseUrl + path, requestParams).then((response) => + this.parseResponse(response), + ) as Promise["response"]>; + } } // @@ -3798,6 +4722,21 @@ export function createApiClient(fetcher: Fetcher, baseUrl?: string) { api.get("/users").then((users) => console.log(users)); api.post("/users", { body: { name: "John" } }).then((user) => console.log(user)); api.put("/users/:id", { path: { id: 1 }, body: { name: "John" } }).then((user) => console.log(user)); + + // With error handling + const result = await api.get("/users/{id}", { path: { id: "123" }, withResponse: true }); + if (result.ok) { + // Access data directly + const user = result.data; + console.log(user); + + // Or use the json() method for compatibility + const userFromJson = await result.json(); + console.log(userFromJson); + } else { + const error = result.data; + console.error(`Error ${result.status}:`, error); + } */ // ; @@ -1859,6 +1864,13 @@ export const post_ContainerCreate = Type.Object({ ]), }), response: ContainerCreateResponse, + responses: Type.Object({ + 201: ContainerCreateResponse, + 400: ErrorResponse, + 404: ErrorResponse, + 409: ErrorResponse, + 500: ErrorResponse, + }), }); export type get_ContainerInspect = Static; @@ -1905,6 +1917,39 @@ export const get_ContainerInspect = Type.Object({ NetworkSettings: NetworkSettings, }), ), + responses: Type.Object({ + 200: Type.Partial( + Type.Object({ + Id: Type.String(), + Created: Type.String(), + Path: Type.String(), + Args: Type.Array(Type.String()), + State: ContainerState, + Image: Type.String(), + ResolvConfPath: Type.String(), + HostnamePath: Type.String(), + HostsPath: Type.String(), + LogPath: Type.String(), + Name: Type.String(), + RestartCount: Type.Number(), + Driver: Type.String(), + Platform: Type.String(), + MountLabel: Type.String(), + ProcessLabel: Type.String(), + AppArmorProfile: Type.String(), + ExecIDs: Type.Union([Type.Array(Type.String()), Type.Null()]), + HostConfig: HostConfig, + GraphDriver: GraphDriverData, + SizeRw: Type.Number(), + SizeRootFs: Type.Number(), + Mounts: Type.Array(MountPoint), + Config: ContainerConfig, + NetworkSettings: NetworkSettings, + }), + ), + 404: ErrorResponse, + 500: ErrorResponse, + }), }); export type get_ContainerTop = Static; @@ -1928,6 +1973,16 @@ export const get_ContainerTop = Type.Object({ Processes: Type.Array(Type.Array(Type.String())), }), ), + responses: Type.Object({ + 200: Type.Partial( + Type.Object({ + Titles: Type.Array(Type.String()), + Processes: Type.Array(Type.Array(Type.String())), + }), + ), + 404: ErrorResponse, + 500: ErrorResponse, + }), }); export type get_ContainerLogs = Static; @@ -1952,6 +2007,11 @@ export const get_ContainerLogs = Type.Object({ }), }), response: Type.Unknown(), + responses: Type.Object({ + 200: Type.Unknown(), + 404: Type.Unknown(), + 500: Type.Unknown(), + }), }); export type get_ContainerChanges = Static; @@ -1965,6 +2025,11 @@ export const get_ContainerChanges = Type.Object({ }), }), response: Type.Array(FilesystemChange), + responses: Type.Object({ + 200: Type.Array(FilesystemChange), + 404: ErrorResponse, + 500: ErrorResponse, + }), }); export type get_ContainerExport = Static; @@ -1978,6 +2043,11 @@ export const get_ContainerExport = Type.Object({ }), }), response: Type.Unknown(), + responses: Type.Object({ + 200: Type.Unknown(), + 404: Type.Unknown(), + 500: Type.Unknown(), + }), }); export type get_ContainerStats = Static; @@ -1997,6 +2067,11 @@ export const get_ContainerStats = Type.Object({ }), }), response: Type.Record(Type.String(), Type.Unknown()), + responses: Type.Object({ + 200: Type.Record(Type.String(), Type.Unknown()), + 404: ErrorResponse, + 500: ErrorResponse, + }), }); export type post_ContainerResize = Static; @@ -2016,6 +2091,11 @@ export const post_ContainerResize = Type.Object({ }), }), response: Type.Unknown(), + responses: Type.Object({ + 200: Type.Unknown(), + 404: Type.Unknown(), + 500: Type.Unknown(), + }), }); export type post_ContainerStart = Static; @@ -2034,6 +2114,12 @@ export const post_ContainerStart = Type.Object({ }), }), response: Type.Unknown(), + responses: Type.Object({ + 204: Type.Unknown(), + 304: Type.Unknown(), + 404: ErrorResponse, + 500: ErrorResponse, + }), }); export type post_ContainerStop = Static; @@ -2053,6 +2139,12 @@ export const post_ContainerStop = Type.Object({ }), }), response: Type.Unknown(), + responses: Type.Object({ + 204: Type.Unknown(), + 304: Type.Unknown(), + 404: ErrorResponse, + 500: ErrorResponse, + }), }); export type post_ContainerRestart = Static; @@ -2072,6 +2164,11 @@ export const post_ContainerRestart = Type.Object({ }), }), response: Type.Unknown(), + responses: Type.Object({ + 204: Type.Unknown(), + 404: ErrorResponse, + 500: ErrorResponse, + }), }); export type post_ContainerKill = Static; @@ -2090,6 +2187,12 @@ export const post_ContainerKill = Type.Object({ }), }), response: Type.Unknown(), + responses: Type.Object({ + 204: Type.Unknown(), + 404: ErrorResponse, + 409: ErrorResponse, + 500: ErrorResponse, + }), }); export type post_ContainerUpdate = Static; @@ -2115,6 +2218,15 @@ export const post_ContainerUpdate = Type.Object({ Warnings: Type.Array(Type.String()), }), ), + responses: Type.Object({ + 200: Type.Partial( + Type.Object({ + Warnings: Type.Array(Type.String()), + }), + ), + 404: ErrorResponse, + 500: ErrorResponse, + }), }); export type post_ContainerRename = Static; @@ -2131,6 +2243,12 @@ export const post_ContainerRename = Type.Object({ }), }), response: Type.Unknown(), + responses: Type.Object({ + 204: Type.Unknown(), + 404: ErrorResponse, + 409: ErrorResponse, + 500: ErrorResponse, + }), }); export type post_ContainerPause = Static; @@ -2144,6 +2262,11 @@ export const post_ContainerPause = Type.Object({ }), }), response: Type.Unknown(), + responses: Type.Object({ + 204: Type.Unknown(), + 404: ErrorResponse, + 500: ErrorResponse, + }), }); export type post_ContainerUnpause = Static; @@ -2157,6 +2280,11 @@ export const post_ContainerUnpause = Type.Object({ }), }), response: Type.Unknown(), + responses: Type.Object({ + 204: Type.Unknown(), + 404: ErrorResponse, + 500: ErrorResponse, + }), }); export type post_ContainerAttach = Static; @@ -2180,6 +2308,13 @@ export const post_ContainerAttach = Type.Object({ }), }), response: Type.Unknown(), + responses: Type.Object({ + 101: Type.Unknown(), + 200: Type.Unknown(), + 400: Type.Unknown(), + 404: Type.Unknown(), + 500: Type.Unknown(), + }), }); export type get_ContainerAttachWebsocket = Static; @@ -2203,6 +2338,13 @@ export const get_ContainerAttachWebsocket = Type.Object({ }), }), response: Type.Unknown(), + responses: Type.Object({ + 101: Type.Unknown(), + 200: Type.Unknown(), + 400: ErrorResponse, + 404: ErrorResponse, + 500: ErrorResponse, + }), }); export type post_ContainerWait = Static; @@ -2221,6 +2363,12 @@ export const post_ContainerWait = Type.Object({ }), }), response: ContainerWaitResponse, + responses: Type.Object({ + 200: ContainerWaitResponse, + 400: ErrorResponse, + 404: ErrorResponse, + 500: ErrorResponse, + }), }); export type delete_ContainerDelete = Static; @@ -2241,6 +2389,13 @@ export const delete_ContainerDelete = Type.Object({ }), }), response: Type.Unknown(), + responses: Type.Object({ + 204: Type.Unknown(), + 400: ErrorResponse, + 404: ErrorResponse, + 409: ErrorResponse, + 500: ErrorResponse, + }), }); export type get_ContainerArchive = Static; @@ -2257,6 +2412,12 @@ export const get_ContainerArchive = Type.Object({ }), }), response: Type.Unknown(), + responses: Type.Object({ + 200: Type.Unknown(), + 400: Type.Unknown(), + 404: Type.Unknown(), + 500: Type.Unknown(), + }), }); export type put_PutContainerArchive = Static; @@ -2276,6 +2437,13 @@ export const put_PutContainerArchive = Type.Object({ body: Type.String(), }), response: Type.Unknown(), + responses: Type.Object({ + 200: Type.Unknown(), + 400: ErrorResponse, + 403: ErrorResponse, + 404: ErrorResponse, + 500: ErrorResponse, + }), }); export type head_ContainerArchiveInfo = Static; @@ -2292,6 +2460,12 @@ export const head_ContainerArchiveInfo = Type.Object({ }), }), response: Type.Unknown(), + responses: Type.Object({ + 200: Type.Unknown(), + 400: ErrorResponse, + 404: ErrorResponse, + 500: ErrorResponse, + }), responseHeaders: Type.Object({ "x-docker-container-path-stat": Type.String(), }), @@ -2315,6 +2489,15 @@ export const post_ContainerPrune = Type.Object({ SpaceReclaimed: Type.Number(), }), ), + responses: Type.Object({ + 200: Type.Partial( + Type.Object({ + ContainersDeleted: Type.Array(Type.String()), + SpaceReclaimed: Type.Number(), + }), + ), + 500: ErrorResponse, + }), }); export type get_ImageList = Static; @@ -2333,6 +2516,10 @@ export const get_ImageList = Type.Object({ ), }), response: Type.Array(ImageSummary), + responses: Type.Object({ + 200: Type.Array(ImageSummary), + 500: ErrorResponse, + }), }); export type post_ImageBuild = Static; @@ -2378,6 +2565,11 @@ export const post_ImageBuild = Type.Object({ body: Type.String(), }), response: Type.Unknown(), + responses: Type.Object({ + 200: Type.Unknown(), + 400: ErrorResponse, + 500: ErrorResponse, + }), }); export type post_BuildPrune = Static; @@ -2400,6 +2592,15 @@ export const post_BuildPrune = Type.Object({ SpaceReclaimed: Type.Number(), }), ), + responses: Type.Object({ + 200: Type.Partial( + Type.Object({ + CachesDeleted: Type.Array(Type.String()), + SpaceReclaimed: Type.Number(), + }), + ), + 500: ErrorResponse, + }), }); export type post_ImageCreate = Static; @@ -2427,6 +2628,11 @@ export const post_ImageCreate = Type.Object({ body: Type.String(), }), response: Type.Unknown(), + responses: Type.Object({ + 200: Type.Unknown(), + 404: ErrorResponse, + 500: ErrorResponse, + }), }); export type get_ImageInspect = Static; @@ -2440,6 +2646,11 @@ export const get_ImageInspect = Type.Object({ }), }), response: ImageInspect, + responses: Type.Object({ + 200: ImageInspect, + 404: ErrorResponse, + 500: ErrorResponse, + }), }); export type get_ImageHistory = Static; @@ -2462,6 +2673,20 @@ export const get_ImageHistory = Type.Object({ Comment: Type.String(), }), ), + responses: Type.Object({ + 200: Type.Array( + Type.Object({ + Id: Type.String(), + Created: Type.Number(), + CreatedBy: Type.String(), + Tags: Type.Array(Type.String()), + Size: Type.Number(), + Comment: Type.String(), + }), + ), + 404: ErrorResponse, + 500: ErrorResponse, + }), }); export type post_ImagePush = Static; @@ -2483,6 +2708,11 @@ export const post_ImagePush = Type.Object({ }), }), response: Type.Unknown(), + responses: Type.Object({ + 200: Type.Unknown(), + 404: ErrorResponse, + 500: ErrorResponse, + }), }); export type post_ImageTag = Static; @@ -2502,6 +2732,13 @@ export const post_ImageTag = Type.Object({ }), }), response: Type.Unknown(), + responses: Type.Object({ + 201: Type.Unknown(), + 400: ErrorResponse, + 404: ErrorResponse, + 409: ErrorResponse, + 500: ErrorResponse, + }), }); export type delete_ImageDelete = Static; @@ -2521,6 +2758,12 @@ export const delete_ImageDelete = Type.Object({ }), }), response: Type.Array(ImageDeleteResponseItem), + responses: Type.Object({ + 200: Type.Array(ImageDeleteResponseItem), + 404: ErrorResponse, + 409: ErrorResponse, + 500: ErrorResponse, + }), }); export type get_ImageSearch = Static; @@ -2546,6 +2789,20 @@ export const get_ImageSearch = Type.Object({ }), ), ), + responses: Type.Object({ + 200: Type.Array( + Type.Partial( + Type.Object({ + description: Type.String(), + is_official: Type.Boolean(), + is_automated: Type.Boolean(), + name: Type.String(), + star_count: Type.Number(), + }), + ), + ), + 500: ErrorResponse, + }), }); export type post_ImagePrune = Static; @@ -2566,6 +2823,15 @@ export const post_ImagePrune = Type.Object({ SpaceReclaimed: Type.Number(), }), ), + responses: Type.Object({ + 200: Type.Partial( + Type.Object({ + ImagesDeleted: Type.Array(ImageDeleteResponseItem), + SpaceReclaimed: Type.Number(), + }), + ), + 500: ErrorResponse, + }), }); export type post_SystemAuth = Static; @@ -2576,7 +2842,19 @@ export const post_SystemAuth = Type.Object({ parameters: Type.Object({ body: AuthConfig, }), - response: Type.Unknown(), + response: Type.Object({ + Status: Type.String(), + IdentityToken: Type.Optional(Type.Union([Type.String(), Type.Undefined()])), + }), + responses: Type.Object({ + 200: Type.Object({ + Status: Type.String(), + IdentityToken: Type.Optional(Type.Union([Type.String(), Type.Undefined()])), + }), + 204: Type.Unknown(), + 401: ErrorResponse, + 500: ErrorResponse, + }), }); export type get_SystemInfo = Static; @@ -2586,6 +2864,10 @@ export const get_SystemInfo = Type.Object({ requestFormat: Type.Literal("json"), parameters: Type.Never(), response: SystemInfo, + responses: Type.Object({ + 200: SystemInfo, + 500: ErrorResponse, + }), }); export type get_SystemVersion = Static; @@ -2595,6 +2877,10 @@ export const get_SystemVersion = Type.Object({ requestFormat: Type.Literal("json"), parameters: Type.Never(), response: SystemVersion, + responses: Type.Object({ + 200: SystemVersion, + 500: ErrorResponse, + }), }); export type get_SystemPing = Static; @@ -2604,6 +2890,10 @@ export const get_SystemPing = Type.Object({ requestFormat: Type.Literal("json"), parameters: Type.Never(), response: Type.Unknown(), + responses: Type.Object({ + 200: Type.Unknown(), + 500: Type.Unknown(), + }), responseHeaders: Type.Object({ swarm: Type.Union([ Type.Literal("inactive"), @@ -2628,6 +2918,10 @@ export const head_SystemPingHead = Type.Object({ requestFormat: Type.Literal("json"), parameters: Type.Never(), response: Type.Unknown(), + responses: Type.Object({ + 200: Type.Unknown(), + 500: Type.Unknown(), + }), responseHeaders: Type.Object({ swarm: Type.Union([ Type.Literal("inactive"), @@ -2665,6 +2959,11 @@ export const post_ImageCommit = Type.Object({ body: ContainerConfig, }), response: IdResponse, + responses: Type.Object({ + 201: IdResponse, + 404: ErrorResponse, + 500: ErrorResponse, + }), }); export type get_SystemEvents = Static; @@ -2682,6 +2981,11 @@ export const get_SystemEvents = Type.Object({ ), }), response: EventMessage, + responses: Type.Object({ + 200: EventMessage, + 400: ErrorResponse, + 500: ErrorResponse, + }), }); export type get_SystemDataUsage = Static; @@ -2712,6 +3016,18 @@ export const get_SystemDataUsage = Type.Object({ BuildCache: Type.Array(BuildCache), }), ), + responses: Type.Object({ + 200: Type.Partial( + Type.Object({ + LayersSize: Type.Number(), + Images: Type.Array(ImageSummary), + Containers: Type.Array(ContainerSummary), + Volumes: Type.Array(Volume), + BuildCache: Type.Array(BuildCache), + }), + ), + 500: ErrorResponse, + }), }); export type get_ImageGet = Static; @@ -2725,6 +3041,10 @@ export const get_ImageGet = Type.Object({ }), }), response: Type.Unknown(), + responses: Type.Object({ + 200: Type.Unknown(), + 500: Type.Unknown(), + }), }); export type get_ImageGetAll = Static; @@ -2740,6 +3060,10 @@ export const get_ImageGetAll = Type.Object({ ), }), response: Type.Unknown(), + responses: Type.Object({ + 200: Type.Unknown(), + 500: Type.Unknown(), + }), }); export type post_ImageLoad = Static; @@ -2755,6 +3079,10 @@ export const post_ImageLoad = Type.Object({ ), }), response: Type.Unknown(), + responses: Type.Object({ + 200: Type.Unknown(), + 500: ErrorResponse, + }), }); export type post_ContainerExec = Static; @@ -2783,6 +3111,12 @@ export const post_ContainerExec = Type.Object({ ), }), response: IdResponse, + responses: Type.Object({ + 201: IdResponse, + 404: ErrorResponse, + 409: ErrorResponse, + 500: ErrorResponse, + }), }); export type post_ExecStart = Static; @@ -2803,6 +3137,11 @@ export const post_ExecStart = Type.Object({ ), }), response: Type.Unknown(), + responses: Type.Object({ + 200: Type.Unknown(), + 404: Type.Unknown(), + 409: Type.Unknown(), + }), }); export type post_ExecResize = Static; @@ -2822,6 +3161,12 @@ export const post_ExecResize = Type.Object({ }), }), response: Type.Unknown(), + responses: Type.Object({ + 200: Type.Unknown(), + 400: ErrorResponse, + 404: ErrorResponse, + 500: ErrorResponse, + }), }); export type get_ExecInspect = Static; @@ -2849,6 +3194,25 @@ export const get_ExecInspect = Type.Object({ Pid: Type.Number(), }), ), + responses: Type.Object({ + 200: Type.Partial( + Type.Object({ + CanRemove: Type.Boolean(), + DetachKeys: Type.String(), + ID: Type.String(), + Running: Type.Boolean(), + ExitCode: Type.Number(), + ProcessConfig: ProcessConfig, + OpenStdin: Type.Boolean(), + OpenStderr: Type.Boolean(), + OpenStdout: Type.Boolean(), + ContainerID: Type.String(), + Pid: Type.Number(), + }), + ), + 404: ErrorResponse, + 500: ErrorResponse, + }), }); export type get_VolumeList = Static; @@ -2864,6 +3228,10 @@ export const get_VolumeList = Type.Object({ ), }), response: VolumeListResponse, + responses: Type.Object({ + 200: VolumeListResponse, + 500: ErrorResponse, + }), }); export type post_VolumeCreate = Static; @@ -2875,6 +3243,10 @@ export const post_VolumeCreate = Type.Object({ body: VolumeCreateOptions, }), response: Volume, + responses: Type.Object({ + 201: Volume, + 500: ErrorResponse, + }), }); export type get_VolumeInspect = Static; @@ -2888,6 +3260,11 @@ export const get_VolumeInspect = Type.Object({ }), }), response: Volume, + responses: Type.Object({ + 200: Volume, + 404: ErrorResponse, + 500: ErrorResponse, + }), }); export type put_VolumeUpdate = Static; @@ -2909,6 +3286,13 @@ export const put_VolumeUpdate = Type.Object({ ), }), response: Type.Unknown(), + responses: Type.Object({ + 200: Type.Unknown(), + 400: ErrorResponse, + 404: ErrorResponse, + 500: ErrorResponse, + 503: ErrorResponse, + }), }); export type delete_VolumeDelete = Static; @@ -2927,6 +3311,12 @@ export const delete_VolumeDelete = Type.Object({ }), }), response: Type.Unknown(), + responses: Type.Object({ + 204: Type.Unknown(), + 404: ErrorResponse, + 409: ErrorResponse, + 500: ErrorResponse, + }), }); export type post_VolumePrune = Static; @@ -2947,6 +3337,15 @@ export const post_VolumePrune = Type.Object({ SpaceReclaimed: Type.Number(), }), ), + responses: Type.Object({ + 200: Type.Partial( + Type.Object({ + VolumesDeleted: Type.Array(Type.String()), + SpaceReclaimed: Type.Number(), + }), + ), + 500: ErrorResponse, + }), }); export type get_NetworkList = Static; @@ -2962,6 +3361,10 @@ export const get_NetworkList = Type.Object({ ), }), response: Type.Array(Network), + responses: Type.Object({ + 200: Type.Array(Network), + 500: ErrorResponse, + }), }); export type get_NetworkInspect = Static; @@ -2981,6 +3384,11 @@ export const get_NetworkInspect = Type.Object({ }), }), response: Network, + responses: Type.Object({ + 200: Network, + 404: ErrorResponse, + 500: ErrorResponse, + }), }); export type delete_NetworkDelete = Static; @@ -2994,6 +3402,12 @@ export const delete_NetworkDelete = Type.Object({ }), }), response: Type.Unknown(), + responses: Type.Object({ + 204: Type.Unknown(), + 403: ErrorResponse, + 404: ErrorResponse, + 500: ErrorResponse, + }), }); export type post_NetworkCreate = Static; @@ -3021,6 +3435,17 @@ export const post_NetworkCreate = Type.Object({ Warning: Type.String(), }), ), + responses: Type.Object({ + 201: Type.Partial( + Type.Object({ + Id: Type.String(), + Warning: Type.String(), + }), + ), + 403: ErrorResponse, + 404: ErrorResponse, + 500: ErrorResponse, + }), }); export type post_NetworkConnect = Static; @@ -3040,6 +3465,12 @@ export const post_NetworkConnect = Type.Object({ ), }), response: Type.Unknown(), + responses: Type.Object({ + 200: Type.Unknown(), + 403: ErrorResponse, + 404: ErrorResponse, + 500: ErrorResponse, + }), }); export type post_NetworkDisconnect = Static; @@ -3059,6 +3490,12 @@ export const post_NetworkDisconnect = Type.Object({ ), }), response: Type.Unknown(), + responses: Type.Object({ + 200: Type.Unknown(), + 403: ErrorResponse, + 404: ErrorResponse, + 500: ErrorResponse, + }), }); export type post_NetworkPrune = Static; @@ -3078,6 +3515,14 @@ export const post_NetworkPrune = Type.Object({ NetworksDeleted: Type.Array(Type.String()), }), ), + responses: Type.Object({ + 200: Type.Partial( + Type.Object({ + NetworksDeleted: Type.Array(Type.String()), + }), + ), + 500: ErrorResponse, + }), }); export type get_PluginList = Static; @@ -3093,6 +3538,10 @@ export const get_PluginList = Type.Object({ ), }), response: Type.Array(Plugin), + responses: Type.Object({ + 200: Type.Array(Plugin), + 500: ErrorResponse, + }), }); export type get_GetPluginPrivileges = Static; @@ -3106,6 +3555,10 @@ export const get_GetPluginPrivileges = Type.Object({ }), }), response: Type.Array(PluginPrivilege), + responses: Type.Object({ + 200: Type.Array(PluginPrivilege), + 500: ErrorResponse, + }), }); export type post_PluginPull = Static; @@ -3126,6 +3579,10 @@ export const post_PluginPull = Type.Object({ body: Type.Array(PluginPrivilege), }), response: Type.Unknown(), + responses: Type.Object({ + 204: Type.Unknown(), + 500: ErrorResponse, + }), }); export type get_PluginInspect = Static; @@ -3139,6 +3596,11 @@ export const get_PluginInspect = Type.Object({ }), }), response: Plugin, + responses: Type.Object({ + 200: Plugin, + 404: ErrorResponse, + 500: ErrorResponse, + }), }); export type delete_PluginDelete = Static; @@ -3157,6 +3619,11 @@ export const delete_PluginDelete = Type.Object({ }), }), response: Plugin, + responses: Type.Object({ + 200: Plugin, + 404: ErrorResponse, + 500: ErrorResponse, + }), }); export type post_PluginEnable = Static; @@ -3175,6 +3642,11 @@ export const post_PluginEnable = Type.Object({ }), }), response: Type.Unknown(), + responses: Type.Object({ + 200: Type.Unknown(), + 404: ErrorResponse, + 500: ErrorResponse, + }), }); export type post_PluginDisable = Static; @@ -3193,6 +3665,11 @@ export const post_PluginDisable = Type.Object({ }), }), response: Type.Unknown(), + responses: Type.Object({ + 200: Type.Unknown(), + 404: ErrorResponse, + 500: ErrorResponse, + }), }); export type post_PluginUpgrade = Static; @@ -3215,6 +3692,11 @@ export const post_PluginUpgrade = Type.Object({ body: Type.Array(PluginPrivilege), }), response: Type.Unknown(), + responses: Type.Object({ + 204: Type.Unknown(), + 404: ErrorResponse, + 500: ErrorResponse, + }), }); export type post_PluginCreate = Static; @@ -3228,6 +3710,10 @@ export const post_PluginCreate = Type.Object({ }), }), response: Type.Unknown(), + responses: Type.Object({ + 204: Type.Unknown(), + 500: ErrorResponse, + }), }); export type post_PluginPush = Static; @@ -3241,6 +3727,11 @@ export const post_PluginPush = Type.Object({ }), }), response: Type.Unknown(), + responses: Type.Object({ + 200: Type.Unknown(), + 404: ErrorResponse, + 500: ErrorResponse, + }), }); export type post_PluginSet = Static; @@ -3255,6 +3746,11 @@ export const post_PluginSet = Type.Object({ body: Type.Array(Type.String()), }), response: Type.Unknown(), + responses: Type.Object({ + 204: Type.Unknown(), + 404: ErrorResponse, + 500: ErrorResponse, + }), }); export type get_NodeList = Static; @@ -3270,6 +3766,11 @@ export const get_NodeList = Type.Object({ ), }), response: Type.Array(Node), + responses: Type.Object({ + 200: Type.Array(Node), + 500: ErrorResponse, + 503: ErrorResponse, + }), }); export type get_NodeInspect = Static; @@ -3283,6 +3784,12 @@ export const get_NodeInspect = Type.Object({ }), }), response: Node, + responses: Type.Object({ + 200: Node, + 404: ErrorResponse, + 500: ErrorResponse, + 503: ErrorResponse, + }), }); export type delete_NodeDelete = Static; @@ -3301,6 +3808,12 @@ export const delete_NodeDelete = Type.Object({ }), }), response: Type.Unknown(), + responses: Type.Object({ + 200: Type.Unknown(), + 404: ErrorResponse, + 500: ErrorResponse, + 503: ErrorResponse, + }), }); export type post_NodeUpdate = Static; @@ -3318,6 +3831,13 @@ export const post_NodeUpdate = Type.Object({ body: NodeSpec, }), response: Type.Unknown(), + responses: Type.Object({ + 200: Type.Unknown(), + 400: ErrorResponse, + 404: ErrorResponse, + 500: ErrorResponse, + 503: ErrorResponse, + }), }); export type get_SwarmInspect = Static; @@ -3327,6 +3847,12 @@ export const get_SwarmInspect = Type.Object({ requestFormat: Type.Literal("json"), parameters: Type.Never(), response: Swarm, + responses: Type.Object({ + 200: Swarm, + 404: ErrorResponse, + 500: ErrorResponse, + 503: ErrorResponse, + }), }); export type post_SwarmInit = Static; @@ -3349,6 +3875,12 @@ export const post_SwarmInit = Type.Object({ ), }), response: Type.String(), + responses: Type.Object({ + 200: Type.String(), + 400: ErrorResponse, + 500: ErrorResponse, + 503: ErrorResponse, + }), }); export type post_SwarmJoin = Static; @@ -3368,6 +3900,12 @@ export const post_SwarmJoin = Type.Object({ ), }), response: Type.Unknown(), + responses: Type.Object({ + 200: Type.Unknown(), + 400: ErrorResponse, + 500: ErrorResponse, + 503: ErrorResponse, + }), }); export type post_SwarmLeave = Static; @@ -3383,6 +3921,11 @@ export const post_SwarmLeave = Type.Object({ ), }), response: Type.Unknown(), + responses: Type.Object({ + 200: Type.Unknown(), + 500: ErrorResponse, + 503: ErrorResponse, + }), }); export type post_SwarmUpdate = Static; @@ -3400,6 +3943,12 @@ export const post_SwarmUpdate = Type.Object({ body: SwarmSpec, }), response: Type.Unknown(), + responses: Type.Object({ + 200: Type.Unknown(), + 400: ErrorResponse, + 500: ErrorResponse, + 503: ErrorResponse, + }), }); export type get_SwarmUnlockkey = Static; @@ -3413,6 +3962,15 @@ export const get_SwarmUnlockkey = Type.Object({ UnlockKey: Type.String(), }), ), + responses: Type.Object({ + 200: Type.Partial( + Type.Object({ + UnlockKey: Type.String(), + }), + ), + 500: ErrorResponse, + 503: ErrorResponse, + }), }); export type post_SwarmUnlock = Static; @@ -3428,6 +3986,11 @@ export const post_SwarmUnlock = Type.Object({ ), }), response: Type.Unknown(), + responses: Type.Object({ + 200: Type.Unknown(), + 500: ErrorResponse, + 503: ErrorResponse, + }), }); export type get_ServiceList = Static; @@ -3444,6 +4007,11 @@ export const get_ServiceList = Type.Object({ ), }), response: Type.Array(Service), + responses: Type.Object({ + 200: Type.Array(Service), + 500: ErrorResponse, + 503: ErrorResponse, + }), }); export type post_ServiceCreate = Static; @@ -3465,6 +4033,19 @@ export const post_ServiceCreate = Type.Object({ Warning: Type.String(), }), ), + responses: Type.Object({ + 201: Type.Partial( + Type.Object({ + ID: Type.String(), + Warning: Type.String(), + }), + ), + 400: ErrorResponse, + 403: ErrorResponse, + 409: ErrorResponse, + 500: ErrorResponse, + 503: ErrorResponse, + }), }); export type get_ServiceInspect = Static; @@ -3483,6 +4064,12 @@ export const get_ServiceInspect = Type.Object({ }), }), response: Service, + responses: Type.Object({ + 200: Service, + 404: ErrorResponse, + 500: ErrorResponse, + 503: ErrorResponse, + }), }); export type delete_ServiceDelete = Static; @@ -3496,6 +4083,12 @@ export const delete_ServiceDelete = Type.Object({ }), }), response: Type.Unknown(), + responses: Type.Object({ + 200: Type.Unknown(), + 404: ErrorResponse, + 500: ErrorResponse, + 503: ErrorResponse, + }), }); export type post_ServiceUpdate = Static; @@ -3522,6 +4115,13 @@ export const post_ServiceUpdate = Type.Object({ body: Type.Intersect([ServiceSpec, Type.Record(Type.String(), Type.Unknown())]), }), response: ServiceUpdateResponse, + responses: Type.Object({ + 200: ServiceUpdateResponse, + 400: ErrorResponse, + 404: ErrorResponse, + 500: ErrorResponse, + 503: ErrorResponse, + }), }); export type get_ServiceLogs = Static; @@ -3546,6 +4146,12 @@ export const get_ServiceLogs = Type.Object({ }), }), response: Type.Unknown(), + responses: Type.Object({ + 200: Type.Unknown(), + 404: Type.Unknown(), + 500: Type.Unknown(), + 503: Type.Unknown(), + }), }); export type get_TaskList = Static; @@ -3561,6 +4167,11 @@ export const get_TaskList = Type.Object({ ), }), response: Type.Array(Task), + responses: Type.Object({ + 200: Type.Array(Task), + 500: ErrorResponse, + 503: ErrorResponse, + }), }); export type get_TaskInspect = Static; @@ -3574,6 +4185,12 @@ export const get_TaskInspect = Type.Object({ }), }), response: Task, + responses: Type.Object({ + 200: Task, + 404: ErrorResponse, + 500: ErrorResponse, + 503: ErrorResponse, + }), }); export type get_TaskLogs = Static; @@ -3598,6 +4215,12 @@ export const get_TaskLogs = Type.Object({ }), }), response: Type.Unknown(), + responses: Type.Object({ + 200: Type.Unknown(), + 404: Type.Unknown(), + 500: Type.Unknown(), + 503: Type.Unknown(), + }), }); export type get_SecretList = Static; @@ -3613,6 +4236,11 @@ export const get_SecretList = Type.Object({ ), }), response: Type.Array(Secret), + responses: Type.Object({ + 200: Type.Array(Secret), + 500: ErrorResponse, + 503: ErrorResponse, + }), }); export type post_SecretCreate = Static; @@ -3624,6 +4252,12 @@ export const post_SecretCreate = Type.Object({ body: Type.Intersect([SecretSpec, Type.Record(Type.String(), Type.Unknown())]), }), response: IdResponse, + responses: Type.Object({ + 201: IdResponse, + 409: ErrorResponse, + 500: ErrorResponse, + 503: ErrorResponse, + }), }); export type get_SecretInspect = Static; @@ -3637,6 +4271,12 @@ export const get_SecretInspect = Type.Object({ }), }), response: Secret, + responses: Type.Object({ + 200: Secret, + 404: ErrorResponse, + 500: ErrorResponse, + 503: ErrorResponse, + }), }); export type delete_SecretDelete = Static; @@ -3650,6 +4290,12 @@ export const delete_SecretDelete = Type.Object({ }), }), response: Type.Unknown(), + responses: Type.Object({ + 204: Type.Unknown(), + 404: ErrorResponse, + 500: ErrorResponse, + 503: ErrorResponse, + }), }); export type post_SecretUpdate = Static; @@ -3667,6 +4313,13 @@ export const post_SecretUpdate = Type.Object({ body: SecretSpec, }), response: Type.Unknown(), + responses: Type.Object({ + 200: Type.Unknown(), + 400: ErrorResponse, + 404: ErrorResponse, + 500: ErrorResponse, + 503: ErrorResponse, + }), }); export type get_ConfigList = Static; @@ -3682,6 +4335,11 @@ export const get_ConfigList = Type.Object({ ), }), response: Type.Array(Config), + responses: Type.Object({ + 200: Type.Array(Config), + 500: ErrorResponse, + 503: ErrorResponse, + }), }); export type post_ConfigCreate = Static; @@ -3693,6 +4351,12 @@ export const post_ConfigCreate = Type.Object({ body: Type.Intersect([ConfigSpec, Type.Record(Type.String(), Type.Unknown())]), }), response: IdResponse, + responses: Type.Object({ + 201: IdResponse, + 409: ErrorResponse, + 500: ErrorResponse, + 503: ErrorResponse, + }), }); export type get_ConfigInspect = Static; @@ -3706,6 +4370,12 @@ export const get_ConfigInspect = Type.Object({ }), }), response: Config, + responses: Type.Object({ + 200: Config, + 404: ErrorResponse, + 500: ErrorResponse, + 503: ErrorResponse, + }), }); export type delete_ConfigDelete = Static; @@ -3719,6 +4389,12 @@ export const delete_ConfigDelete = Type.Object({ }), }), response: Type.Unknown(), + responses: Type.Object({ + 204: Type.Unknown(), + 404: ErrorResponse, + 500: ErrorResponse, + 503: ErrorResponse, + }), }); export type post_ConfigUpdate = Static; @@ -3736,6 +4412,13 @@ export const post_ConfigUpdate = Type.Object({ body: ConfigSpec, }), response: Type.Unknown(), + responses: Type.Object({ + 200: Type.Unknown(), + 400: ErrorResponse, + 404: ErrorResponse, + 500: ErrorResponse, + 503: ErrorResponse, + }), }); export type get_DistributionInspect = Static; @@ -3749,6 +4432,11 @@ export const get_DistributionInspect = Type.Object({ }), }), response: DistributionInspect, + responses: Type.Object({ + 200: DistributionInspect, + 401: ErrorResponse, + 500: ErrorResponse, + }), }); export type post_Session = Static; @@ -3758,6 +4446,11 @@ export const post_Session = Type.Object({ requestFormat: Type.Literal("json"), parameters: Type.Never(), response: Type.Unknown(), + responses: Type.Object({ + 101: Type.Unknown(), + 400: Type.Unknown(), + 500: Type.Unknown(), + }), }); type __ENDPOINTS_END__ = Static; @@ -3910,6 +4603,7 @@ type RequestFormat = "json" | "form-data" | "form-url" | "binary" | "text"; export type DefaultEndpoint = { parameters?: EndpointParameters | undefined; response: unknown; + responses?: Record; responseHeaders?: Record; }; @@ -3925,11 +4619,97 @@ export type Endpoint = { areParametersRequired: boolean; }; response: TConfig["response"]; + responses?: TConfig["responses"]; responseHeaders?: TConfig["responseHeaders"]; }; export type Fetcher = (method: Method, url: string, parameters?: EndpointParameters | undefined) => Promise; +// Status code type for success responses +export type SuccessStatusCode = + | 200 + | 201 + | 202 + | 203 + | 204 + | 205 + | 206 + | 207 + | 208 + | 226 + | 300 + | 301 + | 302 + | 303 + | 304 + | 305 + | 306 + | 307 + | 308; + +// Error handling types +export type TypedApiResponse< + TSuccess, + TAllResponses extends Record = {}, +> = keyof TAllResponses extends never + ? Omit & { + ok: true; + status: number; + data: TSuccess; + json: () => Promise; + } + : { + [K in keyof TAllResponses]: K extends string + ? K extends `${infer TStatusCode extends number}` + ? TStatusCode extends SuccessStatusCode + ? Omit & { + ok: true; + status: TStatusCode; + data: TSuccess; + json: () => Promise; + } + : Omit & { + ok: false; + status: TStatusCode; + data: TAllResponses[K]; + json: () => Promise; + } + : never + : K extends number + ? K extends SuccessStatusCode + ? Omit & { + ok: true; + status: K; + data: TSuccess; + json: () => Promise; + } + : Omit & { + ok: false; + status: K; + data: TAllResponses[K]; + json: () => Promise; + } + : never; + }[keyof TAllResponses]; + +export type SafeApiResponse = TEndpoint extends { response: infer TSuccess; responses: infer TResponses } + ? TResponses extends Record + ? TypedApiResponse + : Omit & { + ok: true; + status: number; + data: TSuccess; + json: () => Promise; + } + : TEndpoint extends { response: infer TSuccess } + ? Omit & { + ok: true; + status: number; + data: TSuccess; + json: () => Promise; + } + : never; + type RequiredKeys = { [P in keyof T]-?: undefined extends T[P] ? never : P; }[keyof T]; @@ -3960,55 +4740,227 @@ export class ApiClient { // get( path: Path, - ...params: MaybeOptionalArg["parameters"]> - ): Promise["response"]> { - return this.fetcher("get", this.baseUrl + path, params[0]).then((response) => - this.parseResponse(response), - ) as Promise["response"]>; + ...params: MaybeOptionalArg["parameters"] & { withResponse?: false }> + ): Promise["response"]>; + + get( + path: Path, + ...params: MaybeOptionalArg["parameters"] & { withResponse: true }> + ): Promise>; + + get( + path: Path, + ...params: MaybeOptionalArg + ): Promise { + const requestParams = params[0]; + const withResponse = requestParams?.withResponse; + + // Remove withResponse from params before passing to fetcher + const { withResponse: _, ...fetchParams } = requestParams || {}; + + if (withResponse) { + return this.fetcher("get", this.baseUrl + path, Object.keys(fetchParams).length ? fetchParams : undefined).then( + async (response) => { + // Parse the response data + const data = await this.parseResponse(response); + + // Override properties while keeping the original Response object + const typedResponse = Object.assign(response, { + ok: response.ok, + status: response.status, + data: data, + json: () => Promise.resolve(data), + }); + return typedResponse; + }, + ); + } else { + return this.fetcher("get", this.baseUrl + path, requestParams).then((response) => + this.parseResponse(response), + ) as Promise["response"]>; + } } // // post( path: Path, - ...params: MaybeOptionalArg["parameters"]> - ): Promise["response"]> { - return this.fetcher("post", this.baseUrl + path, params[0]).then((response) => - this.parseResponse(response), - ) as Promise["response"]>; + ...params: MaybeOptionalArg["parameters"] & { withResponse?: false }> + ): Promise["response"]>; + + post( + path: Path, + ...params: MaybeOptionalArg["parameters"] & { withResponse: true }> + ): Promise>; + + post( + path: Path, + ...params: MaybeOptionalArg + ): Promise { + const requestParams = params[0]; + const withResponse = requestParams?.withResponse; + + // Remove withResponse from params before passing to fetcher + const { withResponse: _, ...fetchParams } = requestParams || {}; + + if (withResponse) { + return this.fetcher("post", this.baseUrl + path, Object.keys(fetchParams).length ? fetchParams : undefined).then( + async (response) => { + // Parse the response data + const data = await this.parseResponse(response); + + // Override properties while keeping the original Response object + const typedResponse = Object.assign(response, { + ok: response.ok, + status: response.status, + data: data, + json: () => Promise.resolve(data), + }); + return typedResponse; + }, + ); + } else { + return this.fetcher("post", this.baseUrl + path, requestParams).then((response) => + this.parseResponse(response), + ) as Promise["response"]>; + } } // // delete( path: Path, - ...params: MaybeOptionalArg["parameters"]> - ): Promise["response"]> { - return this.fetcher("delete", this.baseUrl + path, params[0]).then((response) => - this.parseResponse(response), - ) as Promise["response"]>; + ...params: MaybeOptionalArg["parameters"] & { withResponse?: false }> + ): Promise["response"]>; + + delete( + path: Path, + ...params: MaybeOptionalArg["parameters"] & { withResponse: true }> + ): Promise>; + + delete( + path: Path, + ...params: MaybeOptionalArg + ): Promise { + const requestParams = params[0]; + const withResponse = requestParams?.withResponse; + + // Remove withResponse from params before passing to fetcher + const { withResponse: _, ...fetchParams } = requestParams || {}; + + if (withResponse) { + return this.fetcher( + "delete", + this.baseUrl + path, + Object.keys(fetchParams).length ? fetchParams : undefined, + ).then(async (response) => { + // Parse the response data + const data = await this.parseResponse(response); + + // Override properties while keeping the original Response object + const typedResponse = Object.assign(response, { + ok: response.ok, + status: response.status, + data: data, + json: () => Promise.resolve(data), + }); + return typedResponse; + }); + } else { + return this.fetcher("delete", this.baseUrl + path, requestParams).then((response) => + this.parseResponse(response), + ) as Promise["response"]>; + } } // // put( path: Path, - ...params: MaybeOptionalArg["parameters"]> - ): Promise["response"]> { - return this.fetcher("put", this.baseUrl + path, params[0]).then((response) => - this.parseResponse(response), - ) as Promise["response"]>; + ...params: MaybeOptionalArg["parameters"] & { withResponse?: false }> + ): Promise["response"]>; + + put( + path: Path, + ...params: MaybeOptionalArg["parameters"] & { withResponse: true }> + ): Promise>; + + put( + path: Path, + ...params: MaybeOptionalArg + ): Promise { + const requestParams = params[0]; + const withResponse = requestParams?.withResponse; + + // Remove withResponse from params before passing to fetcher + const { withResponse: _, ...fetchParams } = requestParams || {}; + + if (withResponse) { + return this.fetcher("put", this.baseUrl + path, Object.keys(fetchParams).length ? fetchParams : undefined).then( + async (response) => { + // Parse the response data + const data = await this.parseResponse(response); + + // Override properties while keeping the original Response object + const typedResponse = Object.assign(response, { + ok: response.ok, + status: response.status, + data: data, + json: () => Promise.resolve(data), + }); + return typedResponse; + }, + ); + } else { + return this.fetcher("put", this.baseUrl + path, requestParams).then((response) => + this.parseResponse(response), + ) as Promise["response"]>; + } } // // head( path: Path, - ...params: MaybeOptionalArg["parameters"]> - ): Promise["response"]> { - return this.fetcher("head", this.baseUrl + path, params[0]).then((response) => - this.parseResponse(response), - ) as Promise["response"]>; + ...params: MaybeOptionalArg["parameters"] & { withResponse?: false }> + ): Promise["response"]>; + + head( + path: Path, + ...params: MaybeOptionalArg["parameters"] & { withResponse: true }> + ): Promise>; + + head( + path: Path, + ...params: MaybeOptionalArg + ): Promise { + const requestParams = params[0]; + const withResponse = requestParams?.withResponse; + + // Remove withResponse from params before passing to fetcher + const { withResponse: _, ...fetchParams } = requestParams || {}; + + if (withResponse) { + return this.fetcher("head", this.baseUrl + path, Object.keys(fetchParams).length ? fetchParams : undefined).then( + async (response) => { + // Parse the response data + const data = await this.parseResponse(response); + + // Override properties while keeping the original Response object + const typedResponse = Object.assign(response, { + ok: response.ok, + status: response.status, + data: data, + json: () => Promise.resolve(data), + }); + return typedResponse; + }, + ); + } else { + return this.fetcher("head", this.baseUrl + path, requestParams).then((response) => + this.parseResponse(response), + ) as Promise["response"]>; + } } // @@ -4047,6 +4999,21 @@ export function createApiClient(fetcher: Fetcher, baseUrl?: string) { api.get("/users").then((users) => console.log(users)); api.post("/users", { body: { name: "John" } }).then((user) => console.log(user)); api.put("/users/:id", { path: { id: 1 }, body: { name: "John" } }).then((user) => console.log(user)); + + // With error handling + const result = await api.get("/users/{id}", { path: { id: "123" }, withResponse: true }); + if (result.ok) { + // Access data directly + const user = result.data; + console.log(user); + + // Or use the json() method for compatibility + const userFromJson = await result.json(); + console.log(userFromJson); + } else { + const error = result.data; + console.error(`Error ${result.status}:`, error); + } */ // ; @@ -1683,6 +1688,13 @@ export const post_ContainerCreate = v.object({ ]), }), response: ContainerCreateResponse, + responses: v.object({ + "201": ContainerCreateResponse, + "400": ErrorResponse, + "404": ErrorResponse, + "409": ErrorResponse, + "500": ErrorResponse, + }), }); export type get_ContainerInspect = v.InferOutput; @@ -1725,6 +1737,37 @@ export const get_ContainerInspect = v.object({ Config: v.optional(ContainerConfig), NetworkSettings: v.optional(NetworkSettings), }), + responses: v.object({ + "200": v.object({ + Id: v.optional(v.string()), + Created: v.optional(v.string()), + Path: v.optional(v.string()), + Args: v.optional(v.array(v.string())), + State: v.optional(ContainerState), + Image: v.optional(v.string()), + ResolvConfPath: v.optional(v.string()), + HostnamePath: v.optional(v.string()), + HostsPath: v.optional(v.string()), + LogPath: v.optional(v.string()), + Name: v.optional(v.string()), + RestartCount: v.optional(v.number()), + Driver: v.optional(v.string()), + Platform: v.optional(v.string()), + MountLabel: v.optional(v.string()), + ProcessLabel: v.optional(v.string()), + AppArmorProfile: v.optional(v.string()), + ExecIDs: v.optional(v.union([v.array(v.string()), v.null()])), + HostConfig: v.optional(HostConfig), + GraphDriver: v.optional(GraphDriverData), + SizeRw: v.optional(v.number()), + SizeRootFs: v.optional(v.number()), + Mounts: v.optional(v.array(MountPoint)), + Config: v.optional(ContainerConfig), + NetworkSettings: v.optional(NetworkSettings), + }), + "404": ErrorResponse, + "500": ErrorResponse, + }), }); export type get_ContainerTop = v.InferOutput; @@ -1744,6 +1787,14 @@ export const get_ContainerTop = v.object({ Titles: v.optional(v.array(v.string())), Processes: v.optional(v.array(v.array(v.string()))), }), + responses: v.object({ + "200": v.object({ + Titles: v.optional(v.array(v.string())), + Processes: v.optional(v.array(v.array(v.string()))), + }), + "404": ErrorResponse, + "500": ErrorResponse, + }), }); export type get_ContainerLogs = v.InferOutput; @@ -1766,6 +1817,11 @@ export const get_ContainerLogs = v.object({ }), }), response: v.unknown(), + responses: v.object({ + "200": v.unknown(), + "404": v.unknown(), + "500": v.unknown(), + }), }); export type get_ContainerChanges = v.InferOutput; @@ -1779,6 +1835,11 @@ export const get_ContainerChanges = v.object({ }), }), response: v.array(FilesystemChange), + responses: v.object({ + "200": v.array(FilesystemChange), + "404": ErrorResponse, + "500": ErrorResponse, + }), }); export type get_ContainerExport = v.InferOutput; @@ -1792,6 +1853,11 @@ export const get_ContainerExport = v.object({ }), }), response: v.unknown(), + responses: v.object({ + "200": v.unknown(), + "404": v.unknown(), + "500": v.unknown(), + }), }); export type get_ContainerStats = v.InferOutput; @@ -1809,6 +1875,11 @@ export const get_ContainerStats = v.object({ }), }), response: v.record(v.string(), v.unknown()), + responses: v.object({ + "200": v.record(v.string(), v.unknown()), + "404": ErrorResponse, + "500": ErrorResponse, + }), }); export type post_ContainerResize = v.InferOutput; @@ -1826,6 +1897,11 @@ export const post_ContainerResize = v.object({ }), }), response: v.unknown(), + responses: v.object({ + "200": v.unknown(), + "404": v.unknown(), + "500": v.unknown(), + }), }); export type post_ContainerStart = v.InferOutput; @@ -1842,6 +1918,12 @@ export const post_ContainerStart = v.object({ }), }), response: v.unknown(), + responses: v.object({ + "204": v.unknown(), + "304": v.unknown(), + "404": ErrorResponse, + "500": ErrorResponse, + }), }); export type post_ContainerStop = v.InferOutput; @@ -1859,6 +1941,12 @@ export const post_ContainerStop = v.object({ }), }), response: v.unknown(), + responses: v.object({ + "204": v.unknown(), + "304": v.unknown(), + "404": ErrorResponse, + "500": ErrorResponse, + }), }); export type post_ContainerRestart = v.InferOutput; @@ -1876,6 +1964,11 @@ export const post_ContainerRestart = v.object({ }), }), response: v.unknown(), + responses: v.object({ + "204": v.unknown(), + "404": ErrorResponse, + "500": ErrorResponse, + }), }); export type post_ContainerKill = v.InferOutput; @@ -1892,6 +1985,12 @@ export const post_ContainerKill = v.object({ }), }), response: v.unknown(), + responses: v.object({ + "204": v.unknown(), + "404": ErrorResponse, + "409": ErrorResponse, + "500": ErrorResponse, + }), }); export type post_ContainerUpdate = v.InferOutput; @@ -1913,6 +2012,13 @@ export const post_ContainerUpdate = v.object({ response: v.object({ Warnings: v.optional(v.array(v.string())), }), + responses: v.object({ + "200": v.object({ + Warnings: v.optional(v.array(v.string())), + }), + "404": ErrorResponse, + "500": ErrorResponse, + }), }); export type post_ContainerRename = v.InferOutput; @@ -1929,6 +2035,12 @@ export const post_ContainerRename = v.object({ }), }), response: v.unknown(), + responses: v.object({ + "204": v.unknown(), + "404": ErrorResponse, + "409": ErrorResponse, + "500": ErrorResponse, + }), }); export type post_ContainerPause = v.InferOutput; @@ -1942,6 +2054,11 @@ export const post_ContainerPause = v.object({ }), }), response: v.unknown(), + responses: v.object({ + "204": v.unknown(), + "404": ErrorResponse, + "500": ErrorResponse, + }), }); export type post_ContainerUnpause = v.InferOutput; @@ -1955,6 +2072,11 @@ export const post_ContainerUnpause = v.object({ }), }), response: v.unknown(), + responses: v.object({ + "204": v.unknown(), + "404": ErrorResponse, + "500": ErrorResponse, + }), }); export type post_ContainerAttach = v.InferOutput; @@ -1976,6 +2098,13 @@ export const post_ContainerAttach = v.object({ }), }), response: v.unknown(), + responses: v.object({ + "101": v.unknown(), + "200": v.unknown(), + "400": v.unknown(), + "404": v.unknown(), + "500": v.unknown(), + }), }); export type get_ContainerAttachWebsocket = v.InferOutput; @@ -1997,6 +2126,13 @@ export const get_ContainerAttachWebsocket = v.object({ }), }), response: v.unknown(), + responses: v.object({ + "101": v.unknown(), + "200": v.unknown(), + "400": ErrorResponse, + "404": ErrorResponse, + "500": ErrorResponse, + }), }); export type post_ContainerWait = v.InferOutput; @@ -2013,6 +2149,12 @@ export const post_ContainerWait = v.object({ }), }), response: ContainerWaitResponse, + responses: v.object({ + "200": ContainerWaitResponse, + "400": ErrorResponse, + "404": ErrorResponse, + "500": ErrorResponse, + }), }); export type delete_ContainerDelete = v.InferOutput; @@ -2031,6 +2173,13 @@ export const delete_ContainerDelete = v.object({ }), }), response: v.unknown(), + responses: v.object({ + "204": v.unknown(), + "400": ErrorResponse, + "404": ErrorResponse, + "409": ErrorResponse, + "500": ErrorResponse, + }), }); export type get_ContainerArchive = v.InferOutput; @@ -2047,6 +2196,12 @@ export const get_ContainerArchive = v.object({ }), }), response: v.unknown(), + responses: v.object({ + "200": v.unknown(), + "400": v.unknown(), + "404": v.unknown(), + "500": v.unknown(), + }), }); export type put_PutContainerArchive = v.InferOutput; @@ -2066,6 +2221,13 @@ export const put_PutContainerArchive = v.object({ body: v.string(), }), response: v.unknown(), + responses: v.object({ + "200": v.unknown(), + "400": ErrorResponse, + "403": ErrorResponse, + "404": ErrorResponse, + "500": ErrorResponse, + }), }); export type head_ContainerArchiveInfo = v.InferOutput; @@ -2082,6 +2244,12 @@ export const head_ContainerArchiveInfo = v.object({ }), }), response: v.unknown(), + responses: v.object({ + "200": v.unknown(), + "400": ErrorResponse, + "404": ErrorResponse, + "500": ErrorResponse, + }), responseHeaders: v.object({ "x-docker-container-path-stat": v.string(), }), @@ -2101,6 +2269,13 @@ export const post_ContainerPrune = v.object({ ContainersDeleted: v.optional(v.array(v.string())), SpaceReclaimed: v.optional(v.number()), }), + responses: v.object({ + "200": v.object({ + ContainersDeleted: v.optional(v.array(v.string())), + SpaceReclaimed: v.optional(v.number()), + }), + "500": ErrorResponse, + }), }); export type get_ImageList = v.InferOutput; @@ -2117,6 +2292,10 @@ export const get_ImageList = v.object({ }), }), response: v.array(ImageSummary), + responses: v.object({ + "200": v.array(ImageSummary), + "500": ErrorResponse, + }), }); export type post_ImageBuild = v.InferOutput; @@ -2158,6 +2337,11 @@ export const post_ImageBuild = v.object({ body: v.string(), }), response: v.unknown(), + responses: v.object({ + "200": v.unknown(), + "400": ErrorResponse, + "500": ErrorResponse, + }), }); export type post_BuildPrune = v.InferOutput; @@ -2176,6 +2360,13 @@ export const post_BuildPrune = v.object({ CachesDeleted: v.optional(v.array(v.string())), SpaceReclaimed: v.optional(v.number()), }), + responses: v.object({ + "200": v.object({ + CachesDeleted: v.optional(v.array(v.string())), + SpaceReclaimed: v.optional(v.number()), + }), + "500": ErrorResponse, + }), }); export type post_ImageCreate = v.InferOutput; @@ -2199,6 +2390,11 @@ export const post_ImageCreate = v.object({ body: v.string(), }), response: v.unknown(), + responses: v.object({ + "200": v.unknown(), + "404": ErrorResponse, + "500": ErrorResponse, + }), }); export type get_ImageInspect = v.InferOutput; @@ -2212,6 +2408,11 @@ export const get_ImageInspect = v.object({ }), }), response: ImageInspect, + responses: v.object({ + "200": ImageInspect, + "404": ErrorResponse, + "500": ErrorResponse, + }), }); export type get_ImageHistory = v.InferOutput; @@ -2234,6 +2435,20 @@ export const get_ImageHistory = v.object({ Comment: v.string(), }), ), + responses: v.object({ + "200": v.array( + v.object({ + Id: v.string(), + Created: v.number(), + CreatedBy: v.string(), + Tags: v.array(v.string()), + Size: v.number(), + Comment: v.string(), + }), + ), + "404": ErrorResponse, + "500": ErrorResponse, + }), }); export type post_ImagePush = v.InferOutput; @@ -2253,6 +2468,11 @@ export const post_ImagePush = v.object({ }), }), response: v.unknown(), + responses: v.object({ + "200": v.unknown(), + "404": ErrorResponse, + "500": ErrorResponse, + }), }); export type post_ImageTag = v.InferOutput; @@ -2270,6 +2490,13 @@ export const post_ImageTag = v.object({ }), }), response: v.unknown(), + responses: v.object({ + "201": v.unknown(), + "400": ErrorResponse, + "404": ErrorResponse, + "409": ErrorResponse, + "500": ErrorResponse, + }), }); export type delete_ImageDelete = v.InferOutput; @@ -2287,6 +2514,12 @@ export const delete_ImageDelete = v.object({ }), }), response: v.array(ImageDeleteResponseItem), + responses: v.object({ + "200": v.array(ImageDeleteResponseItem), + "404": ErrorResponse, + "409": ErrorResponse, + "500": ErrorResponse, + }), }); export type get_ImageSearch = v.InferOutput; @@ -2310,6 +2543,18 @@ export const get_ImageSearch = v.object({ star_count: v.optional(v.number()), }), ), + responses: v.object({ + "200": v.array( + v.object({ + description: v.optional(v.string()), + is_official: v.optional(v.boolean()), + is_automated: v.optional(v.boolean()), + name: v.optional(v.string()), + star_count: v.optional(v.number()), + }), + ), + "500": ErrorResponse, + }), }); export type post_ImagePrune = v.InferOutput; @@ -2326,6 +2571,13 @@ export const post_ImagePrune = v.object({ ImagesDeleted: v.optional(v.array(ImageDeleteResponseItem)), SpaceReclaimed: v.optional(v.number()), }), + responses: v.object({ + "200": v.object({ + ImagesDeleted: v.optional(v.array(ImageDeleteResponseItem)), + SpaceReclaimed: v.optional(v.number()), + }), + "500": ErrorResponse, + }), }); export type post_SystemAuth = v.InferOutput; @@ -2336,7 +2588,19 @@ export const post_SystemAuth = v.object({ parameters: v.object({ body: AuthConfig, }), - response: v.unknown(), + response: v.object({ + Status: v.string(), + IdentityToken: v.optional(v.union([v.string(), v.undefined()])), + }), + responses: v.object({ + "200": v.object({ + Status: v.string(), + IdentityToken: v.optional(v.union([v.string(), v.undefined()])), + }), + "204": v.unknown(), + "401": ErrorResponse, + "500": ErrorResponse, + }), }); export type get_SystemInfo = v.InferOutput; @@ -2346,6 +2610,10 @@ export const get_SystemInfo = v.object({ requestFormat: v.literal("json"), parameters: v.never(), response: SystemInfo, + responses: v.object({ + "200": SystemInfo, + "500": ErrorResponse, + }), }); export type get_SystemVersion = v.InferOutput; @@ -2355,6 +2623,10 @@ export const get_SystemVersion = v.object({ requestFormat: v.literal("json"), parameters: v.never(), response: SystemVersion, + responses: v.object({ + "200": SystemVersion, + "500": ErrorResponse, + }), }); export type get_SystemPing = v.InferOutput; @@ -2364,6 +2636,10 @@ export const get_SystemPing = v.object({ requestFormat: v.literal("json"), parameters: v.never(), response: v.unknown(), + responses: v.object({ + "200": v.unknown(), + "500": v.unknown(), + }), responseHeaders: v.object({ swarm: v.union([ v.literal("inactive"), @@ -2388,6 +2664,10 @@ export const head_SystemPingHead = v.object({ requestFormat: v.literal("json"), parameters: v.never(), response: v.unknown(), + responses: v.object({ + "200": v.unknown(), + "500": v.unknown(), + }), responseHeaders: v.object({ swarm: v.union([ v.literal("inactive"), @@ -2423,6 +2703,11 @@ export const post_ImageCommit = v.object({ body: ContainerConfig, }), response: IdResponse, + responses: v.object({ + "201": IdResponse, + "404": ErrorResponse, + "500": ErrorResponse, + }), }); export type get_SystemEvents = v.InferOutput; @@ -2438,6 +2723,11 @@ export const get_SystemEvents = v.object({ }), }), response: EventMessage, + responses: v.object({ + "200": EventMessage, + "400": ErrorResponse, + "500": ErrorResponse, + }), }); export type get_SystemDataUsage = v.InferOutput; @@ -2459,6 +2749,16 @@ export const get_SystemDataUsage = v.object({ Volumes: v.optional(v.array(Volume)), BuildCache: v.optional(v.array(BuildCache)), }), + responses: v.object({ + "200": v.object({ + LayersSize: v.optional(v.number()), + Images: v.optional(v.array(ImageSummary)), + Containers: v.optional(v.array(ContainerSummary)), + Volumes: v.optional(v.array(Volume)), + BuildCache: v.optional(v.array(BuildCache)), + }), + "500": ErrorResponse, + }), }); export type get_ImageGet = v.InferOutput; @@ -2472,6 +2772,10 @@ export const get_ImageGet = v.object({ }), }), response: v.unknown(), + responses: v.object({ + "200": v.unknown(), + "500": v.unknown(), + }), }); export type get_ImageGetAll = v.InferOutput; @@ -2485,6 +2789,10 @@ export const get_ImageGetAll = v.object({ }), }), response: v.unknown(), + responses: v.object({ + "200": v.unknown(), + "500": v.unknown(), + }), }); export type post_ImageLoad = v.InferOutput; @@ -2498,6 +2806,10 @@ export const post_ImageLoad = v.object({ }), }), response: v.unknown(), + responses: v.object({ + "200": v.unknown(), + "500": ErrorResponse, + }), }); export type post_ContainerExec = v.InferOutput; @@ -2524,6 +2836,12 @@ export const post_ContainerExec = v.object({ }), }), response: IdResponse, + responses: v.object({ + "201": IdResponse, + "404": ErrorResponse, + "409": ErrorResponse, + "500": ErrorResponse, + }), }); export type post_ExecStart = v.InferOutput; @@ -2542,6 +2860,11 @@ export const post_ExecStart = v.object({ }), }), response: v.unknown(), + responses: v.object({ + "200": v.unknown(), + "404": v.unknown(), + "409": v.unknown(), + }), }); export type post_ExecResize = v.InferOutput; @@ -2559,6 +2882,12 @@ export const post_ExecResize = v.object({ }), }), response: v.unknown(), + responses: v.object({ + "200": v.unknown(), + "400": ErrorResponse, + "404": ErrorResponse, + "500": ErrorResponse, + }), }); export type get_ExecInspect = v.InferOutput; @@ -2584,6 +2913,23 @@ export const get_ExecInspect = v.object({ ContainerID: v.optional(v.string()), Pid: v.optional(v.number()), }), + responses: v.object({ + "200": v.object({ + CanRemove: v.optional(v.boolean()), + DetachKeys: v.optional(v.string()), + ID: v.optional(v.string()), + Running: v.optional(v.boolean()), + ExitCode: v.optional(v.number()), + ProcessConfig: v.optional(ProcessConfig), + OpenStdin: v.optional(v.boolean()), + OpenStderr: v.optional(v.boolean()), + OpenStdout: v.optional(v.boolean()), + ContainerID: v.optional(v.string()), + Pid: v.optional(v.number()), + }), + "404": ErrorResponse, + "500": ErrorResponse, + }), }); export type get_VolumeList = v.InferOutput; @@ -2597,6 +2943,10 @@ export const get_VolumeList = v.object({ }), }), response: VolumeListResponse, + responses: v.object({ + "200": VolumeListResponse, + "500": ErrorResponse, + }), }); export type post_VolumeCreate = v.InferOutput; @@ -2608,6 +2958,10 @@ export const post_VolumeCreate = v.object({ body: VolumeCreateOptions, }), response: Volume, + responses: v.object({ + "201": Volume, + "500": ErrorResponse, + }), }); export type get_VolumeInspect = v.InferOutput; @@ -2621,6 +2975,11 @@ export const get_VolumeInspect = v.object({ }), }), response: Volume, + responses: v.object({ + "200": Volume, + "404": ErrorResponse, + "500": ErrorResponse, + }), }); export type put_VolumeUpdate = v.InferOutput; @@ -2640,6 +2999,13 @@ export const put_VolumeUpdate = v.object({ }), }), response: v.unknown(), + responses: v.object({ + "200": v.unknown(), + "400": ErrorResponse, + "404": ErrorResponse, + "500": ErrorResponse, + "503": ErrorResponse, + }), }); export type delete_VolumeDelete = v.InferOutput; @@ -2656,6 +3022,12 @@ export const delete_VolumeDelete = v.object({ }), }), response: v.unknown(), + responses: v.object({ + "204": v.unknown(), + "404": ErrorResponse, + "409": ErrorResponse, + "500": ErrorResponse, + }), }); export type post_VolumePrune = v.InferOutput; @@ -2672,6 +3044,13 @@ export const post_VolumePrune = v.object({ VolumesDeleted: v.optional(v.array(v.string())), SpaceReclaimed: v.optional(v.number()), }), + responses: v.object({ + "200": v.object({ + VolumesDeleted: v.optional(v.array(v.string())), + SpaceReclaimed: v.optional(v.number()), + }), + "500": ErrorResponse, + }), }); export type get_NetworkList = v.InferOutput; @@ -2685,6 +3064,10 @@ export const get_NetworkList = v.object({ }), }), response: v.array(Network), + responses: v.object({ + "200": v.array(Network), + "500": ErrorResponse, + }), }); export type get_NetworkInspect = v.InferOutput; @@ -2702,6 +3085,11 @@ export const get_NetworkInspect = v.object({ }), }), response: Network, + responses: v.object({ + "200": Network, + "404": ErrorResponse, + "500": ErrorResponse, + }), }); export type delete_NetworkDelete = v.InferOutput; @@ -2715,6 +3103,12 @@ export const delete_NetworkDelete = v.object({ }), }), response: v.unknown(), + responses: v.object({ + "204": v.unknown(), + "403": ErrorResponse, + "404": ErrorResponse, + "500": ErrorResponse, + }), }); export type post_NetworkCreate = v.InferOutput; @@ -2740,6 +3134,15 @@ export const post_NetworkCreate = v.object({ Id: v.optional(v.string()), Warning: v.optional(v.string()), }), + responses: v.object({ + "201": v.object({ + Id: v.optional(v.string()), + Warning: v.optional(v.string()), + }), + "403": ErrorResponse, + "404": ErrorResponse, + "500": ErrorResponse, + }), }); export type post_NetworkConnect = v.InferOutput; @@ -2757,6 +3160,12 @@ export const post_NetworkConnect = v.object({ }), }), response: v.unknown(), + responses: v.object({ + "200": v.unknown(), + "403": ErrorResponse, + "404": ErrorResponse, + "500": ErrorResponse, + }), }); export type post_NetworkDisconnect = v.InferOutput; @@ -2774,6 +3183,12 @@ export const post_NetworkDisconnect = v.object({ }), }), response: v.unknown(), + responses: v.object({ + "200": v.unknown(), + "403": ErrorResponse, + "404": ErrorResponse, + "500": ErrorResponse, + }), }); export type post_NetworkPrune = v.InferOutput; @@ -2789,6 +3204,12 @@ export const post_NetworkPrune = v.object({ response: v.object({ NetworksDeleted: v.optional(v.array(v.string())), }), + responses: v.object({ + "200": v.object({ + NetworksDeleted: v.optional(v.array(v.string())), + }), + "500": ErrorResponse, + }), }); export type get_PluginList = v.InferOutput; @@ -2802,6 +3223,10 @@ export const get_PluginList = v.object({ }), }), response: v.array(Plugin), + responses: v.object({ + "200": v.array(Plugin), + "500": ErrorResponse, + }), }); export type get_GetPluginPrivileges = v.InferOutput; @@ -2815,6 +3240,10 @@ export const get_GetPluginPrivileges = v.object({ }), }), response: v.array(PluginPrivilege), + responses: v.object({ + "200": v.array(PluginPrivilege), + "500": ErrorResponse, + }), }); export type post_PluginPull = v.InferOutput; @@ -2833,6 +3262,10 @@ export const post_PluginPull = v.object({ body: v.array(PluginPrivilege), }), response: v.unknown(), + responses: v.object({ + "204": v.unknown(), + "500": ErrorResponse, + }), }); export type get_PluginInspect = v.InferOutput; @@ -2846,6 +3279,11 @@ export const get_PluginInspect = v.object({ }), }), response: Plugin, + responses: v.object({ + "200": Plugin, + "404": ErrorResponse, + "500": ErrorResponse, + }), }); export type delete_PluginDelete = v.InferOutput; @@ -2862,6 +3300,11 @@ export const delete_PluginDelete = v.object({ }), }), response: Plugin, + responses: v.object({ + "200": Plugin, + "404": ErrorResponse, + "500": ErrorResponse, + }), }); export type post_PluginEnable = v.InferOutput; @@ -2878,6 +3321,11 @@ export const post_PluginEnable = v.object({ }), }), response: v.unknown(), + responses: v.object({ + "200": v.unknown(), + "404": ErrorResponse, + "500": ErrorResponse, + }), }); export type post_PluginDisable = v.InferOutput; @@ -2894,6 +3342,11 @@ export const post_PluginDisable = v.object({ }), }), response: v.unknown(), + responses: v.object({ + "200": v.unknown(), + "404": ErrorResponse, + "500": ErrorResponse, + }), }); export type post_PluginUpgrade = v.InferOutput; @@ -2914,6 +3367,11 @@ export const post_PluginUpgrade = v.object({ body: v.array(PluginPrivilege), }), response: v.unknown(), + responses: v.object({ + "204": v.unknown(), + "404": ErrorResponse, + "500": ErrorResponse, + }), }); export type post_PluginCreate = v.InferOutput; @@ -2927,6 +3385,10 @@ export const post_PluginCreate = v.object({ }), }), response: v.unknown(), + responses: v.object({ + "204": v.unknown(), + "500": ErrorResponse, + }), }); export type post_PluginPush = v.InferOutput; @@ -2940,6 +3402,11 @@ export const post_PluginPush = v.object({ }), }), response: v.unknown(), + responses: v.object({ + "200": v.unknown(), + "404": ErrorResponse, + "500": ErrorResponse, + }), }); export type post_PluginSet = v.InferOutput; @@ -2954,6 +3421,11 @@ export const post_PluginSet = v.object({ body: v.array(v.string()), }), response: v.unknown(), + responses: v.object({ + "204": v.unknown(), + "404": ErrorResponse, + "500": ErrorResponse, + }), }); export type get_NodeList = v.InferOutput; @@ -2967,6 +3439,11 @@ export const get_NodeList = v.object({ }), }), response: v.array(Node), + responses: v.object({ + "200": v.array(Node), + "500": ErrorResponse, + "503": ErrorResponse, + }), }); export type get_NodeInspect = v.InferOutput; @@ -2980,6 +3457,12 @@ export const get_NodeInspect = v.object({ }), }), response: Node, + responses: v.object({ + "200": Node, + "404": ErrorResponse, + "500": ErrorResponse, + "503": ErrorResponse, + }), }); export type delete_NodeDelete = v.InferOutput; @@ -2996,6 +3479,12 @@ export const delete_NodeDelete = v.object({ }), }), response: v.unknown(), + responses: v.object({ + "200": v.unknown(), + "404": ErrorResponse, + "500": ErrorResponse, + "503": ErrorResponse, + }), }); export type post_NodeUpdate = v.InferOutput; @@ -3013,6 +3502,13 @@ export const post_NodeUpdate = v.object({ body: NodeSpec, }), response: v.unknown(), + responses: v.object({ + "200": v.unknown(), + "400": ErrorResponse, + "404": ErrorResponse, + "500": ErrorResponse, + "503": ErrorResponse, + }), }); export type get_SwarmInspect = v.InferOutput; @@ -3022,6 +3518,12 @@ export const get_SwarmInspect = v.object({ requestFormat: v.literal("json"), parameters: v.never(), response: Swarm, + responses: v.object({ + "200": Swarm, + "404": ErrorResponse, + "500": ErrorResponse, + "503": ErrorResponse, + }), }); export type post_SwarmInit = v.InferOutput; @@ -3042,6 +3544,12 @@ export const post_SwarmInit = v.object({ }), }), response: v.string(), + responses: v.object({ + "200": v.string(), + "400": ErrorResponse, + "500": ErrorResponse, + "503": ErrorResponse, + }), }); export type post_SwarmJoin = v.InferOutput; @@ -3059,6 +3567,12 @@ export const post_SwarmJoin = v.object({ }), }), response: v.unknown(), + responses: v.object({ + "200": v.unknown(), + "400": ErrorResponse, + "500": ErrorResponse, + "503": ErrorResponse, + }), }); export type post_SwarmLeave = v.InferOutput; @@ -3072,6 +3586,11 @@ export const post_SwarmLeave = v.object({ }), }), response: v.unknown(), + responses: v.object({ + "200": v.unknown(), + "500": ErrorResponse, + "503": ErrorResponse, + }), }); export type post_SwarmUpdate = v.InferOutput; @@ -3089,6 +3608,12 @@ export const post_SwarmUpdate = v.object({ body: SwarmSpec, }), response: v.unknown(), + responses: v.object({ + "200": v.unknown(), + "400": ErrorResponse, + "500": ErrorResponse, + "503": ErrorResponse, + }), }); export type get_SwarmUnlockkey = v.InferOutput; @@ -3100,6 +3625,13 @@ export const get_SwarmUnlockkey = v.object({ response: v.object({ UnlockKey: v.optional(v.string()), }), + responses: v.object({ + "200": v.object({ + UnlockKey: v.optional(v.string()), + }), + "500": ErrorResponse, + "503": ErrorResponse, + }), }); export type post_SwarmUnlock = v.InferOutput; @@ -3113,6 +3645,11 @@ export const post_SwarmUnlock = v.object({ }), }), response: v.unknown(), + responses: v.object({ + "200": v.unknown(), + "500": ErrorResponse, + "503": ErrorResponse, + }), }); export type get_ServiceList = v.InferOutput; @@ -3127,6 +3664,11 @@ export const get_ServiceList = v.object({ }), }), response: v.array(Service), + responses: v.object({ + "200": v.array(Service), + "500": ErrorResponse, + "503": ErrorResponse, + }), }); export type post_ServiceCreate = v.InferOutput; @@ -3144,6 +3686,17 @@ export const post_ServiceCreate = v.object({ ID: v.optional(v.string()), Warning: v.optional(v.string()), }), + responses: v.object({ + "201": v.object({ + ID: v.optional(v.string()), + Warning: v.optional(v.string()), + }), + "400": ErrorResponse, + "403": ErrorResponse, + "409": ErrorResponse, + "500": ErrorResponse, + "503": ErrorResponse, + }), }); export type get_ServiceInspect = v.InferOutput; @@ -3160,6 +3713,12 @@ export const get_ServiceInspect = v.object({ }), }), response: Service, + responses: v.object({ + "200": Service, + "404": ErrorResponse, + "500": ErrorResponse, + "503": ErrorResponse, + }), }); export type delete_ServiceDelete = v.InferOutput; @@ -3173,6 +3732,12 @@ export const delete_ServiceDelete = v.object({ }), }), response: v.unknown(), + responses: v.object({ + "200": v.unknown(), + "404": ErrorResponse, + "500": ErrorResponse, + "503": ErrorResponse, + }), }); export type post_ServiceUpdate = v.InferOutput; @@ -3195,6 +3760,13 @@ export const post_ServiceUpdate = v.object({ body: v.intersect([ServiceSpec, v.record(v.string(), v.unknown())]), }), response: ServiceUpdateResponse, + responses: v.object({ + "200": ServiceUpdateResponse, + "400": ErrorResponse, + "404": ErrorResponse, + "500": ErrorResponse, + "503": ErrorResponse, + }), }); export type get_ServiceLogs = v.InferOutput; @@ -3217,6 +3789,12 @@ export const get_ServiceLogs = v.object({ }), }), response: v.unknown(), + responses: v.object({ + "200": v.unknown(), + "404": v.unknown(), + "500": v.unknown(), + "503": v.unknown(), + }), }); export type get_TaskList = v.InferOutput; @@ -3230,6 +3808,11 @@ export const get_TaskList = v.object({ }), }), response: v.array(Task), + responses: v.object({ + "200": v.array(Task), + "500": ErrorResponse, + "503": ErrorResponse, + }), }); export type get_TaskInspect = v.InferOutput; @@ -3243,6 +3826,12 @@ export const get_TaskInspect = v.object({ }), }), response: Task, + responses: v.object({ + "200": Task, + "404": ErrorResponse, + "500": ErrorResponse, + "503": ErrorResponse, + }), }); export type get_TaskLogs = v.InferOutput; @@ -3265,6 +3854,12 @@ export const get_TaskLogs = v.object({ }), }), response: v.unknown(), + responses: v.object({ + "200": v.unknown(), + "404": v.unknown(), + "500": v.unknown(), + "503": v.unknown(), + }), }); export type get_SecretList = v.InferOutput; @@ -3278,6 +3873,11 @@ export const get_SecretList = v.object({ }), }), response: v.array(Secret), + responses: v.object({ + "200": v.array(Secret), + "500": ErrorResponse, + "503": ErrorResponse, + }), }); export type post_SecretCreate = v.InferOutput; @@ -3289,6 +3889,12 @@ export const post_SecretCreate = v.object({ body: v.intersect([SecretSpec, v.record(v.string(), v.unknown())]), }), response: IdResponse, + responses: v.object({ + "201": IdResponse, + "409": ErrorResponse, + "500": ErrorResponse, + "503": ErrorResponse, + }), }); export type get_SecretInspect = v.InferOutput; @@ -3302,6 +3908,12 @@ export const get_SecretInspect = v.object({ }), }), response: Secret, + responses: v.object({ + "200": Secret, + "404": ErrorResponse, + "500": ErrorResponse, + "503": ErrorResponse, + }), }); export type delete_SecretDelete = v.InferOutput; @@ -3315,6 +3927,12 @@ export const delete_SecretDelete = v.object({ }), }), response: v.unknown(), + responses: v.object({ + "204": v.unknown(), + "404": ErrorResponse, + "500": ErrorResponse, + "503": ErrorResponse, + }), }); export type post_SecretUpdate = v.InferOutput; @@ -3332,6 +3950,13 @@ export const post_SecretUpdate = v.object({ body: SecretSpec, }), response: v.unknown(), + responses: v.object({ + "200": v.unknown(), + "400": ErrorResponse, + "404": ErrorResponse, + "500": ErrorResponse, + "503": ErrorResponse, + }), }); export type get_ConfigList = v.InferOutput; @@ -3345,6 +3970,11 @@ export const get_ConfigList = v.object({ }), }), response: v.array(Config), + responses: v.object({ + "200": v.array(Config), + "500": ErrorResponse, + "503": ErrorResponse, + }), }); export type post_ConfigCreate = v.InferOutput; @@ -3356,6 +3986,12 @@ export const post_ConfigCreate = v.object({ body: v.intersect([ConfigSpec, v.record(v.string(), v.unknown())]), }), response: IdResponse, + responses: v.object({ + "201": IdResponse, + "409": ErrorResponse, + "500": ErrorResponse, + "503": ErrorResponse, + }), }); export type get_ConfigInspect = v.InferOutput; @@ -3369,6 +4005,12 @@ export const get_ConfigInspect = v.object({ }), }), response: Config, + responses: v.object({ + "200": Config, + "404": ErrorResponse, + "500": ErrorResponse, + "503": ErrorResponse, + }), }); export type delete_ConfigDelete = v.InferOutput; @@ -3382,6 +4024,12 @@ export const delete_ConfigDelete = v.object({ }), }), response: v.unknown(), + responses: v.object({ + "204": v.unknown(), + "404": ErrorResponse, + "500": ErrorResponse, + "503": ErrorResponse, + }), }); export type post_ConfigUpdate = v.InferOutput; @@ -3399,6 +4047,13 @@ export const post_ConfigUpdate = v.object({ body: ConfigSpec, }), response: v.unknown(), + responses: v.object({ + "200": v.unknown(), + "400": ErrorResponse, + "404": ErrorResponse, + "500": ErrorResponse, + "503": ErrorResponse, + }), }); export type get_DistributionInspect = v.InferOutput; @@ -3412,6 +4067,11 @@ export const get_DistributionInspect = v.object({ }), }), response: DistributionInspect, + responses: v.object({ + "200": DistributionInspect, + "401": ErrorResponse, + "500": ErrorResponse, + }), }); export type post_Session = v.InferOutput; @@ -3421,6 +4081,11 @@ export const post_Session = v.object({ requestFormat: v.literal("json"), parameters: v.never(), response: v.unknown(), + responses: v.object({ + "101": v.unknown(), + "400": v.unknown(), + "500": v.unknown(), + }), }); export type __ENDPOINTS_END__ = v.InferOutput; @@ -3573,6 +4238,7 @@ type RequestFormat = "json" | "form-data" | "form-url" | "binary" | "text"; export type DefaultEndpoint = { parameters?: EndpointParameters | undefined; response: unknown; + responses?: Record; responseHeaders?: Record; }; @@ -3588,11 +4254,97 @@ export type Endpoint = { areParametersRequired: boolean; }; response: TConfig["response"]; + responses?: TConfig["responses"]; responseHeaders?: TConfig["responseHeaders"]; }; export type Fetcher = (method: Method, url: string, parameters?: EndpointParameters | undefined) => Promise; +// Status code type for success responses +export type SuccessStatusCode = + | 200 + | 201 + | 202 + | 203 + | 204 + | 205 + | 206 + | 207 + | 208 + | 226 + | 300 + | 301 + | 302 + | 303 + | 304 + | 305 + | 306 + | 307 + | 308; + +// Error handling types +export type TypedApiResponse< + TSuccess, + TAllResponses extends Record = {}, +> = keyof TAllResponses extends never + ? Omit & { + ok: true; + status: number; + data: TSuccess; + json: () => Promise; + } + : { + [K in keyof TAllResponses]: K extends string + ? K extends `${infer TStatusCode extends number}` + ? TStatusCode extends SuccessStatusCode + ? Omit & { + ok: true; + status: TStatusCode; + data: TSuccess; + json: () => Promise; + } + : Omit & { + ok: false; + status: TStatusCode; + data: TAllResponses[K]; + json: () => Promise; + } + : never + : K extends number + ? K extends SuccessStatusCode + ? Omit & { + ok: true; + status: K; + data: TSuccess; + json: () => Promise; + } + : Omit & { + ok: false; + status: K; + data: TAllResponses[K]; + json: () => Promise; + } + : never; + }[keyof TAllResponses]; + +export type SafeApiResponse = TEndpoint extends { response: infer TSuccess; responses: infer TResponses } + ? TResponses extends Record + ? TypedApiResponse + : Omit & { + ok: true; + status: number; + data: TSuccess; + json: () => Promise; + } + : TEndpoint extends { response: infer TSuccess } + ? Omit & { + ok: true; + status: number; + data: TSuccess; + json: () => Promise; + } + : never; + type RequiredKeys = { [P in keyof T]-?: undefined extends T[P] ? never : P; }[keyof T]; @@ -3623,55 +4375,227 @@ export class ApiClient { // get( path: Path, - ...params: MaybeOptionalArg["parameters"]> - ): Promise["response"]> { - return this.fetcher("get", this.baseUrl + path, params[0]).then((response) => - this.parseResponse(response), - ) as Promise["response"]>; + ...params: MaybeOptionalArg["parameters"] & { withResponse?: false }> + ): Promise["response"]>; + + get( + path: Path, + ...params: MaybeOptionalArg["parameters"] & { withResponse: true }> + ): Promise>; + + get( + path: Path, + ...params: MaybeOptionalArg + ): Promise { + const requestParams = params[0]; + const withResponse = requestParams?.withResponse; + + // Remove withResponse from params before passing to fetcher + const { withResponse: _, ...fetchParams } = requestParams || {}; + + if (withResponse) { + return this.fetcher("get", this.baseUrl + path, Object.keys(fetchParams).length ? fetchParams : undefined).then( + async (response) => { + // Parse the response data + const data = await this.parseResponse(response); + + // Override properties while keeping the original Response object + const typedResponse = Object.assign(response, { + ok: response.ok, + status: response.status, + data: data, + json: () => Promise.resolve(data), + }); + return typedResponse; + }, + ); + } else { + return this.fetcher("get", this.baseUrl + path, requestParams).then((response) => + this.parseResponse(response), + ) as Promise["response"]>; + } } // // post( path: Path, - ...params: MaybeOptionalArg["parameters"]> - ): Promise["response"]> { - return this.fetcher("post", this.baseUrl + path, params[0]).then((response) => - this.parseResponse(response), - ) as Promise["response"]>; + ...params: MaybeOptionalArg["parameters"] & { withResponse?: false }> + ): Promise["response"]>; + + post( + path: Path, + ...params: MaybeOptionalArg["parameters"] & { withResponse: true }> + ): Promise>; + + post( + path: Path, + ...params: MaybeOptionalArg + ): Promise { + const requestParams = params[0]; + const withResponse = requestParams?.withResponse; + + // Remove withResponse from params before passing to fetcher + const { withResponse: _, ...fetchParams } = requestParams || {}; + + if (withResponse) { + return this.fetcher("post", this.baseUrl + path, Object.keys(fetchParams).length ? fetchParams : undefined).then( + async (response) => { + // Parse the response data + const data = await this.parseResponse(response); + + // Override properties while keeping the original Response object + const typedResponse = Object.assign(response, { + ok: response.ok, + status: response.status, + data: data, + json: () => Promise.resolve(data), + }); + return typedResponse; + }, + ); + } else { + return this.fetcher("post", this.baseUrl + path, requestParams).then((response) => + this.parseResponse(response), + ) as Promise["response"]>; + } } // // delete( path: Path, - ...params: MaybeOptionalArg["parameters"]> - ): Promise["response"]> { - return this.fetcher("delete", this.baseUrl + path, params[0]).then((response) => - this.parseResponse(response), - ) as Promise["response"]>; + ...params: MaybeOptionalArg["parameters"] & { withResponse?: false }> + ): Promise["response"]>; + + delete( + path: Path, + ...params: MaybeOptionalArg["parameters"] & { withResponse: true }> + ): Promise>; + + delete( + path: Path, + ...params: MaybeOptionalArg + ): Promise { + const requestParams = params[0]; + const withResponse = requestParams?.withResponse; + + // Remove withResponse from params before passing to fetcher + const { withResponse: _, ...fetchParams } = requestParams || {}; + + if (withResponse) { + return this.fetcher( + "delete", + this.baseUrl + path, + Object.keys(fetchParams).length ? fetchParams : undefined, + ).then(async (response) => { + // Parse the response data + const data = await this.parseResponse(response); + + // Override properties while keeping the original Response object + const typedResponse = Object.assign(response, { + ok: response.ok, + status: response.status, + data: data, + json: () => Promise.resolve(data), + }); + return typedResponse; + }); + } else { + return this.fetcher("delete", this.baseUrl + path, requestParams).then((response) => + this.parseResponse(response), + ) as Promise["response"]>; + } } // // put( path: Path, - ...params: MaybeOptionalArg["parameters"]> - ): Promise["response"]> { - return this.fetcher("put", this.baseUrl + path, params[0]).then((response) => - this.parseResponse(response), - ) as Promise["response"]>; + ...params: MaybeOptionalArg["parameters"] & { withResponse?: false }> + ): Promise["response"]>; + + put( + path: Path, + ...params: MaybeOptionalArg["parameters"] & { withResponse: true }> + ): Promise>; + + put( + path: Path, + ...params: MaybeOptionalArg + ): Promise { + const requestParams = params[0]; + const withResponse = requestParams?.withResponse; + + // Remove withResponse from params before passing to fetcher + const { withResponse: _, ...fetchParams } = requestParams || {}; + + if (withResponse) { + return this.fetcher("put", this.baseUrl + path, Object.keys(fetchParams).length ? fetchParams : undefined).then( + async (response) => { + // Parse the response data + const data = await this.parseResponse(response); + + // Override properties while keeping the original Response object + const typedResponse = Object.assign(response, { + ok: response.ok, + status: response.status, + data: data, + json: () => Promise.resolve(data), + }); + return typedResponse; + }, + ); + } else { + return this.fetcher("put", this.baseUrl + path, requestParams).then((response) => + this.parseResponse(response), + ) as Promise["response"]>; + } } // // head( path: Path, - ...params: MaybeOptionalArg["parameters"]> - ): Promise["response"]> { - return this.fetcher("head", this.baseUrl + path, params[0]).then((response) => - this.parseResponse(response), - ) as Promise["response"]>; + ...params: MaybeOptionalArg["parameters"] & { withResponse?: false }> + ): Promise["response"]>; + + head( + path: Path, + ...params: MaybeOptionalArg["parameters"] & { withResponse: true }> + ): Promise>; + + head( + path: Path, + ...params: MaybeOptionalArg + ): Promise { + const requestParams = params[0]; + const withResponse = requestParams?.withResponse; + + // Remove withResponse from params before passing to fetcher + const { withResponse: _, ...fetchParams } = requestParams || {}; + + if (withResponse) { + return this.fetcher("head", this.baseUrl + path, Object.keys(fetchParams).length ? fetchParams : undefined).then( + async (response) => { + // Parse the response data + const data = await this.parseResponse(response); + + // Override properties while keeping the original Response object + const typedResponse = Object.assign(response, { + ok: response.ok, + status: response.status, + data: data, + json: () => Promise.resolve(data), + }); + return typedResponse; + }, + ); + } else { + return this.fetcher("head", this.baseUrl + path, requestParams).then((response) => + this.parseResponse(response), + ) as Promise["response"]>; + } } // @@ -3710,6 +4634,21 @@ export function createApiClient(fetcher: Fetcher, baseUrl?: string) { api.get("/users").then((users) => console.log(users)); api.post("/users", { body: { name: "John" } }).then((user) => console.log(user)); api.put("/users/:id", { path: { id: 1 }, body: { name: "John" } }).then((user) => console.log(user)); + + // With error handling + const result = await api.get("/users/{id}", { path: { id: "123" }, withResponse: true }); + if (result.ok) { + // Access data directly + const user = result.data; + console.log(user); + + // Or use the json() method for compatibility + const userFromJson = await result.json(); + console.log(userFromJson); + } else { + const error = result.data; + console.error(`Error ${result.status}:`, error); + } */ // value === null).required() as y.MixedSchema, + ]) + .required() + .optional(), + HostConfig: HostConfig.optional(), + GraphDriver: GraphDriverData.optional(), + SizeRw: y.number().required().optional(), + SizeRootFs: y.number().required().optional(), + Mounts: y.array(MountPoint).optional(), + Config: ContainerConfig.optional(), + NetworkSettings: NetworkSettings.optional(), + }), + "404": ErrorResponse, + "500": ErrorResponse, + }), }; export type get_ContainerTop = typeof get_ContainerTop; @@ -2065,6 +2115,14 @@ export const get_ContainerTop = { Titles: y.array(y.string().required()).optional(), Processes: y.array(y.array(y.string().required())).optional(), }), + responses: y.object({ + "200": y.object({ + Titles: y.array(y.string().required()).optional(), + Processes: y.array(y.array(y.string().required())).optional(), + }), + "404": ErrorResponse, + "500": ErrorResponse, + }), }; export type get_ContainerLogs = typeof get_ContainerLogs; @@ -2087,6 +2145,11 @@ export const get_ContainerLogs = { }), }), response: y.mixed((value): value is any => true).required() as y.MixedSchema, + responses: y.object({ + "200": y.mixed((value): value is any => true).required() as y.MixedSchema, + "404": y.mixed((value): value is any => true).required() as y.MixedSchema, + "500": y.mixed((value): value is any => true).required() as y.MixedSchema, + }), }; export type get_ContainerChanges = typeof get_ContainerChanges; @@ -2100,6 +2163,11 @@ export const get_ContainerChanges = { }), }), response: y.array(FilesystemChange), + responses: y.object({ + "200": y.array(FilesystemChange), + "404": ErrorResponse, + "500": ErrorResponse, + }), }; export type get_ContainerExport = typeof get_ContainerExport; @@ -2113,6 +2181,11 @@ export const get_ContainerExport = { }), }), response: y.mixed((value): value is any => true).required() as y.MixedSchema, + responses: y.object({ + "200": y.mixed((value): value is any => true).required() as y.MixedSchema, + "404": y.mixed((value): value is any => true).required() as y.MixedSchema, + "500": y.mixed((value): value is any => true).required() as y.MixedSchema, + }), }; export type get_ContainerStats = typeof get_ContainerStats; @@ -2130,6 +2203,11 @@ export const get_ContainerStats = { }), }), response: y.mixed(/* unsupported */), + responses: y.object({ + "200": y.mixed(/* unsupported */), + "404": ErrorResponse, + "500": ErrorResponse, + }), }; export type post_ContainerResize = typeof post_ContainerResize; @@ -2147,6 +2225,11 @@ export const post_ContainerResize = { }), }), response: y.mixed((value): value is any => true).required() as y.MixedSchema, + responses: y.object({ + "200": y.mixed((value): value is any => true).required() as y.MixedSchema, + "404": y.mixed((value): value is any => true).required() as y.MixedSchema, + "500": y.mixed((value): value is any => true).required() as y.MixedSchema, + }), }; export type post_ContainerStart = typeof post_ContainerStart; @@ -2163,6 +2246,12 @@ export const post_ContainerStart = { }), }), response: y.mixed((value): value is any => true).required() as y.MixedSchema, + responses: y.object({ + "204": y.mixed((value): value is any => true).required() as y.MixedSchema, + "304": y.mixed((value): value is any => true).required() as y.MixedSchema, + "404": ErrorResponse, + "500": ErrorResponse, + }), }; export type post_ContainerStop = typeof post_ContainerStop; @@ -2180,6 +2269,12 @@ export const post_ContainerStop = { }), }), response: y.mixed((value): value is any => true).required() as y.MixedSchema, + responses: y.object({ + "204": y.mixed((value): value is any => true).required() as y.MixedSchema, + "304": y.mixed((value): value is any => true).required() as y.MixedSchema, + "404": ErrorResponse, + "500": ErrorResponse, + }), }; export type post_ContainerRestart = typeof post_ContainerRestart; @@ -2197,6 +2292,11 @@ export const post_ContainerRestart = { }), }), response: y.mixed((value): value is any => true).required() as y.MixedSchema, + responses: y.object({ + "204": y.mixed((value): value is any => true).required() as y.MixedSchema, + "404": ErrorResponse, + "500": ErrorResponse, + }), }; export type post_ContainerKill = typeof post_ContainerKill; @@ -2213,6 +2313,12 @@ export const post_ContainerKill = { }), }), response: y.mixed((value): value is any => true).required() as y.MixedSchema, + responses: y.object({ + "204": y.mixed((value): value is any => true).required() as y.MixedSchema, + "404": ErrorResponse, + "409": ErrorResponse, + "500": ErrorResponse, + }), }; export type post_ContainerUpdate = typeof post_ContainerUpdate; @@ -2291,6 +2397,13 @@ export const post_ContainerUpdate = { response: y.object({ Warnings: y.array(y.string().required()).optional(), }), + responses: y.object({ + "200": y.object({ + Warnings: y.array(y.string().required()).optional(), + }), + "404": ErrorResponse, + "500": ErrorResponse, + }), }; export type post_ContainerRename = typeof post_ContainerRename; @@ -2307,6 +2420,12 @@ export const post_ContainerRename = { }), }), response: y.mixed((value): value is any => true).required() as y.MixedSchema, + responses: y.object({ + "204": y.mixed((value): value is any => true).required() as y.MixedSchema, + "404": ErrorResponse, + "409": ErrorResponse, + "500": ErrorResponse, + }), }; export type post_ContainerPause = typeof post_ContainerPause; @@ -2320,6 +2439,11 @@ export const post_ContainerPause = { }), }), response: y.mixed((value): value is any => true).required() as y.MixedSchema, + responses: y.object({ + "204": y.mixed((value): value is any => true).required() as y.MixedSchema, + "404": ErrorResponse, + "500": ErrorResponse, + }), }; export type post_ContainerUnpause = typeof post_ContainerUnpause; @@ -2333,6 +2457,11 @@ export const post_ContainerUnpause = { }), }), response: y.mixed((value): value is any => true).required() as y.MixedSchema, + responses: y.object({ + "204": y.mixed((value): value is any => true).required() as y.MixedSchema, + "404": ErrorResponse, + "500": ErrorResponse, + }), }; export type post_ContainerAttach = typeof post_ContainerAttach; @@ -2354,6 +2483,13 @@ export const post_ContainerAttach = { }), }), response: y.mixed((value): value is any => true).required() as y.MixedSchema, + responses: y.object({ + "101": y.mixed((value): value is any => true).required() as y.MixedSchema, + "200": y.mixed((value): value is any => true).required() as y.MixedSchema, + "400": y.mixed((value): value is any => true).required() as y.MixedSchema, + "404": y.mixed((value): value is any => true).required() as y.MixedSchema, + "500": y.mixed((value): value is any => true).required() as y.MixedSchema, + }), }; export type get_ContainerAttachWebsocket = typeof get_ContainerAttachWebsocket; @@ -2375,6 +2511,13 @@ export const get_ContainerAttachWebsocket = { }), }), response: y.mixed((value): value is any => true).required() as y.MixedSchema, + responses: y.object({ + "101": y.mixed((value): value is any => true).required() as y.MixedSchema, + "200": y.mixed((value): value is any => true).required() as y.MixedSchema, + "400": ErrorResponse, + "404": ErrorResponse, + "500": ErrorResponse, + }), }; export type post_ContainerWait = typeof post_ContainerWait; @@ -2391,6 +2534,12 @@ export const post_ContainerWait = { }), }), response: ContainerWaitResponse, + responses: y.object({ + "200": ContainerWaitResponse, + "400": ErrorResponse, + "404": ErrorResponse, + "500": ErrorResponse, + }), }; export type delete_ContainerDelete = typeof delete_ContainerDelete; @@ -2409,6 +2558,13 @@ export const delete_ContainerDelete = { }), }), response: y.mixed((value): value is any => true).required() as y.MixedSchema, + responses: y.object({ + "204": y.mixed((value): value is any => true).required() as y.MixedSchema, + "400": ErrorResponse, + "404": ErrorResponse, + "409": ErrorResponse, + "500": ErrorResponse, + }), }; export type get_ContainerArchive = typeof get_ContainerArchive; @@ -2425,6 +2581,12 @@ export const get_ContainerArchive = { }), }), response: y.mixed((value): value is any => true).required() as y.MixedSchema, + responses: y.object({ + "200": y.mixed((value): value is any => true).required() as y.MixedSchema, + "400": y.mixed((value): value is any => true).required() as y.MixedSchema, + "404": y.mixed((value): value is any => true).required() as y.MixedSchema, + "500": y.mixed((value): value is any => true).required() as y.MixedSchema, + }), }; export type put_PutContainerArchive = typeof put_PutContainerArchive; @@ -2458,6 +2620,13 @@ export const put_PutContainerArchive = { body: y.string().required(), }), response: y.mixed((value): value is any => true).required() as y.MixedSchema, + responses: y.object({ + "200": y.mixed((value): value is any => true).required() as y.MixedSchema, + "400": ErrorResponse, + "403": ErrorResponse, + "404": ErrorResponse, + "500": ErrorResponse, + }), }; export type head_ContainerArchiveInfo = typeof head_ContainerArchiveInfo; @@ -2474,6 +2643,12 @@ export const head_ContainerArchiveInfo = { }), }), response: y.mixed((value): value is any => true).required() as y.MixedSchema, + responses: y.object({ + "200": y.mixed((value): value is any => true).required() as y.MixedSchema, + "400": ErrorResponse, + "404": ErrorResponse, + "500": ErrorResponse, + }), responseHeaders: y.object({ "x-docker-container-path-stat": y.string().required(), }), @@ -2493,6 +2668,13 @@ export const post_ContainerPrune = { ContainersDeleted: y.array(y.string().required()).optional(), SpaceReclaimed: y.number().required().optional(), }), + responses: y.object({ + "200": y.object({ + ContainersDeleted: y.array(y.string().required()).optional(), + SpaceReclaimed: y.number().required().optional(), + }), + "500": ErrorResponse, + }), }; export type get_ImageList = typeof get_ImageList; @@ -2509,6 +2691,10 @@ export const get_ImageList = { }), }), response: y.array(ImageSummary), + responses: y.object({ + "200": y.array(ImageSummary), + "500": ErrorResponse, + }), }; export type post_ImageBuild = typeof post_ImageBuild; @@ -2553,6 +2739,11 @@ export const post_ImageBuild = { body: y.string().required(), }), response: y.mixed((value): value is any => true).required() as y.MixedSchema, + responses: y.object({ + "200": y.mixed((value): value is any => true).required() as y.MixedSchema, + "400": ErrorResponse, + "500": ErrorResponse, + }), }; export type post_BuildPrune = typeof post_BuildPrune; @@ -2571,6 +2762,13 @@ export const post_BuildPrune = { CachesDeleted: y.array(y.string().required()).optional(), SpaceReclaimed: y.number().required().optional(), }), + responses: y.object({ + "200": y.object({ + CachesDeleted: y.array(y.string().required()).optional(), + SpaceReclaimed: y.number().required().optional(), + }), + "500": ErrorResponse, + }), }; export type post_ImageCreate = typeof post_ImageCreate; @@ -2594,6 +2792,11 @@ export const post_ImageCreate = { body: y.string().required(), }), response: y.mixed((value): value is any => true).required() as y.MixedSchema, + responses: y.object({ + "200": y.mixed((value): value is any => true).required() as y.MixedSchema, + "404": ErrorResponse, + "500": ErrorResponse, + }), }; export type get_ImageInspect = typeof get_ImageInspect; @@ -2607,6 +2810,11 @@ export const get_ImageInspect = { }), }), response: ImageInspect, + responses: y.object({ + "200": ImageInspect, + "404": ErrorResponse, + "500": ErrorResponse, + }), }; export type get_ImageHistory = typeof get_ImageHistory; @@ -2629,6 +2837,20 @@ export const get_ImageHistory = { Comment: y.string().required(), }), ), + responses: y.object({ + "200": y.array( + y.object({ + Id: y.string().required(), + Created: y.number().required(), + CreatedBy: y.string().required(), + Tags: y.array(y.string().required()), + Size: y.number().required(), + Comment: y.string().required(), + }), + ), + "404": ErrorResponse, + "500": ErrorResponse, + }), }; export type post_ImagePush = typeof post_ImagePush; @@ -2648,6 +2870,11 @@ export const post_ImagePush = { }), }), response: y.mixed((value): value is any => true).required() as y.MixedSchema, + responses: y.object({ + "200": y.mixed((value): value is any => true).required() as y.MixedSchema, + "404": ErrorResponse, + "500": ErrorResponse, + }), }; export type post_ImageTag = typeof post_ImageTag; @@ -2665,6 +2892,13 @@ export const post_ImageTag = { }), }), response: y.mixed((value): value is any => true).required() as y.MixedSchema, + responses: y.object({ + "201": y.mixed((value): value is any => true).required() as y.MixedSchema, + "400": ErrorResponse, + "404": ErrorResponse, + "409": ErrorResponse, + "500": ErrorResponse, + }), }; export type delete_ImageDelete = typeof delete_ImageDelete; @@ -2682,6 +2916,12 @@ export const delete_ImageDelete = { }), }), response: y.array(ImageDeleteResponseItem), + responses: y.object({ + "200": y.array(ImageDeleteResponseItem), + "404": ErrorResponse, + "409": ErrorResponse, + "500": ErrorResponse, + }), }; export type get_ImageSearch = typeof get_ImageSearch; @@ -2719,6 +2959,18 @@ export const get_ImageSearch = { star_count: y.number().required().optional(), }), ), + responses: y.object({ + "200": y.array( + y.object({ + description: y.string().required().optional(), + is_official: y.boolean().required().optional(), + is_automated: y.boolean().required().optional(), + name: y.string().required().optional(), + star_count: y.number().required().optional(), + }), + ), + "500": ErrorResponse, + }), }; export type post_ImagePrune = typeof post_ImagePrune; @@ -2735,6 +2987,13 @@ export const post_ImagePrune = { ImagesDeleted: y.array(ImageDeleteResponseItem).optional(), SpaceReclaimed: y.number().required().optional(), }), + responses: y.object({ + "200": y.object({ + ImagesDeleted: y.array(ImageDeleteResponseItem).optional(), + SpaceReclaimed: y.number().required().optional(), + }), + "500": ErrorResponse, + }), }; export type post_SystemAuth = typeof post_SystemAuth; @@ -2745,7 +3004,30 @@ export const post_SystemAuth = { parameters: y.object({ body: AuthConfig, }), - response: y.mixed((value): value is any => true).required() as y.MixedSchema, + response: y.object({ + Status: y.string().required(), + IdentityToken: y + .mixed() + .oneOf([y.string().required(), y.mixed((value): value is any => value === undefined) as y.MixedSchema]) + .required() + .optional(), + }), + responses: y.object({ + "200": y.object({ + Status: y.string().required(), + IdentityToken: y + .mixed() + .oneOf([ + y.string().required(), + y.mixed((value): value is any => value === undefined) as y.MixedSchema, + ]) + .required() + .optional(), + }), + "204": y.mixed((value): value is any => true).required() as y.MixedSchema, + "401": ErrorResponse, + "500": ErrorResponse, + }), }; export type get_SystemInfo = typeof get_SystemInfo; @@ -2755,6 +3037,10 @@ export const get_SystemInfo = { requestFormat: y.mixed((value): value is "json" => value === "json").required(), parameters: y.mixed((value): value is never => false).required(), response: SystemInfo, + responses: y.object({ + "200": SystemInfo, + "500": ErrorResponse, + }), }; export type get_SystemVersion = typeof get_SystemVersion; @@ -2764,6 +3050,10 @@ export const get_SystemVersion = { requestFormat: y.mixed((value): value is "json" => value === "json").required(), parameters: y.mixed((value): value is never => false).required(), response: SystemVersion, + responses: y.object({ + "200": SystemVersion, + "500": ErrorResponse, + }), }; export type get_SystemPing = typeof get_SystemPing; @@ -2773,6 +3063,10 @@ export const get_SystemPing = { requestFormat: y.mixed((value): value is "json" => value === "json").required(), parameters: y.mixed((value): value is never => false).required(), response: y.mixed((value): value is any => true).required() as y.MixedSchema, + responses: y.object({ + "200": y.mixed((value): value is any => true).required() as y.MixedSchema, + "500": y.mixed((value): value is any => true).required() as y.MixedSchema, + }), responseHeaders: y.object({ swarm: y.mixed().oneOf(["inactive", "pending", "error", "locked", "active/worker", "active/manager"]).required(), "docker-experimental": y.boolean().required(), @@ -2790,6 +3084,10 @@ export const head_SystemPingHead = { requestFormat: y.mixed((value): value is "json" => value === "json").required(), parameters: y.mixed((value): value is never => false).required(), response: y.mixed((value): value is any => true).required() as y.MixedSchema, + responses: y.object({ + "200": y.mixed((value): value is any => true).required() as y.MixedSchema, + "500": y.mixed((value): value is any => true).required() as y.MixedSchema, + }), responseHeaders: y.object({ swarm: y.mixed().oneOf(["inactive", "pending", "error", "locked", "active/worker", "active/manager"]).required(), "docker-experimental": y.boolean().required(), @@ -2818,6 +3116,11 @@ export const post_ImageCommit = { body: ContainerConfig, }), response: IdResponse, + responses: y.object({ + "201": IdResponse, + "404": ErrorResponse, + "500": ErrorResponse, + }), }; export type get_SystemEvents = typeof get_SystemEvents; @@ -2833,6 +3136,11 @@ export const get_SystemEvents = { }), }), response: EventMessage, + responses: y.object({ + "200": EventMessage, + "400": ErrorResponse, + "500": ErrorResponse, + }), }; export type get_SystemDataUsage = typeof get_SystemDataUsage; @@ -2852,6 +3160,16 @@ export const get_SystemDataUsage = { Volumes: y.array(Volume).optional(), BuildCache: y.array(BuildCache).optional(), }), + responses: y.object({ + "200": y.object({ + LayersSize: y.number().required().optional(), + Images: y.array(ImageSummary).optional(), + Containers: y.array(ContainerSummary).optional(), + Volumes: y.array(Volume).optional(), + BuildCache: y.array(BuildCache).optional(), + }), + "500": ErrorResponse, + }), }; export type get_ImageGet = typeof get_ImageGet; @@ -2865,6 +3183,10 @@ export const get_ImageGet = { }), }), response: y.mixed((value): value is any => true).required() as y.MixedSchema, + responses: y.object({ + "200": y.mixed((value): value is any => true).required() as y.MixedSchema, + "500": y.mixed((value): value is any => true).required() as y.MixedSchema, + }), }; export type get_ImageGetAll = typeof get_ImageGetAll; @@ -2878,6 +3200,10 @@ export const get_ImageGetAll = { }), }), response: y.mixed((value): value is any => true).required() as y.MixedSchema, + responses: y.object({ + "200": y.mixed((value): value is any => true).required() as y.MixedSchema, + "500": y.mixed((value): value is any => true).required() as y.MixedSchema, + }), }; export type post_ImageLoad = typeof post_ImageLoad; @@ -2891,6 +3217,10 @@ export const post_ImageLoad = { }), }), response: y.mixed((value): value is any => true).required() as y.MixedSchema, + responses: y.object({ + "200": y.mixed((value): value is any => true).required() as y.MixedSchema, + "500": ErrorResponse, + }), }; export type post_ContainerExec = typeof post_ContainerExec; @@ -2924,6 +3254,12 @@ export const post_ContainerExec = { }), }), response: IdResponse, + responses: y.object({ + "201": IdResponse, + "404": ErrorResponse, + "409": ErrorResponse, + "500": ErrorResponse, + }), }; export type post_ExecStart = typeof post_ExecStart; @@ -2949,6 +3285,11 @@ export const post_ExecStart = { }), }), response: y.mixed((value): value is any => true).required() as y.MixedSchema, + responses: y.object({ + "200": y.mixed((value): value is any => true).required() as y.MixedSchema, + "404": y.mixed((value): value is any => true).required() as y.MixedSchema, + "409": y.mixed((value): value is any => true).required() as y.MixedSchema, + }), }; export type post_ExecResize = typeof post_ExecResize; @@ -2966,6 +3307,12 @@ export const post_ExecResize = { }), }), response: y.mixed((value): value is any => true).required() as y.MixedSchema, + responses: y.object({ + "200": y.mixed((value): value is any => true).required() as y.MixedSchema, + "400": ErrorResponse, + "404": ErrorResponse, + "500": ErrorResponse, + }), }; export type get_ExecInspect = typeof get_ExecInspect; @@ -2991,6 +3338,23 @@ export const get_ExecInspect = { ContainerID: y.string().required().optional(), Pid: y.number().required().optional(), }), + responses: y.object({ + "200": y.object({ + CanRemove: y.boolean().required().optional(), + DetachKeys: y.string().required().optional(), + ID: y.string().required().optional(), + Running: y.boolean().required().optional(), + ExitCode: y.number().required().optional(), + ProcessConfig: ProcessConfig.optional(), + OpenStdin: y.boolean().required().optional(), + OpenStderr: y.boolean().required().optional(), + OpenStdout: y.boolean().required().optional(), + ContainerID: y.string().required().optional(), + Pid: y.number().required().optional(), + }), + "404": ErrorResponse, + "500": ErrorResponse, + }), }; export type get_VolumeList = typeof get_VolumeList; @@ -3004,6 +3368,10 @@ export const get_VolumeList = { }), }), response: VolumeListResponse, + responses: y.object({ + "200": VolumeListResponse, + "500": ErrorResponse, + }), }; export type post_VolumeCreate = typeof post_VolumeCreate; @@ -3015,6 +3383,10 @@ export const post_VolumeCreate = { body: VolumeCreateOptions, }), response: Volume, + responses: y.object({ + "201": Volume, + "500": ErrorResponse, + }), }; export type get_VolumeInspect = typeof get_VolumeInspect; @@ -3028,6 +3400,11 @@ export const get_VolumeInspect = { }), }), response: Volume, + responses: y.object({ + "200": Volume, + "404": ErrorResponse, + "500": ErrorResponse, + }), }; export type put_VolumeUpdate = typeof put_VolumeUpdate; @@ -3047,6 +3424,13 @@ export const put_VolumeUpdate = { }), }), response: y.mixed((value): value is any => true).required() as y.MixedSchema, + responses: y.object({ + "200": y.mixed((value): value is any => true).required() as y.MixedSchema, + "400": ErrorResponse, + "404": ErrorResponse, + "500": ErrorResponse, + "503": ErrorResponse, + }), }; export type delete_VolumeDelete = typeof delete_VolumeDelete; @@ -3063,6 +3447,12 @@ export const delete_VolumeDelete = { }), }), response: y.mixed((value): value is any => true).required() as y.MixedSchema, + responses: y.object({ + "204": y.mixed((value): value is any => true).required() as y.MixedSchema, + "404": ErrorResponse, + "409": ErrorResponse, + "500": ErrorResponse, + }), }; export type post_VolumePrune = typeof post_VolumePrune; @@ -3079,6 +3469,13 @@ export const post_VolumePrune = { VolumesDeleted: y.array(y.string().required()).optional(), SpaceReclaimed: y.number().required().optional(), }), + responses: y.object({ + "200": y.object({ + VolumesDeleted: y.array(y.string().required()).optional(), + SpaceReclaimed: y.number().required().optional(), + }), + "500": ErrorResponse, + }), }; export type get_NetworkList = typeof get_NetworkList; @@ -3092,6 +3489,10 @@ export const get_NetworkList = { }), }), response: y.array(Network), + responses: y.object({ + "200": y.array(Network), + "500": ErrorResponse, + }), }; export type get_NetworkInspect = typeof get_NetworkInspect; @@ -3109,6 +3510,11 @@ export const get_NetworkInspect = { }), }), response: Network, + responses: y.object({ + "200": Network, + "404": ErrorResponse, + "500": ErrorResponse, + }), }; export type delete_NetworkDelete = typeof delete_NetworkDelete; @@ -3122,6 +3528,12 @@ export const delete_NetworkDelete = { }), }), response: y.mixed((value): value is any => true).required() as y.MixedSchema, + responses: y.object({ + "204": y.mixed((value): value is any => true).required() as y.MixedSchema, + "403": ErrorResponse, + "404": ErrorResponse, + "500": ErrorResponse, + }), }; export type post_NetworkCreate = typeof post_NetworkCreate; @@ -3207,6 +3619,15 @@ export const post_NetworkCreate = { Id: y.string().required().optional(), Warning: y.string().required().optional(), }), + responses: y.object({ + "201": y.object({ + Id: y.string().required().optional(), + Warning: y.string().required().optional(), + }), + "403": ErrorResponse, + "404": ErrorResponse, + "500": ErrorResponse, + }), }; export type post_NetworkConnect = typeof post_NetworkConnect; @@ -3224,6 +3645,12 @@ export const post_NetworkConnect = { }), }), response: y.mixed((value): value is any => true).required() as y.MixedSchema, + responses: y.object({ + "200": y.mixed((value): value is any => true).required() as y.MixedSchema, + "403": ErrorResponse, + "404": ErrorResponse, + "500": ErrorResponse, + }), }; export type post_NetworkDisconnect = typeof post_NetworkDisconnect; @@ -3241,6 +3668,12 @@ export const post_NetworkDisconnect = { }), }), response: y.mixed((value): value is any => true).required() as y.MixedSchema, + responses: y.object({ + "200": y.mixed((value): value is any => true).required() as y.MixedSchema, + "403": ErrorResponse, + "404": ErrorResponse, + "500": ErrorResponse, + }), }; export type post_NetworkPrune = typeof post_NetworkPrune; @@ -3256,6 +3689,12 @@ export const post_NetworkPrune = { response: y.object({ NetworksDeleted: y.array(y.string().required()).optional(), }), + responses: y.object({ + "200": y.object({ + NetworksDeleted: y.array(y.string().required()).optional(), + }), + "500": ErrorResponse, + }), }; export type get_PluginList = typeof get_PluginList; @@ -3269,6 +3708,10 @@ export const get_PluginList = { }), }), response: y.array(Plugin), + responses: y.object({ + "200": y.array(Plugin), + "500": ErrorResponse, + }), }; export type get_GetPluginPrivileges = typeof get_GetPluginPrivileges; @@ -3282,6 +3725,10 @@ export const get_GetPluginPrivileges = { }), }), response: y.array(PluginPrivilege), + responses: y.object({ + "200": y.array(PluginPrivilege), + "500": ErrorResponse, + }), }; export type post_PluginPull = typeof post_PluginPull; @@ -3307,6 +3754,10 @@ export const post_PluginPull = { body: y.array(PluginPrivilege), }), response: y.mixed((value): value is any => true).required() as y.MixedSchema, + responses: y.object({ + "204": y.mixed((value): value is any => true).required() as y.MixedSchema, + "500": ErrorResponse, + }), }; export type get_PluginInspect = typeof get_PluginInspect; @@ -3320,6 +3771,11 @@ export const get_PluginInspect = { }), }), response: Plugin, + responses: y.object({ + "200": Plugin, + "404": ErrorResponse, + "500": ErrorResponse, + }), }; export type delete_PluginDelete = typeof delete_PluginDelete; @@ -3336,6 +3792,11 @@ export const delete_PluginDelete = { }), }), response: Plugin, + responses: y.object({ + "200": Plugin, + "404": ErrorResponse, + "500": ErrorResponse, + }), }; export type post_PluginEnable = typeof post_PluginEnable; @@ -3352,6 +3813,11 @@ export const post_PluginEnable = { }), }), response: y.mixed((value): value is any => true).required() as y.MixedSchema, + responses: y.object({ + "200": y.mixed((value): value is any => true).required() as y.MixedSchema, + "404": ErrorResponse, + "500": ErrorResponse, + }), }; export type post_PluginDisable = typeof post_PluginDisable; @@ -3368,6 +3834,11 @@ export const post_PluginDisable = { }), }), response: y.mixed((value): value is any => true).required() as y.MixedSchema, + responses: y.object({ + "200": y.mixed((value): value is any => true).required() as y.MixedSchema, + "404": ErrorResponse, + "500": ErrorResponse, + }), }; export type post_PluginUpgrade = typeof post_PluginUpgrade; @@ -3388,6 +3859,11 @@ export const post_PluginUpgrade = { body: y.array(PluginPrivilege), }), response: y.mixed((value): value is any => true).required() as y.MixedSchema, + responses: y.object({ + "204": y.mixed((value): value is any => true).required() as y.MixedSchema, + "404": ErrorResponse, + "500": ErrorResponse, + }), }; export type post_PluginCreate = typeof post_PluginCreate; @@ -3401,6 +3877,10 @@ export const post_PluginCreate = { }), }), response: y.mixed((value): value is any => true).required() as y.MixedSchema, + responses: y.object({ + "204": y.mixed((value): value is any => true).required() as y.MixedSchema, + "500": ErrorResponse, + }), }; export type post_PluginPush = typeof post_PluginPush; @@ -3414,6 +3894,11 @@ export const post_PluginPush = { }), }), response: y.mixed((value): value is any => true).required() as y.MixedSchema, + responses: y.object({ + "200": y.mixed((value): value is any => true).required() as y.MixedSchema, + "404": ErrorResponse, + "500": ErrorResponse, + }), }; export type post_PluginSet = typeof post_PluginSet; @@ -3428,6 +3913,11 @@ export const post_PluginSet = { body: y.array(y.string().required()), }), response: y.mixed((value): value is any => true).required() as y.MixedSchema, + responses: y.object({ + "204": y.mixed((value): value is any => true).required() as y.MixedSchema, + "404": ErrorResponse, + "500": ErrorResponse, + }), }; export type get_NodeList = typeof get_NodeList; @@ -3441,6 +3931,11 @@ export const get_NodeList = { }), }), response: y.array(Node), + responses: y.object({ + "200": y.array(Node), + "500": ErrorResponse, + "503": ErrorResponse, + }), }; export type get_NodeInspect = typeof get_NodeInspect; @@ -3454,6 +3949,12 @@ export const get_NodeInspect = { }), }), response: Node, + responses: y.object({ + "200": Node, + "404": ErrorResponse, + "500": ErrorResponse, + "503": ErrorResponse, + }), }; export type delete_NodeDelete = typeof delete_NodeDelete; @@ -3470,6 +3971,12 @@ export const delete_NodeDelete = { }), }), response: y.mixed((value): value is any => true).required() as y.MixedSchema, + responses: y.object({ + "200": y.mixed((value): value is any => true).required() as y.MixedSchema, + "404": ErrorResponse, + "500": ErrorResponse, + "503": ErrorResponse, + }), }; export type post_NodeUpdate = typeof post_NodeUpdate; @@ -3487,6 +3994,13 @@ export const post_NodeUpdate = { body: NodeSpec, }), response: y.mixed((value): value is any => true).required() as y.MixedSchema, + responses: y.object({ + "200": y.mixed((value): value is any => true).required() as y.MixedSchema, + "400": ErrorResponse, + "404": ErrorResponse, + "500": ErrorResponse, + "503": ErrorResponse, + }), }; export type get_SwarmInspect = typeof get_SwarmInspect; @@ -3496,6 +4010,12 @@ export const get_SwarmInspect = { requestFormat: y.mixed((value): value is "json" => value === "json").required(), parameters: y.mixed((value): value is never => false).required(), response: Swarm, + responses: y.object({ + "200": Swarm, + "404": ErrorResponse, + "500": ErrorResponse, + "503": ErrorResponse, + }), }; export type post_SwarmInit = typeof post_SwarmInit; @@ -3516,6 +4036,12 @@ export const post_SwarmInit = { }), }), response: y.string().required(), + responses: y.object({ + "200": y.string().required(), + "400": ErrorResponse, + "500": ErrorResponse, + "503": ErrorResponse, + }), }; export type post_SwarmJoin = typeof post_SwarmJoin; @@ -3533,6 +4059,12 @@ export const post_SwarmJoin = { }), }), response: y.mixed((value): value is any => true).required() as y.MixedSchema, + responses: y.object({ + "200": y.mixed((value): value is any => true).required() as y.MixedSchema, + "400": ErrorResponse, + "500": ErrorResponse, + "503": ErrorResponse, + }), }; export type post_SwarmLeave = typeof post_SwarmLeave; @@ -3546,6 +4078,11 @@ export const post_SwarmLeave = { }), }), response: y.mixed((value): value is any => true).required() as y.MixedSchema, + responses: y.object({ + "200": y.mixed((value): value is any => true).required() as y.MixedSchema, + "500": ErrorResponse, + "503": ErrorResponse, + }), }; export type post_SwarmUpdate = typeof post_SwarmUpdate; @@ -3584,6 +4121,12 @@ export const post_SwarmUpdate = { body: SwarmSpec, }), response: y.mixed((value): value is any => true).required() as y.MixedSchema, + responses: y.object({ + "200": y.mixed((value): value is any => true).required() as y.MixedSchema, + "400": ErrorResponse, + "500": ErrorResponse, + "503": ErrorResponse, + }), }; export type get_SwarmUnlockkey = typeof get_SwarmUnlockkey; @@ -3595,6 +4138,13 @@ export const get_SwarmUnlockkey = { response: y.object({ UnlockKey: y.string().required().optional(), }), + responses: y.object({ + "200": y.object({ + UnlockKey: y.string().required().optional(), + }), + "500": ErrorResponse, + "503": ErrorResponse, + }), }; export type post_SwarmUnlock = typeof post_SwarmUnlock; @@ -3608,6 +4158,11 @@ export const post_SwarmUnlock = { }), }), response: y.mixed((value): value is any => true).required() as y.MixedSchema, + responses: y.object({ + "200": y.mixed((value): value is any => true).required() as y.MixedSchema, + "500": ErrorResponse, + "503": ErrorResponse, + }), }; export type get_ServiceList = typeof get_ServiceList; @@ -3622,6 +4177,11 @@ export const get_ServiceList = { }), }), response: y.array(Service), + responses: y.object({ + "200": y.array(Service), + "500": ErrorResponse, + "503": ErrorResponse, + }), }; export type post_ServiceCreate = typeof post_ServiceCreate; @@ -3639,6 +4199,17 @@ export const post_ServiceCreate = { ID: y.string().required().optional(), Warning: y.string().required().optional(), }), + responses: y.object({ + "201": y.object({ + ID: y.string().required().optional(), + Warning: y.string().required().optional(), + }), + "400": ErrorResponse, + "403": ErrorResponse, + "409": ErrorResponse, + "500": ErrorResponse, + "503": ErrorResponse, + }), }; export type get_ServiceInspect = typeof get_ServiceInspect; @@ -3655,6 +4226,12 @@ export const get_ServiceInspect = { }), }), response: Service, + responses: y.object({ + "200": Service, + "404": ErrorResponse, + "500": ErrorResponse, + "503": ErrorResponse, + }), }; export type delete_ServiceDelete = typeof delete_ServiceDelete; @@ -3668,6 +4245,12 @@ export const delete_ServiceDelete = { }), }), response: y.mixed((value): value is any => true).required() as y.MixedSchema, + responses: y.object({ + "200": y.mixed((value): value is any => true).required() as y.MixedSchema, + "404": ErrorResponse, + "500": ErrorResponse, + "503": ErrorResponse, + }), }; export type post_ServiceUpdate = typeof post_ServiceUpdate; @@ -3704,6 +4287,13 @@ export const post_ServiceUpdate = { body: y.mixed(/* unsupported */), }), response: ServiceUpdateResponse, + responses: y.object({ + "200": ServiceUpdateResponse, + "400": ErrorResponse, + "404": ErrorResponse, + "500": ErrorResponse, + "503": ErrorResponse, + }), }; export type get_ServiceLogs = typeof get_ServiceLogs; @@ -3726,6 +4316,12 @@ export const get_ServiceLogs = { }), }), response: y.mixed((value): value is any => true).required() as y.MixedSchema, + responses: y.object({ + "200": y.mixed((value): value is any => true).required() as y.MixedSchema, + "404": y.mixed((value): value is any => true).required() as y.MixedSchema, + "500": y.mixed((value): value is any => true).required() as y.MixedSchema, + "503": y.mixed((value): value is any => true).required() as y.MixedSchema, + }), }; export type get_TaskList = typeof get_TaskList; @@ -3739,6 +4335,11 @@ export const get_TaskList = { }), }), response: y.array(Task), + responses: y.object({ + "200": y.array(Task), + "500": ErrorResponse, + "503": ErrorResponse, + }), }; export type get_TaskInspect = typeof get_TaskInspect; @@ -3752,6 +4353,12 @@ export const get_TaskInspect = { }), }), response: Task, + responses: y.object({ + "200": Task, + "404": ErrorResponse, + "500": ErrorResponse, + "503": ErrorResponse, + }), }; export type get_TaskLogs = typeof get_TaskLogs; @@ -3774,6 +4381,12 @@ export const get_TaskLogs = { }), }), response: y.mixed((value): value is any => true).required() as y.MixedSchema, + responses: y.object({ + "200": y.mixed((value): value is any => true).required() as y.MixedSchema, + "404": y.mixed((value): value is any => true).required() as y.MixedSchema, + "500": y.mixed((value): value is any => true).required() as y.MixedSchema, + "503": y.mixed((value): value is any => true).required() as y.MixedSchema, + }), }; export type get_SecretList = typeof get_SecretList; @@ -3787,6 +4400,11 @@ export const get_SecretList = { }), }), response: y.array(Secret), + responses: y.object({ + "200": y.array(Secret), + "500": ErrorResponse, + "503": ErrorResponse, + }), }; export type post_SecretCreate = typeof post_SecretCreate; @@ -3798,6 +4416,12 @@ export const post_SecretCreate = { body: y.mixed(/* unsupported */), }), response: IdResponse, + responses: y.object({ + "201": IdResponse, + "409": ErrorResponse, + "500": ErrorResponse, + "503": ErrorResponse, + }), }; export type get_SecretInspect = typeof get_SecretInspect; @@ -3811,6 +4435,12 @@ export const get_SecretInspect = { }), }), response: Secret, + responses: y.object({ + "200": Secret, + "404": ErrorResponse, + "500": ErrorResponse, + "503": ErrorResponse, + }), }; export type delete_SecretDelete = typeof delete_SecretDelete; @@ -3824,6 +4454,12 @@ export const delete_SecretDelete = { }), }), response: y.mixed((value): value is any => true).required() as y.MixedSchema, + responses: y.object({ + "204": y.mixed((value): value is any => true).required() as y.MixedSchema, + "404": ErrorResponse, + "500": ErrorResponse, + "503": ErrorResponse, + }), }; export type post_SecretUpdate = typeof post_SecretUpdate; @@ -3841,6 +4477,13 @@ export const post_SecretUpdate = { body: SecretSpec, }), response: y.mixed((value): value is any => true).required() as y.MixedSchema, + responses: y.object({ + "200": y.mixed((value): value is any => true).required() as y.MixedSchema, + "400": ErrorResponse, + "404": ErrorResponse, + "500": ErrorResponse, + "503": ErrorResponse, + }), }; export type get_ConfigList = typeof get_ConfigList; @@ -3854,6 +4497,11 @@ export const get_ConfigList = { }), }), response: y.array(Config), + responses: y.object({ + "200": y.array(Config), + "500": ErrorResponse, + "503": ErrorResponse, + }), }; export type post_ConfigCreate = typeof post_ConfigCreate; @@ -3865,6 +4513,12 @@ export const post_ConfigCreate = { body: y.mixed(/* unsupported */), }), response: IdResponse, + responses: y.object({ + "201": IdResponse, + "409": ErrorResponse, + "500": ErrorResponse, + "503": ErrorResponse, + }), }; export type get_ConfigInspect = typeof get_ConfigInspect; @@ -3878,6 +4532,12 @@ export const get_ConfigInspect = { }), }), response: Config, + responses: y.object({ + "200": Config, + "404": ErrorResponse, + "500": ErrorResponse, + "503": ErrorResponse, + }), }; export type delete_ConfigDelete = typeof delete_ConfigDelete; @@ -3891,6 +4551,12 @@ export const delete_ConfigDelete = { }), }), response: y.mixed((value): value is any => true).required() as y.MixedSchema, + responses: y.object({ + "204": y.mixed((value): value is any => true).required() as y.MixedSchema, + "404": ErrorResponse, + "500": ErrorResponse, + "503": ErrorResponse, + }), }; export type post_ConfigUpdate = typeof post_ConfigUpdate; @@ -3908,6 +4574,13 @@ export const post_ConfigUpdate = { body: ConfigSpec, }), response: y.mixed((value): value is any => true).required() as y.MixedSchema, + responses: y.object({ + "200": y.mixed((value): value is any => true).required() as y.MixedSchema, + "400": ErrorResponse, + "404": ErrorResponse, + "500": ErrorResponse, + "503": ErrorResponse, + }), }; export type get_DistributionInspect = typeof get_DistributionInspect; @@ -3921,6 +4594,11 @@ export const get_DistributionInspect = { }), }), response: DistributionInspect, + responses: y.object({ + "200": DistributionInspect, + "401": ErrorResponse, + "500": ErrorResponse, + }), }; export type post_Session = typeof post_Session; @@ -3930,6 +4608,11 @@ export const post_Session = { requestFormat: y.mixed((value): value is "json" => value === "json").required(), parameters: y.mixed((value): value is never => false).required(), response: y.mixed((value): value is any => true).required() as y.MixedSchema, + responses: y.object({ + "101": y.mixed((value): value is any => true).required() as y.MixedSchema, + "400": y.mixed((value): value is any => true).required() as y.MixedSchema, + "500": y.mixed((value): value is any => true).required() as y.MixedSchema, + }), }; // @@ -4079,6 +4762,7 @@ type RequestFormat = "json" | "form-data" | "form-url" | "binary" | "text"; export type DefaultEndpoint = { parameters?: EndpointParameters | undefined; response: unknown; + responses?: Record; responseHeaders?: Record; }; @@ -4094,11 +4778,97 @@ export type Endpoint = { areParametersRequired: boolean; }; response: TConfig["response"]; + responses?: TConfig["responses"]; responseHeaders?: TConfig["responseHeaders"]; }; export type Fetcher = (method: Method, url: string, parameters?: EndpointParameters | undefined) => Promise; +// Status code type for success responses +export type SuccessStatusCode = + | 200 + | 201 + | 202 + | 203 + | 204 + | 205 + | 206 + | 207 + | 208 + | 226 + | 300 + | 301 + | 302 + | 303 + | 304 + | 305 + | 306 + | 307 + | 308; + +// Error handling types +export type TypedApiResponse< + TSuccess, + TAllResponses extends Record = {}, +> = keyof TAllResponses extends never + ? Omit & { + ok: true; + status: number; + data: TSuccess; + json: () => Promise; + } + : { + [K in keyof TAllResponses]: K extends string + ? K extends `${infer TStatusCode extends number}` + ? TStatusCode extends SuccessStatusCode + ? Omit & { + ok: true; + status: TStatusCode; + data: TSuccess; + json: () => Promise; + } + : Omit & { + ok: false; + status: TStatusCode; + data: TAllResponses[K]; + json: () => Promise; + } + : never + : K extends number + ? K extends SuccessStatusCode + ? Omit & { + ok: true; + status: K; + data: TSuccess; + json: () => Promise; + } + : Omit & { + ok: false; + status: K; + data: TAllResponses[K]; + json: () => Promise; + } + : never; + }[keyof TAllResponses]; + +export type SafeApiResponse = TEndpoint extends { response: infer TSuccess; responses: infer TResponses } + ? TResponses extends Record + ? TypedApiResponse + : Omit & { + ok: true; + status: number; + data: TSuccess; + json: () => Promise; + } + : TEndpoint extends { response: infer TSuccess } + ? Omit & { + ok: true; + status: number; + data: TSuccess; + json: () => Promise; + } + : never; + type RequiredKeys = { [P in keyof T]-?: undefined extends T[P] ? never : P; }[keyof T]; @@ -4129,55 +4899,227 @@ export class ApiClient { // get( path: Path, - ...params: MaybeOptionalArg> - ): Promise> { - return this.fetcher("get", this.baseUrl + path, params[0]).then((response) => - this.parseResponse(response), - ) as Promise>; + ...params: MaybeOptionalArg & { withResponse?: false }> + ): Promise>; + + get( + path: Path, + ...params: MaybeOptionalArg & { withResponse: true }> + ): Promise>; + + get( + path: Path, + ...params: MaybeOptionalArg + ): Promise { + const requestParams = params[0]; + const withResponse = requestParams?.withResponse; + + // Remove withResponse from params before passing to fetcher + const { withResponse: _, ...fetchParams } = requestParams || {}; + + if (withResponse) { + return this.fetcher("get", this.baseUrl + path, Object.keys(fetchParams).length ? fetchParams : undefined).then( + async (response) => { + // Parse the response data + const data = await this.parseResponse(response); + + // Override properties while keeping the original Response object + const typedResponse = Object.assign(response, { + ok: response.ok, + status: response.status, + data: data, + json: () => Promise.resolve(data), + }); + return typedResponse; + }, + ); + } else { + return this.fetcher("get", this.baseUrl + path, requestParams).then((response) => + this.parseResponse(response), + ) as Promise>; + } } // // post( path: Path, - ...params: MaybeOptionalArg> - ): Promise> { - return this.fetcher("post", this.baseUrl + path, params[0]).then((response) => - this.parseResponse(response), - ) as Promise>; + ...params: MaybeOptionalArg & { withResponse?: false }> + ): Promise>; + + post( + path: Path, + ...params: MaybeOptionalArg & { withResponse: true }> + ): Promise>; + + post( + path: Path, + ...params: MaybeOptionalArg + ): Promise { + const requestParams = params[0]; + const withResponse = requestParams?.withResponse; + + // Remove withResponse from params before passing to fetcher + const { withResponse: _, ...fetchParams } = requestParams || {}; + + if (withResponse) { + return this.fetcher("post", this.baseUrl + path, Object.keys(fetchParams).length ? fetchParams : undefined).then( + async (response) => { + // Parse the response data + const data = await this.parseResponse(response); + + // Override properties while keeping the original Response object + const typedResponse = Object.assign(response, { + ok: response.ok, + status: response.status, + data: data, + json: () => Promise.resolve(data), + }); + return typedResponse; + }, + ); + } else { + return this.fetcher("post", this.baseUrl + path, requestParams).then((response) => + this.parseResponse(response), + ) as Promise>; + } } // // delete( path: Path, - ...params: MaybeOptionalArg> - ): Promise> { - return this.fetcher("delete", this.baseUrl + path, params[0]).then((response) => - this.parseResponse(response), - ) as Promise>; + ...params: MaybeOptionalArg & { withResponse?: false }> + ): Promise>; + + delete( + path: Path, + ...params: MaybeOptionalArg & { withResponse: true }> + ): Promise>; + + delete( + path: Path, + ...params: MaybeOptionalArg + ): Promise { + const requestParams = params[0]; + const withResponse = requestParams?.withResponse; + + // Remove withResponse from params before passing to fetcher + const { withResponse: _, ...fetchParams } = requestParams || {}; + + if (withResponse) { + return this.fetcher( + "delete", + this.baseUrl + path, + Object.keys(fetchParams).length ? fetchParams : undefined, + ).then(async (response) => { + // Parse the response data + const data = await this.parseResponse(response); + + // Override properties while keeping the original Response object + const typedResponse = Object.assign(response, { + ok: response.ok, + status: response.status, + data: data, + json: () => Promise.resolve(data), + }); + return typedResponse; + }); + } else { + return this.fetcher("delete", this.baseUrl + path, requestParams).then((response) => + this.parseResponse(response), + ) as Promise>; + } } // // put( path: Path, - ...params: MaybeOptionalArg> - ): Promise> { - return this.fetcher("put", this.baseUrl + path, params[0]).then((response) => - this.parseResponse(response), - ) as Promise>; + ...params: MaybeOptionalArg & { withResponse?: false }> + ): Promise>; + + put( + path: Path, + ...params: MaybeOptionalArg & { withResponse: true }> + ): Promise>; + + put( + path: Path, + ...params: MaybeOptionalArg + ): Promise { + const requestParams = params[0]; + const withResponse = requestParams?.withResponse; + + // Remove withResponse from params before passing to fetcher + const { withResponse: _, ...fetchParams } = requestParams || {}; + + if (withResponse) { + return this.fetcher("put", this.baseUrl + path, Object.keys(fetchParams).length ? fetchParams : undefined).then( + async (response) => { + // Parse the response data + const data = await this.parseResponse(response); + + // Override properties while keeping the original Response object + const typedResponse = Object.assign(response, { + ok: response.ok, + status: response.status, + data: data, + json: () => Promise.resolve(data), + }); + return typedResponse; + }, + ); + } else { + return this.fetcher("put", this.baseUrl + path, requestParams).then((response) => + this.parseResponse(response), + ) as Promise>; + } } // // head( path: Path, - ...params: MaybeOptionalArg> - ): Promise> { - return this.fetcher("head", this.baseUrl + path, params[0]).then((response) => - this.parseResponse(response), - ) as Promise>; + ...params: MaybeOptionalArg & { withResponse?: false }> + ): Promise>; + + head( + path: Path, + ...params: MaybeOptionalArg & { withResponse: true }> + ): Promise>; + + head( + path: Path, + ...params: MaybeOptionalArg + ): Promise { + const requestParams = params[0]; + const withResponse = requestParams?.withResponse; + + // Remove withResponse from params before passing to fetcher + const { withResponse: _, ...fetchParams } = requestParams || {}; + + if (withResponse) { + return this.fetcher("head", this.baseUrl + path, Object.keys(fetchParams).length ? fetchParams : undefined).then( + async (response) => { + // Parse the response data + const data = await this.parseResponse(response); + + // Override properties while keeping the original Response object + const typedResponse = Object.assign(response, { + ok: response.ok, + status: response.status, + data: data, + json: () => Promise.resolve(data), + }); + return typedResponse; + }, + ); + } else { + return this.fetcher("head", this.baseUrl + path, requestParams).then((response) => + this.parseResponse(response), + ) as Promise>; + } } // @@ -4216,6 +5158,21 @@ export function createApiClient(fetcher: Fetcher, baseUrl?: string) { api.get("/users").then((users) => console.log(users)); api.post("/users", { body: { name: "John" } }).then((user) => console.log(user)); api.put("/users/:id", { path: { id: 1 }, body: { name: "John" } }).then((user) => console.log(user)); + + // With error handling + const result = await api.get("/users/{id}", { path: { id: "123" }, withResponse: true }); + if (result.ok) { + // Access data directly + const user = result.data; + console.log(user); + + // Or use the json() method for compatibility + const userFromJson = await result.json(); + console.log(userFromJson); + } else { + const error = result.data; + console.error(`Error ${result.status}:`, error); + } */ // @@ -3559,6 +4224,7 @@ type RequestFormat = "json" | "form-data" | "form-url" | "binary" | "text"; export type DefaultEndpoint = { parameters?: EndpointParameters | undefined; response: unknown; + responses?: Record; responseHeaders?: Record; }; @@ -3574,11 +4240,97 @@ export type Endpoint = { areParametersRequired: boolean; }; response: TConfig["response"]; + responses?: TConfig["responses"]; responseHeaders?: TConfig["responseHeaders"]; }; export type Fetcher = (method: Method, url: string, parameters?: EndpointParameters | undefined) => Promise; +// Status code type for success responses +export type SuccessStatusCode = + | 200 + | 201 + | 202 + | 203 + | 204 + | 205 + | 206 + | 207 + | 208 + | 226 + | 300 + | 301 + | 302 + | 303 + | 304 + | 305 + | 306 + | 307 + | 308; + +// Error handling types +export type TypedApiResponse< + TSuccess, + TAllResponses extends Record = {}, +> = keyof TAllResponses extends never + ? Omit & { + ok: true; + status: number; + data: TSuccess; + json: () => Promise; + } + : { + [K in keyof TAllResponses]: K extends string + ? K extends `${infer TStatusCode extends number}` + ? TStatusCode extends SuccessStatusCode + ? Omit & { + ok: true; + status: TStatusCode; + data: TSuccess; + json: () => Promise; + } + : Omit & { + ok: false; + status: TStatusCode; + data: TAllResponses[K]; + json: () => Promise; + } + : never + : K extends number + ? K extends SuccessStatusCode + ? Omit & { + ok: true; + status: K; + data: TSuccess; + json: () => Promise; + } + : Omit & { + ok: false; + status: K; + data: TAllResponses[K]; + json: () => Promise; + } + : never; + }[keyof TAllResponses]; + +export type SafeApiResponse = TEndpoint extends { response: infer TSuccess; responses: infer TResponses } + ? TResponses extends Record + ? TypedApiResponse + : Omit & { + ok: true; + status: number; + data: TSuccess; + json: () => Promise; + } + : TEndpoint extends { response: infer TSuccess } + ? Omit & { + ok: true; + status: number; + data: TSuccess; + json: () => Promise; + } + : never; + type RequiredKeys = { [P in keyof T]-?: undefined extends T[P] ? never : P; }[keyof T]; @@ -3609,55 +4361,227 @@ export class ApiClient { // get( path: Path, - ...params: MaybeOptionalArg> - ): Promise> { - return this.fetcher("get", this.baseUrl + path, params[0]).then((response) => - this.parseResponse(response), - ) as Promise>; + ...params: MaybeOptionalArg & { withResponse?: false }> + ): Promise>; + + get( + path: Path, + ...params: MaybeOptionalArg & { withResponse: true }> + ): Promise>; + + get( + path: Path, + ...params: MaybeOptionalArg + ): Promise { + const requestParams = params[0]; + const withResponse = requestParams?.withResponse; + + // Remove withResponse from params before passing to fetcher + const { withResponse: _, ...fetchParams } = requestParams || {}; + + if (withResponse) { + return this.fetcher("get", this.baseUrl + path, Object.keys(fetchParams).length ? fetchParams : undefined).then( + async (response) => { + // Parse the response data + const data = await this.parseResponse(response); + + // Override properties while keeping the original Response object + const typedResponse = Object.assign(response, { + ok: response.ok, + status: response.status, + data: data, + json: () => Promise.resolve(data), + }); + return typedResponse; + }, + ); + } else { + return this.fetcher("get", this.baseUrl + path, requestParams).then((response) => + this.parseResponse(response), + ) as Promise>; + } } // // post( path: Path, - ...params: MaybeOptionalArg> - ): Promise> { - return this.fetcher("post", this.baseUrl + path, params[0]).then((response) => - this.parseResponse(response), - ) as Promise>; + ...params: MaybeOptionalArg & { withResponse?: false }> + ): Promise>; + + post( + path: Path, + ...params: MaybeOptionalArg & { withResponse: true }> + ): Promise>; + + post( + path: Path, + ...params: MaybeOptionalArg + ): Promise { + const requestParams = params[0]; + const withResponse = requestParams?.withResponse; + + // Remove withResponse from params before passing to fetcher + const { withResponse: _, ...fetchParams } = requestParams || {}; + + if (withResponse) { + return this.fetcher("post", this.baseUrl + path, Object.keys(fetchParams).length ? fetchParams : undefined).then( + async (response) => { + // Parse the response data + const data = await this.parseResponse(response); + + // Override properties while keeping the original Response object + const typedResponse = Object.assign(response, { + ok: response.ok, + status: response.status, + data: data, + json: () => Promise.resolve(data), + }); + return typedResponse; + }, + ); + } else { + return this.fetcher("post", this.baseUrl + path, requestParams).then((response) => + this.parseResponse(response), + ) as Promise>; + } } // // delete( path: Path, - ...params: MaybeOptionalArg> - ): Promise> { - return this.fetcher("delete", this.baseUrl + path, params[0]).then((response) => - this.parseResponse(response), - ) as Promise>; + ...params: MaybeOptionalArg & { withResponse?: false }> + ): Promise>; + + delete( + path: Path, + ...params: MaybeOptionalArg & { withResponse: true }> + ): Promise>; + + delete( + path: Path, + ...params: MaybeOptionalArg + ): Promise { + const requestParams = params[0]; + const withResponse = requestParams?.withResponse; + + // Remove withResponse from params before passing to fetcher + const { withResponse: _, ...fetchParams } = requestParams || {}; + + if (withResponse) { + return this.fetcher( + "delete", + this.baseUrl + path, + Object.keys(fetchParams).length ? fetchParams : undefined, + ).then(async (response) => { + // Parse the response data + const data = await this.parseResponse(response); + + // Override properties while keeping the original Response object + const typedResponse = Object.assign(response, { + ok: response.ok, + status: response.status, + data: data, + json: () => Promise.resolve(data), + }); + return typedResponse; + }); + } else { + return this.fetcher("delete", this.baseUrl + path, requestParams).then((response) => + this.parseResponse(response), + ) as Promise>; + } } // // put( path: Path, - ...params: MaybeOptionalArg> - ): Promise> { - return this.fetcher("put", this.baseUrl + path, params[0]).then((response) => - this.parseResponse(response), - ) as Promise>; + ...params: MaybeOptionalArg & { withResponse?: false }> + ): Promise>; + + put( + path: Path, + ...params: MaybeOptionalArg & { withResponse: true }> + ): Promise>; + + put( + path: Path, + ...params: MaybeOptionalArg + ): Promise { + const requestParams = params[0]; + const withResponse = requestParams?.withResponse; + + // Remove withResponse from params before passing to fetcher + const { withResponse: _, ...fetchParams } = requestParams || {}; + + if (withResponse) { + return this.fetcher("put", this.baseUrl + path, Object.keys(fetchParams).length ? fetchParams : undefined).then( + async (response) => { + // Parse the response data + const data = await this.parseResponse(response); + + // Override properties while keeping the original Response object + const typedResponse = Object.assign(response, { + ok: response.ok, + status: response.status, + data: data, + json: () => Promise.resolve(data), + }); + return typedResponse; + }, + ); + } else { + return this.fetcher("put", this.baseUrl + path, requestParams).then((response) => + this.parseResponse(response), + ) as Promise>; + } } // // head( path: Path, - ...params: MaybeOptionalArg> - ): Promise> { - return this.fetcher("head", this.baseUrl + path, params[0]).then((response) => - this.parseResponse(response), - ) as Promise>; + ...params: MaybeOptionalArg & { withResponse?: false }> + ): Promise>; + + head( + path: Path, + ...params: MaybeOptionalArg & { withResponse: true }> + ): Promise>; + + head( + path: Path, + ...params: MaybeOptionalArg + ): Promise { + const requestParams = params[0]; + const withResponse = requestParams?.withResponse; + + // Remove withResponse from params before passing to fetcher + const { withResponse: _, ...fetchParams } = requestParams || {}; + + if (withResponse) { + return this.fetcher("head", this.baseUrl + path, Object.keys(fetchParams).length ? fetchParams : undefined).then( + async (response) => { + // Parse the response data + const data = await this.parseResponse(response); + + // Override properties while keeping the original Response object + const typedResponse = Object.assign(response, { + ok: response.ok, + status: response.status, + data: data, + json: () => Promise.resolve(data), + }); + return typedResponse; + }, + ); + } else { + return this.fetcher("head", this.baseUrl + path, requestParams).then((response) => + this.parseResponse(response), + ) as Promise>; + } } // @@ -3696,6 +4620,21 @@ export function createApiClient(fetcher: Fetcher, baseUrl?: string) { api.get("/users").then((users) => console.log(users)); api.post("/users", { body: { name: "John" } }).then((user) => console.log(user)); api.put("/users/:id", { path: { id: 1 }, body: { name: "John" } }).then((user) => console.log(user)); + + // With error handling + const result = await api.get("/users/{id}", { path: { id: "123" }, withResponse: true }); + if (result.ok) { + // Access data directly + const user = result.data; + console.log(user); + + // Or use the json() method for compatibility + const userFromJson = await result.json(); + console.log(userFromJson); + } else { + const error = result.data; + console.error(`Error ${result.status}:`, error); + } */ // ; responseHeaders?: Record; }; @@ -82,11 +89,97 @@ export type Endpoint = { areParametersRequired: boolean; }; response: TConfig["response"]; + responses?: TConfig["responses"]; responseHeaders?: TConfig["responseHeaders"]; }; export type Fetcher = (method: Method, url: string, parameters?: EndpointParameters | undefined) => Promise; +// Status code type for success responses +export type SuccessStatusCode = + | 200 + | 201 + | 202 + | 203 + | 204 + | 205 + | 206 + | 207 + | 208 + | 226 + | 300 + | 301 + | 302 + | 303 + | 304 + | 305 + | 306 + | 307 + | 308; + +// Error handling types +export type TypedApiResponse< + TSuccess, + TAllResponses extends Record = {}, +> = keyof TAllResponses extends never + ? Omit & { + ok: true; + status: number; + data: TSuccess; + json: () => Promise; + } + : { + [K in keyof TAllResponses]: K extends string + ? K extends `${infer TStatusCode extends number}` + ? TStatusCode extends SuccessStatusCode + ? Omit & { + ok: true; + status: TStatusCode; + data: TSuccess; + json: () => Promise; + } + : Omit & { + ok: false; + status: TStatusCode; + data: TAllResponses[K]; + json: () => Promise; + } + : never + : K extends number + ? K extends SuccessStatusCode + ? Omit & { + ok: true; + status: K; + data: TSuccess; + json: () => Promise; + } + : Omit & { + ok: false; + status: K; + data: TAllResponses[K]; + json: () => Promise; + } + : never; + }[keyof TAllResponses]; + +export type SafeApiResponse = TEndpoint extends { response: infer TSuccess; responses: infer TResponses } + ? TResponses extends Record + ? TypedApiResponse + : Omit & { + ok: true; + status: number; + data: TSuccess; + json: () => Promise; + } + : TEndpoint extends { response: infer TSuccess } + ? Omit & { + ok: true; + status: number; + data: TSuccess; + json: () => Promise; + } + : never; + type RequiredKeys = { [P in keyof T]-?: undefined extends T[P] ? never : P; }[keyof T]; @@ -117,22 +210,90 @@ export class ApiClient { // get( path: Path, - ...params: MaybeOptionalArg - ): Promise { - return this.fetcher("get", this.baseUrl + path, params[0]).then((response) => - this.parseResponse(response), - ) as Promise; + ...params: MaybeOptionalArg + ): Promise; + + get( + path: Path, + ...params: MaybeOptionalArg + ): Promise>; + + get( + path: Path, + ...params: MaybeOptionalArg + ): Promise { + const requestParams = params[0]; + const withResponse = requestParams?.withResponse; + + // Remove withResponse from params before passing to fetcher + const { withResponse: _, ...fetchParams } = requestParams || {}; + + if (withResponse) { + return this.fetcher("get", this.baseUrl + path, Object.keys(fetchParams).length ? fetchParams : undefined).then( + async (response) => { + // Parse the response data + const data = await this.parseResponse(response); + + // Override properties while keeping the original Response object + const typedResponse = Object.assign(response, { + ok: response.ok, + status: response.status, + data: data, + json: () => Promise.resolve(data), + }); + return typedResponse; + }, + ); + } else { + return this.fetcher("get", this.baseUrl + path, requestParams).then((response) => + this.parseResponse(response), + ) as Promise; + } } // // post( path: Path, - ...params: MaybeOptionalArg - ): Promise { - return this.fetcher("post", this.baseUrl + path, params[0]).then((response) => - this.parseResponse(response), - ) as Promise; + ...params: MaybeOptionalArg + ): Promise; + + post( + path: Path, + ...params: MaybeOptionalArg + ): Promise>; + + post( + path: Path, + ...params: MaybeOptionalArg + ): Promise { + const requestParams = params[0]; + const withResponse = requestParams?.withResponse; + + // Remove withResponse from params before passing to fetcher + const { withResponse: _, ...fetchParams } = requestParams || {}; + + if (withResponse) { + return this.fetcher("post", this.baseUrl + path, Object.keys(fetchParams).length ? fetchParams : undefined).then( + async (response) => { + // Parse the response data + const data = await this.parseResponse(response); + + // Override properties while keeping the original Response object + const typedResponse = Object.assign(response, { + ok: response.ok, + status: response.status, + data: data, + json: () => Promise.resolve(data), + }); + return typedResponse; + }, + ); + } else { + return this.fetcher("post", this.baseUrl + path, requestParams).then((response) => + this.parseResponse(response), + ) as Promise; + } } // @@ -171,6 +332,21 @@ export function createApiClient(fetcher: Fetcher, baseUrl?: string) { api.get("/users").then((users) => console.log(users)); api.post("/users", { body: { name: "John" } }).then((user) => console.log(user)); api.put("/users/:id", { path: { id: 1 }, body: { name: "John" } }).then((user) => console.log(user)); + + // With error handling + const result = await api.get("/users/{id}", { path: { id: "123" }, withResponse: true }); + if (result.ok) { + // Access data directly + const user = result.data; + console.log(user); + + // Or use the json() method for compatibility + const userFromJson = await result.json(); + console.log(userFromJson); + } else { + const error = result.data; + console.error(`Error ${result.status}:`, error); + } */ // ; + responses: { 200: Array }; }; export type post_Very_very_very_very_very_very_very_very_very_very_long = { method: "POST"; @@ -21,6 +22,7 @@ export namespace Endpoints { body: Partial<{ username: string }>; }; response: unknown; + responses: { 201: unknown }; }; // @@ -59,6 +61,7 @@ type RequestFormat = "json" | "form-data" | "form-url" | "binary" | "text"; export type DefaultEndpoint = { parameters?: EndpointParameters | undefined; response: unknown; + responses?: Record; responseHeaders?: Record; }; @@ -74,11 +77,97 @@ export type Endpoint = { areParametersRequired: boolean; }; response: TConfig["response"]; + responses?: TConfig["responses"]; responseHeaders?: TConfig["responseHeaders"]; }; export type Fetcher = (method: Method, url: string, parameters?: EndpointParameters | undefined) => Promise; +// Status code type for success responses +export type SuccessStatusCode = + | 200 + | 201 + | 202 + | 203 + | 204 + | 205 + | 206 + | 207 + | 208 + | 226 + | 300 + | 301 + | 302 + | 303 + | 304 + | 305 + | 306 + | 307 + | 308; + +// Error handling types +export type TypedApiResponse< + TSuccess, + TAllResponses extends Record = {}, +> = keyof TAllResponses extends never + ? Omit & { + ok: true; + status: number; + data: TSuccess; + json: () => Promise; + } + : { + [K in keyof TAllResponses]: K extends string + ? K extends `${infer TStatusCode extends number}` + ? TStatusCode extends SuccessStatusCode + ? Omit & { + ok: true; + status: TStatusCode; + data: TSuccess; + json: () => Promise; + } + : Omit & { + ok: false; + status: TStatusCode; + data: TAllResponses[K]; + json: () => Promise; + } + : never + : K extends number + ? K extends SuccessStatusCode + ? Omit & { + ok: true; + status: K; + data: TSuccess; + json: () => Promise; + } + : Omit & { + ok: false; + status: K; + data: TAllResponses[K]; + json: () => Promise; + } + : never; + }[keyof TAllResponses]; + +export type SafeApiResponse = TEndpoint extends { response: infer TSuccess; responses: infer TResponses } + ? TResponses extends Record + ? TypedApiResponse + : Omit & { + ok: true; + status: number; + data: TSuccess; + json: () => Promise; + } + : TEndpoint extends { response: infer TSuccess } + ? Omit & { + ok: true; + status: number; + data: TSuccess; + json: () => Promise; + } + : never; + type RequiredKeys = { [P in keyof T]-?: undefined extends T[P] ? never : P; }[keyof T]; @@ -109,22 +198,90 @@ export class ApiClient { // get( path: Path, - ...params: MaybeOptionalArg - ): Promise { - return this.fetcher("get", this.baseUrl + path, params[0]).then((response) => - this.parseResponse(response), - ) as Promise; + ...params: MaybeOptionalArg + ): Promise; + + get( + path: Path, + ...params: MaybeOptionalArg + ): Promise>; + + get( + path: Path, + ...params: MaybeOptionalArg + ): Promise { + const requestParams = params[0]; + const withResponse = requestParams?.withResponse; + + // Remove withResponse from params before passing to fetcher + const { withResponse: _, ...fetchParams } = requestParams || {}; + + if (withResponse) { + return this.fetcher("get", this.baseUrl + path, Object.keys(fetchParams).length ? fetchParams : undefined).then( + async (response) => { + // Parse the response data + const data = await this.parseResponse(response); + + // Override properties while keeping the original Response object + const typedResponse = Object.assign(response, { + ok: response.ok, + status: response.status, + data: data, + json: () => Promise.resolve(data), + }); + return typedResponse; + }, + ); + } else { + return this.fetcher("get", this.baseUrl + path, requestParams).then((response) => + this.parseResponse(response), + ) as Promise; + } } // // post( path: Path, - ...params: MaybeOptionalArg - ): Promise { - return this.fetcher("post", this.baseUrl + path, params[0]).then((response) => - this.parseResponse(response), - ) as Promise; + ...params: MaybeOptionalArg + ): Promise; + + post( + path: Path, + ...params: MaybeOptionalArg + ): Promise>; + + post( + path: Path, + ...params: MaybeOptionalArg + ): Promise { + const requestParams = params[0]; + const withResponse = requestParams?.withResponse; + + // Remove withResponse from params before passing to fetcher + const { withResponse: _, ...fetchParams } = requestParams || {}; + + if (withResponse) { + return this.fetcher("post", this.baseUrl + path, Object.keys(fetchParams).length ? fetchParams : undefined).then( + async (response) => { + // Parse the response data + const data = await this.parseResponse(response); + + // Override properties while keeping the original Response object + const typedResponse = Object.assign(response, { + ok: response.ok, + status: response.status, + data: data, + json: () => Promise.resolve(data), + }); + return typedResponse; + }, + ); + } else { + return this.fetcher("post", this.baseUrl + path, requestParams).then((response) => + this.parseResponse(response), + ) as Promise; + } } // @@ -163,6 +320,21 @@ export function createApiClient(fetcher: Fetcher, baseUrl?: string) { api.get("/users").then((users) => console.log(users)); api.post("/users", { body: { name: "John" } }).then((user) => console.log(user)); api.put("/users/:id", { path: { id: 1 }, body: { name: "John" } }).then((user) => console.log(user)); + + // With error handling + const result = await api.get("/users/{id}", { path: { id: "123" }, withResponse: true }); + if (result.ok) { + // Access data directly + const user = result.data; + console.log(user); + + // Or use the json() method for compatibility + const userFromJson = await result.json(); + console.log(userFromJson); + } else { + const error = result.data; + console.error(`Error ${result.status}:`, error); + } */ // ; @@ -63,6 +69,7 @@ type RequestFormat = "json" | "form-data" | "form-url" | "binary" | "text"; export type DefaultEndpoint = { parameters?: EndpointParameters | undefined; response: unknown; + responses?: Record; responseHeaders?: Record; }; @@ -78,11 +85,97 @@ export type Endpoint = { areParametersRequired: boolean; }; response: TConfig["response"]; + responses?: TConfig["responses"]; responseHeaders?: TConfig["responseHeaders"]; }; export type Fetcher = (method: Method, url: string, parameters?: EndpointParameters | undefined) => Promise; +// Status code type for success responses +export type SuccessStatusCode = + | 200 + | 201 + | 202 + | 203 + | 204 + | 205 + | 206 + | 207 + | 208 + | 226 + | 300 + | 301 + | 302 + | 303 + | 304 + | 305 + | 306 + | 307 + | 308; + +// Error handling types +export type TypedApiResponse< + TSuccess, + TAllResponses extends Record = {}, +> = keyof TAllResponses extends never + ? Omit & { + ok: true; + status: number; + data: TSuccess; + json: () => Promise; + } + : { + [K in keyof TAllResponses]: K extends string + ? K extends `${infer TStatusCode extends number}` + ? TStatusCode extends SuccessStatusCode + ? Omit & { + ok: true; + status: TStatusCode; + data: TSuccess; + json: () => Promise; + } + : Omit & { + ok: false; + status: TStatusCode; + data: TAllResponses[K]; + json: () => Promise; + } + : never + : K extends number + ? K extends SuccessStatusCode + ? Omit & { + ok: true; + status: K; + data: TSuccess; + json: () => Promise; + } + : Omit & { + ok: false; + status: K; + data: TAllResponses[K]; + json: () => Promise; + } + : never; + }[keyof TAllResponses]; + +export type SafeApiResponse = TEndpoint extends { response: infer TSuccess; responses: infer TResponses } + ? TResponses extends Record + ? TypedApiResponse + : Omit & { + ok: true; + status: number; + data: TSuccess; + json: () => Promise; + } + : TEndpoint extends { response: infer TSuccess } + ? Omit & { + ok: true; + status: number; + data: TSuccess; + json: () => Promise; + } + : never; + type RequiredKeys = { [P in keyof T]-?: undefined extends T[P] ? never : P; }[keyof T]; @@ -113,22 +206,90 @@ export class ApiClient { // get( path: Path, - ...params: MaybeOptionalArg["parameters"]> - ): Promise["response"]> { - return this.fetcher("get", this.baseUrl + path, params[0]).then((response) => - this.parseResponse(response), - ) as Promise["response"]>; + ...params: MaybeOptionalArg["parameters"] & { withResponse?: false }> + ): Promise["response"]>; + + get( + path: Path, + ...params: MaybeOptionalArg["parameters"] & { withResponse: true }> + ): Promise>; + + get( + path: Path, + ...params: MaybeOptionalArg + ): Promise { + const requestParams = params[0]; + const withResponse = requestParams?.withResponse; + + // Remove withResponse from params before passing to fetcher + const { withResponse: _, ...fetchParams } = requestParams || {}; + + if (withResponse) { + return this.fetcher("get", this.baseUrl + path, Object.keys(fetchParams).length ? fetchParams : undefined).then( + async (response) => { + // Parse the response data + const data = await this.parseResponse(response); + + // Override properties while keeping the original Response object + const typedResponse = Object.assign(response, { + ok: response.ok, + status: response.status, + data: data, + json: () => Promise.resolve(data), + }); + return typedResponse; + }, + ); + } else { + return this.fetcher("get", this.baseUrl + path, requestParams).then((response) => + this.parseResponse(response), + ) as Promise["response"]>; + } } // // post( path: Path, - ...params: MaybeOptionalArg["parameters"]> - ): Promise["response"]> { - return this.fetcher("post", this.baseUrl + path, params[0]).then((response) => - this.parseResponse(response), - ) as Promise["response"]>; + ...params: MaybeOptionalArg["parameters"] & { withResponse?: false }> + ): Promise["response"]>; + + post( + path: Path, + ...params: MaybeOptionalArg["parameters"] & { withResponse: true }> + ): Promise>; + + post( + path: Path, + ...params: MaybeOptionalArg + ): Promise { + const requestParams = params[0]; + const withResponse = requestParams?.withResponse; + + // Remove withResponse from params before passing to fetcher + const { withResponse: _, ...fetchParams } = requestParams || {}; + + if (withResponse) { + return this.fetcher("post", this.baseUrl + path, Object.keys(fetchParams).length ? fetchParams : undefined).then( + async (response) => { + // Parse the response data + const data = await this.parseResponse(response); + + // Override properties while keeping the original Response object + const typedResponse = Object.assign(response, { + ok: response.ok, + status: response.status, + data: data, + json: () => Promise.resolve(data), + }); + return typedResponse; + }, + ); + } else { + return this.fetcher("post", this.baseUrl + path, requestParams).then((response) => + this.parseResponse(response), + ) as Promise["response"]>; + } } // @@ -167,6 +328,21 @@ export function createApiClient(fetcher: Fetcher, baseUrl?: string) { api.get("/users").then((users) => console.log(users)); api.post("/users", { body: { name: "John" } }).then((user) => console.log(user)); api.put("/users/:id", { path: { id: 1 }, body: { name: "John" } }).then((user) => console.log(user)); + + // With error handling + const result = await api.get("/users/{id}", { path: { id: "123" }, withResponse: true }); + if (result.ok) { + // Access data directly + const user = result.data; + console.log(user); + + // Or use the json() method for compatibility + const userFromJson = await result.json(); + console.log(userFromJson); + } else { + const error = result.data; + console.error(`Error ${result.status}:`, error); + } */ // ; @@ -65,6 +71,7 @@ type RequestFormat = "json" | "form-data" | "form-url" | "binary" | "text"; export type DefaultEndpoint = { parameters?: EndpointParameters | undefined; response: unknown; + responses?: Record; responseHeaders?: Record; }; @@ -80,11 +87,97 @@ export type Endpoint = { areParametersRequired: boolean; }; response: TConfig["response"]; + responses?: TConfig["responses"]; responseHeaders?: TConfig["responseHeaders"]; }; export type Fetcher = (method: Method, url: string, parameters?: EndpointParameters | undefined) => Promise; +// Status code type for success responses +export type SuccessStatusCode = + | 200 + | 201 + | 202 + | 203 + | 204 + | 205 + | 206 + | 207 + | 208 + | 226 + | 300 + | 301 + | 302 + | 303 + | 304 + | 305 + | 306 + | 307 + | 308; + +// Error handling types +export type TypedApiResponse< + TSuccess, + TAllResponses extends Record = {}, +> = keyof TAllResponses extends never + ? Omit & { + ok: true; + status: number; + data: TSuccess; + json: () => Promise; + } + : { + [K in keyof TAllResponses]: K extends string + ? K extends `${infer TStatusCode extends number}` + ? TStatusCode extends SuccessStatusCode + ? Omit & { + ok: true; + status: TStatusCode; + data: TSuccess; + json: () => Promise; + } + : Omit & { + ok: false; + status: TStatusCode; + data: TAllResponses[K]; + json: () => Promise; + } + : never + : K extends number + ? K extends SuccessStatusCode + ? Omit & { + ok: true; + status: K; + data: TSuccess; + json: () => Promise; + } + : Omit & { + ok: false; + status: K; + data: TAllResponses[K]; + json: () => Promise; + } + : never; + }[keyof TAllResponses]; + +export type SafeApiResponse = TEndpoint extends { response: infer TSuccess; responses: infer TResponses } + ? TResponses extends Record + ? TypedApiResponse + : Omit & { + ok: true; + status: number; + data: TSuccess; + json: () => Promise; + } + : TEndpoint extends { response: infer TSuccess } + ? Omit & { + ok: true; + status: number; + data: TSuccess; + json: () => Promise; + } + : never; + type RequiredKeys = { [P in keyof T]-?: undefined extends T[P] ? never : P; }[keyof T]; @@ -115,22 +208,90 @@ export class ApiClient { // get( path: Path, - ...params: MaybeOptionalArg["parameters"]> - ): Promise["response"]> { - return this.fetcher("get", this.baseUrl + path, params[0]).then((response) => - this.parseResponse(response), - ) as Promise["response"]>; + ...params: MaybeOptionalArg["parameters"] & { withResponse?: false }> + ): Promise["response"]>; + + get( + path: Path, + ...params: MaybeOptionalArg["parameters"] & { withResponse: true }> + ): Promise>; + + get( + path: Path, + ...params: MaybeOptionalArg + ): Promise { + const requestParams = params[0]; + const withResponse = requestParams?.withResponse; + + // Remove withResponse from params before passing to fetcher + const { withResponse: _, ...fetchParams } = requestParams || {}; + + if (withResponse) { + return this.fetcher("get", this.baseUrl + path, Object.keys(fetchParams).length ? fetchParams : undefined).then( + async (response) => { + // Parse the response data + const data = await this.parseResponse(response); + + // Override properties while keeping the original Response object + const typedResponse = Object.assign(response, { + ok: response.ok, + status: response.status, + data: data, + json: () => Promise.resolve(data), + }); + return typedResponse; + }, + ); + } else { + return this.fetcher("get", this.baseUrl + path, requestParams).then((response) => + this.parseResponse(response), + ) as Promise["response"]>; + } } // // post( path: Path, - ...params: MaybeOptionalArg["parameters"]> - ): Promise["response"]> { - return this.fetcher("post", this.baseUrl + path, params[0]).then((response) => - this.parseResponse(response), - ) as Promise["response"]>; + ...params: MaybeOptionalArg["parameters"] & { withResponse?: false }> + ): Promise["response"]>; + + post( + path: Path, + ...params: MaybeOptionalArg["parameters"] & { withResponse: true }> + ): Promise>; + + post( + path: Path, + ...params: MaybeOptionalArg + ): Promise { + const requestParams = params[0]; + const withResponse = requestParams?.withResponse; + + // Remove withResponse from params before passing to fetcher + const { withResponse: _, ...fetchParams } = requestParams || {}; + + if (withResponse) { + return this.fetcher("post", this.baseUrl + path, Object.keys(fetchParams).length ? fetchParams : undefined).then( + async (response) => { + // Parse the response data + const data = await this.parseResponse(response); + + // Override properties while keeping the original Response object + const typedResponse = Object.assign(response, { + ok: response.ok, + status: response.status, + data: data, + json: () => Promise.resolve(data), + }); + return typedResponse; + }, + ); + } else { + return this.fetcher("post", this.baseUrl + path, requestParams).then((response) => + this.parseResponse(response), + ) as Promise["response"]>; + } } // @@ -169,6 +330,21 @@ export function createApiClient(fetcher: Fetcher, baseUrl?: string) { api.get("/users").then((users) => console.log(users)); api.post("/users", { body: { name: "John" } }).then((user) => console.log(user)); api.put("/users/:id", { path: { id: 1 }, body: { name: "John" } }).then((user) => console.log(user)); + + // With error handling + const result = await api.get("/users/{id}", { path: { id: "123" }, withResponse: true }); + if (result.ok) { + // Access data directly + const user = result.data; + console.log(user); + + // Or use the json() method for compatibility + const userFromJson = await result.json(); + console.log(userFromJson); + } else { + const error = result.data; + console.error(`Error ${result.status}:`, error); + } */ // ; @@ -63,6 +69,7 @@ type RequestFormat = "json" | "form-data" | "form-url" | "binary" | "text"; export type DefaultEndpoint = { parameters?: EndpointParameters | undefined; response: unknown; + responses?: Record; responseHeaders?: Record; }; @@ -78,11 +85,97 @@ export type Endpoint = { areParametersRequired: boolean; }; response: TConfig["response"]; + responses?: TConfig["responses"]; responseHeaders?: TConfig["responseHeaders"]; }; export type Fetcher = (method: Method, url: string, parameters?: EndpointParameters | undefined) => Promise; +// Status code type for success responses +export type SuccessStatusCode = + | 200 + | 201 + | 202 + | 203 + | 204 + | 205 + | 206 + | 207 + | 208 + | 226 + | 300 + | 301 + | 302 + | 303 + | 304 + | 305 + | 306 + | 307 + | 308; + +// Error handling types +export type TypedApiResponse< + TSuccess, + TAllResponses extends Record = {}, +> = keyof TAllResponses extends never + ? Omit & { + ok: true; + status: number; + data: TSuccess; + json: () => Promise; + } + : { + [K in keyof TAllResponses]: K extends string + ? K extends `${infer TStatusCode extends number}` + ? TStatusCode extends SuccessStatusCode + ? Omit & { + ok: true; + status: TStatusCode; + data: TSuccess; + json: () => Promise; + } + : Omit & { + ok: false; + status: TStatusCode; + data: TAllResponses[K]; + json: () => Promise; + } + : never + : K extends number + ? K extends SuccessStatusCode + ? Omit & { + ok: true; + status: K; + data: TSuccess; + json: () => Promise; + } + : Omit & { + ok: false; + status: K; + data: TAllResponses[K]; + json: () => Promise; + } + : never; + }[keyof TAllResponses]; + +export type SafeApiResponse = TEndpoint extends { response: infer TSuccess; responses: infer TResponses } + ? TResponses extends Record + ? TypedApiResponse + : Omit & { + ok: true; + status: number; + data: TSuccess; + json: () => Promise; + } + : TEndpoint extends { response: infer TSuccess } + ? Omit & { + ok: true; + status: number; + data: TSuccess; + json: () => Promise; + } + : never; + type RequiredKeys = { [P in keyof T]-?: undefined extends T[P] ? never : P; }[keyof T]; @@ -113,22 +206,90 @@ export class ApiClient { // get( path: Path, - ...params: MaybeOptionalArg["parameters"]> - ): Promise["response"]> { - return this.fetcher("get", this.baseUrl + path, params[0]).then((response) => - this.parseResponse(response), - ) as Promise["response"]>; + ...params: MaybeOptionalArg["parameters"] & { withResponse?: false }> + ): Promise["response"]>; + + get( + path: Path, + ...params: MaybeOptionalArg["parameters"] & { withResponse: true }> + ): Promise>; + + get( + path: Path, + ...params: MaybeOptionalArg + ): Promise { + const requestParams = params[0]; + const withResponse = requestParams?.withResponse; + + // Remove withResponse from params before passing to fetcher + const { withResponse: _, ...fetchParams } = requestParams || {}; + + if (withResponse) { + return this.fetcher("get", this.baseUrl + path, Object.keys(fetchParams).length ? fetchParams : undefined).then( + async (response) => { + // Parse the response data + const data = await this.parseResponse(response); + + // Override properties while keeping the original Response object + const typedResponse = Object.assign(response, { + ok: response.ok, + status: response.status, + data: data, + json: () => Promise.resolve(data), + }); + return typedResponse; + }, + ); + } else { + return this.fetcher("get", this.baseUrl + path, requestParams).then((response) => + this.parseResponse(response), + ) as Promise["response"]>; + } } // // post( path: Path, - ...params: MaybeOptionalArg["parameters"]> - ): Promise["response"]> { - return this.fetcher("post", this.baseUrl + path, params[0]).then((response) => - this.parseResponse(response), - ) as Promise["response"]>; + ...params: MaybeOptionalArg["parameters"] & { withResponse?: false }> + ): Promise["response"]>; + + post( + path: Path, + ...params: MaybeOptionalArg["parameters"] & { withResponse: true }> + ): Promise>; + + post( + path: Path, + ...params: MaybeOptionalArg + ): Promise { + const requestParams = params[0]; + const withResponse = requestParams?.withResponse; + + // Remove withResponse from params before passing to fetcher + const { withResponse: _, ...fetchParams } = requestParams || {}; + + if (withResponse) { + return this.fetcher("post", this.baseUrl + path, Object.keys(fetchParams).length ? fetchParams : undefined).then( + async (response) => { + // Parse the response data + const data = await this.parseResponse(response); + + // Override properties while keeping the original Response object + const typedResponse = Object.assign(response, { + ok: response.ok, + status: response.status, + data: data, + json: () => Promise.resolve(data), + }); + return typedResponse; + }, + ); + } else { + return this.fetcher("post", this.baseUrl + path, requestParams).then((response) => + this.parseResponse(response), + ) as Promise["response"]>; + } } // @@ -167,6 +328,21 @@ export function createApiClient(fetcher: Fetcher, baseUrl?: string) { api.get("/users").then((users) => console.log(users)); api.post("/users", { body: { name: "John" } }).then((user) => console.log(user)); api.put("/users/:id", { path: { id: 1 }, body: { name: "John" } }).then((user) => console.log(user)); + + // With error handling + const result = await api.get("/users/{id}", { path: { id: "123" }, withResponse: true }); + if (result.ok) { + // Access data directly + const user = result.data; + console.log(user); + + // Or use the json() method for compatibility + const userFromJson = await result.json(); + console.log(userFromJson); + } else { + const error = result.data; + console.error(`Error ${result.status}:`, error); + } */ // value === "json").required(), parameters: y.mixed((value): value is never => false).required(), response: y.array(y.string().required()), + responses: y.object({ + "200": y.array(y.string().required()), + }), }; export type post_Very_very_very_very_very_very_very_very_very_very_long = @@ -21,6 +24,9 @@ export const post_Very_very_very_very_very_very_very_very_very_very_long = { }), }), response: y.mixed((value): value is any => true).required() as y.MixedSchema, + responses: y.object({ + "201": y.mixed((value): value is any => true).required() as y.MixedSchema, + }), }; // @@ -56,6 +62,7 @@ type RequestFormat = "json" | "form-data" | "form-url" | "binary" | "text"; export type DefaultEndpoint = { parameters?: EndpointParameters | undefined; response: unknown; + responses?: Record; responseHeaders?: Record; }; @@ -71,11 +78,97 @@ export type Endpoint = { areParametersRequired: boolean; }; response: TConfig["response"]; + responses?: TConfig["responses"]; responseHeaders?: TConfig["responseHeaders"]; }; export type Fetcher = (method: Method, url: string, parameters?: EndpointParameters | undefined) => Promise; +// Status code type for success responses +export type SuccessStatusCode = + | 200 + | 201 + | 202 + | 203 + | 204 + | 205 + | 206 + | 207 + | 208 + | 226 + | 300 + | 301 + | 302 + | 303 + | 304 + | 305 + | 306 + | 307 + | 308; + +// Error handling types +export type TypedApiResponse< + TSuccess, + TAllResponses extends Record = {}, +> = keyof TAllResponses extends never + ? Omit & { + ok: true; + status: number; + data: TSuccess; + json: () => Promise; + } + : { + [K in keyof TAllResponses]: K extends string + ? K extends `${infer TStatusCode extends number}` + ? TStatusCode extends SuccessStatusCode + ? Omit & { + ok: true; + status: TStatusCode; + data: TSuccess; + json: () => Promise; + } + : Omit & { + ok: false; + status: TStatusCode; + data: TAllResponses[K]; + json: () => Promise; + } + : never + : K extends number + ? K extends SuccessStatusCode + ? Omit & { + ok: true; + status: K; + data: TSuccess; + json: () => Promise; + } + : Omit & { + ok: false; + status: K; + data: TAllResponses[K]; + json: () => Promise; + } + : never; + }[keyof TAllResponses]; + +export type SafeApiResponse = TEndpoint extends { response: infer TSuccess; responses: infer TResponses } + ? TResponses extends Record + ? TypedApiResponse + : Omit & { + ok: true; + status: number; + data: TSuccess; + json: () => Promise; + } + : TEndpoint extends { response: infer TSuccess } + ? Omit & { + ok: true; + status: number; + data: TSuccess; + json: () => Promise; + } + : never; + type RequiredKeys = { [P in keyof T]-?: undefined extends T[P] ? never : P; }[keyof T]; @@ -106,22 +199,90 @@ export class ApiClient { // get( path: Path, - ...params: MaybeOptionalArg> - ): Promise> { - return this.fetcher("get", this.baseUrl + path, params[0]).then((response) => - this.parseResponse(response), - ) as Promise>; + ...params: MaybeOptionalArg & { withResponse?: false }> + ): Promise>; + + get( + path: Path, + ...params: MaybeOptionalArg & { withResponse: true }> + ): Promise>; + + get( + path: Path, + ...params: MaybeOptionalArg + ): Promise { + const requestParams = params[0]; + const withResponse = requestParams?.withResponse; + + // Remove withResponse from params before passing to fetcher + const { withResponse: _, ...fetchParams } = requestParams || {}; + + if (withResponse) { + return this.fetcher("get", this.baseUrl + path, Object.keys(fetchParams).length ? fetchParams : undefined).then( + async (response) => { + // Parse the response data + const data = await this.parseResponse(response); + + // Override properties while keeping the original Response object + const typedResponse = Object.assign(response, { + ok: response.ok, + status: response.status, + data: data, + json: () => Promise.resolve(data), + }); + return typedResponse; + }, + ); + } else { + return this.fetcher("get", this.baseUrl + path, requestParams).then((response) => + this.parseResponse(response), + ) as Promise>; + } } // // post( path: Path, - ...params: MaybeOptionalArg> - ): Promise> { - return this.fetcher("post", this.baseUrl + path, params[0]).then((response) => - this.parseResponse(response), - ) as Promise>; + ...params: MaybeOptionalArg & { withResponse?: false }> + ): Promise>; + + post( + path: Path, + ...params: MaybeOptionalArg & { withResponse: true }> + ): Promise>; + + post( + path: Path, + ...params: MaybeOptionalArg + ): Promise { + const requestParams = params[0]; + const withResponse = requestParams?.withResponse; + + // Remove withResponse from params before passing to fetcher + const { withResponse: _, ...fetchParams } = requestParams || {}; + + if (withResponse) { + return this.fetcher("post", this.baseUrl + path, Object.keys(fetchParams).length ? fetchParams : undefined).then( + async (response) => { + // Parse the response data + const data = await this.parseResponse(response); + + // Override properties while keeping the original Response object + const typedResponse = Object.assign(response, { + ok: response.ok, + status: response.status, + data: data, + json: () => Promise.resolve(data), + }); + return typedResponse; + }, + ); + } else { + return this.fetcher("post", this.baseUrl + path, requestParams).then((response) => + this.parseResponse(response), + ) as Promise>; + } } // @@ -160,6 +321,21 @@ export function createApiClient(fetcher: Fetcher, baseUrl?: string) { api.get("/users").then((users) => console.log(users)); api.post("/users", { body: { name: "John" } }).then((user) => console.log(user)); api.put("/users/:id", { path: { id: 1 }, body: { name: "John" } }).then((user) => console.log(user)); + + // With error handling + const result = await api.get("/users/{id}", { path: { id: "123" }, withResponse: true }); + if (result.ok) { + // Access data directly + const user = result.data; + console.log(user); + + // Or use the json() method for compatibility + const userFromJson = await result.json(); + console.log(userFromJson); + } else { + const error = result.data; + console.error(`Error ${result.status}:`, error); + } */ // @@ -56,6 +62,7 @@ type RequestFormat = "json" | "form-data" | "form-url" | "binary" | "text"; export type DefaultEndpoint = { parameters?: EndpointParameters | undefined; response: unknown; + responses?: Record; responseHeaders?: Record; }; @@ -71,11 +78,97 @@ export type Endpoint = { areParametersRequired: boolean; }; response: TConfig["response"]; + responses?: TConfig["responses"]; responseHeaders?: TConfig["responseHeaders"]; }; export type Fetcher = (method: Method, url: string, parameters?: EndpointParameters | undefined) => Promise; +// Status code type for success responses +export type SuccessStatusCode = + | 200 + | 201 + | 202 + | 203 + | 204 + | 205 + | 206 + | 207 + | 208 + | 226 + | 300 + | 301 + | 302 + | 303 + | 304 + | 305 + | 306 + | 307 + | 308; + +// Error handling types +export type TypedApiResponse< + TSuccess, + TAllResponses extends Record = {}, +> = keyof TAllResponses extends never + ? Omit & { + ok: true; + status: number; + data: TSuccess; + json: () => Promise; + } + : { + [K in keyof TAllResponses]: K extends string + ? K extends `${infer TStatusCode extends number}` + ? TStatusCode extends SuccessStatusCode + ? Omit & { + ok: true; + status: TStatusCode; + data: TSuccess; + json: () => Promise; + } + : Omit & { + ok: false; + status: TStatusCode; + data: TAllResponses[K]; + json: () => Promise; + } + : never + : K extends number + ? K extends SuccessStatusCode + ? Omit & { + ok: true; + status: K; + data: TSuccess; + json: () => Promise; + } + : Omit & { + ok: false; + status: K; + data: TAllResponses[K]; + json: () => Promise; + } + : never; + }[keyof TAllResponses]; + +export type SafeApiResponse = TEndpoint extends { response: infer TSuccess; responses: infer TResponses } + ? TResponses extends Record + ? TypedApiResponse + : Omit & { + ok: true; + status: number; + data: TSuccess; + json: () => Promise; + } + : TEndpoint extends { response: infer TSuccess } + ? Omit & { + ok: true; + status: number; + data: TSuccess; + json: () => Promise; + } + : never; + type RequiredKeys = { [P in keyof T]-?: undefined extends T[P] ? never : P; }[keyof T]; @@ -106,22 +199,90 @@ export class ApiClient { // get( path: Path, - ...params: MaybeOptionalArg> - ): Promise> { - return this.fetcher("get", this.baseUrl + path, params[0]).then((response) => - this.parseResponse(response), - ) as Promise>; + ...params: MaybeOptionalArg & { withResponse?: false }> + ): Promise>; + + get( + path: Path, + ...params: MaybeOptionalArg & { withResponse: true }> + ): Promise>; + + get( + path: Path, + ...params: MaybeOptionalArg + ): Promise { + const requestParams = params[0]; + const withResponse = requestParams?.withResponse; + + // Remove withResponse from params before passing to fetcher + const { withResponse: _, ...fetchParams } = requestParams || {}; + + if (withResponse) { + return this.fetcher("get", this.baseUrl + path, Object.keys(fetchParams).length ? fetchParams : undefined).then( + async (response) => { + // Parse the response data + const data = await this.parseResponse(response); + + // Override properties while keeping the original Response object + const typedResponse = Object.assign(response, { + ok: response.ok, + status: response.status, + data: data, + json: () => Promise.resolve(data), + }); + return typedResponse; + }, + ); + } else { + return this.fetcher("get", this.baseUrl + path, requestParams).then((response) => + this.parseResponse(response), + ) as Promise>; + } } // // post( path: Path, - ...params: MaybeOptionalArg> - ): Promise> { - return this.fetcher("post", this.baseUrl + path, params[0]).then((response) => - this.parseResponse(response), - ) as Promise>; + ...params: MaybeOptionalArg & { withResponse?: false }> + ): Promise>; + + post( + path: Path, + ...params: MaybeOptionalArg & { withResponse: true }> + ): Promise>; + + post( + path: Path, + ...params: MaybeOptionalArg + ): Promise { + const requestParams = params[0]; + const withResponse = requestParams?.withResponse; + + // Remove withResponse from params before passing to fetcher + const { withResponse: _, ...fetchParams } = requestParams || {}; + + if (withResponse) { + return this.fetcher("post", this.baseUrl + path, Object.keys(fetchParams).length ? fetchParams : undefined).then( + async (response) => { + // Parse the response data + const data = await this.parseResponse(response); + + // Override properties while keeping the original Response object + const typedResponse = Object.assign(response, { + ok: response.ok, + status: response.status, + data: data, + json: () => Promise.resolve(data), + }); + return typedResponse; + }, + ); + } else { + return this.fetcher("post", this.baseUrl + path, requestParams).then((response) => + this.parseResponse(response), + ) as Promise>; + } } // @@ -160,6 +321,21 @@ export function createApiClient(fetcher: Fetcher, baseUrl?: string) { api.get("/users").then((users) => console.log(users)); api.post("/users", { body: { name: "John" } }).then((user) => console.log(user)); api.put("/users/:id", { path: { id: 1 }, body: { name: "John" } }).then((user) => console.log(user)); + + // With error handling + const result = await api.get("/users/{id}", { path: { id: "123" }, withResponse: true }); + if (result.ok) { + // Access data directly + const user = result.data; + console.log(user); + + // Or use the json() method for compatibility + const userFromJson = await result.json(); + console.log(userFromJson); + } else { + const error = result.data; + console.error(`Error ${result.status}:`, error); + } */ // ; responseHeaders?: Record; }; @@ -393,11 +477,97 @@ export type Endpoint = { areParametersRequired: boolean; }; response: TConfig["response"]; + responses?: TConfig["responses"]; responseHeaders?: TConfig["responseHeaders"]; }; export type Fetcher = (method: Method, url: string, parameters?: EndpointParameters | undefined) => Promise; +// Status code type for success responses +export type SuccessStatusCode = + | 200 + | 201 + | 202 + | 203 + | 204 + | 205 + | 206 + | 207 + | 208 + | 226 + | 300 + | 301 + | 302 + | 303 + | 304 + | 305 + | 306 + | 307 + | 308; + +// Error handling types +export type TypedApiResponse< + TSuccess, + TAllResponses extends Record = {}, +> = keyof TAllResponses extends never + ? Omit & { + ok: true; + status: number; + data: TSuccess; + json: () => Promise; + } + : { + [K in keyof TAllResponses]: K extends string + ? K extends `${infer TStatusCode extends number}` + ? TStatusCode extends SuccessStatusCode + ? Omit & { + ok: true; + status: TStatusCode; + data: TSuccess; + json: () => Promise; + } + : Omit & { + ok: false; + status: TStatusCode; + data: TAllResponses[K]; + json: () => Promise; + } + : never + : K extends number + ? K extends SuccessStatusCode + ? Omit & { + ok: true; + status: K; + data: TSuccess; + json: () => Promise; + } + : Omit & { + ok: false; + status: K; + data: TAllResponses[K]; + json: () => Promise; + } + : never; + }[keyof TAllResponses]; + +export type SafeApiResponse = TEndpoint extends { response: infer TSuccess; responses: infer TResponses } + ? TResponses extends Record + ? TypedApiResponse + : Omit & { + ok: true; + status: number; + data: TSuccess; + json: () => Promise; + } + : TEndpoint extends { response: infer TSuccess } + ? Omit & { + ok: true; + status: number; + data: TSuccess; + json: () => Promise; + } + : never; + type RequiredKeys = { [P in keyof T]-?: undefined extends T[P] ? never : P; }[keyof T]; @@ -428,44 +598,182 @@ export class ApiClient { // put( path: Path, - ...params: MaybeOptionalArg - ): Promise { - return this.fetcher("put", this.baseUrl + path, params[0]).then((response) => - this.parseResponse(response), - ) as Promise; + ...params: MaybeOptionalArg + ): Promise; + + put( + path: Path, + ...params: MaybeOptionalArg + ): Promise>; + + put( + path: Path, + ...params: MaybeOptionalArg + ): Promise { + const requestParams = params[0]; + const withResponse = requestParams?.withResponse; + + // Remove withResponse from params before passing to fetcher + const { withResponse: _, ...fetchParams } = requestParams || {}; + + if (withResponse) { + return this.fetcher("put", this.baseUrl + path, Object.keys(fetchParams).length ? fetchParams : undefined).then( + async (response) => { + // Parse the response data + const data = await this.parseResponse(response); + + // Override properties while keeping the original Response object + const typedResponse = Object.assign(response, { + ok: response.ok, + status: response.status, + data: data, + json: () => Promise.resolve(data), + }); + return typedResponse; + }, + ); + } else { + return this.fetcher("put", this.baseUrl + path, requestParams).then((response) => + this.parseResponse(response), + ) as Promise; + } } // // post( path: Path, - ...params: MaybeOptionalArg - ): Promise { - return this.fetcher("post", this.baseUrl + path, params[0]).then((response) => - this.parseResponse(response), - ) as Promise; + ...params: MaybeOptionalArg + ): Promise; + + post( + path: Path, + ...params: MaybeOptionalArg + ): Promise>; + + post( + path: Path, + ...params: MaybeOptionalArg + ): Promise { + const requestParams = params[0]; + const withResponse = requestParams?.withResponse; + + // Remove withResponse from params before passing to fetcher + const { withResponse: _, ...fetchParams } = requestParams || {}; + + if (withResponse) { + return this.fetcher("post", this.baseUrl + path, Object.keys(fetchParams).length ? fetchParams : undefined).then( + async (response) => { + // Parse the response data + const data = await this.parseResponse(response); + + // Override properties while keeping the original Response object + const typedResponse = Object.assign(response, { + ok: response.ok, + status: response.status, + data: data, + json: () => Promise.resolve(data), + }); + return typedResponse; + }, + ); + } else { + return this.fetcher("post", this.baseUrl + path, requestParams).then((response) => + this.parseResponse(response), + ) as Promise; + } } // // get( path: Path, - ...params: MaybeOptionalArg - ): Promise { - return this.fetcher("get", this.baseUrl + path, params[0]).then((response) => - this.parseResponse(response), - ) as Promise; + ...params: MaybeOptionalArg + ): Promise; + + get( + path: Path, + ...params: MaybeOptionalArg + ): Promise>; + + get( + path: Path, + ...params: MaybeOptionalArg + ): Promise { + const requestParams = params[0]; + const withResponse = requestParams?.withResponse; + + // Remove withResponse from params before passing to fetcher + const { withResponse: _, ...fetchParams } = requestParams || {}; + + if (withResponse) { + return this.fetcher("get", this.baseUrl + path, Object.keys(fetchParams).length ? fetchParams : undefined).then( + async (response) => { + // Parse the response data + const data = await this.parseResponse(response); + + // Override properties while keeping the original Response object + const typedResponse = Object.assign(response, { + ok: response.ok, + status: response.status, + data: data, + json: () => Promise.resolve(data), + }); + return typedResponse; + }, + ); + } else { + return this.fetcher("get", this.baseUrl + path, requestParams).then((response) => + this.parseResponse(response), + ) as Promise; + } } // // delete( path: Path, - ...params: MaybeOptionalArg - ): Promise { - return this.fetcher("delete", this.baseUrl + path, params[0]).then((response) => - this.parseResponse(response), - ) as Promise; + ...params: MaybeOptionalArg + ): Promise; + + delete( + path: Path, + ...params: MaybeOptionalArg + ): Promise>; + + delete( + path: Path, + ...params: MaybeOptionalArg + ): Promise { + const requestParams = params[0]; + const withResponse = requestParams?.withResponse; + + // Remove withResponse from params before passing to fetcher + const { withResponse: _, ...fetchParams } = requestParams || {}; + + if (withResponse) { + return this.fetcher( + "delete", + this.baseUrl + path, + Object.keys(fetchParams).length ? fetchParams : undefined, + ).then(async (response) => { + // Parse the response data + const data = await this.parseResponse(response); + + // Override properties while keeping the original Response object + const typedResponse = Object.assign(response, { + ok: response.ok, + status: response.status, + data: data, + json: () => Promise.resolve(data), + }); + return typedResponse; + }); + } else { + return this.fetcher("delete", this.baseUrl + path, requestParams).then((response) => + this.parseResponse(response), + ) as Promise; + } } // @@ -504,6 +812,21 @@ export function createApiClient(fetcher: Fetcher, baseUrl?: string) { api.get("/users").then((users) => console.log(users)); api.post("/users", { body: { name: "John" } }).then((user) => console.log(user)); api.put("/users/:id", { path: { id: 1 }, body: { name: "John" } }).then((user) => console.log(user)); + + // With error handling + const result = await api.get("/users/{id}", { path: { id: "123" }, withResponse: true }); + if (result.ok) { + // Access data directly + const user = result.data; + console.log(user); + + // Or use the json() method for compatibility + const userFromJson = await result.json(); + console.log(userFromJson); + } else { + const error = result.data; + console.error(`Error ${result.status}:`, error); + } */ // ; }; response: Array; + responses: { 200: Array; 400: { code: number; message: string } }; }; export type get_FindPetsByTags = { method: "GET"; @@ -73,6 +76,7 @@ export namespace Endpoints { query: Partial<{ tags: Array }>; }; response: Array; + responses: { 200: Array; 400: unknown }; }; export type get_GetPetById = { method: "GET"; @@ -82,6 +86,7 @@ export namespace Endpoints { path: { petId: number }; }; response: Schemas.Pet; + responses: { 200: Schemas.Pet; 400: { code: number; message: string }; 404: { code: number; message: string } }; }; export type post_UpdatePetWithForm = { method: "POST"; @@ -92,6 +97,7 @@ export namespace Endpoints { path: { petId: number }; }; response: unknown; + responses: { 405: unknown }; }; export type delete_DeletePet = { method: "DELETE"; @@ -102,6 +108,7 @@ export namespace Endpoints { header: Partial<{ api_key: string }>; }; response: unknown; + responses: { 400: unknown }; }; export type post_UploadFile = { method: "POST"; @@ -114,6 +121,7 @@ export namespace Endpoints { body: string; }; response: Schemas.ApiResponse; + responses: { 200: Schemas.ApiResponse }; }; export type get_GetInventory = { method: "GET"; @@ -121,6 +129,7 @@ export namespace Endpoints { requestFormat: "json"; parameters: never; response: Record; + responses: { 200: Record }; }; export type post_PlaceOrder = { method: "POST"; @@ -130,6 +139,7 @@ export namespace Endpoints { body: Schemas.Order; }; response: Schemas.Order; + responses: { 200: Schemas.Order; 405: unknown }; }; export type get_GetOrderById = { method: "GET"; @@ -139,6 +149,7 @@ export namespace Endpoints { path: { orderId: number }; }; response: Schemas.Order; + responses: { 200: Schemas.Order; 400: unknown; 404: unknown }; }; export type delete_DeleteOrder = { method: "DELETE"; @@ -148,6 +159,7 @@ export namespace Endpoints { path: { orderId: number }; }; response: unknown; + responses: { 400: unknown; 404: unknown }; }; export type post_CreateUser = { method: "POST"; @@ -157,6 +169,7 @@ export namespace Endpoints { body: Schemas.User; }; response: Schemas.User; + responses: { default: Schemas.User }; }; export type post_CreateUsersWithListInput = { method: "POST"; @@ -166,6 +179,7 @@ export namespace Endpoints { body: Array; }; response: Schemas.User; + responses: { 200: Schemas.User; default: unknown }; }; export type get_LoginUser = { method: "GET"; @@ -175,6 +189,7 @@ export namespace Endpoints { query: Partial<{ username: string; password: string }>; }; response: string; + responses: { 200: string; 400: unknown }; responseHeaders: { "x-rate-limit": number; "x-expires-after": string }; }; export type get_LogoutUser = { @@ -183,6 +198,7 @@ export namespace Endpoints { requestFormat: "json"; parameters: never; response: unknown; + responses: { default: unknown }; }; export type get_GetUserByName = { method: "GET"; @@ -192,6 +208,7 @@ export namespace Endpoints { path: { username: string }; }; response: Schemas.User; + responses: { 200: Schemas.User; 400: unknown; 404: unknown }; }; export type put_UpdateUser = { method: "PUT"; @@ -203,6 +220,7 @@ export namespace Endpoints { body: Schemas.User; }; response: unknown; + responses: { default: unknown }; }; export type delete_DeleteUser = { method: "DELETE"; @@ -212,6 +230,7 @@ export namespace Endpoints { path: { username: string }; }; response: unknown; + responses: { 400: unknown; 404: unknown }; }; // @@ -273,6 +292,7 @@ type RequestFormat = "json" | "form-data" | "form-url" | "binary" | "text"; export type DefaultEndpoint = { parameters?: EndpointParameters | undefined; response: unknown; + responses?: Record; responseHeaders?: Record; }; @@ -288,11 +308,97 @@ export type Endpoint = { areParametersRequired: boolean; }; response: TConfig["response"]; + responses?: TConfig["responses"]; responseHeaders?: TConfig["responseHeaders"]; }; export type Fetcher = (method: Method, url: string, parameters?: EndpointParameters | undefined) => Promise; +// Status code type for success responses +export type SuccessStatusCode = + | 200 + | 201 + | 202 + | 203 + | 204 + | 205 + | 206 + | 207 + | 208 + | 226 + | 300 + | 301 + | 302 + | 303 + | 304 + | 305 + | 306 + | 307 + | 308; + +// Error handling types +export type TypedApiResponse< + TSuccess, + TAllResponses extends Record = {}, +> = keyof TAllResponses extends never + ? Omit & { + ok: true; + status: number; + data: TSuccess; + json: () => Promise; + } + : { + [K in keyof TAllResponses]: K extends string + ? K extends `${infer TStatusCode extends number}` + ? TStatusCode extends SuccessStatusCode + ? Omit & { + ok: true; + status: TStatusCode; + data: TSuccess; + json: () => Promise; + } + : Omit & { + ok: false; + status: TStatusCode; + data: TAllResponses[K]; + json: () => Promise; + } + : never + : K extends number + ? K extends SuccessStatusCode + ? Omit & { + ok: true; + status: K; + data: TSuccess; + json: () => Promise; + } + : Omit & { + ok: false; + status: K; + data: TAllResponses[K]; + json: () => Promise; + } + : never; + }[keyof TAllResponses]; + +export type SafeApiResponse = TEndpoint extends { response: infer TSuccess; responses: infer TResponses } + ? TResponses extends Record + ? TypedApiResponse + : Omit & { + ok: true; + status: number; + data: TSuccess; + json: () => Promise; + } + : TEndpoint extends { response: infer TSuccess } + ? Omit & { + ok: true; + status: number; + data: TSuccess; + json: () => Promise; + } + : never; + type RequiredKeys = { [P in keyof T]-?: undefined extends T[P] ? never : P; }[keyof T]; @@ -323,44 +429,182 @@ export class ApiClient { // put( path: Path, - ...params: MaybeOptionalArg - ): Promise { - return this.fetcher("put", this.baseUrl + path, params[0]).then((response) => - this.parseResponse(response), - ) as Promise; + ...params: MaybeOptionalArg + ): Promise; + + put( + path: Path, + ...params: MaybeOptionalArg + ): Promise>; + + put( + path: Path, + ...params: MaybeOptionalArg + ): Promise { + const requestParams = params[0]; + const withResponse = requestParams?.withResponse; + + // Remove withResponse from params before passing to fetcher + const { withResponse: _, ...fetchParams } = requestParams || {}; + + if (withResponse) { + return this.fetcher("put", this.baseUrl + path, Object.keys(fetchParams).length ? fetchParams : undefined).then( + async (response) => { + // Parse the response data + const data = await this.parseResponse(response); + + // Override properties while keeping the original Response object + const typedResponse = Object.assign(response, { + ok: response.ok, + status: response.status, + data: data, + json: () => Promise.resolve(data), + }); + return typedResponse; + }, + ); + } else { + return this.fetcher("put", this.baseUrl + path, requestParams).then((response) => + this.parseResponse(response), + ) as Promise; + } } // // post( path: Path, - ...params: MaybeOptionalArg - ): Promise { - return this.fetcher("post", this.baseUrl + path, params[0]).then((response) => - this.parseResponse(response), - ) as Promise; + ...params: MaybeOptionalArg + ): Promise; + + post( + path: Path, + ...params: MaybeOptionalArg + ): Promise>; + + post( + path: Path, + ...params: MaybeOptionalArg + ): Promise { + const requestParams = params[0]; + const withResponse = requestParams?.withResponse; + + // Remove withResponse from params before passing to fetcher + const { withResponse: _, ...fetchParams } = requestParams || {}; + + if (withResponse) { + return this.fetcher("post", this.baseUrl + path, Object.keys(fetchParams).length ? fetchParams : undefined).then( + async (response) => { + // Parse the response data + const data = await this.parseResponse(response); + + // Override properties while keeping the original Response object + const typedResponse = Object.assign(response, { + ok: response.ok, + status: response.status, + data: data, + json: () => Promise.resolve(data), + }); + return typedResponse; + }, + ); + } else { + return this.fetcher("post", this.baseUrl + path, requestParams).then((response) => + this.parseResponse(response), + ) as Promise; + } } // // get( path: Path, - ...params: MaybeOptionalArg - ): Promise { - return this.fetcher("get", this.baseUrl + path, params[0]).then((response) => - this.parseResponse(response), - ) as Promise; + ...params: MaybeOptionalArg + ): Promise; + + get( + path: Path, + ...params: MaybeOptionalArg + ): Promise>; + + get( + path: Path, + ...params: MaybeOptionalArg + ): Promise { + const requestParams = params[0]; + const withResponse = requestParams?.withResponse; + + // Remove withResponse from params before passing to fetcher + const { withResponse: _, ...fetchParams } = requestParams || {}; + + if (withResponse) { + return this.fetcher("get", this.baseUrl + path, Object.keys(fetchParams).length ? fetchParams : undefined).then( + async (response) => { + // Parse the response data + const data = await this.parseResponse(response); + + // Override properties while keeping the original Response object + const typedResponse = Object.assign(response, { + ok: response.ok, + status: response.status, + data: data, + json: () => Promise.resolve(data), + }); + return typedResponse; + }, + ); + } else { + return this.fetcher("get", this.baseUrl + path, requestParams).then((response) => + this.parseResponse(response), + ) as Promise; + } } // // delete( path: Path, - ...params: MaybeOptionalArg - ): Promise { - return this.fetcher("delete", this.baseUrl + path, params[0]).then((response) => - this.parseResponse(response), - ) as Promise; + ...params: MaybeOptionalArg + ): Promise; + + delete( + path: Path, + ...params: MaybeOptionalArg + ): Promise>; + + delete( + path: Path, + ...params: MaybeOptionalArg + ): Promise { + const requestParams = params[0]; + const withResponse = requestParams?.withResponse; + + // Remove withResponse from params before passing to fetcher + const { withResponse: _, ...fetchParams } = requestParams || {}; + + if (withResponse) { + return this.fetcher( + "delete", + this.baseUrl + path, + Object.keys(fetchParams).length ? fetchParams : undefined, + ).then(async (response) => { + // Parse the response data + const data = await this.parseResponse(response); + + // Override properties while keeping the original Response object + const typedResponse = Object.assign(response, { + ok: response.ok, + status: response.status, + data: data, + json: () => Promise.resolve(data), + }); + return typedResponse; + }); + } else { + return this.fetcher("delete", this.baseUrl + path, requestParams).then((response) => + this.parseResponse(response), + ) as Promise; + } } // @@ -399,6 +643,21 @@ export function createApiClient(fetcher: Fetcher, baseUrl?: string) { api.get("/users").then((users) => console.log(users)); api.post("/users", { body: { name: "John" } }).then((user) => console.log(user)); api.put("/users/:id", { path: { id: 1 }, body: { name: "John" } }).then((user) => console.log(user)); + + // With error handling + const result = await api.get("/users/{id}", { path: { id: "123" }, withResponse: true }); + if (result.ok) { + // Access data directly + const user = result.data; + console.log(user); + + // Or use the json() method for compatibility + const userFromJson = await result.json(); + console.log(userFromJson); + } else { + const error = result.data; + console.error(`Error ${result.status}:`, error); + } */ // ; @@ -92,6 +98,10 @@ export const post_AddPet = t.type({ body: Pet, }), response: Pet, + responses: t.type({ + "200": Pet, + "405": t.unknown, + }), }); export type get_FindPetsByStatus = t.TypeOf; @@ -105,6 +115,13 @@ export const get_FindPetsByStatus = t.type({ }), }), response: t.array(Pet), + responses: t.type({ + "200": t.array(Pet), + "400": t.type({ + code: t.number, + message: t.string, + }), + }), }); export type get_FindPetsByTags = t.TypeOf; @@ -118,6 +135,10 @@ export const get_FindPetsByTags = t.type({ }), }), response: t.array(Pet), + responses: t.type({ + "200": t.array(Pet), + "400": t.unknown, + }), }); export type get_GetPetById = t.TypeOf; @@ -131,6 +152,17 @@ export const get_GetPetById = t.type({ }), }), response: Pet, + responses: t.type({ + "200": Pet, + "400": t.type({ + code: t.number, + message: t.string, + }), + "404": t.type({ + code: t.number, + message: t.string, + }), + }), }); export type post_UpdatePetWithForm = t.TypeOf; @@ -148,6 +180,9 @@ export const post_UpdatePetWithForm = t.type({ }), }), response: t.unknown, + responses: t.type({ + "405": t.unknown, + }), }); export type delete_DeletePet = t.TypeOf; @@ -164,6 +199,9 @@ export const delete_DeletePet = t.type({ }), }), response: t.unknown, + responses: t.type({ + "400": t.unknown, + }), }); export type post_UploadFile = t.TypeOf; @@ -181,6 +219,9 @@ export const post_UploadFile = t.type({ body: t.string, }), response: ApiResponse, + responses: t.type({ + "200": ApiResponse, + }), }); export type get_GetInventory = t.TypeOf; @@ -190,6 +231,9 @@ export const get_GetInventory = t.type({ requestFormat: t.literal("json"), parameters: t.never, response: t.record(t.string, t.number), + responses: t.type({ + "200": t.record(t.string, t.number), + }), }); export type post_PlaceOrder = t.TypeOf; @@ -201,6 +245,10 @@ export const post_PlaceOrder = t.type({ body: Order, }), response: Order, + responses: t.type({ + "200": Order, + "405": t.unknown, + }), }); export type get_GetOrderById = t.TypeOf; @@ -214,6 +262,11 @@ export const get_GetOrderById = t.type({ }), }), response: Order, + responses: t.type({ + "200": Order, + "400": t.unknown, + "404": t.unknown, + }), }); export type delete_DeleteOrder = t.TypeOf; @@ -227,6 +280,10 @@ export const delete_DeleteOrder = t.type({ }), }), response: t.unknown, + responses: t.type({ + "400": t.unknown, + "404": t.unknown, + }), }); export type post_CreateUser = t.TypeOf; @@ -238,6 +295,9 @@ export const post_CreateUser = t.type({ body: User, }), response: User, + responses: t.type({ + default: User, + }), }); export type post_CreateUsersWithListInput = t.TypeOf; @@ -249,6 +309,10 @@ export const post_CreateUsersWithListInput = t.type({ body: t.array(User), }), response: User, + responses: t.type({ + "200": User, + default: t.unknown, + }), }); export type get_LoginUser = t.TypeOf; @@ -263,6 +327,10 @@ export const get_LoginUser = t.type({ }), }), response: t.string, + responses: t.type({ + "200": t.string, + "400": t.unknown, + }), responseHeaders: t.type({ "x-rate-limit": t.number, "x-expires-after": t.string, @@ -276,6 +344,9 @@ export const get_LogoutUser = t.type({ requestFormat: t.literal("json"), parameters: t.never, response: t.unknown, + responses: t.type({ + default: t.unknown, + }), }); export type get_GetUserByName = t.TypeOf; @@ -289,6 +360,11 @@ export const get_GetUserByName = t.type({ }), }), response: User, + responses: t.type({ + "200": User, + "400": t.unknown, + "404": t.unknown, + }), }); export type put_UpdateUser = t.TypeOf; @@ -303,6 +379,9 @@ export const put_UpdateUser = t.type({ body: User, }), response: t.unknown, + responses: t.type({ + default: t.unknown, + }), }); export type delete_DeleteUser = t.TypeOf; @@ -316,6 +395,10 @@ export const delete_DeleteUser = t.type({ }), }), response: t.unknown, + responses: t.type({ + "400": t.unknown, + "404": t.unknown, + }), }); export type __ENDPOINTS_END__ = t.TypeOf; @@ -377,6 +460,7 @@ type RequestFormat = "json" | "form-data" | "form-url" | "binary" | "text"; export type DefaultEndpoint = { parameters?: EndpointParameters | undefined; response: unknown; + responses?: Record; responseHeaders?: Record; }; @@ -392,11 +476,97 @@ export type Endpoint = { areParametersRequired: boolean; }; response: TConfig["response"]; + responses?: TConfig["responses"]; responseHeaders?: TConfig["responseHeaders"]; }; export type Fetcher = (method: Method, url: string, parameters?: EndpointParameters | undefined) => Promise; +// Status code type for success responses +export type SuccessStatusCode = + | 200 + | 201 + | 202 + | 203 + | 204 + | 205 + | 206 + | 207 + | 208 + | 226 + | 300 + | 301 + | 302 + | 303 + | 304 + | 305 + | 306 + | 307 + | 308; + +// Error handling types +export type TypedApiResponse< + TSuccess, + TAllResponses extends Record = {}, +> = keyof TAllResponses extends never + ? Omit & { + ok: true; + status: number; + data: TSuccess; + json: () => Promise; + } + : { + [K in keyof TAllResponses]: K extends string + ? K extends `${infer TStatusCode extends number}` + ? TStatusCode extends SuccessStatusCode + ? Omit & { + ok: true; + status: TStatusCode; + data: TSuccess; + json: () => Promise; + } + : Omit & { + ok: false; + status: TStatusCode; + data: TAllResponses[K]; + json: () => Promise; + } + : never + : K extends number + ? K extends SuccessStatusCode + ? Omit & { + ok: true; + status: K; + data: TSuccess; + json: () => Promise; + } + : Omit & { + ok: false; + status: K; + data: TAllResponses[K]; + json: () => Promise; + } + : never; + }[keyof TAllResponses]; + +export type SafeApiResponse = TEndpoint extends { response: infer TSuccess; responses: infer TResponses } + ? TResponses extends Record + ? TypedApiResponse + : Omit & { + ok: true; + status: number; + data: TSuccess; + json: () => Promise; + } + : TEndpoint extends { response: infer TSuccess } + ? Omit & { + ok: true; + status: number; + data: TSuccess; + json: () => Promise; + } + : never; + type RequiredKeys = { [P in keyof T]-?: undefined extends T[P] ? never : P; }[keyof T]; @@ -427,44 +597,182 @@ export class ApiClient { // put( path: Path, - ...params: MaybeOptionalArg["parameters"]> - ): Promise["response"]> { - return this.fetcher("put", this.baseUrl + path, params[0]).then((response) => - this.parseResponse(response), - ) as Promise["response"]>; + ...params: MaybeOptionalArg["parameters"] & { withResponse?: false }> + ): Promise["response"]>; + + put( + path: Path, + ...params: MaybeOptionalArg["parameters"] & { withResponse: true }> + ): Promise>; + + put( + path: Path, + ...params: MaybeOptionalArg + ): Promise { + const requestParams = params[0]; + const withResponse = requestParams?.withResponse; + + // Remove withResponse from params before passing to fetcher + const { withResponse: _, ...fetchParams } = requestParams || {}; + + if (withResponse) { + return this.fetcher("put", this.baseUrl + path, Object.keys(fetchParams).length ? fetchParams : undefined).then( + async (response) => { + // Parse the response data + const data = await this.parseResponse(response); + + // Override properties while keeping the original Response object + const typedResponse = Object.assign(response, { + ok: response.ok, + status: response.status, + data: data, + json: () => Promise.resolve(data), + }); + return typedResponse; + }, + ); + } else { + return this.fetcher("put", this.baseUrl + path, requestParams).then((response) => + this.parseResponse(response), + ) as Promise["response"]>; + } } // // post( path: Path, - ...params: MaybeOptionalArg["parameters"]> - ): Promise["response"]> { - return this.fetcher("post", this.baseUrl + path, params[0]).then((response) => - this.parseResponse(response), - ) as Promise["response"]>; + ...params: MaybeOptionalArg["parameters"] & { withResponse?: false }> + ): Promise["response"]>; + + post( + path: Path, + ...params: MaybeOptionalArg["parameters"] & { withResponse: true }> + ): Promise>; + + post( + path: Path, + ...params: MaybeOptionalArg + ): Promise { + const requestParams = params[0]; + const withResponse = requestParams?.withResponse; + + // Remove withResponse from params before passing to fetcher + const { withResponse: _, ...fetchParams } = requestParams || {}; + + if (withResponse) { + return this.fetcher("post", this.baseUrl + path, Object.keys(fetchParams).length ? fetchParams : undefined).then( + async (response) => { + // Parse the response data + const data = await this.parseResponse(response); + + // Override properties while keeping the original Response object + const typedResponse = Object.assign(response, { + ok: response.ok, + status: response.status, + data: data, + json: () => Promise.resolve(data), + }); + return typedResponse; + }, + ); + } else { + return this.fetcher("post", this.baseUrl + path, requestParams).then((response) => + this.parseResponse(response), + ) as Promise["response"]>; + } } // // get( path: Path, - ...params: MaybeOptionalArg["parameters"]> - ): Promise["response"]> { - return this.fetcher("get", this.baseUrl + path, params[0]).then((response) => - this.parseResponse(response), - ) as Promise["response"]>; + ...params: MaybeOptionalArg["parameters"] & { withResponse?: false }> + ): Promise["response"]>; + + get( + path: Path, + ...params: MaybeOptionalArg["parameters"] & { withResponse: true }> + ): Promise>; + + get( + path: Path, + ...params: MaybeOptionalArg + ): Promise { + const requestParams = params[0]; + const withResponse = requestParams?.withResponse; + + // Remove withResponse from params before passing to fetcher + const { withResponse: _, ...fetchParams } = requestParams || {}; + + if (withResponse) { + return this.fetcher("get", this.baseUrl + path, Object.keys(fetchParams).length ? fetchParams : undefined).then( + async (response) => { + // Parse the response data + const data = await this.parseResponse(response); + + // Override properties while keeping the original Response object + const typedResponse = Object.assign(response, { + ok: response.ok, + status: response.status, + data: data, + json: () => Promise.resolve(data), + }); + return typedResponse; + }, + ); + } else { + return this.fetcher("get", this.baseUrl + path, requestParams).then((response) => + this.parseResponse(response), + ) as Promise["response"]>; + } } // // delete( path: Path, - ...params: MaybeOptionalArg["parameters"]> - ): Promise["response"]> { - return this.fetcher("delete", this.baseUrl + path, params[0]).then((response) => - this.parseResponse(response), - ) as Promise["response"]>; + ...params: MaybeOptionalArg["parameters"] & { withResponse?: false }> + ): Promise["response"]>; + + delete( + path: Path, + ...params: MaybeOptionalArg["parameters"] & { withResponse: true }> + ): Promise>; + + delete( + path: Path, + ...params: MaybeOptionalArg + ): Promise { + const requestParams = params[0]; + const withResponse = requestParams?.withResponse; + + // Remove withResponse from params before passing to fetcher + const { withResponse: _, ...fetchParams } = requestParams || {}; + + if (withResponse) { + return this.fetcher( + "delete", + this.baseUrl + path, + Object.keys(fetchParams).length ? fetchParams : undefined, + ).then(async (response) => { + // Parse the response data + const data = await this.parseResponse(response); + + // Override properties while keeping the original Response object + const typedResponse = Object.assign(response, { + ok: response.ok, + status: response.status, + data: data, + json: () => Promise.resolve(data), + }); + return typedResponse; + }); + } else { + return this.fetcher("delete", this.baseUrl + path, requestParams).then((response) => + this.parseResponse(response), + ) as Promise["response"]>; + } } // @@ -503,6 +811,21 @@ export function createApiClient(fetcher: Fetcher, baseUrl?: string) { api.get("/users").then((users) => console.log(users)); api.post("/users", { body: { name: "John" } }).then((user) => console.log(user)); api.put("/users/:id", { path: { id: 1 }, body: { name: "John" } }).then((user) => console.log(user)); + + // With error handling + const result = await api.get("/users/{id}", { path: { id: "123" }, withResponse: true }); + if (result.ok) { + // Access data directly + const user = result.data; + console.log(user); + + // Or use the json() method for compatibility + const userFromJson = await result.json(); + console.log(userFromJson); + } else { + const error = result.data; + console.error(`Error ${result.status}:`, error); + } */ // ; @@ -108,6 +114,10 @@ export const post_AddPet = Type.Object({ body: Pet, }), response: Pet, + responses: Type.Object({ + 200: Pet, + 405: Type.Unknown(), + }), }); export type get_FindPetsByStatus = Static; @@ -123,6 +133,13 @@ export const get_FindPetsByStatus = Type.Object({ ), }), response: Type.Array(Pet), + responses: Type.Object({ + 200: Type.Array(Pet), + 400: Type.Object({ + code: Type.Number(), + message: Type.String(), + }), + }), }); export type get_FindPetsByTags = Static; @@ -138,6 +155,10 @@ export const get_FindPetsByTags = Type.Object({ ), }), response: Type.Array(Pet), + responses: Type.Object({ + 200: Type.Array(Pet), + 400: Type.Unknown(), + }), }); export type get_GetPetById = Static; @@ -151,6 +172,17 @@ export const get_GetPetById = Type.Object({ }), }), response: Pet, + responses: Type.Object({ + 200: Pet, + 400: Type.Object({ + code: Type.Number(), + message: Type.String(), + }), + 404: Type.Object({ + code: Type.Number(), + message: Type.String(), + }), + }), }); export type post_UpdatePetWithForm = Static; @@ -170,6 +202,9 @@ export const post_UpdatePetWithForm = Type.Object({ }), }), response: Type.Unknown(), + responses: Type.Object({ + 405: Type.Unknown(), + }), }); export type delete_DeletePet = Static; @@ -188,6 +223,9 @@ export const delete_DeletePet = Type.Object({ ), }), response: Type.Unknown(), + responses: Type.Object({ + 400: Type.Unknown(), + }), }); export type post_UploadFile = Static; @@ -207,6 +245,9 @@ export const post_UploadFile = Type.Object({ body: Type.String(), }), response: ApiResponse, + responses: Type.Object({ + 200: ApiResponse, + }), }); export type get_GetInventory = Static; @@ -216,6 +257,9 @@ export const get_GetInventory = Type.Object({ requestFormat: Type.Literal("json"), parameters: Type.Never(), response: Type.Record(Type.String(), Type.Number()), + responses: Type.Object({ + 200: Type.Record(Type.String(), Type.Number()), + }), }); export type post_PlaceOrder = Static; @@ -227,6 +271,10 @@ export const post_PlaceOrder = Type.Object({ body: Order, }), response: Order, + responses: Type.Object({ + 200: Order, + 405: Type.Unknown(), + }), }); export type get_GetOrderById = Static; @@ -240,6 +288,11 @@ export const get_GetOrderById = Type.Object({ }), }), response: Order, + responses: Type.Object({ + 200: Order, + 400: Type.Unknown(), + 404: Type.Unknown(), + }), }); export type delete_DeleteOrder = Static; @@ -253,6 +306,10 @@ export const delete_DeleteOrder = Type.Object({ }), }), response: Type.Unknown(), + responses: Type.Object({ + 400: Type.Unknown(), + 404: Type.Unknown(), + }), }); export type post_CreateUser = Static; @@ -264,6 +321,9 @@ export const post_CreateUser = Type.Object({ body: User, }), response: User, + responses: Type.Object({ + default: User, + }), }); export type post_CreateUsersWithListInput = Static; @@ -275,6 +335,10 @@ export const post_CreateUsersWithListInput = Type.Object({ body: Type.Array(User), }), response: User, + responses: Type.Object({ + 200: User, + default: Type.Unknown(), + }), }); export type get_LoginUser = Static; @@ -291,6 +355,10 @@ export const get_LoginUser = Type.Object({ ), }), response: Type.String(), + responses: Type.Object({ + 200: Type.String(), + 400: Type.Unknown(), + }), responseHeaders: Type.Object({ "x-rate-limit": Type.Number(), "x-expires-after": Type.String(), @@ -304,6 +372,9 @@ export const get_LogoutUser = Type.Object({ requestFormat: Type.Literal("json"), parameters: Type.Never(), response: Type.Unknown(), + responses: Type.Object({ + default: Type.Unknown(), + }), }); export type get_GetUserByName = Static; @@ -317,6 +388,11 @@ export const get_GetUserByName = Type.Object({ }), }), response: User, + responses: Type.Object({ + 200: User, + 400: Type.Unknown(), + 404: Type.Unknown(), + }), }); export type put_UpdateUser = Static; @@ -331,6 +407,9 @@ export const put_UpdateUser = Type.Object({ body: User, }), response: Type.Unknown(), + responses: Type.Object({ + default: Type.Unknown(), + }), }); export type delete_DeleteUser = Static; @@ -344,6 +423,10 @@ export const delete_DeleteUser = Type.Object({ }), }), response: Type.Unknown(), + responses: Type.Object({ + 400: Type.Unknown(), + 404: Type.Unknown(), + }), }); type __ENDPOINTS_END__ = Static; @@ -405,6 +488,7 @@ type RequestFormat = "json" | "form-data" | "form-url" | "binary" | "text"; export type DefaultEndpoint = { parameters?: EndpointParameters | undefined; response: unknown; + responses?: Record; responseHeaders?: Record; }; @@ -420,11 +504,97 @@ export type Endpoint = { areParametersRequired: boolean; }; response: TConfig["response"]; + responses?: TConfig["responses"]; responseHeaders?: TConfig["responseHeaders"]; }; export type Fetcher = (method: Method, url: string, parameters?: EndpointParameters | undefined) => Promise; +// Status code type for success responses +export type SuccessStatusCode = + | 200 + | 201 + | 202 + | 203 + | 204 + | 205 + | 206 + | 207 + | 208 + | 226 + | 300 + | 301 + | 302 + | 303 + | 304 + | 305 + | 306 + | 307 + | 308; + +// Error handling types +export type TypedApiResponse< + TSuccess, + TAllResponses extends Record = {}, +> = keyof TAllResponses extends never + ? Omit & { + ok: true; + status: number; + data: TSuccess; + json: () => Promise; + } + : { + [K in keyof TAllResponses]: K extends string + ? K extends `${infer TStatusCode extends number}` + ? TStatusCode extends SuccessStatusCode + ? Omit & { + ok: true; + status: TStatusCode; + data: TSuccess; + json: () => Promise; + } + : Omit & { + ok: false; + status: TStatusCode; + data: TAllResponses[K]; + json: () => Promise; + } + : never + : K extends number + ? K extends SuccessStatusCode + ? Omit & { + ok: true; + status: K; + data: TSuccess; + json: () => Promise; + } + : Omit & { + ok: false; + status: K; + data: TAllResponses[K]; + json: () => Promise; + } + : never; + }[keyof TAllResponses]; + +export type SafeApiResponse = TEndpoint extends { response: infer TSuccess; responses: infer TResponses } + ? TResponses extends Record + ? TypedApiResponse + : Omit & { + ok: true; + status: number; + data: TSuccess; + json: () => Promise; + } + : TEndpoint extends { response: infer TSuccess } + ? Omit & { + ok: true; + status: number; + data: TSuccess; + json: () => Promise; + } + : never; + type RequiredKeys = { [P in keyof T]-?: undefined extends T[P] ? never : P; }[keyof T]; @@ -455,44 +625,182 @@ export class ApiClient { // put( path: Path, - ...params: MaybeOptionalArg["parameters"]> - ): Promise["response"]> { - return this.fetcher("put", this.baseUrl + path, params[0]).then((response) => - this.parseResponse(response), - ) as Promise["response"]>; + ...params: MaybeOptionalArg["parameters"] & { withResponse?: false }> + ): Promise["response"]>; + + put( + path: Path, + ...params: MaybeOptionalArg["parameters"] & { withResponse: true }> + ): Promise>; + + put( + path: Path, + ...params: MaybeOptionalArg + ): Promise { + const requestParams = params[0]; + const withResponse = requestParams?.withResponse; + + // Remove withResponse from params before passing to fetcher + const { withResponse: _, ...fetchParams } = requestParams || {}; + + if (withResponse) { + return this.fetcher("put", this.baseUrl + path, Object.keys(fetchParams).length ? fetchParams : undefined).then( + async (response) => { + // Parse the response data + const data = await this.parseResponse(response); + + // Override properties while keeping the original Response object + const typedResponse = Object.assign(response, { + ok: response.ok, + status: response.status, + data: data, + json: () => Promise.resolve(data), + }); + return typedResponse; + }, + ); + } else { + return this.fetcher("put", this.baseUrl + path, requestParams).then((response) => + this.parseResponse(response), + ) as Promise["response"]>; + } } // // post( path: Path, - ...params: MaybeOptionalArg["parameters"]> - ): Promise["response"]> { - return this.fetcher("post", this.baseUrl + path, params[0]).then((response) => - this.parseResponse(response), - ) as Promise["response"]>; + ...params: MaybeOptionalArg["parameters"] & { withResponse?: false }> + ): Promise["response"]>; + + post( + path: Path, + ...params: MaybeOptionalArg["parameters"] & { withResponse: true }> + ): Promise>; + + post( + path: Path, + ...params: MaybeOptionalArg + ): Promise { + const requestParams = params[0]; + const withResponse = requestParams?.withResponse; + + // Remove withResponse from params before passing to fetcher + const { withResponse: _, ...fetchParams } = requestParams || {}; + + if (withResponse) { + return this.fetcher("post", this.baseUrl + path, Object.keys(fetchParams).length ? fetchParams : undefined).then( + async (response) => { + // Parse the response data + const data = await this.parseResponse(response); + + // Override properties while keeping the original Response object + const typedResponse = Object.assign(response, { + ok: response.ok, + status: response.status, + data: data, + json: () => Promise.resolve(data), + }); + return typedResponse; + }, + ); + } else { + return this.fetcher("post", this.baseUrl + path, requestParams).then((response) => + this.parseResponse(response), + ) as Promise["response"]>; + } } // // get( path: Path, - ...params: MaybeOptionalArg["parameters"]> - ): Promise["response"]> { - return this.fetcher("get", this.baseUrl + path, params[0]).then((response) => - this.parseResponse(response), - ) as Promise["response"]>; + ...params: MaybeOptionalArg["parameters"] & { withResponse?: false }> + ): Promise["response"]>; + + get( + path: Path, + ...params: MaybeOptionalArg["parameters"] & { withResponse: true }> + ): Promise>; + + get( + path: Path, + ...params: MaybeOptionalArg + ): Promise { + const requestParams = params[0]; + const withResponse = requestParams?.withResponse; + + // Remove withResponse from params before passing to fetcher + const { withResponse: _, ...fetchParams } = requestParams || {}; + + if (withResponse) { + return this.fetcher("get", this.baseUrl + path, Object.keys(fetchParams).length ? fetchParams : undefined).then( + async (response) => { + // Parse the response data + const data = await this.parseResponse(response); + + // Override properties while keeping the original Response object + const typedResponse = Object.assign(response, { + ok: response.ok, + status: response.status, + data: data, + json: () => Promise.resolve(data), + }); + return typedResponse; + }, + ); + } else { + return this.fetcher("get", this.baseUrl + path, requestParams).then((response) => + this.parseResponse(response), + ) as Promise["response"]>; + } } // // delete( path: Path, - ...params: MaybeOptionalArg["parameters"]> - ): Promise["response"]> { - return this.fetcher("delete", this.baseUrl + path, params[0]).then((response) => - this.parseResponse(response), - ) as Promise["response"]>; + ...params: MaybeOptionalArg["parameters"] & { withResponse?: false }> + ): Promise["response"]>; + + delete( + path: Path, + ...params: MaybeOptionalArg["parameters"] & { withResponse: true }> + ): Promise>; + + delete( + path: Path, + ...params: MaybeOptionalArg + ): Promise { + const requestParams = params[0]; + const withResponse = requestParams?.withResponse; + + // Remove withResponse from params before passing to fetcher + const { withResponse: _, ...fetchParams } = requestParams || {}; + + if (withResponse) { + return this.fetcher( + "delete", + this.baseUrl + path, + Object.keys(fetchParams).length ? fetchParams : undefined, + ).then(async (response) => { + // Parse the response data + const data = await this.parseResponse(response); + + // Override properties while keeping the original Response object + const typedResponse = Object.assign(response, { + ok: response.ok, + status: response.status, + data: data, + json: () => Promise.resolve(data), + }); + return typedResponse; + }); + } else { + return this.fetcher("delete", this.baseUrl + path, requestParams).then((response) => + this.parseResponse(response), + ) as Promise["response"]>; + } } // @@ -531,6 +839,21 @@ export function createApiClient(fetcher: Fetcher, baseUrl?: string) { api.get("/users").then((users) => console.log(users)); api.post("/users", { body: { name: "John" } }).then((user) => console.log(user)); api.put("/users/:id", { path: { id: 1 }, body: { name: "John" } }).then((user) => console.log(user)); + + // With error handling + const result = await api.get("/users/{id}", { path: { id: "123" }, withResponse: true }); + if (result.ok) { + // Access data directly + const user = result.data; + console.log(user); + + // Or use the json() method for compatibility + const userFromJson = await result.json(); + console.log(userFromJson); + } else { + const error = result.data; + console.error(`Error ${result.status}:`, error); + } */ // ; @@ -91,6 +97,10 @@ export const post_AddPet = v.object({ body: Pet, }), response: Pet, + responses: v.object({ + "200": Pet, + "405": v.unknown(), + }), }); export type get_FindPetsByStatus = v.InferOutput; @@ -104,6 +114,13 @@ export const get_FindPetsByStatus = v.object({ }), }), response: v.array(Pet), + responses: v.object({ + "200": v.array(Pet), + "400": v.object({ + code: v.number(), + message: v.string(), + }), + }), }); export type get_FindPetsByTags = v.InferOutput; @@ -117,6 +134,10 @@ export const get_FindPetsByTags = v.object({ }), }), response: v.array(Pet), + responses: v.object({ + "200": v.array(Pet), + "400": v.unknown(), + }), }); export type get_GetPetById = v.InferOutput; @@ -130,6 +151,17 @@ export const get_GetPetById = v.object({ }), }), response: Pet, + responses: v.object({ + "200": Pet, + "400": v.object({ + code: v.number(), + message: v.string(), + }), + "404": v.object({ + code: v.number(), + message: v.string(), + }), + }), }); export type post_UpdatePetWithForm = v.InferOutput; @@ -147,6 +179,9 @@ export const post_UpdatePetWithForm = v.object({ }), }), response: v.unknown(), + responses: v.object({ + "405": v.unknown(), + }), }); export type delete_DeletePet = v.InferOutput; @@ -163,6 +198,9 @@ export const delete_DeletePet = v.object({ }), }), response: v.unknown(), + responses: v.object({ + "400": v.unknown(), + }), }); export type post_UploadFile = v.InferOutput; @@ -180,6 +218,9 @@ export const post_UploadFile = v.object({ body: v.string(), }), response: ApiResponse, + responses: v.object({ + "200": ApiResponse, + }), }); export type get_GetInventory = v.InferOutput; @@ -189,6 +230,9 @@ export const get_GetInventory = v.object({ requestFormat: v.literal("json"), parameters: v.never(), response: v.record(v.string(), v.number()), + responses: v.object({ + "200": v.record(v.string(), v.number()), + }), }); export type post_PlaceOrder = v.InferOutput; @@ -200,6 +244,10 @@ export const post_PlaceOrder = v.object({ body: Order, }), response: Order, + responses: v.object({ + "200": Order, + "405": v.unknown(), + }), }); export type get_GetOrderById = v.InferOutput; @@ -213,6 +261,11 @@ export const get_GetOrderById = v.object({ }), }), response: Order, + responses: v.object({ + "200": Order, + "400": v.unknown(), + "404": v.unknown(), + }), }); export type delete_DeleteOrder = v.InferOutput; @@ -226,6 +279,10 @@ export const delete_DeleteOrder = v.object({ }), }), response: v.unknown(), + responses: v.object({ + "400": v.unknown(), + "404": v.unknown(), + }), }); export type post_CreateUser = v.InferOutput; @@ -237,6 +294,9 @@ export const post_CreateUser = v.object({ body: User, }), response: User, + responses: v.object({ + default: User, + }), }); export type post_CreateUsersWithListInput = v.InferOutput; @@ -248,6 +308,10 @@ export const post_CreateUsersWithListInput = v.object({ body: v.array(User), }), response: User, + responses: v.object({ + "200": User, + default: v.unknown(), + }), }); export type get_LoginUser = v.InferOutput; @@ -262,6 +326,10 @@ export const get_LoginUser = v.object({ }), }), response: v.string(), + responses: v.object({ + "200": v.string(), + "400": v.unknown(), + }), responseHeaders: v.object({ "x-rate-limit": v.number(), "x-expires-after": v.string(), @@ -275,6 +343,9 @@ export const get_LogoutUser = v.object({ requestFormat: v.literal("json"), parameters: v.never(), response: v.unknown(), + responses: v.object({ + default: v.unknown(), + }), }); export type get_GetUserByName = v.InferOutput; @@ -288,6 +359,11 @@ export const get_GetUserByName = v.object({ }), }), response: User, + responses: v.object({ + "200": User, + "400": v.unknown(), + "404": v.unknown(), + }), }); export type put_UpdateUser = v.InferOutput; @@ -302,6 +378,9 @@ export const put_UpdateUser = v.object({ body: User, }), response: v.unknown(), + responses: v.object({ + default: v.unknown(), + }), }); export type delete_DeleteUser = v.InferOutput; @@ -315,6 +394,10 @@ export const delete_DeleteUser = v.object({ }), }), response: v.unknown(), + responses: v.object({ + "400": v.unknown(), + "404": v.unknown(), + }), }); export type __ENDPOINTS_END__ = v.InferOutput; @@ -376,6 +459,7 @@ type RequestFormat = "json" | "form-data" | "form-url" | "binary" | "text"; export type DefaultEndpoint = { parameters?: EndpointParameters | undefined; response: unknown; + responses?: Record; responseHeaders?: Record; }; @@ -391,11 +475,97 @@ export type Endpoint = { areParametersRequired: boolean; }; response: TConfig["response"]; + responses?: TConfig["responses"]; responseHeaders?: TConfig["responseHeaders"]; }; export type Fetcher = (method: Method, url: string, parameters?: EndpointParameters | undefined) => Promise; +// Status code type for success responses +export type SuccessStatusCode = + | 200 + | 201 + | 202 + | 203 + | 204 + | 205 + | 206 + | 207 + | 208 + | 226 + | 300 + | 301 + | 302 + | 303 + | 304 + | 305 + | 306 + | 307 + | 308; + +// Error handling types +export type TypedApiResponse< + TSuccess, + TAllResponses extends Record = {}, +> = keyof TAllResponses extends never + ? Omit & { + ok: true; + status: number; + data: TSuccess; + json: () => Promise; + } + : { + [K in keyof TAllResponses]: K extends string + ? K extends `${infer TStatusCode extends number}` + ? TStatusCode extends SuccessStatusCode + ? Omit & { + ok: true; + status: TStatusCode; + data: TSuccess; + json: () => Promise; + } + : Omit & { + ok: false; + status: TStatusCode; + data: TAllResponses[K]; + json: () => Promise; + } + : never + : K extends number + ? K extends SuccessStatusCode + ? Omit & { + ok: true; + status: K; + data: TSuccess; + json: () => Promise; + } + : Omit & { + ok: false; + status: K; + data: TAllResponses[K]; + json: () => Promise; + } + : never; + }[keyof TAllResponses]; + +export type SafeApiResponse = TEndpoint extends { response: infer TSuccess; responses: infer TResponses } + ? TResponses extends Record + ? TypedApiResponse + : Omit & { + ok: true; + status: number; + data: TSuccess; + json: () => Promise; + } + : TEndpoint extends { response: infer TSuccess } + ? Omit & { + ok: true; + status: number; + data: TSuccess; + json: () => Promise; + } + : never; + type RequiredKeys = { [P in keyof T]-?: undefined extends T[P] ? never : P; }[keyof T]; @@ -426,44 +596,182 @@ export class ApiClient { // put( path: Path, - ...params: MaybeOptionalArg["parameters"]> - ): Promise["response"]> { - return this.fetcher("put", this.baseUrl + path, params[0]).then((response) => - this.parseResponse(response), - ) as Promise["response"]>; + ...params: MaybeOptionalArg["parameters"] & { withResponse?: false }> + ): Promise["response"]>; + + put( + path: Path, + ...params: MaybeOptionalArg["parameters"] & { withResponse: true }> + ): Promise>; + + put( + path: Path, + ...params: MaybeOptionalArg + ): Promise { + const requestParams = params[0]; + const withResponse = requestParams?.withResponse; + + // Remove withResponse from params before passing to fetcher + const { withResponse: _, ...fetchParams } = requestParams || {}; + + if (withResponse) { + return this.fetcher("put", this.baseUrl + path, Object.keys(fetchParams).length ? fetchParams : undefined).then( + async (response) => { + // Parse the response data + const data = await this.parseResponse(response); + + // Override properties while keeping the original Response object + const typedResponse = Object.assign(response, { + ok: response.ok, + status: response.status, + data: data, + json: () => Promise.resolve(data), + }); + return typedResponse; + }, + ); + } else { + return this.fetcher("put", this.baseUrl + path, requestParams).then((response) => + this.parseResponse(response), + ) as Promise["response"]>; + } } // // post( path: Path, - ...params: MaybeOptionalArg["parameters"]> - ): Promise["response"]> { - return this.fetcher("post", this.baseUrl + path, params[0]).then((response) => - this.parseResponse(response), - ) as Promise["response"]>; + ...params: MaybeOptionalArg["parameters"] & { withResponse?: false }> + ): Promise["response"]>; + + post( + path: Path, + ...params: MaybeOptionalArg["parameters"] & { withResponse: true }> + ): Promise>; + + post( + path: Path, + ...params: MaybeOptionalArg + ): Promise { + const requestParams = params[0]; + const withResponse = requestParams?.withResponse; + + // Remove withResponse from params before passing to fetcher + const { withResponse: _, ...fetchParams } = requestParams || {}; + + if (withResponse) { + return this.fetcher("post", this.baseUrl + path, Object.keys(fetchParams).length ? fetchParams : undefined).then( + async (response) => { + // Parse the response data + const data = await this.parseResponse(response); + + // Override properties while keeping the original Response object + const typedResponse = Object.assign(response, { + ok: response.ok, + status: response.status, + data: data, + json: () => Promise.resolve(data), + }); + return typedResponse; + }, + ); + } else { + return this.fetcher("post", this.baseUrl + path, requestParams).then((response) => + this.parseResponse(response), + ) as Promise["response"]>; + } } // // get( path: Path, - ...params: MaybeOptionalArg["parameters"]> - ): Promise["response"]> { - return this.fetcher("get", this.baseUrl + path, params[0]).then((response) => - this.parseResponse(response), - ) as Promise["response"]>; + ...params: MaybeOptionalArg["parameters"] & { withResponse?: false }> + ): Promise["response"]>; + + get( + path: Path, + ...params: MaybeOptionalArg["parameters"] & { withResponse: true }> + ): Promise>; + + get( + path: Path, + ...params: MaybeOptionalArg + ): Promise { + const requestParams = params[0]; + const withResponse = requestParams?.withResponse; + + // Remove withResponse from params before passing to fetcher + const { withResponse: _, ...fetchParams } = requestParams || {}; + + if (withResponse) { + return this.fetcher("get", this.baseUrl + path, Object.keys(fetchParams).length ? fetchParams : undefined).then( + async (response) => { + // Parse the response data + const data = await this.parseResponse(response); + + // Override properties while keeping the original Response object + const typedResponse = Object.assign(response, { + ok: response.ok, + status: response.status, + data: data, + json: () => Promise.resolve(data), + }); + return typedResponse; + }, + ); + } else { + return this.fetcher("get", this.baseUrl + path, requestParams).then((response) => + this.parseResponse(response), + ) as Promise["response"]>; + } } // // delete( path: Path, - ...params: MaybeOptionalArg["parameters"]> - ): Promise["response"]> { - return this.fetcher("delete", this.baseUrl + path, params[0]).then((response) => - this.parseResponse(response), - ) as Promise["response"]>; + ...params: MaybeOptionalArg["parameters"] & { withResponse?: false }> + ): Promise["response"]>; + + delete( + path: Path, + ...params: MaybeOptionalArg["parameters"] & { withResponse: true }> + ): Promise>; + + delete( + path: Path, + ...params: MaybeOptionalArg + ): Promise { + const requestParams = params[0]; + const withResponse = requestParams?.withResponse; + + // Remove withResponse from params before passing to fetcher + const { withResponse: _, ...fetchParams } = requestParams || {}; + + if (withResponse) { + return this.fetcher( + "delete", + this.baseUrl + path, + Object.keys(fetchParams).length ? fetchParams : undefined, + ).then(async (response) => { + // Parse the response data + const data = await this.parseResponse(response); + + // Override properties while keeping the original Response object + const typedResponse = Object.assign(response, { + ok: response.ok, + status: response.status, + data: data, + json: () => Promise.resolve(data), + }); + return typedResponse; + }); + } else { + return this.fetcher("delete", this.baseUrl + path, requestParams).then((response) => + this.parseResponse(response), + ) as Promise["response"]>; + } } // @@ -502,6 +810,21 @@ export function createApiClient(fetcher: Fetcher, baseUrl?: string) { api.get("/users").then((users) => console.log(users)); api.post("/users", { body: { name: "John" } }).then((user) => console.log(user)); api.put("/users/:id", { path: { id: 1 }, body: { name: "John" } }).then((user) => console.log(user)); + + // With error handling + const result = await api.get("/users/{id}", { path: { id: "123" }, withResponse: true }); + if (result.ok) { + // Access data directly + const user = result.data; + console.log(user); + + // Or use the json() method for compatibility + const userFromJson = await result.json(); + console.log(userFromJson); + } else { + const error = result.data; + console.error(`Error ${result.status}:`, error); + } */ // true).required() as y.MixedSchema, + "404": y.mixed((value): value is any => true).required() as y.MixedSchema, + "405": y.mixed((value): value is any => true).required() as y.MixedSchema, + }), }; export type post_AddPet = typeof post_AddPet; @@ -105,6 +111,10 @@ export const post_AddPet = { body: Pet, }), response: Pet, + responses: y.object({ + "200": Pet, + "405": y.mixed((value): value is any => true).required() as y.MixedSchema, + }), }; export type get_FindPetsByStatus = typeof get_FindPetsByStatus; @@ -118,6 +128,13 @@ export const get_FindPetsByStatus = { }), }), response: y.array(Pet), + responses: y.object({ + "200": y.array(Pet), + "400": y.object({ + code: y.number().required(), + message: y.string().required(), + }), + }), }; export type get_FindPetsByTags = typeof get_FindPetsByTags; @@ -131,6 +148,10 @@ export const get_FindPetsByTags = { }), }), response: y.array(Pet), + responses: y.object({ + "200": y.array(Pet), + "400": y.mixed((value): value is any => true).required() as y.MixedSchema, + }), }; export type get_GetPetById = typeof get_GetPetById; @@ -144,6 +165,17 @@ export const get_GetPetById = { }), }), response: Pet, + responses: y.object({ + "200": Pet, + "400": y.object({ + code: y.number().required(), + message: y.string().required(), + }), + "404": y.object({ + code: y.number().required(), + message: y.string().required(), + }), + }), }; export type post_UpdatePetWithForm = typeof post_UpdatePetWithForm; @@ -161,6 +193,9 @@ export const post_UpdatePetWithForm = { }), }), response: y.mixed((value): value is any => true).required() as y.MixedSchema, + responses: y.object({ + "405": y.mixed((value): value is any => true).required() as y.MixedSchema, + }), }; export type delete_DeletePet = typeof delete_DeletePet; @@ -177,6 +212,9 @@ export const delete_DeletePet = { }), }), response: y.mixed((value): value is any => true).required() as y.MixedSchema, + responses: y.object({ + "400": y.mixed((value): value is any => true).required() as y.MixedSchema, + }), }; export type post_UploadFile = typeof post_UploadFile; @@ -194,6 +232,9 @@ export const post_UploadFile = { body: y.string().required(), }), response: ApiResponse, + responses: y.object({ + "200": ApiResponse, + }), }; export type get_GetInventory = typeof get_GetInventory; @@ -203,6 +244,9 @@ export const get_GetInventory = { requestFormat: y.mixed((value): value is "json" => value === "json").required(), parameters: y.mixed((value): value is never => false).required(), response: y.mixed(/* unsupported */), + responses: y.object({ + "200": y.mixed(/* unsupported */), + }), }; export type post_PlaceOrder = typeof post_PlaceOrder; @@ -214,6 +258,10 @@ export const post_PlaceOrder = { body: Order, }), response: Order, + responses: y.object({ + "200": Order, + "405": y.mixed((value): value is any => true).required() as y.MixedSchema, + }), }; export type get_GetOrderById = typeof get_GetOrderById; @@ -227,6 +275,11 @@ export const get_GetOrderById = { }), }), response: Order, + responses: y.object({ + "200": Order, + "400": y.mixed((value): value is any => true).required() as y.MixedSchema, + "404": y.mixed((value): value is any => true).required() as y.MixedSchema, + }), }; export type delete_DeleteOrder = typeof delete_DeleteOrder; @@ -240,6 +293,10 @@ export const delete_DeleteOrder = { }), }), response: y.mixed((value): value is any => true).required() as y.MixedSchema, + responses: y.object({ + "400": y.mixed((value): value is any => true).required() as y.MixedSchema, + "404": y.mixed((value): value is any => true).required() as y.MixedSchema, + }), }; export type post_CreateUser = typeof post_CreateUser; @@ -251,6 +308,9 @@ export const post_CreateUser = { body: User, }), response: User, + responses: y.object({ + default: User, + }), }; export type post_CreateUsersWithListInput = typeof post_CreateUsersWithListInput; @@ -262,6 +322,10 @@ export const post_CreateUsersWithListInput = { body: y.array(User), }), response: User, + responses: y.object({ + "200": User, + default: y.mixed((value): value is any => true).required() as y.MixedSchema, + }), }; export type get_LoginUser = typeof get_LoginUser; @@ -276,6 +340,10 @@ export const get_LoginUser = { }), }), response: y.string().required(), + responses: y.object({ + "200": y.string().required(), + "400": y.mixed((value): value is any => true).required() as y.MixedSchema, + }), responseHeaders: y.object({ "x-rate-limit": y.number().required(), "x-expires-after": y.string().required(), @@ -289,6 +357,9 @@ export const get_LogoutUser = { requestFormat: y.mixed((value): value is "json" => value === "json").required(), parameters: y.mixed((value): value is never => false).required(), response: y.mixed((value): value is any => true).required() as y.MixedSchema, + responses: y.object({ + default: y.mixed((value): value is any => true).required() as y.MixedSchema, + }), }; export type get_GetUserByName = typeof get_GetUserByName; @@ -302,6 +373,11 @@ export const get_GetUserByName = { }), }), response: User, + responses: y.object({ + "200": User, + "400": y.mixed((value): value is any => true).required() as y.MixedSchema, + "404": y.mixed((value): value is any => true).required() as y.MixedSchema, + }), }; export type put_UpdateUser = typeof put_UpdateUser; @@ -316,6 +392,9 @@ export const put_UpdateUser = { body: User, }), response: y.mixed((value): value is any => true).required() as y.MixedSchema, + responses: y.object({ + default: y.mixed((value): value is any => true).required() as y.MixedSchema, + }), }; export type delete_DeleteUser = typeof delete_DeleteUser; @@ -329,6 +408,10 @@ export const delete_DeleteUser = { }), }), response: y.mixed((value): value is any => true).required() as y.MixedSchema, + responses: y.object({ + "400": y.mixed((value): value is any => true).required() as y.MixedSchema, + "404": y.mixed((value): value is any => true).required() as y.MixedSchema, + }), }; // @@ -387,6 +470,7 @@ type RequestFormat = "json" | "form-data" | "form-url" | "binary" | "text"; export type DefaultEndpoint = { parameters?: EndpointParameters | undefined; response: unknown; + responses?: Record; responseHeaders?: Record; }; @@ -402,11 +486,97 @@ export type Endpoint = { areParametersRequired: boolean; }; response: TConfig["response"]; + responses?: TConfig["responses"]; responseHeaders?: TConfig["responseHeaders"]; }; export type Fetcher = (method: Method, url: string, parameters?: EndpointParameters | undefined) => Promise; +// Status code type for success responses +export type SuccessStatusCode = + | 200 + | 201 + | 202 + | 203 + | 204 + | 205 + | 206 + | 207 + | 208 + | 226 + | 300 + | 301 + | 302 + | 303 + | 304 + | 305 + | 306 + | 307 + | 308; + +// Error handling types +export type TypedApiResponse< + TSuccess, + TAllResponses extends Record = {}, +> = keyof TAllResponses extends never + ? Omit & { + ok: true; + status: number; + data: TSuccess; + json: () => Promise; + } + : { + [K in keyof TAllResponses]: K extends string + ? K extends `${infer TStatusCode extends number}` + ? TStatusCode extends SuccessStatusCode + ? Omit & { + ok: true; + status: TStatusCode; + data: TSuccess; + json: () => Promise; + } + : Omit & { + ok: false; + status: TStatusCode; + data: TAllResponses[K]; + json: () => Promise; + } + : never + : K extends number + ? K extends SuccessStatusCode + ? Omit & { + ok: true; + status: K; + data: TSuccess; + json: () => Promise; + } + : Omit & { + ok: false; + status: K; + data: TAllResponses[K]; + json: () => Promise; + } + : never; + }[keyof TAllResponses]; + +export type SafeApiResponse = TEndpoint extends { response: infer TSuccess; responses: infer TResponses } + ? TResponses extends Record + ? TypedApiResponse + : Omit & { + ok: true; + status: number; + data: TSuccess; + json: () => Promise; + } + : TEndpoint extends { response: infer TSuccess } + ? Omit & { + ok: true; + status: number; + data: TSuccess; + json: () => Promise; + } + : never; + type RequiredKeys = { [P in keyof T]-?: undefined extends T[P] ? never : P; }[keyof T]; @@ -437,44 +607,182 @@ export class ApiClient { // put( path: Path, - ...params: MaybeOptionalArg> - ): Promise> { - return this.fetcher("put", this.baseUrl + path, params[0]).then((response) => - this.parseResponse(response), - ) as Promise>; + ...params: MaybeOptionalArg & { withResponse?: false }> + ): Promise>; + + put( + path: Path, + ...params: MaybeOptionalArg & { withResponse: true }> + ): Promise>; + + put( + path: Path, + ...params: MaybeOptionalArg + ): Promise { + const requestParams = params[0]; + const withResponse = requestParams?.withResponse; + + // Remove withResponse from params before passing to fetcher + const { withResponse: _, ...fetchParams } = requestParams || {}; + + if (withResponse) { + return this.fetcher("put", this.baseUrl + path, Object.keys(fetchParams).length ? fetchParams : undefined).then( + async (response) => { + // Parse the response data + const data = await this.parseResponse(response); + + // Override properties while keeping the original Response object + const typedResponse = Object.assign(response, { + ok: response.ok, + status: response.status, + data: data, + json: () => Promise.resolve(data), + }); + return typedResponse; + }, + ); + } else { + return this.fetcher("put", this.baseUrl + path, requestParams).then((response) => + this.parseResponse(response), + ) as Promise>; + } } // // post( path: Path, - ...params: MaybeOptionalArg> - ): Promise> { - return this.fetcher("post", this.baseUrl + path, params[0]).then((response) => - this.parseResponse(response), - ) as Promise>; + ...params: MaybeOptionalArg & { withResponse?: false }> + ): Promise>; + + post( + path: Path, + ...params: MaybeOptionalArg & { withResponse: true }> + ): Promise>; + + post( + path: Path, + ...params: MaybeOptionalArg + ): Promise { + const requestParams = params[0]; + const withResponse = requestParams?.withResponse; + + // Remove withResponse from params before passing to fetcher + const { withResponse: _, ...fetchParams } = requestParams || {}; + + if (withResponse) { + return this.fetcher("post", this.baseUrl + path, Object.keys(fetchParams).length ? fetchParams : undefined).then( + async (response) => { + // Parse the response data + const data = await this.parseResponse(response); + + // Override properties while keeping the original Response object + const typedResponse = Object.assign(response, { + ok: response.ok, + status: response.status, + data: data, + json: () => Promise.resolve(data), + }); + return typedResponse; + }, + ); + } else { + return this.fetcher("post", this.baseUrl + path, requestParams).then((response) => + this.parseResponse(response), + ) as Promise>; + } } // // get( path: Path, - ...params: MaybeOptionalArg> - ): Promise> { - return this.fetcher("get", this.baseUrl + path, params[0]).then((response) => - this.parseResponse(response), - ) as Promise>; + ...params: MaybeOptionalArg & { withResponse?: false }> + ): Promise>; + + get( + path: Path, + ...params: MaybeOptionalArg & { withResponse: true }> + ): Promise>; + + get( + path: Path, + ...params: MaybeOptionalArg + ): Promise { + const requestParams = params[0]; + const withResponse = requestParams?.withResponse; + + // Remove withResponse from params before passing to fetcher + const { withResponse: _, ...fetchParams } = requestParams || {}; + + if (withResponse) { + return this.fetcher("get", this.baseUrl + path, Object.keys(fetchParams).length ? fetchParams : undefined).then( + async (response) => { + // Parse the response data + const data = await this.parseResponse(response); + + // Override properties while keeping the original Response object + const typedResponse = Object.assign(response, { + ok: response.ok, + status: response.status, + data: data, + json: () => Promise.resolve(data), + }); + return typedResponse; + }, + ); + } else { + return this.fetcher("get", this.baseUrl + path, requestParams).then((response) => + this.parseResponse(response), + ) as Promise>; + } } // // delete( path: Path, - ...params: MaybeOptionalArg> - ): Promise> { - return this.fetcher("delete", this.baseUrl + path, params[0]).then((response) => - this.parseResponse(response), - ) as Promise>; + ...params: MaybeOptionalArg & { withResponse?: false }> + ): Promise>; + + delete( + path: Path, + ...params: MaybeOptionalArg & { withResponse: true }> + ): Promise>; + + delete( + path: Path, + ...params: MaybeOptionalArg + ): Promise { + const requestParams = params[0]; + const withResponse = requestParams?.withResponse; + + // Remove withResponse from params before passing to fetcher + const { withResponse: _, ...fetchParams } = requestParams || {}; + + if (withResponse) { + return this.fetcher( + "delete", + this.baseUrl + path, + Object.keys(fetchParams).length ? fetchParams : undefined, + ).then(async (response) => { + // Parse the response data + const data = await this.parseResponse(response); + + // Override properties while keeping the original Response object + const typedResponse = Object.assign(response, { + ok: response.ok, + status: response.status, + data: data, + json: () => Promise.resolve(data), + }); + return typedResponse; + }); + } else { + return this.fetcher("delete", this.baseUrl + path, requestParams).then((response) => + this.parseResponse(response), + ) as Promise>; + } } // @@ -513,6 +821,21 @@ export function createApiClient(fetcher: Fetcher, baseUrl?: string) { api.get("/users").then((users) => console.log(users)); api.post("/users", { body: { name: "John" } }).then((user) => console.log(user)); api.put("/users/:id", { path: { id: 1 }, body: { name: "John" } }).then((user) => console.log(user)); + + // With error handling + const result = await api.get("/users/{id}", { path: { id: "123" }, withResponse: true }); + if (result.ok) { + // Access data directly + const user = result.data; + console.log(user); + + // Or use the json() method for compatibility + const userFromJson = await result.json(); + console.log(userFromJson); + } else { + const error = result.data; + console.error(`Error ${result.status}:`, error); + } */ // @@ -370,6 +453,7 @@ type RequestFormat = "json" | "form-data" | "form-url" | "binary" | "text"; export type DefaultEndpoint = { parameters?: EndpointParameters | undefined; response: unknown; + responses?: Record; responseHeaders?: Record; }; @@ -385,11 +469,97 @@ export type Endpoint = { areParametersRequired: boolean; }; response: TConfig["response"]; + responses?: TConfig["responses"]; responseHeaders?: TConfig["responseHeaders"]; }; export type Fetcher = (method: Method, url: string, parameters?: EndpointParameters | undefined) => Promise; +// Status code type for success responses +export type SuccessStatusCode = + | 200 + | 201 + | 202 + | 203 + | 204 + | 205 + | 206 + | 207 + | 208 + | 226 + | 300 + | 301 + | 302 + | 303 + | 304 + | 305 + | 306 + | 307 + | 308; + +// Error handling types +export type TypedApiResponse< + TSuccess, + TAllResponses extends Record = {}, +> = keyof TAllResponses extends never + ? Omit & { + ok: true; + status: number; + data: TSuccess; + json: () => Promise; + } + : { + [K in keyof TAllResponses]: K extends string + ? K extends `${infer TStatusCode extends number}` + ? TStatusCode extends SuccessStatusCode + ? Omit & { + ok: true; + status: TStatusCode; + data: TSuccess; + json: () => Promise; + } + : Omit & { + ok: false; + status: TStatusCode; + data: TAllResponses[K]; + json: () => Promise; + } + : never + : K extends number + ? K extends SuccessStatusCode + ? Omit & { + ok: true; + status: K; + data: TSuccess; + json: () => Promise; + } + : Omit & { + ok: false; + status: K; + data: TAllResponses[K]; + json: () => Promise; + } + : never; + }[keyof TAllResponses]; + +export type SafeApiResponse = TEndpoint extends { response: infer TSuccess; responses: infer TResponses } + ? TResponses extends Record + ? TypedApiResponse + : Omit & { + ok: true; + status: number; + data: TSuccess; + json: () => Promise; + } + : TEndpoint extends { response: infer TSuccess } + ? Omit & { + ok: true; + status: number; + data: TSuccess; + json: () => Promise; + } + : never; + type RequiredKeys = { [P in keyof T]-?: undefined extends T[P] ? never : P; }[keyof T]; @@ -420,44 +590,182 @@ export class ApiClient { // put( path: Path, - ...params: MaybeOptionalArg> - ): Promise> { - return this.fetcher("put", this.baseUrl + path, params[0]).then((response) => - this.parseResponse(response), - ) as Promise>; + ...params: MaybeOptionalArg & { withResponse?: false }> + ): Promise>; + + put( + path: Path, + ...params: MaybeOptionalArg & { withResponse: true }> + ): Promise>; + + put( + path: Path, + ...params: MaybeOptionalArg + ): Promise { + const requestParams = params[0]; + const withResponse = requestParams?.withResponse; + + // Remove withResponse from params before passing to fetcher + const { withResponse: _, ...fetchParams } = requestParams || {}; + + if (withResponse) { + return this.fetcher("put", this.baseUrl + path, Object.keys(fetchParams).length ? fetchParams : undefined).then( + async (response) => { + // Parse the response data + const data = await this.parseResponse(response); + + // Override properties while keeping the original Response object + const typedResponse = Object.assign(response, { + ok: response.ok, + status: response.status, + data: data, + json: () => Promise.resolve(data), + }); + return typedResponse; + }, + ); + } else { + return this.fetcher("put", this.baseUrl + path, requestParams).then((response) => + this.parseResponse(response), + ) as Promise>; + } } // // post( path: Path, - ...params: MaybeOptionalArg> - ): Promise> { - return this.fetcher("post", this.baseUrl + path, params[0]).then((response) => - this.parseResponse(response), - ) as Promise>; + ...params: MaybeOptionalArg & { withResponse?: false }> + ): Promise>; + + post( + path: Path, + ...params: MaybeOptionalArg & { withResponse: true }> + ): Promise>; + + post( + path: Path, + ...params: MaybeOptionalArg + ): Promise { + const requestParams = params[0]; + const withResponse = requestParams?.withResponse; + + // Remove withResponse from params before passing to fetcher + const { withResponse: _, ...fetchParams } = requestParams || {}; + + if (withResponse) { + return this.fetcher("post", this.baseUrl + path, Object.keys(fetchParams).length ? fetchParams : undefined).then( + async (response) => { + // Parse the response data + const data = await this.parseResponse(response); + + // Override properties while keeping the original Response object + const typedResponse = Object.assign(response, { + ok: response.ok, + status: response.status, + data: data, + json: () => Promise.resolve(data), + }); + return typedResponse; + }, + ); + } else { + return this.fetcher("post", this.baseUrl + path, requestParams).then((response) => + this.parseResponse(response), + ) as Promise>; + } } // // get( path: Path, - ...params: MaybeOptionalArg> - ): Promise> { - return this.fetcher("get", this.baseUrl + path, params[0]).then((response) => - this.parseResponse(response), - ) as Promise>; + ...params: MaybeOptionalArg & { withResponse?: false }> + ): Promise>; + + get( + path: Path, + ...params: MaybeOptionalArg & { withResponse: true }> + ): Promise>; + + get( + path: Path, + ...params: MaybeOptionalArg + ): Promise { + const requestParams = params[0]; + const withResponse = requestParams?.withResponse; + + // Remove withResponse from params before passing to fetcher + const { withResponse: _, ...fetchParams } = requestParams || {}; + + if (withResponse) { + return this.fetcher("get", this.baseUrl + path, Object.keys(fetchParams).length ? fetchParams : undefined).then( + async (response) => { + // Parse the response data + const data = await this.parseResponse(response); + + // Override properties while keeping the original Response object + const typedResponse = Object.assign(response, { + ok: response.ok, + status: response.status, + data: data, + json: () => Promise.resolve(data), + }); + return typedResponse; + }, + ); + } else { + return this.fetcher("get", this.baseUrl + path, requestParams).then((response) => + this.parseResponse(response), + ) as Promise>; + } } // // delete( path: Path, - ...params: MaybeOptionalArg> - ): Promise> { - return this.fetcher("delete", this.baseUrl + path, params[0]).then((response) => - this.parseResponse(response), - ) as Promise>; + ...params: MaybeOptionalArg & { withResponse?: false }> + ): Promise>; + + delete( + path: Path, + ...params: MaybeOptionalArg & { withResponse: true }> + ): Promise>; + + delete( + path: Path, + ...params: MaybeOptionalArg + ): Promise { + const requestParams = params[0]; + const withResponse = requestParams?.withResponse; + + // Remove withResponse from params before passing to fetcher + const { withResponse: _, ...fetchParams } = requestParams || {}; + + if (withResponse) { + return this.fetcher( + "delete", + this.baseUrl + path, + Object.keys(fetchParams).length ? fetchParams : undefined, + ).then(async (response) => { + // Parse the response data + const data = await this.parseResponse(response); + + // Override properties while keeping the original Response object + const typedResponse = Object.assign(response, { + ok: response.ok, + status: response.status, + data: data, + json: () => Promise.resolve(data), + }); + return typedResponse; + }); + } else { + return this.fetcher("delete", this.baseUrl + path, requestParams).then((response) => + this.parseResponse(response), + ) as Promise>; + } } // @@ -496,6 +804,21 @@ export function createApiClient(fetcher: Fetcher, baseUrl?: string) { api.get("/users").then((users) => console.log(users)); api.post("/users", { body: { name: "John" } }).then((user) => console.log(user)); api.put("/users/:id", { path: { id: 1 }, body: { name: "John" } }).then((user) => console.log(user)); + + // With error handling + const result = await api.get("/users/{id}", { path: { id: "123" }, withResponse: true }); + if (result.ok) { + // Access data directly + const user = result.data; + console.log(user); + + // Or use the json() method for compatibility + const userFromJson = await result.json(); + console.log(userFromJson); + } else { + const error = result.data; + console.error(`Error ${result.status}:`, error); + } */ // { test("petstore", async ({ expect }) => { const openApiDoc = (await SwaggerParser.parse("./tests/samples/petstore.yaml")) as OpenAPIObject; - expect(await generateTanstackQueryFile({ - ...mapOpenApiEndpoints(openApiDoc), - relativeApiClientPath: "./api.client.ts" - })).toMatchInlineSnapshot(` + expect( + await generateTanstackQueryFile({ + ...mapOpenApiEndpoints(openApiDoc), + relativeApiClientPath: "./api.client.ts", + }), + ).toMatchInlineSnapshot(` "import { queryOptions } from "@tanstack/react-query"; - import type { EndpointByMethod, ApiClient } from "./api.client.ts"; + import type { EndpointByMethod, ApiClient, SafeApiResponse } from "./api.client.ts"; type EndpointQueryKey = [ TOptions & { @@ -66,6 +68,48 @@ describe("generator", () => { type MaybeOptionalArg = RequiredKeys extends never ? [config?: T] : [config: T]; + type ErrorStatusCode = + | 400 + | 401 + | 402 + | 403 + | 404 + | 405 + | 406 + | 407 + | 408 + | 409 + | 410 + | 411 + | 412 + | 413 + | 414 + | 415 + | 416 + | 417 + | 418 + | 421 + | 422 + | 423 + | 424 + | 425 + | 426 + | 428 + | 429 + | 431 + | 451 + | 500 + | 501 + | 502 + | 503 + | 504 + | 505 + | 506 + | 507 + | 508 + | 510 + | 511; + // // @@ -77,18 +121,20 @@ describe("generator", () => { path: Path, ...params: MaybeOptionalArg ) { - const queryKey = createQueryKey(path, params[0]); + const queryKey = createQueryKey(path as string, params[0]); const query = { /** type-only property if you need easy access to the endpoint params */ "~endpoint": {} as TEndpoint, queryKey, queryOptions: queryOptions({ queryFn: async ({ queryKey, signal }) => { - const res = await this.client.put(path, { - ...params, - ...queryKey[0], + const requestParams = { + ...(params[0] || {}), + ...(queryKey[0] || {}), signal, - }); + withResponse: false as const, + }; + const res = await this.client.put(path, requestParams); return res as TEndpoint["response"]; }, queryKey: queryKey, @@ -96,11 +142,13 @@ describe("generator", () => { mutationOptions: { mutationKey: queryKey, mutationFn: async (localOptions: TEndpoint extends { parameters: infer Parameters } ? Parameters : never) => { - const res = await this.client.put(path, { - ...params, - ...queryKey[0], - ...localOptions, - }); + const requestParams = { + ...(params[0] || {}), + ...(queryKey[0] || {}), + ...(localOptions || {}), + withResponse: false as const, + }; + const res = await this.client.put(path, requestParams); return res as TEndpoint["response"]; }, }, @@ -115,18 +163,20 @@ describe("generator", () => { path: Path, ...params: MaybeOptionalArg ) { - const queryKey = createQueryKey(path, params[0]); + const queryKey = createQueryKey(path as string, params[0]); const query = { /** type-only property if you need easy access to the endpoint params */ "~endpoint": {} as TEndpoint, queryKey, queryOptions: queryOptions({ queryFn: async ({ queryKey, signal }) => { - const res = await this.client.post(path, { - ...params, - ...queryKey[0], + const requestParams = { + ...(params[0] || {}), + ...(queryKey[0] || {}), signal, - }); + withResponse: false as const, + }; + const res = await this.client.post(path, requestParams); return res as TEndpoint["response"]; }, queryKey: queryKey, @@ -134,11 +184,13 @@ describe("generator", () => { mutationOptions: { mutationKey: queryKey, mutationFn: async (localOptions: TEndpoint extends { parameters: infer Parameters } ? Parameters : never) => { - const res = await this.client.post(path, { - ...params, - ...queryKey[0], - ...localOptions, - }); + const requestParams = { + ...(params[0] || {}), + ...(queryKey[0] || {}), + ...(localOptions || {}), + withResponse: false as const, + }; + const res = await this.client.post(path, requestParams); return res as TEndpoint["response"]; }, }, @@ -153,18 +205,20 @@ describe("generator", () => { path: Path, ...params: MaybeOptionalArg ) { - const queryKey = createQueryKey(path, params[0]); + const queryKey = createQueryKey(path as string, params[0]); const query = { /** type-only property if you need easy access to the endpoint params */ "~endpoint": {} as TEndpoint, queryKey, queryOptions: queryOptions({ queryFn: async ({ queryKey, signal }) => { - const res = await this.client.get(path, { - ...params, - ...queryKey[0], + const requestParams = { + ...(params[0] || {}), + ...(queryKey[0] || {}), signal, - }); + withResponse: false as const, + }; + const res = await this.client.get(path, requestParams); return res as TEndpoint["response"]; }, queryKey: queryKey, @@ -172,11 +226,13 @@ describe("generator", () => { mutationOptions: { mutationKey: queryKey, mutationFn: async (localOptions: TEndpoint extends { parameters: infer Parameters } ? Parameters : never) => { - const res = await this.client.get(path, { - ...params, - ...queryKey[0], - ...localOptions, - }); + const requestParams = { + ...(params[0] || {}), + ...(queryKey[0] || {}), + ...(localOptions || {}), + withResponse: false as const, + }; + const res = await this.client.get(path, requestParams); return res as TEndpoint["response"]; }, }, @@ -191,18 +247,20 @@ describe("generator", () => { path: Path, ...params: MaybeOptionalArg ) { - const queryKey = createQueryKey(path, params[0]); + const queryKey = createQueryKey(path as string, params[0]); const query = { /** type-only property if you need easy access to the endpoint params */ "~endpoint": {} as TEndpoint, queryKey, queryOptions: queryOptions({ queryFn: async ({ queryKey, signal }) => { - const res = await this.client.delete(path, { - ...params, - ...queryKey[0], + const requestParams = { + ...(params[0] || {}), + ...(queryKey[0] || {}), signal, - }); + withResponse: false as const, + }; + const res = await this.client.delete(path, requestParams); return res as TEndpoint["response"]; }, queryKey: queryKey, @@ -210,11 +268,13 @@ describe("generator", () => { mutationOptions: { mutationKey: queryKey, mutationFn: async (localOptions: TEndpoint extends { parameters: infer Parameters } ? Parameters : never) => { - const res = await this.client.delete(path, { - ...params, - ...queryKey[0], - ...localOptions, - }); + const requestParams = { + ...(params[0] || {}), + ...(queryKey[0] || {}), + ...(localOptions || {}), + withResponse: false as const, + }; + const res = await this.client.delete(path, requestParams); return res as TEndpoint["response"]; }, }, @@ -232,16 +292,42 @@ describe("generator", () => { TMethod extends keyof EndpointByMethod, TPath extends keyof EndpointByMethod[TMethod], TEndpoint extends EndpointByMethod[TMethod][TPath], - TSelection, + TWithResponse extends boolean = false, + TSelection = TWithResponse extends true + ? SafeApiResponse + : TEndpoint extends { response: infer Res } + ? Res + : never, + TError = TEndpoint extends { responses: infer TResponses } + ? TResponses extends Record + ? { + [K in keyof TResponses]: K extends string + ? K extends \`\${infer TStatusCode extends number}\` + ? TStatusCode extends ErrorStatusCode + ? Omit & { status: TStatusCode; data: TResponses[K] } + : never + : never + : K extends number + ? K extends ErrorStatusCode + ? Omit & { status: K; data: TResponses[K] } + : never + : never; + }[keyof TResponses] + : Error + : Error, >( method: TMethod, path: TPath, - selectFn?: ( - res: Omit & { - /** [MDN Reference](https://developer.mozilla.org/docs/Web/API/Request/json) */ - json: () => Promise; - }, - ) => TSelection, + options?: { + withResponse?: TWithResponse; + selectFn?: ( + res: TWithResponse extends true + ? SafeApiResponse + : TEndpoint extends { response: infer Res } + ? Res + : never, + ) => TSelection; + }, ) { const mutationKey = [{ method, path }] as const; return { @@ -250,12 +336,49 @@ describe("generator", () => { mutationKey: mutationKey, mutationOptions: { mutationKey: mutationKey, - mutationFn: async (params: TEndpoint extends { parameters: infer Parameters } ? Parameters : never) => { - const response = await this.client.request(method, path, params); - const res = selectFn ? selectFn(response) : response; - return res as unknown extends TSelection ? typeof response : Awaited; + mutationFn: async ( + params: TEndpoint extends { parameters: infer Parameters } ? Parameters : never, + ): Promise => { + const withResponse = options?.withResponse ?? false; + const selectFn = options?.selectFn; + + if (withResponse) { + // Type assertion is safe because we're handling the method dynamically + const response = await (this.client as any)[method](path, { ...(params as any), withResponse: true }); + if (!response.ok) { + // Create a Response-like error object with additional data property + const error = Object.assign(Object.create(Response.prototype), { + ...response, + data: response.data, + }) as TError; + throw error; + } + const res = selectFn ? selectFn(response as any) : response; + return res as TSelection; + } + + // Type assertion is safe because we're handling the method dynamically + // Always get the full response for error handling, even when withResponse is false + const response = await (this.client as any)[method](path, { ...(params as any), withResponse: true }); + if (!response.ok) { + // Create a Response-like error object with additional data property + const error = Object.assign(Object.create(Response.prototype), { + ...response, + data: response.data, + }) as TError; + throw error; + } + + // Return just the data if withResponse is false, otherwise return the full response + const finalResponse = withResponse ? response : response.data; + const res = selectFn ? selectFn(finalResponse as any) : finalResponse; + return res as TSelection; }, - }, + } as import("@tanstack/react-query").UseMutationOptions< + TSelection, + TError, + TEndpoint extends { parameters: infer Parameters } ? Parameters : never + >, }; } // diff --git a/test-new-api.ts b/test-new-api.ts new file mode 100644 index 0000000..eb163fc --- /dev/null +++ b/test-new-api.ts @@ -0,0 +1,57 @@ +import { createApiClient } from './packages/typed-openapi/tests/snapshots/petstore.client'; + +// Test type inference with the updated API +const api = createApiClient((method, url, params) => + fetch(url, { method, body: JSON.stringify(params) }).then((res) => res.json()), +); + +async function testNewTypeInference() { + console.log("Testing new API with improved type inference..."); + + // Test 1: Simple usage without error handling + const pets = await api.get("/pet/findByStatus", { query: { status: "available" } }); + console.log("✓ Basic API call works"); + + // Test 2: Error handling with inline withResponse parameter + const result = await api.get("/pet/findByStatus", { + query: { status: "available" }, + withResponse: true + }); + + if (result.ok) { + console.log("✓ Success case: data is properly typed"); + // result.data should be Array + console.log("Data type:", Array.isArray(result.data) ? 'Array' : typeof result.data); + } else { + console.log("✓ Error case: status and error are properly typed"); + console.log("Status:", result.status, "(type:", typeof result.status, ")"); + + // Test status discrimination + if (result.status === 400) { + console.log("✓ Status 400 properly discriminated"); + // result.error should be { code: number; message: string } + console.log("Error type for 400:", typeof result.error); + if (typeof result.error === 'object' && result.error && 'code' in result.error) { + console.log("✓ Error has proper schema with code and message"); + } + } + } + + // Test 3: Another endpoint to verify generic behavior + const userResult = await api.get("/pet/{petId}", { + path: { petId: 123 }, + withResponse: true + }); + + if (!userResult.ok) { + console.log("Pet by ID error status:", userResult.status); + if (userResult.status === 404) { + console.log("✓ 404 status properly typed for pet endpoint"); + } + } + + console.log("🎉 All type inference tests completed!"); +} + +// Run the test +testNewTypeInference().catch(console.error);