fork of hey-api/openapi-ts because I need some additional things

Merge pull request #1596 from hey-api/fix/text-plain-body-serializer

fix: do not use a body serializer on text/plain sdks

authored by

Lubos and committed by
GitHub
7eebbefb 548e5d8a

+486 -29
+5
.changeset/neat-donkeys-pay.md
··· 1 + --- 2 + '@hey-api/openapi-ts': patch 3 + --- 4 + 5 + fix: do not use a body serializer on text/plain sdks
+8
.changeset/six-horses-report.md
··· 1 + --- 2 + '@hey-api/client-axios': patch 3 + '@hey-api/client-fetch': patch 4 + '@hey-api/client-nuxt': patch 5 + '@hey-api/openapi-ts': patch 6 + --- 7 + 8 + fix: add null to valid bodySerializer types
+24
examples/openapi-ts-sample/.gitignore
··· 1 + # Logs 2 + logs 3 + *.log 4 + npm-debug.log* 5 + yarn-debug.log* 6 + yarn-error.log* 7 + pnpm-debug.log* 8 + lerna-debug.log* 9 + 10 + node_modules 11 + dist 12 + dist-ssr 13 + *.local 14 + 15 + # Editor directories and files 16 + .vscode/* 17 + !.vscode/extensions.json 18 + .idea 19 + .DS_Store 20 + *.suo 21 + *.ntvs* 22 + *.njsproj 23 + *.sln 24 + *.sw?
+13
examples/openapi-ts-sample/index.html
··· 1 + <!doctype html> 2 + <html lang="en"> 3 + <head> 4 + <meta charset="UTF-8" /> 5 + <!-- <link rel="icon" type="image/svg+xml" href="/vite.svg" /> --> 6 + <meta name="viewport" content="width=device-width, initial-scale=1.0" /> 7 + <title>Hey API + Fetch API Demo</title> 8 + </head> 9 + <body> 10 + <div id="root"></div> 11 + <script type="module" src="/src/main.tsx"></script> 12 + </body> 13 + </html>
+20
examples/openapi-ts-sample/openapi-ts.config.ts
··· 1 + import { defineConfig } from '@hey-api/openapi-ts'; 2 + 3 + export default defineConfig({ 4 + client: '@hey-api/client-fetch', 5 + input: 6 + '../../packages/openapi-ts/test/spec/2.0.x/body-response-text-plain.yaml', 7 + output: { 8 + format: 'prettier', 9 + lint: 'eslint', 10 + path: './src/client', 11 + }, 12 + plugins: [ 13 + '@hey-api/schemas', 14 + '@hey-api/sdk', 15 + { 16 + enums: 'javascript', 17 + name: '@hey-api/typescript', 18 + }, 19 + ], 20 + });
+39
examples/openapi-ts-sample/package.json
··· 1 + { 2 + "name": "@example/openapi-ts-sample", 3 + "private": true, 4 + "version": "0.0.1", 5 + "type": "module", 6 + "scripts": { 7 + "build": "tsc && vite build", 8 + "dev": "vite", 9 + "lint": "eslint . --ext ts,tsx --report-unused-disable-directives --max-warnings 0", 10 + "openapi-ts": "openapi-ts", 11 + "preview": "vite preview", 12 + "typecheck": "tsc --noEmit" 13 + }, 14 + "dependencies": { 15 + "@hey-api/client-fetch": "workspace:*", 16 + "@radix-ui/react-form": "0.1.1", 17 + "@radix-ui/react-icons": "1.3.2", 18 + "@radix-ui/themes": "3.1.6", 19 + "react": "19.0.0", 20 + "react-dom": "19.0.0" 21 + }, 22 + "devDependencies": { 23 + "@hey-api/openapi-ts": "workspace:*", 24 + "@types/react": "19.0.1", 25 + "@types/react-dom": "19.0.1", 26 + "@typescript-eslint/eslint-plugin": "7.18.0", 27 + "@typescript-eslint/parser": "7.15.0", 28 + "@vitejs/plugin-react": "4.3.1", 29 + "autoprefixer": "10.4.19", 30 + "eslint": "9.17.0", 31 + "eslint-plugin-react-hooks": "4.6.2", 32 + "eslint-plugin-react-refresh": "0.4.7", 33 + "postcss": "8.4.39", 34 + "prettier": "3.4.2", 35 + "tailwindcss": "3.4.4", 36 + "typescript": "5.5.3", 37 + "vite": "6.0.7" 38 + } 39 + }
+6
examples/openapi-ts-sample/postcss.config.js
··· 1 + export default { 2 + plugins: { 3 + autoprefixer: {}, 4 + tailwindcss: {}, 5 + }, 6 + };
+3
examples/openapi-ts-sample/src/App.css
··· 1 + @tailwind base; 2 + @tailwind components; 3 + @tailwind utilities;
+40
examples/openapi-ts-sample/src/App.tsx
··· 1 + import './App.css'; 2 + 3 + import { 4 + Box, 5 + Button, 6 + Container, 7 + Flex, 8 + Heading, 9 + Section, 10 + } from '@radix-ui/themes'; 11 + 12 + import { postFoo } from './client/sdk.gen'; 13 + 14 + function App() { 15 + const onClick = async () => { 16 + postFoo({ 17 + body: 'foo', 18 + }); 19 + }; 20 + 21 + return ( 22 + <Box 23 + style={{ background: 'var(--gray-a2)', borderRadius: 'var(--radius-3)' }} 24 + > 25 + <Container size="1"> 26 + <Section size="1" /> 27 + <Flex align="center"> 28 + <Heading>sample for internal testing</Heading> 29 + </Flex> 30 + <Section size="1" /> 31 + <Flex direction="column" gapY="2"> 32 + <Button onClick={onClick}>Click me</Button> 33 + </Flex> 34 + <Section size="1" /> 35 + </Container> 36 + </Box> 37 + ); 38 + } 39 + 40 + export default App;
+3
examples/openapi-ts-sample/src/client/index.ts
··· 1 + // This file is auto-generated by @hey-api/openapi-ts 2 + export * from './sdk.gen'; 3 + export * from './types.gen';
+24
examples/openapi-ts-sample/src/client/sdk.gen.ts
··· 1 + // This file is auto-generated by @hey-api/openapi-ts 2 + 3 + import { 4 + createClient, 5 + createConfig, 6 + type Options, 7 + } from '@hey-api/client-fetch'; 8 + 9 + import type { PostFooData, PostFooResponse } from './types.gen'; 10 + 11 + export const client = createClient(createConfig()); 12 + 13 + export const postFoo = <ThrowOnError extends boolean = false>( 14 + options: Options<PostFooData, ThrowOnError>, 15 + ) => 16 + (options?.client ?? client).post<PostFooResponse, unknown, ThrowOnError>({ 17 + bodySerializer: null, 18 + url: '/foo', 19 + ...options, 20 + headers: { 21 + 'Content-Type': 'text/plain', 22 + ...options?.headers, 23 + }, 24 + });
+17
examples/openapi-ts-sample/src/client/types.gen.ts
··· 1 + // This file is auto-generated by @hey-api/openapi-ts 2 + 3 + export type PostFooData = { 4 + body: string; 5 + path?: never; 6 + query?: never; 7 + url: '/foo'; 8 + }; 9 + 10 + export type PostFooResponses = { 11 + /** 12 + * OK 13 + */ 14 + 200: string; 15 + }; 16 + 17 + export type PostFooResponse = PostFooResponses[keyof PostFooResponses];
+26
examples/openapi-ts-sample/src/main.tsx
··· 1 + import '@radix-ui/themes/styles.css'; 2 + 3 + import { Theme } from '@radix-ui/themes'; 4 + import React from 'react'; 5 + import ReactDOM from 'react-dom/client'; 6 + 7 + import App from './App.tsx'; 8 + import { client } from './client/sdk.gen'; 9 + 10 + // configure internal service client 11 + client.setConfig({ 12 + // set default base url for requests 13 + baseUrl: 'https://petstore3.swagger.io/api/v3', 14 + // set default headers for requests 15 + headers: { 16 + Authorization: 'Bearer <token_from_service_client>', 17 + }, 18 + }); 19 + 20 + ReactDOM.createRoot(document.getElementById('root')!).render( 21 + <React.StrictMode> 22 + <Theme appearance="dark"> 23 + <App /> 24 + </Theme> 25 + </React.StrictMode>, 26 + );
+1
examples/openapi-ts-sample/src/vite-env.d.ts
··· 1 + /// <reference types="vite/client" />
+8
examples/openapi-ts-sample/tailwind.config.js
··· 1 + /** @type {import('tailwindcss').Config} */ 2 + export default { 3 + content: ['./index.html', './src/**/*.{html,js,ts,jsx,tsx}'], 4 + plugins: [], 5 + theme: { 6 + extend: {}, 7 + }, 8 + };
+25
examples/openapi-ts-sample/tsconfig.json
··· 1 + { 2 + "compilerOptions": { 3 + "target": "ES2020", 4 + "useDefineForClassFields": true, 5 + "lib": ["ES2020", "DOM", "DOM.Iterable"], 6 + "module": "ESNext", 7 + "skipLibCheck": true, 8 + 9 + /* Bundler mode */ 10 + "moduleResolution": "bundler", 11 + "allowImportingTsExtensions": true, 12 + "resolveJsonModule": true, 13 + "isolatedModules": true, 14 + "noEmit": true, 15 + "jsx": "react-jsx", 16 + 17 + /* Linting */ 18 + "strict": true, 19 + "noUnusedLocals": true, 20 + "noUnusedParameters": true, 21 + "noFallthroughCasesInSwitch": true 22 + }, 23 + "include": ["src"], 24 + "references": [{ "path": "./tsconfig.node.json" }] 25 + }
+11
examples/openapi-ts-sample/tsconfig.node.json
··· 1 + { 2 + "compilerOptions": { 3 + "composite": true, 4 + "skipLibCheck": true, 5 + "module": "ESNext", 6 + "moduleResolution": "bundler", 7 + "allowSyntheticDefaultImports": true, 8 + "strict": true 9 + }, 10 + "include": ["vite.config.ts"] 11 + }
+7
examples/openapi-ts-sample/vite.config.ts
··· 1 + import react from '@vitejs/plugin-react'; 2 + import { defineConfig } from 'vite'; 3 + 4 + // https://vitejs.dev/config/ 5 + export default defineConfig({ 6 + plugins: [react()], 7 + });
+1 -1
packages/client-axios/src/types.ts
··· 32 32 * A function for serializing request body parameter. By default, 33 33 * {@link JSON.stringify()} will be used. 34 34 */ 35 - bodySerializer?: BodySerializer; 35 + bodySerializer?: BodySerializer | null; 36 36 /** 37 37 * An object containing any HTTP headers that you want to pre-populate your 38 38 * `Headers` object with.
+1 -1
packages/client-fetch/src/types.ts
··· 24 24 * A function for serializing request body parameter. By default, 25 25 * {@link JSON.stringify()} will be used. 26 26 */ 27 - bodySerializer?: BodySerializer; 27 + bodySerializer?: BodySerializer | null; 28 28 /** 29 29 * Fetch API implementation. You can use this option to provide a custom 30 30 * fetch instance.
+1 -1
packages/client-nuxt/src/types.ts
··· 65 65 * A function for serializing request body parameter. By default, 66 66 * {@link JSON.stringify()} will be used. 67 67 */ 68 - bodySerializer?: BodySerializer; 68 + bodySerializer?: BodySerializer | null; 69 69 /** 70 70 * An object containing any HTTP headers that you want to pre-populate your 71 71 * `Headers` object with.
+55 -19
packages/openapi-ts/src/ir/__tests__/mediaType.test.ts
··· 1 1 import { describe, expect, it } from 'vitest'; 2 2 3 - import { isMediaTypeFileLike } from '../mediaType'; 3 + import type { IRMediaType } from '../mediaType'; 4 + import { isMediaTypeFileLike, mediaTypeToIrMediaType } from '../mediaType'; 4 5 5 6 describe('isMediaTypeFileLike', () => { 6 7 const scenarios: Array<{ 7 - fileLike: ReturnType<typeof isMediaTypeFileLike>; 8 - mediaType: Parameters<typeof isMediaTypeFileLike>[0]['mediaType']; 8 + mediaType: string; 9 + response: boolean; 9 10 }> = [ 10 11 { 11 - fileLike: false, 12 12 mediaType: 'application/json', 13 + response: false, 13 14 }, 14 15 { 15 - fileLike: true, 16 16 mediaType: 'application/json+download', 17 + response: true, 17 18 }, 18 19 { 19 - fileLike: false, 20 20 mediaType: 'application/json; charset=ascii', 21 + response: false, 21 22 }, 22 23 { 23 - fileLike: true, 24 24 mediaType: 'application/octet-stream', 25 + response: true, 25 26 }, 26 27 { 27 - fileLike: true, 28 28 mediaType: 'application/pdf', 29 + response: true, 29 30 }, 30 31 { 31 - fileLike: true, 32 32 mediaType: 'application/xml; charset=utf-8', 33 + response: true, 33 34 }, 34 35 { 35 - fileLike: true, 36 36 mediaType: 'application/zip', 37 + response: true, 37 38 }, 38 39 { 39 - fileLike: false, 40 40 mediaType: 'image/jpeg', 41 + response: false, 41 42 }, 42 43 { 43 - fileLike: false, 44 44 mediaType: 'image/jpeg; charset=utf-8', 45 + response: false, 45 46 }, 46 47 { 47 - fileLike: false, 48 48 mediaType: 'text/html; charset=utf-8', 49 + response: false, 49 50 }, 50 51 { 51 - fileLike: true, 52 52 mediaType: 'text/javascript; charset=ISO-8859-1', 53 + response: true, 53 54 }, 54 55 { 55 - fileLike: false, 56 56 mediaType: 'text/plain; charset=utf-8', 57 + response: false, 57 58 }, 58 59 { 59 - fileLike: true, 60 60 mediaType: 'video/mp4', 61 + response: true, 61 62 }, 62 63 ]; 63 64 64 65 it.each(scenarios)( 65 - 'detects $mediaType as file-like? $fileLike', 66 - async ({ fileLike, mediaType }) => { 67 - expect(isMediaTypeFileLike({ mediaType })).toEqual(fileLike); 66 + 'detects $mediaType as file-like? $response', 67 + async ({ mediaType, response }) => { 68 + expect(isMediaTypeFileLike({ mediaType })).toEqual(response); 69 + }, 70 + ); 71 + }); 72 + 73 + describe('mediaTypeToIrMediaType', () => { 74 + const scenarios: Array<{ 75 + mediaType: string; 76 + response: IRMediaType | undefined; 77 + }> = [ 78 + { 79 + mediaType: 'multipart/form-data', 80 + response: 'form-data', 81 + }, 82 + { 83 + mediaType: 'application/json', 84 + response: 'json', 85 + }, 86 + { 87 + mediaType: 'text/plain; charset=utf-8', 88 + response: 'text', 89 + }, 90 + { 91 + mediaType: 'application/x-www-form-urlencoded', 92 + response: 'url-search-params', 93 + }, 94 + { 95 + mediaType: 'application/foo', 96 + response: undefined, 97 + }, 98 + ]; 99 + 100 + it.each(scenarios)( 101 + 'ir media type for $mediaType: $response', 102 + async ({ mediaType, response }) => { 103 + expect(mediaTypeToIrMediaType({ mediaType })).toEqual(response); 68 104 }, 69 105 ); 70 106 });
+7 -1
packages/openapi-ts/src/ir/mediaType.ts
··· 2 2 /^(application\/(pdf|rtf|msword|vnd\.(ms-|openxmlformats-officedocument\.)|zip|x-(7z|tar|rar|zip|iso)|octet-stream|gzip|x-msdownload|json\+download|xml|x-yaml|x-7z-compressed|x-tar)|text\/(yaml|css|javascript)|audio\/(mpeg|wav)|video\/(mp4|x-matroska)|image\/(vnd\.adobe\.photoshop|svg\+xml))(; ?charset=[^;]+)?$/i; 3 3 const jsonMimeRegExp = /^application\/(.*\+)?json(;.*)?$/i; 4 4 const multipartFormDataMimeRegExp = /^multipart\/form-data(;.*)?$/i; 5 + const textMimeRegExp = /^text\/[a-z0-9.+-]+(;.*)?$/i; 5 6 const xWwwFormUrlEncodedMimeRegExp = 6 7 /^application\/x-www-form-urlencoded(;.*)?$/i; 7 8 8 - export type IRMediaType = 'form-data' | 'json' | 'url-search-params'; 9 + export type IRMediaType = 'form-data' | 'json' | 'text' | 'url-search-params'; 9 10 10 11 export const isMediaTypeFileLike = ({ 11 12 mediaType, ··· 29 30 multipartFormDataMimeRegExp.lastIndex = 0; 30 31 if (multipartFormDataMimeRegExp.test(mediaType)) { 31 32 return 'form-data'; 33 + } 34 + 35 + textMimeRegExp.lastIndex = 0; 36 + if (textMimeRegExp.test(mediaType)) { 37 + return 'text'; 32 38 } 33 39 34 40 xWwwFormUrlEncodedMimeRegExp.lastIndex = 0;
+8
packages/openapi-ts/src/plugins/@hey-api/sdk/plugin.ts
··· 254 254 }); 255 255 break; 256 256 case 'json': 257 + // jsonBodySerializer is the default, no need to specify 258 + break; 259 + case 'text': 260 + // ensure we don't use any serializer by default 261 + requestOptions.push({ 262 + key: 'bodySerializer', 263 + value: null, 264 + }); 257 265 break; 258 266 case 'url-search-params': 259 267 requestOptions.push({ spread: 'urlSearchParamsBodySerializer' });
+1
packages/openapi-ts/test/2.0.x.test.ts
··· 38 38 config: createConfig({ 39 39 input: 'body-response-text-plain.yaml', 40 40 output: 'body-response-text-plain', 41 + plugins: ['@hey-api/typescript', '@hey-api/sdk'], 41 42 }), 42 43 description: 'handle text/plain content type', 43 44 },
+1
packages/openapi-ts/test/3.0.x.test.ts
··· 67 67 config: createConfig({ 68 68 input: 'body-response-text-plain.yaml', 69 69 output: 'body-response-text-plain', 70 + plugins: ['@hey-api/typescript', '@hey-api/sdk'], 70 71 }), 71 72 description: 'handle text/plain content type', 72 73 },
+1
packages/openapi-ts/test/3.1.x.test.ts
··· 67 67 config: createConfig({ 68 68 input: 'body-response-text-plain.yaml', 69 69 output: 'body-response-text-plain', 70 + plugins: ['@hey-api/typescript', '@hey-api/sdk'], 70 71 }), 71 72 description: 'handle text/plain content type', 72 73 },
+2 -1
packages/openapi-ts/test/__snapshots__/2.0.x/body-response-text-plain/index.ts
··· 1 1 // This file is auto-generated by @hey-api/openapi-ts 2 - export * from './types.gen'; 2 + export * from './types.gen'; 3 + export * from './sdk.gen';
+18
packages/openapi-ts/test/__snapshots__/2.0.x/body-response-text-plain/sdk.gen.ts
··· 1 + // This file is auto-generated by @hey-api/openapi-ts 2 + 3 + import { createClient, createConfig, type Options } from '@hey-api/client-fetch'; 4 + import type { PostFooData, PostFooResponse } from './types.gen'; 5 + 6 + export const client = createClient(createConfig()); 7 + 8 + export const postFoo = <ThrowOnError extends boolean = false>(options: Options<PostFooData, ThrowOnError>) => { 9 + return (options?.client ?? client).post<PostFooResponse, unknown, ThrowOnError>({ 10 + bodySerializer: null, 11 + url: '/foo', 12 + ...options, 13 + headers: { 14 + 'Content-Type': 'text/plain', 15 + ...options?.headers 16 + } 17 + }); 18 + };
+2 -1
packages/openapi-ts/test/__snapshots__/3.0.x/body-response-text-plain/index.ts
··· 1 1 // This file is auto-generated by @hey-api/openapi-ts 2 - export * from './types.gen'; 2 + export * from './types.gen'; 3 + export * from './sdk.gen';
+18
packages/openapi-ts/test/__snapshots__/3.0.x/body-response-text-plain/sdk.gen.ts
··· 1 + // This file is auto-generated by @hey-api/openapi-ts 2 + 3 + import { createClient, createConfig, type Options } from '@hey-api/client-fetch'; 4 + import type { PostFooData, PostFooResponse } from './types.gen'; 5 + 6 + export const client = createClient(createConfig()); 7 + 8 + export const postFoo = <ThrowOnError extends boolean = false>(options: Options<PostFooData, ThrowOnError>) => { 9 + return (options?.client ?? client).post<PostFooResponse, unknown, ThrowOnError>({ 10 + bodySerializer: null, 11 + url: '/foo', 12 + ...options, 13 + headers: { 14 + 'Content-Type': 'text/plain', 15 + ...options?.headers 16 + } 17 + }); 18 + };
+2 -1
packages/openapi-ts/test/__snapshots__/3.1.x/body-response-text-plain/index.ts
··· 1 1 // This file is auto-generated by @hey-api/openapi-ts 2 - export * from './types.gen'; 2 + export * from './types.gen'; 3 + export * from './sdk.gen';
+18
packages/openapi-ts/test/__snapshots__/3.1.x/body-response-text-plain/sdk.gen.ts
··· 1 + // This file is auto-generated by @hey-api/openapi-ts 2 + 3 + import { createClient, createConfig, type Options } from '@hey-api/client-fetch'; 4 + import type { PostFooData, PostFooResponse } from './types.gen'; 5 + 6 + export const client = createClient(createConfig()); 7 + 8 + export const postFoo = <ThrowOnError extends boolean = false>(options: Options<PostFooData, ThrowOnError>) => { 9 + return (options?.client ?? client).post<PostFooResponse, unknown, ThrowOnError>({ 10 + bodySerializer: null, 11 + url: '/foo', 12 + ...options, 13 + headers: { 14 + 'Content-Type': 'text/plain', 15 + ...options?.headers 16 + } 17 + }); 18 + };
+1 -1
packages/openapi-ts/test/__snapshots__/3.1.x/clients/@hey-api/client-axios/bundle/client/types.ts
··· 32 32 * A function for serializing request body parameter. By default, 33 33 * {@link JSON.stringify()} will be used. 34 34 */ 35 - bodySerializer?: BodySerializer; 35 + bodySerializer?: BodySerializer | null; 36 36 /** 37 37 * An object containing any HTTP headers that you want to pre-populate your 38 38 * `Headers` object with.
+1 -1
packages/openapi-ts/test/__snapshots__/3.1.x/clients/@hey-api/client-fetch/bundle/client/types.ts
··· 24 24 * A function for serializing request body parameter. By default, 25 25 * {@link JSON.stringify()} will be used. 26 26 */ 27 - bodySerializer?: BodySerializer; 27 + bodySerializer?: BodySerializer | null; 28 28 /** 29 29 * Fetch API implementation. You can use this option to provide a custom 30 30 * fetch instance.
+1 -1
packages/openapi-ts/test/__snapshots__/3.1.x/clients/@hey-api/client-nuxt/bundle/client/types.ts
··· 65 65 * A function for serializing request body parameter. By default, 66 66 * {@link JSON.stringify()} will be used. 67 67 */ 68 - bodySerializer?: BodySerializer; 68 + bodySerializer?: BodySerializer | null; 69 69 /** 70 70 * An object containing any HTTP headers that you want to pre-populate your 71 71 * `Headers` object with.
+67
pnpm-lock.yaml
··· 275 275 specifier: workspace:* 276 276 version: link:../../packages/openapi-ts 277 277 278 + examples/openapi-ts-sample: 279 + dependencies: 280 + '@hey-api/client-fetch': 281 + specifier: workspace:* 282 + version: link:../../packages/client-fetch 283 + '@radix-ui/react-form': 284 + specifier: 0.1.1 285 + version: 0.1.1(@types/react-dom@19.0.1)(@types/react@19.0.1)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) 286 + '@radix-ui/react-icons': 287 + specifier: 1.3.2 288 + version: 1.3.2(react@19.0.0) 289 + '@radix-ui/themes': 290 + specifier: 3.1.6 291 + version: 3.1.6(@types/react-dom@19.0.1)(@types/react@19.0.1)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) 292 + react: 293 + specifier: 19.0.0 294 + version: 19.0.0 295 + react-dom: 296 + specifier: 19.0.0 297 + version: 19.0.0(react@19.0.0) 298 + devDependencies: 299 + '@hey-api/openapi-ts': 300 + specifier: workspace:* 301 + version: link:../../packages/openapi-ts 302 + '@types/react': 303 + specifier: 19.0.1 304 + version: 19.0.1 305 + '@types/react-dom': 306 + specifier: 19.0.1 307 + version: 19.0.1 308 + '@typescript-eslint/eslint-plugin': 309 + specifier: 7.18.0 310 + version: 7.18.0(@typescript-eslint/parser@7.15.0(eslint@9.17.0(jiti@2.4.2))(typescript@5.5.3))(eslint@9.17.0(jiti@2.4.2))(typescript@5.5.3) 311 + '@typescript-eslint/parser': 312 + specifier: 7.15.0 313 + version: 7.15.0(eslint@9.17.0(jiti@2.4.2))(typescript@5.5.3) 314 + '@vitejs/plugin-react': 315 + specifier: 4.3.1 316 + version: 4.3.1(vite@6.0.7(@types/node@22.10.5)(jiti@2.4.2)(less@4.2.0)(sass@1.80.7)(terser@5.36.0)(yaml@2.7.0)) 317 + autoprefixer: 318 + specifier: 10.4.19 319 + version: 10.4.19(postcss@8.4.39) 320 + eslint: 321 + specifier: 9.17.0 322 + version: 9.17.0(jiti@2.4.2) 323 + eslint-plugin-react-hooks: 324 + specifier: 4.6.2 325 + version: 4.6.2(eslint@9.17.0(jiti@2.4.2)) 326 + eslint-plugin-react-refresh: 327 + specifier: 0.4.7 328 + version: 0.4.7(eslint@9.17.0(jiti@2.4.2)) 329 + postcss: 330 + specifier: 8.4.39 331 + version: 8.4.39 332 + prettier: 333 + specifier: 3.4.2 334 + version: 3.4.2 335 + tailwindcss: 336 + specifier: 3.4.4 337 + version: 3.4.4(ts-node@10.9.2(@types/node@22.10.5)(typescript@5.5.3)) 338 + typescript: 339 + specifier: 5.5.3 340 + version: 5.5.3 341 + vite: 342 + specifier: 6.0.7 343 + version: 6.0.7(@types/node@22.10.5)(jiti@2.4.2)(less@4.2.0)(sass@1.80.7)(terser@5.36.0)(yaml@2.7.0) 344 + 278 345 examples/openapi-ts-tanstack-angular-query-experimental: 279 346 dependencies: 280 347 '@angular/animations':