diff --git a/.gitignore b/.gitignore
index a9ecaad..62dd696 100644
--- a/.gitignore
+++ b/.gitignore
@@ -26,3 +26,5 @@ dist-ssr
openapi
*.tsbuildinfo
coverage
+
+.agents
\ No newline at end of file
diff --git a/.serena/.gitignore b/.serena/.gitignore
new file mode 100644
index 0000000..14d86ad
--- /dev/null
+++ b/.serena/.gitignore
@@ -0,0 +1 @@
+/cache
diff --git a/.serena/memories/project_purpose.md b/.serena/memories/project_purpose.md
new file mode 100644
index 0000000..dffe31d
--- /dev/null
+++ b/.serena/memories/project_purpose.md
@@ -0,0 +1,3 @@
+# Project purpose
+- Library: `@7nohe/openapi-react-query-codegen` generates React Query (TanStack Query) hooks, prefetch/ensure helpers, and TS clients from an OpenAPI schema by leveraging `@hey-api/openapi-ts`.
+- Outputs React Query wrappers around the generated request client, supporting useQuery/useSuspenseQuery/useMutation/useInfiniteQuery and query key functions.
\ No newline at end of file
diff --git a/.serena/memories/style_and_conventions.md b/.serena/memories/style_and_conventions.md
new file mode 100644
index 0000000..9deb58c
--- /dev/null
+++ b/.serena/memories/style_and_conventions.md
@@ -0,0 +1,5 @@
+# Style and conventions
+- Code: TypeScript strict, NodeNext modules, ESNext target. Imports organized; prefer named imports. Uses ts-morph/TypeScript factory for AST-based codegen.
+- Formatting/lint: Biome enforced (formatter + linter). 2-space indent, spaces not tabs. Import organization enabled. Biome ignores build artifacts (dist, docs/.astro, examples outputs).
+- Generated output: Comments include generator version header. Use double quotes and trailing commas (ts-morph manipulation settings). Keep code ASCII unless needed.
+- Tests: Vitest. Coverage flag enabled in test script.
\ No newline at end of file
diff --git a/.serena/memories/suggested_commands.md b/.serena/memories/suggested_commands.md
new file mode 100644
index 0000000..20b98cd
--- /dev/null
+++ b/.serena/memories/suggested_commands.md
@@ -0,0 +1,10 @@
+# Suggested commands
+- Install deps: `pnpm install`
+- Build generator: `pnpm build`
+- Lint (Biome): `pnpm lint`
+- Fix lint/format: `pnpm lint:fix`
+- Tests (Vitest + coverage): `pnpm test`
+- Update snapshots: `pnpm snapshot`
+- Preview example generation (build then generate in sample app): `pnpm preview:react`, `pnpm preview:nextjs`, `pnpm preview:tanstack-router`
+- Release (bumpp + git-ensure): `pnpm release`
+- Docs (Astro) lives under docs/; run from that workspace if needed.
\ No newline at end of file
diff --git a/.serena/memories/task_completion.md b/.serena/memories/task_completion.md
new file mode 100644
index 0000000..43c80eb
--- /dev/null
+++ b/.serena/memories/task_completion.md
@@ -0,0 +1,5 @@
+# What to run before finishing a task
+- Run `pnpm lint` (Biome) to ensure style/lint compliance.
+- Run `pnpm test` for Vitest with coverage (or `pnpm snapshot` if snapshots changed intentionally).
+- Run `pnpm build` to confirm the generator compiles to dist.
+- If touching example outputs, rerun the relevant `preview:*` command to verify generation still works.
\ No newline at end of file
diff --git a/.serena/memories/tech_stack_and_structure.md b/.serena/memories/tech_stack_and_structure.md
new file mode 100644
index 0000000..055653b
--- /dev/null
+++ b/.serena/memories/tech_stack_and_structure.md
@@ -0,0 +1,5 @@
+# Tech stack and structure
+- Runtime/tooling: Node (>=14), pnpm (>=9), TypeScript (strict, NodeNext). Uses ts-morph and TypeScript factory APIs for codegen. Tests: Vitest. Lint/format: Biome. Bundling/CLI output in `dist/` via `tsc`.
+- Source layout: `src/` contains CLI/generator pieces (generate.mts, createSource.mts, createImports.mts, createExports.mts, format.mts, service.mts, etc.). `tests/` holds Vitest suites. `examples/` has sample apps per framework; `docs/` uses Astro for docs site. Built artifacts land in `dist/`.
+- Config: `tsconfig.json` sets strict + ESNext + DOM libs, NodeNext module resolution. `biome.json` enables formatter/linter with 2-space indent and import organization; ignores dist/examples build outputs.
+- Scripts of interest (package.json): build via `pnpm build` (rimraf dist && tsc), lint via `pnpm lint` (biome check), tests via `pnpm test` (vitest --coverage.enabled true), preview generators under examples (preview:*), release uses bumpp/git-ensure.
\ No newline at end of file
diff --git a/.serena/project.yml b/.serena/project.yml
new file mode 100644
index 0000000..b5cc3fe
--- /dev/null
+++ b/.serena/project.yml
@@ -0,0 +1,84 @@
+# list of languages for which language servers are started; choose from:
+# al bash clojure cpp csharp csharp_omnisharp
+# dart elixir elm erlang fortran go
+# haskell java julia kotlin lua markdown
+# nix perl php python python_jedi r
+# rego ruby ruby_solargraph rust scala swift
+# terraform typescript typescript_vts yaml zig
+# Note:
+# - For C, use cpp
+# - For JavaScript, use typescript
+# Special requirements:
+# - csharp: Requires the presence of a .sln file in the project folder.
+# When using multiple languages, the first language server that supports a given file will be used for that file.
+# The first language is the default language and the respective language server will be used as a fallback.
+# Note that when using the JetBrains backend, language servers are not used and this list is correspondingly ignored.
+languages:
+- typescript
+
+# the encoding used by text files in the project
+# For a list of possible encodings, see https://docs.python.org/3.11/library/codecs.html#standard-encodings
+encoding: "utf-8"
+
+# whether to use the project's gitignore file to ignore files
+# Added on 2025-04-07
+ignore_all_files_in_gitignore: true
+
+# list of additional paths to ignore
+# same syntax as gitignore, so you can use * and **
+# Was previously called `ignored_dirs`, please update your config if you are using that.
+# Added (renamed) on 2025-04-07
+ignored_paths: []
+
+# whether the project is in read-only mode
+# If set to true, all editing tools will be disabled and attempts to use them will result in an error
+# Added on 2025-04-18
+read_only: false
+
+# list of tool names to exclude. We recommend not excluding any tools, see the readme for more details.
+# Below is the complete list of tools for convenience.
+# To make sure you have the latest list of tools, and to view their descriptions,
+# execute `uv run scripts/print_tool_overview.py`.
+#
+# * `activate_project`: Activates a project by name.
+# * `check_onboarding_performed`: Checks whether project onboarding was already performed.
+# * `create_text_file`: Creates/overwrites a file in the project directory.
+# * `delete_lines`: Deletes a range of lines within a file.
+# * `delete_memory`: Deletes a memory from Serena's project-specific memory store.
+# * `execute_shell_command`: Executes a shell command.
+# * `find_referencing_code_snippets`: Finds code snippets in which the symbol at the given location is referenced.
+# * `find_referencing_symbols`: Finds symbols that reference the symbol at the given location (optionally filtered by type).
+# * `find_symbol`: Performs a global (or local) search for symbols with/containing a given name/substring (optionally filtered by type).
+# * `get_current_config`: Prints the current configuration of the agent, including the active and available projects, tools, contexts, and modes.
+# * `get_symbols_overview`: Gets an overview of the top-level symbols defined in a given file.
+# * `initial_instructions`: Gets the initial instructions for the current project.
+# Should only be used in settings where the system prompt cannot be set,
+# e.g. in clients you have no control over, like Claude Desktop.
+# * `insert_after_symbol`: Inserts content after the end of the definition of a given symbol.
+# * `insert_at_line`: Inserts content at a given line in a file.
+# * `insert_before_symbol`: Inserts content before the beginning of the definition of a given symbol.
+# * `list_dir`: Lists files and directories in the given directory (optionally with recursion).
+# * `list_memories`: Lists memories in Serena's project-specific memory store.
+# * `onboarding`: Performs onboarding (identifying the project structure and essential tasks, e.g. for testing or building).
+# * `prepare_for_new_conversation`: Provides instructions for preparing for a new conversation (in order to continue with the necessary context).
+# * `read_file`: Reads a file within the project directory.
+# * `read_memory`: Reads the memory with the given name from Serena's project-specific memory store.
+# * `remove_project`: Removes a project from the Serena configuration.
+# * `replace_lines`: Replaces a range of lines within a file with new content.
+# * `replace_symbol_body`: Replaces the full definition of a symbol.
+# * `restart_language_server`: Restarts the language server, may be necessary when edits not through Serena happen.
+# * `search_for_pattern`: Performs a search for a pattern in the project.
+# * `summarize_changes`: Provides instructions for summarizing the changes made to the codebase.
+# * `switch_modes`: Activates modes by providing a list of their names
+# * `think_about_collected_information`: Thinking tool for pondering the completeness of collected information.
+# * `think_about_task_adherence`: Thinking tool for determining whether the agent is still on track with the current task.
+# * `think_about_whether_you_are_done`: Thinking tool for determining whether the task is truly completed.
+# * `write_memory`: Writes a named memory (for future reference) to Serena's project-specific memory store.
+excluded_tools: []
+
+# initial prompt for the project. It will always be given to the LLM upon activating the project
+# (contrary to the memories, which are loaded on demand).
+initial_prompt: ""
+
+project_name: "openapi-react-query-codegen"
+included_optional_tools: []
diff --git a/AGENTS.md b/AGENTS.md
new file mode 100644
index 0000000..f5ebc15
--- /dev/null
+++ b/AGENTS.md
@@ -0,0 +1,33 @@
+# Repository Guidelines
+
+## Project Structure & Module Organization
+- Source code lives in `src/` (CLI entry `cli.mts`, generator pipeline `generate.mts`, codegen helpers like `createSource.mts`, `createImports.mts`, `createExports.mts`, `service.mts`, formatting in `format.mts`).
+- Tests reside in `tests/` (Vitest).
+- Example apps under `examples/` (React/Next.js/TanStack Router) consume the generated client.
+- Docs site in `docs/` (Astro). Build artifacts output to `dist/`.
+
+## Build, Test, and Development Commands
+- Install: `pnpm install`
+- Build generator: `pnpm build` (cleans `dist/`, runs `tsc`).
+- Lint/format check: `pnpm lint` (Biome). Auto-fix: `pnpm lint:fix`.
+- Tests: `pnpm test` (Vitest with coverage). Snapshots: `pnpm snapshot`.
+- Preview generation into examples: `pnpm preview:react`, `pnpm preview:nextjs`, `pnpm preview:tanstack-router`.
+
+## Coding Style & Naming Conventions
+- Language: TypeScript (strict, ESNext, NodeNext). Keep code in modules (`.mts`), output compiled to `dist/`.
+- Formatting/linting via Biome: 2-space indent, double quotes, trailing commas, organized imports. Run formatters before committing.
+- Generated outputs include a header comment with package version; preserve this when modifying generation.
+- Prefer descriptive function names and explicit types; avoid implicit `any`.
+
+## Testing Guidelines
+- Framework: Vitest. Coverage enabled by default.
+- Place tests in `tests/`; mirror generator behavior with snapshot tests where helpful.
+- After generator changes, run tests and consider regenerating example outputs to manually diff.
+
+## Commit & Pull Request Guidelines
+- Commits: clear, descriptive messages (e.g., `fix: align imports for generated queries`, `chore: update ts-morph config`). Avoid bundling unrelated changes.
+- Pull requests: include summary of changes, affected areas (e.g., codegen output, docs, examples), and test commands run. Link issues when applicable. Add before/after notes or sample generated snippets if behavior changes.
+
+## Agent-Specific Notes
+- Use AST-aware paths (ts-morph/TypeScript factory) when editing generators to keep output structurally valid.
+- Respect ignore patterns in `biome.json` and avoid checking in `dist/` or example-generated artifacts unless explicitly intended.
diff --git a/CLAUDE.md b/CLAUDE.md
new file mode 100644
index 0000000..98cfcb6
--- /dev/null
+++ b/CLAUDE.md
@@ -0,0 +1,73 @@
+# CLAUDE.md
+
+This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
+
+## Project Overview
+
+OpenAPI React Query Codegen generates React Query (TanStack Query) hooks from OpenAPI specifications. It uses `@hey-api/openapi-ts` to generate TypeScript clients and then creates additional query/mutation hooks on top.
+
+## Commands
+
+```bash
+# Build
+npm run build
+
+# Run tests with coverage
+npm test
+
+# Run a single test file
+npx vitest tests/generate.test.ts
+
+# Update snapshots
+npm run snapshot
+
+# Lint
+npm run lint
+npm run lint:fix
+
+# Preview generated output in example apps
+npm run preview:react
+npm run preview:nextjs
+npm run preview:tanstack-router
+```
+
+## Architecture
+
+### Code Generation Pipeline
+
+1. **CLI Entry** (`src/cli.mts`): Parses command-line options using Commander
+2. **Generate** (`src/generate.mts`): Orchestrates the generation process:
+ - Calls `@hey-api/openapi-ts` to generate base TypeScript client in `openapi/requests/`
+ - Calls `createSource()` to generate React Query hooks in `openapi/queries/`
+3. **Service Parsing** (`src/service.mts`): Uses ts-morph to parse the generated `services.gen.ts` file and extract function descriptions (method name, HTTP method, JSDoc, etc.)
+4. **Export Creation** (`src/createExports.mts`): Routes methods to appropriate generators based on HTTP method:
+ - GET methods → `createUseQuery()` (queries, suspense queries, infinite queries)
+ - POST/PUT/PATCH/DELETE → `createUseMutation()`
+5. **Hook Generators**:
+ - `src/createUseQuery.mts`: Generates `useQuery`, `useSuspenseQuery`, and `useInfiniteQuery` hooks
+ - `src/createUseMutation.mts`: Generates `useMutation` hooks
+ - `src/createPrefetchOrEnsure.mts`: Generates `prefetchQuery` and `ensureQueryData` functions
+6. **Print** (`src/print.mts`): Writes generated TypeScript to files
+
+### Generated Output Structure
+
+The tool generates files in `openapi/queries/`:
+- `common.ts`: Shared types, query keys, and key functions
+- `queries.ts`: `useQuery` and `useMutation` hooks
+- `suspense.ts`: `useSuspenseQuery` hooks
+- `infiniteQueries.ts`: `useInfiniteQuery` hooks
+- `prefetch.ts`: `prefetchQuery` functions
+- `ensureQueryData.ts`: `ensureQueryData` functions
+- `index.ts`: Re-exports
+
+### Key Dependencies
+
+- **ts-morph**: AST manipulation for reading the generated service file
+- **typescript**: AST creation for generating new TypeScript code
+- **@hey-api/openapi-ts**: Base OpenAPI to TypeScript client generator
+
+## Testing
+
+Tests use Vitest with snapshot testing. Test files in `tests/` correspond to source modules. The `tests/utils.ts` file provides a shared `project` fixture using `examples/petstore.yaml`.
+
+Coverage thresholds: 95% lines/functions/statements, 90% branches.
diff --git a/docs/src/content/docs/examples/tanstack-router.md b/docs/src/content/docs/examples/tanstack-router.md
index 4bbc0c1..7b09dbb 100644
--- a/docs/src/content/docs/examples/tanstack-router.md
+++ b/docs/src/content/docs/examples/tanstack-router.md
@@ -1,6 +1,148 @@
---
title: TanStack Router Example
-description: A simple example of using TanStack Router with OpenAPI React Query Codegen.
+description: Using TanStack Router with OpenAPI React Query Codegen for data loading and prefetching.
---
-Example of using Next.js can be found in the [`examples/tanstack-router-app`](https://github.com/7nohe/openapi-react-query-codegen/tree/main/examples/tanstack-router-app) directory of the repository.
+Example of using TanStack Router can be found in the [`examples/tanstack-router-app`](https://github.com/7nohe/openapi-react-query-codegen/tree/main/examples/tanstack-router-app) directory of the repository.
+
+## Route Data Loading
+
+Use the generated `ensureQueryData` functions in your route loaders to prefetch data before the route renders:
+
+```tsx
+// routes/pets.$petId.tsx
+import { createFileRoute } from "@tanstack/react-router";
+import { ensureUseFindPetByIdData } from "../openapi/queries/ensureQueryData";
+import { useFindPetById } from "../openapi/queries";
+import { queryClient } from "../queryClient";
+
+export const Route = createFileRoute("/pets/$petId")({
+ loader: ({ params }) =>
+ ensureUseFindPetByIdData(queryClient, {
+ path: { petId: Number(params.petId) },
+ }),
+ component: PetDetail,
+});
+
+function PetDetail() {
+ const { petId } = Route.useParams();
+ const { data } = useFindPetById({ path: { petId: Number(petId) } });
+
+ return
{data?.name}
;
+}
+```
+
+### For SSR / TanStack Start
+
+When using SSR or TanStack Start, pass `queryClient` from the router context:
+
+```tsx
+export const Route = createFileRoute("/pets/$petId")({
+ loader: ({ context, params }) =>
+ ensureUseFindPetByIdData(context.queryClient, {
+ path: { petId: Number(params.petId) },
+ }),
+ component: PetDetail,
+});
+```
+
+### Operations Without Path Parameters
+
+```tsx
+import { ensureUseFindPetsData } from "../openapi/queries/ensureQueryData";
+
+export const Route = createFileRoute("/pets")({
+ loader: () => ensureUseFindPetsData(queryClient),
+ component: PetList,
+});
+```
+
+## Prefetching on Hover/Touch
+
+Use `prefetchQuery` functions for custom prefetch triggers:
+
+```tsx
+import { prefetchUseFindPetById } from "../openapi/queries/prefetch";
+import { queryClient } from "../queryClient";
+
+function PetLink({ petId }: { petId: number }) {
+ const handlePrefetch = () => {
+ prefetchUseFindPetById(queryClient, { path: { petId } });
+ };
+
+ return (
+
+ View Pet
+
+ );
+}
+```
+
+## Router Configuration
+
+### External Cache Settings
+
+When using TanStack Query as an external cache, configure the router to delegate cache freshness to React Query:
+
+```tsx
+import { createRouter } from "@tanstack/react-router";
+import { routeTree } from "./routeTree.gen";
+
+const router = createRouter({
+ routeTree,
+ defaultPreloadStaleTime: 0, // Let React Query handle cache freshness
+});
+```
+
+### Link Preloading
+
+TanStack Router's ` ` component supports intent-based preloading:
+
+```tsx
+
+ View Pet
+
+```
+
+Or set it globally:
+
+```tsx
+const router = createRouter({
+ routeTree,
+ defaultPreload: "intent",
+ defaultPreloadStaleTime: 0,
+});
+```
+
+When using `preload="intent"`, the router automatically calls the route's `loader` on hover/touch.
+
+## Important Notes
+
+### Router Params Are Strings
+
+TanStack Router params are always strings. You must parse them to the correct type:
+
+```tsx
+loader: ({ params }) =>
+ ensureUseFindPetByIdData(queryClient, {
+ path: { petId: Number(params.petId) }, // Convert string to number
+ }),
+```
+
+For type-safe parsing, consider using TanStack Router's `parseParams`:
+
+```tsx
+export const Route = createFileRoute("/pets/$petId")({
+ parseParams: (params) => ({
+ petId: Number(params.petId),
+ }),
+ loader: ({ params }) =>
+ ensureUseFindPetByIdData(queryClient, {
+ path: { petId: params.petId }, // Already a number
+ }),
+});
+```
diff --git a/docs/src/content/docs/guides/cli-options.mdx b/docs/src/content/docs/guides/cli-options.mdx
index 5ac41a8..9c82745 100644
--- a/docs/src/content/docs/guides/cli-options.mdx
+++ b/docs/src/content/docs/guides/cli-options.mdx
@@ -39,6 +39,8 @@ The available options are:
- `@hey-api/client-fetch`
- `@hey-api/client-axios`
+Note: these client plugins are provided by `@hey-api/openapi-ts`, so you don't need to install `@hey-api/client-fetch` or `@hey-api/client-axios` separately. If you use the axios client, you still need to add `axios` to your project dependencies.
+
More details about the clients can be found in [Hey API Documentation](https://heyapi.vercel.app/openapi-ts/clients.html)
### --format \
@@ -88,4 +90,3 @@ The available options are:
- `json`
- `form`
-
diff --git a/docs/src/content/docs/guides/introduction.mdx b/docs/src/content/docs/guides/introduction.mdx
index 283df3b..636fc49 100644
--- a/docs/src/content/docs/guides/introduction.mdx
+++ b/docs/src/content/docs/guides/introduction.mdx
@@ -177,7 +177,7 @@ export default App;
- ensureQueryData.ts Generated ensureQueryData functions
- queries.ts Generated query/mutation hooks
- infiniteQueries.ts Generated infinite query hooks
- - suspenses.ts Generated suspense hooks
+ - suspense.ts Generated suspense hooks
- prefetch.ts Generated prefetch functions
- requests Output code generated by `@hey-api/openapi-ts`
diff --git a/docs/src/content/docs/index.mdx b/docs/src/content/docs/index.mdx
index 40ce37e..b8593af 100644
--- a/docs/src/content/docs/index.mdx
+++ b/docs/src/content/docs/index.mdx
@@ -24,7 +24,7 @@ import { Card, CardGrid } from '@astrojs/starlight/components';
Generates custom react hooks that use React(TanStack) Query's useQuery, useSuspenseQuery, useMutation and useInfiniteQuery hooks.
- Generates custom functions that use React Query's `ensureQueryData` and `prefetchQuery` functions to integrate into frameworks like Next.js and Remix.
+ Generates custom functions that use React Query's `ensureQueryData` and `prefetchQuery` functions to integrate into frameworks like Next.js, Remix, and TanStack Router.
Generates pure TypeScript clients generated by [@hey-api/openapi-ts](https://github.com/hey-api/openapi-ts) in case you still want to do type-safe API calls without React Query.
diff --git a/examples/nextjs-app/fetchClient.ts b/examples/nextjs-app/fetchClient.ts
index 0ccb3c7..dd8cbe5 100644
--- a/examples/nextjs-app/fetchClient.ts
+++ b/examples/nextjs-app/fetchClient.ts
@@ -1,4 +1,4 @@
-import { client } from "@/openapi/requests/services.gen";
+import { client } from "@/openapi/requests/client.gen";
client.setConfig({
baseUrl: "http://localhost:4010",
diff --git a/examples/react-app/package.json b/examples/react-app/package.json
index db1d4c7..474664a 100644
--- a/examples/react-app/package.json
+++ b/examples/react-app/package.json
@@ -13,7 +13,6 @@
"test:generated": "tsc -p ./tsconfig.json --noEmit"
},
"dependencies": {
- "@hey-api/client-axios": "^0.2.7",
"@tanstack/react-query": "^5.59.13",
"@tanstack/react-query-devtools": "^5.32.1",
"axios": "^1.7.7",
diff --git a/examples/react-app/src/App.tsx b/examples/react-app/src/App.tsx
index 68f4bf3..53d8bf8 100644
--- a/examples/react-app/src/App.tsx
+++ b/examples/react-app/src/App.tsx
@@ -1,7 +1,6 @@
import "./App.css";
import { useState } from "react";
-import { createClient } from "@hey-api/client-fetch";
import {
UseFindPetsKeyFn,
useAddPet,
@@ -9,11 +8,12 @@ import {
useGetNotDefined,
usePostNotDefined,
} from "../openapi/queries";
+import { client } from "../openapi/requests/client.gen";
import { SuspenseParent } from "./components/SuspenseParent";
import { queryClient } from "./queryClient";
function App() {
- createClient({ baseUrl: "http://localhost:4010" });
+ client.setConfig({ baseUrl: "http://localhost:4010" });
const [tags, _setTags] = useState([]);
const [limit, _setLimit] = useState(10);
diff --git a/examples/react-router-6-app/package.json b/examples/react-router-6-app/package.json
index 761c156..32dac7b 100644
--- a/examples/react-router-6-app/package.json
+++ b/examples/react-router-6-app/package.json
@@ -13,10 +13,8 @@
"test:generated": "tsc -p ./tsconfig.json --noEmit"
},
"dependencies": {
- "@hey-api/client-axios": "^0.2.7",
"@tanstack/react-query": "^5.59.13",
"@tanstack/react-query-devtools": "^5.32.1",
- "axios": "^1.7.7",
"form-data": "~4.0.0",
"react": "^18.3.1",
"react-dom": "^18.3.1",
diff --git a/examples/react-router-6-app/src/axios.ts b/examples/react-router-6-app/src/axios.ts
index f1c0877..8c5b73f 100644
--- a/examples/react-router-6-app/src/axios.ts
+++ b/examples/react-router-6-app/src/axios.ts
@@ -1,4 +1,4 @@
-import { client } from "../openapi/requests/services.gen";
+import { client } from "../openapi/requests/client.gen";
client.setConfig({
baseUrl: "http://localhost:4010",
diff --git a/examples/react-router-7-app/fetchClient.ts b/examples/react-router-7-app/fetchClient.ts
index 6fc2ae9..3f96c09 100644
--- a/examples/react-router-7-app/fetchClient.ts
+++ b/examples/react-router-7-app/fetchClient.ts
@@ -1,4 +1,4 @@
-import { client } from "./openapi/requests/services.gen";
+import { client } from "./openapi/requests/client.gen";
client.setConfig({
baseUrl: "http://localhost:4010",
diff --git a/examples/tanstack-router-app/src/fetchClient.ts b/examples/tanstack-router-app/src/fetchClient.ts
index 7497a3b..99d80c9 100644
--- a/examples/tanstack-router-app/src/fetchClient.ts
+++ b/examples/tanstack-router-app/src/fetchClient.ts
@@ -1,4 +1,4 @@
-import { client } from "../openapi/requests/services.gen";
+import { client } from "../openapi/requests/client.gen";
client.setConfig({
baseUrl: "http://localhost:4010",
diff --git a/package.json b/package.json
index c89bb46..3fc76a8 100644
--- a/package.json
+++ b/package.json
@@ -47,8 +47,7 @@
"license": "MIT",
"author": "Daiki Urata (@7nohe)",
"dependencies": {
- "@hey-api/client-fetch": "0.4.0",
- "@hey-api/openapi-ts": "0.53.8",
+ "@hey-api/openapi-ts": "0.73.0",
"cross-spawn": "^7.0.3"
},
"devDependencies": {
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index b199c6b..572a10b 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -8,12 +8,9 @@ importers:
.:
dependencies:
- '@hey-api/client-fetch':
- specifier: 0.4.0
- version: 0.4.0
'@hey-api/openapi-ts':
- specifier: 0.53.8
- version: 0.53.8(magicast@0.3.5)(typescript@5.6.2)
+ specifier: 0.73.0
+ version: 0.73.0(magicast@0.3.5)(typescript@5.6.2)
cross-spawn:
specifier: ^7.0.3
version: 7.0.3
@@ -112,9 +109,6 @@ importers:
examples/react-app:
dependencies:
- '@hey-api/client-axios':
- specifier: ^0.2.7
- version: 0.2.7(axios@1.7.7)
'@tanstack/react-query':
specifier: ^5.59.13
version: 5.59.13(react@18.3.1)
@@ -161,18 +155,12 @@ importers:
examples/react-router-6-app:
dependencies:
- '@hey-api/client-axios':
- specifier: ^0.2.7
- version: 0.2.7(axios@1.7.7)
'@tanstack/react-query':
specifier: ^5.59.13
version: 5.59.13(react@18.3.1)
'@tanstack/react-query-devtools':
specifier: ^5.32.1
version: 5.45.0(@tanstack/react-query@5.59.13(react@18.3.1))(react@18.3.1)
- axios:
- specifier: ^1.7.7
- version: 1.7.7
form-data:
specifier: ~4.0.0
version: 4.0.0
@@ -331,10 +319,6 @@ packages:
resolution: {integrity: sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw==}
engines: {node: '>=6.0.0'}
- '@apidevtools/json-schema-ref-parser@11.7.0':
- resolution: {integrity: sha512-pRrmXMCwnmrkS3MLgAIW5dXRzeTv6GLjkjb4HmxNnvAKXN1Nfzp4KmGADBQvlVUcqi+a5D+hfGDLLnd5NnYxog==}
- engines: {node: '>= 16'}
-
'@astrojs/check@0.9.4':
resolution: {integrity: sha512-IOheHwCtpUfvogHHsvu0AbeRZEnjJg3MopdLddkJE70mULItS/Vh37BHcI00mcOJcH1vhD3odbpvWokpxam7xA==}
hasBin: true
@@ -1295,20 +1279,16 @@ packages:
resolution: {integrity: sha512-8YXBE2ZcU/pImVOHX7MWrSR/X5up7t6rPWZlk34RwZEcdr3ua6X+32pSd6XuOQRN+vbuvYNfA6iey8NbrjuMFQ==}
engines: {node: '>=14.0.0', npm: '>=6.0.0'}
- '@hey-api/client-axios@0.2.7':
- resolution: {integrity: sha512-3691It5Bt87/kS1K5+vPt6RdSk/gCnkiaEgjrasgRWKHktJ727f+7QWs+KfmCTSGeXf5ODTu7zNOBwzVkLzGkA==}
- peerDependencies:
- axios: '>= 1.0.0 < 2'
-
- '@hey-api/client-fetch@0.4.0':
- resolution: {integrity: sha512-T8T3yCl2+AiVVDP6tvfnU/rXOkEHddMTOYCZXUVbydj7URVErh5BelIa8UWBkFYZBP2/mi2nViScNhe9eBolPw==}
+ '@hey-api/json-schema-ref-parser@1.0.6':
+ resolution: {integrity: sha512-yktiFZoWPtEW8QKS65eqKwA5MTKp88CyiL8q72WynrBs/73SAaxlSWlA2zW/DZlywZ5hX1OYzrCC0wFdvO9c2w==}
+ engines: {node: '>= 16'}
- '@hey-api/openapi-ts@0.53.8':
- resolution: {integrity: sha512-UbiaIq+JNgG00N/iWYk+LSivOBgWsfGxEHDleWEgQcQr3q7oZJTKL8oH87+KkFDDbUngm1g8lnKI/zLdu1aElQ==}
- engines: {node: ^18.0.0 || >=20.0.0}
+ '@hey-api/openapi-ts@0.73.0':
+ resolution: {integrity: sha512-sUscR3OIGW0k9U//28Cu6BTp3XaogWMDORj9H+5Du9E5AvTT7LZbCEDvkLhebFOPkp2cZAQfd66HiZsiwssBcQ==}
+ engines: {node: ^18.18.0 || ^20.9.0 || >=22.10.0}
hasBin: true
peerDependencies:
- typescript: ^5.x
+ typescript: ^5.5.3
'@img/sharp-darwin-arm64@0.33.5':
resolution: {integrity: sha512-UT4p+iz/2H4twwAoLCqfA9UH5pI6DggwKEGuaPy7nCVQ8ZsiY5PIcrRvD1DzuY3qYL07NtIQcWnBSY/heikIFQ==}
@@ -2183,6 +2163,10 @@ packages:
ansi-align@3.0.1:
resolution: {integrity: sha512-IOfwwBF5iczOjp/WeY4YxyjqAFMQoZufdQWDd19SEExbVLNXqvpzSJ/M7Za4/sCPmQ0+GRquoA7bGcINcxew6w==}
+ ansi-colors@4.1.3:
+ resolution: {integrity: sha512-/6w/C21Pm1A7aZitlI5Ni/2J6FFQN8i1Cvz3kHABAAbw93v/NlvKdVOqz7CCWz/3iv/JplRSEEZ83XION15ovw==}
+ engines: {node: '>=6'}
+
ansi-regex@5.0.1:
resolution: {integrity: sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==}
engines: {node: '>=8'}
@@ -2372,6 +2356,10 @@ packages:
buffer@5.7.1:
resolution: {integrity: sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==}
+ bundle-name@4.1.0:
+ resolution: {integrity: sha512-tjwM5exMg6BGRI+kNmTntNsvdZS1X8BFYS6tnJ2hdH0kVxM6/eVZ2xy+FqStSWvYmtfFMDLIxurorHwDKfDz5Q==}
+ engines: {node: '>=18'}
+
busboy@1.6.0:
resolution: {integrity: sha512-8SFQbg/0hQ9xy3UNTB0YEnsNBbWfhf7RtnzpL7TkBiTBRfrQ9Fxcnz7VJsleJpyp6rVLvXiuORqjlHi5q+PYuA==}
engines: {node: '>=10.16.0'}
@@ -2528,6 +2516,10 @@ packages:
color-string@1.9.1:
resolution: {integrity: sha512-shrVawQFojnZv6xM40anx4CkoDP+fZsw/ZerEMsW/pyzsRbElpsL/DBVW7q3ExxwusdNXI3lXpuhEZkzs8p5Eg==}
+ color-support@1.1.3:
+ resolution: {integrity: sha512-qiBjkpbMLO/HL68y+lh4q0/O1MZFj2RX6X/KmMa3+gJD3z+WwI1ZzDHysvqHGS3mP6mznPckpXmw1nI9cJjyRg==}
+ hasBin: true
+
color@4.2.3:
resolution: {integrity: sha512-1rXeuUUiGGrykh+CeBdu5Ie7OJwinCgQY0bc7GCRxy5xVHy+moaqkpL/jqQq0MtQOeYcrqEz4abc5f0KtU7W4A==}
engines: {node: '>=12.5.0'}
@@ -2543,6 +2535,10 @@ packages:
resolution: {integrity: sha512-Vw8qHK3bZM9y/P10u3Vib8o/DdkvA2OtPtZvD871QKjy74Wj1WSKFILMPRPSdUSx5RFK1arlJzEtA4PkFgnbuA==}
engines: {node: '>=18'}
+ commander@13.0.0:
+ resolution: {integrity: sha512-oPYleIY8wmTVzkvQq10AEok6YcTC4sRUBl8F9gVuwchGVUCTbl/vhLTaQqutuuySYOsu8YTgV+OxKc/8Yvx+mQ==}
+ engines: {node: '>=18'}
+
commander@2.20.3:
resolution: {integrity: sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==}
@@ -2692,10 +2688,22 @@ packages:
resolution: {integrity: sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==}
engines: {node: '>=4.0.0'}
+ default-browser-id@5.0.1:
+ resolution: {integrity: sha512-x1VCxdX4t+8wVfd1so/9w+vQ4vx7lKd2Qp5tDRutErwmR85OgmfX7RlLRMWafRMY7hbEiXIbudNrjOAPa/hL8Q==}
+ engines: {node: '>=18'}
+
+ default-browser@5.4.0:
+ resolution: {integrity: sha512-XDuvSq38Hr1MdN47EDvYtx3U0MTqpCEn+F6ft8z2vYDzMrvQhVp0ui9oQdqW3MvK3vqUETglt1tVGgjLuJ5izg==}
+ engines: {node: '>=18'}
+
define-data-property@1.1.4:
resolution: {integrity: sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==}
engines: {node: '>= 0.4'}
+ define-lazy-prop@3.0.0:
+ resolution: {integrity: sha512-N+MeXYoqr3pOgn8xfyRPREN7gHakLYjhsHhWGT3fWAiL4IkAt0iDw14QiiEm2bE30c5XX5q0FtAA3CK5f9/BUg==}
+ engines: {node: '>=12'}
+
define-properties@1.2.1:
resolution: {integrity: sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg==}
engines: {node: '>= 0.4'}
@@ -4185,9 +4193,6 @@ packages:
resolution: {integrity: sha512-byy+U7gp+FVwmyzKPYhW2h5l3crpmGsxl7X2s8y43IgxvG4g3QZ6CffDtsNQy1WsmZpQbO+ybo0AlW7TY6DcBQ==}
engines: {node: '>= 0.4'}
- ohash@1.1.3:
- resolution: {integrity: sha512-zuHHiGTYTA1sYJ/wZN+t5HKZaH23i4yI1HMwbuXm24Nid7Dv0KcuRlKoNKS9UNfAVSBlnGLcuQrnOKWOZoEGaw==}
-
ohash@1.1.4:
resolution: {integrity: sha512-FlDryZAahJmEF3VR3w1KogSEdWX3WhA5GPakFx4J81kEAiHyLMpdLLElS8n8dfNadMgAne/MywcvmogzscVt4g==}
@@ -4220,6 +4225,10 @@ packages:
ono@4.0.11:
resolution: {integrity: sha512-jQ31cORBFE6td25deYeD80wxKBMj+zBmHTrVxnc6CKhx8gho6ipmWM5zj/oeoqioZ99yqBls9Z/9Nss7J26G2g==}
+ open@10.1.2:
+ resolution: {integrity: sha512-cxN6aIDPz6rm8hbebcP7vrQNhvRcveZoJU72Y7vskh4oIm+BZwBECnx5nTmrlres1Qapvx27Qo1Auukpf8PKXw==}
+ engines: {node: '>=18'}
+
openapi3-ts@2.0.2:
resolution: {integrity: sha512-TxhYBMoqx9frXyOgnRHufjQfPXomTIHYKhSKJ6jHfj13kS8OEIhvmE8CTuQyKtjjWttAjX5DPxM1vmalEpo8Qw==}
@@ -4751,6 +4760,10 @@ packages:
engines: {node: '>=18.0.0', npm: '>=8.0.0'}
hasBin: true
+ run-applescript@7.1.0:
+ resolution: {integrity: sha512-DPe5pVFaAsinSaV6QjQ6gdiedWDcRCbUuiQfQa2wmWV7+xC9bGulGI8+TdRmoFkAPaBXk8CrAbnlY2ISniJ47Q==}
+ engines: {node: '>=18'}
+
run-parallel@1.2.0:
resolution: {integrity: sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==}
@@ -5776,12 +5789,6 @@ snapshots:
'@jridgewell/gen-mapping': 0.3.5
'@jridgewell/trace-mapping': 0.3.25
- '@apidevtools/json-schema-ref-parser@11.7.0':
- dependencies:
- '@jsdevtools/ono': 7.1.3
- '@types/json-schema': 7.0.15
- js-yaml: 4.1.0
-
'@astrojs/check@0.9.4(prettier@3.3.3)(typescript@5.6.3)':
dependencies:
'@astrojs/language-server': 2.15.0(prettier@3.3.3)(typescript@5.6.3)
@@ -6721,18 +6728,22 @@ snapshots:
'@faker-js/faker@6.3.1': {}
- '@hey-api/client-axios@0.2.7(axios@1.7.7)':
+ '@hey-api/json-schema-ref-parser@1.0.6':
dependencies:
- axios: 1.7.7
-
- '@hey-api/client-fetch@0.4.0': {}
+ '@jsdevtools/ono': 7.1.3
+ '@types/json-schema': 7.0.15
+ js-yaml: 4.1.0
+ lodash: 4.17.21
- '@hey-api/openapi-ts@0.53.8(magicast@0.3.5)(typescript@5.6.2)':
+ '@hey-api/openapi-ts@0.73.0(magicast@0.3.5)(typescript@5.6.2)':
dependencies:
- '@apidevtools/json-schema-ref-parser': 11.7.0
+ '@hey-api/json-schema-ref-parser': 1.0.6
+ ansi-colors: 4.1.3
c12: 2.0.1(magicast@0.3.5)
- commander: 12.1.0
+ color-support: 1.1.3
+ commander: 13.0.0
handlebars: 4.7.8
+ open: 10.1.2
typescript: 5.6.2
transitivePeerDependencies:
- magicast
@@ -7752,6 +7763,8 @@ snapshots:
dependencies:
string-width: 4.2.3
+ ansi-colors@4.1.3: {}
+
ansi-regex@5.0.1: {}
ansi-regex@6.0.1: {}
@@ -8047,6 +8060,10 @@ snapshots:
base64-js: 1.5.1
ieee754: 1.2.1
+ bundle-name@4.1.0:
+ dependencies:
+ run-applescript: 7.1.0
+
busboy@1.6.0:
dependencies:
streamsearch: 1.1.0
@@ -8204,6 +8221,8 @@ snapshots:
color-name: 1.1.4
simple-swizzle: 0.2.2
+ color-support@1.1.3: {}
+
color@4.2.3:
dependencies:
color-convert: 2.0.1
@@ -8217,6 +8236,8 @@ snapshots:
commander@12.1.0: {}
+ commander@13.0.0: {}
+
commander@2.20.3:
optional: true
@@ -8352,12 +8373,21 @@ snapshots:
deep-extend@0.6.0: {}
+ default-browser-id@5.0.1: {}
+
+ default-browser@5.4.0:
+ dependencies:
+ bundle-name: 4.1.0
+ default-browser-id: 5.0.1
+
define-data-property@1.1.4:
dependencies:
es-define-property: 1.0.0
es-errors: 1.3.0
gopd: 1.0.1
+ define-lazy-prop@3.0.0: {}
+
define-properties@1.2.1:
dependencies:
define-data-property: 1.1.4
@@ -8872,7 +8902,7 @@ snapshots:
defu: 6.1.4
node-fetch-native: 1.6.4
nypm: 0.3.8
- ohash: 1.1.3
+ ohash: 1.1.4
pathe: 1.1.2
tar: 6.2.1
@@ -10324,8 +10354,6 @@ snapshots:
has-symbols: 1.0.3
object-keys: 1.1.1
- ohash@1.1.3: {}
-
ohash@1.1.4: {}
on-finished@2.3.0:
@@ -10358,6 +10386,13 @@ snapshots:
dependencies:
format-util: 1.0.5
+ open@10.1.2:
+ dependencies:
+ default-browser: 5.4.0
+ define-lazy-prop: 3.0.0
+ is-inside-container: 1.0.0
+ is-wsl: 3.1.0
+
openapi3-ts@2.0.2:
dependencies:
yaml: 1.10.2
@@ -11012,6 +11047,8 @@ snapshots:
'@rollup/rollup-win32-x64-msvc': 4.24.0
fsevents: 2.3.3
+ run-applescript@7.1.0: {}
+
run-parallel@1.2.0:
dependencies:
queue-microtask: 1.2.3
diff --git a/src/common.mts b/src/common.mts
index f3cb036..add0533 100644
--- a/src/common.mts
+++ b/src/common.mts
@@ -1,36 +1,17 @@
import type { PathLike } from "node:fs";
import { stat } from "node:fs/promises";
import path from "node:path";
-import type {
- ClassDeclaration,
- ParameterDeclaration,
- SourceFile,
- Type,
- VariableDeclaration,
+import {
+ ArrowFunction,
+ type ClassDeclaration,
+ type ParameterDeclaration,
+ type SourceFile,
+ type VariableDeclaration,
} from "ts-morph";
-import { ArrowFunction } from "ts-morph";
import ts from "typescript";
import type { LimitedUserConfig } from "./cli.mjs";
import { queriesOutputPath, requestsOutputPath } from "./constants.mjs";
-export const TData = ts.factory.createIdentifier("TData");
-export const TError = ts.factory.createIdentifier("TError");
-export const TContext = ts.factory.createIdentifier("TContext");
-
-export const EqualsOrGreaterThanToken = ts.factory.createToken(
- ts.SyntaxKind.EqualsGreaterThanToken,
-);
-
-export const QuestionToken = ts.factory.createToken(
- ts.SyntaxKind.QuestionToken,
-);
-
-export const queryKeyGenericType =
- ts.factory.createTypeReferenceNode("TQueryKey");
-export const queryKeyConstraint = ts.factory.createTypeReferenceNode("Array", [
- ts.factory.createKeywordTypeNode(ts.SyntaxKind.UnknownKeyword),
-]);
-
export const capitalizeFirstLetter = (str: string) => {
return str.charAt(0).toUpperCase() + str.slice(1);
};
@@ -63,7 +44,6 @@ export const getNameFromVariable = (variable: VariableDeclaration) => {
export type FunctionDescription = {
node: SourceFile;
method: VariableDeclaration;
- methodBlock: ts.Block;
httpMethodName: string;
jsDoc: string;
isDeprecated: boolean;
@@ -200,172 +180,3 @@ export function buildRequestsOutputPath(outputPath: string) {
export function buildQueriesOutputPath(outputPath: string) {
return path.join(outputPath, queriesOutputPath);
}
-
-export function getQueryKeyFnName(queryKey: string) {
- return `${capitalizeFirstLetter(queryKey)}Fn`;
-}
-
-/**
- * Create QueryKey/MutationKey exports
- */
-export function createQueryKeyExport({
- methodName,
- queryKey,
-}: {
- methodName: string;
- queryKey: string;
-}) {
- return ts.factory.createVariableStatement(
- [ts.factory.createModifier(ts.SyntaxKind.ExportKeyword)],
- ts.factory.createVariableDeclarationList(
- [
- ts.factory.createVariableDeclaration(
- ts.factory.createIdentifier(queryKey),
- undefined,
- undefined,
- ts.factory.createStringLiteral(
- `${capitalizeFirstLetter(methodName)}`,
- ),
- ),
- ],
- ts.NodeFlags.Const,
- ),
- );
-}
-
-export function createQueryKeyFnExport(
- queryKey: string,
- method: VariableDeclaration,
- type: "query" | "mutation" = "query",
- modelNames: string[] = [],
-) {
- // Mutation keys don't require clientOptions
- const params =
- type === "query"
- ? getRequestParamFromMethod(method, undefined, modelNames)
- : null;
-
- // override key is used to allow the user to override the the queryKey values
- const overrideKey = ts.factory.createParameterDeclaration(
- undefined,
- undefined,
- ts.factory.createIdentifier(type === "query" ? "queryKey" : "mutationKey"),
- QuestionToken,
- ts.factory.createTypeReferenceNode("Array", []),
- );
-
- return ts.factory.createVariableStatement(
- [ts.factory.createModifier(ts.SyntaxKind.ExportKeyword)],
- ts.factory.createVariableDeclarationList(
- [
- ts.factory.createVariableDeclaration(
- ts.factory.createIdentifier(getQueryKeyFnName(queryKey)),
- undefined,
- undefined,
- ts.factory.createArrowFunction(
- undefined,
- undefined,
- params ? [params, overrideKey] : [overrideKey],
- undefined,
- EqualsOrGreaterThanToken,
- type === "query"
- ? queryKeyFn(queryKey, method)
- : mutationKeyFn(queryKey),
- ),
- ),
- ],
- ts.NodeFlags.Const,
- ),
- );
-}
-
-function queryKeyFn(
- queryKey: string,
- method: VariableDeclaration,
-): ts.Expression {
- return ts.factory.createArrayLiteralExpression(
- [
- ts.factory.createIdentifier(queryKey),
- ts.factory.createSpreadElement(
- ts.factory.createParenthesizedExpression(
- ts.factory.createBinaryExpression(
- ts.factory.createIdentifier("queryKey"),
- ts.factory.createToken(ts.SyntaxKind.QuestionQuestionToken),
- getVariableArrowFunctionParameters(method)
- ? // [...clientOptions]
- ts.factory.createArrayLiteralExpression([
- ts.factory.createIdentifier("clientOptions"),
- ])
- : // []
- ts.factory.createArrayLiteralExpression(),
- ),
- ),
- ),
- ],
- false,
- );
-}
-
-function mutationKeyFn(mutationKey: string): ts.Expression {
- return ts.factory.createArrayLiteralExpression(
- [
- ts.factory.createIdentifier(mutationKey),
- ts.factory.createSpreadElement(
- ts.factory.createParenthesizedExpression(
- ts.factory.createBinaryExpression(
- ts.factory.createIdentifier("mutationKey"),
- ts.factory.createToken(ts.SyntaxKind.QuestionQuestionToken),
- ts.factory.createArrayLiteralExpression(),
- ),
- ),
- ),
- ],
- false,
- );
-}
-
-export function getRequestParamFromMethod(
- method: VariableDeclaration,
- pageParam?: string,
- modelNames: string[] = [],
-) {
- if (!getVariableArrowFunctionParameters(method).length) {
- return null;
- }
- const methodName = getNameFromVariable(method);
-
- const params = getVariableArrowFunctionParameters(method).flatMap((param) => {
- const paramNodes = extractPropertiesFromObjectParam(param);
-
- return paramNodes
- .filter((p) => p.name !== pageParam)
- .map((refParam) => ({
- name: refParam.name,
- // TODO: Client -> Client
- typeName: getShortType(refParam.type?.getText() ?? ""),
- optional: refParam.optional,
- }));
- });
-
- const areAllPropertiesOptional = params.every((param) => param.optional);
-
- return ts.factory.createParameterDeclaration(
- undefined,
- undefined,
- ts.factory.createIdentifier("clientOptions"),
- undefined,
- ts.factory.createTypeReferenceNode(ts.factory.createIdentifier("Options"), [
- ts.factory.createTypeReferenceNode(
- modelNames.includes(`${capitalizeFirstLetter(methodName)}Data`)
- ? `${capitalizeFirstLetter(methodName)}Data`
- : "unknown",
- ),
- ts.factory.createTypeReferenceNode(ts.factory.createIdentifier("true")),
- ]),
- // if all params are optional, we create an empty object literal
- // so the hook can be called without any parameters
- areAllPropertiesOptional
- ? ts.factory.createObjectLiteralExpression()
- : undefined,
- );
-}
diff --git a/src/constants.mts b/src/constants.mts
index 651e6e8..cd1b751 100644
--- a/src/constants.mts
+++ b/src/constants.mts
@@ -2,7 +2,7 @@ export const defaultOutputPath = "openapi";
export const queriesOutputPath = "queries";
export const requestsOutputPath = "requests";
-export const serviceFileName = "services.gen";
+export const serviceFileName = "sdk.gen";
export const modelsFileName = "types.gen";
export const OpenApiRqFiles = {
diff --git a/src/createExports.mts b/src/createExports.mts
deleted file mode 100644
index 5a82414..0000000
--- a/src/createExports.mts
+++ /dev/null
@@ -1,190 +0,0 @@
-import type { UserConfig } from "@hey-api/openapi-ts";
-import type { Project } from "ts-morph";
-import ts from "typescript";
-import { capitalizeFirstLetter } from "./common.mjs";
-import { modelsFileName } from "./constants.mjs";
-import { createPrefetchOrEnsure } from "./createPrefetchOrEnsure.mjs";
-import { createUseMutation } from "./createUseMutation.mjs";
-import { createUseQuery } from "./createUseQuery.mjs";
-import type { Service } from "./service.mjs";
-
-export const createExports = ({
- service,
- client,
- project,
- pageParam,
- nextPageParam,
- initialPageParam,
-}: {
- service: Service;
- client: UserConfig["client"];
- project: Project;
- pageParam: string;
- nextPageParam: string;
- initialPageParam: string;
-}) => {
- const { methods } = service;
- const methodDataNames = methods.reduce(
- (acc, data) => {
- const methodName = data.method.getName();
- acc[`${capitalizeFirstLetter(methodName)}Data`] = methodName;
- return acc;
- },
- {} as { [key: string]: string },
- );
- const modelsFile = project
- .getSourceFiles?.()
- .find((sourceFile) => sourceFile.getFilePath().includes(modelsFileName));
-
- const modelDeclarations = modelsFile?.getExportedDeclarations();
- const entries = modelDeclarations?.entries();
- const modelNames: string[] = [];
- const paginatableMethods: string[] = [];
- for (const [key, value] of entries ?? []) {
- modelNames.push(key);
- const node = value[0].compilerNode;
- if (ts.isTypeAliasDeclaration(node) && methodDataNames[key] !== undefined) {
- // get the type alias declaration
- const typeAliasDeclaration = node.type;
- if (typeAliasDeclaration.kind === ts.SyntaxKind.TypeLiteral) {
- const query = (typeAliasDeclaration as ts.TypeLiteralNode).members.find(
- (m) =>
- m.kind === ts.SyntaxKind.PropertySignature &&
- m.name?.getText() === "query",
- );
- if (
- query &&
- ((query as ts.PropertySignature).type as ts.TypeLiteralNode).members
- .map((m) => m.name?.getText())
- .includes(pageParam)
- ) {
- paginatableMethods.push(methodDataNames[key]);
- }
- }
- }
- }
-
- const allGet = methods.filter((m) =>
- m.httpMethodName.toUpperCase().includes("GET"),
- );
- const allPost = methods.filter((m) =>
- m.httpMethodName.toUpperCase().includes("POST"),
- );
- const allPut = methods.filter((m) =>
- m.httpMethodName.toUpperCase().includes("PUT"),
- );
- const allPatch = methods.filter((m) =>
- m.httpMethodName.toUpperCase().includes("PATCH"),
- );
- const allDelete = methods.filter((m) =>
- m.httpMethodName.toUpperCase().includes("DELETE"),
- );
-
- const allGetQueries = allGet.map((m) =>
- createUseQuery({
- functionDescription: m,
- client,
- pageParam,
- nextPageParam,
- initialPageParam,
- paginatableMethods,
- modelNames,
- }),
- );
- const allPrefetchQueries = allGet.map((m) =>
- createPrefetchOrEnsure({ ...m, functionType: "prefetch", modelNames }),
- );
- const allEnsureQueries = allGet.map((m) =>
- createPrefetchOrEnsure({ ...m, functionType: "ensure", modelNames }),
- );
-
- const allPostMutations = allPost.map((m) =>
- createUseMutation({ functionDescription: m, modelNames, client }),
- );
- const allPutMutations = allPut.map((m) =>
- createUseMutation({ functionDescription: m, modelNames, client }),
- );
- const allPatchMutations = allPatch.map((m) =>
- createUseMutation({ functionDescription: m, modelNames, client }),
- );
- const allDeleteMutations = allDelete.map((m) =>
- createUseMutation({ functionDescription: m, modelNames, client }),
- );
-
- const allQueries = [...allGetQueries];
- const allMutations = [
- ...allPostMutations,
- ...allPutMutations,
- ...allPatchMutations,
- ...allDeleteMutations,
- ];
-
- const commonInQueries = allQueries.flatMap(
- ({ apiResponse, returnType, key, queryKeyFn }) => [
- apiResponse,
- returnType,
- key,
- queryKeyFn,
- ],
- );
- const commonInMutations = allMutations.flatMap(
- ({ mutationResult, key, mutationKeyFn }) => [
- mutationResult,
- key,
- mutationKeyFn,
- ],
- );
-
- const allCommon = [...commonInQueries, ...commonInMutations];
-
- const mainQueries = allQueries.flatMap(({ queryHook }) => [queryHook]);
- const mainMutations = allMutations.flatMap(({ mutationHook }) => [
- mutationHook,
- ]);
-
- const mainExports = [...mainQueries, ...mainMutations];
-
- const infiniteQueriesExports = allQueries
- .flatMap(({ infiniteQueryHook }) => [infiniteQueryHook])
- .filter(Boolean) as ts.VariableStatement[];
-
- const suspenseQueries = allQueries.flatMap(({ suspenseQueryHook }) => [
- suspenseQueryHook,
- ]);
-
- const suspenseExports = [...suspenseQueries];
-
- const allPrefetches = allPrefetchQueries.flatMap(({ hook }) => [hook]);
-
- const allEnsures = allEnsureQueries.flatMap(({ hook }) => [hook]);
-
- const allPrefetchExports = [...allPrefetches];
-
- return {
- /**
- * Common types and variables between queries (regular and suspense) and mutations
- */
- allCommon,
- /**
- * Main exports are the hooks that are used in the components
- */
- mainExports,
- /**
- * Infinite queries exports are the hooks that are used in the infinite scroll components
- */
- infiniteQueriesExports,
- /**
- * Suspense exports are the hooks that are used in the suspense components
- */
- suspenseExports,
- /**
- * Prefetch exports are the hooks that are used in the prefetch components
- */
- allPrefetchExports,
-
- /**
- * Ensure exports are the hooks that are used in the loader components
- */
- allEnsures,
- };
-};
diff --git a/src/createImports.mts b/src/createImports.mts
deleted file mode 100644
index 9c0f1ce..0000000
--- a/src/createImports.mts
+++ /dev/null
@@ -1,179 +0,0 @@
-import { posix } from "node:path";
-import type { UserConfig } from "@hey-api/openapi-ts";
-import type { Project } from "ts-morph";
-import ts from "typescript";
-import { modelsFileName, serviceFileName } from "./constants.mjs";
-
-const { join } = posix;
-
-export const createImports = ({
- project,
- client,
-}: {
- project: Project;
- client: UserConfig["client"];
-}) => {
- const modelsFile = project
- .getSourceFiles()
- .find((sourceFile) => sourceFile.getFilePath().includes(modelsFileName));
-
- const serviceFile = project.getSourceFileOrThrow(`${serviceFileName}.ts`);
-
- if (!modelsFile) {
- console.warn(`
-⚠️ WARNING: No models file found.
- This may be an error if \`.components.schemas\` or \`.components.parameters\` is defined in your OpenAPI input.`);
- }
-
- const modelNames = modelsFile
- ? Array.from(modelsFile.getExportedDeclarations().keys())
- : [];
-
- const serviceExports = Array.from(
- serviceFile.getExportedDeclarations().keys(),
- );
-
- const serviceNames = serviceExports;
-
- const imports = [
- ts.factory.createImportDeclaration(
- undefined,
- ts.factory.createImportClause(
- false,
- undefined,
- ts.factory.createNamedImports([
- ts.factory.createImportSpecifier(
- true,
- undefined,
- ts.factory.createIdentifier("Options"),
- ),
- ]),
- ),
- ts.factory.createStringLiteral(
- client === "@hey-api/client-axios"
- ? "@hey-api/client-axios"
- : "@hey-api/client-fetch",
- ),
- undefined,
- ),
- ts.factory.createImportDeclaration(
- undefined,
- ts.factory.createImportClause(
- false,
- undefined,
- ts.factory.createNamedImports([
- ts.factory.createImportSpecifier(
- true,
- undefined,
- ts.factory.createIdentifier("QueryClient"),
- ),
- ts.factory.createImportSpecifier(
- false,
- undefined,
- ts.factory.createIdentifier("useQuery"),
- ),
- ts.factory.createImportSpecifier(
- false,
- undefined,
- ts.factory.createIdentifier("useSuspenseQuery"),
- ),
- ts.factory.createImportSpecifier(
- false,
- undefined,
- ts.factory.createIdentifier("useMutation"),
- ),
- ts.factory.createImportSpecifier(
- false,
- undefined,
- ts.factory.createIdentifier("UseQueryResult"),
- ),
- ts.factory.createImportSpecifier(
- false,
- undefined,
- ts.factory.createIdentifier("UseQueryOptions"),
- ),
- ts.factory.createImportSpecifier(
- false,
- undefined,
- ts.factory.createIdentifier("UseMutationOptions"),
- ),
- ts.factory.createImportSpecifier(
- false,
- undefined,
- ts.factory.createIdentifier("UseMutationResult"),
- ),
- ts.factory.createImportSpecifier(
- false,
- undefined,
- ts.factory.createIdentifier("UseSuspenseQueryOptions"),
- ),
- ]),
- ),
- ts.factory.createStringLiteral("@tanstack/react-query"),
- undefined,
- ),
- ts.factory.createImportDeclaration(
- undefined,
- ts.factory.createImportClause(
- false,
- undefined,
- ts.factory.createNamedImports([
- // import all class names from service file
- ...serviceNames.map((serviceName) =>
- ts.factory.createImportSpecifier(
- false,
- undefined,
- ts.factory.createIdentifier(serviceName),
- ),
- ),
- ]),
- ),
- ts.factory.createStringLiteral(join("../requests", serviceFileName)),
- undefined,
- ),
- ];
- if (modelsFile) {
- // import all the models by name
- imports.push(
- ts.factory.createImportDeclaration(
- undefined,
- ts.factory.createImportClause(
- false,
- undefined,
- ts.factory.createNamedImports([
- ...modelNames.map((modelName) =>
- ts.factory.createImportSpecifier(
- false,
- undefined,
- ts.factory.createIdentifier(modelName),
- ),
- ),
- ]),
- ),
- ts.factory.createStringLiteral(join("../requests/", modelsFileName)),
- undefined,
- ),
- );
- }
-
- if (client === "@hey-api/client-axios") {
- imports.push(
- ts.factory.createImportDeclaration(
- undefined,
- ts.factory.createImportClause(
- false,
- undefined,
- ts.factory.createNamedImports([
- ts.factory.createImportSpecifier(
- false,
- undefined,
- ts.factory.createIdentifier("AxiosError"),
- ),
- ]),
- ),
- ts.factory.createStringLiteral("axios"),
- ),
- );
- }
- return imports;
-};
diff --git a/src/createPrefetchOrEnsure.mts b/src/createPrefetchOrEnsure.mts
deleted file mode 100644
index ce857e1..0000000
--- a/src/createPrefetchOrEnsure.mts
+++ /dev/null
@@ -1,179 +0,0 @@
-import type { VariableDeclaration } from "ts-morph";
-import ts from "typescript";
-import {
- BuildCommonTypeName,
- EqualsOrGreaterThanToken,
- getNameFromVariable,
- getQueryKeyFnName,
- getRequestParamFromMethod,
- getVariableArrowFunctionParameters,
-} from "./common.mjs";
-import type { FunctionDescription } from "./common.mjs";
-import {
- createQueryKeyFromMethod,
- hookNameFromMethod,
-} from "./createUseQuery.mjs";
-import { addJSDocToNode } from "./util.mjs";
-
-/**
- * Creates a prefetch/ensure function for a query
- */
-function createPrefetchOrEnsureHook({
- requestParams,
- method,
- functionType,
-}: {
- requestParams: ts.ParameterDeclaration[];
- method: VariableDeclaration;
- functionType: "prefetch" | "ensure";
-}) {
- const methodName = getNameFromVariable(method);
- const queryName = hookNameFromMethod({ method });
- let customHookName = `prefetch${
- queryName.charAt(0).toUpperCase() + queryName.slice(1)
- }`;
-
- if (functionType === "ensure") {
- customHookName = `ensure${
- queryName.charAt(0).toUpperCase() + queryName.slice(1)
- }Data`;
- }
- const queryKey = createQueryKeyFromMethod({ method });
-
- // const
- const hookExport = ts.factory.createVariableStatement(
- // export
- [ts.factory.createModifier(ts.SyntaxKind.ExportKeyword)],
- ts.factory.createVariableDeclarationList(
- [
- ts.factory.createVariableDeclaration(
- ts.factory.createIdentifier(customHookName),
- undefined,
- undefined,
- ts.factory.createArrowFunction(
- undefined,
- undefined,
- [
- ts.factory.createParameterDeclaration(
- undefined,
- undefined,
- "queryClient",
- undefined,
- ts.factory.createTypeReferenceNode(
- ts.factory.createIdentifier("QueryClient"),
- ),
- ),
- ...requestParams,
- ],
- undefined,
- EqualsOrGreaterThanToken,
- ts.factory.createCallExpression(
- ts.factory.createIdentifier(
- `queryClient.${functionType === "prefetch" ? "prefetchQuery" : "ensureQueryData"}`,
- ),
- undefined,
- [
- ts.factory.createObjectLiteralExpression([
- ts.factory.createPropertyAssignment(
- ts.factory.createIdentifier("queryKey"),
- ts.factory.createCallExpression(
- BuildCommonTypeName(getQueryKeyFnName(queryKey)),
- undefined,
-
- [ts.factory.createIdentifier("clientOptions")],
- ),
- ),
- ts.factory.createPropertyAssignment(
- ts.factory.createIdentifier("queryFn"),
- ts.factory.createArrowFunction(
- undefined,
- undefined,
- [],
- undefined,
- EqualsOrGreaterThanToken,
- ts.factory.createCallExpression(
- ts.factory.createPropertyAccessExpression(
- ts.factory.createCallExpression(
- ts.factory.createIdentifier(methodName),
-
- undefined,
- // { ...clientOptions }
- getVariableArrowFunctionParameters(method).length
- ? [
- ts.factory.createObjectLiteralExpression([
- ts.factory.createSpreadAssignment(
- ts.factory.createIdentifier(
- "clientOptions",
- ),
- ),
- ]),
- ]
- : undefined,
- ),
- ts.factory.createIdentifier("then"),
- ),
- undefined,
- [
- ts.factory.createArrowFunction(
- undefined,
- undefined,
- [
- ts.factory.createParameterDeclaration(
- undefined,
- undefined,
- ts.factory.createIdentifier("response"),
- undefined,
- undefined,
- undefined,
- ),
- ],
- undefined,
- ts.factory.createToken(
- ts.SyntaxKind.EqualsGreaterThanToken,
- ),
- ts.factory.createPropertyAccessExpression(
- ts.factory.createIdentifier("response"),
- ts.factory.createIdentifier("data"),
- ),
- ),
- ],
- ),
- ),
- ),
- ]),
- ],
- ),
- ),
- ),
- ],
- ts.NodeFlags.Const,
- ),
- );
- return hookExport;
-}
-
-export const createPrefetchOrEnsure = ({
- method,
- jsDoc,
- functionType,
- modelNames,
-}: FunctionDescription & {
- functionType: "prefetch" | "ensure";
- modelNames: string[];
-}) => {
- const requestParam = getRequestParamFromMethod(method, undefined, modelNames);
-
- const requestParams = requestParam ? [requestParam] : [];
-
- const prefetchOrEnsureHook = createPrefetchOrEnsureHook({
- requestParams,
- method,
- functionType,
- });
-
- const hookWithJsDoc = addJSDocToNode(prefetchOrEnsureHook, jsDoc);
-
- return {
- hook: hookWithJsDoc,
- };
-};
diff --git a/src/createSource.mts b/src/createSource.mts
index 71e2fc4..3a24f2b 100644
--- a/src/createSource.mts
+++ b/src/createSource.mts
@@ -1,287 +1,50 @@
import { join } from "node:path";
-import type { UserConfig } from "@hey-api/openapi-ts";
import { Project } from "ts-morph";
-import ts from "typescript";
-import { OpenApiRqFiles } from "./constants.mjs";
-import { createExports } from "./createExports.mjs";
-import { createImports } from "./createImports.mjs";
-import { getServices } from "./service.mjs";
+import { buildGenerationContext, parseOperations } from "./parseOperations.mjs";
+import { generateAllFiles } from "./tsmorph/index.mjs";
+import type { GeneratedFile } from "./types.mjs";
-const createSourceFile = async ({
+type ClientType = "@hey-api/client-fetch" | "@hey-api/client-axios";
+
+/**
+ * Create source files using ts-morph based generation.
+ */
+export const createSource = async ({
outputPath,
client,
+ version,
pageParam,
nextPageParam,
initialPageParam,
}: {
outputPath: string;
- client: UserConfig["client"];
+ client: ClientType;
+ version: string;
pageParam: string;
nextPageParam: string;
initialPageParam: string;
-}) => {
+}): Promise => {
+ // Initialize ts-morph project to read the generated OpenAPI client
const project = new Project({
- // Optionally specify compiler options, tsconfig.json, in-memory file system, and more here.
- // If you initialize with a tsconfig.json, then it will automatically populate the project
- // with the associated source files.
- // Read more: https://ts-morph.com/setup/
skipAddingFilesFromTsConfig: true,
});
const sourceFiles = join(process.cwd(), outputPath);
project.addSourceFilesAtPaths(`${sourceFiles}/**/*`);
- const service = await getServices(project);
+ // Parse operations from the service file
+ const operations = await parseOperations(project, pageParam);
- const imports = createImports({
- project,
- client,
- });
-
- const exports = createExports({
- service,
- client,
+ // Build generation context
+ const ctx = buildGenerationContext(
project,
+ client as "@hey-api/client-fetch" | "@hey-api/client-axios",
pageParam,
nextPageParam,
initialPageParam,
- });
-
- const commonSource = ts.factory.createSourceFile(
- [...imports, ...exports.allCommon],
- ts.factory.createToken(ts.SyntaxKind.EndOfFileToken),
- ts.NodeFlags.None,
- );
-
- const commonImport = ts.factory.createImportDeclaration(
- undefined,
- ts.factory.createImportClause(
- false,
- ts.factory.createIdentifier("* as Common"),
- undefined,
- ),
- ts.factory.createStringLiteral(`./${OpenApiRqFiles.common}`),
- undefined,
- );
-
- const commonExport = ts.factory.createExportDeclaration(
- undefined,
- false,
- undefined,
- ts.factory.createStringLiteral(`./${OpenApiRqFiles.common}`),
- undefined,
- );
-
- const queriesExport = ts.factory.createExportDeclaration(
- undefined,
- false,
- undefined,
- ts.factory.createStringLiteral(`./${OpenApiRqFiles.queries}`),
- undefined,
- );
-
- const mainSource = ts.factory.createSourceFile(
- [commonImport, ...imports, ...exports.mainExports],
- ts.factory.createToken(ts.SyntaxKind.EndOfFileToken),
- ts.NodeFlags.None,
- );
-
- const infiniteQueriesSource = ts.factory.createSourceFile(
- [commonImport, ...imports, ...exports.infiniteQueriesExports],
- ts.factory.createToken(ts.SyntaxKind.EndOfFileToken),
- ts.NodeFlags.None,
- );
-
- const suspenseSource = ts.factory.createSourceFile(
- [commonImport, ...imports, ...exports.suspenseExports],
- ts.factory.createToken(ts.SyntaxKind.EndOfFileToken),
- ts.NodeFlags.None,
- );
-
- const indexSource = ts.factory.createSourceFile(
- [commonExport, queriesExport],
- ts.factory.createToken(ts.SyntaxKind.EndOfFileToken),
- ts.NodeFlags.None,
- );
-
- const prefetchSource = ts.factory.createSourceFile(
- [commonImport, ...imports, ...exports.allPrefetchExports],
- ts.factory.createToken(ts.SyntaxKind.EndOfFileToken),
- ts.NodeFlags.None,
- );
-
- const ensureSource = ts.factory.createSourceFile(
- [commonImport, ...imports, ...exports.allEnsures],
- ts.factory.createToken(ts.SyntaxKind.EndOfFileToken),
- ts.NodeFlags.None,
- );
-
- return {
- commonSource,
- infiniteQueriesSource,
- mainSource,
- suspenseSource,
- indexSource,
- prefetchSource,
- ensureSource,
- };
-};
-
-export const createSource = async ({
- outputPath,
- client,
- version,
- pageParam,
- nextPageParam,
- initialPageParam,
-}: {
- outputPath: string;
- client: UserConfig["client"];
- version: string;
- pageParam: string;
- nextPageParam: string;
- initialPageParam: string;
-}) => {
- const queriesFile = ts.createSourceFile(
- `${OpenApiRqFiles.queries}.ts`,
- "",
- ts.ScriptTarget.Latest,
- false,
- ts.ScriptKind.TS,
- );
- const infiniteQueriesFile = ts.createSourceFile(
- `${OpenApiRqFiles.infiniteQueries}.ts`,
- "",
- ts.ScriptTarget.Latest,
- false,
- ts.ScriptKind.TS,
- );
- const commonFile = ts.createSourceFile(
- `${OpenApiRqFiles.common}.ts`,
- "",
- ts.ScriptTarget.Latest,
- false,
- ts.ScriptKind.TS,
- );
- const suspenseFile = ts.createSourceFile(
- `${OpenApiRqFiles.suspense}.ts`,
- "",
- ts.ScriptTarget.Latest,
- false,
- ts.ScriptKind.TS,
- );
-
- const indexFile = ts.createSourceFile(
- `${OpenApiRqFiles.index}.ts`,
- "",
- ts.ScriptTarget.Latest,
- false,
- ts.ScriptKind.TS,
- );
-
- const prefetchFile = ts.createSourceFile(
- `${OpenApiRqFiles.prefetch}.ts`,
- "",
- ts.ScriptTarget.Latest,
- false,
- ts.ScriptKind.TS,
+ version,
);
- const ensureQueryDataFile = ts.createSourceFile(
- `${OpenApiRqFiles.ensureQueryData}.ts`,
- "",
- ts.ScriptTarget.Latest,
- false,
- ts.ScriptKind.TS,
- );
-
- const printer = ts.createPrinter({
- newLine: ts.NewLineKind.LineFeed,
- removeComments: false,
- });
-
- const {
- commonSource,
- mainSource,
- infiniteQueriesSource,
- suspenseSource,
- indexSource,
- prefetchSource,
- ensureSource,
- } = await createSourceFile({
- outputPath,
- client,
- pageParam,
- nextPageParam,
- initialPageParam,
- });
-
- const comment = `// generated with @7nohe/openapi-react-query-codegen@${version} \n\n`;
-
- const commonResult =
- comment +
- printer.printNode(ts.EmitHint.Unspecified, commonSource, commonFile);
-
- const mainResult =
- comment +
- printer.printNode(ts.EmitHint.Unspecified, mainSource, queriesFile);
-
- const infiniteQueriesResult =
- comment +
- printer.printNode(
- ts.EmitHint.Unspecified,
- infiniteQueriesSource,
- infiniteQueriesFile,
- );
-
- const suspenseResult =
- comment +
- printer.printNode(ts.EmitHint.Unspecified, suspenseSource, suspenseFile);
-
- const indexResult =
- comment +
- printer.printNode(ts.EmitHint.Unspecified, indexSource, indexFile);
-
- const prefetchResult =
- comment +
- printer.printNode(ts.EmitHint.Unspecified, prefetchSource, prefetchFile);
-
- const enqureResult =
- comment +
- printer.printNode(
- ts.EmitHint.Unspecified,
- ensureSource,
- ensureQueryDataFile,
- );
-
- return [
- {
- name: `${OpenApiRqFiles.index}.ts`,
- content: indexResult,
- },
- {
- name: `${OpenApiRqFiles.common}.ts`,
- content: commonResult,
- },
- {
- name: `${OpenApiRqFiles.infiniteQueries}.ts`,
- content: infiniteQueriesResult,
- },
- {
- name: `${OpenApiRqFiles.queries}.ts`,
- content: mainResult,
- },
- {
- name: `${OpenApiRqFiles.suspense}.ts`,
- content: suspenseResult,
- },
- {
- name: `${OpenApiRqFiles.prefetch}.ts`,
- content: prefetchResult,
- },
- {
- name: `${OpenApiRqFiles.ensureQueryData}.ts`,
- content: enqureResult,
- },
- ];
+ // Generate all files using ts-morph
+ return generateAllFiles(operations, ctx);
};
diff --git a/src/createUseMutation.mts b/src/createUseMutation.mts
deleted file mode 100644
index c7ed8b2..0000000
--- a/src/createUseMutation.mts
+++ /dev/null
@@ -1,272 +0,0 @@
-import type { UserConfig } from "@hey-api/openapi-ts";
-import ts from "typescript";
-import {
- BuildCommonTypeName,
- EqualsOrGreaterThanToken,
- type FunctionDescription,
- TContext,
- TData,
- TError,
- capitalizeFirstLetter,
- createQueryKeyExport,
- createQueryKeyFnExport,
- getNameFromVariable,
- getQueryKeyFnName,
- getVariableArrowFunctionParameters,
- queryKeyConstraint,
- queryKeyGenericType,
-} from "./common.mjs";
-import { createQueryKeyFromMethod } from "./createUseQuery.mjs";
-import { addJSDocToNode } from "./util.mjs";
-
-/**
- * Awaited>
- */
-function generateAwaitedReturnType({ methodName }: { methodName: string }) {
- return ts.factory.createTypeReferenceNode(
- ts.factory.createIdentifier("Awaited"),
- [
- ts.factory.createTypeReferenceNode(
- ts.factory.createIdentifier("ReturnType"),
- [
- ts.factory.createTypeQueryNode(
- ts.factory.createIdentifier(methodName),
-
- undefined,
- ),
- ],
- ),
- ],
- );
-}
-
-export const createUseMutation = ({
- functionDescription: { method, jsDoc },
- modelNames,
- client,
-}: {
- functionDescription: FunctionDescription;
- modelNames: string[];
- client: UserConfig["client"];
-}) => {
- const methodName = getNameFromVariable(method);
- const mutationKey = createQueryKeyFromMethod({ method });
- const awaitedResponseDataType = generateAwaitedReturnType({
- methodName,
- });
-
- const mutationResult = ts.factory.createTypeAliasDeclaration(
- [ts.factory.createModifier(ts.SyntaxKind.ExportKeyword)],
- ts.factory.createIdentifier(
- `${capitalizeFirstLetter(methodName)}MutationResult`,
- ),
- undefined,
- awaitedResponseDataType,
- );
-
- // `TData = Common.AddPetMutationResult`
- const responseDataType = ts.factory.createTypeParameterDeclaration(
- undefined,
- TData,
- undefined,
- ts.factory.createTypeReferenceNode(
- BuildCommonTypeName(mutationResult.name),
- ),
- );
-
- // @hey-api/client-axios -> `TError = AxiosError`
- // @hey-api/client-fetch -> `TError = AddPetError`
- const responseErrorType = ts.factory.createTypeParameterDeclaration(
- undefined,
- TError,
- undefined,
- client === "@hey-api/client-axios"
- ? ts.factory.createTypeReferenceNode(
- ts.factory.createIdentifier("AxiosError"),
- [
- ts.factory.createTypeReferenceNode(
- ts.factory.createIdentifier(
- `${capitalizeFirstLetter(methodName)}Error`,
- ),
- ),
- ],
- )
- : ts.factory.createTypeReferenceNode(
- `${capitalizeFirstLetter(methodName)}Error`,
- ),
- );
-
- const methodParameters =
- getVariableArrowFunctionParameters(method).length !== 0
- ? ts.factory.createTypeReferenceNode(
- ts.factory.createIdentifier("Options"),
- [
- ts.factory.createTypeReferenceNode(
- modelNames.includes(`${capitalizeFirstLetter(methodName)}Data`)
- ? `${capitalizeFirstLetter(methodName)}Data`
- : "unknown",
- ),
- ts.factory.createLiteralTypeNode(ts.factory.createTrue()),
- ],
- )
- : ts.factory.createKeywordTypeNode(ts.SyntaxKind.VoidKeyword);
-
- const exportHook = ts.factory.createVariableStatement(
- [ts.factory.createModifier(ts.SyntaxKind.ExportKeyword)],
- ts.factory.createVariableDeclarationList(
- [
- ts.factory.createVariableDeclaration(
- ts.factory.createIdentifier(
- `use${capitalizeFirstLetter(methodName)}`,
- ),
- undefined,
- undefined,
- ts.factory.createArrowFunction(
- undefined,
- ts.factory.createNodeArray([
- responseDataType,
- responseErrorType,
- ts.factory.createTypeParameterDeclaration(
- undefined,
- "TQueryKey",
- queryKeyConstraint,
- ts.factory.createArrayTypeNode(
- ts.factory.createKeywordTypeNode(
- ts.SyntaxKind.UnknownKeyword,
- ),
- ),
- ),
- ts.factory.createTypeParameterDeclaration(
- undefined,
- TContext,
- undefined,
- ts.factory.createKeywordTypeNode(ts.SyntaxKind.UnknownKeyword),
- ),
- ]),
- [
- ts.factory.createParameterDeclaration(
- undefined,
- undefined,
- ts.factory.createIdentifier("mutationKey"),
- ts.factory.createToken(ts.SyntaxKind.QuestionToken),
- queryKeyGenericType,
- ),
- ts.factory.createParameterDeclaration(
- undefined,
- undefined,
- ts.factory.createIdentifier("options"),
- ts.factory.createToken(ts.SyntaxKind.QuestionToken),
- ts.factory.createTypeReferenceNode(
- ts.factory.createIdentifier("Omit"),
- [
- ts.factory.createTypeReferenceNode(
- ts.factory.createIdentifier("UseMutationOptions"),
- [
- ts.factory.createTypeReferenceNode(TData),
- ts.factory.createTypeReferenceNode(TError),
- methodParameters,
- ts.factory.createTypeReferenceNode(TContext),
- ],
- ),
- ts.factory.createUnionTypeNode([
- ts.factory.createLiteralTypeNode(
- ts.factory.createStringLiteral("mutationKey"),
- ),
- ts.factory.createLiteralTypeNode(
- ts.factory.createStringLiteral("mutationFn"),
- ),
- ]),
- ],
- ),
- undefined,
- ),
- ],
- undefined,
- ts.factory.createToken(ts.SyntaxKind.EqualsGreaterThanToken),
- ts.factory.createCallExpression(
- ts.factory.createIdentifier("useMutation"),
- [
- ts.factory.createTypeReferenceNode(TData),
- ts.factory.createTypeReferenceNode(TError),
- methodParameters,
- ts.factory.createTypeReferenceNode(TContext),
- ],
- [
- ts.factory.createObjectLiteralExpression([
- ts.factory.createPropertyAssignment(
- ts.factory.createIdentifier("mutationKey"),
- ts.factory.createCallExpression(
- BuildCommonTypeName(getQueryKeyFnName(mutationKey)),
- undefined,
- [ts.factory.createIdentifier("mutationKey")],
- ),
- ),
- ts.factory.createPropertyAssignment(
- ts.factory.createIdentifier("mutationFn"),
- // (clientOptions) => addPet(clientOptions).then(response => response.data as TData) as unknown as Promise
- ts.factory.createArrowFunction(
- undefined,
- undefined,
- [
- ts.factory.createParameterDeclaration(
- undefined,
- undefined,
- ts.factory.createIdentifier("clientOptions"),
- undefined,
- undefined,
- undefined,
- ),
- ],
- undefined,
- EqualsOrGreaterThanToken,
- ts.factory.createAsExpression(
- ts.factory.createAsExpression(
- ts.factory.createCallExpression(
- ts.factory.createIdentifier(methodName),
- undefined,
- getVariableArrowFunctionParameters(method).length >
- 0
- ? [ts.factory.createIdentifier("clientOptions")]
- : undefined,
- ),
- ts.factory.createKeywordTypeNode(
- ts.SyntaxKind.UnknownKeyword,
- ),
- ),
-
- ts.factory.createTypeReferenceNode(
- ts.factory.createIdentifier("Promise"),
- [ts.factory.createTypeReferenceNode(TData)],
- ),
- ),
- ),
- ),
- ts.factory.createSpreadAssignment(
- ts.factory.createIdentifier("options"),
- ),
- ]),
- ],
- ),
- ),
- ),
- ],
- ts.NodeFlags.Const,
- ),
- );
-
- const hookWithJsDoc = addJSDocToNode(exportHook, jsDoc);
-
- const mutationKeyExport = createQueryKeyExport({
- methodName,
- queryKey: mutationKey,
- });
-
- const mutationKeyFn = createQueryKeyFnExport(mutationKey, method, "mutation");
-
- return {
- mutationResult,
- key: mutationKeyExport,
- mutationHook: hookWithJsDoc,
- mutationKeyFn,
- };
-};
diff --git a/src/createUseQuery.mts b/src/createUseQuery.mts
deleted file mode 100644
index bd03fd2..0000000
--- a/src/createUseQuery.mts
+++ /dev/null
@@ -1,632 +0,0 @@
-import type { UserConfig } from "@hey-api/openapi-ts";
-import type { VariableDeclaration } from "ts-morph";
-import ts from "typescript";
-import {
- BuildCommonTypeName,
- EqualsOrGreaterThanToken,
- TData,
- TError,
- capitalizeFirstLetter,
- createQueryKeyExport,
- createQueryKeyFnExport,
- getNameFromVariable,
- getQueryKeyFnName,
- getRequestParamFromMethod,
- getVariableArrowFunctionParameters,
- queryKeyConstraint,
- queryKeyGenericType,
-} from "./common.mjs";
-import type { FunctionDescription } from "./common.mjs";
-import { addJSDocToNode } from "./util.mjs";
-
-const createApiResponseType = ({
- methodName,
- client,
-}: {
- methodName: string;
- client: UserConfig["client"];
-}) => {
- /** Awaited> */
- const awaitedResponseDataType = ts.factory.createIndexedAccessTypeNode(
- ts.factory.createTypeReferenceNode(ts.factory.createIdentifier("Awaited"), [
- ts.factory.createTypeReferenceNode(
- ts.factory.createIdentifier("ReturnType"),
- [
- ts.factory.createTypeQueryNode(
- ts.factory.createIdentifier(methodName),
- undefined,
- ),
- ],
- ),
- ]),
- ts.factory.createLiteralTypeNode(ts.factory.createStringLiteral("data")),
- );
- /** DefaultResponseDataType
- * export type MyClassMethodDefaultResponse = Awaited>
- */
- const apiResponse = ts.factory.createTypeAliasDeclaration(
- [ts.factory.createModifier(ts.SyntaxKind.ExportKeyword)],
- ts.factory.createIdentifier(
- `${capitalizeFirstLetter(methodName)}DefaultResponse`,
- ),
- undefined,
- awaitedResponseDataType,
- );
-
- const responseDataType = ts.factory.createTypeParameterDeclaration(
- undefined,
- TData.text,
- undefined,
- ts.factory.createTypeReferenceNode(BuildCommonTypeName(apiResponse.name)),
- );
-
- // Response data type for suspense - wrap with NonNullable to exclude undefined
- const suspenseResponseDataType = ts.factory.createTypeParameterDeclaration(
- undefined,
- TData.text,
- undefined,
- ts.factory.createTypeReferenceNode(
- ts.factory.createIdentifier("NonNullable"),
- [
- ts.factory.createTypeReferenceNode(
- BuildCommonTypeName(apiResponse.name),
- ),
- ],
- ),
- );
-
- const responseErrorType = ts.factory.createTypeParameterDeclaration(
- undefined,
- TError.text,
- undefined,
- client === "@hey-api/client-axios"
- ? ts.factory.createTypeReferenceNode(
- ts.factory.createIdentifier("AxiosError"),
- [
- ts.factory.createTypeReferenceNode(
- ts.factory.createIdentifier(
- `${capitalizeFirstLetter(methodName)}Error`,
- ),
- ),
- ],
- )
- : ts.factory.createTypeReferenceNode(
- `${capitalizeFirstLetter(methodName)}Error`,
- ),
- );
-
- return {
- /**
- * DefaultResponseDataType
- *
- * export type MyClassMethodDefaultResponse = Awaited>
- */
- apiResponse,
- /**
- * This will be the name of the type of the response type of the method
- *
- * MyClassMethodDefaultResponse
- */
- responseDataType,
- /**
- * ResponseDataType for suspense - wrap with NonNullable to exclude undefined
- *
- * NonNullable
- */
- suspenseResponseDataType,
- /**
- * ErrorDataType
- *
- * MyClassMethodError
- */
- responseErrorType,
- };
-};
-
-/**
- * Return Type
- *
- * export const classNameMethodNameQueryResult = UseQueryResult;
- */
-function createReturnTypeExport({
- methodName,
- defaultApiResponse,
-}: {
- methodName: string;
- defaultApiResponse: ts.TypeAliasDeclaration;
-}) {
- return ts.factory.createTypeAliasDeclaration(
- [ts.factory.createModifier(ts.SyntaxKind.ExportKeyword)],
- ts.factory.createIdentifier(
- `${capitalizeFirstLetter(methodName)}QueryResult`,
- ),
- [
- ts.factory.createTypeParameterDeclaration(
- undefined,
- TData,
- undefined,
- ts.factory.createTypeReferenceNode(defaultApiResponse.name),
- ),
- ts.factory.createTypeParameterDeclaration(
- undefined,
- TError,
- undefined,
- ts.factory.createKeywordTypeNode(ts.SyntaxKind.UnknownKeyword),
- ),
- ],
- ts.factory.createTypeReferenceNode(
- ts.factory.createIdentifier("UseQueryResult"),
- [
- ts.factory.createTypeReferenceNode(TData),
- ts.factory.createTypeReferenceNode(TError),
- ],
- ),
- );
-}
-
-export function hookNameFromMethod({
- method,
-}: {
- method: VariableDeclaration;
-}) {
- const methodName = getNameFromVariable(method);
- return `use${capitalizeFirstLetter(methodName)}`;
-}
-
-export function createQueryKeyFromMethod({
- method,
-}: {
- method: VariableDeclaration;
-}) {
- const customHookName = hookNameFromMethod({ method });
- const queryKey = `${customHookName}Key`;
- return queryKey;
-}
-
-/**
- * Creates a custom hook for a query
- * @param queryString The type of query to use from react-query
- * @param suffix The suffix to append to the hook name
- */
-function createQueryHook({
- queryString,
- suffix,
- responseDataType,
- responseErrorType,
- requestParams,
- method,
- pageParam,
- nextPageParam,
- initialPageParam,
-}: {
- queryString: "useSuspenseQuery" | "useQuery" | "useInfiniteQuery";
- suffix: string;
- responseDataType: ts.TypeParameterDeclaration;
- responseErrorType: ts.TypeParameterDeclaration;
- requestParams: ts.ParameterDeclaration[];
- method: VariableDeclaration;
- pageParam?: string;
- nextPageParam?: string;
- initialPageParam?: string;
-}) {
- const methodName = getNameFromVariable(method);
- const customHookName = hookNameFromMethod({ method });
- const queryKey = createQueryKeyFromMethod({ method });
-
- if (
- queryString === "useInfiniteQuery" &&
- (pageParam === undefined || nextPageParam === undefined)
- ) {
- throw new Error(
- "pageParam and nextPageParam are required for infinite queries",
- );
- }
-
- const isInfiniteQuery = queryString === "useInfiniteQuery";
- const isSuspenseQuery = queryString === "useSuspenseQuery";
-
- const responseDataTypeRef = responseDataType.default as ts.TypeReferenceNode;
- const responseDataTypeIdentifier =
- responseDataTypeRef.typeName as ts.Identifier;
-
- const hookExport = ts.factory.createVariableStatement(
- [ts.factory.createModifier(ts.SyntaxKind.ExportKeyword)],
- ts.factory.createVariableDeclarationList(
- [
- ts.factory.createVariableDeclaration(
- ts.factory.createIdentifier(`${customHookName}${suffix}`),
- undefined,
- undefined,
- ts.factory.createArrowFunction(
- undefined,
- ts.factory.createNodeArray([
- isInfiniteQuery
- ? ts.factory.createTypeParameterDeclaration(
- undefined,
- TData,
- undefined,
- ts.factory.createTypeReferenceNode(
- ts.factory.createIdentifier("InfiniteData"),
- [
- ts.factory.createTypeReferenceNode(
- responseDataTypeIdentifier,
- ),
- ],
- ),
- )
- : responseDataType,
- responseErrorType,
- ts.factory.createTypeParameterDeclaration(
- undefined,
- "TQueryKey",
- queryKeyConstraint,
- ts.factory.createArrayTypeNode(
- ts.factory.createKeywordTypeNode(
- ts.SyntaxKind.UnknownKeyword,
- ),
- ),
- ),
- ]),
- [
- ...requestParams,
- ts.factory.createParameterDeclaration(
- undefined,
- undefined,
- ts.factory.createIdentifier("queryKey"),
- ts.factory.createToken(ts.SyntaxKind.QuestionToken),
- queryKeyGenericType,
- ),
- ts.factory.createParameterDeclaration(
- undefined,
- undefined,
- ts.factory.createIdentifier("options"),
- ts.factory.createToken(ts.SyntaxKind.QuestionToken),
- ts.factory.createTypeReferenceNode(
- ts.factory.createIdentifier("Omit"),
- [
- ts.factory.createTypeReferenceNode(
- ts.factory.createIdentifier(
- isInfiniteQuery
- ? "UseInfiniteQueryOptions"
- : isSuspenseQuery
- ? "UseSuspenseQueryOptions"
- : "UseQueryOptions",
- ),
- [
- ts.factory.createTypeReferenceNode(TData),
- ts.factory.createTypeReferenceNode(TError),
- ],
- ),
- ts.factory.createUnionTypeNode([
- ts.factory.createLiteralTypeNode(
- ts.factory.createStringLiteral("queryKey"),
- ),
- ts.factory.createLiteralTypeNode(
- ts.factory.createStringLiteral("queryFn"),
- ),
- ]),
- ],
- ),
- ),
- ],
- undefined,
- EqualsOrGreaterThanToken,
- ts.factory.createCallExpression(
- ts.factory.createIdentifier(queryString),
- isInfiniteQuery
- ? []
- : [
- ts.factory.createTypeReferenceNode(TData),
- ts.factory.createTypeReferenceNode(TError),
- ],
- [
- ts.factory.createObjectLiteralExpression([
- ts.factory.createPropertyAssignment(
- ts.factory.createIdentifier("queryKey"),
- ts.factory.createCallExpression(
- BuildCommonTypeName(getQueryKeyFnName(queryKey)),
- undefined,
- [
- ts.factory.createIdentifier("clientOptions"),
- ts.factory.createIdentifier("queryKey"),
- ],
- ),
- ),
- ts.factory.createPropertyAssignment(
- ts.factory.createIdentifier("queryFn"),
- ts.factory.createArrowFunction(
- undefined,
- undefined,
- isInfiniteQuery
- ? [
- ts.factory.createParameterDeclaration(
- undefined,
- undefined,
- ts.factory.createObjectBindingPattern([
- ts.factory.createBindingElement(
- undefined,
- undefined,
- ts.factory.createIdentifier("pageParam"),
- undefined,
- ),
- ]),
- undefined,
- undefined,
- ),
- ]
- : [],
- undefined,
- EqualsOrGreaterThanToken,
- ts.factory.createAsExpression(
- ts.factory.createCallExpression(
- ts.factory.createPropertyAccessExpression(
- ts.factory.createCallExpression(
- ts.factory.createIdentifier(methodName),
- undefined,
- pageParam && isInfiniteQuery
- ? [
- // { ...clientOptions, query: { ...clientOptions.query, page: pageParam as number } }
- ts.factory.createObjectLiteralExpression([
- ts.factory.createSpreadAssignment(
- ts.factory.createIdentifier(
- "clientOptions",
- ),
- ),
- ts.factory.createPropertyAssignment(
- ts.factory.createIdentifier("query"),
- ts.factory.createObjectLiteralExpression(
- [
- ts.factory.createSpreadAssignment(
- ts.factory.createPropertyAccessExpression(
- ts.factory.createIdentifier(
- "clientOptions",
- ),
- ts.factory.createIdentifier(
- "query",
- ),
- ),
- ),
- ts.factory.createPropertyAssignment(
- ts.factory.createIdentifier(
- pageParam,
- ),
- ts.factory.createAsExpression(
- ts.factory.createIdentifier(
- "pageParam",
- ),
- ts.factory.createKeywordTypeNode(
- ts.SyntaxKind.NumberKeyword,
- ),
- ),
- ),
- ],
- ),
- ),
- ]),
- ]
- : // { ...clientOptions }
- getVariableArrowFunctionParameters(method)
- .length > 0
- ? [
- ts.factory.createObjectLiteralExpression([
- ts.factory.createSpreadAssignment(
- ts.factory.createIdentifier(
- "clientOptions",
- ),
- ),
- ]),
- ]
- : undefined,
- ),
- ts.factory.createIdentifier("then"),
- ),
- undefined,
- [
- ts.factory.createArrowFunction(
- undefined,
- undefined,
- [
- ts.factory.createParameterDeclaration(
- undefined,
- undefined,
- ts.factory.createIdentifier("response"),
- undefined,
- undefined,
- undefined,
- ),
- ],
- undefined,
- EqualsOrGreaterThanToken,
- ts.factory.createAsExpression(
- ts.factory.createPropertyAccessExpression(
- ts.factory.createIdentifier("response"),
- ts.factory.createIdentifier("data"),
- ),
- ts.factory.createTypeReferenceNode(TData),
- ),
- ),
- ],
- ),
- ts.factory.createTypeReferenceNode(TData),
- ),
- ),
- ),
- ...createInfiniteQueryParams(
- pageParam,
- nextPageParam,
- initialPageParam,
- ),
- ts.factory.createSpreadAssignment(
- ts.factory.createIdentifier("options"),
- ),
- ]),
- ],
- ),
- ),
- ),
- ],
- ts.NodeFlags.Const,
- ),
- );
- return hookExport;
-}
-
-export const createUseQuery = ({
- functionDescription: { method, jsDoc },
- client,
- pageParam,
- nextPageParam,
- initialPageParam,
- paginatableMethods,
- modelNames,
-}: {
- functionDescription: FunctionDescription;
- client: UserConfig["client"];
- pageParam: string;
- nextPageParam: string;
- initialPageParam: string;
- paginatableMethods: string[];
- modelNames: string[];
-}) => {
- const methodName = getNameFromVariable(method);
- const queryKey = createQueryKeyFromMethod({ method });
- const {
- apiResponse: defaultApiResponse,
- responseDataType,
- suspenseResponseDataType,
- responseErrorType,
- } = createApiResponseType({
- methodName,
- client,
- });
-
- const requestParam = getRequestParamFromMethod(method, undefined, modelNames);
- const infiniteRequestParam = getRequestParamFromMethod(
- method,
- pageParam,
- modelNames,
- );
-
- const requestParams = requestParam ? [requestParam] : [];
-
- const queryHook = createQueryHook({
- queryString: "useQuery",
- suffix: "",
- responseDataType,
- responseErrorType,
- requestParams,
- method,
- });
-
- const suspenseQueryHook = createQueryHook({
- queryString: "useSuspenseQuery",
- suffix: "Suspense",
- responseDataType: suspenseResponseDataType,
- responseErrorType,
- requestParams,
- method,
- });
- const isInfiniteQuery = paginatableMethods.includes(methodName);
-
- const infiniteQueryHook = isInfiniteQuery
- ? createQueryHook({
- queryString: "useInfiniteQuery",
- suffix: "Infinite",
- responseDataType,
- responseErrorType,
- requestParams: infiniteRequestParam ? [infiniteRequestParam] : [],
- method,
- pageParam,
- nextPageParam,
- initialPageParam,
- })
- : undefined;
-
- const hookWithJsDoc = addJSDocToNode(queryHook, jsDoc);
- const suspenseHookWithJsDoc = addJSDocToNode(suspenseQueryHook, jsDoc);
- const infiniteHookWithJsDoc = infiniteQueryHook
- ? addJSDocToNode(infiniteQueryHook, jsDoc)
- : undefined;
-
- const returnTypeExport = createReturnTypeExport({
- methodName,
- defaultApiResponse,
- });
-
- const queryKeyExport = createQueryKeyExport({
- methodName,
- queryKey,
- });
-
- const queryKeyFn = createQueryKeyFnExport(
- queryKey,
- method,
- "query",
- modelNames,
- );
-
- return {
- apiResponse: defaultApiResponse,
- returnType: returnTypeExport,
- key: queryKeyExport,
- queryHook: hookWithJsDoc,
- suspenseQueryHook: suspenseHookWithJsDoc,
- infiniteQueryHook: infiniteHookWithJsDoc,
- queryKeyFn,
- };
-};
-
-function createInfiniteQueryParams(
- pageParam?: string,
- nextPageParam?: string,
- initialPageParam = "1",
-) {
- if (pageParam === undefined || nextPageParam === undefined) {
- return [];
- }
- return [
- ts.factory.createPropertyAssignment(
- ts.factory.createIdentifier("initialPageParam"),
- ts.factory.createStringLiteral(initialPageParam),
- ),
- ts.factory.createPropertyAssignment(
- ts.factory.createIdentifier("getNextPageParam"),
- // (response) => (response as { nextPage: number }).nextPage,
- ts.factory.createArrowFunction(
- undefined,
- undefined,
- [
- ts.factory.createParameterDeclaration(
- undefined,
- undefined,
- ts.factory.createIdentifier("response"),
- undefined,
- undefined,
- ),
- ],
- undefined,
- EqualsOrGreaterThanToken,
- ts.factory.createPropertyAccessExpression(
- ts.factory.createParenthesizedExpression(
- ts.factory.createAsExpression(
- ts.factory.createIdentifier("response"),
- nextPageParam.split(".").reduceRight((acc, segment) => {
- return ts.factory.createTypeLiteralNode([
- ts.factory.createPropertySignature(
- undefined,
- ts.factory.createIdentifier(segment),
- undefined,
- acc,
- ),
- ]);
- }, ts.factory.createKeywordTypeNode(
- ts.SyntaxKind.NumberKeyword,
- ) as ts.TypeNode),
- ),
- ),
- ts.factory.createIdentifier(nextPageParam),
- ),
- ),
- ),
- ];
-}
diff --git a/src/generate.mts b/src/generate.mts
index 229f0c7..f0481b2 100644
--- a/src/generate.mts
+++ b/src/generate.mts
@@ -13,37 +13,40 @@ export async function generate(options: LimitedUserConfig, version: string) {
const openApiOutputPath = buildRequestsOutputPath(options.output);
const formattedOptions = formatOptions(options);
+ const clientPlugin = formattedOptions.client ?? "@hey-api/client-fetch";
+
const config: UserConfig = {
- client: formattedOptions.client,
- debug: formattedOptions.debug,
- dryRun: false,
- exportCore: true,
+ input: formattedOptions.input,
output: {
format: formattedOptions.format,
lint: formattedOptions.lint,
path: openApiOutputPath,
},
- input: formattedOptions.input,
- schemas: {
- export: !formattedOptions.noSchemas,
- type: formattedOptions.schemaType,
- },
- services: {
- export: true,
- asClass: false,
- operationId: !formattedOptions.noOperationId,
- },
- types: {
- dates: formattedOptions.useDateType,
- export: true,
- enums: formattedOptions.enums,
- },
- useOptions: true,
+ plugins: [
+ clientPlugin,
+ {
+ name: "@hey-api/typescript",
+ enums: formattedOptions.enums,
+ },
+ {
+ name: "@hey-api/sdk",
+ asClass: false,
+ operationId: !formattedOptions.noOperationId,
+ },
+ ...(formattedOptions.noSchemas
+ ? []
+ : [
+ {
+ name: "@hey-api/schemas" as const,
+ type: formattedOptions.schemaType,
+ },
+ ]),
+ ],
};
await createClient(config);
const source = await createSource({
outputPath: openApiOutputPath,
- client: formattedOptions.client,
+ client: clientPlugin as "@hey-api/client-fetch" | "@hey-api/client-axios",
version,
pageParam: formattedOptions.pageParam,
nextPageParam: formattedOptions.nextPageParam,
diff --git a/src/parseOperations.mts b/src/parseOperations.mts
new file mode 100644
index 0000000..0b1bec8
--- /dev/null
+++ b/src/parseOperations.mts
@@ -0,0 +1,167 @@
+import type { Project, VariableDeclaration } from "ts-morph";
+import ts from "typescript";
+import {
+ capitalizeFirstLetter,
+ extractPropertiesFromObjectParam,
+ getNameFromVariable,
+ getShortType,
+ getVariableArrowFunctionParameters,
+} from "./common.mjs";
+import { modelsFileName, serviceFileName } from "./constants.mjs";
+import { getServices } from "./service.mjs";
+import type {
+ GenerationContext,
+ OperationInfo,
+ OperationParameter,
+} from "./types.mjs";
+
+/**
+ * Extract parameter information from a method's variable declaration.
+ */
+function extractParameters(
+ method: VariableDeclaration,
+ pageParam?: string,
+): OperationParameter[] {
+ const arrowParams = getVariableArrowFunctionParameters(method);
+ if (!arrowParams.length) {
+ return [];
+ }
+
+ return arrowParams.flatMap((param) => {
+ const paramNodes = extractPropertiesFromObjectParam(param);
+ return paramNodes
+ .filter((p) => p.name !== pageParam)
+ .map((refParam) => ({
+ name: refParam.name,
+ typeName: getShortType(refParam.type?.getText() ?? ""),
+ optional: refParam.optional,
+ }));
+ });
+}
+
+/**
+ * Get paginatable methods by checking if their Data type has the pageParam in query property.
+ * Uses TypeScript compiler API for accurate AST traversal.
+ */
+function getPaginatableMethods(project: Project, pageParam: string): string[] {
+ const modelsFile = project
+ .getSourceFiles()
+ .find((sf) => sf.getFilePath().includes(modelsFileName));
+
+ if (!modelsFile) return [];
+
+ const paginatableMethods: string[] = [];
+ const modelDeclarations = modelsFile.getExportedDeclarations();
+ const entries = modelDeclarations.entries();
+
+ for (const [key, value] of entries) {
+ // Check if this is a *Data type (e.g., FindPetsData)
+ if (!key.endsWith("Data")) continue;
+
+ const node = value[0].compilerNode;
+ if (!ts.isTypeAliasDeclaration(node)) continue;
+
+ const typeAliasDeclaration = node.type;
+ if (typeAliasDeclaration.kind !== ts.SyntaxKind.TypeLiteral) continue;
+
+ // Look for 'query' property in the type literal
+ const query = (typeAliasDeclaration as ts.TypeLiteralNode).members.find(
+ (m) =>
+ m.kind === ts.SyntaxKind.PropertySignature &&
+ m.name?.getText() === "query",
+ );
+
+ if (!query) continue;
+
+ // Check if query type has the pageParam
+ const queryType = (query as ts.PropertySignature).type;
+ if (!queryType || queryType.kind !== ts.SyntaxKind.TypeLiteral) continue;
+
+ const hasPageParam = (queryType as ts.TypeLiteralNode).members.some(
+ (m) => m.name?.getText() === pageParam,
+ );
+
+ if (hasPageParam) {
+ // Extract method name from Data type name (e.g., "FindPetsData" -> "findPets")
+ const methodName = key.slice(0, -4); // Remove "Data" suffix
+ // Convert first letter to lowercase
+ const methodNameLower =
+ methodName.charAt(0).toLowerCase() + methodName.slice(1);
+ paginatableMethods.push(methodNameLower);
+ }
+ }
+
+ return paginatableMethods;
+}
+
+/**
+ * Parse operations from the OpenAPI-generated service file and return normalized DTOs.
+ */
+export async function parseOperations(
+ project: Project,
+ pageParam: string,
+): Promise {
+ const service = await getServices(project);
+ const { methods } = service;
+ const paginatableMethods = getPaginatableMethods(project, pageParam);
+
+ return methods.map((desc) => {
+ const methodName = getNameFromVariable(desc.method);
+ const httpMethod = desc.httpMethodName.toUpperCase();
+ const parameters = extractParameters(desc.method);
+ const allParamsOptional = parameters.every((p) => p.optional);
+ const isPaginatable =
+ httpMethod === "GET" && paginatableMethods.includes(methodName);
+
+ return {
+ methodName,
+ capitalizedMethodName: capitalizeFirstLetter(methodName),
+ httpMethod,
+ jsDoc: desc.jsDoc,
+ isDeprecated: desc.isDeprecated,
+ parameters,
+ allParamsOptional,
+ isPaginatable,
+ };
+ });
+}
+
+/**
+ * Build generation context from project configuration.
+ */
+export function buildGenerationContext(
+ project: Project,
+ client: GenerationContext["client"],
+ pageParam: string,
+ nextPageParam: string,
+ initialPageParam: string,
+ version: string,
+): GenerationContext {
+ const modelsFile = project
+ .getSourceFiles()
+ .find((sf) => sf.getFilePath().includes(modelsFileName));
+
+ const serviceFile = project
+ .getSourceFiles()
+ .find((sf) => sf.getFilePath().includes(serviceFileName));
+
+ if (!serviceFile) {
+ throw new Error("No service node found");
+ }
+
+ const modelNames = modelsFile
+ ? Array.from(modelsFile.getExportedDeclarations().keys())
+ : [];
+
+ const serviceNames = Array.from(serviceFile.getExportedDeclarations().keys());
+
+ return {
+ client,
+ modelNames,
+ serviceNames,
+ pageParam,
+ nextPageParam,
+ initialPageParam,
+ version,
+ };
+}
diff --git a/src/service.mts b/src/service.mts
index 8baa695..f637796 100644
--- a/src/service.mts
+++ b/src/service.mts
@@ -24,79 +24,101 @@ export async function getServices(project: Project): Promise {
} satisfies Service;
}
+/**
+ * Extract the call expression from an arrow function body.
+ * Handles both block body (with return statement) and expression body.
+ */
+function extractCallExpression(
+ body: ts.ConciseBody,
+): ts.CallExpression | undefined {
+ // Block body: { return client.get(...); }
+ if (ts.isBlock(body)) {
+ const returnStatement = body.statements.find(
+ (s) => s.kind === ts.SyntaxKind.ReturnStatement,
+ ) as ts.ReturnStatement | undefined;
+ if (
+ returnStatement?.expression &&
+ ts.isCallExpression(returnStatement.expression)
+ ) {
+ return returnStatement.expression;
+ }
+ return undefined;
+ }
+
+ // Expression body: client.get(...) or (options?.client ?? _heyApiClient).get(...)
+ if (ts.isCallExpression(body)) {
+ return body;
+ }
+
+ return undefined;
+}
+
export function getMethodsFromService(node: SourceFile): FunctionDescription[] {
const variableStatements = node.getVariableStatements();
- // The first variable statement is `const client = createClient(createConfig())`, so we skip it
- return variableStatements.splice(1).flatMap((variableStatement) => {
+ // In v0.73+, sdk.gen.ts exports functions directly (no client initialization)
+ return variableStatements.flatMap((variableStatement) => {
const declarations = variableStatement.getDeclarations();
- return declarations.map((declaration) => {
- if (!ts.isVariableDeclaration(declaration.compilerNode)) {
- throw new Error("Variable declaration not found");
- }
- const initializer = declaration.getInitializer();
- if (!initializer) {
- throw new Error("Initializer not found");
- }
- if (!ts.isArrowFunction(initializer.compilerNode)) {
- throw new Error("Arrow function not found");
- }
- const methodBlockNode = initializer.compilerNode.body;
- if (!methodBlockNode || !ts.isBlock(methodBlockNode)) {
- throw new Error("Method block not found");
- }
- const foundReturnStatement = methodBlockNode.statements.find(
- (s) => s.kind === ts.SyntaxKind.ReturnStatement,
- );
- if (!foundReturnStatement) {
- throw new Error("Return statement not found");
- }
- const returnStatement = foundReturnStatement as ts.ReturnStatement;
- const foundCallExpression = returnStatement.expression;
- if (!foundCallExpression) {
- throw new Error("Call expression not found");
- }
- const callExpression = foundCallExpression as ts.CallExpression;
-
- const propertyAccessExpression =
- callExpression.expression as ts.PropertyAccessExpression;
- const httpMethodName = propertyAccessExpression.name.getText();
-
- if (!httpMethodName) {
- throw new Error("httpMethodName not found");
- }
-
- const getAllChildren = (tsNode: ts.Node): Array => {
- const childItems = tsNode.getChildren(node.compilerNode);
- if (childItems.length) {
- const allChildren = childItems.map(getAllChildren);
- return [tsNode].concat(allChildren.flat());
+ return declarations
+ .map((declaration) => {
+ if (!ts.isVariableDeclaration(declaration.compilerNode)) {
+ return null;
+ }
+ const initializer = declaration.getInitializer();
+ if (!initializer) {
+ return null;
+ }
+ if (!ts.isArrowFunction(initializer.compilerNode)) {
+ return null;
+ }
+
+ const callExpression = extractCallExpression(
+ initializer.compilerNode.body,
+ );
+ if (!callExpression) {
+ return null;
+ }
+
+ // Get the HTTP method name from the call expression (e.g., .get, .post, .delete)
+ const expression = callExpression.expression;
+ if (!ts.isPropertyAccessExpression(expression)) {
+ return null;
+ }
+ const httpMethodName = expression.name.getText();
+
+ if (!httpMethodName) {
+ return null;
}
- return [tsNode];
- };
-
- const children = getAllChildren(initializer.compilerNode);
- // get all JSDoc comments
- // this should be an array of 1 or 0
- const jsDocs = children
- .filter((c) => c.kind === ts.SyntaxKind.JSDoc)
- .map((c) => c.getText(node.compilerNode));
- // get the first JSDoc comment
- const jsDoc = jsDocs?.[0];
- const isDeprecated = children.some(
- (c) => c.kind === ts.SyntaxKind.JSDocDeprecatedTag,
- );
-
- const methodDescription: FunctionDescription = {
- node,
- method: declaration,
- methodBlock: methodBlockNode,
- httpMethodName,
- jsDoc,
- isDeprecated,
- } satisfies FunctionDescription;
-
- return methodDescription;
- });
+
+ const getAllChildren = (tsNode: ts.Node): Array => {
+ const childItems = tsNode.getChildren(node.compilerNode);
+ if (childItems.length) {
+ const allChildren = childItems.map(getAllChildren);
+ return [tsNode].concat(allChildren.flat());
+ }
+ return [tsNode];
+ };
+
+ const children = getAllChildren(initializer.compilerNode);
+ // get all JSDoc comments
+ const jsDocs = children
+ .filter((c) => c.kind === ts.SyntaxKind.JSDoc)
+ .map((c) => c.getText(node.compilerNode));
+ const jsDoc = jsDocs?.[0];
+ const isDeprecated = children.some(
+ (c) => c.kind === ts.SyntaxKind.JSDocDeprecatedTag,
+ );
+
+ const methodDescription: FunctionDescription = {
+ node,
+ method: declaration,
+ httpMethodName,
+ jsDoc,
+ isDeprecated,
+ } satisfies FunctionDescription;
+
+ return methodDescription;
+ })
+ .filter((desc): desc is FunctionDescription => desc !== null);
});
}
diff --git a/src/tsmorph/buildCommon.mts b/src/tsmorph/buildCommon.mts
new file mode 100644
index 0000000..03b66df
--- /dev/null
+++ b/src/tsmorph/buildCommon.mts
@@ -0,0 +1,152 @@
+import {
+ StructureKind,
+ type TypeAliasDeclarationStructure,
+ VariableDeclarationKind,
+ type VariableStatementStructure,
+} from "ts-morph";
+import type { GenerationContext, OperationInfo } from "../types.mjs";
+
+/**
+ * Build the default response type alias.
+ * Example: export type FindPetsDefaultResponse = Awaited>["data"];
+ */
+export function buildDefaultResponseType(
+ op: OperationInfo,
+): TypeAliasDeclarationStructure {
+ return {
+ kind: StructureKind.TypeAlias,
+ isExported: true,
+ name: `${op.capitalizedMethodName}DefaultResponse`,
+ type: `Awaited>["data"]`,
+ };
+}
+
+/**
+ * Build the query result type alias.
+ * Example: export type FindPetsQueryResult = UseQueryResult;
+ */
+export function buildQueryResultType(
+ op: OperationInfo,
+): TypeAliasDeclarationStructure {
+ return {
+ kind: StructureKind.TypeAlias,
+ isExported: true,
+ name: `${op.capitalizedMethodName}QueryResult`,
+ typeParameters: [
+ { name: "TData", default: `${op.capitalizedMethodName}DefaultResponse` },
+ { name: "TError", default: "unknown" },
+ ],
+ type: "UseQueryResult",
+ };
+}
+
+/**
+ * Build the mutation result type alias.
+ * Example: export type AddPetMutationResult = Awaited>;
+ */
+export function buildMutationResultType(
+ op: OperationInfo,
+): TypeAliasDeclarationStructure {
+ return {
+ kind: StructureKind.TypeAlias,
+ isExported: true,
+ name: `${op.capitalizedMethodName}MutationResult`,
+ type: `Awaited>`,
+ };
+}
+
+/**
+ * Build query key constant.
+ * Example: export const useFindPetsKey = "FindPets";
+ */
+export function buildQueryKeyConst(
+ op: OperationInfo,
+): VariableStatementStructure {
+ return {
+ kind: StructureKind.VariableStatement,
+ isExported: true,
+ declarationKind: VariableDeclarationKind.Const,
+ declarations: [
+ {
+ name: `use${op.capitalizedMethodName}Key`,
+ initializer: `"${op.capitalizedMethodName}"`,
+ },
+ ],
+ };
+}
+
+/**
+ * Build mutation key constant.
+ * Example: export const useAddPetKey = "AddPet";
+ */
+export function buildMutationKeyConst(
+ op: OperationInfo,
+): VariableStatementStructure {
+ return {
+ kind: StructureKind.VariableStatement,
+ isExported: true,
+ declarationKind: VariableDeclarationKind.Const,
+ declarations: [
+ {
+ name: `use${op.capitalizedMethodName}Key`,
+ initializer: `"${op.capitalizedMethodName}"`,
+ },
+ ],
+ };
+}
+
+/**
+ * Build query key function.
+ * Example: export const UseFindPetsKeyFn = (clientOptions: Options = {}, queryKey?: Array) =>
+ * [useFindPetsKey, ...(queryKey ?? [clientOptions])];
+ */
+export function buildQueryKeyFn(
+ op: OperationInfo,
+ ctx: GenerationContext,
+): VariableStatementStructure {
+ const dataTypeName = ctx.modelNames.includes(
+ `${op.capitalizedMethodName}Data`,
+ )
+ ? `${op.capitalizedMethodName}Data`
+ : "unknown";
+
+ const params: string[] = [];
+ const defaultValue = op.allParamsOptional ? " = {}" : "";
+ params.push(`clientOptions: Options<${dataTypeName}, true>${defaultValue}`);
+ params.push("queryKey?: Array");
+
+ const fallbackArray = "[clientOptions]";
+
+ return {
+ kind: StructureKind.VariableStatement,
+ isExported: true,
+ declarationKind: VariableDeclarationKind.Const,
+ declarations: [
+ {
+ name: `Use${op.capitalizedMethodName}KeyFn`,
+ initializer: `(${params.join(", ")}) => [use${op.capitalizedMethodName}Key, ...(queryKey ?? ${fallbackArray})]`,
+ },
+ ],
+ };
+}
+
+/**
+ * Build mutation key function.
+ * Example: export const UseAddPetKeyFn = (mutationKey?: Array) =>
+ * [useAddPetKey, ...(mutationKey ?? [])];
+ */
+export function buildMutationKeyFn(
+ op: OperationInfo,
+): VariableStatementStructure {
+ return {
+ kind: StructureKind.VariableStatement,
+ isExported: true,
+ declarationKind: VariableDeclarationKind.Const,
+ declarations: [
+ {
+ name: `Use${op.capitalizedMethodName}KeyFn`,
+ initializer: `(mutationKey?: Array) => [use${op.capitalizedMethodName}Key, ...(mutationKey ?? [])]`,
+ },
+ ],
+ };
+}
diff --git a/src/tsmorph/buildKeys.mts b/src/tsmorph/buildKeys.mts
new file mode 100644
index 0000000..c32f5bb
--- /dev/null
+++ b/src/tsmorph/buildKeys.mts
@@ -0,0 +1,138 @@
+import {
+ StructureKind,
+ VariableDeclarationKind,
+ type VariableStatementStructure,
+} from "ts-morph";
+import type { GenerationContext, OperationInfo } from "../types.mjs";
+
+/**
+ * Build query key constant name (e.g., "findPetsQueryKey").
+ */
+export function getQueryKeyName(op: OperationInfo): string {
+ return `${op.methodName}QueryKey`;
+}
+
+/**
+ * Build mutation key constant name (e.g., "addPetMutationKey").
+ */
+export function getMutationKeyName(op: OperationInfo): string {
+ return `${op.methodName}MutationKey`;
+}
+
+/**
+ * Build query key fn name (e.g., "FindPetsQueryKeyFn").
+ */
+export function getQueryKeyFnName(op: OperationInfo): string {
+ return `${op.capitalizedMethodName}QueryKeyFn`;
+}
+
+/**
+ * Build mutation key fn name (e.g., "AddPetMutationKeyFn").
+ */
+export function getMutationKeyFnName(op: OperationInfo): string {
+ return `${op.capitalizedMethodName}MutationKeyFn`;
+}
+
+/**
+ * Build the query key constant export.
+ * Example: export const findPetsQueryKey = "FindPets";
+ */
+export function buildQueryKeyExport(
+ op: OperationInfo,
+): VariableStatementStructure {
+ return {
+ kind: StructureKind.VariableStatement,
+ isExported: true,
+ declarationKind: VariableDeclarationKind.Const,
+ declarations: [
+ {
+ name: getQueryKeyName(op),
+ initializer: `"${op.capitalizedMethodName}"`,
+ },
+ ],
+ };
+}
+
+/**
+ * Build the mutation key constant export.
+ * Example: export const addPetMutationKey = "AddPet";
+ */
+export function buildMutationKeyExport(
+ op: OperationInfo,
+): VariableStatementStructure {
+ return {
+ kind: StructureKind.VariableStatement,
+ isExported: true,
+ declarationKind: VariableDeclarationKind.Const,
+ declarations: [
+ {
+ name: getMutationKeyName(op),
+ initializer: `"${op.capitalizedMethodName}"`,
+ },
+ ],
+ };
+}
+
+/**
+ * Build the query key function export.
+ * Example:
+ * export const FindPetsQueryKeyFn = (clientOptions: Options, queryKey?: Array) =>
+ * [findPetsQueryKey, ...(queryKey ?? [clientOptions])] as const;
+ */
+export function buildQueryKeyFnExport(
+ op: OperationInfo,
+ ctx: GenerationContext,
+): VariableStatementStructure {
+ const hasParams = op.parameters.length > 0;
+ const dataTypeName = ctx.modelNames.includes(
+ `${op.capitalizedMethodName}Data`,
+ )
+ ? `${op.capitalizedMethodName}Data`
+ : "unknown";
+
+ const params: string[] = [];
+ if (hasParams) {
+ const defaultValue = op.allParamsOptional ? " = {}" : "";
+ params.push(`clientOptions: Options<${dataTypeName}, true>${defaultValue}`);
+ }
+ params.push("queryKey?: Array");
+
+ const fallbackArray = hasParams ? "[clientOptions]" : "[]";
+ const body = `[${getQueryKeyName(op)}, ...(queryKey ?? ${fallbackArray})] as const`;
+
+ return {
+ kind: StructureKind.VariableStatement,
+ isExported: true,
+ declarationKind: VariableDeclarationKind.Const,
+ declarations: [
+ {
+ name: getQueryKeyFnName(op),
+ initializer: `(${params.join(", ")}) => ${body}`,
+ },
+ ],
+ };
+}
+
+/**
+ * Build the mutation key function export.
+ * Example:
+ * export const AddPetMutationKeyFn = (mutationKey?: Array) =>
+ * [addPetMutationKey, ...(mutationKey ?? [])] as const;
+ */
+export function buildMutationKeyFnExport(
+ op: OperationInfo,
+): VariableStatementStructure {
+ const body = `[${getMutationKeyName(op)}, ...(mutationKey ?? [])] as const`;
+
+ return {
+ kind: StructureKind.VariableStatement,
+ isExported: true,
+ declarationKind: VariableDeclarationKind.Const,
+ declarations: [
+ {
+ name: getMutationKeyFnName(op),
+ initializer: `(mutationKey?: Array) => ${body}`,
+ },
+ ],
+ };
+}
diff --git a/src/tsmorph/buildMutationHooks.mts b/src/tsmorph/buildMutationHooks.mts
new file mode 100644
index 0000000..c884fc4
--- /dev/null
+++ b/src/tsmorph/buildMutationHooks.mts
@@ -0,0 +1,62 @@
+import {
+ StructureKind,
+ VariableDeclarationKind,
+ type VariableStatementStructure,
+} from "ts-morph";
+import type { GenerationContext, OperationInfo } from "../types.mjs";
+
+/**
+ * Get the error type string based on client type.
+ */
+function getErrorType(op: OperationInfo, ctx: GenerationContext): string {
+ const errorTypeName = `${op.capitalizedMethodName}Error`;
+ if (ctx.client === "@hey-api/client-axios") {
+ return `AxiosError<${errorTypeName}>`;
+ }
+ return errorTypeName;
+}
+
+/**
+ * Build useMutation hook.
+ * Example:
+ * export const useAddPet = = unknown[], TContext = unknown>(
+ * mutationKey?: TQueryKey,
+ * options?: Omit, TContext>, "mutationKey" | "mutationFn">
+ * ) => useMutation, TContext>({
+ * mutationKey: Common.UseAddPetKeyFn(mutationKey),
+ * mutationFn: clientOptions => addPet(clientOptions) as unknown as Promise,
+ * ...options
+ * });
+ */
+export function buildUseMutationHook(
+ op: OperationInfo,
+ ctx: GenerationContext,
+): VariableStatementStructure {
+ const hookName = `use${op.capitalizedMethodName}`;
+ const errorType = getErrorType(op, ctx);
+ const dataTypeDefault = `Common.${op.capitalizedMethodName}MutationResult`;
+
+ const dataTypeName = ctx.modelNames.includes(
+ `${op.capitalizedMethodName}Data`,
+ )
+ ? `${op.capitalizedMethodName}Data`
+ : "unknown";
+
+ const optionsType = `Options<${dataTypeName}, true>`;
+
+ const mutationFn = `clientOptions => ${op.methodName}(clientOptions) as unknown as Promise`;
+
+ const body = `useMutation({ mutationKey: Common.Use${op.capitalizedMethodName}KeyFn(mutationKey), mutationFn: ${mutationFn}, ...options })`;
+
+ return {
+ kind: StructureKind.VariableStatement,
+ isExported: true,
+ declarationKind: VariableDeclarationKind.Const,
+ declarations: [
+ {
+ name: hookName,
+ initializer: ` = unknown[], TContext = unknown>(mutationKey?: TQueryKey, options?: Omit, "mutationKey" | "mutationFn">) => ${body}`,
+ },
+ ],
+ };
+}
diff --git a/src/tsmorph/buildQueryHooks.mts b/src/tsmorph/buildQueryHooks.mts
new file mode 100644
index 0000000..8f58a02
--- /dev/null
+++ b/src/tsmorph/buildQueryHooks.mts
@@ -0,0 +1,292 @@
+import {
+ StructureKind,
+ VariableDeclarationKind,
+ type VariableStatementStructure,
+} from "ts-morph";
+import type { GenerationContext, OperationInfo } from "../types.mjs";
+
+type QueryHookType = "useQuery" | "useSuspenseQuery" | "useInfiniteQuery";
+
+/**
+ * Get the error type string based on client type.
+ */
+function getErrorType(op: OperationInfo, ctx: GenerationContext): string {
+ const errorTypeName = `${op.capitalizedMethodName}Error`;
+ if (ctx.client === "@hey-api/client-axios") {
+ return `AxiosError<${errorTypeName}>`;
+ }
+ return errorTypeName;
+}
+
+/**
+ * Get the data type based on hook type.
+ */
+function getDataTypeDefault(
+ op: OperationInfo,
+ hookType: QueryHookType,
+): string {
+ const baseType = `Common.${op.capitalizedMethodName}DefaultResponse`;
+ if (hookType === "useSuspenseQuery") {
+ return `NonNullable<${baseType}>`;
+ }
+ if (hookType === "useInfiniteQuery") {
+ return `InfiniteData<${baseType}>`;
+ }
+ return baseType;
+}
+
+/**
+ * Get the options type name.
+ */
+function getOptionsTypeName(hookType: QueryHookType): string {
+ switch (hookType) {
+ case "useSuspenseQuery":
+ return "UseSuspenseQueryOptions";
+ case "useInfiniteQuery":
+ return "UseInfiniteQueryOptions";
+ default:
+ return "UseQueryOptions";
+ }
+}
+
+/**
+ * Build the client options parameter string.
+ */
+function buildClientOptionsParam(
+ op: OperationInfo,
+ ctx: GenerationContext,
+): string {
+ const dataTypeName = ctx.modelNames.includes(
+ `${op.capitalizedMethodName}Data`,
+ )
+ ? `${op.capitalizedMethodName}Data`
+ : "unknown";
+
+ const hasParams = op.parameters.length > 0;
+ if (!hasParams) {
+ return `clientOptions: Options<${dataTypeName}, true> = {}`;
+ }
+
+ const defaultValue = op.allParamsOptional ? " = {}" : "";
+ return `clientOptions: Options<${dataTypeName}, true>${defaultValue}`;
+}
+
+/**
+ * Build useQuery hook.
+ * Example:
+ * export const useFindPets = = unknown[]>(
+ * clientOptions: Options = {},
+ * queryKey?: TQueryKey,
+ * options?: Omit, "queryKey" | "queryFn">
+ * ) => useQuery({
+ * queryKey: Common.UseFindPetsKeyFn(clientOptions, queryKey),
+ * queryFn: () => findPets({ ...clientOptions }).then(response => response.data as TData) as TData,
+ * ...options
+ * });
+ */
+export function buildUseQueryHook(
+ op: OperationInfo,
+ ctx: GenerationContext,
+): VariableStatementStructure {
+ const hookName = `use${op.capitalizedMethodName}`;
+ const errorType = getErrorType(op, ctx);
+ const dataTypeDefault = getDataTypeDefault(op, "useQuery");
+ const clientOptionsParam = buildClientOptionsParam(op, ctx);
+ const hasParams = op.parameters.length > 0;
+
+ // Build the queryFn body
+ const callArgs = hasParams ? "{ ...clientOptions }" : "{ ...clientOptions }";
+ const queryFn = `() => ${op.methodName}(${callArgs}).then(response => response.data as TData) as TData`;
+
+ const body = `useQuery({ queryKey: Common.Use${op.capitalizedMethodName}KeyFn(clientOptions, queryKey), queryFn: ${queryFn}, ...options })`;
+
+ return {
+ kind: StructureKind.VariableStatement,
+ isExported: true,
+ declarationKind: VariableDeclarationKind.Const,
+ declarations: [
+ {
+ name: hookName,
+ initializer: ` = unknown[]>(${clientOptionsParam}, queryKey?: TQueryKey, options?: Omit, "queryKey" | "queryFn">) => ${body}`,
+ },
+ ],
+ };
+}
+
+/**
+ * Build useSuspenseQuery hook.
+ */
+export function buildUseSuspenseQueryHook(
+ op: OperationInfo,
+ ctx: GenerationContext,
+): VariableStatementStructure {
+ const hookName = `use${op.capitalizedMethodName}Suspense`;
+ const errorType = getErrorType(op, ctx);
+ const dataTypeDefault = getDataTypeDefault(op, "useSuspenseQuery");
+ const clientOptionsParam = buildClientOptionsParam(op, ctx);
+ const hasParams = op.parameters.length > 0;
+
+ const callArgs = hasParams ? "{ ...clientOptions }" : "{ ...clientOptions }";
+ const queryFn = `() => ${op.methodName}(${callArgs}).then(response => response.data as TData) as TData`;
+
+ const body = `useSuspenseQuery({ queryKey: Common.Use${op.capitalizedMethodName}KeyFn(clientOptions, queryKey), queryFn: ${queryFn}, ...options })`;
+
+ return {
+ kind: StructureKind.VariableStatement,
+ isExported: true,
+ declarationKind: VariableDeclarationKind.Const,
+ declarations: [
+ {
+ name: hookName,
+ initializer: ` = unknown[]>(${clientOptionsParam}, queryKey?: TQueryKey, options?: Omit, "queryKey" | "queryFn">) => ${body}`,
+ },
+ ],
+ };
+}
+
+/**
+ * Build the nested type for getNextPageParam.
+ * E.g., "meta.next" becomes "{ meta: { next: number } }"
+ */
+function buildNestedNextPageType(nextPageParam: string): string {
+ const segments = nextPageParam.split(".");
+ return segments.reduceRight((acc, segment) => {
+ return `{ ${segment}: ${acc} }`;
+ }, "number");
+}
+
+/**
+ * Build useInfiniteQuery hook.
+ */
+export function buildUseInfiniteQueryHook(
+ op: OperationInfo,
+ ctx: GenerationContext,
+): VariableStatementStructure | null {
+ if (!op.isPaginatable) {
+ return null;
+ }
+
+ const hookName = `use${op.capitalizedMethodName}Infinite`;
+ const errorType = getErrorType(op, ctx);
+ const baseDataType = `Common.${op.capitalizedMethodName}DefaultResponse`;
+ const dataTypeName = ctx.modelNames.includes(
+ `${op.capitalizedMethodName}Data`,
+ )
+ ? `${op.capitalizedMethodName}Data`
+ : "unknown";
+
+ const defaultValue = op.allParamsOptional ? " = {}" : "";
+ const clientOptionsParam = `clientOptions: Options<${dataTypeName}, true>${defaultValue}`;
+
+ // Build the queryFn with pageParam handling
+ const queryFn = `({ pageParam }) => ${op.methodName}({ ...clientOptions, query: { ...clientOptions.query, ${ctx.pageParam}: pageParam as number } }).then(response => response.data as TData) as TData`;
+
+ // Build getNextPageParam with nested type
+ const nestedType = buildNestedNextPageType(ctx.nextPageParam);
+ const getNextPageParam = `getNextPageParam: (response) => (response as ${nestedType}).${ctx.nextPageParam}`;
+
+ // initialPageParam is a string literal
+ const infiniteOptions = `initialPageParam: "${ctx.initialPageParam}", ${getNextPageParam}`;
+
+ const body = `useInfiniteQuery({ queryKey: Common.Use${op.capitalizedMethodName}KeyFn(clientOptions, queryKey), queryFn: ${queryFn}, ${infiniteOptions}, ...options })`;
+
+ return {
+ kind: StructureKind.VariableStatement,
+ isExported: true,
+ declarationKind: VariableDeclarationKind.Const,
+ declarations: [
+ {
+ name: hookName,
+ initializer: `, TError = ${errorType}, TQueryKey extends Array = unknown[]>(${clientOptionsParam}, queryKey?: TQueryKey, options?: Omit, "queryKey" | "queryFn">) => ${body}`,
+ },
+ ],
+ };
+}
+
+/**
+ * Build prefetch function.
+ * Example:
+ * export const prefetchUseFindPets = (queryClient: QueryClient, clientOptions: Options = {}) =>
+ * queryClient.prefetchQuery({
+ * queryKey: Common.UseFindPetsKeyFn(clientOptions),
+ * queryFn: () => findPets({ ...clientOptions }).then(response => response.data)
+ * });
+ */
+export function buildPrefetchFn(
+ op: OperationInfo,
+ ctx: GenerationContext,
+): VariableStatementStructure {
+ const fnName = `prefetchUse${op.capitalizedMethodName}`;
+ const dataTypeName = ctx.modelNames.includes(
+ `${op.capitalizedMethodName}Data`,
+ )
+ ? `${op.capitalizedMethodName}Data`
+ : "unknown";
+
+ const hasParams = op.parameters.length > 0;
+ const defaultValue = op.allParamsOptional ? " = {}" : "";
+ const clientOptionsParam = hasParams
+ ? `clientOptions: Options<${dataTypeName}, true>${defaultValue}`
+ : `clientOptions: Options<${dataTypeName}, true> = {}`;
+
+ const callArgs = "{ ...clientOptions }";
+ const queryFn = `() => ${op.methodName}(${callArgs}).then(response => response.data)`;
+
+ const body = `queryClient.prefetchQuery({ queryKey: Common.Use${op.capitalizedMethodName}KeyFn(clientOptions), queryFn: ${queryFn} })`;
+
+ return {
+ kind: StructureKind.VariableStatement,
+ isExported: true,
+ declarationKind: VariableDeclarationKind.Const,
+ declarations: [
+ {
+ name: fnName,
+ initializer: `(queryClient: QueryClient, ${clientOptionsParam}) => ${body}`,
+ },
+ ],
+ };
+}
+
+/**
+ * Build ensureQueryData function.
+ * Example:
+ * export const ensureUseFindPetsData = (queryClient: QueryClient, clientOptions: Options = {}) =>
+ * queryClient.ensureQueryData({
+ * queryKey: Common.UseFindPetsKeyFn(clientOptions),
+ * queryFn: () => findPets({ ...clientOptions }).then(response => response.data)
+ * });
+ */
+export function buildEnsureQueryDataFn(
+ op: OperationInfo,
+ ctx: GenerationContext,
+): VariableStatementStructure {
+ const fnName = `ensureUse${op.capitalizedMethodName}Data`;
+ const dataTypeName = ctx.modelNames.includes(
+ `${op.capitalizedMethodName}Data`,
+ )
+ ? `${op.capitalizedMethodName}Data`
+ : "unknown";
+
+ const hasParams = op.parameters.length > 0;
+ const defaultValue = op.allParamsOptional ? " = {}" : "";
+ const clientOptionsParam = hasParams
+ ? `clientOptions: Options<${dataTypeName}, true>${defaultValue}`
+ : `clientOptions: Options<${dataTypeName}, true> = {}`;
+
+ const callArgs = "{ ...clientOptions }";
+ const queryFn = `() => ${op.methodName}(${callArgs}).then(response => response.data)`;
+
+ const body = `queryClient.ensureQueryData({ queryKey: Common.Use${op.capitalizedMethodName}KeyFn(clientOptions), queryFn: ${queryFn} })`;
+
+ return {
+ kind: StructureKind.VariableStatement,
+ isExported: true,
+ declarationKind: VariableDeclarationKind.Const,
+ declarations: [
+ {
+ name: fnName,
+ initializer: `(queryClient: QueryClient, ${clientOptionsParam}) => ${body}`,
+ },
+ ],
+ };
+}
diff --git a/src/tsmorph/generateFiles.mts b/src/tsmorph/generateFiles.mts
new file mode 100644
index 0000000..b389aa2
--- /dev/null
+++ b/src/tsmorph/generateFiles.mts
@@ -0,0 +1,359 @@
+import {
+ type ExportDeclarationStructure,
+ type ImportDeclarationStructure,
+ Project,
+ StructureKind,
+ type TypeAliasDeclarationStructure,
+ type VariableStatementStructure,
+} from "ts-morph";
+import { OpenApiRqFiles } from "../constants.mjs";
+import type {
+ GeneratedFile,
+ GenerationContext,
+ OperationInfo,
+} from "../types.mjs";
+import {
+ buildDefaultResponseType,
+ buildMutationKeyConst,
+ buildMutationKeyFn,
+ buildMutationResultType,
+ buildQueryKeyConst,
+ buildQueryKeyFn,
+ buildQueryResultType,
+} from "./buildCommon.mjs";
+import { buildUseMutationHook } from "./buildMutationHooks.mjs";
+import {
+ buildEnsureQueryDataFn,
+ buildPrefetchFn,
+ buildUseInfiniteQueryHook,
+ buildUseQueryHook,
+ buildUseSuspenseQueryHook,
+} from "./buildQueryHooks.mjs";
+import {
+ buildAxiosErrorImport,
+ buildClientImport,
+ buildCommonImport,
+ buildModelImport,
+ buildQueryImport,
+ buildServiceImport,
+ createGenerationProject,
+} from "./projectFactory.mjs";
+
+/**
+ * Build imports for common.ts file.
+ */
+function buildCommonFileImports(
+ ctx: GenerationContext,
+): ImportDeclarationStructure[] {
+ const imports: ImportDeclarationStructure[] = [
+ buildClientImport(ctx),
+ buildQueryImport(),
+ buildServiceImport(ctx),
+ ];
+
+ const modelImport = buildModelImport(ctx);
+ if (modelImport) {
+ imports.push(modelImport);
+ }
+
+ if (ctx.client === "@hey-api/client-axios") {
+ imports.push(buildAxiosErrorImport());
+ }
+
+ return imports;
+}
+
+/**
+ * Build imports for hook files (queries, suspense, infinite, prefetch, ensure).
+ */
+function buildHookFileImports(
+ ctx: GenerationContext,
+): ImportDeclarationStructure[] {
+ return [buildCommonImport(), ...buildCommonFileImports(ctx)];
+}
+
+/**
+ * Generate the index.ts file content.
+ */
+function generateIndexFile(ctx: GenerationContext): string {
+ const project = createGenerationProject();
+ const sourceFile = project.createSourceFile(
+ `${OpenApiRqFiles.index}.ts`,
+ undefined,
+ { overwrite: true },
+ );
+
+ const exports: ExportDeclarationStructure[] = [
+ {
+ kind: StructureKind.ExportDeclaration,
+ moduleSpecifier: "./common",
+ },
+ {
+ kind: StructureKind.ExportDeclaration,
+ moduleSpecifier: "./queries",
+ },
+ ];
+
+ sourceFile.addExportDeclarations(exports);
+
+ return sourceFile.getFullText();
+}
+
+/**
+ * Generate the common.ts file content.
+ */
+function generateCommonFile(
+ operations: OperationInfo[],
+ ctx: GenerationContext,
+): string {
+ const project = createGenerationProject();
+ const sourceFile = project.createSourceFile(
+ `${OpenApiRqFiles.common}.ts`,
+ undefined,
+ { overwrite: true },
+ );
+
+ // Add imports
+ sourceFile.addImportDeclarations(buildCommonFileImports(ctx));
+
+ // Group operations by HTTP method
+ const getOperations = operations.filter((op) => op.httpMethod === "GET");
+ const mutationOperations = operations.filter((op) =>
+ ["POST", "PUT", "PATCH", "DELETE"].includes(op.httpMethod),
+ );
+
+ // Add query types and keys
+ for (const op of getOperations) {
+ sourceFile.addTypeAlias(buildDefaultResponseType(op));
+ sourceFile.addTypeAlias(buildQueryResultType(op));
+ sourceFile.addVariableStatement(buildQueryKeyConst(op));
+ sourceFile.addVariableStatement(buildQueryKeyFn(op, ctx));
+ }
+
+ // Add mutation types and keys
+ for (const op of mutationOperations) {
+ sourceFile.addTypeAlias(buildMutationResultType(op));
+ sourceFile.addVariableStatement(buildMutationKeyConst(op));
+ sourceFile.addVariableStatement(buildMutationKeyFn(op));
+ }
+
+ return sourceFile.getFullText();
+}
+
+/**
+ * Generate the queries.ts file content.
+ */
+function generateQueriesFile(
+ operations: OperationInfo[],
+ ctx: GenerationContext,
+): string {
+ const project = createGenerationProject();
+ const sourceFile = project.createSourceFile(
+ `${OpenApiRqFiles.queries}.ts`,
+ undefined,
+ { overwrite: true },
+ );
+
+ // Add imports
+ sourceFile.addImportDeclarations(buildHookFileImports(ctx));
+
+ // Group operations
+ const getOperations = operations.filter((op) => op.httpMethod === "GET");
+ const mutationOperations = operations.filter((op) =>
+ ["POST", "PUT", "PATCH", "DELETE"].includes(op.httpMethod),
+ );
+
+ // Add useQuery hooks
+ for (const op of getOperations) {
+ sourceFile.addVariableStatement(buildUseQueryHook(op, ctx));
+ }
+
+ // Add useMutation hooks
+ for (const op of mutationOperations) {
+ sourceFile.addVariableStatement(buildUseMutationHook(op, ctx));
+ }
+
+ return sourceFile.getFullText();
+}
+
+/**
+ * Generate the suspense.ts file content.
+ */
+function generateSuspenseFile(
+ operations: OperationInfo[],
+ ctx: GenerationContext,
+): string {
+ const project = createGenerationProject();
+ const sourceFile = project.createSourceFile(
+ `${OpenApiRqFiles.suspense}.ts`,
+ undefined,
+ { overwrite: true },
+ );
+
+ // Add imports
+ sourceFile.addImportDeclarations(buildHookFileImports(ctx));
+
+ // Only GET operations for suspense
+ const getOperations = operations.filter((op) => op.httpMethod === "GET");
+
+ // Add useSuspenseQuery hooks
+ for (const op of getOperations) {
+ sourceFile.addVariableStatement(buildUseSuspenseQueryHook(op, ctx));
+ }
+
+ return sourceFile.getFullText();
+}
+
+/**
+ * Generate the infiniteQueries.ts file content.
+ */
+function generateInfiniteQueriesFile(
+ operations: OperationInfo[],
+ ctx: GenerationContext,
+): string {
+ const project = createGenerationProject();
+ const sourceFile = project.createSourceFile(
+ `${OpenApiRqFiles.infiniteQueries}.ts`,
+ undefined,
+ { overwrite: true },
+ );
+
+ // Add imports
+ sourceFile.addImportDeclarations(buildHookFileImports(ctx));
+
+ // Only paginatable GET operations
+ const paginatableOperations = operations.filter(
+ (op) => op.httpMethod === "GET" && op.isPaginatable,
+ );
+
+ // Add useInfiniteQuery hooks
+ for (const op of paginatableOperations) {
+ const hook = buildUseInfiniteQueryHook(op, ctx);
+ if (hook) {
+ sourceFile.addVariableStatement(hook);
+ }
+ }
+
+ return sourceFile.getFullText();
+}
+
+/**
+ * Generate the prefetch.ts file content.
+ */
+function generatePrefetchFile(
+ operations: OperationInfo[],
+ ctx: GenerationContext,
+): string {
+ const project = createGenerationProject();
+ const sourceFile = project.createSourceFile(
+ `${OpenApiRqFiles.prefetch}.ts`,
+ undefined,
+ { overwrite: true },
+ );
+
+ // Add imports
+ sourceFile.addImportDeclarations(buildHookFileImports(ctx));
+
+ // Only GET operations for prefetch
+ const getOperations = operations.filter((op) => op.httpMethod === "GET");
+
+ // Add prefetch functions
+ for (const op of getOperations) {
+ sourceFile.addVariableStatement(buildPrefetchFn(op, ctx));
+ }
+
+ return sourceFile.getFullText();
+}
+
+/**
+ * Generate the ensureQueryData.ts file content.
+ */
+function generateEnsureQueryDataFile(
+ operations: OperationInfo[],
+ ctx: GenerationContext,
+): string {
+ const project = createGenerationProject();
+ const sourceFile = project.createSourceFile(
+ `${OpenApiRqFiles.ensureQueryData}.ts`,
+ undefined,
+ { overwrite: true },
+ );
+
+ // Add imports
+ sourceFile.addImportDeclarations(buildHookFileImports(ctx));
+
+ // Only GET operations for ensure
+ const getOperations = operations.filter((op) => op.httpMethod === "GET");
+
+ // Add ensureQueryData functions
+ for (const op of getOperations) {
+ sourceFile.addVariableStatement(buildEnsureQueryDataFn(op, ctx));
+ }
+
+ return sourceFile.getFullText();
+}
+
+/**
+ * Add the generated header comment to file content.
+ */
+function addHeaderComment(content: string, version: string): string {
+ const comment = `// generated with @7nohe/openapi-react-query-codegen@${version} \n\n`;
+ return comment + content;
+}
+
+/**
+ * Generate all files using ts-morph.
+ */
+export function generateAllFiles(
+ operations: OperationInfo[],
+ ctx: GenerationContext,
+): GeneratedFile[] {
+ return [
+ {
+ name: `${OpenApiRqFiles.index}.ts`,
+ content: addHeaderComment(generateIndexFile(ctx), ctx.version),
+ },
+ {
+ name: `${OpenApiRqFiles.common}.ts`,
+ content: addHeaderComment(
+ generateCommonFile(operations, ctx),
+ ctx.version,
+ ),
+ },
+ {
+ name: `${OpenApiRqFiles.queries}.ts`,
+ content: addHeaderComment(
+ generateQueriesFile(operations, ctx),
+ ctx.version,
+ ),
+ },
+ {
+ name: `${OpenApiRqFiles.suspense}.ts`,
+ content: addHeaderComment(
+ generateSuspenseFile(operations, ctx),
+ ctx.version,
+ ),
+ },
+ {
+ name: `${OpenApiRqFiles.infiniteQueries}.ts`,
+ content: addHeaderComment(
+ generateInfiniteQueriesFile(operations, ctx),
+ ctx.version,
+ ),
+ },
+ {
+ name: `${OpenApiRqFiles.prefetch}.ts`,
+ content: addHeaderComment(
+ generatePrefetchFile(operations, ctx),
+ ctx.version,
+ ),
+ },
+ {
+ name: `${OpenApiRqFiles.ensureQueryData}.ts`,
+ content: addHeaderComment(
+ generateEnsureQueryDataFile(operations, ctx),
+ ctx.version,
+ ),
+ },
+ ];
+}
diff --git a/src/tsmorph/index.mts b/src/tsmorph/index.mts
new file mode 100644
index 0000000..59e3c0a
--- /dev/null
+++ b/src/tsmorph/index.mts
@@ -0,0 +1,5 @@
+export { generateAllFiles } from "./generateFiles.mjs";
+export { createGenerationProject } from "./projectFactory.mjs";
+export * from "./buildCommon.mjs";
+export * from "./buildQueryHooks.mjs";
+export * from "./buildMutationHooks.mjs";
diff --git a/src/tsmorph/projectFactory.mts b/src/tsmorph/projectFactory.mts
new file mode 100644
index 0000000..00a26b4
--- /dev/null
+++ b/src/tsmorph/projectFactory.mts
@@ -0,0 +1,151 @@
+import {
+ type ImportDeclarationStructure,
+ IndentationText,
+ NewLineKind,
+ Project,
+ QuoteKind,
+ StructureKind,
+} from "ts-morph";
+import type { GenerationContext } from "../types.mjs";
+
+/**
+ * Create a shared ts-morph Project for code generation.
+ * Uses consistent formatting settings to match existing output.
+ */
+export function createGenerationProject(): Project {
+ return new Project({
+ useInMemoryFileSystem: true,
+ compilerOptions: {
+ strict: true,
+ },
+ manipulationSettings: {
+ indentationText: IndentationText.TwoSpaces,
+ newLineKind: NewLineKind.LineFeed,
+ quoteKind: QuoteKind.Double,
+ useTrailingCommas: true,
+ },
+ });
+}
+
+/**
+ * Build import structure for client library.
+ * In v0.73+, Options type is exported from the generated client file.
+ */
+export function buildClientImport(
+ _ctx: GenerationContext,
+): ImportDeclarationStructure {
+ return {
+ kind: StructureKind.ImportDeclaration,
+ moduleSpecifier: "../requests/client",
+ namedImports: [{ name: "Options", isTypeOnly: true }],
+ };
+}
+
+/**
+ * Build import structure for TanStack Query.
+ */
+export function buildQueryImport(): ImportDeclarationStructure {
+ return {
+ kind: StructureKind.ImportDeclaration,
+ moduleSpecifier: "@tanstack/react-query",
+ namedImports: [
+ { name: "QueryClient", isTypeOnly: true },
+ { name: "useQuery" },
+ { name: "useSuspenseQuery" },
+ { name: "useInfiniteQuery" },
+ { name: "useMutation" },
+ { name: "UseQueryResult" },
+ { name: "UseQueryOptions" },
+ { name: "UseInfiniteQueryOptions" },
+ { name: "UseMutationOptions" },
+ { name: "UseMutationResult" },
+ { name: "UseSuspenseQueryOptions" },
+ { name: "InfiniteData" },
+ ],
+ };
+}
+
+/**
+ * Build import structure for services.
+ */
+export function buildServiceImport(
+ ctx: GenerationContext,
+): ImportDeclarationStructure {
+ return {
+ kind: StructureKind.ImportDeclaration,
+ moduleSpecifier: "../requests/sdk.gen",
+ namedImports: ctx.serviceNames.map((name) => ({ name })),
+ };
+}
+
+/**
+ * Build import structure for models.
+ */
+export function buildModelImport(
+ ctx: GenerationContext,
+): ImportDeclarationStructure | null {
+ if (ctx.modelNames.length === 0) {
+ return null;
+ }
+
+ return {
+ kind: StructureKind.ImportDeclaration,
+ moduleSpecifier: "../requests/types.gen",
+ namedImports: ctx.modelNames.map((name) => ({ name })),
+ };
+}
+
+/**
+ * Build import structure for axios error type.
+ */
+export function buildAxiosErrorImport(): ImportDeclarationStructure {
+ return {
+ kind: StructureKind.ImportDeclaration,
+ moduleSpecifier: "axios",
+ namedImports: [{ name: "AxiosError" }],
+ };
+}
+
+/**
+ * Build import for Common namespace.
+ */
+export function buildCommonImport(): ImportDeclarationStructure {
+ return {
+ kind: StructureKind.ImportDeclaration,
+ moduleSpecifier: "./common",
+ namespaceImport: "Common",
+ };
+}
+
+/**
+ * Build all imports needed for the common file.
+ */
+export function buildCommonFileImports(
+ ctx: GenerationContext,
+): ImportDeclarationStructure[] {
+ const imports: ImportDeclarationStructure[] = [
+ buildClientImport(ctx),
+ buildQueryImport(),
+ buildServiceImport(ctx),
+ ];
+
+ const modelImport = buildModelImport(ctx);
+ if (modelImport) {
+ imports.push(modelImport);
+ }
+
+ if (ctx.client === "@hey-api/client-axios") {
+ imports.push(buildAxiosErrorImport());
+ }
+
+ return imports;
+}
+
+/**
+ * Build all imports needed for hook files (queries, suspense, infinite).
+ */
+export function buildHookFileImports(
+ ctx: GenerationContext,
+): ImportDeclarationStructure[] {
+ return [buildCommonImport(), ...buildCommonFileImports(ctx)];
+}
diff --git a/src/types.mts b/src/types.mts
new file mode 100644
index 0000000..ec13a15
--- /dev/null
+++ b/src/types.mts
@@ -0,0 +1,62 @@
+/**
+ * Normalized operation information extracted from the OpenAPI service.
+ * This is a pure JSON-serializable structure that can be consumed by generators.
+ */
+export interface OperationInfo {
+ /** Method/function name as defined in service (e.g., "findPets") */
+ methodName: string;
+ /** Capitalized method name (e.g., "FindPets") */
+ capitalizedMethodName: string;
+ /** HTTP method (e.g., "GET", "POST", "PUT", "PATCH", "DELETE") */
+ httpMethod: string;
+ /** JSDoc comment string (if present) */
+ jsDoc?: string;
+ /** Whether the operation is deprecated */
+ isDeprecated: boolean;
+ /** Parameter information for the operation */
+ parameters: OperationParameter[];
+ /** Whether all parameters are optional */
+ allParamsOptional: boolean;
+ /** Whether this operation supports pagination (for infinite queries) */
+ isPaginatable: boolean;
+}
+
+export interface OperationParameter {
+ /** Parameter name */
+ name: string;
+ /** TypeScript type as string */
+ typeName: string;
+ /** Whether this parameter is optional */
+ optional: boolean;
+}
+
+/**
+ * Context for generating hooks and utilities.
+ * Contains shared information needed across all generators.
+ */
+export interface GenerationContext {
+ /** Client type: "@hey-api/client-fetch" or "@hey-api/client-axios" */
+ client: "@hey-api/client-fetch" | "@hey-api/client-axios";
+ /** Model type names exported from the models file */
+ modelNames: string[];
+ /** Service function names exported from the service file */
+ serviceNames: string[];
+ /** Page param name for infinite queries (e.g., "page") */
+ pageParam: string;
+ /** Next page param name for infinite queries (e.g., "nextPage") */
+ nextPageParam: string;
+ /** Initial page param value for infinite queries */
+ initialPageParam: string;
+ /** Package version for generated comment */
+ version: string;
+}
+
+/**
+ * Generated output for a single file.
+ */
+export interface GeneratedFile {
+ /** Filename without path (e.g., "queries.ts") */
+ name: string;
+ /** File content as string */
+ content: string;
+}
diff --git a/tests/__snapshots__/createSource.test.ts.snap b/tests/__snapshots__/createSource.test.ts.snap
index 5face87..4a71cd1 100644
--- a/tests/__snapshots__/createSource.test.ts.snap
+++ b/tests/__snapshots__/createSource.test.ts.snap
@@ -11,34 +11,48 @@ export * from "./queries";
exports[`createSource > createSource - @hey-api/client-axios 2`] = `
"// generated with @7nohe/openapi-react-query-codegen@1.0.0
-import { type Options } from "@hey-api/client-axios";
-import { type QueryClient, useQuery, useSuspenseQuery, useMutation, UseQueryResult, UseQueryOptions, UseMutationOptions, UseMutationResult, UseSuspenseQueryOptions } from "@tanstack/react-query";
-import { client, findPets, addPet, getNotDefined, postNotDefined, findPetById, deletePet, findPaginatedPets } from "../requests/services.gen";
-import { Pet, NewPet, Error, FindPetsData, FindPetsResponse, FindPetsError, AddPetData, AddPetResponse, AddPetError, GetNotDefinedResponse, GetNotDefinedError, PostNotDefinedResponse, PostNotDefinedError, FindPetByIdData, FindPetByIdResponse, FindPetByIdError, DeletePetData, DeletePetResponse, DeletePetError, FindPaginatedPetsData, FindPaginatedPetsResponse, FindPaginatedPetsError } from "../requests/types.gen";
+import { type Options } from "../requests/client";
+import { type QueryClient, useQuery, useSuspenseQuery, useInfiniteQuery, useMutation, UseQueryResult, UseQueryOptions, UseInfiniteQueryOptions, UseMutationOptions, UseMutationResult, UseSuspenseQueryOptions, InfiniteData } from "@tanstack/react-query";
+import { Options, findPets, addPet, getNotDefined, postNotDefined, deletePet, findPetById, findPaginatedPets } from "../requests/sdk.gen";
+import { Pet, NewPet, _Error, FindPetsData, FindPetsErrors, FindPetsError, FindPetsResponses, FindPetsResponse, AddPetData, AddPetErrors, AddPetError, AddPetResponses, AddPetResponse, GetNotDefinedData, GetNotDefinedErrors, GetNotDefinedResponses, PostNotDefinedData, PostNotDefinedErrors, PostNotDefinedResponses, DeletePetData, DeletePetErrors, DeletePetError, DeletePetResponses, DeletePetResponse, FindPetByIdData, FindPetByIdErrors, FindPetByIdError, FindPetByIdResponses, FindPetByIdResponse, FindPaginatedPetsData, FindPaginatedPetsResponses, FindPaginatedPetsResponse, ClientOptions } from "../requests/types.gen";
import { AxiosError } from "axios";
+
export type FindPetsDefaultResponse = Awaited>["data"];
export type FindPetsQueryResult = UseQueryResult;
+
export const useFindPetsKey = "FindPets";
export const UseFindPetsKeyFn = (clientOptions: Options = {}, queryKey?: Array) => [useFindPetsKey, ...(queryKey ?? [clientOptions])];
+
export type GetNotDefinedDefaultResponse = Awaited>["data"];
export type GetNotDefinedQueryResult = UseQueryResult;
+
export const useGetNotDefinedKey = "GetNotDefined";
-export const UseGetNotDefinedKeyFn = (clientOptions: Options = {}, queryKey?: Array) => [useGetNotDefinedKey, ...(queryKey ?? [clientOptions])];
+export const UseGetNotDefinedKeyFn = (clientOptions: Options = {}, queryKey?: Array) => [useGetNotDefinedKey, ...(queryKey ?? [clientOptions])];
+
export type FindPetByIdDefaultResponse = Awaited>["data"];
export type FindPetByIdQueryResult = UseQueryResult;
+
export const useFindPetByIdKey = "FindPetById";
-export const UseFindPetByIdKeyFn = (clientOptions: Options, queryKey?: Array) => [useFindPetByIdKey, ...(queryKey ?? [clientOptions])];
+export const UseFindPetByIdKeyFn = (clientOptions: Options = {}, queryKey?: Array) => [useFindPetByIdKey, ...(queryKey ?? [clientOptions])];
+
export type FindPaginatedPetsDefaultResponse = Awaited>["data"];
export type FindPaginatedPetsQueryResult = UseQueryResult;
+
export const useFindPaginatedPetsKey = "FindPaginatedPets";
export const UseFindPaginatedPetsKeyFn = (clientOptions: Options = {}, queryKey?: Array) => [useFindPaginatedPetsKey, ...(queryKey ?? [clientOptions])];
+
export type AddPetMutationResult = Awaited>;
+
export const useAddPetKey = "AddPet";
export const UseAddPetKeyFn = (mutationKey?: Array) => [useAddPetKey, ...(mutationKey ?? [])];
+
export type PostNotDefinedMutationResult = Awaited>;
+
export const usePostNotDefinedKey = "PostNotDefined";
export const UsePostNotDefinedKeyFn = (mutationKey?: Array) => [usePostNotDefinedKey, ...(mutationKey ?? [])];
+
export type DeletePetMutationResult = Awaited>;
+
export const useDeletePetKey = "DeletePet";
export const UseDeletePetKeyFn = (mutationKey?: Array) => [useDeletePetKey, ...(mutationKey ?? [])];
"
@@ -48,17 +62,18 @@ exports[`createSource > createSource - @hey-api/client-axios 3`] = `
"// generated with @7nohe/openapi-react-query-codegen@1.0.0
import * as Common from "./common";
-import { type Options } from "@hey-api/client-axios";
-import { type QueryClient, useQuery, useSuspenseQuery, useMutation, UseQueryResult, UseQueryOptions, UseMutationOptions, UseMutationResult, UseSuspenseQueryOptions } from "@tanstack/react-query";
-import { client, findPets, addPet, getNotDefined, postNotDefined, findPetById, deletePet, findPaginatedPets } from "../requests/services.gen";
-import { Pet, NewPet, Error, FindPetsData, FindPetsResponse, FindPetsError, AddPetData, AddPetResponse, AddPetError, GetNotDefinedResponse, GetNotDefinedError, PostNotDefinedResponse, PostNotDefinedError, FindPetByIdData, FindPetByIdResponse, FindPetByIdError, DeletePetData, DeletePetResponse, DeletePetError, FindPaginatedPetsData, FindPaginatedPetsResponse, FindPaginatedPetsError } from "../requests/types.gen";
+import { type Options } from "../requests/client";
+import { type QueryClient, useQuery, useSuspenseQuery, useInfiniteQuery, useMutation, UseQueryResult, UseQueryOptions, UseInfiniteQueryOptions, UseMutationOptions, UseMutationResult, UseSuspenseQueryOptions, InfiniteData } from "@tanstack/react-query";
+import { Options, findPets, addPet, getNotDefined, postNotDefined, deletePet, findPetById, findPaginatedPets } from "../requests/sdk.gen";
+import { Pet, NewPet, _Error, FindPetsData, FindPetsErrors, FindPetsError, FindPetsResponses, FindPetsResponse, AddPetData, AddPetErrors, AddPetError, AddPetResponses, AddPetResponse, GetNotDefinedData, GetNotDefinedErrors, GetNotDefinedResponses, PostNotDefinedData, PostNotDefinedErrors, PostNotDefinedResponses, DeletePetData, DeletePetErrors, DeletePetError, DeletePetResponses, DeletePetResponse, FindPetByIdData, FindPetByIdErrors, FindPetByIdError, FindPetByIdResponses, FindPetByIdResponse, FindPaginatedPetsData, FindPaginatedPetsResponses, FindPaginatedPetsResponse, ClientOptions } from "../requests/types.gen";
import { AxiosError } from "axios";
+
export const useFindPets = , TQueryKey extends Array = unknown[]>(clientOptions: Options = {}, queryKey?: TQueryKey, options?: Omit, "queryKey" | "queryFn">) => useQuery({ queryKey: Common.UseFindPetsKeyFn(clientOptions, queryKey), queryFn: () => findPets({ ...clientOptions }).then(response => response.data as TData) as TData, ...options });
-export const useGetNotDefined = , TQueryKey extends Array = unknown[]>(clientOptions: Options = {}, queryKey?: TQueryKey, options?: Omit, "queryKey" | "queryFn">) => useQuery({ queryKey: Common.UseGetNotDefinedKeyFn(clientOptions, queryKey), queryFn: () => getNotDefined({ ...clientOptions }).then(response => response.data as TData) as TData, ...options });
-export const useFindPetById = , TQueryKey extends Array = unknown[]>(clientOptions: Options, queryKey?: TQueryKey, options?: Omit, "queryKey" | "queryFn">) => useQuery({ queryKey: Common.UseFindPetByIdKeyFn(clientOptions, queryKey), queryFn: () => findPetById({ ...clientOptions }).then(response => response.data as TData) as TData, ...options });
+export const useGetNotDefined = , TQueryKey extends Array = unknown[]>(clientOptions: Options = {}, queryKey?: TQueryKey, options?: Omit, "queryKey" | "queryFn">) => useQuery({ queryKey: Common.UseGetNotDefinedKeyFn(clientOptions, queryKey), queryFn: () => getNotDefined({ ...clientOptions }).then(response => response.data as TData) as TData, ...options });
+export const useFindPetById = , TQueryKey extends Array = unknown[]>(clientOptions: Options = {}, queryKey?: TQueryKey, options?: Omit, "queryKey" | "queryFn">) => useQuery({ queryKey: Common.UseFindPetByIdKeyFn(clientOptions, queryKey), queryFn: () => findPetById({ ...clientOptions }).then(response => response.data as TData) as TData, ...options });
export const useFindPaginatedPets = , TQueryKey extends Array = unknown[]>(clientOptions: Options = {}, queryKey?: TQueryKey, options?: Omit, "queryKey" | "queryFn">) => useQuery({ queryKey: Common.UseFindPaginatedPetsKeyFn(clientOptions, queryKey), queryFn: () => findPaginatedPets({ ...clientOptions }).then(response => response.data as TData) as TData, ...options });
export const useAddPet = , TQueryKey extends Array = unknown[], TContext = unknown>(mutationKey?: TQueryKey, options?: Omit, TContext>, "mutationKey" | "mutationFn">) => useMutation, TContext>({ mutationKey: Common.UseAddPetKeyFn(mutationKey), mutationFn: clientOptions => addPet(clientOptions) as unknown as Promise, ...options });
-export const usePostNotDefined = , TQueryKey extends Array = unknown[], TContext = unknown>(mutationKey?: TQueryKey, options?: Omit, TContext>, "mutationKey" | "mutationFn">) => useMutation, TContext>({ mutationKey: Common.UsePostNotDefinedKeyFn(mutationKey), mutationFn: clientOptions => postNotDefined(clientOptions) as unknown as Promise, ...options });
+export const usePostNotDefined = , TQueryKey extends Array = unknown[], TContext = unknown>(mutationKey?: TQueryKey, options?: Omit, TContext>, "mutationKey" | "mutationFn">) => useMutation, TContext>({ mutationKey: Common.UsePostNotDefinedKeyFn(mutationKey), mutationFn: clientOptions => postNotDefined(clientOptions) as unknown as Promise, ...options });
export const useDeletePet = , TQueryKey extends Array = unknown[], TContext = unknown>(mutationKey?: TQueryKey, options?: Omit, TContext>, "mutationKey" | "mutationFn">) => useMutation, TContext>({ mutationKey: Common.UseDeletePetKeyFn(mutationKey), mutationFn: clientOptions => deletePet(clientOptions) as unknown as Promise, ...options });
"
`;
@@ -67,14 +82,15 @@ exports[`createSource > createSource - @hey-api/client-axios 4`] = `
"// generated with @7nohe/openapi-react-query-codegen@1.0.0
import * as Common from "./common";
-import { type Options } from "@hey-api/client-axios";
-import { type QueryClient, useQuery, useSuspenseQuery, useMutation, UseQueryResult, UseQueryOptions, UseMutationOptions, UseMutationResult, UseSuspenseQueryOptions } from "@tanstack/react-query";
-import { client, findPets, addPet, getNotDefined, postNotDefined, findPetById, deletePet, findPaginatedPets } from "../requests/services.gen";
-import { Pet, NewPet, Error, FindPetsData, FindPetsResponse, FindPetsError, AddPetData, AddPetResponse, AddPetError, GetNotDefinedResponse, GetNotDefinedError, PostNotDefinedResponse, PostNotDefinedError, FindPetByIdData, FindPetByIdResponse, FindPetByIdError, DeletePetData, DeletePetResponse, DeletePetError, FindPaginatedPetsData, FindPaginatedPetsResponse, FindPaginatedPetsError } from "../requests/types.gen";
+import { type Options } from "../requests/client";
+import { type QueryClient, useQuery, useSuspenseQuery, useInfiniteQuery, useMutation, UseQueryResult, UseQueryOptions, UseInfiniteQueryOptions, UseMutationOptions, UseMutationResult, UseSuspenseQueryOptions, InfiniteData } from "@tanstack/react-query";
+import { Options, findPets, addPet, getNotDefined, postNotDefined, deletePet, findPetById, findPaginatedPets } from "../requests/sdk.gen";
+import { Pet, NewPet, _Error, FindPetsData, FindPetsErrors, FindPetsError, FindPetsResponses, FindPetsResponse, AddPetData, AddPetErrors, AddPetError, AddPetResponses, AddPetResponse, GetNotDefinedData, GetNotDefinedErrors, GetNotDefinedResponses, PostNotDefinedData, PostNotDefinedErrors, PostNotDefinedResponses, DeletePetData, DeletePetErrors, DeletePetError, DeletePetResponses, DeletePetResponse, FindPetByIdData, FindPetByIdErrors, FindPetByIdError, FindPetByIdResponses, FindPetByIdResponse, FindPaginatedPetsData, FindPaginatedPetsResponses, FindPaginatedPetsResponse, ClientOptions } from "../requests/types.gen";
import { AxiosError } from "axios";
+
export const useFindPetsSuspense = , TError = AxiosError, TQueryKey extends Array = unknown[]>(clientOptions: Options = {}, queryKey?: TQueryKey, options?: Omit, "queryKey" | "queryFn">) => useSuspenseQuery({ queryKey: Common.UseFindPetsKeyFn(clientOptions, queryKey), queryFn: () => findPets({ ...clientOptions }).then(response => response.data as TData) as TData, ...options });
-export const useGetNotDefinedSuspense = , TError = AxiosError, TQueryKey extends Array = unknown[]>(clientOptions: Options = {}, queryKey?: TQueryKey, options?: Omit, "queryKey" | "queryFn">) => useSuspenseQuery({ queryKey: Common.UseGetNotDefinedKeyFn(clientOptions, queryKey), queryFn: () => getNotDefined({ ...clientOptions }).then(response => response.data as TData) as TData, ...options });
-export const useFindPetByIdSuspense = , TError = AxiosError, TQueryKey extends Array = unknown[]>(clientOptions: Options, queryKey?: TQueryKey, options?: Omit, "queryKey" | "queryFn">) => useSuspenseQuery({ queryKey: Common.UseFindPetByIdKeyFn(clientOptions, queryKey), queryFn: () => findPetById({ ...clientOptions }).then(response => response.data as TData) as TData, ...options });
+export const useGetNotDefinedSuspense = , TError = AxiosError, TQueryKey extends Array = unknown[]>(clientOptions: Options = {}, queryKey?: TQueryKey, options?: Omit, "queryKey" | "queryFn">) => useSuspenseQuery({ queryKey: Common.UseGetNotDefinedKeyFn(clientOptions, queryKey), queryFn: () => getNotDefined({ ...clientOptions }).then(response => response.data as TData) as TData, ...options });
+export const useFindPetByIdSuspense = , TError = AxiosError, TQueryKey extends Array = unknown[]>(clientOptions: Options = {}, queryKey?: TQueryKey, options?: Omit, "queryKey" | "queryFn">) => useSuspenseQuery({ queryKey: Common.UseFindPetByIdKeyFn(clientOptions, queryKey), queryFn: () => findPetById({ ...clientOptions }).then(response => response.data as TData) as TData, ...options });
export const useFindPaginatedPetsSuspense = , TError = AxiosError, TQueryKey extends Array = unknown[]>(clientOptions: Options = {}, queryKey?: TQueryKey, options?: Omit, "queryKey" | "queryFn">) => useSuspenseQuery({ queryKey: Common.UseFindPaginatedPetsKeyFn(clientOptions, queryKey), queryFn: () => findPaginatedPets({ ...clientOptions }).then(response => response.data as TData) as TData, ...options });
"
`;
@@ -83,14 +99,15 @@ exports[`createSource > createSource - @hey-api/client-axios 5`] = `
"// generated with @7nohe/openapi-react-query-codegen@1.0.0
import * as Common from "./common";
-import { type Options } from "@hey-api/client-axios";
-import { type QueryClient, useQuery, useSuspenseQuery, useMutation, UseQueryResult, UseQueryOptions, UseMutationOptions, UseMutationResult, UseSuspenseQueryOptions } from "@tanstack/react-query";
-import { client, findPets, addPet, getNotDefined, postNotDefined, findPetById, deletePet, findPaginatedPets } from "../requests/services.gen";
-import { Pet, NewPet, Error, FindPetsData, FindPetsResponse, FindPetsError, AddPetData, AddPetResponse, AddPetError, GetNotDefinedResponse, GetNotDefinedError, PostNotDefinedResponse, PostNotDefinedError, FindPetByIdData, FindPetByIdResponse, FindPetByIdError, DeletePetData, DeletePetResponse, DeletePetError, FindPaginatedPetsData, FindPaginatedPetsResponse, FindPaginatedPetsError } from "../requests/types.gen";
+import { type Options } from "../requests/client";
+import { type QueryClient, useQuery, useSuspenseQuery, useInfiniteQuery, useMutation, UseQueryResult, UseQueryOptions, UseInfiniteQueryOptions, UseMutationOptions, UseMutationResult, UseSuspenseQueryOptions, InfiniteData } from "@tanstack/react-query";
+import { Options, findPets, addPet, getNotDefined, postNotDefined, deletePet, findPetById, findPaginatedPets } from "../requests/sdk.gen";
+import { Pet, NewPet, _Error, FindPetsData, FindPetsErrors, FindPetsError, FindPetsResponses, FindPetsResponse, AddPetData, AddPetErrors, AddPetError, AddPetResponses, AddPetResponse, GetNotDefinedData, GetNotDefinedErrors, GetNotDefinedResponses, PostNotDefinedData, PostNotDefinedErrors, PostNotDefinedResponses, DeletePetData, DeletePetErrors, DeletePetError, DeletePetResponses, DeletePetResponse, FindPetByIdData, FindPetByIdErrors, FindPetByIdError, FindPetByIdResponses, FindPetByIdResponse, FindPaginatedPetsData, FindPaginatedPetsResponses, FindPaginatedPetsResponse, ClientOptions } from "../requests/types.gen";
import { AxiosError } from "axios";
+
export const prefetchUseFindPets = (queryClient: QueryClient, clientOptions: Options = {}) => queryClient.prefetchQuery({ queryKey: Common.UseFindPetsKeyFn(clientOptions), queryFn: () => findPets({ ...clientOptions }).then(response => response.data) });
-export const prefetchUseGetNotDefined = (queryClient: QueryClient, clientOptions: Options = {}) => queryClient.prefetchQuery({ queryKey: Common.UseGetNotDefinedKeyFn(clientOptions), queryFn: () => getNotDefined({ ...clientOptions }).then(response => response.data) });
-export const prefetchUseFindPetById = (queryClient: QueryClient, clientOptions: Options) => queryClient.prefetchQuery({ queryKey: Common.UseFindPetByIdKeyFn(clientOptions), queryFn: () => findPetById({ ...clientOptions }).then(response => response.data) });
+export const prefetchUseGetNotDefined = (queryClient: QueryClient, clientOptions: Options = {}) => queryClient.prefetchQuery({ queryKey: Common.UseGetNotDefinedKeyFn(clientOptions), queryFn: () => getNotDefined({ ...clientOptions }).then(response => response.data) });
+export const prefetchUseFindPetById = (queryClient: QueryClient, clientOptions: Options = {}) => queryClient.prefetchQuery({ queryKey: Common.UseFindPetByIdKeyFn(clientOptions), queryFn: () => findPetById({ ...clientOptions }).then(response => response.data) });
export const prefetchUseFindPaginatedPets = (queryClient: QueryClient, clientOptions: Options = {}) => queryClient.prefetchQuery({ queryKey: Common.UseFindPaginatedPetsKeyFn(clientOptions), queryFn: () => findPaginatedPets({ ...clientOptions }).then(response => response.data) });
"
`;
@@ -106,33 +123,47 @@ export * from "./queries";
exports[`createSource > createSource - @hey-api/client-fetch 2`] = `
"// generated with @7nohe/openapi-react-query-codegen@1.0.0
-import { type Options } from "@hey-api/client-fetch";
-import { type QueryClient, useQuery, useSuspenseQuery, useMutation, UseQueryResult, UseQueryOptions, UseMutationOptions, UseMutationResult, UseSuspenseQueryOptions } from "@tanstack/react-query";
-import { client, findPets, addPet, getNotDefined, postNotDefined, findPetById, deletePet, findPaginatedPets } from "../requests/services.gen";
-import { Pet, NewPet, Error, FindPetsData, FindPetsResponse, FindPetsError, AddPetData, AddPetResponse, AddPetError, GetNotDefinedResponse, GetNotDefinedError, PostNotDefinedResponse, PostNotDefinedError, FindPetByIdData, FindPetByIdResponse, FindPetByIdError, DeletePetData, DeletePetResponse, DeletePetError, FindPaginatedPetsData, FindPaginatedPetsResponse, FindPaginatedPetsError } from "../requests/types.gen";
+import { type Options } from "../requests/client";
+import { type QueryClient, useQuery, useSuspenseQuery, useInfiniteQuery, useMutation, UseQueryResult, UseQueryOptions, UseInfiniteQueryOptions, UseMutationOptions, UseMutationResult, UseSuspenseQueryOptions, InfiniteData } from "@tanstack/react-query";
+import { Options, findPets, addPet, getNotDefined, postNotDefined, deletePet, findPetById, findPaginatedPets } from "../requests/sdk.gen";
+import { Pet, NewPet, _Error, FindPetsData, FindPetsErrors, FindPetsError, FindPetsResponses, FindPetsResponse, AddPetData, AddPetErrors, AddPetError, AddPetResponses, AddPetResponse, GetNotDefinedData, GetNotDefinedErrors, GetNotDefinedResponses, PostNotDefinedData, PostNotDefinedErrors, PostNotDefinedResponses, DeletePetData, DeletePetErrors, DeletePetError, DeletePetResponses, DeletePetResponse, FindPetByIdData, FindPetByIdErrors, FindPetByIdError, FindPetByIdResponses, FindPetByIdResponse, FindPaginatedPetsData, FindPaginatedPetsResponses, FindPaginatedPetsResponse, ClientOptions } from "../requests/types.gen";
+
export type FindPetsDefaultResponse = Awaited>["data"];
export type FindPetsQueryResult = UseQueryResult;
+
export const useFindPetsKey = "FindPets";
export const UseFindPetsKeyFn = (clientOptions: Options = {}, queryKey?: Array) => [useFindPetsKey, ...(queryKey ?? [clientOptions])];
+
export type GetNotDefinedDefaultResponse = Awaited>["data"];
export type GetNotDefinedQueryResult = UseQueryResult;
+
export const useGetNotDefinedKey = "GetNotDefined";
-export const UseGetNotDefinedKeyFn = (clientOptions: Options = {}, queryKey?: Array) => [useGetNotDefinedKey, ...(queryKey ?? [clientOptions])];
+export const UseGetNotDefinedKeyFn = (clientOptions: Options = {}, queryKey?: Array) => [useGetNotDefinedKey, ...(queryKey ?? [clientOptions])];
+
export type FindPetByIdDefaultResponse = Awaited>["data"];
export type FindPetByIdQueryResult = UseQueryResult;
+
export const useFindPetByIdKey = "FindPetById";
-export const UseFindPetByIdKeyFn = (clientOptions: Options, queryKey?: Array) => [useFindPetByIdKey, ...(queryKey ?? [clientOptions])];
+export const UseFindPetByIdKeyFn = (clientOptions: Options = {}, queryKey?: Array) => [useFindPetByIdKey, ...(queryKey ?? [clientOptions])];
+
export type FindPaginatedPetsDefaultResponse = Awaited>["data"];
export type FindPaginatedPetsQueryResult = UseQueryResult;
+
export const useFindPaginatedPetsKey = "FindPaginatedPets";
export const UseFindPaginatedPetsKeyFn = (clientOptions: Options = {}, queryKey?: Array) => [useFindPaginatedPetsKey, ...(queryKey ?? [clientOptions])];
+
export type AddPetMutationResult = Awaited>;
+
export const useAddPetKey = "AddPet";
export const UseAddPetKeyFn = (mutationKey?: Array) => [useAddPetKey, ...(mutationKey ?? [])];
+
export type PostNotDefinedMutationResult = Awaited>;
+
export const usePostNotDefinedKey = "PostNotDefined";
export const UsePostNotDefinedKeyFn = (mutationKey?: Array) => [usePostNotDefinedKey, ...(mutationKey ?? [])];
+
export type DeletePetMutationResult = Awaited>;
+
export const useDeletePetKey = "DeletePet";
export const UseDeletePetKeyFn = (mutationKey?: Array) => [useDeletePetKey, ...(mutationKey ?? [])];
"
@@ -142,16 +173,17 @@ exports[`createSource > createSource - @hey-api/client-fetch 3`] = `
"// generated with @7nohe/openapi-react-query-codegen@1.0.0
import * as Common from "./common";
-import { type Options } from "@hey-api/client-fetch";
-import { type QueryClient, useQuery, useSuspenseQuery, useMutation, UseQueryResult, UseQueryOptions, UseMutationOptions, UseMutationResult, UseSuspenseQueryOptions } from "@tanstack/react-query";
-import { client, findPets, addPet, getNotDefined, postNotDefined, findPetById, deletePet, findPaginatedPets } from "../requests/services.gen";
-import { Pet, NewPet, Error, FindPetsData, FindPetsResponse, FindPetsError, AddPetData, AddPetResponse, AddPetError, GetNotDefinedResponse, GetNotDefinedError, PostNotDefinedResponse, PostNotDefinedError, FindPetByIdData, FindPetByIdResponse, FindPetByIdError, DeletePetData, DeletePetResponse, DeletePetError, FindPaginatedPetsData, FindPaginatedPetsResponse, FindPaginatedPetsError } from "../requests/types.gen";
+import { type Options } from "../requests/client";
+import { type QueryClient, useQuery, useSuspenseQuery, useInfiniteQuery, useMutation, UseQueryResult, UseQueryOptions, UseInfiniteQueryOptions, UseMutationOptions, UseMutationResult, UseSuspenseQueryOptions, InfiniteData } from "@tanstack/react-query";
+import { Options, findPets, addPet, getNotDefined, postNotDefined, deletePet, findPetById, findPaginatedPets } from "../requests/sdk.gen";
+import { Pet, NewPet, _Error, FindPetsData, FindPetsErrors, FindPetsError, FindPetsResponses, FindPetsResponse, AddPetData, AddPetErrors, AddPetError, AddPetResponses, AddPetResponse, GetNotDefinedData, GetNotDefinedErrors, GetNotDefinedResponses, PostNotDefinedData, PostNotDefinedErrors, PostNotDefinedResponses, DeletePetData, DeletePetErrors, DeletePetError, DeletePetResponses, DeletePetResponse, FindPetByIdData, FindPetByIdErrors, FindPetByIdError, FindPetByIdResponses, FindPetByIdResponse, FindPaginatedPetsData, FindPaginatedPetsResponses, FindPaginatedPetsResponse, ClientOptions } from "../requests/types.gen";
+
export const useFindPets = = unknown[]>(clientOptions: Options = {}, queryKey?: TQueryKey, options?: Omit, "queryKey" | "queryFn">) => useQuery({ queryKey: Common.UseFindPetsKeyFn(clientOptions, queryKey), queryFn: () => findPets({ ...clientOptions }).then(response => response.data as TData) as TData, ...options });
-export const useGetNotDefined = = unknown[]>(clientOptions: Options = {}, queryKey?: TQueryKey, options?: Omit, "queryKey" | "queryFn">) => useQuery({ queryKey: Common.UseGetNotDefinedKeyFn(clientOptions, queryKey), queryFn: () => getNotDefined({ ...clientOptions }).then(response => response.data as TData) as TData, ...options });
-export const useFindPetById = = unknown[]>(clientOptions: Options, queryKey?: TQueryKey, options?: Omit, "queryKey" | "queryFn">) => useQuery({ queryKey: Common.UseFindPetByIdKeyFn(clientOptions, queryKey), queryFn: () => findPetById({ ...clientOptions }).then(response => response.data as TData) as TData, ...options });
+export const useGetNotDefined = = unknown[]>(clientOptions: Options = {}, queryKey?: TQueryKey, options?: Omit, "queryKey" | "queryFn">) => useQuery({ queryKey: Common.UseGetNotDefinedKeyFn(clientOptions, queryKey), queryFn: () => getNotDefined({ ...clientOptions }).then(response => response.data as TData) as TData, ...options });
+export const useFindPetById = = unknown[]>(clientOptions: Options = {}, queryKey?: TQueryKey, options?: Omit, "queryKey" | "queryFn">) => useQuery({ queryKey: Common.UseFindPetByIdKeyFn(clientOptions, queryKey), queryFn: () => findPetById({ ...clientOptions }).then(response => response.data as TData) as TData, ...options });
export const useFindPaginatedPets = = unknown[]>(clientOptions: Options = {}, queryKey?: TQueryKey, options?: Omit, "queryKey" | "queryFn">) => useQuery({ queryKey: Common.UseFindPaginatedPetsKeyFn(clientOptions, queryKey), queryFn: () => findPaginatedPets({ ...clientOptions }).then(response => response.data as TData) as TData, ...options });
export const useAddPet = = unknown[], TContext = unknown>(mutationKey?: TQueryKey, options?: Omit, TContext>, "mutationKey" | "mutationFn">) => useMutation, TContext>({ mutationKey: Common.UseAddPetKeyFn(mutationKey), mutationFn: clientOptions => addPet(clientOptions) as unknown as Promise, ...options });
-export const usePostNotDefined = = unknown[], TContext = unknown>(mutationKey?: TQueryKey, options?: Omit, TContext>, "mutationKey" | "mutationFn">) => useMutation, TContext>({ mutationKey: Common.UsePostNotDefinedKeyFn(mutationKey), mutationFn: clientOptions => postNotDefined(clientOptions) as unknown as Promise, ...options });
+export const usePostNotDefined = = unknown[], TContext = unknown>(mutationKey?: TQueryKey, options?: Omit, TContext>, "mutationKey" | "mutationFn">) => useMutation, TContext>({ mutationKey: Common.UsePostNotDefinedKeyFn(mutationKey), mutationFn: clientOptions => postNotDefined(clientOptions) as unknown as Promise, ...options });
export const useDeletePet = = unknown[], TContext = unknown>(mutationKey?: TQueryKey, options?: Omit, TContext>, "mutationKey" | "mutationFn">) => useMutation, TContext>({ mutationKey: Common.UseDeletePetKeyFn(mutationKey), mutationFn: clientOptions => deletePet(clientOptions) as unknown as Promise, ...options });
"
`;
@@ -160,13 +192,14 @@ exports[`createSource > createSource - @hey-api/client-fetch 4`] = `
"// generated with @7nohe/openapi-react-query-codegen@1.0.0
import * as Common from "./common";
-import { type Options } from "@hey-api/client-fetch";
-import { type QueryClient, useQuery, useSuspenseQuery, useMutation, UseQueryResult, UseQueryOptions, UseMutationOptions, UseMutationResult, UseSuspenseQueryOptions } from "@tanstack/react-query";
-import { client, findPets, addPet, getNotDefined, postNotDefined, findPetById, deletePet, findPaginatedPets } from "../requests/services.gen";
-import { Pet, NewPet, Error, FindPetsData, FindPetsResponse, FindPetsError, AddPetData, AddPetResponse, AddPetError, GetNotDefinedResponse, GetNotDefinedError, PostNotDefinedResponse, PostNotDefinedError, FindPetByIdData, FindPetByIdResponse, FindPetByIdError, DeletePetData, DeletePetResponse, DeletePetError, FindPaginatedPetsData, FindPaginatedPetsResponse, FindPaginatedPetsError } from "../requests/types.gen";
+import { type Options } from "../requests/client";
+import { type QueryClient, useQuery, useSuspenseQuery, useInfiniteQuery, useMutation, UseQueryResult, UseQueryOptions, UseInfiniteQueryOptions, UseMutationOptions, UseMutationResult, UseSuspenseQueryOptions, InfiniteData } from "@tanstack/react-query";
+import { Options, findPets, addPet, getNotDefined, postNotDefined, deletePet, findPetById, findPaginatedPets } from "../requests/sdk.gen";
+import { Pet, NewPet, _Error, FindPetsData, FindPetsErrors, FindPetsError, FindPetsResponses, FindPetsResponse, AddPetData, AddPetErrors, AddPetError, AddPetResponses, AddPetResponse, GetNotDefinedData, GetNotDefinedErrors, GetNotDefinedResponses, PostNotDefinedData, PostNotDefinedErrors, PostNotDefinedResponses, DeletePetData, DeletePetErrors, DeletePetError, DeletePetResponses, DeletePetResponse, FindPetByIdData, FindPetByIdErrors, FindPetByIdError, FindPetByIdResponses, FindPetByIdResponse, FindPaginatedPetsData, FindPaginatedPetsResponses, FindPaginatedPetsResponse, ClientOptions } from "../requests/types.gen";
+
export const useFindPetsSuspense =