From f6fc823ef231b1abc277620cf3040bf3ca2b50bc Mon Sep 17 00:00:00 2001 From: Urata Daiki <7nohe@users.noreply.github.com> Date: Sun, 7 Dec 2025 22:51:27 +0900 Subject: [PATCH 01/31] docs: add AI assistant guidelines (CLAUDE.md, AGENTS.md) --- .gitignore | 2 + .serena/.gitignore | 1 + .serena/memories/project_purpose.md | 3 + .serena/memories/style_and_conventions.md | 5 ++ .serena/memories/suggested_commands.md | 10 +++ .serena/memories/task_completion.md | 5 ++ .serena/memories/tech_stack_and_structure.md | 5 ++ .serena/project.yml | 84 ++++++++++++++++++++ AGENTS.md | 33 ++++++++ CLAUDE.md | 73 +++++++++++++++++ 10 files changed, 221 insertions(+) create mode 100644 .serena/.gitignore create mode 100644 .serena/memories/project_purpose.md create mode 100644 .serena/memories/style_and_conventions.md create mode 100644 .serena/memories/suggested_commands.md create mode 100644 .serena/memories/task_completion.md create mode 100644 .serena/memories/tech_stack_and_structure.md create mode 100644 .serena/project.yml create mode 100644 AGENTS.md create mode 100644 CLAUDE.md 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. From 606ec4470fb1f617058a2a1b8bd6cab798fde694 Mon Sep 17 00:00:00 2001 From: Urata Daiki <7nohe@users.noreply.github.com> Date: Sun, 14 Dec 2025 12:52:27 +0900 Subject: [PATCH 02/31] refactor: migrate code generation to a ts-morph pipeline --- src/common.mts | 201 +----- src/createExports.mts | 190 ------ src/createImports.mts | 179 ----- src/createPrefetchOrEnsure.mts | 179 ----- src/createSource.mts | 276 +------- src/createUseMutation.mts | 272 -------- src/createUseQuery.mts | 632 ------------------ src/parseOperations.mts | 161 +++++ src/service.mts | 1 - src/tsmorph/buildCommon.mts | 152 +++++ src/tsmorph/buildKeys.mts | 138 ++++ src/tsmorph/buildMutationHooks.mts | 62 ++ src/tsmorph/buildQueryHooks.mts | 292 ++++++++ src/tsmorph/generateFiles.mts | 359 ++++++++++ src/tsmorph/index.mts | 5 + src/tsmorph/projectFactory.mts | 155 +++++ src/types.mts | 62 ++ tests/__snapshots__/createSource.test.ts.snap | 50 +- tests/__snapshots__/generate.test.ts.snap | 32 +- tests/createExports.test.ts | 93 --- tests/createImports.test.ts | 52 -- tests/createSource.test.ts | 13 + tests/generate.test.ts | 2 +- tests/parseOperations.test.ts | 188 ++++++ tests/tsmorph/buildCommon.test.ts | 192 ++++++ tests/tsmorph/buildKeys.test.ts | 214 ++++++ tests/tsmorph/buildMutationHooks.test.ts | 174 +++++ tests/tsmorph/buildQueryHooks.test.ts | 240 +++++++ tests/tsmorph/projectFactory.test.ts | 213 ++++++ tests/types.test.ts | 148 ++++ 30 files changed, 2861 insertions(+), 2066 deletions(-) delete mode 100644 src/createExports.mts delete mode 100644 src/createImports.mts delete mode 100644 src/createPrefetchOrEnsure.mts delete mode 100644 src/createUseMutation.mts delete mode 100644 src/createUseQuery.mts create mode 100644 src/parseOperations.mts create mode 100644 src/tsmorph/buildCommon.mts create mode 100644 src/tsmorph/buildKeys.mts create mode 100644 src/tsmorph/buildMutationHooks.mts create mode 100644 src/tsmorph/buildQueryHooks.mts create mode 100644 src/tsmorph/generateFiles.mts create mode 100644 src/tsmorph/index.mts create mode 100644 src/tsmorph/projectFactory.mts create mode 100644 src/types.mts delete mode 100644 tests/createExports.test.ts delete mode 100644 tests/createImports.test.ts create mode 100644 tests/parseOperations.test.ts create mode 100644 tests/tsmorph/buildCommon.test.ts create mode 100644 tests/tsmorph/buildKeys.test.ts create mode 100644 tests/tsmorph/buildMutationHooks.test.ts create mode 100644 tests/tsmorph/buildQueryHooks.test.ts create mode 100644 tests/tsmorph/projectFactory.test.ts create mode 100644 tests/types.test.ts 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/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..d916e1c 100644 --- a/src/createSource.mts +++ b/src/createSource.mts @@ -1,287 +1,49 @@ 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 ({ +/** + * Create source files using ts-morph based generation. + */ +export const createSource = async ({ outputPath, client, + version, pageParam, nextPageParam, initialPageParam, }: { outputPath: string; client: UserConfig["client"]; + 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/parseOperations.mts b/src/parseOperations.mts new file mode 100644 index 0000000..d5c6a30 --- /dev/null +++ b/src/parseOperations.mts @@ -0,0 +1,161 @@ +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.getSourceFileOrThrow(`${serviceFileName}.ts`); + + 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..36e3e4d 100644 --- a/src/service.mts +++ b/src/service.mts @@ -90,7 +90,6 @@ export function getMethodsFromService(node: SourceFile): FunctionDescription[] { const methodDescription: FunctionDescription = { node, method: declaration, - methodBlock: methodBlockNode, httpMethodName, jsDoc, isDeprecated, 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..f75b8e4 --- /dev/null +++ b/src/tsmorph/projectFactory.mts @@ -0,0 +1,155 @@ +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. + */ +export function buildClientImport( + ctx: GenerationContext, +): ImportDeclarationStructure { + const moduleSpecifier = + ctx.client === "@hey-api/client-axios" + ? "@hey-api/client-axios" + : "@hey-api/client-fetch"; + + return { + kind: StructureKind.ImportDeclaration, + moduleSpecifier, + 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/services.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..d759777 100644 --- a/tests/__snapshots__/createSource.test.ts.snap +++ b/tests/__snapshots__/createSource.test.ts.snap @@ -12,33 +12,47 @@ 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 { type QueryClient, useQuery, useSuspenseQuery, useInfiniteQuery, useMutation, UseQueryResult, UseQueryOptions, UseInfiniteQueryOptions, UseMutationOptions, UseMutationResult, UseSuspenseQueryOptions, InfiniteData } 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 { 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 type FindPetByIdDefaultResponse = Awaited>["data"]; export type FindPetByIdQueryResult = UseQueryResult; + export const useFindPetByIdKey = "FindPetById"; 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 ?? [])]; " @@ -49,10 +63,11 @@ exports[`createSource > createSource - @hey-api/client-axios 3`] = ` 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 { type QueryClient, useQuery, useSuspenseQuery, useInfiniteQuery, useMutation, UseQueryResult, UseQueryOptions, UseInfiniteQueryOptions, UseMutationOptions, UseMutationResult, UseSuspenseQueryOptions, InfiniteData } 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 { 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 }); @@ -68,10 +83,11 @@ exports[`createSource > createSource - @hey-api/client-axios 4`] = ` 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 { type QueryClient, useQuery, useSuspenseQuery, useInfiniteQuery, useMutation, UseQueryResult, UseQueryOptions, UseInfiniteQueryOptions, UseMutationOptions, UseMutationResult, UseSuspenseQueryOptions, InfiniteData } 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 { 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 }); @@ -84,10 +100,11 @@ exports[`createSource > createSource - @hey-api/client-axios 5`] = ` 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 { type QueryClient, useQuery, useSuspenseQuery, useInfiniteQuery, useMutation, UseQueryResult, UseQueryOptions, UseInfiniteQueryOptions, UseMutationOptions, UseMutationResult, UseSuspenseQueryOptions, InfiniteData } 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 { 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) }); @@ -107,32 +124,46 @@ 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 { type QueryClient, useQuery, useSuspenseQuery, useInfiniteQuery, useMutation, UseQueryResult, UseQueryOptions, UseInfiniteQueryOptions, UseMutationOptions, UseMutationResult, UseSuspenseQueryOptions, InfiniteData } 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"; + 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 type FindPetByIdDefaultResponse = Awaited>["data"]; export type FindPetByIdQueryResult = UseQueryResult; + export const useFindPetByIdKey = "FindPetById"; 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 ?? [])]; " @@ -143,9 +174,10 @@ exports[`createSource > createSource - @hey-api/client-fetch 3`] = ` 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 { type QueryClient, useQuery, useSuspenseQuery, useInfiniteQuery, useMutation, UseQueryResult, UseQueryOptions, UseInfiniteQueryOptions, UseMutationOptions, UseMutationResult, UseSuspenseQueryOptions, InfiniteData } 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"; + 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 }); @@ -161,9 +193,10 @@ exports[`createSource > createSource - @hey-api/client-fetch 4`] = ` 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 { type QueryClient, useQuery, useSuspenseQuery, useInfiniteQuery, useMutation, UseQueryResult, UseQueryOptions, UseInfiniteQueryOptions, UseMutationOptions, UseMutationResult, UseSuspenseQueryOptions, InfiniteData } 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"; + export const useFindPetsSuspense = , TError = FindPetsError, 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 = GetNotDefinedError, 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 = FindPetByIdError, 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 }); @@ -176,9 +209,10 @@ exports[`createSource > createSource - @hey-api/client-fetch 5`] = ` 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 { type QueryClient, useQuery, useSuspenseQuery, useInfiniteQuery, useMutation, UseQueryResult, UseQueryOptions, UseInfiniteQueryOptions, UseMutationOptions, UseMutationResult, UseSuspenseQueryOptions, InfiniteData } 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"; + 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) }); diff --git a/tests/__snapshots__/generate.test.ts.snap b/tests/__snapshots__/generate.test.ts.snap index 233a8b0..5603878 100644 --- a/tests/__snapshots__/generate.test.ts.snap +++ b/tests/__snapshots__/generate.test.ts.snap @@ -19,6 +19,7 @@ import type { FindPetByIdData, FindPetsData, } from "../requests/types.gen"; + export type FindPetsDefaultResponse = Awaited< ReturnType >["data"]; @@ -26,11 +27,13 @@ export type FindPetsQueryResult< TData = FindPetsDefaultResponse, TError = unknown, > = UseQueryResult; + export const useFindPetsKey = "FindPets"; export const UseFindPetsKeyFn = ( clientOptions: Options = {}, queryKey?: Array, ) => [useFindPetsKey, ...(queryKey ?? [clientOptions])]; + export type GetNotDefinedDefaultResponse = Awaited< ReturnType >["data"]; @@ -38,11 +41,13 @@ export type GetNotDefinedQueryResult< TData = GetNotDefinedDefaultResponse, TError = unknown, > = UseQueryResult; + export const useGetNotDefinedKey = "GetNotDefined"; export const UseGetNotDefinedKeyFn = ( clientOptions: Options = {}, queryKey?: Array, ) => [useGetNotDefinedKey, ...(queryKey ?? [clientOptions])]; + export type FindPetByIdDefaultResponse = Awaited< ReturnType >["data"]; @@ -50,11 +55,13 @@ export type FindPetByIdQueryResult< TData = FindPetByIdDefaultResponse, TError = unknown, > = UseQueryResult; + export const useFindPetByIdKey = "FindPetById"; export const UseFindPetByIdKeyFn = ( clientOptions: Options, queryKey?: Array, ) => [useFindPetByIdKey, ...(queryKey ?? [clientOptions])]; + export type FindPaginatedPetsDefaultResponse = Awaited< ReturnType >["data"]; @@ -62,26 +69,33 @@ export type FindPaginatedPetsQueryResult< TData = FindPaginatedPetsDefaultResponse, TError = unknown, > = 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< ReturnType >; + 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, @@ -107,6 +121,7 @@ import type { FindPetsData, } from "../requests/types.gen"; import * as Common from "./common"; + export const ensureUseFindPetsData = ( queryClient: QueryClient, clientOptions: Options = {}, @@ -158,12 +173,18 @@ exports[`generate > infiniteQueries.ts 1`] = ` "// generated with @7nohe/openapi-react-query-codegen@1.0.0 import type { Options } from "@hey-api/client-fetch"; +import { + type InfiniteData, + useInfiniteQuery, + type UseInfiniteQueryOptions, +} from "@tanstack/react-query"; import { findPaginatedPets } from "../requests/services.gen"; import type { FindPaginatedPetsData, FindPaginatedPetsError, } from "../requests/types.gen"; import * as Common from "./common"; + export const useFindPaginatedPetsInfinite = < TData = InfiniteData, TError = FindPaginatedPetsError, @@ -185,13 +206,7 @@ export const useFindPaginatedPetsInfinite = < }).then((response) => response.data as TData) as TData, initialPageParam: "initial", getNextPageParam: (response) => - ( - response as { - meta: { - next: number; - }; - } - ).meta.next, + (response as { meta: { next: number } }).meta.next, ...options, }); " @@ -214,6 +229,7 @@ import type { FindPetsData, } from "../requests/types.gen"; import * as Common from "./common"; + export const prefetchUseFindPets = ( queryClient: QueryClient, clientOptions: Options = {}, @@ -287,6 +303,7 @@ import type { PostNotDefinedError, } from "../requests/types.gen"; import * as Common from "./common"; + export const useFindPets = < TData = Common.FindPetsDefaultResponse, TError = FindPetsError, @@ -436,6 +453,7 @@ import type { GetNotDefinedError, } from "../requests/types.gen"; import * as Common from "./common"; + export const useFindPetsSuspense = < TData = NonNullable, TError = FindPetsError, diff --git a/tests/createExports.test.ts b/tests/createExports.test.ts deleted file mode 100644 index b09abe1..0000000 --- a/tests/createExports.test.ts +++ /dev/null @@ -1,93 +0,0 @@ -import path from "node:path"; -import { Project, SyntaxKind } from "ts-morph"; -import { afterAll, beforeAll, describe, expect, test } from "vitest"; -import { createExports } from "../src/createExports.mts"; -import { getServices } from "../src/service.mts"; -import { cleanOutputs, generateTSClients, outputPath } from "./utils"; - -const fileName = "createExports"; - -describe(fileName, () => { - beforeAll(async () => await generateTSClients(fileName)); - afterAll(async () => await cleanOutputs(fileName)); - - test("createExports", async () => { - const project = new Project({ - skipAddingFilesFromTsConfig: true, - }); - project.addSourceFilesAtPaths(path.join(outputPath(fileName), "**", "*")); - const service = await getServices(project); - const exports = createExports({ - service, - project, - pageParam: "page", - nextPageParam: "nextPage", - initialPageParam: "initial", - client: "@hey-api/client-fetch", - }); - - const commonTypes = exports.allCommon - .filter((c) => c.kind === SyntaxKind.TypeAliasDeclaration) - // @ts-ignore - .map((e) => e.name.escapedText); - expect(commonTypes).toStrictEqual([ - "FindPetsDefaultResponse", - "FindPetsQueryResult", - "GetNotDefinedDefaultResponse", - "GetNotDefinedQueryResult", - "FindPetByIdDefaultResponse", - "FindPetByIdQueryResult", - "FindPaginatedPetsDefaultResponse", - "FindPaginatedPetsQueryResult", - "AddPetMutationResult", - "PostNotDefinedMutationResult", - "DeletePetMutationResult", - ]); - - const constants = exports.allCommon - .filter((c) => c.kind === SyntaxKind.VariableStatement) - // @ts-ignore - .map((c) => c.declarationList.declarations[0].name.escapedText); - expect(constants).toStrictEqual([ - "useFindPetsKey", - "UseFindPetsKeyFn", - "useGetNotDefinedKey", - "UseGetNotDefinedKeyFn", - "useFindPetByIdKey", - "UseFindPetByIdKeyFn", - "useFindPaginatedPetsKey", - "UseFindPaginatedPetsKeyFn", - "useAddPetKey", - "UseAddPetKeyFn", - "usePostNotDefinedKey", - "UsePostNotDefinedKeyFn", - "useDeletePetKey", - "UseDeletePetKeyFn", - ]); - - const mainExports = exports.mainExports.map( - // @ts-ignore - (e) => e.declarationList.declarations[0].name.escapedText, - ); - expect(mainExports).toStrictEqual([ - "useFindPets", - "useGetNotDefined", - "useFindPetById", - "useFindPaginatedPets", - "useAddPet", - "usePostNotDefined", - "useDeletePet", - ]); - - const suspenseExports = exports.suspenseExports.map( - // @ts-ignore - (e) => e.declarationList.declarations[0].name.escapedText, - ); - expect(suspenseExports).toStrictEqual([ - "useFindPetsSuspense", - "useGetNotDefinedSuspense", - "useFindPetByIdSuspense", - "useFindPaginatedPetsSuspense", - ]); - }); -}); diff --git a/tests/createImports.test.ts b/tests/createImports.test.ts deleted file mode 100644 index 7c6ee1f..0000000 --- a/tests/createImports.test.ts +++ /dev/null @@ -1,52 +0,0 @@ -import path from "node:path"; -import { Project } from "ts-morph"; -import { describe, expect, test } from "vitest"; -import { createImports } from "../src/createImports.mts"; -import { cleanOutputs, generateTSClients, outputPath } from "./utils"; - -const fileName = "createImports"; - -describe(fileName, () => { - test("createImports", async () => { - await generateTSClients(fileName); - const project = new Project({ - skipAddingFilesFromTsConfig: true, - }); - project.addSourceFilesAtPaths(path.join(outputPath(fileName), "**", "*")); - const imports = createImports({ - project, - }); - - // @ts-ignore - const moduleNames = imports.map((i) => i.moduleSpecifier.text); - expect(moduleNames).toStrictEqual([ - "@hey-api/client-fetch", - "@tanstack/react-query", - "../requests/services.gen", - "../requests/types.gen", - ]); - await cleanOutputs(fileName); - }); - - test("createImports (No models)", async () => { - const fileName = "createImportsNoModels"; - await generateTSClients(fileName, "no-models.yaml"); - const project = new Project({ - skipAddingFilesFromTsConfig: true, - }); - project.addSourceFilesAtPaths(path.join(outputPath(fileName), "**", "*")); - const imports = createImports({ - project, - }); - - // @ts-ignore - const moduleNames = imports.map((i) => i.moduleSpecifier.text); - expect(moduleNames).toStrictEqual([ - "@hey-api/client-fetch", - "@tanstack/react-query", - "../requests/services.gen", - "../requests/types.gen", - ]); - await cleanOutputs(fileName); - }); -}); diff --git a/tests/createSource.test.ts b/tests/createSource.test.ts index ef37401..8b6eba4 100644 --- a/tests/createSource.test.ts +++ b/tests/createSource.test.ts @@ -1,7 +1,9 @@ import { afterAll, beforeAll, describe, expect, test } from "vitest"; import { createSource } from "../src/createSource.mjs"; import { cleanOutputs, generateTSClients, outputPath } from "./utils"; + const fileName = "createSource"; + describe(fileName, () => { beforeAll(async () => await generateTSClients(fileName)); afterAll(async () => await cleanOutputs(fileName)); @@ -16,6 +18,17 @@ describe(fileName, () => { client: "@hey-api/client-fetch", }); + expect(source).toHaveLength(7); + expect(source.map((s) => s.name)).toEqual([ + "index.ts", + "common.ts", + "queries.ts", + "suspense.ts", + "infiniteQueries.ts", + "prefetch.ts", + "ensureQueryData.ts", + ]); + const indexTs = source.find((s) => s.name === "index.ts"); expect(indexTs?.content).toMatchSnapshot(); diff --git a/tests/generate.test.ts b/tests/generate.test.ts index d4ece8c..8bea8ff 100644 --- a/tests/generate.test.ts +++ b/tests/generate.test.ts @@ -26,7 +26,7 @@ describe("generate", () => { operationId: true, }; await generate(options, "1.0.0"); - }); + }, 60000); afterAll(async () => { if (existsSync(path.join(__dirname, "outputs"))) { diff --git a/tests/parseOperations.test.ts b/tests/parseOperations.test.ts new file mode 100644 index 0000000..ec6c542 --- /dev/null +++ b/tests/parseOperations.test.ts @@ -0,0 +1,188 @@ +import { join } from "node:path"; +import { Project } from "ts-morph"; +import { afterAll, beforeAll, describe, expect, it } from "vitest"; +import { + buildGenerationContext, + parseOperations, +} from "../src/parseOperations.mjs"; +import { cleanOutputs, generateTSClients, outputPath } from "./utils"; + +const fileName = "parseOperations"; + +describe("parseOperations", () => { + beforeAll(async () => await generateTSClients(fileName)); + afterAll(async () => await cleanOutputs(fileName)); + + describe("parseOperations", () => { + it("should parse GET operations", async () => { + const project = new Project({ skipAddingFilesFromTsConfig: true }); + const sourceFiles = join(process.cwd(), outputPath(fileName)); + project.addSourceFilesAtPaths(`${sourceFiles}/**/*`); + + const operations = await parseOperations(project, "page"); + + const getOps = operations.filter((op) => op.httpMethod === "GET"); + expect(getOps.length).toBeGreaterThan(0); + + const findPets = operations.find((op) => op.methodName === "findPets"); + expect(findPets).toBeDefined(); + expect(findPets?.httpMethod).toBe("GET"); + expect(findPets?.capitalizedMethodName).toBe("FindPets"); + }); + + it("should parse POST operations", async () => { + const project = new Project({ skipAddingFilesFromTsConfig: true }); + const sourceFiles = join(process.cwd(), outputPath(fileName)); + project.addSourceFilesAtPaths(`${sourceFiles}/**/*`); + + const operations = await parseOperations(project, "page"); + + const postOps = operations.filter((op) => op.httpMethod === "POST"); + expect(postOps.length).toBeGreaterThan(0); + + const addPet = operations.find((op) => op.methodName === "addPet"); + expect(addPet).toBeDefined(); + expect(addPet?.httpMethod).toBe("POST"); + }); + + it("should parse DELETE operations", async () => { + const project = new Project({ skipAddingFilesFromTsConfig: true }); + const sourceFiles = join(process.cwd(), outputPath(fileName)); + project.addSourceFilesAtPaths(`${sourceFiles}/**/*`); + + const operations = await parseOperations(project, "page"); + + const deletePet = operations.find((op) => op.methodName === "deletePet"); + expect(deletePet).toBeDefined(); + expect(deletePet?.httpMethod).toBe("DELETE"); + }); + + it("should parse all GET operations as potentially paginatable", async () => { + const project = new Project({ skipAddingFilesFromTsConfig: true }); + const sourceFiles = join(process.cwd(), outputPath(fileName)); + project.addSourceFilesAtPaths(`${sourceFiles}/**/*`); + + const operations = await parseOperations(project, "page"); + + // findPaginatedPets should exist and be a GET operation + const findPaginatedPets = operations.find( + (op) => op.methodName === "findPaginatedPets", + ); + expect(findPaginatedPets).toBeDefined(); + expect(findPaginatedPets?.httpMethod).toBe("GET"); + // Note: isPaginatable detection uses simplified regex which may not detect all cases + // The actual pagination support is validated via createSourceV2 integration tests + }); + + it("should extract parameters correctly", async () => { + const project = new Project({ skipAddingFilesFromTsConfig: true }); + const sourceFiles = join(process.cwd(), outputPath(fileName)); + project.addSourceFilesAtPaths(`${sourceFiles}/**/*`); + + const operations = await parseOperations(project, "page"); + + const findPetById = operations.find( + (op) => op.methodName === "findPetById", + ); + expect(findPetById).toBeDefined(); + expect(findPetById?.parameters.length).toBeGreaterThan(0); + // findPetById requires an id parameter, so not all params are optional + expect(findPetById?.allParamsOptional).toBe(false); + }); + + it("should detect operations with all optional parameters", async () => { + const project = new Project({ skipAddingFilesFromTsConfig: true }); + const sourceFiles = join(process.cwd(), outputPath(fileName)); + project.addSourceFilesAtPaths(`${sourceFiles}/**/*`); + + const operations = await parseOperations(project, "page"); + + const findPets = operations.find((op) => op.methodName === "findPets"); + expect(findPets).toBeDefined(); + // findPets has optional limit and tags parameters + expect(findPets?.allParamsOptional).toBe(true); + }); + }); + + describe("buildGenerationContext", () => { + it("should build context with fetch client", async () => { + const project = new Project({ skipAddingFilesFromTsConfig: true }); + const sourceFiles = join(process.cwd(), outputPath(fileName)); + project.addSourceFilesAtPaths(`${sourceFiles}/**/*`); + + const ctx = buildGenerationContext( + project, + "@hey-api/client-fetch", + "page", + "nextPage", + "1", + "1.0.0", + ); + + expect(ctx.client).toBe("@hey-api/client-fetch"); + expect(ctx.pageParam).toBe("page"); + expect(ctx.nextPageParam).toBe("nextPage"); + expect(ctx.initialPageParam).toBe("1"); + expect(ctx.version).toBe("1.0.0"); + expect(ctx.serviceNames.length).toBeGreaterThan(0); + expect(ctx.modelNames.length).toBeGreaterThan(0); + }); + + it("should build context with axios client", async () => { + const project = new Project({ skipAddingFilesFromTsConfig: true }); + const sourceFiles = join(process.cwd(), outputPath(fileName)); + project.addSourceFilesAtPaths(`${sourceFiles}/**/*`); + + const ctx = buildGenerationContext( + project, + "@hey-api/client-axios", + "offset", + "next", + "0", + "2.0.0", + ); + + expect(ctx.client).toBe("@hey-api/client-axios"); + expect(ctx.pageParam).toBe("offset"); + expect(ctx.version).toBe("2.0.0"); + }); + + it("should include model names", async () => { + const project = new Project({ skipAddingFilesFromTsConfig: true }); + const sourceFiles = join(process.cwd(), outputPath(fileName)); + project.addSourceFilesAtPaths(`${sourceFiles}/**/*`); + + const ctx = buildGenerationContext( + project, + "@hey-api/client-fetch", + "page", + "nextPage", + "1", + "1.0.0", + ); + + expect(ctx.modelNames).toContain("Pet"); + expect(ctx.modelNames).toContain("NewPet"); + expect(ctx.modelNames).toContain("Error"); + }); + + it("should include service names", async () => { + const project = new Project({ skipAddingFilesFromTsConfig: true }); + const sourceFiles = join(process.cwd(), outputPath(fileName)); + project.addSourceFilesAtPaths(`${sourceFiles}/**/*`); + + const ctx = buildGenerationContext( + project, + "@hey-api/client-fetch", + "page", + "nextPage", + "1", + "1.0.0", + ); + + expect(ctx.serviceNames).toContain("findPets"); + expect(ctx.serviceNames).toContain("addPet"); + expect(ctx.serviceNames).toContain("deletePet"); + }); + }); +}); diff --git a/tests/tsmorph/buildCommon.test.ts b/tests/tsmorph/buildCommon.test.ts new file mode 100644 index 0000000..e6b23eb --- /dev/null +++ b/tests/tsmorph/buildCommon.test.ts @@ -0,0 +1,192 @@ +import { StructureKind, VariableDeclarationKind } from "ts-morph"; +import { describe, expect, it } from "vitest"; +import { + buildDefaultResponseType, + buildMutationKeyConst, + buildMutationKeyFn, + buildMutationResultType, + buildQueryKeyConst, + buildQueryKeyFn, + buildQueryResultType, +} from "../../src/tsmorph/buildCommon.mjs"; +import type { GenerationContext, OperationInfo } from "../../src/types.mjs"; + +const mockOperation: OperationInfo = { + methodName: "findPets", + capitalizedMethodName: "FindPets", + httpMethod: "GET", + isDeprecated: false, + parameters: [{ name: "limit", typeName: "number", optional: true }], + allParamsOptional: true, + isPaginatable: false, +}; + +const mockMutationOperation: OperationInfo = { + methodName: "addPet", + capitalizedMethodName: "AddPet", + httpMethod: "POST", + isDeprecated: false, + parameters: [{ name: "body", typeName: "NewPet", optional: false }], + allParamsOptional: false, + isPaginatable: false, +}; + +const mockContext: GenerationContext = { + client: "@hey-api/client-fetch", + modelNames: ["Pet", "NewPet", "FindPetsData", "AddPetData"], + serviceNames: ["findPets", "addPet"], + pageParam: "page", + nextPageParam: "nextPage", + initialPageParam: "1", + version: "1.0.0", +}; + +describe("buildCommon", () => { + describe("buildDefaultResponseType", () => { + it("should build default response type alias", () => { + const result = buildDefaultResponseType(mockOperation); + + expect(result.kind).toBe(StructureKind.TypeAlias); + expect(result.isExported).toBe(true); + expect(result.name).toBe("FindPetsDefaultResponse"); + expect(result.type).toBe('Awaited>["data"]'); + }); + + it("should use capitalized method name", () => { + const op: OperationInfo = { + ...mockOperation, + methodName: "getPetById", + capitalizedMethodName: "GetPetById", + }; + const result = buildDefaultResponseType(op); + + expect(result.name).toBe("GetPetByIdDefaultResponse"); + expect(result.type).toContain("getPetById"); + }); + }); + + describe("buildQueryResultType", () => { + it("should build query result type alias", () => { + const result = buildQueryResultType(mockOperation); + + expect(result.kind).toBe(StructureKind.TypeAlias); + expect(result.isExported).toBe(true); + expect(result.name).toBe("FindPetsQueryResult"); + expect(result.type).toBe("UseQueryResult"); + expect(result.typeParameters).toHaveLength(2); + expect(result.typeParameters?.[0]).toEqual({ + name: "TData", + default: "FindPetsDefaultResponse", + }); + expect(result.typeParameters?.[1]).toEqual({ + name: "TError", + default: "unknown", + }); + }); + }); + + describe("buildMutationResultType", () => { + it("should build mutation result type alias", () => { + const result = buildMutationResultType(mockMutationOperation); + + expect(result.kind).toBe(StructureKind.TypeAlias); + expect(result.isExported).toBe(true); + expect(result.name).toBe("AddPetMutationResult"); + expect(result.type).toBe("Awaited>"); + }); + }); + + describe("buildQueryKeyConst", () => { + it("should build query key constant", () => { + const result = buildQueryKeyConst(mockOperation); + + expect(result.kind).toBe(StructureKind.VariableStatement); + expect(result.isExported).toBe(true); + expect(result.declarationKind).toBe(VariableDeclarationKind.Const); + expect(result.declarations).toHaveLength(1); + expect(result.declarations[0].name).toBe("useFindPetsKey"); + expect(result.declarations[0].initializer).toBe('"FindPets"'); + }); + }); + + describe("buildMutationKeyConst", () => { + it("should build mutation key constant", () => { + const result = buildMutationKeyConst(mockMutationOperation); + + expect(result.kind).toBe(StructureKind.VariableStatement); + expect(result.isExported).toBe(true); + expect(result.declarationKind).toBe(VariableDeclarationKind.Const); + expect(result.declarations[0].name).toBe("useAddPetKey"); + expect(result.declarations[0].initializer).toBe('"AddPet"'); + }); + }); + + describe("buildQueryKeyFn", () => { + it("should build query key function with parameters", () => { + const result = buildQueryKeyFn(mockOperation, mockContext); + + expect(result.kind).toBe(StructureKind.VariableStatement); + expect(result.isExported).toBe(true); + expect(result.declarations[0].name).toBe("UseFindPetsKeyFn"); + + const initializer = result.declarations[0].initializer as string; + expect(initializer).toContain( + "clientOptions: Options", + ); + expect(initializer).toContain("= {}"); // default value for optional params + expect(initializer).toContain("queryKey?: Array"); + expect(initializer).toContain("[useFindPetsKey,"); + expect(initializer).toContain("[clientOptions]"); // fallback array + }); + + it("should build query key function without default value for required params", () => { + const op: OperationInfo = { + ...mockOperation, + methodName: "getPetById", + capitalizedMethodName: "GetPetById", + parameters: [{ name: "id", typeName: "number", optional: false }], + allParamsOptional: false, + }; + const ctx: GenerationContext = { + ...mockContext, + modelNames: [...mockContext.modelNames, "GetPetByIdData"], + }; + + const result = buildQueryKeyFn(op, ctx); + const initializer = result.declarations[0].initializer as string; + + expect(initializer).toContain( + "clientOptions: Options", + ); + expect(initializer).not.toContain("= {}"); + }); + + it("should use unknown for missing data type", () => { + const op: OperationInfo = { + ...mockOperation, + methodName: "unknownMethod", + capitalizedMethodName: "UnknownMethod", + }; + + const result = buildQueryKeyFn(op, mockContext); + const initializer = result.declarations[0].initializer as string; + + expect(initializer).toContain("Options"); + }); + }); + + describe("buildMutationKeyFn", () => { + it("should build mutation key function", () => { + const result = buildMutationKeyFn(mockMutationOperation); + + expect(result.kind).toBe(StructureKind.VariableStatement); + expect(result.isExported).toBe(true); + expect(result.declarations[0].name).toBe("UseAddPetKeyFn"); + + const initializer = result.declarations[0].initializer as string; + expect(initializer).toContain("mutationKey?: Array"); + expect(initializer).toContain("[useAddPetKey,"); + expect(initializer).toContain("mutationKey ?? []"); + }); + }); +}); diff --git a/tests/tsmorph/buildKeys.test.ts b/tests/tsmorph/buildKeys.test.ts new file mode 100644 index 0000000..7133838 --- /dev/null +++ b/tests/tsmorph/buildKeys.test.ts @@ -0,0 +1,214 @@ +import { StructureKind, VariableDeclarationKind } from "ts-morph"; +import { describe, expect, it } from "vitest"; +import { + buildMutationKeyExport, + buildMutationKeyFnExport, + buildQueryKeyExport, + buildQueryKeyFnExport, + getMutationKeyFnName, + getMutationKeyName, + getQueryKeyFnName, + getQueryKeyName, +} from "../../src/tsmorph/buildKeys.mjs"; +import type { GenerationContext, OperationInfo } from "../../src/types.mjs"; + +const mockQueryOperation: OperationInfo = { + methodName: "findPets", + capitalizedMethodName: "FindPets", + httpMethod: "GET", + isDeprecated: false, + parameters: [{ name: "limit", typeName: "number", optional: true }], + allParamsOptional: true, + isPaginatable: false, +}; + +const mockRequiredParamsOperation: OperationInfo = { + methodName: "findPetById", + capitalizedMethodName: "FindPetById", + httpMethod: "GET", + isDeprecated: false, + parameters: [{ name: "id", typeName: "number", optional: false }], + allParamsOptional: false, + isPaginatable: false, +}; + +const mockNoParamsOperation: OperationInfo = { + methodName: "listAllPets", + capitalizedMethodName: "ListAllPets", + httpMethod: "GET", + isDeprecated: false, + parameters: [], + allParamsOptional: true, + isPaginatable: false, +}; + +const mockMutationOperation: OperationInfo = { + methodName: "addPet", + capitalizedMethodName: "AddPet", + httpMethod: "POST", + isDeprecated: false, + parameters: [{ name: "body", typeName: "NewPet", optional: false }], + allParamsOptional: false, + isPaginatable: false, +}; + +const mockContext: GenerationContext = { + client: "@hey-api/client-fetch", + modelNames: [ + "Pet", + "NewPet", + "FindPetsData", + "FindPetByIdData", + "AddPetData", + ], + serviceNames: ["findPets", "findPetById", "listAllPets", "addPet"], + pageParam: "page", + nextPageParam: "nextPage", + initialPageParam: "1", + version: "1.0.0", +}; + +describe("buildKeys", () => { + describe("getQueryKeyName", () => { + it("should return query key name", () => { + expect(getQueryKeyName(mockQueryOperation)).toBe("findPetsQueryKey"); + }); + + it("should use method name as base", () => { + expect(getQueryKeyName(mockRequiredParamsOperation)).toBe( + "findPetByIdQueryKey", + ); + }); + }); + + describe("getMutationKeyName", () => { + it("should return mutation key name", () => { + expect(getMutationKeyName(mockMutationOperation)).toBe( + "addPetMutationKey", + ); + }); + }); + + describe("getQueryKeyFnName", () => { + it("should return query key function name", () => { + expect(getQueryKeyFnName(mockQueryOperation)).toBe("FindPetsQueryKeyFn"); + }); + + it("should use capitalized method name", () => { + expect(getQueryKeyFnName(mockRequiredParamsOperation)).toBe( + "FindPetByIdQueryKeyFn", + ); + }); + }); + + describe("getMutationKeyFnName", () => { + it("should return mutation key function name", () => { + expect(getMutationKeyFnName(mockMutationOperation)).toBe( + "AddPetMutationKeyFn", + ); + }); + }); + + describe("buildQueryKeyExport", () => { + it("should build query key constant", () => { + const result = buildQueryKeyExport(mockQueryOperation); + + expect(result.kind).toBe(StructureKind.VariableStatement); + expect(result.isExported).toBe(true); + expect(result.declarationKind).toBe(VariableDeclarationKind.Const); + expect(result.declarations).toHaveLength(1); + expect(result.declarations[0].name).toBe("findPetsQueryKey"); + expect(result.declarations[0].initializer).toBe('"FindPets"'); + }); + + it("should use capitalized method name as value", () => { + const result = buildQueryKeyExport(mockRequiredParamsOperation); + + expect(result.declarations[0].name).toBe("findPetByIdQueryKey"); + expect(result.declarations[0].initializer).toBe('"FindPetById"'); + }); + }); + + describe("buildMutationKeyExport", () => { + it("should build mutation key constant", () => { + const result = buildMutationKeyExport(mockMutationOperation); + + expect(result.kind).toBe(StructureKind.VariableStatement); + expect(result.isExported).toBe(true); + expect(result.declarationKind).toBe(VariableDeclarationKind.Const); + expect(result.declarations[0].name).toBe("addPetMutationKey"); + expect(result.declarations[0].initializer).toBe('"AddPet"'); + }); + }); + + describe("buildQueryKeyFnExport", () => { + it("should build query key function with parameters", () => { + const result = buildQueryKeyFnExport(mockQueryOperation, mockContext); + + expect(result.kind).toBe(StructureKind.VariableStatement); + expect(result.isExported).toBe(true); + expect(result.declarations[0].name).toBe("FindPetsQueryKeyFn"); + + const initializer = result.declarations[0].initializer as string; + expect(initializer).toContain( + "clientOptions: Options", + ); + expect(initializer).toContain("= {}"); // default value for optional params + expect(initializer).toContain("queryKey?: Array"); + expect(initializer).toContain("[findPetsQueryKey,"); + expect(initializer).toContain("[clientOptions]"); // fallback array + expect(initializer).toContain("as const"); + }); + + it("should build query key function without default value for required params", () => { + const result = buildQueryKeyFnExport( + mockRequiredParamsOperation, + mockContext, + ); + const initializer = result.declarations[0].initializer as string; + + expect(initializer).toContain( + "clientOptions: Options", + ); + expect(initializer).not.toContain("= {}"); + }); + + it("should build query key function without clientOptions for no params operation", () => { + const result = buildQueryKeyFnExport(mockNoParamsOperation, mockContext); + const initializer = result.declarations[0].initializer as string; + + expect(initializer).not.toContain("clientOptions"); + expect(initializer).toContain("queryKey?: Array"); + expect(initializer).toContain("queryKey ?? []"); // empty fallback + }); + + it("should use unknown for missing data type", () => { + const op: OperationInfo = { + ...mockQueryOperation, + methodName: "unknownMethod", + capitalizedMethodName: "UnknownMethod", + }; + + const result = buildQueryKeyFnExport(op, mockContext); + const initializer = result.declarations[0].initializer as string; + + expect(initializer).toContain("Options"); + }); + }); + + describe("buildMutationKeyFnExport", () => { + it("should build mutation key function", () => { + const result = buildMutationKeyFnExport(mockMutationOperation); + + expect(result.kind).toBe(StructureKind.VariableStatement); + expect(result.isExported).toBe(true); + expect(result.declarations[0].name).toBe("AddPetMutationKeyFn"); + + const initializer = result.declarations[0].initializer as string; + expect(initializer).toContain("mutationKey?: Array"); + expect(initializer).toContain("[addPetMutationKey,"); + expect(initializer).toContain("mutationKey ?? []"); + expect(initializer).toContain("as const"); + }); + }); +}); diff --git a/tests/tsmorph/buildMutationHooks.test.ts b/tests/tsmorph/buildMutationHooks.test.ts new file mode 100644 index 0000000..b4cfc1c --- /dev/null +++ b/tests/tsmorph/buildMutationHooks.test.ts @@ -0,0 +1,174 @@ +import { StructureKind, VariableDeclarationKind } from "ts-morph"; +import { describe, expect, it } from "vitest"; +import { buildUseMutationHook } from "../../src/tsmorph/buildMutationHooks.mjs"; +import type { GenerationContext, OperationInfo } from "../../src/types.mjs"; + +const mockPostOperation: OperationInfo = { + methodName: "addPet", + capitalizedMethodName: "AddPet", + httpMethod: "POST", + isDeprecated: false, + parameters: [{ name: "body", typeName: "NewPet", optional: false }], + allParamsOptional: false, + isPaginatable: false, +}; + +const mockDeleteOperation: OperationInfo = { + methodName: "deletePet", + capitalizedMethodName: "DeletePet", + httpMethod: "DELETE", + isDeprecated: false, + parameters: [{ name: "id", typeName: "number", optional: false }], + allParamsOptional: false, + isPaginatable: false, +}; + +const mockPutOperation: OperationInfo = { + methodName: "updatePet", + capitalizedMethodName: "UpdatePet", + httpMethod: "PUT", + isDeprecated: false, + parameters: [ + { name: "id", typeName: "number", optional: false }, + { name: "body", typeName: "Pet", optional: false }, + ], + allParamsOptional: false, + isPaginatable: false, +}; + +const mockPatchOperation: OperationInfo = { + methodName: "patchPet", + capitalizedMethodName: "PatchPet", + httpMethod: "PATCH", + isDeprecated: false, + parameters: [{ name: "body", typeName: "Partial", optional: true }], + allParamsOptional: true, + isPaginatable: false, +}; + +const mockFetchContext: GenerationContext = { + client: "@hey-api/client-fetch", + modelNames: [ + "Pet", + "NewPet", + "AddPetData", + "DeletePetData", + "UpdatePetData", + "PatchPetData", + ], + serviceNames: ["addPet", "deletePet", "updatePet", "patchPet"], + pageParam: "page", + nextPageParam: "nextPage", + initialPageParam: "1", + version: "1.0.0", +}; + +const mockAxiosContext: GenerationContext = { + ...mockFetchContext, + client: "@hey-api/client-axios", +}; + +describe("buildMutationHooks", () => { + describe("buildUseMutationHook", () => { + it("should build useMutation hook for POST operation", () => { + const result = buildUseMutationHook(mockPostOperation, mockFetchContext); + + expect(result.kind).toBe(StructureKind.VariableStatement); + expect(result.isExported).toBe(true); + expect(result.declarationKind).toBe(VariableDeclarationKind.Const); + expect(result.declarations[0].name).toBe("useAddPet"); + + const initializer = result.declarations[0].initializer as string; + expect(initializer).toContain("TData = Common.AddPetMutationResult"); + expect(initializer).toContain("TError = AddPetError"); + expect(initializer).toContain("TContext = unknown"); + expect(initializer).toContain( + "useMutation, TContext>", + ); + expect(initializer).toContain("Common.UseAddPetKeyFn(mutationKey)"); + expect(initializer).toContain( + "addPet(clientOptions) as unknown as Promise", + ); + }); + + it("should build useMutation hook for DELETE operation", () => { + const result = buildUseMutationHook( + mockDeleteOperation, + mockFetchContext, + ); + + expect(result.declarations[0].name).toBe("useDeletePet"); + + const initializer = result.declarations[0].initializer as string; + expect(initializer).toContain("Common.DeletePetMutationResult"); + expect(initializer).toContain("deletePet(clientOptions)"); + }); + + it("should build useMutation hook for PUT operation", () => { + const result = buildUseMutationHook(mockPutOperation, mockFetchContext); + + expect(result.declarations[0].name).toBe("useUpdatePet"); + + const initializer = result.declarations[0].initializer as string; + expect(initializer).toContain("Common.UpdatePetMutationResult"); + expect(initializer).toContain("updatePet(clientOptions)"); + }); + + it("should build useMutation hook for PATCH operation", () => { + const result = buildUseMutationHook(mockPatchOperation, mockFetchContext); + + expect(result.declarations[0].name).toBe("usePatchPet"); + + const initializer = result.declarations[0].initializer as string; + expect(initializer).toContain("Common.PatchPetMutationResult"); + expect(initializer).toContain("patchPet(clientOptions)"); + }); + + it("should use AxiosError for axios client", () => { + const result = buildUseMutationHook(mockPostOperation, mockAxiosContext); + const initializer = result.declarations[0].initializer as string; + + expect(initializer).toContain("TError = AxiosError"); + }); + + it("should include mutationKey parameter", () => { + const result = buildUseMutationHook(mockPostOperation, mockFetchContext); + const initializer = result.declarations[0].initializer as string; + + expect(initializer).toContain("mutationKey?: TQueryKey"); + }); + + it("should include options parameter with Omit type", () => { + const result = buildUseMutationHook(mockPostOperation, mockFetchContext); + const initializer = result.declarations[0].initializer as string; + + expect(initializer).toContain( + 'Omit, TContext>, "mutationKey" | "mutationFn">', + ); + }); + + it("should use unknown for missing data type", () => { + const opWithoutData: OperationInfo = { + ...mockPostOperation, + methodName: "unknownMutation", + capitalizedMethodName: "UnknownMutation", + }; + const ctx: GenerationContext = { + ...mockFetchContext, + modelNames: ["Pet", "NewPet"], // no UnknownMutationData + }; + + const result = buildUseMutationHook(opWithoutData, ctx); + const initializer = result.declarations[0].initializer as string; + + expect(initializer).toContain("Options"); + }); + + it("should spread options at the end", () => { + const result = buildUseMutationHook(mockPostOperation, mockFetchContext); + const initializer = result.declarations[0].initializer as string; + + expect(initializer).toContain("...options })"); + }); + }); +}); diff --git a/tests/tsmorph/buildQueryHooks.test.ts b/tests/tsmorph/buildQueryHooks.test.ts new file mode 100644 index 0000000..39d7bd2 --- /dev/null +++ b/tests/tsmorph/buildQueryHooks.test.ts @@ -0,0 +1,240 @@ +import { StructureKind, VariableDeclarationKind } from "ts-morph"; +import { describe, expect, it } from "vitest"; +import { + buildEnsureQueryDataFn, + buildPrefetchFn, + buildUseInfiniteQueryHook, + buildUseQueryHook, + buildUseSuspenseQueryHook, +} from "../../src/tsmorph/buildQueryHooks.mjs"; +import type { GenerationContext, OperationInfo } from "../../src/types.mjs"; + +const mockOperation: OperationInfo = { + methodName: "findPets", + capitalizedMethodName: "FindPets", + httpMethod: "GET", + isDeprecated: false, + parameters: [{ name: "limit", typeName: "number", optional: true }], + allParamsOptional: true, + isPaginatable: false, +}; + +const mockPaginatableOperation: OperationInfo = { + methodName: "findPaginatedPets", + capitalizedMethodName: "FindPaginatedPets", + httpMethod: "GET", + isDeprecated: false, + parameters: [{ name: "page", typeName: "number", optional: true }], + allParamsOptional: true, + isPaginatable: true, +}; + +const mockRequiredParamsOperation: OperationInfo = { + methodName: "findPetById", + capitalizedMethodName: "FindPetById", + httpMethod: "GET", + isDeprecated: false, + parameters: [{ name: "id", typeName: "number", optional: false }], + allParamsOptional: false, + isPaginatable: false, +}; + +const mockFetchContext: GenerationContext = { + client: "@hey-api/client-fetch", + modelNames: [ + "Pet", + "FindPetsData", + "FindPaginatedPetsData", + "FindPetByIdData", + ], + serviceNames: ["findPets", "findPaginatedPets", "findPetById"], + pageParam: "page", + nextPageParam: "nextPage", + initialPageParam: "1", + version: "1.0.0", +}; + +const mockAxiosContext: GenerationContext = { + ...mockFetchContext, + client: "@hey-api/client-axios", +}; + +describe("buildQueryHooks", () => { + describe("buildUseQueryHook", () => { + it("should build useQuery hook with fetch client", () => { + const result = buildUseQueryHook(mockOperation, mockFetchContext); + + expect(result.kind).toBe(StructureKind.VariableStatement); + expect(result.isExported).toBe(true); + expect(result.declarationKind).toBe(VariableDeclarationKind.Const); + expect(result.declarations[0].name).toBe("useFindPets"); + + const initializer = result.declarations[0].initializer as string; + expect(initializer).toContain("TData = Common.FindPetsDefaultResponse"); + expect(initializer).toContain("TError = FindPetsError"); + expect(initializer).toContain("useQuery"); + expect(initializer).toContain( + "Common.UseFindPetsKeyFn(clientOptions, queryKey)", + ); + expect(initializer).toContain("findPets({ ...clientOptions })"); + expect(initializer).toContain("response.data as TData"); + }); + + it("should build useQuery hook with axios client and AxiosError", () => { + const result = buildUseQueryHook(mockOperation, mockAxiosContext); + const initializer = result.declarations[0].initializer as string; + + expect(initializer).toContain("TError = AxiosError"); + }); + + it("should include default value for optional params", () => { + const result = buildUseQueryHook(mockOperation, mockFetchContext); + const initializer = result.declarations[0].initializer as string; + + expect(initializer).toContain( + "clientOptions: Options = {}", + ); + }); + + it("should not include default value for required params", () => { + const result = buildUseQueryHook( + mockRequiredParamsOperation, + mockFetchContext, + ); + const initializer = result.declarations[0].initializer as string; + + expect(initializer).toContain( + "clientOptions: Options", + ); + expect(initializer).not.toContain("= {}"); + }); + }); + + describe("buildUseSuspenseQueryHook", () => { + it("should build useSuspenseQuery hook", () => { + const result = buildUseSuspenseQueryHook(mockOperation, mockFetchContext); + + expect(result.declarations[0].name).toBe("useFindPetsSuspense"); + + const initializer = result.declarations[0].initializer as string; + expect(initializer).toContain( + "TData = NonNullable", + ); + expect(initializer).toContain("useSuspenseQuery"); + expect(initializer).toContain("UseSuspenseQueryOptions"); + }); + + it("should use NonNullable wrapper for data type", () => { + const result = buildUseSuspenseQueryHook(mockOperation, mockFetchContext); + const initializer = result.declarations[0].initializer as string; + + expect(initializer).toContain( + "NonNullable", + ); + }); + }); + + describe("buildUseInfiniteQueryHook", () => { + it("should return null for non-paginatable operation", () => { + const result = buildUseInfiniteQueryHook(mockOperation, mockFetchContext); + + expect(result).toBeNull(); + }); + + it("should build useInfiniteQuery hook for paginatable operation", () => { + const result = buildUseInfiniteQueryHook( + mockPaginatableOperation, + mockFetchContext, + ); + + expect(result).not.toBeNull(); + expect(result?.declarations[0].name).toBe("useFindPaginatedPetsInfinite"); + + const initializer = result?.declarations[0].initializer as string; + expect(initializer).toContain( + "InfiniteData", + ); + expect(initializer).toContain("useInfiniteQuery"); + expect(initializer).toContain("pageParam"); + expect(initializer).toContain("getNextPageParam"); + expect(initializer).toContain('initialPageParam: "1"'); + }); + + it("should include pageParam in queryFn", () => { + const result = buildUseInfiniteQueryHook( + mockPaginatableOperation, + mockFetchContext, + ); + const initializer = result?.declarations[0].initializer as string; + + expect(initializer).toContain("page: pageParam as number"); + }); + }); + + describe("buildPrefetchFn", () => { + it("should build prefetch function", () => { + const result = buildPrefetchFn(mockOperation, mockFetchContext); + + expect(result.declarations[0].name).toBe("prefetchUseFindPets"); + + const initializer = result.declarations[0].initializer as string; + expect(initializer).toContain("queryClient: QueryClient"); + expect(initializer).toContain( + "clientOptions: Options", + ); + expect(initializer).toContain("queryClient.prefetchQuery"); + expect(initializer).toContain("Common.UseFindPetsKeyFn(clientOptions)"); + expect(initializer).toContain("findPets({ ...clientOptions })"); + expect(initializer).toContain("response.data"); + }); + + it("should include default value for optional params", () => { + const result = buildPrefetchFn(mockOperation, mockFetchContext); + const initializer = result.declarations[0].initializer as string; + + expect(initializer).toContain("= {}"); + }); + + it("should not include default value for required params", () => { + const result = buildPrefetchFn( + mockRequiredParamsOperation, + mockFetchContext, + ); + const initializer = result.declarations[0].initializer as string; + + expect(initializer).toContain( + "clientOptions: Options", + ); + // The line should not have " = {}" after the type + expect(initializer).toMatch(/Options\)/); + }); + }); + + describe("buildEnsureQueryDataFn", () => { + it("should build ensureQueryData function", () => { + const result = buildEnsureQueryDataFn(mockOperation, mockFetchContext); + + expect(result.declarations[0].name).toBe("ensureUseFindPetsData"); + + const initializer = result.declarations[0].initializer as string; + expect(initializer).toContain("queryClient: QueryClient"); + expect(initializer).toContain("queryClient.ensureQueryData"); + expect(initializer).toContain("Common.UseFindPetsKeyFn(clientOptions)"); + }); + + it("should be similar to prefetch but use ensureQueryData", () => { + const prefetchResult = buildPrefetchFn(mockOperation, mockFetchContext); + const ensureResult = buildEnsureQueryDataFn( + mockOperation, + mockFetchContext, + ); + + const prefetchInit = prefetchResult.declarations[0].initializer as string; + const ensureInit = ensureResult.declarations[0].initializer as string; + + expect(prefetchInit).toContain("prefetchQuery"); + expect(ensureInit).toContain("ensureQueryData"); + expect(ensureInit).not.toContain("prefetchQuery"); + }); + }); +}); diff --git a/tests/tsmorph/projectFactory.test.ts b/tests/tsmorph/projectFactory.test.ts new file mode 100644 index 0000000..0d687e5 --- /dev/null +++ b/tests/tsmorph/projectFactory.test.ts @@ -0,0 +1,213 @@ +import { IndentationText, QuoteKind, StructureKind } from "ts-morph"; +import { describe, expect, it } from "vitest"; +import { + buildAxiosErrorImport, + buildClientImport, + buildCommonFileImports, + buildCommonImport, + buildHookFileImports, + buildModelImport, + buildQueryImport, + buildServiceImport, + createGenerationProject, +} from "../../src/tsmorph/projectFactory.mjs"; +import type { GenerationContext } from "../../src/types.mjs"; + +const mockFetchContext: GenerationContext = { + client: "@hey-api/client-fetch", + modelNames: ["Pet", "NewPet", "Error"], + serviceNames: ["findPets", "addPet", "deletePet"], + pageParam: "page", + nextPageParam: "nextPage", + initialPageParam: "1", + version: "1.0.0", +}; + +const mockAxiosContext: GenerationContext = { + ...mockFetchContext, + client: "@hey-api/client-axios", +}; + +const mockEmptyModelsContext: GenerationContext = { + ...mockFetchContext, + modelNames: [], +}; + +describe("projectFactory", () => { + describe("createGenerationProject", () => { + it("should create a ts-morph project", () => { + const project = createGenerationProject(); + + expect(project).toBeDefined(); + expect(project.getSourceFiles()).toHaveLength(0); + }); + + it("should use in-memory file system", () => { + const project = createGenerationProject(); + const sourceFile = project.createSourceFile("test.ts", "const x = 1;"); + + expect(sourceFile.getFullText()).toContain("const x = 1;"); + }); + + it("should use double quotes", () => { + const project = createGenerationProject(); + const sourceFile = project.createSourceFile("test.ts", ""); + sourceFile.addImportDeclaration({ + moduleSpecifier: "test-module", + namedImports: ["Test"], + }); + + const text = sourceFile.getFullText(); + expect(text).toContain('"test-module"'); + }); + }); + + describe("buildClientImport", () => { + it("should build import for fetch client", () => { + const result = buildClientImport(mockFetchContext); + + expect(result.kind).toBe(StructureKind.ImportDeclaration); + expect(result.moduleSpecifier).toBe("@hey-api/client-fetch"); + expect(result.namedImports).toEqual([ + { name: "Options", isTypeOnly: true }, + ]); + }); + + it("should build import for axios client", () => { + const result = buildClientImport(mockAxiosContext); + + expect(result.moduleSpecifier).toBe("@hey-api/client-axios"); + }); + }); + + describe("buildQueryImport", () => { + it("should build import for TanStack Query", () => { + const result = buildQueryImport(); + + expect(result.kind).toBe(StructureKind.ImportDeclaration); + expect(result.moduleSpecifier).toBe("@tanstack/react-query"); + expect(result.namedImports).toContainEqual({ + name: "QueryClient", + isTypeOnly: true, + }); + expect(result.namedImports).toContainEqual({ name: "useQuery" }); + expect(result.namedImports).toContainEqual({ name: "useSuspenseQuery" }); + expect(result.namedImports).toContainEqual({ name: "useMutation" }); + expect(result.namedImports).toContainEqual({ name: "UseQueryResult" }); + expect(result.namedImports).toContainEqual({ name: "UseQueryOptions" }); + expect(result.namedImports).toContainEqual({ + name: "UseMutationOptions", + }); + expect(result.namedImports).toContainEqual({ name: "UseMutationResult" }); + expect(result.namedImports).toContainEqual({ + name: "UseSuspenseQueryOptions", + }); + }); + }); + + describe("buildServiceImport", () => { + it("should build import for services", () => { + const result = buildServiceImport(mockFetchContext); + + expect(result.kind).toBe(StructureKind.ImportDeclaration); + expect(result.moduleSpecifier).toBe("../requests/services.gen"); + expect(result.namedImports).toContainEqual({ name: "findPets" }); + expect(result.namedImports).toContainEqual({ name: "addPet" }); + expect(result.namedImports).toContainEqual({ name: "deletePet" }); + }); + }); + + describe("buildModelImport", () => { + it("should build import for models", () => { + const result = buildModelImport(mockFetchContext); + + expect(result).not.toBeNull(); + expect(result?.kind).toBe(StructureKind.ImportDeclaration); + expect(result?.moduleSpecifier).toBe("../requests/types.gen"); + expect(result?.namedImports).toContainEqual({ name: "Pet" }); + expect(result?.namedImports).toContainEqual({ name: "NewPet" }); + expect(result?.namedImports).toContainEqual({ name: "Error" }); + }); + + it("should return null when no models", () => { + const result = buildModelImport(mockEmptyModelsContext); + + expect(result).toBeNull(); + }); + }); + + describe("buildAxiosErrorImport", () => { + it("should build import for AxiosError", () => { + const result = buildAxiosErrorImport(); + + expect(result.kind).toBe(StructureKind.ImportDeclaration); + expect(result.moduleSpecifier).toBe("axios"); + expect(result.namedImports).toContainEqual({ name: "AxiosError" }); + }); + }); + + describe("buildCommonImport", () => { + it("should build namespace import for Common", () => { + const result = buildCommonImport(); + + expect(result.kind).toBe(StructureKind.ImportDeclaration); + expect(result.moduleSpecifier).toBe("./common"); + expect(result.namespaceImport).toBe("Common"); + }); + }); + + describe("buildCommonFileImports", () => { + it("should build imports for common file with fetch client", () => { + const result = buildCommonFileImports(mockFetchContext); + + expect(result.length).toBeGreaterThanOrEqual(3); + expect( + result.some((i) => i.moduleSpecifier === "@hey-api/client-fetch"), + ).toBe(true); + expect( + result.some((i) => i.moduleSpecifier === "@tanstack/react-query"), + ).toBe(true); + expect( + result.some((i) => i.moduleSpecifier === "../requests/services.gen"), + ).toBe(true); + expect( + result.some((i) => i.moduleSpecifier === "../requests/types.gen"), + ).toBe(true); + // Should not have axios import + expect(result.some((i) => i.moduleSpecifier === "axios")).toBe(false); + }); + + it("should build imports for common file with axios client", () => { + const result = buildCommonFileImports(mockAxiosContext); + + expect( + result.some((i) => i.moduleSpecifier === "@hey-api/client-axios"), + ).toBe(true); + expect(result.some((i) => i.moduleSpecifier === "axios")).toBe(true); + }); + + it("should not include model import when no models", () => { + const result = buildCommonFileImports(mockEmptyModelsContext); + + expect( + result.some((i) => i.moduleSpecifier === "../requests/types.gen"), + ).toBe(false); + }); + }); + + describe("buildHookFileImports", () => { + it("should include Common import plus all common file imports", () => { + const result = buildHookFileImports(mockFetchContext); + + expect(result.length).toBeGreaterThanOrEqual(4); + expect(result[0].moduleSpecifier).toBe("./common"); + expect(result[0].namespaceImport).toBe("Common"); + expect( + result.some((i) => i.moduleSpecifier === "@hey-api/client-fetch"), + ).toBe(true); + expect( + result.some((i) => i.moduleSpecifier === "@tanstack/react-query"), + ).toBe(true); + }); + }); +}); diff --git a/tests/types.test.ts b/tests/types.test.ts new file mode 100644 index 0000000..7dea70d --- /dev/null +++ b/tests/types.test.ts @@ -0,0 +1,148 @@ +import { describe, expect, it } from "vitest"; +import type { + GeneratedFile, + GenerationContext, + OperationInfo, + OperationParameter, +} from "../src/types.mjs"; + +describe("types", () => { + describe("OperationInfo", () => { + it("should define correct structure for GET operation", () => { + const op: OperationInfo = { + methodName: "findPets", + capitalizedMethodName: "FindPets", + httpMethod: "GET", + jsDoc: "/** Find all pets */", + isDeprecated: false, + parameters: [ + { name: "limit", typeName: "number", optional: true }, + { name: "tags", typeName: "string[]", optional: true }, + ], + allParamsOptional: true, + isPaginatable: false, + }; + + expect(op.methodName).toBe("findPets"); + expect(op.capitalizedMethodName).toBe("FindPets"); + expect(op.httpMethod).toBe("GET"); + expect(op.isDeprecated).toBe(false); + expect(op.parameters).toHaveLength(2); + expect(op.allParamsOptional).toBe(true); + expect(op.isPaginatable).toBe(false); + }); + + it("should define correct structure for POST operation", () => { + const op: OperationInfo = { + methodName: "addPet", + capitalizedMethodName: "AddPet", + httpMethod: "POST", + isDeprecated: false, + parameters: [{ name: "body", typeName: "NewPet", optional: false }], + allParamsOptional: false, + isPaginatable: false, + }; + + expect(op.httpMethod).toBe("POST"); + expect(op.allParamsOptional).toBe(false); + expect(op.jsDoc).toBeUndefined(); + }); + + it("should define correct structure for deprecated operation", () => { + const op: OperationInfo = { + methodName: "oldEndpoint", + capitalizedMethodName: "OldEndpoint", + httpMethod: "GET", + jsDoc: "/** @deprecated Use newEndpoint instead */", + isDeprecated: true, + parameters: [], + allParamsOptional: true, + isPaginatable: false, + }; + + expect(op.isDeprecated).toBe(true); + }); + + it("should define correct structure for paginatable operation", () => { + const op: OperationInfo = { + methodName: "listItems", + capitalizedMethodName: "ListItems", + httpMethod: "GET", + isDeprecated: false, + parameters: [{ name: "page", typeName: "number", optional: true }], + allParamsOptional: true, + isPaginatable: true, + }; + + expect(op.isPaginatable).toBe(true); + }); + }); + + describe("OperationParameter", () => { + it("should define required parameter", () => { + const param: OperationParameter = { + name: "id", + typeName: "number", + optional: false, + }; + + expect(param.name).toBe("id"); + expect(param.typeName).toBe("number"); + expect(param.optional).toBe(false); + }); + + it("should define optional parameter", () => { + const param: OperationParameter = { + name: "limit", + typeName: "number", + optional: true, + }; + + expect(param.optional).toBe(true); + }); + }); + + describe("GenerationContext", () => { + it("should define context for fetch client", () => { + const ctx: GenerationContext = { + client: "@hey-api/client-fetch", + modelNames: ["Pet", "NewPet", "Error"], + serviceNames: ["findPets", "addPet"], + pageParam: "page", + nextPageParam: "nextPage", + initialPageParam: "1", + version: "1.0.0", + }; + + expect(ctx.client).toBe("@hey-api/client-fetch"); + expect(ctx.modelNames).toContain("Pet"); + expect(ctx.serviceNames).toContain("findPets"); + }); + + it("should define context for axios client", () => { + const ctx: GenerationContext = { + client: "@hey-api/client-axios", + modelNames: [], + serviceNames: ["getData"], + pageParam: "offset", + nextPageParam: "next", + initialPageParam: "0", + version: "2.0.0", + }; + + expect(ctx.client).toBe("@hey-api/client-axios"); + }); + }); + + describe("GeneratedFile", () => { + it("should define generated file structure", () => { + const file: GeneratedFile = { + name: "queries.ts", + content: "export const useFindPets = ...", + }; + + expect(file.name).toBe("queries.ts"); + expect(file.content).toContain("useFindPets"); + }); + }); +}); From 8a866af14d7e758dc178fd51313d1f53a587c432 Mon Sep 17 00:00:00 2001 From: Urata Daiki <7nohe@users.noreply.github.com> Date: Tue, 16 Dec 2025 22:24:31 +0900 Subject: [PATCH 03/31] docs: add TanStack Router integration guide using ensureQueryData (#190) --- .../content/docs/examples/tanstack-router.md | 146 +++++++++++++++++- docs/src/content/docs/guides/introduction.mdx | 2 +- docs/src/content/docs/index.mdx | 2 +- 3 files changed, 146 insertions(+), 4 deletions(-) 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/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. From 41e1b269c478cae659c56006bc5c0255fcb40261 Mon Sep 17 00:00:00 2001 From: Urata Daiki <7nohe@users.noreply.github.com> Date: Sun, 8 Mar 2026 21:31:24 +0900 Subject: [PATCH 04/31] feat: upgrade hey-api generator to v0.73 --- docs/src/content/docs/guides/cli-options.mdx | 3 +- examples/nextjs-app/fetchClient.ts | 2 +- examples/react-app/package.json | 1 - examples/react-app/src/App.tsx | 4 +- examples/react-router-6-app/package.json | 2 - examples/react-router-6-app/src/axios.ts | 2 +- examples/react-router-7-app/fetchClient.ts | 2 +- .../tanstack-router-app/src/fetchClient.ts | 2 +- package.json | 3 +- pnpm-lock.yaml | 133 +++++++++------ src/constants.mts | 2 +- src/createSource.mts | 5 +- src/generate.mts | 45 ++--- src/parseOperations.mts | 8 +- src/service.mts | 159 ++++++++++-------- src/tsmorph/projectFactory.mts | 12 +- tests/__snapshots__/createSource.test.ts.snap | 84 ++++----- tests/__snapshots__/generate.test.ts.snap | 69 ++++---- tests/parseOperations.test.ts | 39 ++--- tests/service.test.ts | 66 ++++---- tests/tsmorph/buildQueryHooks.test.ts | 89 ++++++++++ tests/tsmorph/projectFactory.test.ts | 20 ++- tests/utils.ts | 14 +- tsconfig.json | 1 + 24 files changed, 459 insertions(+), 308 deletions(-) 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/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/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/createSource.mts b/src/createSource.mts index d916e1c..3a24f2b 100644 --- a/src/createSource.mts +++ b/src/createSource.mts @@ -1,10 +1,11 @@ import { join } from "node:path"; -import type { UserConfig } from "@hey-api/openapi-ts"; import { Project } from "ts-morph"; import { buildGenerationContext, parseOperations } from "./parseOperations.mjs"; import { generateAllFiles } from "./tsmorph/index.mjs"; import type { GeneratedFile } from "./types.mjs"; +type ClientType = "@hey-api/client-fetch" | "@hey-api/client-axios"; + /** * Create source files using ts-morph based generation. */ @@ -17,7 +18,7 @@ export const createSource = async ({ initialPageParam, }: { outputPath: string; - client: UserConfig["client"]; + client: ClientType; version: string; pageParam: string; nextPageParam: string; 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 index d5c6a30..0b1bec8 100644 --- a/src/parseOperations.mts +++ b/src/parseOperations.mts @@ -141,7 +141,13 @@ export function buildGenerationContext( .getSourceFiles() .find((sf) => sf.getFilePath().includes(modelsFileName)); - const serviceFile = project.getSourceFileOrThrow(`${serviceFileName}.ts`); + 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()) diff --git a/src/service.mts b/src/service.mts index 36e3e4d..f637796 100644 --- a/src/service.mts +++ b/src/service.mts @@ -24,78 +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, - 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/projectFactory.mts b/src/tsmorph/projectFactory.mts index f75b8e4..00a26b4 100644 --- a/src/tsmorph/projectFactory.mts +++ b/src/tsmorph/projectFactory.mts @@ -29,18 +29,14 @@ export function createGenerationProject(): Project { /** * Build import structure for client library. + * In v0.73+, Options type is exported from the generated client file. */ export function buildClientImport( - ctx: GenerationContext, + _ctx: GenerationContext, ): ImportDeclarationStructure { - const moduleSpecifier = - ctx.client === "@hey-api/client-axios" - ? "@hey-api/client-axios" - : "@hey-api/client-fetch"; - return { kind: StructureKind.ImportDeclaration, - moduleSpecifier, + moduleSpecifier: "../requests/client", namedImports: [{ name: "Options", isTypeOnly: true }], }; } @@ -77,7 +73,7 @@ export function buildServiceImport( ): ImportDeclarationStructure { return { kind: StructureKind.ImportDeclaration, - moduleSpecifier: "../requests/services.gen", + moduleSpecifier: "../requests/sdk.gen", namedImports: ctx.serviceNames.map((name) => ({ name })), }; } diff --git a/tests/__snapshots__/createSource.test.ts.snap b/tests/__snapshots__/createSource.test.ts.snap index d759777..4a71cd1 100644 --- a/tests/__snapshots__/createSource.test.ts.snap +++ b/tests/__snapshots__/createSource.test.ts.snap @@ -11,10 +11,10 @@ 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 Options } from "../requests/client"; import { type QueryClient, useQuery, useSuspenseQuery, useInfiniteQuery, useMutation, UseQueryResult, UseQueryOptions, UseInfiniteQueryOptions, UseMutationOptions, UseMutationResult, UseSuspenseQueryOptions, InfiniteData } 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 { 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"]; @@ -27,13 +27,13 @@ export type GetNotDefinedDefaultResponse = Awaited = 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; @@ -62,18 +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 Options } from "../requests/client"; import { type QueryClient, useQuery, useSuspenseQuery, useInfiniteQuery, useMutation, UseQueryResult, UseQueryOptions, UseInfiniteQueryOptions, UseMutationOptions, UseMutationResult, UseSuspenseQueryOptions, InfiniteData } 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 { 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 }); " `; @@ -82,15 +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 Options } from "../requests/client"; import { type QueryClient, useQuery, useSuspenseQuery, useInfiniteQuery, useMutation, UseQueryResult, UseQueryOptions, UseInfiniteQueryOptions, UseMutationOptions, UseMutationResult, UseSuspenseQueryOptions, InfiniteData } 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 { 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 }); " `; @@ -99,15 +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 Options } from "../requests/client"; import { type QueryClient, useQuery, useSuspenseQuery, useInfiniteQuery, useMutation, UseQueryResult, UseQueryOptions, UseInfiniteQueryOptions, UseMutationOptions, UseMutationResult, UseSuspenseQueryOptions, InfiniteData } 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 { 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) }); " `; @@ -123,10 +123,10 @@ 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 Options } from "../requests/client"; import { type QueryClient, useQuery, useSuspenseQuery, useInfiniteQuery, useMutation, UseQueryResult, UseQueryOptions, UseInfiniteQueryOptions, UseMutationOptions, UseMutationResult, UseSuspenseQueryOptions, InfiniteData } 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 { 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; @@ -138,13 +138,13 @@ export type GetNotDefinedDefaultResponse = Awaited = 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; @@ -173,17 +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 Options } from "../requests/client"; import { type QueryClient, useQuery, useSuspenseQuery, useInfiniteQuery, useMutation, UseQueryResult, UseQueryOptions, UseInfiniteQueryOptions, UseMutationOptions, UseMutationResult, UseSuspenseQueryOptions, InfiniteData } 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 { 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 }); " `; @@ -192,14 +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 Options } from "../requests/client"; import { type QueryClient, useQuery, useSuspenseQuery, useInfiniteQuery, useMutation, UseQueryResult, UseQueryOptions, UseInfiniteQueryOptions, UseMutationOptions, UseMutationResult, UseSuspenseQueryOptions, InfiniteData } 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 { 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 = , TError = FindPetsError, 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 = GetNotDefinedError, 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 = FindPetByIdError, 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 = GetNotDefinedError, 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 = FindPetByIdError, 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 = FindPaginatedPetsError, 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 }); " `; @@ -208,14 +208,14 @@ exports[`createSource > createSource - @hey-api/client-fetch 5`] = ` "// 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 Options } from "../requests/client"; import { type QueryClient, useQuery, useSuspenseQuery, useInfiniteQuery, useMutation, UseQueryResult, UseQueryOptions, UseInfiniteQueryOptions, UseMutationOptions, UseMutationResult, UseSuspenseQueryOptions, InfiniteData } 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 { 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 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) }); " `; diff --git a/tests/__snapshots__/generate.test.ts.snap b/tests/__snapshots__/generate.test.ts.snap index 5603878..9cd69fd 100644 --- a/tests/__snapshots__/generate.test.ts.snap +++ b/tests/__snapshots__/generate.test.ts.snap @@ -3,8 +3,8 @@ exports[`generate > common.ts 1`] = ` "// generated with @7nohe/openapi-react-query-codegen@1.0.0 -import type { Options } from "@hey-api/client-fetch"; import type { UseQueryResult } from "@tanstack/react-query"; +import type { Options } from "../requests/client"; import type { addPet, deletePet, @@ -13,11 +13,12 @@ import type { findPets, getNotDefined, postNotDefined, -} from "../requests/services.gen"; +} from "../requests/sdk.gen"; import type { FindPaginatedPetsData, FindPetByIdData, FindPetsData, + GetNotDefinedData, } from "../requests/types.gen"; export type FindPetsDefaultResponse = Awaited< @@ -44,7 +45,7 @@ export type GetNotDefinedQueryResult< export const useGetNotDefinedKey = "GetNotDefined"; export const UseGetNotDefinedKeyFn = ( - clientOptions: Options = {}, + clientOptions: Options = {}, queryKey?: Array, ) => [useGetNotDefinedKey, ...(queryKey ?? [clientOptions])]; @@ -58,7 +59,7 @@ export type FindPetByIdQueryResult< export const useFindPetByIdKey = "FindPetById"; export const UseFindPetByIdKeyFn = ( - clientOptions: Options, + clientOptions: Options = {}, queryKey?: Array, ) => [useFindPetByIdKey, ...(queryKey ?? [clientOptions])]; @@ -107,18 +108,19 @@ export const UseDeletePetKeyFn = (mutationKey?: Array) => [ exports[`generate > ensureQueryData.ts 1`] = ` "// generated with @7nohe/openapi-react-query-codegen@1.0.0 -import type { Options } from "@hey-api/client-fetch"; import type { QueryClient } from "@tanstack/react-query"; +import type { Options } from "../requests/client"; import { findPaginatedPets, findPetById, findPets, getNotDefined, -} from "../requests/services.gen"; +} from "../requests/sdk.gen"; import type { FindPaginatedPetsData, FindPetByIdData, FindPetsData, + GetNotDefinedData, } from "../requests/types.gen"; import * as Common from "./common"; @@ -133,7 +135,7 @@ export const ensureUseFindPetsData = ( }); export const ensureUseGetNotDefinedData = ( queryClient: QueryClient, - clientOptions: Options = {}, + clientOptions: Options = {}, ) => queryClient.ensureQueryData({ queryKey: Common.UseGetNotDefinedKeyFn(clientOptions), @@ -142,7 +144,7 @@ export const ensureUseGetNotDefinedData = ( }); export const ensureUseFindPetByIdData = ( queryClient: QueryClient, - clientOptions: Options, + clientOptions: Options = {}, ) => queryClient.ensureQueryData({ queryKey: Common.UseFindPetByIdKeyFn(clientOptions), @@ -172,17 +174,14 @@ export * from "./queries"; exports[`generate > infiniteQueries.ts 1`] = ` "// generated with @7nohe/openapi-react-query-codegen@1.0.0 -import type { Options } from "@hey-api/client-fetch"; import { type InfiniteData, useInfiniteQuery, type UseInfiniteQueryOptions, } from "@tanstack/react-query"; -import { findPaginatedPets } from "../requests/services.gen"; -import type { - FindPaginatedPetsData, - FindPaginatedPetsError, -} from "../requests/types.gen"; +import type { Options } from "../requests/client"; +import { findPaginatedPets } from "../requests/sdk.gen"; +import type { FindPaginatedPetsData } from "../requests/types.gen"; import * as Common from "./common"; export const useFindPaginatedPetsInfinite = < @@ -215,18 +214,19 @@ export const useFindPaginatedPetsInfinite = < exports[`generate > prefetch.ts 1`] = ` "// generated with @7nohe/openapi-react-query-codegen@1.0.0 -import type { Options } from "@hey-api/client-fetch"; import type { QueryClient } from "@tanstack/react-query"; +import type { Options } from "../requests/client"; import { findPaginatedPets, findPetById, findPets, getNotDefined, -} from "../requests/services.gen"; +} from "../requests/sdk.gen"; import type { FindPaginatedPetsData, FindPetByIdData, FindPetsData, + GetNotDefinedData, } from "../requests/types.gen"; import * as Common from "./common"; @@ -241,7 +241,7 @@ export const prefetchUseFindPets = ( }); export const prefetchUseGetNotDefined = ( queryClient: QueryClient, - clientOptions: Options = {}, + clientOptions: Options = {}, ) => queryClient.prefetchQuery({ queryKey: Common.UseGetNotDefinedKeyFn(clientOptions), @@ -250,7 +250,7 @@ export const prefetchUseGetNotDefined = ( }); export const prefetchUseFindPetById = ( queryClient: QueryClient, - clientOptions: Options, + clientOptions: Options = {}, ) => queryClient.prefetchQuery({ queryKey: Common.UseFindPetByIdKeyFn(clientOptions), @@ -272,13 +272,13 @@ export const prefetchUseFindPaginatedPets = ( exports[`generate > queries.ts 1`] = ` "// generated with @7nohe/openapi-react-query-codegen@1.0.0 -import type { Options } from "@hey-api/client-fetch"; import { useMutation, type UseMutationOptions, useQuery, type UseQueryOptions, } from "@tanstack/react-query"; +import type { Options } from "../requests/client"; import { addPet, deletePet, @@ -287,20 +287,19 @@ import { findPets, getNotDefined, postNotDefined, -} from "../requests/services.gen"; +} from "../requests/sdk.gen"; import type { AddPetData, AddPetError, DeletePetData, DeletePetError, FindPaginatedPetsData, - FindPaginatedPetsError, FindPetByIdData, FindPetByIdError, FindPetsData, FindPetsError, - GetNotDefinedError, - PostNotDefinedError, + GetNotDefinedData, + PostNotDefinedData, } from "../requests/types.gen"; import * as Common from "./common"; @@ -326,7 +325,7 @@ export const useGetNotDefined = < TError = GetNotDefinedError, TQueryKey extends Array = unknown[], >( - clientOptions: Options = {}, + clientOptions: Options = {}, queryKey?: TQueryKey, options?: Omit, "queryKey" | "queryFn">, ) => @@ -343,7 +342,7 @@ export const useFindPetById = < TError = FindPetByIdError, TQueryKey extends Array = unknown[], >( - clientOptions: Options, + clientOptions: Options = {}, queryKey?: TQueryKey, options?: Omit, "queryKey" | "queryFn">, ) => @@ -398,11 +397,16 @@ export const usePostNotDefined = < >( mutationKey?: TQueryKey, options?: Omit< - UseMutationOptions, TContext>, + UseMutationOptions< + TData, + TError, + Options, + TContext + >, "mutationKey" | "mutationFn" >, ) => - useMutation, TContext>({ + useMutation, TContext>({ mutationKey: Common.UsePostNotDefinedKeyFn(mutationKey), mutationFn: (clientOptions) => postNotDefined(clientOptions) as unknown as Promise, @@ -432,25 +436,24 @@ export const useDeletePet = < exports[`generate > suspense.ts 1`] = ` "// generated with @7nohe/openapi-react-query-codegen@1.0.0 -import type { Options } from "@hey-api/client-fetch"; import { useSuspenseQuery, type UseSuspenseQueryOptions, } from "@tanstack/react-query"; +import type { Options } from "../requests/client"; import { findPaginatedPets, findPetById, findPets, getNotDefined, -} from "../requests/services.gen"; +} from "../requests/sdk.gen"; import type { FindPaginatedPetsData, - FindPaginatedPetsError, FindPetByIdData, FindPetByIdError, FindPetsData, FindPetsError, - GetNotDefinedError, + GetNotDefinedData, } from "../requests/types.gen"; import * as Common from "./common"; @@ -479,7 +482,7 @@ export const useGetNotDefinedSuspense = < TError = GetNotDefinedError, TQueryKey extends Array = unknown[], >( - clientOptions: Options = {}, + clientOptions: Options = {}, queryKey?: TQueryKey, options?: Omit< UseSuspenseQueryOptions, @@ -499,7 +502,7 @@ export const useFindPetByIdSuspense = < TError = FindPetByIdError, TQueryKey extends Array = unknown[], >( - clientOptions: Options, + clientOptions: Options = {}, queryKey?: TQueryKey, options?: Omit< UseSuspenseQueryOptions, diff --git a/tests/parseOperations.test.ts b/tests/parseOperations.test.ts index ec6c542..3b54ec0 100644 --- a/tests/parseOperations.test.ts +++ b/tests/parseOperations.test.ts @@ -1,4 +1,3 @@ -import { join } from "node:path"; import { Project } from "ts-morph"; import { afterAll, beforeAll, describe, expect, it } from "vitest"; import { @@ -16,8 +15,7 @@ describe("parseOperations", () => { describe("parseOperations", () => { it("should parse GET operations", async () => { const project = new Project({ skipAddingFilesFromTsConfig: true }); - const sourceFiles = join(process.cwd(), outputPath(fileName)); - project.addSourceFilesAtPaths(`${sourceFiles}/**/*`); + project.addSourceFilesAtPaths(`${outputPath(fileName)}/**/*`); const operations = await parseOperations(project, "page"); @@ -32,8 +30,7 @@ describe("parseOperations", () => { it("should parse POST operations", async () => { const project = new Project({ skipAddingFilesFromTsConfig: true }); - const sourceFiles = join(process.cwd(), outputPath(fileName)); - project.addSourceFilesAtPaths(`${sourceFiles}/**/*`); + project.addSourceFilesAtPaths(`${outputPath(fileName)}/**/*`); const operations = await parseOperations(project, "page"); @@ -47,8 +44,7 @@ describe("parseOperations", () => { it("should parse DELETE operations", async () => { const project = new Project({ skipAddingFilesFromTsConfig: true }); - const sourceFiles = join(process.cwd(), outputPath(fileName)); - project.addSourceFilesAtPaths(`${sourceFiles}/**/*`); + project.addSourceFilesAtPaths(`${outputPath(fileName)}/**/*`); const operations = await parseOperations(project, "page"); @@ -59,8 +55,7 @@ describe("parseOperations", () => { it("should parse all GET operations as potentially paginatable", async () => { const project = new Project({ skipAddingFilesFromTsConfig: true }); - const sourceFiles = join(process.cwd(), outputPath(fileName)); - project.addSourceFilesAtPaths(`${sourceFiles}/**/*`); + project.addSourceFilesAtPaths(`${outputPath(fileName)}/**/*`); const operations = await parseOperations(project, "page"); @@ -76,8 +71,7 @@ describe("parseOperations", () => { it("should extract parameters correctly", async () => { const project = new Project({ skipAddingFilesFromTsConfig: true }); - const sourceFiles = join(process.cwd(), outputPath(fileName)); - project.addSourceFilesAtPaths(`${sourceFiles}/**/*`); + project.addSourceFilesAtPaths(`${outputPath(fileName)}/**/*`); const operations = await parseOperations(project, "page"); @@ -86,14 +80,14 @@ describe("parseOperations", () => { ); expect(findPetById).toBeDefined(); expect(findPetById?.parameters.length).toBeGreaterThan(0); - // findPetById requires an id parameter, so not all params are optional - expect(findPetById?.allParamsOptional).toBe(false); + // In v0.73+, the options parameter is always optional + // The required path parameters are nested within the options + expect(findPetById?.allParamsOptional).toBe(true); }); it("should detect operations with all optional parameters", async () => { const project = new Project({ skipAddingFilesFromTsConfig: true }); - const sourceFiles = join(process.cwd(), outputPath(fileName)); - project.addSourceFilesAtPaths(`${sourceFiles}/**/*`); + project.addSourceFilesAtPaths(`${outputPath(fileName)}/**/*`); const operations = await parseOperations(project, "page"); @@ -107,8 +101,7 @@ describe("parseOperations", () => { describe("buildGenerationContext", () => { it("should build context with fetch client", async () => { const project = new Project({ skipAddingFilesFromTsConfig: true }); - const sourceFiles = join(process.cwd(), outputPath(fileName)); - project.addSourceFilesAtPaths(`${sourceFiles}/**/*`); + project.addSourceFilesAtPaths(`${outputPath(fileName)}/**/*`); const ctx = buildGenerationContext( project, @@ -130,8 +123,7 @@ describe("parseOperations", () => { it("should build context with axios client", async () => { const project = new Project({ skipAddingFilesFromTsConfig: true }); - const sourceFiles = join(process.cwd(), outputPath(fileName)); - project.addSourceFilesAtPaths(`${sourceFiles}/**/*`); + project.addSourceFilesAtPaths(`${outputPath(fileName)}/**/*`); const ctx = buildGenerationContext( project, @@ -149,8 +141,7 @@ describe("parseOperations", () => { it("should include model names", async () => { const project = new Project({ skipAddingFilesFromTsConfig: true }); - const sourceFiles = join(process.cwd(), outputPath(fileName)); - project.addSourceFilesAtPaths(`${sourceFiles}/**/*`); + project.addSourceFilesAtPaths(`${outputPath(fileName)}/**/*`); const ctx = buildGenerationContext( project, @@ -163,13 +154,13 @@ describe("parseOperations", () => { expect(ctx.modelNames).toContain("Pet"); expect(ctx.modelNames).toContain("NewPet"); - expect(ctx.modelNames).toContain("Error"); + // In v0.73+, Error is renamed to _Error to avoid conflict with built-in Error + expect(ctx.modelNames).toContain("_Error"); }); it("should include service names", async () => { const project = new Project({ skipAddingFilesFromTsConfig: true }); - const sourceFiles = join(process.cwd(), outputPath(fileName)); - project.addSourceFilesAtPaths(`${sourceFiles}/**/*`); + project.addSourceFilesAtPaths(`${outputPath(fileName)}/**/*`); const ctx = buildGenerationContext( project, diff --git a/tests/service.test.ts b/tests/service.test.ts index b4f254d..bd31590 100644 --- a/tests/service.test.ts +++ b/tests/service.test.ts @@ -18,15 +18,15 @@ describe(fileName, () => { const service = await getServices(project); const methodNames = service.methods.map((m) => m.method.getName()); - expect(methodNames).toEqual([ - "findPets", - "addPet", - "getNotDefined", - "postNotDefined", - "findPetById", - "deletePet", - "findPaginatedPets", - ]); + // In v0.73+, the order may differ slightly but should contain all methods + expect(methodNames).toContain("findPets"); + expect(methodNames).toContain("addPet"); + expect(methodNames).toContain("getNotDefined"); + expect(methodNames).toContain("postNotDefined"); + expect(methodNames).toContain("findPetById"); + expect(methodNames).toContain("deletePet"); + expect(methodNames).toContain("findPaginatedPets"); + expect(methodNames).toHaveLength(7); }); test("getServices (No service node found)", async () => { @@ -39,68 +39,60 @@ describe(fileName, () => { ); }); - test('getMethodsFromService - throw error "Arrow function not found"', async () => { + // In v0.73+, getMethodsFromService skips invalid entries instead of throwing + test("getMethodsFromService - skips non-arrow functions", () => { const source = ` - const client = createClient(createConfig()) const foo = "bar" `; const project = new Project(); const sourceFile = project.createSourceFile("test.ts", source); - await expect(() => getMethodsFromService(sourceFile)).toThrowError( - "Arrow function not found", - ); + const result = getMethodsFromService(sourceFile); + expect(result).toEqual([]); }); - test('getMethodsFromService - throw error "Initializer not found"', async () => { + test("getMethodsFromService - skips variables without initializer", () => { const source = ` - const client = createClient(createConfig()) - const foo + declare const foo: string `; const project = new Project(); const sourceFile = project.createSourceFile("test.ts", source); - await expect(() => getMethodsFromService(sourceFile)).toThrowError( - "Initializer not found", - ); + const result = getMethodsFromService(sourceFile); + expect(result).toEqual([]); }); - test('getMethodsFromService - throw error "Return statement not found"', async () => { + test("getMethodsFromService - skips arrow functions without HTTP method call", () => { const source = ` - const client = createClient(createConfig()) const foo = () => {} `; const project = new Project(); const sourceFile = project.createSourceFile("test.ts", source); - await expect(() => getMethodsFromService(sourceFile)).toThrowError( - "Return statement not found", - ); + const result = getMethodsFromService(sourceFile); + expect(result).toEqual([]); }); - test('getMethodsFromService - throw error "Call expression not found"', async () => { + test("getMethodsFromService - skips expression body without call expression", () => { const source = ` - const client = createClient(createConfig()) - const foo = () => { return } + const foo = () => "bar" `; const project = new Project(); const sourceFile = project.createSourceFile("test.ts", source); - await expect(() => getMethodsFromService(sourceFile)).toThrowError( - "Call expression not found", - ); + const result = getMethodsFromService(sourceFile); + expect(result).toEqual([]); }); - test('getMethodsFromService - throw error "Method block not found"', async () => { + test("getMethodsFromService - parses valid SDK function with expression body", () => { const source = ` - const client = createClient(createConfig()) - const foo = () => + const findPets = (options) => client.get({ url: '/pets', ...options }) `; const project = new Project(); const sourceFile = project.createSourceFile("test.ts", source); - await expect(() => getMethodsFromService(sourceFile)).toThrowError( - "Method block not found", - ); + const result = getMethodsFromService(sourceFile); + expect(result).toHaveLength(1); + expect(result[0].httpMethodName).toBe("get"); }); }); diff --git a/tests/tsmorph/buildQueryHooks.test.ts b/tests/tsmorph/buildQueryHooks.test.ts index 39d7bd2..164b854 100644 --- a/tests/tsmorph/buildQueryHooks.test.ts +++ b/tests/tsmorph/buildQueryHooks.test.ts @@ -39,6 +39,26 @@ const mockRequiredParamsOperation: OperationInfo = { isPaginatable: false, }; +const mockNoParamsOperation: OperationInfo = { + methodName: "getStatus", + capitalizedMethodName: "GetStatus", + httpMethod: "GET", + isDeprecated: false, + parameters: [], + allParamsOptional: true, + isPaginatable: false, +}; + +const mockPaginatableNoDataOperation: OperationInfo = { + methodName: "listThings", + capitalizedMethodName: "ListThings", + httpMethod: "GET", + isDeprecated: false, + parameters: [], + allParamsOptional: true, + isPaginatable: true, +}; + const mockFetchContext: GenerationContext = { client: "@hey-api/client-fetch", modelNames: [ @@ -59,6 +79,11 @@ const mockAxiosContext: GenerationContext = { client: "@hey-api/client-axios", }; +const mockUnknownDataContext: GenerationContext = { + ...mockFetchContext, + modelNames: [], +}; + describe("buildQueryHooks", () => { describe("buildUseQueryHook", () => { it("should build useQuery hook with fetch client", () => { @@ -108,6 +133,19 @@ describe("buildQueryHooks", () => { ); expect(initializer).not.toContain("= {}"); }); + + it("should handle operations without params and unknown data type", () => { + const result = buildUseQueryHook( + mockNoParamsOperation, + mockUnknownDataContext, + ); + const initializer = result.declarations[0].initializer as string; + + expect(initializer).toContain( + "clientOptions: Options = {}", + ); + expect(initializer).toContain("getStatus({ ...clientOptions })"); + }); }); describe("buildUseSuspenseQueryHook", () => { @@ -132,6 +170,19 @@ describe("buildQueryHooks", () => { "NonNullable", ); }); + + it("should handle operations without params and unknown data type", () => { + const result = buildUseSuspenseQueryHook( + mockNoParamsOperation, + mockUnknownDataContext, + ); + const initializer = result.declarations[0].initializer as string; + + expect(initializer).toContain( + "clientOptions: Options = {}", + ); + expect(initializer).toContain("getStatus({ ...clientOptions })"); + }); }); describe("buildUseInfiniteQueryHook", () => { @@ -169,6 +220,18 @@ describe("buildQueryHooks", () => { expect(initializer).toContain("page: pageParam as number"); }); + + it("should use unknown data type when not present in modelNames", () => { + const result = buildUseInfiniteQueryHook( + mockPaginatableNoDataOperation, + mockUnknownDataContext, + ); + const initializer = result?.declarations[0].initializer as string; + + expect(initializer).toContain( + "clientOptions: Options = {}", + ); + }); }); describe("buildPrefetchFn", () => { @@ -208,6 +271,19 @@ describe("buildQueryHooks", () => { // The line should not have " = {}" after the type expect(initializer).toMatch(/Options\)/); }); + + it("should handle operations without params and unknown data type", () => { + const result = buildPrefetchFn( + mockNoParamsOperation, + mockUnknownDataContext, + ); + const initializer = result.declarations[0].initializer as string; + + expect(initializer).toContain( + "clientOptions: Options = {}", + ); + expect(initializer).toContain("getStatus({ ...clientOptions })"); + }); }); describe("buildEnsureQueryDataFn", () => { @@ -236,5 +312,18 @@ describe("buildQueryHooks", () => { expect(ensureInit).toContain("ensureQueryData"); expect(ensureInit).not.toContain("prefetchQuery"); }); + + it("should handle operations without params and unknown data type", () => { + const result = buildEnsureQueryDataFn( + mockNoParamsOperation, + mockUnknownDataContext, + ); + const initializer = result.declarations[0].initializer as string; + + expect(initializer).toContain( + "clientOptions: Options = {}", + ); + expect(initializer).toContain("getStatus({ ...clientOptions })"); + }); }); }); diff --git a/tests/tsmorph/projectFactory.test.ts b/tests/tsmorph/projectFactory.test.ts index 0d687e5..257df6d 100644 --- a/tests/tsmorph/projectFactory.test.ts +++ b/tests/tsmorph/projectFactory.test.ts @@ -67,7 +67,8 @@ describe("projectFactory", () => { const result = buildClientImport(mockFetchContext); expect(result.kind).toBe(StructureKind.ImportDeclaration); - expect(result.moduleSpecifier).toBe("@hey-api/client-fetch"); + // In v0.73+, Options is imported from the generated client file + expect(result.moduleSpecifier).toBe("../requests/client"); expect(result.namedImports).toEqual([ { name: "Options", isTypeOnly: true }, ]); @@ -76,7 +77,8 @@ describe("projectFactory", () => { it("should build import for axios client", () => { const result = buildClientImport(mockAxiosContext); - expect(result.moduleSpecifier).toBe("@hey-api/client-axios"); + // In v0.73+, client type doesn't affect the import path + expect(result.moduleSpecifier).toBe("../requests/client"); }); }); @@ -110,7 +112,8 @@ describe("projectFactory", () => { const result = buildServiceImport(mockFetchContext); expect(result.kind).toBe(StructureKind.ImportDeclaration); - expect(result.moduleSpecifier).toBe("../requests/services.gen"); + // In v0.73+, the file is renamed from services.gen to sdk.gen + expect(result.moduleSpecifier).toBe("../requests/sdk.gen"); expect(result.namedImports).toContainEqual({ name: "findPets" }); expect(result.namedImports).toContainEqual({ name: "addPet" }); expect(result.namedImports).toContainEqual({ name: "deletePet" }); @@ -161,14 +164,15 @@ describe("projectFactory", () => { const result = buildCommonFileImports(mockFetchContext); expect(result.length).toBeGreaterThanOrEqual(3); + // In v0.73+, Options is imported from ../requests/client expect( - result.some((i) => i.moduleSpecifier === "@hey-api/client-fetch"), + result.some((i) => i.moduleSpecifier === "../requests/client"), ).toBe(true); expect( result.some((i) => i.moduleSpecifier === "@tanstack/react-query"), ).toBe(true); expect( - result.some((i) => i.moduleSpecifier === "../requests/services.gen"), + result.some((i) => i.moduleSpecifier === "../requests/sdk.gen"), ).toBe(true); expect( result.some((i) => i.moduleSpecifier === "../requests/types.gen"), @@ -180,8 +184,9 @@ describe("projectFactory", () => { it("should build imports for common file with axios client", () => { const result = buildCommonFileImports(mockAxiosContext); + // In v0.73+, Options is imported from ../requests/client regardless of axios expect( - result.some((i) => i.moduleSpecifier === "@hey-api/client-axios"), + result.some((i) => i.moduleSpecifier === "../requests/client"), ).toBe(true); expect(result.some((i) => i.moduleSpecifier === "axios")).toBe(true); }); @@ -202,8 +207,9 @@ describe("projectFactory", () => { expect(result.length).toBeGreaterThanOrEqual(4); expect(result[0].moduleSpecifier).toBe("./common"); expect(result[0].namespaceImport).toBe("Common"); + // In v0.73+, Options is imported from ../requests/client expect( - result.some((i) => i.moduleSpecifier === "@hey-api/client-fetch"), + result.some((i) => i.moduleSpecifier === "../requests/client"), ).toBe(true); expect( result.some((i) => i.moduleSpecifier === "@tanstack/react-query"), diff --git a/tests/utils.ts b/tests/utils.ts index 4da25b7..9a47b3b 100644 --- a/tests/utils.ts +++ b/tests/utils.ts @@ -8,11 +8,17 @@ export const outputPath = (prefix: string) => export const generateTSClients = async (prefix: string, inputFile?: string) => { const options: UserConfig = { input: path.join(__dirname, "inputs", inputFile ?? "petstore.yaml"), - client: "@hey-api/client-fetch", output: outputPath(prefix), - services: { - asClass: false, - }, + plugins: [ + "@hey-api/client-fetch", + { + name: "@hey-api/typescript", + }, + { + name: "@hey-api/sdk", + asClass: false, + }, + ], }; await createClient(options); }; diff --git a/tsconfig.json b/tsconfig.json index 864319e..7141541 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -8,6 +8,7 @@ "noImplicitAny": true, "downlevelIteration": true, "resolveJsonModule": true, + "skipLibCheck": true, "outDir": "dist", "lib": ["ESNext", "DOM"], "target": "ESNext", From 07ed7a261b6e2d601d4fde4d520b608a28414d79 Mon Sep 17 00:00:00 2001 From: Urata Daiki <7nohe@users.noreply.github.com> Date: Fri, 10 Jul 2026 23:21:34 +0900 Subject: [PATCH 05/31] fix: adapt ts-morph pipeline to hey-api 0.92+ SDK semantics - TError defaults fall back to unknown (AxiosError for axios) when the operation has no generated Error type (e.g. no error responses defined) - allParamsOptional now uses the SDK function's parameter optionality as the authoritative check; in 0.92+ the options parameter is required when the operation has required path params, so hooks no longer emit an invalid `= {}` default - Drop redundant client.setConfig from the react-app example; axios.ts already configures baseURL Verified: 153 unit tests pass and all 5 example apps typecheck their regenerated output (test:generated). Co-Authored-By: Claude Fable 5 --- examples/react-app/src/App.tsx | 3 -- src/parseOperations.mts | 7 +++- src/tsmorph/buildMutationHooks.mts | 8 +++- src/tsmorph/buildQueryHooks.mts | 8 +++- tests/__snapshots__/createSource.test.ts.snap | 36 +++++++++--------- tests/__snapshots__/generate.test.ts.snap | 38 +++++++++---------- tests/parseOperations.test.ts | 6 +-- tests/tsmorph/buildMutationHooks.test.ts | 4 ++ tests/tsmorph/buildQueryHooks.test.ts | 3 ++ 9 files changed, 65 insertions(+), 48 deletions(-) diff --git a/examples/react-app/src/App.tsx b/examples/react-app/src/App.tsx index 53d8bf8..e8489f4 100644 --- a/examples/react-app/src/App.tsx +++ b/examples/react-app/src/App.tsx @@ -8,13 +8,10 @@ import { useGetNotDefined, usePostNotDefined, } from "../openapi/queries"; -import { client } from "../openapi/requests/client.gen"; import { SuspenseParent } from "./components/SuspenseParent"; import { queryClient } from "./queryClient"; function App() { - client.setConfig({ baseUrl: "http://localhost:4010" }); - const [tags, _setTags] = useState([]); const [limit, _setLimit] = useState(10); diff --git a/src/parseOperations.mts b/src/parseOperations.mts index 0b1bec8..1be20f8 100644 --- a/src/parseOperations.mts +++ b/src/parseOperations.mts @@ -109,7 +109,12 @@ export async function parseOperations( const methodName = getNameFromVariable(desc.method); const httpMethod = desc.httpMethodName.toUpperCase(); const parameters = extractParameters(desc.method); - const allParamsOptional = parameters.every((p) => p.optional); + // Use the SDK function's parameter optionality as the authoritative check. + // Generic types like Options may not resolve correctly + // via extractPropertiesFromObjectParam for type alias properties (path, url). + const sdkParams = getVariableArrowFunctionParameters(desc.method); + const allParamsOptional = + sdkParams.length === 0 || sdkParams[0].isOptional(); const isPaginatable = httpMethod === "GET" && paginatableMethods.includes(methodName); diff --git a/src/tsmorph/buildMutationHooks.mts b/src/tsmorph/buildMutationHooks.mts index c884fc4..9fd517c 100644 --- a/src/tsmorph/buildMutationHooks.mts +++ b/src/tsmorph/buildMutationHooks.mts @@ -10,10 +10,14 @@ import type { GenerationContext, OperationInfo } from "../types.mjs"; */ function getErrorType(op: OperationInfo, ctx: GenerationContext): string { const errorTypeName = `${op.capitalizedMethodName}Error`; + // Operations without error responses have no generated Error type + const errorType = ctx.modelNames.includes(errorTypeName) + ? errorTypeName + : "unknown"; if (ctx.client === "@hey-api/client-axios") { - return `AxiosError<${errorTypeName}>`; + return `AxiosError<${errorType}>`; } - return errorTypeName; + return errorType; } /** diff --git a/src/tsmorph/buildQueryHooks.mts b/src/tsmorph/buildQueryHooks.mts index 8f58a02..9e6dd2f 100644 --- a/src/tsmorph/buildQueryHooks.mts +++ b/src/tsmorph/buildQueryHooks.mts @@ -12,10 +12,14 @@ type QueryHookType = "useQuery" | "useSuspenseQuery" | "useInfiniteQuery"; */ function getErrorType(op: OperationInfo, ctx: GenerationContext): string { const errorTypeName = `${op.capitalizedMethodName}Error`; + // Operations without error responses have no generated Error type + const errorType = ctx.modelNames.includes(errorTypeName) + ? errorTypeName + : "unknown"; if (ctx.client === "@hey-api/client-axios") { - return `AxiosError<${errorTypeName}>`; + return `AxiosError<${errorType}>`; } - return errorTypeName; + return errorType; } /** diff --git a/tests/__snapshots__/createSource.test.ts.snap b/tests/__snapshots__/createSource.test.ts.snap index 02fe081..a307c57 100644 --- a/tests/__snapshots__/createSource.test.ts.snap +++ b/tests/__snapshots__/createSource.test.ts.snap @@ -33,7 +33,7 @@ export type FindPetByIdDefaultResponse = Awaited> 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; @@ -69,11 +69,11 @@ import { ClientOptions, Pet, NewPet, Error, FindPetsData, FindPetsErrors, FindPe 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 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 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 }); " `; @@ -89,9 +89,9 @@ import { ClientOptions, Pet, NewPet, Error, FindPetsData, FindPetsErrors, FindPe 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 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 }); +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 }); " `; @@ -107,7 +107,7 @@ 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 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) }); " `; @@ -144,7 +144,7 @@ export type FindPetByIdDefaultResponse = Awaited> 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; @@ -179,11 +179,11 @@ import { Options, findPets, addPet, getNotDefined, postNotDefined, deletePet, fi import { ClientOptions, 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 } 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 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 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 }); " `; @@ -198,9 +198,9 @@ import { Options, findPets, addPet, getNotDefined, postNotDefined, deletePet, fi import { ClientOptions, 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 } from "../requests/types.gen"; export const useFindPetsSuspense = , TError = FindPetsError, 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 = GetNotDefinedError, 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 = FindPetByIdError, 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 = FindPaginatedPetsError, 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 }); +export const useGetNotDefinedSuspense = , TError = unknown, 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 = FindPetByIdError, 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 = unknown, 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 }); " `; @@ -215,7 +215,7 @@ import { ClientOptions, Pet, NewPet, Error, FindPetsData, FindPetsErrors, FindPe 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 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) }); " `; diff --git a/tests/__snapshots__/generate.test.ts.snap b/tests/__snapshots__/generate.test.ts.snap index 18b17f4..5990510 100644 --- a/tests/__snapshots__/generate.test.ts.snap +++ b/tests/__snapshots__/generate.test.ts.snap @@ -59,7 +59,7 @@ export type FindPetByIdQueryResult< export const useFindPetByIdKey = "FindPetById"; export const UseFindPetByIdKeyFn = ( - clientOptions: Options = {}, + clientOptions: Options, queryKey?: Array, ) => [useFindPetByIdKey, ...(queryKey ?? [clientOptions])]; @@ -144,7 +144,7 @@ export const ensureUseGetNotDefinedData = ( }); export const ensureUseFindPetByIdData = ( queryClient: QueryClient, - clientOptions: Options = {}, + clientOptions: Options, ) => queryClient.ensureQueryData({ queryKey: Common.UseFindPetByIdKeyFn(clientOptions), @@ -186,7 +186,7 @@ import * as Common from "./common"; export const useFindPaginatedPetsInfinite = < TData = InfiniteData, - TError = FindPaginatedPetsError, + TError = unknown, TQueryKey extends Array = unknown[], >( clientOptions: Options = {}, @@ -250,7 +250,7 @@ export const prefetchUseGetNotDefined = ( }); export const prefetchUseFindPetById = ( queryClient: QueryClient, - clientOptions: Options = {}, + clientOptions: Options, ) => queryClient.prefetchQuery({ queryKey: Common.UseFindPetByIdKeyFn(clientOptions), @@ -322,7 +322,7 @@ export const useFindPets = < }); export const useGetNotDefined = < TData = Common.GetNotDefinedDefaultResponse, - TError = GetNotDefinedError, + TError = unknown, TQueryKey extends Array = unknown[], >( clientOptions: Options = {}, @@ -342,7 +342,7 @@ export const useFindPetById = < TError = FindPetByIdError, TQueryKey extends Array = unknown[], >( - clientOptions: Options = {}, + clientOptions: Options, queryKey?: TQueryKey, options?: Omit, "queryKey" | "queryFn">, ) => @@ -356,7 +356,7 @@ export const useFindPetById = < }); export const useFindPaginatedPets = < TData = Common.FindPaginatedPetsDefaultResponse, - TError = FindPaginatedPetsError, + TError = unknown, TQueryKey extends Array = unknown[], >( clientOptions: Options = {}, @@ -391,7 +391,7 @@ export const useAddPet = < }); export const usePostNotDefined = < TData = Common.PostNotDefinedMutationResult, - TError = PostNotDefinedError, + TError = unknown, TQueryKey extends Array = unknown[], TContext = unknown, >( @@ -479,7 +479,7 @@ export const useFindPetsSuspense = < }); export const useGetNotDefinedSuspense = < TData = NonNullable, - TError = GetNotDefinedError, + TError = unknown, TQueryKey extends Array = unknown[], >( clientOptions: Options = {}, @@ -502,7 +502,7 @@ export const useFindPetByIdSuspense = < TError = FindPetByIdError, TQueryKey extends Array = unknown[], >( - clientOptions: Options = {}, + clientOptions: Options, queryKey?: TQueryKey, options?: Omit< UseSuspenseQueryOptions, @@ -519,7 +519,7 @@ export const useFindPetByIdSuspense = < }); export const useFindPaginatedPetsSuspense = < TData = NonNullable, - TError = FindPaginatedPetsError, + TError = unknown, TQueryKey extends Array = unknown[], >( clientOptions: Options = {}, @@ -551,11 +551,11 @@ import { DeletePetsByIdData, DeletePetsByIdError, GetNotDefinedData, GetPaginate import * as Common from "./common"; export const useGetPets = , TQueryKey extends Array = unknown[]>(clientOptions: Options = {}, queryKey?: TQueryKey, options?: Omit, "queryKey" | "queryFn">) => useQuery({ queryKey: Common.UseGetPetsKeyFn(clientOptions, queryKey), queryFn: () => getPets({ ...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 useGetPetsById = , TQueryKey extends Array = unknown[]>(clientOptions: Options = {}, queryKey?: TQueryKey, options?: Omit, "queryKey" | "queryFn">) => useQuery({ queryKey: Common.UseGetPetsByIdKeyFn(clientOptions, queryKey), queryFn: () => getPetsById({ ...clientOptions }).then(response => response.data as TData) as TData, ...options }); -export const useGetPaginatedPets = , TQueryKey extends Array = unknown[]>(clientOptions: Options = {}, queryKey?: TQueryKey, options?: Omit, "queryKey" | "queryFn">) => useQuery({ queryKey: Common.UseGetPaginatedPetsKeyFn(clientOptions, queryKey), queryFn: () => getPaginatedPets({ ...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 useGetPetsById = , TQueryKey extends Array = unknown[]>(clientOptions: Options, queryKey?: TQueryKey, options?: Omit, "queryKey" | "queryFn">) => useQuery({ queryKey: Common.UseGetPetsByIdKeyFn(clientOptions, queryKey), queryFn: () => getPetsById({ ...clientOptions }).then(response => response.data as TData) as TData, ...options }); +export const useGetPaginatedPets = , TQueryKey extends Array = unknown[]>(clientOptions: Options = {}, queryKey?: TQueryKey, options?: Omit, "queryKey" | "queryFn">) => useQuery({ queryKey: Common.UseGetPaginatedPetsKeyFn(clientOptions, queryKey), queryFn: () => getPaginatedPets({ ...clientOptions }).then(response => response.data as TData) as TData, ...options }); export const usePostPets = , TQueryKey extends Array = unknown[], TContext = unknown>(mutationKey?: TQueryKey, options?: Omit, TContext>, "mutationKey" | "mutationFn">) => useMutation, TContext>({ mutationKey: Common.UsePostPetsKeyFn(mutationKey), mutationFn: clientOptions => postPets(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 useDeletePetsById = , TQueryKey extends Array = unknown[], TContext = unknown>(mutationKey?: TQueryKey, options?: Omit, TContext>, "mutationKey" | "mutationFn">) => useMutation, TContext>({ mutationKey: Common.UseDeletePetsByIdKeyFn(mutationKey), mutationFn: clientOptions => deletePetsById(clientOptions) as unknown as Promise, ...options }); " `; @@ -570,11 +570,11 @@ import { AddPetData, AddPetError, DeletePetData, DeletePetError, FindPaginatedPe import * as Common from "./common"; 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 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 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 }); " `; diff --git a/tests/parseOperations.test.ts b/tests/parseOperations.test.ts index 66e8e8b..a0888e1 100644 --- a/tests/parseOperations.test.ts +++ b/tests/parseOperations.test.ts @@ -80,9 +80,9 @@ describe("parseOperations", () => { ); expect(findPetById).toBeDefined(); expect(findPetById?.parameters.length).toBeGreaterThan(0); - // In v0.73+, the options parameter is always optional - // The required path parameters are nested within the options - expect(findPetById?.allParamsOptional).toBe(true); + // In 0.92+, the SDK options parameter is required when the operation + // has required parameters (e.g. path params) + expect(findPetById?.allParamsOptional).toBe(false); }); it("should detect operations with all optional parameters", async () => { diff --git a/tests/tsmorph/buildMutationHooks.test.ts b/tests/tsmorph/buildMutationHooks.test.ts index b4cfc1c..86b9a0e 100644 --- a/tests/tsmorph/buildMutationHooks.test.ts +++ b/tests/tsmorph/buildMutationHooks.test.ts @@ -52,9 +52,13 @@ const mockFetchContext: GenerationContext = { "Pet", "NewPet", "AddPetData", + "AddPetError", "DeletePetData", + "DeletePetError", "UpdatePetData", + "UpdatePetError", "PatchPetData", + "PatchPetError", ], serviceNames: ["addPet", "deletePet", "updatePet", "patchPet"], pageParam: "page", diff --git a/tests/tsmorph/buildQueryHooks.test.ts b/tests/tsmorph/buildQueryHooks.test.ts index 164b854..4dbf052 100644 --- a/tests/tsmorph/buildQueryHooks.test.ts +++ b/tests/tsmorph/buildQueryHooks.test.ts @@ -64,8 +64,11 @@ const mockFetchContext: GenerationContext = { modelNames: [ "Pet", "FindPetsData", + "FindPetsError", "FindPaginatedPetsData", + "FindPaginatedPetsError", "FindPetByIdData", + "FindPetByIdError", ], serviceNames: ["findPets", "findPaginatedPets", "findPetById"], pageParam: "page", From adab0605136d2b787894a507644e81e1a3566e78 Mon Sep 17 00:00:00 2001 From: Urata Daiki <7nohe@users.noreply.github.com> Date: Fri, 10 Jul 2026 23:39:47 +0900 Subject: [PATCH 06/31] fix: restore v2-compatible Options type and operation JSDoc in generated hooks MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Import Options from ../requests/sdk.gen instead of ../requests/client; the sdk.gen Options is extended with `client` and `meta`, which v2 hooks accepted (per-request client override would fail to typecheck otherwise) - Emit the operation's JSDoc (description and @deprecated) on every generated hook, matching v2 output With this, regenerated output for all 5 example apps is semantically identical to v2.2.0 — remaining diffs are import statement merging and blank lines only. Co-Authored-By: Claude Fable 5 --- src/tsmorph/buildMutationHooks.mts | 2 + src/tsmorph/buildQueryHooks.mts | 10 ++ src/tsmorph/projectFactory.mts | 7 +- tests/__snapshots__/createSource.test.ts.snap | 152 +++++++++++++++- tests/__snapshots__/generate.test.ts.snap | 170 ++++++++++++++++-- tests/tsmorph/projectFactory.test.ts | 18 +- 6 files changed, 328 insertions(+), 31 deletions(-) diff --git a/src/tsmorph/buildMutationHooks.mts b/src/tsmorph/buildMutationHooks.mts index 9fd517c..8046383 100644 --- a/src/tsmorph/buildMutationHooks.mts +++ b/src/tsmorph/buildMutationHooks.mts @@ -54,6 +54,8 @@ export function buildUseMutationHook( return { kind: StructureKind.VariableStatement, + // Copy the operation's JSDoc (description and @deprecated) from the SDK function + leadingTrivia: op.jsDoc ? `${op.jsDoc}\n` : undefined, isExported: true, declarationKind: VariableDeclarationKind.Const, declarations: [ diff --git a/src/tsmorph/buildQueryHooks.mts b/src/tsmorph/buildQueryHooks.mts index 9e6dd2f..aa2e653 100644 --- a/src/tsmorph/buildQueryHooks.mts +++ b/src/tsmorph/buildQueryHooks.mts @@ -106,6 +106,8 @@ export function buildUseQueryHook( return { kind: StructureKind.VariableStatement, + // Copy the operation's JSDoc (description and @deprecated) from the SDK function + leadingTrivia: op.jsDoc ? `${op.jsDoc}\n` : undefined, isExported: true, declarationKind: VariableDeclarationKind.Const, declarations: [ @@ -137,6 +139,8 @@ export function buildUseSuspenseQueryHook( return { kind: StructureKind.VariableStatement, + // Copy the operation's JSDoc (description and @deprecated) from the SDK function + leadingTrivia: op.jsDoc ? `${op.jsDoc}\n` : undefined, isExported: true, declarationKind: VariableDeclarationKind.Const, declarations: [ @@ -196,6 +200,8 @@ export function buildUseInfiniteQueryHook( return { kind: StructureKind.VariableStatement, + // Copy the operation's JSDoc (description and @deprecated) from the SDK function + leadingTrivia: op.jsDoc ? `${op.jsDoc}\n` : undefined, isExported: true, declarationKind: VariableDeclarationKind.Const, declarations: [ @@ -240,6 +246,8 @@ export function buildPrefetchFn( return { kind: StructureKind.VariableStatement, + // Copy the operation's JSDoc (description and @deprecated) from the SDK function + leadingTrivia: op.jsDoc ? `${op.jsDoc}\n` : undefined, isExported: true, declarationKind: VariableDeclarationKind.Const, declarations: [ @@ -284,6 +292,8 @@ export function buildEnsureQueryDataFn( return { kind: StructureKind.VariableStatement, + // Copy the operation's JSDoc (description and @deprecated) from the SDK function + leadingTrivia: op.jsDoc ? `${op.jsDoc}\n` : undefined, isExported: true, declarationKind: VariableDeclarationKind.Const, declarations: [ diff --git a/src/tsmorph/projectFactory.mts b/src/tsmorph/projectFactory.mts index 00a26b4..6dcc0d7 100644 --- a/src/tsmorph/projectFactory.mts +++ b/src/tsmorph/projectFactory.mts @@ -28,15 +28,16 @@ export function createGenerationProject(): Project { } /** - * Build import structure for client library. - * In v0.73+, Options type is exported from the generated client file. + * Build import structure for the Options type. + * sdk.gen re-exports Options extended with `client` and `meta`, which the + * base client Options lacks; hooks must accept those properties. */ export function buildClientImport( _ctx: GenerationContext, ): ImportDeclarationStructure { return { kind: StructureKind.ImportDeclaration, - moduleSpecifier: "../requests/client", + moduleSpecifier: "../requests/sdk.gen", namedImports: [{ name: "Options", isTypeOnly: true }], }; } diff --git a/tests/__snapshots__/createSource.test.ts.snap b/tests/__snapshots__/createSource.test.ts.snap index a307c57..468b373 100644 --- a/tests/__snapshots__/createSource.test.ts.snap +++ b/tests/__snapshots__/createSource.test.ts.snap @@ -11,7 +11,7 @@ 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 "../requests/client"; +import { type Options } from "../requests/sdk.gen"; 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 { ClientOptions, 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 } from "../requests/types.gen"; @@ -62,18 +62,48 @@ 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 "../requests/client"; +import { type Options } from "../requests/sdk.gen"; 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 { ClientOptions, 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 } from "../requests/types.gen"; import { AxiosError } from "axios"; +/** + * Returns all pets from the system that the user has access to + * Nam sed condimentum est. Maecenas tempor sagittis sapien, nec rhoncus sem sagittis sit amet. Aenean at gravida augue, ac iaculis sem. Curabitur odio lorem, ornare eget elementum nec, cursus id lectus. Duis mi turpis, pulvinar ac eros ac, tincidunt varius justo. In hac habitasse platea dictumst. Integer at adipiscing ante, a sagittis ligula. Aenean pharetra tempor ante molestie imperdiet. Vivamus id aliquam diam. Cras quis velit non tortor eleifend sagittis. Praesent at enim pharetra urna volutpat venenatis eget eget mauris. In eleifend fermentum facilisis. Praesent enim enim, gravida ac sodales sed, placerat id erat. Suspendisse lacus dolor, consectetur non augue vel, vehicula interdum libero. Morbi euismod sagittis libero sed lacinia. + * + * Sed tempus felis lobortis leo pulvinar rutrum. Nam mattis velit nisl, eu condimentum ligula luctus nec. Phasellus semper velit eget aliquet faucibus. In a mattis elit. Phasellus vel urna viverra, condimentum lorem id, rhoncus nibh. Ut pellentesque posuere elementum. Sed a varius odio. Morbi rhoncus ligula libero, vel eleifend nunc tristique vitae. Fusce et sem dui. Aenean nec scelerisque tortor. Fusce malesuada accumsan magna vel tempus. Quisque mollis felis eu dolor tristique, sit amet auctor felis gravida. Sed libero lorem, molestie sed nisl in, accumsan tempor nisi. Fusce sollicitudin massa ut lacinia mattis. Sed vel eleifend lorem. Pellentesque vitae felis pretium, pulvinar elit eu, euismod sapien. + * + */ 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 }); +/** + * This path is not fully defined. + * + * @deprecated + */ 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 }); +/** + * Returns a user based on a single ID, if the user does not have access to the pet + */ 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 }); +/** + * Returns paginated pets from the system that the user has access to + * + */ 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 }); +/** + * Creates a new pet in the store. Duplicates are allowed + */ 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 }); +/** + * This path is not defined at all. + * + * @deprecated + */ 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 }); +/** + * deletes a single pet based on the ID supplied + */ 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 }); " `; @@ -82,15 +112,34 @@ 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 "../requests/client"; +import { type Options } from "../requests/sdk.gen"; 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 { ClientOptions, 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 } from "../requests/types.gen"; import { AxiosError } from "axios"; +/** + * Returns all pets from the system that the user has access to + * Nam sed condimentum est. Maecenas tempor sagittis sapien, nec rhoncus sem sagittis sit amet. Aenean at gravida augue, ac iaculis sem. Curabitur odio lorem, ornare eget elementum nec, cursus id lectus. Duis mi turpis, pulvinar ac eros ac, tincidunt varius justo. In hac habitasse platea dictumst. Integer at adipiscing ante, a sagittis ligula. Aenean pharetra tempor ante molestie imperdiet. Vivamus id aliquam diam. Cras quis velit non tortor eleifend sagittis. Praesent at enim pharetra urna volutpat venenatis eget eget mauris. In eleifend fermentum facilisis. Praesent enim enim, gravida ac sodales sed, placerat id erat. Suspendisse lacus dolor, consectetur non augue vel, vehicula interdum libero. Morbi euismod sagittis libero sed lacinia. + * + * Sed tempus felis lobortis leo pulvinar rutrum. Nam mattis velit nisl, eu condimentum ligula luctus nec. Phasellus semper velit eget aliquet faucibus. In a mattis elit. Phasellus vel urna viverra, condimentum lorem id, rhoncus nibh. Ut pellentesque posuere elementum. Sed a varius odio. Morbi rhoncus ligula libero, vel eleifend nunc tristique vitae. Fusce et sem dui. Aenean nec scelerisque tortor. Fusce malesuada accumsan magna vel tempus. Quisque mollis felis eu dolor tristique, sit amet auctor felis gravida. Sed libero lorem, molestie sed nisl in, accumsan tempor nisi. Fusce sollicitudin massa ut lacinia mattis. Sed vel eleifend lorem. Pellentesque vitae felis pretium, pulvinar elit eu, euismod sapien. + * + */ 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 }); +/** + * This path is not fully defined. + * + * @deprecated + */ 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 }); +/** + * Returns a user based on a single ID, if the user does not have access to the pet + */ 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 }); +/** + * Returns paginated pets from the system that the user has access to + * + */ 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 }); " `; @@ -99,15 +148,34 @@ 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 "../requests/client"; +import { type Options } from "../requests/sdk.gen"; 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 { ClientOptions, 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 } from "../requests/types.gen"; import { AxiosError } from "axios"; +/** + * Returns all pets from the system that the user has access to + * Nam sed condimentum est. Maecenas tempor sagittis sapien, nec rhoncus sem sagittis sit amet. Aenean at gravida augue, ac iaculis sem. Curabitur odio lorem, ornare eget elementum nec, cursus id lectus. Duis mi turpis, pulvinar ac eros ac, tincidunt varius justo. In hac habitasse platea dictumst. Integer at adipiscing ante, a sagittis ligula. Aenean pharetra tempor ante molestie imperdiet. Vivamus id aliquam diam. Cras quis velit non tortor eleifend sagittis. Praesent at enim pharetra urna volutpat venenatis eget eget mauris. In eleifend fermentum facilisis. Praesent enim enim, gravida ac sodales sed, placerat id erat. Suspendisse lacus dolor, consectetur non augue vel, vehicula interdum libero. Morbi euismod sagittis libero sed lacinia. + * + * Sed tempus felis lobortis leo pulvinar rutrum. Nam mattis velit nisl, eu condimentum ligula luctus nec. Phasellus semper velit eget aliquet faucibus. In a mattis elit. Phasellus vel urna viverra, condimentum lorem id, rhoncus nibh. Ut pellentesque posuere elementum. Sed a varius odio. Morbi rhoncus ligula libero, vel eleifend nunc tristique vitae. Fusce et sem dui. Aenean nec scelerisque tortor. Fusce malesuada accumsan magna vel tempus. Quisque mollis felis eu dolor tristique, sit amet auctor felis gravida. Sed libero lorem, molestie sed nisl in, accumsan tempor nisi. Fusce sollicitudin massa ut lacinia mattis. Sed vel eleifend lorem. Pellentesque vitae felis pretium, pulvinar elit eu, euismod sapien. + * + */ export const prefetchUseFindPets = (queryClient: QueryClient, clientOptions: Options = {}) => queryClient.prefetchQuery({ queryKey: Common.UseFindPetsKeyFn(clientOptions), queryFn: () => findPets({ ...clientOptions }).then(response => response.data) }); +/** + * This path is not fully defined. + * + * @deprecated + */ export const prefetchUseGetNotDefined = (queryClient: QueryClient, clientOptions: Options = {}) => queryClient.prefetchQuery({ queryKey: Common.UseGetNotDefinedKeyFn(clientOptions), queryFn: () => getNotDefined({ ...clientOptions }).then(response => response.data) }); +/** + * Returns a user based on a single ID, if the user does not have access to the pet + */ export const prefetchUseFindPetById = (queryClient: QueryClient, clientOptions: Options) => queryClient.prefetchQuery({ queryKey: Common.UseFindPetByIdKeyFn(clientOptions), queryFn: () => findPetById({ ...clientOptions }).then(response => response.data) }); +/** + * Returns paginated pets from the system that the user has access to + * + */ export const prefetchUseFindPaginatedPets = (queryClient: QueryClient, clientOptions: Options = {}) => queryClient.prefetchQuery({ queryKey: Common.UseFindPaginatedPetsKeyFn(clientOptions), queryFn: () => findPaginatedPets({ ...clientOptions }).then(response => response.data) }); " `; @@ -123,7 +191,7 @@ 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 "../requests/client"; +import { type Options } from "../requests/sdk.gen"; 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 { ClientOptions, 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 } from "../requests/types.gen"; @@ -173,17 +241,47 @@ 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 "../requests/client"; +import { type Options } from "../requests/sdk.gen"; 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 { ClientOptions, 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 } from "../requests/types.gen"; +/** + * Returns all pets from the system that the user has access to + * Nam sed condimentum est. Maecenas tempor sagittis sapien, nec rhoncus sem sagittis sit amet. Aenean at gravida augue, ac iaculis sem. Curabitur odio lorem, ornare eget elementum nec, cursus id lectus. Duis mi turpis, pulvinar ac eros ac, tincidunt varius justo. In hac habitasse platea dictumst. Integer at adipiscing ante, a sagittis ligula. Aenean pharetra tempor ante molestie imperdiet. Vivamus id aliquam diam. Cras quis velit non tortor eleifend sagittis. Praesent at enim pharetra urna volutpat venenatis eget eget mauris. In eleifend fermentum facilisis. Praesent enim enim, gravida ac sodales sed, placerat id erat. Suspendisse lacus dolor, consectetur non augue vel, vehicula interdum libero. Morbi euismod sagittis libero sed lacinia. + * + * Sed tempus felis lobortis leo pulvinar rutrum. Nam mattis velit nisl, eu condimentum ligula luctus nec. Phasellus semper velit eget aliquet faucibus. In a mattis elit. Phasellus vel urna viverra, condimentum lorem id, rhoncus nibh. Ut pellentesque posuere elementum. Sed a varius odio. Morbi rhoncus ligula libero, vel eleifend nunc tristique vitae. Fusce et sem dui. Aenean nec scelerisque tortor. Fusce malesuada accumsan magna vel tempus. Quisque mollis felis eu dolor tristique, sit amet auctor felis gravida. Sed libero lorem, molestie sed nisl in, accumsan tempor nisi. Fusce sollicitudin massa ut lacinia mattis. Sed vel eleifend lorem. Pellentesque vitae felis pretium, pulvinar elit eu, euismod sapien. + * + */ 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 }); +/** + * This path is not fully defined. + * + * @deprecated + */ 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 }); +/** + * Returns a user based on a single ID, if the user does not have access to the pet + */ 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 }); +/** + * Returns paginated pets from the system that the user has access to + * + */ 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 }); +/** + * Creates a new pet in the store. Duplicates are allowed + */ 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 }); +/** + * This path is not defined at all. + * + * @deprecated + */ 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 }); +/** + * deletes a single pet based on the ID supplied + */ 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 }); " `; @@ -192,14 +290,33 @@ 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 "../requests/client"; +import { type Options } from "../requests/sdk.gen"; 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 { ClientOptions, 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 } from "../requests/types.gen"; +/** + * Returns all pets from the system that the user has access to + * Nam sed condimentum est. Maecenas tempor sagittis sapien, nec rhoncus sem sagittis sit amet. Aenean at gravida augue, ac iaculis sem. Curabitur odio lorem, ornare eget elementum nec, cursus id lectus. Duis mi turpis, pulvinar ac eros ac, tincidunt varius justo. In hac habitasse platea dictumst. Integer at adipiscing ante, a sagittis ligula. Aenean pharetra tempor ante molestie imperdiet. Vivamus id aliquam diam. Cras quis velit non tortor eleifend sagittis. Praesent at enim pharetra urna volutpat venenatis eget eget mauris. In eleifend fermentum facilisis. Praesent enim enim, gravida ac sodales sed, placerat id erat. Suspendisse lacus dolor, consectetur non augue vel, vehicula interdum libero. Morbi euismod sagittis libero sed lacinia. + * + * Sed tempus felis lobortis leo pulvinar rutrum. Nam mattis velit nisl, eu condimentum ligula luctus nec. Phasellus semper velit eget aliquet faucibus. In a mattis elit. Phasellus vel urna viverra, condimentum lorem id, rhoncus nibh. Ut pellentesque posuere elementum. Sed a varius odio. Morbi rhoncus ligula libero, vel eleifend nunc tristique vitae. Fusce et sem dui. Aenean nec scelerisque tortor. Fusce malesuada accumsan magna vel tempus. Quisque mollis felis eu dolor tristique, sit amet auctor felis gravida. Sed libero lorem, molestie sed nisl in, accumsan tempor nisi. Fusce sollicitudin massa ut lacinia mattis. Sed vel eleifend lorem. Pellentesque vitae felis pretium, pulvinar elit eu, euismod sapien. + * + */ export const useFindPetsSuspense = , TError = FindPetsError, 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 }); +/** + * This path is not fully defined. + * + * @deprecated + */ export const useGetNotDefinedSuspense = , TError = unknown, 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 }); +/** + * Returns a user based on a single ID, if the user does not have access to the pet + */ export const useFindPetByIdSuspense = , TError = FindPetByIdError, 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 }); +/** + * Returns paginated pets from the system that the user has access to + * + */ export const useFindPaginatedPetsSuspense = , TError = unknown, 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 }); " `; @@ -208,14 +325,33 @@ exports[`createSource > createSource - @hey-api/client-fetch 5`] = ` "// generated with @7nohe/openapi-react-query-codegen@1.0.0 import * as Common from "./common"; -import { type Options } from "../requests/client"; +import { type Options } from "../requests/sdk.gen"; 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 { ClientOptions, 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 } from "../requests/types.gen"; +/** + * Returns all pets from the system that the user has access to + * Nam sed condimentum est. Maecenas tempor sagittis sapien, nec rhoncus sem sagittis sit amet. Aenean at gravida augue, ac iaculis sem. Curabitur odio lorem, ornare eget elementum nec, cursus id lectus. Duis mi turpis, pulvinar ac eros ac, tincidunt varius justo. In hac habitasse platea dictumst. Integer at adipiscing ante, a sagittis ligula. Aenean pharetra tempor ante molestie imperdiet. Vivamus id aliquam diam. Cras quis velit non tortor eleifend sagittis. Praesent at enim pharetra urna volutpat venenatis eget eget mauris. In eleifend fermentum facilisis. Praesent enim enim, gravida ac sodales sed, placerat id erat. Suspendisse lacus dolor, consectetur non augue vel, vehicula interdum libero. Morbi euismod sagittis libero sed lacinia. + * + * Sed tempus felis lobortis leo pulvinar rutrum. Nam mattis velit nisl, eu condimentum ligula luctus nec. Phasellus semper velit eget aliquet faucibus. In a mattis elit. Phasellus vel urna viverra, condimentum lorem id, rhoncus nibh. Ut pellentesque posuere elementum. Sed a varius odio. Morbi rhoncus ligula libero, vel eleifend nunc tristique vitae. Fusce et sem dui. Aenean nec scelerisque tortor. Fusce malesuada accumsan magna vel tempus. Quisque mollis felis eu dolor tristique, sit amet auctor felis gravida. Sed libero lorem, molestie sed nisl in, accumsan tempor nisi. Fusce sollicitudin massa ut lacinia mattis. Sed vel eleifend lorem. Pellentesque vitae felis pretium, pulvinar elit eu, euismod sapien. + * + */ export const prefetchUseFindPets = (queryClient: QueryClient, clientOptions: Options = {}) => queryClient.prefetchQuery({ queryKey: Common.UseFindPetsKeyFn(clientOptions), queryFn: () => findPets({ ...clientOptions }).then(response => response.data) }); +/** + * This path is not fully defined. + * + * @deprecated + */ export const prefetchUseGetNotDefined = (queryClient: QueryClient, clientOptions: Options = {}) => queryClient.prefetchQuery({ queryKey: Common.UseGetNotDefinedKeyFn(clientOptions), queryFn: () => getNotDefined({ ...clientOptions }).then(response => response.data) }); +/** + * Returns a user based on a single ID, if the user does not have access to the pet + */ export const prefetchUseFindPetById = (queryClient: QueryClient, clientOptions: Options) => queryClient.prefetchQuery({ queryKey: Common.UseFindPetByIdKeyFn(clientOptions), queryFn: () => findPetById({ ...clientOptions }).then(response => response.data) }); +/** + * Returns paginated pets from the system that the user has access to + * + */ export const prefetchUseFindPaginatedPets = (queryClient: QueryClient, clientOptions: Options = {}) => queryClient.prefetchQuery({ queryKey: Common.UseFindPaginatedPetsKeyFn(clientOptions), queryFn: () => findPaginatedPets({ ...clientOptions }).then(response => response.data) }); " `; diff --git a/tests/__snapshots__/generate.test.ts.snap b/tests/__snapshots__/generate.test.ts.snap index 5990510..ccdd64a 100644 --- a/tests/__snapshots__/generate.test.ts.snap +++ b/tests/__snapshots__/generate.test.ts.snap @@ -4,7 +4,6 @@ exports[`generate > common.ts 1`] = ` "// generated with @7nohe/openapi-react-query-codegen@1.0.0 import type { UseQueryResult } from "@tanstack/react-query"; -import type { Options } from "../requests/client"; import type { addPet, deletePet, @@ -12,6 +11,7 @@ import type { findPetById, findPets, getNotDefined, + Options, postNotDefined, } from "../requests/sdk.gen"; import type { @@ -109,12 +109,12 @@ exports[`generate > ensureQueryData.ts 1`] = ` "// generated with @7nohe/openapi-react-query-codegen@1.0.0 import type { QueryClient } from "@tanstack/react-query"; -import type { Options } from "../requests/client"; import { findPaginatedPets, findPetById, findPets, getNotDefined, + type Options, } from "../requests/sdk.gen"; import type { FindPaginatedPetsData, @@ -124,6 +124,13 @@ import type { } from "../requests/types.gen"; import * as Common from "./common"; +/** + * Returns all pets from the system that the user has access to + * Nam sed condimentum est. Maecenas tempor sagittis sapien, nec rhoncus sem sagittis sit amet. Aenean at gravida augue, ac iaculis sem. Curabitur odio lorem, ornare eget elementum nec, cursus id lectus. Duis mi turpis, pulvinar ac eros ac, tincidunt varius justo. In hac habitasse platea dictumst. Integer at adipiscing ante, a sagittis ligula. Aenean pharetra tempor ante molestie imperdiet. Vivamus id aliquam diam. Cras quis velit non tortor eleifend sagittis. Praesent at enim pharetra urna volutpat venenatis eget eget mauris. In eleifend fermentum facilisis. Praesent enim enim, gravida ac sodales sed, placerat id erat. Suspendisse lacus dolor, consectetur non augue vel, vehicula interdum libero. Morbi euismod sagittis libero sed lacinia. + * + * Sed tempus felis lobortis leo pulvinar rutrum. Nam mattis velit nisl, eu condimentum ligula luctus nec. Phasellus semper velit eget aliquet faucibus. In a mattis elit. Phasellus vel urna viverra, condimentum lorem id, rhoncus nibh. Ut pellentesque posuere elementum. Sed a varius odio. Morbi rhoncus ligula libero, vel eleifend nunc tristique vitae. Fusce et sem dui. Aenean nec scelerisque tortor. Fusce malesuada accumsan magna vel tempus. Quisque mollis felis eu dolor tristique, sit amet auctor felis gravida. Sed libero lorem, molestie sed nisl in, accumsan tempor nisi. Fusce sollicitudin massa ut lacinia mattis. Sed vel eleifend lorem. Pellentesque vitae felis pretium, pulvinar elit eu, euismod sapien. + * + */ export const ensureUseFindPetsData = ( queryClient: QueryClient, clientOptions: Options = {}, @@ -133,6 +140,11 @@ export const ensureUseFindPetsData = ( queryFn: () => findPets({ ...clientOptions }).then((response) => response.data), }); +/** + * This path is not fully defined. + * + * @deprecated + */ export const ensureUseGetNotDefinedData = ( queryClient: QueryClient, clientOptions: Options = {}, @@ -142,6 +154,9 @@ export const ensureUseGetNotDefinedData = ( queryFn: () => getNotDefined({ ...clientOptions }).then((response) => response.data), }); +/** + * Returns a user based on a single ID, if the user does not have access to the pet + */ export const ensureUseFindPetByIdData = ( queryClient: QueryClient, clientOptions: Options, @@ -151,6 +166,10 @@ export const ensureUseFindPetByIdData = ( queryFn: () => findPetById({ ...clientOptions }).then((response) => response.data), }); +/** + * Returns paginated pets from the system that the user has access to + * + */ export const ensureUseFindPaginatedPetsData = ( queryClient: QueryClient, clientOptions: Options = {}, @@ -179,11 +198,14 @@ import { useInfiniteQuery, type UseInfiniteQueryOptions, } from "@tanstack/react-query"; -import type { Options } from "../requests/client"; -import { findPaginatedPets } from "../requests/sdk.gen"; +import { findPaginatedPets, type Options } from "../requests/sdk.gen"; import type { FindPaginatedPetsData } from "../requests/types.gen"; import * as Common from "./common"; +/** + * Returns paginated pets from the system that the user has access to + * + */ export const useFindPaginatedPetsInfinite = < TData = InfiniteData, TError = unknown, @@ -215,12 +237,12 @@ exports[`generate > prefetch.ts 1`] = ` "// generated with @7nohe/openapi-react-query-codegen@1.0.0 import type { QueryClient } from "@tanstack/react-query"; -import type { Options } from "../requests/client"; import { findPaginatedPets, findPetById, findPets, getNotDefined, + type Options, } from "../requests/sdk.gen"; import type { FindPaginatedPetsData, @@ -230,6 +252,13 @@ import type { } from "../requests/types.gen"; import * as Common from "./common"; +/** + * Returns all pets from the system that the user has access to + * Nam sed condimentum est. Maecenas tempor sagittis sapien, nec rhoncus sem sagittis sit amet. Aenean at gravida augue, ac iaculis sem. Curabitur odio lorem, ornare eget elementum nec, cursus id lectus. Duis mi turpis, pulvinar ac eros ac, tincidunt varius justo. In hac habitasse platea dictumst. Integer at adipiscing ante, a sagittis ligula. Aenean pharetra tempor ante molestie imperdiet. Vivamus id aliquam diam. Cras quis velit non tortor eleifend sagittis. Praesent at enim pharetra urna volutpat venenatis eget eget mauris. In eleifend fermentum facilisis. Praesent enim enim, gravida ac sodales sed, placerat id erat. Suspendisse lacus dolor, consectetur non augue vel, vehicula interdum libero. Morbi euismod sagittis libero sed lacinia. + * + * Sed tempus felis lobortis leo pulvinar rutrum. Nam mattis velit nisl, eu condimentum ligula luctus nec. Phasellus semper velit eget aliquet faucibus. In a mattis elit. Phasellus vel urna viverra, condimentum lorem id, rhoncus nibh. Ut pellentesque posuere elementum. Sed a varius odio. Morbi rhoncus ligula libero, vel eleifend nunc tristique vitae. Fusce et sem dui. Aenean nec scelerisque tortor. Fusce malesuada accumsan magna vel tempus. Quisque mollis felis eu dolor tristique, sit amet auctor felis gravida. Sed libero lorem, molestie sed nisl in, accumsan tempor nisi. Fusce sollicitudin massa ut lacinia mattis. Sed vel eleifend lorem. Pellentesque vitae felis pretium, pulvinar elit eu, euismod sapien. + * + */ export const prefetchUseFindPets = ( queryClient: QueryClient, clientOptions: Options = {}, @@ -239,6 +268,11 @@ export const prefetchUseFindPets = ( queryFn: () => findPets({ ...clientOptions }).then((response) => response.data), }); +/** + * This path is not fully defined. + * + * @deprecated + */ export const prefetchUseGetNotDefined = ( queryClient: QueryClient, clientOptions: Options = {}, @@ -248,6 +282,9 @@ export const prefetchUseGetNotDefined = ( queryFn: () => getNotDefined({ ...clientOptions }).then((response) => response.data), }); +/** + * Returns a user based on a single ID, if the user does not have access to the pet + */ export const prefetchUseFindPetById = ( queryClient: QueryClient, clientOptions: Options, @@ -257,6 +294,10 @@ export const prefetchUseFindPetById = ( queryFn: () => findPetById({ ...clientOptions }).then((response) => response.data), }); +/** + * Returns paginated pets from the system that the user has access to + * + */ export const prefetchUseFindPaginatedPets = ( queryClient: QueryClient, clientOptions: Options = {}, @@ -278,7 +319,6 @@ import { useQuery, type UseQueryOptions, } from "@tanstack/react-query"; -import type { Options } from "../requests/client"; import { addPet, deletePet, @@ -286,6 +326,7 @@ import { findPetById, findPets, getNotDefined, + type Options, postNotDefined, } from "../requests/sdk.gen"; import type { @@ -303,6 +344,13 @@ import type { } from "../requests/types.gen"; import * as Common from "./common"; +/** + * Returns all pets from the system that the user has access to + * Nam sed condimentum est. Maecenas tempor sagittis sapien, nec rhoncus sem sagittis sit amet. Aenean at gravida augue, ac iaculis sem. Curabitur odio lorem, ornare eget elementum nec, cursus id lectus. Duis mi turpis, pulvinar ac eros ac, tincidunt varius justo. In hac habitasse platea dictumst. Integer at adipiscing ante, a sagittis ligula. Aenean pharetra tempor ante molestie imperdiet. Vivamus id aliquam diam. Cras quis velit non tortor eleifend sagittis. Praesent at enim pharetra urna volutpat venenatis eget eget mauris. In eleifend fermentum facilisis. Praesent enim enim, gravida ac sodales sed, placerat id erat. Suspendisse lacus dolor, consectetur non augue vel, vehicula interdum libero. Morbi euismod sagittis libero sed lacinia. + * + * Sed tempus felis lobortis leo pulvinar rutrum. Nam mattis velit nisl, eu condimentum ligula luctus nec. Phasellus semper velit eget aliquet faucibus. In a mattis elit. Phasellus vel urna viverra, condimentum lorem id, rhoncus nibh. Ut pellentesque posuere elementum. Sed a varius odio. Morbi rhoncus ligula libero, vel eleifend nunc tristique vitae. Fusce et sem dui. Aenean nec scelerisque tortor. Fusce malesuada accumsan magna vel tempus. Quisque mollis felis eu dolor tristique, sit amet auctor felis gravida. Sed libero lorem, molestie sed nisl in, accumsan tempor nisi. Fusce sollicitudin massa ut lacinia mattis. Sed vel eleifend lorem. Pellentesque vitae felis pretium, pulvinar elit eu, euismod sapien. + * + */ export const useFindPets = < TData = Common.FindPetsDefaultResponse, TError = FindPetsError, @@ -320,6 +368,11 @@ export const useFindPets = < ) as TData, ...options, }); +/** + * This path is not fully defined. + * + * @deprecated + */ export const useGetNotDefined = < TData = Common.GetNotDefinedDefaultResponse, TError = unknown, @@ -337,6 +390,9 @@ export const useGetNotDefined = < ) as TData, ...options, }); +/** + * Returns a user based on a single ID, if the user does not have access to the pet + */ export const useFindPetById = < TData = Common.FindPetByIdDefaultResponse, TError = FindPetByIdError, @@ -354,6 +410,10 @@ export const useFindPetById = < ) as TData, ...options, }); +/** + * Returns paginated pets from the system that the user has access to + * + */ export const useFindPaginatedPets = < TData = Common.FindPaginatedPetsDefaultResponse, TError = unknown, @@ -371,6 +431,9 @@ export const useFindPaginatedPets = < ) as TData, ...options, }); +/** + * Creates a new pet in the store. Duplicates are allowed + */ export const useAddPet = < TData = Common.AddPetMutationResult, TError = AddPetError, @@ -389,6 +452,11 @@ export const useAddPet = < addPet(clientOptions) as unknown as Promise, ...options, }); +/** + * This path is not defined at all. + * + * @deprecated + */ export const usePostNotDefined = < TData = Common.PostNotDefinedMutationResult, TError = unknown, @@ -412,6 +480,9 @@ export const usePostNotDefined = < postNotDefined(clientOptions) as unknown as Promise, ...options, }); +/** + * deletes a single pet based on the ID supplied + */ export const useDeletePet = < TData = Common.DeletePetMutationResult, TError = DeletePetError, @@ -440,12 +511,12 @@ import { useSuspenseQuery, type UseSuspenseQueryOptions, } from "@tanstack/react-query"; -import type { Options } from "../requests/client"; import { findPaginatedPets, findPetById, findPets, getNotDefined, + type Options, } from "../requests/sdk.gen"; import type { FindPaginatedPetsData, @@ -457,6 +528,13 @@ import type { } from "../requests/types.gen"; import * as Common from "./common"; +/** + * Returns all pets from the system that the user has access to + * Nam sed condimentum est. Maecenas tempor sagittis sapien, nec rhoncus sem sagittis sit amet. Aenean at gravida augue, ac iaculis sem. Curabitur odio lorem, ornare eget elementum nec, cursus id lectus. Duis mi turpis, pulvinar ac eros ac, tincidunt varius justo. In hac habitasse platea dictumst. Integer at adipiscing ante, a sagittis ligula. Aenean pharetra tempor ante molestie imperdiet. Vivamus id aliquam diam. Cras quis velit non tortor eleifend sagittis. Praesent at enim pharetra urna volutpat venenatis eget eget mauris. In eleifend fermentum facilisis. Praesent enim enim, gravida ac sodales sed, placerat id erat. Suspendisse lacus dolor, consectetur non augue vel, vehicula interdum libero. Morbi euismod sagittis libero sed lacinia. + * + * Sed tempus felis lobortis leo pulvinar rutrum. Nam mattis velit nisl, eu condimentum ligula luctus nec. Phasellus semper velit eget aliquet faucibus. In a mattis elit. Phasellus vel urna viverra, condimentum lorem id, rhoncus nibh. Ut pellentesque posuere elementum. Sed a varius odio. Morbi rhoncus ligula libero, vel eleifend nunc tristique vitae. Fusce et sem dui. Aenean nec scelerisque tortor. Fusce malesuada accumsan magna vel tempus. Quisque mollis felis eu dolor tristique, sit amet auctor felis gravida. Sed libero lorem, molestie sed nisl in, accumsan tempor nisi. Fusce sollicitudin massa ut lacinia mattis. Sed vel eleifend lorem. Pellentesque vitae felis pretium, pulvinar elit eu, euismod sapien. + * + */ export const useFindPetsSuspense = < TData = NonNullable, TError = FindPetsError, @@ -477,6 +555,11 @@ export const useFindPetsSuspense = < ) as TData, ...options, }); +/** + * This path is not fully defined. + * + * @deprecated + */ export const useGetNotDefinedSuspense = < TData = NonNullable, TError = unknown, @@ -497,6 +580,9 @@ export const useGetNotDefinedSuspense = < ) as TData, ...options, }); +/** + * Returns a user based on a single ID, if the user does not have access to the pet + */ export const useFindPetByIdSuspense = < TData = NonNullable, TError = FindPetByIdError, @@ -517,6 +603,10 @@ export const useFindPetByIdSuspense = < ) as TData, ...options, }); +/** + * Returns paginated pets from the system that the user has access to + * + */ export const useFindPaginatedPetsSuspense = < TData = NonNullable, TError = unknown, @@ -545,17 +635,46 @@ exports[`generate - axios client with enums, noOperationId, schemaType > queries import { useMutation, UseMutationOptions, useQuery, UseQueryOptions } from "@tanstack/react-query"; import { AxiosError } from "axios"; -import { type Options } from "../requests/client"; -import { deletePetsById, getNotDefined, getPaginatedPets, getPets, getPetsById, postNotDefined, postPets } from "../requests/sdk.gen"; +import { deletePetsById, getNotDefined, getPaginatedPets, getPets, getPetsById, type Options, postNotDefined, postPets } from "../requests/sdk.gen"; import { DeletePetsByIdData, DeletePetsByIdError, GetNotDefinedData, GetPaginatedPetsData, GetPetsByIdData, GetPetsByIdError, GetPetsData, GetPetsError, PostNotDefinedData, PostPetsData, PostPetsError } from "../requests/types.gen"; import * as Common from "./common"; +/** + * Returns all pets from the system that the user has access to + * Nam sed condimentum est. Maecenas tempor sagittis sapien, nec rhoncus sem sagittis sit amet. Aenean at gravida augue, ac iaculis sem. Curabitur odio lorem, ornare eget elementum nec, cursus id lectus. Duis mi turpis, pulvinar ac eros ac, tincidunt varius justo. In hac habitasse platea dictumst. Integer at adipiscing ante, a sagittis ligula. Aenean pharetra tempor ante molestie imperdiet. Vivamus id aliquam diam. Cras quis velit non tortor eleifend sagittis. Praesent at enim pharetra urna volutpat venenatis eget eget mauris. In eleifend fermentum facilisis. Praesent enim enim, gravida ac sodales sed, placerat id erat. Suspendisse lacus dolor, consectetur non augue vel, vehicula interdum libero. Morbi euismod sagittis libero sed lacinia. + * + * Sed tempus felis lobortis leo pulvinar rutrum. Nam mattis velit nisl, eu condimentum ligula luctus nec. Phasellus semper velit eget aliquet faucibus. In a mattis elit. Phasellus vel urna viverra, condimentum lorem id, rhoncus nibh. Ut pellentesque posuere elementum. Sed a varius odio. Morbi rhoncus ligula libero, vel eleifend nunc tristique vitae. Fusce et sem dui. Aenean nec scelerisque tortor. Fusce malesuada accumsan magna vel tempus. Quisque mollis felis eu dolor tristique, sit amet auctor felis gravida. Sed libero lorem, molestie sed nisl in, accumsan tempor nisi. Fusce sollicitudin massa ut lacinia mattis. Sed vel eleifend lorem. Pellentesque vitae felis pretium, pulvinar elit eu, euismod sapien. + * + */ export const useGetPets = , TQueryKey extends Array = unknown[]>(clientOptions: Options = {}, queryKey?: TQueryKey, options?: Omit, "queryKey" | "queryFn">) => useQuery({ queryKey: Common.UseGetPetsKeyFn(clientOptions, queryKey), queryFn: () => getPets({ ...clientOptions }).then(response => response.data as TData) as TData, ...options }); +/** + * This path is not fully defined. + * + * @deprecated + */ 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 }); +/** + * Returns a user based on a single ID, if the user does not have access to the pet + */ export const useGetPetsById = , TQueryKey extends Array = unknown[]>(clientOptions: Options, queryKey?: TQueryKey, options?: Omit, "queryKey" | "queryFn">) => useQuery({ queryKey: Common.UseGetPetsByIdKeyFn(clientOptions, queryKey), queryFn: () => getPetsById({ ...clientOptions }).then(response => response.data as TData) as TData, ...options }); +/** + * Returns paginated pets from the system that the user has access to + * + */ export const useGetPaginatedPets = , TQueryKey extends Array = unknown[]>(clientOptions: Options = {}, queryKey?: TQueryKey, options?: Omit, "queryKey" | "queryFn">) => useQuery({ queryKey: Common.UseGetPaginatedPetsKeyFn(clientOptions, queryKey), queryFn: () => getPaginatedPets({ ...clientOptions }).then(response => response.data as TData) as TData, ...options }); +/** + * Creates a new pet in the store. Duplicates are allowed + */ export const usePostPets = , TQueryKey extends Array = unknown[], TContext = unknown>(mutationKey?: TQueryKey, options?: Omit, TContext>, "mutationKey" | "mutationFn">) => useMutation, TContext>({ mutationKey: Common.UsePostPetsKeyFn(mutationKey), mutationFn: clientOptions => postPets(clientOptions) as unknown as Promise, ...options }); +/** + * This path is not defined at all. + * + * @deprecated + */ 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 }); +/** + * deletes a single pet based on the ID supplied + */ export const useDeletePetsById = , TQueryKey extends Array = unknown[], TContext = unknown>(mutationKey?: TQueryKey, options?: Omit, TContext>, "mutationKey" | "mutationFn">) => useMutation, TContext>({ mutationKey: Common.UseDeletePetsByIdKeyFn(mutationKey), mutationFn: clientOptions => deletePetsById(clientOptions) as unknown as Promise, ...options }); " `; @@ -564,17 +683,46 @@ exports[`generate - noSchemas option > queries.ts 1`] = ` "// generated with @7nohe/openapi-react-query-codegen@1.0.0 import { useMutation, UseMutationOptions, useQuery, UseQueryOptions } from "@tanstack/react-query"; -import { type Options } from "../requests/client"; -import { addPet, deletePet, findPaginatedPets, findPetById, findPets, getNotDefined, postNotDefined } from "../requests/sdk.gen"; +import { addPet, deletePet, findPaginatedPets, findPetById, findPets, getNotDefined, type Options, postNotDefined } from "../requests/sdk.gen"; import { AddPetData, AddPetError, DeletePetData, DeletePetError, FindPaginatedPetsData, FindPetByIdData, FindPetByIdError, FindPetsData, FindPetsError, GetNotDefinedData, PostNotDefinedData } from "../requests/types.gen"; import * as Common from "./common"; +/** + * Returns all pets from the system that the user has access to + * Nam sed condimentum est. Maecenas tempor sagittis sapien, nec rhoncus sem sagittis sit amet. Aenean at gravida augue, ac iaculis sem. Curabitur odio lorem, ornare eget elementum nec, cursus id lectus. Duis mi turpis, pulvinar ac eros ac, tincidunt varius justo. In hac habitasse platea dictumst. Integer at adipiscing ante, a sagittis ligula. Aenean pharetra tempor ante molestie imperdiet. Vivamus id aliquam diam. Cras quis velit non tortor eleifend sagittis. Praesent at enim pharetra urna volutpat venenatis eget eget mauris. In eleifend fermentum facilisis. Praesent enim enim, gravida ac sodales sed, placerat id erat. Suspendisse lacus dolor, consectetur non augue vel, vehicula interdum libero. Morbi euismod sagittis libero sed lacinia. + * + * Sed tempus felis lobortis leo pulvinar rutrum. Nam mattis velit nisl, eu condimentum ligula luctus nec. Phasellus semper velit eget aliquet faucibus. In a mattis elit. Phasellus vel urna viverra, condimentum lorem id, rhoncus nibh. Ut pellentesque posuere elementum. Sed a varius odio. Morbi rhoncus ligula libero, vel eleifend nunc tristique vitae. Fusce et sem dui. Aenean nec scelerisque tortor. Fusce malesuada accumsan magna vel tempus. Quisque mollis felis eu dolor tristique, sit amet auctor felis gravida. Sed libero lorem, molestie sed nisl in, accumsan tempor nisi. Fusce sollicitudin massa ut lacinia mattis. Sed vel eleifend lorem. Pellentesque vitae felis pretium, pulvinar elit eu, euismod sapien. + * + */ 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 }); +/** + * This path is not fully defined. + * + * @deprecated + */ 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 }); +/** + * Returns a user based on a single ID, if the user does not have access to the pet + */ 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 }); +/** + * Returns paginated pets from the system that the user has access to + * + */ 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 }); +/** + * Creates a new pet in the store. Duplicates are allowed + */ 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 }); +/** + * This path is not defined at all. + * + * @deprecated + */ 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 }); +/** + * deletes a single pet based on the ID supplied + */ 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 }); " `; diff --git a/tests/tsmorph/projectFactory.test.ts b/tests/tsmorph/projectFactory.test.ts index 257df6d..afd3bcd 100644 --- a/tests/tsmorph/projectFactory.test.ts +++ b/tests/tsmorph/projectFactory.test.ts @@ -67,8 +67,8 @@ describe("projectFactory", () => { const result = buildClientImport(mockFetchContext); expect(result.kind).toBe(StructureKind.ImportDeclaration); - // In v0.73+, Options is imported from the generated client file - expect(result.moduleSpecifier).toBe("../requests/client"); + // Options comes from sdk.gen (extended with client/meta properties) + expect(result.moduleSpecifier).toBe("../requests/sdk.gen"); expect(result.namedImports).toEqual([ { name: "Options", isTypeOnly: true }, ]); @@ -78,7 +78,7 @@ describe("projectFactory", () => { const result = buildClientImport(mockAxiosContext); // In v0.73+, client type doesn't affect the import path - expect(result.moduleSpecifier).toBe("../requests/client"); + expect(result.moduleSpecifier).toBe("../requests/sdk.gen"); }); }); @@ -164,9 +164,9 @@ describe("projectFactory", () => { const result = buildCommonFileImports(mockFetchContext); expect(result.length).toBeGreaterThanOrEqual(3); - // In v0.73+, Options is imported from ../requests/client + // Options is imported from ../requests/sdk.gen expect( - result.some((i) => i.moduleSpecifier === "../requests/client"), + result.some((i) => i.moduleSpecifier === "../requests/sdk.gen"), ).toBe(true); expect( result.some((i) => i.moduleSpecifier === "@tanstack/react-query"), @@ -184,9 +184,9 @@ describe("projectFactory", () => { it("should build imports for common file with axios client", () => { const result = buildCommonFileImports(mockAxiosContext); - // In v0.73+, Options is imported from ../requests/client regardless of axios + // Options is imported from ../requests/sdk.gen regardless of axios expect( - result.some((i) => i.moduleSpecifier === "../requests/client"), + result.some((i) => i.moduleSpecifier === "../requests/sdk.gen"), ).toBe(true); expect(result.some((i) => i.moduleSpecifier === "axios")).toBe(true); }); @@ -207,9 +207,9 @@ describe("projectFactory", () => { expect(result.length).toBeGreaterThanOrEqual(4); expect(result[0].moduleSpecifier).toBe("./common"); expect(result[0].namespaceImport).toBe("Common"); - // In v0.73+, Options is imported from ../requests/client + // Options is imported from ../requests/sdk.gen expect( - result.some((i) => i.moduleSpecifier === "../requests/client"), + result.some((i) => i.moduleSpecifier === "../requests/sdk.gen"), ).toBe(true); expect( result.some((i) => i.moduleSpecifier === "@tanstack/react-query"), From bcf2c1a7c778bf2e6c29a361e3e09011dffd8522 Mon Sep 17 00:00:00 2001 From: Urata Daiki <7nohe@users.noreply.github.com> Date: Fri, 10 Jul 2026 23:42:44 +0900 Subject: [PATCH 07/31] chore: release v3.0.0-beta.1 Prerelease tags publish to the beta dist-tag so latest keeps pointing at the current stable release. Co-Authored-By: Claude Fable 5 --- .github/workflows/release.yml | 9 ++++++++- package.json | 2 +- 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 27a58ab..df5a1f9 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -61,7 +61,14 @@ jobs: - name: Publish release if: ${{ github.event_name == 'push' }} - run: pnpm publish --no-git-checks + # Prerelease versions (e.g. 3.0.0-beta.1) go to the beta dist-tag so + # they never override latest + run: | + if node -e "process.exit(require('./package.json').version.includes('-') ? 0 : 1)"; then + pnpm publish --no-git-checks --tag beta + else + pnpm publish --no-git-checks + fi - name: Publish prerelease if: ${{ github.event_name == 'issue_comment' }} diff --git a/package.json b/package.json index d88065f..21d5360 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@7nohe/openapi-react-query-codegen", - "version": "2.2.0", + "version": "3.0.0-beta.1", "description": "OpenAPI React Query Codegen", "bin": { "openapi-rq": "dist/cli.mjs" From 8486a9feed2f7c1618c8940e84dcd94c462b809f Mon Sep 17 00:00:00 2001 From: Urata Daiki <7nohe@users.noreply.github.com> Date: Sat, 11 Jul 2026 07:16:10 +0900 Subject: [PATCH 08/31] feat: generate queryOptions/infiniteQueryOptions factories and dedicated infinite query keys queryOptions.ts (#173): - Every GET operation gets a queryOptions() factory (e.g. findPetsOptions) usable with useQuery, useQueries, prefetchQuery, ensureQueryData, setQueryData and other TanStack Query utilities with full type safety - Paginatable operations get an infiniteQueryOptions() factory with initialPageParam/getNextPageParam wired to the CLI options; numeric initialPageParam is emitted as a number literal so the inferred pageParam type matches getNextPageParam Dedicated infinite query keys (#140): - Infinite hooks and factories now use useInfiniteKey ("Infinite") and UseInfiniteKeyFn, so cached InfiniteData no longer collides with the plain query cache for the same operation - The infinite client options type excludes the page parameter, which is supplied by TanStack Query's pageParam mechanism; specs with a required page parameter now compile BREAKING CHANGE: useInfiniteQuery hooks changed their cache key from UseKeyFn to UseInfiniteKeyFn and no longer accept the page parameter in clientOptions. Verified: 165 unit tests pass; all 5 example apps regenerate and typecheck (test:generated). Co-Authored-By: Claude Fable 5 --- docs/src/content/docs/guides/usage.mdx | 36 ++++ src/constants.mts | 1 + src/tsmorph/buildCommon.mts | 80 ++++++++ src/tsmorph/buildQueryHooks.mts | 31 +++- src/tsmorph/buildQueryOptions.mts | 102 +++++++++++ src/tsmorph/generateFiles.mts | 70 +++++++ src/tsmorph/projectFactory.mts | 11 ++ tests/__snapshots__/createSource.test.ts.snap | 12 ++ tests/__snapshots__/generate.test.ts.snap | 20 +- tests/createSource.test.ts | 3 +- tests/tsmorph/buildCommon.test.ts | 66 +++++++ tests/tsmorph/buildQueryHooks.test.ts | 2 +- tests/tsmorph/buildQueryOptions.test.ts | 171 ++++++++++++++++++ 13 files changed, 590 insertions(+), 15 deletions(-) create mode 100644 src/tsmorph/buildQueryOptions.mts create mode 100644 tests/tsmorph/buildQueryOptions.test.ts diff --git a/docs/src/content/docs/guides/usage.mdx b/docs/src/content/docs/guides/usage.mdx index 2b8cea0..f043bb3 100644 --- a/docs/src/content/docs/guides/usage.mdx +++ b/docs/src/content/docs/guides/usage.mdx @@ -217,3 +217,39 @@ const { data, fetchNextPage } = useFindPaginatedPetsInfinite({ query: { tags: [], limit: 10 } }); ``` + +## Using the generated `queryOptions` factories + +Every GET operation also gets a [queryOptions](https://tanstack.com/query/latest/docs/framework/react/guides/query-options) factory in `queryOptions.ts`. The factory bundles the query key and query function into one type-safe object, so you can reuse it with any TanStack Query utility — `useQuery`, `useSuspenseQuery`, `useQueries`, `queryClient.prefetchQuery`, `queryClient.ensureQueryData`, `queryClient.setQueryData`, and more. + +```tsx +import { useQuery, useQueries, useQueryClient } from "@tanstack/react-query"; +import { findPetsOptions, findPetByIdOptions } from "../openapi/queries"; + +// Equivalent to the generated useFindPets hook, but composable +const { data } = useQuery(findPetsOptions({ query: { tags: [], limit: 10 } })); + +// Fetch multiple queries in parallel +const results = useQueries({ + queries: [1, 2, 3].map((id) => findPetByIdOptions({ path: { id } })), +}); + +// Type-safe cache interaction +const queryClient = useQueryClient(); +const pets = queryClient.getQueryData(findPetsOptions().queryKey); +``` + +Paginatable operations additionally get an `infiniteQueryOptions` factory. It uses a dedicated query key (suffixed with `Infinite`) so cached infinite data never collides with the plain query cache for the same operation. + +```tsx +import { useInfiniteQuery, useQueryClient } from "@tanstack/react-query"; +import { findPaginatedPetsInfiniteOptions } from "../openapi/queries"; + +const { data, fetchNextPage } = useInfiniteQuery( + findPaginatedPetsInfiniteOptions({ query: { tags: [], limit: 10 } }), +); + +// Prefetch an infinite query on the server or in a router loader +const queryClient = useQueryClient(); +await queryClient.prefetchInfiniteQuery(findPaginatedPetsInfiniteOptions()); +``` diff --git a/src/constants.mts b/src/constants.mts index cd1b751..f82f650 100644 --- a/src/constants.mts +++ b/src/constants.mts @@ -7,6 +7,7 @@ export const modelsFileName = "types.gen"; export const OpenApiRqFiles = { queries: "queries", + queryOptions: "queryOptions", infiniteQueries: "infiniteQueries", common: "common", suspense: "suspense", diff --git a/src/tsmorph/buildCommon.mts b/src/tsmorph/buildCommon.mts index 03b66df..ce669b9 100644 --- a/src/tsmorph/buildCommon.mts +++ b/src/tsmorph/buildCommon.mts @@ -150,3 +150,83 @@ export function buildMutationKeyFn( ], }; } + +/** + * Build the client options type for infinite queries. + * The page parameter is excluded because TanStack Query supplies it via the + * pageParam mechanism (#140). + * Example: + * export type FindPaginatedPetsInfiniteClientOptions = Omit, "query"> & + * { query?: Omit, "page"> }; + */ +export function buildInfiniteClientOptionsType( + op: OperationInfo, + ctx: GenerationContext, +): TypeAliasDeclarationStructure { + const dataTypeName = ctx.modelNames.includes( + `${op.capitalizedMethodName}Data`, + ) + ? `${op.capitalizedMethodName}Data` + : "unknown"; + + const type = + dataTypeName === "unknown" + ? "Options" + : `Omit, "query"> & { query?: Omit, "${ctx.pageParam}"> }`; + + return { + kind: StructureKind.TypeAlias, + isExported: true, + name: `${op.capitalizedMethodName}InfiniteClientOptions`, + type, + }; +} + +/** + * Build the infinite query key constant. + * Kept distinct from the plain query key so cached InfiniteData never + * collides with plain query data for the same operation (#140). + * Example: export const useFindPaginatedPetsInfiniteKey = "FindPaginatedPetsInfinite"; + */ +export function buildInfiniteQueryKeyConst( + op: OperationInfo, +): VariableStatementStructure { + return { + kind: StructureKind.VariableStatement, + isExported: true, + declarationKind: VariableDeclarationKind.Const, + declarations: [ + { + name: `use${op.capitalizedMethodName}InfiniteKey`, + initializer: `"${op.capitalizedMethodName}Infinite"`, + }, + ], + }; +} + +/** + * Build the infinite query key function. + * Example: export const UseFindPaginatedPetsInfiniteKeyFn = (clientOptions: FindPaginatedPetsInfiniteClientOptions = {}, queryKey?: Array) => + * [useFindPaginatedPetsInfiniteKey, ...(queryKey ?? [clientOptions])]; + */ +export function buildInfiniteQueryKeyFn( + op: OperationInfo, +): VariableStatementStructure { + const defaultValue = op.allParamsOptional ? " = {}" : ""; + const params = [ + `clientOptions: ${op.capitalizedMethodName}InfiniteClientOptions${defaultValue}`, + "queryKey?: Array", + ]; + + return { + kind: StructureKind.VariableStatement, + isExported: true, + declarationKind: VariableDeclarationKind.Const, + declarations: [ + { + name: `Use${op.capitalizedMethodName}InfiniteKeyFn`, + initializer: `(${params.join(", ")}) => [use${op.capitalizedMethodName}InfiniteKey, ...(queryKey ?? [clientOptions])]`, + }, + ], + }; +} diff --git a/src/tsmorph/buildQueryHooks.mts b/src/tsmorph/buildQueryHooks.mts index aa2e653..639f27a 100644 --- a/src/tsmorph/buildQueryHooks.mts +++ b/src/tsmorph/buildQueryHooks.mts @@ -53,10 +53,23 @@ function getOptionsTypeName(hookType: QueryHookType): string { } } +/** + * Resolve the generated Data type name for an operation, falling back to + * unknown when the operation has no generated Data type. + */ +export function getDataTypeName( + op: OperationInfo, + ctx: GenerationContext, +): string { + return ctx.modelNames.includes(`${op.capitalizedMethodName}Data`) + ? `${op.capitalizedMethodName}Data` + : "unknown"; +} + /** * Build the client options parameter string. */ -function buildClientOptionsParam( +export function buildClientOptionsParam( op: OperationInfo, ctx: GenerationContext, ): string { @@ -156,7 +169,7 @@ export function buildUseSuspenseQueryHook( * Build the nested type for getNextPageParam. * E.g., "meta.next" becomes "{ meta: { next: number } }" */ -function buildNestedNextPageType(nextPageParam: string): string { +export function buildNestedNextPageType(nextPageParam: string): string { const segments = nextPageParam.split("."); return segments.reduceRight((acc, segment) => { return `{ ${segment}: ${acc} }`; @@ -177,17 +190,15 @@ export function buildUseInfiniteQueryHook( 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 dataTypeName = getDataTypeName(op, ctx); + // Infinite queries take a dedicated options type that excludes the page + // parameter — it is supplied by TanStack Query's pageParam mechanism const defaultValue = op.allParamsOptional ? " = {}" : ""; - const clientOptionsParam = `clientOptions: Options<${dataTypeName}, true>${defaultValue}`; + const clientOptionsParam = `clientOptions: Common.${op.capitalizedMethodName}InfiniteClientOptions${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`; + const queryFn = `({ pageParam }) => ${op.methodName}({ ...clientOptions, query: { ...clientOptions.query, ${ctx.pageParam}: pageParam as number } } as Options<${dataTypeName}, true>).then(response => response.data as TData) as TData`; // Build getNextPageParam with nested type const nestedType = buildNestedNextPageType(ctx.nextPageParam); @@ -196,7 +207,7 @@ export function buildUseInfiniteQueryHook( // 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 })`; + const body = `useInfiniteQuery({ queryKey: Common.Use${op.capitalizedMethodName}InfiniteKeyFn(clientOptions, queryKey), queryFn: ${queryFn}, ${infiniteOptions}, ...options })`; return { kind: StructureKind.VariableStatement, diff --git a/src/tsmorph/buildQueryOptions.mts b/src/tsmorph/buildQueryOptions.mts new file mode 100644 index 0000000..f173357 --- /dev/null +++ b/src/tsmorph/buildQueryOptions.mts @@ -0,0 +1,102 @@ +import { + StructureKind, + VariableDeclarationKind, + type VariableStatementStructure, +} from "ts-morph"; +import type { GenerationContext, OperationInfo } from "../types.mjs"; +import { + buildClientOptionsParam, + buildNestedNextPageType, + getDataTypeName, +} from "./buildQueryHooks.mjs"; + +/** + * Build a queryOptions factory for a GET operation. + * The factory centralizes queryKey and queryFn so they can be reused with + * every TanStack Query utility (useQuery, useQueries, prefetchQuery, + * ensureQueryData, setQueryData, ...) with full type safety. + * Example: + * export const findPetsOptions = (clientOptions: Options = {}, queryKey?: Array) => + * queryOptions({ + * queryKey: Common.UseFindPetsKeyFn(clientOptions, queryKey), + * queryFn: () => findPets({ ...clientOptions }).then(response => response.data), + * }); + */ +export function buildQueryOptionsFn( + op: OperationInfo, + ctx: GenerationContext, +): VariableStatementStructure { + const fnName = `${op.methodName}Options`; + const clientOptionsParam = buildClientOptionsParam(op, ctx); + + const queryFn = `() => ${op.methodName}({ ...clientOptions }).then(response => response.data)`; + const body = `queryOptions({ queryKey: Common.Use${op.capitalizedMethodName}KeyFn(clientOptions, queryKey), queryFn: ${queryFn} })`; + + return { + kind: StructureKind.VariableStatement, + // Copy the operation's JSDoc (description and @deprecated) from the SDK function + leadingTrivia: op.jsDoc ? `${op.jsDoc}\n` : undefined, + isExported: true, + declarationKind: VariableDeclarationKind.Const, + declarations: [ + { + name: fnName, + initializer: `(${clientOptionsParam}, queryKey?: Array) => ${body}`, + }, + ], + }; +} + +/** + * Build an infiniteQueryOptions factory for a paginatable GET operation. + * Uses the dedicated infinite query key and page-less options type. + * Example: + * export const findPaginatedPetsInfiniteOptions = (clientOptions: Common.FindPaginatedPetsInfiniteClientOptions = {}, queryKey?: Array) => + * infiniteQueryOptions({ + * queryKey: Common.UseFindPaginatedPetsInfiniteKeyFn(clientOptions, queryKey), + * queryFn: ({ pageParam }) => findPaginatedPets({ ...clientOptions, query: { ...clientOptions.query, page: pageParam } } as Options).then(response => response.data), + * initialPageParam: 1, + * getNextPageParam: (response) => (response as { nextPage: number }).nextPage, + * }); + */ +export function buildInfiniteQueryOptionsFn( + op: OperationInfo, + ctx: GenerationContext, +): VariableStatementStructure | null { + if (!op.isPaginatable) { + return null; + } + + const fnName = `${op.methodName}InfiniteOptions`; + const dataTypeName = getDataTypeName(op, ctx); + + const defaultValue = op.allParamsOptional ? " = {}" : ""; + const clientOptionsParam = `clientOptions: Common.${op.capitalizedMethodName}InfiniteClientOptions${defaultValue}`; + + const queryFn = `({ pageParam }) => ${op.methodName}({ ...clientOptions, query: { ...clientOptions.query, ${ctx.pageParam}: pageParam } } as Options<${dataTypeName}, true>).then(response => response.data)`; + + // Emit a numeric literal when possible so the inferred pageParam type + // matches what getNextPageParam returns + const initialPageParam = /^-?\d+$/.test(ctx.initialPageParam) + ? ctx.initialPageParam + : JSON.stringify(ctx.initialPageParam); + + const nestedType = buildNestedNextPageType(ctx.nextPageParam); + const getNextPageParam = `(response) => (response as ${nestedType}).${ctx.nextPageParam}`; + + const body = `infiniteQueryOptions({ queryKey: Common.Use${op.capitalizedMethodName}InfiniteKeyFn(clientOptions, queryKey), queryFn: ${queryFn}, initialPageParam: ${initialPageParam}, getNextPageParam: ${getNextPageParam} })`; + + return { + kind: StructureKind.VariableStatement, + // Copy the operation's JSDoc (description and @deprecated) from the SDK function + leadingTrivia: op.jsDoc ? `${op.jsDoc}\n` : undefined, + isExported: true, + declarationKind: VariableDeclarationKind.Const, + declarations: [ + { + name: fnName, + initializer: `(${clientOptionsParam}, queryKey?: Array) => ${body}`, + }, + ], + }; +} diff --git a/src/tsmorph/generateFiles.mts b/src/tsmorph/generateFiles.mts index b389aa2..651d06f 100644 --- a/src/tsmorph/generateFiles.mts +++ b/src/tsmorph/generateFiles.mts @@ -14,6 +14,9 @@ import type { } from "../types.mjs"; import { buildDefaultResponseType, + buildInfiniteClientOptionsType, + buildInfiniteQueryKeyConst, + buildInfiniteQueryKeyFn, buildMutationKeyConst, buildMutationKeyFn, buildMutationResultType, @@ -29,12 +32,17 @@ import { buildUseQueryHook, buildUseSuspenseQueryHook, } from "./buildQueryHooks.mjs"; +import { + buildInfiniteQueryOptionsFn, + buildQueryOptionsFn, +} from "./buildQueryOptions.mjs"; import { buildAxiosErrorImport, buildClientImport, buildCommonImport, buildModelImport, buildQueryImport, + buildQueryOptionsImport, buildServiceImport, createGenerationProject, } from "./projectFactory.mjs"; @@ -92,6 +100,10 @@ function generateIndexFile(ctx: GenerationContext): string { kind: StructureKind.ExportDeclaration, moduleSpecifier: "./queries", }, + { + kind: StructureKind.ExportDeclaration, + moduleSpecifier: "./queryOptions", + }, ]; sourceFile.addExportDeclarations(exports); @@ -130,6 +142,13 @@ function generateCommonFile( sourceFile.addVariableStatement(buildQueryKeyFn(op, ctx)); } + // Add dedicated infinite query types and keys for paginatable operations + for (const op of getOperations.filter((o) => o.isPaginatable)) { + sourceFile.addTypeAlias(buildInfiniteClientOptionsType(op, ctx)); + sourceFile.addVariableStatement(buildInfiniteQueryKeyConst(op)); + sourceFile.addVariableStatement(buildInfiniteQueryKeyFn(op)); + } + // Add mutation types and keys for (const op of mutationOperations) { sourceFile.addTypeAlias(buildMutationResultType(op)); @@ -176,6 +195,50 @@ function generateQueriesFile( return sourceFile.getFullText(); } +/** + * Generate the queryOptions.ts file content. + */ +function generateQueryOptionsFile( + operations: OperationInfo[], + ctx: GenerationContext, +): string { + const project = createGenerationProject(); + const sourceFile = project.createSourceFile( + `${OpenApiRqFiles.queryOptions}.ts`, + undefined, + { overwrite: true }, + ); + + // Add imports + const imports: ImportDeclarationStructure[] = [ + buildCommonImport(), + buildQueryOptionsImport(), + buildClientImport(ctx), + buildServiceImport(ctx), + ]; + const modelImport = buildModelImport(ctx); + if (modelImport) { + imports.push(modelImport); + } + sourceFile.addImportDeclarations(imports); + + // Only GET operations have query options + const getOperations = operations.filter((op) => op.httpMethod === "GET"); + + for (const op of getOperations) { + sourceFile.addVariableStatement(buildQueryOptionsFn(op, ctx)); + } + + for (const op of getOperations) { + const infiniteOptions = buildInfiniteQueryOptionsFn(op, ctx); + if (infiniteOptions) { + sourceFile.addVariableStatement(infiniteOptions); + } + } + + return sourceFile.getFullText(); +} + /** * Generate the suspense.ts file content. */ @@ -327,6 +390,13 @@ export function generateAllFiles( ctx.version, ), }, + { + name: `${OpenApiRqFiles.queryOptions}.ts`, + content: addHeaderComment( + generateQueryOptionsFile(operations, ctx), + ctx.version, + ), + }, { name: `${OpenApiRqFiles.suspense}.ts`, content: addHeaderComment( diff --git a/src/tsmorph/projectFactory.mts b/src/tsmorph/projectFactory.mts index 6dcc0d7..999e3f1 100644 --- a/src/tsmorph/projectFactory.mts +++ b/src/tsmorph/projectFactory.mts @@ -66,6 +66,17 @@ export function buildQueryImport(): ImportDeclarationStructure { }; } +/** + * Build import structure for the queryOptions/infiniteQueryOptions helpers. + */ +export function buildQueryOptionsImport(): ImportDeclarationStructure { + return { + kind: StructureKind.ImportDeclaration, + moduleSpecifier: "@tanstack/react-query", + namedImports: [{ name: "queryOptions" }, { name: "infiniteQueryOptions" }], + }; +} + /** * Build import structure for services. */ diff --git a/tests/__snapshots__/createSource.test.ts.snap b/tests/__snapshots__/createSource.test.ts.snap index 468b373..02bf2ec 100644 --- a/tests/__snapshots__/createSource.test.ts.snap +++ b/tests/__snapshots__/createSource.test.ts.snap @@ -5,6 +5,7 @@ exports[`createSource > createSource - @hey-api/client-axios 1`] = ` export * from "./common"; export * from "./queries"; +export * from "./queryOptions"; " `; @@ -41,6 +42,11 @@ export type FindPaginatedPetsQueryResult = {}, queryKey?: Array) => [useFindPaginatedPetsKey, ...(queryKey ?? [clientOptions])]; +export type FindPaginatedPetsInfiniteClientOptions = Omit, "query"> & { query?: Omit, "page"> }; + +export const useFindPaginatedPetsInfiniteKey = "FindPaginatedPetsInfinite"; +export const UseFindPaginatedPetsInfiniteKeyFn = (clientOptions: FindPaginatedPetsInfiniteClientOptions = {}, queryKey?: Array) => [useFindPaginatedPetsInfiniteKey, ...(queryKey ?? [clientOptions])]; + export type AddPetMutationResult = Awaited>; export const useAddPetKey = "AddPet"; @@ -185,6 +191,7 @@ exports[`createSource > createSource - @hey-api/client-fetch 1`] = ` export * from "./common"; export * from "./queries"; +export * from "./queryOptions"; " `; @@ -220,6 +227,11 @@ export type FindPaginatedPetsQueryResult = {}, queryKey?: Array) => [useFindPaginatedPetsKey, ...(queryKey ?? [clientOptions])]; +export type FindPaginatedPetsInfiniteClientOptions = Omit, "query"> & { query?: Omit, "page"> }; + +export const useFindPaginatedPetsInfiniteKey = "FindPaginatedPetsInfinite"; +export const UseFindPaginatedPetsInfiniteKeyFn = (clientOptions: FindPaginatedPetsInfiniteClientOptions = {}, queryKey?: Array) => [useFindPaginatedPetsInfiniteKey, ...(queryKey ?? [clientOptions])]; + export type AddPetMutationResult = Awaited>; export const useAddPetKey = "AddPet"; diff --git a/tests/__snapshots__/generate.test.ts.snap b/tests/__snapshots__/generate.test.ts.snap index ccdd64a..c7c5a79 100644 --- a/tests/__snapshots__/generate.test.ts.snap +++ b/tests/__snapshots__/generate.test.ts.snap @@ -77,6 +77,17 @@ export const UseFindPaginatedPetsKeyFn = ( queryKey?: Array, ) => [useFindPaginatedPetsKey, ...(queryKey ?? [clientOptions])]; +export type FindPaginatedPetsInfiniteClientOptions = Omit< + Options, + "query" +> & { query?: Omit, "page"> }; + +export const useFindPaginatedPetsInfiniteKey = "FindPaginatedPetsInfinite"; +export const UseFindPaginatedPetsInfiniteKeyFn = ( + clientOptions: FindPaginatedPetsInfiniteClientOptions = {}, + queryKey?: Array, +) => [useFindPaginatedPetsInfiniteKey, ...(queryKey ?? [clientOptions])]; + export type AddPetMutationResult = Awaited>; export const useAddPetKey = "AddPet"; @@ -187,6 +198,7 @@ exports[`generate > index.ts 1`] = ` export * from "./common"; export * from "./queries"; +export * from "./queryOptions"; " `; @@ -211,7 +223,7 @@ export const useFindPaginatedPetsInfinite = < TError = unknown, TQueryKey extends Array = unknown[], >( - clientOptions: Options = {}, + clientOptions: Common.FindPaginatedPetsInfiniteClientOptions = {}, queryKey?: TQueryKey, options?: Omit< UseInfiniteQueryOptions, @@ -219,12 +231,14 @@ export const useFindPaginatedPetsInfinite = < >, ) => useInfiniteQuery({ - queryKey: Common.UseFindPaginatedPetsKeyFn(clientOptions, queryKey), + queryKey: Common.UseFindPaginatedPetsInfiniteKeyFn(clientOptions, queryKey), queryFn: ({ pageParam }) => findPaginatedPets({ ...clientOptions, query: { ...clientOptions.query, page: pageParam as number }, - }).then((response) => response.data as TData) as TData, + } as Options).then( + (response) => response.data as TData, + ) as TData, initialPageParam: "initial", getNextPageParam: (response) => (response as { meta: { next: number } }).meta.next, diff --git a/tests/createSource.test.ts b/tests/createSource.test.ts index 8b6eba4..d6e3852 100644 --- a/tests/createSource.test.ts +++ b/tests/createSource.test.ts @@ -18,11 +18,12 @@ describe(fileName, () => { client: "@hey-api/client-fetch", }); - expect(source).toHaveLength(7); + expect(source).toHaveLength(8); expect(source.map((s) => s.name)).toEqual([ "index.ts", "common.ts", "queries.ts", + "queryOptions.ts", "suspense.ts", "infiniteQueries.ts", "prefetch.ts", diff --git a/tests/tsmorph/buildCommon.test.ts b/tests/tsmorph/buildCommon.test.ts index e6b23eb..528fb09 100644 --- a/tests/tsmorph/buildCommon.test.ts +++ b/tests/tsmorph/buildCommon.test.ts @@ -2,6 +2,9 @@ import { StructureKind, VariableDeclarationKind } from "ts-morph"; import { describe, expect, it } from "vitest"; import { buildDefaultResponseType, + buildInfiniteClientOptionsType, + buildInfiniteQueryKeyConst, + buildInfiniteQueryKeyFn, buildMutationKeyConst, buildMutationKeyFn, buildMutationResultType, @@ -31,6 +34,16 @@ const mockMutationOperation: OperationInfo = { isPaginatable: false, }; +const mockPaginatableOperation: OperationInfo = { + methodName: "findPaginatedPets", + capitalizedMethodName: "FindPaginatedPets", + httpMethod: "GET", + isDeprecated: false, + parameters: [{ name: "page", typeName: "number", optional: true }], + allParamsOptional: true, + isPaginatable: true, +}; + const mockContext: GenerationContext = { client: "@hey-api/client-fetch", modelNames: ["Pet", "NewPet", "FindPetsData", "AddPetData"], @@ -189,4 +202,57 @@ describe("buildCommon", () => { expect(initializer).toContain("mutationKey ?? []"); }); }); + describe("buildInfiniteClientOptionsType", () => { + it("should exclude the page param from the query type", () => { + const ctx: GenerationContext = { + ...mockContext, + modelNames: [...mockContext.modelNames, "FindPaginatedPetsData"], + }; + const result = buildInfiniteClientOptionsType( + mockPaginatableOperation, + ctx, + ); + + expect(result.isExported).toBe(true); + expect(result.name).toBe("FindPaginatedPetsInfiniteClientOptions"); + expect(result.type).toBe( + 'Omit, "query"> & { query?: Omit, "page"> }', + ); + }); + + it("should fall back to Options without a Data type", () => { + const result = buildInfiniteClientOptionsType( + mockPaginatableOperation, + mockContext, + ); + + expect(result.type).toBe("Options"); + }); + }); + + describe("buildInfiniteQueryKeyConst", () => { + it("should build a distinct key for infinite queries", () => { + const result = buildInfiniteQueryKeyConst(mockPaginatableOperation); + + expect(result.declarations[0].name).toBe( + "useFindPaginatedPetsInfiniteKey", + ); + expect(result.declarations[0].initializer).toBe( + '"FindPaginatedPetsInfinite"', + ); + }); + }); + + describe("buildInfiniteQueryKeyFn", () => { + it("should build a key fn typed with the infinite client options", () => { + const result = buildInfiniteQueryKeyFn(mockPaginatableOperation); + + expect(result.declarations[0].name).toBe( + "UseFindPaginatedPetsInfiniteKeyFn", + ); + expect(result.declarations[0].initializer).toBe( + "(clientOptions: FindPaginatedPetsInfiniteClientOptions = {}, queryKey?: Array) => [useFindPaginatedPetsInfiniteKey, ...(queryKey ?? [clientOptions])]", + ); + }); + }); }); diff --git a/tests/tsmorph/buildQueryHooks.test.ts b/tests/tsmorph/buildQueryHooks.test.ts index 4dbf052..235916d 100644 --- a/tests/tsmorph/buildQueryHooks.test.ts +++ b/tests/tsmorph/buildQueryHooks.test.ts @@ -232,7 +232,7 @@ describe("buildQueryHooks", () => { const initializer = result?.declarations[0].initializer as string; expect(initializer).toContain( - "clientOptions: Options = {}", + "clientOptions: Common.ListThingsInfiniteClientOptions = {}", ); }); }); diff --git a/tests/tsmorph/buildQueryOptions.test.ts b/tests/tsmorph/buildQueryOptions.test.ts new file mode 100644 index 0000000..6728b51 --- /dev/null +++ b/tests/tsmorph/buildQueryOptions.test.ts @@ -0,0 +1,171 @@ +import { StructureKind, VariableDeclarationKind } from "ts-morph"; +import { describe, expect, it } from "vitest"; +import { + buildInfiniteQueryOptionsFn, + buildQueryOptionsFn, +} from "../../src/tsmorph/buildQueryOptions.mjs"; +import type { GenerationContext, OperationInfo } from "../../src/types.mjs"; + +const mockOperation: OperationInfo = { + methodName: "findPets", + capitalizedMethodName: "FindPets", + httpMethod: "GET", + isDeprecated: false, + parameters: [{ name: "limit", typeName: "number", optional: true }], + allParamsOptional: true, + isPaginatable: false, +}; + +const mockPaginatableOperation: OperationInfo = { + methodName: "findPaginatedPets", + capitalizedMethodName: "FindPaginatedPets", + httpMethod: "GET", + isDeprecated: false, + parameters: [{ name: "page", typeName: "number", optional: true }], + allParamsOptional: true, + isPaginatable: true, +}; + +const mockRequiredParamsOperation: OperationInfo = { + methodName: "findPetById", + capitalizedMethodName: "FindPetById", + httpMethod: "GET", + isDeprecated: false, + parameters: [{ name: "id", typeName: "number", optional: false }], + allParamsOptional: false, + isPaginatable: false, +}; + +const mockContext: GenerationContext = { + client: "@hey-api/client-fetch", + modelNames: [ + "Pet", + "FindPetsData", + "FindPaginatedPetsData", + "FindPetByIdData", + ], + serviceNames: ["findPets", "findPaginatedPets", "findPetById"], + pageParam: "page", + nextPageParam: "nextPage", + initialPageParam: "1", + version: "1.0.0", +}; + +describe("buildQueryOptions", () => { + describe("buildQueryOptionsFn", () => { + it("should build a queryOptions factory", () => { + const result = buildQueryOptionsFn(mockOperation, mockContext); + + expect(result.kind).toBe(StructureKind.VariableStatement); + expect(result.isExported).toBe(true); + expect(result.declarationKind).toBe(VariableDeclarationKind.Const); + expect(result.declarations[0].name).toBe("findPetsOptions"); + + const initializer = result.declarations[0].initializer as string; + expect(initializer).toContain( + "clientOptions: Options = {}", + ); + expect(initializer).toContain( + "queryOptions({ queryKey: Common.UseFindPetsKeyFn(clientOptions, queryKey)", + ); + expect(initializer).toContain( + "queryFn: () => findPets({ ...clientOptions }).then(response => response.data)", + ); + }); + + it("should not add a default value when the operation has required params", () => { + const result = buildQueryOptionsFn( + mockRequiredParamsOperation, + mockContext, + ); + const initializer = result.declarations[0].initializer as string; + + expect(initializer).toContain( + "clientOptions: Options,", + ); + expect(initializer).not.toContain( + "clientOptions: Options = {}", + ); + }); + + it("should copy the operation JSDoc as leading trivia", () => { + const op: OperationInfo = { + ...mockOperation, + jsDoc: "/**\n * Returns all pets\n */", + }; + const result = buildQueryOptionsFn(op, mockContext); + + expect(result.leadingTrivia).toBe("/**\n * Returns all pets\n */\n"); + }); + }); + + describe("buildInfiniteQueryOptionsFn", () => { + it("should return null for non-paginatable operations", () => { + expect(buildInfiniteQueryOptionsFn(mockOperation, mockContext)).toBe( + null, + ); + }); + + it("should build an infiniteQueryOptions factory with the dedicated infinite key", () => { + const result = buildInfiniteQueryOptionsFn( + mockPaginatableOperation, + mockContext, + ); + + expect(result?.declarations[0].name).toBe( + "findPaginatedPetsInfiniteOptions", + ); + + const initializer = result?.declarations[0].initializer as string; + expect(initializer).toContain( + "clientOptions: Common.FindPaginatedPetsInfiniteClientOptions = {}", + ); + expect(initializer).toContain( + "infiniteQueryOptions({ queryKey: Common.UseFindPaginatedPetsInfiniteKeyFn(clientOptions, queryKey)", + ); + expect(initializer).toContain( + "query: { ...clientOptions.query, page: pageParam } } as Options", + ); + expect(initializer).toContain( + "getNextPageParam: (response) => (response as { nextPage: number }).nextPage", + ); + }); + + it("should emit a numeric initialPageParam so the pageParam type matches getNextPageParam", () => { + const result = buildInfiniteQueryOptionsFn( + mockPaginatableOperation, + mockContext, + ); + const initializer = result?.declarations[0].initializer as string; + + expect(initializer).toContain("initialPageParam: 1,"); + expect(initializer).not.toContain('initialPageParam: "1"'); + }); + + it("should quote a non-numeric initialPageParam", () => { + const ctx: GenerationContext = { + ...mockContext, + initialPageParam: "cursor-start", + }; + const result = buildInfiniteQueryOptionsFn(mockPaginatableOperation, ctx); + const initializer = result?.declarations[0].initializer as string; + + expect(initializer).toContain('initialPageParam: "cursor-start",'); + }); + + it("should respect a custom pageParam and nested nextPageParam", () => { + const ctx: GenerationContext = { + ...mockContext, + pageParam: "offset", + nextPageParam: "meta.next", + }; + const result = buildInfiniteQueryOptionsFn(mockPaginatableOperation, ctx); + const initializer = result?.declarations[0].initializer as string; + + expect(initializer).toContain("offset: pageParam"); + expect(initializer).toContain( + "(response as { meta: { next: number } }).meta.next", + ); + }); + }); +}); From 24bd4dd92be37b2e640bdfb26cf2b2108d7f75d7 Mon Sep 17 00:00:00 2001 From: Urata Daiki <7nohe@users.noreply.github.com> Date: Sat, 11 Jul 2026 07:17:57 +0900 Subject: [PATCH 09/31] chore: release v3.0.0-beta.2 Co-Authored-By: Claude Fable 5 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 21d5360..0ede827 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@7nohe/openapi-react-query-codegen", - "version": "3.0.0-beta.1", + "version": "3.0.0-beta.2", "description": "OpenAPI React Query Codegen", "bin": { "openapi-rq": "dist/cli.mjs" From a631ad138d3a71cfd50d6b996c67b311abbecddc Mon Sep 17 00:00:00 2001 From: Urata Daiki <7nohe@users.noreply.github.com> Date: Sat, 11 Jul 2026 07:22:45 +0900 Subject: [PATCH 10/31] docs: add v2 to v3 migration guide Co-Authored-By: Claude Fable 5 --- docs/astro.config.mjs | 1 + .../content/docs/guides/migrating-to-v3.mdx | 70 +++++++++++++++++++ 2 files changed, 71 insertions(+) create mode 100644 docs/src/content/docs/guides/migrating-to-v3.mdx diff --git a/docs/astro.config.mjs b/docs/astro.config.mjs index 9ffe68b..3f1891c 100644 --- a/docs/astro.config.mjs +++ b/docs/astro.config.mjs @@ -31,6 +31,7 @@ export default defineConfig({ { slug: "guides/introduction" }, { slug: "guides/usage" }, { slug: "guides/cli-options" }, + { slug: "guides/migrating-to-v3" }, ], }, { diff --git a/docs/src/content/docs/guides/migrating-to-v3.mdx b/docs/src/content/docs/guides/migrating-to-v3.mdx new file mode 100644 index 0000000..7cd3b42 --- /dev/null +++ b/docs/src/content/docs/guides/migrating-to-v3.mdx @@ -0,0 +1,70 @@ +--- +title: Migrating to v3 +description: Migration guide from v2 to v3 of OpenAPI React Query Codegen. +--- + +v3 is a full rewrite of the code generator on top of a ts-morph pipeline, but the generated output is intentionally kept compatible with v2. For most projects, migrating means updating the package and regenerating — no code changes. + +## TL;DR + +1. Update the package and regenerate your client: + + ```bash + npm install -D @7nohe/openapi-react-query-codegen@beta + npx openapi-rq -i ./petstore.yaml + ``` + +2. If you don't use infinite queries, you are done. +3. If you use infinite queries (`useXxxInfinite`), fix the type errors the compiler reports — see below. They are mechanical. + +## What stays the same + +- All generated hooks (`useQuery`, `useSuspenseQuery`, `useMutation`), prefetch and `ensureQueryData` functions, key constants, and key functions keep their v2 names and signatures. +- JSDoc comments (including `@deprecated`) are still emitted on every hook. +- Environment requirements are unchanged from v2.2.0: Node.js 22.18+, `typescript` 5.x or 6.x, `ts-morph` 28.x. + +## What's new + +Every GET operation now also gets a [queryOptions factory](/guides/usage/#using-the-generated-queryoptions-factories) in `queryOptions.ts`, and paginatable operations get an `infiniteQueryOptions` factory. These are additive — existing code is unaffected. + +```tsx +import { useQuery } from "@tanstack/react-query"; +import { findPetsOptions } from "../openapi/queries"; + +const { data } = useQuery(findPetsOptions({ query: { limit: 10 } })); +``` + +## Breaking changes: infinite queries + +In v2, an infinite query shared its cache key with the plain query for the same operation, which corrupted the cache when both were used ([#140](https://github.com/7nohe/openapi-react-query-codegen/issues/140)). v3 gives infinite queries their own keys and types. + +### 1. Cache keys have an `Infinite` suffix + +The cache key changed from `["FindPaginatedPets"]` to `["FindPaginatedPetsInfinite"]`. If you interact with the infinite query cache manually, switch to the new key exports: + +```diff +- queryClient.invalidateQueries({ queryKey: [useFindPaginatedPetsKey] }); ++ queryClient.invalidateQueries({ queryKey: [useFindPaginatedPetsInfiniteKey] }); + +- queryClient.setQueryData(UseFindPaginatedPetsKeyFn(options), updater); ++ queryClient.setQueryData(UseFindPaginatedPetsInfiniteKeyFn(options), updater); +``` + +If you persist the query cache (e.g. `persistQueryClient`), previously cached infinite data will not match the new key and will be refetched once after the upgrade. + +### 2. The page parameter is no longer accepted in `clientOptions` + +Infinite hooks now take a dedicated options type that excludes the page parameter — TanStack Query supplies it through the `pageParam` mechanism. In v2 a page value passed here was silently overwritten, so removing it does not change behavior: + +```diff + const { data, fetchNextPage } = useFindPaginatedPetsInfinite({ +- query: { page: 1, tags: [], limit: 10 }, ++ query: { tags: [], limit: 10 }, + }); +``` + +This also fixes compilation for OpenAPI specs where the page parameter is required. + +### 3. Specs with a required page parameter now compile + +No action needed — this was previously a compile error in the generated code. The generated `XxxInfiniteClientOptions` type makes the page parameter unnecessary while keeping all other parameters typed as before. From f778c1c0ac09f407ea80eb84cfc3a0cd995e9b1c Mon Sep 17 00:00:00 2001 From: Urata Daiki <7nohe@users.noreply.github.com> Date: Sat, 18 Jul 2026 20:38:31 +0900 Subject: [PATCH 11/31] chore: update dev dependencies (biome 2, vitest 4, commander 15, rimraf 6, lefthook 2) - Migrate biome config to 2.x (tailwindDirectives, svg exclusion, example rule overrides) - Remove dead code surfaced by biome 2 noUnusedVariables/noUnusedImports - Keep TypeScript on 6.x (ts-morph 28 does not support the TS 7 native compiler yet) --- biome.json | 58 +- .../app/components/PaginatedPets.tsx | 2 +- examples/nextjs-app/app/components/Pets.tsx | 2 +- examples/nextjs-app/app/page.tsx | 2 +- examples/nextjs-app/app/providers.tsx | 2 +- examples/react-router-6-app/src/main.tsx | 2 +- .../app/routes/_index/route.tsx | 5 +- examples/react-router-7-app/providers.tsx | 3 +- examples/tanstack-router-app/src/main.tsx | 2 +- .../tanstack-router-app/src/routes/__root.tsx | 2 +- package.json | 23 +- pnpm-lock.yaml | 1719 +++++++++++------ src/generate.mts | 2 +- src/tsmorph/buildQueryHooks.mts | 14 - src/tsmorph/generateFiles.mts | 7 +- src/tsmorph/index.mts | 6 +- tests/common.test.ts | 2 +- tests/service.test.ts | 1 + tests/tsmorph/projectFactory.test.ts | 2 +- tests/utils.test.ts | 2 - tests/utils.ts | 2 +- 21 files changed, 1232 insertions(+), 628 deletions(-) diff --git a/biome.json b/biome.json index a499589..76c446f 100644 --- a/biome.json +++ b/biome.json @@ -1,33 +1,38 @@ { - "$schema": "https://biomejs.dev/schemas/1.9.3/schema.json", - "organizeImports": { - "enabled": true - }, + "$schema": "https://biomejs.dev/schemas/2.5.4/schema.json", + "assist": { "actions": { "source": { "organizeImports": "on" } } }, "files": { - "ignore": [ - ".vscode", - "dist", - "examples/react-app/openapi", - "coverage", - "examples/nextjs-app/openapi", - "examples/nextjs-app/.next", - "examples/tanstack-router-app/openapi", - "examples/tanstack-router-app/src/routeTree.gen.ts", - "examples/react-router-6-app/openapi", - "examples/react-router-7-app/openapi", - "examples/react-router-7-app/.react-router", - "docs/.astro" + "includes": [ + "**", + "!**/.vscode", + "!**/dist", + "!**/examples/react-app/openapi", + "!**/coverage", + "!**/examples/nextjs-app/openapi", + "!**/examples/nextjs-app/.next", + "!**/examples/tanstack-router-app/openapi", + "!**/examples/tanstack-router-app/src/routeTree.gen.ts", + "!**/examples/react-router-6-app/openapi", + "!**/examples/react-router-7-app/openapi", + "!**/examples/react-router-7-app/.react-router", + "!**/docs/.astro", + "!**/*.svg" ] }, + "css": { + "parser": { + "tailwindDirectives": true + } + }, "linter": { "enabled": true, "rules": { - "recommended": true + "preset": "recommended" } }, "overrides": [ { - "include": ["src/vendor-typestubs.d.ts"], + "includes": ["**/src/vendor-typestubs.d.ts"], "linter": { "rules": { "suspicious": { @@ -35,6 +40,21 @@ } } } + }, + { + "includes": ["examples/**"], + "linter": { + "rules": { + "correctness": { + "noUnusedVariables": "off", + "noUnusedFunctionParameters": "off" + }, + "suspicious": { + "noArrayIndexKey": "off", + "noUnknownAtRules": "off" + } + } + } } ], "formatter": { diff --git a/examples/nextjs-app/app/components/PaginatedPets.tsx b/examples/nextjs-app/app/components/PaginatedPets.tsx index 56e64e6..f235572 100644 --- a/examples/nextjs-app/app/components/PaginatedPets.tsx +++ b/examples/nextjs-app/app/components/PaginatedPets.tsx @@ -1,8 +1,8 @@ "use client"; -import { useFindPaginatedPetsInfinite } from "@/openapi/queries/infiniteQueries"; import { ReactQueryDevtools } from "@tanstack/react-query-devtools"; import React from "react"; +import { useFindPaginatedPetsInfinite } from "@/openapi/queries/infiniteQueries"; export default function PaginatedPets() { const { data, fetchNextPage } = useFindPaginatedPetsInfinite({ diff --git a/examples/nextjs-app/app/components/Pets.tsx b/examples/nextjs-app/app/components/Pets.tsx index d3eacdc..d3c9e22 100644 --- a/examples/nextjs-app/app/components/Pets.tsx +++ b/examples/nextjs-app/app/components/Pets.tsx @@ -1,7 +1,7 @@ "use client"; -import { useFindPets } from "@/openapi/queries"; import { ReactQueryDevtools } from "@tanstack/react-query-devtools"; +import { useFindPets } from "@/openapi/queries"; export default function Pets() { const { data } = useFindPets({ diff --git a/examples/nextjs-app/app/page.tsx b/examples/nextjs-app/app/page.tsx index f61e199..445c892 100644 --- a/examples/nextjs-app/app/page.tsx +++ b/examples/nextjs-app/app/page.tsx @@ -1,7 +1,7 @@ import { + dehydrate, HydrationBoundary, QueryClient, - dehydrate, } from "@tanstack/react-query"; import Link from "next/link"; import { prefetchUseFindPets } from "../openapi/queries/prefetch"; diff --git a/examples/nextjs-app/app/providers.tsx b/examples/nextjs-app/app/providers.tsx index 86d555c..c2d7026 100644 --- a/examples/nextjs-app/app/providers.tsx +++ b/examples/nextjs-app/app/providers.tsx @@ -18,7 +18,7 @@ function makeQueryClient() { }); } -let browserQueryClient: QueryClient | undefined = undefined; +let browserQueryClient: QueryClient | undefined; function getQueryClient() { if (typeof window === "undefined") { diff --git a/examples/react-router-6-app/src/main.tsx b/examples/react-router-6-app/src/main.tsx index 5656b4d..f2ce7e9 100644 --- a/examples/react-router-6-app/src/main.tsx +++ b/examples/react-router-6-app/src/main.tsx @@ -6,7 +6,7 @@ import { QueryClientProvider } from "@tanstack/react-query"; import { ReactQueryDevtools } from "@tanstack/react-query-devtools"; import { queryClient } from "./queryClient"; import "./axios"; -import { RouterProvider, createBrowserRouter } from "react-router-dom"; +import { createBrowserRouter, RouterProvider } from "react-router-dom"; const router = createBrowserRouter([ { diff --git a/examples/react-router-7-app/app/routes/_index/route.tsx b/examples/react-router-7-app/app/routes/_index/route.tsx index 1d9abbc..85e354a 100644 --- a/examples/react-router-7-app/app/routes/_index/route.tsx +++ b/examples/react-router-7-app/app/routes/_index/route.tsx @@ -1,11 +1,10 @@ -import { useState } from "react"; - import { + dehydrate, HydrationBoundary, QueryClient, - dehydrate, useQueryClient, } from "@tanstack/react-query"; +import { useState } from "react"; import { UseFindPetsKeyFn, useAddPet, diff --git a/examples/react-router-7-app/providers.tsx b/examples/react-router-7-app/providers.tsx index 070cf98..60a5fed 100644 --- a/examples/react-router-7-app/providers.tsx +++ b/examples/react-router-7-app/providers.tsx @@ -1,6 +1,5 @@ import "./fetchClient"; -import { QueryClientProvider } from "@tanstack/react-query"; -import { QueryClient } from "@tanstack/react-query"; +import { QueryClient, QueryClientProvider } from "@tanstack/react-query"; import { ReactQueryDevtools } from "@tanstack/react-query-devtools"; import React from "react"; diff --git a/examples/tanstack-router-app/src/main.tsx b/examples/tanstack-router-app/src/main.tsx index dbdfd37..6ea0129 100644 --- a/examples/tanstack-router-app/src/main.tsx +++ b/examples/tanstack-router-app/src/main.tsx @@ -1,5 +1,5 @@ import { QueryClient, QueryClientProvider } from "@tanstack/react-query"; -import { RouterProvider, createRouter } from "@tanstack/react-router"; +import { createRouter, RouterProvider } from "@tanstack/react-router"; import "./fetchClient"; import ReactDOM from "react-dom/client"; import { routeTree } from "./routeTree.gen"; diff --git a/examples/tanstack-router-app/src/routes/__root.tsx b/examples/tanstack-router-app/src/routes/__root.tsx index fa0384a..5fef501 100644 --- a/examples/tanstack-router-app/src/routes/__root.tsx +++ b/examples/tanstack-router-app/src/routes/__root.tsx @@ -1,9 +1,9 @@ import type { QueryClient } from "@tanstack/react-query"; import { ReactQueryDevtools } from "@tanstack/react-query-devtools"; import { + createRootRouteWithContext, Link, Outlet, - createRootRouteWithContext, } from "@tanstack/react-router"; import { TanStackRouterDevtools } from "@tanstack/router-devtools"; diff --git a/package.json b/package.json index 0ede827..5a8bc9d 100644 --- a/package.json +++ b/package.json @@ -7,7 +7,9 @@ }, "private": false, "type": "module", - "workspaces": ["examples/*"], + "workspaces": [ + "examples/*" + ], "scripts": { "build": "rimraf dist && tsc -p tsconfig.json", "lint": "biome check .", @@ -33,7 +35,9 @@ }, "homepage": "https://github.com/7nohe/openapi-react-query-codegen", "bugs": "https://github.com/7nohe/openapi-react-query-codegen/issues", - "files": ["dist"], + "files": [ + "dist" + ], "keywords": [ "codegen", "react-query", @@ -51,17 +55,18 @@ "cross-spawn": "^7.0.3" }, "devDependencies": { - "@biomejs/biome": "^1.9.3", + "@biomejs/biome": "^2.5.4", "@types/cross-spawn": "^6.0.6", - "@types/node": "^22.7.4", + "@types/node": "^22.20.1", "@types/semver": "^7.7.1", - "@vitest/coverage-v8": "^1.5.0", - "commander": "^12.0.0", - "lefthook": "^1.6.10", - "rimraf": "^5.0.5", + "@vitest/coverage-v8": "^4.1.10", + "commander": "^15.0.0", + "lefthook": "^2.1.10", + "rimraf": "^6.1.3", "ts-morph": "^28.0.0", "typescript": "^6.0.3", - "vitest": "^1.5.0" + "vite": "^7", + "vitest": "^4.1.10" }, "peerDependencies": { "commander": "12.x", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index c2bae92..5654994 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -18,44 +18,47 @@ importers: dependencies: '@hey-api/openapi-ts': specifier: 0.99.0 - version: 0.99.0(patch_hash=uzpdsaqpo2bd4ydyvbzbqpdgxq)(magicast@0.3.5)(typescript@6.0.3) + version: 0.99.0(patch_hash=uzpdsaqpo2bd4ydyvbzbqpdgxq)(magicast@0.5.3)(typescript@6.0.3) cross-spawn: specifier: ^7.0.3 version: 7.0.3 devDependencies: '@biomejs/biome': - specifier: ^1.9.3 - version: 1.9.3 + specifier: ^2.5.4 + version: 2.5.4 '@types/cross-spawn': specifier: ^6.0.6 version: 6.0.6 '@types/node': - specifier: ^22.7.4 - version: 22.7.4 + specifier: ^22.20.1 + version: 22.20.1 '@types/semver': specifier: ^7.7.1 version: 7.7.1 '@vitest/coverage-v8': - specifier: ^1.5.0 - version: 1.6.0(vitest@1.6.0(@types/node@22.7.4)(terser@5.34.1)) + specifier: ^4.1.10 + version: 4.1.10(vitest@4.1.10) commander: - specifier: ^12.0.0 - version: 12.1.0 + specifier: ^15.0.0 + version: 15.0.0 lefthook: - specifier: ^1.6.10 - version: 1.6.15 + specifier: ^2.1.10 + version: 2.1.10 rimraf: - specifier: ^5.0.5 - version: 5.0.7 + specifier: ^6.1.3 + version: 6.1.3 ts-morph: specifier: ^28.0.0 version: 28.0.0 typescript: specifier: ^6.0.3 version: 6.0.3 + vite: + specifier: ^7 + version: 7.3.6(@types/node@22.20.1)(jiti@2.6.1)(terser@5.34.1)(tsx@4.19.1)(yaml@2.5.1) vitest: - specifier: ^1.5.0 - version: 1.6.0(@types/node@22.7.4)(terser@5.34.1) + specifier: ^4.1.10 + version: 4.1.10(@types/node@22.20.1)(@vitest/coverage-v8@4.1.10)(vite@7.3.6(@types/node@22.20.1)(jiti@2.6.1)(terser@5.34.1)(tsx@4.19.1)(yaml@2.5.1)) docs: dependencies: @@ -64,10 +67,10 @@ importers: version: 0.9.4(prettier@3.3.3)(typescript@5.6.3) '@astrojs/starlight': specifier: ^0.28.3 - version: 0.28.3(astro@4.16.2(@types/node@22.7.4)(rollup@4.24.0)(terser@5.34.1)(typescript@5.6.3)) + version: 0.28.3(astro@4.16.2(@types/node@22.20.1)(rollup@4.62.2)(terser@5.34.1)(typescript@5.6.3)) astro: specifier: ^4.15.3 - version: 4.16.2(@types/node@22.7.4)(rollup@4.24.0)(terser@5.34.1)(typescript@5.6.3) + version: 4.16.2(@types/node@22.20.1)(rollup@4.62.2)(terser@5.34.1)(typescript@5.6.3) sharp: specifier: ^0.32.5 version: 0.32.6 @@ -156,7 +159,7 @@ importers: version: 18.3.0 '@vitejs/plugin-react': specifier: ^4.2.1 - version: 4.3.1(vite@5.2.13(@types/node@22.7.4)(terser@5.34.1)) + version: 4.3.1(vite@5.2.13(@types/node@22.20.1)(terser@5.34.1)) npm-run-all: specifier: ^4.1.5 version: 4.1.5 @@ -165,7 +168,7 @@ importers: version: 5.4.5 vite: specifier: ^5.0.12 - version: 5.2.13(@types/node@22.7.4)(terser@5.34.1) + version: 5.2.13(@types/node@22.20.1)(terser@5.34.1) examples/react-router-6-app: dependencies: @@ -202,7 +205,7 @@ importers: version: 18.3.0 '@vitejs/plugin-react': specifier: ^4.2.1 - version: 4.3.1(vite@5.4.8(@types/node@22.7.4)(terser@5.34.1)) + version: 4.3.1(vite@5.4.8(@types/node@22.20.1)(terser@5.34.1)) npm-run-all: specifier: ^4.1.5 version: 4.1.5 @@ -211,13 +214,13 @@ importers: version: 5.6.3 vite: specifier: ^5.0.12 - version: 5.4.8(@types/node@22.7.4)(terser@5.34.1) + version: 5.4.8(@types/node@22.20.1)(terser@5.34.1) examples/react-router-7-app: dependencies: '@react-router/fs-routes': specifier: ^7.0.0-pre.2 - version: 7.0.0-pre.2(@react-router/dev@7.0.0-pre.2(@react-router/serve@7.0.0-pre.2(react-router@7.0.0-pre.2(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(typescript@5.6.3))(@types/node@22.7.4)(react-router@7.0.0-pre.2(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(terser@5.34.1)(typescript@5.6.3)(vite@5.4.8(@types/node@22.7.4)(terser@5.34.1)))(typescript@5.6.3) + version: 7.0.0-pre.2(@react-router/dev@7.0.0-pre.2(@react-router/serve@7.0.0-pre.2(react-router@7.0.0-pre.2(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(typescript@5.6.3))(@types/node@22.20.1)(react-router@7.0.0-pre.2(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(terser@5.34.1)(typescript@5.6.3)(vite@5.4.8(@types/node@22.20.1)(terser@5.34.1)))(typescript@5.6.3) '@react-router/node': specifier: ^7.0.0-pre.2 version: 7.0.0-pre.2(react-router@7.0.0-pre.2(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(typescript@5.6.3) @@ -254,7 +257,7 @@ importers: version: 1.9.3 '@react-router/dev': specifier: ^7.0.0-pre.2 - version: 7.0.0-pre.2(@react-router/serve@7.0.0-pre.2(react-router@7.0.0-pre.2(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(typescript@5.6.3))(@types/node@22.7.4)(react-router@7.0.0-pre.2(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(terser@5.34.1)(typescript@5.6.3)(vite@5.4.8(@types/node@22.7.4)(terser@5.34.1)) + version: 7.0.0-pre.2(@react-router/serve@7.0.0-pre.2(react-router@7.0.0-pre.2(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(typescript@5.6.3))(@types/node@22.20.1)(react-router@7.0.0-pre.2(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(terser@5.34.1)(typescript@5.6.3)(vite@5.4.8(@types/node@22.20.1)(terser@5.34.1)) '@stoplight/prism-cli': specifier: ^5.5.2 version: 5.8.1 @@ -266,7 +269,7 @@ importers: version: 18.3.0 '@vitejs/plugin-react': specifier: ^4.2.1 - version: 4.3.1(vite@5.4.8(@types/node@22.7.4)(terser@5.34.1)) + version: 4.3.1(vite@5.4.8(@types/node@22.20.1)(terser@5.34.1)) npm-run-all: specifier: ^4.1.5 version: 4.1.5 @@ -275,7 +278,7 @@ importers: version: 5.6.3 vite: specifier: ^5.0.12 - version: 5.4.8(@types/node@22.7.4)(terser@5.34.1) + version: 5.4.8(@types/node@22.20.1)(terser@5.34.1) examples/tanstack-router-app: dependencies: @@ -309,7 +312,7 @@ importers: version: 5.8.1 '@tanstack/router-plugin': specifier: ^1.58.4 - version: 1.62.0(vite@5.4.8(@types/node@22.7.4)(terser@5.34.1)) + version: 1.62.0(vite@5.4.8(@types/node@22.20.1)(terser@5.34.1)) '@types/react': specifier: ^18.3.3 version: 18.3.3 @@ -318,13 +321,13 @@ importers: version: 18.3.0 '@vitejs/plugin-react': specifier: ^4.3.1 - version: 4.3.1(vite@5.4.8(@types/node@22.7.4)(terser@5.34.1)) + version: 4.3.1(vite@5.4.8(@types/node@22.20.1)(terser@5.34.1)) npm-run-all: specifier: ^4.1.5 version: 4.1.5 vite: specifier: ^5.4.4 - version: 5.4.8(@types/node@22.7.4)(terser@5.34.1) + version: 5.4.8(@types/node@22.20.1)(terser@5.34.1) packages: @@ -550,6 +553,10 @@ packages: resolution: {integrity: sha512-4A/SCr/2KLd5jrtOMFzaKjVtAei3+2r/NChoBNoZ3EyP/+GlhoaEGoWOZUmFmoITP7zOJyHIMm+DYRd8o3PvHA==} engines: {node: '>=6.9.0'} + '@babel/helper-string-parser@7.29.7': + resolution: {integrity: sha512-Pb5ijPrZ89GDH8223L4UP8i6QApWxs04RbPQJTeWDV0/keR2E36MeKnyr6LYmUUvqRRI+Iv87SuF1W6ErINzYw==} + engines: {node: '>=6.9.0'} + '@babel/helper-validator-identifier@7.24.7': resolution: {integrity: sha512-rR+PBcQ1SMQDDyF6X0wxtG8QyLCgUB0eRAGguqRLfkCA87l7yAP7ehq8SNj96OOGTO8OBV70KhuFYcIkHXOg0w==} engines: {node: '>=6.9.0'} @@ -562,6 +569,10 @@ packages: resolution: {integrity: sha512-Ed61U6XJc3CVRfkERJWDz4dJwKe7iLmmJsbOGu9wSloNSFttHV0I8g6UAgb7qnK5ly5bGLPd4oXZlxCdANBOWQ==} engines: {node: '>=6.9.0'} + '@babel/helper-validator-identifier@7.29.7': + resolution: {integrity: sha512-qehxGkRj55h/ff8EMaJ+cYhyaKlHIxqYDn682wQD7RNp9UujOQsHog2uS0r2vzr4pW+sXf90NeeayjcNaX3fFg==} + engines: {node: '>=6.9.0'} + '@babel/helper-validator-option@7.24.7': resolution: {integrity: sha512-yy1/KvjhV/ZCL+SM7hBrvnZJ3ZuT9OuZgIJAGpPEToANvc3iM6iDvBnRjtElWibHU6n8/LPR/EjX9EtIEYO3pw==} engines: {node: '>=6.9.0'} @@ -609,6 +620,11 @@ packages: engines: {node: '>=6.0.0'} hasBin: true + '@babel/parser@7.29.7': + resolution: {integrity: sha512-hnORnjP/1P/zFEndoeX+n+t1RwWRJiJpM/jO7FW32Kn9r5+sJB2JWOdYo4L6k78j15eCwY3Gm/7364B1EMwtNg==} + engines: {node: '>=6.0.0'} + hasBin: true + '@babel/plugin-syntax-decorators@7.25.9': resolution: {integrity: sha512-ryzI0McXUPJnRCvMo4lumIKZUzhYUO/ScI+Mz4YVaTLt04DHNSjEUjKVvbzQjZFLuod/cYEc07mJWhzl6v4DPg==} engines: {node: '>=6.9.0'} @@ -715,8 +731,13 @@ packages: resolution: {integrity: sha512-OwS2CM5KocvQ/k7dFJa8i5bNGJP0hXWfVCfDkqRFP1IreH1JDC7wG6eCYCi0+McbfT8OR/kNqsI0UU0xP9H6PQ==} engines: {node: '>=6.9.0'} - '@bcoe/v8-coverage@0.2.3': - resolution: {integrity: sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==} + '@babel/types@7.29.7': + resolution: {integrity: sha512-4zBIxpPzowiZpusoFkyGVwakdRJUyuH5PxQ/PrqghfdFWWasvnCdPfQXHrenDai+gyLARulZjZowCOj6fjT4pA==} + engines: {node: '>=6.9.0'} + + '@bcoe/v8-coverage@1.0.2': + resolution: {integrity: sha512-6zABk/ECA/QYSCQ1NGiVwwbQerUCZ+TQbp64Q3AgmfNvurHH0j8TtXa1qbShXA6qqkpAj4V5W8pP6mLe1mcMqA==} + engines: {node: '>=18'} '@biomejs/biome@1.8.1': resolution: {integrity: sha512-fQXGfvq6DIXem12dGQCM2tNF+vsNHH1qs3C7WeOu75Pd0trduoTmoO7G4ntLJ2qDs5wuw981H+cxQhi1uHnAtA==} @@ -728,6 +749,11 @@ packages: engines: {node: '>=14.21.3'} hasBin: true + '@biomejs/biome@2.5.4': + resolution: {integrity: sha512-xy5FNE5kQJKyK5MR1gJy6ztXYx4WBAbYGlK04lMEgmyPRWKybY9NFwiG9yo0XdzOU8Xvhj41u034J1ywfoWfMw==} + engines: {node: '>=14.21.3'} + hasBin: true + '@biomejs/cli-darwin-arm64@1.8.1': resolution: {integrity: sha512-XLiB7Uu6GALIOBWzQ2aMD0ru4Ly5/qSeQF7kk3AabzJ/kwsEWSe33iVySBP/SS2qv25cgqNiLksjGcw2bHT3mw==} engines: {node: '>=14.21.3'} @@ -740,6 +766,12 @@ packages: cpu: [arm64] os: [darwin] + '@biomejs/cli-darwin-arm64@2.5.4': + resolution: {integrity: sha512-4o3NFRobXHynkgcFVrlZsoDAFtF2ldlEGN8sORSws5ZQqyY4PXnPUIylu4ksfyHuwkfvDREuWh3JK+niRwGq3w==} + engines: {node: '>=14.21.3'} + cpu: [arm64] + os: [darwin] + '@biomejs/cli-darwin-x64@1.8.1': resolution: {integrity: sha512-uMTSxVLMfqkBVqyc25hSn83jBbp+wtWjzM/pHFlKXt3htJuw7FErVGW0nmQ9Sxa9vJ7GcqoltLMl28VQRIMYzg==} engines: {node: '>=14.21.3'} @@ -752,6 +784,12 @@ packages: cpu: [x64] os: [darwin] + '@biomejs/cli-darwin-x64@2.5.4': + resolution: {integrity: sha512-D32P5HkU2Y6PySuC/WsVDTOgsDwVFmujzhhhOQjajtATpVWFDXuVd3oRbsWNSEA+aaFzyzZm22szsyydBYlSyQ==} + engines: {node: '>=14.21.3'} + cpu: [x64] + os: [darwin] + '@biomejs/cli-linux-arm64-musl@1.8.1': resolution: {integrity: sha512-UQ8Wc01J0wQL+5AYOc7qkJn20B4PZmQL1KrmDZh7ot0DvD6aX4+8mmfd/dG5b6Zjo/44QvCKcvkFGCMRYuhWZA==} engines: {node: '>=14.21.3'} @@ -764,6 +802,12 @@ packages: cpu: [arm64] os: [linux] + '@biomejs/cli-linux-arm64-musl@2.5.4': + resolution: {integrity: sha512-Rpm5/AT1m+DlJmUoYvS4/vXc+0tXJPJ2NQz25TGPyHVF5JrWy75PE0GH6kVxsKtQDuCH4OgzquZq0R4kj/wCVg==} + engines: {node: '>=14.21.3'} + cpu: [arm64] + os: [linux] + '@biomejs/cli-linux-arm64@1.8.1': resolution: {integrity: sha512-3SzZRuC/9Oi2P2IBNPsEj0KXxSXUEYRR2kfRF/Ve8QAfGgrt4qnwuWd6QQKKN5R+oYH691qjm+cXBKEcrP1v/Q==} engines: {node: '>=14.21.3'} @@ -776,6 +820,12 @@ packages: cpu: [arm64] os: [linux] + '@biomejs/cli-linux-arm64@2.5.4': + resolution: {integrity: sha512-pSEfW7B8kTsXUjUxC1xVVK+y85Ht3C5XxZ9gclmC7/3Ku9Vqz8jmI7k0p/BNIjQ6t4sFERI2sFeH73ybiZl6YQ==} + engines: {node: '>=14.21.3'} + cpu: [arm64] + os: [linux] + '@biomejs/cli-linux-x64-musl@1.8.1': resolution: {integrity: sha512-fYbP/kNu/rtZ4kKzWVocIdqZOtBSUEg9qUhZaao3dy3CRzafR6u6KDtBeSCnt47O+iLnks1eOR1TUxzr5+QuqA==} engines: {node: '>=14.21.3'} @@ -788,6 +838,12 @@ packages: cpu: [x64] os: [linux] + '@biomejs/cli-linux-x64-musl@2.5.4': + resolution: {integrity: sha512-aby/PohmmgbShcHqFsZVzG8H6D98+P+A6xRWRrQcLW1pCjabcov5UUlke4UqNQBYTkDQav+jB4zyyDDeKB2GaA==} + engines: {node: '>=14.21.3'} + cpu: [x64] + os: [linux] + '@biomejs/cli-linux-x64@1.8.1': resolution: {integrity: sha512-AeBycVdNrTzsyYKEOtR2R0Ph0hCD0sCshcp2aOnfGP0hCZbtFg09D0SdKLbyzKntisY41HxKVrydYiaApp+2uw==} engines: {node: '>=14.21.3'} @@ -800,6 +856,12 @@ packages: cpu: [x64] os: [linux] + '@biomejs/cli-linux-x64@2.5.4': + resolution: {integrity: sha512-FNxojWJkL7EajAuzBgoLe0T2G0y112M4lBrDIFl/DomFTx8yqenYOIdsRLNXvOvBBofE8hJi85LjzLmBDpY7/Q==} + engines: {node: '>=14.21.3'} + cpu: [x64] + os: [linux] + '@biomejs/cli-win32-arm64@1.8.1': resolution: {integrity: sha512-6tEd1H/iFKpgpE3OIB7oNgW5XkjiVMzMRPL8zYoZ036YfuJ5nMYm9eB9H/y81+8Z76vL48fiYzMPotJwukGPqQ==} engines: {node: '>=14.21.3'} @@ -812,6 +874,12 @@ packages: cpu: [arm64] os: [win32] + '@biomejs/cli-win32-arm64@2.5.4': + resolution: {integrity: sha512-emoXexPZIPAZkz2RKmA95WJUqK3I5MJNYtwEbL5ESciRzhmFMMyekDhNG8hpeOaK+ZGRDxAU4wvGuA5IHQ0h0w==} + engines: {node: '>=14.21.3'} + cpu: [arm64] + os: [win32] + '@biomejs/cli-win32-x64@1.8.1': resolution: {integrity: sha512-g2H31jJzYmS4jkvl6TiyEjEX+Nv79a5km/xn+5DARTp5MBFzC9gwceusSSB2AkJKqZzY131AiACAWjKrVt5Ijw==} engines: {node: '>=14.21.3'} @@ -824,6 +892,12 @@ packages: cpu: [x64] os: [win32] + '@biomejs/cli-win32-x64@2.5.4': + resolution: {integrity: sha512-U1jaluLw1qQc2Tx7/CeSoL9N5XcqIH+GWjpUAy1ouB5nVjSCMNO+NNHdY3RAs8zxNurLWAdj6pehQdCA2zyU+Q==} + engines: {node: '>=14.21.3'} + cpu: [x64] + os: [win32] + '@cspotcode/source-map-support@0.8.1': resolution: {integrity: sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==} engines: {node: '>=12'} @@ -874,6 +948,12 @@ packages: cpu: [ppc64] os: [aix] + '@esbuild/aix-ppc64@0.28.1': + resolution: {integrity: sha512-Svl7tq8k/08+p6CXPpRjQ1fKX+1odH/BQbb48fV6fj3CWHhsoIOoY87w1oHXm0qEpkIK3ZfVgp0hed3XBXzXMQ==} + engines: {node: '>=18'} + cpu: [ppc64] + os: [aix] + '@esbuild/android-arm64@0.20.2': resolution: {integrity: sha512-mRzjLacRtl/tWU0SvD8lUEwb61yP9cqQo6noDZP/O8VkwafSYwZ4yWy24kan8jE/IMERpYncRt2dw438LP3Xmg==} engines: {node: '>=12'} @@ -892,6 +972,12 @@ packages: cpu: [arm64] os: [android] + '@esbuild/android-arm64@0.28.1': + resolution: {integrity: sha512-34EGEbCIAgosYz6goLcopX6Mo7NyGv9tfwEM2/7Ce2VcVRk568iSvniGWcUXIy7wEDR1wzolcxcriFVrWYcwBg==} + engines: {node: '>=18'} + cpu: [arm64] + os: [android] + '@esbuild/android-arm@0.20.2': resolution: {integrity: sha512-t98Ra6pw2VaDhqNWO2Oph2LXbz/EJcnLmKLGBJwEwXX/JAN83Fym1rU8l0JUWK6HkIbWONCSSatf4sf2NBRx/w==} engines: {node: '>=12'} @@ -910,6 +996,12 @@ packages: cpu: [arm] os: [android] + '@esbuild/android-arm@0.28.1': + resolution: {integrity: sha512-0k2F129Xdio1TdJfzJ8sy1Q47vUD2NnwdhiAf7drUN1EBTfPf4hsFCtmMgu/6m8JSzsBrlmVjudMBQqOfG8usQ==} + engines: {node: '>=18'} + cpu: [arm] + os: [android] + '@esbuild/android-x64@0.20.2': resolution: {integrity: sha512-btzExgV+/lMGDDa194CcUQm53ncxzeBrWJcncOBxuC6ndBkKxnHdFJn86mCIgTELsooUmwUm9FkhSp5HYu00Rg==} engines: {node: '>=12'} @@ -928,6 +1020,12 @@ packages: cpu: [x64] os: [android] + '@esbuild/android-x64@0.28.1': + resolution: {integrity: sha512-dbwY7ltSMDWsRatcRpCnES4F+im88OCUgGZjy52shC7GqHRE/cYlxNbB4Z4UpJswpcc4Qxd2oE/ufM0p61IKng==} + engines: {node: '>=18'} + cpu: [x64] + os: [android] + '@esbuild/darwin-arm64@0.20.2': resolution: {integrity: sha512-4J6IRT+10J3aJH3l1yzEg9y3wkTDgDk7TSDFX+wKFiWjqWp/iCfLIYzGyasx9l0SAFPT1HwSCR+0w/h1ES/MjA==} engines: {node: '>=12'} @@ -946,6 +1044,12 @@ packages: cpu: [arm64] os: [darwin] + '@esbuild/darwin-arm64@0.28.1': + resolution: {integrity: sha512-TZbWkQY7kvTAXbXUT7uVACR5cMHsDiSz9z7ZKAX/RTq/WJEk3QyRr0wZpNhBDX+/0CtdqUIJlOiodQcta6tY3Q==} + engines: {node: '>=18'} + cpu: [arm64] + os: [darwin] + '@esbuild/darwin-x64@0.20.2': resolution: {integrity: sha512-tBcXp9KNphnNH0dfhv8KYkZhjc+H3XBkF5DKtswJblV7KlT9EI2+jeA8DgBjp908WEuYll6pF+UStUCfEpdysA==} engines: {node: '>=12'} @@ -964,6 +1068,12 @@ packages: cpu: [x64] os: [darwin] + '@esbuild/darwin-x64@0.28.1': + resolution: {integrity: sha512-zfdzgK9ACBNZLI/CyHTOx81SyNbM6YXn7rxSgX97VjyiPl9W1i4Ka4fgKECEoFCKGpvBj5qArWIGgQjOwkgskQ==} + engines: {node: '>=18'} + cpu: [x64] + os: [darwin] + '@esbuild/freebsd-arm64@0.20.2': resolution: {integrity: sha512-d3qI41G4SuLiCGCFGUrKsSeTXyWG6yem1KcGZVS+3FYlYhtNoNgYrWcvkOoaqMhwXSMrZRl69ArHsGJ9mYdbbw==} engines: {node: '>=12'} @@ -982,6 +1092,12 @@ packages: cpu: [arm64] os: [freebsd] + '@esbuild/freebsd-arm64@0.28.1': + resolution: {integrity: sha512-wG2EA8ENdEI0qhkSZMjfqrdY+ziCYCPMmtZjjIwOmXFjmyzEHn+UUxk5of+SYsjtfs3VpnlC7QLzSI5hY/rOAw==} + engines: {node: '>=18'} + cpu: [arm64] + os: [freebsd] + '@esbuild/freebsd-x64@0.20.2': resolution: {integrity: sha512-d+DipyvHRuqEeM5zDivKV1KuXn9WeRX6vqSqIDgwIfPQtwMP4jaDsQsDncjTDDsExT4lR/91OLjRo8bmC1e+Cw==} engines: {node: '>=12'} @@ -1000,6 +1116,12 @@ packages: cpu: [x64] os: [freebsd] + '@esbuild/freebsd-x64@0.28.1': + resolution: {integrity: sha512-i7dZ9vQgnvSCzi/rYCXNgtF/U+eKZNJBzu3eTQbRgHnM7tNSizLOkRFAl3qzVc/Op/u5YkHHa4pf/3DOYHthLQ==} + engines: {node: '>=18'} + cpu: [x64] + os: [freebsd] + '@esbuild/linux-arm64@0.20.2': resolution: {integrity: sha512-9pb6rBjGvTFNira2FLIWqDk/uaf42sSyLE8j1rnUpuzsODBq7FvpwHYZxQ/It/8b+QOS1RYfqgGFNLRI+qlq2A==} engines: {node: '>=12'} @@ -1018,6 +1140,12 @@ packages: cpu: [arm64] os: [linux] + '@esbuild/linux-arm64@0.28.1': + resolution: {integrity: sha512-yHs+0uc8+nvEAfAfxrWQKK5peSNzBc4PegcMO0EJ2hT71uA7vB8Ihg2e77R2P7SG5uYjPbHlLLmve4LLLRCf0g==} + engines: {node: '>=18'} + cpu: [arm64] + os: [linux] + '@esbuild/linux-arm@0.20.2': resolution: {integrity: sha512-VhLPeR8HTMPccbuWWcEUD1Az68TqaTYyj6nfE4QByZIQEQVWBB8vup8PpR7y1QHL3CpcF6xd5WVBU/+SBEvGTg==} engines: {node: '>=12'} @@ -1036,6 +1164,12 @@ packages: cpu: [arm] os: [linux] + '@esbuild/linux-arm@0.28.1': + resolution: {integrity: sha512-qVXBOHQS+d5Y722GwJzJUtOLlX7km3CraOaGormF1pDtPd2C/l1SHRPgjLunLGe51Sh5YYWKMFDyV4SxgMQYTQ==} + engines: {node: '>=18'} + cpu: [arm] + os: [linux] + '@esbuild/linux-ia32@0.20.2': resolution: {integrity: sha512-o10utieEkNPFDZFQm9CoP7Tvb33UutoJqg3qKf1PWVeeJhJw0Q347PxMvBgVVFgouYLGIhFYG0UGdBumROyiig==} engines: {node: '>=12'} @@ -1054,6 +1188,12 @@ packages: cpu: [ia32] os: [linux] + '@esbuild/linux-ia32@0.28.1': + resolution: {integrity: sha512-d1z4ZuP0ajrfz/FhGT4vv278rX8KnPPJx8i5+AtK7TYbx9Le9F1hyzurZpkEyjkGa9dUGhQow4C1NmeGvqxN2w==} + engines: {node: '>=18'} + cpu: [ia32] + os: [linux] + '@esbuild/linux-loong64@0.20.2': resolution: {integrity: sha512-PR7sp6R/UC4CFVomVINKJ80pMFlfDfMQMYynX7t1tNTeivQ6XdX5r2XovMmha/VjR1YN/HgHWsVcTRIMkymrgQ==} engines: {node: '>=12'} @@ -1072,6 +1212,12 @@ packages: cpu: [loong64] os: [linux] + '@esbuild/linux-loong64@0.28.1': + resolution: {integrity: sha512-M5sRjUVZrkm1OAPR3dlOYzNmN+loZKGVi1VUQGrwuqLcbR6qeAz+famMhjASeH3YVKvZz+zT1jlh/keC3Rj/lg==} + engines: {node: '>=18'} + cpu: [loong64] + os: [linux] + '@esbuild/linux-mips64el@0.20.2': resolution: {integrity: sha512-4BlTqeutE/KnOiTG5Y6Sb/Hw6hsBOZapOVF6njAESHInhlQAghVVZL1ZpIctBOoTFbQyGW+LsVYZ8lSSB3wkjA==} engines: {node: '>=12'} @@ -1090,6 +1236,12 @@ packages: cpu: [mips64el] os: [linux] + '@esbuild/linux-mips64el@0.28.1': + resolution: {integrity: sha512-mRObBZeHh2OxcBFPWE/FjylkRgZdYuiTR3vaTozquCGOH14iP9oN4x4Ge81CoIDYQrXmIxpFumJBu5MtZpnQJQ==} + engines: {node: '>=18'} + cpu: [mips64el] + os: [linux] + '@esbuild/linux-ppc64@0.20.2': resolution: {integrity: sha512-rD3KsaDprDcfajSKdn25ooz5J5/fWBylaaXkuotBDGnMnDP1Uv5DLAN/45qfnf3JDYyJv/ytGHQaziHUdyzaAg==} engines: {node: '>=12'} @@ -1108,6 +1260,12 @@ packages: cpu: [ppc64] os: [linux] + '@esbuild/linux-ppc64@0.28.1': + resolution: {integrity: sha512-slScBsMAb3GFDcdrCgLwZtPYRoH2H/youv10QiZyRjmsP48fznoveWytSgCI/R0ZcUgpc0ZhIUEx6LHts8yrfQ==} + engines: {node: '>=18'} + cpu: [ppc64] + os: [linux] + '@esbuild/linux-riscv64@0.20.2': resolution: {integrity: sha512-snwmBKacKmwTMmhLlz/3aH1Q9T8v45bKYGE3j26TsaOVtjIag4wLfWSiZykXzXuE1kbCE+zJRmwp+ZbIHinnVg==} engines: {node: '>=12'} @@ -1126,6 +1284,12 @@ packages: cpu: [riscv64] os: [linux] + '@esbuild/linux-riscv64@0.28.1': + resolution: {integrity: sha512-kw0owk1o0GFETUJyW0jc0G4Yzs0BHZn0JDZ8JRT088vjJYX777BAs1fDGxAC+q831qOs2DTC96mNsG2opdfyyQ==} + engines: {node: '>=18'} + cpu: [riscv64] + os: [linux] + '@esbuild/linux-s390x@0.20.2': resolution: {integrity: sha512-wcWISOobRWNm3cezm5HOZcYz1sKoHLd8VL1dl309DiixxVFoFe/o8HnwuIwn6sXre88Nwj+VwZUvJf4AFxkyrQ==} engines: {node: '>=12'} @@ -1144,6 +1308,12 @@ packages: cpu: [s390x] os: [linux] + '@esbuild/linux-s390x@0.28.1': + resolution: {integrity: sha512-/lAIjX8aYFRByhh6L5rYtPEDRqa9de/4V/juOXcta5frjvzXO4/sqEtyytse0g3zZFuWu5cDN0MkLz2qRDD2Ag==} + engines: {node: '>=18'} + cpu: [s390x] + os: [linux] + '@esbuild/linux-x64@0.20.2': resolution: {integrity: sha512-1MdwI6OOTsfQfek8sLwgyjOXAu+wKhLEoaOLTjbijk6E2WONYpH9ZU2mNtR+lZ2B4uwr+usqGuVfFT9tMtGvGw==} engines: {node: '>=12'} @@ -1162,6 +1332,18 @@ packages: cpu: [x64] os: [linux] + '@esbuild/linux-x64@0.28.1': + resolution: {integrity: sha512-u/anNYF2mmVOEDwLtnQ1wOr3EZ9sTNGLWrsYGYwHWzGA3Si84IOkHXlbWTD1NB+9/1lcnweYKO54uhxZydNzfA==} + engines: {node: '>=18'} + cpu: [x64] + os: [linux] + + '@esbuild/netbsd-arm64@0.28.1': + resolution: {integrity: sha512-oks0DYbLwWMmaakTsCb+zL4E+aHRVLom9IJZOAthMQEPiQmydXHkziYEsGYRx0uNV/IjEKGAV941JzH02pflqw==} + engines: {node: '>=18'} + cpu: [arm64] + os: [netbsd] + '@esbuild/netbsd-x64@0.20.2': resolution: {integrity: sha512-K8/DhBxcVQkzYc43yJXDSyjlFeHQJBiowJ0uVL6Tor3jGQfSGHNNJcWxNbOI8v5k82prYqzPuwkzHt3J1T1iZQ==} engines: {node: '>=12'} @@ -1180,12 +1362,24 @@ packages: cpu: [x64] os: [netbsd] + '@esbuild/netbsd-x64@0.28.1': + resolution: {integrity: sha512-aeL6lAnN89Hz43Mlh1G8ARasbuoYvSITDEx0tHh5b7jJnHcssqgjy9Yx430GDpmCa6OyrKoS0aNRjKundRizGg==} + engines: {node: '>=18'} + cpu: [x64] + os: [netbsd] + '@esbuild/openbsd-arm64@0.23.1': resolution: {integrity: sha512-3x37szhLexNA4bXhLrCC/LImN/YtWis6WXr1VESlfVtVeoFJBRINPJ3f0a/6LV8zpikqoUg4hyXw0sFBt5Cr+Q==} engines: {node: '>=18'} cpu: [arm64] os: [openbsd] + '@esbuild/openbsd-arm64@0.28.1': + resolution: {integrity: sha512-MEFJe5C3R8pwXdZ5Y21oo6m7ePiS0d9pWucn99O/wvyJZChoIQKrQDxKrGeW8F5+T0okTHesAmDeiHDTIq0V/Q==} + engines: {node: '>=18'} + cpu: [arm64] + os: [openbsd] + '@esbuild/openbsd-x64@0.20.2': resolution: {integrity: sha512-eMpKlV0SThJmmJgiVyN9jTPJ2VBPquf6Kt/nAoo6DgHAoN57K15ZghiHaMvqjCye/uU4X5u3YSMgVBI1h3vKrQ==} engines: {node: '>=12'} @@ -1204,6 +1398,18 @@ packages: cpu: [x64] os: [openbsd] + '@esbuild/openbsd-x64@0.28.1': + resolution: {integrity: sha512-i/ZLIOafE0Z8cI/XANJAixoJL/uRAoS2xOA3rb0xN+KK0K177cMAsQYkzHtBrtMXAKuAc7HGgcWiZ/sRC1Nxgw==} + engines: {node: '>=18'} + cpu: [x64] + os: [openbsd] + + '@esbuild/openharmony-arm64@0.28.1': + resolution: {integrity: sha512-ge+Z7EXFNt2BO1oAMsVpiQ8EwndV9i1xXerAeTIK7AtPs3bKFXQM7nlRxDSIUIMeueR1CNXxqztLzdNeReKBJg==} + engines: {node: '>=18'} + cpu: [arm64] + os: [openharmony] + '@esbuild/sunos-x64@0.20.2': resolution: {integrity: sha512-2UyFtRC6cXLyejf/YEld4Hajo7UHILetzE1vsRcGL3earZEW77JxrFjH4Ez2qaTiEfMgAXxfAZCm1fvM/G/o8w==} engines: {node: '>=12'} @@ -1222,6 +1428,12 @@ packages: cpu: [x64] os: [sunos] + '@esbuild/sunos-x64@0.28.1': + resolution: {integrity: sha512-BEjgtECkL3vY+SaSQ6nzVfiALUeFxpawyp8Jmf5PtYhf1Ug40N1h/hxlhts+f1FvSvarEigdxS3BlSMI2PJLcQ==} + engines: {node: '>=18'} + cpu: [x64] + os: [sunos] + '@esbuild/win32-arm64@0.20.2': resolution: {integrity: sha512-GRibxoawM9ZCnDxnP3usoUDO9vUkpAxIIZ6GQI+IlVmr5kP3zUq+l17xELTHMWTWzjxa2guPNyrpq1GWmPvcGQ==} engines: {node: '>=12'} @@ -1240,6 +1452,12 @@ packages: cpu: [arm64] os: [win32] + '@esbuild/win32-arm64@0.28.1': + resolution: {integrity: sha512-lCv9eK/H6ZJWbE7bh2nw54CZ9M2nupBxJcTsdk/QQnWkdSjKGuxmmH8/GWrlT1eMmZfn4dGcCjRte397WqfQXA==} + engines: {node: '>=18'} + cpu: [arm64] + os: [win32] + '@esbuild/win32-ia32@0.20.2': resolution: {integrity: sha512-HfLOfn9YWmkSKRQqovpnITazdtquEW8/SoHW7pWpuEeguaZI4QnCRW6b+oZTztdBnZOS2hqJ6im/D5cPzBTTlQ==} engines: {node: '>=12'} @@ -1258,6 +1476,12 @@ packages: cpu: [ia32] os: [win32] + '@esbuild/win32-ia32@0.28.1': + resolution: {integrity: sha512-zvb/mB2bSCoJOpoCBgYKKpX6YM6mJBlBUVUtVj41DlZJVEB6/0CKlRYxP5wWl1C1ILiCoAU5wZZ4q1P3qeS6Eg==} + engines: {node: '>=18'} + cpu: [ia32] + os: [win32] + '@esbuild/win32-x64@0.20.2': resolution: {integrity: sha512-N49X4lJX27+l9jbLKSqZ6bKNjzQvHaT8IIFUy+YIqmXQdjYCToGWwOItDrfby14c78aDd5NHQl29xingXfCdLQ==} engines: {node: '>=12'} @@ -1276,6 +1500,12 @@ packages: cpu: [x64] os: [win32] + '@esbuild/win32-x64@0.28.1': + resolution: {integrity: sha512-bm4Mowrv+GXMlpWX++EcXw/iLyd1o3+bJkC2DkWXYVvgZCqD/bSj9ctZeAMC3cIxgjRVR2Dufaiu4YPxr5gW1A==} + engines: {node: '>=18'} + cpu: [x64] + os: [win32] + '@expressive-code/core@0.35.6': resolution: {integrity: sha512-xGqCkmfkgT7lr/rvmfnYdDSeTdCSp1otAHgoFS6wNEeO7wGDPpxdosVqYiIcQ8CfWUABh/pGqWG90q+MV3824A==} @@ -1442,14 +1672,6 @@ packages: resolution: {integrity: sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==} engines: {node: '>=12'} - '@istanbuljs/schema@0.1.3': - resolution: {integrity: sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA==} - engines: {node: '>=8'} - - '@jest/schemas@29.6.3': - resolution: {integrity: sha512-mo5j5X+jIZmJQveBKeS/clAueipV7KgiX1vMgCxam1RNYiqE1w62n0/tJJnHtjW8ZHcQco5gY85jA3mi0L+nSA==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} - '@jridgewell/gen-mapping@0.3.5': resolution: {integrity: sha512-IzL8ZoEDIBRWEzlCcRhOaCupYyN5gdIK+Q6fbFdPDg6HqX6jpkItn7DFIpW9LQzXG6Df9sA7+OKnq0qlz/GaQg==} engines: {node: '>=6.0.0'} @@ -1465,15 +1687,18 @@ packages: '@jridgewell/source-map@0.3.6': resolution: {integrity: sha512-1ZJTZebgqllO79ue2bm3rIGud/bOe0pP5BjSRCRxxYkEZS8STV7zN84UBbiYu7jy+eCKSnVIUgoWWE/tt+shMQ==} - '@jridgewell/sourcemap-codec@1.4.15': - resolution: {integrity: sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg==} - '@jridgewell/sourcemap-codec@1.5.0': resolution: {integrity: sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ==} + '@jridgewell/sourcemap-codec@1.5.5': + resolution: {integrity: sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==} + '@jridgewell/trace-mapping@0.3.25': resolution: {integrity: sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==} + '@jridgewell/trace-mapping@0.3.31': + resolution: {integrity: sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==} + '@jridgewell/trace-mapping@0.3.9': resolution: {integrity: sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==} @@ -1682,6 +1907,11 @@ packages: cpu: [arm] os: [android] + '@rollup/rollup-android-arm-eabi@4.62.2': + resolution: {integrity: sha512-6o7ZLZK+BeenkZCFNDXqpbjw9bD6nuWonvS/lwQJp7NoVVxm6p3qE7qQ5jGuBjiFsgvqjD8mZAU5oWxTmbOeOg==} + cpu: [arm] + os: [android] + '@rollup/rollup-android-arm64@4.18.0': resolution: {integrity: sha512-avCea0RAP03lTsDhEyfy+hpfr85KfyTctMADqHVhLAF3MlIkq83CP8UfAHUssgXTYd+6er6PaAhx/QGv4L1EiA==} cpu: [arm64] @@ -1692,6 +1922,11 @@ packages: cpu: [arm64] os: [android] + '@rollup/rollup-android-arm64@4.62.2': + resolution: {integrity: sha512-BaH7BllCACHoH1LguOU56UItGfUWjujlO65kS9LAodViaN4bwIKd7oeW/ZHJ/4ljr/7MIiENnNy3HJ0zXv8Zkw==} + cpu: [arm64] + os: [android] + '@rollup/rollup-darwin-arm64@4.18.0': resolution: {integrity: sha512-IWfdwU7KDSm07Ty0PuA/W2JYoZ4iTj3TUQjkVsO/6U+4I1jN5lcR71ZEvRh52sDOERdnNhhHU57UITXz5jC1/w==} cpu: [arm64] @@ -1702,6 +1937,11 @@ packages: cpu: [arm64] os: [darwin] + '@rollup/rollup-darwin-arm64@4.62.2': + resolution: {integrity: sha512-v39RCCvj4He82I9sFmk+M1VZ0PLM9sfsLVikjfx2hYBNALhrrOR2D3JjQA6AhlaSOgcR+RzrKY7e1+bT6SUO/A==} + cpu: [arm64] + os: [darwin] + '@rollup/rollup-darwin-x64@4.18.0': resolution: {integrity: sha512-n2LMsUz7Ynu7DoQrSQkBf8iNrjOGyPLrdSg802vk6XT3FtsgX6JbE8IHRvposskFm9SNxzkLYGSq9QdpLYpRNA==} cpu: [x64] @@ -1712,6 +1952,21 @@ packages: cpu: [x64] os: [darwin] + '@rollup/rollup-darwin-x64@4.62.2': + resolution: {integrity: sha512-yl0y2vq3S3lHeuXhEdss6TWfKW8vkujImO12tn4ZkG/4oghr09LvdYm2RElVjokTQiUvDUGXLGsYeLqUMCKpGA==} + cpu: [x64] + os: [darwin] + + '@rollup/rollup-freebsd-arm64@4.62.2': + resolution: {integrity: sha512-tT4pvt4qXD+vEoezupCWi+a1F0vvDiksiHc+PxRlYTOH1I6/X4id9jPxTP+Fg+545euaFT1jJVs4CEdHZAU1vw==} + cpu: [arm64] + os: [freebsd] + + '@rollup/rollup-freebsd-x64@4.62.2': + resolution: {integrity: sha512-6nU5F2wCW+qvCBhTn1pdIU3bzsIoF7EUwsCDRxilWGprQR6yd508YnH9+OKFCwpfS8pjZqDUmnCAr7exax0XCg==} + cpu: [x64] + os: [freebsd] + '@rollup/rollup-linux-arm-gnueabihf@4.18.0': resolution: {integrity: sha512-C/zbRYRXFjWvz9Z4haRxcTdnkPt1BtCkz+7RtBSuNmKzMzp3ZxdM28Mpccn6pt28/UWUCTXa+b0Mx1k3g6NOMA==} cpu: [arm] @@ -1722,6 +1977,11 @@ packages: cpu: [arm] os: [linux] + '@rollup/rollup-linux-arm-gnueabihf@4.62.2': + resolution: {integrity: sha512-n1GJHPOvpIfhi3TmrCeh6S6URt9BFCt0KQE3qvexyGCTAKpR4Lg+eWvNZEqu7epxwus/8ElT3hacYEucm49SZg==} + cpu: [arm] + os: [linux] + '@rollup/rollup-linux-arm-musleabihf@4.18.0': resolution: {integrity: sha512-l3m9ewPgjQSXrUMHg93vt0hYCGnrMOcUpTz6FLtbwljo2HluS4zTXFy2571YQbisTnfTKPZ01u/ukJdQTLGh9A==} cpu: [arm] @@ -1732,6 +1992,11 @@ packages: cpu: [arm] os: [linux] + '@rollup/rollup-linux-arm-musleabihf@4.62.2': + resolution: {integrity: sha512-JqgflS8wEB+UXV/vS1RpRbifGBeN4D5lz8D8oOFbFZw4vedvdOgCFAjfBmIMdW3yL10XpQQ0Ambepw6MXrhOnA==} + cpu: [arm] + os: [linux] + '@rollup/rollup-linux-arm64-gnu@4.18.0': resolution: {integrity: sha512-rJ5D47d8WD7J+7STKdCUAgmQk49xuFrRi9pZkWoRD1UeSMakbcepWXPF8ycChBoAqs1pb2wzvbY6Q33WmN2ftw==} cpu: [arm64] @@ -1742,6 +2007,11 @@ packages: cpu: [arm64] os: [linux] + '@rollup/rollup-linux-arm64-gnu@4.62.2': + resolution: {integrity: sha512-wnFJkogWvN4jm/hQRF2UBaeUmk20j5+DmHvoyWii2b8HJDyvz1MF2OU/6ynXt2KR63rbZLWkFpoytpdc/yBuSA==} + cpu: [arm64] + os: [linux] + '@rollup/rollup-linux-arm64-musl@4.18.0': resolution: {integrity: sha512-be6Yx37b24ZwxQ+wOQXXLZqpq4jTckJhtGlWGZs68TgdKXJgw54lUUoFYrg6Zs/kjzAQwEwYbp8JxZVzZLRepQ==} cpu: [arm64] @@ -1752,6 +2022,21 @@ packages: cpu: [arm64] os: [linux] + '@rollup/rollup-linux-arm64-musl@4.62.2': + resolution: {integrity: sha512-HVu2bp0zhvJ8xHEV9+UUs7S90VadmBSY3LcIMvozbPo4AuMGDWlz3ymHLHZPX4hR67TKTt8Qp5PJ5RBg/i+RMQ==} + cpu: [arm64] + os: [linux] + + '@rollup/rollup-linux-loong64-gnu@4.62.2': + resolution: {integrity: sha512-mQqqAV8QaoSgr9I2fKDLY2BAVvmKjWoGiu/cSYQonsLvtqwEn1E4QYfnCOcp5zoEqNhsDYin1s6jx/VJmrxlZg==} + cpu: [loong64] + os: [linux] + + '@rollup/rollup-linux-loong64-musl@4.62.2': + resolution: {integrity: sha512-IxKLoxCQ2IWi6bT2akyDUBGsOImDKB+sPp4EsTmwFQ/fMwpCKm8uLSSgP/Kx/QYUgKis6SEZ5/Nlhup0DIA0PQ==} + cpu: [loong64] + os: [linux] + '@rollup/rollup-linux-powerpc64le-gnu@4.18.0': resolution: {integrity: sha512-hNVMQK+qrA9Todu9+wqrXOHxFiD5YmdEi3paj6vP02Kx1hjd2LLYR2eaN7DsEshg09+9uzWi2W18MJDlG0cxJA==} cpu: [ppc64] @@ -1762,6 +2047,16 @@ packages: cpu: [ppc64] os: [linux] + '@rollup/rollup-linux-ppc64-gnu@4.62.2': + resolution: {integrity: sha512-Mk5ha2RQSgyFfmYYLkBpPnUk8D8FriBxesO1u9O75X0mHgXL1UQcH5Itl2lurWL2tj0RxV9b9tJgipac0hRY9A==} + cpu: [ppc64] + os: [linux] + + '@rollup/rollup-linux-ppc64-musl@4.62.2': + resolution: {integrity: sha512-CjvEnqJL/0/TQ3TXX3OPIJ/kmBellrWd4heXUmHeJlTnmwjKpSJzoehLaL6Xk0ZnMHBu9dZuFADNOrtjF4v+2w==} + cpu: [ppc64] + os: [linux] + '@rollup/rollup-linux-riscv64-gnu@4.18.0': resolution: {integrity: sha512-ROCM7i+m1NfdrsmvwSzoxp9HFtmKGHEqu5NNDiZWQtXLA8S5HBCkVvKAxJ8U+CVctHwV2Gb5VUaK7UAkzhDjlg==} cpu: [riscv64] @@ -1772,6 +2067,16 @@ packages: cpu: [riscv64] os: [linux] + '@rollup/rollup-linux-riscv64-gnu@4.62.2': + resolution: {integrity: sha512-1SiZbzwdkaDURsew/tSOrooKiYy7EQGT6m8ufavAi9NEyQb/6VuIxFXAL1fqa4iZe3g4NbNk4P7J32z2tw5Mgg==} + cpu: [riscv64] + os: [linux] + + '@rollup/rollup-linux-riscv64-musl@4.62.2': + resolution: {integrity: sha512-nQts12zJ3NQRoE6uYljOH89v7szzLDvG2JD/vsX+vGXU8w/At1GowTZ5/7qeFQ8m7L55rpR8Okugnuo5bgjy2Q==} + cpu: [riscv64] + os: [linux] + '@rollup/rollup-linux-s390x-gnu@4.18.0': resolution: {integrity: sha512-0UyyRHyDN42QL+NbqevXIIUnKA47A+45WyasO+y2bGJ1mhQrfrtXUpTxCOrfxCR4esV3/RLYyucGVPiUsO8xjg==} cpu: [s390x] @@ -1782,6 +2087,11 @@ packages: cpu: [s390x] os: [linux] + '@rollup/rollup-linux-s390x-gnu@4.62.2': + resolution: {integrity: sha512-E9/ll019jhPIJgpzfZoIkBGhcz+kKNgVWYRY0zr9srBdPPFVpvOKW8VaJKUbeK+eZXyQF9ltME+Kk6affeaPgg==} + cpu: [s390x] + os: [linux] + '@rollup/rollup-linux-x64-gnu@4.18.0': resolution: {integrity: sha512-xuglR2rBVHA5UsI8h8UbX4VJ470PtGCf5Vpswh7p2ukaqBGFTnsfzxUBetoWBWymHMxbIG0Cmx7Y9qDZzr648w==} cpu: [x64] @@ -1792,6 +2102,11 @@ packages: cpu: [x64] os: [linux] + '@rollup/rollup-linux-x64-gnu@4.62.2': + resolution: {integrity: sha512-5BqxR/pshjey51iliyzTD5Xi3EN0aLmQ2lZ3lvefVV9c82BvrLo2/6OT55iifpWBufs6kdwWbuOKS841DrmK9A==} + cpu: [x64] + os: [linux] + '@rollup/rollup-linux-x64-musl@4.18.0': resolution: {integrity: sha512-LKaqQL9osY/ir2geuLVvRRs+utWUNilzdE90TpyoX0eNqPzWjRm14oMEE+YLve4k/NAqCdPkGYDaDF5Sw+xBfg==} cpu: [x64] @@ -1802,6 +2117,21 @@ packages: cpu: [x64] os: [linux] + '@rollup/rollup-linux-x64-musl@4.62.2': + resolution: {integrity: sha512-uNN83XxQrRAh/w0/pmAfibcwyb6YWt4gP+dpnQKPVJshAloQ785ii8CT8ZCIxkGg9opVsvAlGhFitSm6D1Jjpg==} + cpu: [x64] + os: [linux] + + '@rollup/rollup-openbsd-x64@4.62.2': + resolution: {integrity: sha512-srjEIxSH3LRnJN6THczDHWQplqEMFiAJrTab0msUryh9kwNpkICf3Ea6q6MN/2cZwRFUNx5w+h6Hpi4QuHS6Zg==} + cpu: [x64] + os: [openbsd] + + '@rollup/rollup-openharmony-arm64@4.62.2': + resolution: {integrity: sha512-8hOJnxgbyObnCm5AlRA3A931xX19xq80RjVTKgJOvEKWqJruP/Uf12IbAOaDjjEXYRewwHLfmF0YRIdK3OwKWA==} + cpu: [arm64] + os: [openharmony] + '@rollup/rollup-win32-arm64-msvc@4.18.0': resolution: {integrity: sha512-7J6TkZQFGo9qBKH0pk2cEVSRhJbL6MtfWxth7Y5YmZs57Pi+4x6c2dStAUvaQkHQLnEQv1jzBUW43GvZW8OFqA==} cpu: [arm64] @@ -1812,6 +2142,11 @@ packages: cpu: [arm64] os: [win32] + '@rollup/rollup-win32-arm64-msvc@4.62.2': + resolution: {integrity: sha512-mmF4AY1i0hG/bLWUctUq59gtmgaSIRa3cu/A3JFRp/sCNEme2bgDEiDS22P9FbnJB8NJNF4jPJiSP5RHQpUTDg==} + cpu: [arm64] + os: [win32] + '@rollup/rollup-win32-ia32-msvc@4.18.0': resolution: {integrity: sha512-Txjh+IxBPbkUB9+SXZMpv+b/vnTEtFyfWZgJ6iyCmt2tdx0OF5WhFowLmnh8ENGNpfUlUZkdI//4IEmhwPieNg==} cpu: [ia32] @@ -1822,6 +2157,16 @@ packages: cpu: [ia32] os: [win32] + '@rollup/rollup-win32-ia32-msvc@4.62.2': + resolution: {integrity: sha512-DZgkknc6jhHrk46V25vbAM0zZkyP0nSDkJB8/dRkLTxv470dOmWDqGoEJl/9A0dFfS7yE3REOwNDxpHwSLSt0Q==} + cpu: [ia32] + os: [win32] + + '@rollup/rollup-win32-x64-gnu@4.62.2': + resolution: {integrity: sha512-T6xr6ucWSFto+VGajA8YH26LdpHRuP4YLHEKAtCWvJDOlnmWcDZVCI2Jmjr+IFHDlt2zRaTAKE4tfjTaWLgJBg==} + cpu: [x64] + os: [win32] + '@rollup/rollup-win32-x64-msvc@4.18.0': resolution: {integrity: sha512-UOo5FdvOL0+eIVTgS4tIdbW+TtnBLWg1YBCcU2KWM7nuNwRz9bksDX1bekJJCpu25N1DVWaCwnT39dVQxzqS8g==} cpu: [x64] @@ -1832,6 +2177,11 @@ packages: cpu: [x64] os: [win32] + '@rollup/rollup-win32-x64-msvc@4.62.2': + resolution: {integrity: sha512-BfzEnDJOt9T8M989/lA37EcJgat01wLRnoi5dQf3QzOH7jzpqTAzdDbVfRljVr5r+jzKqpbHeyOfAaXxAd0PAA==} + cpu: [x64] + os: [win32] + '@shikijs/core@1.22.0': resolution: {integrity: sha512-S8sMe4q71TJAW+qG93s5VaiihujRK6rqDFqBnxqvga/3LvqHEnxqBIOPkt//IdXVtHkQWKu4nOQNk0uBGicU7Q==} @@ -1847,8 +2197,8 @@ packages: '@shikijs/vscode-textmate@9.3.0': resolution: {integrity: sha512-jn7/7ky30idSkd/O5yDBfAnVt+JJpepofP/POZ1iMOxK59cOfqIgg/Dj0eFsjOTMw+4ycJN0uhZH/Eb0bs/EUA==} - '@sinclair/typebox@0.27.8': - resolution: {integrity: sha512-+Fj43pSMwJs4KRrH/938Uf+uAELIgVBmQzg/q1YG10djyfA3TnrU8N8XzqCh/okZdszqBQTZf96idMfE5lnwTA==} + '@standard-schema/spec@1.1.0': + resolution: {integrity: sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w==} '@stoplight/http-spec@7.0.3': resolution: {integrity: sha512-r9Y8rT4RbqY7NWqSXjiqtBq0Nme2K5cArSX9gDPeuud8F4CwbizP7xkUwLdwDdHgoJkyIQ3vkFJpHzUVCQeOOA==} @@ -2036,6 +2386,9 @@ packages: '@types/babel__traverse@7.20.6': resolution: {integrity: sha512-r1bzfrm0tomOI8g1SzvCaQHo6Lcv6zu0EA+W2kHrt8dyrHQxGzBBL4kdkzIS+jBMV+EYcMAEAqXqYaLJq5rOZg==} + '@types/chai@5.2.3': + resolution: {integrity: sha512-Mw558oeA9fFbv65/y4mHtXDs9bPnFMZAL/jxdPFUpOHHIXX91mcgEHbS5Lahr+pwZFR8A7GQleRWeI6cGFC2UA==} + '@types/cookie@0.6.0': resolution: {integrity: sha512-4Kh9a6B2bQciAhf7FSuMRRkUWecJgJu9nPnx3yzpsfXX/c50REIqpHY4C82bXP90qrLtXtkDxTZosYO3UpOwlA==} @@ -2045,6 +2398,9 @@ packages: '@types/debug@4.1.12': resolution: {integrity: sha512-vIChWdVG3LG1SMxEvI/AK+FWJthlrqlTu7fbrlywTkkaONwk/UAGaULXRlf8vkzFBLVm0zkMdCquhL5aOjhXPQ==} + '@types/deep-eql@4.0.2': + resolution: {integrity: sha512-c9h9dVVMigMPc4bwTvC5dxqtqJZwQPePsWjPlpSOnojbor6pGqdk541lfA7AqFQr5pB1BRdq0juY9db81BwyFw==} + '@types/estree-jsx@1.0.5': resolution: {integrity: sha512-52CcUVNFyfb1A2ALocQw/Dd1BQFNmSdkuC3BkZ6iqhdMfQz7JWOFRuJFloOzjk+6WijU56m9oKXFAXc7o3Towg==} @@ -2054,6 +2410,9 @@ packages: '@types/estree@1.0.6': resolution: {integrity: sha512-AYnb1nQyY49te+VRAVgmzfcgjYS91mY5P0TKUDCLEM+gNnA+3T6rWITXRLYCpahpqSQbN5cE+gHpnPyXjHWxcw==} + '@types/estree@1.0.9': + resolution: {integrity: sha512-GhdPgy1el4/ImP05X05Uw4cw2/M93BCUmnEvWZNStlCzEKME4Fkk+YpoA5OiHNQmoS7Cafb8Xa3Pya8m1Qrzeg==} + '@types/hast@3.0.4': resolution: {integrity: sha512-WPs+bbQw5aCj+x6laNGWLH3wviHtoCv/P3+otBhbOhJgG8qtpdAMlTCxLtsTWA7LH1Oh/bFCHsBn0TPS5m30EQ==} @@ -2081,6 +2440,9 @@ packages: '@types/node@20.14.2': resolution: {integrity: sha512-xyu6WAMVwv6AKFLB+e/7ySZVr/0zLCzOa7rSpq6jNwpqOrUbcACDWC+53d4n2QHOnDou0fbIsg8wZu/sxrnI4Q==} + '@types/node@22.20.1': + resolution: {integrity: sha512-EANqOCF9QFyra+4pfxUcX9STKJpCLjMbObVzljIJomAWSnuSIEAvyzEU53GaajbXJEgdh0iEcPL+DGvpUd4k1Q==} + '@types/node@22.7.4': resolution: {integrity: sha512-y+NPi1rFzDs1NdQHHToqeiX2TIS79SWEAw9GYhkkx8bD0ChpfqC+n2j5OXOCpzfojBEBt6DnEnnG9MY0zk1XLg==} @@ -2113,6 +2475,7 @@ packages: '@ungap/structured-clone@1.2.0': resolution: {integrity: sha512-zuVdFrMJiuCDQUMCzQaD6KL28MjnqqN8XnAqiEq9PNm/hCPTSGfrXCOfwj1ow4LFb/tNymJPwsNbVePc1xFqrQ==} + deprecated: Potential CWE-502 - Update to 1.3.1 or higher '@vitejs/plugin-react@4.3.1': resolution: {integrity: sha512-m/V2syj5CuVnaxcUJOQRel/Wr31FFXRFlnOoq1TVtkCxsY5veGMTEmpWHndrhB2U8ScHtCQB1e+4hWYExQc6Lg==} @@ -2120,25 +2483,43 @@ packages: peerDependencies: vite: ^4.2.0 || ^5.0.0 - '@vitest/coverage-v8@1.6.0': - resolution: {integrity: sha512-KvapcbMY/8GYIG0rlwwOKCVNRc0OL20rrhFkg/CHNzncV03TE2XWvO5w9uZYoxNiMEBacAJt3unSOiZ7svePew==} + '@vitest/coverage-v8@4.1.10': + resolution: {integrity: sha512-IM49HmthevbgAO4anp1hwtoT9wYe59w0LR00gr+eagHE+ZJ5lK4sLPeO0ubgoJcwLk6dehU3R24N+FbEEKDc8g==} peerDependencies: - vitest: 1.6.0 + '@vitest/browser': 4.1.10 + vitest: 4.1.10 + peerDependenciesMeta: + '@vitest/browser': + optional: true - '@vitest/expect@1.6.0': - resolution: {integrity: sha512-ixEvFVQjycy/oNgHjqsL6AZCDduC+tflRluaHIzKIsdbzkLn2U/iBnVeJwB6HsIjQBdfMR8Z0tRxKUsvFJEeWQ==} + '@vitest/expect@4.1.10': + resolution: {integrity: sha512-YsCn+qAk1GWjQOWFEsEcL2gNQ0zmVmQu3T03qP6UyjhtmdtwtbuI+DASn/7iQB3HGTXkdBwGddzxPlmiql5vlA==} - '@vitest/runner@1.6.0': - resolution: {integrity: sha512-P4xgwPjwesuBiHisAVz/LSSZtDjOTPYZVmNAnpHHSR6ONrf8eCJOFRvUwdHn30F5M1fxhqtl7QZQUk2dprIXAg==} + '@vitest/mocker@4.1.10': + resolution: {integrity: sha512-v0xaezt+DKEmKfaxg133ldzADrwLGd7Ze1MfQQTYfvs8OqZIwbxyxaYURivwV7sWy5fqn3rH5uOrSp07bp44Ow==} + peerDependencies: + msw: ^2.4.9 + vite: ^6.0.0 || ^7.0.0 || ^8.0.0 + peerDependenciesMeta: + msw: + optional: true + vite: + optional: true + + '@vitest/pretty-format@4.1.10': + resolution: {integrity: sha512-W1HsjSH4MXQ9YfmmhLAoIYf1HRfekQCGngeIgcei6MP5QQGWUe0gkopdZQaVCFO+JDJMrAJGwa5pRpNpvy4P8Q==} + + '@vitest/runner@4.1.10': + resolution: {integrity: sha512-IKI6kpIH+LmpROplyLwBBaCfMgOZOMsygVa6BARD6ahA04VRuJSa6OaVG7kRvSEMD870Vd91rSSw0eegtWyLGg==} - '@vitest/snapshot@1.6.0': - resolution: {integrity: sha512-+Hx43f8Chus+DCmygqqfetcAZrDJwvTj0ymqjQq4CvmpKFSTVteEOBzCusu1x2tt4OJcvBflyHUE0DZSLgEMtQ==} + '@vitest/snapshot@4.1.10': + resolution: {integrity: sha512-xRkfOT1qpTAi/Ti4Y1LtfRc3kEuqxGw59eN2jN9pRWMtS/XDevekhcFSqvQqjUNGksfjMJu3Y+oJ+4Ypn2OaJw==} - '@vitest/spy@1.6.0': - resolution: {integrity: sha512-leUTap6B/cqi/bQkXUu6bQV5TZPx7pmMBKBQiI0rJA8c3pB56ZsaTbREnF7CJfmvAS4V2cXIBAh/3rVwrrCYgw==} + '@vitest/spy@4.1.10': + resolution: {integrity: sha512-PLf/Ugvoq5wO/b4rwYCR1h2PSIdXz7wnkQFMiUpLdtM7l6pqVFcQIBEHyT1+l+cj7mNwAfZHzqXqDyjvOuwbDw==} - '@vitest/utils@1.6.0': - resolution: {integrity: sha512-21cPiuGMoMZwiOHa2i4LXkMkMkCGzA+MVFV70jRwHo95dL4x/ts5GZhML1QWuy7yfp3WzK3lRvZi3JnXTYqrBw==} + '@vitest/utils@4.1.10': + resolution: {integrity: sha512-fy9am/HWxbaGt/Sawrp90vt6Y6jQwf1RX77cz3uwoJwJVMli/e1IEwRPnMNJ7vKfPTwo0diXifkpPvwH9v7nGA==} '@volar/kit@2.4.6': resolution: {integrity: sha512-OaMtpmLns6IYD1nOSd0NdG/F5KzJ7Jr4B7TLeb4byPzu+ExuuRVeO56Dn1C7Frnw6bGudUQd90cpQAmxdB+RlQ==} @@ -2185,11 +2566,6 @@ packages: resolution: {integrity: sha512-cjkyv4OtNCIeqhHrfS81QWXoCBPExR/J62oyEqepVw8WaQeSqpW2uhuLPh1m9eWhDuOo/jUXVTlifvesOWp/4A==} engines: {node: '>=0.4.0'} - acorn@8.11.3: - resolution: {integrity: sha512-Y9rRfJG5jcKOE0CLisYbojUjIrIEE7AGMzA/Sm4BslANhbS+cDMpgBdcPT91oJ7OuJ9hYJBx59RjbhxVnrF8Xg==} - engines: {node: '>=0.4.0'} - hasBin: true - acorn@8.12.1: resolution: {integrity: sha512-tcpGyI9zbizT9JbV6oYE477V6mTlXvvi0T0G3SNIYE2apm/G5huBa1+K89VGeovbg+jycCrfhl3ADxErOuO6Jg==} engines: {node: '>=0.4.0'} @@ -2233,10 +2609,6 @@ packages: resolution: {integrity: sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==} engines: {node: '>=8'} - ansi-styles@5.2.0: - resolution: {integrity: sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==} - engines: {node: '>=10'} - ansi-styles@6.2.1: resolution: {integrity: sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==} engines: {node: '>=12'} @@ -2278,8 +2650,12 @@ packages: resolution: {integrity: sha512-bMxMKAjg13EBSVscxTaYA4mRc5t1UAXa2kXiGTNfZ079HIWXEkKmkgFrh/nJqamaLSrXO5H4WFFkPEaLJWbs3A==} engines: {node: '>= 0.4'} - assertion-error@1.1.0: - resolution: {integrity: sha512-jgsaNduz+ndvGyFt3uSuWqvy4lCnIJiovtouQN5JZHOKCS2QuhEdbcQHFhVksz2N2U9hXJo8odG7ETyWlEeuDw==} + assertion-error@2.0.1: + resolution: {integrity: sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==} + engines: {node: '>=12'} + + ast-v8-to-istanbul@1.0.5: + resolution: {integrity: sha512-UPAgKJFSEGMWSDr3LX4tqnAb4f7KGT8O40Tyx8wbYmmZ/yn58lNCm8h3svs3eXgiGd5AXxz8NDOvXWvicq+rJA==} astring@1.9.0: resolution: {integrity: sha512-LElXdjswlqjWrPpJFg1Fx4wpkOCxj1TDHlSV4PlaRxHGWko024xICaa97ZkMfs6DRKlCguiAI+rbXv5GWwXIkg==} @@ -2325,6 +2701,10 @@ packages: balanced-match@1.0.2: resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==} + balanced-match@4.0.4: + resolution: {integrity: sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==} + engines: {node: 18 || 20 || >=22} + bare-events@2.5.0: resolution: {integrity: sha512-/E8dDe9dsbLyh2qrZ64PEPadOQ0F4gbl1sUJOrmph7xOiIxfY8vwab/4bFLh4Y88/Hk/ujKcrQKc+ps0mv873A==} @@ -2383,6 +2763,10 @@ packages: brace-expansion@2.0.1: resolution: {integrity: sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==} + brace-expansion@5.0.7: + resolution: {integrity: sha512-7oFy703dxfY3/NLxC1fh2SUCQ0H9rmAY+5EpDVfXjUTTs+HEwR2nYaqLv+GWcTsumwxPfiz6CzCNkwXwBUwqCA==} + engines: {node: 18 || 20 || >=22} + braces@3.0.3: resolution: {integrity: sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==} engines: {node: '>=8'} @@ -2461,9 +2845,9 @@ packages: ccount@2.0.1: resolution: {integrity: sha512-eyrF0jiFpY+3drT6383f1qhkbGsLSifNAjA61IUjZjmLCWjItY6LB9ft9YhoDgwfmclB2zhu51Lc7+95b8NRAg==} - chai@4.4.1: - resolution: {integrity: sha512-13sOfMv2+DWduEU+/xbun3LScLoqN17nBeTLUsmDfKdoiC1fr0n9PU4guu4AhRcOVFk/sW8LyZWHuhWtQZiF+g==} - engines: {node: '>=4'} + chai@6.2.2: + resolution: {integrity: sha512-NUPRluOfOiTKBKvWPtSD4PhFvWCqOi0BGStNWs57X9js7XGTprSmFoz5F0tWhR4WPjNeR9jXqdC7/UpSJTnlRg==} + engines: {node: '>=18'} chalk@2.4.2: resolution: {integrity: sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==} @@ -2493,9 +2877,6 @@ packages: resolution: {integrity: sha512-6dVyOOYjpfFcL1Y4qChrAoQLRHvj2ziyhcm0QJlhOcAhykL/k1kTUPbeo+87MNRTRdk2OIIsIXbuF3x2wi5EXg==} engines: {node: '>=4.0.0'} - check-error@1.0.3: - resolution: {integrity: sha512-iKEoDYaRmd1mxM90a2OEfWhjsjPpYPuQ+lMYsoxB126+t8fw7ySEO48nmDg5COTjxDI65/Y2OWpeEHk3ZOe8zg==} - chokidar@3.6.0: resolution: {integrity: sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==} engines: {node: '>= 8.10.0'} @@ -2578,10 +2959,6 @@ packages: comma-separated-tokens@2.0.3: resolution: {integrity: sha512-Fu4hJdvzeylCfQPp9SGWidpzrMs7tTrlu6Vb8XGaRGck8QSNZJJp538Wrb60Lax4fPwR64ViY468OIUTbRlGZg==} - commander@12.1.0: - resolution: {integrity: sha512-Vw8qHK3bZM9y/P10u3Vib8o/DdkvA2OtPtZvD871QKjy74Wj1WSKFILMPRPSdUSx5RFK1arlJzEtA4PkFgnbuA==} - engines: {node: '>=18'} - commander@15.0.0: resolution: {integrity: sha512-z67u4ZhzCL/Tydu1lJARtEZYWbWaN7oYLHbsuzocr6y4N6WZAagG3RQ4FW61V1/0+jImpj293XfrcYnd1qxtPg==} engines: {node: '>=22.12.0'} @@ -2613,9 +2990,6 @@ packages: concat-map@0.0.1: resolution: {integrity: sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==} - confbox@0.1.7: - resolution: {integrity: sha512-uJcB/FKZtBMCJpK8MQji6bJHgu1tixKPxRLeGkNzBoOZzpnZUJm0jm2/sBDWcuBx1dYgxV4JU+g5hmNxCyAmdA==} - confbox@0.2.4: resolution: {integrity: sha512-ysOGlgTFbN2/Y6Cg3Iye8YKulHw+R2fNXHrgSmXISQdMnomY6eNDprVdW9R5xBguEqI954+S6709UyiO7B+6OQ==} @@ -2730,10 +3104,6 @@ packages: babel-plugin-macros: optional: true - deep-eql@4.1.4: - resolution: {integrity: sha512-SUwdGfqdKOwxCPeVYjwSyRpJ7Z+fhpwIAtmCUdZIWZ/YP5R9WAsyuSgpLVDi9bjWoN2LXHNss/dk3urXtdQxGg==} - engines: {node: '>=6'} - deep-extend@0.6.0: resolution: {integrity: sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==} engines: {node: '>=4.0.0'} @@ -2797,10 +3167,6 @@ packages: didyoumean@1.2.2: resolution: {integrity: sha512-gxtyfqMg7GKyhQmb056K7M3xszy/myH8w+B4RT+QXBQsvAOdc3XymqDDPHx1BgPgsdAA5SIifona89YtRATDzw==} - diff-sequences@29.6.3: - resolution: {integrity: sha512-EjePK1srD3P08o2j4f0ExnylqRs5B9tJjcp9t1krH2qRi8CCdsYfwe9JgSLurFBWwq4uOlipzfk5fHNvwFKr8Q==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} - diff@4.0.2: resolution: {integrity: sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==} engines: {node: '>=0.3.1'} @@ -2887,6 +3253,9 @@ packages: es-module-lexer@1.5.4: resolution: {integrity: sha512-MVNK56NiMrOwitFB7cqDwq0CQutbw+0BvLshJSse0MUNU+y1FC3bUS/AQg7oUng+/wKrrki7JfmwtVHkVfPLlw==} + es-module-lexer@2.3.1: + resolution: {integrity: sha512-shc1dbU90Yl/xq1QrC7QRtfcwURZuVRfPhZbDoldJ1cn1gzDvBaBWlv0eFolj5+0znnPJz5TXLxsN77X/12KTA==} + es-object-atoms@1.0.0: resolution: {integrity: sha512-MZ4iQ6JwHOBQjahnjwaC1ZtIBH+2ohjamzAO3oaHcXYup7qxjF2fixyH+Q71voWHeOkI2q/TnJao/KfXYIZWbw==} engines: {node: '>= 0.4'} @@ -2914,6 +3283,11 @@ packages: engines: {node: '>=18'} hasBin: true + esbuild@0.28.1: + resolution: {integrity: sha512-HrJrvZv5ayxBzPfwphOoNzkzOIIlifzk0KJrGK2c8R4+LKpMtpYLQeUdjnwjWv/LZlkH2laZk+4w78pi99D4Vw==} + engines: {node: '>=18'} + hasBin: true + escalade@3.1.2: resolution: {integrity: sha512-ErCHMCae19vR8vQGe50xIsVomy19rg6gFu3+r3jkEO46suLMWBksvVyoGgQV+jOfl84ZSOSlmv6Gxa89PmTGmA==} engines: {node: '>=6'} @@ -2966,10 +3340,6 @@ packages: eventemitter3@5.0.1: resolution: {integrity: sha512-GWkBvjiSZK87ELrYOSESUYeVIc9mvLLf/nXalMOS5dYrgZq9o5OVkbZAVM06CVxYsCwH9BDZFPlQTlPA1j4ahA==} - execa@8.0.1: - resolution: {integrity: sha512-VyhnebXciFV2DESc+p6B+y0LjSm0krU4OgJN44qFAhBY0TJ+1V61tYD2+wHusZ6F9n5K+vl8k0sTy7PEfV4qpg==} - engines: {node: '>=16.17'} - exit-hook@2.2.1: resolution: {integrity: sha512-eNTPlAD67BmP31LDINZ3U7HSF8l57TxOY2PmBJ1shpCvpnxBF93mWCE8YHBnXs8qiUZJc9WDcWIeC3a2HIAMfw==} engines: {node: '>=6'} @@ -2978,6 +3348,10 @@ packages: resolution: {integrity: sha512-XYfuKMvj4O35f/pOXLObndIRvyQ+/+6AhODh+OKWj9S9498pHHn/IMszH+gt0fBCRWMNfk1ZSp5x3AifmnI2vg==} engines: {node: '>=6'} + expect-type@1.4.0: + resolution: {integrity: sha512-KfYbmpRm0VbLjEvVa9yGwCi9GI34xvi7A/HXYWQO65CSD2u3MczUJSuwXKFIxlGsgBQizV9q5J9NHj4VG0n+pA==} + engines: {node: '>=12.0.0'} + express@4.21.1: resolution: {integrity: sha512-YSFlK1Ee0/GC8QaO91tHcDxJiE/X4FbpAyQWkxAvG6AXCuR65YzK8ua6D9hvi/TzUfZMpc+BwuM1IPw8fmQBiQ==} engines: {node: '>= 0.10.0'} @@ -3116,9 +3490,6 @@ packages: resolution: {integrity: sha512-oRXApq54ETRj4eMiFzGnHWGy+zo5raudjuxN0b8H7s/RU2oW0Wvsx9O0ACRN/kRq9E8Vu/ReskGB5o3ji+FzHQ==} engines: {node: '>=12'} - fs.realpath@1.0.0: - resolution: {integrity: sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==} - fsevents@2.3.3: resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==} engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} @@ -3146,9 +3517,6 @@ packages: resolution: {integrity: sha512-2nk+7SIVb14QrgXFHcm84tD4bKQz0RxPuMT8Ag5KPOq7J5fEmAg0UbXdTOSHqNuHSU28k55qnceesxXRZGzKWA==} engines: {node: '>=18'} - get-func-name@2.0.2: - resolution: {integrity: sha512-8vXOvuE167CtIc3OyItco7N/dpRtBbYOsPsXCz7X/PMnlGjYjSGuZJgM1Y7mmew7BKf9BqvLX2tnOVy1BBUsxQ==} - get-intrinsic@1.2.4: resolution: {integrity: sha512-5uYhsJH8VJBTv7oslg4BznJYhDoRI6waYCxMmCdnTrcCrHA/fCFKoTFz2JKKE0HdDFUF7/oQuhzumXJK7paBRQ==} engines: {node: '>= 0.4'} @@ -3157,10 +3525,6 @@ packages: resolution: {integrity: sha512-g/Q1aTSDOxFpchXC4i8ZWvxA1lnPqx/JHqcpIw0/LX9T8x/GBbi6YnlN5nhaKIFkT8oFsscUKgDJYxfwfS6QsQ==} engines: {node: '>=8'} - get-stream@8.0.1: - resolution: {integrity: sha512-VaUJspBffn/LMCJVoMvSAdmscJyS1auj5Zulnn5UoYcY531UWmdwhRWkcGKnGU93m5HSXP9LP2usOryrBtQowA==} - engines: {node: '>=16'} - get-symbol-description@1.0.2: resolution: {integrity: sha512-g0QYk1dZBxGwk+Ngc+ltRH2IBp2f7zBkBMBJZCDerh6EhlhSR6+9irMCuT/09zD6qkarHUSn529sK/yL4S27mg==} engines: {node: '>= 0.4'} @@ -3195,9 +3559,9 @@ packages: deprecated: Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me hasBin: true - glob@7.2.3: - resolution: {integrity: sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==} - deprecated: Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me + glob@13.0.6: + resolution: {integrity: sha512-Wjlyrolmm8uDpm/ogGyXZXb1Z+Ca2B8NbJwqBVg0axK9GbBeoS7yGV6vjXnYdGm6X53iehEuxxbyiKp8QmN4Vw==} + engines: {node: 18 || 20 || >=22} globals@11.12.0: resolution: {integrity: sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==} @@ -3359,10 +3723,6 @@ packages: resolution: {integrity: sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==} engines: {node: '>= 6'} - human-signals@5.0.0: - resolution: {integrity: sha512-AXcZb6vzzrFAUE61HnN4mpLqd/cSIwNQjtNWR0euPm6y0iqx3G4gOXaIDdtdDwZmhwe82LA6+zinmW4UBWVePQ==} - engines: {node: '>=16.17.0'} - i18next@23.15.2: resolution: {integrity: sha512-zcPSWzCvw6uKnuYHIqs4W7hTuB9e3AFcSdZgvCWoPXIZsBjBd4djN2/2uOHIB+1DFFkQnMBXvhNg7J3WyCuywQ==} @@ -3380,10 +3740,6 @@ packages: import-meta-resolve@4.1.0: resolution: {integrity: sha512-I6fiaX09Xivtk+THaMfAwnA3MVA5Big1WHF1Dfx9hFuvNIWpXnorlkzhcQf6ehrqQiiZECRt1poOAkPmer3ruw==} - inflight@1.0.6: - resolution: {integrity: sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==} - deprecated: This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful. - inherits@2.0.4: resolution: {integrity: sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==} @@ -3525,10 +3881,6 @@ packages: resolution: {integrity: sha512-nA2hv5XIhLR3uVzDDfCIknerhx8XUKnstuOERPNNIinXG7v9u+ohXF67vxm4TPTEPU6lm61ZkwP3c9PCB97rhg==} engines: {node: '>= 0.4'} - is-stream@3.0.0: - resolution: {integrity: sha512-LnQR4bZ9IADDRSkvpqMGvt/tEJWclzklNgSw48V5EAaAeDd6qGvN8ei6k5p0tvxSR171VmGyHuTiAOfxAbr8kA==} - engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} - is-string@1.0.7: resolution: {integrity: sha512-tE2UXzivje6ofPW7l23cjDOMa09gb7xlAqG6jG5ej6uPV32TlWP3NKPigtaGeHNu9fohccRYvIiZMfOOnOYUtg==} engines: {node: '>= 0.4'} @@ -3580,12 +3932,8 @@ packages: resolution: {integrity: sha512-GCfE1mtsHGOELCU8e/Z7YWzpmybrx/+dSTfLrvY8qRmaY6zXTKWn6WQIjaAFw069icm6GVMNkgu0NzI4iPZUNw==} engines: {node: '>=10'} - istanbul-lib-source-maps@5.0.4: - resolution: {integrity: sha512-wHOoEsNJTVltaJp8eVkm8w+GVkVNHT2YDYo53YdzQEL2gWm1hBX5cGFR9hQJtuGLebidVX7et3+dmDZrmclduw==} - engines: {node: '>=10'} - - istanbul-reports@3.1.7: - resolution: {integrity: sha512-BewmUXImeuRk2YY0PVbxgKAysvhRPUQE0h5QRM++nVWyubKGV0l8qQ5op8+B2DOmwSe63Jivj0BjkPQVf8fP5g==} + istanbul-reports@3.2.0: + resolution: {integrity: sha512-HGYWWS/ehqTV3xN10i23tkPkpH46MLCIMFNCaaKNavAXTF1RkqxawEPtnjnGZ6XKSInBKkiOA5BKS+aZiY3AvA==} engines: {node: '>=8'} jackspeak@3.4.0: @@ -3600,12 +3948,12 @@ packages: resolution: {integrity: sha512-ekilCSN1jwRvIbgeg/57YFh8qQDNbwDb9xT/qu2DAHbFFZUicIl4ygVaAvzveMhMVr3LnpSKTNnwt8PoOfmKhQ==} hasBin: true + js-tokens@10.0.0: + resolution: {integrity: sha512-lM/UBzQmfJRo9ABXbPWemivdCW8V2G8FHaHdypQaIy523snUjog0W71ayWXTjiR+ixeMyVHN2XcpnTd/liPg/Q==} + js-tokens@4.0.0: resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==} - js-tokens@9.0.0: - resolution: {integrity: sha512-WriZw1luRMlmV3LGJaR6QOJjWwgLUTf89OwT2lUOyjX2dJGBwgmIkbcz+7WFZjrZM635JOIR517++e/67CP9dQ==} - js-yaml@3.14.1: resolution: {integrity: sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==} hasBin: true @@ -3688,48 +4036,58 @@ packages: resolution: {integrity: sha512-o+NO+8WrRiQEE4/7nwRJhN1HWpVmJm511pBHUxPLtp0BUISzlBplORYSmTclCnJvQq2tKu/sgl3xVpkc7ZWuQQ==} engines: {node: '>=6'} - lefthook-darwin-arm64@1.6.15: - resolution: {integrity: sha512-PQKFipNueV2i/W3XI+fDDwzV3YdnJ1AqwIP2BwDpzlSJtQQarsAG7lRvFdjkPGplVLqWoohTQa1/ooBmg+g3dw==} + lefthook-darwin-arm64@2.1.10: + resolution: {integrity: sha512-nw+X8wRNDoUUV6WSteyKBbcLySq+fsmZt5WV/s50ZJpysmsDKJOUMln6SllNfP+60dzUahAO7REco/2633BsLg==} cpu: [arm64] os: [darwin] - lefthook-darwin-x64@1.6.15: - resolution: {integrity: sha512-dNAZp281EJ1vyovszftVO+uk/xJZbomrtfuQeZ3tAk8Xybu6b4+XSoBklH5eRfl46/TWUNVkSF5owYG6+ZtvIA==} + lefthook-darwin-x64@2.1.10: + resolution: {integrity: sha512-KQ/bHmvpkFdHMn4pZnUdTf+GuSC+aBBgBTxZT4GW+6cSf+qbErKZBhK7cH6BmILsvx43+VzEArvHYY7YOfRFOQ==} cpu: [x64] os: [darwin] - lefthook-freebsd-arm64@1.6.15: - resolution: {integrity: sha512-GWGt5jDLOcICjsoPZV4tFjjQJ3v9uNqHXg80QXx+Pb7HSqLFp3OnUEfjV2IO27lOln7+AMTF6WWigJl/NKllKw==} + lefthook-freebsd-arm64@2.1.10: + resolution: {integrity: sha512-8su6DwydP7+pv7kG0zCtjphqsw4ouOnfexRUErapy5GTxYBoUOhYz3RSHTSWNRsK6W4jva7FPUh2Lp5/PSn30w==} cpu: [arm64] os: [freebsd] - lefthook-freebsd-x64@1.6.15: - resolution: {integrity: sha512-803r+OYRpY5CBa8LU83EINO+Mi5k7rfflApMJuEIzcH1pFlEjbLttGy2hJX19m1kTKzkz/HuzFl6znbkmZGttw==} + lefthook-freebsd-x64@2.1.10: + resolution: {integrity: sha512-GeAJEFxko3Lk+AsnS3NleAFrpyMLFUKOlgJvPKuU0xHwVEI/z+ZoCcmuO0BX+4CS0NLbZhC/YQAvBASqDvvVdQ==} cpu: [x64] os: [freebsd] - lefthook-linux-arm64@1.6.15: - resolution: {integrity: sha512-4rATbRhhBj4VNnvAEGRXvQL+POO4xwdUdCc2aSBcKdRFnabYabWm9ebSp55id7nDt3mVf7FBKDCl7A3kzUxehQ==} + lefthook-linux-arm64@2.1.10: + resolution: {integrity: sha512-1sHTCmpTWjVMs+yKPBLRNT1kuuIr1yjietlk7rCB6wFPVOS6Ph3o2zPFH2AvW1UymHlqwyHXzBr9EtDpQ7j1mQ==} cpu: [arm64] os: [linux] - lefthook-linux-x64@1.6.15: - resolution: {integrity: sha512-VhDL/po/EujilZKq14frjzOgApHrI1bNfghvuWkNz+5LGphe1/iSXV1DKDDOrGprl/vp2p+QUaJW8HMKBiKgTw==} + lefthook-linux-x64@2.1.10: + resolution: {integrity: sha512-z/VlRB3bh6mBvW3r1rwnJ5vP8z+Krx5gJzkZ4veDXh+6FlRTx8wtd3g3fllOv/yZMxkgmL3fQoFXv05Esa7vBQ==} cpu: [x64] os: [linux] - lefthook-windows-arm64@1.6.15: - resolution: {integrity: sha512-0HcX/tPEktPjkFVrAIUXzlWIN5VhkBqbYl7xofWoYMjdYetFU2dvC2UDqp/ENVA/PxelszgTVLUVaI0RVJHDoA==} + lefthook-openbsd-arm64@2.1.10: + resolution: {integrity: sha512-430zL8sSIKw5P0YXGG6PB+eAhHa06n0PXuaERaAQE4Ss3odfqwnl5Mq9hQmkEnOS1EGiQEKkd0UHv/i4PtMNIQ==} + cpu: [arm64] + os: [openbsd] + + lefthook-openbsd-x64@2.1.10: + resolution: {integrity: sha512-bgkO8PphGZVDhQgCJ524aYYPI5491pVmCiLPGjBIo1AvOSlIyw4N1Y+1C3QfqwEmechzw+Aq16SNc8pqv6UuXg==} + cpu: [x64] + os: [openbsd] + + lefthook-windows-arm64@2.1.10: + resolution: {integrity: sha512-5Q6etF0Fla2DDA4ilDySrdNgiR5+W7cJZwnZ69Je3kvWCaWm4wnkuc8FEdjp3kiL2x3ZXipdI00f5vpO8aWmog==} cpu: [arm64] os: [win32] - lefthook-windows-x64@1.6.15: - resolution: {integrity: sha512-oBTfUbJRNOSuR1XsS5frGPCY8p74KXNVOuMX+Oun6kyBSutqe3kmafZ3nytbugJdzGx4bGfYxLISM8EoEkgThA==} + lefthook-windows-x64@2.1.10: + resolution: {integrity: sha512-c/XH8YZtylG4XaxzqFfXluvq2LXq2W/p54Bnzn3+Z7E5X2Fk3JlFJAibulMbIt2+w8T7UI/r97ok5GqE4kGaeA==} cpu: [x64] os: [win32] - lefthook@1.6.15: - resolution: {integrity: sha512-Jjsz5ln/khEBEWH0ZWtK4A14F5aIGk3iwfyHpqqnxpF79OQR8MYCUN2VzpTk5XgzbokMi/M7CJ17/LPAYBRUEw==} + lefthook@2.1.10: + resolution: {integrity: sha512-K7mM4WoqMwqfXYK11EHy+lSH1uW8XHni3Yn/bSqyerPkUPygGdf3xn18JoV5HyA06xuQL3ofGAOjG01QX9oJ4w==} hasBin: true lilconfig@2.1.0: @@ -3755,10 +4113,6 @@ packages: resolution: {integrity: sha512-OfCBkGEw4nN6JLtgRidPX6QxjBQGQf72q3si2uvqyFEMbycSFFHwAZeXx6cJgFM9wmLrf9zBwCP3Ivqa+LLZPw==} engines: {node: '>=6'} - local-pkg@0.5.0: - resolution: {integrity: sha512-ok6z3qlYyCDS4ZEU27HaU6x/xZa9Whf8jD4ptH5UZTQYZVYeb9bnZ3ojVhiJNLiXK1Hfc0GNbLXcmZ5plLDDBg==} - engines: {node: '>=14'} - locate-path@2.0.0: resolution: {integrity: sha512-NCI2kiDkyR7VeEKm27Kda/iQHyKJe1Bu0FlTbYp3CqJu+9IFe9bLyAjMxf5ZDDbEg+iMPzB5zYyUTSm8wVTKmA==} engines: {node: '>=4'} @@ -3784,13 +4138,14 @@ packages: resolution: {integrity: sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==} hasBin: true - loupe@2.3.7: - resolution: {integrity: sha512-zSMINGVYkdpYSOBmLi0D1Uo7JU9nVdQKrHxC8eYlV+9YKK9WePqAlL7lSlorG/U2Fw1w0hTBmaa/jrQ3UbPHtA==} - lru-cache@10.2.2: resolution: {integrity: sha512-9hp3Vp2/hFQUiIwKo8XCeFVnrg8Pk3TYNPIR7tJADKi5YfcF7vEaK7avFHTlSy3kOKYaJQaalfEo6YuXdceBOQ==} engines: {node: 14 || >=16.14} + lru-cache@11.5.2: + resolution: {integrity: sha512-4pfM1Ff0x50o0tQwb5ucw/RzNyD0/YJME6IVcStalZuMWxdt3sR3huStTtxz4PUmvZfRguvDejasvQ2kifR11g==} + engines: {node: 20 || >=22} + lru-cache@5.1.1: resolution: {integrity: sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==} @@ -3802,18 +4157,18 @@ packages: resolution: {integrity: sha512-jumlc0BIUrS3qJGgIkWZsyfAM7NCWiBcCDhnd+3NNM5KbBmLTgHVfWBcg6W+rLUsIpzpERPsvwUP7CckAQSOoA==} engines: {node: '>=12'} - magic-string@0.30.10: - resolution: {integrity: sha512-iIRwTIf0QKV3UAnYK4PU8uiEc4SRh5jX0mwpIwETPpHdhVM4f53RSwS/vXvN1JhGX+Cs7B8qIq3d6AH49O5fAQ==} - magic-string@0.30.11: resolution: {integrity: sha512-+Wri9p0QHMy+545hKww7YAu5NyzF8iomPL/RQazugQ9+Ez4Ic3mERMd8ZTX5rfK944j+560ZJi8iAwgak1Ac7A==} - magicast@0.3.4: - resolution: {integrity: sha512-TyDF/Pn36bBji9rWKHlZe+PZb6Mx5V8IHCSxk7X4aljM4e/vyDvZZYwHewdVaqiA0nb3ghfHU/6AUpDxWoER2Q==} + magic-string@0.30.21: + resolution: {integrity: sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==} magicast@0.3.5: resolution: {integrity: sha512-L0WhttDl+2BOsybvEOLK7fW3UA0OQ0IQ2d6Zl2x/a6vVRs3bAY0ECOSHHeL5jD+SbOpOCUEi0y1DgHEn9Qn1AQ==} + magicast@0.5.3: + resolution: {integrity: sha512-pVKE4UdSQ7DvHzivsCIFx2BJn1mHG6KsyrFcaxFx6tONdneEuThrDx0Cj3AMg58KyN4pzYT+LHOotxDQDjNvkw==} + make-dir@4.0.0: resolution: {integrity: sha512-hXdUTZYIVOt1Ex//jAQi+wTZZpUpwBj/0QsOzqegb3rGMMeJiSEu5xLHnYfBrRV4RH2+OCSOO95Is/7x1WJ4bw==} engines: {node: '>=10'} @@ -3893,9 +4248,6 @@ packages: merge-descriptors@1.0.3: resolution: {integrity: sha512-gaNvAS7TZ897/rVaZ0nMtAyxNyi/pdbjbAwUpFQpN70GqnVfOiXpeUUMKRBmzXaSQ8DdTX4/0ms62r2K+hE6mQ==} - merge-stream@2.0.0: - resolution: {integrity: sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==} - merge2@1.4.1: resolution: {integrity: sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==} engines: {node: '>= 8'} @@ -4040,10 +4392,6 @@ packages: engines: {node: '>=4'} hasBin: true - mimic-fn@4.0.0: - resolution: {integrity: sha512-vqiC06CuhBTUdZH+RYl8sFrL096vA45Ok5ISO6sE/Mr1jRbGH4Csnhi8f3wKVl7x8mO4Au7Ir9D3Oyv1VYMFJw==} - engines: {node: '>=12'} - mimic-function@5.0.1: resolution: {integrity: sha512-VP79XUPxV2CigYP3jWwAUFSku2aKqBH7uTAapFWCBqutsbmDo96KY5o8uh6U+/YSIn5OxJnXp73beVkpqMIGhA==} engines: {node: '>=18'} @@ -4056,6 +4404,10 @@ packages: resolution: {integrity: sha512-enIvLvRAFZYXJzkCYG5RKmPfrFArdLv+R+lbQ53BmIMLIry74bjKzX6iHAm8WYamJkhSSEabrWN5D97XnKObjQ==} engines: {node: 20 || >=22} + minimatch@10.2.5: + resolution: {integrity: sha512-MULkVLfKGYDFYejP07QOurDLLQpcjk7Fw+7jXS2R2czRQzR56yHRveU5NDJEOviH+hETZKSkIk5c+T23GjFUMg==} + engines: {node: 18 || 20 || >=22} + minimatch@3.1.2: resolution: {integrity: sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==} @@ -4073,6 +4425,10 @@ packages: resolution: {integrity: sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==} engines: {node: '>=16 || 14 >=14.17'} + minipass@7.1.3: + resolution: {integrity: sha512-tEBHqDnIoM/1rXME1zgka9g6Q2lcoCkxHLuc7ODJ5BxbP5d4c2Z5cGgtXAku59200Cx7diuHTOYfSBD8n6mm8A==} + engines: {node: '>=16 || 14 >=14.17'} + mkdirp-classic@0.5.3: resolution: {integrity: sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A==} @@ -4080,9 +4436,6 @@ packages: resolution: {integrity: sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==} hasBin: true - mlly@1.7.1: - resolution: {integrity: sha512-rrVRZRELyQzrIUAVMHxP97kv+G786pHmOKzuFII8zDYahFBS7qnHh2AlYSl1GAHhaMPCz6/oHjVMcfFYgFYHgA==} - morgan@1.10.0: resolution: {integrity: sha512-AbegBVI4sh6El+1gNwvD5YIck7nSA36weD7xvIxG4in80j/UoK8AEGaWnnz8v1GxonMCltmlNs5ZKbGvl9b1XQ==} engines: {node: '>= 0.8.0'} @@ -4106,6 +4459,11 @@ packages: mz@2.7.0: resolution: {integrity: sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q==} + nanoid@3.3.16: + resolution: {integrity: sha512-bzlKTyNJ7+LdGIIwy8ijFpIqEQIvafahV7eYykJ8Cvh42EdJeODoJ6gUJXpQJvej1BddH8OqTXZNE/KfbWAu8Q==} + engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} + hasBin: true + nanoid@3.3.7: resolution: {integrity: sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g==} engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} @@ -4204,10 +4562,6 @@ packages: engines: {node: '>= 4'} hasBin: true - npm-run-path@5.3.0: - resolution: {integrity: sha512-ppwTtiJZq0O/ai0z7yfudtBpWIoxM8yE6nHi1X47eFR2EWORqfbu6CnPlNsjeN683eT0qG6H/Pyf9fCcvjnnnQ==} - engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} - nth-check@2.1.1: resolution: {integrity: sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w==} @@ -4230,6 +4584,10 @@ packages: resolution: {integrity: sha512-byy+U7gp+FVwmyzKPYhW2h5l3crpmGsxl7X2s8y43IgxvG4g3QZ6CffDtsNQy1WsmZpQbO+ybo0AlW7TY6DcBQ==} engines: {node: '>= 0.4'} + obug@2.1.4: + resolution: {integrity: sha512-4a+OsYv9UktOJKE+l1A4OufDgdRF9PifWj+tJnHURo/P+WOxpG4GzUFL9qCalmWauao6ogiG+QvnCovwPoyAWA==} + engines: {node: '>=12.20.0'} + ohash@2.0.11: resolution: {integrity: sha512-RdR9FQrFwNBNXAr4GixM8YaRZRJ5PUWbKYbE5eOsrwAjJW0q2REGcf79oYPsLyskQCZG1PLN+S/K1V00joZAoQ==} @@ -4248,10 +4606,6 @@ packages: once@1.4.0: resolution: {integrity: sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==} - onetime@6.0.0: - resolution: {integrity: sha512-1FlR+gjXK7X+AsAHso35MnyN5KqGwJRi/31ft6x0M194ht7S+rWAvd7PHss9xSKMzE0asv1pyIHaJYq+BbacAQ==} - engines: {node: '>=12'} - onetime@7.0.0: resolution: {integrity: sha512-VXJjc87FScF88uafS3JllDgvAm+c/Slfz06lorj2uAY34rlUu0Nt+v8wreiImcrgAjjIHp1rXpTDlLOGw29WwQ==} engines: {node: '>=18'} @@ -4282,10 +4636,6 @@ packages: resolution: {integrity: sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==} engines: {node: '>=6'} - p-limit@5.0.0: - resolution: {integrity: sha512-/Eaoq+QyLSiXQ4lyYV23f14mZRQcXnxfHrN0vCai+ak9G0pp9iEQukIIZq5NccEvwRB8PUnZT0KsOoDCINS1qQ==} - engines: {node: '>=18'} - p-limit@6.1.0: resolution: {integrity: sha512-H0jc0q1vOzlEk0TqAKXKZxdl7kX3OFUzCnNVUnq5Pc3DGo0kpeaMuPqxQn235HibwBEb0/pm9dgKTjXy66fBkg==} engines: {node: '>=18'} @@ -4314,6 +4664,9 @@ packages: resolution: {integrity: sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==} engines: {node: '>=6'} + package-json-from-dist@1.0.1: + resolution: {integrity: sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==} + pagefind@1.1.1: resolution: {integrity: sha512-U2YR0dQN5B2fbIXrLtt/UXNS0yWSSYfePaad1KcBPTi0p+zRtsVjwmoPaMQgTks5DnHNbmDxyJUL5TGaLljK3A==} hasBin: true @@ -4355,10 +4708,6 @@ packages: resolution: {integrity: sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==} engines: {node: '>=8'} - path-is-absolute@1.0.1: - resolution: {integrity: sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==} - engines: {node: '>=0.10.0'} - path-key@2.0.1: resolution: {integrity: sha512-fEHGKCSmUSDPv4uoj8AlD+joPlq3peND+HRYyxFz4KPw4z926S/b8rIuFs2FYJg3BwsxJf6A9/3eIdLaYC+9Dw==} engines: {node: '>=4'} @@ -4367,10 +4716,6 @@ packages: resolution: {integrity: sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==} engines: {node: '>=8'} - path-key@4.0.0: - resolution: {integrity: sha512-haREypq7xkM7ErfgIyA0z+Bj4AGKlMSdlQE2jvJo6huWD1EdkKYV+G/T4nq0YEF2vgTT8kqMFKo1uHn950r4SQ==} - engines: {node: '>=12'} - path-parse@1.0.7: resolution: {integrity: sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==} @@ -4378,6 +4723,10 @@ packages: resolution: {integrity: sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==} engines: {node: '>=16 || 14 >=14.18'} + path-scurry@2.0.2: + resolution: {integrity: sha512-3O/iVVsJAPsOnpwWIeD+d6z/7PmqApyQePUtCndjatj/9I5LylHvt5qluFaBT3I5h3r1ejfR056c+FCv+NnNXg==} + engines: {node: 18 || 20 || >=22} + path-to-regexp@0.1.10: resolution: {integrity: sha512-7lf7qcQidTku0Gu3YDPc8DJ1q7OOucfa/BSsIwjuh56VU7katFvuM8hULfkwB3Fns/rsVF7PwPKVw1sl5KQS9w==} @@ -4391,9 +4740,6 @@ packages: pathe@2.0.3: resolution: {integrity: sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==} - pathval@1.1.1: - resolution: {integrity: sha512-Dp6zGqpTdETdR63lehJYPeIOqpiNBNtc7BpWSLrOje7UaIsE5aY92r/AunQA7rsXvet3lrJ3JnZX29UPTKXyKQ==} - peek-stream@1.1.3: resolution: {integrity: sha512-FhJ+YbOSBb9/rIl2ZeE/QHEsWn7PqNYt8ARAY3kIgNGOk13g9FGyIY6JIl/xB/3TFRVoTv5as0l11weORrTekA==} @@ -4409,6 +4755,9 @@ packages: picocolors@1.1.0: resolution: {integrity: sha512-TQ92mBOW0l3LeMeyLV6mzy/kWr8lkd/hp3mTg7wYK7zJhuBStmGMBG0BdeDZS/dZx1IukaX6Bk11zcln25o1Aw==} + picocolors@1.1.1: + resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==} + picomatch@2.3.1: resolution: {integrity: sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==} engines: {node: '>=8.6'} @@ -4453,9 +4802,6 @@ packages: resolution: {integrity: sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ==} engines: {node: '>=8'} - pkg-types@1.1.1: - resolution: {integrity: sha512-ko14TjmDuQJ14zsotODv7dBlwxKhUKQEhuhmbqo1uCi9BB0Z2alo/wAXg6q1dTR5TyuqYyWhjtfe/Tsh+X28jQ==} - pkg-types@2.3.0: resolution: {integrity: sha512-SIqCzDRg0s9npO5XQ3tNZioRY1uK06lA41ynBC1YmFTmnY6FjUjVt6s4LoADmwoig1qqD0oK8h1p/8mlMx8Oig==} @@ -4512,6 +4858,10 @@ packages: resolution: {integrity: sha512-56rxCq7G/XfB4EkXq9Egn5GCqugWvDFjafDOThIdMBsI15iqPqR5r15TfSr1YPYeEI19YeaXMCbY6u88Y76GLQ==} engines: {node: ^10 || ^12 || >=14} + postcss@8.5.19: + resolution: {integrity: sha512-Mz8SaolMd8nB+G13WkORcxQKHZ/NE4xXevtkJHVuG+guo9/wYKlIMTKAqGdEmYOXR2ijPjTYNHssizdaVSUNdQ==} + engines: {node: ^10 || ^12 || >=14} + postman-collection@4.4.0: resolution: {integrity: sha512-2BGDFcUwlK08CqZFUlIC8kwRJueVzPjZnnokWPtJCd9f2J06HBQpGL7t2P1Ud1NEsK9NHq9wdipUhWLOPj5s/Q==} engines: {node: '>=10'} @@ -4527,6 +4877,7 @@ packages: prebuild-install@7.1.2: resolution: {integrity: sha512-UnNke3IQb6sgarcZIDU3gbMeTp/9SSU1DAIkil7PrqG1vZlBtY5msYccSKSHDqa3hNg436IXK+SNImReuA1wEQ==} engines: {node: '>=10'} + deprecated: No longer maintained. Please contact the author of the relevant native addon; alternatives are available. hasBin: true preferred-pm@4.0.0: @@ -4546,10 +4897,6 @@ packages: pretty-data@0.40.0: resolution: {integrity: sha512-YFLnEdDEDnkt/GEhet5CYZHCvALw6+Elyb/tp8kQG03ZSIuzeaDWpZYndCXwgqu4NAjh1PI534dhDS1mHarRnQ==} - pretty-format@29.7.0: - resolution: {integrity: sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} - prismjs@1.29.0: resolution: {integrity: sha512-Kx/1w86q/epKcmte75LNrEoT+lX8pBpavuAbvJWRXar7Hz8jrtF+e3vY751p0R8H9HdArwaCTNDDzHg/ScJK1Q==} engines: {node: '>=6'} @@ -4636,9 +4983,6 @@ packages: peerDependencies: react: ^18.3.1 - react-is@18.3.1: - resolution: {integrity: sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==} - react-refresh@0.14.2: resolution: {integrity: sha512-jCvmsr+1IUSMUyzOkRcvnVbX3ZYC6g9TDrDbFuFmRDq7PD4yaGbLKNQL6k2jnArV8hjYxh7hVhAZB6s9HDGpZA==} engines: {node: '>=0.10.0'} @@ -4798,9 +5142,9 @@ packages: resolution: {integrity: sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==} engines: {iojs: '>=1.0.0', node: '>=0.10.0'} - rimraf@5.0.7: - resolution: {integrity: sha512-nV6YcJo5wbLW77m+8KjH8aB/7/rxQy9SZ0HY5shnwULfS+9nmTtVXAJET5NdZmCzA4fPI/Hm1wo/Po/4mopOdg==} - engines: {node: '>=14.18'} + rimraf@6.1.3: + resolution: {integrity: sha512-LKg+Cr2ZF61fkcaK1UdkH2yEBBKnYjTyWzTJT6KNPcSPaiT7HSdhtMXQuN5wkTX0Xu72KQ1l8S42rlmexS2hSA==} + engines: {node: 20 || >=22} hasBin: true rollup@4.18.0: @@ -4813,6 +5157,11 @@ packages: engines: {node: '>=18.0.0', npm: '>=8.0.0'} hasBin: true + rollup@4.62.2: + resolution: {integrity: sha512-RFnrW4lhXA3s3eqHDZvN654g8OTjzRfqpIRJYczCGB6HzphckVAi/Qh4tbPUbRuDi7s1Llv8g/NspLkttY3gTA==} + 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'} @@ -5018,8 +5367,8 @@ packages: resolution: {integrity: sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==} engines: {node: '>= 0.8'} - std-env@3.7.0: - resolution: {integrity: sha512-JPbdCEQLj1w5GilpiHAx3qJvFndqybBysA3qUOnznweH4QbNYUsW/ea8QzSrnh0vNsezMMw5bcVool8lM0gwzg==} + std-env@4.2.0: + resolution: {integrity: sha512-oCUKSupKTHX53EyjDtuZQ64pjLJ6yYCtpmEw0goYxtjG9KpbRe8KAsl2tBUGU9DyMcJ0RwJ8GqJAFzMXcXW1Rw==} stdin-discarder@0.2.2: resolution: {integrity: sha512-UhDfHmA92YAlNnCfhmq0VeNL5bDbiZGg7sZ2IvPsXubGkiNa9EC+tUTsjBRsYUAz87btI6/1wf4XoVvQ3uRnmQ==} @@ -5093,17 +5442,10 @@ packages: resolution: {integrity: sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA==} engines: {node: '>=4'} - strip-final-newline@3.0.0: - resolution: {integrity: sha512-dOESqjYr96iWYylGObzd39EuNTa5VJxyvVAEm5Jnh7KGo75V43Hk1odPQkNDyXNmUR6k+gEiDVXnjB8HJ3crXw==} - engines: {node: '>=12'} - strip-json-comments@2.0.1: resolution: {integrity: sha512-4gB8na07fecVVkOI6Rs4e7T6NOTki5EmL7TUduTs6bu3EdnSycntVJ4re8kgZA+wx9IueI2Y11bfbgwtzuE0KQ==} engines: {node: '>=0.10.0'} - strip-literal@2.1.0: - resolution: {integrity: sha512-Op+UycaUt/8FbN/Z2TWPBLge3jWrP3xj10f3fnYxf052bKuS3EKs1ZQcVGjnEMdsNVAM+plXRdmjrZ/KgG3Skw==} - strnum@1.0.5: resolution: {integrity: sha512-J8bbNyKKXl5qYcR36TIO8W3mVGVHrmmxsd5PAItGkmyzwJvybiw2IVq5nqd0i4LSNSkB/sx9VHllbfFdr9k1JA==} @@ -5166,10 +5508,6 @@ packages: engines: {node: '>=10'} hasBin: true - test-exclude@6.0.0: - resolution: {integrity: sha512-cAGWPIyOHU6zlmg88jwm7VRyXnMN7iV68OGAbYDk/Mh/xC/pzVPlQtY6ngoIH/5/tciuhGfvESU8GrHrcxD56w==} - engines: {node: '>=8'} - text-decoder@1.2.0: resolution: {integrity: sha512-n1yg1mOj9DNpk3NeZOx7T6jchTbyJS3i3cucbNN6FcdPriMZx7NsgrGpWWdWZZGxD7ES1XB+3uoqHMgOKaN+fg==} @@ -5189,22 +5527,22 @@ packages: tiny-warning@1.0.3: resolution: {integrity: sha512-lBN9zLN/oAf68o3zNXYrdCt1kP8WsiGW8Oo2ka41b2IM5JL/S1CTyX1rW0mb/zSuJun0ZUrDxx4sqvYS2FWzPA==} - tinybench@2.8.0: - resolution: {integrity: sha512-1/eK7zUnIklz4JUUlL+658n58XO2hHLQfSk1Zf2LKieUjxidN16eKFEoDEfjHc3ohofSSqK3X5yO6VGb6iW8Lw==} + tinybench@2.9.0: + resolution: {integrity: sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==} tinyexec@0.3.0: resolution: {integrity: sha512-tVGE0mVJPGb0chKhqmsoosjsS+qUnJVGJpZgsHYQcGoPlG3B51R3PouqTgEGH2Dc9jjFyOqOpix6ZHNMXp1FZg==} + tinyexec@1.2.4: + resolution: {integrity: sha512-SHf/r48b7vOrjve9PxJo3MN5v5yuyjHvdUcrQffT3WXMUfnGmHDVbC4k3sHJaJTgZCwpUplIaAo5ANtMyp3YHg==} + engines: {node: '>=18'} + tinyglobby@0.2.15: resolution: {integrity: sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==} engines: {node: '>=12.0.0'} - tinypool@0.8.4: - resolution: {integrity: sha512-i11VH5gS6IFeLY3gMBQ00/MmLncVP7JLXOw1vlgkytLmJK7QnEr7NXf0LBdxfmNPAeyetukOk0bOYrJrFGjYJQ==} - engines: {node: '>=14.0.0'} - - tinyspy@2.2.1: - resolution: {integrity: sha512-KYad6Vy5VDWV4GH3fjpseMQ/XU2BhIYP7Vzd0LG44qRWm/Yt2WCOTicFdvmgo6gWaqooMQCawTtILVQJupKu7A==} + tinyrainbow@3.1.0: + resolution: {integrity: sha512-Bf+ILmBgretUrdJxzXM0SgXLZ3XfiaUuOj/IKQHuTXip+05Xn+uyEYdVg0kYDipTBcLrCVyUzAPz7QmArb0mmw==} engines: {node: '>=14.0.0'} to-fast-properties@2.0.0: @@ -5251,6 +5589,7 @@ packages: tsconfck@3.1.3: resolution: {integrity: sha512-ulNZP1SVpRDesxeMLON/LtWM8HIgAJEIVpVVhBM6gsmvQ8+Rh+ZG7FWGvHh7Ah3pRABwVJWklWCr/BTZSv0xnQ==} engines: {node: ^18 || >=20} + deprecated: unmaintained hasBin: true peerDependencies: typescript: ^5.0.0 @@ -5272,10 +5611,6 @@ packages: turbo-stream@2.4.0: resolution: {integrity: sha512-FHncC10WpBd2eOmGwpmQsWLDoK4cqsA/UT/GqNoaKOQnT8uzhtCbg3EoUDMvqpOSAI0S26mr0rkjzbOO6S3v1g==} - type-detect@4.0.8: - resolution: {integrity: sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==} - engines: {node: '>=4'} - type-fest@4.26.1: resolution: {integrity: sha512-yOGpmOAL7CkKe/91I5O3gPICmJNLJ1G4zFYVAsRHg7M64biSnPtRj0WNQt++bRkjYOqjWXrhnUw1utzmVErAdg==} engines: {node: '>=16'} @@ -5321,9 +5656,6 @@ packages: engines: {node: '>=14.17'} hasBin: true - ufo@1.5.3: - resolution: {integrity: sha512-Y7HYmWaFwPUmkoQCUIAYpKqkOf+SbVj/2fJJZ4RJMCfZp0rTGwRbzQD+HghfnhKOjL9E01okqz+ncJskGYfBNw==} - unbox-primitive@1.0.2: resolution: {integrity: sha512-61pPlCD9h51VoreyJ0BReideM3MDKMKnh6+V9L08331ipq6Q8OFXZYiqP6n/tbHx4s5I9uRhcye6BrbkizkBDw==} @@ -5333,6 +5665,9 @@ packages: undici-types@6.19.8: resolution: {integrity: sha512-ve2KP6f/JnbPBFyobGHuerC9g1FYGn/F8n1LWTwNxCEzd6IfqTwUQcNXgEtmmQ6DlRrC1hrSrBnCZPokRrDHjw==} + undici-types@6.21.0: + resolution: {integrity: sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==} + undici@6.20.1: resolution: {integrity: sha512-AjQF1QsmqfJys+LXfGTNum+qw4S88CojRInG/6t31W/1fk6G59s92bnAvGz5Cmur+kQv2SURXEvvudLmbrE8QA==} engines: {node: '>=18.17'} @@ -5426,6 +5761,7 @@ packages: uuid@8.3.2: resolution: {integrity: sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==} + deprecated: uuid@10 and below is no longer supported. For ESM codebases, update to uuid@latest. For CommonJS codebases, use uuid@11 (but be aware this version will likely be deprecated in 2028). hasBin: true v8-compile-cache-lib@3.0.1: @@ -5538,6 +5874,46 @@ packages: terser: optional: true + vite@7.3.6: + resolution: {integrity: sha512-4XP60spRGjSZFf1qYH+dJIkK2znL3zQfl9KkOV9MkkRR/3Dls0dxaBsQPTloEc5BLXWPL9vsOxopxyKoMmDueg==} + engines: {node: ^20.19.0 || >=22.12.0} + hasBin: true + peerDependencies: + '@types/node': ^20.19.0 || >=22.12.0 + jiti: '>=1.21.0' + less: ^4.0.0 + lightningcss: ^1.21.0 + sass: ^1.70.0 + sass-embedded: ^1.70.0 + stylus: '>=0.54.8' + sugarss: ^5.0.0 + terser: ^5.16.0 + tsx: ^4.8.1 + yaml: ^2.4.2 + peerDependenciesMeta: + '@types/node': + optional: true + jiti: + optional: true + less: + optional: true + lightningcss: + optional: true + sass: + optional: true + sass-embedded: + optional: true + stylus: + optional: true + sugarss: + optional: true + terser: + optional: true + tsx: + optional: true + yaml: + optional: true + vitefu@1.0.2: resolution: {integrity: sha512-0/iAvbXyM3RiPPJ4lyD4w6Mjgtf4ejTK6TPvTNG3H32PLwuT0N/ZjJLiXug7ETE/LWtTeHw9WRv7uX/tIKYyKg==} peerDependencies: @@ -5546,23 +5922,39 @@ packages: vite: optional: true - vitest@1.6.0: - resolution: {integrity: sha512-H5r/dN06swuFnzNFhq/dnz37bPXnq8xB2xB5JOVk8K09rUtoeNN+LHWkoQ0A/i3hvbUKKcCei9KpbxqHMLhLLA==} - engines: {node: ^18.0.0 || >=20.0.0} + vitest@4.1.10: + resolution: {integrity: sha512-R9jUTe5S4Qb0HCd4TNqpC7oGcrMssMRGXLW80ubjWsW9VH5GF8y1Y0SFLY9AbqSk6nt0PnOx4H4WNJYZ13GUPw==} + engines: {node: ^20.0.0 || ^22.0.0 || >=24.0.0} hasBin: true peerDependencies: '@edge-runtime/vm': '*' - '@types/node': ^18.0.0 || >=20.0.0 - '@vitest/browser': 1.6.0 - '@vitest/ui': 1.6.0 + '@opentelemetry/api': ^1.9.0 + '@types/node': ^20.0.0 || ^22.0.0 || >=24.0.0 + '@vitest/browser-playwright': 4.1.10 + '@vitest/browser-preview': 4.1.10 + '@vitest/browser-webdriverio': 4.1.10 + '@vitest/coverage-istanbul': 4.1.10 + '@vitest/coverage-v8': 4.1.10 + '@vitest/ui': 4.1.10 happy-dom: '*' jsdom: '*' + vite: ^6.0.0 || ^7.0.0 || ^8.0.0 peerDependenciesMeta: '@edge-runtime/vm': optional: true + '@opentelemetry/api': + optional: true '@types/node': optional: true - '@vitest/browser': + '@vitest/browser-playwright': + optional: true + '@vitest/browser-preview': + optional: true + '@vitest/browser-webdriverio': + optional: true + '@vitest/coverage-istanbul': + optional: true + '@vitest/coverage-v8': optional: true '@vitest/ui': optional: true @@ -5728,8 +6120,8 @@ packages: engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0} hasBin: true - why-is-node-running@2.2.2: - resolution: {integrity: sha512-6tSwToZxTOcotxHeA+qGCq1mVzKR3CwcJGmVcY+QE8SHy6TnpFnh8PAvPNHYr7EcuVeG0QSMxtYCuO1ta/G/oA==} + why-is-node-running@2.3.0: + resolution: {integrity: sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w==} engines: {node: '>=8'} hasBin: true @@ -5815,10 +6207,6 @@ packages: resolution: {integrity: sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==} engines: {node: '>=6'} - yocto-queue@1.0.0: - resolution: {integrity: sha512-9bnSc/HEW2uRy67wc+T8UwauLuPJVn28jb+GtJY16iiKWyvmYJRXVT4UamsAEGQfPohgr2q4Tq0sQbQlxTfi1g==} - engines: {node: '>=12.20'} - yocto-queue@1.1.1: resolution: {integrity: sha512-b4JR1PFR10y1mKjhHY9LaGo6tmrgjit7hxVIeAmyMw3jegXR4dhYqLaQF5zMXZxY7tLpMyJeLjr1C4rLmkVe8g==} engines: {node: '>=12.20'} @@ -5912,12 +6300,12 @@ snapshots: transitivePeerDependencies: - supports-color - '@astrojs/mdx@3.1.8(astro@4.16.2(@types/node@22.7.4)(rollup@4.24.0)(terser@5.34.1)(typescript@5.6.3))': + '@astrojs/mdx@3.1.8(astro@4.16.2(@types/node@22.20.1)(rollup@4.62.2)(terser@5.34.1)(typescript@5.6.3))': dependencies: '@astrojs/markdown-remark': 5.3.0 '@mdx-js/mdx': 3.0.1 acorn: 8.12.1 - astro: 4.16.2(@types/node@22.7.4)(rollup@4.24.0)(terser@5.34.1)(typescript@5.6.3) + astro: 4.16.2(@types/node@22.20.1)(rollup@4.62.2)(terser@5.34.1)(typescript@5.6.3) es-module-lexer: 1.5.4 estree-util-visit: 2.0.0 gray-matter: 4.0.3 @@ -5942,15 +6330,15 @@ snapshots: stream-replace-string: 2.0.0 zod: 3.23.8 - '@astrojs/starlight@0.28.3(astro@4.16.2(@types/node@22.7.4)(rollup@4.24.0)(terser@5.34.1)(typescript@5.6.3))': + '@astrojs/starlight@0.28.3(astro@4.16.2(@types/node@22.20.1)(rollup@4.62.2)(terser@5.34.1)(typescript@5.6.3))': dependencies: - '@astrojs/mdx': 3.1.8(astro@4.16.2(@types/node@22.7.4)(rollup@4.24.0)(terser@5.34.1)(typescript@5.6.3)) + '@astrojs/mdx': 3.1.8(astro@4.16.2(@types/node@22.20.1)(rollup@4.62.2)(terser@5.34.1)(typescript@5.6.3)) '@astrojs/sitemap': 3.2.0 '@pagefind/default-ui': 1.1.1 '@types/hast': 3.0.4 '@types/mdast': 4.0.4 - astro: 4.16.2(@types/node@22.7.4)(rollup@4.24.0)(terser@5.34.1)(typescript@5.6.3) - astro-expressive-code: 0.35.6(astro@4.16.2(@types/node@22.7.4)(rollup@4.24.0)(terser@5.34.1)(typescript@5.6.3)) + astro: 4.16.2(@types/node@22.20.1)(rollup@4.62.2)(terser@5.34.1)(typescript@5.6.3) + astro-expressive-code: 0.35.6(astro@4.16.2(@types/node@22.20.1)(rollup@4.62.2)(terser@5.34.1)(typescript@5.6.3)) bcp-47: 2.1.0 hast-util-from-html: 2.0.3 hast-util-select: 6.0.3 @@ -6047,7 +6435,7 @@ snapshots: '@babel/generator@7.24.7': dependencies: - '@babel/types': 7.24.7 + '@babel/types': 7.25.9 '@jridgewell/gen-mapping': 0.3.5 '@jridgewell/trace-mapping': 0.3.25 jsesc: 2.5.2 @@ -6068,7 +6456,7 @@ snapshots: '@babel/helper-annotate-as-pure@7.25.7': dependencies: - '@babel/types': 7.25.7 + '@babel/types': 7.25.9 '@babel/helper-annotate-as-pure@7.25.9': dependencies: @@ -6133,7 +6521,7 @@ snapshots: '@babel/helper-module-imports@7.25.7': dependencies: '@babel/traverse': 7.25.7 - '@babel/types': 7.25.7 + '@babel/types': 7.25.9 transitivePeerDependencies: - supports-color @@ -6232,12 +6620,16 @@ snapshots: '@babel/helper-string-parser@7.25.9': {} + '@babel/helper-string-parser@7.29.7': {} + '@babel/helper-validator-identifier@7.24.7': {} '@babel/helper-validator-identifier@7.25.7': {} '@babel/helper-validator-identifier@7.25.9': {} + '@babel/helper-validator-identifier@7.29.7': {} + '@babel/helper-validator-option@7.24.7': {} '@babel/helper-validator-option@7.25.7': {} @@ -6247,23 +6639,23 @@ snapshots: '@babel/helpers@7.24.7': dependencies: '@babel/template': 7.24.7 - '@babel/types': 7.24.7 + '@babel/types': 7.25.9 '@babel/helpers@7.25.7': dependencies: '@babel/template': 7.25.7 - '@babel/types': 7.25.7 + '@babel/types': 7.25.9 '@babel/highlight@7.24.7': dependencies: - '@babel/helper-validator-identifier': 7.24.7 + '@babel/helper-validator-identifier': 7.25.9 chalk: 2.4.2 js-tokens: 4.0.0 picocolors: 1.1.0 '@babel/highlight@7.25.7': dependencies: - '@babel/helper-validator-identifier': 7.25.7 + '@babel/helper-validator-identifier': 7.25.9 chalk: 2.4.2 js-tokens: 4.0.0 picocolors: 1.1.0 @@ -6277,7 +6669,7 @@ snapshots: '@babel/parser@7.24.7': dependencies: - '@babel/types': 7.24.7 + '@babel/types': 7.25.9 '@babel/parser@7.25.7': dependencies: @@ -6287,6 +6679,10 @@ snapshots: dependencies: '@babel/types': 7.25.9 + '@babel/parser@7.29.7': + dependencies: + '@babel/types': 7.29.7 + '@babel/plugin-syntax-decorators@7.25.9(@babel/core@7.25.7)': dependencies: '@babel/core': 7.25.7 @@ -6371,8 +6767,8 @@ snapshots: '@babel/template@7.24.7': dependencies: '@babel/code-frame': 7.24.7 - '@babel/parser': 7.24.7 - '@babel/types': 7.24.7 + '@babel/parser': 7.25.9 + '@babel/types': 7.25.9 '@babel/template@7.25.7': dependencies: @@ -6394,8 +6790,8 @@ snapshots: '@babel/helper-function-name': 7.24.7 '@babel/helper-hoist-variables': 7.24.7 '@babel/helper-split-export-declaration': 7.24.7 - '@babel/parser': 7.24.7 - '@babel/types': 7.24.7 + '@babel/parser': 7.25.9 + '@babel/types': 7.25.9 debug: 4.3.5 globals: 11.12.0 transitivePeerDependencies: @@ -6442,7 +6838,12 @@ snapshots: '@babel/helper-string-parser': 7.25.9 '@babel/helper-validator-identifier': 7.25.9 - '@bcoe/v8-coverage@0.2.3': {} + '@babel/types@7.29.7': + dependencies: + '@babel/helper-string-parser': 7.29.7 + '@babel/helper-validator-identifier': 7.29.7 + + '@bcoe/v8-coverage@1.0.2': {} '@biomejs/biome@1.8.1': optionalDependencies: @@ -6466,54 +6867,89 @@ snapshots: '@biomejs/cli-win32-arm64': 1.9.3 '@biomejs/cli-win32-x64': 1.9.3 + '@biomejs/biome@2.5.4': + optionalDependencies: + '@biomejs/cli-darwin-arm64': 2.5.4 + '@biomejs/cli-darwin-x64': 2.5.4 + '@biomejs/cli-linux-arm64': 2.5.4 + '@biomejs/cli-linux-arm64-musl': 2.5.4 + '@biomejs/cli-linux-x64': 2.5.4 + '@biomejs/cli-linux-x64-musl': 2.5.4 + '@biomejs/cli-win32-arm64': 2.5.4 + '@biomejs/cli-win32-x64': 2.5.4 + '@biomejs/cli-darwin-arm64@1.8.1': optional: true '@biomejs/cli-darwin-arm64@1.9.3': optional: true + '@biomejs/cli-darwin-arm64@2.5.4': + optional: true + '@biomejs/cli-darwin-x64@1.8.1': optional: true '@biomejs/cli-darwin-x64@1.9.3': optional: true + '@biomejs/cli-darwin-x64@2.5.4': + optional: true + '@biomejs/cli-linux-arm64-musl@1.8.1': optional: true '@biomejs/cli-linux-arm64-musl@1.9.3': optional: true + '@biomejs/cli-linux-arm64-musl@2.5.4': + optional: true + '@biomejs/cli-linux-arm64@1.8.1': optional: true '@biomejs/cli-linux-arm64@1.9.3': optional: true + '@biomejs/cli-linux-arm64@2.5.4': + optional: true + '@biomejs/cli-linux-x64-musl@1.8.1': optional: true '@biomejs/cli-linux-x64-musl@1.9.3': optional: true + '@biomejs/cli-linux-x64-musl@2.5.4': + optional: true + '@biomejs/cli-linux-x64@1.8.1': optional: true '@biomejs/cli-linux-x64@1.9.3': optional: true + '@biomejs/cli-linux-x64@2.5.4': + optional: true + '@biomejs/cli-win32-arm64@1.8.1': optional: true '@biomejs/cli-win32-arm64@1.9.3': optional: true + '@biomejs/cli-win32-arm64@2.5.4': + optional: true + '@biomejs/cli-win32-x64@1.8.1': optional: true '@biomejs/cli-win32-x64@1.9.3': optional: true + '@biomejs/cli-win32-x64@2.5.4': + optional: true + '@cspotcode/source-map-support@0.8.1': dependencies: '@jridgewell/trace-mapping': 0.3.9 @@ -6558,6 +6994,9 @@ snapshots: '@esbuild/aix-ppc64@0.23.1': optional: true + '@esbuild/aix-ppc64@0.28.1': + optional: true + '@esbuild/android-arm64@0.20.2': optional: true @@ -6567,6 +7006,9 @@ snapshots: '@esbuild/android-arm64@0.23.1': optional: true + '@esbuild/android-arm64@0.28.1': + optional: true + '@esbuild/android-arm@0.20.2': optional: true @@ -6576,6 +7018,9 @@ snapshots: '@esbuild/android-arm@0.23.1': optional: true + '@esbuild/android-arm@0.28.1': + optional: true + '@esbuild/android-x64@0.20.2': optional: true @@ -6585,6 +7030,9 @@ snapshots: '@esbuild/android-x64@0.23.1': optional: true + '@esbuild/android-x64@0.28.1': + optional: true + '@esbuild/darwin-arm64@0.20.2': optional: true @@ -6594,6 +7042,9 @@ snapshots: '@esbuild/darwin-arm64@0.23.1': optional: true + '@esbuild/darwin-arm64@0.28.1': + optional: true + '@esbuild/darwin-x64@0.20.2': optional: true @@ -6603,6 +7054,9 @@ snapshots: '@esbuild/darwin-x64@0.23.1': optional: true + '@esbuild/darwin-x64@0.28.1': + optional: true + '@esbuild/freebsd-arm64@0.20.2': optional: true @@ -6612,6 +7066,9 @@ snapshots: '@esbuild/freebsd-arm64@0.23.1': optional: true + '@esbuild/freebsd-arm64@0.28.1': + optional: true + '@esbuild/freebsd-x64@0.20.2': optional: true @@ -6621,6 +7078,9 @@ snapshots: '@esbuild/freebsd-x64@0.23.1': optional: true + '@esbuild/freebsd-x64@0.28.1': + optional: true + '@esbuild/linux-arm64@0.20.2': optional: true @@ -6630,6 +7090,9 @@ snapshots: '@esbuild/linux-arm64@0.23.1': optional: true + '@esbuild/linux-arm64@0.28.1': + optional: true + '@esbuild/linux-arm@0.20.2': optional: true @@ -6639,6 +7102,9 @@ snapshots: '@esbuild/linux-arm@0.23.1': optional: true + '@esbuild/linux-arm@0.28.1': + optional: true + '@esbuild/linux-ia32@0.20.2': optional: true @@ -6648,6 +7114,9 @@ snapshots: '@esbuild/linux-ia32@0.23.1': optional: true + '@esbuild/linux-ia32@0.28.1': + optional: true + '@esbuild/linux-loong64@0.20.2': optional: true @@ -6657,6 +7126,9 @@ snapshots: '@esbuild/linux-loong64@0.23.1': optional: true + '@esbuild/linux-loong64@0.28.1': + optional: true + '@esbuild/linux-mips64el@0.20.2': optional: true @@ -6666,6 +7138,9 @@ snapshots: '@esbuild/linux-mips64el@0.23.1': optional: true + '@esbuild/linux-mips64el@0.28.1': + optional: true + '@esbuild/linux-ppc64@0.20.2': optional: true @@ -6675,6 +7150,9 @@ snapshots: '@esbuild/linux-ppc64@0.23.1': optional: true + '@esbuild/linux-ppc64@0.28.1': + optional: true + '@esbuild/linux-riscv64@0.20.2': optional: true @@ -6684,6 +7162,9 @@ snapshots: '@esbuild/linux-riscv64@0.23.1': optional: true + '@esbuild/linux-riscv64@0.28.1': + optional: true + '@esbuild/linux-s390x@0.20.2': optional: true @@ -6693,6 +7174,9 @@ snapshots: '@esbuild/linux-s390x@0.23.1': optional: true + '@esbuild/linux-s390x@0.28.1': + optional: true + '@esbuild/linux-x64@0.20.2': optional: true @@ -6702,6 +7186,12 @@ snapshots: '@esbuild/linux-x64@0.23.1': optional: true + '@esbuild/linux-x64@0.28.1': + optional: true + + '@esbuild/netbsd-arm64@0.28.1': + optional: true + '@esbuild/netbsd-x64@0.20.2': optional: true @@ -6711,9 +7201,15 @@ snapshots: '@esbuild/netbsd-x64@0.23.1': optional: true + '@esbuild/netbsd-x64@0.28.1': + optional: true + '@esbuild/openbsd-arm64@0.23.1': optional: true + '@esbuild/openbsd-arm64@0.28.1': + optional: true + '@esbuild/openbsd-x64@0.20.2': optional: true @@ -6723,6 +7219,12 @@ snapshots: '@esbuild/openbsd-x64@0.23.1': optional: true + '@esbuild/openbsd-x64@0.28.1': + optional: true + + '@esbuild/openharmony-arm64@0.28.1': + optional: true + '@esbuild/sunos-x64@0.20.2': optional: true @@ -6732,6 +7234,9 @@ snapshots: '@esbuild/sunos-x64@0.23.1': optional: true + '@esbuild/sunos-x64@0.28.1': + optional: true + '@esbuild/win32-arm64@0.20.2': optional: true @@ -6741,6 +7246,9 @@ snapshots: '@esbuild/win32-arm64@0.23.1': optional: true + '@esbuild/win32-arm64@0.28.1': + optional: true + '@esbuild/win32-ia32@0.20.2': optional: true @@ -6750,6 +7258,9 @@ snapshots: '@esbuild/win32-ia32@0.23.1': optional: true + '@esbuild/win32-ia32@0.28.1': + optional: true + '@esbuild/win32-x64@0.20.2': optional: true @@ -6759,6 +7270,9 @@ snapshots: '@esbuild/win32-x64@0.23.1': optional: true + '@esbuild/win32-x64@0.28.1': + optional: true + '@expressive-code/core@0.35.6': dependencies: '@ctrl/tinycolor': 4.1.0 @@ -6790,11 +7304,11 @@ snapshots: '@hey-api/client-fetch@0.6.0': {} - '@hey-api/codegen-core@0.9.1(magicast@0.3.5)': + '@hey-api/codegen-core@0.9.1(magicast@0.5.3)': dependencies: '@hey-api/types': 0.1.4 ansi-colors: 4.1.3 - c12: 3.3.4(magicast@0.3.5) + c12: 3.3.4(magicast@0.5.3) color-support: 1.1.3 transitivePeerDependencies: - magicast @@ -6805,11 +7319,11 @@ snapshots: '@types/json-schema': 7.0.15 js-yaml: 4.2.0 - '@hey-api/openapi-ts@0.99.0(patch_hash=uzpdsaqpo2bd4ydyvbzbqpdgxq)(magicast@0.3.5)(typescript@6.0.3)': + '@hey-api/openapi-ts@0.99.0(patch_hash=uzpdsaqpo2bd4ydyvbzbqpdgxq)(magicast@0.5.3)(typescript@6.0.3)': dependencies: - '@hey-api/codegen-core': 0.9.1(magicast@0.3.5) + '@hey-api/codegen-core': 0.9.1(magicast@0.5.3) '@hey-api/json-schema-ref-parser': 1.4.4 - '@hey-api/shared': 0.5.0(patch_hash=yrzpnh6wq6q4fblbmxcdyvqm3a)(magicast@0.3.5) + '@hey-api/shared': 0.5.0(patch_hash=yrzpnh6wq6q4fblbmxcdyvqm3a)(magicast@0.5.3) '@hey-api/spec-types': 0.2.0 '@hey-api/types': 0.1.4 '@lukeed/ms': 2.0.2 @@ -6821,9 +7335,9 @@ snapshots: transitivePeerDependencies: - magicast - '@hey-api/shared@0.5.0(patch_hash=yrzpnh6wq6q4fblbmxcdyvqm3a)(magicast@0.3.5)': + '@hey-api/shared@0.5.0(patch_hash=yrzpnh6wq6q4fblbmxcdyvqm3a)(magicast@0.5.3)': dependencies: - '@hey-api/codegen-core': 0.9.1(magicast@0.3.5) + '@hey-api/codegen-core': 0.9.1(magicast@0.5.3) '@hey-api/json-schema-ref-parser': 1.4.4 '@hey-api/spec-types': 0.2.0 '@hey-api/types': 0.1.4 @@ -6930,16 +7444,10 @@ snapshots: wrap-ansi: 8.1.0 wrap-ansi-cjs: wrap-ansi@7.0.0 - '@istanbuljs/schema@0.1.3': {} - - '@jest/schemas@29.6.3': - dependencies: - '@sinclair/typebox': 0.27.8 - '@jridgewell/gen-mapping@0.3.5': dependencies: '@jridgewell/set-array': 1.2.1 - '@jridgewell/sourcemap-codec': 1.4.15 + '@jridgewell/sourcemap-codec': 1.5.0 '@jridgewell/trace-mapping': 0.3.25 '@jridgewell/resolve-uri@3.1.2': {} @@ -6949,22 +7457,27 @@ snapshots: '@jridgewell/source-map@0.3.6': dependencies: '@jridgewell/gen-mapping': 0.3.5 - '@jridgewell/trace-mapping': 0.3.25 + '@jridgewell/trace-mapping': 0.3.31 optional: true - '@jridgewell/sourcemap-codec@1.4.15': {} - '@jridgewell/sourcemap-codec@1.5.0': {} + '@jridgewell/sourcemap-codec@1.5.5': {} + '@jridgewell/trace-mapping@0.3.25': dependencies: '@jridgewell/resolve-uri': 3.1.2 - '@jridgewell/sourcemap-codec': 1.4.15 + '@jridgewell/sourcemap-codec': 1.5.0 - '@jridgewell/trace-mapping@0.3.9': + '@jridgewell/trace-mapping@0.3.31': dependencies: '@jridgewell/resolve-uri': 3.1.2 '@jridgewell/sourcemap-codec': 1.5.0 + + '@jridgewell/trace-mapping@0.3.9': + dependencies: + '@jridgewell/resolve-uri': 3.1.2 + '@jridgewell/sourcemap-codec': 1.5.5 optional: true '@jsdevtools/ono@7.1.3': {} @@ -7091,7 +7604,7 @@ snapshots: '@pkgjs/parseargs@0.11.0': optional: true - '@react-router/dev@7.0.0-pre.2(@react-router/serve@7.0.0-pre.2(react-router@7.0.0-pre.2(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(typescript@5.6.3))(@types/node@22.7.4)(react-router@7.0.0-pre.2(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(terser@5.34.1)(typescript@5.6.3)(vite@5.4.8(@types/node@22.7.4)(terser@5.34.1))': + '@react-router/dev@7.0.0-pre.2(@react-router/serve@7.0.0-pre.2(react-router@7.0.0-pre.2(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(typescript@5.6.3))(@types/node@22.20.1)(react-router@7.0.0-pre.2(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(terser@5.34.1)(typescript@5.6.3)(vite@5.4.8(@types/node@22.20.1)(terser@5.34.1))': dependencies: '@babel/core': 7.25.7 '@babel/generator': 7.25.9 @@ -7123,8 +7636,8 @@ snapshots: semver: 7.6.3 set-cookie-parser: 2.7.1 valibot: 0.41.0(typescript@5.6.3) - vite: 5.4.8(@types/node@22.7.4)(terser@5.34.1) - vite-node: 1.6.0(@types/node@22.7.4)(terser@5.34.1) + vite: 5.4.8(@types/node@22.20.1)(terser@5.34.1) + vite-node: 1.6.0(@types/node@22.20.1)(terser@5.34.1) optionalDependencies: '@react-router/serve': 7.0.0-pre.2(react-router@7.0.0-pre.2(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(typescript@5.6.3) typescript: 5.6.3 @@ -7149,9 +7662,9 @@ snapshots: optionalDependencies: typescript: 5.6.3 - '@react-router/fs-routes@7.0.0-pre.2(@react-router/dev@7.0.0-pre.2(@react-router/serve@7.0.0-pre.2(react-router@7.0.0-pre.2(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(typescript@5.6.3))(@types/node@22.7.4)(react-router@7.0.0-pre.2(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(terser@5.34.1)(typescript@5.6.3)(vite@5.4.8(@types/node@22.7.4)(terser@5.34.1)))(typescript@5.6.3)': + '@react-router/fs-routes@7.0.0-pre.2(@react-router/dev@7.0.0-pre.2(@react-router/serve@7.0.0-pre.2(react-router@7.0.0-pre.2(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(typescript@5.6.3))(@types/node@22.20.1)(react-router@7.0.0-pre.2(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(terser@5.34.1)(typescript@5.6.3)(vite@5.4.8(@types/node@22.20.1)(terser@5.34.1)))(typescript@5.6.3)': dependencies: - '@react-router/dev': 7.0.0-pre.2(@react-router/serve@7.0.0-pre.2(react-router@7.0.0-pre.2(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(typescript@5.6.3))(@types/node@22.7.4)(react-router@7.0.0-pre.2(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(terser@5.34.1)(typescript@5.6.3)(vite@5.4.8(@types/node@22.7.4)(terser@5.34.1)) + '@react-router/dev': 7.0.0-pre.2(@react-router/serve@7.0.0-pre.2(react-router@7.0.0-pre.2(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(typescript@5.6.3))(@types/node@22.20.1)(react-router@7.0.0-pre.2(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(terser@5.34.1)(typescript@5.6.3)(vite@5.4.8(@types/node@22.20.1)(terser@5.34.1)) minimatch: 9.0.4 optionalDependencies: typescript: 5.6.3 @@ -7182,13 +7695,13 @@ snapshots: '@remix-run/router@1.20.0': {} - '@rollup/pluginutils@5.1.2(rollup@4.24.0)': + '@rollup/pluginutils@5.1.2(rollup@4.62.2)': dependencies: '@types/estree': 1.0.5 estree-walker: 2.0.2 picomatch: 2.3.1 optionalDependencies: - rollup: 4.24.0 + rollup: 4.62.2 '@rollup/rollup-android-arm-eabi@4.18.0': optional: true @@ -7196,96 +7709,171 @@ snapshots: '@rollup/rollup-android-arm-eabi@4.24.0': optional: true + '@rollup/rollup-android-arm-eabi@4.62.2': + optional: true + '@rollup/rollup-android-arm64@4.18.0': optional: true '@rollup/rollup-android-arm64@4.24.0': optional: true + '@rollup/rollup-android-arm64@4.62.2': + optional: true + '@rollup/rollup-darwin-arm64@4.18.0': optional: true '@rollup/rollup-darwin-arm64@4.24.0': optional: true + '@rollup/rollup-darwin-arm64@4.62.2': + optional: true + '@rollup/rollup-darwin-x64@4.18.0': optional: true '@rollup/rollup-darwin-x64@4.24.0': optional: true + '@rollup/rollup-darwin-x64@4.62.2': + optional: true + + '@rollup/rollup-freebsd-arm64@4.62.2': + optional: true + + '@rollup/rollup-freebsd-x64@4.62.2': + optional: true + '@rollup/rollup-linux-arm-gnueabihf@4.18.0': optional: true '@rollup/rollup-linux-arm-gnueabihf@4.24.0': optional: true + '@rollup/rollup-linux-arm-gnueabihf@4.62.2': + optional: true + '@rollup/rollup-linux-arm-musleabihf@4.18.0': optional: true '@rollup/rollup-linux-arm-musleabihf@4.24.0': optional: true + '@rollup/rollup-linux-arm-musleabihf@4.62.2': + optional: true + '@rollup/rollup-linux-arm64-gnu@4.18.0': optional: true '@rollup/rollup-linux-arm64-gnu@4.24.0': optional: true + '@rollup/rollup-linux-arm64-gnu@4.62.2': + optional: true + '@rollup/rollup-linux-arm64-musl@4.18.0': optional: true '@rollup/rollup-linux-arm64-musl@4.24.0': optional: true + '@rollup/rollup-linux-arm64-musl@4.62.2': + optional: true + + '@rollup/rollup-linux-loong64-gnu@4.62.2': + optional: true + + '@rollup/rollup-linux-loong64-musl@4.62.2': + optional: true + '@rollup/rollup-linux-powerpc64le-gnu@4.18.0': optional: true '@rollup/rollup-linux-powerpc64le-gnu@4.24.0': optional: true + '@rollup/rollup-linux-ppc64-gnu@4.62.2': + optional: true + + '@rollup/rollup-linux-ppc64-musl@4.62.2': + optional: true + '@rollup/rollup-linux-riscv64-gnu@4.18.0': optional: true '@rollup/rollup-linux-riscv64-gnu@4.24.0': optional: true + '@rollup/rollup-linux-riscv64-gnu@4.62.2': + optional: true + + '@rollup/rollup-linux-riscv64-musl@4.62.2': + optional: true + '@rollup/rollup-linux-s390x-gnu@4.18.0': optional: true '@rollup/rollup-linux-s390x-gnu@4.24.0': optional: true + '@rollup/rollup-linux-s390x-gnu@4.62.2': + optional: true + '@rollup/rollup-linux-x64-gnu@4.18.0': optional: true '@rollup/rollup-linux-x64-gnu@4.24.0': optional: true + '@rollup/rollup-linux-x64-gnu@4.62.2': + optional: true + '@rollup/rollup-linux-x64-musl@4.18.0': optional: true '@rollup/rollup-linux-x64-musl@4.24.0': optional: true + '@rollup/rollup-linux-x64-musl@4.62.2': + optional: true + + '@rollup/rollup-openbsd-x64@4.62.2': + optional: true + + '@rollup/rollup-openharmony-arm64@4.62.2': + optional: true + '@rollup/rollup-win32-arm64-msvc@4.18.0': optional: true '@rollup/rollup-win32-arm64-msvc@4.24.0': optional: true + '@rollup/rollup-win32-arm64-msvc@4.62.2': + optional: true + '@rollup/rollup-win32-ia32-msvc@4.18.0': optional: true '@rollup/rollup-win32-ia32-msvc@4.24.0': optional: true + '@rollup/rollup-win32-ia32-msvc@4.62.2': + optional: true + + '@rollup/rollup-win32-x64-gnu@4.62.2': + optional: true + '@rollup/rollup-win32-x64-msvc@4.18.0': optional: true '@rollup/rollup-win32-x64-msvc@4.24.0': optional: true + '@rollup/rollup-win32-x64-msvc@4.62.2': + optional: true + '@shikijs/core@1.22.0': dependencies: '@shikijs/engine-javascript': 1.22.0 @@ -7313,7 +7901,7 @@ snapshots: '@shikijs/vscode-textmate@9.3.0': {} - '@sinclair/typebox@0.27.8': {} + '@standard-schema/spec@1.1.0': {} '@stoplight/http-spec@7.0.3': dependencies: @@ -7552,7 +8140,7 @@ snapshots: tsx: 4.19.1 zod: 3.23.8 - '@tanstack/router-plugin@1.62.0(vite@5.4.8(@types/node@22.7.4)(terser@5.34.1))': + '@tanstack/router-plugin@1.62.0(vite@5.4.8(@types/node@22.20.1)(terser@5.34.1))': dependencies: '@babel/core': 7.25.7 '@babel/generator': 7.25.7 @@ -7573,7 +8161,7 @@ snapshots: unplugin: 1.14.1 zod: 3.23.8 optionalDependencies: - vite: 5.4.8(@types/node@22.7.4)(terser@5.34.1) + vite: 5.4.8(@types/node@22.20.1)(terser@5.34.1) transitivePeerDependencies: - supports-color - webpack-sources @@ -7627,16 +8215,23 @@ snapshots: dependencies: '@babel/types': 7.25.7 + '@types/chai@5.2.3': + dependencies: + '@types/deep-eql': 4.0.2 + assertion-error: 2.0.1 + '@types/cookie@0.6.0': {} '@types/cross-spawn@6.0.6': dependencies: - '@types/node': 20.14.2 + '@types/node': 22.20.1 '@types/debug@4.1.12': dependencies: '@types/ms': 0.7.34 + '@types/deep-eql@4.0.2': {} + '@types/estree-jsx@1.0.5': dependencies: '@types/estree': 1.0.6 @@ -7645,6 +8240,8 @@ snapshots: '@types/estree@1.0.6': {} + '@types/estree@1.0.9': {} + '@types/hast@3.0.4': dependencies: '@types/unist': 3.0.3 @@ -7671,6 +8268,10 @@ snapshots: dependencies: undici-types: 5.26.5 + '@types/node@22.20.1': + dependencies: + undici-types: 6.21.0 + '@types/node@22.7.4': dependencies: undici-types: 6.19.8 @@ -7696,7 +8297,7 @@ snapshots: '@types/type-is@1.6.6': dependencies: - '@types/node': 20.14.2 + '@types/node': 22.7.4 '@types/unist@2.0.11': {} @@ -7704,75 +8305,82 @@ snapshots: '@ungap/structured-clone@1.2.0': {} - '@vitejs/plugin-react@4.3.1(vite@5.2.13(@types/node@22.7.4)(terser@5.34.1))': + '@vitejs/plugin-react@4.3.1(vite@5.2.13(@types/node@22.20.1)(terser@5.34.1))': dependencies: '@babel/core': 7.24.7 '@babel/plugin-transform-react-jsx-self': 7.24.7(@babel/core@7.24.7) '@babel/plugin-transform-react-jsx-source': 7.24.7(@babel/core@7.24.7) '@types/babel__core': 7.20.5 react-refresh: 0.14.2 - vite: 5.2.13(@types/node@22.7.4)(terser@5.34.1) + vite: 5.2.13(@types/node@22.20.1)(terser@5.34.1) transitivePeerDependencies: - supports-color - '@vitejs/plugin-react@4.3.1(vite@5.4.8(@types/node@22.7.4)(terser@5.34.1))': + '@vitejs/plugin-react@4.3.1(vite@5.4.8(@types/node@22.20.1)(terser@5.34.1))': dependencies: '@babel/core': 7.24.7 '@babel/plugin-transform-react-jsx-self': 7.24.7(@babel/core@7.24.7) '@babel/plugin-transform-react-jsx-source': 7.24.7(@babel/core@7.24.7) '@types/babel__core': 7.20.5 react-refresh: 0.14.2 - vite: 5.4.8(@types/node@22.7.4)(terser@5.34.1) + vite: 5.4.8(@types/node@22.20.1)(terser@5.34.1) transitivePeerDependencies: - supports-color - '@vitest/coverage-v8@1.6.0(vitest@1.6.0(@types/node@22.7.4)(terser@5.34.1))': + '@vitest/coverage-v8@4.1.10(vitest@4.1.10)': dependencies: - '@ampproject/remapping': 2.3.0 - '@bcoe/v8-coverage': 0.2.3 - debug: 4.3.5 + '@bcoe/v8-coverage': 1.0.2 + '@vitest/utils': 4.1.10 + ast-v8-to-istanbul: 1.0.5 istanbul-lib-coverage: 3.2.2 istanbul-lib-report: 3.0.1 - istanbul-lib-source-maps: 5.0.4 - istanbul-reports: 3.1.7 - magic-string: 0.30.10 - magicast: 0.3.4 - picocolors: 1.0.1 - std-env: 3.7.0 - strip-literal: 2.1.0 - test-exclude: 6.0.0 - vitest: 1.6.0(@types/node@22.7.4)(terser@5.34.1) - transitivePeerDependencies: - - supports-color + istanbul-reports: 3.2.0 + magicast: 0.5.3 + obug: 2.1.4 + std-env: 4.2.0 + tinyrainbow: 3.1.0 + vitest: 4.1.10(@types/node@22.20.1)(@vitest/coverage-v8@4.1.10)(vite@7.3.6(@types/node@22.20.1)(jiti@2.6.1)(terser@5.34.1)(tsx@4.19.1)(yaml@2.5.1)) - '@vitest/expect@1.6.0': + '@vitest/expect@4.1.10': dependencies: - '@vitest/spy': 1.6.0 - '@vitest/utils': 1.6.0 - chai: 4.4.1 + '@standard-schema/spec': 1.1.0 + '@types/chai': 5.2.3 + '@vitest/spy': 4.1.10 + '@vitest/utils': 4.1.10 + chai: 6.2.2 + tinyrainbow: 3.1.0 - '@vitest/runner@1.6.0': + '@vitest/mocker@4.1.10(vite@7.3.6(@types/node@22.20.1)(jiti@2.6.1)(terser@5.34.1)(tsx@4.19.1)(yaml@2.5.1))': dependencies: - '@vitest/utils': 1.6.0 - p-limit: 5.0.0 - pathe: 1.1.2 + '@vitest/spy': 4.1.10 + estree-walker: 3.0.3 + magic-string: 0.30.21 + optionalDependencies: + vite: 7.3.6(@types/node@22.20.1)(jiti@2.6.1)(terser@5.34.1)(tsx@4.19.1)(yaml@2.5.1) - '@vitest/snapshot@1.6.0': + '@vitest/pretty-format@4.1.10': dependencies: - magic-string: 0.30.10 - pathe: 1.1.2 - pretty-format: 29.7.0 + tinyrainbow: 3.1.0 - '@vitest/spy@1.6.0': + '@vitest/runner@4.1.10': dependencies: - tinyspy: 2.2.1 + '@vitest/utils': 4.1.10 + pathe: 2.0.3 - '@vitest/utils@1.6.0': + '@vitest/snapshot@4.1.10': dependencies: - diff-sequences: 29.6.3 - estree-walker: 3.0.3 - loupe: 2.3.7 - pretty-format: 29.7.0 + '@vitest/pretty-format': 4.1.10 + '@vitest/utils': 4.1.10 + magic-string: 0.30.21 + pathe: 2.0.3 + + '@vitest/spy@4.1.10': {} + + '@vitest/utils@4.1.10': + dependencies: + '@vitest/pretty-format': 4.1.10 + convert-source-map: 2.0.0 + tinyrainbow: 3.1.0 '@volar/kit@2.4.6(typescript@5.6.3)': dependencies: @@ -7837,9 +8445,8 @@ snapshots: dependencies: acorn: 8.12.1 - acorn-walk@8.3.2: {} - - acorn@8.11.3: {} + acorn-walk@8.3.2: + optional: true acorn@8.12.1: {} @@ -7878,8 +8485,6 @@ snapshots: dependencies: color-convert: 2.0.1 - ansi-styles@5.2.0: {} - ansi-styles@6.2.1: {} any-promise@1.3.0: {} @@ -7922,16 +8527,22 @@ snapshots: is-array-buffer: 3.0.4 is-shared-array-buffer: 1.0.3 - assertion-error@1.1.0: {} + assertion-error@2.0.1: {} + + ast-v8-to-istanbul@1.0.5: + dependencies: + '@jridgewell/trace-mapping': 0.3.31 + estree-walker: 3.0.3 + js-tokens: 10.0.0 astring@1.9.0: {} - astro-expressive-code@0.35.6(astro@4.16.2(@types/node@22.7.4)(rollup@4.24.0)(terser@5.34.1)(typescript@5.6.3)): + astro-expressive-code@0.35.6(astro@4.16.2(@types/node@22.20.1)(rollup@4.62.2)(terser@5.34.1)(typescript@5.6.3)): dependencies: - astro: 4.16.2(@types/node@22.7.4)(rollup@4.24.0)(terser@5.34.1)(typescript@5.6.3) + astro: 4.16.2(@types/node@22.20.1)(rollup@4.62.2)(terser@5.34.1)(typescript@5.6.3) rehype-expressive-code: 0.35.6 - astro@4.16.2(@types/node@22.7.4)(rollup@4.24.0)(terser@5.34.1)(typescript@5.6.3): + astro@4.16.2(@types/node@22.20.1)(rollup@4.62.2)(terser@5.34.1)(typescript@5.6.3): dependencies: '@astrojs/compiler': 2.10.3 '@astrojs/internal-helpers': 0.4.1 @@ -7941,7 +8552,7 @@ snapshots: '@babel/plugin-transform-react-jsx': 7.25.7(@babel/core@7.25.7) '@babel/types': 7.25.7 '@oslojs/encoding': 1.1.0 - '@rollup/pluginutils': 5.1.2(rollup@4.24.0) + '@rollup/pluginutils': 5.1.2(rollup@4.62.2) '@types/babel__core': 7.20.5 '@types/cookie': 0.6.0 acorn: 8.12.1 @@ -7987,8 +8598,8 @@ snapshots: tsconfck: 3.1.3(typescript@5.6.3) unist-util-visit: 5.0.0 vfile: 6.0.3 - vite: 5.4.8(@types/node@22.7.4)(terser@5.34.1) - vitefu: 1.0.2(vite@5.4.8(@types/node@22.7.4)(terser@5.34.1)) + vite: 5.4.8(@types/node@22.20.1)(terser@5.34.1) + vitefu: 1.0.2(vite@5.4.8(@types/node@22.20.1)(terser@5.34.1)) which-pm: 3.0.0 xxhash-wasm: 1.0.2 yargs-parser: 21.1.1 @@ -8043,6 +8654,8 @@ snapshots: balanced-match@1.0.2: {} + balanced-match@4.0.4: {} + bare-events@2.5.0: optional: true @@ -8132,6 +8745,10 @@ snapshots: dependencies: balanced-match: 1.0.2 + brace-expansion@5.0.7: + dependencies: + balanced-match: 4.0.4 + braces@3.0.3: dependencies: fill-range: 7.1.1 @@ -8173,7 +8790,7 @@ snapshots: bytes@3.1.2: {} - c12@3.3.4(magicast@0.3.5): + c12@3.3.4(magicast@0.5.3): dependencies: chokidar: 5.0.0 confbox: 0.2.4 @@ -8188,7 +8805,7 @@ snapshots: pkg-types: 2.3.0 rc9: 3.0.1 optionalDependencies: - magicast: 0.3.5 + magicast: 0.5.3 cac@6.7.14: {} @@ -8214,15 +8831,7 @@ snapshots: ccount@2.0.1: {} - chai@4.4.1: - dependencies: - assertion-error: 1.1.0 - check-error: 1.0.3 - deep-eql: 4.1.4 - get-func-name: 2.0.2 - loupe: 2.3.7 - pathval: 1.1.1 - type-detect: 4.0.8 + chai@6.2.2: {} chalk@2.4.2: dependencies: @@ -8247,10 +8856,6 @@ snapshots: charset@1.0.1: {} - check-error@1.0.3: - dependencies: - get-func-name: 2.0.2 - chokidar@3.6.0: dependencies: anymatch: 3.1.3 @@ -8333,8 +8938,6 @@ snapshots: comma-separated-tokens@2.0.3: {} - commander@12.1.0: {} - commander@15.0.0: {} commander@2.20.3: @@ -8375,8 +8978,6 @@ snapshots: concat-map@0.0.1: {} - confbox@0.1.7: {} - confbox@0.2.4: {} content-disposition@0.5.4: @@ -8472,10 +9073,6 @@ snapshots: dedent@1.5.3: {} - deep-eql@4.1.4: - dependencies: - type-detect: 4.0.8 - deep-extend@0.6.0: {} default-browser-id@5.0.1: {} @@ -8525,8 +9122,6 @@ snapshots: didyoumean@1.2.2: {} - diff-sequences@29.6.3: {} - diff@4.0.2: optional: true @@ -8639,6 +9234,8 @@ snapshots: es-module-lexer@1.5.4: {} + es-module-lexer@2.3.1: {} + es-object-atoms@1.0.0: dependencies: es-errors: 1.3.0 @@ -8734,6 +9331,35 @@ snapshots: '@esbuild/win32-ia32': 0.23.1 '@esbuild/win32-x64': 0.23.1 + esbuild@0.28.1: + optionalDependencies: + '@esbuild/aix-ppc64': 0.28.1 + '@esbuild/android-arm': 0.28.1 + '@esbuild/android-arm64': 0.28.1 + '@esbuild/android-x64': 0.28.1 + '@esbuild/darwin-arm64': 0.28.1 + '@esbuild/darwin-x64': 0.28.1 + '@esbuild/freebsd-arm64': 0.28.1 + '@esbuild/freebsd-x64': 0.28.1 + '@esbuild/linux-arm': 0.28.1 + '@esbuild/linux-arm64': 0.28.1 + '@esbuild/linux-ia32': 0.28.1 + '@esbuild/linux-loong64': 0.28.1 + '@esbuild/linux-mips64el': 0.28.1 + '@esbuild/linux-ppc64': 0.28.1 + '@esbuild/linux-riscv64': 0.28.1 + '@esbuild/linux-s390x': 0.28.1 + '@esbuild/linux-x64': 0.28.1 + '@esbuild/netbsd-arm64': 0.28.1 + '@esbuild/netbsd-x64': 0.28.1 + '@esbuild/openbsd-arm64': 0.28.1 + '@esbuild/openbsd-x64': 0.28.1 + '@esbuild/openharmony-arm64': 0.28.1 + '@esbuild/sunos-x64': 0.28.1 + '@esbuild/win32-arm64': 0.28.1 + '@esbuild/win32-ia32': 0.28.1 + '@esbuild/win32-x64': 0.28.1 + escalade@3.1.2: {} escalade@3.2.0: {} @@ -8780,22 +9406,12 @@ snapshots: eventemitter3@5.0.1: {} - execa@8.0.1: - dependencies: - cross-spawn: 7.0.6 - get-stream: 8.0.1 - human-signals: 5.0.0 - is-stream: 3.0.0 - merge-stream: 2.0.0 - npm-run-path: 5.3.0 - onetime: 6.0.0 - signal-exit: 4.1.0 - strip-final-newline: 3.0.0 - exit-hook@2.2.1: {} expand-template@2.0.3: {} + expect-type@1.4.0: {} + express@4.21.1: dependencies: accepts: 1.3.8 @@ -8956,8 +9572,6 @@ snapshots: jsonfile: 6.1.0 universalify: 2.0.1 - fs.realpath@1.0.0: {} - fsevents@2.3.3: optional: true @@ -8978,8 +9592,6 @@ snapshots: get-east-asian-width@1.2.0: {} - get-func-name@2.0.2: {} - get-intrinsic@1.2.4: dependencies: es-errors: 1.3.0 @@ -8990,8 +9602,6 @@ snapshots: get-port@5.1.1: {} - get-stream@8.0.1: {} - get-symbol-description@1.0.2: dependencies: call-bind: 1.0.7 @@ -9028,14 +9638,11 @@ snapshots: minipass: 7.1.2 path-scurry: 1.11.1 - glob@7.2.3: + glob@13.0.6: dependencies: - fs.realpath: 1.0.0 - inflight: 1.0.6 - inherits: 2.0.4 - minimatch: 3.1.2 - once: 1.4.0 - path-is-absolute: 1.0.1 + minimatch: 10.2.5 + minipass: 7.1.3 + path-scurry: 2.0.2 globals@11.12.0: {} @@ -9332,8 +9939,6 @@ snapshots: transitivePeerDependencies: - supports-color - human-signals@5.0.0: {} - i18next@23.15.2: dependencies: '@babel/runtime': 7.25.7 @@ -9350,11 +9955,6 @@ snapshots: import-meta-resolve@4.1.0: {} - inflight@1.0.6: - dependencies: - once: 1.4.0 - wrappy: 1.0.2 - inherits@2.0.4: {} ini@1.3.8: {} @@ -9469,8 +10069,6 @@ snapshots: dependencies: call-bind: 1.0.7 - is-stream@3.0.0: {} - is-string@1.0.7: dependencies: has-tostringtag: 1.0.2 @@ -9518,15 +10116,7 @@ snapshots: make-dir: 4.0.0 supports-color: 7.2.0 - istanbul-lib-source-maps@5.0.4: - dependencies: - '@jridgewell/trace-mapping': 0.3.25 - debug: 4.3.5 - istanbul-lib-coverage: 3.2.2 - transitivePeerDependencies: - - supports-color - - istanbul-reports@3.1.7: + istanbul-reports@3.2.0: dependencies: html-escaper: 2.0.2 istanbul-lib-report: 3.0.1 @@ -9541,9 +10131,9 @@ snapshots: jiti@2.6.1: {} - js-tokens@4.0.0: {} + js-tokens@10.0.0: {} - js-tokens@9.0.0: {} + js-tokens@4.0.0: {} js-yaml@3.14.1: dependencies: @@ -9613,40 +10203,48 @@ snapshots: kleur@4.1.5: {} - lefthook-darwin-arm64@1.6.15: + lefthook-darwin-arm64@2.1.10: + optional: true + + lefthook-darwin-x64@2.1.10: + optional: true + + lefthook-freebsd-arm64@2.1.10: optional: true - lefthook-darwin-x64@1.6.15: + lefthook-freebsd-x64@2.1.10: optional: true - lefthook-freebsd-arm64@1.6.15: + lefthook-linux-arm64@2.1.10: optional: true - lefthook-freebsd-x64@1.6.15: + lefthook-linux-x64@2.1.10: optional: true - lefthook-linux-arm64@1.6.15: + lefthook-openbsd-arm64@2.1.10: optional: true - lefthook-linux-x64@1.6.15: + lefthook-openbsd-x64@2.1.10: optional: true - lefthook-windows-arm64@1.6.15: + lefthook-windows-arm64@2.1.10: optional: true - lefthook-windows-x64@1.6.15: + lefthook-windows-x64@2.1.10: optional: true - lefthook@1.6.15: + lefthook@2.1.10: optionalDependencies: - lefthook-darwin-arm64: 1.6.15 - lefthook-darwin-x64: 1.6.15 - lefthook-freebsd-arm64: 1.6.15 - lefthook-freebsd-x64: 1.6.15 - lefthook-linux-arm64: 1.6.15 - lefthook-linux-x64: 1.6.15 - lefthook-windows-arm64: 1.6.15 - lefthook-windows-x64: 1.6.15 + lefthook-darwin-arm64: 2.1.10 + lefthook-darwin-x64: 2.1.10 + lefthook-freebsd-arm64: 2.1.10 + lefthook-freebsd-x64: 2.1.10 + lefthook-linux-arm64: 2.1.10 + lefthook-linux-x64: 2.1.10 + lefthook-openbsd-arm64: 2.1.10 + lefthook-openbsd-x64: 2.1.10 + lefthook-windows-arm64: 2.1.10 + lefthook-windows-x64: 2.1.10 lilconfig@2.1.0: {} @@ -9670,11 +10268,6 @@ snapshots: pify: 4.0.1 strip-bom: 3.0.0 - local-pkg@0.5.0: - dependencies: - mlly: 1.7.1 - pkg-types: 1.1.1 - locate-path@2.0.0: dependencies: p-locate: 2.0.0 @@ -9699,12 +10292,10 @@ snapshots: dependencies: js-tokens: 4.0.0 - loupe@2.3.7: - dependencies: - get-func-name: 2.0.2 - lru-cache@10.2.2: {} + lru-cache@11.5.2: {} + lru-cache@5.1.1: dependencies: yallist: 3.1.1 @@ -9715,19 +10306,13 @@ snapshots: lru-cache@7.18.3: {} - magic-string@0.30.10: - dependencies: - '@jridgewell/sourcemap-codec': 1.4.15 - magic-string@0.30.11: dependencies: '@jridgewell/sourcemap-codec': 1.5.0 - magicast@0.3.4: + magic-string@0.30.21: dependencies: - '@babel/parser': 7.24.7 - '@babel/types': 7.24.7 - source-map-js: 1.2.0 + '@jridgewell/sourcemap-codec': 1.5.5 magicast@0.3.5: dependencies: @@ -9735,9 +10320,15 @@ snapshots: '@babel/types': 7.25.7 source-map-js: 1.2.1 + magicast@0.5.3: + dependencies: + '@babel/parser': 7.29.7 + '@babel/types': 7.29.7 + source-map-js: 1.2.1 + make-dir@4.0.0: dependencies: - semver: 7.7.3 + semver: 7.8.4 make-error@1.3.6: optional: true @@ -9933,8 +10524,6 @@ snapshots: merge-descriptors@1.0.3: {} - merge-stream@2.0.0: {} - merge2@1.4.1: {} methods@1.1.2: {} @@ -10241,8 +10830,6 @@ snapshots: mime@1.6.0: {} - mimic-fn@4.0.0: {} - mimic-function@5.0.1: {} mimic-response@3.1.0: {} @@ -10251,6 +10838,10 @@ snapshots: dependencies: '@isaacs/brace-expansion': 5.0.0 + minimatch@10.2.5: + dependencies: + brace-expansion: 5.0.7 + minimatch@3.1.2: dependencies: brace-expansion: 1.1.11 @@ -10265,19 +10856,14 @@ snapshots: minipass@7.1.2: {} + minipass@7.1.3: {} + mkdirp-classic@0.5.3: {} mkdirp@0.5.6: dependencies: minimist: 1.2.8 - mlly@1.7.1: - dependencies: - acorn: 8.11.3 - pathe: 1.1.2 - pkg-types: 1.1.1 - ufo: 1.5.3 - morgan@1.10.0: dependencies: basic-auth: 2.0.1 @@ -10304,6 +10890,8 @@ snapshots: object-assign: 4.1.1 thenify-all: 1.6.0 + nanoid@3.3.16: {} + nanoid@3.3.7: {} napi-build-utils@1.0.2: {} @@ -10377,7 +10965,7 @@ snapshots: npm-install-checks@6.3.0: dependencies: - semver: 7.7.3 + semver: 7.8.4 npm-normalize-package-bin@3.0.1: {} @@ -10385,7 +10973,7 @@ snapshots: dependencies: hosted-git-info: 6.1.1 proc-log: 3.0.0 - semver: 7.7.3 + semver: 7.8.4 validate-npm-package-name: 5.0.1 npm-pick-manifest@8.0.2: @@ -10393,7 +10981,7 @@ snapshots: npm-install-checks: 6.3.0 npm-normalize-package-bin: 3.0.1 npm-package-arg: 10.1.0 - semver: 7.7.3 + semver: 7.8.4 npm-run-all@4.1.5: dependencies: @@ -10407,10 +10995,6 @@ snapshots: shell-quote: 1.8.1 string.prototype.padend: 3.1.6 - npm-run-path@5.3.0: - dependencies: - path-key: 4.0.0 - nth-check@2.1.1: dependencies: boolbase: 1.0.0 @@ -10430,6 +11014,8 @@ snapshots: has-symbols: 1.0.3 object-keys: 1.1.1 + obug@2.1.4: {} + ohash@2.0.11: {} on-finished@2.3.0: @@ -10446,10 +11032,6 @@ snapshots: dependencies: wrappy: 1.0.2 - onetime@6.0.0: - dependencies: - mimic-fn: 4.0.0 - onetime@7.0.0: dependencies: mimic-function: 5.0.1 @@ -10495,10 +11077,6 @@ snapshots: dependencies: p-try: 2.2.0 - p-limit@5.0.0: - dependencies: - yocto-queue: 1.0.0 - p-limit@6.1.0: dependencies: yocto-queue: 1.1.1 @@ -10522,6 +11100,8 @@ snapshots: p-try@2.2.0: {} + package-json-from-dist@1.0.1: {} + pagefind@1.1.1: optionalDependencies: '@pagefind/darwin-arm64': 1.1.1 @@ -10575,14 +11155,10 @@ snapshots: path-exists@4.0.0: {} - path-is-absolute@1.0.1: {} - path-key@2.0.1: {} path-key@3.1.1: {} - path-key@4.0.0: {} - path-parse@1.0.7: {} path-scurry@1.11.1: @@ -10590,6 +11166,11 @@ snapshots: lru-cache: 10.2.2 minipass: 7.1.2 + path-scurry@2.0.2: + dependencies: + lru-cache: 11.5.2 + minipass: 7.1.3 + path-to-regexp@0.1.10: {} path-type@3.0.0: @@ -10600,8 +11181,6 @@ snapshots: pathe@2.0.3: {} - pathval@1.1.1: {} - peek-stream@1.1.3: dependencies: buffer-from: 1.1.2 @@ -10620,6 +11199,8 @@ snapshots: picocolors@1.1.0: {} + picocolors@1.1.1: {} + picomatch@2.3.1: {} picomatch@4.0.3: {} @@ -10655,12 +11236,6 @@ snapshots: dependencies: find-up: 4.1.0 - pkg-types@1.1.1: - dependencies: - confbox: 0.1.7 - mlly: 1.7.1 - pathe: 1.1.2 - pkg-types@2.3.0: dependencies: confbox: 0.2.4 @@ -10724,6 +11299,12 @@ snapshots: picocolors: 1.1.0 source-map-js: 1.2.1 + postcss@8.5.19: + dependencies: + nanoid: 3.3.16 + picocolors: 1.1.1 + source-map-js: 1.2.1 + postman-collection@4.4.0: dependencies: '@faker-js/faker': 5.5.3 @@ -10771,12 +11352,6 @@ snapshots: pretty-data@0.40.0: {} - pretty-format@29.7.0: - dependencies: - '@jest/schemas': 29.6.3 - ansi-styles: 5.2.0 - react-is: 18.3.1 - prismjs@1.29.0: {} proc-log@3.0.0: {} @@ -10861,8 +11436,6 @@ snapshots: react: 18.3.1 scheduler: 0.23.2 - react-is@18.3.1: {} - react-refresh@0.14.2: {} react-router-dom@6.27.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1): @@ -11085,9 +11658,10 @@ snapshots: reusify@1.0.4: {} - rimraf@5.0.7: + rimraf@6.1.3: dependencies: - glob: 10.4.1 + glob: 13.0.6 + package-json-from-dist: 1.0.1 rollup@4.18.0: dependencies: @@ -11133,6 +11707,37 @@ snapshots: '@rollup/rollup-win32-x64-msvc': 4.24.0 fsevents: 2.3.3 + rollup@4.62.2: + dependencies: + '@types/estree': 1.0.9 + optionalDependencies: + '@rollup/rollup-android-arm-eabi': 4.62.2 + '@rollup/rollup-android-arm64': 4.62.2 + '@rollup/rollup-darwin-arm64': 4.62.2 + '@rollup/rollup-darwin-x64': 4.62.2 + '@rollup/rollup-freebsd-arm64': 4.62.2 + '@rollup/rollup-freebsd-x64': 4.62.2 + '@rollup/rollup-linux-arm-gnueabihf': 4.62.2 + '@rollup/rollup-linux-arm-musleabihf': 4.62.2 + '@rollup/rollup-linux-arm64-gnu': 4.62.2 + '@rollup/rollup-linux-arm64-musl': 4.62.2 + '@rollup/rollup-linux-loong64-gnu': 4.62.2 + '@rollup/rollup-linux-loong64-musl': 4.62.2 + '@rollup/rollup-linux-ppc64-gnu': 4.62.2 + '@rollup/rollup-linux-ppc64-musl': 4.62.2 + '@rollup/rollup-linux-riscv64-gnu': 4.62.2 + '@rollup/rollup-linux-riscv64-musl': 4.62.2 + '@rollup/rollup-linux-s390x-gnu': 4.62.2 + '@rollup/rollup-linux-x64-gnu': 4.62.2 + '@rollup/rollup-linux-x64-musl': 4.62.2 + '@rollup/rollup-openbsd-x64': 4.62.2 + '@rollup/rollup-openharmony-arm64': 4.62.2 + '@rollup/rollup-win32-arm64-msvc': 4.62.2 + '@rollup/rollup-win32-ia32-msvc': 4.62.2 + '@rollup/rollup-win32-x64-gnu': 4.62.2 + '@rollup/rollup-win32-x64-msvc': 4.62.2 + fsevents: 2.3.3 + run-applescript@7.1.0: {} run-parallel@1.2.0: @@ -11377,7 +11982,7 @@ snapshots: statuses@2.0.1: {} - std-env@3.7.0: {} + std-env@4.2.0: {} stdin-discarder@0.2.2: {} @@ -11466,14 +12071,8 @@ snapshots: strip-bom@3.0.0: {} - strip-final-newline@3.0.0: {} - strip-json-comments@2.0.1: {} - strip-literal@2.1.0: - dependencies: - js-tokens: 9.0.0 - strnum@1.0.5: {} style-to-object@0.4.4: @@ -11573,12 +12172,6 @@ snapshots: source-map-support: 0.5.21 optional: true - test-exclude@6.0.0: - dependencies: - '@istanbuljs/schema': 0.1.3 - glob: 7.2.3 - minimatch: 3.1.2 - text-decoder@1.2.0: dependencies: b4a: 1.6.7 @@ -11600,18 +12193,18 @@ snapshots: tiny-warning@1.0.3: {} - tinybench@2.8.0: {} + tinybench@2.9.0: {} tinyexec@0.3.0: {} + tinyexec@1.2.4: {} + tinyglobby@0.2.15: dependencies: fdir: 6.5.0(picomatch@4.0.3) picomatch: 4.0.3 - tinypool@0.8.4: {} - - tinyspy@2.2.1: {} + tinyrainbow@3.1.0: {} to-fast-properties@2.0.0: {} @@ -11672,8 +12265,6 @@ snapshots: turbo-stream@2.4.0: {} - type-detect@4.0.8: {} - type-fest@4.26.1: {} type-is@1.6.18: @@ -11717,7 +12308,7 @@ snapshots: typescript-auto-import-cache@0.3.3: dependencies: - semver: 7.7.3 + semver: 7.8.4 typescript@5.4.5: {} @@ -11725,8 +12316,6 @@ snapshots: typescript@6.0.3: {} - ufo@1.5.3: {} - unbox-primitive@1.0.2: dependencies: call-bind: 1.0.7 @@ -11738,6 +12327,8 @@ snapshots: undici-types@6.19.8: {} + undici-types@6.21.0: {} + undici@6.20.1: {} unified@11.0.5: @@ -11883,13 +12474,13 @@ snapshots: '@types/unist': 3.0.3 vfile-message: 4.0.2 - vite-node@1.6.0(@types/node@22.7.4)(terser@5.34.1): + vite-node@1.6.0(@types/node@22.20.1)(terser@5.34.1): dependencies: cac: 6.7.14 debug: 4.3.5 pathe: 1.1.2 picocolors: 1.0.1 - vite: 5.4.8(@types/node@22.7.4)(terser@5.34.1) + vite: 5.4.8(@types/node@22.20.1)(terser@5.34.1) transitivePeerDependencies: - '@types/node' - less @@ -11901,63 +12492,73 @@ snapshots: - supports-color - terser - vite@5.2.13(@types/node@22.7.4)(terser@5.34.1): + vite@5.2.13(@types/node@22.20.1)(terser@5.34.1): dependencies: esbuild: 0.20.2 postcss: 8.4.38 rollup: 4.18.0 optionalDependencies: - '@types/node': 22.7.4 + '@types/node': 22.20.1 fsevents: 2.3.3 terser: 5.34.1 - vite@5.4.8(@types/node@22.7.4)(terser@5.34.1): + vite@5.4.8(@types/node@22.20.1)(terser@5.34.1): dependencies: esbuild: 0.21.5 postcss: 8.4.47 rollup: 4.24.0 optionalDependencies: - '@types/node': 22.7.4 + '@types/node': 22.20.1 fsevents: 2.3.3 terser: 5.34.1 - vitefu@1.0.2(vite@5.4.8(@types/node@22.7.4)(terser@5.34.1)): + vite@7.3.6(@types/node@22.20.1)(jiti@2.6.1)(terser@5.34.1)(tsx@4.19.1)(yaml@2.5.1): + dependencies: + esbuild: 0.28.1 + fdir: 6.5.0(picomatch@4.0.3) + picomatch: 4.0.3 + postcss: 8.5.19 + rollup: 4.62.2 + tinyglobby: 0.2.15 optionalDependencies: - vite: 5.4.8(@types/node@22.7.4)(terser@5.34.1) + '@types/node': 22.20.1 + fsevents: 2.3.3 + jiti: 2.6.1 + terser: 5.34.1 + tsx: 4.19.1 + yaml: 2.5.1 - vitest@1.6.0(@types/node@22.7.4)(terser@5.34.1): - dependencies: - '@vitest/expect': 1.6.0 - '@vitest/runner': 1.6.0 - '@vitest/snapshot': 1.6.0 - '@vitest/spy': 1.6.0 - '@vitest/utils': 1.6.0 - acorn-walk: 8.3.2 - chai: 4.4.1 - debug: 4.3.5 - execa: 8.0.1 - local-pkg: 0.5.0 - magic-string: 0.30.10 - pathe: 1.1.2 - picocolors: 1.0.1 - std-env: 3.7.0 - strip-literal: 2.1.0 - tinybench: 2.8.0 - tinypool: 0.8.4 - vite: 5.2.13(@types/node@22.7.4)(terser@5.34.1) - vite-node: 1.6.0(@types/node@22.7.4)(terser@5.34.1) - why-is-node-running: 2.2.2 + vitefu@1.0.2(vite@5.4.8(@types/node@22.20.1)(terser@5.34.1)): optionalDependencies: - '@types/node': 22.7.4 + vite: 5.4.8(@types/node@22.20.1)(terser@5.34.1) + + vitest@4.1.10(@types/node@22.20.1)(@vitest/coverage-v8@4.1.10)(vite@7.3.6(@types/node@22.20.1)(jiti@2.6.1)(terser@5.34.1)(tsx@4.19.1)(yaml@2.5.1)): + dependencies: + '@vitest/expect': 4.1.10 + '@vitest/mocker': 4.1.10(vite@7.3.6(@types/node@22.20.1)(jiti@2.6.1)(terser@5.34.1)(tsx@4.19.1)(yaml@2.5.1)) + '@vitest/pretty-format': 4.1.10 + '@vitest/runner': 4.1.10 + '@vitest/snapshot': 4.1.10 + '@vitest/spy': 4.1.10 + '@vitest/utils': 4.1.10 + es-module-lexer: 2.3.1 + expect-type: 1.4.0 + magic-string: 0.30.21 + obug: 2.1.4 + pathe: 2.0.3 + picomatch: 4.0.3 + std-env: 4.2.0 + tinybench: 2.9.0 + tinyexec: 1.2.4 + tinyglobby: 0.2.15 + tinyrainbow: 3.1.0 + vite: 7.3.6(@types/node@22.20.1)(jiti@2.6.1)(terser@5.34.1)(tsx@4.19.1)(yaml@2.5.1) + why-is-node-running: 2.3.0 + optionalDependencies: + '@types/node': 22.20.1 + '@vitest/coverage-v8': 4.1.10(vitest@4.1.10) transitivePeerDependencies: - - less - - lightningcss - - sass - - sass-embedded - - stylus - - sugarss - - supports-color - - terser + - msw volar-service-css@0.0.61(@volar/language-service@2.4.6): dependencies: @@ -12120,7 +12721,7 @@ snapshots: dependencies: isexe: 2.0.0 - why-is-node-running@2.2.2: + why-is-node-running@2.3.0: dependencies: siginfo: 2.0.0 stackback: 0.0.2 @@ -12214,8 +12815,6 @@ snapshots: yn@3.1.1: optional: true - yocto-queue@1.0.0: {} - yocto-queue@1.1.1: {} zod-to-json-schema@3.23.3(zod@3.23.8): diff --git a/src/generate.mts b/src/generate.mts index e53b99d..594142e 100644 --- a/src/generate.mts +++ b/src/generate.mts @@ -1,6 +1,6 @@ import { writeFile } from "node:fs/promises"; import path from "node:path"; -import { type UserConfig, createClient } from "@hey-api/openapi-ts"; +import { createClient, type UserConfig } from "@hey-api/openapi-ts"; import type { LimitedUserConfig } from "./cli.mjs"; import { buildQueriesOutputPath, diff --git a/src/tsmorph/buildQueryHooks.mts b/src/tsmorph/buildQueryHooks.mts index 639f27a..26ddba2 100644 --- a/src/tsmorph/buildQueryHooks.mts +++ b/src/tsmorph/buildQueryHooks.mts @@ -39,20 +39,6 @@ function getDataTypeDefault( 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"; - } -} - /** * Resolve the generated Data type name for an operation, falling back to * unknown when the operation has no generated Data type. diff --git a/src/tsmorph/generateFiles.mts b/src/tsmorph/generateFiles.mts index 651d06f..3e76e61 100644 --- a/src/tsmorph/generateFiles.mts +++ b/src/tsmorph/generateFiles.mts @@ -1,10 +1,7 @@ import { type ExportDeclarationStructure, type ImportDeclarationStructure, - Project, StructureKind, - type TypeAliasDeclarationStructure, - type VariableStatementStructure, } from "ts-morph"; import { OpenApiRqFiles } from "../constants.mjs"; import type { @@ -83,7 +80,7 @@ function buildHookFileImports( /** * Generate the index.ts file content. */ -function generateIndexFile(ctx: GenerationContext): string { +function generateIndexFile(): string { const project = createGenerationProject(); const sourceFile = project.createSourceFile( `${OpenApiRqFiles.index}.ts`, @@ -374,7 +371,7 @@ export function generateAllFiles( return [ { name: `${OpenApiRqFiles.index}.ts`, - content: addHeaderComment(generateIndexFile(ctx), ctx.version), + content: addHeaderComment(generateIndexFile(), ctx.version), }, { name: `${OpenApiRqFiles.common}.ts`, diff --git a/src/tsmorph/index.mts b/src/tsmorph/index.mts index 59e3c0a..1d40289 100644 --- a/src/tsmorph/index.mts +++ b/src/tsmorph/index.mts @@ -1,5 +1,5 @@ -export { generateAllFiles } from "./generateFiles.mjs"; -export { createGenerationProject } from "./projectFactory.mjs"; export * from "./buildCommon.mjs"; -export * from "./buildQueryHooks.mjs"; export * from "./buildMutationHooks.mjs"; +export * from "./buildQueryHooks.mjs"; +export { generateAllFiles } from "./generateFiles.mjs"; +export { createGenerationProject } from "./projectFactory.mjs"; diff --git a/tests/common.test.ts b/tests/common.test.ts index ccbe714..613a7b0 100644 --- a/tests/common.test.ts +++ b/tests/common.test.ts @@ -15,8 +15,8 @@ import { exists, extractPropertiesFromObjectParam, formatOptions, - getClassNameFromClassNode, getClassesFromService, + getClassNameFromClassNode, getNameFromVariable, getShortType, getVariableArrowFunctionParameters, diff --git a/tests/service.test.ts b/tests/service.test.ts index a393783..d21ad26 100644 --- a/tests/service.test.ts +++ b/tests/service.test.ts @@ -3,6 +3,7 @@ import { Project } from "ts-morph"; import { afterAll, beforeAll, describe, expect, test } from "vitest"; import { getMethodsFromService, getServices } from "../src/service.mjs"; import { cleanOutputs, generateTSClients } from "./utils"; + const fileName = "service"; describe(fileName, () => { beforeAll(async () => await generateTSClients(fileName)); diff --git a/tests/tsmorph/projectFactory.test.ts b/tests/tsmorph/projectFactory.test.ts index afd3bcd..f4e0583 100644 --- a/tests/tsmorph/projectFactory.test.ts +++ b/tests/tsmorph/projectFactory.test.ts @@ -1,4 +1,4 @@ -import { IndentationText, QuoteKind, StructureKind } from "ts-morph"; +import { StructureKind } from "ts-morph"; import { describe, expect, it } from "vitest"; import { buildAxiosErrorImport, diff --git a/tests/utils.test.ts b/tests/utils.test.ts index 47e8d92..394b3f8 100644 --- a/tests/utils.test.ts +++ b/tests/utils.test.ts @@ -52,8 +52,6 @@ describe("utils", () => { * This is a test 2 */`; - const deprecated = true; - // find class node const foundNode = sourceFile.getClasses()[0]; diff --git a/tests/utils.ts b/tests/utils.ts index 63fc49d..9761863 100644 --- a/tests/utils.ts +++ b/tests/utils.ts @@ -1,7 +1,7 @@ import { existsSync } from "node:fs"; import { rm } from "node:fs/promises"; import path from "node:path"; -import { type UserConfig, createClient } from "@hey-api/openapi-ts"; +import { createClient, type UserConfig } from "@hey-api/openapi-ts"; export const outputPath = (prefix: string) => path.join("tests", `${prefix}-outputs`); From 43e0fc81bcef1e58efc365bcc3ce1ca518998d4d Mon Sep 17 00:00:00 2001 From: Urata Daiki <7nohe@users.noreply.github.com> Date: Sat, 18 Jul 2026 20:39:08 +0900 Subject: [PATCH 12/31] feat: declare @tanstack/react-query as a peer dependency and widen commander range Closes #134 --- package.json | 3 ++- pnpm-lock.yaml | 3 +++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/package.json b/package.json index 5a8bc9d..648fee3 100644 --- a/package.json +++ b/package.json @@ -69,7 +69,8 @@ "vitest": "^4.1.10" }, "peerDependencies": { - "commander": "12.x", + "@tanstack/react-query": "^5.0.0", + "commander": "12.x || 13.x || 14.x || 15.x", "ts-morph": "28.x", "typescript": "5.x || 6.x" }, diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 5654994..4a489c5 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -19,6 +19,9 @@ importers: '@hey-api/openapi-ts': specifier: 0.99.0 version: 0.99.0(patch_hash=uzpdsaqpo2bd4ydyvbzbqpdgxq)(magicast@0.5.3)(typescript@6.0.3) + '@tanstack/react-query': + specifier: ^5.0.0 + version: 5.59.13(react@18.3.1) cross-spawn: specifier: ^7.0.3 version: 7.0.3 From a85f3b1036fb57b4037ed6ef18fcf566c20a6eb9 Mon Sep 17 00:00:00 2001 From: Urata Daiki <7nohe@users.noreply.github.com> Date: Sat, 18 Jul 2026 20:42:09 +0900 Subject: [PATCH 13/31] feat: make initialPageParam and getNextPageParam overridable in infinite query hooks The generated options parameter no longer requires initialPageParam and getNextPageParam (they are provided by the generator), while still allowing callers to override both for custom pagination schemes. Closes #156 Refs #146, #139 --- examples/react-app/tsconfig.json | 2 +- src/tsmorph/buildQueryHooks.mts | 2 +- tests/__snapshots__/generate.test.ts.snap | 10 ++++++++-- tests/tsmorph/buildQueryHooks.test.ts | 19 +++++++++++++++++++ 4 files changed, 29 insertions(+), 4 deletions(-) diff --git a/examples/react-app/tsconfig.json b/examples/react-app/tsconfig.json index 4e730f0..d1ced64 100644 --- a/examples/react-app/tsconfig.json +++ b/examples/react-app/tsconfig.json @@ -5,7 +5,7 @@ "lib": ["DOM", "DOM.Iterable", "ESNext"], "allowJs": false, "skipLibCheck": true, - "esModuleInterop": false, + "esModuleInterop": true, "allowSyntheticDefaultImports": true, "strict": true, "forceConsistentCasingInFileNames": true, diff --git a/src/tsmorph/buildQueryHooks.mts b/src/tsmorph/buildQueryHooks.mts index 26ddba2..46ed872 100644 --- a/src/tsmorph/buildQueryHooks.mts +++ b/src/tsmorph/buildQueryHooks.mts @@ -204,7 +204,7 @@ export function buildUseInfiniteQueryHook( declarations: [ { name: hookName, - initializer: `, TError = ${errorType}, TQueryKey extends Array = unknown[]>(${clientOptionsParam}, queryKey?: TQueryKey, options?: Omit, "queryKey" | "queryFn">) => ${body}`, + initializer: `, TError = ${errorType}, TQueryKey extends Array = unknown[]>(${clientOptionsParam}, queryKey?: TQueryKey, options?: Omit, "queryKey" | "queryFn" | "initialPageParam" | "getNextPageParam"> & Partial, "initialPageParam" | "getNextPageParam">>) => ${body}`, }, ], }; diff --git a/tests/__snapshots__/generate.test.ts.snap b/tests/__snapshots__/generate.test.ts.snap index c7c5a79..42168c2 100644 --- a/tests/__snapshots__/generate.test.ts.snap +++ b/tests/__snapshots__/generate.test.ts.snap @@ -227,8 +227,14 @@ export const useFindPaginatedPetsInfinite = < queryKey?: TQueryKey, options?: Omit< UseInfiniteQueryOptions, - "queryKey" | "queryFn" - >, + "queryKey" | "queryFn" | "initialPageParam" | "getNextPageParam" + > & + Partial< + Pick< + UseInfiniteQueryOptions, + "initialPageParam" | "getNextPageParam" + > + >, ) => useInfiniteQuery({ queryKey: Common.UseFindPaginatedPetsInfiniteKeyFn(clientOptions, queryKey), diff --git a/tests/tsmorph/buildQueryHooks.test.ts b/tests/tsmorph/buildQueryHooks.test.ts index 235916d..9ddb3d6 100644 --- a/tests/tsmorph/buildQueryHooks.test.ts +++ b/tests/tsmorph/buildQueryHooks.test.ts @@ -214,6 +214,25 @@ describe("buildQueryHooks", () => { expect(initializer).toContain('initialPageParam: "1"'); }); + it("should make initialPageParam and getNextPageParam optional overrides", () => { + const result = buildUseInfiniteQueryHook( + mockPaginatableOperation, + mockFetchContext, + ); + const initializer = result?.declarations[0].initializer as string; + + // The options type must not require initialPageParam/getNextPageParam + // (#156) while still allowing callers to override them (#146) + expect(initializer).toContain( + '"queryKey" | "queryFn" | "initialPageParam" | "getNextPageParam"', + ); + expect(initializer).toContain( + 'Partial, "initialPageParam" | "getNextPageParam">>', + ); + // options spread last so caller overrides win at runtime + expect(initializer).toMatch(/\.\.\.options\s*\}\)/); + }); + it("should include pageParam in queryFn", () => { const result = buildUseInfiniteQueryHook( mockPaginatableOperation, From e2163b2389acde86b3422789a8cdafb4679775fe Mon Sep 17 00:00:00 2001 From: Urata Daiki <7nohe@users.noreply.github.com> Date: Sat, 18 Jul 2026 20:45:25 +0900 Subject: [PATCH 14/31] feat!: hierarchical infinite query keys for granular invalidation Infinite query keys now share the plain query key as their first segment (["FindPets", "infinite", params] instead of ["FindPetsInfinite", params]), so invalidating [useFindPetsKey] matches both the plain and infinite cache entries of an operation while cached InfiniteData still never collides with plain query data. BREAKING CHANGE: useXxxInfiniteKey is now a readonly tuple instead of a string. Closes #174 Refs #140 --- .../content/docs/guides/migrating-to-v3.mdx | 32 ++++++++++++++----- src/tsmorph/buildCommon.mts | 17 ++++++---- tests/__snapshots__/createSource.test.ts.snap | 8 ++--- tests/__snapshots__/generate.test.ts.snap | 7 ++-- tests/tsmorph/buildCommon.test.ts | 6 ++-- 5 files changed, 47 insertions(+), 23 deletions(-) diff --git a/docs/src/content/docs/guides/migrating-to-v3.mdx b/docs/src/content/docs/guides/migrating-to-v3.mdx index 7cd3b42..53c34fb 100644 --- a/docs/src/content/docs/guides/migrating-to-v3.mdx +++ b/docs/src/content/docs/guides/migrating-to-v3.mdx @@ -21,7 +21,8 @@ v3 is a full rewrite of the code generator on top of a ts-morph pipeline, but th - All generated hooks (`useQuery`, `useSuspenseQuery`, `useMutation`), prefetch and `ensureQueryData` functions, key constants, and key functions keep their v2 names and signatures. - JSDoc comments (including `@deprecated`) are still emitted on every hook. -- Environment requirements are unchanged from v2.2.0: Node.js 22.18+, `typescript` 5.x or 6.x, `ts-morph` 28.x. +- Environment requirements are mostly unchanged from v2.2.0: Node.js 22.18+, `typescript` 5.x or 6.x, `ts-morph` 28.x. `commander` 12.x through 15.x are now all accepted. +- `@tanstack/react-query` 5.x is now declared as a peer dependency ([#134](https://github.com/7nohe/openapi-react-query-codegen/issues/134)). It was always required at runtime by the generated code; the declaration just makes your package manager enforce it. ## What's new @@ -34,22 +35,37 @@ import { findPetsOptions } from "../openapi/queries"; const { data } = useQuery(findPetsOptions({ query: { limit: 10 } })); ``` +Infinite query hooks also accept `initialPageParam` and `getNextPageParam` overrides now ([#156](https://github.com/7nohe/openapi-react-query-codegen/issues/156), [#146](https://github.com/7nohe/openapi-react-query-codegen/issues/146)), so custom pagination schemes no longer require editing the generated code: + +```tsx +const { data } = useFindPaginatedPetsInfinite({ query: { limit: 10 } }, undefined, { + initialPageParam: "", + getNextPageParam: (lastPage) => lastPage.meta?.cursor, +}); +``` + ## Breaking changes: infinite queries In v2, an infinite query shared its cache key with the plain query for the same operation, which corrupted the cache when both were used ([#140](https://github.com/7nohe/openapi-react-query-codegen/issues/140)). v3 gives infinite queries their own keys and types. -### 1. Cache keys have an `Infinite` suffix +### 1. Cache keys are hierarchical -The cache key changed from `["FindPaginatedPets"]` to `["FindPaginatedPetsInfinite"]`. If you interact with the infinite query cache manually, switch to the new key exports: +The cache key changed from `["FindPaginatedPets"]` (shared with the plain query) to `["FindPaginatedPets", "infinite"]`. Because the plain key stays the first segment, prefix matching now gives you granular invalidation ([#174](https://github.com/7nohe/openapi-react-query-codegen/issues/174)): -```diff -- queryClient.invalidateQueries({ queryKey: [useFindPaginatedPetsKey] }); -+ queryClient.invalidateQueries({ queryKey: [useFindPaginatedPetsInfiniteKey] }); +```tsx +// Invalidate both the plain AND the infinite cache entries of the operation +queryClient.invalidateQueries({ queryKey: [useFindPaginatedPetsKey] }); -- queryClient.setQueryData(UseFindPaginatedPetsKeyFn(options), updater); -+ queryClient.setQueryData(UseFindPaginatedPetsInfiniteKeyFn(options), updater); +// Invalidate only the infinite entries +// (useFindPaginatedPetsInfiniteKey is already an array — don't wrap it) +queryClient.invalidateQueries({ queryKey: useFindPaginatedPetsInfiniteKey }); + +// Target one exact query, params included +queryClient.setQueryData(UseFindPaginatedPetsInfiniteKeyFn(options), updater); ``` +Note that `useFindPaginatedPetsInfiniteKey` is now a `readonly ["FindPaginatedPets", "infinite"]` tuple instead of a string. If you built keys manually from the string constant, use the exported key functions instead. + If you persist the query cache (e.g. `persistQueryClient`), previously cached infinite data will not match the new key and will be refetched once after the upgrade. ### 2. The page parameter is no longer accepted in `clientOptions` diff --git a/src/tsmorph/buildCommon.mts b/src/tsmorph/buildCommon.mts index ce669b9..e81aa52 100644 --- a/src/tsmorph/buildCommon.mts +++ b/src/tsmorph/buildCommon.mts @@ -184,9 +184,11 @@ export function buildInfiniteClientOptionsType( /** * Build the infinite query key constant. - * Kept distinct from the plain query key so cached InfiniteData never - * collides with plain query data for the same operation (#140). - * Example: export const useFindPaginatedPetsInfiniteKey = "FindPaginatedPetsInfinite"; + * Shares the plain query key as its first segment so a single + * `invalidateQueries({ queryKey: [useXKey] })` matches both the plain and the + * infinite cache entries of an operation (#174), while the extra "infinite" + * segment keeps cached InfiniteData from colliding with plain query data (#140). + * Example: export const useFindPaginatedPetsInfiniteKey = [useFindPaginatedPetsKey, "infinite"] as const; */ export function buildInfiniteQueryKeyConst( op: OperationInfo, @@ -198,7 +200,7 @@ export function buildInfiniteQueryKeyConst( declarations: [ { name: `use${op.capitalizedMethodName}InfiniteKey`, - initializer: `"${op.capitalizedMethodName}Infinite"`, + initializer: `[use${op.capitalizedMethodName}Key, "infinite"] as const`, }, ], }; @@ -206,8 +208,11 @@ export function buildInfiniteQueryKeyConst( /** * Build the infinite query key function. + * The custom queryKey argument only replaces the params segment — the + * hierarchical [opKey, "infinite"] prefix is always preserved so + * prefix-based invalidation keeps working. * Example: export const UseFindPaginatedPetsInfiniteKeyFn = (clientOptions: FindPaginatedPetsInfiniteClientOptions = {}, queryKey?: Array) => - * [useFindPaginatedPetsInfiniteKey, ...(queryKey ?? [clientOptions])]; + * [...useFindPaginatedPetsInfiniteKey, ...(queryKey ?? [clientOptions])]; */ export function buildInfiniteQueryKeyFn( op: OperationInfo, @@ -225,7 +230,7 @@ export function buildInfiniteQueryKeyFn( declarations: [ { name: `Use${op.capitalizedMethodName}InfiniteKeyFn`, - initializer: `(${params.join(", ")}) => [use${op.capitalizedMethodName}InfiniteKey, ...(queryKey ?? [clientOptions])]`, + initializer: `(${params.join(", ")}) => [...use${op.capitalizedMethodName}InfiniteKey, ...(queryKey ?? [clientOptions])]`, }, ], }; diff --git a/tests/__snapshots__/createSource.test.ts.snap b/tests/__snapshots__/createSource.test.ts.snap index 02bf2ec..01f6932 100644 --- a/tests/__snapshots__/createSource.test.ts.snap +++ b/tests/__snapshots__/createSource.test.ts.snap @@ -44,8 +44,8 @@ export const UseFindPaginatedPetsKeyFn = (clientOptions: Options, "query"> & { query?: Omit, "page"> }; -export const useFindPaginatedPetsInfiniteKey = "FindPaginatedPetsInfinite"; -export const UseFindPaginatedPetsInfiniteKeyFn = (clientOptions: FindPaginatedPetsInfiniteClientOptions = {}, queryKey?: Array) => [useFindPaginatedPetsInfiniteKey, ...(queryKey ?? [clientOptions])]; +export const useFindPaginatedPetsInfiniteKey = [useFindPaginatedPetsKey, "infinite"] as const; +export const UseFindPaginatedPetsInfiniteKeyFn = (clientOptions: FindPaginatedPetsInfiniteClientOptions = {}, queryKey?: Array) => [...useFindPaginatedPetsInfiniteKey, ...(queryKey ?? [clientOptions])]; export type AddPetMutationResult = Awaited>; @@ -229,8 +229,8 @@ export const UseFindPaginatedPetsKeyFn = (clientOptions: Options, "query"> & { query?: Omit, "page"> }; -export const useFindPaginatedPetsInfiniteKey = "FindPaginatedPetsInfinite"; -export const UseFindPaginatedPetsInfiniteKeyFn = (clientOptions: FindPaginatedPetsInfiniteClientOptions = {}, queryKey?: Array) => [useFindPaginatedPetsInfiniteKey, ...(queryKey ?? [clientOptions])]; +export const useFindPaginatedPetsInfiniteKey = [useFindPaginatedPetsKey, "infinite"] as const; +export const UseFindPaginatedPetsInfiniteKeyFn = (clientOptions: FindPaginatedPetsInfiniteClientOptions = {}, queryKey?: Array) => [...useFindPaginatedPetsInfiniteKey, ...(queryKey ?? [clientOptions])]; export type AddPetMutationResult = Awaited>; diff --git a/tests/__snapshots__/generate.test.ts.snap b/tests/__snapshots__/generate.test.ts.snap index 42168c2..c5aa774 100644 --- a/tests/__snapshots__/generate.test.ts.snap +++ b/tests/__snapshots__/generate.test.ts.snap @@ -82,11 +82,14 @@ export type FindPaginatedPetsInfiniteClientOptions = Omit< "query" > & { query?: Omit, "page"> }; -export const useFindPaginatedPetsInfiniteKey = "FindPaginatedPetsInfinite"; +export const useFindPaginatedPetsInfiniteKey = [ + useFindPaginatedPetsKey, + "infinite", +] as const; export const UseFindPaginatedPetsInfiniteKeyFn = ( clientOptions: FindPaginatedPetsInfiniteClientOptions = {}, queryKey?: Array, -) => [useFindPaginatedPetsInfiniteKey, ...(queryKey ?? [clientOptions])]; +) => [...useFindPaginatedPetsInfiniteKey, ...(queryKey ?? [clientOptions])]; export type AddPetMutationResult = Awaited>; diff --git a/tests/tsmorph/buildCommon.test.ts b/tests/tsmorph/buildCommon.test.ts index 528fb09..3a5579f 100644 --- a/tests/tsmorph/buildCommon.test.ts +++ b/tests/tsmorph/buildCommon.test.ts @@ -231,14 +231,14 @@ describe("buildCommon", () => { }); describe("buildInfiniteQueryKeyConst", () => { - it("should build a distinct key for infinite queries", () => { + it("should build a hierarchical key sharing the plain key prefix", () => { const result = buildInfiniteQueryKeyConst(mockPaginatableOperation); expect(result.declarations[0].name).toBe( "useFindPaginatedPetsInfiniteKey", ); expect(result.declarations[0].initializer).toBe( - '"FindPaginatedPetsInfinite"', + '[useFindPaginatedPetsKey, "infinite"] as const', ); }); }); @@ -251,7 +251,7 @@ describe("buildCommon", () => { "UseFindPaginatedPetsInfiniteKeyFn", ); expect(result.declarations[0].initializer).toBe( - "(clientOptions: FindPaginatedPetsInfiniteClientOptions = {}, queryKey?: Array) => [useFindPaginatedPetsInfiniteKey, ...(queryKey ?? [clientOptions])]", + "(clientOptions: FindPaginatedPetsInfiniteClientOptions = {}, queryKey?: Array) => [...useFindPaginatedPetsInfiniteKey, ...(queryKey ?? [clientOptions])]", ); }); }); From 36202b2432fcbb3970eb720cf2530f9ee024e86f Mon Sep 17 00:00:00 2001 From: Urata Daiki <7nohe@users.noreply.github.com> Date: Sat, 18 Jul 2026 21:52:02 +0900 Subject: [PATCH 15/31] feat: complete the infinite query family and surface SDK errors - Generate prefetchUseXxxInfinite via queryClient.prefetchInfiniteQuery for SSR hydration of paginated lists (#155) - Generate useXxxSuspenseInfinite hooks sharing the infinite cache key - Accept TanStack Query options in prefetch and ensure functions (#157) - Call the SDK with throwOnError: true everywhere so error responses reject instead of resolving undefined data; fixes ensureQueryData caching undefined and error states never firing (#172) Closes #155 Closes #157 Closes #172 --- .../content/docs/guides/migrating-to-v3.mdx | 22 ++ src/tsmorph/buildMutationHooks.mts | 4 +- src/tsmorph/buildQueryHooks.mts | 135 +++++++++-- src/tsmorph/buildQueryOptions.mts | 6 +- src/tsmorph/generateFiles.mts | 18 ++ src/tsmorph/projectFactory.mts | 5 + tests/__snapshots__/createSource.test.ts.snap | 96 ++++---- tests/__snapshots__/generate.test.ts.snap | 212 ++++++++++++++---- tests/tsmorph/buildMutationHooks.test.ts | 14 +- tests/tsmorph/buildQueryHooks.test.ts | 121 +++++++++- tests/tsmorph/buildQueryOptions.test.ts | 4 +- 11 files changed, 527 insertions(+), 110 deletions(-) diff --git a/docs/src/content/docs/guides/migrating-to-v3.mdx b/docs/src/content/docs/guides/migrating-to-v3.mdx index 53c34fb..2f15122 100644 --- a/docs/src/content/docs/guides/migrating-to-v3.mdx +++ b/docs/src/content/docs/guides/migrating-to-v3.mdx @@ -44,6 +44,28 @@ const { data } = useFindPaginatedPetsInfinite({ query: { limit: 10 } }, undefine }); ``` +The infinite query family is now complete ([#155](https://github.com/7nohe/openapi-react-query-codegen/issues/155)): + +- `prefetchUseXxxInfinite(queryClient, clientOptions)` — prefetch the first page on the server for SSR/Next.js hydration +- `useXxxSuspenseInfinite(clientOptions, queryKey?, options?)` — Suspense variant sharing the same cache key as `useXxxInfinite` + +Prefetch and ensure functions also take TanStack Query options now ([#157](https://github.com/7nohe/openapi-react-query-codegen/issues/157)): + +```tsx +await prefetchUseFindPets(queryClient, {}, { staleTime: 5_000 }); +const pets = await ensureUseFindPetsData(queryClient, {}, { revalidateIfStale: true }); +``` + +## Behavior change: error responses now reject + +Generated query/mutation functions now call the SDK with `throwOnError: true` ([#172](https://github.com/7nohe/openapi-react-query-codegen/issues/172)). In v2, an error response silently resolved with `undefined` data (the hey-api runtime default is `throwOnError: false`), which broke `ensureQueryData` at runtime and never surfaced errors to TanStack Query. In v3, error responses reject, so: + +- `isError` / `error` on hooks now actually fire on HTTP error responses +- `ensureUseXxxData` rejects instead of caching `undefined` +- mutation `onError` callbacks fire as the types always claimed + +If you relied on errors being swallowed, handle them via TanStack Query's error state or `try/catch` around `ensure*` calls. + ## Breaking changes: infinite queries In v2, an infinite query shared its cache key with the plain query for the same operation, which corrupted the cache when both were used ([#140](https://github.com/7nohe/openapi-react-query-codegen/issues/140)). v3 gives infinite queries their own keys and types. diff --git a/src/tsmorph/buildMutationHooks.mts b/src/tsmorph/buildMutationHooks.mts index 8046383..99a8281 100644 --- a/src/tsmorph/buildMutationHooks.mts +++ b/src/tsmorph/buildMutationHooks.mts @@ -48,7 +48,9 @@ export function buildUseMutationHook( const optionsType = `Options<${dataTypeName}, true>`; - const mutationFn = `clientOptions => ${op.methodName}(clientOptions) as unknown as Promise`; + // throwOnError: true forces the SDK call to reject on error responses so + // the mutation error state fires; the hey-api runtime default is false (#172) + const mutationFn = `clientOptions => ${op.methodName}({ ...clientOptions, throwOnError: true }) as unknown as Promise`; const body = `useMutation({ mutationKey: Common.Use${op.capitalizedMethodName}KeyFn(mutationKey), mutationFn: ${mutationFn}, ...options })`; diff --git a/src/tsmorph/buildQueryHooks.mts b/src/tsmorph/buildQueryHooks.mts index 46ed872..b6b4f93 100644 --- a/src/tsmorph/buildQueryHooks.mts +++ b/src/tsmorph/buildQueryHooks.mts @@ -95,11 +95,11 @@ export function buildUseQueryHook( 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`; + // throwOnError: true forces the SDK call to reject on error responses; the + // hey-api runtime default is false, which would resolve undefined data and + // swallow the error instead of surfacing it to TanStack Query (#172) + const queryFn = `() => ${op.methodName}({ ...clientOptions, throwOnError: true }).then(response => response.data as TData) as TData`; const body = `useQuery({ queryKey: Common.Use${op.capitalizedMethodName}KeyFn(clientOptions, queryKey), queryFn: ${queryFn}, ...options })`; @@ -129,10 +129,8 @@ export function buildUseSuspenseQueryHook( 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 queryFn = `() => ${op.methodName}({ ...clientOptions, throwOnError: true }).then(response => response.data as TData) as TData`; const body = `useSuspenseQuery({ queryKey: Common.Use${op.capitalizedMethodName}KeyFn(clientOptions, queryKey), queryFn: ${queryFn}, ...options })`; @@ -184,7 +182,7 @@ export function buildUseInfiniteQueryHook( const clientOptionsParam = `clientOptions: Common.${op.capitalizedMethodName}InfiniteClientOptions${defaultValue}`; // Build the queryFn with pageParam handling - const queryFn = `({ pageParam }) => ${op.methodName}({ ...clientOptions, query: { ...clientOptions.query, ${ctx.pageParam}: pageParam as number } } as Options<${dataTypeName}, true>).then(response => response.data as TData) as TData`; + const queryFn = `({ pageParam }) => ${op.methodName}({ ...clientOptions, query: { ...clientOptions.query, ${ctx.pageParam}: pageParam as number }, throwOnError: true } as Options<${dataTypeName}, true>).then(response => response.data as TData) as TData`; // Build getNextPageParam with nested type const nestedType = buildNestedNextPageType(ctx.nextPageParam); @@ -210,13 +208,59 @@ export function buildUseInfiniteQueryHook( }; } +/** + * Build useSuspenseInfiniteQuery hook. + * Shares the infinite query key (and therefore the cache) with the + * non-suspense useInfiniteQuery hook for the same operation. + */ +export function buildUseSuspenseInfiniteQueryHook( + op: OperationInfo, + ctx: GenerationContext, +): VariableStatementStructure | null { + if (!op.isPaginatable) { + return null; + } + + const hookName = `use${op.capitalizedMethodName}SuspenseInfinite`; + const errorType = getErrorType(op, ctx); + const baseDataType = `Common.${op.capitalizedMethodName}DefaultResponse`; + const dataTypeName = getDataTypeName(op, ctx); + + const defaultValue = op.allParamsOptional ? " = {}" : ""; + const clientOptionsParam = `clientOptions: Common.${op.capitalizedMethodName}InfiniteClientOptions${defaultValue}`; + + const queryFn = `({ pageParam }) => ${op.methodName}({ ...clientOptions, query: { ...clientOptions.query, ${ctx.pageParam}: pageParam as number }, throwOnError: true } as Options<${dataTypeName}, true>).then(response => response.data as TData) as TData`; + + const nestedType = buildNestedNextPageType(ctx.nextPageParam); + const getNextPageParam = `getNextPageParam: (response) => (response as ${nestedType}).${ctx.nextPageParam}`; + + const infiniteOptions = `initialPageParam: "${ctx.initialPageParam}", ${getNextPageParam}`; + + const body = `useSuspenseInfiniteQuery({ queryKey: Common.Use${op.capitalizedMethodName}InfiniteKeyFn(clientOptions, queryKey), queryFn: ${queryFn}, ${infiniteOptions}, ...options })`; + + return { + kind: StructureKind.VariableStatement, + // Copy the operation's JSDoc (description and @deprecated) from the SDK function + leadingTrivia: op.jsDoc ? `${op.jsDoc}\n` : undefined, + isExported: true, + declarationKind: VariableDeclarationKind.Const, + declarations: [ + { + name: hookName, + initializer: `>, TError = ${errorType}, TQueryKey extends Array = unknown[]>(${clientOptionsParam}, queryKey?: TQueryKey, options?: Omit, "queryKey" | "queryFn" | "initialPageParam" | "getNextPageParam"> & Partial, "initialPageParam" | "getNextPageParam">>) => ${body}`, + }, + ], + }; +} + /** * Build prefetch function. * Example: - * export const prefetchUseFindPets = (queryClient: QueryClient, clientOptions: Options = {}) => + * export const prefetchUseFindPets = (queryClient: QueryClient, clientOptions: Options = {}, options?: Omit, "queryKey" | "queryFn">) => * queryClient.prefetchQuery({ * queryKey: Common.UseFindPetsKeyFn(clientOptions), - * queryFn: () => findPets({ ...clientOptions }).then(response => response.data) + * queryFn: () => findPets({ ...clientOptions, throwOnError: true }).then(response => response.data), + * ...options * }); */ export function buildPrefetchFn( @@ -236,10 +280,65 @@ export function buildPrefetchFn( ? `clientOptions: Options<${dataTypeName}, true>${defaultValue}` : `clientOptions: Options<${dataTypeName}, true> = {}`; - const callArgs = "{ ...clientOptions }"; - const queryFn = `() => ${op.methodName}(${callArgs}).then(response => response.data)`; + const queryFn = `() => ${op.methodName}({ ...clientOptions, throwOnError: true }).then(response => response.data)`; + + const optionsParam = `options?: Omit, "queryKey" | "queryFn">`; + const body = `queryClient.prefetchQuery({ queryKey: Common.Use${op.capitalizedMethodName}KeyFn(clientOptions), queryFn: ${queryFn}, ...options })`; + + return { + kind: StructureKind.VariableStatement, + // Copy the operation's JSDoc (description and @deprecated) from the SDK function + leadingTrivia: op.jsDoc ? `${op.jsDoc}\n` : undefined, + isExported: true, + declarationKind: VariableDeclarationKind.Const, + declarations: [ + { + name: fnName, + initializer: `(queryClient: QueryClient, ${clientOptionsParam}, ${optionsParam}) => ${body}`, + }, + ], + }; +} + +/** + * Build prefetchInfiniteQuery function for a paginatable operation. + * Example: + * export const prefetchUseFindPaginatedPetsInfinite = (queryClient: QueryClient, clientOptions: Common.FindPaginatedPetsInfiniteClientOptions = {}) => + * queryClient.prefetchInfiniteQuery({ + * queryKey: Common.UseFindPaginatedPetsInfiniteKeyFn(clientOptions), + * queryFn: ({ pageParam }) => findPaginatedPets({ ...clientOptions, query: { ...clientOptions.query, page: pageParam }, throwOnError: true } as Options).then(response => response.data), + * initialPageParam: 1, + * getNextPageParam: (response) => (response as { nextPage: number }).nextPage + * }); + */ +export function buildPrefetchInfiniteQueryFn( + op: OperationInfo, + ctx: GenerationContext, +): VariableStatementStructure | null { + if (!op.isPaginatable) { + return null; + } + + const fnName = `prefetchUse${op.capitalizedMethodName}Infinite`; + const dataTypeName = getDataTypeName(op, ctx); + + const defaultValue = op.allParamsOptional ? " = {}" : ""; + const clientOptionsParam = `clientOptions: Common.${op.capitalizedMethodName}InfiniteClientOptions${defaultValue}`; + + const queryFn = `({ pageParam }) => ${op.methodName}({ ...clientOptions, query: { ...clientOptions.query, ${ctx.pageParam}: pageParam }, throwOnError: true } as Options<${dataTypeName}, true>).then(response => response.data)`; - const body = `queryClient.prefetchQuery({ queryKey: Common.Use${op.capitalizedMethodName}KeyFn(clientOptions), queryFn: ${queryFn} })`; + // Emit a numeric literal when possible so the inferred pageParam type + // matches what getNextPageParam returns + const initialPageParam = /^-?\d+$/.test(ctx.initialPageParam) + ? ctx.initialPageParam + : JSON.stringify(ctx.initialPageParam); + + const nestedType = buildNestedNextPageType(ctx.nextPageParam); + // prefetchInfiniteQuery does not contextually type getNextPageParam's + // parameter, so annotate it explicitly to satisfy noImplicitAny + const getNextPageParam = `getNextPageParam: (response: unknown) => (response as ${nestedType}).${ctx.nextPageParam}`; + + const body = `queryClient.prefetchInfiniteQuery({ queryKey: Common.Use${op.capitalizedMethodName}InfiniteKeyFn(clientOptions), queryFn: ${queryFn}, initialPageParam: ${initialPageParam}, ${getNextPageParam} })`; return { kind: StructureKind.VariableStatement, @@ -282,10 +381,12 @@ export function buildEnsureQueryDataFn( ? `clientOptions: Options<${dataTypeName}, true>${defaultValue}` : `clientOptions: Options<${dataTypeName}, true> = {}`; - const callArgs = "{ ...clientOptions }"; - const queryFn = `() => ${op.methodName}(${callArgs}).then(response => response.data)`; + // throwOnError: true makes the SDK call reject on error responses so + // ensureQueryData rejects instead of caching undefined (#172) + const queryFn = `() => ${op.methodName}({ ...clientOptions, throwOnError: true }).then(response => response.data)`; - const body = `queryClient.ensureQueryData({ queryKey: Common.Use${op.capitalizedMethodName}KeyFn(clientOptions), queryFn: ${queryFn} })`; + const optionsParam = `options?: Omit, "queryKey" | "queryFn">`; + const body = `queryClient.ensureQueryData({ queryKey: Common.Use${op.capitalizedMethodName}KeyFn(clientOptions), queryFn: ${queryFn}, ...options })`; return { kind: StructureKind.VariableStatement, @@ -296,7 +397,7 @@ export function buildEnsureQueryDataFn( declarations: [ { name: fnName, - initializer: `(queryClient: QueryClient, ${clientOptionsParam}) => ${body}`, + initializer: `(queryClient: QueryClient, ${clientOptionsParam}, ${optionsParam}) => ${body}`, }, ], }; diff --git a/src/tsmorph/buildQueryOptions.mts b/src/tsmorph/buildQueryOptions.mts index f173357..46ff880 100644 --- a/src/tsmorph/buildQueryOptions.mts +++ b/src/tsmorph/buildQueryOptions.mts @@ -29,7 +29,9 @@ export function buildQueryOptionsFn( const fnName = `${op.methodName}Options`; const clientOptionsParam = buildClientOptionsParam(op, ctx); - const queryFn = `() => ${op.methodName}({ ...clientOptions }).then(response => response.data)`; + // throwOnError: true forces the SDK call to reject on error responses; the + // hey-api runtime default is false, which would resolve undefined data (#172) + const queryFn = `() => ${op.methodName}({ ...clientOptions, throwOnError: true }).then(response => response.data)`; const body = `queryOptions({ queryKey: Common.Use${op.capitalizedMethodName}KeyFn(clientOptions, queryKey), queryFn: ${queryFn} })`; return { @@ -73,7 +75,7 @@ export function buildInfiniteQueryOptionsFn( const defaultValue = op.allParamsOptional ? " = {}" : ""; const clientOptionsParam = `clientOptions: Common.${op.capitalizedMethodName}InfiniteClientOptions${defaultValue}`; - const queryFn = `({ pageParam }) => ${op.methodName}({ ...clientOptions, query: { ...clientOptions.query, ${ctx.pageParam}: pageParam } } as Options<${dataTypeName}, true>).then(response => response.data)`; + const queryFn = `({ pageParam }) => ${op.methodName}({ ...clientOptions, query: { ...clientOptions.query, ${ctx.pageParam}: pageParam }, throwOnError: true } as Options<${dataTypeName}, true>).then(response => response.data)`; // Emit a numeric literal when possible so the inferred pageParam type // matches what getNextPageParam returns diff --git a/src/tsmorph/generateFiles.mts b/src/tsmorph/generateFiles.mts index 3e76e61..c4cee55 100644 --- a/src/tsmorph/generateFiles.mts +++ b/src/tsmorph/generateFiles.mts @@ -25,8 +25,10 @@ import { buildUseMutationHook } from "./buildMutationHooks.mjs"; import { buildEnsureQueryDataFn, buildPrefetchFn, + buildPrefetchInfiniteQueryFn, buildUseInfiniteQueryHook, buildUseQueryHook, + buildUseSuspenseInfiniteQueryHook, buildUseSuspenseQueryHook, } from "./buildQueryHooks.mjs"; import { @@ -261,6 +263,14 @@ function generateSuspenseFile( sourceFile.addVariableStatement(buildUseSuspenseQueryHook(op, ctx)); } + // Add useSuspenseInfiniteQuery hooks for paginatable operations + for (const op of getOperations) { + const hook = buildUseSuspenseInfiniteQueryHook(op, ctx); + if (hook) { + sourceFile.addVariableStatement(hook); + } + } + return sourceFile.getFullText(); } @@ -322,6 +332,14 @@ function generatePrefetchFile( sourceFile.addVariableStatement(buildPrefetchFn(op, ctx)); } + // Add prefetchInfiniteQuery functions for paginatable operations + for (const op of getOperations) { + const fn = buildPrefetchInfiniteQueryFn(op, ctx); + if (fn) { + sourceFile.addVariableStatement(fn); + } + } + return sourceFile.getFullText(); } diff --git a/src/tsmorph/projectFactory.mts b/src/tsmorph/projectFactory.mts index 999e3f1..e42ab63 100644 --- a/src/tsmorph/projectFactory.mts +++ b/src/tsmorph/projectFactory.mts @@ -54,14 +54,19 @@ export function buildQueryImport(): ImportDeclarationStructure { { name: "useQuery" }, { name: "useSuspenseQuery" }, { name: "useInfiniteQuery" }, + { name: "useSuspenseInfiniteQuery" }, { name: "useMutation" }, { name: "UseQueryResult" }, { name: "UseQueryOptions" }, { name: "UseInfiniteQueryOptions" }, + { name: "UseSuspenseInfiniteQueryOptions" }, { name: "UseMutationOptions" }, { name: "UseMutationResult" }, { name: "UseSuspenseQueryOptions" }, { name: "InfiniteData" }, + { name: "FetchQueryOptions", isTypeOnly: true }, + { name: "FetchInfiniteQueryOptions", isTypeOnly: true }, + { name: "EnsureQueryDataOptions", isTypeOnly: true }, ], }; } diff --git a/tests/__snapshots__/createSource.test.ts.snap b/tests/__snapshots__/createSource.test.ts.snap index 01f6932..37c76e0 100644 --- a/tests/__snapshots__/createSource.test.ts.snap +++ b/tests/__snapshots__/createSource.test.ts.snap @@ -13,7 +13,7 @@ exports[`createSource > createSource - @hey-api/client-axios 2`] = ` "// generated with @7nohe/openapi-react-query-codegen@1.0.0 import { type Options } from "../requests/sdk.gen"; -import { type QueryClient, useQuery, useSuspenseQuery, useInfiniteQuery, useMutation, UseQueryResult, UseQueryOptions, UseInfiniteQueryOptions, UseMutationOptions, UseMutationResult, UseSuspenseQueryOptions, InfiniteData } from "@tanstack/react-query"; +import { type QueryClient, useQuery, useSuspenseQuery, useInfiniteQuery, useSuspenseInfiniteQuery, useMutation, UseQueryResult, UseQueryOptions, UseInfiniteQueryOptions, UseSuspenseInfiniteQueryOptions, UseMutationOptions, UseMutationResult, UseSuspenseQueryOptions, InfiniteData, type FetchQueryOptions, type FetchInfiniteQueryOptions, type EnsureQueryDataOptions } from "@tanstack/react-query"; import { Options, findPets, addPet, getNotDefined, postNotDefined, deletePet, findPetById, findPaginatedPets } from "../requests/sdk.gen"; import { ClientOptions, 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 } from "../requests/types.gen"; import { AxiosError } from "axios"; @@ -69,7 +69,7 @@ exports[`createSource > createSource - @hey-api/client-axios 3`] = ` import * as Common from "./common"; import { type Options } from "../requests/sdk.gen"; -import { type QueryClient, useQuery, useSuspenseQuery, useInfiniteQuery, useMutation, UseQueryResult, UseQueryOptions, UseInfiniteQueryOptions, UseMutationOptions, UseMutationResult, UseSuspenseQueryOptions, InfiniteData } from "@tanstack/react-query"; +import { type QueryClient, useQuery, useSuspenseQuery, useInfiniteQuery, useSuspenseInfiniteQuery, useMutation, UseQueryResult, UseQueryOptions, UseInfiniteQueryOptions, UseSuspenseInfiniteQueryOptions, UseMutationOptions, UseMutationResult, UseSuspenseQueryOptions, InfiniteData, type FetchQueryOptions, type FetchInfiniteQueryOptions, type EnsureQueryDataOptions } from "@tanstack/react-query"; import { Options, findPets, addPet, getNotDefined, postNotDefined, deletePet, findPetById, findPaginatedPets } from "../requests/sdk.gen"; import { ClientOptions, 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 } from "../requests/types.gen"; import { AxiosError } from "axios"; @@ -81,36 +81,36 @@ import { AxiosError } from "axios"; * Sed tempus felis lobortis leo pulvinar rutrum. Nam mattis velit nisl, eu condimentum ligula luctus nec. Phasellus semper velit eget aliquet faucibus. In a mattis elit. Phasellus vel urna viverra, condimentum lorem id, rhoncus nibh. Ut pellentesque posuere elementum. Sed a varius odio. Morbi rhoncus ligula libero, vel eleifend nunc tristique vitae. Fusce et sem dui. Aenean nec scelerisque tortor. Fusce malesuada accumsan magna vel tempus. Quisque mollis felis eu dolor tristique, sit amet auctor felis gravida. Sed libero lorem, molestie sed nisl in, accumsan tempor nisi. Fusce sollicitudin massa ut lacinia mattis. Sed vel eleifend lorem. Pellentesque vitae felis pretium, pulvinar elit eu, euismod sapien. * */ -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 useFindPets = , TQueryKey extends Array = unknown[]>(clientOptions: Options = {}, queryKey?: TQueryKey, options?: Omit, "queryKey" | "queryFn">) => useQuery({ queryKey: Common.UseFindPetsKeyFn(clientOptions, queryKey), queryFn: () => findPets({ ...clientOptions, throwOnError: true }).then(response => response.data as TData) as TData, ...options }); /** * This path is not fully defined. * * @deprecated */ -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 useGetNotDefined = , TQueryKey extends Array = unknown[]>(clientOptions: Options = {}, queryKey?: TQueryKey, options?: Omit, "queryKey" | "queryFn">) => useQuery({ queryKey: Common.UseGetNotDefinedKeyFn(clientOptions, queryKey), queryFn: () => getNotDefined({ ...clientOptions, throwOnError: true }).then(response => response.data as TData) as TData, ...options }); /** * Returns a user based on a single ID, if the user does not have access to the pet */ -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 useFindPetById = , TQueryKey extends Array = unknown[]>(clientOptions: Options, queryKey?: TQueryKey, options?: Omit, "queryKey" | "queryFn">) => useQuery({ queryKey: Common.UseFindPetByIdKeyFn(clientOptions, queryKey), queryFn: () => findPetById({ ...clientOptions, throwOnError: true }).then(response => response.data as TData) as TData, ...options }); /** * Returns paginated pets from the system that the user has access to * */ -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 useFindPaginatedPets = , TQueryKey extends Array = unknown[]>(clientOptions: Options = {}, queryKey?: TQueryKey, options?: Omit, "queryKey" | "queryFn">) => useQuery({ queryKey: Common.UseFindPaginatedPetsKeyFn(clientOptions, queryKey), queryFn: () => findPaginatedPets({ ...clientOptions, throwOnError: true }).then(response => response.data as TData) as TData, ...options }); /** * Creates a new pet in the store. Duplicates are allowed */ -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 useAddPet = , TQueryKey extends Array = unknown[], TContext = unknown>(mutationKey?: TQueryKey, options?: Omit, TContext>, "mutationKey" | "mutationFn">) => useMutation, TContext>({ mutationKey: Common.UseAddPetKeyFn(mutationKey), mutationFn: clientOptions => addPet({ ...clientOptions, throwOnError: true }) as unknown as Promise, ...options }); /** * This path is not defined at all. * * @deprecated */ -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, throwOnError: true }) as unknown as Promise, ...options }); /** * deletes a single pet based on the ID supplied */ -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 }); +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, throwOnError: true }) as unknown as Promise, ...options }); " `; @@ -119,7 +119,7 @@ exports[`createSource > createSource - @hey-api/client-axios 4`] = ` import * as Common from "./common"; import { type Options } from "../requests/sdk.gen"; -import { type QueryClient, useQuery, useSuspenseQuery, useInfiniteQuery, useMutation, UseQueryResult, UseQueryOptions, UseInfiniteQueryOptions, UseMutationOptions, UseMutationResult, UseSuspenseQueryOptions, InfiniteData } from "@tanstack/react-query"; +import { type QueryClient, useQuery, useSuspenseQuery, useInfiniteQuery, useSuspenseInfiniteQuery, useMutation, UseQueryResult, UseQueryOptions, UseInfiniteQueryOptions, UseSuspenseInfiniteQueryOptions, UseMutationOptions, UseMutationResult, UseSuspenseQueryOptions, InfiniteData, type FetchQueryOptions, type FetchInfiniteQueryOptions, type EnsureQueryDataOptions } from "@tanstack/react-query"; import { Options, findPets, addPet, getNotDefined, postNotDefined, deletePet, findPetById, findPaginatedPets } from "../requests/sdk.gen"; import { ClientOptions, 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 } from "../requests/types.gen"; import { AxiosError } from "axios"; @@ -131,22 +131,27 @@ import { AxiosError } from "axios"; * Sed tempus felis lobortis leo pulvinar rutrum. Nam mattis velit nisl, eu condimentum ligula luctus nec. Phasellus semper velit eget aliquet faucibus. In a mattis elit. Phasellus vel urna viverra, condimentum lorem id, rhoncus nibh. Ut pellentesque posuere elementum. Sed a varius odio. Morbi rhoncus ligula libero, vel eleifend nunc tristique vitae. Fusce et sem dui. Aenean nec scelerisque tortor. Fusce malesuada accumsan magna vel tempus. Quisque mollis felis eu dolor tristique, sit amet auctor felis gravida. Sed libero lorem, molestie sed nisl in, accumsan tempor nisi. Fusce sollicitudin massa ut lacinia mattis. Sed vel eleifend lorem. Pellentesque vitae felis pretium, pulvinar elit eu, euismod sapien. * */ -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 useFindPetsSuspense = , TError = AxiosError, TQueryKey extends Array = unknown[]>(clientOptions: Options = {}, queryKey?: TQueryKey, options?: Omit, "queryKey" | "queryFn">) => useSuspenseQuery({ queryKey: Common.UseFindPetsKeyFn(clientOptions, queryKey), queryFn: () => findPets({ ...clientOptions, throwOnError: true }).then(response => response.data as TData) as TData, ...options }); /** * This path is not fully defined. * * @deprecated */ -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 useGetNotDefinedSuspense = , TError = AxiosError, TQueryKey extends Array = unknown[]>(clientOptions: Options = {}, queryKey?: TQueryKey, options?: Omit, "queryKey" | "queryFn">) => useSuspenseQuery({ queryKey: Common.UseGetNotDefinedKeyFn(clientOptions, queryKey), queryFn: () => getNotDefined({ ...clientOptions, throwOnError: true }).then(response => response.data as TData) as TData, ...options }); /** * Returns a user based on a single ID, if the user does not have access to the pet */ -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 useFindPetByIdSuspense = , TError = AxiosError, TQueryKey extends Array = unknown[]>(clientOptions: Options, queryKey?: TQueryKey, options?: Omit, "queryKey" | "queryFn">) => useSuspenseQuery({ queryKey: Common.UseFindPetByIdKeyFn(clientOptions, queryKey), queryFn: () => findPetById({ ...clientOptions, throwOnError: true }).then(response => response.data as TData) as TData, ...options }); /** * Returns paginated pets from the system that the user has access to * */ -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 }); +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, throwOnError: true }).then(response => response.data as TData) as TData, ...options }); +/** + * Returns paginated pets from the system that the user has access to + * + */ +export const useFindPaginatedPetsSuspenseInfinite = >, TError = AxiosError, TQueryKey extends Array = unknown[]>(clientOptions: Common.FindPaginatedPetsInfiniteClientOptions = {}, queryKey?: TQueryKey, options?: Omit, "queryKey" | "queryFn" | "initialPageParam" | "getNextPageParam"> & Partial, "initialPageParam" | "getNextPageParam">>) => useSuspenseInfiniteQuery({ queryKey: Common.UseFindPaginatedPetsInfiniteKeyFn(clientOptions, queryKey), queryFn: ({ pageParam }) => findPaginatedPets({ ...clientOptions, query: { ...clientOptions.query, page: pageParam as number }, throwOnError: true } as Options).then(response => response.data as TData) as TData, initialPageParam: "1", getNextPageParam: (response) => (response as { nextPage: number }).nextPage, ...options }); " `; @@ -155,7 +160,7 @@ exports[`createSource > createSource - @hey-api/client-axios 5`] = ` import * as Common from "./common"; import { type Options } from "../requests/sdk.gen"; -import { type QueryClient, useQuery, useSuspenseQuery, useInfiniteQuery, useMutation, UseQueryResult, UseQueryOptions, UseInfiniteQueryOptions, UseMutationOptions, UseMutationResult, UseSuspenseQueryOptions, InfiniteData } from "@tanstack/react-query"; +import { type QueryClient, useQuery, useSuspenseQuery, useInfiniteQuery, useSuspenseInfiniteQuery, useMutation, UseQueryResult, UseQueryOptions, UseInfiniteQueryOptions, UseSuspenseInfiniteQueryOptions, UseMutationOptions, UseMutationResult, UseSuspenseQueryOptions, InfiniteData, type FetchQueryOptions, type FetchInfiniteQueryOptions, type EnsureQueryDataOptions } from "@tanstack/react-query"; import { Options, findPets, addPet, getNotDefined, postNotDefined, deletePet, findPetById, findPaginatedPets } from "../requests/sdk.gen"; import { ClientOptions, 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 } from "../requests/types.gen"; import { AxiosError } from "axios"; @@ -167,22 +172,27 @@ import { AxiosError } from "axios"; * Sed tempus felis lobortis leo pulvinar rutrum. Nam mattis velit nisl, eu condimentum ligula luctus nec. Phasellus semper velit eget aliquet faucibus. In a mattis elit. Phasellus vel urna viverra, condimentum lorem id, rhoncus nibh. Ut pellentesque posuere elementum. Sed a varius odio. Morbi rhoncus ligula libero, vel eleifend nunc tristique vitae. Fusce et sem dui. Aenean nec scelerisque tortor. Fusce malesuada accumsan magna vel tempus. Quisque mollis felis eu dolor tristique, sit amet auctor felis gravida. Sed libero lorem, molestie sed nisl in, accumsan tempor nisi. Fusce sollicitudin massa ut lacinia mattis. Sed vel eleifend lorem. Pellentesque vitae felis pretium, pulvinar elit eu, euismod sapien. * */ -export const prefetchUseFindPets = (queryClient: QueryClient, clientOptions: Options = {}) => queryClient.prefetchQuery({ queryKey: Common.UseFindPetsKeyFn(clientOptions), queryFn: () => findPets({ ...clientOptions }).then(response => response.data) }); +export const prefetchUseFindPets = (queryClient: QueryClient, clientOptions: Options = {}, options?: Omit, "queryKey" | "queryFn">) => queryClient.prefetchQuery({ queryKey: Common.UseFindPetsKeyFn(clientOptions), queryFn: () => findPets({ ...clientOptions, throwOnError: true }).then(response => response.data), ...options }); /** * This path is not fully defined. * * @deprecated */ -export const prefetchUseGetNotDefined = (queryClient: QueryClient, clientOptions: Options = {}) => queryClient.prefetchQuery({ queryKey: Common.UseGetNotDefinedKeyFn(clientOptions), queryFn: () => getNotDefined({ ...clientOptions }).then(response => response.data) }); +export const prefetchUseGetNotDefined = (queryClient: QueryClient, clientOptions: Options = {}, options?: Omit, "queryKey" | "queryFn">) => queryClient.prefetchQuery({ queryKey: Common.UseGetNotDefinedKeyFn(clientOptions), queryFn: () => getNotDefined({ ...clientOptions, throwOnError: true }).then(response => response.data), ...options }); /** * Returns a user based on a single ID, if the user does not have access to the pet */ -export const prefetchUseFindPetById = (queryClient: QueryClient, clientOptions: Options) => queryClient.prefetchQuery({ queryKey: Common.UseFindPetByIdKeyFn(clientOptions), queryFn: () => findPetById({ ...clientOptions }).then(response => response.data) }); +export const prefetchUseFindPetById = (queryClient: QueryClient, clientOptions: Options, options?: Omit, "queryKey" | "queryFn">) => queryClient.prefetchQuery({ queryKey: Common.UseFindPetByIdKeyFn(clientOptions), queryFn: () => findPetById({ ...clientOptions, throwOnError: true }).then(response => response.data), ...options }); +/** + * Returns paginated pets from the system that the user has access to + * + */ +export const prefetchUseFindPaginatedPets = (queryClient: QueryClient, clientOptions: Options = {}, options?: Omit, "queryKey" | "queryFn">) => queryClient.prefetchQuery({ queryKey: Common.UseFindPaginatedPetsKeyFn(clientOptions), queryFn: () => findPaginatedPets({ ...clientOptions, throwOnError: true }).then(response => response.data), ...options }); /** * Returns paginated pets from the system that the user has access to * */ -export const prefetchUseFindPaginatedPets = (queryClient: QueryClient, clientOptions: Options = {}) => queryClient.prefetchQuery({ queryKey: Common.UseFindPaginatedPetsKeyFn(clientOptions), queryFn: () => findPaginatedPets({ ...clientOptions }).then(response => response.data) }); +export const prefetchUseFindPaginatedPetsInfinite = (queryClient: QueryClient, clientOptions: Common.FindPaginatedPetsInfiniteClientOptions = {}) => queryClient.prefetchInfiniteQuery({ queryKey: Common.UseFindPaginatedPetsInfiniteKeyFn(clientOptions), queryFn: ({ pageParam }) => findPaginatedPets({ ...clientOptions, query: { ...clientOptions.query, page: pageParam }, throwOnError: true } as Options).then(response => response.data), initialPageParam: 1, getNextPageParam: (response: unknown) => (response as { nextPage: number }).nextPage }); " `; @@ -199,7 +209,7 @@ exports[`createSource > createSource - @hey-api/client-fetch 2`] = ` "// generated with @7nohe/openapi-react-query-codegen@1.0.0 import { type Options } from "../requests/sdk.gen"; -import { type QueryClient, useQuery, useSuspenseQuery, useInfiniteQuery, useMutation, UseQueryResult, UseQueryOptions, UseInfiniteQueryOptions, UseMutationOptions, UseMutationResult, UseSuspenseQueryOptions, InfiniteData } from "@tanstack/react-query"; +import { type QueryClient, useQuery, useSuspenseQuery, useInfiniteQuery, useSuspenseInfiniteQuery, useMutation, UseQueryResult, UseQueryOptions, UseInfiniteQueryOptions, UseSuspenseInfiniteQueryOptions, UseMutationOptions, UseMutationResult, UseSuspenseQueryOptions, InfiniteData, type FetchQueryOptions, type FetchInfiniteQueryOptions, type EnsureQueryDataOptions } from "@tanstack/react-query"; import { Options, findPets, addPet, getNotDefined, postNotDefined, deletePet, findPetById, findPaginatedPets } from "../requests/sdk.gen"; import { ClientOptions, 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 } from "../requests/types.gen"; @@ -254,7 +264,7 @@ exports[`createSource > createSource - @hey-api/client-fetch 3`] = ` import * as Common from "./common"; import { type Options } from "../requests/sdk.gen"; -import { type QueryClient, useQuery, useSuspenseQuery, useInfiniteQuery, useMutation, UseQueryResult, UseQueryOptions, UseInfiniteQueryOptions, UseMutationOptions, UseMutationResult, UseSuspenseQueryOptions, InfiniteData } from "@tanstack/react-query"; +import { type QueryClient, useQuery, useSuspenseQuery, useInfiniteQuery, useSuspenseInfiniteQuery, useMutation, UseQueryResult, UseQueryOptions, UseInfiniteQueryOptions, UseSuspenseInfiniteQueryOptions, UseMutationOptions, UseMutationResult, UseSuspenseQueryOptions, InfiniteData, type FetchQueryOptions, type FetchInfiniteQueryOptions, type EnsureQueryDataOptions } from "@tanstack/react-query"; import { Options, findPets, addPet, getNotDefined, postNotDefined, deletePet, findPetById, findPaginatedPets } from "../requests/sdk.gen"; import { ClientOptions, 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 } from "../requests/types.gen"; @@ -265,36 +275,36 @@ import { ClientOptions, Pet, NewPet, Error, FindPetsData, FindPetsErrors, FindPe * Sed tempus felis lobortis leo pulvinar rutrum. Nam mattis velit nisl, eu condimentum ligula luctus nec. Phasellus semper velit eget aliquet faucibus. In a mattis elit. Phasellus vel urna viverra, condimentum lorem id, rhoncus nibh. Ut pellentesque posuere elementum. Sed a varius odio. Morbi rhoncus ligula libero, vel eleifend nunc tristique vitae. Fusce et sem dui. Aenean nec scelerisque tortor. Fusce malesuada accumsan magna vel tempus. Quisque mollis felis eu dolor tristique, sit amet auctor felis gravida. Sed libero lorem, molestie sed nisl in, accumsan tempor nisi. Fusce sollicitudin massa ut lacinia mattis. Sed vel eleifend lorem. Pellentesque vitae felis pretium, pulvinar elit eu, euismod sapien. * */ -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 useFindPets = = unknown[]>(clientOptions: Options = {}, queryKey?: TQueryKey, options?: Omit, "queryKey" | "queryFn">) => useQuery({ queryKey: Common.UseFindPetsKeyFn(clientOptions, queryKey), queryFn: () => findPets({ ...clientOptions, throwOnError: true }).then(response => response.data as TData) as TData, ...options }); /** * This path is not fully defined. * * @deprecated */ -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 useGetNotDefined = = unknown[]>(clientOptions: Options = {}, queryKey?: TQueryKey, options?: Omit, "queryKey" | "queryFn">) => useQuery({ queryKey: Common.UseGetNotDefinedKeyFn(clientOptions, queryKey), queryFn: () => getNotDefined({ ...clientOptions, throwOnError: true }).then(response => response.data as TData) as TData, ...options }); /** * Returns a user based on a single ID, if the user does not have access to the pet */ -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 useFindPetById = = unknown[]>(clientOptions: Options, queryKey?: TQueryKey, options?: Omit, "queryKey" | "queryFn">) => useQuery({ queryKey: Common.UseFindPetByIdKeyFn(clientOptions, queryKey), queryFn: () => findPetById({ ...clientOptions, throwOnError: true }).then(response => response.data as TData) as TData, ...options }); /** * Returns paginated pets from the system that the user has access to * */ -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 useFindPaginatedPets = = unknown[]>(clientOptions: Options = {}, queryKey?: TQueryKey, options?: Omit, "queryKey" | "queryFn">) => useQuery({ queryKey: Common.UseFindPaginatedPetsKeyFn(clientOptions, queryKey), queryFn: () => findPaginatedPets({ ...clientOptions, throwOnError: true }).then(response => response.data as TData) as TData, ...options }); /** * Creates a new pet in the store. Duplicates are allowed */ -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 useAddPet = = unknown[], TContext = unknown>(mutationKey?: TQueryKey, options?: Omit, TContext>, "mutationKey" | "mutationFn">) => useMutation, TContext>({ mutationKey: Common.UseAddPetKeyFn(mutationKey), mutationFn: clientOptions => addPet({ ...clientOptions, throwOnError: true }) as unknown as Promise, ...options }); /** * This path is not defined at all. * * @deprecated */ -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, throwOnError: true }) as unknown as Promise, ...options }); /** * deletes a single pet based on the ID supplied */ -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 }); +export const useDeletePet = = unknown[], TContext = unknown>(mutationKey?: TQueryKey, options?: Omit, TContext>, "mutationKey" | "mutationFn">) => useMutation, TContext>({ mutationKey: Common.UseDeletePetKeyFn(mutationKey), mutationFn: clientOptions => deletePet({ ...clientOptions, throwOnError: true }) as unknown as Promise, ...options }); " `; @@ -303,7 +313,7 @@ exports[`createSource > createSource - @hey-api/client-fetch 4`] = ` import * as Common from "./common"; import { type Options } from "../requests/sdk.gen"; -import { type QueryClient, useQuery, useSuspenseQuery, useInfiniteQuery, useMutation, UseQueryResult, UseQueryOptions, UseInfiniteQueryOptions, UseMutationOptions, UseMutationResult, UseSuspenseQueryOptions, InfiniteData } from "@tanstack/react-query"; +import { type QueryClient, useQuery, useSuspenseQuery, useInfiniteQuery, useSuspenseInfiniteQuery, useMutation, UseQueryResult, UseQueryOptions, UseInfiniteQueryOptions, UseSuspenseInfiniteQueryOptions, UseMutationOptions, UseMutationResult, UseSuspenseQueryOptions, InfiniteData, type FetchQueryOptions, type FetchInfiniteQueryOptions, type EnsureQueryDataOptions } from "@tanstack/react-query"; import { Options, findPets, addPet, getNotDefined, postNotDefined, deletePet, findPetById, findPaginatedPets } from "../requests/sdk.gen"; import { ClientOptions, 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 } from "../requests/types.gen"; @@ -314,22 +324,27 @@ import { ClientOptions, Pet, NewPet, Error, FindPetsData, FindPetsErrors, FindPe * Sed tempus felis lobortis leo pulvinar rutrum. Nam mattis velit nisl, eu condimentum ligula luctus nec. Phasellus semper velit eget aliquet faucibus. In a mattis elit. Phasellus vel urna viverra, condimentum lorem id, rhoncus nibh. Ut pellentesque posuere elementum. Sed a varius odio. Morbi rhoncus ligula libero, vel eleifend nunc tristique vitae. Fusce et sem dui. Aenean nec scelerisque tortor. Fusce malesuada accumsan magna vel tempus. Quisque mollis felis eu dolor tristique, sit amet auctor felis gravida. Sed libero lorem, molestie sed nisl in, accumsan tempor nisi. Fusce sollicitudin massa ut lacinia mattis. Sed vel eleifend lorem. Pellentesque vitae felis pretium, pulvinar elit eu, euismod sapien. * */ -export const useFindPetsSuspense = , TError = FindPetsError, 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 useFindPetsSuspense = , TError = FindPetsError, TQueryKey extends Array = unknown[]>(clientOptions: Options = {}, queryKey?: TQueryKey, options?: Omit, "queryKey" | "queryFn">) => useSuspenseQuery({ queryKey: Common.UseFindPetsKeyFn(clientOptions, queryKey), queryFn: () => findPets({ ...clientOptions, throwOnError: true }).then(response => response.data as TData) as TData, ...options }); /** * This path is not fully defined. * * @deprecated */ -export const useGetNotDefinedSuspense = , TError = unknown, 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 useGetNotDefinedSuspense = , TError = unknown, TQueryKey extends Array = unknown[]>(clientOptions: Options = {}, queryKey?: TQueryKey, options?: Omit, "queryKey" | "queryFn">) => useSuspenseQuery({ queryKey: Common.UseGetNotDefinedKeyFn(clientOptions, queryKey), queryFn: () => getNotDefined({ ...clientOptions, throwOnError: true }).then(response => response.data as TData) as TData, ...options }); /** * Returns a user based on a single ID, if the user does not have access to the pet */ -export const useFindPetByIdSuspense = , TError = FindPetByIdError, 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 useFindPetByIdSuspense = , TError = FindPetByIdError, TQueryKey extends Array = unknown[]>(clientOptions: Options, queryKey?: TQueryKey, options?: Omit, "queryKey" | "queryFn">) => useSuspenseQuery({ queryKey: Common.UseFindPetByIdKeyFn(clientOptions, queryKey), queryFn: () => findPetById({ ...clientOptions, throwOnError: true }).then(response => response.data as TData) as TData, ...options }); /** * Returns paginated pets from the system that the user has access to * */ -export const useFindPaginatedPetsSuspense = , TError = unknown, 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 }); +export const useFindPaginatedPetsSuspense = , TError = unknown, TQueryKey extends Array = unknown[]>(clientOptions: Options = {}, queryKey?: TQueryKey, options?: Omit, "queryKey" | "queryFn">) => useSuspenseQuery({ queryKey: Common.UseFindPaginatedPetsKeyFn(clientOptions, queryKey), queryFn: () => findPaginatedPets({ ...clientOptions, throwOnError: true }).then(response => response.data as TData) as TData, ...options }); +/** + * Returns paginated pets from the system that the user has access to + * + */ +export const useFindPaginatedPetsSuspenseInfinite = >, TError = unknown, TQueryKey extends Array = unknown[]>(clientOptions: Common.FindPaginatedPetsInfiniteClientOptions = {}, queryKey?: TQueryKey, options?: Omit, "queryKey" | "queryFn" | "initialPageParam" | "getNextPageParam"> & Partial, "initialPageParam" | "getNextPageParam">>) => useSuspenseInfiniteQuery({ queryKey: Common.UseFindPaginatedPetsInfiniteKeyFn(clientOptions, queryKey), queryFn: ({ pageParam }) => findPaginatedPets({ ...clientOptions, query: { ...clientOptions.query, page: pageParam as number }, throwOnError: true } as Options).then(response => response.data as TData) as TData, initialPageParam: "1", getNextPageParam: (response) => (response as { nextPage: number }).nextPage, ...options }); " `; @@ -338,7 +353,7 @@ exports[`createSource > createSource - @hey-api/client-fetch 5`] = ` import * as Common from "./common"; import { type Options } from "../requests/sdk.gen"; -import { type QueryClient, useQuery, useSuspenseQuery, useInfiniteQuery, useMutation, UseQueryResult, UseQueryOptions, UseInfiniteQueryOptions, UseMutationOptions, UseMutationResult, UseSuspenseQueryOptions, InfiniteData } from "@tanstack/react-query"; +import { type QueryClient, useQuery, useSuspenseQuery, useInfiniteQuery, useSuspenseInfiniteQuery, useMutation, UseQueryResult, UseQueryOptions, UseInfiniteQueryOptions, UseSuspenseInfiniteQueryOptions, UseMutationOptions, UseMutationResult, UseSuspenseQueryOptions, InfiniteData, type FetchQueryOptions, type FetchInfiniteQueryOptions, type EnsureQueryDataOptions } from "@tanstack/react-query"; import { Options, findPets, addPet, getNotDefined, postNotDefined, deletePet, findPetById, findPaginatedPets } from "../requests/sdk.gen"; import { ClientOptions, 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 } from "../requests/types.gen"; @@ -349,21 +364,26 @@ import { ClientOptions, Pet, NewPet, Error, FindPetsData, FindPetsErrors, FindPe * Sed tempus felis lobortis leo pulvinar rutrum. Nam mattis velit nisl, eu condimentum ligula luctus nec. Phasellus semper velit eget aliquet faucibus. In a mattis elit. Phasellus vel urna viverra, condimentum lorem id, rhoncus nibh. Ut pellentesque posuere elementum. Sed a varius odio. Morbi rhoncus ligula libero, vel eleifend nunc tristique vitae. Fusce et sem dui. Aenean nec scelerisque tortor. Fusce malesuada accumsan magna vel tempus. Quisque mollis felis eu dolor tristique, sit amet auctor felis gravida. Sed libero lorem, molestie sed nisl in, accumsan tempor nisi. Fusce sollicitudin massa ut lacinia mattis. Sed vel eleifend lorem. Pellentesque vitae felis pretium, pulvinar elit eu, euismod sapien. * */ -export const prefetchUseFindPets = (queryClient: QueryClient, clientOptions: Options = {}) => queryClient.prefetchQuery({ queryKey: Common.UseFindPetsKeyFn(clientOptions), queryFn: () => findPets({ ...clientOptions }).then(response => response.data) }); +export const prefetchUseFindPets = (queryClient: QueryClient, clientOptions: Options = {}, options?: Omit, "queryKey" | "queryFn">) => queryClient.prefetchQuery({ queryKey: Common.UseFindPetsKeyFn(clientOptions), queryFn: () => findPets({ ...clientOptions, throwOnError: true }).then(response => response.data), ...options }); /** * This path is not fully defined. * * @deprecated */ -export const prefetchUseGetNotDefined = (queryClient: QueryClient, clientOptions: Options = {}) => queryClient.prefetchQuery({ queryKey: Common.UseGetNotDefinedKeyFn(clientOptions), queryFn: () => getNotDefined({ ...clientOptions }).then(response => response.data) }); +export const prefetchUseGetNotDefined = (queryClient: QueryClient, clientOptions: Options = {}, options?: Omit, "queryKey" | "queryFn">) => queryClient.prefetchQuery({ queryKey: Common.UseGetNotDefinedKeyFn(clientOptions), queryFn: () => getNotDefined({ ...clientOptions, throwOnError: true }).then(response => response.data), ...options }); /** * Returns a user based on a single ID, if the user does not have access to the pet */ -export const prefetchUseFindPetById = (queryClient: QueryClient, clientOptions: Options) => queryClient.prefetchQuery({ queryKey: Common.UseFindPetByIdKeyFn(clientOptions), queryFn: () => findPetById({ ...clientOptions }).then(response => response.data) }); +export const prefetchUseFindPetById = (queryClient: QueryClient, clientOptions: Options, options?: Omit, "queryKey" | "queryFn">) => queryClient.prefetchQuery({ queryKey: Common.UseFindPetByIdKeyFn(clientOptions), queryFn: () => findPetById({ ...clientOptions, throwOnError: true }).then(response => response.data), ...options }); +/** + * Returns paginated pets from the system that the user has access to + * + */ +export const prefetchUseFindPaginatedPets = (queryClient: QueryClient, clientOptions: Options = {}, options?: Omit, "queryKey" | "queryFn">) => queryClient.prefetchQuery({ queryKey: Common.UseFindPaginatedPetsKeyFn(clientOptions), queryFn: () => findPaginatedPets({ ...clientOptions, throwOnError: true }).then(response => response.data), ...options }); /** * Returns paginated pets from the system that the user has access to * */ -export const prefetchUseFindPaginatedPets = (queryClient: QueryClient, clientOptions: Options = {}) => queryClient.prefetchQuery({ queryKey: Common.UseFindPaginatedPetsKeyFn(clientOptions), queryFn: () => findPaginatedPets({ ...clientOptions }).then(response => response.data) }); +export const prefetchUseFindPaginatedPetsInfinite = (queryClient: QueryClient, clientOptions: Common.FindPaginatedPetsInfiniteClientOptions = {}) => queryClient.prefetchInfiniteQuery({ queryKey: Common.UseFindPaginatedPetsInfiniteKeyFn(clientOptions), queryFn: ({ pageParam }) => findPaginatedPets({ ...clientOptions, query: { ...clientOptions.query, page: pageParam }, throwOnError: true } as Options).then(response => response.data), initialPageParam: 1, getNextPageParam: (response: unknown) => (response as { nextPage: number }).nextPage }); " `; diff --git a/tests/__snapshots__/generate.test.ts.snap b/tests/__snapshots__/generate.test.ts.snap index c5aa774..f6dcbc9 100644 --- a/tests/__snapshots__/generate.test.ts.snap +++ b/tests/__snapshots__/generate.test.ts.snap @@ -11,8 +11,8 @@ import type { findPetById, findPets, getNotDefined, - Options, postNotDefined, + Options, } from "../requests/sdk.gen"; import type { FindPaginatedPetsData, @@ -122,7 +122,10 @@ export const UseDeletePetKeyFn = (mutationKey?: Array) => [ exports[`generate > ensureQueryData.ts 1`] = ` "// generated with @7nohe/openapi-react-query-codegen@1.0.0 -import type { QueryClient } from "@tanstack/react-query"; +import type { + EnsureQueryDataOptions, + QueryClient, +} from "@tanstack/react-query"; import { findPaginatedPets, findPetById, @@ -148,11 +151,18 @@ import * as Common from "./common"; export const ensureUseFindPetsData = ( queryClient: QueryClient, clientOptions: Options = {}, + options?: Omit< + EnsureQueryDataOptions, + "queryKey" | "queryFn" + >, ) => queryClient.ensureQueryData({ queryKey: Common.UseFindPetsKeyFn(clientOptions), queryFn: () => - findPets({ ...clientOptions }).then((response) => response.data), + findPets({ ...clientOptions, throwOnError: true }).then( + (response) => response.data, + ), + ...options, }); /** * This path is not fully defined. @@ -162,11 +172,18 @@ export const ensureUseFindPetsData = ( export const ensureUseGetNotDefinedData = ( queryClient: QueryClient, clientOptions: Options = {}, + options?: Omit< + EnsureQueryDataOptions, + "queryKey" | "queryFn" + >, ) => queryClient.ensureQueryData({ queryKey: Common.UseGetNotDefinedKeyFn(clientOptions), queryFn: () => - getNotDefined({ ...clientOptions }).then((response) => response.data), + getNotDefined({ ...clientOptions, throwOnError: true }).then( + (response) => response.data, + ), + ...options, }); /** * Returns a user based on a single ID, if the user does not have access to the pet @@ -174,11 +191,18 @@ export const ensureUseGetNotDefinedData = ( export const ensureUseFindPetByIdData = ( queryClient: QueryClient, clientOptions: Options, + options?: Omit< + EnsureQueryDataOptions, + "queryKey" | "queryFn" + >, ) => queryClient.ensureQueryData({ queryKey: Common.UseFindPetByIdKeyFn(clientOptions), queryFn: () => - findPetById({ ...clientOptions }).then((response) => response.data), + findPetById({ ...clientOptions, throwOnError: true }).then( + (response) => response.data, + ), + ...options, }); /** * Returns paginated pets from the system that the user has access to @@ -187,11 +211,18 @@ export const ensureUseFindPetByIdData = ( export const ensureUseFindPaginatedPetsData = ( queryClient: QueryClient, clientOptions: Options = {}, + options?: Omit< + EnsureQueryDataOptions, + "queryKey" | "queryFn" + >, ) => queryClient.ensureQueryData({ queryKey: Common.UseFindPaginatedPetsKeyFn(clientOptions), queryFn: () => - findPaginatedPets({ ...clientOptions }).then((response) => response.data), + findPaginatedPets({ ...clientOptions, throwOnError: true }).then( + (response) => response.data, + ), + ...options, }); " `; @@ -245,6 +276,7 @@ export const useFindPaginatedPetsInfinite = < findPaginatedPets({ ...clientOptions, query: { ...clientOptions.query, page: pageParam as number }, + throwOnError: true, } as Options).then( (response) => response.data as TData, ) as TData, @@ -259,7 +291,10 @@ export const useFindPaginatedPetsInfinite = < exports[`generate > prefetch.ts 1`] = ` "// generated with @7nohe/openapi-react-query-codegen@1.0.0 -import type { QueryClient } from "@tanstack/react-query"; +import type { + FetchQueryOptions, + QueryClient, +} from "@tanstack/react-query"; import { findPaginatedPets, findPetById, @@ -285,11 +320,18 @@ import * as Common from "./common"; export const prefetchUseFindPets = ( queryClient: QueryClient, clientOptions: Options = {}, + options?: Omit< + FetchQueryOptions, + "queryKey" | "queryFn" + >, ) => queryClient.prefetchQuery({ queryKey: Common.UseFindPetsKeyFn(clientOptions), queryFn: () => - findPets({ ...clientOptions }).then((response) => response.data), + findPets({ ...clientOptions, throwOnError: true }).then( + (response) => response.data, + ), + ...options, }); /** * This path is not fully defined. @@ -299,11 +341,18 @@ export const prefetchUseFindPets = ( export const prefetchUseGetNotDefined = ( queryClient: QueryClient, clientOptions: Options = {}, + options?: Omit< + FetchQueryOptions, + "queryKey" | "queryFn" + >, ) => queryClient.prefetchQuery({ queryKey: Common.UseGetNotDefinedKeyFn(clientOptions), queryFn: () => - getNotDefined({ ...clientOptions }).then((response) => response.data), + getNotDefined({ ...clientOptions, throwOnError: true }).then( + (response) => response.data, + ), + ...options, }); /** * Returns a user based on a single ID, if the user does not have access to the pet @@ -311,11 +360,18 @@ export const prefetchUseGetNotDefined = ( export const prefetchUseFindPetById = ( queryClient: QueryClient, clientOptions: Options, + options?: Omit< + FetchQueryOptions, + "queryKey" | "queryFn" + >, ) => queryClient.prefetchQuery({ queryKey: Common.UseFindPetByIdKeyFn(clientOptions), queryFn: () => - findPetById({ ...clientOptions }).then((response) => response.data), + findPetById({ ...clientOptions, throwOnError: true }).then( + (response) => response.data, + ), + ...options, }); /** * Returns paginated pets from the system that the user has access to @@ -324,11 +380,40 @@ export const prefetchUseFindPetById = ( export const prefetchUseFindPaginatedPets = ( queryClient: QueryClient, clientOptions: Options = {}, + options?: Omit< + FetchQueryOptions, + "queryKey" | "queryFn" + >, ) => queryClient.prefetchQuery({ queryKey: Common.UseFindPaginatedPetsKeyFn(clientOptions), queryFn: () => - findPaginatedPets({ ...clientOptions }).then((response) => response.data), + findPaginatedPets({ ...clientOptions, throwOnError: true }).then( + (response) => response.data, + ), + ...options, + }); +/** + * Returns paginated pets from the system that the user has access to + * + */ +export const prefetchUseFindPaginatedPetsInfinite = ( + queryClient: QueryClient, + clientOptions: Common.FindPaginatedPetsInfiniteClientOptions = {}, +) => + queryClient.prefetchInfiniteQuery({ + queryKey: Common.UseFindPaginatedPetsInfiniteKeyFn(clientOptions), + queryFn: ({ pageParam }) => + findPaginatedPets({ + ...clientOptions, + query: { ...clientOptions.query, page: pageParam }, + throwOnError: true, + } as Options).then( + (response) => response.data, + ), + initialPageParam: "initial", + getNextPageParam: (response: unknown) => + (response as { meta: { next: number } }).meta.next, }); " `; @@ -349,8 +434,8 @@ import { findPetById, findPets, getNotDefined, - type Options, postNotDefined, + type Options, } from "../requests/sdk.gen"; import type { AddPetData, @@ -386,7 +471,7 @@ export const useFindPets = < useQuery({ queryKey: Common.UseFindPetsKeyFn(clientOptions, queryKey), queryFn: () => - findPets({ ...clientOptions }).then( + findPets({ ...clientOptions, throwOnError: true }).then( (response) => response.data as TData, ) as TData, ...options, @@ -408,7 +493,7 @@ export const useGetNotDefined = < useQuery({ queryKey: Common.UseGetNotDefinedKeyFn(clientOptions, queryKey), queryFn: () => - getNotDefined({ ...clientOptions }).then( + getNotDefined({ ...clientOptions, throwOnError: true }).then( (response) => response.data as TData, ) as TData, ...options, @@ -428,7 +513,7 @@ export const useFindPetById = < useQuery({ queryKey: Common.UseFindPetByIdKeyFn(clientOptions, queryKey), queryFn: () => - findPetById({ ...clientOptions }).then( + findPetById({ ...clientOptions, throwOnError: true }).then( (response) => response.data as TData, ) as TData, ...options, @@ -449,7 +534,7 @@ export const useFindPaginatedPets = < useQuery({ queryKey: Common.UseFindPaginatedPetsKeyFn(clientOptions, queryKey), queryFn: () => - findPaginatedPets({ ...clientOptions }).then( + findPaginatedPets({ ...clientOptions, throwOnError: true }).then( (response) => response.data as TData, ) as TData, ...options, @@ -472,7 +557,10 @@ export const useAddPet = < useMutation, TContext>({ mutationKey: Common.UseAddPetKeyFn(mutationKey), mutationFn: (clientOptions) => - addPet(clientOptions) as unknown as Promise, + addPet({ + ...clientOptions, + throwOnError: true, + }) as unknown as Promise, ...options, }); /** @@ -500,7 +588,10 @@ export const usePostNotDefined = < useMutation, TContext>({ mutationKey: Common.UsePostNotDefinedKeyFn(mutationKey), mutationFn: (clientOptions) => - postNotDefined(clientOptions) as unknown as Promise, + postNotDefined({ + ...clientOptions, + throwOnError: true, + }) as unknown as Promise, ...options, }); /** @@ -521,7 +612,10 @@ export const useDeletePet = < useMutation, TContext>({ mutationKey: Common.UseDeletePetKeyFn(mutationKey), mutationFn: (clientOptions) => - deletePet(clientOptions) as unknown as Promise, + deletePet({ + ...clientOptions, + throwOnError: true, + }) as unknown as Promise, ...options, }); " @@ -531,6 +625,9 @@ exports[`generate > suspense.ts 1`] = ` "// generated with @7nohe/openapi-react-query-codegen@1.0.0 import { + type InfiniteData, + useSuspenseInfiniteQuery, + type UseSuspenseInfiniteQueryOptions, useSuspenseQuery, type UseSuspenseQueryOptions, } from "@tanstack/react-query"; @@ -573,7 +670,7 @@ export const useFindPetsSuspense = < useSuspenseQuery({ queryKey: Common.UseFindPetsKeyFn(clientOptions, queryKey), queryFn: () => - findPets({ ...clientOptions }).then( + findPets({ ...clientOptions, throwOnError: true }).then( (response) => response.data as TData, ) as TData, ...options, @@ -598,7 +695,7 @@ export const useGetNotDefinedSuspense = < useSuspenseQuery({ queryKey: Common.UseGetNotDefinedKeyFn(clientOptions, queryKey), queryFn: () => - getNotDefined({ ...clientOptions }).then( + getNotDefined({ ...clientOptions, throwOnError: true }).then( (response) => response.data as TData, ) as TData, ...options, @@ -621,7 +718,7 @@ export const useFindPetByIdSuspense = < useSuspenseQuery({ queryKey: Common.UseFindPetByIdKeyFn(clientOptions, queryKey), queryFn: () => - findPetById({ ...clientOptions }).then( + findPetById({ ...clientOptions, throwOnError: true }).then( (response) => response.data as TData, ) as TData, ...options, @@ -645,9 +742,46 @@ export const useFindPaginatedPetsSuspense = < useSuspenseQuery({ queryKey: Common.UseFindPaginatedPetsKeyFn(clientOptions, queryKey), queryFn: () => - findPaginatedPets({ ...clientOptions }).then( + findPaginatedPets({ ...clientOptions, throwOnError: true }).then( + (response) => response.data as TData, + ) as TData, + ...options, + }); +/** + * Returns paginated pets from the system that the user has access to + * + */ +export const useFindPaginatedPetsSuspenseInfinite = < + TData = InfiniteData>, + TError = unknown, + TQueryKey extends Array = unknown[], +>( + clientOptions: Common.FindPaginatedPetsInfiniteClientOptions = {}, + queryKey?: TQueryKey, + options?: Omit< + UseSuspenseInfiniteQueryOptions, + "queryKey" | "queryFn" | "initialPageParam" | "getNextPageParam" + > & + Partial< + Pick< + UseSuspenseInfiniteQueryOptions, + "initialPageParam" | "getNextPageParam" + > + >, +) => + useSuspenseInfiniteQuery({ + queryKey: Common.UseFindPaginatedPetsInfiniteKeyFn(clientOptions, queryKey), + queryFn: ({ pageParam }) => + findPaginatedPets({ + ...clientOptions, + query: { ...clientOptions.query, page: pageParam as number }, + throwOnError: true, + } as Options).then( (response) => response.data as TData, ) as TData, + initialPageParam: "initial", + getNextPageParam: (response) => + (response as { meta: { next: number } }).meta.next, ...options, }); " @@ -658,7 +792,7 @@ exports[`generate - axios client with enums, noOperationId, schemaType > queries import { useMutation, UseMutationOptions, useQuery, UseQueryOptions } from "@tanstack/react-query"; import { AxiosError } from "axios"; -import { deletePetsById, getNotDefined, getPaginatedPets, getPets, getPetsById, type Options, postNotDefined, postPets } from "../requests/sdk.gen"; +import { deletePetsById, getNotDefined, getPaginatedPets, getPets, getPetsById, postNotDefined, postPets, type Options } from "../requests/sdk.gen"; import { DeletePetsByIdData, DeletePetsByIdError, GetNotDefinedData, GetPaginatedPetsData, GetPetsByIdData, GetPetsByIdError, GetPetsData, GetPetsError, PostNotDefinedData, PostPetsData, PostPetsError } from "../requests/types.gen"; import * as Common from "./common"; @@ -669,36 +803,36 @@ import * as Common from "./common"; * Sed tempus felis lobortis leo pulvinar rutrum. Nam mattis velit nisl, eu condimentum ligula luctus nec. Phasellus semper velit eget aliquet faucibus. In a mattis elit. Phasellus vel urna viverra, condimentum lorem id, rhoncus nibh. Ut pellentesque posuere elementum. Sed a varius odio. Morbi rhoncus ligula libero, vel eleifend nunc tristique vitae. Fusce et sem dui. Aenean nec scelerisque tortor. Fusce malesuada accumsan magna vel tempus. Quisque mollis felis eu dolor tristique, sit amet auctor felis gravida. Sed libero lorem, molestie sed nisl in, accumsan tempor nisi. Fusce sollicitudin massa ut lacinia mattis. Sed vel eleifend lorem. Pellentesque vitae felis pretium, pulvinar elit eu, euismod sapien. * */ -export const useGetPets = , TQueryKey extends Array = unknown[]>(clientOptions: Options = {}, queryKey?: TQueryKey, options?: Omit, "queryKey" | "queryFn">) => useQuery({ queryKey: Common.UseGetPetsKeyFn(clientOptions, queryKey), queryFn: () => getPets({ ...clientOptions }).then(response => response.data as TData) as TData, ...options }); +export const useGetPets = , TQueryKey extends Array = unknown[]>(clientOptions: Options = {}, queryKey?: TQueryKey, options?: Omit, "queryKey" | "queryFn">) => useQuery({ queryKey: Common.UseGetPetsKeyFn(clientOptions, queryKey), queryFn: () => getPets({ ...clientOptions, throwOnError: true }).then(response => response.data as TData) as TData, ...options }); /** * This path is not fully defined. * * @deprecated */ -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 useGetNotDefined = , TQueryKey extends Array = unknown[]>(clientOptions: Options = {}, queryKey?: TQueryKey, options?: Omit, "queryKey" | "queryFn">) => useQuery({ queryKey: Common.UseGetNotDefinedKeyFn(clientOptions, queryKey), queryFn: () => getNotDefined({ ...clientOptions, throwOnError: true }).then(response => response.data as TData) as TData, ...options }); /** * Returns a user based on a single ID, if the user does not have access to the pet */ -export const useGetPetsById = , TQueryKey extends Array = unknown[]>(clientOptions: Options, queryKey?: TQueryKey, options?: Omit, "queryKey" | "queryFn">) => useQuery({ queryKey: Common.UseGetPetsByIdKeyFn(clientOptions, queryKey), queryFn: () => getPetsById({ ...clientOptions }).then(response => response.data as TData) as TData, ...options }); +export const useGetPetsById = , TQueryKey extends Array = unknown[]>(clientOptions: Options, queryKey?: TQueryKey, options?: Omit, "queryKey" | "queryFn">) => useQuery({ queryKey: Common.UseGetPetsByIdKeyFn(clientOptions, queryKey), queryFn: () => getPetsById({ ...clientOptions, throwOnError: true }).then(response => response.data as TData) as TData, ...options }); /** * Returns paginated pets from the system that the user has access to * */ -export const useGetPaginatedPets = , TQueryKey extends Array = unknown[]>(clientOptions: Options = {}, queryKey?: TQueryKey, options?: Omit, "queryKey" | "queryFn">) => useQuery({ queryKey: Common.UseGetPaginatedPetsKeyFn(clientOptions, queryKey), queryFn: () => getPaginatedPets({ ...clientOptions }).then(response => response.data as TData) as TData, ...options }); +export const useGetPaginatedPets = , TQueryKey extends Array = unknown[]>(clientOptions: Options = {}, queryKey?: TQueryKey, options?: Omit, "queryKey" | "queryFn">) => useQuery({ queryKey: Common.UseGetPaginatedPetsKeyFn(clientOptions, queryKey), queryFn: () => getPaginatedPets({ ...clientOptions, throwOnError: true }).then(response => response.data as TData) as TData, ...options }); /** * Creates a new pet in the store. Duplicates are allowed */ -export const usePostPets = , TQueryKey extends Array = unknown[], TContext = unknown>(mutationKey?: TQueryKey, options?: Omit, TContext>, "mutationKey" | "mutationFn">) => useMutation, TContext>({ mutationKey: Common.UsePostPetsKeyFn(mutationKey), mutationFn: clientOptions => postPets(clientOptions) as unknown as Promise, ...options }); +export const usePostPets = , TQueryKey extends Array = unknown[], TContext = unknown>(mutationKey?: TQueryKey, options?: Omit, TContext>, "mutationKey" | "mutationFn">) => useMutation, TContext>({ mutationKey: Common.UsePostPetsKeyFn(mutationKey), mutationFn: clientOptions => postPets({ ...clientOptions, throwOnError: true }) as unknown as Promise, ...options }); /** * This path is not defined at all. * * @deprecated */ -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, throwOnError: true }) as unknown as Promise, ...options }); /** * deletes a single pet based on the ID supplied */ -export const useDeletePetsById = , TQueryKey extends Array = unknown[], TContext = unknown>(mutationKey?: TQueryKey, options?: Omit, TContext>, "mutationKey" | "mutationFn">) => useMutation, TContext>({ mutationKey: Common.UseDeletePetsByIdKeyFn(mutationKey), mutationFn: clientOptions => deletePetsById(clientOptions) as unknown as Promise, ...options }); +export const useDeletePetsById = , TQueryKey extends Array = unknown[], TContext = unknown>(mutationKey?: TQueryKey, options?: Omit, TContext>, "mutationKey" | "mutationFn">) => useMutation, TContext>({ mutationKey: Common.UseDeletePetsByIdKeyFn(mutationKey), mutationFn: clientOptions => deletePetsById({ ...clientOptions, throwOnError: true }) as unknown as Promise, ...options }); " `; @@ -706,7 +840,7 @@ exports[`generate - noSchemas option > queries.ts 1`] = ` "// generated with @7nohe/openapi-react-query-codegen@1.0.0 import { useMutation, UseMutationOptions, useQuery, UseQueryOptions } from "@tanstack/react-query"; -import { addPet, deletePet, findPaginatedPets, findPetById, findPets, getNotDefined, type Options, postNotDefined } from "../requests/sdk.gen"; +import { addPet, deletePet, findPaginatedPets, findPetById, findPets, getNotDefined, postNotDefined, type Options } from "../requests/sdk.gen"; import { AddPetData, AddPetError, DeletePetData, DeletePetError, FindPaginatedPetsData, FindPetByIdData, FindPetByIdError, FindPetsData, FindPetsError, GetNotDefinedData, PostNotDefinedData } from "../requests/types.gen"; import * as Common from "./common"; @@ -717,35 +851,35 @@ import * as Common from "./common"; * Sed tempus felis lobortis leo pulvinar rutrum. Nam mattis velit nisl, eu condimentum ligula luctus nec. Phasellus semper velit eget aliquet faucibus. In a mattis elit. Phasellus vel urna viverra, condimentum lorem id, rhoncus nibh. Ut pellentesque posuere elementum. Sed a varius odio. Morbi rhoncus ligula libero, vel eleifend nunc tristique vitae. Fusce et sem dui. Aenean nec scelerisque tortor. Fusce malesuada accumsan magna vel tempus. Quisque mollis felis eu dolor tristique, sit amet auctor felis gravida. Sed libero lorem, molestie sed nisl in, accumsan tempor nisi. Fusce sollicitudin massa ut lacinia mattis. Sed vel eleifend lorem. Pellentesque vitae felis pretium, pulvinar elit eu, euismod sapien. * */ -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 useFindPets = = unknown[]>(clientOptions: Options = {}, queryKey?: TQueryKey, options?: Omit, "queryKey" | "queryFn">) => useQuery({ queryKey: Common.UseFindPetsKeyFn(clientOptions, queryKey), queryFn: () => findPets({ ...clientOptions, throwOnError: true }).then(response => response.data as TData) as TData, ...options }); /** * This path is not fully defined. * * @deprecated */ -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 useGetNotDefined = = unknown[]>(clientOptions: Options = {}, queryKey?: TQueryKey, options?: Omit, "queryKey" | "queryFn">) => useQuery({ queryKey: Common.UseGetNotDefinedKeyFn(clientOptions, queryKey), queryFn: () => getNotDefined({ ...clientOptions, throwOnError: true }).then(response => response.data as TData) as TData, ...options }); /** * Returns a user based on a single ID, if the user does not have access to the pet */ -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 useFindPetById = = unknown[]>(clientOptions: Options, queryKey?: TQueryKey, options?: Omit, "queryKey" | "queryFn">) => useQuery({ queryKey: Common.UseFindPetByIdKeyFn(clientOptions, queryKey), queryFn: () => findPetById({ ...clientOptions, throwOnError: true }).then(response => response.data as TData) as TData, ...options }); /** * Returns paginated pets from the system that the user has access to * */ -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 useFindPaginatedPets = = unknown[]>(clientOptions: Options = {}, queryKey?: TQueryKey, options?: Omit, "queryKey" | "queryFn">) => useQuery({ queryKey: Common.UseFindPaginatedPetsKeyFn(clientOptions, queryKey), queryFn: () => findPaginatedPets({ ...clientOptions, throwOnError: true }).then(response => response.data as TData) as TData, ...options }); /** * Creates a new pet in the store. Duplicates are allowed */ -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 useAddPet = = unknown[], TContext = unknown>(mutationKey?: TQueryKey, options?: Omit, TContext>, "mutationKey" | "mutationFn">) => useMutation, TContext>({ mutationKey: Common.UseAddPetKeyFn(mutationKey), mutationFn: clientOptions => addPet({ ...clientOptions, throwOnError: true }) as unknown as Promise, ...options }); /** * This path is not defined at all. * * @deprecated */ -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, throwOnError: true }) as unknown as Promise, ...options }); /** * deletes a single pet based on the ID supplied */ -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 }); +export const useDeletePet = = unknown[], TContext = unknown>(mutationKey?: TQueryKey, options?: Omit, TContext>, "mutationKey" | "mutationFn">) => useMutation, TContext>({ mutationKey: Common.UseDeletePetKeyFn(mutationKey), mutationFn: clientOptions => deletePet({ ...clientOptions, throwOnError: true }) as unknown as Promise, ...options }); " `; diff --git a/tests/tsmorph/buildMutationHooks.test.ts b/tests/tsmorph/buildMutationHooks.test.ts index 86b9a0e..16019c2 100644 --- a/tests/tsmorph/buildMutationHooks.test.ts +++ b/tests/tsmorph/buildMutationHooks.test.ts @@ -91,7 +91,7 @@ describe("buildMutationHooks", () => { ); expect(initializer).toContain("Common.UseAddPetKeyFn(mutationKey)"); expect(initializer).toContain( - "addPet(clientOptions) as unknown as Promise", + "addPet({ ...clientOptions, throwOnError: true }) as unknown as Promise", ); }); @@ -105,7 +105,9 @@ describe("buildMutationHooks", () => { const initializer = result.declarations[0].initializer as string; expect(initializer).toContain("Common.DeletePetMutationResult"); - expect(initializer).toContain("deletePet(clientOptions)"); + expect(initializer).toContain( + "deletePet({ ...clientOptions, throwOnError: true })", + ); }); it("should build useMutation hook for PUT operation", () => { @@ -115,7 +117,9 @@ describe("buildMutationHooks", () => { const initializer = result.declarations[0].initializer as string; expect(initializer).toContain("Common.UpdatePetMutationResult"); - expect(initializer).toContain("updatePet(clientOptions)"); + expect(initializer).toContain( + "updatePet({ ...clientOptions, throwOnError: true })", + ); }); it("should build useMutation hook for PATCH operation", () => { @@ -125,7 +129,9 @@ describe("buildMutationHooks", () => { const initializer = result.declarations[0].initializer as string; expect(initializer).toContain("Common.PatchPetMutationResult"); - expect(initializer).toContain("patchPet(clientOptions)"); + expect(initializer).toContain( + "patchPet({ ...clientOptions, throwOnError: true })", + ); }); it("should use AxiosError for axios client", () => { diff --git a/tests/tsmorph/buildQueryHooks.test.ts b/tests/tsmorph/buildQueryHooks.test.ts index 9ddb3d6..07b8c40 100644 --- a/tests/tsmorph/buildQueryHooks.test.ts +++ b/tests/tsmorph/buildQueryHooks.test.ts @@ -3,8 +3,10 @@ import { describe, expect, it } from "vitest"; import { buildEnsureQueryDataFn, buildPrefetchFn, + buildPrefetchInfiniteQueryFn, buildUseInfiniteQueryHook, buildUseQueryHook, + buildUseSuspenseInfiniteQueryHook, buildUseSuspenseQueryHook, } from "../../src/tsmorph/buildQueryHooks.mjs"; import type { GenerationContext, OperationInfo } from "../../src/types.mjs"; @@ -104,7 +106,9 @@ describe("buildQueryHooks", () => { expect(initializer).toContain( "Common.UseFindPetsKeyFn(clientOptions, queryKey)", ); - expect(initializer).toContain("findPets({ ...clientOptions })"); + expect(initializer).toContain( + "findPets({ ...clientOptions, throwOnError: true })", + ); expect(initializer).toContain("response.data as TData"); }); @@ -147,7 +151,9 @@ describe("buildQueryHooks", () => { expect(initializer).toContain( "clientOptions: Options = {}", ); - expect(initializer).toContain("getStatus({ ...clientOptions })"); + expect(initializer).toContain( + "getStatus({ ...clientOptions, throwOnError: true })", + ); }); }); @@ -184,7 +190,9 @@ describe("buildQueryHooks", () => { expect(initializer).toContain( "clientOptions: Options = {}", ); - expect(initializer).toContain("getStatus({ ...clientOptions })"); + expect(initializer).toContain( + "getStatus({ ...clientOptions, throwOnError: true })", + ); }); }); @@ -269,7 +277,9 @@ describe("buildQueryHooks", () => { ); expect(initializer).toContain("queryClient.prefetchQuery"); expect(initializer).toContain("Common.UseFindPetsKeyFn(clientOptions)"); - expect(initializer).toContain("findPets({ ...clientOptions })"); + expect(initializer).toContain( + "findPets({ ...clientOptions, throwOnError: true })", + ); expect(initializer).toContain("response.data"); }); @@ -291,7 +301,18 @@ describe("buildQueryHooks", () => { "clientOptions: Options", ); // The line should not have " = {}" after the type - expect(initializer).toMatch(/Options\)/); + expect(initializer).toMatch(/Options,/); + expect(initializer).not.toContain("Options = {}"); + }); + + it("should accept fetch query options (#157)", () => { + const result = buildPrefetchFn(mockOperation, mockFetchContext); + const initializer = result.declarations[0].initializer as string; + + expect(initializer).toContain( + 'options?: Omit, "queryKey" | "queryFn">', + ); + expect(initializer).toMatch(/\.\.\.options\s*\}\)/); }); it("should handle operations without params and unknown data type", () => { @@ -304,7 +325,9 @@ describe("buildQueryHooks", () => { expect(initializer).toContain( "clientOptions: Options = {}", ); - expect(initializer).toContain("getStatus({ ...clientOptions })"); + expect(initializer).toContain( + "getStatus({ ...clientOptions, throwOnError: true })", + ); }); }); @@ -345,7 +368,91 @@ describe("buildQueryHooks", () => { expect(initializer).toContain( "clientOptions: Options = {}", ); - expect(initializer).toContain("getStatus({ ...clientOptions })"); + expect(initializer).toContain( + "getStatus({ ...clientOptions, throwOnError: true })", + ); + }); + + it("should accept ensure query data options (#157)", () => { + const result = buildEnsureQueryDataFn(mockOperation, mockFetchContext); + const initializer = result.declarations[0].initializer as string; + + expect(initializer).toContain( + 'options?: Omit, "queryKey" | "queryFn">', + ); + expect(initializer).toMatch(/\.\.\.options\s*\}\)/); + }); + }); + + describe("buildPrefetchInfiniteQueryFn", () => { + it("should return null for non-paginatable operation", () => { + const result = buildPrefetchInfiniteQueryFn( + mockOperation, + mockFetchContext, + ); + + expect(result).toBeNull(); + }); + + it("should build prefetchInfiniteQuery function (#155)", () => { + const result = buildPrefetchInfiniteQueryFn( + mockPaginatableOperation, + mockFetchContext, + ); + + expect(result).not.toBeNull(); + expect(result?.declarations[0].name).toBe( + "prefetchUseFindPaginatedPetsInfinite", + ); + + const initializer = result?.declarations[0].initializer as string; + expect(initializer).toContain("queryClient.prefetchInfiniteQuery"); + expect(initializer).toContain( + "Common.UseFindPaginatedPetsInfiniteKeyFn(clientOptions)", + ); + expect(initializer).toContain( + "clientOptions: Common.FindPaginatedPetsInfiniteClientOptions = {}", + ); + expect(initializer).toContain("initialPageParam: 1"); + expect(initializer).toContain("getNextPageParam"); + expect(initializer).toContain("throwOnError: true"); + }); + }); + + describe("buildUseSuspenseInfiniteQueryHook", () => { + it("should return null for non-paginatable operation", () => { + const result = buildUseSuspenseInfiniteQueryHook( + mockOperation, + mockFetchContext, + ); + + expect(result).toBeNull(); + }); + + it("should build useSuspenseInfiniteQuery hook", () => { + const result = buildUseSuspenseInfiniteQueryHook( + mockPaginatableOperation, + mockFetchContext, + ); + + expect(result).not.toBeNull(); + expect(result?.declarations[0].name).toBe( + "useFindPaginatedPetsSuspenseInfinite", + ); + + const initializer = result?.declarations[0].initializer as string; + expect(initializer).toContain("useSuspenseInfiniteQuery"); + expect(initializer).toContain( + "InfiniteData>", + ); + // Shares the infinite key (and cache) with the non-suspense hook + expect(initializer).toContain( + "Common.UseFindPaginatedPetsInfiniteKeyFn(clientOptions, queryKey)", + ); + // Pagination options are optional overrides, same as useInfiniteQuery + expect(initializer).toContain( + 'Partial, "initialPageParam" | "getNextPageParam">>', + ); }); }); }); diff --git a/tests/tsmorph/buildQueryOptions.test.ts b/tests/tsmorph/buildQueryOptions.test.ts index 6728b51..5cf2b2f 100644 --- a/tests/tsmorph/buildQueryOptions.test.ts +++ b/tests/tsmorph/buildQueryOptions.test.ts @@ -69,7 +69,7 @@ describe("buildQueryOptions", () => { "queryOptions({ queryKey: Common.UseFindPetsKeyFn(clientOptions, queryKey)", ); expect(initializer).toContain( - "queryFn: () => findPets({ ...clientOptions }).then(response => response.data)", + "queryFn: () => findPets({ ...clientOptions, throwOnError: true }).then(response => response.data)", ); }); @@ -124,7 +124,7 @@ describe("buildQueryOptions", () => { "infiniteQueryOptions({ queryKey: Common.UseFindPaginatedPetsInfiniteKeyFn(clientOptions, queryKey)", ); expect(initializer).toContain( - "query: { ...clientOptions.query, page: pageParam } } as Options", + "query: { ...clientOptions.query, page: pageParam }, throwOnError: true } as Options", ); expect(initializer).toContain( "getNextPageParam: (response) => (response as { nextPage: number }).nextPage", From 383c2413539157f55d64f93fc9dd9d2a592c20a6 Mon Sep 17 00:00:00 2001 From: Urata Daiki <7nohe@users.noreply.github.com> Date: Sat, 18 Jul 2026 21:54:21 +0900 Subject: [PATCH 16/31] docs: positioning README, hey-api version policy, IR boundary guardrail, Renovate - README: feature overview with queryOptions-first positioning, comparison with the hey-api tanstack-query plugin and Orval, stability policy, requirements, quick start - Contributing guide and AGENTS.md: document the exact-pin + patch upgrade workflow for hey-api and the OperationInfo/GenerationContext IR boundary that keeps the generation layer backend-portable - Add renovate.json: grouped hey-api update PRs (snapshot suite as the breaking-change detector) and grouped non-major dev tooling updates --- AGENTS.md | 10 +++++ README.md | 60 ++++++++++++++++++++++++--- docs/src/content/docs/contributing.md | 14 +++++++ renovate.json | 20 +++++++++ 4 files changed, 99 insertions(+), 5 deletions(-) create mode 100644 renovate.json diff --git a/AGENTS.md b/AGENTS.md index f5ebc15..2c2b4ef 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -28,6 +28,16 @@ - 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. +## Architecture: IR Boundary (Backend Portability) +- The generation pipeline is split by an intermediate representation (IR): `OperationInfo` and `GenerationContext` in `src/types.mts`. +- hey-api-specific knowledge must stay confined to the parsing side: the `createClient` invocation in `src/generate.mts` and the `sdk.gen`/`types.gen` parsing in `src/service.mts` / `src/createSource.mts`. +- Generation-side modules (`src/tsmorph/build*.mts`, `generateFiles.mts`) should consume only the IR. Do not add new hey-api-specific parsing or imports there; extend the IR instead. +- This boundary is what keeps the generator portable to a different SDK backend without rewriting the generation layer. Treat leaks across it as review findings. + +## hey-api Version Policy +- `@hey-api/openapi-ts` is pinned to an exact version and patched via `pnpm.patchedDependencies` when needed (see `patches/`). +- Upgrades: bump the pin, run the full snapshot suite, regenerate an example app and type-check it, then release as a minor version. Breaking changes in hey-api are absorbed here — they must not leak into the generated API surface outside a major version. + ## 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/README.md b/README.md index 652c5ea..a61aef7 100644 --- a/README.md +++ b/README.md @@ -1,12 +1,62 @@ # OpenAPI React Query Codegen -> Code generator for creating [React Query (also known as TanStack Query)](https://tanstack.com/query) hooks based on your OpenAPI schema. +> Code generator for [TanStack Query (React Query)](https://tanstack.com/query) based on your OpenAPI schema — `queryOptions` factories following the official TanStack Query v5 pattern, plus ready-to-use hooks, prefetch, ensure, suspense, and infinite query helpers. [![npm version](https://badge.fury.io/js/%407nohe%2Fopenapi-react-query-codegen.svg)](https://badge.fury.io/js/%407nohe%2Fopenapi-react-query-codegen) +📖 **[Documentation](https://openapi-react-query-codegen.vercel.app)** · [Migrating to v3](https://openapi-react-query-codegen.vercel.app/guides/migrating-to-v3/) + ## Features -- Generates custom react hooks that use React Query's `useQuery`, `useSuspenseQuery`, `useMutation` and `useInfiniteQuery` hooks -- Generates custom functions that use React Query's `ensureQueryData` and `prefetchQuery` functions -- Generates query keys and functions for query caching -- Generates pure TypeScript clients generated by [@hey-api/openapi-ts](https://github.com/hey-api/openapi-ts) +- **`queryOptions` / `infiniteQueryOptions` factories** for every GET operation — the [TanStack Query v5 recommended pattern](https://tanstack.com/query/latest/docs/framework/react/guides/query-options), composable with `useQuery`, `useQueries`, `useSuspenseQuery`, `prefetchQuery`, `ensureQueryData`, and `setQueryData` with full type safety +- **Custom hooks**: `useQuery`, `useSuspenseQuery`, `useMutation`, `useInfiniteQuery`, and `useSuspenseInfiniteQuery` variants per operation +- **SSR helpers**: `prefetchQuery`, `prefetchInfiniteQuery`, and `ensureQueryData` functions per operation — ready for Next.js App Router hydration +- **Hierarchical query keys** with exported key constants and functions: invalidate one exact query, all infinite pages of an operation, or every cache entry of an operation with a single prefix +- **Pure TypeScript clients** generated by [@hey-api/openapi-ts](https://github.com/hey-api/openapi-ts) (fetch and axios) + +## Quick start + +```bash +npm install -D @7nohe/openapi-react-query-codegen +npx openapi-rq -i ./petstore.yaml +``` + +```tsx +import { useQuery } from "@tanstack/react-query"; +import { findPetsOptions } from "./openapi/queries"; + +function Pets() { + const { data } = useQuery(findPetsOptions({ query: { limit: 10 } })); + // ...or use the generated hook directly: useFindPets({ query: { limit: 10 } }) +} +``` + +See the [documentation](https://openapi-react-query-codegen.vercel.app) for CLI options, SSR recipes, and infinite query usage. + +## How it compares + +| | This library | @hey-api tanstack-query plugin | Orval | +|---|---|---|---| +| `queryOptions` / `infiniteQueryOptions` factories (TanStack v5 pattern) | ✅ | ✅ | ❌ | +| Ready-to-use hooks (`useQuery` / suspense / infinite variants) | ✅ | ❌ (options only) | ✅ | +| SSR helpers (`prefetchQuery` / `prefetchInfiniteQuery` / `ensureQueryData`) | ✅ | ❌ | Partial (`usePrefetch`) | +| Hierarchical query keys for granular invalidation | ✅ | ✅ (tags) | Partial | +| Stable release line | ✅ SemVer | pre-1.0, frequent breaking changes | ✅ | +| MSW mock generation | ❌ (out of scope) | ❌ | ✅ | +| Vue / Solid / Svelte / Angular | ❌ React-focused | ✅ | ✅ | + +**Scope**: this library is deliberately React-focused and does not generate API mocks — use Orval if MSW mocks are your priority, or hey-api's own plugin if you need non-React frameworks. + +## Stability policy + +This library builds on [@hey-api/openapi-ts](https://github.com/hey-api/openapi-ts), which is pre-1.0 and moves fast. We **pin the exact hey-api version** and absorb its breaking changes for you: hey-api upgrades land here only after our full snapshot-test suite passes, and are released as minor versions. Your generated API surface follows SemVer — breaking output changes only happen in major versions, with a migration guide. + +## Requirements + +- Node.js 22.18+ +- `@tanstack/react-query` 5.x (peer dependency) +- `typescript` 5.x or 6.x, `ts-morph` 28.x, `commander` 12–15 (peer dependencies) + +## License + +MIT diff --git a/docs/src/content/docs/contributing.md b/docs/src/content/docs/contributing.md index 4d703e4..49c0238 100644 --- a/docs/src/content/docs/contributing.md +++ b/docs/src/content/docs/contributing.md @@ -46,6 +46,20 @@ npm run build && pnpm --filter @7nohe/react-app generate:api && pnpm --filter @7 pnpm --filter docs dev ``` +## hey-api version policy + +`@hey-api/openapi-ts` is **exact-pinned** because it is pre-1.0 and ships frequent breaking changes. This library's job is to absorb those changes so downstream users get a stable, SemVer-respecting API surface: + +1. Bump the pin (Renovate opens grouped PRs automatically). +2. Run the full snapshot suite (`pnpm test`) — snapshots are the breaking-change detector. +3. Regenerate an example app and type-check it (see "Build example and validate generated code" above). +4. Recreate the type patches if needed (see below). +5. Release as a **minor** version. If a hey-api change forces the generated API surface to change, hold it for the next major and document it in the migration guide. + +### Architecture guardrail: the IR boundary + +The generator is split by an intermediate representation — `OperationInfo` / `GenerationContext` in `src/types.mts`. hey-api-specific knowledge belongs only in the parsing side (`src/generate.mts`, `src/service.mts`, `src/createSource.mts`); the generation side (`src/tsmorph/`) must consume only the IR. Keeping this boundary tight is what makes a future SDK-backend switch a parsing-layer rewrite instead of a full rewrite — please flag boundary leaks in review. + ## Type patches for dependencies This project compiles with `skipLibCheck: false`, so type errors inside dependency declaration files fail the build. Two mechanisms keep it green: diff --git a/renovate.json b/renovate.json new file mode 100644 index 0000000..d9eda30 --- /dev/null +++ b/renovate.json @@ -0,0 +1,20 @@ +{ + "$schema": "https://docs.renovatebot.com/renovate-schema.json", + "extends": ["config:recommended"], + "ignorePaths": ["examples/**", "docs/**"], + "packageRules": [ + { + "description": "hey-api is exact-pinned; every update PR must pass the full snapshot suite before merging (see the stability policy in README)", + "matchPackageNames": ["@hey-api/openapi-ts", "@hey-api/shared"], + "rangeStrategy": "pin", + "groupName": "hey-api", + "prPriority": 10 + }, + { + "description": "Group non-major dev tooling updates to keep PR noise low", + "matchDepTypes": ["devDependencies"], + "matchUpdateTypes": ["minor", "patch"], + "groupName": "dev tooling (non-major)" + } + ] +} From 5e014386a966db543aab120cc069c26e4d28b1c4 Mon Sep 17 00:00:00 2001 From: Urata Daiki <7nohe@users.noreply.github.com> Date: Sat, 18 Jul 2026 22:10:13 +0900 Subject: [PATCH 17/31] refactor: extract shared infinite-query builders and align generated output Review cleanup of the recent feature work: - Extract SDK_CALL_ARGS, buildPagedQueryFn, formatInitialPageParam, buildGetNextPageParamExpr, buildInfiniteClientOptionsParam, and buildOverridableInfiniteOptionsType so the throwOnError call shape and the infinite-query templates live in one place instead of ten - Merge buildUseInfiniteQueryHook / buildUseSuspenseInfiniteQueryHook into one parameterized implementation - Emit initialPageParam as a numeric literal in hooks too, matching the factories and prefetch functions (server-prefetched page 1 now hydrates the same pageParam the client refetches with) - Give prefetchUseXxxInfinite the same options passthrough as its siblings, which also makes the FetchInfiniteQueryOptions import real - Reuse buildClientOptionsParam / getDataTypeName in prefetch, ensure, and queryOptions builders instead of inlined copies - generateIndexFile returns its constant content without a ts-morph project; suspense/prefetch loops filter paginatable operations up front - examples: use key={pet.id} instead of array-index keys and drop the noArrayIndexKey override --- biome.json | 1 - examples/nextjs-app/app/components/Pets.tsx | 4 +- examples/react-app/src/App.tsx | 4 +- examples/react-router-6-app/src/App.tsx | 4 +- .../app/routes/_index/route.tsx | 4 +- src/tsmorph/buildMutationHooks.mts | 5 +- src/tsmorph/buildQueryHooks.mts | 280 ++++++++---------- src/tsmorph/buildQueryOptions.mts | 37 +-- src/tsmorph/generateFiles.mts | 37 +-- tests/__snapshots__/createSource.test.ts.snap | 8 +- tests/__snapshots__/generate.test.ts.snap | 12 +- tests/tsmorph/buildQueryHooks.test.ts | 2 +- tests/tsmorph/buildQueryOptions.test.ts | 4 +- 13 files changed, 170 insertions(+), 232 deletions(-) diff --git a/biome.json b/biome.json index 76c446f..687b15a 100644 --- a/biome.json +++ b/biome.json @@ -50,7 +50,6 @@ "noUnusedFunctionParameters": "off" }, "suspicious": { - "noArrayIndexKey": "off", "noUnknownAtRules": "off" } } diff --git a/examples/nextjs-app/app/components/Pets.tsx b/examples/nextjs-app/app/components/Pets.tsx index d3c9e22..a2a1391 100644 --- a/examples/nextjs-app/app/components/Pets.tsx +++ b/examples/nextjs-app/app/components/Pets.tsx @@ -13,9 +13,7 @@ export default function Pets() {

Pet List

    {Array.isArray(data) && - data?.map((pet, index) => ( -
  • {pet.name}
  • - ))} + data?.map((pet) =>
  • {pet.name}
  • )}
diff --git a/examples/react-app/src/App.tsx b/examples/react-app/src/App.tsx index e8489f4..464d39d 100644 --- a/examples/react-app/src/App.tsx +++ b/examples/react-app/src/App.tsx @@ -85,9 +85,7 @@ function App() { )}
    {Array.isArray(data) && - data?.map((pet, index) => ( -
  • {pet.name}
  • - ))} + data?.map((pet) =>
  • {pet.name}
  • )}

Suspense Components

diff --git a/examples/react-router-6-app/src/App.tsx b/examples/react-router-6-app/src/App.tsx index b4f1376..8f9c509 100644 --- a/examples/react-router-6-app/src/App.tsx +++ b/examples/react-router-6-app/src/App.tsx @@ -85,9 +85,7 @@ export function Compoment() { )}
    {Array.isArray(data) && - data?.map((pet, index) => ( -
  • {pet.name}
  • - ))} + data?.map((pet) =>
  • {pet.name}
  • )}
); diff --git a/examples/react-router-7-app/app/routes/_index/route.tsx b/examples/react-router-7-app/app/routes/_index/route.tsx index 85e354a..ecd7f0f 100644 --- a/examples/react-router-7-app/app/routes/_index/route.tsx +++ b/examples/react-router-7-app/app/routes/_index/route.tsx @@ -90,9 +90,7 @@ function Pets() { )}
    {Array.isArray(data) && - data?.map((pet, index) => ( -
  • {pet.name}
  • - ))} + data?.map((pet) =>
  • {pet.name}
  • )}
); diff --git a/src/tsmorph/buildMutationHooks.mts b/src/tsmorph/buildMutationHooks.mts index 99a8281..489b973 100644 --- a/src/tsmorph/buildMutationHooks.mts +++ b/src/tsmorph/buildMutationHooks.mts @@ -4,6 +4,7 @@ import { type VariableStatementStructure, } from "ts-morph"; import type { GenerationContext, OperationInfo } from "../types.mjs"; +import { SDK_CALL_ARGS } from "./buildQueryHooks.mjs"; /** * Get the error type string based on client type. @@ -48,9 +49,7 @@ export function buildUseMutationHook( const optionsType = `Options<${dataTypeName}, true>`; - // throwOnError: true forces the SDK call to reject on error responses so - // the mutation error state fires; the hey-api runtime default is false (#172) - const mutationFn = `clientOptions => ${op.methodName}({ ...clientOptions, throwOnError: true }) as unknown as Promise`; + const mutationFn = `clientOptions => ${op.methodName}(${SDK_CALL_ARGS}) as unknown as Promise`; const body = `useMutation({ mutationKey: Common.Use${op.capitalizedMethodName}KeyFn(mutationKey), mutationFn: ${mutationFn}, ...options })`; diff --git a/src/tsmorph/buildQueryHooks.mts b/src/tsmorph/buildQueryHooks.mts index b6b4f93..bc32387 100644 --- a/src/tsmorph/buildQueryHooks.mts +++ b/src/tsmorph/buildQueryHooks.mts @@ -5,8 +5,6 @@ import { } from "ts-morph"; import type { GenerationContext, OperationInfo } from "../types.mjs"; -type QueryHookType = "useQuery" | "useSuspenseQuery" | "useInfiniteQuery"; - /** * Get the error type string based on client type. */ @@ -22,23 +20,6 @@ function getErrorType(op: OperationInfo, ctx: GenerationContext): string { return errorType; } -/** - * 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; -} - /** * Resolve the generated Data type name for an operation, falling back to * unknown when the operation has no generated Data type. @@ -52,6 +33,14 @@ export function getDataTypeName( : "unknown"; } +/** + * SDK call arguments shared by every generated queryFn/mutationFn. + * throwOnError: true forces the SDK call to reject on error responses; the + * hey-api runtime default is false, which would resolve undefined data and + * swallow the error instead of surfacing it to TanStack Query (#172). + */ +export const SDK_CALL_ARGS = "{ ...clientOptions, throwOnError: true }"; + /** * Build the client options parameter string. */ @@ -59,11 +48,7 @@ export function buildClientOptionsParam( op: OperationInfo, ctx: GenerationContext, ): string { - const dataTypeName = ctx.modelNames.includes( - `${op.capitalizedMethodName}Data`, - ) - ? `${op.capitalizedMethodName}Data` - : "unknown"; + const dataTypeName = getDataTypeName(op, ctx); const hasParams = op.parameters.length > 0; if (!hasParams) { @@ -74,6 +59,74 @@ export function buildClientOptionsParam( return `clientOptions: Options<${dataTypeName}, true>${defaultValue}`; } +/** + * Build the clientOptions parameter typed with the page-less infinite + * options type — the page parameter is supplied by TanStack Query's + * pageParam mechanism. + */ +export function buildInfiniteClientOptionsParam(op: OperationInfo): string { + const defaultValue = op.allParamsOptional ? " = {}" : ""; + return `clientOptions: Common.${op.capitalizedMethodName}InfiniteClientOptions${defaultValue}`; +} + +/** + * Build the paginated SDK call shared by every infinite query builder. + */ +export function buildPagedQueryFn( + op: OperationInfo, + ctx: GenerationContext, + castTData: boolean, +): string { + const dataTypeName = getDataTypeName(op, ctx); + const thenClause = castTData + ? ".then(response => response.data as TData) as TData" + : ".then(response => response.data)"; + return `({ pageParam }) => ${op.methodName}({ ...clientOptions, query: { ...clientOptions.query, ${ctx.pageParam}: pageParam as number }, throwOnError: true } as Options<${dataTypeName}, true>)${thenClause}`; +} + +/** + * Format the initialPageParam literal. Emits a numeric literal when possible + * so the inferred pageParam type matches what getNextPageParam returns. + */ +export function formatInitialPageParam(ctx: GenerationContext): string { + return /^-?\d+$/.test(ctx.initialPageParam) + ? ctx.initialPageParam + : JSON.stringify(ctx.initialPageParam); +} + +/** + * Build the nested type for getNextPageParam. + * E.g., "meta.next" becomes "{ meta: { next: number } }" + */ +export function buildNestedNextPageType(nextPageParam: string): string { + const segments = nextPageParam.split("."); + return segments.reduceRight((acc, segment) => { + return `{ ${segment}: ${acc} }`; + }, "number"); +} + +/** + * Build the getNextPageParam expression. The parameter is annotated because + * not every TanStack entry point contextually types it (prefetchInfiniteQuery + * does not, which would fail noImplicitAny). + */ +export function buildGetNextPageParamExpr(ctx: GenerationContext): string { + const nestedType = buildNestedNextPageType(ctx.nextPageParam); + return `(response: unknown) => (response as ${nestedType}).${ctx.nextPageParam}`; +} + +/** + * Build an options type where the pagination fields TanStack Query marks as + * required become optional overrides: the generator supplies them, and + * callers may replace them for custom pagination schemes (#156, #146). + */ +export function buildOverridableInfiniteOptionsType( + optionsTypeName: string, +): string { + const instantiated = `${optionsTypeName}`; + return `Omit<${instantiated}, "queryKey" | "queryFn" | "initialPageParam" | "getNextPageParam"> & Partial>`; +} + /** * Build useQuery hook. * Example: @@ -83,7 +136,7 @@ export function buildClientOptionsParam( * options?: Omit, "queryKey" | "queryFn"> * ) => useQuery({ * queryKey: Common.UseFindPetsKeyFn(clientOptions, queryKey), - * queryFn: () => findPets({ ...clientOptions }).then(response => response.data as TData) as TData, + * queryFn: () => findPets({ ...clientOptions, throwOnError: true }).then(response => response.data as TData) as TData, * ...options * }); */ @@ -93,13 +146,10 @@ export function buildUseQueryHook( ): VariableStatementStructure { const hookName = `use${op.capitalizedMethodName}`; const errorType = getErrorType(op, ctx); - const dataTypeDefault = getDataTypeDefault(op, "useQuery"); + const dataTypeDefault = `Common.${op.capitalizedMethodName}DefaultResponse`; const clientOptionsParam = buildClientOptionsParam(op, ctx); - // throwOnError: true forces the SDK call to reject on error responses; the - // hey-api runtime default is false, which would resolve undefined data and - // swallow the error instead of surfacing it to TanStack Query (#172) - const queryFn = `() => ${op.methodName}({ ...clientOptions, throwOnError: true }).then(response => response.data as TData) as TData`; + const queryFn = `() => ${op.methodName}(${SDK_CALL_ARGS}).then(response => response.data as TData) as TData`; const body = `useQuery({ queryKey: Common.Use${op.capitalizedMethodName}KeyFn(clientOptions, queryKey), queryFn: ${queryFn}, ...options })`; @@ -127,10 +177,10 @@ export function buildUseSuspenseQueryHook( ): VariableStatementStructure { const hookName = `use${op.capitalizedMethodName}Suspense`; const errorType = getErrorType(op, ctx); - const dataTypeDefault = getDataTypeDefault(op, "useSuspenseQuery"); + const dataTypeDefault = `NonNullable`; const clientOptionsParam = buildClientOptionsParam(op, ctx); - const queryFn = `() => ${op.methodName}({ ...clientOptions, throwOnError: true }).then(response => response.data as TData) as TData`; + const queryFn = `() => ${op.methodName}(${SDK_CALL_ARGS}).then(response => response.data as TData) as TData`; const body = `useSuspenseQuery({ queryKey: Common.Use${op.capitalizedMethodName}KeyFn(clientOptions, queryKey), queryFn: ${queryFn}, ...options })`; @@ -150,48 +200,38 @@ export function buildUseSuspenseQueryHook( } /** - * Build the nested type for getNextPageParam. - * E.g., "meta.next" becomes "{ meta: { next: number } }" - */ -export function buildNestedNextPageType(nextPageParam: string): string { - const segments = nextPageParam.split("."); - return segments.reduceRight((acc, segment) => { - return `{ ${segment}: ${acc} }`; - }, "number"); -} - -/** - * Build useInfiniteQuery hook. + * Build a useInfiniteQuery / useSuspenseInfiniteQuery hook. Both variants + * share the infinite query key (and therefore the cache); they differ only + * in the TanStack hook called, the options type, and the NonNullable TData + * default of the suspense variant. */ -export function buildUseInfiniteQueryHook( +function buildInfiniteHook( op: OperationInfo, ctx: GenerationContext, + suspense: boolean, ): VariableStatementStructure | null { if (!op.isPaginatable) { return null; } - const hookName = `use${op.capitalizedMethodName}Infinite`; + const hookCall = suspense ? "useSuspenseInfiniteQuery" : "useInfiniteQuery"; + const optionsTypeName = suspense + ? "UseSuspenseInfiniteQueryOptions" + : "UseInfiniteQueryOptions"; + const hookName = suspense + ? `use${op.capitalizedMethodName}SuspenseInfinite` + : `use${op.capitalizedMethodName}Infinite`; + const errorType = getErrorType(op, ctx); const baseDataType = `Common.${op.capitalizedMethodName}DefaultResponse`; - const dataTypeName = getDataTypeName(op, ctx); - - // Infinite queries take a dedicated options type that excludes the page - // parameter — it is supplied by TanStack Query's pageParam mechanism - const defaultValue = op.allParamsOptional ? " = {}" : ""; - const clientOptionsParam = `clientOptions: Common.${op.capitalizedMethodName}InfiniteClientOptions${defaultValue}`; - - // Build the queryFn with pageParam handling - const queryFn = `({ pageParam }) => ${op.methodName}({ ...clientOptions, query: { ...clientOptions.query, ${ctx.pageParam}: pageParam as number }, throwOnError: true } as Options<${dataTypeName}, true>).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}`; + const dataTypeDefault = suspense + ? `InfiniteData>` + : `InfiniteData<${baseDataType}>`; - // initialPageParam is a string literal - const infiniteOptions = `initialPageParam: "${ctx.initialPageParam}", ${getNextPageParam}`; + const queryFn = buildPagedQueryFn(op, ctx, true); + const infiniteOptions = `initialPageParam: ${formatInitialPageParam(ctx)}, getNextPageParam: ${buildGetNextPageParamExpr(ctx)}`; - const body = `useInfiniteQuery({ queryKey: Common.Use${op.capitalizedMethodName}InfiniteKeyFn(clientOptions, queryKey), queryFn: ${queryFn}, ${infiniteOptions}, ...options })`; + const body = `${hookCall}({ queryKey: Common.Use${op.capitalizedMethodName}InfiniteKeyFn(clientOptions, queryKey), queryFn: ${queryFn}, ${infiniteOptions}, ...options })`; return { kind: StructureKind.VariableStatement, @@ -202,55 +242,30 @@ export function buildUseInfiniteQueryHook( declarations: [ { name: hookName, - initializer: `, TError = ${errorType}, TQueryKey extends Array = unknown[]>(${clientOptionsParam}, queryKey?: TQueryKey, options?: Omit, "queryKey" | "queryFn" | "initialPageParam" | "getNextPageParam"> & Partial, "initialPageParam" | "getNextPageParam">>) => ${body}`, + initializer: ` = unknown[]>(${buildInfiniteClientOptionsParam(op)}, queryKey?: TQueryKey, options?: ${buildOverridableInfiniteOptionsType(optionsTypeName)}) => ${body}`, }, ], }; } +/** + * Build useInfiniteQuery hook. + */ +export function buildUseInfiniteQueryHook( + op: OperationInfo, + ctx: GenerationContext, +): VariableStatementStructure | null { + return buildInfiniteHook(op, ctx, false); +} + /** * Build useSuspenseInfiniteQuery hook. - * Shares the infinite query key (and therefore the cache) with the - * non-suspense useInfiniteQuery hook for the same operation. */ export function buildUseSuspenseInfiniteQueryHook( op: OperationInfo, ctx: GenerationContext, ): VariableStatementStructure | null { - if (!op.isPaginatable) { - return null; - } - - const hookName = `use${op.capitalizedMethodName}SuspenseInfinite`; - const errorType = getErrorType(op, ctx); - const baseDataType = `Common.${op.capitalizedMethodName}DefaultResponse`; - const dataTypeName = getDataTypeName(op, ctx); - - const defaultValue = op.allParamsOptional ? " = {}" : ""; - const clientOptionsParam = `clientOptions: Common.${op.capitalizedMethodName}InfiniteClientOptions${defaultValue}`; - - const queryFn = `({ pageParam }) => ${op.methodName}({ ...clientOptions, query: { ...clientOptions.query, ${ctx.pageParam}: pageParam as number }, throwOnError: true } as Options<${dataTypeName}, true>).then(response => response.data as TData) as TData`; - - const nestedType = buildNestedNextPageType(ctx.nextPageParam); - const getNextPageParam = `getNextPageParam: (response) => (response as ${nestedType}).${ctx.nextPageParam}`; - - const infiniteOptions = `initialPageParam: "${ctx.initialPageParam}", ${getNextPageParam}`; - - const body = `useSuspenseInfiniteQuery({ queryKey: Common.Use${op.capitalizedMethodName}InfiniteKeyFn(clientOptions, queryKey), queryFn: ${queryFn}, ${infiniteOptions}, ...options })`; - - return { - kind: StructureKind.VariableStatement, - // Copy the operation's JSDoc (description and @deprecated) from the SDK function - leadingTrivia: op.jsDoc ? `${op.jsDoc}\n` : undefined, - isExported: true, - declarationKind: VariableDeclarationKind.Const, - declarations: [ - { - name: hookName, - initializer: `>, TError = ${errorType}, TQueryKey extends Array = unknown[]>(${clientOptionsParam}, queryKey?: TQueryKey, options?: Omit, "queryKey" | "queryFn" | "initialPageParam" | "getNextPageParam"> & Partial, "initialPageParam" | "getNextPageParam">>) => ${body}`, - }, - ], - }; + return buildInfiniteHook(op, ctx, true); } /** @@ -268,19 +283,8 @@ export function buildPrefetchFn( 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 queryFn = `() => ${op.methodName}({ ...clientOptions, throwOnError: true }).then(response => response.data)`; + const queryFn = `() => ${op.methodName}(${SDK_CALL_ARGS}).then(response => response.data)`; const optionsParam = `options?: Omit, "queryKey" | "queryFn">`; const body = `queryClient.prefetchQuery({ queryKey: Common.Use${op.capitalizedMethodName}KeyFn(clientOptions), queryFn: ${queryFn}, ...options })`; @@ -294,7 +298,7 @@ export function buildPrefetchFn( declarations: [ { name: fnName, - initializer: `(queryClient: QueryClient, ${clientOptionsParam}, ${optionsParam}) => ${body}`, + initializer: `(queryClient: QueryClient, ${buildClientOptionsParam(op, ctx)}, ${optionsParam}) => ${body}`, }, ], }; @@ -303,12 +307,13 @@ export function buildPrefetchFn( /** * Build prefetchInfiniteQuery function for a paginatable operation. * Example: - * export const prefetchUseFindPaginatedPetsInfinite = (queryClient: QueryClient, clientOptions: Common.FindPaginatedPetsInfiniteClientOptions = {}) => + * export const prefetchUseFindPaginatedPetsInfinite = (queryClient: QueryClient, clientOptions: Common.FindPaginatedPetsInfiniteClientOptions = {}, options?: Omit, "queryKey" | "queryFn" | "initialPageParam" | "getNextPageParam">) => * queryClient.prefetchInfiniteQuery({ * queryKey: Common.UseFindPaginatedPetsInfiniteKeyFn(clientOptions), - * queryFn: ({ pageParam }) => findPaginatedPets({ ...clientOptions, query: { ...clientOptions.query, page: pageParam }, throwOnError: true } as Options).then(response => response.data), + * queryFn: ({ pageParam }) => findPaginatedPets({ ...clientOptions, query: { ...clientOptions.query, page: pageParam as number }, throwOnError: true } as Options).then(response => response.data), * initialPageParam: 1, - * getNextPageParam: (response) => (response as { nextPage: number }).nextPage + * getNextPageParam: (response: unknown) => (response as { nextPage: number }).nextPage, + * ...options * }); */ export function buildPrefetchInfiniteQueryFn( @@ -320,25 +325,12 @@ export function buildPrefetchInfiniteQueryFn( } const fnName = `prefetchUse${op.capitalizedMethodName}Infinite`; - const dataTypeName = getDataTypeName(op, ctx); - - const defaultValue = op.allParamsOptional ? " = {}" : ""; - const clientOptionsParam = `clientOptions: Common.${op.capitalizedMethodName}InfiniteClientOptions${defaultValue}`; - - const queryFn = `({ pageParam }) => ${op.methodName}({ ...clientOptions, query: { ...clientOptions.query, ${ctx.pageParam}: pageParam }, throwOnError: true } as Options<${dataTypeName}, true>).then(response => response.data)`; - - // Emit a numeric literal when possible so the inferred pageParam type - // matches what getNextPageParam returns - const initialPageParam = /^-?\d+$/.test(ctx.initialPageParam) - ? ctx.initialPageParam - : JSON.stringify(ctx.initialPageParam); - const nestedType = buildNestedNextPageType(ctx.nextPageParam); - // prefetchInfiniteQuery does not contextually type getNextPageParam's - // parameter, so annotate it explicitly to satisfy noImplicitAny - const getNextPageParam = `getNextPageParam: (response: unknown) => (response as ${nestedType}).${ctx.nextPageParam}`; + const queryFn = buildPagedQueryFn(op, ctx, false); + const infiniteOptions = `initialPageParam: ${formatInitialPageParam(ctx)}, getNextPageParam: ${buildGetNextPageParamExpr(ctx)}`; - const body = `queryClient.prefetchInfiniteQuery({ queryKey: Common.Use${op.capitalizedMethodName}InfiniteKeyFn(clientOptions), queryFn: ${queryFn}, initialPageParam: ${initialPageParam}, ${getNextPageParam} })`; + const optionsParam = `options?: Omit, "queryKey" | "queryFn" | "initialPageParam" | "getNextPageParam">`; + const body = `queryClient.prefetchInfiniteQuery({ queryKey: Common.Use${op.capitalizedMethodName}InfiniteKeyFn(clientOptions), queryFn: ${queryFn}, ${infiniteOptions}, ...options })`; return { kind: StructureKind.VariableStatement, @@ -349,7 +341,7 @@ export function buildPrefetchInfiniteQueryFn( declarations: [ { name: fnName, - initializer: `(queryClient: QueryClient, ${clientOptionsParam}) => ${body}`, + initializer: `(queryClient: QueryClient, ${buildInfiniteClientOptionsParam(op)}, ${optionsParam}) => ${body}`, }, ], }; @@ -358,10 +350,11 @@ export function buildPrefetchInfiniteQueryFn( /** * Build ensureQueryData function. * Example: - * export const ensureUseFindPetsData = (queryClient: QueryClient, clientOptions: Options = {}) => + * export const ensureUseFindPetsData = (queryClient: QueryClient, clientOptions: Options = {}, options?: Omit, "queryKey" | "queryFn">) => * queryClient.ensureQueryData({ * queryKey: Common.UseFindPetsKeyFn(clientOptions), - * queryFn: () => findPets({ ...clientOptions }).then(response => response.data) + * queryFn: () => findPets({ ...clientOptions, throwOnError: true }).then(response => response.data), + * ...options * }); */ export function buildEnsureQueryDataFn( @@ -369,21 +362,8 @@ export function buildEnsureQueryDataFn( 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> = {}`; - // throwOnError: true makes the SDK call reject on error responses so - // ensureQueryData rejects instead of caching undefined (#172) - const queryFn = `() => ${op.methodName}({ ...clientOptions, throwOnError: true }).then(response => response.data)`; + const queryFn = `() => ${op.methodName}(${SDK_CALL_ARGS}).then(response => response.data)`; const optionsParam = `options?: Omit, "queryKey" | "queryFn">`; const body = `queryClient.ensureQueryData({ queryKey: Common.Use${op.capitalizedMethodName}KeyFn(clientOptions), queryFn: ${queryFn}, ...options })`; @@ -397,7 +377,7 @@ export function buildEnsureQueryDataFn( declarations: [ { name: fnName, - initializer: `(queryClient: QueryClient, ${clientOptionsParam}, ${optionsParam}) => ${body}`, + initializer: `(queryClient: QueryClient, ${buildClientOptionsParam(op, ctx)}, ${optionsParam}) => ${body}`, }, ], }; diff --git a/src/tsmorph/buildQueryOptions.mts b/src/tsmorph/buildQueryOptions.mts index 46ff880..4e4b400 100644 --- a/src/tsmorph/buildQueryOptions.mts +++ b/src/tsmorph/buildQueryOptions.mts @@ -6,8 +6,11 @@ import { import type { GenerationContext, OperationInfo } from "../types.mjs"; import { buildClientOptionsParam, - buildNestedNextPageType, - getDataTypeName, + buildGetNextPageParamExpr, + buildInfiniteClientOptionsParam, + buildPagedQueryFn, + formatInitialPageParam, + SDK_CALL_ARGS, } from "./buildQueryHooks.mjs"; /** @@ -19,7 +22,7 @@ import { * export const findPetsOptions = (clientOptions: Options = {}, queryKey?: Array) => * queryOptions({ * queryKey: Common.UseFindPetsKeyFn(clientOptions, queryKey), - * queryFn: () => findPets({ ...clientOptions }).then(response => response.data), + * queryFn: () => findPets({ ...clientOptions, throwOnError: true }).then(response => response.data), * }); */ export function buildQueryOptionsFn( @@ -29,9 +32,7 @@ export function buildQueryOptionsFn( const fnName = `${op.methodName}Options`; const clientOptionsParam = buildClientOptionsParam(op, ctx); - // throwOnError: true forces the SDK call to reject on error responses; the - // hey-api runtime default is false, which would resolve undefined data (#172) - const queryFn = `() => ${op.methodName}({ ...clientOptions, throwOnError: true }).then(response => response.data)`; + const queryFn = `() => ${op.methodName}(${SDK_CALL_ARGS}).then(response => response.data)`; const body = `queryOptions({ queryKey: Common.Use${op.capitalizedMethodName}KeyFn(clientOptions, queryKey), queryFn: ${queryFn} })`; return { @@ -56,9 +57,9 @@ export function buildQueryOptionsFn( * export const findPaginatedPetsInfiniteOptions = (clientOptions: Common.FindPaginatedPetsInfiniteClientOptions = {}, queryKey?: Array) => * infiniteQueryOptions({ * queryKey: Common.UseFindPaginatedPetsInfiniteKeyFn(clientOptions, queryKey), - * queryFn: ({ pageParam }) => findPaginatedPets({ ...clientOptions, query: { ...clientOptions.query, page: pageParam } } as Options).then(response => response.data), + * queryFn: ({ pageParam }) => findPaginatedPets({ ...clientOptions, query: { ...clientOptions.query, page: pageParam as number }, throwOnError: true } as Options).then(response => response.data), * initialPageParam: 1, - * getNextPageParam: (response) => (response as { nextPage: number }).nextPage, + * getNextPageParam: (response: unknown) => (response as { nextPage: number }).nextPage, * }); */ export function buildInfiniteQueryOptionsFn( @@ -70,23 +71,11 @@ export function buildInfiniteQueryOptionsFn( } const fnName = `${op.methodName}InfiniteOptions`; - const dataTypeName = getDataTypeName(op, ctx); - const defaultValue = op.allParamsOptional ? " = {}" : ""; - const clientOptionsParam = `clientOptions: Common.${op.capitalizedMethodName}InfiniteClientOptions${defaultValue}`; + const queryFn = buildPagedQueryFn(op, ctx, false); + const infiniteOptions = `initialPageParam: ${formatInitialPageParam(ctx)}, getNextPageParam: ${buildGetNextPageParamExpr(ctx)}`; - const queryFn = `({ pageParam }) => ${op.methodName}({ ...clientOptions, query: { ...clientOptions.query, ${ctx.pageParam}: pageParam }, throwOnError: true } as Options<${dataTypeName}, true>).then(response => response.data)`; - - // Emit a numeric literal when possible so the inferred pageParam type - // matches what getNextPageParam returns - const initialPageParam = /^-?\d+$/.test(ctx.initialPageParam) - ? ctx.initialPageParam - : JSON.stringify(ctx.initialPageParam); - - const nestedType = buildNestedNextPageType(ctx.nextPageParam); - const getNextPageParam = `(response) => (response as ${nestedType}).${ctx.nextPageParam}`; - - const body = `infiniteQueryOptions({ queryKey: Common.Use${op.capitalizedMethodName}InfiniteKeyFn(clientOptions, queryKey), queryFn: ${queryFn}, initialPageParam: ${initialPageParam}, getNextPageParam: ${getNextPageParam} })`; + const body = `infiniteQueryOptions({ queryKey: Common.Use${op.capitalizedMethodName}InfiniteKeyFn(clientOptions, queryKey), queryFn: ${queryFn}, ${infiniteOptions} })`; return { kind: StructureKind.VariableStatement, @@ -97,7 +86,7 @@ export function buildInfiniteQueryOptionsFn( declarations: [ { name: fnName, - initializer: `(${clientOptionsParam}, queryKey?: Array) => ${body}`, + initializer: `(${buildInfiniteClientOptionsParam(op)}, queryKey?: Array) => ${body}`, }, ], }; diff --git a/src/tsmorph/generateFiles.mts b/src/tsmorph/generateFiles.mts index c4cee55..b94fe1a 100644 --- a/src/tsmorph/generateFiles.mts +++ b/src/tsmorph/generateFiles.mts @@ -1,8 +1,4 @@ -import { - type ExportDeclarationStructure, - type ImportDeclarationStructure, - StructureKind, -} from "ts-morph"; +import { type ImportDeclarationStructure, StructureKind } from "ts-morph"; import { OpenApiRqFiles } from "../constants.mjs"; import type { GeneratedFile, @@ -81,33 +77,10 @@ function buildHookFileImports( /** * Generate the index.ts file content. + * The content is constant, so no ts-morph project is needed. */ function generateIndexFile(): 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", - }, - { - kind: StructureKind.ExportDeclaration, - moduleSpecifier: "./queryOptions", - }, - ]; - - sourceFile.addExportDeclarations(exports); - - return sourceFile.getFullText(); + return `export * from "./common";\nexport * from "./queries";\nexport * from "./queryOptions";\n`; } /** @@ -264,7 +237,7 @@ function generateSuspenseFile( } // Add useSuspenseInfiniteQuery hooks for paginatable operations - for (const op of getOperations) { + for (const op of getOperations.filter((o) => o.isPaginatable)) { const hook = buildUseSuspenseInfiniteQueryHook(op, ctx); if (hook) { sourceFile.addVariableStatement(hook); @@ -333,7 +306,7 @@ function generatePrefetchFile( } // Add prefetchInfiniteQuery functions for paginatable operations - for (const op of getOperations) { + for (const op of getOperations.filter((o) => o.isPaginatable)) { const fn = buildPrefetchInfiniteQueryFn(op, ctx); if (fn) { sourceFile.addVariableStatement(fn); diff --git a/tests/__snapshots__/createSource.test.ts.snap b/tests/__snapshots__/createSource.test.ts.snap index 37c76e0..1d0c9ee 100644 --- a/tests/__snapshots__/createSource.test.ts.snap +++ b/tests/__snapshots__/createSource.test.ts.snap @@ -151,7 +151,7 @@ export const useFindPaginatedPetsSuspense = >, TError = AxiosError, TQueryKey extends Array = unknown[]>(clientOptions: Common.FindPaginatedPetsInfiniteClientOptions = {}, queryKey?: TQueryKey, options?: Omit, "queryKey" | "queryFn" | "initialPageParam" | "getNextPageParam"> & Partial, "initialPageParam" | "getNextPageParam">>) => useSuspenseInfiniteQuery({ queryKey: Common.UseFindPaginatedPetsInfiniteKeyFn(clientOptions, queryKey), queryFn: ({ pageParam }) => findPaginatedPets({ ...clientOptions, query: { ...clientOptions.query, page: pageParam as number }, throwOnError: true } as Options).then(response => response.data as TData) as TData, initialPageParam: "1", getNextPageParam: (response) => (response as { nextPage: number }).nextPage, ...options }); +export const useFindPaginatedPetsSuspenseInfinite = >, TError = AxiosError, TQueryKey extends Array = unknown[]>(clientOptions: Common.FindPaginatedPetsInfiniteClientOptions = {}, queryKey?: TQueryKey, options?: Omit, "queryKey" | "queryFn" | "initialPageParam" | "getNextPageParam"> & Partial, "initialPageParam" | "getNextPageParam">>) => useSuspenseInfiniteQuery({ queryKey: Common.UseFindPaginatedPetsInfiniteKeyFn(clientOptions, queryKey), queryFn: ({ pageParam }) => findPaginatedPets({ ...clientOptions, query: { ...clientOptions.query, page: pageParam as number }, throwOnError: true } as Options).then(response => response.data as TData) as TData, initialPageParam: 1, getNextPageParam: (response: unknown) => (response as { nextPage: number }).nextPage, ...options }); " `; @@ -192,7 +192,7 @@ export const prefetchUseFindPaginatedPets = (queryClient: QueryClient, clientOpt * Returns paginated pets from the system that the user has access to * */ -export const prefetchUseFindPaginatedPetsInfinite = (queryClient: QueryClient, clientOptions: Common.FindPaginatedPetsInfiniteClientOptions = {}) => queryClient.prefetchInfiniteQuery({ queryKey: Common.UseFindPaginatedPetsInfiniteKeyFn(clientOptions), queryFn: ({ pageParam }) => findPaginatedPets({ ...clientOptions, query: { ...clientOptions.query, page: pageParam }, throwOnError: true } as Options).then(response => response.data), initialPageParam: 1, getNextPageParam: (response: unknown) => (response as { nextPage: number }).nextPage }); +export const prefetchUseFindPaginatedPetsInfinite = (queryClient: QueryClient, clientOptions: Common.FindPaginatedPetsInfiniteClientOptions = {}, options?: Omit, "queryKey" | "queryFn" | "initialPageParam" | "getNextPageParam">) => queryClient.prefetchInfiniteQuery({ queryKey: Common.UseFindPaginatedPetsInfiniteKeyFn(clientOptions), queryFn: ({ pageParam }) => findPaginatedPets({ ...clientOptions, query: { ...clientOptions.query, page: pageParam as number }, throwOnError: true } as Options).then(response => response.data), initialPageParam: 1, getNextPageParam: (response: unknown) => (response as { nextPage: number }).nextPage, ...options }); " `; @@ -344,7 +344,7 @@ export const useFindPaginatedPetsSuspense = >, TError = unknown, TQueryKey extends Array = unknown[]>(clientOptions: Common.FindPaginatedPetsInfiniteClientOptions = {}, queryKey?: TQueryKey, options?: Omit, "queryKey" | "queryFn" | "initialPageParam" | "getNextPageParam"> & Partial, "initialPageParam" | "getNextPageParam">>) => useSuspenseInfiniteQuery({ queryKey: Common.UseFindPaginatedPetsInfiniteKeyFn(clientOptions, queryKey), queryFn: ({ pageParam }) => findPaginatedPets({ ...clientOptions, query: { ...clientOptions.query, page: pageParam as number }, throwOnError: true } as Options).then(response => response.data as TData) as TData, initialPageParam: "1", getNextPageParam: (response) => (response as { nextPage: number }).nextPage, ...options }); +export const useFindPaginatedPetsSuspenseInfinite = >, TError = unknown, TQueryKey extends Array = unknown[]>(clientOptions: Common.FindPaginatedPetsInfiniteClientOptions = {}, queryKey?: TQueryKey, options?: Omit, "queryKey" | "queryFn" | "initialPageParam" | "getNextPageParam"> & Partial, "initialPageParam" | "getNextPageParam">>) => useSuspenseInfiniteQuery({ queryKey: Common.UseFindPaginatedPetsInfiniteKeyFn(clientOptions, queryKey), queryFn: ({ pageParam }) => findPaginatedPets({ ...clientOptions, query: { ...clientOptions.query, page: pageParam as number }, throwOnError: true } as Options).then(response => response.data as TData) as TData, initialPageParam: 1, getNextPageParam: (response: unknown) => (response as { nextPage: number }).nextPage, ...options }); " `; @@ -384,6 +384,6 @@ export const prefetchUseFindPaginatedPets = (queryClient: QueryClient, clientOpt * Returns paginated pets from the system that the user has access to * */ -export const prefetchUseFindPaginatedPetsInfinite = (queryClient: QueryClient, clientOptions: Common.FindPaginatedPetsInfiniteClientOptions = {}) => queryClient.prefetchInfiniteQuery({ queryKey: Common.UseFindPaginatedPetsInfiniteKeyFn(clientOptions), queryFn: ({ pageParam }) => findPaginatedPets({ ...clientOptions, query: { ...clientOptions.query, page: pageParam }, throwOnError: true } as Options).then(response => response.data), initialPageParam: 1, getNextPageParam: (response: unknown) => (response as { nextPage: number }).nextPage }); +export const prefetchUseFindPaginatedPetsInfinite = (queryClient: QueryClient, clientOptions: Common.FindPaginatedPetsInfiniteClientOptions = {}, options?: Omit, "queryKey" | "queryFn" | "initialPageParam" | "getNextPageParam">) => queryClient.prefetchInfiniteQuery({ queryKey: Common.UseFindPaginatedPetsInfiniteKeyFn(clientOptions), queryFn: ({ pageParam }) => findPaginatedPets({ ...clientOptions, query: { ...clientOptions.query, page: pageParam as number }, throwOnError: true } as Options).then(response => response.data), initialPageParam: 1, getNextPageParam: (response: unknown) => (response as { nextPage: number }).nextPage, ...options }); " `; diff --git a/tests/__snapshots__/generate.test.ts.snap b/tests/__snapshots__/generate.test.ts.snap index f6dcbc9..b60f6a6 100644 --- a/tests/__snapshots__/generate.test.ts.snap +++ b/tests/__snapshots__/generate.test.ts.snap @@ -281,7 +281,7 @@ export const useFindPaginatedPetsInfinite = < (response) => response.data as TData, ) as TData, initialPageParam: "initial", - getNextPageParam: (response) => + getNextPageParam: (response: unknown) => (response as { meta: { next: number } }).meta.next, ...options, }); @@ -292,6 +292,7 @@ exports[`generate > prefetch.ts 1`] = ` "// generated with @7nohe/openapi-react-query-codegen@1.0.0 import type { + FetchInfiniteQueryOptions, FetchQueryOptions, QueryClient, } from "@tanstack/react-query"; @@ -400,13 +401,17 @@ export const prefetchUseFindPaginatedPets = ( export const prefetchUseFindPaginatedPetsInfinite = ( queryClient: QueryClient, clientOptions: Common.FindPaginatedPetsInfiniteClientOptions = {}, + options?: Omit< + FetchInfiniteQueryOptions, + "queryKey" | "queryFn" | "initialPageParam" | "getNextPageParam" + >, ) => queryClient.prefetchInfiniteQuery({ queryKey: Common.UseFindPaginatedPetsInfiniteKeyFn(clientOptions), queryFn: ({ pageParam }) => findPaginatedPets({ ...clientOptions, - query: { ...clientOptions.query, page: pageParam }, + query: { ...clientOptions.query, page: pageParam as number }, throwOnError: true, } as Options).then( (response) => response.data, @@ -414,6 +419,7 @@ export const prefetchUseFindPaginatedPetsInfinite = ( initialPageParam: "initial", getNextPageParam: (response: unknown) => (response as { meta: { next: number } }).meta.next, + ...options, }); " `; @@ -780,7 +786,7 @@ export const useFindPaginatedPetsSuspenseInfinite = < (response) => response.data as TData, ) as TData, initialPageParam: "initial", - getNextPageParam: (response) => + getNextPageParam: (response: unknown) => (response as { meta: { next: number } }).meta.next, ...options, }); diff --git a/tests/tsmorph/buildQueryHooks.test.ts b/tests/tsmorph/buildQueryHooks.test.ts index 07b8c40..21cfcb4 100644 --- a/tests/tsmorph/buildQueryHooks.test.ts +++ b/tests/tsmorph/buildQueryHooks.test.ts @@ -219,7 +219,7 @@ describe("buildQueryHooks", () => { expect(initializer).toContain("useInfiniteQuery"); expect(initializer).toContain("pageParam"); expect(initializer).toContain("getNextPageParam"); - expect(initializer).toContain('initialPageParam: "1"'); + expect(initializer).toContain("initialPageParam: 1"); }); it("should make initialPageParam and getNextPageParam optional overrides", () => { diff --git a/tests/tsmorph/buildQueryOptions.test.ts b/tests/tsmorph/buildQueryOptions.test.ts index 5cf2b2f..3adb0c6 100644 --- a/tests/tsmorph/buildQueryOptions.test.ts +++ b/tests/tsmorph/buildQueryOptions.test.ts @@ -124,10 +124,10 @@ describe("buildQueryOptions", () => { "infiniteQueryOptions({ queryKey: Common.UseFindPaginatedPetsInfiniteKeyFn(clientOptions, queryKey)", ); expect(initializer).toContain( - "query: { ...clientOptions.query, page: pageParam }, throwOnError: true } as Options", + "query: { ...clientOptions.query, page: pageParam as number }, throwOnError: true } as Options", ); expect(initializer).toContain( - "getNextPageParam: (response) => (response as { nextPage: number }).nextPage", + "getNextPageParam: (response: unknown) => (response as { nextPage: number }).nextPage", ); }); From 96fb0454b291dc9ec97c63ae63ec51912eb0286a Mon Sep 17 00:00:00 2001 From: Urata Daiki <7nohe@users.noreply.github.com> Date: Sat, 18 Jul 2026 23:09:33 +0900 Subject: [PATCH 18/31] docs: polish the v3 migration guide for release - Point the install command at ^3 instead of the beta tag - Call out the throwOnError behavior change in the TL;DR (the one change the compiler cannot catch) - Document the options parameter on prefetchUseXxxInfinite --- docs/src/content/docs/guides/migrating-to-v3.mdx | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/docs/src/content/docs/guides/migrating-to-v3.mdx b/docs/src/content/docs/guides/migrating-to-v3.mdx index 2f15122..f18fadf 100644 --- a/docs/src/content/docs/guides/migrating-to-v3.mdx +++ b/docs/src/content/docs/guides/migrating-to-v3.mdx @@ -10,12 +10,13 @@ v3 is a full rewrite of the code generator on top of a ts-morph pipeline, but th 1. Update the package and regenerate your client: ```bash - npm install -D @7nohe/openapi-react-query-codegen@beta + npm install -D @7nohe/openapi-react-query-codegen@^3 npx openapi-rq -i ./petstore.yaml ``` -2. If you don't use infinite queries, you are done. +2. Be aware of the one change the compiler cannot catch: [error responses now reject](#behavior-change-error-responses-now-reject) instead of silently resolving `undefined` data. If your UI already handles TanStack Query error states, nothing to do. 3. If you use infinite queries (`useXxxInfinite`), fix the type errors the compiler reports — see below. They are mechanical. +4. Everything else is compatible — you are done. ## What stays the same @@ -46,7 +47,7 @@ const { data } = useFindPaginatedPetsInfinite({ query: { limit: 10 } }, undefine The infinite query family is now complete ([#155](https://github.com/7nohe/openapi-react-query-codegen/issues/155)): -- `prefetchUseXxxInfinite(queryClient, clientOptions)` — prefetch the first page on the server for SSR/Next.js hydration +- `prefetchUseXxxInfinite(queryClient, clientOptions, options?)` — prefetch the first page (or more, via `options.pages`) on the server for SSR/Next.js hydration - `useXxxSuspenseInfinite(clientOptions, queryKey?, options?)` — Suspense variant sharing the same cache key as `useXxxInfinite` Prefetch and ensure functions also take TanStack Query options now ([#157](https://github.com/7nohe/openapi-react-query-codegen/issues/157)): From 975e25be9feeb613478c6db6e403ad65beaec078 Mon Sep 17 00:00:00 2001 From: Urata Daiki <7nohe@users.noreply.github.com> Date: Sat, 18 Jul 2026 23:14:15 +0900 Subject: [PATCH 19/31] test: verify generated code at runtime against a live HTTP server Exercises the generated queryOptions factories, ensureQueryData, prefetchInfiniteQuery, hierarchical key invalidation, and the throwOnError behavior (#172) with real requests instead of type checks only. Runs in CI after the example codegen step. --- examples/react-app/verify-runtime.test.ts | 111 ++++++++++++++++++++++ 1 file changed, 111 insertions(+) create mode 100644 examples/react-app/verify-runtime.test.ts diff --git a/examples/react-app/verify-runtime.test.ts b/examples/react-app/verify-runtime.test.ts new file mode 100644 index 0000000..9cbfb6b --- /dev/null +++ b/examples/react-app/verify-runtime.test.ts @@ -0,0 +1,111 @@ +// Runtime verification of the generated code against a live HTTP server. +// Runs in CI after `generate:api` — exercises #172 (error responses reject), +// #155 (prefetchInfiniteQuery), #174 (hierarchical key invalidation), and +// the queryOptions factories with real requests, not just type checks. +import http from "node:http"; +import type { AddressInfo } from "node:net"; +import { QueryClient } from "@tanstack/react-query"; +import { afterAll, beforeAll, expect, test } from "vitest"; +import { + UseFindPaginatedPetsInfiniteKeyFn, + useFindPaginatedPetsKey, +} from "./openapi/queries/common"; +import { + ensureUseFindPetByIdData, + ensureUseFindPetsData, +} from "./openapi/queries/ensureQueryData"; +import { prefetchUseFindPaginatedPetsInfinite } from "./openapi/queries/prefetch"; +import { + findPaginatedPetsInfiniteOptions, + findPetsOptions, +} from "./openapi/queries/queryOptions"; +import { client } from "./openapi/requests/client.gen"; + +const server = http.createServer((req, res) => { + const url = new URL(req.url ?? "/", "http://localhost"); + res.setHeader("content-type", "application/json"); + if (url.pathname === "/pets") { + res.end(JSON.stringify([{ id: 1, name: "Momo", tag: "cat" }])); + return; + } + if (url.pathname === "/paginated-pets") { + const page = Number(url.searchParams.get("page") ?? "1"); + res.end( + JSON.stringify({ + pets: [{ id: page, name: `pet-page-${page}` }], + nextPage: page + 1, + }), + ); + return; + } + if (url.pathname.startsWith("/pets/")) { + res.statusCode = 500; + res.end(JSON.stringify({ code: 500, message: "boom" })); + return; + } + res.statusCode = 404; + res.end("{}"); +}); + +const qc = new QueryClient({ + defaultOptions: { queries: { retry: false } }, +}); + +beforeAll(async () => { + await new Promise((resolve) => server.listen(0, resolve)); + const { port } = server.address() as AddressInfo; + // Deliberately NO throwOnError here — the generated code must set it per call + client.setConfig({ baseURL: `http://localhost:${port}` }); +}); + +afterAll(() => server.close()); + +test("queryOptions factory fetches real data via fetchQuery", async () => { + const pets = await qc.fetchQuery(findPetsOptions()); + expect(pets?.[0]?.name).toBe("Momo"); +}); + +test("ensureQueryData resolves data on success", async () => { + const pets = await ensureUseFindPetsData(qc); + expect(Array.isArray(pets)).toBe(true); +}); + +test("#172: error responses reject instead of resolving undefined", async () => { + await expect( + ensureUseFindPetByIdData(qc, { path: { id: 999 } }, { retry: false }), + ).rejects.toBeTruthy(); + // and nothing bogus was cached + expect(qc.getQueryData(["FindPetById", { path: { id: 999 } }])).toBe( + undefined, + ); +}); + +test("#155: prefetchInfiniteQuery caches page 1 under the hierarchical key with a numeric pageParam", async () => { + await prefetchUseFindPaginatedPetsInfinite(qc, { query: { limit: 1 } }); + const key = UseFindPaginatedPetsInfiniteKeyFn({ query: { limit: 1 } }); + expect(key.slice(0, 2)).toEqual(["FindPaginatedPets", "infinite"]); + const cached = qc.getQueryData<{ + pages: Array<{ pets?: Array<{ name?: string }> }>; + pageParams: unknown[]; + }>(key); + expect(cached?.pages).toHaveLength(1); + expect(cached?.pages[0]?.pets?.[0]?.name).toBe("pet-page-1"); + expect(cached?.pageParams[0]).toBe(1); +}); + +test("infiniteQueryOptions factory follows nextPage across two pages", async () => { + const result = await qc.fetchInfiniteQuery({ + ...findPaginatedPetsInfiniteOptions({ query: { limit: 2 } }), + pages: 2, + }); + expect(result.pages).toHaveLength(2); + expect(result.pages[1]?.pets?.[0]?.name).toBe("pet-page-2"); + expect(result.pageParams).toEqual([1, 2]); +}); + +test("#174: invalidating the plain key prefix marks infinite entries stale", async () => { + const key = UseFindPaginatedPetsInfiniteKeyFn({ query: { limit: 1 } }); + expect(qc.getQueryState(key)?.isInvalidated).toBe(false); + await qc.invalidateQueries({ queryKey: [useFindPaginatedPetsKey] }); + expect(qc.getQueryState(key)?.isInvalidated).toBe(true); +}); From 25798ca2d22f7095e066238dae879ee2e05d0074 Mon Sep 17 00:00:00 2001 From: Urata Daiki <7nohe@users.noreply.github.com> Date: Sat, 18 Jul 2026 23:36:43 +0900 Subject: [PATCH 20/31] chore: release v3.0.0-beta.3 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 648fee3..b3a0aee 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@7nohe/openapi-react-query-codegen", - "version": "3.0.0-beta.2", + "version": "3.0.0-beta.3", "description": "OpenAPI React Query Codegen", "bin": { "openapi-rq": "dist/cli.mjs" From 853d69d81af61bfbf8408a6ce1174112fec3fef7 Mon Sep 17 00:00:00 2001 From: Urata Daiki <7nohe@users.noreply.github.com> Date: Sat, 18 Jul 2026 23:41:15 +0900 Subject: [PATCH 21/31] docs(examples): showcase SSR infinite prefetch and drop obsolete throwOnError workaround MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - nextjs-app: prefetch the first infinite page on the server with prefetchUseFindPaginatedPetsInfinite + HydrationBoundary - react-app / react-router-6-app: remove the client-level throwOnError workaround — v3 generated code sets it per call (#172) --- .../app/components/PaginatedPets.tsx | 2 +- .../nextjs-app/app/infinite-loader/page.tsx | 20 ++++++++++++++++++- examples/react-app/src/axios.ts | 1 - examples/react-router-6-app/src/axios.ts | 1 - 4 files changed, 20 insertions(+), 4 deletions(-) diff --git a/examples/nextjs-app/app/components/PaginatedPets.tsx b/examples/nextjs-app/app/components/PaginatedPets.tsx index f235572..ad17e15 100644 --- a/examples/nextjs-app/app/components/PaginatedPets.tsx +++ b/examples/nextjs-app/app/components/PaginatedPets.tsx @@ -16,7 +16,7 @@ export default function PaginatedPets() { <>

Pet List with Pagination

    - {data?.pages.map((group, i) => ( + {data?.pages.map((group) => ( {group?.pets?.map((pet) => (
  • {pet.name}
  • diff --git a/examples/nextjs-app/app/infinite-loader/page.tsx b/examples/nextjs-app/app/infinite-loader/page.tsx index 60268fc..31deaf5 100644 --- a/examples/nextjs-app/app/infinite-loader/page.tsx +++ b/examples/nextjs-app/app/infinite-loader/page.tsx @@ -1,9 +1,27 @@ +import { + dehydrate, + HydrationBoundary, + QueryClient, +} from "@tanstack/react-query"; +import { prefetchUseFindPaginatedPetsInfinite } from "../../openapi/queries/prefetch"; import PaginatedPets from "../components/PaginatedPets"; export default async function InfiniteLoaderPage() { + const queryClient = new QueryClient(); + + // Prefetch the first page on the server; the client picks up the cache + // through HydrationBoundary and continues with fetchNextPage. The + // clientOptions must match the ones used by useFindPaginatedPetsInfinite + // so both sides resolve the same query key. + await prefetchUseFindPaginatedPetsInfinite(queryClient, { + query: { limit: 10, tags: [] }, + }); + return (
    - + + +
    ); } diff --git a/examples/react-app/src/axios.ts b/examples/react-app/src/axios.ts index 22aaac4..e767177 100644 --- a/examples/react-app/src/axios.ts +++ b/examples/react-app/src/axios.ts @@ -2,5 +2,4 @@ import { client } from "../openapi/requests/services.gen"; client.setConfig({ baseURL: "http://localhost:4010", - throwOnError: true, }); diff --git a/examples/react-router-6-app/src/axios.ts b/examples/react-router-6-app/src/axios.ts index 8c5b73f..99d80c9 100644 --- a/examples/react-router-6-app/src/axios.ts +++ b/examples/react-router-6-app/src/axios.ts @@ -2,5 +2,4 @@ import { client } from "../openapi/requests/client.gen"; client.setConfig({ baseUrl: "http://localhost:4010", - throwOnError: true, }); From fab6733b95abf42d06ea164154e49f18febcc7f2 Mon Sep 17 00:00:00 2001 From: Urata Daiki <7nohe@users.noreply.github.com> Date: Sun, 19 Jul 2026 17:21:32 +0900 Subject: [PATCH 22/31] fix: preserve blank initial page parameter `safeParseNumber` relied on `Number(value)`, and `Number("")` evaluates to 0. As a result `formatOptions` coerced `--initialPageParam ""` into a numeric 0, so the generated infinite queries sent `page=0` instead of the blank first page the API expected. Treat blank strings as NaN so `formatOptions` falls through to the passthrough branch and keeps the original value. All four infinite surfaces (hook, suspense hook, prefetch, queryOptions factory) now emit `initialPageParam: ""`. Closes #177 --- src/common.mts | 6 ++++++ tests/common.test.ts | 19 +++++++++++++++++++ 2 files changed, 25 insertions(+) diff --git a/src/common.mts b/src/common.mts index c7b78ce..75a94e3 100644 --- a/src/common.mts +++ b/src/common.mts @@ -78,6 +78,12 @@ export function BuildCommonTypeName(name: string | ts.Identifier) { * @returns The parsed number or NaN if the value is not a valid number. */ export function safeParseNumber(value: unknown): number { + // `Number("")` is 0, which would silently turn a blank option such as + // `--initialPageParam ""` into a numeric 0. Treat blank strings as NaN so + // callers keep the original value. + if (typeof value === "string" && value.trim() === "") { + return Number.NaN; + } const parsed = Number(value); if (!Number.isNaN(parsed) && Number.isFinite(parsed)) { return parsed; diff --git a/tests/common.test.ts b/tests/common.test.ts index 613a7b0..5734a64 100644 --- a/tests/common.test.ts +++ b/tests/common.test.ts @@ -37,6 +37,12 @@ describe("common", () => { const falseVal = safeParseNumber(false); expect(falseVal).toBe(0); + + const emptyString = safeParseNumber(""); + expect(emptyString).toBeNaN(); + + const blankString = safeParseNumber(" "); + expect(blankString).toBeNaN(); }); test("capitalizeFirstLetter", () => { @@ -166,6 +172,19 @@ describe("common", () => { expect(formatted.debug).toStrictEqual(123); }); + test("formatOptions - preserves a blank initial page parameter", () => { + const options: LimitedUserConfig = { + input: "input", + output: "output", + pageParam: "page", + nextPageParam: "nextPage", + initialPageParam: "", + }; + const formatted = formatOptions(options); + + expect(formatted.initialPageParam).toBe(""); + }); + test("formatOptions - leaves other values unchanged", () => { const options: LimitedUserConfig = { input: "input", From dc2720d64d2104b5ab0891f71fd50af9788b819e Mon Sep 17 00:00:00 2001 From: Urata Daiki <7nohe@users.noreply.github.com> Date: Sun, 19 Jul 2026 17:50:03 +0900 Subject: [PATCH 23/31] chore: drop unused StructureKind import `generateFiles.mts` imported `StructureKind` but never referenced it, so Biome reported a noUnusedImports warning. The file only needs the `ImportDeclarationStructure` type, so collapse the statement into a plain type-only import. --- src/tsmorph/generateFiles.mts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/tsmorph/generateFiles.mts b/src/tsmorph/generateFiles.mts index b94fe1a..a0fbfab 100644 --- a/src/tsmorph/generateFiles.mts +++ b/src/tsmorph/generateFiles.mts @@ -1,4 +1,4 @@ -import { type ImportDeclarationStructure, StructureKind } from "ts-morph"; +import type { ImportDeclarationStructure } from "ts-morph"; import { OpenApiRqFiles } from "../constants.mjs"; import type { GeneratedFile, From f6014ebe2a8bbaa382044b001cfdffcbd2c38280 Mon Sep 17 00:00:00 2001 From: Urata Daiki <7nohe@users.noreply.github.com> Date: Sun, 19 Jul 2026 18:05:21 +0900 Subject: [PATCH 24/31] feat: add --omitInitialPageParam to send no initial page param Some APIs expect no page parameter (or an empty one) on the first request of a paginated endpoint. Previously the only knob was --initialPageParam, which always emits a concrete value. With --omitInitialPageParam the generated infinite queries emit `initialPageParam: undefined` and spread the page param into the request only once TanStack Query supplies one, so the first request carries no page param at all. The flag overrides --initialPageParam. Verified end-to-end: the generated output typechecks against the example app (TanStack Query infers TPageParam as `number | undefined`, so `initialPageParam: undefined` is valid) across all four infinite surfaces (hook, suspense, prefetch, queryOptions factory). Refs #177 --- docs/src/content/docs/guides/cli-options.mdx | 4 +++ src/cli.mts | 5 ++++ src/createSource.mts | 3 +++ src/generate.mts | 1 + src/parseOperations.mts | 2 ++ src/tsmorph/buildQueryHooks.mts | 15 ++++++++--- src/types.mts | 2 ++ tests/createSource.test.ts | 28 ++++++++++++++++++++ tests/parseOperations.test.ts | 5 ++++ 9 files changed, 62 insertions(+), 3 deletions(-) diff --git a/docs/src/content/docs/guides/cli-options.mdx b/docs/src/content/docs/guides/cli-options.mdx index 2646eb0..60f9443 100644 --- a/docs/src/content/docs/guides/cli-options.mdx +++ b/docs/src/content/docs/guides/cli-options.mdx @@ -25,6 +25,10 @@ Name of the response parameter used for next page. The default value is `nextPag Initial page value to infinite query. The default value is `1`. +### --omitInitialPageParam + +Send no initial page parameter at all. When set, the generated infinite queries use `initialPageParam: undefined` and omit the page parameter from the first request entirely, so the initial page is fetched without it. Useful for APIs that expect no page parameter (or an empty one) on the first request. This overrides `--initialPageParam`. + ## Client Options The generated clients (under the `openapi/requests` directory) are produced by `@hey-api/openapi-ts` using its plugins system. The CLI options below are mapped to the corresponding plugin configurations. diff --git a/src/cli.mts b/src/cli.mts index 461d70f..488a19d 100644 --- a/src/cli.mts +++ b/src/cli.mts @@ -23,6 +23,7 @@ export type LimitedUserConfig = { pageParam: string; nextPageParam: string; initialPageParam: string | number; + omitInitialPageParam?: boolean; }; async function setupProgram() { @@ -91,6 +92,10 @@ async function setupProgram() { "nextPage", ) .option("--initialPageParam ", "Initial page value to query", "1") + .option( + "--omitInitialPageParam", + "Send no initial page parameter at all (overrides --initialPageParam)", + ) .parse(); const options = program.opts(); diff --git a/src/createSource.mts b/src/createSource.mts index 3a24f2b..fe88e79 100644 --- a/src/createSource.mts +++ b/src/createSource.mts @@ -16,6 +16,7 @@ export const createSource = async ({ pageParam, nextPageParam, initialPageParam, + omitInitialPageParam, }: { outputPath: string; client: ClientType; @@ -23,6 +24,7 @@ export const createSource = async ({ pageParam: string; nextPageParam: string; initialPageParam: string; + omitInitialPageParam: boolean; }): Promise => { // Initialize ts-morph project to read the generated OpenAPI client const project = new Project({ @@ -42,6 +44,7 @@ export const createSource = async ({ pageParam, nextPageParam, initialPageParam, + omitInitialPageParam, version, ); diff --git a/src/generate.mts b/src/generate.mts index 594142e..79af325 100644 --- a/src/generate.mts +++ b/src/generate.mts @@ -78,6 +78,7 @@ export async function generate(options: LimitedUserConfig, version: string) { pageParam: formattedOptions.pageParam, nextPageParam: formattedOptions.nextPageParam, initialPageParam: formattedOptions.initialPageParam.toString(), + omitInitialPageParam: formattedOptions.omitInitialPageParam ?? false, }); await print(source, formattedOptions); const queriesOutputPath = buildQueriesOutputPath(options.output); diff --git a/src/parseOperations.mts b/src/parseOperations.mts index 1be20f8..f5bed99 100644 --- a/src/parseOperations.mts +++ b/src/parseOperations.mts @@ -140,6 +140,7 @@ export function buildGenerationContext( pageParam: string, nextPageParam: string, initialPageParam: string, + omitInitialPageParam: boolean, version: string, ): GenerationContext { const modelsFile = project @@ -167,6 +168,7 @@ export function buildGenerationContext( pageParam, nextPageParam, initialPageParam, + omitInitialPageParam, version, }; } diff --git a/src/tsmorph/buildQueryHooks.mts b/src/tsmorph/buildQueryHooks.mts index bc32387..738e179 100644 --- a/src/tsmorph/buildQueryHooks.mts +++ b/src/tsmorph/buildQueryHooks.mts @@ -81,14 +81,23 @@ export function buildPagedQueryFn( const thenClause = castTData ? ".then(response => response.data as TData) as TData" : ".then(response => response.data)"; - return `({ pageParam }) => ${op.methodName}({ ...clientOptions, query: { ...clientOptions.query, ${ctx.pageParam}: pageParam as number }, throwOnError: true } as Options<${dataTypeName}, true>)${thenClause}`; + // When the initial page param is omitted, the first request must send no + // page param at all, so spread it in only once TanStack Query provides one. + const pageQuery = ctx.omitInitialPageParam + ? `...(pageParam === undefined ? {} : { ${ctx.pageParam}: pageParam as number })` + : `${ctx.pageParam}: pageParam as number`; + return `({ pageParam }) => ${op.methodName}({ ...clientOptions, query: { ...clientOptions.query, ${pageQuery} }, throwOnError: true } as Options<${dataTypeName}, true>)${thenClause}`; } /** - * Format the initialPageParam literal. Emits a numeric literal when possible - * so the inferred pageParam type matches what getNextPageParam returns. + * Format the initialPageParam literal. Emits `undefined` when the caller opted + * to omit it (#177); otherwise a numeric literal when possible so the inferred + * pageParam type matches what getNextPageParam returns. */ export function formatInitialPageParam(ctx: GenerationContext): string { + if (ctx.omitInitialPageParam) { + return "undefined"; + } return /^-?\d+$/.test(ctx.initialPageParam) ? ctx.initialPageParam : JSON.stringify(ctx.initialPageParam); diff --git a/src/types.mts b/src/types.mts index ec13a15..0af5dc0 100644 --- a/src/types.mts +++ b/src/types.mts @@ -47,6 +47,8 @@ export interface GenerationContext { nextPageParam: string; /** Initial page param value for infinite queries */ initialPageParam: string; + /** Omit the initial page param entirely (sends no page param on the first request) */ + omitInitialPageParam: boolean; /** Package version for generated comment */ version: string; } diff --git a/tests/createSource.test.ts b/tests/createSource.test.ts index d6e3852..5b0e022 100644 --- a/tests/createSource.test.ts +++ b/tests/createSource.test.ts @@ -15,6 +15,7 @@ describe(fileName, () => { pageParam: "page", nextPageParam: "nextPage", initialPageParam: "1", + omitInitialPageParam: false, client: "@hey-api/client-fetch", }); @@ -53,6 +54,7 @@ describe(fileName, () => { pageParam: "page", nextPageParam: "nextPage", initialPageParam: "1", + omitInitialPageParam: false, client: "@hey-api/client-axios", }); @@ -71,4 +73,30 @@ describe(fileName, () => { const prefetchTs = source.find((s) => s.name === "prefetch.ts"); expect(prefetchTs?.content).toMatchSnapshot(); }); + + test("createSource - omitInitialPageParam", async () => { + const source = await createSource({ + outputPath: outputPath(fileName), + version: "1.0.0", + pageParam: "page", + nextPageParam: "nextPage", + initialPageParam: "1", + omitInitialPageParam: true, + client: "@hey-api/client-fetch", + }); + + const infiniteQueriesTs = source.find( + (s) => s.name === "infiniteQueries.ts", + ); + + // The initial page param is emitted as `undefined` instead of a literal. + expect(infiniteQueriesTs?.content).toContain("initialPageParam: undefined"); + expect(infiniteQueriesTs?.content).not.toContain("initialPageParam: 1"); + + // The page param is spread in only once TanStack Query supplies one, so the + // first request sends no page param at all (#177). + expect(infiniteQueriesTs?.content).toContain( + "...(pageParam === undefined ? {} : { page: pageParam as number })", + ); + }); }); diff --git a/tests/parseOperations.test.ts b/tests/parseOperations.test.ts index a0888e1..2d4518e 100644 --- a/tests/parseOperations.test.ts +++ b/tests/parseOperations.test.ts @@ -109,6 +109,7 @@ describe("parseOperations", () => { "page", "nextPage", "1", + false, "1.0.0", ); @@ -116,6 +117,7 @@ describe("parseOperations", () => { expect(ctx.pageParam).toBe("page"); expect(ctx.nextPageParam).toBe("nextPage"); expect(ctx.initialPageParam).toBe("1"); + expect(ctx.omitInitialPageParam).toBe(false); expect(ctx.version).toBe("1.0.0"); expect(ctx.serviceNames.length).toBeGreaterThan(0); expect(ctx.modelNames.length).toBeGreaterThan(0); @@ -131,6 +133,7 @@ describe("parseOperations", () => { "offset", "next", "0", + false, "2.0.0", ); @@ -149,6 +152,7 @@ describe("parseOperations", () => { "page", "nextPage", "1", + false, "1.0.0", ); @@ -168,6 +172,7 @@ describe("parseOperations", () => { "page", "nextPage", "1", + false, "1.0.0", ); From 09d3a33633691a7b7ac121c0ab6ceefe53cb525d Mon Sep 17 00:00:00 2001 From: Urata Daiki <7nohe@users.noreply.github.com> Date: Sun, 19 Jul 2026 18:11:53 +0900 Subject: [PATCH 25/31] chore: release v3.0.0-beta.4 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index b3a0aee..4dde131 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@7nohe/openapi-react-query-codegen", - "version": "3.0.0-beta.3", + "version": "3.0.0-beta.4", "description": "OpenAPI React Query Codegen", "bin": { "openapi-rq": "dist/cli.mjs" From e65134d45d2a438cda765e4d7c17cba948e80c6f Mon Sep 17 00:00:00 2001 From: Urata Daiki <7nohe@users.noreply.github.com> Date: Sun, 19 Jul 2026 18:44:32 +0900 Subject: [PATCH 26/31] fix(examples): correct client config key and error typing in react-app MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The react-app example targets the hey-api fetch client, whose Config expects `baseUrl` (not axios-style `baseURL`) and which throws the OpenAPI `Error` schema body (`{ code, message }`) rather than an axios error with a `.response` property. - src/axios.ts, verify-runtime.test.ts: pass `baseUrl` to `client.setConfig` — the `baseURL` typo silently left the client unconfigured, so runtime tests hit the default URL instead of the in-process test server - src/App.tsx: read `error.message` directly instead of the nonexistent `error.response?.data.message` `npx tsc -p ./tsconfig.json --noEmit` in examples/react-app is now clean and verify-runtime.test.ts passes 6/6 locally. --- examples/react-app/src/App.tsx | 4 ++-- examples/react-app/src/axios.ts | 2 +- examples/react-app/verify-runtime.test.ts | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/examples/react-app/src/App.tsx b/examples/react-app/src/App.tsx index 464d39d..7f981fb 100644 --- a/examples/react-app/src/App.tsx +++ b/examples/react-app/src/App.tsx @@ -65,8 +65,8 @@ function App() { console.log("success"); }, onError: (error) => { - console.log(error.response); - setErrorText(`Error: ${error.response?.data.message}`); + console.log(error); + setErrorText(`Error: ${error.message}`); }, }, ); diff --git a/examples/react-app/src/axios.ts b/examples/react-app/src/axios.ts index e767177..7497a3b 100644 --- a/examples/react-app/src/axios.ts +++ b/examples/react-app/src/axios.ts @@ -1,5 +1,5 @@ import { client } from "../openapi/requests/services.gen"; client.setConfig({ - baseURL: "http://localhost:4010", + baseUrl: "http://localhost:4010", }); diff --git a/examples/react-app/verify-runtime.test.ts b/examples/react-app/verify-runtime.test.ts index 9cbfb6b..7cfde71 100644 --- a/examples/react-app/verify-runtime.test.ts +++ b/examples/react-app/verify-runtime.test.ts @@ -55,7 +55,7 @@ beforeAll(async () => { await new Promise((resolve) => server.listen(0, resolve)); const { port } = server.address() as AddressInfo; // Deliberately NO throwOnError here — the generated code must set it per call - client.setConfig({ baseURL: `http://localhost:${port}` }); + client.setConfig({ baseUrl: `http://localhost:${port}` }); }); afterAll(() => server.close()); From 5362ab5dc27a15afb4be976f23696875c8dc71ef Mon Sep 17 00:00:00 2001 From: Urata Daiki <7nohe@users.noreply.github.com> Date: Sun, 19 Jul 2026 20:10:30 +0900 Subject: [PATCH 27/31] Revert "fix(examples): correct client config key and error typing in react-app" This reverts commit e65134d45d2a438cda765e4d7c17cba948e80c6f. --- examples/react-app/src/App.tsx | 4 ++-- examples/react-app/src/axios.ts | 2 +- examples/react-app/verify-runtime.test.ts | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/examples/react-app/src/App.tsx b/examples/react-app/src/App.tsx index 7f981fb..464d39d 100644 --- a/examples/react-app/src/App.tsx +++ b/examples/react-app/src/App.tsx @@ -65,8 +65,8 @@ function App() { console.log("success"); }, onError: (error) => { - console.log(error); - setErrorText(`Error: ${error.message}`); + console.log(error.response); + setErrorText(`Error: ${error.response?.data.message}`); }, }, ); diff --git a/examples/react-app/src/axios.ts b/examples/react-app/src/axios.ts index 7497a3b..e767177 100644 --- a/examples/react-app/src/axios.ts +++ b/examples/react-app/src/axios.ts @@ -1,5 +1,5 @@ import { client } from "../openapi/requests/services.gen"; client.setConfig({ - baseUrl: "http://localhost:4010", + baseURL: "http://localhost:4010", }); diff --git a/examples/react-app/verify-runtime.test.ts b/examples/react-app/verify-runtime.test.ts index 7cfde71..9cbfb6b 100644 --- a/examples/react-app/verify-runtime.test.ts +++ b/examples/react-app/verify-runtime.test.ts @@ -55,7 +55,7 @@ beforeAll(async () => { await new Promise((resolve) => server.listen(0, resolve)); const { port } = server.address() as AddressInfo; // Deliberately NO throwOnError here — the generated code must set it per call - client.setConfig({ baseUrl: `http://localhost:${port}` }); + client.setConfig({ baseURL: `http://localhost:${port}` }); }); afterAll(() => server.close()); From 058c6d06c9a4d829c8a8103b64e2640f90fdcbe9 Mon Sep 17 00:00:00 2001 From: Urata Daiki <7nohe@users.noreply.github.com> Date: Mon, 20 Jul 2026 10:57:36 +0900 Subject: [PATCH 28/31] fix: emit package declaration files exports.types in package.json points to dist/generate.d.mts, but tsconfig.json was missing declaration: true, so the build never produced the .d.mts file. TypeScript consumers hit TS7016. --- tsconfig.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tsconfig.json b/tsconfig.json index 5e2dd8c..bfe4d47 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -11,7 +11,8 @@ "outDir": "dist", "lib": ["ESNext", "DOM"], "target": "ESNext", - "rootDir": "src" + "rootDir": "src", + "declaration": true }, "include": ["src"] } From 08f76f286cf82adf59391a1e70b0c53f2e696ce4 Mon Sep 17 00:00:00 2001 From: Urata Daiki <7nohe@users.noreply.github.com> Date: Mon, 20 Jul 2026 16:58:18 +0900 Subject: [PATCH 29/31] fix: improve generated request handling --- docs/src/content/docs/guides/cli-options.mdx | 6 +- docs/src/content/docs/guides/usage.mdx | 34 +++ src/cli.mts | 2 +- src/generate.mts | 19 +- src/parseOperations.mts | 62 +++++- src/tsmorph/buildQueryHooks.mts | 58 ++++-- src/tsmorph/buildQueryOptions.mts | 6 +- src/types.mts | 6 + tests/__snapshots__/createSource.test.ts.snap | 56 ++--- tests/__snapshots__/generate.test.ts.snap | 197 ++++++++++++++---- tests/generate.test.ts | 57 +++++ tests/inputs/dates.yaml | 27 +++ tests/inputs/string-pagination.yaml | 32 +++ tests/parseOperations.test.ts | 27 ++- tests/tsmorph/buildMutationHooks.test.ts | 17 ++ tests/tsmorph/buildQueryHooks.test.ts | 40 +++- tests/tsmorph/buildQueryOptions.test.ts | 26 ++- 17 files changed, 557 insertions(+), 115 deletions(-) create mode 100644 tests/inputs/dates.yaml create mode 100644 tests/inputs/string-pagination.yaml diff --git a/docs/src/content/docs/guides/cli-options.mdx b/docs/src/content/docs/guides/cli-options.mdx index 60f9443..42380d4 100644 --- a/docs/src/content/docs/guides/cli-options.mdx +++ b/docs/src/content/docs/guides/cli-options.mdx @@ -77,9 +77,9 @@ The available options are: ### --useDateType -> **Deprecated:** This option is currently accepted but has no effect. It will be removed in a future version. - -Use Date type instead of string for date. The default value is `false`. +Use `Date` instead of `string` for `date` and `date-time` model properties. +Generated SDK transformers also convert matching response values into `Date` +objects at runtime. The default value is `false`. ### --debug diff --git a/docs/src/content/docs/guides/usage.mdx b/docs/src/content/docs/guides/usage.mdx index f043bb3..b296c91 100644 --- a/docs/src/content/docs/guides/usage.mdx +++ b/docs/src/content/docs/guides/usage.mdx @@ -103,8 +103,42 @@ function App() { export default App; ``` +Generated query functions forward TanStack Query's `AbortSignal` to the HTTP +client. Requests are therefore cancelled when a query becomes stale or when +you call `queryClient.cancelQueries`. + +Mutations can be cancelled explicitly by passing a signal in the generated +client options: + +```tsx +const controller = new AbortController(); +const { mutate } = useAddPet(); + +mutate({ + body: { name: "Fluffy" }, + signal: controller.signal, +}); + +controller.abort(); +``` + Invalidating queries after a mutation is important to ensure the cache is updated with the new data. This is done by calling the `queryClient.invalidateQueries` function with the query key used by the query hook. +Mutation results contain the complete SDK response, not only the response +body. This makes response headers available in `onSuccess` or from +`mutateAsync`: + +```tsx +const { mutateAsync } = useDownloadReport(); +const result = await mutateAsync({ body: { reportId } }); + +// @hey-api/client-fetch +const disposition = result.response.headers.get("content-disposition"); + +// @hey-api/client-axios +const axiosDisposition = result.headers["content-disposition"]; +``` + Learn more about invalidating queries [here](https://tanstack.com/query/latest/docs/framework/react/guides/query-invalidation). To ensure the query key is created the same way as the query hook, you can use the query key function exported by the generated query hooks. diff --git a/src/cli.mts b/src/cli.mts index 488a19d..dec3edd 100644 --- a/src/cli.mts +++ b/src/cli.mts @@ -71,7 +71,7 @@ async function setupProgram() { ) .option( "--useDateType", - "Use Date type instead of string for date types for models, this will not convert the data to a Date object", + "Use Date for date/date-time model properties and convert response values to Date objects", ) .option("--debug", "Run in debug mode?") .option("--noSchemas", "Disable generating JSON schemas") diff --git a/src/generate.mts b/src/generate.mts index 79af325..1a781c2 100644 --- a/src/generate.mts +++ b/src/generate.mts @@ -29,16 +29,20 @@ export async function generate(options: LimitedUserConfig, version: string) { } : "@hey-api/typescript"; - const sdkPlugin: NonNullable[number] = - formattedOptions.noOperationId + const sdkPlugin: NonNullable[number] = { + name: "@hey-api/sdk" as const, + ...(formattedOptions.noOperationId ? { - name: "@hey-api/sdk" as const, // `operationId: false` was deprecated in favor of `operations.nesting` operations: { nesting: "id" as const, }, } - : "@hey-api/sdk"; + : {}), + ...(formattedOptions.useDateType + ? { transformer: "@hey-api/transformers" as const } + : {}), + }; const plugins: NonNullable[number][] = [ clientPlugin, @@ -46,6 +50,13 @@ export async function generate(options: LimitedUserConfig, version: string) { sdkPlugin, ]; + if (formattedOptions.useDateType) { + plugins.push({ + name: "@hey-api/transformers", + dates: "date", + }); + } + // Conditionally add schemas plugin if (!formattedOptions.noSchemas) { plugins.push( diff --git a/src/parseOperations.mts b/src/parseOperations.mts index f5bed99..e5d8971 100644 --- a/src/parseOperations.mts +++ b/src/parseOperations.mts @@ -13,8 +13,36 @@ import type { GenerationContext, OperationInfo, OperationParameter, + PageParamTypeKind, } from "./types.mjs"; +type PageParamInfo = { + type: string; + typeKind: PageParamTypeKind; +}; + +function getPageParamTypeKind(type: ts.Type): PageParamTypeKind { + const types = type.isUnion() + ? type.types.filter( + (item) => !(item.flags & (ts.TypeFlags.Null | ts.TypeFlags.Undefined)), + ) + : [type]; + + if ( + types.length > 0 && + types.every((item) => item.flags & ts.TypeFlags.StringLike) + ) { + return "string"; + } + if ( + types.length > 0 && + types.every((item) => item.flags & ts.TypeFlags.NumberLike) + ) { + return "number"; + } + return "other"; +} + /** * Extract parameter information from a method's variable declaration. */ @@ -43,14 +71,18 @@ function extractParameters( * 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[] { +function getPaginatableMethods( + project: Project, + pageParam: string, +): Map { const modelsFile = project .getSourceFiles() .find((sf) => sf.getFilePath().includes(modelsFileName)); - if (!modelsFile) return []; + if (!modelsFile) return new Map(); - const paginatableMethods: string[] = []; + const paginatableMethods = new Map(); + const typeChecker = project.getTypeChecker().compilerObject; const modelDeclarations = modelsFile.getExportedDeclarations(); const entries = modelDeclarations.entries(); @@ -77,17 +109,27 @@ function getPaginatableMethods(project: Project, pageParam: string): string[] { 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, + const pageParamNode = (queryType as ts.TypeLiteralNode).members.find( + (m): m is ts.PropertySignature => + ts.isPropertySignature(m) && m.name?.getText() === pageParam, ); - if (hasPageParam) { + if (pageParamNode) { // 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); + const pageParamType = pageParamNode.type?.getText( + modelsFile.compilerNode, + ); + const resolvedType = typeChecker.getTypeAtLocation( + pageParamNode.type ?? pageParamNode, + ); + paginatableMethods.set(methodNameLower, { + type: pageParamType ?? "unknown", + typeKind: getPageParamTypeKind(resolvedType), + }); } } @@ -115,8 +157,8 @@ export async function parseOperations( const sdkParams = getVariableArrowFunctionParameters(desc.method); const allParamsOptional = sdkParams.length === 0 || sdkParams[0].isOptional(); - const isPaginatable = - httpMethod === "GET" && paginatableMethods.includes(methodName); + const pageParamInfo = paginatableMethods.get(methodName); + const isPaginatable = httpMethod === "GET" && pageParamInfo !== undefined; return { methodName, @@ -127,6 +169,8 @@ export async function parseOperations( parameters, allParamsOptional, isPaginatable, + pageParamType: isPaginatable ? pageParamInfo.type : undefined, + pageParamTypeKind: isPaginatable ? pageParamInfo.typeKind : undefined, }; }); } diff --git a/src/tsmorph/buildQueryHooks.mts b/src/tsmorph/buildQueryHooks.mts index 738e179..7095a53 100644 --- a/src/tsmorph/buildQueryHooks.mts +++ b/src/tsmorph/buildQueryHooks.mts @@ -41,6 +41,15 @@ export function getDataTypeName( */ export const SDK_CALL_ARGS = "{ ...clientOptions, throwOnError: true }"; +/** SDK call arguments for TanStack query functions with cancellation. */ +export const QUERY_SDK_CALL_ARGS = + "{ ...clientOptions, signal, throwOnError: true }"; + +/** Resolve the OpenAPI page parameter type, preserving older numeric output. */ +export function getPageParamType(op: OperationInfo): string { + return op.pageParamType ?? "number"; +} + /** * Build the client options parameter string. */ @@ -81,12 +90,13 @@ export function buildPagedQueryFn( const thenClause = castTData ? ".then(response => response.data as TData) as TData" : ".then(response => response.data)"; + const pageParamType = getPageParamType(op); // When the initial page param is omitted, the first request must send no // page param at all, so spread it in only once TanStack Query provides one. const pageQuery = ctx.omitInitialPageParam - ? `...(pageParam === undefined ? {} : { ${ctx.pageParam}: pageParam as number })` - : `${ctx.pageParam}: pageParam as number`; - return `({ pageParam }) => ${op.methodName}({ ...clientOptions, query: { ...clientOptions.query, ${pageQuery} }, throwOnError: true } as Options<${dataTypeName}, true>)${thenClause}`; + ? `...(pageParam === undefined ? {} : { ${ctx.pageParam}: pageParam as ${pageParamType} })` + : `${ctx.pageParam}: pageParam as ${pageParamType}`; + return `({ pageParam, signal }) => ${op.methodName}({ ...clientOptions, query: { ...clientOptions.query, ${pageQuery} }, signal, throwOnError: true } as Options<${dataTypeName}, true>)${thenClause}`; } /** @@ -94,10 +104,21 @@ export function buildPagedQueryFn( * to omit it (#177); otherwise a numeric literal when possible so the inferred * pageParam type matches what getNextPageParam returns. */ -export function formatInitialPageParam(ctx: GenerationContext): string { +export function formatInitialPageParam( + ctx: GenerationContext, + op?: OperationInfo, +): string { if (ctx.omitInitialPageParam) { return "undefined"; } + const isStringPageParam = op + ? op.pageParamTypeKind === "string" || + (op.pageParamTypeKind === undefined && + /\bstring\b/.test(getPageParamType(op))) + : false; + if (isStringPageParam) { + return JSON.stringify(ctx.initialPageParam); + } return /^-?\d+$/.test(ctx.initialPageParam) ? ctx.initialPageParam : JSON.stringify(ctx.initialPageParam); @@ -107,11 +128,14 @@ export function formatInitialPageParam(ctx: GenerationContext): string { * Build the nested type for getNextPageParam. * E.g., "meta.next" becomes "{ meta: { next: number } }" */ -export function buildNestedNextPageType(nextPageParam: string): string { +export function buildNestedNextPageType( + nextPageParam: string, + pageParamType = "number", +): string { const segments = nextPageParam.split("."); return segments.reduceRight((acc, segment) => { return `{ ${segment}: ${acc} }`; - }, "number"); + }, pageParamType); } /** @@ -119,8 +143,14 @@ export function buildNestedNextPageType(nextPageParam: string): string { * not every TanStack entry point contextually types it (prefetchInfiniteQuery * does not, which would fail noImplicitAny). */ -export function buildGetNextPageParamExpr(ctx: GenerationContext): string { - const nestedType = buildNestedNextPageType(ctx.nextPageParam); +export function buildGetNextPageParamExpr( + ctx: GenerationContext, + op?: OperationInfo, +): string { + const nestedType = buildNestedNextPageType( + ctx.nextPageParam, + op ? getPageParamType(op) : "number", + ); return `(response: unknown) => (response as ${nestedType}).${ctx.nextPageParam}`; } @@ -158,7 +188,7 @@ export function buildUseQueryHook( const dataTypeDefault = `Common.${op.capitalizedMethodName}DefaultResponse`; const clientOptionsParam = buildClientOptionsParam(op, ctx); - const queryFn = `() => ${op.methodName}(${SDK_CALL_ARGS}).then(response => response.data as TData) as TData`; + const queryFn = `({ signal }) => ${op.methodName}(${QUERY_SDK_CALL_ARGS}).then(response => response.data as TData) as TData`; const body = `useQuery({ queryKey: Common.Use${op.capitalizedMethodName}KeyFn(clientOptions, queryKey), queryFn: ${queryFn}, ...options })`; @@ -189,7 +219,7 @@ export function buildUseSuspenseQueryHook( const dataTypeDefault = `NonNullable`; const clientOptionsParam = buildClientOptionsParam(op, ctx); - const queryFn = `() => ${op.methodName}(${SDK_CALL_ARGS}).then(response => response.data as TData) as TData`; + const queryFn = `({ signal }) => ${op.methodName}(${QUERY_SDK_CALL_ARGS}).then(response => response.data as TData) as TData`; const body = `useSuspenseQuery({ queryKey: Common.Use${op.capitalizedMethodName}KeyFn(clientOptions, queryKey), queryFn: ${queryFn}, ...options })`; @@ -238,7 +268,7 @@ function buildInfiniteHook( : `InfiniteData<${baseDataType}>`; const queryFn = buildPagedQueryFn(op, ctx, true); - const infiniteOptions = `initialPageParam: ${formatInitialPageParam(ctx)}, getNextPageParam: ${buildGetNextPageParamExpr(ctx)}`; + const infiniteOptions = `initialPageParam: ${formatInitialPageParam(ctx, op)}, getNextPageParam: ${buildGetNextPageParamExpr(ctx, op)}`; const body = `${hookCall}({ queryKey: Common.Use${op.capitalizedMethodName}InfiniteKeyFn(clientOptions, queryKey), queryFn: ${queryFn}, ${infiniteOptions}, ...options })`; @@ -293,7 +323,7 @@ export function buildPrefetchFn( ): VariableStatementStructure { const fnName = `prefetchUse${op.capitalizedMethodName}`; - const queryFn = `() => ${op.methodName}(${SDK_CALL_ARGS}).then(response => response.data)`; + const queryFn = `({ signal }) => ${op.methodName}(${QUERY_SDK_CALL_ARGS}).then(response => response.data)`; const optionsParam = `options?: Omit, "queryKey" | "queryFn">`; const body = `queryClient.prefetchQuery({ queryKey: Common.Use${op.capitalizedMethodName}KeyFn(clientOptions), queryFn: ${queryFn}, ...options })`; @@ -336,7 +366,7 @@ export function buildPrefetchInfiniteQueryFn( const fnName = `prefetchUse${op.capitalizedMethodName}Infinite`; const queryFn = buildPagedQueryFn(op, ctx, false); - const infiniteOptions = `initialPageParam: ${formatInitialPageParam(ctx)}, getNextPageParam: ${buildGetNextPageParamExpr(ctx)}`; + const infiniteOptions = `initialPageParam: ${formatInitialPageParam(ctx, op)}, getNextPageParam: ${buildGetNextPageParamExpr(ctx, op)}`; const optionsParam = `options?: Omit, "queryKey" | "queryFn" | "initialPageParam" | "getNextPageParam">`; const body = `queryClient.prefetchInfiniteQuery({ queryKey: Common.Use${op.capitalizedMethodName}InfiniteKeyFn(clientOptions), queryFn: ${queryFn}, ${infiniteOptions}, ...options })`; @@ -372,7 +402,7 @@ export function buildEnsureQueryDataFn( ): VariableStatementStructure { const fnName = `ensureUse${op.capitalizedMethodName}Data`; - const queryFn = `() => ${op.methodName}(${SDK_CALL_ARGS}).then(response => response.data)`; + const queryFn = `({ signal }) => ${op.methodName}(${QUERY_SDK_CALL_ARGS}).then(response => response.data)`; const optionsParam = `options?: Omit, "queryKey" | "queryFn">`; const body = `queryClient.ensureQueryData({ queryKey: Common.Use${op.capitalizedMethodName}KeyFn(clientOptions), queryFn: ${queryFn}, ...options })`; diff --git a/src/tsmorph/buildQueryOptions.mts b/src/tsmorph/buildQueryOptions.mts index 4e4b400..da82de4 100644 --- a/src/tsmorph/buildQueryOptions.mts +++ b/src/tsmorph/buildQueryOptions.mts @@ -10,7 +10,7 @@ import { buildInfiniteClientOptionsParam, buildPagedQueryFn, formatInitialPageParam, - SDK_CALL_ARGS, + QUERY_SDK_CALL_ARGS, } from "./buildQueryHooks.mjs"; /** @@ -32,7 +32,7 @@ export function buildQueryOptionsFn( const fnName = `${op.methodName}Options`; const clientOptionsParam = buildClientOptionsParam(op, ctx); - const queryFn = `() => ${op.methodName}(${SDK_CALL_ARGS}).then(response => response.data)`; + const queryFn = `({ signal }) => ${op.methodName}(${QUERY_SDK_CALL_ARGS}).then(response => response.data)`; const body = `queryOptions({ queryKey: Common.Use${op.capitalizedMethodName}KeyFn(clientOptions, queryKey), queryFn: ${queryFn} })`; return { @@ -73,7 +73,7 @@ export function buildInfiniteQueryOptionsFn( const fnName = `${op.methodName}InfiniteOptions`; const queryFn = buildPagedQueryFn(op, ctx, false); - const infiniteOptions = `initialPageParam: ${formatInitialPageParam(ctx)}, getNextPageParam: ${buildGetNextPageParamExpr(ctx)}`; + const infiniteOptions = `initialPageParam: ${formatInitialPageParam(ctx, op)}, getNextPageParam: ${buildGetNextPageParamExpr(ctx, op)}`; const body = `infiniteQueryOptions({ queryKey: Common.Use${op.capitalizedMethodName}InfiniteKeyFn(clientOptions, queryKey), queryFn: ${queryFn}, ${infiniteOptions} })`; diff --git a/src/types.mts b/src/types.mts index 0af5dc0..f10457a 100644 --- a/src/types.mts +++ b/src/types.mts @@ -19,8 +19,14 @@ export interface OperationInfo { allParamsOptional: boolean; /** Whether this operation supports pagination (for infinite queries) */ isPaginatable: boolean; + /** Type of the configured page query parameter */ + pageParamType?: string; + /** Resolved primitive kind of the configured page query parameter */ + pageParamTypeKind?: PageParamTypeKind; } +export type PageParamTypeKind = "string" | "number" | "other"; + export interface OperationParameter { /** Parameter name */ name: string; diff --git a/tests/__snapshots__/createSource.test.ts.snap b/tests/__snapshots__/createSource.test.ts.snap index 1d0c9ee..92d216c 100644 --- a/tests/__snapshots__/createSource.test.ts.snap +++ b/tests/__snapshots__/createSource.test.ts.snap @@ -81,22 +81,22 @@ import { AxiosError } from "axios"; * Sed tempus felis lobortis leo pulvinar rutrum. Nam mattis velit nisl, eu condimentum ligula luctus nec. Phasellus semper velit eget aliquet faucibus. In a mattis elit. Phasellus vel urna viverra, condimentum lorem id, rhoncus nibh. Ut pellentesque posuere elementum. Sed a varius odio. Morbi rhoncus ligula libero, vel eleifend nunc tristique vitae. Fusce et sem dui. Aenean nec scelerisque tortor. Fusce malesuada accumsan magna vel tempus. Quisque mollis felis eu dolor tristique, sit amet auctor felis gravida. Sed libero lorem, molestie sed nisl in, accumsan tempor nisi. Fusce sollicitudin massa ut lacinia mattis. Sed vel eleifend lorem. Pellentesque vitae felis pretium, pulvinar elit eu, euismod sapien. * */ -export const useFindPets = , TQueryKey extends Array = unknown[]>(clientOptions: Options = {}, queryKey?: TQueryKey, options?: Omit, "queryKey" | "queryFn">) => useQuery({ queryKey: Common.UseFindPetsKeyFn(clientOptions, queryKey), queryFn: () => findPets({ ...clientOptions, throwOnError: true }).then(response => response.data as TData) as TData, ...options }); +export const useFindPets = , TQueryKey extends Array = unknown[]>(clientOptions: Options = {}, queryKey?: TQueryKey, options?: Omit, "queryKey" | "queryFn">) => useQuery({ queryKey: Common.UseFindPetsKeyFn(clientOptions, queryKey), queryFn: ({ signal }) => findPets({ ...clientOptions, signal, throwOnError: true }).then(response => response.data as TData) as TData, ...options }); /** * This path is not fully defined. * * @deprecated */ -export const useGetNotDefined = , TQueryKey extends Array = unknown[]>(clientOptions: Options = {}, queryKey?: TQueryKey, options?: Omit, "queryKey" | "queryFn">) => useQuery({ queryKey: Common.UseGetNotDefinedKeyFn(clientOptions, queryKey), queryFn: () => getNotDefined({ ...clientOptions, throwOnError: true }).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: ({ signal }) => getNotDefined({ ...clientOptions, signal, throwOnError: true }).then(response => response.data as TData) as TData, ...options }); /** * Returns a user based on a single ID, if the user does not have access to the pet */ -export const useFindPetById = , TQueryKey extends Array = unknown[]>(clientOptions: Options, queryKey?: TQueryKey, options?: Omit, "queryKey" | "queryFn">) => useQuery({ queryKey: Common.UseFindPetByIdKeyFn(clientOptions, queryKey), queryFn: () => findPetById({ ...clientOptions, throwOnError: true }).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: ({ signal }) => findPetById({ ...clientOptions, signal, throwOnError: true }).then(response => response.data as TData) as TData, ...options }); /** * Returns paginated pets from the system that the user has access to * */ -export const useFindPaginatedPets = , TQueryKey extends Array = unknown[]>(clientOptions: Options = {}, queryKey?: TQueryKey, options?: Omit, "queryKey" | "queryFn">) => useQuery({ queryKey: Common.UseFindPaginatedPetsKeyFn(clientOptions, queryKey), queryFn: () => findPaginatedPets({ ...clientOptions, throwOnError: true }).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: ({ signal }) => findPaginatedPets({ ...clientOptions, signal, throwOnError: true }).then(response => response.data as TData) as TData, ...options }); /** * Creates a new pet in the store. Duplicates are allowed */ @@ -131,27 +131,27 @@ import { AxiosError } from "axios"; * Sed tempus felis lobortis leo pulvinar rutrum. Nam mattis velit nisl, eu condimentum ligula luctus nec. Phasellus semper velit eget aliquet faucibus. In a mattis elit. Phasellus vel urna viverra, condimentum lorem id, rhoncus nibh. Ut pellentesque posuere elementum. Sed a varius odio. Morbi rhoncus ligula libero, vel eleifend nunc tristique vitae. Fusce et sem dui. Aenean nec scelerisque tortor. Fusce malesuada accumsan magna vel tempus. Quisque mollis felis eu dolor tristique, sit amet auctor felis gravida. Sed libero lorem, molestie sed nisl in, accumsan tempor nisi. Fusce sollicitudin massa ut lacinia mattis. Sed vel eleifend lorem. Pellentesque vitae felis pretium, pulvinar elit eu, euismod sapien. * */ -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, throwOnError: true }).then(response => response.data as TData) as TData, ...options }); +export const useFindPetsSuspense = , TError = AxiosError, TQueryKey extends Array = unknown[]>(clientOptions: Options = {}, queryKey?: TQueryKey, options?: Omit, "queryKey" | "queryFn">) => useSuspenseQuery({ queryKey: Common.UseFindPetsKeyFn(clientOptions, queryKey), queryFn: ({ signal }) => findPets({ ...clientOptions, signal, throwOnError: true }).then(response => response.data as TData) as TData, ...options }); /** * This path is not fully defined. * * @deprecated */ -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, throwOnError: true }).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: ({ signal }) => getNotDefined({ ...clientOptions, signal, throwOnError: true }).then(response => response.data as TData) as TData, ...options }); /** * Returns a user based on a single ID, if the user does not have access to the pet */ -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, throwOnError: true }).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: ({ signal }) => findPetById({ ...clientOptions, signal, throwOnError: true }).then(response => response.data as TData) as TData, ...options }); /** * Returns paginated pets from the system that the user has access to * */ -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, throwOnError: true }).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: ({ signal }) => findPaginatedPets({ ...clientOptions, signal, throwOnError: true }).then(response => response.data as TData) as TData, ...options }); /** * Returns paginated pets from the system that the user has access to * */ -export const useFindPaginatedPetsSuspenseInfinite = >, TError = AxiosError, TQueryKey extends Array = unknown[]>(clientOptions: Common.FindPaginatedPetsInfiniteClientOptions = {}, queryKey?: TQueryKey, options?: Omit, "queryKey" | "queryFn" | "initialPageParam" | "getNextPageParam"> & Partial, "initialPageParam" | "getNextPageParam">>) => useSuspenseInfiniteQuery({ queryKey: Common.UseFindPaginatedPetsInfiniteKeyFn(clientOptions, queryKey), queryFn: ({ pageParam }) => findPaginatedPets({ ...clientOptions, query: { ...clientOptions.query, page: pageParam as number }, throwOnError: true } as Options).then(response => response.data as TData) as TData, initialPageParam: 1, getNextPageParam: (response: unknown) => (response as { nextPage: number }).nextPage, ...options }); +export const useFindPaginatedPetsSuspenseInfinite = >, TError = AxiosError, TQueryKey extends Array = unknown[]>(clientOptions: Common.FindPaginatedPetsInfiniteClientOptions = {}, queryKey?: TQueryKey, options?: Omit, "queryKey" | "queryFn" | "initialPageParam" | "getNextPageParam"> & Partial, "initialPageParam" | "getNextPageParam">>) => useSuspenseInfiniteQuery({ queryKey: Common.UseFindPaginatedPetsInfiniteKeyFn(clientOptions, queryKey), queryFn: ({ pageParam, signal }) => findPaginatedPets({ ...clientOptions, query: { ...clientOptions.query, page: pageParam as number }, signal, throwOnError: true } as Options).then(response => response.data as TData) as TData, initialPageParam: 1, getNextPageParam: (response: unknown) => (response as { nextPage: number }).nextPage, ...options }); " `; @@ -172,27 +172,27 @@ import { AxiosError } from "axios"; * Sed tempus felis lobortis leo pulvinar rutrum. Nam mattis velit nisl, eu condimentum ligula luctus nec. Phasellus semper velit eget aliquet faucibus. In a mattis elit. Phasellus vel urna viverra, condimentum lorem id, rhoncus nibh. Ut pellentesque posuere elementum. Sed a varius odio. Morbi rhoncus ligula libero, vel eleifend nunc tristique vitae. Fusce et sem dui. Aenean nec scelerisque tortor. Fusce malesuada accumsan magna vel tempus. Quisque mollis felis eu dolor tristique, sit amet auctor felis gravida. Sed libero lorem, molestie sed nisl in, accumsan tempor nisi. Fusce sollicitudin massa ut lacinia mattis. Sed vel eleifend lorem. Pellentesque vitae felis pretium, pulvinar elit eu, euismod sapien. * */ -export const prefetchUseFindPets = (queryClient: QueryClient, clientOptions: Options = {}, options?: Omit, "queryKey" | "queryFn">) => queryClient.prefetchQuery({ queryKey: Common.UseFindPetsKeyFn(clientOptions), queryFn: () => findPets({ ...clientOptions, throwOnError: true }).then(response => response.data), ...options }); +export const prefetchUseFindPets = (queryClient: QueryClient, clientOptions: Options = {}, options?: Omit, "queryKey" | "queryFn">) => queryClient.prefetchQuery({ queryKey: Common.UseFindPetsKeyFn(clientOptions), queryFn: ({ signal }) => findPets({ ...clientOptions, signal, throwOnError: true }).then(response => response.data), ...options }); /** * This path is not fully defined. * * @deprecated */ -export const prefetchUseGetNotDefined = (queryClient: QueryClient, clientOptions: Options = {}, options?: Omit, "queryKey" | "queryFn">) => queryClient.prefetchQuery({ queryKey: Common.UseGetNotDefinedKeyFn(clientOptions), queryFn: () => getNotDefined({ ...clientOptions, throwOnError: true }).then(response => response.data), ...options }); +export const prefetchUseGetNotDefined = (queryClient: QueryClient, clientOptions: Options = {}, options?: Omit, "queryKey" | "queryFn">) => queryClient.prefetchQuery({ queryKey: Common.UseGetNotDefinedKeyFn(clientOptions), queryFn: ({ signal }) => getNotDefined({ ...clientOptions, signal, throwOnError: true }).then(response => response.data), ...options }); /** * Returns a user based on a single ID, if the user does not have access to the pet */ -export const prefetchUseFindPetById = (queryClient: QueryClient, clientOptions: Options, options?: Omit, "queryKey" | "queryFn">) => queryClient.prefetchQuery({ queryKey: Common.UseFindPetByIdKeyFn(clientOptions), queryFn: () => findPetById({ ...clientOptions, throwOnError: true }).then(response => response.data), ...options }); +export const prefetchUseFindPetById = (queryClient: QueryClient, clientOptions: Options, options?: Omit, "queryKey" | "queryFn">) => queryClient.prefetchQuery({ queryKey: Common.UseFindPetByIdKeyFn(clientOptions), queryFn: ({ signal }) => findPetById({ ...clientOptions, signal, throwOnError: true }).then(response => response.data), ...options }); /** * Returns paginated pets from the system that the user has access to * */ -export const prefetchUseFindPaginatedPets = (queryClient: QueryClient, clientOptions: Options = {}, options?: Omit, "queryKey" | "queryFn">) => queryClient.prefetchQuery({ queryKey: Common.UseFindPaginatedPetsKeyFn(clientOptions), queryFn: () => findPaginatedPets({ ...clientOptions, throwOnError: true }).then(response => response.data), ...options }); +export const prefetchUseFindPaginatedPets = (queryClient: QueryClient, clientOptions: Options = {}, options?: Omit, "queryKey" | "queryFn">) => queryClient.prefetchQuery({ queryKey: Common.UseFindPaginatedPetsKeyFn(clientOptions), queryFn: ({ signal }) => findPaginatedPets({ ...clientOptions, signal, throwOnError: true }).then(response => response.data), ...options }); /** * Returns paginated pets from the system that the user has access to * */ -export const prefetchUseFindPaginatedPetsInfinite = (queryClient: QueryClient, clientOptions: Common.FindPaginatedPetsInfiniteClientOptions = {}, options?: Omit, "queryKey" | "queryFn" | "initialPageParam" | "getNextPageParam">) => queryClient.prefetchInfiniteQuery({ queryKey: Common.UseFindPaginatedPetsInfiniteKeyFn(clientOptions), queryFn: ({ pageParam }) => findPaginatedPets({ ...clientOptions, query: { ...clientOptions.query, page: pageParam as number }, throwOnError: true } as Options).then(response => response.data), initialPageParam: 1, getNextPageParam: (response: unknown) => (response as { nextPage: number }).nextPage, ...options }); +export const prefetchUseFindPaginatedPetsInfinite = (queryClient: QueryClient, clientOptions: Common.FindPaginatedPetsInfiniteClientOptions = {}, options?: Omit, "queryKey" | "queryFn" | "initialPageParam" | "getNextPageParam">) => queryClient.prefetchInfiniteQuery({ queryKey: Common.UseFindPaginatedPetsInfiniteKeyFn(clientOptions), queryFn: ({ pageParam, signal }) => findPaginatedPets({ ...clientOptions, query: { ...clientOptions.query, page: pageParam as number }, signal, throwOnError: true } as Options).then(response => response.data), initialPageParam: 1, getNextPageParam: (response: unknown) => (response as { nextPage: number }).nextPage, ...options }); " `; @@ -275,22 +275,22 @@ import { ClientOptions, Pet, NewPet, Error, FindPetsData, FindPetsErrors, FindPe * Sed tempus felis lobortis leo pulvinar rutrum. Nam mattis velit nisl, eu condimentum ligula luctus nec. Phasellus semper velit eget aliquet faucibus. In a mattis elit. Phasellus vel urna viverra, condimentum lorem id, rhoncus nibh. Ut pellentesque posuere elementum. Sed a varius odio. Morbi rhoncus ligula libero, vel eleifend nunc tristique vitae. Fusce et sem dui. Aenean nec scelerisque tortor. Fusce malesuada accumsan magna vel tempus. Quisque mollis felis eu dolor tristique, sit amet auctor felis gravida. Sed libero lorem, molestie sed nisl in, accumsan tempor nisi. Fusce sollicitudin massa ut lacinia mattis. Sed vel eleifend lorem. Pellentesque vitae felis pretium, pulvinar elit eu, euismod sapien. * */ -export const useFindPets = = unknown[]>(clientOptions: Options = {}, queryKey?: TQueryKey, options?: Omit, "queryKey" | "queryFn">) => useQuery({ queryKey: Common.UseFindPetsKeyFn(clientOptions, queryKey), queryFn: () => findPets({ ...clientOptions, throwOnError: true }).then(response => response.data as TData) as TData, ...options }); +export const useFindPets = = unknown[]>(clientOptions: Options = {}, queryKey?: TQueryKey, options?: Omit, "queryKey" | "queryFn">) => useQuery({ queryKey: Common.UseFindPetsKeyFn(clientOptions, queryKey), queryFn: ({ signal }) => findPets({ ...clientOptions, signal, throwOnError: true }).then(response => response.data as TData) as TData, ...options }); /** * This path is not fully defined. * * @deprecated */ -export const useGetNotDefined = = unknown[]>(clientOptions: Options = {}, queryKey?: TQueryKey, options?: Omit, "queryKey" | "queryFn">) => useQuery({ queryKey: Common.UseGetNotDefinedKeyFn(clientOptions, queryKey), queryFn: () => getNotDefined({ ...clientOptions, throwOnError: true }).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: ({ signal }) => getNotDefined({ ...clientOptions, signal, throwOnError: true }).then(response => response.data as TData) as TData, ...options }); /** * Returns a user based on a single ID, if the user does not have access to the pet */ -export const useFindPetById = = unknown[]>(clientOptions: Options, queryKey?: TQueryKey, options?: Omit, "queryKey" | "queryFn">) => useQuery({ queryKey: Common.UseFindPetByIdKeyFn(clientOptions, queryKey), queryFn: () => findPetById({ ...clientOptions, throwOnError: true }).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: ({ signal }) => findPetById({ ...clientOptions, signal, throwOnError: true }).then(response => response.data as TData) as TData, ...options }); /** * Returns paginated pets from the system that the user has access to * */ -export const useFindPaginatedPets = = unknown[]>(clientOptions: Options = {}, queryKey?: TQueryKey, options?: Omit, "queryKey" | "queryFn">) => useQuery({ queryKey: Common.UseFindPaginatedPetsKeyFn(clientOptions, queryKey), queryFn: () => findPaginatedPets({ ...clientOptions, throwOnError: true }).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: ({ signal }) => findPaginatedPets({ ...clientOptions, signal, throwOnError: true }).then(response => response.data as TData) as TData, ...options }); /** * Creates a new pet in the store. Duplicates are allowed */ @@ -324,27 +324,27 @@ import { ClientOptions, Pet, NewPet, Error, FindPetsData, FindPetsErrors, FindPe * Sed tempus felis lobortis leo pulvinar rutrum. Nam mattis velit nisl, eu condimentum ligula luctus nec. Phasellus semper velit eget aliquet faucibus. In a mattis elit. Phasellus vel urna viverra, condimentum lorem id, rhoncus nibh. Ut pellentesque posuere elementum. Sed a varius odio. Morbi rhoncus ligula libero, vel eleifend nunc tristique vitae. Fusce et sem dui. Aenean nec scelerisque tortor. Fusce malesuada accumsan magna vel tempus. Quisque mollis felis eu dolor tristique, sit amet auctor felis gravida. Sed libero lorem, molestie sed nisl in, accumsan tempor nisi. Fusce sollicitudin massa ut lacinia mattis. Sed vel eleifend lorem. Pellentesque vitae felis pretium, pulvinar elit eu, euismod sapien. * */ -export const useFindPetsSuspense = , TError = FindPetsError, TQueryKey extends Array = unknown[]>(clientOptions: Options = {}, queryKey?: TQueryKey, options?: Omit, "queryKey" | "queryFn">) => useSuspenseQuery({ queryKey: Common.UseFindPetsKeyFn(clientOptions, queryKey), queryFn: () => findPets({ ...clientOptions, throwOnError: true }).then(response => response.data as TData) as TData, ...options }); +export const useFindPetsSuspense = , TError = FindPetsError, TQueryKey extends Array = unknown[]>(clientOptions: Options = {}, queryKey?: TQueryKey, options?: Omit, "queryKey" | "queryFn">) => useSuspenseQuery({ queryKey: Common.UseFindPetsKeyFn(clientOptions, queryKey), queryFn: ({ signal }) => findPets({ ...clientOptions, signal, throwOnError: true }).then(response => response.data as TData) as TData, ...options }); /** * This path is not fully defined. * * @deprecated */ -export const useGetNotDefinedSuspense = , TError = unknown, TQueryKey extends Array = unknown[]>(clientOptions: Options = {}, queryKey?: TQueryKey, options?: Omit, "queryKey" | "queryFn">) => useSuspenseQuery({ queryKey: Common.UseGetNotDefinedKeyFn(clientOptions, queryKey), queryFn: () => getNotDefined({ ...clientOptions, throwOnError: true }).then(response => response.data as TData) as TData, ...options }); +export const useGetNotDefinedSuspense = , TError = unknown, TQueryKey extends Array = unknown[]>(clientOptions: Options = {}, queryKey?: TQueryKey, options?: Omit, "queryKey" | "queryFn">) => useSuspenseQuery({ queryKey: Common.UseGetNotDefinedKeyFn(clientOptions, queryKey), queryFn: ({ signal }) => getNotDefined({ ...clientOptions, signal, throwOnError: true }).then(response => response.data as TData) as TData, ...options }); /** * Returns a user based on a single ID, if the user does not have access to the pet */ -export const useFindPetByIdSuspense = , TError = FindPetByIdError, TQueryKey extends Array = unknown[]>(clientOptions: Options, queryKey?: TQueryKey, options?: Omit, "queryKey" | "queryFn">) => useSuspenseQuery({ queryKey: Common.UseFindPetByIdKeyFn(clientOptions, queryKey), queryFn: () => findPetById({ ...clientOptions, throwOnError: true }).then(response => response.data as TData) as TData, ...options }); +export const useFindPetByIdSuspense = , TError = FindPetByIdError, TQueryKey extends Array = unknown[]>(clientOptions: Options, queryKey?: TQueryKey, options?: Omit, "queryKey" | "queryFn">) => useSuspenseQuery({ queryKey: Common.UseFindPetByIdKeyFn(clientOptions, queryKey), queryFn: ({ signal }) => findPetById({ ...clientOptions, signal, throwOnError: true }).then(response => response.data as TData) as TData, ...options }); /** * Returns paginated pets from the system that the user has access to * */ -export const useFindPaginatedPetsSuspense = , TError = unknown, TQueryKey extends Array = unknown[]>(clientOptions: Options = {}, queryKey?: TQueryKey, options?: Omit, "queryKey" | "queryFn">) => useSuspenseQuery({ queryKey: Common.UseFindPaginatedPetsKeyFn(clientOptions, queryKey), queryFn: () => findPaginatedPets({ ...clientOptions, throwOnError: true }).then(response => response.data as TData) as TData, ...options }); +export const useFindPaginatedPetsSuspense = , TError = unknown, TQueryKey extends Array = unknown[]>(clientOptions: Options = {}, queryKey?: TQueryKey, options?: Omit, "queryKey" | "queryFn">) => useSuspenseQuery({ queryKey: Common.UseFindPaginatedPetsKeyFn(clientOptions, queryKey), queryFn: ({ signal }) => findPaginatedPets({ ...clientOptions, signal, throwOnError: true }).then(response => response.data as TData) as TData, ...options }); /** * Returns paginated pets from the system that the user has access to * */ -export const useFindPaginatedPetsSuspenseInfinite = >, TError = unknown, TQueryKey extends Array = unknown[]>(clientOptions: Common.FindPaginatedPetsInfiniteClientOptions = {}, queryKey?: TQueryKey, options?: Omit, "queryKey" | "queryFn" | "initialPageParam" | "getNextPageParam"> & Partial, "initialPageParam" | "getNextPageParam">>) => useSuspenseInfiniteQuery({ queryKey: Common.UseFindPaginatedPetsInfiniteKeyFn(clientOptions, queryKey), queryFn: ({ pageParam }) => findPaginatedPets({ ...clientOptions, query: { ...clientOptions.query, page: pageParam as number }, throwOnError: true } as Options).then(response => response.data as TData) as TData, initialPageParam: 1, getNextPageParam: (response: unknown) => (response as { nextPage: number }).nextPage, ...options }); +export const useFindPaginatedPetsSuspenseInfinite = >, TError = unknown, TQueryKey extends Array = unknown[]>(clientOptions: Common.FindPaginatedPetsInfiniteClientOptions = {}, queryKey?: TQueryKey, options?: Omit, "queryKey" | "queryFn" | "initialPageParam" | "getNextPageParam"> & Partial, "initialPageParam" | "getNextPageParam">>) => useSuspenseInfiniteQuery({ queryKey: Common.UseFindPaginatedPetsInfiniteKeyFn(clientOptions, queryKey), queryFn: ({ pageParam, signal }) => findPaginatedPets({ ...clientOptions, query: { ...clientOptions.query, page: pageParam as number }, signal, throwOnError: true } as Options).then(response => response.data as TData) as TData, initialPageParam: 1, getNextPageParam: (response: unknown) => (response as { nextPage: number }).nextPage, ...options }); " `; @@ -364,26 +364,26 @@ import { ClientOptions, Pet, NewPet, Error, FindPetsData, FindPetsErrors, FindPe * Sed tempus felis lobortis leo pulvinar rutrum. Nam mattis velit nisl, eu condimentum ligula luctus nec. Phasellus semper velit eget aliquet faucibus. In a mattis elit. Phasellus vel urna viverra, condimentum lorem id, rhoncus nibh. Ut pellentesque posuere elementum. Sed a varius odio. Morbi rhoncus ligula libero, vel eleifend nunc tristique vitae. Fusce et sem dui. Aenean nec scelerisque tortor. Fusce malesuada accumsan magna vel tempus. Quisque mollis felis eu dolor tristique, sit amet auctor felis gravida. Sed libero lorem, molestie sed nisl in, accumsan tempor nisi. Fusce sollicitudin massa ut lacinia mattis. Sed vel eleifend lorem. Pellentesque vitae felis pretium, pulvinar elit eu, euismod sapien. * */ -export const prefetchUseFindPets = (queryClient: QueryClient, clientOptions: Options = {}, options?: Omit, "queryKey" | "queryFn">) => queryClient.prefetchQuery({ queryKey: Common.UseFindPetsKeyFn(clientOptions), queryFn: () => findPets({ ...clientOptions, throwOnError: true }).then(response => response.data), ...options }); +export const prefetchUseFindPets = (queryClient: QueryClient, clientOptions: Options = {}, options?: Omit, "queryKey" | "queryFn">) => queryClient.prefetchQuery({ queryKey: Common.UseFindPetsKeyFn(clientOptions), queryFn: ({ signal }) => findPets({ ...clientOptions, signal, throwOnError: true }).then(response => response.data), ...options }); /** * This path is not fully defined. * * @deprecated */ -export const prefetchUseGetNotDefined = (queryClient: QueryClient, clientOptions: Options = {}, options?: Omit, "queryKey" | "queryFn">) => queryClient.prefetchQuery({ queryKey: Common.UseGetNotDefinedKeyFn(clientOptions), queryFn: () => getNotDefined({ ...clientOptions, throwOnError: true }).then(response => response.data), ...options }); +export const prefetchUseGetNotDefined = (queryClient: QueryClient, clientOptions: Options = {}, options?: Omit, "queryKey" | "queryFn">) => queryClient.prefetchQuery({ queryKey: Common.UseGetNotDefinedKeyFn(clientOptions), queryFn: ({ signal }) => getNotDefined({ ...clientOptions, signal, throwOnError: true }).then(response => response.data), ...options }); /** * Returns a user based on a single ID, if the user does not have access to the pet */ -export const prefetchUseFindPetById = (queryClient: QueryClient, clientOptions: Options, options?: Omit, "queryKey" | "queryFn">) => queryClient.prefetchQuery({ queryKey: Common.UseFindPetByIdKeyFn(clientOptions), queryFn: () => findPetById({ ...clientOptions, throwOnError: true }).then(response => response.data), ...options }); +export const prefetchUseFindPetById = (queryClient: QueryClient, clientOptions: Options, options?: Omit, "queryKey" | "queryFn">) => queryClient.prefetchQuery({ queryKey: Common.UseFindPetByIdKeyFn(clientOptions), queryFn: ({ signal }) => findPetById({ ...clientOptions, signal, throwOnError: true }).then(response => response.data), ...options }); /** * Returns paginated pets from the system that the user has access to * */ -export const prefetchUseFindPaginatedPets = (queryClient: QueryClient, clientOptions: Options = {}, options?: Omit, "queryKey" | "queryFn">) => queryClient.prefetchQuery({ queryKey: Common.UseFindPaginatedPetsKeyFn(clientOptions), queryFn: () => findPaginatedPets({ ...clientOptions, throwOnError: true }).then(response => response.data), ...options }); +export const prefetchUseFindPaginatedPets = (queryClient: QueryClient, clientOptions: Options = {}, options?: Omit, "queryKey" | "queryFn">) => queryClient.prefetchQuery({ queryKey: Common.UseFindPaginatedPetsKeyFn(clientOptions), queryFn: ({ signal }) => findPaginatedPets({ ...clientOptions, signal, throwOnError: true }).then(response => response.data), ...options }); /** * Returns paginated pets from the system that the user has access to * */ -export const prefetchUseFindPaginatedPetsInfinite = (queryClient: QueryClient, clientOptions: Common.FindPaginatedPetsInfiniteClientOptions = {}, options?: Omit, "queryKey" | "queryFn" | "initialPageParam" | "getNextPageParam">) => queryClient.prefetchInfiniteQuery({ queryKey: Common.UseFindPaginatedPetsInfiniteKeyFn(clientOptions), queryFn: ({ pageParam }) => findPaginatedPets({ ...clientOptions, query: { ...clientOptions.query, page: pageParam as number }, throwOnError: true } as Options).then(response => response.data), initialPageParam: 1, getNextPageParam: (response: unknown) => (response as { nextPage: number }).nextPage, ...options }); +export const prefetchUseFindPaginatedPetsInfinite = (queryClient: QueryClient, clientOptions: Common.FindPaginatedPetsInfiniteClientOptions = {}, options?: Omit, "queryKey" | "queryFn" | "initialPageParam" | "getNextPageParam">) => queryClient.prefetchInfiniteQuery({ queryKey: Common.UseFindPaginatedPetsInfiniteKeyFn(clientOptions), queryFn: ({ pageParam, signal }) => findPaginatedPets({ ...clientOptions, query: { ...clientOptions.query, page: pageParam as number }, signal, throwOnError: true } as Options).then(response => response.data), initialPageParam: 1, getNextPageParam: (response: unknown) => (response as { nextPage: number }).nextPage, ...options }); " `; diff --git a/tests/__snapshots__/generate.test.ts.snap b/tests/__snapshots__/generate.test.ts.snap index b60f6a6..bab3ae7 100644 --- a/tests/__snapshots__/generate.test.ts.snap +++ b/tests/__snapshots__/generate.test.ts.snap @@ -158,8 +158,8 @@ export const ensureUseFindPetsData = ( ) => queryClient.ensureQueryData({ queryKey: Common.UseFindPetsKeyFn(clientOptions), - queryFn: () => - findPets({ ...clientOptions, throwOnError: true }).then( + queryFn: ({ signal }) => + findPets({ ...clientOptions, signal, throwOnError: true }).then( (response) => response.data, ), ...options, @@ -179,8 +179,8 @@ export const ensureUseGetNotDefinedData = ( ) => queryClient.ensureQueryData({ queryKey: Common.UseGetNotDefinedKeyFn(clientOptions), - queryFn: () => - getNotDefined({ ...clientOptions, throwOnError: true }).then( + queryFn: ({ signal }) => + getNotDefined({ ...clientOptions, signal, throwOnError: true }).then( (response) => response.data, ), ...options, @@ -198,8 +198,8 @@ export const ensureUseFindPetByIdData = ( ) => queryClient.ensureQueryData({ queryKey: Common.UseFindPetByIdKeyFn(clientOptions), - queryFn: () => - findPetById({ ...clientOptions, throwOnError: true }).then( + queryFn: ({ signal }) => + findPetById({ ...clientOptions, signal, throwOnError: true }).then( (response) => response.data, ), ...options, @@ -218,8 +218,8 @@ export const ensureUseFindPaginatedPetsData = ( ) => queryClient.ensureQueryData({ queryKey: Common.UseFindPaginatedPetsKeyFn(clientOptions), - queryFn: () => - findPaginatedPets({ ...clientOptions, throwOnError: true }).then( + queryFn: ({ signal }) => + findPaginatedPets({ ...clientOptions, signal, throwOnError: true }).then( (response) => response.data, ), ...options, @@ -272,10 +272,11 @@ export const useFindPaginatedPetsInfinite = < ) => useInfiniteQuery({ queryKey: Common.UseFindPaginatedPetsInfiniteKeyFn(clientOptions, queryKey), - queryFn: ({ pageParam }) => + queryFn: ({ pageParam, signal }) => findPaginatedPets({ ...clientOptions, query: { ...clientOptions.query, page: pageParam as number }, + signal, throwOnError: true, } as Options).then( (response) => response.data as TData, @@ -328,8 +329,8 @@ export const prefetchUseFindPets = ( ) => queryClient.prefetchQuery({ queryKey: Common.UseFindPetsKeyFn(clientOptions), - queryFn: () => - findPets({ ...clientOptions, throwOnError: true }).then( + queryFn: ({ signal }) => + findPets({ ...clientOptions, signal, throwOnError: true }).then( (response) => response.data, ), ...options, @@ -349,8 +350,8 @@ export const prefetchUseGetNotDefined = ( ) => queryClient.prefetchQuery({ queryKey: Common.UseGetNotDefinedKeyFn(clientOptions), - queryFn: () => - getNotDefined({ ...clientOptions, throwOnError: true }).then( + queryFn: ({ signal }) => + getNotDefined({ ...clientOptions, signal, throwOnError: true }).then( (response) => response.data, ), ...options, @@ -368,8 +369,8 @@ export const prefetchUseFindPetById = ( ) => queryClient.prefetchQuery({ queryKey: Common.UseFindPetByIdKeyFn(clientOptions), - queryFn: () => - findPetById({ ...clientOptions, throwOnError: true }).then( + queryFn: ({ signal }) => + findPetById({ ...clientOptions, signal, throwOnError: true }).then( (response) => response.data, ), ...options, @@ -388,8 +389,8 @@ export const prefetchUseFindPaginatedPets = ( ) => queryClient.prefetchQuery({ queryKey: Common.UseFindPaginatedPetsKeyFn(clientOptions), - queryFn: () => - findPaginatedPets({ ...clientOptions, throwOnError: true }).then( + queryFn: ({ signal }) => + findPaginatedPets({ ...clientOptions, signal, throwOnError: true }).then( (response) => response.data, ), ...options, @@ -408,10 +409,11 @@ export const prefetchUseFindPaginatedPetsInfinite = ( ) => queryClient.prefetchInfiniteQuery({ queryKey: Common.UseFindPaginatedPetsInfiniteKeyFn(clientOptions), - queryFn: ({ pageParam }) => + queryFn: ({ pageParam, signal }) => findPaginatedPets({ ...clientOptions, query: { ...clientOptions.query, page: pageParam as number }, + signal, throwOnError: true, } as Options).then( (response) => response.data, @@ -476,8 +478,8 @@ export const useFindPets = < ) => useQuery({ queryKey: Common.UseFindPetsKeyFn(clientOptions, queryKey), - queryFn: () => - findPets({ ...clientOptions, throwOnError: true }).then( + queryFn: ({ signal }) => + findPets({ ...clientOptions, signal, throwOnError: true }).then( (response) => response.data as TData, ) as TData, ...options, @@ -498,8 +500,8 @@ export const useGetNotDefined = < ) => useQuery({ queryKey: Common.UseGetNotDefinedKeyFn(clientOptions, queryKey), - queryFn: () => - getNotDefined({ ...clientOptions, throwOnError: true }).then( + queryFn: ({ signal }) => + getNotDefined({ ...clientOptions, signal, throwOnError: true }).then( (response) => response.data as TData, ) as TData, ...options, @@ -518,8 +520,8 @@ export const useFindPetById = < ) => useQuery({ queryKey: Common.UseFindPetByIdKeyFn(clientOptions, queryKey), - queryFn: () => - findPetById({ ...clientOptions, throwOnError: true }).then( + queryFn: ({ signal }) => + findPetById({ ...clientOptions, signal, throwOnError: true }).then( (response) => response.data as TData, ) as TData, ...options, @@ -539,8 +541,8 @@ export const useFindPaginatedPets = < ) => useQuery({ queryKey: Common.UseFindPaginatedPetsKeyFn(clientOptions, queryKey), - queryFn: () => - findPaginatedPets({ ...clientOptions, throwOnError: true }).then( + queryFn: ({ signal }) => + findPaginatedPets({ ...clientOptions, signal, throwOnError: true }).then( (response) => response.data as TData, ) as TData, ...options, @@ -627,6 +629,114 @@ export const useDeletePet = < " `; +exports[`generate > queryOptions.ts 1`] = ` +"// generated with @7nohe/openapi-react-query-codegen@1.0.0 + +import { infiniteQueryOptions, queryOptions } from "@tanstack/react-query"; +import { + findPaginatedPets, + findPetById, + findPets, + getNotDefined, + type Options, +} from "../requests/sdk.gen"; +import type { + FindPaginatedPetsData, + FindPetByIdData, + FindPetsData, + GetNotDefinedData, +} from "../requests/types.gen"; +import * as Common from "./common"; + +/** + * Returns all pets from the system that the user has access to + * Nam sed condimentum est. Maecenas tempor sagittis sapien, nec rhoncus sem sagittis sit amet. Aenean at gravida augue, ac iaculis sem. Curabitur odio lorem, ornare eget elementum nec, cursus id lectus. Duis mi turpis, pulvinar ac eros ac, tincidunt varius justo. In hac habitasse platea dictumst. Integer at adipiscing ante, a sagittis ligula. Aenean pharetra tempor ante molestie imperdiet. Vivamus id aliquam diam. Cras quis velit non tortor eleifend sagittis. Praesent at enim pharetra urna volutpat venenatis eget eget mauris. In eleifend fermentum facilisis. Praesent enim enim, gravida ac sodales sed, placerat id erat. Suspendisse lacus dolor, consectetur non augue vel, vehicula interdum libero. Morbi euismod sagittis libero sed lacinia. + * + * Sed tempus felis lobortis leo pulvinar rutrum. Nam mattis velit nisl, eu condimentum ligula luctus nec. Phasellus semper velit eget aliquet faucibus. In a mattis elit. Phasellus vel urna viverra, condimentum lorem id, rhoncus nibh. Ut pellentesque posuere elementum. Sed a varius odio. Morbi rhoncus ligula libero, vel eleifend nunc tristique vitae. Fusce et sem dui. Aenean nec scelerisque tortor. Fusce malesuada accumsan magna vel tempus. Quisque mollis felis eu dolor tristique, sit amet auctor felis gravida. Sed libero lorem, molestie sed nisl in, accumsan tempor nisi. Fusce sollicitudin massa ut lacinia mattis. Sed vel eleifend lorem. Pellentesque vitae felis pretium, pulvinar elit eu, euismod sapien. + * + */ +export const findPetsOptions = ( + clientOptions: Options = {}, + queryKey?: Array, +) => + queryOptions({ + queryKey: Common.UseFindPetsKeyFn(clientOptions, queryKey), + queryFn: ({ signal }) => + findPets({ ...clientOptions, signal, throwOnError: true }).then( + (response) => response.data, + ), + }); +/** + * This path is not fully defined. + * + * @deprecated + */ +export const getNotDefinedOptions = ( + clientOptions: Options = {}, + queryKey?: Array, +) => + queryOptions({ + queryKey: Common.UseGetNotDefinedKeyFn(clientOptions, queryKey), + queryFn: ({ signal }) => + getNotDefined({ ...clientOptions, signal, throwOnError: true }).then( + (response) => response.data, + ), + }); +/** + * Returns a user based on a single ID, if the user does not have access to the pet + */ +export const findPetByIdOptions = ( + clientOptions: Options, + queryKey?: Array, +) => + queryOptions({ + queryKey: Common.UseFindPetByIdKeyFn(clientOptions, queryKey), + queryFn: ({ signal }) => + findPetById({ ...clientOptions, signal, throwOnError: true }).then( + (response) => response.data, + ), + }); +/** + * Returns paginated pets from the system that the user has access to + * + */ +export const findPaginatedPetsOptions = ( + clientOptions: Options = {}, + queryKey?: Array, +) => + queryOptions({ + queryKey: Common.UseFindPaginatedPetsKeyFn(clientOptions, queryKey), + queryFn: ({ signal }) => + findPaginatedPets({ ...clientOptions, signal, throwOnError: true }).then( + (response) => response.data, + ), + }); +/** + * Returns paginated pets from the system that the user has access to + * + */ +export const findPaginatedPetsInfiniteOptions = ( + clientOptions: Common.FindPaginatedPetsInfiniteClientOptions = {}, + queryKey?: Array, +) => + infiniteQueryOptions({ + queryKey: Common.UseFindPaginatedPetsInfiniteKeyFn(clientOptions, queryKey), + queryFn: ({ pageParam, signal }) => + findPaginatedPets({ + ...clientOptions, + query: { ...clientOptions.query, page: pageParam as number }, + signal, + throwOnError: true, + } as Options).then( + (response) => response.data, + ), + initialPageParam: "initial", + getNextPageParam: (response: unknown) => + (response as { meta: { next: number } }).meta.next, + }); +" +`; + exports[`generate > suspense.ts 1`] = ` "// generated with @7nohe/openapi-react-query-codegen@1.0.0 @@ -675,8 +785,8 @@ export const useFindPetsSuspense = < ) => useSuspenseQuery({ queryKey: Common.UseFindPetsKeyFn(clientOptions, queryKey), - queryFn: () => - findPets({ ...clientOptions, throwOnError: true }).then( + queryFn: ({ signal }) => + findPets({ ...clientOptions, signal, throwOnError: true }).then( (response) => response.data as TData, ) as TData, ...options, @@ -700,8 +810,8 @@ export const useGetNotDefinedSuspense = < ) => useSuspenseQuery({ queryKey: Common.UseGetNotDefinedKeyFn(clientOptions, queryKey), - queryFn: () => - getNotDefined({ ...clientOptions, throwOnError: true }).then( + queryFn: ({ signal }) => + getNotDefined({ ...clientOptions, signal, throwOnError: true }).then( (response) => response.data as TData, ) as TData, ...options, @@ -723,8 +833,8 @@ export const useFindPetByIdSuspense = < ) => useSuspenseQuery({ queryKey: Common.UseFindPetByIdKeyFn(clientOptions, queryKey), - queryFn: () => - findPetById({ ...clientOptions, throwOnError: true }).then( + queryFn: ({ signal }) => + findPetById({ ...clientOptions, signal, throwOnError: true }).then( (response) => response.data as TData, ) as TData, ...options, @@ -747,8 +857,8 @@ export const useFindPaginatedPetsSuspense = < ) => useSuspenseQuery({ queryKey: Common.UseFindPaginatedPetsKeyFn(clientOptions, queryKey), - queryFn: () => - findPaginatedPets({ ...clientOptions, throwOnError: true }).then( + queryFn: ({ signal }) => + findPaginatedPets({ ...clientOptions, signal, throwOnError: true }).then( (response) => response.data as TData, ) as TData, ...options, @@ -777,10 +887,11 @@ export const useFindPaginatedPetsSuspenseInfinite = < ) => useSuspenseInfiniteQuery({ queryKey: Common.UseFindPaginatedPetsInfiniteKeyFn(clientOptions, queryKey), - queryFn: ({ pageParam }) => + queryFn: ({ pageParam, signal }) => findPaginatedPets({ ...clientOptions, query: { ...clientOptions.query, page: pageParam as number }, + signal, throwOnError: true, } as Options).then( (response) => response.data as TData, @@ -809,22 +920,22 @@ import * as Common from "./common"; * Sed tempus felis lobortis leo pulvinar rutrum. Nam mattis velit nisl, eu condimentum ligula luctus nec. Phasellus semper velit eget aliquet faucibus. In a mattis elit. Phasellus vel urna viverra, condimentum lorem id, rhoncus nibh. Ut pellentesque posuere elementum. Sed a varius odio. Morbi rhoncus ligula libero, vel eleifend nunc tristique vitae. Fusce et sem dui. Aenean nec scelerisque tortor. Fusce malesuada accumsan magna vel tempus. Quisque mollis felis eu dolor tristique, sit amet auctor felis gravida. Sed libero lorem, molestie sed nisl in, accumsan tempor nisi. Fusce sollicitudin massa ut lacinia mattis. Sed vel eleifend lorem. Pellentesque vitae felis pretium, pulvinar elit eu, euismod sapien. * */ -export const useGetPets = , TQueryKey extends Array = unknown[]>(clientOptions: Options = {}, queryKey?: TQueryKey, options?: Omit, "queryKey" | "queryFn">) => useQuery({ queryKey: Common.UseGetPetsKeyFn(clientOptions, queryKey), queryFn: () => getPets({ ...clientOptions, throwOnError: true }).then(response => response.data as TData) as TData, ...options }); +export const useGetPets = , TQueryKey extends Array = unknown[]>(clientOptions: Options = {}, queryKey?: TQueryKey, options?: Omit, "queryKey" | "queryFn">) => useQuery({ queryKey: Common.UseGetPetsKeyFn(clientOptions, queryKey), queryFn: ({ signal }) => getPets({ ...clientOptions, signal, throwOnError: true }).then(response => response.data as TData) as TData, ...options }); /** * This path is not fully defined. * * @deprecated */ -export const useGetNotDefined = , TQueryKey extends Array = unknown[]>(clientOptions: Options = {}, queryKey?: TQueryKey, options?: Omit, "queryKey" | "queryFn">) => useQuery({ queryKey: Common.UseGetNotDefinedKeyFn(clientOptions, queryKey), queryFn: () => getNotDefined({ ...clientOptions, throwOnError: true }).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: ({ signal }) => getNotDefined({ ...clientOptions, signal, throwOnError: true }).then(response => response.data as TData) as TData, ...options }); /** * Returns a user based on a single ID, if the user does not have access to the pet */ -export const useGetPetsById = , TQueryKey extends Array = unknown[]>(clientOptions: Options, queryKey?: TQueryKey, options?: Omit, "queryKey" | "queryFn">) => useQuery({ queryKey: Common.UseGetPetsByIdKeyFn(clientOptions, queryKey), queryFn: () => getPetsById({ ...clientOptions, throwOnError: true }).then(response => response.data as TData) as TData, ...options }); +export const useGetPetsById = , TQueryKey extends Array = unknown[]>(clientOptions: Options, queryKey?: TQueryKey, options?: Omit, "queryKey" | "queryFn">) => useQuery({ queryKey: Common.UseGetPetsByIdKeyFn(clientOptions, queryKey), queryFn: ({ signal }) => getPetsById({ ...clientOptions, signal, throwOnError: true }).then(response => response.data as TData) as TData, ...options }); /** * Returns paginated pets from the system that the user has access to * */ -export const useGetPaginatedPets = , TQueryKey extends Array = unknown[]>(clientOptions: Options = {}, queryKey?: TQueryKey, options?: Omit, "queryKey" | "queryFn">) => useQuery({ queryKey: Common.UseGetPaginatedPetsKeyFn(clientOptions, queryKey), queryFn: () => getPaginatedPets({ ...clientOptions, throwOnError: true }).then(response => response.data as TData) as TData, ...options }); +export const useGetPaginatedPets = , TQueryKey extends Array = unknown[]>(clientOptions: Options = {}, queryKey?: TQueryKey, options?: Omit, "queryKey" | "queryFn">) => useQuery({ queryKey: Common.UseGetPaginatedPetsKeyFn(clientOptions, queryKey), queryFn: ({ signal }) => getPaginatedPets({ ...clientOptions, signal, throwOnError: true }).then(response => response.data as TData) as TData, ...options }); /** * Creates a new pet in the store. Duplicates are allowed */ @@ -857,22 +968,22 @@ import * as Common from "./common"; * Sed tempus felis lobortis leo pulvinar rutrum. Nam mattis velit nisl, eu condimentum ligula luctus nec. Phasellus semper velit eget aliquet faucibus. In a mattis elit. Phasellus vel urna viverra, condimentum lorem id, rhoncus nibh. Ut pellentesque posuere elementum. Sed a varius odio. Morbi rhoncus ligula libero, vel eleifend nunc tristique vitae. Fusce et sem dui. Aenean nec scelerisque tortor. Fusce malesuada accumsan magna vel tempus. Quisque mollis felis eu dolor tristique, sit amet auctor felis gravida. Sed libero lorem, molestie sed nisl in, accumsan tempor nisi. Fusce sollicitudin massa ut lacinia mattis. Sed vel eleifend lorem. Pellentesque vitae felis pretium, pulvinar elit eu, euismod sapien. * */ -export const useFindPets = = unknown[]>(clientOptions: Options = {}, queryKey?: TQueryKey, options?: Omit, "queryKey" | "queryFn">) => useQuery({ queryKey: Common.UseFindPetsKeyFn(clientOptions, queryKey), queryFn: () => findPets({ ...clientOptions, throwOnError: true }).then(response => response.data as TData) as TData, ...options }); +export const useFindPets = = unknown[]>(clientOptions: Options = {}, queryKey?: TQueryKey, options?: Omit, "queryKey" | "queryFn">) => useQuery({ queryKey: Common.UseFindPetsKeyFn(clientOptions, queryKey), queryFn: ({ signal }) => findPets({ ...clientOptions, signal, throwOnError: true }).then(response => response.data as TData) as TData, ...options }); /** * This path is not fully defined. * * @deprecated */ -export const useGetNotDefined = = unknown[]>(clientOptions: Options = {}, queryKey?: TQueryKey, options?: Omit, "queryKey" | "queryFn">) => useQuery({ queryKey: Common.UseGetNotDefinedKeyFn(clientOptions, queryKey), queryFn: () => getNotDefined({ ...clientOptions, throwOnError: true }).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: ({ signal }) => getNotDefined({ ...clientOptions, signal, throwOnError: true }).then(response => response.data as TData) as TData, ...options }); /** * Returns a user based on a single ID, if the user does not have access to the pet */ -export const useFindPetById = = unknown[]>(clientOptions: Options, queryKey?: TQueryKey, options?: Omit, "queryKey" | "queryFn">) => useQuery({ queryKey: Common.UseFindPetByIdKeyFn(clientOptions, queryKey), queryFn: () => findPetById({ ...clientOptions, throwOnError: true }).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: ({ signal }) => findPetById({ ...clientOptions, signal, throwOnError: true }).then(response => response.data as TData) as TData, ...options }); /** * Returns paginated pets from the system that the user has access to * */ -export const useFindPaginatedPets = = unknown[]>(clientOptions: Options = {}, queryKey?: TQueryKey, options?: Omit, "queryKey" | "queryFn">) => useQuery({ queryKey: Common.UseFindPaginatedPetsKeyFn(clientOptions, queryKey), queryFn: () => findPaginatedPets({ ...clientOptions, throwOnError: true }).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: ({ signal }) => findPaginatedPets({ ...clientOptions, signal, throwOnError: true }).then(response => response.data as TData) as TData, ...options }); /** * Creates a new pet in the store. Duplicates are allowed */ diff --git a/tests/generate.test.ts b/tests/generate.test.ts index a7b4acb..024c7fe 100644 --- a/tests/generate.test.ts +++ b/tests/generate.test.ts @@ -1,6 +1,7 @@ import { existsSync, readFileSync } from "node:fs"; import { rm } from "node:fs/promises"; import path from "node:path"; +import { pathToFileURL } from "node:url"; import { afterAll, beforeAll, describe, expect, test } from "vitest"; import type { LimitedUserConfig } from "../src/cli.mts"; import { generate } from "../src/generate.mjs"; @@ -44,6 +45,10 @@ describe("generate", () => { expect(readOutput("queries.ts")).toMatchSnapshot(); }); + test("queryOptions.ts", () => { + expect(readOutput("queryOptions.ts")).toMatchSnapshot(); + }); + test("infiniteQueries.ts", () => { expect(readOutput("infiniteQueries.ts")).toMatchSnapshot(); }); @@ -132,3 +137,55 @@ describe("generate - noSchemas option", () => { expect(readNoSchemasOutput("queries.ts")).toMatchSnapshot(); }); }); + +describe("generate - useDateType", () => { + const outputDir = "outputs-generate-dates"; + const readDateOutput = (fileName: string) => + readFileSync( + path.join(__dirname, outputDir, "requests", fileName), + "utf-8", + ); + + beforeAll(async () => { + const options: LimitedUserConfig = { + input: path.join(__dirname, "inputs", "dates.yaml"), + output: path.join("tests", outputDir), + client: "@hey-api/client-fetch", + useDateType: true, + pageParam: "page", + nextPageParam: "nextPage", + initialPageParam: "1", + }; + await generate(options, "1.0.0"); + }); + + afterAll(async () => { + if (existsSync(path.join(__dirname, outputDir))) { + await rm(path.join(__dirname, outputDir), { recursive: true }); + } + }); + + test("uses Date in generated model types", () => { + expect(readDateOutput("types.gen.ts")).toContain("createdAt: Date"); + }); + + test("wires the runtime date transformer into the SDK", async () => { + const transformer = readDateOutput("transformers.gen.ts"); + expect(transformer).toContain("new Date"); + expect(transformer).toContain("createdAt"); + + const sdk = readDateOutput("sdk.gen.ts"); + expect(sdk).toContain("listEventsResponseTransformer"); + expect(sdk).toContain("responseTransformer: listEventsResponseTransformer"); + + const transformerModule = await import( + pathToFileURL( + path.join(__dirname, outputDir, "requests", "transformers.gen.ts"), + ).href + ); + const transformed = await transformerModule.listEventsResponseTransformer([ + { createdAt: "2026-07-20T00:00:00.000Z" }, + ]); + expect(transformed[0].createdAt).toBeInstanceOf(Date); + }); +}); diff --git a/tests/inputs/dates.yaml b/tests/inputs/dates.yaml new file mode 100644 index 0000000..b5c843f --- /dev/null +++ b/tests/inputs/dates.yaml @@ -0,0 +1,27 @@ +openapi: 3.0.3 +info: + title: Dates API + version: 1.0.0 +paths: + /events: + get: + operationId: listEvents + responses: + "200": + description: Events + content: + application/json: + schema: + type: array + items: + $ref: "#/components/schemas/Event" +components: + schemas: + Event: + type: object + required: + - createdAt + properties: + createdAt: + type: string + format: date-time diff --git a/tests/inputs/string-pagination.yaml b/tests/inputs/string-pagination.yaml new file mode 100644 index 0000000..63e655a --- /dev/null +++ b/tests/inputs/string-pagination.yaml @@ -0,0 +1,32 @@ +openapi: 3.0.3 +info: + title: Cursor API + version: 1.0.0 +paths: + /items: + get: + operationId: listItems + parameters: + - name: cursor + in: query + required: false + schema: + $ref: "#/components/schemas/Cursor" + responses: + "200": + description: Items + content: + application/json: + schema: + type: object + properties: + nextCursor: + $ref: "#/components/schemas/Cursor" + items: + type: array + items: + type: string +components: + schemas: + Cursor: + type: string diff --git a/tests/parseOperations.test.ts b/tests/parseOperations.test.ts index 2d4518e..30364fc 100644 --- a/tests/parseOperations.test.ts +++ b/tests/parseOperations.test.ts @@ -65,8 +65,9 @@ describe("parseOperations", () => { ); expect(findPaginatedPets).toBeDefined(); expect(findPaginatedPets?.httpMethod).toBe("GET"); - // Note: isPaginatable detection uses simplified regex which may not detect all cases - // The actual pagination support is validated via createSourceV2 integration tests + expect(findPaginatedPets?.isPaginatable).toBe(true); + expect(findPaginatedPets?.pageParamType).toBe("number"); + expect(findPaginatedPets?.pageParamTypeKind).toBe("number"); }); it("should extract parameters correctly", async () => { @@ -182,3 +183,25 @@ describe("parseOperations", () => { }); }); }); + +describe("parseOperations - string pagination", () => { + const stringFileName = "parseOperations-string-pagination"; + + beforeAll( + async () => + await generateTSClients(stringFileName, "string-pagination.yaml"), + ); + afterAll(async () => await cleanOutputs(stringFileName)); + + it("should preserve a string page parameter type", async () => { + const project = new Project({ skipAddingFilesFromTsConfig: true }); + project.addSourceFilesAtPaths(`${outputPath(stringFileName)}/**/*`); + + const operations = await parseOperations(project, "cursor"); + const listItems = operations.find((op) => op.methodName === "listItems"); + + expect(listItems?.isPaginatable).toBe(true); + expect(listItems?.pageParamType).toBe("Cursor"); + expect(listItems?.pageParamTypeKind).toBe("string"); + }); +}); diff --git a/tests/tsmorph/buildMutationHooks.test.ts b/tests/tsmorph/buildMutationHooks.test.ts index 16019c2..1566a8b 100644 --- a/tests/tsmorph/buildMutationHooks.test.ts +++ b/tests/tsmorph/buildMutationHooks.test.ts @@ -95,6 +95,23 @@ describe("buildMutationHooks", () => { ); }); + it("should default to the complete SDK response so headers stay available", () => { + const result = buildUseMutationHook(mockPostOperation, mockFetchContext); + const initializer = result.declarations[0].initializer as string; + + expect(initializer).toContain("TData = Common.AddPetMutationResult"); + expect(initializer).not.toContain('AddPetMutationResult["data"]'); + expect(initializer).not.toContain("response.data"); + }); + + it("should accept an AbortSignal through mutation client options", () => { + const result = buildUseMutationHook(mockPostOperation, mockFetchContext); + const initializer = result.declarations[0].initializer as string; + + expect(initializer).toContain("Options"); + expect(initializer).toContain("...clientOptions"); + }); + it("should build useMutation hook for DELETE operation", () => { const result = buildUseMutationHook( mockDeleteOperation, diff --git a/tests/tsmorph/buildQueryHooks.test.ts b/tests/tsmorph/buildQueryHooks.test.ts index 21cfcb4..f2161f5 100644 --- a/tests/tsmorph/buildQueryHooks.test.ts +++ b/tests/tsmorph/buildQueryHooks.test.ts @@ -29,6 +29,14 @@ const mockPaginatableOperation: OperationInfo = { parameters: [{ name: "page", typeName: "number", optional: true }], allParamsOptional: true, isPaginatable: true, + pageParamType: "number", + pageParamTypeKind: "number", +}; + +const mockStringPaginatableOperation: OperationInfo = { + ...mockPaginatableOperation, + pageParamType: "Cursor", + pageParamTypeKind: "string", }; const mockRequiredParamsOperation: OperationInfo = { @@ -107,8 +115,9 @@ describe("buildQueryHooks", () => { "Common.UseFindPetsKeyFn(clientOptions, queryKey)", ); expect(initializer).toContain( - "findPets({ ...clientOptions, throwOnError: true })", + "findPets({ ...clientOptions, signal, throwOnError: true })", ); + expect(initializer).toContain("queryFn: ({ signal }) =>"); expect(initializer).toContain("response.data as TData"); }); @@ -152,7 +161,7 @@ describe("buildQueryHooks", () => { "clientOptions: Options = {}", ); expect(initializer).toContain( - "getStatus({ ...clientOptions, throwOnError: true })", + "getStatus({ ...clientOptions, signal, throwOnError: true })", ); }); }); @@ -191,7 +200,7 @@ describe("buildQueryHooks", () => { "clientOptions: Options = {}", ); expect(initializer).toContain( - "getStatus({ ...clientOptions, throwOnError: true })", + "getStatus({ ...clientOptions, signal, throwOnError: true })", ); }); }); @@ -249,6 +258,22 @@ describe("buildQueryHooks", () => { const initializer = result?.declarations[0].initializer as string; expect(initializer).toContain("page: pageParam as number"); + expect(initializer).toContain("({ pageParam, signal }) =>"); + expect(initializer).toContain("signal, throwOnError: true"); + }); + + it("should preserve string cursor types and quote the initial value", () => { + const result = buildUseInfiniteQueryHook( + mockStringPaginatableOperation, + mockFetchContext, + ); + const initializer = result?.declarations[0].initializer as string; + + expect(initializer).toContain("page: pageParam as Cursor"); + expect(initializer).toContain('initialPageParam: "1"'); + expect(initializer).toContain( + "(response as { nextPage: Cursor }).nextPage", + ); }); it("should use unknown data type when not present in modelNames", () => { @@ -278,8 +303,9 @@ describe("buildQueryHooks", () => { expect(initializer).toContain("queryClient.prefetchQuery"); expect(initializer).toContain("Common.UseFindPetsKeyFn(clientOptions)"); expect(initializer).toContain( - "findPets({ ...clientOptions, throwOnError: true })", + "findPets({ ...clientOptions, signal, throwOnError: true })", ); + expect(initializer).toContain("queryFn: ({ signal }) =>"); expect(initializer).toContain("response.data"); }); @@ -326,7 +352,7 @@ describe("buildQueryHooks", () => { "clientOptions: Options = {}", ); expect(initializer).toContain( - "getStatus({ ...clientOptions, throwOnError: true })", + "getStatus({ ...clientOptions, signal, throwOnError: true })", ); }); }); @@ -369,7 +395,7 @@ describe("buildQueryHooks", () => { "clientOptions: Options = {}", ); expect(initializer).toContain( - "getStatus({ ...clientOptions, throwOnError: true })", + "getStatus({ ...clientOptions, signal, throwOnError: true })", ); }); @@ -416,6 +442,7 @@ describe("buildQueryHooks", () => { expect(initializer).toContain("initialPageParam: 1"); expect(initializer).toContain("getNextPageParam"); expect(initializer).toContain("throwOnError: true"); + expect(initializer).toContain("({ pageParam, signal }) =>"); }); }); @@ -453,6 +480,7 @@ describe("buildQueryHooks", () => { expect(initializer).toContain( 'Partial, "initialPageParam" | "getNextPageParam">>', ); + expect(initializer).toContain("({ pageParam, signal }) =>"); }); }); }); diff --git a/tests/tsmorph/buildQueryOptions.test.ts b/tests/tsmorph/buildQueryOptions.test.ts index 3adb0c6..42ffc13 100644 --- a/tests/tsmorph/buildQueryOptions.test.ts +++ b/tests/tsmorph/buildQueryOptions.test.ts @@ -24,6 +24,14 @@ const mockPaginatableOperation: OperationInfo = { parameters: [{ name: "page", typeName: "number", optional: true }], allParamsOptional: true, isPaginatable: true, + pageParamType: "number", + pageParamTypeKind: "number", +}; + +const mockStringPaginatableOperation: OperationInfo = { + ...mockPaginatableOperation, + pageParamType: "Cursor", + pageParamTypeKind: "string", }; const mockRequiredParamsOperation: OperationInfo = { @@ -69,7 +77,7 @@ describe("buildQueryOptions", () => { "queryOptions({ queryKey: Common.UseFindPetsKeyFn(clientOptions, queryKey)", ); expect(initializer).toContain( - "queryFn: () => findPets({ ...clientOptions, throwOnError: true }).then(response => response.data)", + "queryFn: ({ signal }) => findPets({ ...clientOptions, signal, throwOnError: true }).then(response => response.data)", ); }); @@ -124,7 +132,7 @@ describe("buildQueryOptions", () => { "infiniteQueryOptions({ queryKey: Common.UseFindPaginatedPetsInfiniteKeyFn(clientOptions, queryKey)", ); expect(initializer).toContain( - "query: { ...clientOptions.query, page: pageParam as number }, throwOnError: true } as Options", + "query: { ...clientOptions.query, page: pageParam as number }, signal, throwOnError: true } as Options", ); expect(initializer).toContain( "getNextPageParam: (response: unknown) => (response as { nextPage: number }).nextPage", @@ -153,6 +161,20 @@ describe("buildQueryOptions", () => { expect(initializer).toContain('initialPageParam: "cursor-start",'); }); + it("should preserve a string cursor type even for numeric-looking values", () => { + const result = buildInfiniteQueryOptionsFn( + mockStringPaginatableOperation, + mockContext, + ); + const initializer = result?.declarations[0].initializer as string; + + expect(initializer).toContain("page: pageParam as Cursor"); + expect(initializer).toContain('initialPageParam: "1"'); + expect(initializer).toContain( + "(response as { nextPage: Cursor }).nextPage", + ); + }); + it("should respect a custom pageParam and nested nextPageParam", () => { const ctx: GenerationContext = { ...mockContext, From dbab2e58c86fcc49e89f1163090c3051ba1653be Mon Sep 17 00:00:00 2001 From: Urata Daiki <7nohe@users.noreply.github.com> Date: Mon, 20 Jul 2026 17:18:01 +0900 Subject: [PATCH 30/31] docs(examples): demonstrate abortable mutations --- examples/react-app/src/App.tsx | 29 +++++++++++++++++++++++++++-- 1 file changed, 27 insertions(+), 2 deletions(-) diff --git a/examples/react-app/src/App.tsx b/examples/react-app/src/App.tsx index 464d39d..7c9e1e5 100644 --- a/examples/react-app/src/App.tsx +++ b/examples/react-app/src/App.tsx @@ -1,5 +1,5 @@ import "./App.css"; -import { useState } from "react"; +import { useEffect, useRef, useState } from "react"; import { UseFindPetsKeyFn, @@ -26,7 +26,15 @@ function App() { const { data: notDefined } = useGetNotDefined(); const { mutate: mutateNotDefined } = usePostNotDefined(); - const { mutate: addPet, isError } = useAddPet(); + const { mutate: addPet, isError, isPending: isAddingPet } = useAddPet(); + const addPetAbortController = useRef(null); + + useEffect( + () => () => { + addPetAbortController.current?.abort(); + }, + [], + ); const [text, setText] = useState(""); const [errorText, setErrorText] = useState(); @@ -53,9 +61,14 @@ function App() { + {isError && (

    Date: Mon, 20 Jul 2026 19:14:05 +0900 Subject: [PATCH 31/31] chore: release v3.0.0-beta.5 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 4dde131..795c958 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@7nohe/openapi-react-query-codegen", - "version": "3.0.0-beta.4", + "version": "3.0.0-beta.5", "description": "OpenAPI React Query Codegen", "bin": { "openapi-rq": "dist/cli.mjs"