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

A way to merge with existing types #42

Closed
toddheslin opened this issue Nov 4, 2020 · 5 comments
Closed

A way to merge with existing types #42

toddheslin opened this issue Nov 4, 2020 · 5 comments

Comments

@toddheslin
Copy link

Firstly, I'm sorry if this is a newbie question. I'm a relatively new convert to typescript and still figuring my way around.

I'm currently using genql to generate a typescript lib from my GraphQL server. I'd like to import that definition into a typebox object (if this is even possible). Here's an example:

import { Static, Type } from '@sinclair/typebox'
import { currencies_enum } from '@data/hasura'

export type TBody = Static<typeof body>
const body = Type.Object({
  message: Type.String(),
  full_name: Type.String(),
  preferred_name: Type.String(),
  email: Type.String(),
  phone: Type.String(),
  password: Type.String(),
  currency: currencies_enum,
})

Where currencies_enum looks like this:

export type currencies_enum = 'AED' | 'AUD' | 'BRL' | 'CAD' | 'CHF' | 'CLP' | 'CNY' | 'CZK' | 'EUR' | 'GBP' | 'HKD' | 'IDR' | 'INR' | 'JPY' | 'KZT' | 'MOP' | 'MXN' | 'MYR' | 'NAD' | 'NGN' | 'NOK' | 'NZD' | 'PHP' | 'PKR' | 'PLN' | 'QAR' | 'RON' | 'RUB' | 'SAR' | 'SEK' | 'SGD' | 'THB' | 'TOP' | 'TRY' | 'TWD' | 'UAH' | 'USD' | 'VND' | 'VUV' | 'XAF' | 'ZAR'

Is there a function I can use to merge that currencies_enum type into the typebox representation? My primary reason for this is such that I can spit out a consistent typescript and json schema representation of the same type.

Thanks in advance. Any insight here would be a learning experience for me. 🙇🏻‍♂️

@sinclairzx81
Copy link
Owner

sinclairzx81 commented Nov 4, 2020

@toddheslin Hi,

TypeBox provides are a couple of ways to approach this. The recommended way would be to leverage Type.Union([...]) with Type.Literal('...') types (as this seems to reflect your example code), however a Type.Enum(...) would also be possible. Both approaches shown below.

Type.Union([...])

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

// export type currencies_enum = 'AED' | 'AUD' | 'BRL' | 'CAD' | ...
export type CurrencyKind = Static<typeof CurrencyKind> // hover to inspect type.
export const CurrencyKind = Type.Union([
    Type.Literal('AED'),
    Type.Literal('AUD'),
    Type.Literal('BRL'),
    Type.Literal('CAD'),
    // ... and so on
])

export type Body = Static<typeof Body> // hover to inspect type.
const Body = Type.Object({
  message:        Type.String(),
  full_name:      Type.String(),
  preferred_name: Type.String(),
  email:          Type.String({ format: 'email' }),
  phone:          Type.String(),
  password:       Type.String({ minLength: 8 }),
  currency:       CurrencyKind,
})

// example
const body: Body = {
    currency:       'AED',
    email:          'dave@domain.com',
    full_name:      'dave',
    preferred_name: 'dave',
    phone:          '12345678',
    password:       'password',
    message:        'hello'
}

Type.Enum(...)

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

export enum CurrencyEnum {
    AED = 'AED',
    AUD = 'AUD',
    BRL = 'BRL',
    CAD = 'CAD',
    // ... and so on
}
export type Body = Static<typeof Body> // hover to inspect type.
const Body = Type.Object({
  message:        Type.String(),
  full_name:      Type.String(),
  preferred_name: Type.String(),
  email:          Type.String({ format: 'email' }),
  phone:          Type.String(),
  password:       Type.String({ minLength: 8 }),
  currency:       Type.Enum(CurrencyEnum),
})

// example
const body: Body = {
    currency:       CurrencyEnum.AED,
    email:          'dave@domain.com',
    full_name:      'dave',
    preferred_name: 'dave',
    phone:          '12345678',
    password:       'password',
    message:        'hello'
}

Just one quick thing, It's wouldn't be possible for TypeBox to infer a JSON Schema from the existing currencies_enum imported from @data/hasura as TypeScript (and by extension, TypeBox) doesn't provide any mechanism to generate the applicable schema object from an existing TS type. For this reason, you will need to replicate the currencies_enum type as a TypeBox type to get both TS type and JSON schema. So long as the TypeBox type and the currencies_enum line up. Things should be ok.

Hope this helps
S

@toddheslin
Copy link
Author

Hey @sinclairzx81 thanks for the speedy and super detailed response. I really appreciate it.

My problem is that the current currency_enum is auto generated from my Hasura graphql endpoint. That means it is frequently regenerated as we add new currencies. I'd like to avoid having to keep a separate type definition if possible.

Let's say that I choose to go with the union of string literals, is there a way I can reuse the existing type definition that is generated into currency_enum type back into Type.Union([ Type.Literal('AED')...]?

@sinclairzx81
Copy link
Owner

@toddheslin Hi.

Unfortunately, this currently isn't possible. TypeScript doesn't provide any mechanisms to turn a TS type (for example type T = 'A' | 'B') into a runtime type representation (for example { type: 'string', enum: ['A', 'B'] }). But it's a much requested feature.

Libraries like TypeBox, io-ts and runtypes work around this by providing type builder API's that let developers write Types in a different way as to be able to embed the type information in the JS while leveraging TypeScript's type mapping facilities to turn that back into a static type, but it doesn't really solve the case where you have an existing type and want runtime type info from it.

Here's a few links related to this topic.

As far as leveraging TypeBox for your use case, you would need to replicate the currency_enum as a Type.Union([..]) (which is a pain, i know).

Hope this helps
S

@sinclairzx81
Copy link
Owner

Closing this one off.

@toddheslin
Copy link
Author

Thanks so much for your detailed reply @sinclairzx81, hope this is useful for someone else too.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants