[HN Gopher] Show HN: PgTyped - Typesafe SQL in TypeScript and Po...
       ___________________________________________________________________
        
       Show HN: PgTyped - Typesafe SQL in TypeScript and Postgres
        
       Author : alde
       Score  : 181 points
       Date   : 2020-05-25 16:29 UTC (6 hours ago)
        
 (HTM) web link (github.com)
 (TXT) w3m dump (github.com)
        
       | hombre_fatal wrote:
       | Almost every top-level comment is someone shilling another
       | project, usually in Golang as if that's even related.
       | 
       | Let's have some Show HN etiquette.
        
       | shrumm wrote:
       | Looks a little like the Typescript equivalent of Xo
       | (https://github.com/xo/xo) for Go. Especially with Go, getting
       | help with some initial scaffolding can be a huge timesaver. I'm
       | assuming it's a similar gain for Typescript.
        
         | tadasv wrote:
         | A better equivalent in Go is https://github.com/kyleconroy/sqlc
        
           | shrumm wrote:
           | thanks! I'll try this - one of my pet peeves with Xo is
           | handling nullable types and more advanced types like JSONB
           | required editing the generated code significantly to make it
           | work. Hopefully sqlc solves that.
        
       | Vinnl wrote:
       | Lots of comments here about similar projects in a different
       | language, but the fact that this targets TypeScript is explicitly
       | what makes it interesting to me. Using regular Javascript
       | database libraries, even ones that have type definitions, require
       | a lot of double typing.
       | 
       | I've been relatively satisfied with TypeORM, but one thing that's
       | been a hurdle for me to some extent is its reliance on
       | experimental decorators, and the resulting incompatibility with
       | Babel - which in turn makes it harder to integrate with the wider
       | ecosystem, e.g. Next.js.
       | 
       | As far as I can see on first glance, there's nothing here yet
       | that makes it incompatible with Babel, so my tip would be to make
       | it an explicit goal to keep it that way :)
        
         | smallnamespace wrote:
         | As usual for Babel, there's a plugin for that:
         | https://github.com/leonardfactory/babel-plugin-transform-typ...
        
         | mikewhy wrote:
         | Opening a can of worms for sure, but the reliance on Babel in
         | the JS community is not a good thing to me. It's another reason
         | why I prefer TypeScript, as in TSC, not whatever equivalent
         | babel happens to support.
        
       | zelly wrote:
       | Is there anything like this for Rust or C++?
       | 
       | I like the idea of code generation instead of doing the work at
       | runtime (like in ORMs). This is like making your database schema
       | the IDL spec.
        
         | K0nserv wrote:
         | There's Diesel[0] for Rust which is a full ORM. It's by Sian
         | Griffin[1] who, as I understand it, is also behind a lot of how
         | rail's ActiveRecord works.
         | 
         | 0: https://diesel.rs/
         | 
         | 1: https://twitter.com/sgrif
        
           | status_quo69 wrote:
           | Just to clarify a bit for other readers since I've worked
           | with diesel for while, diesel isn't a "full" orm, as there
           | are no real helpers provided to you outside of "we can map
           | the result of a db query into a struct(s) that you specify"
           | and some really nice guarantees for compile time queries.
           | Other than that, your struct is a pretty dumb mapped
           | representation and it's on the implementers of the
           | application code to provide sugar for better access patterns.
           | For people coming from something like active record, this is
           | (in my opinion) closer to Arel than ActiveRecord, or closer
           | to sqlalchemy core than sqlalchemy orm. As an example, you
           | won't necessarily be able to do `MyStruct.join(OtherStruct)`
           | and have it magically figure out how to query the database
           | and map the results out of the box.
        
             | status_quo69 wrote:
             | Clarification: compile time query building, not querying.
             | Due to inlining from the compiler, you can almost entirely
             | construct the query at compile time and shave it down to a
             | few string concatenations.
        
         | aganame wrote:
         | https://github.com/launchbadge/sqlx perhaps
         | 
         | Also hugsql for clojure and pugsql for python.
        
           | alde wrote:
           | Sqlx looks nice! I had some ideas about porting pgtyped to
           | rust and utilizing macros to do query type inference at build
           | time, but was worried that such db-connected macros will slow
           | down the build process. Nice to see that it worked out for
           | sqlx.
        
       | emanuelez wrote:
       | You might also want to check Kanel out!
       | https://github.com/kristiandupont/kanel
        
       | garrybelka wrote:
       | How is it different from Slonik? https://github.com/gajus/slonik
        
       | SenpaiHurricane wrote:
       | lol. This reminds me Hibernate xml mappings :D
        
       | JBReefer wrote:
       | So basically Dapper TS? I'm very interested!
        
       | tonyhb wrote:
       | This is similar to sqlc for Golang:
       | https://github.com/kyleconroy/sqlc
       | 
       | If you're looking for the ability to generate type-safe SQL -
       | given you write SQL correctly - this project is pretty good.
       | 
       | Aalso a fan of SQLBoiler
       | (https://github.com/volatiletech/sqlboiler) for Golang, for
       | simple type safety:
       | 
       | `models.Accounts(models.AccountWhere.ID.EQ(id)).One(ctx, db)`.
       | 
       | Though SQLBoiler breaks with left joins, as it auto-generates
       | your structs and maps results 1-1 with table definitions. In this
       | case you have to custom type something, either using sqlc or
       | squirrel.
        
       | ksashikumar wrote:
       | Looks cool! And the header image looks awesome! Did you use any
       | tool to do it?
        
         | alde wrote:
         | Thanks! Not really, just basic vector shapes and an isometric
         | projection grid to make sure perspective is right.
        
       | the_duke wrote:
       | There are some similar projects, like sqlx [1] for Rust. My
       | problem with these is that they don't help to solve the actually
       | hard problems.
       | 
       | While nice to have, preventing bugs with static SQL is usually
       | easy to do by writing a few tests. Most of the SQL related bugs I
       | have encountered were due to queries with dynamic/conditional
       | joins, filters and sorting - and almost every project using a
       | database needs those.
       | 
       | Approaches like this don't help there. That requires heavy-weight
       | solutions that are more cumbersome to use and need a strong type
       | system, like diesel [2] (Rust), Slick [3] (Scala) and some
       | similar Haskell projects.
       | 
       | [1] https://github.com/launchbadge/sqlx
       | 
       | [2] https://github.com/diesel-rs/diesel
       | 
       | [3] https://scala-slick.org/
        
         | sradman wrote:
         | Actually, after my quick scan of the readme, I think pgTyped
         | takes a different approach than the other tools listed. It is a
         | YeSQL-style tool with build-time code generation. The code
         | generation is based on prepared statement metadata rather than
         | table metadata. Like other YeSQL tools, the function name comes
         | from a DocComment annotation in the .sql but the query params
         | and the result set columns come from the prepared statement.
        
         | soulnothing wrote:
         | I'd be curious to hear more about the issues with joins, and
         | dynamic conditionals. I've been working on a type provider lib
         | for sql in kotlin[1].
         | 
         | The join problems I've seen are either the joined table has
         | changed, or altered. The return type of a field may change.
         | 
         | The hard problem I encountered was doing things like json aggs,
         | multiple joins, etc. I'm trying to address this by doing type
         | safe aggregate/join functions. Secondly is query compilation.
         | Compiling the output record of advanced queries into an
         | automatic data class.
         | 
         | 1: https://gitlab.com/AnimusDesign/kotlin-frm
        
         | rubber_duck wrote:
         | > preventing bugs with static SQL is usually easy to do by
         | writing a few tests
         | 
         | I've heard the same argument about TypeScript vs JavaScript and
         | it's something dynamic typing proponents often say but in
         | practice I find immense value in having the types autocompleted
         | and checked in the editor - and I've worked plenty on both
         | sides, current project is substantial RoR codebase, I've worked
         | with Python and node.js backends on mature codebases.
         | Eventually all these languages have some sort of static type
         | hinting efforts to improve tooling - typescript being most
         | successful.
         | 
         | The best thing I saw in this space was F# type providers which
         | didn't require a pre-build step - the language had a mechanism
         | for writing custom type providers that would look up the data
         | source during compilation - unfortunately I didn't get to use
         | it on any real world projects.
        
         | alde wrote:
         | ORMs like Diesel are definitely very useful. The problem I have
         | with them is that their ORM abstraction often leaks. Fixing
         | these abstraction leaks is a hard problem [1]. Ofc, there has
         | been attempts to reconcile relational DBs with OOP like
         | languages, but they are not very popular. [2]
         | 
         | PgTyped and some similar libs try to solve a simpler problem
         | (typing static queries) and can be used to build more complex
         | solutions when needed.
         | 
         | Writing query result/param type assertions by hand and using
         | tests to guarantee type synchronization between DB and code
         | wasn't maintainable on most projects I have seen.
         | 
         | [1] https://en.wikipedia.org/wiki/Object-
         | relational_impedance_mi...
         | 
         | [2] https://en.wikipedia.org/wiki/The_Third_Manifesto
        
         | Dowwie wrote:
         | While the scenario you present _is_ a legitimate reason for
         | using a query builder, it doesn 't justify the investment in
         | using a query builder for the vast majority of db calls. The
         | edge case is not an appropriate goal to impose the cost of
         | using a DSL for the majority of cases. Instead, custom roll an
         | implementation for those complicated calls and stick with a
         | parameterized sql library.
        
       | conroy wrote:
       | As a maintainer of a similar project[0], it's great to see
       | another entry in this space.
       | 
       | sqlc currently has great support for Go and experimental support
       | for Kotlin. I'm planning on adding TypeScript support in the
       | future, so it's great to see that others in the TypeScript
       | community find this workflow useful.
       | 
       | [0] https://github.com/kyleconroy/sqlc
        
       | Allezxandre wrote:
       | My favorite SQL library has been Go-Jet in Go:
       | https://github.com/go-jet/jet
       | 
       | It has a different approach from PgTyped, which generates type-
       | safe TypeScript code from SQL, whereas Go-Jet generates type-safe
       | SQL from Go code
       | 
       | I'd love to try something along the lines of PgTyped and see how
       | the two solutions compare though
        
       | eyelidlessness wrote:
       | I came here to mention a similar approach, which last time I
       | looked was a very compelling experiment[1], but its original
       | author has actually built out a real library, Zapatos[2] which
       | looks very very good.
       | 
       | [1]: https://github.com/jawj/mostly-ormless
       | 
       | [2]: https://jawj.github.io/zapatos/
        
         | alde wrote:
         | Looks like Zapatos still requires the user to manually specify
         | param/result types for custom SQL queries?
        
           | gmac wrote:
           | Zapatos author here. Yes, it does. But for most of what you'd
           | use an ORM for, you probably won't need custom queries.
        
             | eyelidlessness wrote:
             | Hey I'm not sure this is the best venue, but I'm trying to
             | make the case for getting my org off of sequelize, and your
             | library is right in line with my goals. The hardest sell is
             | going to be publicly visible test coverage. Would you
             | welcome a dedicated effort from an early adopter to
             | introduce tests?
        
       | renke1 wrote:
       | Looks pretty cool. What I really want though is a library that
       | let's me write plain SQL queries which are then mapped into
       | nested objects in a smart way without too much manual work (I
       | know Postgres can do JSON stuff, but the queries look pretty
       | complicated for what little they actually do).
       | 
       | Say `SELECT * FROM user LEFT JOIN post ON user.id = post.id`
       | would be mapped to `[{userId: 1, name: renke1, posts: [{postId:
       | 2, title: "foo"]]`.
       | 
       | You probably need some kind of meta data to figure out how tables
       | and thus objects relate to each other though.
       | 
       | Basically, I want to be able to leverage the full power of modern
       | databases without being constrainted by typical ORM limitations.
       | Also, I don't need features like lazy loading, sessions, caches
       | and things like that.
       | 
       | A great advantage is that you can (provided you have some test
       | data) easily test your queries while you develop a new feature
       | (think IntelliJ IDEA where you can simply execute an SQL query on
       | the fly).
        
         | timmy-turner wrote:
         | Writing literal SQL in JS would IMO need more tools than just a
         | preprocessor like you describe.
         | 
         | The few times I tried it (mostly in tests to check that the ORM
         | is working properly) the #1 thing I was missing is a prettier-
         | plugin that automatically formats SQL in the same way it
         | currently works for `html` tagged templates.
         | 
         | I completely agree to the 'constrained by ORM' and 'useless
         | features' part though. Postgres `json_agg` is a godsend and I
         | love to be able to reason over simple joins and queries.
         | 
         | BTW, my own approach to use `json_agg`, `json_build_object` and
         | json columns within a typesafe query-building DSL is this:
         | https://github.com/hoeck/typesafe-query-builder
         | 
         | But its mostly for replacing simple ORM fetches, it wont do
         | complex analytical queries. For that I'd like to write SQL
         | directly as query-DSLs tend to quickly stop being usable in
         | that situation.
        
           | [deleted]
        
           | cyral wrote:
           | One of my favorite features of WebStorm is the (official)
           | database plugin which highlights SQL queries inside JS
           | strings AND has autocomplete and refactoring support that
           | actually uses the live database schema.
        
         | hyeomans wrote:
         | You never mentioned the language, so maybe Dapper?
         | https://github.com/StackExchange/Dapper/
        
         | chatmasta wrote:
         | We've accomplished this with a combination of postgraphile and
         | graphql-codegen. Our setup is basically the same as the
         | postgraphile starter app. [0]
         | 
         | It's pretty awesome.
         | 
         | [0] https://github.com/graphile/starter
        
         | tlarkworthy wrote:
         | It exists! You want Scala Slick plain SQL http://scala-
         | slick.org/doc/3.0.0/sql.html
         | 
         | See tsql string interpolation with typed result sets!
        
         | raziel2p wrote:
         | You'd be constrained by whatever is trying to parse SQL queries
         | to figure out what object structure to return instead.
        
         | bijection wrote:
         | If you were willing to give up a bit of magic, you could
         | probably build this as a thin layer over PgTyped.
         | 
         | The API could be something like this:
         | 
         | Query.sql                 SELECT * FROM user LEFT JOIN post ON
         | user.id = post.id
         | 
         | Application.ts                 const results = await Query()
         | const nested = nest(results, {         parentFields: ['userId',
         | 'name'],         childFields: ['postId', 'title'],
         | childName: 'posts'       )
         | 
         | If you wanted, you wouldn't really have to specify child
         | fields, since they'd just whatever wasn't a parent field. It'd
         | take a bit more work to get it to do multiple levels of
         | nesting, but after a point it doesn't make sense to write
         | queries that return so much duplicate data anyway.
        
         | alde wrote:
         | Thanks! A grouping feature will definitely be useful, I have
         | been thinking about a good way to add it to pgtyped.
         | 
         | Will grouping fields by tables they belong to good enough? Or
         | is there some different grouping logic you have in mind?
        
           | renke1 wrote:
           | That's hard to say, because I am not sure what I really want.
           | 
           | ...but let's say I have this result (from an arbitrary
           | query).                 | user.id | user.name | post.id |
           | post.title |
           | +---------+-----------+---------+------------+       | 1
           | | renke1    | 1       | first      |       | 2       | alde
           | | 2       | second     |       | 2       | alde      | 3
           | | third      |
           | 
           | Now I would like to tell the library: hey, an user can have
           | many posts (1:n), please map this to nested objects.
           | 
           | Of course I don't want to write `SELECT user.id, user.name
           | ... FROM ...` but just `SELECT * FROM ...` (because a table
           | may have a dozen of columns and I don't want to spell out
           | every single one). So the query might have to be rewritten
           | on-the-fly to make the correct projection (otherwise it would
           | be hard to know to which object a value belongs).
           | 
           | I am not sure if that's something your library should do
           | though.
           | 
           | And thinking even more about it, I think this approach
           | wouldn't really work for views (and probably other things)
           | where it's not really clear from which tables the data
           | actually comes from (at least not by only looking at the
           | query).
           | 
           | I guess what I really want is library that takes my SQL
           | query, reads my mind and gives me back some nested objects...
           | and let's not talk about inserts...
        
             | gigatexal wrote:
             | but select * is an anti-pattern. SQL queries should only
             | return the columns that you need not that you might need --
             | usually. Lazy loading and sessions etc., like SQLAlchemy
             | does to get around the N+1 query problem be damned.
        
               | renke1 wrote:
               | I didn't mean the `SELECT STAR` in a literal sense, but
               | more like, please select only the stuff we need. But
               | indeed with PgTyped, as far as I understand it, it
               | wouldn't work because it creates interface from SQL
               | queries.
               | 
               | To achieve what I want you need to do it like all the
               | other ORMs where you have some kind of description of
               | your model and how it maps to tables and columns. Unlike
               | the usual ORM I want to write SQL queries which are then
               | rewritten in an intelligent manner.
               | 
               | So something like query(`SELECT STAR FROM user LEFT JOIN
               | post ...`, UserWithPostsModel). Since the library would
               | know the target model, it could rewrite the `SELECT STAR`
               | to something that only asks for the data it needs.
               | 
               | In other words I want to execute arbitrary queries that a
               | mapped into ad-hoc models (unlike typical ORMs where the
               | model usually maps directly to tables).
               | STAR = *
        
               | gigatexal wrote:
               | Ahh I understand you now.
        
         | garrybelka wrote:
         | GrahpQL might be an answer.Though an incomplete answer at this
         | point due to a mismatch between plain GraphQL and SQL;
         | 
         | And the real issue is how to define and where to place a single
         | source of truth for the schema an operations. So far we saw
         | approaches where:
         | 
         | - GraphQL schema is generated from SQL tables. Makes total
         | sense for a project or a company that looks to capitalize on
         | customers with existing databases (e.g., PostGraphile, Hasura);
         | 
         | - SQL schema is generated from a GraphQL schema;
         | 
         | - SQL schema and TypeScript CRUD resolvers are generated from
         | GraphQL schema (graphback);
         | 
         | - a language is introduced and GraphQL and SQL are generated
         | from that language (Prisma);
         | 
         | - a library and a set of decorators are used to define both
         | GraphQL schema and a typed ORM schema within a standard
         | language (e.g, TypeGraphQL + TypeScript + some ORM such as
         | TypeORM).
        
         | chrischen wrote:
         | See https://typeorm.io/#/
        
           | eyelidlessness wrote:
           | I mean no disrespect to the TypeORM author(s), but I was
           | burned several times by unexpected behavior that resulted in
           | serious data integrity issues. I would caution that anyone
           | adopting this library test any usage heavily, especially
           | anything to do with relations
        
           | rak wrote:
           | MongoDB support is a work in progress for them too but
           | otherwise a fun library.
        
         | gmac wrote:
         | Zapatos can generate the hairy JSON stuff for you, including
         | lateral joins that are equivalent to your example query, which
         | would be:                   const result = await
         | db.select('user', db.all, { lateral: { posts: db.select('post',
         | { userId: db.parent('id') }) } }).run(pool);
         | 
         | And result will have the structure you asked for, and be
         | automatically typed as such.
         | 
         | (Sorry, can't manage helpful indentation from my phone).
         | 
         | See: https://jawj.github.io/zapatos/index.html#joins-as-nested-
         | js...
        
           | renke1 wrote:
           | Thanks, Zapatos looks really nice. That library seems to be
           | basically what I want (aside from writing SQL for joins, but
           | I think it's just not possible in the way I imagined). The
           | documentation is really nice. Much of what is written in it
           | aligns with my thinking. I'll definitely give it a try!
        
         | goliatone wrote:
         | You should check objection.js[1] as I think it would get you
         | what you describe and probably more. It is a relational query
         | builder built on top of knex, which is a query builder.
         | 
         | [1] https://vincit.github.io/objection.js/
        
           | mceachen wrote:
           | I wrote the original typescript bindings for objection. One
           | of the library authors is a contributor to knex.js, which
           | objection uses for query building. Both are quite nice.
           | 
           | With either library, you aren't writing naked SQL, but
           | something like `query.where("updated_at", ">", Date.now())`.
           | Tsc comes into play when you send typed objects into your
           | .where, .insert, ... methods and what you get back from the
           | query.
           | 
           | It's close enough to SQL that you don't lose expressiveness,
           | you're not behind walls of magick due to orm, and you don't
           | have to worry about Bobby Droptables.
        
           | renke1 wrote:
           | Thanks, I'll check it out. But on first glance it doesn't
           | seem to let me write actual SQL queries. Also, it doesn't
           | seem to be typesafe, or is it?
        
             | chrischen wrote:
             | It says it has typescript support.
        
               | renke1 wrote:
               | I didn't check those typings, but I don't think the model
               | itself is type safe from the looks of it.
        
               | mceachen wrote:
               | It is. See my ggp reply.
        
       | adriancooney wrote:
       | I really like the unique approach of the annotated SQL files and
       | can definitely see some use cases where it would be good to
       | declutter the SQL from the code. For me personally, I'd be
       | hesitant to add another build tool to my already bloated
       | toolchain. Could create a special Babel-style "import" type that
       | automatically transforms your code (JIT)? It could remove some of
       | the friction in adoption (for Babel users at least).
       | 
       | Another one in a similar vein with strict typing and really nice
       | SQL interpolation for Postgres: https://github.com/gajus/slonik
        
         | ggregoire wrote:
         | Same, I really like this approach. Some of the benefits are:
         | 
         | - better separation of concerns
         | 
         | - better integration with SQL tools (syntax highlighting,
         | autocompletion, etc)
         | 
         | - way easier to run/test/debug your queries into a database
         | client
         | 
         | - better languages analysis of your projects (e.g. % of SQL in
         | your GitHub/GitLab repo)
         | 
         | --
         | 
         | If anyone interested in applying this approach in your Python
         | projects, I recommend this package:
         | https://github.com/mcfunley/pugsql
        
       ___________________________________________________________________
       (page generated 2020-05-25 23:00 UTC)