Skip to content

Migration from Templates to Generator #2156

@jay-babu

Description

@jay-babu

What is the type of issue?

Documentation is confusing

What is the issue?

I have been trying to migrate from templates to generators but have been having a really hard time. Asking for a more involved example if possible. This was my old template. Not asking for the migration to be done for me, but some pointers or a more involved example in the docs would be nice. also added by kubb.config.ts for reference. Happy to provide openapi spec if that helps.

import { PackageManager } from "@kubb/core";
import transformers from "@kubb/core/transformers";
import { Function } from "@kubb/react";
import { QueryOptions } from "@kubb/swagger-tanstack-query/components";
import type React from "react";

const reactQueryDepRegex = /@tanstack\/(react|solid|vue|svelte)-query/;

export const templates = {
  queryOptions: {
    ...QueryOptions.templates,
    react: ({
      name,
      params,
      JSDoc,
      client,
      hook,
      dataReturnType,
      infinite,
      parser,
      returnType,
      generics,
    }: React.ComponentProps<typeof QueryOptions.templates.react>) => {
      const isV5 = new PackageManager().isValidSync(reactQueryDepRegex, ">=5");
      const isFormData = client.contentType === "multipart/form-data";
      const headers = [
        client.contentType !== "application/json"
          ? `'Content-Type': '${client.contentType}'`
          : undefined,
        client.withHeaders ? "...headers" : undefined,
      ]
        .filter(Boolean)
        .join(", ");

      const clientOptions = [
        `method: "${client.method}"`,
        `url: ${client.path.template}`,
        `signal: signal`,
        client.withQueryParams && !infinite ? "params" : undefined,
        client.withData && !isFormData ? "data" : undefined,
        client.withData && isFormData ? "data: formData" : undefined,
        headers.length
          ? `headers: { ${headers}, ...options.headers }`
          : undefined,
        "...options",
        client.withQueryParams && !!infinite
          ? `params: {
            ...params,
            ['${infinite.queryParam}']: pageParam,
            ...(options.params || {}),
          }`
          : undefined,
      ].filter(Boolean);

      const queryOptions = [
        isV5 && !!infinite
          ? `initialPageParam: ${infinite.initialPageParam}`
          : undefined,
        isV5 && !!infinite && !!infinite.cursorParam
          ? `getNextPageParam: (lastPage) => lastPage['${infinite.cursorParam}']`
          : undefined,
        isV5 && !!infinite && !!infinite.cursorParam
          ? `getPreviousPageParam: (firstPage) => firstPage['${infinite.cursorParam}']`
          : undefined,
        isV5 && !!infinite && !infinite.cursorParam && dataReturnType === "full"
          ? "getNextPageParam: (lastPage, _allPages, lastPageParam) => Array.isArray(lastPage.data) && lastPage.data.length === 0 ? undefined : lastPageParam + 1"
          : undefined,
        isV5 && !!infinite && !infinite.cursorParam && dataReturnType === "data"
          ? "getNextPageParam: (lastPage, _allPages, lastPageParam) => ((lastPage as any).page?.totalPages ?? 0) > ((lastPage as any).page?.number ?? 0) + 1 ? ((lastPage as any).page?.number ?? 0) + 1 : undefined"
          : undefined,
        isV5 && !!infinite && !infinite.cursorParam
          ? "getPreviousPageParam: (_firstPage, _allPages, firstPageParam) => firstPageParam <= 1 ? undefined : firstPageParam - 1"
          : undefined,
      ].filter(Boolean);

      const resolvedClientOptions = `${transformers.createIndent(4)}${clientOptions.join(`,\n${transformers.createIndent(4)}`)}`;
      const resolvedQueryOptions = `${transformers.createIndent(4)}${queryOptions.join(`,\n${transformers.createIndent(4)}`)}`;

      let returnRes = parser ? `return ${parser}(res.data)` : "return res.data";

      if (dataReturnType === "full") {
        returnRes = parser
          ? `return {...res, data: ${parser}(res.data)}`
          : "return res";
      }

      const formData = isFormData
        ? `
         const formData = new FormData()
         if(data) {
          Object.keys(data).forEach((key) => {
            const value = data[key];
            if (typeof key === "string" && (typeof value === "string" || value instanceof Blob)) {
              formData.append(key, value);
            }
          })
         }
        `
        : undefined;

      if (infinite) {
        if (isV5) {
          return (
            <Function name={name} export params={params} JSDoc={JSDoc}>
              {`
             const queryKey = ${hook.queryKey}
      
             return infiniteQueryOptions({
               queryKey,
               queryFn: async ({ pageParam, signal }) => {
                ${hook.children || ""}
                ${formData || ""}
                 const res = await client<${client.generics}>({
                  ${resolvedClientOptions}
                 })
      
                 ${returnRes}
               },
               ${resolvedQueryOptions}
             })
      
             `}
            </Function>
          );
        }

        return (
          <Function
            name={name}
            export
            generics={generics}
            returnType={returnType}
            params={params}
            JSDoc={JSDoc}
          >
            {`
               const queryKey = ${hook.queryKey}
      
               return {
                 queryKey,
                 queryFn: async ({ pageParam, signal }) => {
                   ${hook.children || ""}
                   ${formData || ""}
                   const res = await client<${client.generics}>({
                    ${resolvedClientOptions}
                   })
      
                   ${returnRes}
                 },
                 ${resolvedQueryOptions}
               }
      
               `}
          </Function>
        );
      }
      const timed = `
        async function timed<T>(
          name: string,
          fn: () => Promise<T> | T,
        ): Promise<T> {
          const start = performance.now();

          try {
            const result = await fn();
            const d = performance.now() - start;
            console.log(\`[perf] \${name}: \${d.toFixed(1)}ms\`);
            return result;
          } catch (e) {
            const d = performance.now() - start;
            console.log(\`[perf] \${name}: threw after \${d.toFixed(1)}ms\`);
            throw e;
          }
        }
      `

      if (isV5) {
        return (
          <Function name={name} export params={params} JSDoc={JSDoc}>
            {`
         const queryKey = ${hook.queryKey}

         ${timed}
      
         return queryOptions({
           queryKey,
           queryFn: async ({signal}) => {
             ${hook.children || ""}
             ${formData || ""}
             const res = await timed(\`${name}\`, async () => client<${client.generics}>({
              ${resolvedClientOptions}
             }))
      
             ${returnRes}
           },
           ${resolvedQueryOptions}
         })
      
         `}
          </Function>
        );
      }

      return (
        <Function
          name={name}
          export
          generics={generics}
          returnType={returnType}
          params={params}
          JSDoc={JSDoc}
        >
          {`
             const queryKey = ${hook.queryKey}
      
             return {
               queryKey,
               queryFn: async ({signal}) => {
                 ${hook.children || ""}
                 ${formData || ""}
                 const res = await client<${client.generics}>({
                  ${resolvedClientOptions}
                 })
      
                 ${returnRes}
               },
               ${resolvedQueryOptions}
             }
      
             `}
        </Function>
      );
    },
  },
};
import { defineConfig, UserConfig } from "@kubb/core";
import { pluginOas } from "@kubb/plugin-oas";
import { pluginClient } from "@kubb/swagger-client";
import { pluginFaker } from "@kubb/swagger-faker";
import { pluginMsw } from "@kubb/swagger-msw";
import { pluginTanstackQuery } from "@kubb/swagger-tanstack-query";
import { pluginTs as createSwaggerTs } from "@kubb/swagger-ts";
import fs from "fs";
import { pluginZod } from "@kubb/swagger-zod";
import { Oas } from "./src/codegen/Oas";
import { templates } from "./src/codegen/kubb/templates/templates";

