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 🙏

© 2025 – Pkg Stats / Ryan Hefner

sqlite-prepare

v0.0.9

Published

A type-safe SQL query builder for Cloudflare D1

Downloads

9

Readme

sqlite prepare

A tagged template literal styled sqlite query builder.

Why choose this one?

No, you should probably use drizzle or prisma for robustness, typesafty, and ergonomics.

Why do you build this?

For small and CRUD based projects, sometimes I want to use more flexible and faster ways to build APIs. These cases, setting up a schema would be a nuisance.

However writing raw sql is cumbersome, and the mismatching between the questions marks and the parameters is a pain.

(consider: INSERT INTO panel_info (user, login_time, created_at, last_edited, recuring_period, ..., subscriptions) VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)... so heart breaking to refactor)

With that being said, I will be more than appreciated and happy if you like this project by a newbie.

Installation

npm install sqlite-prepare
# or
yarn add sqlite-prepare
# or
pnpm add sqlite-prepare

Usage

import { build, prepare } from "sqlite-prepare";

// Create a simple query with parameters
const query = build`
  SELECT * FROM users 
  WHERE age > ${21} 
  AND status = ${"active"}
`;

// Execute with Cloudflare D1
const results = await prepare(db, query).all();

// Or

const sql = wrapD1(db);
const query = await sql`
  SELECT * FROM users 
  WHERE age > ${21} 
  AND status = ${"active"}
`.all();

// Nested queries
const subquery = build`SELECT id FROM users WHERE age > ${21}`;
const mainQuery = build`SELECT * FROM posts WHERE author_id IN ${subquery}`;
const posts = await prepare(db, mainQuery).all();

// Raw SQL (use carefully, in case of sql injection)
import { raw } from "sqlite-prepare";
const rawQuery = build`SELECT * FROM ${raw("users")}`;

// example: use raw to create generically dynamic query functions:
const newestEntry = async (table: string) => {
  const allowedTables = ["cache", "pages"];
  const sql = wrapD1(db);
  if (allowedTables.includes(table)) {
    const query = await sql`
      SELECT * FROM ${raw(table)} 
      WHERE age > ${21} 
      AND status = ${"active"}
    `.all();
  } else {
    throw new Error("Illegal operation!");
  }
};

// example: use raw to create queries with optional conditions:
const getEntries = async (
  table: string,
  condition?: { field: string; value: any }
) => {
  const allowedTables = ["cache", "pages"];
  const sql = wrapD1(db);

  if (allowedTables.includes(table)) {
    let query = sql`SELECT * FROM ${raw(table)}`;

    if (condition) {
      query = sql`SELECT * FROM ${raw(table)} WHERE ${raw(condition.field)} = ${
        condition.value
      }`;
    }

    return await query.all();
  } else {
    throw new Error("Illegal operation!");
  }
};

Syntax Highlighting

Inline SQL is a very nice vscode extension, enabling inline sqlite syntax highlighting for not only html but also jsx and tsx files.

(There are tons of such extensions. Search at the marketplace if you want to try them out. )

Features

Parsing parameters:

  • Array: [1, 2, 3, 4, 5] => (?, ?, ?, ?, ?) & [1, 2, 3, 4, 5]
  • Date: Only parameterize, value unchanged, SQLite parameter binding handles this conversion automatically
  • Uint8Array | ArrayBuffer: Only parameterize, value unchanged, SQLite parameter binding handles this conversion automatically
  • Normal javascript object: JSON.stringify

SQL Fragments

Break down complex queries into reusable parts:

import { fragment as f } from "sqlite-prepare";

const columns = f`id, name, created_at`;
const condition = f`status = ${"active"} AND type = ${"user"}`;

const query = sql`
  SELECT ${columns} 
  FROM users 
  WHERE ${condition}
`;

Insert Builder

Type-safe insert operations:

import { insert, into } from "sqlite-prepare";

// Basic insert
const query = insert(into("users"), {
  name: "John",
  age: 30,
});

// With raw SQL
const query = insert(into("users"), {
  created_at: raw("NOW()"),
});

// Batch insert with same structure
const query = insert(into("users"), [
  { name: "John", age: 30 },
  { name: "Jane", age: 25 },
  { name: "Bob", age: 35 }
]);

// Batch insert with different columns
const query = insert(into("users"), [
  { name: "John", age: 30 },
  { name: "Jane", age: 25, role: "admin" },
  { name: "Bob", status: "active" }  // missing columns will be NULL
]);

// Batch insert with complex values
const query = insert(into("users"), [
  { 
    name: "John",
    created_at: raw("datetime('now', '-1 day')"),
    metadata: { role: "user", permissions: ["read"] }
  },
  { 
    name: "Jane",
    created_at: raw("datetime('now')"),
    metadata: { role: "admin", permissions: ["read", "write"] }
  }
]);

// Batch insert with subqueries
const query = insert(into("users"), [
  { 
    name: "John",
    role_id: build`SELECT id FROM roles WHERE name = ${'admin'}`
  },
  { 
    name: "Jane",
    role_id: build`SELECT id FROM roles WHERE name = ${'user'}`
  }
]);

Enhanced Raw SQL

Raw SQL, with supports for parameterization:

import { raw } from "sqlite-prepare";

// Parameterized raw SQL
const condition = raw('age > ? AND status = ?', [18, 'active']);
//or
const condition = raw('age > 18 AND status = 'active''); // not recommended
const query = sql`SELECT * FROM users WHERE ${condition}`;

SQL Validation

Validate raw SQL queries:

import { validate as v } from "sqlite-prepare";
//or use alias
import { v } from "sqlite-prepare";

// Validate against allowed queries
const allowed = ["SELECT * FROM users", "SELECT * FROM posts"];
const validated = v("SELECT * FROM users", allowed);

// Custom validator function
const isSelect = (sql: string) => sql.toLowerCase().startsWith("select");
const validated = v("SELECT * FROM users", isSelect);

String validators can be used againt both string and RawSQL. It only checks the query part for the latter one, the params are ignored.

Validator functions also supports (sqlQuery: RawSQL, parsed: true) => boolean, if you also want to check the params. If the second parameter is passed (no matter true or false), the validate function will pass the entire RawSQL to it.

Discover more at the test suites and the source code.

Database Compatibility

Now tested and works with:

  • Cloudflare D1
  • better-sqlite3
  • Bun's SQLite API
// With better-sqlite3
import Database from "better-sqlite3";
const db = new Database("mydb.sqlite");
const query = prepare(db)`SELECT * FROM users WHERE age > ${18}`;
const results = query.all();

Updates

2025-05-06 v0.0.8

  • Added support for batch insert operations
    • Handles arrays of objects with same or different structures
    • Automatically collects all possible columns across all rows
    • Sets NULL for missing columns in each row
    • Supports complex values in batch inserts:
      • Raw SQL expressions
      • SQL fragments
      • Subqueries
      • JSON objects (automatically stringified)
      • Date objects
      • Binary data
    • Maintains parameterization for SQL injection prevention
    • Fully tested with better-sqlite3 integration tests

License

MIT