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 🙏

© 2026 – Pkg Stats / Ryan Hefner

sqmap

v1.0.0

Published

A lightweight and easy to use micro ORM for TypeScript.

Readme

SQMap

A lightweight and easy to use micro ORM for Node/TypeScript.

SQMap does not try to replace SQL. It gives you a thin, typed layer for common query patterns while keeping SQL generation explicit and predictable.

Supported databases

  • Core query builder (sqmap): DB-agnostic SQL + params generation.
  • PostgreSQL adapter (sqmap/postgres-pg): executes generated SQL with pg.
  • SQLite adapter (sqmap/sqlite-bun): executes generated SQL with Bun's bun:sqlite.

Install

npm i sqmap

WARNING: trusted identifiers only

SQMap parameterizes values, but SQL identifiers (table names, schema names, column names) are interpolated into query strings.

DO NOT pass untrusted user input as identifiers.

Only use identifiers that come from trusted, static application code.

Models

Define one row interface per table, then generate a typed API.

import * as SQM from "sqmap";

export interface UserRow {
  id?: number;
  created?: Date;
  updated?: Date;
  status?: "active" | "banned" | "deactivated";
  username?: string;
  email?: string;
}

// tableName, defaultSchema, format
export const users = SQM.genAPI<UserRow>("users", "app", SQM.FORMATS.POSTGRES_PG);

Built-in formats:

  • SQM.FORMATS.POSTGRES_PG
  • SQM.FORMATS.SQLITE_BUN

genAPI returns SQL only:

interface SQLData {
  query: string;
  params: any[];
}

Insert queries

const sql = users.insert({
  cols: ["status", "username", "email"],
  rows: [{
    status: "active",
    email: "[email protected]",
    password: "ignored" as any
  }],
  return: ["id", "email"]
});

console.log(sql.query);
console.log(sql.params);

Generated SQL and params:

INSERT INTO "app"."users" ("status", "username", "email") VALUES ($1, $2, $3) RETURNING "id", "email";
["active", undefined, "[email protected]"]

Notes:

  • cols controls both generated columns and param order.
  • Extra keys in rows are ignored.
  • Missing values in selected cols become undefined.
  • Multi-row insert is supported.
  • return: "*" and return: ["colA", "colB"] are supported.
  • return: [] omits RETURNING.
  • cols and rows must both be non-empty.

Select queries

const sql = users.select({
  distinct: true,
  cols: ["id", "email"],
  where: [{ id: 42 }],
  in: [["status", "IN", ["active", "banned"]]],
  like: [["email", "ILIKE", "user%"]],
  order: { by: "id", type: "DESC" },
  shift: { limit: 10, offset: 20 }
});

Generated SQL:

SELECT DISTINCT "id", "email" FROM "app"."users" WHERE "id" = $1 AND "status" IN ($2, $3) AND "email" ILIKE $4 ORDER BY "id" DESC LIMIT $5 OFFSET $6;
[42, "active", "banned", "user%", 10, 20]

Capabilities:

  • cols supports explicit columns and "*".
  • distinct: emits SELECT DISTINCT ... when enabled.
  • where supports:
    • Comparison operators: =, <, >, >=, <=, !=
    • Object form: [{ id: 1, email: "[email protected]" }, "!=", "OR"]
    • Object defaults: operator = and predicate join AND when omitted
    • Object null rewrite: [{ verified_at: null, id: 7 }] becomes "verified_at" IS NULL AND "id" = $1
    • Tokenized form: [["id", "=", 1], "OR", "NOT", ["email", "=", "[email protected]"]]
    • Tokenized null rewrite: [["verified_at", "=", null], "OR", ["stripe_id", "!=", null]] becomes IS NULL / IS NOT NULL
  • in: IN and NOT IN
  • like: LIKE, NOT LIKE, ILIKE, NOT ILIKE
  • between: controls how where, in, and like groups are joined (AND default, or OR). It does not change tokenized logic inside each group.
  • order: ASC or DESC
  • shift: LIMIT / OFFSET (null and negative values are ignored, limit-only and offset-only are both supported)

Note:

  • Null rewrite applies to both object and tokenized where predicates for = and != only.