export default defineConfig(async () => {
  let configs: UserConfig[] = [];

  // Try fetching the springdoc config
  try {
    const springDoc = await fetch(
      "http://localhost:9090/actuator/openapi/springdocDefault",
    );
    const springDocJson = await springDoc.json();

    configs.push({
      root: ".",
      input: {
        data: springDocJson,
      },
      output: {
        path: "./src/spring-generated",
      },
      hooks: {
        done: [
          "npx prettier --experimental-cli --write ./src/spring-generated",
        ],
      },
      plugins: [
        pluginOas({ oasClass: Oas, output: false }),
        createSwaggerTs({
          enumType: "enum",
        }),
        pluginZod({
          output: {
            path: "./schemas",
          },
          typed: true,
          coercion: true,
        }),
        pluginFaker({
          output: {
            path: "./faker",
          },
        }),
        pluginMsw({
          output: {
            path: "./msw",
          },
        }),
        pluginClient({
          client: {
            importPath: "../../config/client",
          },
          include: [
            {
              type: "operationId",
              pattern: "streamCandidatePurchaseOrderItems",
            },
            {
              type: "operationId",
              pattern: "getPurchaseOrderItemTransferSuggestions",
            },
            {
              type: "operationId",
              pattern: "updateCustomerHouseAccountCreditLimit",
            },
            {
              type: "operationId",
              pattern: "deleteCohortVendor",
            },
            {
              type: "operationId",
              pattern: "getPurchaseOrderCandidateItems",
            },
            {
              type: "operationId",
              pattern: "getPurchaseOrderItemTransfers",
            },
            {
              type: "operationId",
              pattern: "setUserConfiguration",
            },
          ],
          output: {
            path: "./operations",
          },
        }),
        pluginTanstackQuery({
          framework: "react",
          infinite: {
            queryParam: "page",
          },
          client: {
            importPath: "../../config/client",
          },
          output: {
            path: "./hooks",
          },
          mutate: {
            variablesType: "mutate",
            methods: ["post", "put", "delete", "patch"],
          },
          exclude: [{ type: "operationId", pattern: "emailPurchaseOrder" }],
          templates,
        }),
      ],
    });
  } catch (error) {
    console.error("Failed to fetch spring doc:", error);
  }

  // try fetching phoenix config
  try {
    const phoenixDoc = await fetch("http://localhost:8081/swagger/doc.json");
    const phoenixDocJson = await phoenixDoc.json();

    configs.push({
      root: ".",
      input: {
        data: phoenixDocJson,
      },
      output: {
        path: "./src/phoenix-generated",
        clean: true,
      },
      hooks: {
        done: ["npx prettier --write ./src/phoenix-generated"],
      },
      plugins: [
        pluginOas({ oasClass: Oas, output: false }),
        createSwaggerTs({
          enumType: "enum",
        }),
        pluginClient({
          client: {
            importPath: "../../config/phoenix-client",
          },
          include: [
            { type: "operationId", pattern: "updateOrderItemDecision" },
            { type: "operationId", pattern: "createWebsocketConnection" },
            { type: "operationId", pattern: "removeWebsocketConnection" },
            { type: "operationId", pattern: "getTransactionByTransactionItem" },
            { type: "operationId", pattern: "createAssetForDataLoad" },
            { type: "operationId", pattern: "mapAdventFiles" },
          ],
        }),
        pluginTanstackQuery({
          framework: "react",
          infinite: false,
          client: {
            importPath: "../../config/phoenix-client",
          },
          output: {
            path: "./hooks",
          },
          mutate: {
            variablesType: "mutate",
            methods: ["post", "put", "delete", "patch"],
          },
          templates,
        }),
      ],
    });
  } catch (error) {
    console.error("Failed to fetch phoenix doc:", error);
  }

  // Always add the POSServiceModel config
  configs.push({
    root: ".",
    input: {
      data: fs.readFileSync(
        "./POSServiceModel/postman/schemas/index.yaml",
        "utf-8",
      ),
    },
    output: {
      path: "./src/generated",
    },
    hooks: {
      done: ["npx prettier --experimental-cli --write ./src/generated"],
    },
    plugins: [
      pluginOas({ oasClass: Oas, output: false }),
      createSwaggerTs({
        enumType: "enum",
      }),
      pluginFaker({
        output: {
          path: "./faker",
        },
      }),
      pluginMsw({
        output: {
          path: "./msw",
        },
      }),
      pluginClient({
        client: {
          importPath: "../../config/client",
        },
        include: [
          { type: "operationId", pattern: "getEntityConfigs" },
          { type: "operationId", pattern: "getCartPricing" },
          { type: "operationId", pattern: "getSalesByItemId" },
          { type: "operationId", pattern: "deleteUserFromEntity" },
          { type: "operationId", pattern: "patchUserEntity" },
          { type: "operationId", pattern: "authorizeEmployee" },
          { type: "operationId", pattern: "authorizeUser" },
        ],
        output: {
          path: "./operations",
        },
      }),
      pluginTanstackQuery({
        framework: "react",
        infinite: {
          queryParam: "page",
        },
        client: {
          importPath: "../../config/client",
        },
        output: {
          path: "./hooks",
        },
        templates,
      }),
    ],
  });

  return configs;
});
    "@kubb/cli": "^2.28.4",
    "@kubb/core": "^2.28.4",
    "@kubb/plugin-oas": "^2.28.4",
    "@kubb/swagger": "^2.28.4",
    "@kubb/swagger-client": "^2.28.4",
    "@kubb/swagger-faker": "^2.28.4",
    "@kubb/swagger-msw": "^2.28.4",
    "@kubb/swagger-tanstack-query": "^2.28.4",
    "@kubb/swagger-ts": "^2.28.4",
    "@kubb/swagger-zod": "^2.28.4",

Where did you find it?

https://kubb.dev/knowledge-base/generators

Metadata

Metadata

Assignees

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions