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

generate 'schema' and 'ts interface' in one swoop #25

Closed
chase-moskal opened this issue Jun 17, 2020 · 7 comments
Closed

generate 'schema' and 'ts interface' in one swoop #25

chase-moskal opened this issue Jun 17, 2020 · 7 comments

Comments

@chase-moskal
Copy link

i'm trying to eliminate maintenance. ALL OF IT!

i was wondering if you have a recommendation for a good way i could generate both a table schema and also a matching typescript interface at the same time, or at least make sure these are synced up in lock-step

also, looking at the readme, i'm not clear: are functions like query using typescript generics? can we pass in a the type signature for the result, so that the results come back with full typings?

i'm imagining some idealized situation like along these lines (using my imaginary sugar from issue #24 :)

const userSchema = tableSchema({
  id: {primary: true, unique: true, type: String},
  email: {type: String},
  nickname: {type: String},
})

type UserRow = typeof userSchema
  // type is generated automatically, {id: string; email: string; nickname: string}

const user = first(await query<UserRow>`SELECT * FROM table WHERE column = ${5}`)
  // user is typed as UserRow

do you have any advice, or am i wandering into typeorm territory?

thanks 🍻

@chase-moskal
Copy link
Author

maybe i can make something happen if i flip this upside down, eh?

interface UserRow {
  id: string
  email: string
  nickname: string
}

const userSchema = tableSchema<UserRow>({
  id: {primary: true, unique: true, type: String},
  email: {type: String},
  nickname: {type: String},
})

that could almost be done.. but i don't think we can get the compiler to enforce the type: String's to match the UserRow field types..

let me know if i'm barking up the wrong tree here, just curious about your thoughts and opinions

@malthe
Copy link
Owner

malthe commented Jun 17, 2020

There isn't much support in TypeScript for reflection and I believe the creators of the language have even said that it was a mistake to even upon up the slightest for it (people will always want more, further complicating the language and how its used).

But it's a nice idea and perhaps the winds are changing. Last time I looked, it was simply not feasible without tooling for pre-processing.

@chase-moskal chase-moskal changed the title generate schema ts interface in one swoop generate 'schema' and 'ts interface' in one swoop Jun 18, 2020
@chase-moskal
Copy link
Author

chase-moskal commented Jun 18, 2020

But it's a nice idea and perhaps the winds are changing. Last time I looked, it was simply not feasible without tooling for pre-processing.

i think it may be doable using conditional types.. i'll see if i can look into it!

at the very least we should leverage generics so we can type the query results, we can certainly do that much today

// bad: without a generic type
await query`select * from users where balance >= ${minimum}`
  //> returned type: any

// good: we should allow an optional generic type
await query<UserRow>`select * from users where balance >= ${minimum}`
  //> returned type: UserRow[]

of course that's my sugar, but the core client.query(...) should also leverage generics such that client.query<UserRow>(...) is possible

i'll keep looking into about whether conditional types can allow us to provide a single source of truth for both schema and interface.. or at the very least, if we have to define it two ways, at least have the typescript compiler enforce a match. cheers 🍻

@malthe
Copy link
Owner

malthe commented Jun 18, 2020

But using generics this way is not type-safe; why not just:

await query`select ...` as UserRow.

If we wanted a more strict typing mechanism, columns would also need to be typecast on the query side (i.e. "select a::int, b::text") and this requires a schema-aware query builder. That is, we would need some interface reflection and code generation or metaprogramming to pull this off.

@chase-moskal
Copy link
Author

chase-moskal commented Jun 18, 2020

But using generics this way is not type-safe;

i'm not sure what you mean, that generics aren't type-safe? it doesn't matter anyways:

why not just: as UserRow

i think i realize that you're right! it's simpler and there's no benefit to the generic in this case!

i still have hopes i can leverage some fancy fancy typescript advanced features to create some kind of 1:1 enforcement between a typescript interface and the relevant schema. i think there will still be repetition, but i hope we can at least achieve compiler enforcement of consistency. i'll let you know how it goes!

i'm also going to see if i can implement this tagged-template sugar :)

cheers!

@malthe
Copy link
Owner

malthe commented Jun 19, 2020

In languages that provide compile-time metaprogramming such as Rust, you can easily write a macro to accomplish this. But that's still an open issue.

cheers

@malthe
Copy link
Owner

malthe commented Apr 12, 2024

Since you can now provide a type for query, this is probably as good as we can get without further advances in TypeScript and/or tooling around it.

@malthe malthe closed this as completed Apr 12, 2024
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