{"id":47762388,"url":"https://github.com/aldotestino/okfetch","last_synced_at":"2026-04-03T05:46:52.995Z","repository":{"id":341187880,"uuid":"1132131857","full_name":"aldotestino/okfetch","owner":"aldotestino","description":"okfetch is a small family of TypeScript-first HTTP packages built around one idea: make fetch safer and more composable without hiding how the web platform works.","archived":false,"fork":false,"pushed_at":"2026-03-26T08:54:45.000Z","size":390,"stargazers_count":1,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-03-26T19:56:33.499Z","etag":null,"topics":["api","better-result","client","result-pattern","retries","standard-schema","timeouts","typesafety","typescript","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/aldotestino.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":"AGENTS.md","dco":null,"cla":null}},"created_at":"2026-01-11T11:46:31.000Z","updated_at":"2026-03-25T20:52:44.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/aldotestino/okfetch","commit_stats":null,"previous_names":["aldotestino/kanonic"],"tags_count":15,"template":false,"template_full_name":null,"purl":"pkg:github/aldotestino/okfetch","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/aldotestino%2Fokfetch","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/aldotestino%2Fokfetch/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/aldotestino%2Fokfetch/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/aldotestino%2Fokfetch/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/aldotestino","download_url":"https://codeload.github.com/aldotestino/okfetch/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/aldotestino%2Fokfetch/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":31337137,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-04-03T04:42:29.251Z","status":"ssl_error","status_checked_at":"2026-04-03T04:42:12.667Z","response_time":107,"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","better-result","client","result-pattern","retries","standard-schema","timeouts","typesafety","typescript","validation"],"created_at":"2026-04-03T05:46:52.459Z","updated_at":"2026-04-03T05:46:52.989Z","avatar_url":"https://github.com/aldotestino.png","language":"TypeScript","readme":"[![CI](https://github.com/aldotestino/okfetch/actions/workflows/ci.yml/badge.svg)](https://github.com/aldotestino/okfetch/actions/workflows/ci.yml)\n\n# okfetch\n\n`okfetch` is a small family of TypeScript-first HTTP packages built around one idea: make `fetch` safer and more composable without hiding how the web platform works.\n\n\u003e Heavily inspired by [better-fetch](https://github.com/better-auth/better-fetch) 🙌🏻\n\nThe repo is split into focused packages:\n\n- `@okfetch/fetch` for direct typed requests with validation, retries, plugins, timeouts, auth, and streaming\n- `@okfetch/api` for schema-defined endpoint trees that generate a typed API client\n- `@okfetch/logger` for a ready-made `pino` plugin you can drop into request flows\n\nAll request execution is based on [`better-result`](https://github.com/dmmulroy/better-result), so success and failure stay explicit as data instead of being pushed into exception-based control flow.\n\n## Packages\n\n| Package           | What it does                                                       | Best for                                                 |\n| ----------------- | ------------------------------------------------------------------ | -------------------------------------------------------- |\n| `@okfetch/fetch`  | Direct `fetch` wrapper with runtime validation and lifecycle hooks | Low-level requests and shared transport config           |\n| `@okfetch/api`    | Typed API client generated from endpoint definitions               | Larger applications with repeated API calls              |\n| `@okfetch/logger` | `pino`-based plugin for okfetch hooks                              | Request/response logging without writing your own plugin |\n\nPackage-level docs:\n\n- [packages/fetch/README.md](https://github.com/aldotestino/okfetch/blob/main/packages/fetch/README.md)\n- [packages/api/README.md](https://github.com/aldotestino/okfetch/blob/main/packages/api/README.md)\n- [packages/logger/README.md](https://github.com/aldotestino/okfetch/blob/main/packages/logger/README.md)\n\n## Why okfetch\n\n- Validate response payloads with any Standard Schema-compatible library before they reach business logic\n- Validate endpoint `body`, `params`, and `query` before a request is sent\n- Handle transport and API failures with typed `Result` values\n- Reuse cross-cutting concerns through plugins instead of ad hoc wrappers\n- Add retries, auth, timeouts, and streaming without giving up standard `fetch`\n\n## Installation\n\n### Direct fetch usage\n\n```bash\nbun add @okfetch/fetch better-result\n```\n\n```bash\nnpm install @okfetch/fetch better-result\n```\n\n### Typed API client usage\n\n```bash\nbun add @okfetch/api @okfetch/fetch better-result\n```\n\n```bash\nnpm install @okfetch/api @okfetch/fetch better-result\n```\n\n### Logging plugin\n\n```bash\nbun add @okfetch/logger @okfetch/fetch pino\n```\n\n```bash\nnpm install @okfetch/logger @okfetch/fetch pino\n```\n\n## Quick Start\n\n### 1. Direct requests with `@okfetch/fetch`\n\n```ts\nimport { okfetch } from \"@okfetch/fetch\";\nimport { z } from \"zod/v4\";\n\nconst todoSchema = z.object({\n  completed: z.boolean(),\n  id: z.number(),\n  title: z.string(),\n  userId: z.number(),\n});\n\nconst result = await okfetch(\"https://jsonplaceholder.typicode.com/todos/1\", {\n  outputSchema: todoSchema,\n});\n\nresult.match({\n  err: (error) =\u003e console.error(error._tag, error.message),\n  ok: (todo) =\u003e console.log(todo.title),\n});\n```\n\n### 2. Typed clients with `@okfetch/api`\n\n```ts\nimport { createApi, createEndpoints } from \"@okfetch/api\";\nimport { z } from \"zod/v4\";\n\nconst todoSchema = z.object({\n  completed: z.boolean(),\n  id: z.number(),\n  title: z.string(),\n  userId: z.number(),\n});\n\nconst endpoints = createEndpoints({\n  todos: {\n    get: {\n      method: \"GET\",\n      output: todoSchema,\n      params: z.object({ id: z.number() }),\n      path: \"/todos/:id\",\n    },\n    create: {\n      body: z.object({\n        title: z.string().min(1),\n        userId: z.number(),\n      }),\n      method: \"POST\",\n      output: todoSchema,\n      path: \"/todos\",\n    },\n  },\n});\n\nconst api = createApi({\n  baseURL: \"https://jsonplaceholder.typicode.com\",\n  endpoints,\n});\n\nconst result = await api.todos.get({ params: { id: 1 } });\n```\n\n### 3. Logging with `@okfetch/logger`\n\n```ts\nimport { okfetch } from \"@okfetch/fetch\";\nimport { logger } from \"@okfetch/logger\";\n\nconst result = await okfetch(\"https://example.com/health\", {\n  plugins: [logger()],\n});\n```\n\n## How The Packages Fit Together\n\n`@okfetch/fetch` is the transport core. It owns request execution, retries, streaming support, auth, plugin execution, timeout behavior, and parsing.\n\n`@okfetch/api` sits on top of `@okfetch/fetch`. It turns endpoint definitions into typed client methods and injects request validation based on the schemas attached to each endpoint.\n\n`@okfetch/logger` is optional sugar. It is just a plugin package built on the public `OkfetchPlugin` interface from `@okfetch/fetch`.\n\n## Core Concepts\n\n### `Result` instead of thrown request errors\n\nBoth direct requests and generated client calls resolve to a `Result`.\n\nThat means callers can use `.isOk()`, `.isErr()`, `.map()`, `.match()`, and other `better-result` helpers instead of relying on `try/catch` for expected HTTP and validation failures.\n\n### Validation\n\n`@okfetch/fetch` can validate:\n\n- successful response bodies with `outputSchema`\n- structured API error payloads with `apiErrorDataSchema`\n- stream chunks when `stream: true` is enabled\n\n`@okfetch/api` adds request-side validation for:\n\n- `body`\n- `params`\n- `query`\n\nSchemas are library-agnostic as long as they implement Standard Schema v1, so `zod`, `valibot`, `arktype`, and similar libraries can be passed directly.\n\nHelpers from `@okfetch/fetch`:\n\n```ts\nimport { validateAllErrors, validateClientErrors } from \"@okfetch/fetch\";\n```\n\n- `validateClientErrors` validates only `4xx` responses\n- `validateAllErrors` validates both `4xx` and `5xx` responses\n\n### Retries and timeouts\n\nRetries support:\n\n- `\"fixed\"`\n- `\"linear\"`\n- `\"exponential\"`\n\nYou can set them globally or per request:\n\n```ts\nawait okfetch(\"https://api.example.com/users/1\", {\n  retry: {\n    attempts: 3,\n    initialDelay: 200,\n    strategy: \"exponential\",\n  },\n  timeout: 5000,\n});\n```\n\n### Plugins\n\nPlugins can participate in the request lifecycle through:\n\n- `init`\n- `onRequest`\n- `onResponse`\n- `onSuccess`\n- `onFail`\n- `onRetry`\n\nThis makes it easy to add logging, tracing, metrics, request rewriting, custom auth, or any other cross-cutting concern once and reuse it everywhere.\n\n### Streaming\n\nSet `stream: true` to receive a `ReadableStream`.\n\n```ts\nimport { okfetch } from \"@okfetch/fetch\";\nimport { z } from \"zod/v4\";\n\nconst result = await okfetch(\"https://example.com/events\", {\n  stream: true,\n  outputSchema: z.object({\n    id: z.number(),\n    message: z.string(),\n  }),\n});\n```\n\nEach SSE `data:` chunk is parsed independently. If you pass an `outputSchema`, each chunk is validated before it is emitted by the stream.\n\n## Error Model\n\n`@okfetch/fetch` returns tagged errors:\n\n- `FetchError`\n- `TimeoutError`\n- `ApiError`\n- `ParseError`\n- `ValidationError`\n- `PluginError`\n\n`ValidationError.type` identifies the failing boundary:\n\n- `\"body\"`\n- `\"query\"`\n- `\"params\"`\n- `\"output\"`\n- `\"error\"`\n\n## Example App\n\nA small runnable example lives in [examples/app/index.ts](https://github.com/aldotestino/okfetch/blob/main/examples/app/index.ts).\n\nRun it with:\n\n```bash\nbun run --cwd examples/app dev\n```\n\nIt demonstrates:\n\n- one direct `okfetch(...)` call\n- one generated typed API client\n- one request-side validation failure\n\n## Development\n\nUseful commands from the repo root:\n\n```bash\nbun x ultracite fix\n```\n\n```bash\nbun x ultracite check\n```\n\n```bash\nbun test packages/fetch/src/index.test.ts\n```\n\n```bash\nbun test packages/api/src/index.test.ts\n```\n","funding_links":[],"categories":[],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Faldotestino%2Fokfetch","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Faldotestino%2Fokfetch","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Faldotestino%2Fokfetch/lists"}