Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Possible typescript issue with zod #1445

Closed
komali2 opened this issue Dec 11, 2024 · 2 comments · Fixed by #1462
Closed

Possible typescript issue with zod #1445

komali2 opened this issue Dec 11, 2024 · 2 comments · Fixed by #1462
Labels
bug Something isn't working

Comments

@komali2
Copy link

komali2 commented Dec 11, 2024

What version of kubb is running?

3.0.12

What kind of platform do you use?

Linux

How does your kubb.config.ts config look like

import { defineConfig } from "@kubb/core";
import { pluginTs } from "@kubb/plugin-ts";
import { pluginZod } from "@kubb/plugin-zod";
import { pluginReactQuery } from "@kubb/plugin-react-query";
import { pluginOas } from "@kubb/plugin-oas";

export default defineConfig({
  root: ".",
  input: {
    path: "../backend/openapi.json",
  },
  output: {
    path: "./src/gen",
    clean: true,
  },
  // hooks: {
  //   done: ["npm run lintfix", "npm run format"],
  // },
  plugins: [
    pluginOas(),
    pluginTs({
      output: {
        path: "models",
        barrelType: false,
      },
      group: {
        type: "tag",
        name: ({ group }) => `${group}Controller`,
      },
      enumType: "literal",
    }),
    pluginZod({
      output: {
        path: "./zod",
        barrelType: false,
      },
      group: {
        type: "tag",
        name: ({ group }) => `${group}Schemas`,
      },
      typed: true,
      dateType: "stringOffset",
      unknownType: "unknown",
      importPath: "zod",
    }),
    pluginReactQuery({
      output: {
        path: "./hooks",
        barrelType: false,
        banner: `
import * as _hydration from "@tanstack/query-core";
import * as _build from "@tanstack/react-query";
`,
      },
      group: {
        type: "tag",
        name: ({ group }) => `${group}Hooks`,
      },
      mutation: {
        methods: ["post", "put", "patch", "delete"],
      },
      infinite: {
        queryParam: "page",
        initialPageParam: 0,
        cursorParam: undefined,
      },
      query: {
        methods: ["get"],
        importPath: "@tanstack/react-query",
      },
      parser: "zod",
      pathParamsType: "object",
      suspense: false,
    }),
  ],
});

Swagger/OpenAPI file?

