From cbd5b5e6dafb7daf1414f1845674f3f4f52dacfa Mon Sep 17 00:00:00 2001 From: Benno <57860196+bennobuilder@users.noreply.github.com> Date: Sat, 3 Aug 2024 08:09:39 +0200 Subject: [PATCH 1/6] #main expanded example section (wip) --- docs/examples.md | 81 ++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 81 insertions(+) diff --git a/docs/examples.md b/docs/examples.md index 51895b291..ba1e995c9 100644 --- a/docs/examples.md +++ b/docs/examples.md @@ -13,6 +13,7 @@ Fetching data can be done simply and safely using an **automatically-typed fetch - [openapi-fetch](/openapi-fetch/) (recommended) - [openapi-typescript-fetch](https://www.npmjs.com/package/openapi-typescript-fetch) by [@ajaishankar](https://github.com/ajaishankar) +- [feature-fetch](https://www.npmjs.com/package/feature-fetch) by [builder.group](https://github.com/builder-group) ::: tip @@ -60,6 +61,86 @@ TypeChecking in server environments can be tricky, as you’re often querying da ::: +## Hono with `@blgc/openapi-router` + +```ts [src/router.ts] +import { createHonoOpenApiRouter } from '@blgc/openapi-router'; +import { Hono } from 'hono'; +import { zValidator } from 'validation-adapters/zod'; +import * as z from 'zod'; + +import { paths } from './gen/v1'; +import { PetSchema } from './schemas'; + +export const router = new Hono(); +export const openApiRouter = createHonoOpenApiRouter(router); + +openApiRouter.get('/pet/{petId}', { + pathValidator: zValidator( + z.object({ + petId: z.number() + }) + ), + handler: (c) => { + const { petId } = c.req.valid('param'); + + return c.json({ + name: 'Falko', + photoUrls: [] + }); + } +}); + +openApiRouter.post('/pet', { + bodyValidator: zValidator(PetSchema), + handler: (c) => { + const { name, photoUrls } = c.req.valid('json'); + + return c.json({ name, photoUrls }); + } +}); +``` + +## Express with `@blgc/openapi-router` + +```ts [src/router.ts] +import { createExpressOpenApiRouter } from '@blgc/openapi-router'; +import { Router } from 'express'; +import * as v from 'valibot'; +import { vValidator } from 'validation-adapters/valibot'; + +import { type paths } from './gen/v1'; +import { PetSchema } from './schemas'; + +export const router: Router = Router(); +export const openApiRouter = createExpressOpenApiRouter(router); + +openApiRouter.get('/pet/{petId}', { + pathValidator: vValidator( + v.object({ + petId: v.number() + }) + ), + handler: (req, res) => { + const { petId } = req.params; + + res.send({ + name: 'Falko', + photoUrls: [] + }); + } +}); + +openApiRouter.post('/pet', { + bodyValidator: vValidator(PetSchema), + handler: (req, res) => { + const { name, photoUrls } = req.body; + + res.send({ name, photoUrls }); + } +}); +``` + ## Mock-Service-Worker (MSW) If you are using [Mock Service Worker (MSW)](https://mswjs.io) to define your API mocks, you can use a **small, automatically-typed wrapper** around MSW, which enables you to address conflicts in your API mocks easily when your OpenAPI specification changes. Ultimately, you can have the same level of confidence in your application's API client **and** API mocks. From e0290a8f0f86b6b244ddf4a596d8743307a0ff71 Mon Sep 17 00:00:00 2001 From: Benno <57860196+bennoinbeta@users.noreply.github.com> Date: Wed, 14 Aug 2024 15:05:07 +0200 Subject: [PATCH 2/6] #main updated example section and expanded benchmark --- docs/examples.md | 242 +++++++++++++++++---- docs/openapi-fetch/index.md | 1 + docs/package.json | 2 +- packages/openapi-fetch/package.json | 6 +- packages/openapi-fetch/test/index.bench.js | 22 +- 5 files changed, 221 insertions(+), 52 deletions(-) diff --git a/docs/examples.md b/docs/examples.md index ba1e995c9..d53129939 100644 --- a/docs/examples.md +++ b/docs/examples.md @@ -11,9 +11,135 @@ The types generated by openapi-typescript are universal, and can be used in a va Fetching data can be done simply and safely using an **automatically-typed fetch wrapper**: -- [openapi-fetch](/openapi-fetch/) (recommended) -- [openapi-typescript-fetch](https://www.npmjs.com/package/openapi-typescript-fetch) by [@ajaishankar](https://github.com/ajaishankar) -- [feature-fetch](https://www.npmjs.com/package/feature-fetch) by [builder.group](https://github.com/builder-group) +
+openapi-fetch (recommended) + +::: code-group + +```ts [test/my-project.ts] +import createClient from "openapi-fetch"; +import type { paths } from "./my-openapi-3-schema"; // generated by openapi-typescript + +const client = createClient({ baseUrl: "https://myapi.dev/v1/" }); + +const { + data, // only present if 2XX response + error, // only present if 4XX or 5XX response +} = await client.GET("/blogposts/{post_id}", { + params: { + path: { post_id: "123" }, + }, +}); + +await client.PUT("/blogposts", { + body: { + title: "My New Post", + }, +}); +``` + +::: + +
+ +
+openapi-typescript-fetch by @ajaishankar + +::: code-group + +```ts [test/my-project.ts] +import { Fetcher } from 'openapi-typescript-fetch'; +import type { paths } from './my-openapi-3-schema'; // generated by openapi-typescript + +const fetcher = Fetcher.for(); + +// GET request +const getBlogPost = fetcher.path('/blogposts/{post_id}').method('get').create(); + +try { + const { status, data } = await getBlogPost({ pathParams: { post_id: '123' } }); + console.log(data); +} catch (error) { + console.error('Error:', error); +} + +// PUT request +const updateBlogPost = fetcher.path('/blogposts').method('put').create(); + +try { + await updateBlogPost({ body: { title: 'My New Post' } }); +} catch (error) { + console.error('Error:', error); +} +``` + +::: + +
+ +
+feature-fetch by builder.group + +::: code-group + +```ts [test/my-project.ts] +import { createOpenApiFetchClient } from 'feature-fetch'; +import type { paths } from './my-openapi-3-schema'; // generated by openapi-typescript + +// Create the OpenAPI fetch client +const fetchClient = createOpenApiFetchClient({ + prefixUrl: 'https://myapi.dev/v1' +}); + +// Send a GET request +const response = await fetchClient.get('/blogposts/{post_id}', { + pathParams: { + post_id: '123', + }, +}); + +// Handle the response (Approach 1: Standard if-else) +if (response.isOk()) { + const data = response.value.data; + console.log(data); // Handle successful response +} else { + const error = response.error; + if (error instanceof NetworkError) { + console.error('Network error:', error.message); + } else if (error instanceof RequestError) { + console.error('Request error:', error.message, 'Status:', error.status); + } else { + console.error('Service error:', error.message); + } +} + +// Send a PUT request +const putResponse = await fetchClient.put('/blogposts', { + body: { + title: 'My New Post', + }, +}); + +// Handle the response (Approach 2: Try-catch) +try { + const putData = putResponse.unwrap().data; + console.log(putData); // Handle the successful response +} catch (error) { + // Handle the error + if (error instanceof NetworkError) { + console.error('Network error:', error.message); + } else if (error instanceof RequestError) { + console.error('Request error:', error.message, 'Status:', error.status); + } else { + console.error('Service error:', error.message); + } +} +``` + +::: + +
+ ::: tip @@ -61,7 +187,17 @@ TypeChecking in server environments can be tricky, as you’re often querying da ::: -## Hono with `@blgc/openapi-router` +## Hono with [`@blgc/openapi-router`](https://github.com/builder-group/community/tree/develop/packages/openapi-router) + +Instead of manually typing each route with generics as in the [Hono example](#hono), [`@blgc/openapi-router`](https://github.com/builder-group/community/tree/develop/packages/openapi-router) wraps around the [Hono router](https://hono.dev/docs/api/routing) to deliver full typesafety and enforce your OpenAPI-Schema with validators. + +::: tip Good To Know + +While TypeScript types ensure compile-time safety, they don't enforce runtime schema validation. For runtime compliance, you need to integrate with validation libraries like Zod or Valibot. Although you must define the validation rules manually, they are type-safe to ensure these rules are correctly defined. + +::: + +::: code-group ```ts [src/router.ts] import { createHonoOpenApiRouter } from '@blgc/openapi-router'; @@ -69,39 +205,50 @@ import { Hono } from 'hono'; import { zValidator } from 'validation-adapters/zod'; import * as z from 'zod'; -import { paths } from './gen/v1'; -import { PetSchema } from './schemas'; +import { paths } from './gen/v1'; // OpenAPI-generated types +import { PetSchema } from './schemas'; // Custom reusable Zod schema for validation export const router = new Hono(); export const openApiRouter = createHonoOpenApiRouter(router); +// GET /pet/{petId} openApiRouter.get('/pet/{petId}', { - pathValidator: zValidator( - z.object({ - petId: z.number() - }) - ), - handler: (c) => { - const { petId } = c.req.valid('param'); - - return c.json({ - name: 'Falko', - photoUrls: [] - }); - } + pathValidator: zValidator( + z.object({ + petId: z.number() // Validate that petId is a number + }) + ), + handler: (c) => { + const { petId } = c.req.valid('param'); // Access validated params + return c.json({ name: 'Falko', photoUrls: [] }); + } }); +// POST /pet openApiRouter.post('/pet', { - bodyValidator: zValidator(PetSchema), - handler: (c) => { - const { name, photoUrls } = c.req.valid('json'); - - return c.json({ name, photoUrls }); - } + bodyValidator: zValidator(PetSchema), // Validate request body using PetSchema + handler: (c) => { + const { name, photoUrls } = c.req.valid('json'); // Access validated body data + return c.json({ name, photoUrls }); + } }); ``` -## Express with `@blgc/openapi-router` +::: + +[Full example](https://github.com/builder-group/community/tree/develop/examples/openapi-router/hono/petstore) + +## Express with [`@blgc/openapi-router`](https://github.com/builder-group/community/tree/develop/packages/openapi-router) + +[`@blgc/openapi-router`](https://github.com/builder-group/community/tree/develop/packages/openapi-router) wraps around the [Express router](https://expressjs.com/en/5x/api.html#router) to deliver full typesafety and enforce your OpenAPI-Schema with validators. + +::: tip Good To Know + +While TypeScript types ensure compile-time safety, they don't enforce runtime schema validation. For runtime compliance, you need to integrate with validation libraries like Zod or Valibot. Although you must define the validation rules manually, they are type-safe to ensure these rules are correctly defined. + +::: + +::: code-group ```ts [src/router.ts] import { createExpressOpenApiRouter } from '@blgc/openapi-router'; @@ -109,38 +256,39 @@ import { Router } from 'express'; import * as v from 'valibot'; import { vValidator } from 'validation-adapters/valibot'; -import { type paths } from './gen/v1'; -import { PetSchema } from './schemas'; +import { paths } from './gen/v1'; // OpenAPI-generated types +import { PetSchema } from './schemas'; // Custom reusable Zod schema for validation export const router: Router = Router(); export const openApiRouter = createExpressOpenApiRouter(router); +// GET /pet/{petId} openApiRouter.get('/pet/{petId}', { - pathValidator: vValidator( - v.object({ - petId: v.number() - }) - ), - handler: (req, res) => { - const { petId } = req.params; - - res.send({ - name: 'Falko', - photoUrls: [] - }); - } + pathValidator: vValidator( + v.object({ + petId: v.number() // Validate that petId is a number + }) + ), + handler: (req, res) => { + const { petId } = req.params; // Access validated params + res.send({ name: 'Falko', photoUrls: [] }); + } }); +// POST /pet openApiRouter.post('/pet', { - bodyValidator: vValidator(PetSchema), - handler: (req, res) => { - const { name, photoUrls } = req.body; - - res.send({ name, photoUrls }); - } + bodyValidator: vValidator(PetSchema), // Validate request body using PetSchema + handler: (req, res) => { + const { name, photoUrls } = req.body; // Access validated body data + res.send({ name, photoUrls }); + } }); ``` +::: + +[Full example](https://github.com/builder-group/community/tree/develop/examples/openapi-router/express/petstore) + ## Mock-Service-Worker (MSW) If you are using [Mock Service Worker (MSW)](https://mswjs.io) to define your API mocks, you can use a **small, automatically-typed wrapper** around MSW, which enables you to address conflicts in your API mocks easily when your OpenAPI specification changes. Ultimately, you can have the same level of confidence in your application's API client **and** API mocks. diff --git a/docs/openapi-fetch/index.md b/docs/openapi-fetch/index.md index 1f57879fa..eedb3d3aa 100644 --- a/docs/openapi-fetch/index.md +++ b/docs/openapi-fetch/index.md @@ -10,6 +10,7 @@ openapi-fetch is a type-safe fetch client that pulls in your OpenAPI schema. Wei | :------------------------- | ---------: | :------------------------- | | openapi-fetch | `5 kB` | `300k` ops/s (fastest) | | openapi-typescript-fetch | `4 kB` | `150k` ops/s (2× slower) | +| feature-fetch | `15 kB` | `300k` ops/s (not slower) | | axios | `32 kB` | `225k` ops/s (1.3× slower) | | superagent | `55 kB` | `50k` ops/s (6× slower) | | openapi-typescript-codegen | `367 kB` | `100k` ops/s (3× slower) | diff --git a/docs/package.json b/docs/package.json index 9f3be4baa..194b8eed8 100644 --- a/docs/package.json +++ b/docs/package.json @@ -5,7 +5,7 @@ "type": "module", "scripts": { "build": "pnpm run update-contributors && vitepress build && cp _redirects .vitepress/dist", - "dev": "pnpm run update-contributors && vitepress dev", + "dev": "vitepress dev", "tokens": "co build", "update-contributors": "node scripts/update-contributors.js" }, diff --git a/packages/openapi-fetch/package.json b/packages/openapi-fetch/package.json index d2cfcc368..b8589d808 100644 --- a/packages/openapi-fetch/package.json +++ b/packages/openapi-fetch/package.json @@ -61,6 +61,7 @@ "test:ts": "tsc --noEmit", "test:ts-no-strict": "tsc --noEmit -p test/no-strict-null-checks/tsconfig.json", "test-e2e": "playwright test", + "bench:js": "vitest bench", "e2e-vite-build": "vite build test/fixtures/e2e", "e2e-vite-start": "vite preview test/fixtures/e2e", "version": "pnpm run prepare && pnpm run build" @@ -69,15 +70,16 @@ "openapi-typescript-helpers": "workspace:^" }, "devDependencies": { - "axios": "^1.7.2", + "axios": "^1.7.4", "del-cli": "^5.1.0", "esbuild": "^0.20.2", "execa": "^8.0.1", + "feature-fetch": "^0.0.15", "msw": "^2.3.1", "openapi-typescript": "workspace:^", "openapi-typescript-codegen": "^0.25.0", "openapi-typescript-fetch": "^2.0.0", - "superagent": "^9.0.2", + "superagent": "^10.0.1", "typescript": "^5.4.5", "vite": "^5.3.5" } diff --git a/packages/openapi-fetch/test/index.bench.js b/packages/openapi-fetch/test/index.bench.js index fdf780870..a1f6d97ce 100644 --- a/packages/openapi-fetch/test/index.bench.js +++ b/packages/openapi-fetch/test/index.bench.js @@ -6,6 +6,7 @@ import superagent from "superagent"; import { afterAll, bench, describe } from "vitest"; import createClient, { createPathBasedClient } from "../dist/index.js"; import * as openapiTSCodegen from "./fixtures/openapi-typescript-codegen.min.js"; +import { createApiFetchClient } from 'feature-fetch'; const BASE_URL = "https://api.test.local"; @@ -58,6 +59,10 @@ describe("setup", () => { }); }); + bench('feature-fetch', async () => { + createApiFetchClient({ prefixUrl: BASE_URL }); + }) + // superagent: N/A }); @@ -69,10 +74,10 @@ describe("get (only URL)", () => { baseUrl: BASE_URL, }); const openapiTSFetchGET = openapiTSFetch.path("/url").method("get").create(); - const axiosInstance = axios.create({ baseURL: BASE_URL, }); + const featureFetch = createApiFetchClient({ prefixUrl: BASE_URL }); bench("openapi-fetch", async () => { await openapiFetch.GET("/url"); @@ -97,6 +102,10 @@ describe("get (only URL)", () => { bench("superagent", async () => { await superagent.get(`${BASE_URL}/url`); }); + + bench("feature-fetch", async () => { + await featureFetch.get("/url"); + }); }); describe("get (headers)", () => { @@ -114,10 +123,13 @@ describe("get (headers)", () => { init: { headers: { "x-base-header": 123 } }, }); const openapiTSFetchGET = openapiTSFetch.path("/url").method("get").create(); - const axiosInstance = axios.create({ baseURL: BASE_URL, }); + const featureFetch = createApiFetchClient({ + prefixUrl: BASE_URL, + headers: { "x-base-header": '123' } + }); bench("openapi-fetch", async () => { await openapiFetch.GET("/url", { @@ -150,4 +162,10 @@ describe("get (headers)", () => { bench("superagent", async () => { await superagent.get(`${BASE_URL}/url`).set("x-header-1", 123).set("x-header-2", 456); }); + + bench("feature-fetch", async () => { + await featureFetch.get("/url", { + headers: { "x-header-1": "123", "x-header-2": "456" }, + }); + }); }); From 41bd8af800e9bd57eb71769a4429fadbd73d9400 Mon Sep 17 00:00:00 2001 From: Benno <57860196+bennoinbeta@users.noreply.github.com> Date: Wed, 14 Aug 2024 15:09:00 +0200 Subject: [PATCH 3/6] #main undo dev command change --- docs/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/package.json b/docs/package.json index 194b8eed8..9f3be4baa 100644 --- a/docs/package.json +++ b/docs/package.json @@ -5,7 +5,7 @@ "type": "module", "scripts": { "build": "pnpm run update-contributors && vitepress build && cp _redirects .vitepress/dist", - "dev": "vitepress dev", + "dev": "pnpm run update-contributors && vitepress dev", "tokens": "co build", "update-contributors": "node scripts/update-contributors.js" }, From f4c28bc22db07eb99d4cdad59fe4353f3e8ae3d2 Mon Sep 17 00:00:00 2001 From: Benno <57860196+bennoinbeta@users.noreply.github.com> Date: Wed, 14 Aug 2024 15:11:49 +0200 Subject: [PATCH 4/6] #main fixed pnpm-lock? --- pnpm-lock.yaml | 59 ++++++++++++++++++++++++++++++++++++-------------- 1 file changed, 43 insertions(+), 16 deletions(-) diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 06df6b9f0..39a446be1 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -40,7 +40,7 @@ importers: devDependencies: vitepress: specifier: 1.3.2 - version: 1.3.2(@algolia/client-search@4.24.0)(@types/node@20.14.7)(@types/react@18.3.3)(axios@1.7.2)(postcss@8.4.41)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(search-insights@2.13.0)(typescript@5.5.4) + version: 1.3.2(@algolia/client-search@4.24.0)(@types/node@20.14.7)(@types/react@18.3.3)(axios@1.7.4)(postcss@8.4.41)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(search-insights@2.13.0)(typescript@5.5.4) packages/openapi-fetch: dependencies: @@ -49,8 +49,8 @@ importers: version: link:../openapi-typescript-helpers devDependencies: axios: - specifier: ^1.7.2 - version: 1.7.2 + specifier: ^1.7.4 + version: 1.7.4 del-cli: specifier: ^5.1.0 version: 5.1.0 @@ -60,6 +60,9 @@ importers: execa: specifier: ^8.0.1 version: 8.0.1 + feature-fetch: + specifier: ^0.0.15 + version: 0.0.15 msw: specifier: ^2.3.1 version: 2.3.1(typescript@5.4.5) @@ -73,8 +76,8 @@ importers: specifier: ^2.0.0 version: 2.0.0 superagent: - specifier: ^9.0.2 - version: 9.0.2 + specifier: ^10.0.1 + version: 10.0.1 typescript: specifier: ^5.4.5 version: 5.4.5 @@ -567,6 +570,12 @@ packages: cpu: [x64] os: [win32] + '@blgc/types@0.0.6': + resolution: {integrity: sha512-RyHXG7rLGNZCVyq9B9/WCd0MBTuXlUttE1qUukvvbTUZMa7NXjzoAFDarGf3RSraf5c9/kQgo3nPYIXqTiCbjw==} + + '@blgc/utils@0.0.15': + resolution: {integrity: sha512-iVUnqJxIuukRVEcV1g0JjOaRqibUnwW0d+8MnsBLEGak6tUBNWvPJYnxexutts/epczDr9GgnugLFTXEWb7qJQ==} + '@bundled-es-modules/cookie@2.0.0': resolution: {integrity: sha512-Or6YHg/kamKHpxULAdSqhGqnWFneIXu1NKvvfBBzKGwpVsYuFIQ5aBPHDnnoR3ghW1nvSkALd+EF9iMtY7Vjxw==} @@ -1764,8 +1773,8 @@ packages: resolution: {integrity: sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ==} engines: {node: '>= 0.4'} - axios@1.7.2: - resolution: {integrity: sha512-2A8QhOMrbomlDuiLeK9XibIBzuHeRcqqNOHp0Cyp5EoJ1IFDh+XZH3A6BkXtv0K4gFGCI0Y4BM7B1wOEi0Rmgw==} + axios@1.7.4: + resolution: {integrity: sha512-DukmaFRnY6AzAALSH4J2M3k6PkaC+MfaAGdEERRWcC9q3/TWQwLpHR8ZRLKTdQ3aBDL64EdluRDjJqKw+BPZEw==} axobject-query@4.1.0: resolution: {integrity: sha512-qIj0G9wZbMGNLjLmg1PT6v2mE9AH2zlnADJD/2tC6E00hgmhUOfEB6greHPAfLRSufHqROIUTkw6E+M3lH0PTQ==} @@ -2235,6 +2244,9 @@ packages: fastq@1.17.1: resolution: {integrity: sha512-sRVD3lWVIXWg6By68ZN7vho9a1pQcN/WBFaAAsDDFzlJjvoGx0P8z7V1t72grFJfJhu3YPZBuu25f7Kaw2jN1w==} + feature-fetch@0.0.15: + resolution: {integrity: sha512-fScwADZ5pZ5aXGTd4U+LUX1BQMNISIJHHEXZnAn0mDPDyhrGztXyU/A5EhnPNGXS9ajVxli99/YO3Vkvy/5Stg==} + fill-range@7.0.1: resolution: {integrity: sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==} engines: {node: '>=8'} @@ -3460,8 +3472,8 @@ packages: babel-plugin-macros: optional: true - superagent@9.0.2: - resolution: {integrity: sha512-xuW7dzkUpcJq7QnhOsnNUgtYp3xRwpt2F7abdRYIpCsAt0hhUqia0EdxyXZQQpNmGtsCzYHryaKSV3q3GJnq7w==} + superagent@10.0.1: + resolution: {integrity: sha512-kG7dZ4Z6s6VbCVxd0PJpkYND0X+SW+iIAuboIQyHE7eFSNVprFVTpG1uID3UsVS7Jw47tdPvSiCSGzgXDhFcGQ==} engines: {node: '>=14.18.0'} superjson@2.2.1: @@ -3600,6 +3612,9 @@ packages: resolution: {integrity: sha512-jRKj0n0jXWo6kh62nA5TEh3+4igKDXLvzBJcPpiizP7oOolUrYIxmVBG9TOtHYFHoddUk6YvAkGeGoSVTXfQXQ==} engines: {node: '>=12'} + ts-results-es@4.2.0: + resolution: {integrity: sha512-GfpRk+qvHxa/6gADH8WMN/jXvs5oHYbKtMQc6X9L3VhToy5Lri3iQowyYSytaRcvPDiTT2z3vurzQZXFQFXKRA==} + tslib@2.6.3: resolution: {integrity: sha512-xNvxJEOUiWPGhUuUdQgAJPKOOJfGnIyKySOc09XkKsgdUV/3E2zvwZYdejjmRgPCgcym1juLH3226yA7sEFJKQ==} @@ -4306,6 +4321,10 @@ snapshots: '@biomejs/cli-win32-x64@1.8.1': optional: true + '@blgc/types@0.0.6': {} + + '@blgc/utils@0.0.15': {} + '@bundled-es-modules/cookie@2.0.0': dependencies: cookie: 0.5.0 @@ -5369,13 +5388,13 @@ snapshots: - '@vue/composition-api' - vue - '@vueuse/integrations@10.11.1(axios@1.7.2)(focus-trap@7.5.4)(vue@3.4.37(typescript@5.5.4))': + '@vueuse/integrations@10.11.1(axios@1.7.4)(focus-trap@7.5.4)(vue@3.4.37(typescript@5.5.4))': dependencies: '@vueuse/core': 10.11.1(vue@3.4.37(typescript@5.5.4)) '@vueuse/shared': 10.11.1(vue@3.4.37(typescript@5.5.4)) vue-demi: 0.14.10(vue@3.4.37(typescript@5.5.4)) optionalDependencies: - axios: 1.7.2 + axios: 1.7.4 focus-trap: 7.5.4 transitivePeerDependencies: - '@vue/composition-api' @@ -5514,7 +5533,7 @@ snapshots: dependencies: possible-typed-array-names: 1.0.0 - axios@1.7.2: + axios@1.7.4: dependencies: follow-redirects: 1.15.6 form-data: 4.0.0 @@ -6079,6 +6098,12 @@ snapshots: dependencies: reusify: 1.0.4 + feature-fetch@0.0.15: + dependencies: + '@blgc/types': 0.0.6 + '@blgc/utils': 0.0.15 + ts-results-es: 4.2.0 + fill-range@7.0.1: dependencies: to-regex-range: 5.0.1 @@ -7341,11 +7366,11 @@ snapshots: client-only: 0.0.1 react: 18.3.1 - superagent@9.0.2: + superagent@10.0.1: dependencies: component-emitter: 1.3.1 cookiejar: 2.1.4 - debug: 4.3.5(supports-color@9.4.0) + debug: 4.3.6(supports-color@9.4.0) fast-safe-stringify: 2.1.1 form-data: 4.0.0 formidable: 3.5.1 @@ -7476,6 +7501,8 @@ snapshots: trim-newlines@4.1.1: {} + ts-results-es@4.2.0: {} + tslib@2.6.3: {} tty-table@4.2.3: @@ -7616,7 +7643,7 @@ snapshots: optionalDependencies: vite: 5.3.5(@types/node@20.14.7) - vitepress@1.3.2(@algolia/client-search@4.24.0)(@types/node@20.14.7)(@types/react@18.3.3)(axios@1.7.2)(postcss@8.4.41)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(search-insights@2.13.0)(typescript@5.5.4): + vitepress@1.3.2(@algolia/client-search@4.24.0)(@types/node@20.14.7)(@types/react@18.3.3)(axios@1.7.4)(postcss@8.4.41)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(search-insights@2.13.0)(typescript@5.5.4): dependencies: '@docsearch/css': 3.6.1 '@docsearch/js': 3.6.1(@algolia/client-search@4.24.0)(@types/react@18.3.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(search-insights@2.13.0) @@ -7627,7 +7654,7 @@ snapshots: '@vue/devtools-api': 7.3.7 '@vue/shared': 3.4.37 '@vueuse/core': 10.11.1(vue@3.4.37(typescript@5.5.4)) - '@vueuse/integrations': 10.11.1(axios@1.7.2)(focus-trap@7.5.4)(vue@3.4.37(typescript@5.5.4)) + '@vueuse/integrations': 10.11.1(axios@1.7.4)(focus-trap@7.5.4)(vue@3.4.37(typescript@5.5.4)) focus-trap: 7.5.4 mark.js: 8.11.1 minisearch: 7.1.0 From 2844c142482bfbec5a057f94b37e5cd94d902cc5 Mon Sep 17 00:00:00 2001 From: Benno <57860196+bennoinbeta@users.noreply.github.com> Date: Wed, 14 Aug 2024 15:16:08 +0200 Subject: [PATCH 5/6] #main lint fix --- packages/openapi-fetch/test/index.bench.js | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/packages/openapi-fetch/test/index.bench.js b/packages/openapi-fetch/test/index.bench.js index a1f6d97ce..e8c3e3308 100644 --- a/packages/openapi-fetch/test/index.bench.js +++ b/packages/openapi-fetch/test/index.bench.js @@ -6,7 +6,7 @@ import superagent from "superagent"; import { afterAll, bench, describe } from "vitest"; import createClient, { createPathBasedClient } from "../dist/index.js"; import * as openapiTSCodegen from "./fixtures/openapi-typescript-codegen.min.js"; -import { createApiFetchClient } from 'feature-fetch'; +import { createApiFetchClient } from "feature-fetch"; const BASE_URL = "https://api.test.local"; @@ -59,9 +59,9 @@ describe("setup", () => { }); }); - bench('feature-fetch', async () => { + bench("feature-fetch", async () => { createApiFetchClient({ prefixUrl: BASE_URL }); - }) + }); // superagent: N/A }); @@ -126,10 +126,10 @@ describe("get (headers)", () => { const axiosInstance = axios.create({ baseURL: BASE_URL, }); - const featureFetch = createApiFetchClient({ - prefixUrl: BASE_URL, - headers: { "x-base-header": '123' } - }); + const featureFetch = createApiFetchClient({ + prefixUrl: BASE_URL, + headers: { "x-base-header": "123" }, + }); bench("openapi-fetch", async () => { await openapiFetch.GET("/url", { From 43735dd93acaf28359e2e82532a41b67489e2681 Mon Sep 17 00:00:00 2001 From: Benno <57860196+bennoinbeta@users.noreply.github.com> Date: Fri, 3 Jan 2025 09:45:04 +0100 Subject: [PATCH 6/6] #main updated example section for `@blgc/openapi-router` --- docs/examples.md | 66 +++++++++++++++++++++++++++++---------------- docs/ja/examples.md | 18 ++++++------- 2 files changed, 52 insertions(+), 32 deletions(-) diff --git a/docs/examples.md b/docs/examples.md index 5fb0dc095..d505b9b1a 100644 --- a/docs/examples.md +++ b/docs/examples.md @@ -78,7 +78,7 @@ try {
-feature-fetch by builder.group +feature-fetch by builder.group ::: code-group @@ -257,26 +257,27 @@ TypeChecking in server environments can be tricky, as you’re often querying da ::: -## Hono with [`@blgc/openapi-router`](https://github.com/builder-group/community/tree/develop/packages/openapi-router) +## Hono with [`openapi-ts-router`](https://github.com/builder-group/community/tree/develop/packages/openapi-ts-router) -Instead of manually typing each route with generics as in the [Hono example](#hono), [`@blgc/openapi-router`](https://github.com/builder-group/community/tree/develop/packages/openapi-router) wraps around the [Hono router](https://hono.dev/docs/api/routing) to deliver full typesafety and enforce your OpenAPI-Schema with validators. +[`openapi-ts-router`](https://github.com/builder-group/community/tree/develop/packages/openapi-ts-router) provides full type-safety and runtime validation for your HonoAPI routes by wrapping a [Hono router](https://hono.dev/docs/api/routing): -::: tip Good To Know +::: tip Good to Know -While TypeScript types ensure compile-time safety, they don't enforce runtime schema validation. For runtime compliance, you need to integrate with validation libraries like Zod or Valibot. Although you must define the validation rules manually, they are type-safe to ensure these rules are correctly defined. +While TypeScript ensures compile-time type safety, runtime validation is equally important. `openapi-ts-router` integrates with Zod/Valibot to provide both: +- Types verify your code matches the OpenAPI spec during development +- Validators ensure incoming requests match the spec at runtime ::: ::: code-group ```ts [src/router.ts] -import { createHonoOpenApiRouter } from '@blgc/openapi-router'; import { Hono } from 'hono'; +import { createHonoOpenApiRouter } from 'openapi-ts-router'; import { zValidator } from 'validation-adapters/zod'; import * as z from 'zod'; - import { paths } from './gen/v1'; // OpenAPI-generated types -import { PetSchema } from './schemas'; // Custom reusable Zod schema for validation +import { PetSchema } from './schemas'; // Custom reusable schema for validation export const router = new Hono(); export const openApiRouter = createHonoOpenApiRouter(router); @@ -302,41 +303,51 @@ openApiRouter.post('/pet', { return c.json({ name, photoUrls }); } }); + +// TypeScript will error if route/method doesn't exist in OpenAPI spec +// or if response doesn't match defined schema ``` ::: -[Full example](https://github.com/builder-group/community/tree/develop/examples/openapi-router/hono/petstore) +[Full example](https://github.com/builder-group/community/tree/develop/examples/openapi-ts-router/hono/petstore) + +**Key benefits:** +- Full type safety for routes, methods, params, body and responses +- Runtime validation using Zod/Valibot +- Catches API spec mismatches at compile time +- Zero manual type definitions needed -## Express with [`@blgc/openapi-router`](https://github.com/builder-group/community/tree/develop/packages/openapi-router) +## Express with [`openapi-ts-router`](https://github.com/builder-group/community/tree/develop/packages/openapi-ts-router) -[`@blgc/openapi-router`](https://github.com/builder-group/community/tree/develop/packages/openapi-router) wraps around the [Express router](https://expressjs.com/en/5x/api.html#router) to deliver full typesafety and enforce your OpenAPI-Schema with validators. +[`openapi-ts-router`](https://github.com/builder-group/community/tree/develop/packages/openapi-ts-router) provides full type-safety and runtime validation for your Express API routes by wrapping a [Express router](https://expressjs.com/en/5x/api.html#router): -::: tip Good To Know +::: tip Good to Know -While TypeScript types ensure compile-time safety, they don't enforce runtime schema validation. For runtime compliance, you need to integrate with validation libraries like Zod or Valibot. Although you must define the validation rules manually, they are type-safe to ensure these rules are correctly defined. +While TypeScript ensures compile-time type safety, runtime validation is equally important. `openapi-ts-router` integrates with Zod/Valibot to provide both: +- Types verify your code matches the OpenAPI spec during development +- Validators ensure incoming requests match the spec at runtime ::: ::: code-group ```ts [src/router.ts] -import { createExpressOpenApiRouter } from '@blgc/openapi-router'; import { Router } from 'express'; -import * as v from 'valibot'; -import { vValidator } from 'validation-adapters/valibot'; - +import { createExpressOpenApiRouter } from 'openapi-ts-router'; +import * as z from 'zod'; +import { zValidator } from 'validation-adapters/zod'; import { paths } from './gen/v1'; // OpenAPI-generated types -import { PetSchema } from './schemas'; // Custom reusable Zod schema for validation +import { PetSchema } from './schemas'; // Custom reusable schema for validation export const router: Router = Router(); export const openApiRouter = createExpressOpenApiRouter(router); // GET /pet/{petId} openApiRouter.get('/pet/{petId}', { - pathValidator: vValidator( - v.object({ - petId: v.number() // Validate that petId is a number + pathValidator: zValidator( + z.object({ + petId: z.number() // Validate that petId is a number }) ), handler: (req, res) => { @@ -347,17 +358,26 @@ openApiRouter.get('/pet/{petId}', { // POST /pet openApiRouter.post('/pet', { - bodyValidator: vValidator(PetSchema), // Validate request body using PetSchema + bodyValidator: zValidator(PetSchema), // Validate request body using PetSchema handler: (req, res) => { const { name, photoUrls } = req.body; // Access validated body data res.send({ name, photoUrls }); } }); + +// TypeScript will error if route/method doesn't exist in OpenAPI spec +// or if response doesn't match defined schema ``` ::: -[Full example](https://github.com/builder-group/community/tree/develop/examples/openapi-router/express/petstore) +[Full example](https://github.com/builder-group/community/tree/develop/examples/openapi-ts-router/express/petstore) + +**Key benefits:** +- Full type safety for routes, methods, params, body and responses +- Runtime validation using Zod/Valibot +- Catches API spec mismatches at compile time +- Zero manual type definitions needed ## Mock-Service-Worker (MSW) diff --git a/docs/ja/examples.md b/docs/ja/examples.md index 0e761d0e5..53d50269d 100644 --- a/docs/ja/examples.md +++ b/docs/ja/examples.md @@ -80,7 +80,7 @@ try {
-feature-fetch by builder.group +feature-fetch by builder.group ::: code-group @@ -262,9 +262,9 @@ export default app; ::: -## Hono と [`@blgc/openapi-router`](https://github.com/builder-group/community/tree/develop/packages/openapi-router) +## Hono と [`openapi-ts-router`](https://github.com/builder-group/community/tree/develop/packages/openapi-ts-router) -[Honoの例](#hono) のように、各ルートをジェネリックで手動で型付けする代わりに、[`@blgc/openapi-router`](https://github.com/builder-group/community/tree/develop/packages/openapi-router) は、[Hono router](https://hono.dev/docs/api/routing) をラップして完全な型安全性を提供し、バリデーターを使用してOpenAPIスキーマを強制します。 +[Honoの例](#hono) のように、各ルートをジェネリックで手動で型付けする代わりに、[`openapi-ts-router`](https://github.com/builder-group/community/tree/develop/packages/openapi-ts-router) は、[Hono router](https://hono.dev/docs/api/routing) をラップして完全な型安全性を提供し、バリデーターを使用してOpenAPIスキーマを強制します。 ::: tip 知っておくと良いこと @@ -275,7 +275,7 @@ TypeScriptの型はコンパイル時の安全性を保証しますが、実行 ::: code-group ```ts [src/router.ts] -import { createHonoOpenApiRouter } from "@blgc/openapi-router"; +import { createHonoOpenApiRouter } from "openapi-ts-router"; import { Hono } from "hono"; import { zValidator } from "validation-adapters/zod"; import * as z from "zod"; @@ -311,11 +311,11 @@ openApiRouter.post("/pet", { ::: -[完全な例](https://github.com/builder-group/community/tree/develop/examples/openapi-router/hono/petstore) +[完全な例](https://github.com/builder-group/community/tree/develop/examples/openapi-ts-router/hono/petstore) -## Express と [`@blgc/openapi-router`](https://github.com/builder-group/community/tree/develop/packages/openapi-router) +## Express と [`openapi-ts-router`](https://github.com/builder-group/community/tree/develop/packages/openapi-ts-router) -[`@blgc/openapi-router`](https://github.com/builder-group/community/tree/develop/packages/openapi-router) は、[Express ルーター](https://expressjs.com/en/5x/api.html#router) をラップして、完全な型安全性を提供し、バリデーターを使用して OpenAPI スキーマを強制します。 +[`openapi-ts-router`](https://github.com/builder-group/community/tree/develop/packages/openapi-ts-router) は、[Express ルーター](https://expressjs.com/en/5x/api.html#router) をラップして、完全な型安全性を提供し、バリデーターを使用して OpenAPI スキーマを強制します。 ::: tip 知っておくと良いこと @@ -326,7 +326,7 @@ TypeScriptの型はコンパイル時の安全性を保証しますが、実行 ::: code-group ```ts [src/router.ts] -import { createExpressOpenApiRouter } from "@blgc/openapi-router"; +import { createExpressOpenApiRouter } from "openapi-ts-router"; import { Router } from "express"; import * as v from "valibot"; import { vValidator } from "validation-adapters/valibot"; @@ -362,7 +362,7 @@ openApiRouter.post("/pet", { ::: -[完全な例](https://github.com/builder-group/community/tree/develop/examples/openapi-router/express/petstore) +[完全な例](https://github.com/builder-group/community/tree/develop/examples/openapi-ts-router/express/petstore) ## Mock-Service-Worker (MSW)