{"id":18684947,"url":"https://github.com/tknf/typefetcher","last_synced_at":"2026-04-04T17:10:51.099Z","repository":{"id":57145836,"uuid":"465128479","full_name":"tknf/typefetcher","owner":"tknf","description":"TypeScript-first API client with Standard Schema support, providing excellent DX and strict type safety.","archived":false,"fork":false,"pushed_at":"2025-12-21T01:10:22.000Z","size":321,"stargazers_count":1,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2025-12-22T19:02:26.119Z","etag":null,"topics":["api","api-client","fetch","http","standard-schema","type-safe","validation"],"latest_commit_sha":null,"homepage":"","language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/tknf.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2022-03-02T02:27:07.000Z","updated_at":"2025-12-21T01:10:27.000Z","dependencies_parsed_at":"2022-09-06T12:50:44.459Z","dependency_job_id":null,"html_url":"https://github.com/tknf/typefetcher","commit_stats":null,"previous_names":[],"tags_count":5,"template":false,"template_full_name":null,"purl":"pkg:github/tknf/typefetcher","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/tknf%2Ftypefetcher","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/tknf%2Ftypefetcher/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/tknf%2Ftypefetcher/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/tknf%2Ftypefetcher/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/tknf","download_url":"https://codeload.github.com/tknf/typefetcher/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/tknf%2Ftypefetcher/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":31407644,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-04-04T10:20:44.708Z","status":"ssl_error","status_checked_at":"2026-04-04T10:20:06.846Z","response_time":60,"last_error":"SSL_read: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["api","api-client","fetch","http","standard-schema","type-safe","validation"],"created_at":"2024-11-07T10:19:49.672Z","updated_at":"2026-04-04T17:10:51.077Z","avatar_url":"https://github.com/tknf.png","language":"TypeScript","readme":"\u003cdiv align=\"center\"\u003e\n  \u003cimg src=\"https://raw.githubusercontent.com/tknf/typefetcher/main/docs/typefetcher.png\" alt=\"TypeFetcher Logo\" width=\"250\" height=\"auto\"\u003e\n  \u003ch1\u003e@tknf/typefetcher\u003c/h1\u003e\n  \u003cp\u003eTypeScript-first API client with \u003ca href=\"https://standardschema.dev\"\u003eStandard Schema\u003c/a\u003e support, providing excellent DX and strict type safety.\u003c/p\u003e\n\u003c/div\u003e\n\n[![Github Workflow Status](https://img.shields.io/github/actions/workflow/status/tknf/typefetcher/ci.yaml?branch=main)](https://github.com/tknf/typefetcher/actions)\n[![Github](https://img.shields.io/github/license/tknf/typefetcher)](https://github.com/tknf/typefetcher/blob/main/LICENSE)\n[![npm](https://img.shields.io/npm/v/@tknf/typefetcher)](https://www.npmjs.com/package/@tknf/typefetcher)\n[![npm bundle size](https://img.shields.io/bundlephobia/min/@tknf/typefetcher)](https://bundlephobia.com/package/@tknf/typefetcher)\n[![npm bundle size](https://img.shields.io/bundlephobia/minzip/@tknf/typefetcher)](https://bundlephobia.com/package/@tknf/typefetcher)\n[![Github commit activity](https://img.shields.io/github/commit-activity/m/tknf/typefetcher)](https://github.com/tknf/typefetcher/pulse)\n[![GitHub last commit](https://img.shields.io/github/last-commit/tknf/typefetcher)](https://github.com/tknf/typefetcher/commits/main)\n[![Ask DeepWiki](https://deepwiki.com/badge.svg)](https://deepwiki.com/tknf/typefetcher)\n\n## ✨ Features\n\n- **🎯 Type-Safe**: Full TypeScript support with strict type inference\n- **📊 Standard Schema**: Native support for Zod, Valibot, and other Standard Schema compliant libraries\n- **🔍 Request/Response Validation**: Runtime validation with detailed error messages\n- **💡 Suggestions Only Mode**: Skip validation for performance while keeping type safety\n- **🏗️ Builder Pattern**: Intuitive API inspired by Hono and Octokit\n- **📋 Structured Response**: Rich response metadata (headers, status, URL) with `~raw` access\n- **⚡ Lightweight**: Zero dependencies (except peer dependencies)\n- **🛡️ Error Handling**: Comprehensive error types for different failure scenarios\n- **🎪 Flexible**: Works with any Standard Schema compliant validation library\n- **🚫 AbortSignal Support**: Request cancellation and timeout support\n\n## 📦 Installation\n\n```bash\nnpm install @tknf/typefetcher\n```\n\n### Peer Dependencies\n\nTypeFetcher works with Standard Schema compliant validation libraries. Install one or more:\n\n```bash\n# Zod (requires v3.25.0+ for Standard Schema support)\nnpm install zod\n\n# Valibot (requires v1.0.0+ for Standard Schema support)  \nnpm install valibot\n```\n\n### Node.js Compatibility\n\n- **Node.js 18+**: Built-in fetch support, works out of the box\n- **Node.js \u003c 18**: Provide a custom fetch implementation:\n\n```bash\n# Option 1: node-fetch\nnpm install node-fetch\n\n# Option 2: undici (fast HTTP client)\nnpm install undici\n```\n\n## 🚀 Quick Start\n\n### Basic Usage (No Schema)\n\n```typescript\nimport { TypeFetcher } from \"@tknf/typefetcher\";\n\nconst client = new TypeFetcher({\n  baseURL: \"https://jsonplaceholder.typicode.com\",\n  headers: {\n    \"Authorization\": \"Bearer your-token\"\n  }\n});\n\n// Register endpoints\nconst api = client\n  .addEndpoint(\"GET\", \"/users\")\n  .addEndpoint(\"GET\", \"/users/{id}\")\n  .addEndpoint(\"POST\", \"/users\");\n\n// Make requests (Octokit-style)\nconst response = await api.request(\"GET /users\");\n\n// Structured response with metadata\nconsole.log(\"Data:\", response.data);           // Response body\nconsole.log(\"Status:\", response.status);       // HTTP status code\nconsole.log(\"Headers:\", response.headers);     // Response headers\nconsole.log(\"URL:\", response.url);             // Request URL\nconsole.log(\"Raw:\", response[\"~raw\"]);         // Raw Response object\n\n// Access specific user\nconst userResponse = await api.request(\"GET /users/{id}\", {\n  params: { id: \"1\" }\n});\n\nconst user = userResponse.data; // Just the data\nconst status = userResponse.status; // 200, 404, etc.\n```\n\n### Type-Safe Usage with Zod\n\n```typescript\nimport { TypeFetcher } from \"@tknf/typefetcher\";\nimport { z } from \"zod\";\n\n// Define your schemas\nconst UserSchema = z.object({\n  id: z.number(),\n  name: z.string(),\n  email: z.string().email(),\n});\n\nconst CreateUserSchema = z.object({\n  name: z.string(),\n  email: z.string().email(),\n});\n\nconst PathIdSchema = z.object({\n  id: z.string()\n});\n\n// Create type-safe client\nconst client = new TypeFetcher({\n  baseURL: \"https://api.example.com\"\n});\n\nconst api = client\n  .addEndpoint(\"GET\", \"/users\", {\n    response: z.array(UserSchema)\n  })\n  .addEndpoint(\"GET\", \"/users/{id}\", {\n    params: PathIdSchema,           // ✅ params is required when schema provided\n    response: UserSchema\n  })\n  .addEndpoint(\"POST\", \"/users\", {\n    body: CreateUserSchema,         // ✅ body is required when schema provided\n    response: UserSchema\n  });\n\n// Fully type-safe requests with structured responses\nconst usersResponse = await api.request(\"GET /users\"); \n// Type: StructuredResponse\u003cUser[]\u003e\n\nconst users = usersResponse.data; // User[]\nconst status = usersResponse.status; // number\nconst headers = usersResponse.headers; // Headers\n\nconst userResponse = await api.request(\"GET /users/{id}\", {\n  params: { id: \"123\" } // ✅ TypeScript ensures correct type\n});\n// Type: StructuredResponse\u003cUser\u003e\n\nconst user = userResponse.data; // User object\nif (userResponse.status === 200) {\n  console.log(\"User found:\", user.name);\n}\n\nconst created = await api.request(\"POST /users\", {\n  body: { name: \"Jane\", email: \"jane@example.com\" } // ✅ Validated at runtime\n});\n\n// Access creation details\nconsole.log(\"Created user:\", created.data);\nconsole.log(\"Location:\", created.headers.get(\"location\"));\nconsole.log(\"Status:\", created.status); // 201\n```\n\n## 📊 Response Structure\n\nEvery request returns a structured response with rich metadata:\n\n```typescript\ninterface StructuredResponse\u003cT\u003e {\n  readonly data: T;              // Parsed response data (your API data)\n  readonly headers: Headers;     // Response headers object  \n  readonly status: number;       // HTTP status code (200, 404, etc.)\n  readonly url: string;          // Final request URL\n  readonly \"~raw\": Response;     // Raw fetch Response object\n}\n```\n\n### Working with Response Data\n\n```typescript\nconst response = await api.request(\"GET /users/{id}\", {\n  params: { id: \"123\" }\n});\n\n// Access parsed data (type-safe when schema is provided)\nconst user = response.data;\n\n// Check HTTP status\nif (response.status === 200) {\n  console.log(\"Success!\");\n} else if (response.status === 404) {\n  console.log(\"User not found\");\n}\n\n// Access response headers\nconst contentType = response.headers.get(\"content-type\");\nconst rateLimit = response.headers.get(\"x-rate-limit-remaining\");\n\n// Get request URL (useful for debugging)\nconsole.log(\"Request was made to:\", response.url);\n\n// Access raw Response for advanced use cases\nconst rawResponse = response[\"~raw\"];\nconst responseText = await rawResponse.clone().text();\n```\n\n### Type-Safe Usage with Valibot\n\n```typescript\nimport { TypeFetcher } from \"@tknf/typefetcher\";\nimport * as v from \"valibot\";\n\n// Define Valibot schemas\nconst UserSchema = v.object({\n  id: v.number(),\n  name: v.string(),\n  email: v.pipe(v.string(), v.email()),\n});\n\nconst CreateUserSchema = v.object({\n  name: v.string(),\n  email: v.pipe(v.string(), v.email()),\n});\n\n// Use directly with TypeFetcher\nconst api = client\n  .addEndpoint(\"GET\", \"/users\", {\n    response: v.array(UserSchema)\n  })\n  .addEndpoint(\"POST\", \"/users\", {\n    body: CreateUserSchema,\n    response: UserSchema\n  });\n\n// Same type-safe API as with Zod\nconst usersResponse = await api.request(\"GET /users\");\nconst users = usersResponse.data; // User[]\n\nconst newUserResponse = await api.request(\"POST /users\", {\n  body: { name: \"John\", email: \"john@example.com\" }\n});\nconst newUser = newUserResponse.data; // User\n```\n\n## 📚 API Reference\n\n### TypeFetcher Constructor\n\n```typescript\nnew TypeFetcher(config?: TypeFetcherConfig)\n```\n\n**Parameters:**\n- `config` (optional): Configuration object\n\n**TypeFetcherConfig:**\n```typescript\ninterface TypeFetcherConfig {\n  readonly baseURL?: string;\n  readonly headers?: Record\u003cstring, string\u003e;\n  readonly timeout?: number;\n  readonly fetch?: typeof globalThis.fetch;  // Custom fetch implementation\n  readonly skipValidation?: boolean;         // Skip validation globally (schemas still provide type inference)\n}\n```\n\n### addEndpoint\n\n```typescript\naddEndpoint\u003cMethod, Path, Schema\u003e(\n  method: Method, \n  path: Path, \n  schema?: Schema\n): TypeFetcher\u003c...\u003e\n```\n\nRegisters a new endpoint with optional schema validation.\n\n**Parameters:**\n- `method`: HTTP method (`\"GET\" | \"POST\" | \"PUT\" | \"PATCH\" | \"DELETE\"`)\n- `path`: URL path with optional parameters (e.g., `\"/users/{id}\"`)\n- `schema` (optional): Validation schema object\n\n**Schema Object:**\n```typescript\ninterface EndpointSchema {\n  readonly params?: StandardSchemaV1;      // Path parameters\n  readonly query?: StandardSchemaV1;       // Query parameters  \n  readonly body?: StandardSchemaV1;        // Request body\n  readonly response?: StandardSchemaV1;    // Response validation\n  readonly skipValidation?: boolean;       // Skip validation for this endpoint (overrides global setting)\n}\n```\n\n### request\n\n```typescript\nrequest\u003cK\u003e(key: K, options?: RequestOptions): Promise\u003cStructuredResponse\u003cT\u003e\u003e\n```\n\nExecutes a request to a registered endpoint and returns a structured response.\n\n**Parameters:**\n- `key`: Endpoint key in format `\"METHOD /path\"`\n- `options`: Request options (automatically typed based on schema)\n\n**Request Options:**\n```typescript\ninterface RequestOptions {\n  readonly params?: Record\u003cstring, string\u003e | SchemaType;   // Path parameters\n  readonly query?: Record\u003cstring, string\u003e | SchemaType;    // Query parameters\n  readonly body?: unknown | SchemaType;                    // Request body\n  readonly headers?: Record\u003cstring, string\u003e;               // Custom headers\n  readonly signal?: AbortSignal;                           // Abort signal\n}\n\n// When schema is provided, corresponding fields become required and strongly typed\n```\n\n## 🔧 Advanced Usage\n\n### AbortSignal Support\n\n```typescript\n// Request cancellation\nconst controller = new AbortController();\n\n// Cancel after 5 seconds\nsetTimeout(() =\u003e controller.abort(), 5000);\n\ntry {\n  const response = await api.request(\"GET /users/{id}\", {\n    params: { id: \"123\" },\n    signal: controller.signal\n  });\n  \n  console.log(\"User:\", response.data);\n} catch (error) {\n  if (error.name === 'AbortError') {\n    console.log(\"Request was cancelled\");\n  }\n}\n```\n\n### Custom Headers per Request\n\n```typescript\nconst response = await api.request(\"GET /users/{id}\", {\n  params: { id: \"123\" },\n  headers: {\n    \"Accept-Language\": \"en-US\",\n    \"X-Custom-Header\": \"value\",\n    \"Authorization\": \"Bearer specific-token\" // Override global headers\n  }\n});\n```\n\n### Query Parameters\n\n```typescript\nconst QuerySchema = z.object({\n  page: z.string(),\n  limit: z.string(),\n  search: z.string().optional()\n});\n\nconst api = client.addEndpoint(\"GET\", \"/users\", {\n  query: QuerySchema,\n  response: z.array(UserSchema)\n});\n\nconst response = await api.request(\"GET /users\", {\n  query: {\n    page: \"1\",\n    limit: \"10\",\n    search: \"john\"\n  }\n});\n\nconsole.log(\"Users:\", response.data);\nconsole.log(\"Total pages:\", response.headers.get(\"x-total-pages\"));\n```\n\n### Working with Raw Response\n\nFor advanced use cases, access the raw `Response` object:\n\n```typescript\nconst response = await api.request(\"GET /download/{id}\", {\n  params: { id: \"file123\" }\n});\n\n// Access raw Response\nconst rawResponse = response[\"~raw\"];\n\n// Stream response body\nconst reader = rawResponse.body?.getReader();\nconst contentLength = rawResponse.headers.get(\"content-length\");\n\nconsole.log(`Downloading ${contentLength} bytes`);\n\n// Process stream...\nwhile (reader) {\n  const { done, value } = await reader.read();\n  if (done) break;\n  \n  // Process chunk\n  console.log(`Received ${value.length} bytes`);\n}\n```\n\n### Node.js Usage\n\nFor Node.js environments, you can provide a custom fetch implementation:\n\n```typescript\n// Node.js 18+ (built-in fetch)\nconst client = new TypeFetcher({\n  baseURL: \"https://api.example.com\"\n});\n\n// Node.js \u003c 18 with node-fetch\nimport fetch from \"node-fetch\";\nconst client = new TypeFetcher({\n  baseURL: \"https://api.example.com\",\n  fetch: fetch as unknown as typeof globalThis.fetch\n});\n\n// Using undici for better performance\nimport { fetch } from \"undici\";\nconst client = new TypeFetcher({\n  baseURL: \"https://api.example.com\",\n  fetch: fetch as unknown as typeof globalThis.fetch\n});\n```\n\n### Error Handling\n\n```typescript\nimport { TypeFetcherError, ValidationError } from \"@tknf/typefetcher\";\n\ntry {\n  const response = await api.request(\"GET /users/{id}\", {\n    params: { id: \"123\" }\n  });\n  \n  console.log(\"User:\", response.data);\n  console.log(\"Status:\", response.status);\n} catch (error) {\n  if (error instanceof TypeFetcherError) {\n    // HTTP errors (404, 500, etc.)\n    console.error(`HTTP ${error.status}: ${error.statusText}`);\n    console.error(\"Response data:\", error.data);\n  } else if (error instanceof ValidationError) {\n    // Schema validation errors\n    console.error(\"Validation failed:\", error.message);\n    error.issues.forEach(issue =\u003e {\n      console.error(`- ${issue.message} at ${issue.path?.join('.')}`);\n    });\n  } else {\n    // Other errors (network, abort, etc.)\n    console.error(\"Unexpected error:\", error);\n  }\n}\n```\n\n### Schema Transformations\n\nZod and Valibot schemas with transformations work seamlessly:\n\n```typescript\nconst TransformSchema = z.object({\n  id: z.string().transform(val =\u003e val.toUpperCase()),\n  date: z.string().transform(val =\u003e new Date(val))\n});\n\nconst api = client.addEndpoint(\"GET\", \"/items/{id}\", {\n  params: TransformSchema\n});\n\n// Input is transformed before making the request\nconst response = await api.request(\"GET /items/{id}\", {\n  params: { id: \"abc\", date: \"2023-01-01\" }\n  // Becomes: id=\"ABC\", date=Date object in the actual request\n});\n```\n\n### Suggestions Only Mode (Skip Validation)\n\nFor production environments where validation performance is critical or where API response changes shouldn't break the application, you can skip runtime validation while still maintaining TypeScript type safety:\n\n```typescript\n// Skip validation globally - schemas still provide type inference\nconst client = new TypeFetcher({\n  baseURL: \"https://api.example.com\",\n  skipValidation: true  // All endpoints skip validation by default\n});\n\nconst api = client.addEndpoint(\"GET\", \"/users/{id}\", {\n  response: z.object({\n    id: z.number(),\n    name: z.string(),\n  })\n});\n\n// No runtime validation, but response.data is still typed as { id: number; name: string }\nconst response = await api.request(\"GET /users/{id}\", {\n  params: { id: \"123\" }\n});\n```\n\nYou can also skip validation per-endpoint (overrides global setting):\n\n```typescript\nconst client = new TypeFetcher({\n  baseURL: \"https://api.example.com\",\n  skipValidation: false  // Validation enabled by default\n});\n\nconst api = client\n  .addEndpoint(\"GET\", \"/users\", {\n    response: z.array(UserSchema),\n    skipValidation: true  // Skip validation for this endpoint only\n  })\n  .addEndpoint(\"POST\", \"/users\", {\n    body: CreateUserSchema,\n    response: UserSchema\n    // This endpoint will validate because global default is false\n  });\n```\n\n**Benefits:**\n- **Performance**: Skip validation overhead in production\n- **Resilience**: Avoid errors when API responses change unexpectedly\n- **Type Safety**: TypeScript types are still inferred from schemas\n- **Flexibility**: Configure globally or per-endpoint\n\n**Use Cases:**\n- High-performance production APIs where validation is done server-side\n- Legacy APIs with evolving schemas\n- Development environments where you want types but not strict validation\n\n## 🌟 Why TypeFetcher?\n\n### Standard Schema Native\n\nUnlike other API clients that require adapters or wrappers, TypeFetcher natively supports any [Standard Schema](https://standardschema.dev/) compliant library:\n\n```typescript\n// ❌ Other libraries require adapters\nconst schema = someAdapter(z.string());\n\n// ✅ TypeFetcher uses schemas directly\nconst schema = z.string(); // Works with Zod 3.25.0+\nconst schema = v.string(); // Works with Valibot 1.0.0+\n```\n\n### Rich Response Information\n\nGet comprehensive response metadata without extra work:\n\n```typescript\n// ❌ Traditional fetch\nconst rawResponse = await fetch(\"/api/users\");\nconst data = await rawResponse.json();\n// Lost: headers, status, url information\n\n// ✅ TypeFetcher structured response\nconst response = await api.request(\"GET /users\");\n// Available: data, headers, status, url, ~raw\n```\n\n### Excellent TypeScript Integration\n\n- **Required Parameters**: Schema-specified parameters become required in TypeScript\n- **Type Inference**: Full type inference from schemas to response types\n- **Autocomplete**: Rich IDE support with endpoint and parameter suggestions\n- **Structured Response**: Access both data and metadata with full type safety\n\n### Minimal Bundle Size\n\n- Zero runtime dependencies (except peer dependencies)\n- Tree-shakable exports\n- Only import what you use\n\n## 🔍 Examples\n\n### REST API Client\n\n```typescript\nimport { TypeFetcher } from \"@tknf/typefetcher\";\nimport { z } from \"zod\";\n\nconst PostSchema = z.object({\n  id: z.number(),\n  title: z.string(),\n  body: z.string(),\n  userId: z.number()\n});\n\nclass BlogAPI {\n  private client = new TypeFetcher({\n    baseURL: \"https://jsonplaceholder.typicode.com\"\n  });\n\n  private api = this.client\n    .addEndpoint(\"GET\", \"/posts\", {\n      response: z.array(PostSchema)\n    })\n    .addEndpoint(\"GET\", \"/posts/{id}\", {\n      params: z.object({ id: z.string() }),\n      response: PostSchema\n    })\n    .addEndpoint(\"POST\", \"/posts\", {\n      body: z.object({\n        title: z.string(),\n        body: z.string(),\n        userId: z.number()\n      }),\n      response: PostSchema\n    });\n\n  async getAllPosts() {\n    const response = await this.api.request(\"GET /posts\");\n    return {\n      posts: response.data,\n      count: response.headers.get(\"x-total-count\")\n    };\n  }\n\n  async getPost(id: string) {\n    const response = await this.api.request(\"GET /posts/{id}\", { \n      params: { id } \n    });\n    \n    if (response.status === 404) {\n      throw new Error(\"Post not found\");\n    }\n    \n    return response.data;\n  }\n\n  async createPost(post: { title: string; body: string; userId: number }) {\n    const response = await this.api.request(\"POST /posts\", { body: post });\n    \n    return {\n      post: response.data,\n      location: response.headers.get(\"location\"),\n      status: response.status\n    };\n  }\n}\n```\n\n### File Upload with Progress\n\n```typescript\nconst api = client.addEndpoint(\"POST\", \"/upload\", {\n  body: z.instanceof(FormData),\n  response: z.object({\n    fileId: z.string(),\n    url: z.string()\n  })\n});\n\nasync function uploadFile(file: File, onProgress?: (progress: number) =\u003e void) {\n  const formData = new FormData();\n  formData.append(\"file\", file);\n\n  const response = await api.request(\"POST /upload\", {\n    body: formData,\n    headers: {\n      // Don't set Content-Type, let browser set it with boundary\n    }\n  });\n\n  console.log(\"Upload completed!\");\n  console.log(\"File ID:\", response.data.fileId);\n  console.log(\"File URL:\", response.data.url);\n  console.log(\"Server:\", response.headers.get(\"server\"));\n\n  return response.data;\n}\n```\n\n### Pagination Helper\n\n```typescript\nasync function getAllUsers() {\n  const users = [];\n  let page = 1;\n  let hasMore = true;\n\n  while (hasMore) {\n    const response = await api.request(\"GET /users\", {\n      query: { page: page.toString(), limit: \"50\" }\n    });\n\n    users.push(...response.data);\n\n    // Check if there are more pages\n    const totalPages = parseInt(response.headers.get(\"x-total-pages\") || \"1\");\n    hasMore = page \u003c totalPages;\n    page++;\n  }\n\n  return users;\n}\n```\n\n## 🛠️ Development\n\n```bash\n# Install dependencies\npnpm install\n\n# Run tests\npnpm test\n\n# Run tests with coverage\npnpm run test:coverage\n\n# Type checking\npnpm run typecheck\n\n# Linting\npnpm run lint\n\n# Build\npnpm run build\n```\n\n## 📋 Requirements\n\n- **Node.js**: 16.x or higher\n- **TypeScript**: 5.x or higher\n- **Zod**: 3.25.0+ (if using Zod)\n- **Valibot**: 1.0.0+ (if using Valibot)\n\n## 📄 License\n\nMIT License - see [LICENSE](LICENSE) for details.\n\n## 🤝 Contributing\n\n1. Fork the repository\n2. Create a feature branch\n3. Make your changes\n4. Ensure tests pass: `pnpm run test`\n5. Ensure linting passes: `pnpm run lint`\n6. Submit a pull request\n\n## 👏 Acknowledgments\n\n- [Standard Schema](https://standardschema.dev/) specification\n- [Zod](https://zod.dev/) and [Valibot](https://valibot.dev/) for schema validation\n- [Hono](https://hono.dev/) and [Octokit](https://octokit.github.io/rest.js/) for API design inspiration\n","funding_links":[],"categories":[],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ftknf%2Ftypefetcher","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Ftknf%2Ftypefetcher","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ftknf%2Ftypefetcher/lists"}