{
  "openapi": "3.1.0",
  "info": {
    "title": "Example API",
    "version": "0.1.1"
  },
  "paths": {
    "/steps": {
      "post": {
        "tags": [
          "Steps"
        ],
        "summary": "Create a step",
        "operationId": "create_step",
        "security": [
          {
            "OAuth2PasswordBearer": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CreateStep"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Successful Response",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Step"
                }
              }
            }
          },
          "422": {
            "description": "Unprocessable Entity",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "errors": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "properties": {
                          "code": {
                            "type": "string"
                          },
                          "field": {
                            "type": "array",
                            "items": {
                              "type": "string"
                            }
                          },
                          "message": {
                            "type": "string"
                          }
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Bad Request",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "errors": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "properties": {
                          "code": {
                            "type": "string"
                          },
                          "field": {
                            "type": "array",
                            "items": {
                              "type": "string"
                            }
                          },
                          "message": {
                            "type": "string"
                          }
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "message": {
                      "type": "string"
                    }
                  }
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "errors": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "properties": {
                          "code": {
                            "type": "string"
                          },
                          "field": {
                            "type": "array",
                            "items": {
                              "type": "string"
                            }
                          },
                          "message": {
                            "type": "string"
                          }
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "409": {
            "description": "Conflict",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "errors": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "properties": {
                          "code": {
                            "type": "string"
                          },
                          "field": {
                            "type": "array",
                            "items": {
                              "type": "string"
                            }
                          },
                          "message": {
                            "type": "string"
                          }
                        }
                      }
                    }
                  }
                }
              }
            }
          }
        }
      },
      "get": {
        "tags": [
          "Steps"
        ],
        "summary": "Find steps",
        "operationId": "find_steps",
        "security": [
          {
            "OAuth2PasswordBearer": []
          }
        ],
        "parameters": [
          {
            "name": "page",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer",
              "exclusiveMinimum": 0,
              "default": 1,
              "title": "Page"
            }
          },
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer",
              "exclusiveMinimum": 0,
              "default": 50,
              "title": "Limit"
            }
          },
          {
            "name": "sort",
            "in": "query",
            "required": false,
            "schema": {
              "anyOf": [
                {
                  "enum": [
                    "created_at",
                    "id",
                    "name",
                    "updated_at"
                  ],
                  "type": "string"
                },
                {
                  "type": "null"
                }
              ],
              "title": "Sort"
            }
          },
          {
            "name": "desc",
            "in": "query",
            "required": false,
            "schema": {
              "type": "boolean",
              "default": false,
              "title": "Desc"
            }
          },
          {
            "name": "name",
            "in": "query",
            "required": false,
            "schema": {
              "anyOf": [
                {
                  "type": "string"
                },
                {
                  "type": "null"
                }
              ],
              "title": "Name"
            }
          },
          {
            "name": "project_id",
            "in": "query",
            "required": false,
            "schema": {
              "anyOf": [
                {
                  "type": "integer",
                  "exclusiveMinimum": 0
                },
                {
                  "type": "null"
                }
              ],
              "title": "Project Id"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Successful Response",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Page_Step_"
                }
              }
            }
          },
          "400": {
            "description": "Bad Request",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "errors": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "properties": {
                          "code": {
                            "type": "string"
                          },
                          "field": {
                            "type": "array",
                            "items": {
                              "type": "string"
                            }
                          },
                          "message": {
                            "type": "string"
                          }
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "message": {
                      "type": "string"
                    }
                  }
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "errors": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "properties": {
                          "code": {
                            "type": "string"
                          },
                          "field": {
                            "type": "array",
                            "items": {
                              "type": "string"
                            }
                          },
                          "message": {
                            "type": "string"
                          }
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "404": {
            "description": "Not Found",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "message": {
                      "type": "string"
                    }
                  }
                }
              }
            }
          }
        }
      }
    },
    "/steps/{step_id}": {
      "get": {
        "tags": [
          "Steps"
        ],
        "summary": "Get a step",
        "operationId": "get_step",
        "security": [
          {
            "OAuth2PasswordBearer": []
          }
        ],
        "parameters": [
          {
            "name": "step_id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer",
              "exclusiveMinimum": 0,
              "title": "Step Id"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Successful Response",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Step"
                }
              }
            }
          },
          "400": {
            "description": "Bad Request",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "errors": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "properties": {
                          "code": {
                            "type": "string"
                          },
                          "field": {
                            "type": "array",
                            "items": {
                              "type": "string"
                            }
                          },
                          "message": {
                            "type": "string"
                          }
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "message": {
                      "type": "string"
                    }
                  }
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "errors": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "properties": {
                          "code": {
                            "type": "string"
                          },
                          "field": {
                            "type": "array",
                            "items": {
                              "type": "string"
                            }
                          },
                          "message": {
                            "type": "string"
                          }
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "404": {
            "description": "Not Found",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "message": {
                      "type": "string"
                    }
                  }
                }
              }
            }
          }
        }
      },
      "patch": {
        "tags": [
          "Steps"
        ],
        "summary": "Update a step",
        "operationId": "update_step",
        "security": [
          {
            "OAuth2PasswordBearer": []
          }
        ],
        "parameters": [
          {
            "name": "step_id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer",
              "exclusiveMinimum": 0,
              "title": "Step Id"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/UpdateStep"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Successful Response",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Step"
                }
              }
            }
          },
          "422": {
            "description": "Unprocessable Entity",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "errors": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "properties": {
                          "code": {
                            "type": "string"
                          },
                          "field": {
                            "type": "array",
                            "items": {
                              "type": "string"
                            }
                          },
                          "message": {
                            "type": "string"
                          }
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Bad Request",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "errors": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "properties": {
                          "code": {
                            "type": "string"
                          },
                          "field": {
                            "type": "array",
                            "items": {
                              "type": "string"
                            }
                          },
                          "message": {
                            "type": "string"
                          }
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "message": {
                      "type": "string"
                    }
                  }
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "errors": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "properties": {
                          "code": {
                            "type": "string"
                          },
                          "field": {
                            "type": "array",
                            "items": {
                              "type": "string"
                            }
                          },
                          "message": {
                            "type": "string"
                          }
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "404": {
            "description": "Not Found",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "message": {
                      "type": "string"
                    }
                  }
                }
              }
            }
          },
          "409": {
            "description": "Conflict",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "errors": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "properties": {
                          "code": {
                            "type": "string"
                          },
                          "field": {
                            "type": "array",
                            "items": {
                              "type": "string"
                            }
                          },
                          "message": {
                            "type": "string"
                          }
                        }
                      }
                    }
                  }
                }
              }
            }
          }
        }
      }
    },
  },
  "components": {
    "schemas": {
      "CreateStep": {
        "properties": {
          "description": {
            "type": "string",
            "maxLength": 1000,
            "minLength": 1,
            "title": "Description"
          },
          "name": {
            "type": "string",
            "maxLength": 80,
            "minLength": 1,
            "title": "Name"
          },
          "project_id": {
            "type": "integer",
            "exclusiveMinimum": 0.0,
            "title": "Project Id"
          }
        },
        "type": "object",
        "required": [
          "description",
          "name",
          "project_id"
        ],
        "title": "CreateStep"
      },
      "HTTPValidationError": {
        "properties": {
          "detail": {
            "items": {
              "$ref": "#/components/schemas/ValidationError"
            },
            "type": "array",
            "title": "Detail"
          }
        },
        "type": "object",
        "title": "HTTPValidationError"
      },
      "Page_Step_": {
        "properties": {
          "data": {
            "items": {
              "$ref": "#/components/schemas/Step"
            },
            "type": "array",
            "title": "Data"
          },
          "limit": {
            "type": "integer",
            "exclusiveMinimum": 0.0,
            "title": "Limit"
          },
          "more": {
            "type": "boolean",
            "title": "More"
          },
          "page": {
            "type": "integer",
            "exclusiveMinimum": 0.0,
            "title": "Page"
          }
        },
        "type": "object",
        "required": [
          "data",
          "limit",
          "more",
          "page"
        ],
        "title": "Page[Step]"
      },
      "Step": {
        "properties": {
          "created_at": {
            "type": "string",
            "title": "Created At"
          },
          "deleted_at": {
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "title": "Deleted At"
          },
          "id": {
            "type": "integer",
            "exclusiveMinimum": 0.0,
            "title": "Id"
          },
          "updated_at": {
            "type": "string",
            "title": "Updated At"
          },
          "description": {
            "type": "string",
            "maxLength": 1000,
            "minLength": 1,
            "title": "Description"
          },
          "name": {
            "type": "string",
            "maxLength": 80,
            "minLength": 1,
            "title": "Name"
          },
          "project_id": {
            "type": "integer",
            "exclusiveMinimum": 0.0,
            "title": "Project Id"
          }
        },
        "type": "object",
        "required": [
          "created_at",
          "deleted_at",
          "id",
          "updated_at",
          "description",
          "name",
          "project_id"
        ],
        "title": "Step"
      },
      "UpdateStep": {
        "properties": {
          "description": {
            "anyOf": [
              {
                "type": "string",
                "maxLength": 1000,
                "minLength": 1
              },
              {
                "type": "null"
              }
            ],
            "title": "Description"
          },
          "name": {
            "anyOf": [
              {
                "type": "string",
                "maxLength": 80,
                "minLength": 1
              },
              {
                "type": "null"
              }
            ],
            "title": "Name"
          }
        },
        "type": "object",
        "title": "UpdateStep"
      },
      "ValidationError": {
        "properties": {
          "loc": {
            "items": {
              "anyOf": [
                {
                  "type": "string"
                },
                {
                  "type": "integer"
                }
              ]
            },
            "type": "array",
            "title": "Location"
          },
          "msg": {
            "type": "string",
            "title": "Message"
          },
          "type": {
            "type": "string",
            "title": "Error Type"
          }
        },
        "type": "object",
        "required": [
          "loc",
          "msg",
          "type"
        ],
        "title": "ValidationError"
      }
    },
  }
}

