sinclairzx81 / typebox

Json Schema Type Builder with Static Type Resolution for TypeScript
Other
4.77k stars 152 forks source link

Property '[Kind]' is missing in type 'TObject' but required in type 'TObject<TProperties>' #762

Closed divmgl closed 6 months ago

divmgl commented 6 months ago

I'm exporting a simple Typebox type from one package to another and I'm getting an error. I have Typebox installed at the monorepo level so the versions are the same.

// @server/schemas/LoginSchema.ts

import { Static, Type } from "@sinclair/typebox"

export const LOGIN_SCHEMA = Type.Object({
  email: Type.String(),
  password: Type.String()
})

export const REGISTER_SCHEMA = Type.Object({
  email: Type.String(),
  password: Type.String(),
  confirmPassword: Type.String()
})

export type LoginSchema = Static<typeof LOGIN_SCHEMA>
// @client/LoginPage.tsx
import { LOGIN_SCHEMA, LoginSchema } from "@server/schemas/LoginSchema"

const { handleSubmit, register } = useForm<LoginSchema>({
  resolver: typeboxResolver(LOGIN_SCHEMA)
})

LoginSchema is raising a compiler error:

Argument of type 'TObject<{ email: TString; password: TString; }>' is not assignable to parameter of type 'TObject<TProperties>'.
  Property '[Kind]' is missing in type 'TObject<{ email: TString; password: TString; }>' but required in type 'TObject<TProperties>'.ts(2345)

Unsure what to do here... It started happening out of nowhere. My tsconfig.json:

@server/tsconfig.json
{
  "compilerOptions": {
    "outDir": "build",
    "moduleResolution": "node",
    "target": "ESNext",
    "types": ["node"],
    "allowSyntheticDefaultImports": true,
    "skipLibCheck": true,
    "isolatedModules": true,
    "paths": {
      "@/*": ["./src/*"]
    },
    "composite": true
  },
  "include": ["**/*.ts"],
  "exclude": ["node_modules", "build"]
}
@client/tsconfig.json
{
  "compilerOptions": {
    "target": "ES2020",
    "useDefineForClassFields": true,
    "lib": ["ES2020", "DOM", "DOM.Iterable"],
    "module": "ESNext",
    "skipLibCheck": true,

    /* Bundler mode */
    "moduleResolution": "bundler",
    "allowImportingTsExtensions": true,
    "resolveJsonModule": true,
    "isolatedModules": true,
    "noEmit": true,
    "jsx": "react-jsx",

    /* Linting */
    "strict": true,
    "noUnusedLocals": true,
    "noUnusedParameters": true,
    "noFallthroughCasesInSwitch": true,

    "baseUrl": ".",
    "paths": {
      "@/*": ["./src/*"],
      "@server/*": ["./node_modules/@server/src/*"]
    }
  },
  "include": ["src", "../server/src/schemas"],
  "exclude": ["node_modules", "build", "dist"],
  "references": [{ "path": "../server/tsconfig.json" }]
}
divmgl commented 6 months ago

Declaring LOGIN_SCHEMA in both packages solves the issue. Why would this object differ between packages if they're both using the same version of Typebox?

sinclairzx81 commented 6 months ago

@divmgl Hi,

It's fairly common to put TB types in a shared common package, and import that package into client and server packages. There shouldn't be an issue doing this, however mileage may vary depending on the tooling you use to manage the mono repo. In short, you shouldn't need to replicate the LOGIN_SCHEMA in client and server, but you may want to investigate setting up the shared package.

Generally, when I approach mono repo setups, I don't use composite: true, rather I make use of TypeScript's baseUrl and paths configurations. These configurations live in a tsconfig.json sitting in the project root. To build projects, I typically use esbuild (which understands baseUrl and paths), however more sophisticated tooling such as NX also understands TS path aliasing also.

{
  "compilerOptions": {
    "strict": true,
    "module": "ESNext",
    "target": "ESNext",
    "baseUrl": ".",                                    // tsconfig.json in the root directory
    "paths": {                                         // ... and references these packages with aliases

      "@app/shared": ["./packages/shared/index.ts"],   // type library goes here (i.e. typebox)
      "@app/client": ["./packages/client/index.ts"],   // client library goes here (i.e. react)
      "@app/server": ["./packages/server/index.ts"],   // server library goes here (i.e. express)
    }
  }
}

// ... with the types imported inside the client and server library with.

import { LOGIN_SCHEMA } from '@app/shared'

Posting the above in case it helps, but will close off this issue as it seems more related to project configuration and tooling than TypeBox.

Cheers! S

divmgl commented 6 months ago

While it doesn't necessarily solve the issue I'm having, I think this is an interesting solution to other problems that I'm having in our codebase. So I'll give this all a shot, thanks.