Tinqer API Reference
Reference for adapter execution helpers, typed contexts, and query utilities.
Table of Contents
1. Execution APIs
Tinqer uses a two-step API:
- Plan definition (
define*functions from@tinqerjs/tinqer) - Creates type-safe query plans - Execution or SQL generation (
execute*ortoSqlfrom adapter packages) - Executes plans or generates SQL
Adapter packages live in @tinqerjs/pg-promise-adapter (PostgreSQL) and @tinqerjs/better-sqlite3-adapter (SQLite).
1.1 defineSelect, toSql & executeSelect
Creates SELECT query plans, generates SQL, or executes queries.
Signatures
// Plan definition (from @tinqerjs/tinqer)
function defineSelect<TSchema, TParams, TRecord>(
schema: DatabaseSchema<TSchema>,
builder: (
q: QueryBuilder<TSchema>,
params: TParams,
helpers: QueryHelpers,
) => Queryable<TRecord> | OrderedQueryable<TRecord>,
options?: ParseQueryOptions,
): SelectPlanHandle<TRecord, TParams>;
function defineSelect<TSchema, TParams, TResult>(
schema: DatabaseSchema<TSchema>,
builder: (
q: QueryBuilder<TSchema>,
params: TParams,
helpers: QueryHelpers,
) => TerminalQuery<TResult>,
options?: ParseQueryOptions,
): SelectTerminalHandle<TResult, TParams>;
// SQL generation (from adapter packages)
function toSql<TParams>(
plan: SelectPlanHandle<unknown, TParams> | SelectTerminalHandle<unknown, TParams>,
params: TParams,
): { sql: string; params: Record<string, unknown> };
// Execution (from adapter packages)
async function executeSelect<TSchema, TParams, TQuery>(
db: PgDatabase | BetterSqlite3Database,
schema: DatabaseSchema<TSchema>,
builder: (q: QueryBuilder<TSchema>, params: TParams, helpers: QueryHelpers) => TQuery,
params: TParams,
options?: ExecuteOptions & ParseQueryOptions,
): Promise<
TQuery extends TerminalQuery<infer TResult>
? TResult
: TQuery extends Queryable<infer TRecord> | OrderedQueryable<infer TRecord>
? TRecord[]
: never
>;
Example - SQL Generation
import { createSchema, defineSelect } from "@tinqerjs/tinqer";
import { toSql } from "@tinqerjs/pg-promise-adapter";
interface Schema {
users: { id: number; name: string; age: number };
}
const schema = createSchema<Schema>();
const { sql, params } = toSql(
defineSelect(schema, (q, params: { minAge: number }) =>
q
.from("users")
.where((u) => u.age >= params.minAge)
.select((u) => ({ id: u.id, name: u.name })),
),
{ minAge: 18 },
);
// sql: SELECT "id" AS "id", "name" AS "name" FROM "users" WHERE "age" >= $(minAge)
// params: { minAge: 18 }
Example - Execution
import { createSchema } from "@tinqerjs/tinqer";
import { executeSelect } from "@tinqerjs/pg-promise-adapter";
interface Schema {
users: { id: number; name: string; age: number };
}
const schema = createSchema<Schema>();
const users = await executeSelect(
db,
schema,
(q, params: { minAge: number }) =>
q
.from("users")
.where((u) => u.age >= params.minAge)
.orderBy((u) => u.name),
{ minAge: 21 },
);
// Returns: Array of user objects
Notes
firstOrDefault/singleOrDefault/lastOrDefaultreturnnullwhen no rows match.reverse()flips the effective ordering. If noorderByis present, Tinqer generatesORDER BY 1 DESC.reverse()aftertake()/skip()is not supported.contains(value)is a terminal operation on scalar queries (use.select(...)first).contains()is not supported withtake()/skip().
Example - reverse()
const newestFirst = defineSelect(schema, (q) =>
q
.from("users")
.orderBy((u) => u.id)
.reverse(),
);
Example - contains()
const hasUserId = defineSelect(schema, (q, p: { id: number }) =>
q
.from("users")
.select((u) => u.id)
.contains(p.id),
);
1.2 defineInsert, toSql & executeInsert
Creates INSERT query plans, generates SQL, or executes queries with optional RETURNING clauses.
Signatures
// Plan definition (from @tinqerjs/tinqer)
function defineInsert<TSchema, TParams, TTable, TReturning = never>(
schema: DatabaseSchema<TSchema>,
builder: (
q: QueryBuilder<TSchema>,
params: TParams,
helpers: QueryHelpers,
) => Insertable<TTable> | InsertableWithReturning<TTable, TReturning>,
options?: ParseQueryOptions,
):
| InsertPlanHandleInitial<TTable, TParams>
| InsertPlanHandleWithValues<TTable, TParams>
| InsertPlanHandleWithReturning<TReturning, TParams>;
// SQL generation (from adapter packages)
function toSql<TParams>(
plan:
| InsertPlanHandleInitial<unknown, TParams>
| InsertPlanHandleWithValues<unknown, TParams>
| InsertPlanHandleWithReturning<unknown, TParams>,
params: TParams,
): { sql: string; params: Record<string, unknown> };
// Execution (from adapter packages)
async function executeInsert<TSchema, TTable, TReturning, TParams>(
db: PgDatabase | BetterSqlite3Database,
schema: DatabaseSchema<TSchema>,
builder: (
q: QueryBuilder<TSchema>,
params: TParams,
helpers?: QueryHelpers,
) => Insertable<TTable> | InsertableWithReturning<TTable, TReturning>,
params: TParams,
options?: ExecuteOptions & ParseQueryOptions,
): Promise<TReturning extends never ? number : TReturning[]>;
Example - SQL Generation
import { createSchema, defineInsert } from "@tinqerjs/tinqer";
import { toSql } from "@tinqerjs/pg-promise-adapter";
interface Schema {
users: { id: number; name: string };
}
const schema = createSchema<Schema>();
const { sql, params } = toSql(
defineInsert(schema, (q, params: { name: string }) =>
q.insertInto("users").values({ name: params.name }),
),
{ name: "Alice" },
);
// sql: INSERT INTO "users" ("name") VALUES ($(name))
// params: { name: "Alice" }
Example - Execution
import { createSchema } from "@tinqerjs/tinqer";
import { executeInsert } from "@tinqerjs/pg-promise-adapter";
interface Schema {
users: { id: number; name: string };
}
const schema = createSchema<Schema>();
// Without RETURNING - returns number of rows inserted
const rowCount = await executeInsert(
db,
schema,
(q, params: { name: string }) => q.insertInto("users").values({ name: params.name }),
{ name: "Alice" },
);
// With RETURNING - returns inserted rows
const createdUsers = await executeInsert(
db,
schema,
(q, params: { name: string }) =>
q
.insertInto("users")
.values({ name: params.name })
.returning((u) => ({ id: u.id, name: u.name })),
{ name: "Bob" },
);
Example - Upsert (ON CONFLICT)
// PostgreSQL + SQLite: INSERT ... ON CONFLICT
await executeInsert(
db,
schema,
(q, params: { email: string; name: string }) =>
q
.insertInto("users")
.values({ email: params.email, name: params.name })
.onConflict((u) => u.email)
.doUpdateSet((_existing, excluded) => ({ name: excluded.name })),
{ email: "alice@example.com", name: "Alice" },
);
// Composite conflict targets are supported:
// .onConflict((u) => u.email, (u) => u.name)
1.3 defineUpdate, toSql & executeUpdate
Creates UPDATE query plans, generates SQL, or executes queries with optional RETURNING clauses.
Signatures
// Plan definition (from @tinqerjs/tinqer)
function defineUpdate<TSchema, TParams, TTable, TReturning = never>(
schema: DatabaseSchema<TSchema>,
builder: (
q: QueryBuilder<TSchema>,
params: TParams,
helpers: QueryHelpers,
) =>
| UpdatableWithSet<TTable>
| UpdatableComplete<TTable>
| UpdatableWithReturning<TTable, TReturning>,
options?: ParseQueryOptions,
):
| UpdatePlanHandleInitial<TTable, TParams>
| UpdatePlanHandleWithSet<TTable, TParams>
| UpdatePlanHandleComplete<TTable, TParams>
| UpdatePlanHandleWithReturning<TReturning, TParams>;
// SQL generation (from adapter packages)
function toSql<TParams>(
plan:
| UpdatePlanHandleWithSet<unknown, TParams>
| UpdatePlanHandleComplete<unknown, TParams>
| UpdatePlanHandleWithReturning<unknown, TParams>,
params: TParams,
): { sql: string; params: Record<string, unknown> };
// Execution (from adapter packages)
async function executeUpdate<TSchema, TTable, TReturning, TParams>(
db: PgDatabase | BetterSqlite3Database,
schema: DatabaseSchema<TSchema>,
builder: (
q: QueryBuilder<TSchema>,
params: TParams,
helpers?: QueryHelpers,
) =>
| UpdatableWithSet<TTable>
| UpdatableComplete<TTable>
| UpdatableWithReturning<TTable, TReturning>,
params: TParams,
options?: ExecuteOptions & ParseQueryOptions,
): Promise<TReturning extends never ? number : TReturning[]>;
SET clause
.set(...) supports both direct assignments and column self-references:
q.update("users").set({ status: "inactive" });
q.update("users").set((u) => ({ viewCount: u.viewCount + 1 }));
Notes:
- The lambda form must return an object literal.
.set(...)can only be called once per UPDATE.
Example - SQL Generation
import { createSchema, defineUpdate } from "@tinqerjs/tinqer";
import { toSql } from "@tinqerjs/pg-promise-adapter";
interface Schema {
users: { id: number; name: string; status: string; lastLogin: Date };
}
const schema = createSchema<Schema>();
const { sql, params } = toSql(
defineUpdate(schema, (q, params: { cutoff: Date }) =>
q
.update("users")
.set({ status: "inactive" })
.where((u) => u.lastLogin < params.cutoff),
),
{ cutoff: new Date("2024-01-01") },
);
// sql: UPDATE "users" SET "status" = 'inactive' WHERE "lastLogin" < $(cutoff)
// params: { cutoff: Date }
Example - Execution
import { createSchema } from "@tinqerjs/tinqer";
import { executeUpdate } from "@tinqerjs/pg-promise-adapter";
interface Schema {
users: { id: number; name: string; lastLogin: Date; status: string };
}
const schema = createSchema<Schema>();
// Without RETURNING - returns number of rows updated
const updatedRows = await executeUpdate(
db,
schema,
(q, params: { cutoff: Date }) =>
q
.update("users")
.set({ status: "inactive" })
.where((u) => u.lastLogin < params.cutoff),
{ cutoff: new Date("2024-01-01") },
);
// With RETURNING - returns updated rows
const updatedUsers = await executeUpdate(
db,
schema,
(q, params: { cutoff: Date }) =>
q
.update("users")
.set({ status: "inactive" })
.where((u) => u.lastLogin < params.cutoff)
.returning((u) => ({ id: u.id, status: u.status })),
{ cutoff: new Date("2024-01-01") },
);
1.4 defineDelete, toSql & executeDelete
Creates DELETE query plans, generates SQL, or executes queries.
Signatures
// Plan definition (from @tinqerjs/tinqer)
function defineDelete<TSchema, TParams>(
schema: DatabaseSchema<TSchema>,
builder: (
q: QueryBuilder<TSchema>,
params: TParams,
helpers: QueryHelpers,
) => Deletable<unknown> | DeletableComplete<unknown>,
options?: ParseQueryOptions,
): DeletePlanHandleInitial<unknown, TParams> | DeletePlanHandleComplete<unknown, TParams>;
// SQL generation (from adapter packages)
function toSql<TParams>(
plan: DeletePlanHandleInitial<unknown, TParams> | DeletePlanHandleComplete<unknown, TParams>,
params: TParams,
): { sql: string; params: Record<string, unknown> };
// Execution (from adapter packages)
async function executeDelete<TSchema, TParams>(
db: PgDatabase | BetterSqlite3Database,
schema: DatabaseSchema<TSchema>,
builder: (
q: QueryBuilder<TSchema>,
params: TParams,
helpers?: QueryHelpers,
) => Deletable<unknown> | DeletableComplete<unknown>,
params: TParams,
options?: ExecuteOptions & ParseQueryOptions,
): Promise<number>;
Example - SQL Generation
import { createSchema, defineDelete } from "@tinqerjs/tinqer";
import { toSql } from "@tinqerjs/pg-promise-adapter";
interface Schema {
users: { id: number; name: string; status: string };
}
const schema = createSchema<Schema>();
const { sql, params } = toSql(
defineDelete(schema, (q, params: { status: string }) =>
q.deleteFrom("users").where((u) => u.status === params.status),
),
{ status: "inactive" },
);
// sql: DELETE FROM "users" WHERE "status" = $(status)
// params: { status: "inactive" }
Example - Execution
import { createSchema } from "@tinqerjs/tinqer";
import { executeDelete } from "@tinqerjs/pg-promise-adapter";
interface Schema {
users: { id: number; name: string; status: string };
}
const schema = createSchema<Schema>();
const deletedCount = await executeDelete(
db,
schema,
(q, params: { status: string }) => q.deleteFrom("users").where((u) => u.status === params.status),
{ status: "inactive" },
);
1.5 ExecuteOptions & SqlResult
Both adapters expose ExecuteOptions and SqlResult for inspection and typing.
interface ExecuteOptions {
onSql?: (result: SqlResult<Record<string, unknown>, unknown>) => void;
}
interface SqlResult<TParams, TResult> {
sql: string;
params: TParams;
_resultType?: TResult; // phantom type information
}
Use onSql for logging, testing, or debugging without changing execution flow.
2. Type-Safe Contexts
2.1 createSchema
Creates a phantom-typed DatabaseSchema that ties table names to row types. The schema is passed to execution functions, which provide a type-safe query builder through the lambda’s first parameter.
import { createSchema } from "@tinqerjs/tinqer";
import { executeSelect } from "@tinqerjs/pg-promise-adapter";
interface Schema {
users: { id: number; name: string; email: string };
posts: { id: number; userId: number; title: string };
}
const schema = createSchema<Schema>();
// Schema is passed to executeSelect, which provides the query builder 'q' parameter
const results = await executeSelect(
db,
schema,
(q) => q.from("users").where((u) => u.email.endsWith("@example.com")),
{},
);
2.2 withRowFilters
Attaches row-level predicates to a schema so that SELECT/UPDATE/DELETE operations automatically include them (RLS-like behavior for query generation).
Filters are provided per table, and all tables must be covered. For tables that should not be scoped by row filters, use null.
import { createSchema } from "@tinqerjs/tinqer";
interface Schema {
users: { id: number; orgId: number; email: string };
posts: { id: number; orgId: number; title: string };
}
type ScopeContext = { orgId: number };
const baseSchema = createSchema<Schema>();
const rowFilteredSchema = baseSchema.withRowFilters<ScopeContext>({
users: (u, ctx) => u.orgId === ctx.orgId,
posts: (p, ctx) => p.orgId === ctx.orgId,
});
Notes:
- Row filters are enforced for SELECT/UPDATE/DELETE only (not required for INSERT).
- If a row-filtered schema is used without context binding, it throws (fail closed).
- Unrestricted access is done by using the original/base schema directly.
2.3 withContext
Binds a concrete context object to a row-filtered schema (typically per request).
const schema = rowFilteredSchema.withContext({ orgId: 7 });
Once bound, you can pass the schema to defineSelect / executeSelect / toSql (and the UPDATE/DELETE equivalents) and the policy is applied automatically.
3. Helper Utilities
3.1 createQueryHelpers
Provides helper functions for case-insensitive comparisons and string searches. Helpers are automatically passed as the third parameter to query builder functions.
import { createSchema, defineSelect } from "@tinqerjs/tinqer";
import { toSql } from "@tinqerjs/pg-promise-adapter";
interface Schema {
users: { id: number; name: string };
}
const schema = createSchema<Schema>();
const result = toSql(
defineSelect(schema, (q, params, helpers) =>
q.from("users").where((u) => helpers.functions.icontains(u.name, "alice")),
),
{},
);
Available Helper Functions
Helpers expose the following functions that adapt per database dialect:
helpers.functions.iequals(a, b)- Case-insensitive equalityhelpers.functions.istartsWith(str, prefix)- Case-insensitive startsWithhelpers.functions.iendsWith(str, suffix)- Case-insensitive endsWithhelpers.functions.icontains(str, substring)- Case-insensitive contains
Window Functions
Helpers also include a window-function builder:
helpers.window(row).partitionBy(...).orderBy(...).rowNumber()helpers.window(row).partitionBy(...).orderByDescending(...).rank()helpers.window(row).denseRank()
These are parsed into SQL window functions (they should never run at runtime).
createQueryHelpers
You usually do not need to call this directly (adapters provide helpers automatically), but it is exported for custom integrations:
import { createQueryHelpers } from "@tinqerjs/tinqer";
const helpers = createQueryHelpers();