Update queries

const sql = users.update({
  set: { status: "deactivated" },
  where: [{ id: 42 }],
  in: [["status", "IN", ["active", "banned"]]],
  like: [["email", "ILIKE", "user%"]],
  between: "OR",
  return: ["id", "status"]
});

Generated SQL:

UPDATE "app"."users" SET "status" = $1 WHERE "id" = $2 OR "status" IN ($3, $4) OR "email" ILIKE $5 RETURNING "id", "status";
["deactivated", 42, "active", "banned", "user%"]

Rules:

  • set must be non-empty.
  • At least one filter key is required (where, in, or like).
  • At least one non-empty filter group is required after parsing (where, in, or like).
  • return works like insert.
  • return: [] omits RETURNING.
  • Using only empty/token-only filter arrays throws.

Delete queries

const sql = users.delete({
  where: [{ id: 42 }],
  in: [["status", "IN", ["deactivated"]]],
  like: [["email", "LIKE", "user%"]],
  return: "*"
});

Generated SQL:

DELETE FROM "app"."users" WHERE "id" = $1 AND "status" IN ($2) AND "email" LIKE $3 RETURNING *;
[42, "deactivated", "user%"]

Rules:

  • At least one filter key is required (where, in, or like).
  • At least one non-empty filter group is required after parsing (where, in, or like).
  • return supports "*" or selected columns.
  • return: [] omits RETURNING.
  • Using only empty/token-only filter arrays throws.

Schema behavior

Each query supports schema?: string | null:

  • Omitted: use default schema from genAPI(...).
  • String value: override schema for that query.
  • null: remove schema prefix entirely.
  • Empty string: falls back to default schema.

Boolean expression behavior

For tokenized where / in / like arrays:

  • Allowed tokens: AND, OR, NOT.
  • Leading AND/OR and trailing AND/OR/NOT are normalized away.
  • Invalid tokens, missing logical operators, invalid NOT placement, and consecutive operators throw.
  • IN predicates require at least one value.

Behavior differences:

  • select tolerates token-only/empty filter arrays by emitting no WHERE.
  • update and delete reject token-only/empty filters when they resolve to no predicates (safety guard).

Adapters

PostgreSQL (pg)

import { Pool } from "pg";
import { genPostgresPGAPI, expectOne } from "sqmap/postgres-pg";

interface UserRow {
  id?: number;
  email?: string;
}

const pool = new Pool();
const users = genPostgresPGAPI<UserRow>("users", "app"); // optional third arg: custom format
const client = await pool.connect();

try {
  const result = await users.select(client, {
    cols: ["id", "email"],
    where: [{ id: 1 }]
  });

  const user = expectOne(result, new Error("User not found"));
  console.log(user.email);
} finally {
  client.release();
}

SQLite (Bun)

import { Database } from "bun:sqlite";
import { genSQLiteBunAPI, expect } from "sqmap/sqlite-bun";

interface UserRow {
  id?: number;
  email?: string;
}

const db = new Database(":memory:");
const users = genSQLiteBunAPI<UserRow>("users"); // optional second arg: custom format

const rows = users.select(db, { cols: ["id", "email"] });
const exactlyTwo = expect(rows, 2, new Error("Expected 2 users"));

Both adapters also expose:

  • sql(...): run raw SQL with params
  • expect(...): assert exact row count
  • expectOne(...): assert exactly one row

Adapter notes:

  • genPostgresPGAPI accepts (tableName, schema, format?).
  • genSQLiteBunAPI accepts (tableName, format?) and always generates SQL without schema prefix.

Custom format (placeholders + quoting)

You can provide your own Format:

import * as SQM from "sqmap";

const customFormat: SQM.Format = {
  paramsPrefix: "?",
  paramsStartIndex: 0,
  paramsAppendIndex: false,
  quotingChar: "`"
};

const users = SQM.genAPI<{ id?: number }>("users", "app", customFormat);
const sql = users.select({ cols: ["id"], where: [{ id: 7 }] });

console.log(sql.query);
// SELECT `id` FROM `app`.`users` WHERE `id` = ?;