@synvox/sql v4.2.8
@synvox/sql
sql is another SQL template string library
npm i @synvox/sqlSetting up for your client
@synvox/sql does not depend on a specific SQL client. You need to implement the Pool, PoolClient, and Client interfaces (exported from @synvox/sql) to use the library with the database drivers of your choice. Below is an example using pg:
import { connect, Pool, PoolClient, Client } from "@synvox/sql";
import * as pg from "pg";
// Example Pool implementation
class MyPool extends Pool {
client: pg.Pool;
constructor() {
super();
this.client = new pg.Pool();
}
query(query: string, values: any[]) {
return this.client.query(query, values);
}
async isolate() {
const pgClient = await this.client.connect();
return new MyPoolClient(pgClient);
}
}
// Example PoolClient implementation
class MyPoolClient extends PoolClient {
client: pg.PoolClient;
constructor(client: pg.PoolClient) {
super();
this.client = client;
}
query(query: string, values: any[]) {
return this.client.query(query, values);
}
async release() {
this.client.release();
}
}
// Example Client implementation (no pooling)
class MyClient extends Client {
client: pg.Client;
constructor() {
super();
this.client = new pg.Client();
}
async query(query: string, values: any[]) {
return this.client.query(query, values);
}
}
// Creating a connection using pooling:
const sql = connect(new MyPool());
// or if you don't need pooling:
const sql2 = connect(new MyClient());Executing Queries
exec()
Use exec to run queries that do not need to return rows or for multi-statement execution:
await sql`
create table example (
id serial primary key
)
`.exec();all()
Use all to get the rows returned from a query:
let users = await sql`
select * from users
`.all<User>();first()
Use first to get the first row returned from a query:
let user = await sql`
select * from users
`.first<User>();exists()
Use exists to quickly check if any rows match a condition:
let runJobs = await sql`
select *
from jobs
where run_at < now()
limit 1
`.exists();paginate()
Use paginate to limit and offset results:
let users = await sql`
select * from users
`.paginate<User>({ page: 0, per: 100 });This wraps your query like:
select paginated.* from ( ... ) paginated limit ? offset ?Composing Queries
Subqueries
Compose queries together with subqueries:
let subQuery = sql`select id from other_table`;
await sql`
select *
from table_name
where other_id in (${subQuery})
`.all();Query Builders
await sql`
insert into users ${sql.values({ name: "Ryan", active: false })}
`.exec();
// insert into users(name, active) values ($0, $1)
// $0 = "Ryan"
// $1 = falseawait sql`
insert into users ${sql.values([
{ name: "Ryan", active: false },
{ name: "Nolan", active: false },
])}
`.exec();
// insert into users(name, active) values ($0, $1), ($2, $3)
// $0 = "Ryan"
// $1 = false
// $2 = "Nolan"
// $3 = falseawait sql`
update users
${sql.set({ name: "Ryan", active: true })}
where id = ${1}
`.exec();
// update users set name = $0, active = $1 where id = $2
// $0 = "Ryan"
// $1 = true
// $2 = 1await sql`
select *
from users
where ${sql.and({ id: 1, active: true })}
`.all();
// select * from users where (id = $0 and active = $1)
// $0 = 1
// $1 = trueawait sql`
select *
from users
where ${sql.or({ id: 1, active: true })}
`.all();
// select * from users where (id = $0 or active = $1)
// $0 = 1
// $1 = trueArrays
Convert arrays into a comma-separated list:
await sql`
select *
from users
where id in (${sql.array([1, 2, 3])})
`.exec();
// select * from users where id in ($0, $1, $2)
// $0 = 1
// $1 = 2
// $2 = 3References
Use sql.ref to reference a column or table:
await sql`
select ${sql.ref("users.id")}
from users
`.all();Join
The signature for join is:
join(delimiter: SqlFragment, [first, ...rest]: SqlFragment[]): SqlFragmentSo, the delimiter is the first parameter, and the array of fragments is the second parameter:
await sql`
select ${sql.join(sql`, `, [sql`users.id`, sql`users.name`])}
from users
`.all();
// select users.id, users.name from usersLiterals
Use sql.literal for direct literal insertion:
await sql`
insert into points (location)
values (${sql.literal([100, 100])})
`.exec();Transactions
@synvox/sql supports two transaction helpers:
sql.transaction– Retries on deadlock. Use this for queries that affect only the database.sql.impureTransaction– Does not retry on deadlock by default. Use this if the transaction has side effects outside of the database.
await sql.transaction(async (trxSql) => {
// Use trxSql like normal. Commit and rollback are handled for you.
// If an error is thrown, the transaction will be rolled back.
});Dedicated Connection
Use sql.connection if you need a dedicated connection (via your PoolClient) for a set of queries:
await sql.connection(async (sql) => {
// This block uses a dedicated connection.
await sql`select pg_sleep(1)`.exec();
});
// Connection is automatically released afterward.Previewing Queries
You can preview a query string by calling preview:
let migration = sql`
insert into users ${sql.values({ firstName: "Ryan" })}
`.preview();
// => insert into users (first_name) values ('Ryan')Migrations
If you’ve installed the optional migration helpers (or have your own system), you can run migrations with something like:
import { migrate } from "@synvox/sql/migrate"; // Hypothetical import
// Where directoryName is the path to your migration files:
await migrate(sql, directoryName);A migration file might look like:
// migrations/001-create-users.ts
export async function up(sql) {
await sql`
create table users (
id serial primary key,
first_name text not null,
last_name text not null
)
`.exec();
}Note: Migrations in
@synvox/sqldo not currently support “down” migrations.
Seeds
Similarly, if you have the optional seed helpers:
import { seed } from "@synvox/sql/seed"; // Hypothetical import
await seed(sql, directoryName);A seed file might look like:
// seeds/001-insert-default-users.ts
export async function seed(sql) {
await sql`
insert into users ${sql.values({ first_name: "Ryan", last_name: "Allred" })}
on conflict do nothing
`.exec();
}Types
If you’re using the optional type generator:
import { types } from "@synvox/sql/types"; // Hypothetical import
await types(sql, fileNameToWriteTypes, ["schema_name"]);This will generate a file containing interfaces for each table in the schema. If you omit ["schema_name"], it will generate types for all schemas.
Case Transforms
By default:
- Rows from the database are
camelCased. - Identifiers passed through the helper functions (like
sql.valuesorsql.set) aresnake_casedin the database.
To change this behavior, call connect with a caseMethod option:
const sql = connect(new MyPool(), {
caseMethod: "none", // or 'snake' | 'camel'
});Debugging
This project uses the debug library. To see debug logs, run your app with:
DEBUG=sql:* node yourApp.jsYou’ll see logs for queries, transaction attempts, and errors.
8 months ago
11 months ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
3 years ago
3 years ago
3 years ago
6 years ago
6 years ago
6 years ago
6 years ago
6 years ago
6 years ago
6 years ago
6 years ago
6 years ago