0.1.14 β€’ Published 5 months ago

@flowblade/sql-tag v0.1.14

Weekly downloads
-
License
MIT
Repository
github
Last release
5 months ago

@flowblade/sql-tag

Fast and lightweight (~790B) sql template tag based on sql-template-tag.

npm changelog codecov bundles node browserslist size downloads license

Features

Install

yarn add @flowblade/sql-tag

API

HelpersDescriptionExample
sql.joinJoin array values with optional separatorAND id IN ${sql.join(['1', '3'])
sql.ifConditionally add a statementAND ${sql.if(true, () => sql'deleted_at is null')}
sql.bulkEase bulk inserts
sql.unsafeRawAllow to pass unsafe values in the query.ORDER BY ${sql.unsafeRaw('name desc')}
sql.emptyHelper to represent empty string.${isTrue ? sql'1=1' : sql.empty}

Usage

Basic

import { sql } from '@flowblade/sql-tag';

// πŸ‘ˆ User provided parameters
const params = {
    country: 'BE',
    users: ['John', 'Doe'],
};

const query = sql<{ // πŸ‘ˆ optionally type the result
    id: number;
    username: string;
}>`
   SELECT id, username FROM users
   WHERE country = ${params.country}           -- πŸ‘ˆ simple param
   AND username IN (${sql.join(params.users)}) -- πŸ‘ˆ array param
`;

// query.sql === "SELECT id, username FROM users WHERE country = ? AND username IN (?, ?)";
// query.values === ['BE', 'John', 'Doe'];

Conditionals

import { sql } from '@flowblade/sql-tag';

// πŸ‘ˆ User provided parameters
const userIds = [1, 2];
const limit = 10;

const query = sql<{ // πŸ‘ˆ optionally type the result
    id: number;
    username: string;
}>`
   SELECT id, username FROM users
   WHERE 1=1
   -- πŸ‘‡ alternative 2: with ternary operator and sql.empty
   ${userIds.length > 0 ? sql`AND id IN (${sql.join(userIds)})` : sql.empty}

   -- πŸ‘‡ alternative 2: with usage of sql.if helper
   ${sql.if(
     userIds.length,
     () => sql`AND id IN (${sql.join(userIds)})`
   )}
   LIMIT ${limit}
`;

// query.sql === "SELECT id, username FROM users WHERE 1=1 AND id IN (?, ?) LIMIT ?";
// query.values === [1, 2, 10];

Query composition

You can nest any query into another one.

import {sql} from '@flowblade/sql-tag';

const getSqlUserCountByCountries = (minUsers: number) => sql`
  SELECT
    c.name as country_name,
    count(u.id) as user_count
  FROM country AS c INNER JOIN user u
  ON c.id = u.country_id
  GROUP BY c.name
  HAVING count(u.id) > ${minUsers}
`;

const compression: 'zstd' | 'snappy' | 'gzip' = 'zstd';

// Example base on DuckDb COPY statement
// but you can nest into CTE, table aliases, subqueries etc...
const query = sql`
    COPY
    (${getSqlUserCountByCountries(23)})
    TO 'usercount_by_countries.parquet'
    (FORMAT 'parquet', COMPRESSION ${compression}, ROW_GROUP_SIZE 100000);
`;

console.log(query.values); // [23, 'zstd']
console.log(query.sql);    // "COPY (SELECT...."

Bulk inserts

Ease bulk inserts/merge from multi rows arrays.

import { sql } from '@flowblade/sql-tag';

const insert = sql`
   INSERT INTO product (name, price, stock, status)
   VALUES ${sql.bulk([
     ['Laptop', 999.99, 50, 'active'],
     ['Keyboard', 79.99, 100, 'active'],
   ])}
  `;

const { text, sql, statement, values } = insert;

insert.text;   //=> "INSERT INTO product (name, price, stock, status) VALUES ($1,$2,$3,$4),($5,$6,$7,$8)"
insert.sql;    //=> "INSERT INTO product (name, price, stock, status) VALUES (?,?,?,?),(?,?,?,?),(?,?,?,?)"
insert.values; //=> ["Laptop", 999.99, 50, "active", "Keyboard", 79.99, 100, "active"]

// Example running the query with pglite

const result = await db.query(text, values, {});

Recipes

With transact-sql (mssql)

import { sql } from '@flowblade/sql-tag';

const products = Array.from({ length: 1000 }, (_, i) => ({
  id: i,
  productName: `Product ${i}`,
}));

const limit = 10;

const sqlRaw = sql<{ id: number, name: string }>`
  -- TRANSACT-SQL
  DECLARE @Products NVARCHAR(MAX); -- WARNING LIMIT TO 2GB
  SET @Products = ${JSON.stringify(products)};

  -- DDL (# prefix is equivalent to CREATE TEMPORATY TABLE on other db)
  CREATE TABLE #products (
    productId INT,
    name NVARCHAR(255),
  );

  -- INSERT
  INSERT INTO #products (productId, productName)
     SELECT productId, productName
       FROM OPENJSON(@InitialData) WITH (
           id INT,
           name NVARCHAR(255)
       );

  -- SELECT
  SELECT TOP ${sql.unsafeRaw(String(limit))} id, name
    FROM #products
    ORDER BY id;
`;

Credits

This package won't be possible without the great work of Blake Embrey sql-template-tag.

Some notable differences:

  • Named export for sql: import {sql} from '@flowblade/sql-tag'.
  • Possibility to type the result of the query (ie sql<Row>).
  • Utility functions (join...) are directly available from the sql tag.
  • Add sql.if helper to conditionally add a statement.
  • Rename sqlRaw to sql.unsafeRaw to prevent misuse.

Bundle size

Bundle size is tracked by a size-limit configuration

Scenario (esm)Size (compressed)
import { sql } from '@flowblade/sql-tag~ 790B

Compatibility

LevelCIDescription
Nodeβœ…CI for 18.x, 20.x & 22.x.
Browserβœ…Tested with latest chrome (vitest/playwright)
Browserslistβœ…> 95% on 01/2025. Chrome 96+, Firefox 90+, Edge 19+, ios 15+, Safari 15+ and Opera 77+
Edgeβœ…Ensured on CI with @vercel/edge-runtime.
Cloudflareβœ…Ensured with @cloudflare/vitest-pool-workers (see wrangler.toml
Typescriptβœ…TS 5.0 + / are-the-type-wrong checks on CI.
ES2022βœ…Dist files checked with es-check
Performanceβœ…Monitored with codspeed.io

Contributors

Contributions are welcome. Have a look to the CONTRIBUTING document.

Sponsors

Sponsor, coffee, or star – All is spent for quality time with loved ones. Thanks ! πŸ™β€οΈ

Special thanks to

License

MIT Β© SΓ©bastien Vanvelthem and contributors.

0.1.14

5 months ago

0.1.13

5 months ago

0.1.12

5 months ago

0.1.11

5 months ago

0.1.10

8 months ago

0.1.9

9 months ago

0.1.8

9 months ago

0.1.7

9 months ago

0.1.6

9 months ago

0.1.5

9 months ago

0.1.4

9 months ago

0.1.3

10 months ago

0.1.2

10 months ago

0.1.1

10 months ago

0.1.0

10 months ago