What version of external packages are you using(@tanstack-query, MSW, React, Vue, ...)

{
    "name": "webapp2",
    "private": true,
    "version": "0.2.1",
    "type": "module",
    "scripts": {
        "dev": "vite --port=3000",
        "build": "vite build && tsc --noEmit",
        "serve": "vite preview",
        "start": "vite",
        "lintfix": "npx eslint --fix 'src/**'",
        "lint": "npx eslint 'src/**'",
        "typecheck": "tsc --noEmit --incremental",
        "format": "prettier  --write \"**/*.{ts,tsx,md}\" \"!**/docker/.volumes/postgres/**\"",
        "formatcheck": "prettier --check \"**/*.{ts,tsx,md}\" \"!**/docker/.volumes/postgres/**\"",
        "generate": "kubb generate"
    },
    "dependencies": {
        "@ag-grid-community/react": "^32.1.0",
        "@auth0/auth0-react": "^2.2.4",
        "@aws-sdk/client-ses": "^3.631.0",
        "@contentful/rich-text-plain-text-renderer": "^17.0.0",
        "@contentful/rich-text-react-renderer": "^16.0.0",
        "@headlessui/react": "^2.1.2",
        "@heroicons/react": "^2.1.5",
        "@internationalized/date": "^3.5.6",
        "@mantine/core": "^7.13.2",
        "@mantine/hooks": "^7.13.2",
        "@mantine/notifications": "^7.13.4",
        "@node-rs/argon2": "^1.8.3",
        "@olifog/troute": "^0.4.4",
        "@radix-ui/colors": "^3.0.0",
        "@radix-ui/react-accordion": "^1.2.1",
        "@radix-ui/react-aspect-ratio": "^1.1.0",
        "@radix-ui/react-hover-card": "^1.1.2",
        "@radix-ui/react-icons": "^1.3.0",
        "@radix-ui/react-toast": "^1.2.1",
        "@radix-ui/react-tooltip": "^1.1.3",
        "@tailwindcss/forms": "^0.5.7",
        "@tanstack/react-query": "^5.51.23",
        "@tanstack/react-router": "^1.82.2",
        "@tanstack/router-devtools": "^1.82.2",
        "@types/slug": "^5.0.9",
        "@uidotdev/usehooks": "^2.4.1",
        "@vercel/analytics": "^1.3.1",
        "@vercel/speed-insights": "^1.0.12",
        "ag-grid-community": "^32.1.0",
        "ag-grid-enterprise": "^32.2.1",
        "ag-grid-react": "^32.1.0",
        "arctic": "^1.9.2",
        "axios": "^1.7.8",
        "buffer": "^6.0.3",
        "class-variance-authority": "^0.7.0",
        "clsx": "^2.1.1",
        "contentful": "^11.2.0",
        "cuid": "^3.0.0",
        "d3": "^7.9.0",
        "jwt-decode": "^4.0.0",
        "lucia": "^3.2.0",
        "lucide-react": "^0.441.0",
        "openchemlib": "^8.15.0",
        "oslo": "^1.2.1",
        "plotly.js": "^2.35.2",
        "postcss": "^8.4.47",
        "postcss-preset-mantine": "^1.17.0",
        "postcss-simple-vars": "^7.0.1",
        "posthog-js": "^1.167.0",
        "posthog-node": "^4.2.0",
        "react": "^18.2.0",
        "react-aria-components": "^1.4.1",
        "react-autosuggest": "^10.1.0",
        "react-bootstrap-icons": "^1.11.4",
        "react-countup": "^6.5.3",
        "react-dom": "^18.2.0",
        "react-plotly.js": "^2.6.0",
        "react-resizable-panels": "^2.1.0",
        "redaxios": "^0.5.1",
        "simplebar-react": "^3.2.6",
        "slug": "^10.0.0",
        "stream-browserify": "^3.0.0",
        "tailwind-merge": "^2.5.2",
        "tailwind-variants": "^0.2.1",
        "tailwindcss": "^3.4.1",
        "tailwindcss-animate": "^1.0.7",
        "typescript": "^5.5.4",
        "xlsx": "https://cdn.sheetjs.com/xlsx-0.20.3/xlsx-0.20.3.tgz",
        "zod": "^3.23.8",
        "zustand": "^5.0.1"
    },
    "devDependencies": {
        "@eslint/js": "^9.15.0",
        "@kubb/core": "^3.0.12",
        "@kubb/plugin-client": "^3.0.14",
        "@kubb/plugin-oas": "^3.0.12",
        "@kubb/plugin-react-query": "^3.0.12",
        "@kubb/plugin-ts": "^3.0.12",
        "@kubb/plugin-zod": "^3.0.12",
        "@tanstack/router-plugin": "^1.81.9",
        "@types/d3": "^7.4.3",
        "@types/node": "^20.14.15",
        "@types/plotly.js": "^2.33.3",
        "@types/react": "^18.2.47",
        "@types/react-dom": "^18.2.18",
        "@types/react-plotly.js": "^2.6.3",
        "@vitejs/plugin-react": "^4.3.3",
        "eslint": "^9.15.0",
        "eslint-plugin-react": "^7.37.2",
        "globals": "^15.12.0",
        "kubb": "^3.0.12",
        "prettier": "^3.3.3",
        "typescript-eslint": "^8.15.0",
        "vite": "^5.4.11"
    }
}

