--- name: devup-api description: | Type-safe API client generator from OpenAPI schemas with full ecosystem support. TRIGGER WHEN: - Setting up API client with OpenAPI schema (@devup-api/fetch) - Making typed API requests (GET, POST, PUT, PATCH, DELETE) - Using React Query hooks (@devup-api/react-query) - Using Zod validation schemas (@devup-api/zod) - Building forms with react-hook-form (@devup-api/hookform) - Creating CRUD interfaces (@devup-api/ui) - Configuring Vite/Next.js/Webpack/Rsbuild plugins - Implementing authentication middleware - Using DevupObject for type references --- # devup-api Usage Guide Type-safe API client from OpenAPI. Zero generics, auto-generated types. ## Setup ### Install ```bash # Core + Build Plugin (choose one) npm install @devup-api/fetch @devup-api/vite-plugin # Vite npm install @devup-api/fetch @devup-api/next-plugin # Next.js npm install @devup-api/fetch @devup-api/webpack-plugin # Webpack npm install @devup-api/fetch @devup-api/rsbuild-plugin # Rsbuild # Optional Integrations npm install @devup-api/react-query @tanstack/react-query # React Query npm install @devup-api/zod zod # Zod validation npm install @devup-api/hookform react-hook-form zod # Hook Form npm install @devup-api/ui @tanstack/react-query react-hook-form zod # CRUD UI ``` ### Configure Build Tool ```ts // vite.config.ts import devupApi from '@devup-api/vite-plugin' export default defineConfig({ plugins: [devupApi()] }) // next.config.ts import devupApi from '@devup-api/next-plugin' export default devupApi({ reactStrictMode: true }) ``` ### tsconfig.json ```json { "include": ["src", "df/**/*.d.ts"] } ``` Place `openapi.json` in project root, run `npm run dev`. --- ## @devup-api/fetch — API Client ### Create Client ```ts import { createApi, type DevupObject } from '@devup-api/fetch' const api = createApi('https://api.example.com') // or with options const api = createApi({ baseUrl: 'https://api.example.com', headers: { 'X-Custom': 'value' } }) ``` ### HTTP Methods ```ts // GET const users = await api.get('getUsers', { query: { page: 1, limit: 20 } }) const user = await api.get('/users/{id}', { params: { id: '123' } }) // POST const created = await api.post('createUser', { body: { name: 'John', email: 'john@example.com' } }) // PUT / PATCH / DELETE await api.put('/users/{id}', { params: { id: '1' }, body: { name: 'Jane', email: 'jane@example.com' } }) await api.patch('/users/{id}', { params: { id: '1' }, body: { name: 'Jane' } }) await api.delete('/users/{id}', { params: { id: '1' } }) ``` ### Response Handling ```ts const result = await api.get('getUser', { params: { id: '1' } }) if (result.data) { console.log(result.data.name) // typed response } else if (result.error) { console.error(result.error) // typed error } console.log(result.response.status) // raw Response ``` ### DevupObject (Type References) Use `DevupObject` directly in type annotations without redefining types: ```ts // Direct usage in variable declarations const user: DevupObject['User'] = await fetchUser() const body: DevupObject<'request'>['CreateUserBody'] = { name: 'John', email: 'john@example.com' } const error: DevupObject<'error'>['ErrorResponse'] = result.error // Direct usage in function parameters function displayUser(user: DevupObject['User']) { /* ... */ } // Direct usage in component props function UserCard({ user }: { user: DevupObject['User'] }) { /* ... */ } // Multi-server types const product: DevupObject<'response', 'openapi2.json'>['Product'] = data ``` ### Middleware ```ts // Auth api.use({ onRequest: async ({ request }) => { const token = localStorage.getItem('token') if (token) { const headers = new Headers(request.headers) headers.set('Authorization', `Bearer ${token}`) return new Request(request, { headers }) } } }) // Token Refresh api.use({ onResponse: async ({ request, response }) => { if (response.status === 401) { const newToken = await refreshToken() const headers = new Headers(request.headers) headers.set('Authorization', `Bearer ${newToken}`) return fetch(new Request(request, { headers })) } } }) ``` --- ## @devup-api/react-query — React Query Hooks ```ts import { createApi } from '@devup-api/fetch' import { createQueryClient } from '@devup-api/react-query' const api = createApi('https://api.example.com') const queryClient = createQueryClient(api) ``` ### useQuery ```tsx const { data, isLoading, error, refetch } = queryClient.useQuery( 'get', '/users/{id}', { params: { id: userId } }, { staleTime: 5 * 60 * 1000 } // React Query options ) ``` ### useMutation ```tsx const mutation = queryClient.useMutation('post', 'createUser', { onSuccess: (data) => { tanstackQueryClient.invalidateQueries({ queryKey: ['get', 'getUsers'] }) } }) mutation.mutate({ body: { name: 'John', email: 'john@example.com' } }) ``` ### useSuspenseQuery ```tsx // Use with React Suspense const { data } = queryClient.useSuspenseQuery('get', 'getUsers', {}) ``` ### useInfiniteQuery ```tsx const { data, fetchNextPage, hasNextPage } = queryClient.useInfiniteQuery( 'get', 'getUsers', { initialPageParam: 1, getNextPageParam: (lastPage) => lastPage.nextPage } ) ``` ### useQueries (Parallel) ```tsx const results = queryClient.useQueries([ ['get', '/users/{id}', { params: { id: '1' } }], ['get', '/users/{id}', { params: { id: '2' } }], ]) ``` --- ## @devup-api/zod — Runtime Validation Schemas auto-generated from OpenAPI via virtual module. ```ts import { schemas, responseSchemas, requestSchemas, errorSchemas, pathSchemas } from '@devup-api/zod' // By category const userSchema = responseSchemas.User const createUserSchema = requestSchemas.CreateUserRequest const errorSchema = errorSchemas.ApiError // By path/operationId (for forms) const schema = pathSchemas.post['createUser'] const schema = pathSchemas.put['/users/{id}'] // Multi-server const productSchema = schemas['openapi2.json'].response.Product // Validate const result = userSchema.safeParse(data) if (result.success) { console.log(result.data) } else { console.error(result.error.issues) } // Type inference import { z } from 'zod' type User = z.infer ``` --- ## @devup-api/hookform — React Hook Form Integration Auto-validation with Zod schemas from OpenAPI. ```tsx import { createApi } from '@devup-api/fetch' import { ApiForm, useFormContext, useWatch, useFieldArray, Controller } from '@devup-api/hookform' const api = createApi('https://api.example.com') ``` ### Basic Form ```tsx function FormFields() { const { register, formState: { errors, isSubmitting } } = useFormContext() return ( <> {errors.name && {errors.name.message}} {errors.email && {errors.email.message}} ) } function CreateUserForm() { return ( console.log('Created:', data)} onError={(error) => console.error('Error:', error)} onValidationError={(errors) => console.log('Validation:', errors)} > ) } ``` ### Edit Form ```tsx console.log('Updated:', data)} > ``` ### Props | Prop | Type | Description | |------|------|-------------| | `api` | `DevupApi` | API client | | `method` | `'post' \| 'put' \| 'patch' \| 'delete'` | HTTP method | | `path` | `string` | operationId or path | | `requestOptions` | `{ params?, query?, headers? }` | Additional request options | | `defaultValues` | `object` | Form default values | | `mode` | `'onSubmit' \| 'onBlur' \| 'onChange'` | Validation mode | | `resetOnSuccess` | `boolean` | Reset form after success | | `onSuccess` | `(data) => void` | Success callback | | `onError` | `(error) => void` | API error callback | | `onValidationError` | `(errors) => void` | Validation error callback | --- ## @devup-api/ui — CRUD Components Auto-generated CRUD from OpenAPI tags. ### OpenAPI Tags ```yaml paths: /users/{id}: get: tags: [devup:user:one] # GET single (required) put: tags: [devup:user:edit] # PUT update patch: tags: [devup:user:fix] # PATCH update /users: post: tags: [devup:user:create] # POST create (required) ``` ### Usage ```tsx import { createApi } from '@devup-api/fetch' import { ApiCrud } from '@devup-api/ui' import { crudConfigs } from '@devup-api/ui/crud' const api = createApi('https://api.example.com') ``` ### Create Mode (no params) ```tsx console.log('Created:', data)} /> ``` ### Edit Mode (with params) ```tsx Loading...} oneFallback={
Not found
} onUpdateSuccess={(data) => console.log('Updated:', data)} /> ``` ### Headless Mode (Render Function) ```tsx {({ form, mode, submit, isLoading, one }) => (
{ e.preventDefault(); submit() }}>
)}
``` ### Custom Renderers ```tsx (
)} renderSubmit={({ isLoading, mode }) => ( )} /> ``` ### useApiCrud Hook ```tsx import { useApiCrud } from '@devup-api/ui' const crud = useApiCrud({ config: crudConfigs.user, api, params: userId ? { id: userId } : undefined, onCreateSuccess: (data) => console.log('Created:', data), onUpdateSuccess: (data) => console.log('Updated:', data), }) // crud.mode: 'create' | 'edit' // crud.form: UseFormReturn // crud.one: { data, isLoading, isError } // crud.create: { mutate, isPending } // crud.update: { mutate, isPending } // crud.submit: () => void // crud.isLoading: boolean ``` ### Field Types `text` | `number` | `email` | `password` | `url` | `tel` | `textarea` | `select` | `checkbox` | `radio` | `date` | `datetime` | `time` | `file` | `hidden` | `array` | `object` --- ## Multiple API Servers ```ts // Plugin config devupApi({ openapiFiles: ['openapi.json', 'openapi2.json'] }) // Usage const api1 = createApi({ baseUrl: 'https://api1.com' }) const api2 = createApi({ baseUrl: 'https://api2.com', serverName: 'openapi2.json' }) // Types - use directly without redefining const user: DevupObject['User'] = data // openapi.json const product: DevupObject<'response', 'openapi2.json'>['Product'] = data // openapi2.json ``` --- ## Plugin Options ```ts interface DevupApiOptions { openapiFiles?: string | string[] // default: 'openapi.json' tempDir?: string // default: 'df' convertCase?: 'snake' | 'camel' | 'pascal' | 'maintain' // default: 'camel' requestDefaultNonNullable?: boolean // default: false responseDefaultNonNullable?: boolean // default: true } ``` --- ## Common Patterns ### Request Cancellation ```ts const controller = new AbortController() setTimeout(() => controller.abort(), 5000) await api.get('getUsers', { signal: controller.signal }) ``` ### File Upload ```ts const formData = new FormData() formData.append('file', file) await api.post('/upload', { body: formData }) ``` ### Environment URL ```ts const api = createApi(import.meta.env.VITE_API_URL || 'http://localhost:3000') ``` --- ## Troubleshooting | Issue | Solution | |-------|----------| | Types not appearing | Run `npm run dev`, check tsconfig includes `df/**/*.d.ts` | | operationId not found | Use path `/users/{id}` or verify openapi.json operationId | | Zod schemas empty | Ensure bundler plugin is configured, run dev server | | CRUD config missing | Add `devup:{name}:one` and `devup:{name}:create` tags to OpenAPI |