npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2024 – Pkg Stats / Ryan Hefner

type-safe-sql-query

v0.0.3

Published

Bridges the Gap Between SQL and TypeScript Types

Downloads

4

Readme

type-safe-sql-query

About

type-safe-sql-query provides two well-tested util types

  • InferReturnTypeFromSqlStatement
  • InferParamsTypeFromSqlStatement

that allow us to automatically infer the return type and the parameter type of a SQL statement. e.g.:

type T0 = InferReturnTypeFromSqlStatement<"SELECT * FROM users", Tables>;
// => { id: number, name: string, age: number, email: string }[]

Feel free to be creative and use them however you like.

Try Now

Features

  • Automatic type inference from SQL statements.
  • No compilation step
  • No runtime overhead. 🏎️
  • Zero dependencies.
  • ✅ Minimalistic and easy to use.
  • Well-tested
  • ✅ MIT License

Roadmap / Current Status

⚠️ Please note that this project is in a very early stage and is not yet ready for production use.

The MVP of this project is to support basic CRUD operations on a single table. The following is a list features that are already implemented or are planned to be implemented.

Legend:

  • 📝: Planned
  • 🏗️: In Progress
  • ✅: Done

InferReturnTypeFromSqlStatement

| Feature | Status | | ------------------------------- | ------ | | Support for INSERT statements | ✅ | | Support for UPDATE statements | ✅ | | Support for DELETE statements | ✅ | | Support for SELECT statements | 🏗️ |

InferParamsTypeFromSqlStatement

| Feature | Status | | ------------------------------- | ------ | | Support for INSERT statements | ✅ | | Support for UPDATE statements | ✅ | | Support for DELETE statements | ✅ | | Support for SELECT statements | 🏗️ |

For more details please check the source code and the test files.

Installation

npm install -D type-safe-sql-query

# or
yarn add -D type-safe-sql-query

# or
pnpm add -D type-safe-sql-query

Basic Usage

The following examples demonstrates how to use type-safe-sql-query with MySQL.

import type { InferReturnTypeFromSqlStatement, InferParamsTypeFromSqlStatement } from "type-safe-sql-query";
import type { Tables } from "./tables";

type Result = InferReturnTypeFromSqlStatement<"SELECT * FROM users WHERE name = ? AND age > ?", Tables>;
// Result is: { id: number, name: string, age: number, email: string }[]

type Params = InferParamsTypeFromSqlStatement<"SELECT * FROM users WHERE name = ? AND age > ?", Tables>;
// Params is: [string, number]

type ResultWithAlias = InferReturnTypeFromSqlStatement<"SELECT name AS fullName, age FROM Users", Tables>;
// ResultWithAlias is: { fullName: string, age: number }[]

The examples above assumes that we have a file called tables.ts that contains the type information of our database tables. This file should be auto-generated with schemats for example.

// tables.ts (auto-generated with schemats)

export type Tables = {
  users: {
    id: number;
    name: string;
    age: number;
    email: string;
  };
  // ...
};

Usage with Low Level Database Drivers

The following example demonstrates how to use type-safe-sql-query with the mysql2 driver.

import mysql from "mysql2/promise";
import type { InferParamsTypeFromSqlStatement, InferParamsFromSqlStatement } from "type-safe-sql-query";
import type { Tables } from "./tables";

// Create the connection to database
const connection = await mysql.createConnection({
  host: "localhost",
  user: "root",
  database: "test",
});

// Create a type-safe query wrapper
async function query<S extends string>(
  sql: S,
  params: InferParamsTypeFromSqlStatement<S, Tables>,
): InferReturnTypeFromSqlStatement<S, Tables> {
  const [results] = await connection.query(sql, params);
  return results as any;
}

// Use the type-safe query wrapper to query the database.
const users = await query("SELECT * FROM users WHERE name = ? AND age > ?", ["Michael", 36]);

Other low level database drivers like mysql, postgres or pg should work similarly.

Why did we build this?

  • ORMs and SQL Query Builders tend to be leaky abstractions. => Many people prefer writing SQL directly.
  • ORMs and SQL Query Builders do not use the database as the source of truth. => Potential schema drift.
  • Most ORMs and Query Builders are just wrappers around common packages/drivers like mysql, mysql2, postgres or pg. => Unnecessary performance/runtime overhead.
  • Having a dedicated compilation step that depends on the target platform (e.g., Prisma) is quite cumbersome. => Unnecessary complexity.

Motivation: Do we need an Abstraction?

ThePrimeagen (I think he works at Netflix) recently published some videos about ORMs and SQL:

He did a quick poll in one of those videos and people seem enjoy writing SQL statements. Approximately 50% of the people who voted chose SQL over ORMs and SQL query builders.

Do you like orms (Source: DONT USE AN ORM | Prime Reacts)

Maybe that's also the reason why Drizzle ORM is so popular at the moment (9th Place at 2023 JavaScript Rising Stars) since one of it's main selling points is:

If you know SQL — you know Drizzle.

SQL has been around since the 1970s, and was standardized by the American National Standards Institute (ANSI) in 1986 (A Brief History of SQL and its Usefulness). There must be a good reason why it's still around after approximately half a century and is still one of the most used databases according to the Stackoverflow Survey 2023, while other software technologies barely survive a decade.

so-database-survey-2023 (Source: Stackoverflow Survey 2023)

3 Different Classes of Abstraction

ORMs and SQL query builders are an attempt to abstract away the SQL language. Under the hood they are still using SQL to interact with the database.

The CompSciGuy did a great job in his video The Only Database Abstraction You Need in explaining the 3 classes of query families:

  • Query-By-Example
  • Query-By-API
  • Query-By-Language

Query-By-Example

TODO: add short and simple example

Query-By-API

It's just SQL but we call functions instead

TODO: add short and simple example

Typical examples are:

Query-By-Language

TODO: add short and simple example

A Single Source of Truth?

When developing server code with TypeScript (for example with frameworks like Nextjs, Express, Fastlify or Nestjs), we'll inevitably come up against the question of how to interact with our database.

Dan Vanderkam wrote a great article about TypeScript and SQL: Six Ways to Bridge the Divide.

There's lots of type information in our SQL database (the structure of the tables) and at the time of writing it's challenging to share that type information between the DB and TypeScript.

So you want a single source of truth? Where should it be? In the database or in the TypeScript code?

Most ORMs (like Prisma, TypeORM and Drizzle for example) use TypeScript as the source of truth. We define our models in TypeScript and the ORM generates the types from that. E.g.:

  • Prisma uses the schema.prisma file which is used by a compiler,
  • Drizzle uses the pgSchema object for declaring SQL schemas,
  • and TypeORM uses Entities which are classes that map to a database tables.

In contrast to the choices made by the ORMs mentioned above, I think it's quite obvious that the database should be the source of truth. It's the only place where the data is stored and TypeScript is a consumer of that data and thus should infer the types from the database.