Skip to content

SQL Authoring

The sql tag

The sql tagged template literal is the primary way to write queries. Interpolated values become positional $N parameters — they are never concatenated into SQL text.

typescript
import { sql } from "@phonemyatt/squn";

const userId = 42;
const q = sql`SELECT * FROM users WHERE id = ${userId}`;
// q.text   → "SELECT * FROM users WHERE id = $1"
// q.params → [42]

Nested fragments

Fragments compose. Nested fragments merge inline with renumbered placeholders:

typescript
const minAge = 18;
const filter = sql`age > ${minAge}`;

const query = sql`SELECT * FROM users WHERE ${filter} ORDER BY name`;
// text   → "SELECT * FROM users WHERE age > $1 ORDER BY name"
// params → [18]

sqlRaw

For DDL, literals, or any SQL text where you control every character. Do not pass user input here.

typescript
import { sqlRaw } from "@phonemyatt/squn";

const ddl = sqlRaw("CREATE TABLE IF NOT EXISTS logs (id SERIAL PRIMARY KEY)");

sqlIdentifier

Safely quotes a single identifier:

typescript
import { sqlIdentifier, sqlQualifiedIdentifier } from "@phonemyatt/squn";

sqlIdentifier("user_name")              // → "user_name"
sqlQualifiedIdentifier("public", "users") // → "public"."users"

Throws SecurityError(INVALID_IDENTIFIER) if the name contains characters outside [a-zA-Z0-9_].

sqlIf

Conditionally includes a fragment:

typescript
import { sqlIf } from "@phonemyatt/squn";

const isAdmin = true;
const clause = sqlIf(isAdmin, sql`AND role = ${"admin"}`);

const q = sql`SELECT * FROM users WHERE active = ${true} ${clause}`;

When the condition is false, sqlIf returns an empty fragment — no SQL is added.

sqlJoin

Joins an array of fragments with a separator:

typescript
import { sqlJoin } from "@phonemyatt/squn";

const conditions = [
  sql`age > ${18}`,
  sql`active = ${true}`,
  sql`role = ${"admin"}`,
];

const where = sqlJoin(conditions, " AND ");
const q = sql`SELECT * FROM users WHERE ${where}`;

Building dynamic queries

Combine the helpers to build fully dynamic queries without string concatenation:

typescript
function findUsers(filters: {
  minAge?: number;
  role?: string;
  active?: boolean;
}) {
  const conditions = [
    sqlIf(filters.minAge !== undefined, sql`age >= ${filters.minAge ?? 0}`),
    sqlIf(filters.role !== undefined, sql`role = ${filters.role ?? ""}`),
    sqlIf(filters.active !== undefined, sql`active = ${filters.active ?? true}`),
  ];

  const where = sqlJoin(
    conditions.filter(c => c.text.trim() !== ""),
    " AND "
  );

  return sql`SELECT * FROM users${where.text ? sql` WHERE ${where}` : sqlRaw("")}`;
}

Released under the MIT License.