What steps can reproduce the bug?

  1. Generate zod models using the provided config. Here's an example zod model:
import type { ExperimentContinuousInput } from "../models/ExperimentContinuousInput.ts";
import { z } from "zod";

export const experimentContinuousInputSchema = z.object({
  created_at: z
    .string()
    .describe("ISO 8601 formatted date and time the data was created"),
  deleted_at: z.union([z.string(), z.null()]),
  id: z.number().int().describe("Unique ID"),
  updated_at: z
    .string()
    .describe("ISO 8601 formatted date and time the data was last updated"),
  value: z
    .string()
    .describe("Integer or decimal value represented as a string"),
  continuous_input_id: z
    .number()
    .int()
    .describe("Associated continuous input ID"),
  experiment_id: z.number().int().describe("Associated experiment ID"),
  project_id: z.number().int().describe("Associated project ID"),
  step_id: z.number().int().describe("Associated step ID"),
}) as z.ZodType<ExperimentContinuousInput>;

Note the as z.ZodType<SomeModel>.

  1. Try to access a method that definitely exists, such as pick. Observe a typescript error.
experimentContinuousInputSchema.pick({ value: true });
  1. Confirm the typescript error is fake by successfully using the method.

I suppose this could be an upstream issue with zod as well. Or, I'm misusing one of the libraries.

How often does this bug happen?

Every time

What is the expected behavior?

I would expect the type coercion to carry over all expected methods and properties of a zod schema.

Additional information

No response

@komali2 komali2 added the bug Something isn't working label Dec 11, 2024
Copy link

linear bot commented Dec 11, 2024

@stijnvanhulle
Copy link
Collaborator

When using typed, we add as z.ZodType<TYPE>. This will work fine for most Zod schemas but not every schema. I saw this discussion in the Zod repo: colinhacks/zod#372 (comment).

@colinhacks made a library called toZod that could be used in Kubb to bind the types and schemas. I will add this in the next version of Kubb.

@stijnvanhulle stijnvanhulle linked a pull request Dec 14, 2024 that will close this issue
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug Something isn't working
Projects
None yet
Development

Successfully merging a pull request may close this issue.

2 participants